summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Kreen2012-05-02 13:45:00 +0000
committerMarko Kreen2012-05-10 18:19:24 +0000
commit128f094b0852944366856bdc56badb1b27dad40c (patch)
tree70e051b590ee966e7fa2f735a6914684fc4048ff
parentf82bdd553aabbdee0b44fc2c91e182baa8928d87 (diff)
Group-access roles for SQL functions (draft)
This is attempt for fine-grained access rights for all Skytools SQL schemas. As it still needs review, the rights are not activated by default, instead following sql files are generated: newgrants_<schema>.sql - applies new rights, drop old public access oldgrants_<schema>.sql - restores old rights - public execute privilege to all functions Only thing that is active by default is creation of new groups in upgrade functions. New access roles: pgq_reader Can consume queues (source-side) pgq_writer Can write into queues (source-side / dest-side) Can use pgq_node/pgq_ext schema as regular consumer (dest-side) pgq_admin Admin operations on queues, required for CascadedWorker on dest-side. Member of pgq_reader and pgq_writer. londiste_reader Member of pgq_reader, needs additional read access to tables. (source-side) londiste_writer Member of pgq_admin, needs additional write access to tables. (dest-side)
-rw-r--r--doc/Makefile1
-rw-r--r--doc/sql-grants.txt40
-rwxr-xr-xscripts/grantfu.py331
-rw-r--r--sql/londiste/Makefile14
-rw-r--r--sql/londiste/functions/londiste.upgrade_schema.sql12
-rw-r--r--sql/londiste/structure/grants.ini87
-rw-r--r--sql/londiste/structure/install.sql2
-rw-r--r--sql/pgq/Makefile15
-rw-r--r--sql/pgq/expected/pgq_perms.out42
-rw-r--r--sql/pgq/functions/pgq.grant_perms.sql76
-rw-r--r--sql/pgq/functions/pgq.upgrade_schema.sql17
-rw-r--r--sql/pgq/sql/pgq_perms.sql39
-rw-r--r--sql/pgq/structure/grants.ini100
-rw-r--r--sql/pgq/structure/grants.sql4
-rw-r--r--sql/pgq/structure/install.sql2
-rw-r--r--sql/pgq_coop/Makefile15
-rw-r--r--sql/pgq_coop/structure/grants.ini21
-rw-r--r--sql/pgq_coop/structure/grants.sql2
-rw-r--r--sql/pgq_ext/Makefile14
-rw-r--r--sql/pgq_ext/structure/grants.ini28
-rw-r--r--sql/pgq_ext/structure/grants.sql3
-rw-r--r--sql/pgq_ext/structure/install.sql1
-rw-r--r--sql/pgq_ext/structure/tables.sql1
-rw-r--r--sql/pgq_node/Makefile18
-rw-r--r--sql/pgq_node/functions/pgq_node.upgrade_schema.sql1
-rw-r--r--sql/pgq_node/structure/grants.ini73
-rw-r--r--sql/pgq_node/structure/grants.sql3
-rw-r--r--sql/pgq_node/structure/install.sql3
-rw-r--r--sql/pgq_node/structure/tables.sql1
29 files changed, 935 insertions, 31 deletions
diff --git a/doc/Makefile b/doc/Makefile
index d62a7355..3937572d 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -12,6 +12,7 @@ DOCHTML = \
TODO.html pgq-sql.html pgq-nodupes.html \
faq.html set.notes.html skytools3.html devnotes.html pgqd.html \
londiste3.html walmgr3.html qadmin.html scriptmgr.html \
+ sql-grants.html \
skytools_upgrade.html queue_mover.html queue_splitter.html \
howto/londiste3_cascaded_rep_howto.html \
howto/londiste3_merge_howto.html \
diff --git a/doc/sql-grants.txt b/doc/sql-grants.txt
new file mode 100644
index 00000000..f388910e
--- /dev/null
+++ b/doc/sql-grants.txt
@@ -0,0 +1,40 @@
+
+= SQL permissions (draft) =
+
+== Setup ==
+
+Currently following no-login roles are created during upgrade:
+`pgq_reader`, `pgq_writer`, `pgq_admin`, `londiste_reader`, `londiste_writer`.
+
+Actual grants are not applied to functions, instead default
+`public:execute` grants are kept. New grants can be applied
+manually:
+
+newgrants_<schema>.sql::
+ applies new rights, drop old public access
+
+oldgrants_<schema>.sql::
+ restores old rights - public execute privilege to all functions
+
+== New roles ==
+
+pgq_reader::
+ Can consume queues (source-side)
+
+pgq_writer::
+ Can write into queues (source-side / dest-side)
+ Can use `pgq_node`/`pgq_ext` schema as regular
+ consumer (dest-side)
+
+pgq_admin::
+ Admin operations on queues, required for CascadedWorker on dest-side.
+ Member of `pgq_reader` and `pgq_writer`.
+
+londiste_reader::
+ Member of `pgq_reader`, needs additional read access to tables.
+ (source-side)
+
+londiste_writer::
+ Member of `pgq_admin`, needs additional write access to tables.
+ (dest-side)
+
diff --git a/scripts/grantfu.py b/scripts/grantfu.py
new file mode 100755
index 00000000..5d89d559
--- /dev/null
+++ b/scripts/grantfu.py
@@ -0,0 +1,331 @@
+#! /usr/bin/env python
+
+# GrantFu - GRANT/REVOKE generator for Postgres
+#
+# Copyright (c) 2005 Marko Kreen
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
+"""Generator for PostgreSQL permissions.
+
+Loads config where roles, objects and their mapping is described
+and generates grants based on them.
+
+ConfigParser docs: http://docs.python.org/lib/module-ConfigParser.html
+
+Example:
+--------------------------------------------------------------------
+[DEFAULT]
+users = user1, user2 # users to handle
+groups = group1, group2 # groups to handle
+auto_seq = 0 # dont handle seqs (default)
+ # '!' after a table negates this setting for a table
+seq_name = id # the name for serial field (default: id)
+seq_usage = 0 # should we grant "usage" or "select, update"
+ # for automatically handled sequences
+
+# section names can be random, but if you want to see them
+# in same order as in config file, then order them alphabetically
+[1.section]
+on.tables = testtbl, testtbl_id_seq, # here we handle seq by hand
+ table_with_seq! # handle seq automatically
+ # (table_with_seq_id_seq)
+user1 = select
+group1 = select, insert, update
+
+# instead of 'tables', you may use 'functions', 'languages',
+# 'schemas', 'tablespaces'
+---------------------------------------------------------------------
+"""
+
+import sys, os, getopt
+from ConfigParser import SafeConfigParser
+
+__version__ = "1.0"
+
+R_NEW = 0x01
+R_DEFS = 0x02
+G_DEFS = 0x04
+R_ONLY = 0x80
+
+def usage(err):
+ sys.stderr.write("usage: %s [-r|-R] CONF_FILE\n" % sys.argv[0])
+ sys.stderr.write(" -r Generate also REVOKE commands\n")
+ sys.stderr.write(" -R Generate only REVOKE commands\n")
+ sys.stderr.write(" -d Also REVOKE default perms\n")
+ sys.stderr.write(" -D Only REVOKE default perms\n")
+ sys.stderr.write(" -o Generate default GRANTS\n")
+ sys.stderr.write(" -v Print program version\n")
+ sys.stderr.write(" -t Put everything in one big transaction\n")
+ sys.exit(err)
+
+class PConf(SafeConfigParser):
+ "List support for ConfigParser"
+ def __init__(self, defaults = None):
+ SafeConfigParser.__init__(self, defaults)
+
+ def get_list(self, sect, key):
+ str = self.get(sect, key).strip()
+ res = []
+ if not str:
+ return res
+ for val in str.split(","):
+ res.append(val.strip())
+ return res
+
+class GrantFu:
+ def __init__(self, cf_file, revoke):
+ self.revoke = revoke
+
+ # load config
+ self.cf = PConf()
+ self.cf.read(cf_file)
+ if not self.cf.has_section("GrantFu"):
+ print "Incorrect config file, GrantFu sction missing"
+ sys.exit(1)
+
+ # avoid putting grantfu vars into defaults, thus into every section
+ self.group_list = []
+ self.user_list = []
+ self.auto_seq = 0
+ self.seq_name = "id"
+ self.seq_usage = 0
+ if self.cf.has_option('GrantFu', 'groups'):
+ self.group_list = self.cf.get_list('GrantFu', 'groups')
+ if self.cf.has_option('GrantFu', 'users'):
+ self.user_list += self.cf.get_list('GrantFu', 'users')
+ if self.cf.has_option('GrantFu', 'roles'):
+ self.user_list += self.cf.get_list('GrantFu', 'roles')
+ if self.cf.has_option('GrantFu', 'auto_seq'):
+ self.auto_seq = self.cf.getint('GrantFu', 'auto_seq')
+ if self.cf.has_option('GrantFu', 'seq_name'):
+ self.seq_name = self.cf.get('GrantFu', 'seq_name')
+ if self.cf.has_option('GrantFu', 'seq_usage'):
+ self.seq_usage = self.cf.getint('GrantFu', 'seq_usage')
+
+ # make string of all subjects
+ tmp = []
+ for g in self.group_list:
+ tmp.append("group " + g)
+ for u in self.user_list:
+ tmp.append(u)
+ self.all_subjs = ", ".join(tmp)
+
+ # per-section vars
+ self.sect = None
+ self.seq_list = []
+ self.seq_allowed = []
+
+ def process(self):
+ if len(self.user_list) == 0 and len(self.group_list) == 0:
+ return
+
+ sect_list = self.cf.sections()
+ sect_list.sort()
+ for self.sect in sect_list:
+ if self.sect == "GrantFu":
+ continue
+ print "\n-- %s --" % self.sect
+
+ self.handle_tables()
+ self.handle_other('on.databases', 'DATABASE')
+ self.handle_other('on.functions', 'FUNCTION')
+ self.handle_other('on.languages', 'LANGUAGE')
+ self.handle_other('on.schemas', 'SCHEMA')
+ self.handle_other('on.tablespaces', 'TABLESPACE')
+ self.handle_other('on.sequences', 'SEQUENCE')
+ self.handle_other('on.types', 'TYPE')
+ self.handle_other('on.domains', 'DOMAIN')
+
+ def handle_other(self, listname, obj_type):
+ """Handle grants for all objects except tables."""
+
+ if not self.sect_hasvar(listname):
+ return
+
+ # don't parse list, as in case of functions it may be complicated
+ obj_str = obj_type + " " + self.sect_var(listname)
+
+ if self.revoke & R_NEW:
+ self.gen_revoke(obj_str)
+
+ if self.revoke & R_DEFS:
+ self.gen_revoke_defs(obj_str, obj_type)
+
+ if not self.revoke & R_ONLY:
+ self.gen_one_type(obj_str)
+
+ if self.revoke & G_DEFS:
+ self.gen_defs(obj_str, obj_type)
+
+ def handle_tables(self):
+ """Handle grants for tables and sequences.
+
+ The tricky part here is the automatic handling of sequences."""
+
+ if not self.sect_hasvar('on.tables'):
+ return
+
+ cleaned_list = []
+ table_list = self.sect_list('on.tables')
+ for table in table_list:
+ if table[-1] == '!':
+ table = table[:-1]
+ if not self.auto_seq:
+ self.seq_list.append("%s_%s_seq" % (table, self.seq_name))
+ else:
+ if self.auto_seq:
+ self.seq_list.append("%s_%s_seq" % (table, self.seq_name))
+ cleaned_list.append(table)
+ obj_str = "TABLE " + ", ".join(cleaned_list)
+
+ if self.revoke & R_NEW:
+ self.gen_revoke(obj_str)
+ if self.revoke & R_DEFS:
+ self.gen_revoke_defs(obj_str, "TABLE")
+ if not self.revoke & R_ONLY:
+ self.gen_one_type(obj_str)
+ if self.revoke & G_DEFS:
+ self.gen_defs(obj_str, "TABLE")
+
+ # cleanup
+ self.seq_list = []
+ self.seq_allowed = []
+
+ def gen_revoke(self, obj_str):
+ "Generate revoke for one section / subject type (user or group)"
+
+ if len(self.seq_list) > 0:
+ obj_str += ", " + ", ".join(self.seq_list)
+ obj_str = obj_str.strip().replace('\n', '\n ')
+ print "REVOKE ALL ON %s\n FROM %s CASCADE;" % (obj_str, self.all_subjs)
+
+ def gen_revoke_defs(self, obj_str, obj_type):
+ "Generate revoke defaults for one section"
+
+ # process only things that have default grants to public
+ if obj_type not in ('FUNCTION', 'DATABASE', 'LANGUAGE', 'TYPE', 'DOMAIN'):
+ return
+
+ defrole = 'public'
+
+ # if the sections contains grants to 'public', dont drop
+ if self.sect_hasvar(defrole):
+ return
+
+ obj_str = obj_str.strip().replace('\n', '\n ')
+ print "REVOKE ALL ON %s\n FROM %s CASCADE;" % (obj_str, defrole)
+
+ def gen_defs(self, obj_str, obj_type):
+ "Generate defaults grants for one section"
+
+ if obj_type == "FUNCTION":
+ defgrants = "execute"
+ elif obj_type == "DATABASE":
+ defgrants = "connect, temp"
+ elif obj_type in ("LANGUAGE", "TYPE", "DOMAIN"):
+ defgrants = "usage"
+ else:
+ return
+
+ defrole = 'public'
+
+ obj_str = obj_str.strip().replace('\n', '\n ')
+ print "GRANT %s ON %s\n TO %s;" % (defgrants, obj_str, defrole)
+
+ def gen_one_subj(self, subj, fqsubj, obj_str):
+ if not self.sect_hasvar(subj):
+ return
+ obj_str = obj_str.strip().replace('\n', '\n ')
+ perm = self.sect_var(subj).strip()
+ if perm:
+ print "GRANT %s ON %s\n TO %s;" % (perm, obj_str, fqsubj)
+
+ # check for seq perms
+ if len(self.seq_list) > 0:
+ loperm = perm.lower()
+ if loperm.find("insert") >= 0 or loperm.find("all") >= 0:
+ self.seq_allowed.append(fqsubj)
+
+ def gen_one_type(self, obj_str):
+ "Generate GRANT for one section / one object type in section"
+
+ for u in self.user_list:
+ self.gen_one_subj(u, u, obj_str)
+ for g in self.group_list:
+ self.gen_one_subj(g, "group " + g, obj_str)
+
+ # if there was any seq perms, generate grants
+ if len(self.seq_allowed) > 0:
+ seq_str = ", ".join(self.seq_list)
+ subj_str = ", ".join(self.seq_allowed)
+ if self.seq_usage:
+ cmd = "GRANT usage ON SEQUENCE %s\n TO %s;"
+ else:
+ cmd = "GRANT select, update ON %s\n TO %s;"
+ print cmd % (seq_str, subj_str)
+
+ def sect_var(self, name):
+ return self.cf.get(self.sect, name).strip()
+
+ def sect_list(self, name):
+ return self.cf.get_list(self.sect, name)
+
+ def sect_hasvar(self, name):
+ return self.cf.has_option(self.sect, name)
+
+def main():
+ revoke = 0
+ tx = False
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "vhrRdDot")
+ except getopt.error, det:
+ print "getopt error:", det
+ usage(1)
+
+ for o, v in opts:
+ if o == "-h":
+ usage(0)
+ elif o == "-r":
+ revoke |= R_NEW
+ elif o == "-R":
+ revoke |= R_NEW | R_ONLY
+ elif o == "-d":
+ revoke |= R_DEFS
+ elif o == "-D":
+ revoke |= R_DEFS | R_ONLY
+ elif o == "-o":
+ revoke |= G_DEFS
+ elif o == "-t":
+ tx = True
+ elif o == "-v":
+ print "GrantFu version", __version__
+ sys.exit(0)
+
+ if len(args) != 1:
+ usage(1)
+
+ if tx:
+ print "begin;\n"
+
+ g = GrantFu(args[0], revoke)
+ g.process()
+
+ if tx:
+ print "\ncommit;\n"
+
+if __name__ == '__main__':
+ main()
+
diff --git a/sql/londiste/Makefile b/sql/londiste/Makefile
index c81efb34..55a526dd 100644
--- a/sql/londiste/Makefile
+++ b/sql/londiste/Makefile
@@ -1,5 +1,7 @@
-DATA_built = londiste.sql londiste.upgrade.sql
+DATA_built = londiste.sql londiste.upgrade.sql \
+ structure/oldgrants_londiste.sql \
+ structure/newgrants_londiste.sql
SQLS = $(shell sed -e 's/^[^\\].*//' -e 's/\\i //' structure/install.sql)
FUNCS = $(shell sed -e 's/^[^\\].*//' -e 's/\\i //' $(SQLS))
@@ -8,6 +10,7 @@ SRCS = $(SQLS) $(FUNCS)
NDOC = NaturalDocs
NDOCARGS = -r -o html docs/html -p docs -i docs/sql
CATSQL = ../../scripts/catsql.py
+GRANTFU = ../../scripts/grantfu.py
REGRESS = londiste_install londiste_provider londiste_subscriber \
londiste_fkeys londiste_execute londiste_seqs londiste_merge \
@@ -27,6 +30,15 @@ londiste.sql: $(SRCS)
londiste.upgrade.sql: $(SRCS)
$(CATSQL) structure/upgrade.sql > $@
+structure/newgrants_londiste.sql: structure/grants.ini
+ $(GRANTFU) -r -d -t $< > $@
+
+structure/oldgrants_londiste.sql: structure/grants.ini
+ echo "begin;" > $@
+ $(GRANTFU) -R -o $< >> $@
+ cat structure/grants.sql >> $@
+ echo "commit;" >> $@
+
test: londiste.sql
$(MAKE) installcheck || { filterdiff --format=unified regression.diffs | less; exit 1; }
diff --git a/sql/londiste/functions/londiste.upgrade_schema.sql b/sql/londiste/functions/londiste.upgrade_schema.sql
index b982a0cc..9067ed7d 100644
--- a/sql/londiste/functions/londiste.upgrade_schema.sql
+++ b/sql/londiste/functions/londiste.upgrade_schema.sql
@@ -28,6 +28,18 @@ begin
alter table londiste.table_info add column dest_table text;
end if;
+ -- create roles
+ perform 1 from pg_catalog.pg_roles where rolname = 'londiste_writer';
+ if not found then
+ create role londiste_writer in role pgq_admin;
+ cnt := cnt + 1;
+ end if;
+ perform 1 from pg_catalog.pg_roles where rolname = 'londiste_reader';
+ if not found then
+ create role londiste_reader in role pgq_reader;
+ cnt := cnt + 1;
+ end if;
+
return cnt;
end;
$$ language plpgsql;
diff --git a/sql/londiste/structure/grants.ini b/sql/londiste/structure/grants.ini
new file mode 100644
index 00000000..26dff49a
--- /dev/null
+++ b/sql/londiste/structure/grants.ini
@@ -0,0 +1,87 @@
+
+[GrantFu]
+# roles that we maintain in this file
+roles = londiste_local, londiste_remote, public
+
+
+[1.tables]
+on.tables = londiste.table_info, londiste.seq_info, londiste.pending_fkeys, londiste.applied_execute
+
+londiste_local = select, insert, update, delete
+londiste_remote = select
+
+# backwards compat, should be dropped?
+public = select
+
+
+[2.public.fns]
+on.functions = %(londiste_public_fns)s
+public = execute
+
+
+[3.remote.node]
+on.functions = %(londiste_remote_fns)s
+londiste_remote = execute
+londiste_local = execute
+
+
+[3.local.node]
+on.functions = %(londiste_local_fns)s, %(londiste_internal_fns)s
+londiste_local = execute
+
+
+# define various groups of functions
+[DEFAULT]
+
+# can be executed by everybody, read-only, not secdef
+londiste_public_fns =
+ londiste.find_column_types(text),
+ londiste.find_table_fkeys(text),
+ londiste.find_rel_oid(text, text),
+ londiste.find_table_oid(text),
+ londiste.find_seq_oid(text),
+ londiste.is_replica_func(oid),
+ londiste.quote_fqname(text),
+ londiste.make_fqname(text),
+ londiste.split_fqname(text),
+ londiste.version()
+
+# remote node uses those on provider, read local tables
+londiste_remote_fns =
+ londiste.get_seq_list(text),
+ londiste.get_table_list(text),
+ londiste._coordinate_copy(text, text)
+
+# used by owner only
+londiste_internal_fns =
+ londiste.upgrade_schema()
+
+# used by local worker, admin
+londiste_local_fns =
+ londiste.local_show_missing(text),
+ londiste.local_add_seq(text, text),
+ londiste.local_add_table(text, text, text[], text, text),
+ londiste.local_add_table(text, text, text[], text),
+ londiste.local_add_table(text, text, text[]),
+ londiste.local_add_table(text, text),
+ londiste.local_remove_seq(text, text),
+ londiste.local_remove_table(text, text),
+ londiste.global_add_table(text, text),
+ londiste.global_remove_table(text, text),
+ londiste.global_update_seq(text, text, int8),
+ londiste.global_remove_seq(text, text),
+ londiste.get_table_pending_fkeys(text),
+ londiste.get_valid_pending_fkeys(text),
+ londiste.drop_table_fkey(text, text),
+ londiste.restore_table_fkey(text, text),
+ londiste.execute_start(text, text, text, boolean),
+ londiste.execute_finish(text, text),
+ londiste.root_check_seqs(text, int8),
+ londiste.root_check_seqs(text),
+ londiste.root_notify_change(text, text, text),
+ londiste.local_set_table_state(text, text, text, text),
+ londiste.local_set_table_attrs(text, text, text),
+ londiste.local_set_table_struct(text, text, text),
+ londiste.drop_table_triggers(text, text),
+ londiste.table_info_trigger()
+
diff --git a/sql/londiste/structure/install.sql b/sql/londiste/structure/install.sql
index 00bf5638..fde88003 100644
--- a/sql/londiste/structure/install.sql
+++ b/sql/londiste/structure/install.sql
@@ -1,4 +1,4 @@
\i structure/tables.sql
-\i structure/grants.sql
\i structure/functions.sql
\i structure/triggers.sql
+\i structure/grants.sql
diff --git a/sql/pgq/Makefile b/sql/pgq/Makefile
index 87992bae..487f92c9 100644
--- a/sql/pgq/Makefile
+++ b/sql/pgq/Makefile
@@ -1,6 +1,6 @@
DOCS = README.pgq
-DATA_built = pgq.sql pgq.upgrade.sql
+DATA_built = pgq.sql pgq.upgrade.sql structure/oldgrants_pgq.sql structure/newgrants_pgq.sql
DATA = structure/uninstall_pgq.sql
# scripts that load other sql files
@@ -9,7 +9,7 @@ FUNCS = $(shell sed -e 's/^[^\\].*//' -e 's/\\i //' $(LDRS))
SRCS = structure/tables.sql structure/grants.sql structure/install.sql \
structure/uninstall_pgq.sql $(FUNCS)
-REGRESS = pgq_init pgq_core logutriga sqltriga trunctrg
+REGRESS = pgq_init pgq_core pgq_perms logutriga sqltriga trunctrg
REGRESS_OPTS = --load-language=plpgsql
PG_CONFIG = pg_config
@@ -19,6 +19,7 @@ include $(PGXS)
NDOC = NaturalDocs
NDOCARGS = -r -o html docs/html -p docs -i docs/sql
CATSQL = ../../scripts/catsql.py
+GRANTFU = ../../scripts/grantfu.py
SUBDIRS = lowlevel triggers
@@ -49,6 +50,15 @@ pgq.sql: $(SRCS)
pgq.upgrade.sql: $(SRCS)
$(CATSQL) structure/upgrade.sql > $@
+structure/newgrants_pgq.sql: structure/grants.ini
+ $(GRANTFU) -t -r -d $< > $@
+
+structure/oldgrants_pgq.sql: structure/grants.ini structure/grants.sql
+ echo "begin;" > $@
+ $(GRANTFU) -R -o $< >> $@
+ cat structure/grants.sql >> $@
+ echo "commit;" >> $@
+
#
# docs
#
@@ -79,3 +89,4 @@ test: pgq.sql
ack:
cp results/*.out expected/
+.PHONY: test ack upload cleandox dox
diff --git a/sql/pgq/expected/pgq_perms.out b/sql/pgq/expected/pgq_perms.out
new file mode 100644
index 00000000..83b6ac4d
--- /dev/null
+++ b/sql/pgq/expected/pgq_perms.out
@@ -0,0 +1,42 @@
+\set ECHO off
+drop role if exists pgq_test_producer;
+drop role if exists pgq_test_consumer;
+drop role if exists pgq_test_admin;
+create role pgq_test_consumer with login in role pgq_reader;
+create role pgq_test_producer with login in role pgq_writer;
+create role pgq_test_admin with login in role pgq_admin;
+\c - pgq_test_admin
+select * from pgq.create_queue('pqueue'); -- ok
+ create_queue
+--------------
+ 1
+(1 row)
+
+\c - pgq_test_producer
+select * from pgq.create_queue('pqueue'); -- fail
+ERROR: permission denied for function create_queue
+select * from pgq.insert_event('pqueue', 'test', 'data'); -- ok
+ insert_event
+--------------
+ 1
+(1 row)
+
+select * from pgq.register_consumer('pqueue', 'prod'); -- fail
+ERROR: permission denied for function register_consumer
+\c - pgq_test_consumer
+select * from pgq.create_queue('pqueue'); -- fail
+ERROR: permission denied for function create_queue
+select * from pgq.insert_event('pqueue', 'test', 'data'); -- fail
+ERROR: permission denied for function insert_event
+select * from pgq.register_consumer('pqueue', 'cons'); -- ok
+ register_consumer
+-------------------
+ 1
+(1 row)
+
+select * from pgq.next_batch('pqueue', 'cons'); -- ok
+ next_batch
+------------
+
+(1 row)
+
diff --git a/sql/pgq/functions/pgq.grant_perms.sql b/sql/pgq/functions/pgq.grant_perms.sql
index afabfa5c..db8c6edf 100644
--- a/sql/pgq/functions/pgq.grant_perms.sql
+++ b/sql/pgq/functions/pgq.grant_perms.sql
@@ -14,8 +14,12 @@ returns integer as $$
declare
q record;
i integer;
+ pos integer;
tbl_perms text;
seq_perms text;
+ dst_schema text;
+ dst_table text;
+ part_table text;
begin
select * from pgq.queue into q
where queue_name = x_queue_name;
@@ -23,36 +27,72 @@ begin
raise exception 'Queue not found';
end if;
- if true then
- -- safe, all access must go via functions
- seq_perms := 'select';
- tbl_perms := 'select';
+ -- split data table name to components
+ pos := position('.' in q.queue_data_pfx);
+ if pos > 0 then
+ dst_schema := substring(q.queue_data_pfx for pos - 1);
+ dst_table := substring(q.queue_data_pfx from pos + 1);
else
- -- allow ordinery users to directly insert
- -- to event tables. dangerous.
- seq_perms := 'select, update';
- tbl_perms := 'select, insert';
+ dst_schema := 'public';
+ dst_table := q.queue_data_pfx;
end if;
-- tick seq, normal users don't need to modify it
- execute 'grant ' || seq_perms
- || ' on ' || q.queue_tick_seq || ' to public';
+ execute 'grant select on ' || q.queue_tick_seq || ' to public';
-- event seq
- execute 'grant ' || seq_perms
- || ' on ' || q.queue_event_seq || ' to public';
+ execute 'grant select on ' || q.queue_event_seq || ' to public';
- -- parent table for events
- execute 'grant select on ' || q.queue_data_pfx || ' to public';
+ -- set grants on parent table
+ perform pgq._grant_perms_from('pgq', 'event_template', dst_schema, dst_table);
- -- real event tables
+ -- set grants on real event tables
for i in 0 .. q.queue_ntables - 1 loop
- execute 'grant ' || tbl_perms
- || ' on ' || q.queue_data_pfx || '_' || i::text
- || ' to public';
+ part_table := dst_table || '_' || i::text;
+ perform pgq._grant_perms_from('pgq', 'event_template', dst_schema, part_table);
end loop;
return 1;
end;
$$ language plpgsql security definer;
+
+create or replace function pgq._grant_perms_from(src_schema text, src_table text, dst_schema text, dst_table text)
+returns integer as $$
+-- ----------------------------------------------------------------------
+-- Function: pgq.grant_perms_from(1)
+--
+-- Copy grants from one table to another.
+-- Workaround for missing GRANTS option for CREATE TABLE LIKE.
+-- ----------------------------------------------------------------------
+declare
+ fq_table text;
+ sql text;
+ g record;
+ q_grantee text;
+begin
+ fq_table := quote_ident(dst_schema) || '.' || quote_ident(dst_table);
+
+ for g in
+ select grantor, grantee, privilege_type, is_grantable
+ from information_schema.table_privileges
+ where table_schema = src_schema
+ and table_name = src_table
+ loop
+ if g.grantee = 'PUBLIC' then
+ q_grantee = 'public';
+ else
+ q_grantee = quote_ident(g.grantee);
+ end if;
+ sql := 'grant ' || g.privilege_type || ' on ' || fq_table
+ || ' to ' || q_grantee;
+ if g.is_grantable = 'YES' then
+ sql := sql || ' with grant option';
+ end if;
+ execute sql;
+ end loop;
+
+ return 1;
+end;
+$$ language plpgsql;
+
diff --git a/sql/pgq/functions/pgq.upgrade_schema.sql b/sql/pgq/functions/pgq.upgrade_schema.sql
index 69d1bb17..8ddc8b32 100644
--- a/sql/pgq/functions/pgq.upgrade_schema.sql
+++ b/sql/pgq/functions/pgq.upgrade_schema.sql
@@ -19,6 +19,23 @@ begin
cnt := cnt + 1;
end if;
+ -- create roles
+ perform 1 from pg_catalog.pg_roles where rolname = 'pgq_reader';
+ if not found then
+ create role pgq_reader;
+ cnt := cnt + 1;
+ end if;
+ perform 1 from pg_catalog.pg_roles where rolname = 'pgq_writer';
+ if not found then
+ create role pgq_writer;
+ cnt := cnt + 1;
+ end if;
+ perform 1 from pg_catalog.pg_roles where rolname = 'pgq_admin';
+ if not found then
+ create role pgq_admin in role pgq_reader, pgq_writer;
+ cnt := cnt + 1;
+ end if;
+
return cnt;
end;
$$ language plpgsql;
diff --git a/sql/pgq/sql/pgq_perms.sql b/sql/pgq/sql/pgq_perms.sql
new file mode 100644
index 00000000..c1962a1f
--- /dev/null
+++ b/sql/pgq/sql/pgq_perms.sql
@@ -0,0 +1,39 @@
+\set ECHO off
+\set VERBOSITY 'terse'
+set client_min_messages = 'warning';
+
+-- drop public perms
+\i structure/newgrants_pgq.sql
+
+-- select proname, proacl from pg_proc p, pg_namespace n where n.nspname = 'pgq' and p.pronamespace = n.oid;
+
+\set ECHO all
+
+drop role if exists pgq_test_producer;
+drop role if exists pgq_test_consumer;
+drop role if exists pgq_test_admin;
+
+create role pgq_test_consumer with login in role pgq_reader;
+create role pgq_test_producer with login in role pgq_writer;
+create role pgq_test_admin with login in role pgq_admin;
+
+
+\c - pgq_test_admin
+
+select * from pgq.create_queue('pqueue'); -- ok
+
+\c - pgq_test_producer
+
+select * from pgq.create_queue('pqueue'); -- fail
+
+select * from pgq.insert_event('pqueue', 'test', 'data'); -- ok
+
+select * from pgq.register_consumer('pqueue', 'prod'); -- fail
+
+\c - pgq_test_consumer
+
+select * from pgq.create_queue('pqueue'); -- fail
+select * from pgq.insert_event('pqueue', 'test', 'data'); -- fail
+select * from pgq.register_consumer('pqueue', 'cons'); -- ok
+select * from pgq.next_batch('pqueue', 'cons'); -- ok
+
diff --git a/sql/pgq/structure/grants.ini b/sql/pgq/structure/grants.ini
new file mode 100644
index 00000000..82cb8554
--- /dev/null
+++ b/sql/pgq/structure/grants.ini
@@ -0,0 +1,100 @@
+
+[GrantFu]
+roles = pgq_reader, pgq_writer, pgq_admin, public
+
+[1.public]
+on.functions = %(pgq_generic_fns)s
+public = execute
+
+[2.consumer]
+on.functions = %(pgq_read_fns)s
+pgq_reader = execute
+
+[3.producer]
+on.functions = %(pgq_write_fns)s
+pgq_writer = execute
+
+[4.admin]
+on.functions = %(pgq_system_fns)s
+pgq_admin = execute
+
+[5.meta.tables]
+on.tables =
+ pgq.consumer,
+ pgq.queue,
+ pgq.tick,
+ pgq.subscription
+pgq_reader = select
+public = select
+
+[5.event.tables]
+on.tables = pgq.event_template, pgq.retry_queue
+pgq_reader = select
+
+# drop public access to events
+public =
+
+
+#
+# define various groups of functions
+#
+
+[DEFAULT]
+
+pgq_generic_fns =
+ pgq.seq_getval(text),
+ pgq.get_queue_info(),
+ pgq.get_queue_info(text),
+ pgq.get_consumer_info(),
+ pgq.get_consumer_info(text),
+ pgq.get_consumer_info(text, text),
+ pgq.version()
+
+pgq_read_fns =
+ pgq.batch_event_sql(bigint),
+ pgq.batch_event_tables(bigint),
+ pgq.find_tick_helper(int4, int8, timestamptz, int8, int8, interval),
+ pgq.register_consumer(text, text),
+ pgq.register_consumer_at(text, text, bigint),
+ pgq.unregister_consumer(text, text),
+ pgq.next_batch_info(text, text),
+ pgq.next_batch(text, text),
+ pgq.next_batch_custom(text, text, interval, int4, interval),
+ pgq.get_batch_events(bigint),
+ pgq.get_batch_info(bigint),
+ pgq.get_batch_cursor(bigint, text, int4, text),
+ pgq.get_batch_cursor(bigint, text, int4),
+ pgq.event_retry(bigint, bigint, timestamptz),
+ pgq.event_retry(bigint, bigint, integer),
+ pgq.batch_retry(bigint, integer),
+ pgq.finish_batch(bigint)
+
+pgq_write_fns =
+ pgq.insert_event(text, text, text),
+ pgq.insert_event(text, text, text, text, text, text, text),
+ pgq.current_event_table(text),
+ pgq.sqltriga(),
+ pgq.logutriga()
+
+pgq_system_fns =
+ pgq.ticker(text, bigint, timestamptz, bigint),
+ pgq.ticker(text),
+ pgq.ticker(),
+ pgq.maint_retry_events(),
+ pgq.maint_rotate_tables_step1(text),
+ pgq.maint_rotate_tables_step2(),
+ pgq.maint_tables_to_vacuum(),
+ pgq.maint_operations(),
+ pgq.upgrade_schema(),
+ pgq.grant_perms(text),
+ pgq._grant_perms_from(text,text,text,text),
+ pgq.tune_storage(text),
+ pgq.force_tick(text),
+ pgq.seq_setval(text, int8),
+ pgq.create_queue(text),
+ pgq.drop_queue(text, bool),
+ pgq.drop_queue(text),
+ pgq.set_queue_config(text, text, text),
+ pgq.insert_event_raw(text, bigint, timestamptz, integer, integer, text, text, text, text, text, text),
+ pgq.event_retry_raw(text, text, timestamptz, bigint, timestamptz, integer, text, text, text, text, text, text)
+
diff --git a/sql/pgq/structure/grants.sql b/sql/pgq/structure/grants.sql
index d5f8ef1f..acbd484c 100644
--- a/sql/pgq/structure/grants.sql
+++ b/sql/pgq/structure/grants.sql
@@ -1,5 +1,8 @@
+
grant usage on schema pgq to public;
+
+-- old default grants
grant select on table pgq.consumer to public;
grant select on table pgq.queue to public;
grant select on table pgq.tick to public;
@@ -7,3 +10,4 @@ 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;
+
diff --git a/sql/pgq/structure/install.sql b/sql/pgq/structure/install.sql
index 511aaba7..747801ef 100644
--- a/sql/pgq/structure/install.sql
+++ b/sql/pgq/structure/install.sql
@@ -1,7 +1,7 @@
\i structure/tables.sql
-\i structure/grants.sql
\i structure/func_internal.sql
\i structure/func_public.sql
\i structure/triggers.sql
+\i structure/grants.sql
diff --git a/sql/pgq_coop/Makefile b/sql/pgq_coop/Makefile
index 31c5d329..5561ef71 100644
--- a/sql/pgq_coop/Makefile
+++ b/sql/pgq_coop/Makefile
@@ -1,5 +1,7 @@
-DATA_built = pgq_coop.sql pgq_coop.upgrade.sql
+DATA_built = pgq_coop.sql pgq_coop.upgrade.sql \
+ structure/newgrants_pgq_coop.sql \
+ structure/oldgrants_pgq_coop.sql
SQL_FULL = structure/schema.sql structure/functions.sql structure/grants.sql
@@ -16,6 +18,8 @@ include $(PGXS)
NDOC = NaturalDocs
NDOCARGS = -r -o html docs/html -p docs -i docs/sql
CATSQL = ../../scripts/catsql.py
+GRANTFU = ../../scripts/grantfu.py
+
#
# combined SQL files
@@ -27,6 +31,15 @@ pgq_coop.sql: $(SRCS)
pgq_coop.upgrade.sql: $(SRCS)
$(CATSQL) structure/upgrade.sql > $@
+structure/newgrants_pgq_coop.sql: structure/grants.ini
+ $(GRANTFU) -t -r -d $< > $@
+
+structure/oldgrants_pgq_coop.sql: structure/grants.ini structure/grants.sql
+ echo "begin;" > $@
+ $(GRANTFU) -R -o $< >> $@
+ cat structure/grants.sql >> $@
+ echo "commit;" >> $@
+
#
# docs
#
diff --git a/sql/pgq_coop/structure/grants.ini b/sql/pgq_coop/structure/grants.ini
new file mode 100644
index 00000000..a1e98ea9
--- /dev/null
+++ b/sql/pgq_coop/structure/grants.ini
@@ -0,0 +1,21 @@
+[GrantFu]
+roles = pgq_reader, pgq_writer, pgq_admin, public
+
+[1.consumer]
+on.functions = %(pgq_coop_fns)s
+pgq_reader = execute
+
+[2.public]
+on.functions = pgq_coop.version()
+public = execute
+
+[DEFAULT]
+pgq_coop_fns =
+ pgq_coop.register_subconsumer(text, text, text),
+ pgq_coop.unregister_subconsumer(text, text, text, integer),
+ pgq_coop.next_batch(text, text, text),
+ pgq_coop.next_batch(text, text, text, interval),
+ pgq_coop.next_batch_custom(text, text, text, interval, int4, interval),
+ pgq_coop.next_batch_custom(text, text, text, interval, int4, interval, interval),
+ pgq_coop.finish_batch(bigint)
+
diff --git a/sql/pgq_coop/structure/grants.sql b/sql/pgq_coop/structure/grants.sql
index b3f384cd..2ed2bd20 100644
--- a/sql/pgq_coop/structure/grants.sql
+++ b/sql/pgq_coop/structure/grants.sql
@@ -1,3 +1,3 @@
-grant usage on schema pgq_coop to public;
+GRANT usage ON SCHEMA pgq_coop TO public;
diff --git a/sql/pgq_ext/Makefile b/sql/pgq_ext/Makefile
index 50381f6f..8fec6e61 100644
--- a/sql/pgq_ext/Makefile
+++ b/sql/pgq_ext/Makefile
@@ -1,12 +1,15 @@
DOCS = README.pgq_ext
-DATA_built = pgq_ext.sql pgq_ext.upgrade.sql
+DATA_built = pgq_ext.sql pgq_ext.upgrade.sql \
+ structure/oldgrants_pgq_ext.sql \
+ structure/newgrants_pgq_ext.sql
SRCS = $(wildcard functions/*.sql structure/*.sql)
REGRESS = test_pgq_ext test_upgrade
REGRESS_OPTS = --load-language=plpgsql
+GRANTFU = ../../scripts/grantfu.py
CATSQL = ../../scripts/catsql.py
NDOC = NaturalDocs
NDOCARGS = -r -o html docs/html -p docs -i docs/sql
@@ -21,6 +24,15 @@ pgq_ext.sql: $(SRCS)
pgq_ext.upgrade.sql: $(SRCS)
$(CATSQL) structure/upgrade.sql > $@
+structure/newgrants_pgq_ext.sql: structure/grants.ini
+ $(GRANTFU) -t -r -d $< > $@
+
+structure/oldgrants_pgq_ext.sql: structure/grants.ini structure/grants.sql
+ echo "begin;" > $@
+ $(GRANTFU) -R -o $< >> $@
+ cat structure/grants.sql >> $@
+ echo "commit;" >> $@
+
test: pgq_ext.sql
make installcheck || { less regression.diffs ; exit 1; }
diff --git a/sql/pgq_ext/structure/grants.ini b/sql/pgq_ext/structure/grants.ini
new file mode 100644
index 00000000..e9c6927b
--- /dev/null
+++ b/sql/pgq_ext/structure/grants.ini
@@ -0,0 +1,28 @@
+[GrantFu]
+roles = pgq_writer, public
+
+[1.public]
+on.functions = pgq_ext.version()
+public = execute
+
+[2.pgq_ext]
+on.functions = %(pgq_ext_fns)s
+pgq_writer = execute
+
+
+[DEFAULT]
+pgq_ext_fns =
+ pgq_ext.upgrade_schema(),
+ pgq_ext.is_batch_done(text, text, bigint),
+ pgq_ext.is_batch_done(text, bigint),
+ pgq_ext.set_batch_done(text, text, bigint),
+ pgq_ext.set_batch_done(text, bigint),
+ pgq_ext.is_event_done(text, text, bigint, bigint),
+ pgq_ext.is_event_done(text, bigint, bigint),
+ pgq_ext.set_event_done(text, text, bigint, bigint),
+ pgq_ext.set_event_done(text, bigint, bigint),
+ pgq_ext.get_last_tick(text, text),
+ pgq_ext.get_last_tick(text),
+ pgq_ext.set_last_tick(text, text, bigint),
+ pgq_ext.set_last_tick(text, bigint)
+
diff --git a/sql/pgq_ext/structure/grants.sql b/sql/pgq_ext/structure/grants.sql
new file mode 100644
index 00000000..0b3db788
--- /dev/null
+++ b/sql/pgq_ext/structure/grants.sql
@@ -0,0 +1,3 @@
+
+grant usage on schema pgq_ext to public;
+
diff --git a/sql/pgq_ext/structure/install.sql b/sql/pgq_ext/structure/install.sql
index 973e35b8..4b9be2c5 100644
--- a/sql/pgq_ext/structure/install.sql
+++ b/sql/pgq_ext/structure/install.sql
@@ -1,3 +1,4 @@
\i structure/tables.sql
\i structure/upgrade.sql
+\i structure/grants.sql
diff --git a/sql/pgq_ext/structure/tables.sql b/sql/pgq_ext/structure/tables.sql
index c40368eb..78a1b234 100644
--- a/sql/pgq_ext/structure/tables.sql
+++ b/sql/pgq_ext/structure/tables.sql
@@ -52,7 +52,6 @@ set client_min_messages = 'warning';
set default_with_oids = 'off';
create schema pgq_ext;
-grant usage on schema pgq_ext to public;
--
diff --git a/sql/pgq_node/Makefile b/sql/pgq_node/Makefile
index c3932ca9..c735587a 100644
--- a/sql/pgq_node/Makefile
+++ b/sql/pgq_node/Makefile
@@ -1,9 +1,12 @@
-DATA_built = pgq_node.sql pgq_node.upgrade.sql
+DATA_built = pgq_node.sql pgq_node.upgrade.sql \
+ structure/newgrants_pgq_node.sql \
+ structure/oldgrants_pgq_node.sql
LDRS = structure/functions.sql
FUNCS = $(shell sed -e 's/^[^\\].*//' -e 's/\\i //' $(LDRS))
-SRCS = structure/tables.sql structure/functions.sql $(FUNCS)
+SRCS = structure/tables.sql structure/functions.sql structure/grants.sql \
+ $(FUNCS)
REGRESS = pgq_node_test
REGRESS_OPTS = --load-language=plpgsql
@@ -15,6 +18,7 @@ include $(PGXS)
NDOC = NaturalDocs
NDOCARGS = -r -o html docs/html -p docs -i docs/sql
CATSQL = ../../scripts/catsql.py
+GRANTFU = ../../scripts/grantfu.py
#
# combined SQL files
@@ -26,6 +30,16 @@ pgq_node.sql: $(SRCS)
pgq_node.upgrade.sql: $(SRCS)
$(CATSQL) structure/upgrade.sql > $@
+structure/newgrants_pgq_node.sql: structure/grants.ini
+ $(GRANTFU) -r -d -t $< > $@
+
+structure/oldgrants_pgq_node.sql: structure/grants.ini structure/grants.sql
+ echo "begin;" > $@
+ $(GRANTFU) -R -o $< >> $@
+ cat structure/grants.sql >> $@
+ echo "commit;" >> $@
+
+
#
# docs
#
diff --git a/sql/pgq_node/functions/pgq_node.upgrade_schema.sql b/sql/pgq_node/functions/pgq_node.upgrade_schema.sql
index d9c1b6f4..1678559f 100644
--- a/sql/pgq_node/functions/pgq_node.upgrade_schema.sql
+++ b/sql/pgq_node/functions/pgq_node.upgrade_schema.sql
@@ -19,4 +19,3 @@ begin
end;
$$ language plpgsql;
-
diff --git a/sql/pgq_node/structure/grants.ini b/sql/pgq_node/structure/grants.ini
new file mode 100644
index 00000000..d1cc4558
--- /dev/null
+++ b/sql/pgq_node/structure/grants.ini
@@ -0,0 +1,73 @@
+[GrantFu]
+roles = pgq_writer, pgq_admin, pgq_reader, public
+
+[1.public.fns]
+on.functions = %(pgq_node_public_fns)s
+public = execute
+
+# cascaded consumer, target side
+[2.consumer.fns]
+on.functions = %(pgq_node_consumer_fns)s
+pgq_writer = execute
+pgq_admin = execute
+
+# cascaded worker, target side
+[3.worker.fns]
+on.functions = %(pgq_node_worker_fns)s
+pgq_admin = execute
+
+# cascaded consumer/worker, source side
+[4.remote.fns]
+on.functions = %(pgq_node_remote_fns)s
+pgq_reader = execute
+pgq_writer = execute
+pgq_admin = execute
+
+# called by ticker, upgrade script
+[4.admin.fns]
+on.functions = %(pgq_node_admin_fns)s
+pgq_admin = execute
+
+# define various groups of functions
+[DEFAULT]
+
+pgq_node_remote_fns =
+ pgq_node.get_queue_locations(text),
+ pgq_node.get_node_info(text),
+ pgq_node.get_subscriber_info(text),
+ pgq_node.register_subscriber(text, text, text, int8),
+ pgq_node.unregister_subscriber(text, text),
+ pgq_node.set_subscriber_watermark(text, text, bigint)
+
+pgq_node_public_fns =
+ pgq_node.is_root_node(text),
+ pgq_node.is_leaf_node(text),
+ pgq_node.version()
+
+pgq_node_admin_fns =
+ pgq_node.upgrade_schema(),
+ pgq_node.maint_watermark(text)
+
+pgq_node_consumer_fns =
+ pgq_node.get_consumer_info(text),
+ pgq_node.get_consumer_state(text, text),
+ pgq_node.register_consumer(text, text, text, int8),
+ pgq_node.unregister_consumer(text, text),
+ pgq_node.change_consumer_provider(text, text, text),
+ pgq_node.set_consumer_uptodate(text, text, boolean),
+ pgq_node.set_consumer_paused(text, text, boolean),
+ pgq_node.set_consumer_completed(text, text, int8),
+ pgq_node.set_consumer_error(text, text, text)
+
+pgq_node_worker_fns =
+ pgq_node.register_location(text, text, text, boolean),
+ pgq_node.unregister_location(text, text),
+ pgq_node.create_node(text, text, text, text, text, bigint, text),
+ pgq_node.drop_node(text, text),
+ pgq_node.demote_root(text, int4, text),
+ pgq_node.promote_branch(text),
+ pgq_node.set_node_attrs(text, text),
+ pgq_node.get_worker_state(text),
+ pgq_node.set_global_watermark(text, bigint),
+ pgq_node.set_partition_watermark(text, text, bigint)
+
diff --git a/sql/pgq_node/structure/grants.sql b/sql/pgq_node/structure/grants.sql
new file mode 100644
index 00000000..1efff292
--- /dev/null
+++ b/sql/pgq_node/structure/grants.sql
@@ -0,0 +1,3 @@
+
+grant usage on schema pgq_node to public;
+
diff --git a/sql/pgq_node/structure/install.sql b/sql/pgq_node/structure/install.sql
index a6d95cad..9a2e23e9 100644
--- a/sql/pgq_node/structure/install.sql
+++ b/sql/pgq_node/structure/install.sql
@@ -1,2 +1,5 @@
+
\i structure/tables.sql
\i structure/functions.sql
+\i structure/grants.sql
+
diff --git a/sql/pgq_node/structure/tables.sql b/sql/pgq_node/structure/tables.sql
index bd8d4e1d..464a4454 100644
--- a/sql/pgq_node/structure/tables.sql
+++ b/sql/pgq_node/structure/tables.sql
@@ -16,7 +16,6 @@
-- ----------------------------------------------------------------------
create schema pgq_node;
-grant usage on schema pgq_node to public;
-- ----------------------------------------------------------------------
-- Table: pgq_node.location