diff options
author | Marko Kreen | 2007-09-04 13:05:24 +0000 |
---|---|---|
committer | Marko Kreen | 2007-09-04 13:05:24 +0000 |
commit | 7197a342352d724d72d3a72abe183a0e85a6985c (patch) | |
tree | 1f7ffb134d97272ff5b056ac5d503d7757d41249 | |
parent | 66841401145c2d3bc9d33ea39e226a807987b7f8 (diff) |
upgrade scripts for 2.1.5
-rw-r--r-- | NEWS | 24 | ||||
-rw-r--r-- | upgrade/Makefile | 13 | ||||
-rw-r--r-- | upgrade/final/v2.1.5_londiste.sql | 443 | ||||
-rw-r--r-- | upgrade/final/v2.1.5_pgq_core.sql | 579 | ||||
-rw-r--r-- | upgrade/final/v2.1.5_pgq_ext.sql | 42 | ||||
-rw-r--r-- | upgrade/src/v2.1.5_londiste.sql | 30 | ||||
-rw-r--r-- | upgrade/src/v2.1.5_pgq_core.sql | 19 | ||||
-rw-r--r-- | upgrade/src/v2.1.5_pgq_ext.sql | 6 |
8 files changed, 1151 insertions, 5 deletions
@@ -21,12 +21,26 @@ = Upgrade procedure for database code = - * PgQ (used on Londiste provider side) - XXX + * PgQ (used on Londiste provider side), table structure, plpgsql functions: + + $ psql dbname -f upgrade/final/v2.1.5.pgq_core.sql + + * PgQ new insert_event(), written in C: + + $ psql dbname -f sql/pgq/lowlevel/pgq_lowlevel.sql + + * PgQ new triggers (sqltriga, logtriga, logutriga), written in C: + + $ psql dbname -f sql/pgq/triggers/pgq_triggers.sql + * Londiste (both provider and subscriber side) - XXX - * pgq_ext - XXX + + $ psql dbname -f upgrade/final/v2.1.5.londiste.sql + + * pgq_ext: + + $ psql dbname -f upgrade/final/v2.1.5.pgq_ext.sql + 2007-04-16 - SkyTools 2.1.4 - "Sweets from last Christmas" diff --git a/upgrade/Makefile b/upgrade/Makefile new file mode 100644 index 00000000..d9d494c1 --- /dev/null +++ b/upgrade/Makefile @@ -0,0 +1,13 @@ + +SQLS = v2.1.5_londiste.sql v2.1.5_pgq_core.sql v2.1.5_pgq_ext.sql + +SRCS = $(addprefix src/, $(SQLS)) +DSTS = $(addprefix final/, $(SQLS)) + +CATSQL = python ../scripts/catsql.py + +all: $(DSTS) + +final/%.sql: src/%.sql + $(CATSQL) $< > $@ + diff --git a/upgrade/final/v2.1.5_londiste.sql b/upgrade/final/v2.1.5_londiste.sql new file mode 100644 index 00000000..5cb78f6f --- /dev/null +++ b/upgrade/final/v2.1.5_londiste.sql @@ -0,0 +1,443 @@ + +begin; + +create table londiste.subscriber_pending_fkeys( + from_table text not null, + to_table text not null, + fkey_name text not null, + fkey_def text not null, + + primary key (from_table, fkey_name) +); + +drop function londiste.denytrigger(); + + + +create or replace function londiste.find_table_fkeys(i_table_name text) +returns setof londiste.subscriber_pending_fkeys as $$ +declare + fkey record; + tbl_oid oid; +begin + select londiste.find_table_oid(i_table_name) into tbl_oid; + + for fkey in + select n1.nspname || '.' || t1.relname as from_table, n2.nspname || '.' || t2.relname as to_table, + conname::text as fkey_name, pg_get_constraintdef(c.oid) as fkey_def + from pg_constraint c, pg_namespace n1, pg_class t1, pg_namespace n2, pg_class t2 + where c.contype = 'f' and (c.conrelid = tbl_oid or c.confrelid = tbl_oid) + and t1.oid = c.conrelid and n1.oid = t1.relnamespace + and t2.oid = c.confrelid and n2.oid = t2.relnamespace + order by 1,2,3 + loop + return next fkey; + end loop; + + return; +end; +$$ language plpgsql strict stable; + + + + +create or replace function londiste.find_column_types(tbl text) +returns text as $$ +declare + res text; + col record; + tbl_oid oid; +begin + tbl_oid := londiste.find_table_oid(tbl); + res := ''; + for col in + SELECT CASE WHEN k.attname IS NOT NULL THEN 'k' ELSE 'v' END AS type + FROM pg_attribute a LEFT JOIN ( + SELECT k.attname FROM pg_index i, pg_attribute k + WHERE i.indrelid = tbl_oid AND k.attrelid = i.indexrelid + AND i.indisprimary AND k.attnum > 0 AND NOT k.attisdropped + ) k ON (k.attname = a.attname) + WHERE a.attrelid = tbl_oid AND a.attnum > 0 AND NOT a.attisdropped + ORDER BY a.attnum + loop + res := res || col.type; + end loop; + + return res; +end; +$$ language plpgsql strict stable; + + + + + +create or replace function londiste.subscriber_get_table_pending_fkeys(i_table_name text) +returns setof londiste.subscriber_pending_fkeys as $$ +declare + fkeys record; +begin + for fkeys in + select * + from londiste.subscriber_pending_fkeys + where from_table=i_table_name or to_table=i_table_name + order by 1,2,3 + loop + return next fkeys; + end loop; + + return; +end; +$$ language plpgsql; + + +create or replace function londiste.subscriber_get_queue_valid_pending_fkeys(i_queue_name text) +returns setof londiste.subscriber_pending_fkeys as $$ +declare + fkeys record; +begin + for fkeys in + select pf.* + from londiste.subscriber_pending_fkeys pf + left join londiste.subscriber_table st_from on (st_from.table_name = pf.from_table) + left join londiste.subscriber_table st_to on (st_to.table_name = pf.to_table) + where (st_from.table_name is null or (st_from.merge_state = 'ok' and st_from.snapshot is null)) + and (st_to.table_name is null or (st_to.merge_state = 'ok' and st_to.snapshot is null)) + and (coalesce(st_from.queue_name = i_queue_name, false) + or coalesce(st_to.queue_name = i_queue_name, false)) + order by 1, 2, 3 + loop + return next fkeys; + end loop; + + return; +end; +$$ language plpgsql; + + +create or replace function londiste.subscriber_drop_table_fkey(i_from_table text, i_fkey_name text) +returns integer as $$ +declare + fkey record; +begin + select * into fkey + from londiste.find_table_fkeys(i_from_table) + where fkey_name = i_fkey_name and from_table = i_from_table; + + if not found then + return 1; + end if; + + insert into londiste.subscriber_pending_fkeys values (fkey.from_table, fkey.to_table, i_fkey_name, fkey.fkey_def); + + execute 'alter table only ' || fkey.from_table || ' drop constraint ' || i_fkey_name || ';'; + + return 1; +end; +$$ language plpgsql; + + +create or replace function londiste.subscriber_restore_table_fkey(i_from_table text, i_fkey_name text) +returns integer as $$ +declare + fkey record; +begin + select * into fkey + from londiste.subscriber_pending_fkeys + where fkey_name = i_fkey_name and from_table = i_from_table; + + if not found then + return 1; + end if; + + delete from londiste.subscriber_pending_fkeys where fkey_name = fkey.fkey_name; + + execute 'alter table only ' || fkey.from_table || ' add constraint ' + || fkey.fkey_name || ' ' || fkey.fkey_def || ';'; + + return 1; +end; +$$ language plpgsql; + + + +create or replace function londiste.find_rel_oid(tbl text, kind text) +returns oid as $$ +declare + res oid; + pos integer; + schema text; + name text; +begin + pos := position('.' in tbl); + if pos > 0 then + schema := substring(tbl for pos - 1); + name := substring(tbl from pos + 1); + else + schema := 'public'; + name := tbl; + end if; + select c.oid into res + from pg_namespace n, pg_class c + where c.relnamespace = n.oid + and c.relkind = kind + and n.nspname = schema and c.relname = name; + if not found then + if kind = 'r' then + raise exception 'table not found'; + elsif kind = 'S' then + raise exception 'seq not found'; + else + raise exception 'weird relkind'; + end if; + end if; + + return res; +end; +$$ language plpgsql strict stable; + +create or replace function londiste.find_table_oid(tbl text) +returns oid as $$ +begin + return londiste.find_rel_oid(tbl, 'r'); +end; +$$ language plpgsql strict stable; + +create or replace function londiste.find_seq_oid(tbl text) +returns oid as $$ +begin + return londiste.find_rel_oid(tbl, 'S'); +end; +$$ language plpgsql strict stable; + + + + +create or replace function londiste.get_last_tick(i_consumer text) +returns bigint as $$ +declare + res bigint; +begin + select last_tick_id into res + from londiste.completed + where consumer_id = i_consumer; + return res; +end; +$$ language plpgsql security definer strict stable; + + + +create or replace function londiste.provider_add_table( + i_queue_name text, + i_table_name text, + i_col_types text +) returns integer strict as $$ +declare + tgname text; + sql text; +begin + if londiste.link_source(i_queue_name) is not null then + raise exception 'Linked queue, manipulation not allowed'; + end if; + + if position('k' in i_col_types) < 1 then + raise exception 'need key column'; + end if; + if position('.' in i_table_name) < 1 then + raise exception 'need fully-qualified table name'; + end if; + select queue_name into tgname + from pgq.queue where queue_name = i_queue_name; + if not found then + raise exception 'no such event queue'; + end if; + + tgname := i_queue_name || '_logger'; + tgname := replace(lower(tgname), '.', '_'); + insert into londiste.provider_table + (queue_name, table_name, trigger_name) + values (i_queue_name, i_table_name, tgname); + + perform londiste.provider_create_trigger( + i_queue_name, i_table_name, i_col_types); + + return 1; +end; +$$ language plpgsql security definer; + +create or replace function londiste.provider_add_table( + i_queue_name text, + i_table_name text +) returns integer as $$ +begin + return londiste.provider_add_table(i_queue_name, i_table_name, + londiste.find_column_types(i_table_name)); +end; +$$ language plpgsql security definer; + + + + +create or replace function londiste.provider_create_trigger( + i_queue_name text, + i_table_name text, + i_col_types text +) returns integer strict as $$ +declare + tgname text; +begin + select trigger_name into tgname + from londiste.provider_table + where queue_name = i_queue_name + and table_name = i_table_name; + if not found then + raise exception 'table not found'; + end if; + + execute 'create trigger ' || tgname + || ' after insert or update or delete on ' + || i_table_name + || ' for each row execute procedure pgq.logtriga(' + || quote_literal(i_queue_name) || ', ' + || quote_literal(i_col_types) || ', ' + || quote_literal(i_table_name) || ')'; + + return 1; +end; +$$ language plpgsql security definer; + + + + +create or replace function londiste.provider_notify_change(i_queue_name text) +returns integer as $$ +declare + res text; + tbl record; +begin + res := ''; + for tbl in + select table_name from londiste.provider_table + where queue_name = i_queue_name + order by nr + loop + if res = '' then + res := tbl.table_name; + else + res := res || ',' || tbl.table_name; + end if; + end loop; + + perform pgq.insert_event(i_queue_name, 'T', res); + + return 1; +end; +$$ language plpgsql security definer; + + + + +create or replace function londiste.provider_remove_table( + i_queue_name text, + i_table_name text +) returns integer as $$ +declare + tgname text; +begin + if londiste.link_source(i_queue_name) is not null then + raise exception 'Linked queue, manipulation not allowed'; + end if; + + select trigger_name into tgname from londiste.provider_table + where queue_name = i_queue_name + and table_name = i_table_name; + if not found then + raise exception 'no such table registered'; + end if; + + begin + execute 'drop trigger ' || tgname || ' on ' || i_table_name; + exception + when undefined_table then + raise notice 'table % does not exist', i_table_name; + when undefined_object then + raise notice 'trigger % does not exist on table %', tgname, i_table_name; + end; + + delete from londiste.provider_table + where queue_name = i_queue_name + and table_name = i_table_name; + + return 1; +end; +$$ language plpgsql security definer; + + + + + +create or replace function londiste.set_last_tick( + i_consumer text, + i_tick_id bigint) +returns integer as $$ +begin + if i_tick_id is null then + delete from londiste.completed + where consumer_id = i_consumer; + else + update londiste.completed + set last_tick_id = i_tick_id + where consumer_id = i_consumer; + if not found then + insert into londiste.completed (consumer_id, last_tick_id) + values (i_consumer, i_tick_id); + end if; + end if; + + return 1; +end; +$$ language plpgsql security definer; + + + + +create or replace function londiste.subscriber_remove_table( + i_queue_name text, i_table text) +returns integer as $$ +declare + link text; +begin + delete from londiste.subscriber_table + where queue_name = i_queue_name + and table_name = i_table; + if not found then + raise exception 'no such table'; + end if; + + -- sync link + link := londiste.link_dest(i_queue_name); + if link is not null then + delete from londiste.provider_table + where queue_name = link + and table_name = i_table; + perform londiste.provider_notify_change(link); + end if; + + return 0; +end; +$$ language plpgsql security definer; + + + + + +grant usage on schema londiste to public; +grant select on londiste.provider_table to public; +grant select on londiste.completed to public; +grant select on londiste.link to public; +grant select on londiste.subscriber_table to public; + + + +end; + + diff --git a/upgrade/final/v2.1.5_pgq_core.sql b/upgrade/final/v2.1.5_pgq_core.sql new file mode 100644 index 00000000..1040ed16 --- /dev/null +++ b/upgrade/final/v2.1.5_pgq_core.sql @@ -0,0 +1,579 @@ + +begin; + +alter table pgq.subscription constraint subscription_ukey unique (sub_queue, sub_consumer); +create index rq_retry_owner_idx on pgq.retry_queue (ev_owner, ev_id); + + +create or replace function pgq.current_event_table(x_queue_name text) +returns text as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.current_event_table(1) +-- +-- Return active event table for particular queue. +-- Event can be added to it without going via functions, +-- e.g. by COPY. +-- +-- Note: +-- The result is valid only during current transaction. +-- +-- Permissions: +-- Actual insertion requires superuser access. +-- +-- Parameters: +-- x_queue_name - Queue name. +-- ---------------------------------------------------------------------- +declare + res text; +begin + select queue_data_pfx || '_' || queue_cur_table into res + from pgq.queue where queue_name = x_queue_name; + if not found then + raise exception 'Event queue not found'; + end if; + return res; +end; +$$ language plpgsql; -- no perms needed + + + +create or replace function pgq.event_failed( + x_batch_id bigint, + x_event_id bigint, + x_reason text) +returns integer as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.event_failed(3) +-- +-- Copies the event to failed queue so it can be looked at later. +-- +-- Parameters: +-- x_batch_id - ID of active batch. +-- x_event_id - Event id +-- x_reason - Text to associate with event. +-- +-- Returns: +-- 0 if event was already in queue, 1 otherwise. +-- ---------------------------------------------------------------------- +begin + insert into pgq.failed_queue (ev_failed_reason, ev_failed_time, + ev_id, ev_time, ev_txid, ev_owner, ev_retry, ev_type, ev_data, + ev_extra1, ev_extra2, ev_extra3, ev_extra4) + select x_reason, now(), + ev_id, ev_time, NULL, sub_id, coalesce(ev_retry, 0), + ev_type, ev_data, ev_extra1, ev_extra2, ev_extra3, ev_extra4 + from pgq.get_batch_events(x_batch_id), + pgq.subscription + where sub_batch = x_batch_id + and ev_id = x_event_id; + if not found then + raise exception 'event not found'; + end if; + return 1; + +-- dont worry if the event is already in queue +exception + when unique_violation then + return 0; +end; +$$ language plpgsql security definer; + + + +create or replace function pgq.event_retry( + x_batch_id bigint, + x_event_id bigint, + x_retry_time timestamptz) +returns integer as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.event_retry(3) +-- +-- Put the event into retry queue, to be processed again later. +-- +-- Parameters: +-- x_batch_id - ID of active batch. +-- x_event_id - event id +-- x_retry_time - Time when the event should be put back into queue +-- +-- Returns: +-- nothing +-- ---------------------------------------------------------------------- +begin + insert into pgq.retry_queue (ev_retry_after, + ev_id, ev_time, ev_txid, ev_owner, ev_retry, ev_type, ev_data, + ev_extra1, ev_extra2, ev_extra3, ev_extra4) + select x_retry_time, + ev_id, ev_time, NULL, sub_id, coalesce(ev_retry, 0) + 1, + ev_type, ev_data, ev_extra1, ev_extra2, ev_extra3, ev_extra4 + from pgq.get_batch_events(x_batch_id), + pgq.subscription + where sub_batch = x_batch_id + and ev_id = x_event_id; + if not found then + raise exception 'event not found'; + end if; + return 1; + +-- dont worry if the event is already in queue +exception + when unique_violation then + return 0; +end; +$$ language plpgsql security definer; + + +create or replace function pgq.event_retry( + x_batch_id bigint, + x_event_id bigint, + x_retry_seconds integer) +returns integer as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.event_retry(3) +-- +-- Put the event into retry queue, to be processed later again. +-- +-- Parameters: +-- x_batch_id - ID of active batch. +-- x_event_id - event id +-- x_retry_seconds - Time when the event should be put back into queue +-- +-- Returns: +-- nothing +-- ---------------------------------------------------------------------- +declare + new_retry timestamptz; +begin + new_retry := current_timestamp + ((x_retry_seconds || ' seconds')::interval); + return pgq.event_retry(x_batch_id, x_event_id, new_retry); +end; +$$ language plpgsql security definer; + + + + + +create or replace function pgq.force_tick(i_queue_name text) +returns bigint as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.force_tick(2) +-- +-- Simulate lots of events happening to force ticker to tick. +-- +-- Should be called in loop, with some delay until last tick +-- changes or too much time is passed. +-- +-- Such function is needed because paraller calls of pgq.ticker() are +-- dangerous, and cannot be protected with locks as snapshot +-- is taken before locking. +-- +-- Parameters: +-- i_queue_name - Name of the queue +-- +-- Returns: +-- Currently last tick id. +-- ---------------------------------------------------------------------- +declare + q record; + t record; +begin + -- bump seq and get queue id + select queue_id, + setval(queue_event_seq, nextval(queue_event_seq) + + queue_ticker_max_count * 2) as tmp + into q from pgq.queue + where queue_name = i_queue_name + and not queue_external_ticker; + if not found then + raise exception 'queue not found or ticks not allowed'; + end if; + + -- return last tick id + select tick_id into t from pgq.tick + where tick_queue = q.queue_id + order by tick_queue desc, tick_id desc limit 1; + + return t.tick_id; +end; +$$ language plpgsql security definer; + + + +create or replace function pgq.grant_perms(x_queue_name text) +returns integer as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.grant_perms(1) +-- +-- Make event tables readable by public. +-- +-- Parameters: +-- x_queue_name - Name of the queue. +-- +-- Returns: +-- nothing +-- ---------------------------------------------------------------------- +declare + q record; + i integer; + tbl_perms text; + seq_perms text; +begin + select * from pgq.queue into q + where queue_name = x_queue_name; + if not found then + raise exception 'Queue not found'; + end if; + + if true then + -- safe, all access must go via functions + seq_perms := 'select'; + tbl_perms := 'select'; + else + -- allow ordinery users to directly insert + -- to event tables. dangerous. + seq_perms := 'select, update'; + tbl_perms := 'select, insert'; + end if; + + -- tick seq, normal users don't need to modify it + execute 'grant ' || seq_perms + || ' on ' || q.queue_tick_seq || ' to public'; + + -- event seq + execute 'grant ' || seq_perms + || ' on ' || q.queue_event_seq || ' to public'; + + -- parent table for events + execute 'grant select on ' || q.queue_data_pfx || ' to public'; + + -- real event tables + for i in 0 .. q.queue_ntables - 1 loop + execute 'grant ' || tbl_perms + || ' on ' || q.queue_data_pfx || '_' || i + || ' to public'; + end loop; + + return 1; +end; +$$ language plpgsql security definer; + + + +create or replace function pgq.insert_event(queue_name text, ev_type text, ev_data text) +returns bigint as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.insert_event(3) +-- +-- Insert a event into queue. +-- +-- Parameters: +-- queue_name - Name of the queue +-- ev_type - User-specified type for the event +-- ev_data - User data for the event +-- +-- Returns: +-- Event ID +-- ---------------------------------------------------------------------- +begin + return pgq.insert_event(queue_name, ev_type, ev_data, null, null, null, null); +end; +$$ language plpgsql security definer; + + + +create or replace function pgq.insert_event( + queue_name text, ev_type text, ev_data text, + ev_extra1 text, ev_extra2 text, ev_extra3 text, ev_extra4 text) +returns bigint as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.insert_event(7) +-- +-- Insert a event into queue with all the extra fields. +-- +-- Parameters: +-- queue_name - Name of the queue +-- ev_type - User-specified type for the event +-- ev_data - User data for the event +-- ev_extra1 - Extra data field for the event +-- ev_extra2 - Extra data field for the event +-- ev_extra3 - Extra data field for the event +-- ev_extra4 - Extra data field for the event +-- +-- Returns: +-- Event ID +-- ---------------------------------------------------------------------- +begin + return pgq.insert_event_raw(queue_name, null, now(), null, null, + ev_type, ev_data, ev_extra1, ev_extra2, ev_extra3, ev_extra4); +end; +$$ language plpgsql security definer; + + + +create or replace function pgq.maint_tables_to_vacuum() +returns setof text as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.maint_tables_to_vacuum(0) +-- +-- Returns list of tablenames that need frequent vacuuming. +-- +-- The goal is to avoid hardcoding them into maintenance process. +-- +-- Returns: +-- List of table names. +-- ---------------------------------------------------------------------- +declare + tbl text; + scm text; +begin + return next 'pgq.subscription'; + return next 'pgq.consumer'; + return next 'pgq.queue'; + return next 'pgq.tick'; + return next 'pgq.retry_queue'; + + -- include also txid, pgq_ext and londiste tables if they exist + for scm, tbl in + select n.nspname, t.relname from pg_class t, pg_namespace n + where n.oid = t.relnamespace + and n.nspname = 'txid' and t.relname = 'epoch' + union all + select n.nspname, t.relname from pg_class t, pg_namespace n + where n.oid = t.relnamespace + and n.nspname = 'londiste' and t.relname = 'completed' + union all + select n.nspname, t.relname from pg_class t, pg_namespace n + where n.oid = t.relnamespace + and n.nspname = 'pgq_ext' + and t.relname in ('completed_tick', 'completed_batch', 'completed_event', 'partial_batch') + loop + return next scm || '.' || tbl; + end loop; + + return; +end; +$$ language plpgsql; + + + + +create or replace function pgq.next_batch(x_queue_name text, x_consumer_name text) +returns bigint as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.next_batch(2) +-- +-- Makes next block of events active. +-- +-- If it returns NULL, there is no events available in queue. +-- Consumer should sleep a bith then. +-- +-- Parameters: +-- x_queue_name - Name of the queue +-- x_consumer_name - Name of the consumer +-- +-- Returns: +-- Batch ID or NULL if there are no more events available. +-- ---------------------------------------------------------------------- +declare + next_tick bigint; + batch_id bigint; + errmsg text; + sub record; +begin + select sub_queue, sub_consumer, sub_id, sub_last_tick, sub_batch into sub + from pgq.queue q, pgq.consumer c, pgq.subscription s + where q.queue_name = x_queue_name + and c.co_name = x_consumer_name + and s.sub_queue = q.queue_id + and s.sub_consumer = c.co_id; + if not found then + errmsg := 'Not subscriber to queue: ' + || coalesce(x_queue_name, 'NULL') + || '/' + || coalesce(x_consumer_name, 'NULL'); + raise exception '%', errmsg; + end if; + + -- has already active batch + if sub.sub_batch is not null then + return sub.sub_batch; + end if; + + -- find next tick + select tick_id into next_tick + from pgq.tick + where tick_id > sub.sub_last_tick + and tick_queue = sub.sub_queue + order by tick_queue asc, tick_id asc + limit 1; + if not found then + -- nothing to do + return null; + end if; + + -- get next batch + batch_id := nextval('pgq.batch_id_seq'); + update pgq.subscription + set sub_batch = batch_id, + sub_next_tick = next_tick, + sub_active = now() + where sub_queue = sub.sub_queue + and sub_consumer = sub.sub_consumer; + return batch_id; +end; +$$ language plpgsql security definer; + + + + +create or replace function pgq.register_consumer( + x_queue_name text, + x_consumer_id text) +returns integer as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.register_consumer(2) +-- +-- Subscribe consumer on a queue. +-- +-- From this moment forward, consumer will see all events in the queue. +-- +-- Parameters: +-- x_queue_name - Name of queue +-- x_consumer_name - Name of consumer +-- +-- Returns: +-- 0 - if already registered +-- 1 - if new registration +-- ---------------------------------------------------------------------- +begin + return pgq.register_consumer(x_queue_name, x_consumer_id, NULL); +end; +$$ language plpgsql security definer; + + +create or replace function pgq.register_consumer( + x_queue_name text, + x_consumer_name text, + x_tick_pos bigint) +returns integer as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.register_consumer(3) +-- +-- Extended registration, allows to specify tick_id. +-- +-- Note: +-- For usage in special situations. +-- +-- Parameters: +-- x_queue_name - Name of a queue +-- x_consumer_name - Name of consumer +-- x_tick_pos - Tick ID +-- +-- Returns: +-- 0/1 whether consumer has already registered. +-- ---------------------------------------------------------------------- +declare + tmp text; + last_tick bigint; + x_queue_id integer; + x_consumer_id integer; + queue integer; + sub record; +begin + select queue_id into x_queue_id from pgq.queue + where queue_name = x_queue_name; + if not found then + raise exception 'Event queue not created yet'; + end if; + + -- get consumer and create if new + select co_id into x_consumer_id from pgq.consumer + where co_name = x_consumer_name; + if not found then + insert into pgq.consumer (co_name) values (x_consumer_name); + x_consumer_id := currval('pgq.consumer_co_id_seq'); + end if; + + -- if particular tick was requested, check if it exists + if x_tick_pos is not null then + perform 1 from pgq.tick + where tick_queue = x_queue_id + and tick_id = x_tick_pos; + if not found then + raise exception 'cannot reposition, tick not found: %', x_tick_pos; + end if; + end if; + + -- check if already registered + select sub_last_tick, sub_batch into sub + from pgq.subscription + where sub_consumer = x_consumer_id + and sub_queue = x_queue_id; + if found then + if x_tick_pos is not null then + if sub.sub_batch is not null then + raise exception 'reposition while active not allowed'; + end if; + -- update tick pos if requested + update pgq.subscription + set sub_last_tick = x_tick_pos + where sub_consumer = x_consumer_id + and sub_queue = x_queue_id; + end if; + -- already registered + return 0; + end if; + + -- new registration + if x_tick_pos is null then + -- start from current tick + select tick_id into last_tick from pgq.tick + where tick_queue = x_queue_id + order by tick_queue desc, tick_id desc + limit 1; + if not found then + raise exception 'No ticks for this queue. Please run ticker on database.'; + end if; + else + last_tick := x_tick_pos; + end if; + + -- register + insert into pgq.subscription (sub_queue, sub_consumer, sub_last_tick) + values (x_queue_id, x_consumer_id, last_tick); + return 1; +end; +$$ language plpgsql security definer; + + + + +create or replace function pgq.version() +returns text as $$ +-- ---------------------------------------------------------------------- +-- Function: pgq.version(0) +-- +-- Returns verison string for pgq. ATM its SkyTools version +-- that is only bumped when PGQ database code changes. +-- ---------------------------------------------------------------------- +begin + return '2.1.5'; +end; +$$ language plpgsql; + + + + +grant usage on schema pgq to public; +grant select on table pgq.consumer to public; +grant select on table pgq.queue to public; +grant select on table pgq.tick to public; +grant select on table pgq.queue to public; +grant select on table pgq.subscription to public; +grant select on table pgq.event_template to public; +grant select on table pgq.retry_queue to public; +grant select on table pgq.failed_queue to public; + + +end; + + diff --git a/upgrade/final/v2.1.5_pgq_ext.sql b/upgrade/final/v2.1.5_pgq_ext.sql new file mode 100644 index 00000000..ca538cc4 --- /dev/null +++ b/upgrade/final/v2.1.5_pgq_ext.sql @@ -0,0 +1,42 @@ + +begin; + + + +create or replace function pgq_ext.get_last_tick(a_consumer text) +returns int8 as $$ +declare + res int8; +begin + select last_tick_id into res + from pgq_ext.completed_tick + where consumer_id = a_consumer; + return res; +end; +$$ language plpgsql security definer; + +create or replace function pgq_ext.set_last_tick(a_consumer text, a_tick_id bigint) +returns integer as $$ +begin + if a_tick_id is null then + delete from pgq_ext.completed_tick + where consumer_id = a_consumer; + else + update pgq_ext.completed_tick + set last_tick_id = a_tick_id + where consumer_id = a_consumer; + if not found then + insert into pgq_ext.completed_tick (consumer_id, last_tick_id) + values (a_consumer, a_tick_id); + end if; + end if; + + return 1; +end; +$$ language plpgsql security definer; + + + +end; + + diff --git a/upgrade/src/v2.1.5_londiste.sql b/upgrade/src/v2.1.5_londiste.sql new file mode 100644 index 00000000..f949878d --- /dev/null +++ b/upgrade/src/v2.1.5_londiste.sql @@ -0,0 +1,30 @@ +begin; + +create table londiste.subscriber_pending_fkeys( + from_table text not null, + to_table text not null, + fkey_name text not null, + fkey_def text not null, + + primary key (from_table, fkey_name) +); + +drop function londiste.denytrigger(); + +\i ../sql/londiste/functions/londiste.find_table_fkeys.sql +\i ../sql/londiste/functions/londiste.find_column_types.sql +\i ../sql/londiste/functions/londiste.subscriber_fkeys_funcs.sql + +\i ../sql/londiste/functions/londiste.find_table_oid.sql +\i ../sql/londiste/functions/londiste.get_last_tick.sql +\i ../sql/londiste/functions/londiste.provider_add_table.sql +\i ../sql/londiste/functions/londiste.provider_create_trigger.sql +\i ../sql/londiste/functions/londiste.provider_notify_change.sql +\i ../sql/londiste/functions/londiste.provider_remove_table.sql +\i ../sql/londiste/functions/londiste.set_last_tick.sql +\i ../sql/londiste/functions/londiste.subscriber_remove_table.sql + +\i ../sql/londiste/structure/grants.sql + +end; + diff --git a/upgrade/src/v2.1.5_pgq_core.sql b/upgrade/src/v2.1.5_pgq_core.sql new file mode 100644 index 00000000..f2695d7d --- /dev/null +++ b/upgrade/src/v2.1.5_pgq_core.sql @@ -0,0 +1,19 @@ +begin; + +alter table pgq.subscription constraint subscription_ukey unique (sub_queue, sub_consumer); +create index rq_retry_owner_idx on pgq.retry_queue (ev_owner, ev_id); + +\i ../sql/pgq/functions/pgq.current_event_table.sql +\i ../sql/pgq/functions/pgq.event_failed.sql +\i ../sql/pgq/functions/pgq.event_retry.sql +\i ../sql/pgq/functions/pgq.force_tick.sql +\i ../sql/pgq/functions/pgq.grant_perms.sql +\i ../sql/pgq/functions/pgq.insert_event.sql +\i ../sql/pgq/functions/pgq.maint_tables_to_vacuum.sql +\i ../sql/pgq/functions/pgq.next_batch.sql +\i ../sql/pgq/functions/pgq.register_consumer.sql +\i ../sql/pgq/functions/pgq.version.sql +\i ../sql/pgq/structure/grants.sql + +end; + diff --git a/upgrade/src/v2.1.5_pgq_ext.sql b/upgrade/src/v2.1.5_pgq_ext.sql new file mode 100644 index 00000000..552bfd68 --- /dev/null +++ b/upgrade/src/v2.1.5_pgq_ext.sql @@ -0,0 +1,6 @@ +begin; + +\i ../sql/pgq_ext/functions/track_tick.sql + +end; + |