summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Kreen2007-09-04 13:05:24 +0000
committerMarko Kreen2007-09-04 13:05:24 +0000
commit7197a342352d724d72d3a72abe183a0e85a6985c (patch)
tree1f7ffb134d97272ff5b056ac5d503d7757d41249
parent66841401145c2d3bc9d33ea39e226a807987b7f8 (diff)
upgrade scripts for 2.1.5
-rw-r--r--NEWS24
-rw-r--r--upgrade/Makefile13
-rw-r--r--upgrade/final/v2.1.5_londiste.sql443
-rw-r--r--upgrade/final/v2.1.5_pgq_core.sql579
-rw-r--r--upgrade/final/v2.1.5_pgq_ext.sql42
-rw-r--r--upgrade/src/v2.1.5_londiste.sql30
-rw-r--r--upgrade/src/v2.1.5_pgq_core.sql19
-rw-r--r--upgrade/src/v2.1.5_pgq_ext.sql6
8 files changed, 1151 insertions, 5 deletions
diff --git a/NEWS b/NEWS
index 56ff6d09..006804c7 100644
--- a/NEWS
+++ b/NEWS
@@ -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;
+