\unset ECHO \i test/setup.sql SELECT plan(545); --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 '' ); 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/ 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%'; 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. 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 SELECT 1 AS a, 2 AS b; 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', '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)', '', '' ); -- 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, 'set_eq(select, select)', '', '' ); -- Make sure that dupes are disregarded. SELECT * FROM check_test( set_eq( '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)', '', '' ); -- Try some failures. SELECT * FROM check_test( set_eq( 'anames', 'SELECT id, name FROM annames WHERE name <> ''Anna''' ), false, 'set_eq(prepared, select) fail extra', '', ' Extra records: (44,Anna)' ); SELECT * FROM check_test( set_eq( 'anames', 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')' ), false, 'set_eq(prepared, select) fail extras', '', ' 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, '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', '', ' 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, 'set_eq(select, select) fail extra & 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, 'set_eq(select, select) fail extras & missings', '', ' Extra records: [(](1,Jacob|87,Jackson)[)] [(](1,Jacob|87,Jackson)[)] Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true ); -- Handle failure due to column mismatch. SELECT * FROM check_test( 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', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( 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', '', ' Columns differ between queries: have: (integer) 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(). 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 actual SELECT statements. SELECT * FROM check_test( bag_eq( 'SELECT id, name FROM names WHERE name like ''An%''', 'SELECT id, name FROM annames' ), true, 'bag_eq(select, select)', '', '' ); -- Compare with dupes. SELECT * FROM check_test( bag_eq( '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)', '', '' ); -- And now some failures. SELECT * FROM check_test( bag_eq( 'anames', 'SELECT id, name FROM annames WHERE name <> ''Anna''' ), false, 'bag_eq(prepared, select) fail extra', '', ' Extra records: (44,Anna)' ); SELECT * FROM check_test( bag_eq( 'anames', 'SELECT id, name FROM annames WHERE name NOT IN (''Anna'', ''Angelina'')' ), false, 'bag_eq(prepared, select) fail extras', '', ' 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, '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', '', ' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true ); SELECT * FROM check_test( bag_eq( 'SELECT id, name FROM names WHERE name ~ ''^(An|Jacob)'' AND name <> ''Anna''', 'SELECT id, name FROM annames' ), false, 'bag_eq(select, select) fail extra & missing', '', ' Extra records: (1,Jacob) Missing records: (44,Anna)' ); 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, 'bag_eq(select, select) fail extras & missings', '', ' Extra records: [(](1,Jacob|87,Jackson)[)] [(](1,Jacob|87,Jackson)[)] Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true ); -- Handle failure due to column mismatch. SELECT * FROM check_test( 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', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( 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', '', ' Columns differ between queries: have: (integer) want: (text,integer)' ); -- Handle failure due to missing dupe. SELECT * FROM check_test( bag_eq( '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', '', ' Extra records: (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( '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', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( 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', '', ' Columns differ between queries: have: (integer) want: (text,integer)' ); -- Handle fail with a dupe. SELECT * FROM check_test( set_ne( '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', '', '' ); /****************************************************************************/ -- 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( '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', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle pass with a dupe. SELECT * FROM check_test( bag_ne( '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', '', '' ); -- Handle fail with column mismatch. SELECT * FROM check_test( 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', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( 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', '', ' 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 SELECT id, name FROM toexpect ORDER BY id; 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 actual SELECT statements. SELECT * FROM check_test( results_eq( 'SELECT id, name FROM names WHERE name like ''An%'' ORDER BY id', 'SELECT id, name FROM annames ORDER BY id' ), true, 'results_eq(select, select)', '', '' ); -- 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( 'SELECT id, name FROM dupes ORDER BY pk', 'SELECT id, name FROM dupes ORDER BY pk' ), true, 'results_eq(dupe values, dupe values)', '', '' ); UPDATE dupes SET name = NULL WHERE pk = 1; -- Compare with nulls. SELECT * FROM check_test( results_eq( '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)', '', '' ); UPDATE dupes SET id = NULL, name = NULL; -- Compare only NULLs SELECT * FROM check_test( results_eq( 'SELECT id, name FROM dupes LIMIT 2', 'SELECT id, name FROM dupes LIMIT 2' ), true, 'results_eq(nulls, nulls)', '', '' ); -- Compare differnt rows of NULLs SELECT * FROM check_test( results_eq( 'SELECT id, name FROM dupes LIMIT 2', 'SELECT id, name FROM dupes LIMIT 1' ), false, 'results_eq(nulls, nulls) fail', '', ' Results differ beginning at row 2: have: (,) want: NULL' ); -- And now some failures. SELECT * FROM check_test( results_eq( 'anames_ord', 'SELECT id, name FROM annames WHERE name <> ''Anna''' ), false, 'results_eq(prepared, select) fail', '', ' Results differ beginning at row 3: have: (44,Anna) want: (63,Angel)' ); -- 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. 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( 'SELECT id, name from dubs ORDER BY pk', 'SELECT id, name from dubs ORDER BY pk LIMIT 2' ), false, 'results_eq(values dupe, values)', '', ' Results differ beginning at row 3: have: (1,Anna) want: NULL' ); UPDATE dubs SET name = NULL WHERE pk = 1; -- Handle failure with null. SELECT * FROM check_test( results_eq( '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)', '', ' Results differ beginning at row 1: have: (1,) 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( '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', '', 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)' ); -- 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; 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(); -- 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' ), 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 || ' 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; SELECT * FROM check_test( results_eq( 'cwant'::refcursor, 'chave'::refcursor ), true, 'results_eq(cursor, cursor)', '', '' ); -- 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)', '', '' ); /****************************************************************************/ -- Now test set_has(). SELECT * FROM check_test( set_has( 'anames', 'expect', 'whatever' ), true, 'set_has( prepared, prepared, description )', 'whatever', '' ); PREPARE subset AS SELECT id, name FROM toexpect WHERE id IN (11, 44, 63); 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', 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''' ), true, 'set_has( prepared, dupes )', '', '' ); SELECT * FROM check_test( set_has( 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''', 'SELECT 44 AS a, ''Anna''::text' ), 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 )', '', ' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true ); -- Handle failure due to column mismatch. SELECT * FROM check_test( 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))', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( set_has( 'SELECT 1 UNION SELECT 2', 'SELECT ''foo''::text AS a, 1 UNION SELECT ''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', 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''' ), false, 'bag_has( prepared, dupes )', '', ' Missing records: (44,Anna)' ); SELECT * FROM check_test( bag_has( 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''', 'SELECT 44 AS a, ''Anna''::text' ), 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 )', '', ' Missing records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true ); -- Handle failure due to column mismatch. SELECT * FROM check_test( 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))', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( bag_has( 'SELECT 1 UNION SELECT 2', 'SELECT ''foo''::text AS a, 1 UNION SELECT ''bar'', 2' ), false, 'bag_has((int), (text,int))', '', ' Columns differ between queries: have: (integer) want: (text,integer)' ); /****************************************************************************/ -- Now test set_hasnt(). 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' ), 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', 'SELECT 44 AS a, ''Bob''::text UNION ALL SELECT 44, ''Bob''' ), true, 'set_hasnt( prepared, dupes )', '', '' ); SELECT * FROM check_test( set_hasnt( 'anames', 'SELECT 44 AS a, ''Anna''::text' ), false, 'set_hasnt( prepared, value )', '', ' Extra records: (44,Anna)' ); SELECT * FROM check_test( set_hasnt( 'anames', 'SELECT 44 AS a, ''Anna''::text UNION SELECT 86, ''Angelina''' ), false, 'set_hasnt( prepared, values )', '', ' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true ); -- Handle failure due to column mismatch. SELECT * FROM check_test( 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))', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( set_hasnt( 'SELECT 1 UNION SELECT 2', 'SELECT ''foo''::text AS a, 1 UNION SELECT ''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', 'SELECT 44 AS a, ''Anna''::text' ), false, 'bag_hasnt( prepared, value )', '', ' Extra records: (44,Anna)' ); SELECT * FROM check_test( bag_hasnt( 'anames', 'SELECT 44 AS a, ''Anna''::text UNION SELECT 86, ''Angelina''' ), false, 'bag_hasnt( prepared, values )', '', ' Extra records: [(](44,Anna|86,Angelina)[)] [(](44,Anna|86,Angelina)[)]', true ); -- Handle failure due to column mismatch. SELECT * FROM check_test( 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))', '', ' Columns differ between queries: have: (integer,text) want: (text,integer)' ); -- Handle failure due to column count mismatch. SELECT * FROM check_test( bag_hasnt( 'SELECT 1 UNION SELECT 2', 'SELECT ''foo''::text AS a, 1 UNION SELECT ''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( '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 )', '', ' 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( 'SELECT 44 AS a, ''Anna''::text', 'SELECT 44 AS a, ''Anna''::text UNION ALL SELECT 44, ''Anna''' ), false, 'bag_hasnt( value, dupes )', '', ' Extra records: (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 a missing 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 a missing 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)' ); /****************************************************************************/ -- 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: NULL' ); 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)' ); /****************************************************************************/ -- Now test results_eq(). PREPARE nenames_ord AS SELECT id, name FROM names WHERE name like 'An%' ORDER BY id; 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' ), 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)', '', '' ); 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( 'SELECT id, name FROM dubs ORDER BY pk', 'SELECT id, name FROM buds ORDER BY pk' ), true, 'results_ne(dupe values, dupe values)', '', '' ); 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( '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)', '', '' ); -- Compare only NULLs SELECT * FROM check_test( results_ne( 'SELECT id, name FROM dupes LIMIT 3', 'SELECT id, name FROM dupes LIMIT 2' ), 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', '', '' ); UPDATE dubs SET id = 1, name = 'Anna' WHERE pk IN (1, 3); -- Compare with missing dupe. SELECT * FROM check_test( results_ne( 'SELECT id, name FROM dubs ORDER BY pk', 'SELECT id, name FROM dubs ORDER BY pk LIMIT 2' ), true, 'results_ne(values dupe, values)', '', '' ); UPDATE dubs SET name = NULL where PK = 2; -- Handle pass with null. SELECT * FROM check_test( results_ne( '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)', '', '' ); -- 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; 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)', '', '' ); /****************************************************************************/ -- 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)', '', '' ); CREATE FUNCTION test_empty_fail() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN 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; 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; RETURN; 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'; CREATE TYPE sometype AS ( id INT, name TEXT ); CREATE FUNCTION test_row_eq() RETURNS SETOF TEXT AS $$ DECLARE tap record; BEGIN 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; 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 id, ts FROM someat', ROW(1, '2009-12-04T07:22:52'::timestamp)), true, 'row_eq(qry, record)', '', '' ) AS a(b) LOOP RETURN NEXT tap.b; END LOOP; END IF; RETURN; END; $$ LANGUAGE plpgsql; SELECT * FROM test_row_eq(); /****************************************************************************/ -- Finish the tests and clean up. SELECT * FROM finish(); ROLLBACK;