-- validate lock tuple trigger

-- set defaults for the testing environment
\set ON_ERROR_STOP 1
SET client_min_messages='notice';

-- load extension for PostgreSQL 9.1 and later
CREATE EXTENSION tuplock;

-- test in a distinct schema...
CREATE SCHEMA test_tuplock;
SET search_path TO test_tuplock, public;

CREATE TABLE test(
  tid SERIAL PRIMARY KEY,
  data TEXT NOT NULL,
  -- locking attribute
  lock BOOLEAN NOT NULL DEFAULT FALSE
);

-- create the locking trigger
CREATE TRIGGER test_lock
  BEFORE UPDATE OR DELETE ON test
  FOR EACH ROW EXECUTE PROCEDURE tuplock(lock);

-- must also forbid truncate
CREATE TRIGGER test_lock2
BEFORE TRUNCATE ON test
EXECUTE PROCEDURE tuplock();

-- table for bad examples
CREATE TABLE test2(
  tid SERIAL PRIMARY KEY,
  data TEXT NOT NULL,
  lock BOOLEAN NOT NULL DEFAULT FALSE);

-- current status: 0
SELECT COUNT(*) FROM test;

-- first insert new values
INSERT INTO test(data) VALUES('hello');
INSERT INTO test(data) VALUES('world');
INSERT INTO test(data) VALUES('!');
INSERT INTO test(data) VALUES('foo');
INSERT INTO test(data) VALUES('bla');

-- show all
SELECT * FROM test ORDER BY tid;

-- update is fine
UPDATE test
  SET data='HELLO'
  WHERE data='hello';

-- delete is fine
DELETE FROM test
 WHERE data = '!';

-- then lock all but foo & bla entries
UPDATE test
  SET lock = TRUE
  WHERE lock = FALSE AND length(data) <> 3;

-- errors are fine, now:
\set ON_ERROR_STOP 0

-- now updates are not ok
UPDATE test
  SET data='WORLD'
  WHERE data='world'; -- fails

UPDATE test
  SET lock = FALSE
  WHERE lock; -- fails

-- now deletes are not ok
DELETE FROM test
  WHERE data = 'HELLO'; -- fails

-- as well as truncates
TRUNCATE test;

-- test2
INSERT INTO test2(data)
  VALUES ('hello world');

-- ERROR: insert is not allowed
CREATE TRIGGER test2_lock_insert
  BEFORE INSERT ON test2
  FOR EACH ROW EXECUTE PROCEDURE tuplock(lock);

INSERT INTO test2(data)
  VALUES ('hello world 2');

DROP TRIGGER test2_lock_insert ON test2;

-- ERROR: for each statement
CREATE TRIGGER test2_lock_stmt
  BEFORE UPDATE OR DELETE ON test2
  FOR EACH STATEMENT EXECUTE PROCEDURE tuplock();

UPDATE test2
  SET lock = TRUE
  WHERE data = 'hello world';

DROP TRIGGER test2_lock_stmt ON test2;

-- ERROR: too many arguments
CREATE TRIGGER test2_lock_nargs
  BEFORE UPDATE OR DELETE ON test2
  FOR EACH ROW EXECUTE PROCEDURE tuplock(lock, data);

UPDATE test2
  SET lock = TRUE
  WHERE data = 'hello world';

DROP TRIGGER test2_lock_nargs ON test2;

-- ERROR: no arg
CREATE TRIGGER test2_lock_zero
  BEFORE UPDATE OR DELETE ON test2
  FOR EACH ROW EXECUTE PROCEDURE tuplock();

UPDATE test2
  SET lock = TRUE
  WHERE data = 'hello world';

DROP TRIGGER test2_lock_zero ON test2;

-- ERROR: wrong type
CREATE TRIGGER test2_lock_text
  BEFORE UPDATE OR DELETE ON test2
  FOR EACH ROW EXECUTE PROCEDURE tuplock(data);

UPDATE test2
  SET lock = TRUE
  WHERE data = 'hello world';

DROP TRIGGER test2_lock_text ON test2;

-- ERROR: no such attribute
CREATE TRIGGER test2_lock_foo
  BEFORE UPDATE OR DELETE ON test2
  FOR EACH ROW EXECUTE PROCEDURE tuplock(foo);

UPDATE test2
  SET lock = TRUE
  WHERE data = 'hello world';

DROP TRIGGER test2_lock_foo ON test2;

-- back to normal
\set ON_ERROR_STOP 1

-- this update is ok
UPDATE test
  SET data = 'FOO'
  WHERE data = 'foo';

-- this delete is ok
DELETE FROM test
  WHERE data='bla';

-- show all
SELECT * FROM test ORDER BY tid;

-- cleanup
DROP SCHEMA test_tuplock CASCADE;
DROP EXTENSION tuplock;
