@haathie/pgmb 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haathie/pgmb",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "PG message broker, with a type-safe typescript client with built-in webhook & SSE support.",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -0,0 +1,54 @@
1
+ SET search_path TO pgmb;
2
+ -- Trigger that pushes changes to the events table
3
+ CREATE OR REPLACE FUNCTION push_table_event()
4
+ RETURNS TRIGGER AS $$
5
+ DECLARE
6
+ start_num BIGINT = create_random_bigint();
7
+ BEGIN
8
+ IF TG_OP = 'INSERT' THEN
9
+ INSERT INTO events(id, topic, payload)
10
+ SELECT
11
+ create_event_id(clock_timestamp(), rand := start_num + row_number() OVER ()),
12
+ create_topic(TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP),
13
+ jsonb_strip_nulls(s.data)
14
+ FROM NEW n
15
+ CROSS JOIN LATERAL
16
+ serialise_record_for_event(TG_RELID, TG_OP, n) AS s(data, emit)
17
+ WHERE s.emit;
18
+ ELSIF TG_OP = 'DELETE' THEN
19
+ INSERT INTO events(id, topic, payload)
20
+ SELECT
21
+ create_event_id(clock_timestamp(), rand := start_num + row_number() OVER ()),
22
+ create_topic(TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP),
23
+ jsonb_strip_nulls(to_jsonb(s.data))
24
+ FROM OLD o
25
+ CROSS JOIN LATERAL
26
+ serialise_record_for_event(TG_RELID, TG_OP, o) AS s(data, emit)
27
+ WHERE s.emit;
28
+ ELSIF TG_OP = 'UPDATE' THEN
29
+ -- For updates, we can send both old and new data
30
+ INSERT INTO events(id, topic, payload, metadata)
31
+ SELECT
32
+ create_event_id(clock_timestamp(), rand := start_num + n.rn),
33
+ create_topic(TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP),
34
+ jsonb_diff(n.data, o.data),
35
+ jsonb_build_object('old', jsonb_strip_nulls(o.data))
36
+ FROM (
37
+ SELECT s.data, s.emit, row_number() OVER () AS rn
38
+ FROM NEW n
39
+ CROSS JOIN LATERAL
40
+ serialise_record_for_event(TG_RELID, TG_OP, n) AS s(data, emit)
41
+ ) AS n
42
+ INNER JOIN (
43
+ SELECT s.data, row_number() OVER () AS rn FROM OLD o
44
+ CROSS JOIN LATERAL
45
+ serialise_record_for_event(TG_RELID, TG_OP, o) AS s(data, emit)
46
+ ) AS o ON n.rn = o.rn
47
+ -- ignore rows where data didn't change
48
+ WHERE jsonb_diff(n.data, o.data) is not null AND n.emit;
49
+ END IF;
50
+
51
+ RETURN NULL;
52
+ END
53
+ $$ LANGUAGE plpgsql SECURITY DEFINER VOLATILE PARALLEL UNSAFE
54
+ SET search_path TO pgmb;
@@ -4,6 +4,8 @@ ALTER TYPE config_type ADD VALUE 'use_pg_cron';
4
4
  ALTER TYPE config_type ADD VALUE 'pg_cron_poll_for_events_cron';
5
5
  ALTER TYPE config_type ADD VALUE 'pg_cron_partition_maintenance_cron';
6
6
 
7
+ COMMIT; -- prevents unsafe enum use
8
+
7
9
  INSERT INTO config(id, value) VALUES
8
10
  ('pg_cron_poll_for_events_cron', '1 second'),
9
11
  -- every 30 minutes
@@ -0,0 +1,57 @@
1
+ SET search_path TO pgmb;
2
+
3
+ CREATE OR REPLACE FUNCTION prepare_poll_for_events_fn(
4
+ sql_statements TEXT[]
5
+ ) RETURNS VOID AS $$
6
+ DECLARE
7
+ tmpl_proc_name constant TEXT :=
8
+ 'poll_for_events_tmpl';
9
+ tmpl_proc_placeholder constant TEXT :=
10
+ 'TRUE -- CONDITIONS_SQL_PLACEHOLDER --';
11
+ condition_sql TEXT;
12
+ proc_src TEXT;
13
+ BEGIN
14
+ IF sql_statements = '{}' THEN
15
+ -- no subscriptions, so just use 'FALSE' to avoid any matches
16
+ sql_statements := ARRAY['FALSE'];
17
+ END IF;
18
+ -- build the condition SQL
19
+ condition_sql := FORMAT(
20
+ '('
21
+ || array_to_string(
22
+ ARRAY(
23
+ SELECT
24
+ '(' || stmt || ') AND s.conditions_sql = %L'
25
+ FROM unnest(sql_statements) AS arr(stmt)
26
+ ),
27
+ ') OR ('
28
+ )
29
+ || ')',
30
+ VARIADIC sql_statements
31
+ );
32
+ condition_sql := FORMAT('/* updated at %s */', NOW()) || condition_sql;
33
+
34
+ -- fetch the source of the template procedure
35
+ select pg_get_functiondef(oid) INTO proc_src
36
+ from pg_proc where proname = tmpl_proc_name and
37
+ pronamespace = 'pgmb'::regnamespace;
38
+ IF proc_src IS NULL THEN
39
+ RAISE EXCEPTION 'Template procedure % not found', tmpl_proc_name;
40
+ END IF;
41
+
42
+ -- replace the placeholder with the actual condition SQL
43
+ proc_src := REPLACE(proc_src, tmpl_proc_placeholder, condition_sql);
44
+ proc_src := REPLACE(proc_src, tmpl_proc_name, 'poll_for_events');
45
+
46
+ -- the new poll_for_events function will be created with
47
+ -- the pgmb_reader role, to avoid a bad "conditions_sql"
48
+ -- from having any destructive access to the database.
49
+ EXECUTE proc_src;
50
+ -- changing the owner will ensure that the function is executed with
51
+ -- the pgmb_reader's permissions.
52
+ -- https://www.postgresql.org/docs/current/sql-alterfunction.html
53
+ EXECUTE 'ALTER FUNCTION poll_for_events() OWNER TO pgmb_reader';
54
+ END;
55
+ $$ LANGUAGE plpgsql VOLATILE STRICT PARALLEL UNSAFE
56
+ SET search_path TO pgmb
57
+ SECURITY INVOKER;
package/sql/pgmb.sql CHANGED
@@ -589,9 +589,11 @@ BEGIN
589
589
  -- the new poll_for_events function will be created with
590
590
  -- the pgmb_reader role, to avoid a bad "conditions_sql"
591
591
  -- from having any destructive access to the database.
592
- SET ROLE pgmb_reader;
593
592
  EXECUTE proc_src;
594
- RESET ROLE;
593
+ -- changing the owner will ensure that the function is executed with
594
+ -- the pgmb_reader's permissions.
595
+ -- https://www.postgresql.org/docs/current/sql-alterfunction.html
596
+ EXECUTE 'ALTER FUNCTION poll_for_events() OWNER TO pgmb_reader';
595
597
  END;
596
598
  $$ LANGUAGE plpgsql VOLATILE STRICT PARALLEL UNSAFE
597
599
  SET search_path TO pgmb