Skip to content

Added support for triggers#1426

Merged
Hydrocharged merged 1 commit intomainfrom
daylon/triggers
Apr 25, 2025
Merged

Added support for triggers#1426
Hydrocharged merged 1 commit intomainfrom
daylon/triggers

Conversation

@Hydrocharged
Copy link
Copy Markdown
Collaborator

@Hydrocharged Hydrocharged commented Apr 24, 2025

This adds partial support for triggers. This should cover a decent chunk of the functionality that will be expected. Notably, this is still missing:

  • FOR EACH STATEMENT
  • TRUNCATE
  • INSTEAD OF timing
  • Deferring
  • Specific column triggers for UPDATE
  • CONSTRAINT TRIGGERS
  • Referenced tables (seemingly used for the internal foreign key implementation?)
  • Transition tables
  • String arguments
  • The rest of the variables available inside a trigger interpreted function

@Hydrocharged Hydrocharged requested a review from fulghum April 24, 2025 13:22
@Hydrocharged
Copy link
Copy Markdown
Collaborator Author

This PR does not include support for WHEN, but I'm separating that into a different PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 24, 2025

Main PR
covering_index_scan_postgres 330.88/s 340.37/s +2.8%
index_join_postgres 156.41/s 154.54/s -1.2%
index_join_scan_postgres 188.39/s 187.76/s -0.4%
index_scan_postgres 12.54/s 12.33/s -1.7%
oltp_point_select 2622.48/s 2508.05/s -4.4%
oltp_read_only 1850.69/s 1814.66/s -2.0%
select_random_points 117.34/s 116.99/s -0.3%
select_random_ranges 125.22/s 131.54/s +5.0%
table_scan_postgres 11.76/s 11.62/s -1.2%
types_table_scan_postgres 5.51/s 5.43/s -1.5%

Copy link
Copy Markdown
Contributor

@fulghum fulghum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Very cool to see this big feature. 🚀

Code seemed pretty straightforward. I called out a couple places where I had to stop and dig in to understand what was going on; those would be good places to add some more comments to help future readers. Also suggested a couple more test complex test cases.

@github-actions
Copy link
Copy Markdown
Contributor

Main PR
Total 42090 42090
Successful 16361 16378
Failures 25729 25712
Partial Successes1 5530 5540
Main PR
Successful 38.8715% 38.9119%
Failures 61.1285% 61.0881%

${\color{red}Regressions (212)}$

alter_table

QUERY:          insert into parent values (1, 2, 3);
RECEIVED ERROR: function f() does not exist (errno 1105) (sqlstate HY000)
QUERY:          insert into child values (12, 13, 'testing');
RECEIVED ERROR: function f() does not exist (errno 1105) (sqlstate HY000)

fast_default

QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, 3, 4, 2}
    Doltgres:
        {1, 1, 2, 3, 4, 5}
QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, 3, 4, 2}
    Doltgres:
        {1, 1, 2, 3, 4, �}
QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, 3, �, 2}
    Doltgres:
        {1, 1, 2, 3, �, 5}
QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, 3, �, 2}
    Doltgres:
        {1, 1, 2, 3, �, �}
QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, �, 4, 2}
    Doltgres:
        {1, 1, 2, �, 4, 5}
QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, �, 4, 2}
    Doltgres:
        {1, 1, 2, �, 4, �}
QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, �, �, 2}
    Doltgres:
        {1, 1, 2, �, �, 5}
QUERY:          UPDATE t SET y = 2;
RECEIVED ERROR: function: 'to_json' not found (errno 1105) (sqlstate HY000)
QUERY:          SELECT * FROM t;
RECEIVED ERROR: row sets differ:
    Postgres:
        {1, 1, 2, �, �, 2}
    Doltgres:
        {1, 1, 2, �, �, �}
QUERY:          DELETE FROM leader;
RECEIVED ERROR: table not found: t.a (errno 1146) (sqlstate HY000)

generated

QUERY:          INSERT INTO gtest26 (a) VALUES (-2), (0), (3);
RECEIVED ERROR: column "tg_op" could not be found in any table in scope (errno 1105) (sqlstate HY000)

indexing

QUERY:          select indexrelid::regclass, indrelid::regclass
  from pg_index where indexrelid::regclass::text like 'idxpart%';
RECEIVED ERROR: timeout during Receive

plpgsql

QUERY:          insert into WSlot values ('WS.001.1a', '001', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.001.1b', '001', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.001.2a', '001', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.001.2b', '001', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.001.3a', '001', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.001.3b', '001', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.002.1a', '002', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.002.1b', '002', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.002.2a', '002', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.002.2b', '002', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.002.3a', '002', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.002.3b', '002', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.003.1a', '003', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.003.1b', '003', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.003.2a', '003', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.003.2b', '003', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.003.3a', '003', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.003.3b', '003', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)
QUERY:          insert into WSlot values ('WS.101.1a', '101', '', '');
RECEIVED ERROR: variable `new.backlink` could not be found (errno 1105) (sqlstate HY000)

${\color{lightgreen}Progressions (237)}$

copy

QUERY: create function part_ins_func() returns trigger language plpgsql as $$
begin
  return new;
end;
$$;
QUERY: create trigger part_ins_trig
	before insert on parted_copytest_a2
	for each row
	execute procedure part_ins_func();

copy2

QUERY: CREATE FUNCTION fn_x_before () RETURNS TRIGGER AS '
  BEGIN
		NEW.e := ''before trigger fired''::text;
		return NEW;
	END;
' LANGUAGE plpgsql;
QUERY: CREATE FUNCTION fn_x_after () RETURNS TRIGGER AS '
  BEGIN
		UPDATE x set e=''after trigger fired'' where c=''stuff'';
		return NULL;
	END;
' LANGUAGE plpgsql;
QUERY: CREATE FUNCTION fun_instead_of_insert_tbl() RETURNS trigger AS $$
BEGIN
  INSERT INTO instead_of_insert_tbl (name) VALUES (NEW.str);
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;
QUERY: DROP FUNCTION fn_x_before();
QUERY: DROP FUNCTION fn_x_after();
QUERY: DROP FUNCTION fun_instead_of_insert_tbl();

copydml

QUERY: create function qqq_trig() returns trigger as $$
begin
if tg_op in ('INSERT', 'UPDATE') then
    raise notice '% % %', tg_when, tg_op, new.id;
    return new;
else
    raise notice '% % %', tg_when, tg_op, old.id;
    return old;
end if;
end
$$ language plpgsql;
QUERY: create trigger qqqbef before insert or update or delete on copydml_test
    for each row execute procedure qqq_trig();
QUERY: create trigger qqqaf after insert or update or delete on copydml_test
    for each row execute procedure qqq_trig();
QUERY: drop function qqq_trig();

fast_default

QUERY: CREATE FUNCTION test_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$

begin
    raise notice 'old tuple: %', to_json(OLD)::text;
    if TG_OP = 'DELETE'
    then
       return OLD;
    else
       return NEW;
    end if;
end;

$$;
QUERY: CREATE TRIGGER a BEFORE UPDATE ON t FOR EACH ROW EXECUTE PROCEDURE test_trigger();
QUERY: DROP FUNCTION test_trigger();

generated

QUERY: CREATE FUNCTION gtest_trigger_func() RETURNS trigger
  LANGUAGE plpgsql
AS $$
BEGIN
  IF tg_op IN ('DELETE', 'UPDATE') THEN
    RAISE INFO '%: %: old = %', TG_NAME, TG_WHEN, OLD;
  END IF;
  IF tg_op IN ('INSERT', 'UPDATE') THEN
    RAISE INFO '%: %: new = %', TG_NAME, TG_WHEN, NEW;
  END IF;
  IF tg_op = 'DELETE' THEN
    RETURN OLD;
  ELSE
    RETURN NEW;
  END IF;
END
$$;
QUERY: CREATE TRIGGER gtest1 BEFORE DELETE OR UPDATE ON gtest26
  FOR EACH ROW
  WHEN (OLD.b < 0)  -- ok
  EXECUTE PROCEDURE gtest_trigger_func();
QUERY: CREATE TRIGGER gtest2 BEFORE INSERT ON gtest26
  FOR EACH ROW
  WHEN (NEW.a < 0)
  EXECUTE PROCEDURE gtest_trigger_func();
QUERY: CREATE TRIGGER gtest3 AFTER DELETE OR UPDATE ON gtest26
  FOR EACH ROW
  WHEN (OLD.b < 0)  -- ok
  EXECUTE PROCEDURE gtest_trigger_func();
QUERY: CREATE TRIGGER gtest4 AFTER INSERT OR UPDATE ON gtest26
  FOR EACH ROW
  WHEN (NEW.b < 0)  -- ok
  EXECUTE PROCEDURE gtest_trigger_func();
QUERY: CREATE FUNCTION gtest_trigger_func3() RETURNS trigger
  LANGUAGE plpgsql
AS $$
BEGIN
  RAISE NOTICE 'OK';
  RETURN NEW;
END
$$;
QUERY: CREATE FUNCTION gtest_trigger_func4() RETURNS trigger
  LANGUAGE plpgsql
AS $$
BEGIN
  NEW.a = 10;
  NEW.b = 300;
  RETURN NEW;
END;
$$;
QUERY: CREATE TRIGGER gtest12_01 BEFORE UPDATE ON gtest26
  FOR EACH ROW
  EXECUTE PROCEDURE gtest_trigger_func();
QUERY: CREATE TRIGGER gtest12_02 BEFORE UPDATE ON gtest26
  FOR EACH ROW
  EXECUTE PROCEDURE gtest_trigger_func4();
QUERY: CREATE TRIGGER gtest12_03 BEFORE UPDATE ON gtest26
  FOR EACH ROW
  EXECUTE PROCEDURE gtest_trigger_func();

indirect_toast

QUERY: CREATE FUNCTION update_using_indirect()
        RETURNS trigger
        LANGUAGE plpgsql AS $$
BEGIN
    NEW := make_tuple_indirect(NEW);
    RETURN NEW;
END$$;
QUERY: CREATE TRIGGER indtoasttest_update_indirect
        BEFORE INSERT OR UPDATE
        ON indtoasttest
        FOR EACH ROW
        EXECUTE PROCEDURE update_using_indirect();
QUERY: INSERT INTO indtoasttest(descr, f1, f2) VALUES('one-toasted,one-null, via indirect', repeat('1234567890',30000), NULL);
QUERY: DROP FUNCTION update_using_indirect();

insert

QUERY: create function mlparted11_trig_fn()
returns trigger AS
$$
begin
  NEW.b := 4;
  return NEW;
end;
$$
language plpgsql;
QUERY: create trigger mlparted11_trig before insert ON mlparted11
  for each row execute procedure mlparted11_trig_fn();
QUERY: drop function mlparted11_trig_fn();
QUERY: create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql;
QUERY: create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
QUERY: create or replace function brtrigpartcon1trigf() returns trigger as $$begin new.a := 2; return new; end$$ language plpgsql;
QUERY: drop function brtrigpartcon1trigf();
QUERY: create or replace function donothingbrtrig_func() returns trigger as $$begin raise notice 'b: %', new.b; return NULL; end$$ language plpgsql;
QUERY: create trigger donothingbrtrig1 before insert on donothingbrtrig_test1 for each row execute procedure donothingbrtrig_func();
QUERY: create trigger donothingbrtrig2 before insert on donothingbrtrig_test2 for each row execute procedure donothingbrtrig_func();
QUERY: drop function donothingbrtrig_func();

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct.

@Hydrocharged Hydrocharged merged commit d658d9f into main Apr 25, 2025
14 checks passed
@Hydrocharged Hydrocharged deleted the daylon/triggers branch April 25, 2025 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants