Query permissions for a specific table in redshift (Groups and Users) - amazon-redshift

I am trying to find a query that lets me get the current permissions on a specific table in Redshift, for both groups and users. I know how to do the actual grant, but I am having a heck of a time finding the correct table(s) to query to get existing permissions.
Other information:
This will be used during a programmatic operation where the table is cascade dropped and re-created to ensure we re-apply the same permissions from before the drop. If there is an option to do this in Python without a conn.execute() query I am open to that as well.
Edit:
I did find the below query before and I tried a quick short version to see if I could get groups:
SELECT *
FROM
(
SELECT
schemaname
,objectname
,usename
,groname
,HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'select') AND HAS_TABLE_PRIVILEGE(grp.groname, fullobj, 'select') AND has_schema_privilege(usrs.usename, schemaname, 'usage') AS sel
FROM
(
SELECT schemaname, 't' AS obj_type, tablename AS objectname, schemaname + '.' + tablename AS fullobj FROM pg_tables
UNION
SELECT schemaname, 'v' AS obj_type, viewname AS objectname, schemaname + '.' + viewname AS fullobj FROM pg_views
) AS objs
,(SELECT * FROM pg_user) AS usrs
,(SELECT * FROM pg_group) AS grp
ORDER BY fullobj
)
WHERE sel = true
and objectname = 'table_name'
This gives me an error saying it can't find the username which is a group name.

You can use the following query with objectname filter to find out permissions for a specific table
SELECT *
FROM
(
SELECT
schemaname
,objectname
,usename
,HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'select') AND has_schema_privilege(usrs.usename, schemaname, 'usage') AS sel
,HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'insert') AND has_schema_privilege(usrs.usename, schemaname, 'usage') AS ins
,HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'update') AND has_schema_privilege(usrs.usename, schemaname, 'usage') AS upd
,HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'delete') AND has_schema_privilege(usrs.usename, schemaname, 'usage') AS del
,HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'references') AND has_schema_privilege(usrs.usename, schemaname, 'usage') AS ref
FROM
(
SELECT schemaname, 't' AS obj_type, tablename AS objectname, schemaname + '.' + tablename AS fullobj FROM pg_tables
UNION
SELECT schemaname, 'v' AS obj_type, viewname AS objectname, schemaname + '.' + viewname AS fullobj FROM pg_views
) AS objs
,(SELECT * FROM pg_user) AS usrs
ORDER BY fullobj
)
WHERE (sel = true or ins = true or upd = true or del = true or ref = true)
and objectname = 'my_table'

I finally found something I was able to pick apart for groups and hash together something:
SELECT
namespace, item, type, groname
FROM
(
SELECT
use.usename AS subject,
nsp.nspname AS NAMESPACE,
cls.relname AS item,
cls.relkind AS TYPE,
use2.usename AS OWNER,
cls.relacl
FROM
pg_user use
CROSS JOIN pg_class cls
LEFT JOIN pg_namespace nsp ON cls.relnamespace = nsp.oid
LEFT JOIN pg_user use2 ON cls.relowner = use2.usesysid
WHERE
cls.relowner = use.usesysid
AND nsp.nspname NOT IN ( 'pg_catalog', 'pg_toast', 'information_schema' )
AND nsp.nspname IN ( 'schema' )
AND relacl IS NOT NULL
ORDER BY
subject,
NAMESPACE,
item
)
JOIN pg_group pu ON array_to_string( relacl, '|' ) LIKE'%' || pu.groname || '%'
take apart from How to query user group privileges in postgresql?

Related

redshift listagg on user permission query

I am using listagg to group users having same permissions, based on the query from the below stack question, tweaked it a bit for my needs.
How do I view grants on Redshift
This fails saying listagg is compute node function and should be used on user created table. Any way to use listagg on catalog tables and has_*_privilege function both of which runs on leader node?
with cte1 as (
SELECT *
FROM
( SELECT
fullobj
,usename
,case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'select') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'S' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'insert') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'I' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'update') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'U' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'delete') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'D' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'references') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'R' else '' end AS perm
FROM
(
SELECT schemaname, 't' AS obj_type, tablename AS objectname, schemaname + '.' + tablename AS fullobj FROM pg_tables
WHERE schemaname not in ('pg_internal')
UNION
SELECT schemaname, 'v' AS obj_type, viewname AS objectname, schemaname + '.' + viewname AS fullobj FROM pg_views
WHERE schemaname not in ('pg_internal')
) AS objs
,(SELECT usename::text FROM pg_user) AS usrs
ORDER BY fullobj
) WHERE perm != '')
select fullobj, perm, listagg(usename,',') from cte1 group by fullobj, perm;
SQL Error [500310] [XX000]: [Amazon](500310) Invalid operation: One or more of the used functions must be applied on at least one user created tables. Examples of user table only functions are LISTAGG, MEDIAN, PERCENTILE_CONT, etc;
I tried to store the query output in a table, but its not allowing to store the table since the has_table_privilege function is leader-only function
create table user_perms as
SELECT *
FROM
( SELECT
fullobj
,usename
,case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'select') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'S' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'insert') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'I' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'update') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'U' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'delete') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'D' else '' end ||
case when HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'references') AND has_schema_privilege(usrs.usename, schemaname, 'usage') then 'R' else '' end AS perm
FROM
(
SELECT schemaname, 't' AS obj_type, tablename AS objectname, schemaname + '.' + tablename AS fullobj FROM pg_tables
WHERE schemaname not in ('pg_internal')
UNION
SELECT schemaname, 'v' AS obj_type, viewname AS objectname, schemaname + '.' + viewname AS fullobj FROM pg_views
WHERE schemaname not in ('pg_internal')
) AS objs
,(SELECT usename::text FROM pg_user) AS usrs
ORDER BY fullobj
) WHERE perm != '';
SQL Error [500310] [0A000]: [Amazon](500310) Invalid operation: Specified types or functions (one per INFO message) not supported on Redshift tables.;
Is there any solution to this issue of being able to run listagg on leader node function/tables?
No.
As you have correctly understood, listagg is a function implemented by Redshift, rather than being inherited from Postgres/Paraccel, and it has been implemented only on the worker nodes.
The has function is from Postgres, and is implemented only on the leader-node.
The query planner will not permit a query using a leader-node only function to recruit worker nodes, so you cannot call listagg.
(BTW, if I remember correctly, that 'v' for reltype is also going to pick up materialized views.)
As an aside, you can in fact obtain the information you are looking for directly from the system tables, but this is a long and complex undertaking. I am a Redshift specialist and it took me two months for the first version, although I was working at the time.

"Show Create Table" in Redshift [duplicate]

Team,
I am working on redshift ( 8.0.2 ). I would like to have DDL command in place for any object type ( table / view...) in redshift.
I have below one. but it is not giving the full text.
select s.userid,u.usename,s.starttime, s.type, rtrim(s.text) from svl_statementtext s, pg_user u
where u.usesysid = s.userid
and s.type = 'DDL'
and s.text like '%table11%'
order by s.starttime asc;
userid | usename | starttime | type | text
--------+----------------------------------------------------------------------------------------------------------------------------------+----------------------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
143 | user11 | 2014-04-16 23:42:06.227296 | DDL | CREATE TABLE table11 ( log_time date, user_name text, database_name text, process_id integer, connection_from text, session_id text, session_line_num bigint, command_tag text, session_start_time
143 | user11 | 2014-04-16 23:42:06.234987 | DDL | CREATE TABLE table11 ( log_time date, user_name text, database_name text, process_id integer, connection_from text, session_id text, session_line_num bigint, command_tag text, session_start_time
(2 rows)
in Oracle we have DDL_METADATA.GET_DDL pkg. it will give the full text. I would like to have the same. I tried with STL_DDLTEXT. text is much trimmed.
select xid, starttime, sequence, substring(text,1,40) as text
from stl_ddltext where userid = 100 and text like '%table11%' order by xid desc, sequence;
xid | starttime | sequence | text
--------+----------------------------+----------+------------------------------------------
135475 | 2014-04-16 23:42:06.234987 | 0 | CREATE TABLE table11 ( log_time dat
135475 | 2014-04-16 23:42:06.227296 | 0 | CREATE TABLE table11 ( log_time dat
(2 rows)
I have few more doubts on the first query output. the column lenght of "usename" is too high. how to trim that. If i query pg_user, it is trimmed internally. IN oracle we can have for e.g.
" col <col_name> for a80 "
second doubt: i am getting 2 rows. actually i created only one table. Any reason for 2 rows in the output ?
for e.g. in physical postgre db, if we want to generate any ddl for one function,
we can use below.
in the below, function name is "add"
SELECT pg_catalog.pg_get_functiondef('add'::regproc);
like this, do we have any pkg in Redshift for table/views ?
Thanks
For DDL:
First create the admin view here: https://github.com/awslabs/amazon-redshift-utils/blob/master/src/AdminViews/v_generate_tbl_ddl.sql
Next write a query like this:
select ddl
from admin.v_generate_tbl_ddl
where schemaname = 'some_schema' and tablename='some_table'
order by seq asc
I have not found a single function in Redshift that provides this functionality. You can get the full definition of views by using the pg_get_viewdef function:
SELECT 'create view '|| nc.nspname::information_schema.sql_identifier ||'.'|| c.relname::information_schema.sql_identifier ||' as '||
pg_get_viewdef(c.oid)::information_schema.character_data AS view_definition
FROM pg_namespace nc, pg_class c, pg_user u
WHERE c.relnamespace = nc.oid AND u.usesysid = c.relowner AND c.relkind = 'v'::"char"
AND nc.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema');
For table definitions I've put together a query, but it still needs a little work to fill in some details as noted in the commented lines:
select tm.schemaname||'.'||tm.tablename, 'create table '||tm.schemaname||'.'||tm.tablename
||' ('
||cp.coldef
-- primary key
-- diststyle
-- dist key
||d.distkey
--sort key
|| (select
' sortkey(' ||substr(array_to_string(
array( select ','||cast(column_name as varchar(100)) as str from
(select column_name from information_schema.columns col where col.table_schema= tm.schemaname and col.table_name=tm.tablename) c2
join
(-- gives sort cols
select attrelid as tableid, attname as colname, attsortkeyord as sort_col_order from pg_attribute pa where
pa.attnum > 0 AND NOT pa.attisdropped AND pa.attsortkeyord > 0
) st on tm.tableid=st.tableid and c2.column_name=st.colname order by sort_col_order
)
,'')
,2,10000) || ')'
)
||';'
from
-- t master table list
(
SELECT substring(n.nspname,1,100) as schemaname, substring(c.relname,1,100) as tablename, c.oid as tableid
FROM pg_namespace n, pg_class c
WHERE n.oid = c.relnamespace
AND nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
AND c.relname <> 'temp_staging_tables_1'
-- and c.relname in ('f_recipient_registration','ht_base_document','ht_folder','ht_logical_file','ht_transaction_addresses','ht_ysi_batch','ht_ysi_batch_messages','ht_ysi_files')
) tm
-- cp creates the col params for the create string
join
(select
substr(str,(charindex('QQQ',str)+3),(charindex('ZZZ',str))-(charindex('QQQ',str)+3)) as tableid
,substr(replace(replace(str,'ZZZ',''),'QQQ'||substr(str,(charindex('QQQ',str)+3),(charindex('ZZZ',str))-(charindex('QQQ',str)+3)),''),2,10000) as coldef
from
( select array_to_string(array(
SELECT 'QQQ'||cast(t.tableid as varchar(10))||'ZZZ'|| ','||column_name||' '|| decode(udt_name,'bpchar','char',udt_name) || decode(character_maximum_length,null,'', '('||cast(character_maximum_length as varchar(9))||')' )
-- default
|| decode(substr(column_default,2,8),'identity','',null,'',' default '||column_default||' ')
-- nullable
|| decode(is_nullable,'YES',' NULL ','NO',' NOT NULL ')
-- identity
|| decode(substr(column_default,2,8),'identity',' identity('||substr(column_default,(charindex('''',column_default)+1), (length(column_default)-charindex('''',reverse(column_default))-charindex('''',column_default) ) ) ||') ', '') as str
from
-- ci all the col info
(
select cast(t.tableid as int), cast(table_schema as varchar(100)), cast(table_name as varchar(100)), cast(column_name as varchar(100)),
cast(ordinal_position as int), cast(column_default as varchar(100)), cast(is_nullable as varchar(20)) , cast(udt_name as varchar(50)) ,cast(character_maximum_length as int),
sort_col_order , decode(d.colname,null,0,1) dist_key
from (select * from information_schema.columns c where c.table_schema= t.schemaname and c.table_name=t.tablename) c
left join
(-- gives sort cols
select attrelid as tableid, attname as colname, attsortkeyord as sort_col_order from pg_attribute a where
a.attnum > 0 AND NOT a.attisdropped AND a.attsortkeyord > 0
) s on t.tableid=s.tableid and c.column_name=s.colname
left join
-- gives dist col
(select attrelid as tableid, attname as colname from pg_attribute a where
a.attnum > 0 AND NOT a.attisdropped AND a.attisdistkey = 't'
) d on t.tableid=d.tableid and c.column_name=d.colname
order by ordinal_position
) ci
-- for the working array funct
), '') as str
from
(-- need tableid
SELECT substring(n.nspname,1,100) as schemaname, substring(c.relname,1,100) as tablename, c.oid as tableid
FROM pg_namespace n, pg_class c
WHERE n.oid = c.relnamespace
AND nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
) t
-- for the agg functions that dont exist
-- ) group by table_schema, table_name
)) cp on tm.tableid=cp.tableid
-- add in primary key query here
-- dist key
left join
( select
-- close off the col defs after the primary key
')' ||
' distkey('|| cast(column_name as varchar(100)) ||')' as distkey, t.tableid
from information_schema.columns c
join
(-- need tableid
SELECT substring(n.nspname,1,100) as schemaname, substring(c.relname,1,100) as tablename, c.oid as tableid
FROM pg_namespace n, pg_class c
WHERE n.oid = c.relnamespace
AND nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
) t on c.table_schema= t.schemaname and c.table_name=t.tablename
join
-- gives dist col
(select attrelid as tableid, attname as colname from pg_attribute a where
a.attnum > 0 AND NOT a.attisdropped AND a.attisdistkey = 't'
) d on t.tableid=d.tableid and c.column_name=d.colname
) d on tm.tableid=d.tableid

PostgreSQL - get effective permissions for specified roles on each object type

I am trying to write a query that would (given a list of roles, and list of databases), list effective permissions for object of type database, schema, and table (to start with)
I have been trying to use has_XXX_privilege() functions but the output feels awkward...
Given 3 roles, for example, (app_rwc, app_rw, app_r) and a single db test_db I'd like to get output like this
role, obj_type, obj_name, has_permissions, missing_premissions
app_rwc, DATABASE, test_db, CREATE+CONNECT+TEMPORARY", NULL
app_rw, DATABASE, test_db, CONNECT+TEMPORARY, CREATE
app_r, DATABASE, test_db, CONNECT+TEMPORARY, CREATE
app_rwc, SCHEMA, audit, CREATE+USAGE, NULL
app_rwc, SCHEMA, shared, CREATE+USAGE, NULL
app_rw, SCHEMA, audit, USAGE, CREATE
app_rw, SCHEMA, shared, USAGE, CREATE
app_r, SCHEMA, audit, USAGE, CREATE
app_r, SCHEMA, audit, USAGE, CREATE
app_rwc, TABLE, audit.trail, SELECT+INSERT+UPDATE+DELETE+REFERENCES+TRIGGERS, TRUNCATE
etc
etc
So far this is what I got and it kind of works except it is verbose...
If anyone has a better approach please advise - thanks.
WITH
databases AS (
SELECT * FROM (VALUES ('app_prod')) AS t(database_name)
),
roles AS (
SELECT * FROM (VALUES ('app_rwc'), ('app_rw'), ('app_r')) AS t(role_name)
),
db_permissions AS (
SELECT * FROM (VALUES ('CREATE'), ('CONNECT'), ('TEMPORARY')) AS t(permission_name)
),
schemas AS (
SELECT
schema_name
FROM
information_schema.schemata
WHERE
catalog_name IN (SELECT database_name FROM databases)
AND schema_owner IN (SELECT role_name FROM roles)
),
schema_permissions AS (
SELECT * FROM (VALUES ('CREATE'), ('USAGE')) AS t(permission_name)
),
tables AS (
SELECT table_schema, table_name
FROM information_schema.tables
WHERE
table_catalog IN (SELECT database_name FROM databases)
AND table_schema IN (SELECT schema_name FROM schemas)
AND table_type IN ('BASE TABLE') -- , 'VIEW'
),
table_permissions AS (
SELECT * FROM (VALUES ('SELECT'), ('INSERT'), ('UPDATE'), ('DELETE'), ('TRUNCATE'), ('REFERENCES'), ('TRIGGER')) AS t(permission_name)
)
-- ----------------------------------------------------------------------------
SELECT
'DATABASE' AS obj_type
, databases.database_name AS obj_name
, roles.role_name
, db_permissions.permission_name
, has_database_privilege(roles.role_name, databases.database_name, db_permissions.permission_name) AS has_permission
FROM
databases
CROSS JOIN roles
CROSS JOIN db_permissions
-- ----------------------------------------------------------------------------
UNION ALL
-- ----------------------------------------------------------------------------
SELECT
'SCHEMA' AS obj_type
, schemas.schema_name AS obj_name
, roles.role_name
, schema_permissions.permission_name
, has_schema_privilege(roles.role_name, schemas.schema_name, schema_permissions.permission_name) AS has_permission
FROM
schemas
CROSS JOIN roles
CROSS JOIN schema_permissions
-- ----------------------------------------------------------------------------
UNION ALL
-- ----------------------------------------------------------------------------
SELECT
'TABLE' AS obj_type
, tables.table_schema || '.' || tables.table_name AS obj_name
, roles.role_name
, table_permissions.permission_name
, has_table_privilege(roles.role_name, (tables.table_schema || '.' || tables.table_name),table_permissions.permission_name) AS has_permission
FROM
tables
CROSS JOIN roles
CROSS JOIN table_permissions
UPDATE #1 - Here is expanded query (does types, sequences, and functions) with aggregation (Thanks to #filiprem for the tip!) Still rather large, but it does what I want it to do.
WITH
databases AS (
SELECT unnest('{app_prod}'::text[]) AS dbname
),
roles AS (
SELECT unnest('{app_rwc,app_rw,app_r}'::text[]) AS rname
),
permissions AS (
SELECT 'DATABASE' AS ptype, unnest('{CREATE,CONNECT,TEMPORARY}'::text[]) AS pname
UNION ALL
SELECT 'SCHEMA' AS ptype, unnest('{CREATE,USAGE}'::text[]) AS pname
UNION ALL
SELECT 'TABLE' AS ptype, unnest('{SELECT,INSERT,UPDATE,DELETE,TRUNCATE,REFERENCES,TRIGGER}'::text[]) AS pname
UNION ALL
SELECT 'SEQUENCE' AS ptype, unnest('{USAGE,SELECT,UPDATE}'::text[]) AS pname
UNION ALL
SELECT 'TYPE' AS ptype, unnest('{USAGE}'::text[]) AS pname
UNION ALL
SELECT 'FUNCTION' AS ptype, unnest('{EXECUTE}'::text[]) AS pname
),
schemas AS (
SELECT schema_name AS sname
FROM information_schema.schemata
WHERE catalog_name IN (SELECT dbname FROM databases) -- show schemas that exist in specified DB
AND schema_owner IN (SELECT rname FROM roles) -- show schemas that are owned by specified roles
OR schema_name IN ('public') -- always include these
--OR schema_name IN ('public', 'information_schema', 'pg_catalog')
),
tables AS (
SELECT table_schema AS tschema, table_name AS tname
FROM information_schema.tables
WHERE table_catalog IN (SELECT dbname FROM databases)
AND table_schema IN (SELECT sname FROM schemas)
AND table_type IN ('BASE TABLE') -- , 'VIEW'
),
sequences AS (
SELECT schemaname AS seqschema, sequencename AS seqname
FROM pg_sequences
WHERE schemaname IN (SELECT sname FROM schemas)
),
types AS (
SELECT nspname AS typeschema, typname AS typename, CASE typtype WHEN 'c' THEN 'composite' WHEN 'd' THEN 'domain' WHEN 'e' THEN 'enum' WHEN 'r' THEN 'range' ELSE 'other' END AS typekind
FROM pg_type INNER JOIN pg_namespace ON pg_type.typnamespace = pg_namespace.oid
WHERE nspname IN (SELECT sname FROM schemas)
AND typtype NOT IN ('b','p') -- exclude base and pseudo types
AND typname NOT IN (SELECT seqname FROM sequences) -- exclude sequences
),
functions AS (
SELECT nspname AS fnschema, proname AS fnname, pg_proc.oid AS fnoid, pg_get_function_arguments(pg_proc.oid) AS fnargs
FROM pg_proc INNER JOIN pg_namespace ON pg_proc.pronamespace = pg_namespace.oid
WHERE nspname IN (SELECT sname FROM schemas)
),
final AS (
SELECT
permissions.ptype
, databases.dbname AS obj_name
, roles.rname
, permissions.pname
, has_database_privilege(roles.rname, databases.dbname, permissions.pname) AS has_permission
FROM
databases
CROSS JOIN roles
CROSS JOIN permissions
WHERE
permissions.ptype = 'DATABASE'
UNION ALL -- ----------------------------------------------------------------------------------------------------------
SELECT
permissions.ptype
, schemas.sname AS obj_name
, roles.rname
, permissions.pname
, has_schema_privilege(roles.rname, schemas.sname, permissions.pname) AS has_permission
FROM
schemas
CROSS JOIN roles
CROSS JOIN permissions
WHERE
permissions.ptype = 'SCHEMA'
UNION ALL -- ----------------------------------------------------------------------------------------------------------
SELECT
permissions.ptype
, tables.tschema || '.' || tables.tname AS obj_name
, roles.rname
, permissions.pname
, has_table_privilege(roles.rname, (tables.tschema || '.' || tables.tname), permissions.pname) AS has_permission
FROM
tables
CROSS JOIN roles
CROSS JOIN permissions
WHERE
permissions.ptype = 'TABLE'
UNION ALL -- ----------------------------------------------------------------------------------------------------------
SELECT
permissions.ptype
, sequences.seqschema || '.' || sequences.seqname AS obj_name
, roles.rname
, permissions.pname
, has_sequence_privilege(roles.rname, (sequences.seqschema || '.' || sequences.seqname), permissions.pname) AS has_permission
FROM
sequences
CROSS JOIN roles
CROSS JOIN permissions
WHERE
permissions.ptype = 'SEQUENCE'
UNION ALL -- ----------------------------------------------------------------------------------------------------------
SELECT
permissions.ptype || ' - ' || types.typekind
, types.typeschema || '.' || types.typename AS obj_name
, roles.rname
, permissions.pname
, has_type_privilege(roles.rname, (types.typeschema || '.' || types.typename), permissions.pname) AS has_permission
FROM
types
CROSS JOIN roles
CROSS JOIN permissions
WHERE
permissions.ptype = 'TYPE'
UNION ALL -- ----------------------------------------------------------------------------------------------------------
SELECT
permissions.ptype
, functions.fnschema || '.' || functions.fnname || '(' || fnargs || ')' AS obj_name
, roles.rname
, permissions.pname
, has_function_privilege(roles.rname, functions.fnoid, permissions.pname) AS has_permission
FROM
functions
CROSS JOIN roles
CROSS JOIN permissions
WHERE
permissions.ptype = 'FUNCTION'
)
-- ====================================================================================================================
SELECT
rname AS role_name
, ptype AS object_type
, obj_name AS object_name
, string_agg(DISTINCT CASE WHEN has_permission THEN pname END, ',') AS granted_permissions
, string_agg(DISTINCT CASE WHEN NOT has_permission THEN pname END, ',') AS missing_premissions
FROM
final
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3
Your query is good, you just need to add some aggregation. This is a start:
select obj_type, obj_name, role_name,
array_agg(distinct case when has_permission then permission_name end),
array_agg(distinct case when not has_permission then permission_name end)
from ( /* your query */ ) AS q1
group by 1,2,3
order by 1,2,3

How to get full length of DDL for a table or any object in redshift / postgresql

Team,
I am working on redshift ( 8.0.2 ). I would like to have DDL command in place for any object type ( table / view...) in redshift.
I have below one. but it is not giving the full text.
select s.userid,u.usename,s.starttime, s.type, rtrim(s.text) from svl_statementtext s, pg_user u
where u.usesysid = s.userid
and s.type = 'DDL'
and s.text like '%table11%'
order by s.starttime asc;
userid | usename | starttime | type | text
--------+----------------------------------------------------------------------------------------------------------------------------------+----------------------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
143 | user11 | 2014-04-16 23:42:06.227296 | DDL | CREATE TABLE table11 ( log_time date, user_name text, database_name text, process_id integer, connection_from text, session_id text, session_line_num bigint, command_tag text, session_start_time
143 | user11 | 2014-04-16 23:42:06.234987 | DDL | CREATE TABLE table11 ( log_time date, user_name text, database_name text, process_id integer, connection_from text, session_id text, session_line_num bigint, command_tag text, session_start_time
(2 rows)
in Oracle we have DDL_METADATA.GET_DDL pkg. it will give the full text. I would like to have the same. I tried with STL_DDLTEXT. text is much trimmed.
select xid, starttime, sequence, substring(text,1,40) as text
from stl_ddltext where userid = 100 and text like '%table11%' order by xid desc, sequence;
xid | starttime | sequence | text
--------+----------------------------+----------+------------------------------------------
135475 | 2014-04-16 23:42:06.234987 | 0 | CREATE TABLE table11 ( log_time dat
135475 | 2014-04-16 23:42:06.227296 | 0 | CREATE TABLE table11 ( log_time dat
(2 rows)
I have few more doubts on the first query output. the column lenght of "usename" is too high. how to trim that. If i query pg_user, it is trimmed internally. IN oracle we can have for e.g.
" col <col_name> for a80 "
second doubt: i am getting 2 rows. actually i created only one table. Any reason for 2 rows in the output ?
for e.g. in physical postgre db, if we want to generate any ddl for one function,
we can use below.
in the below, function name is "add"
SELECT pg_catalog.pg_get_functiondef('add'::regproc);
like this, do we have any pkg in Redshift for table/views ?
Thanks
For DDL:
First create the admin view here: https://github.com/awslabs/amazon-redshift-utils/blob/master/src/AdminViews/v_generate_tbl_ddl.sql
Next write a query like this:
select ddl
from admin.v_generate_tbl_ddl
where schemaname = 'some_schema' and tablename='some_table'
order by seq asc
I have not found a single function in Redshift that provides this functionality. You can get the full definition of views by using the pg_get_viewdef function:
SELECT 'create view '|| nc.nspname::information_schema.sql_identifier ||'.'|| c.relname::information_schema.sql_identifier ||' as '||
pg_get_viewdef(c.oid)::information_schema.character_data AS view_definition
FROM pg_namespace nc, pg_class c, pg_user u
WHERE c.relnamespace = nc.oid AND u.usesysid = c.relowner AND c.relkind = 'v'::"char"
AND nc.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema');
For table definitions I've put together a query, but it still needs a little work to fill in some details as noted in the commented lines:
select tm.schemaname||'.'||tm.tablename, 'create table '||tm.schemaname||'.'||tm.tablename
||' ('
||cp.coldef
-- primary key
-- diststyle
-- dist key
||d.distkey
--sort key
|| (select
' sortkey(' ||substr(array_to_string(
array( select ','||cast(column_name as varchar(100)) as str from
(select column_name from information_schema.columns col where col.table_schema= tm.schemaname and col.table_name=tm.tablename) c2
join
(-- gives sort cols
select attrelid as tableid, attname as colname, attsortkeyord as sort_col_order from pg_attribute pa where
pa.attnum > 0 AND NOT pa.attisdropped AND pa.attsortkeyord > 0
) st on tm.tableid=st.tableid and c2.column_name=st.colname order by sort_col_order
)
,'')
,2,10000) || ')'
)
||';'
from
-- t master table list
(
SELECT substring(n.nspname,1,100) as schemaname, substring(c.relname,1,100) as tablename, c.oid as tableid
FROM pg_namespace n, pg_class c
WHERE n.oid = c.relnamespace
AND nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
AND c.relname <> 'temp_staging_tables_1'
-- and c.relname in ('f_recipient_registration','ht_base_document','ht_folder','ht_logical_file','ht_transaction_addresses','ht_ysi_batch','ht_ysi_batch_messages','ht_ysi_files')
) tm
-- cp creates the col params for the create string
join
(select
substr(str,(charindex('QQQ',str)+3),(charindex('ZZZ',str))-(charindex('QQQ',str)+3)) as tableid
,substr(replace(replace(str,'ZZZ',''),'QQQ'||substr(str,(charindex('QQQ',str)+3),(charindex('ZZZ',str))-(charindex('QQQ',str)+3)),''),2,10000) as coldef
from
( select array_to_string(array(
SELECT 'QQQ'||cast(t.tableid as varchar(10))||'ZZZ'|| ','||column_name||' '|| decode(udt_name,'bpchar','char',udt_name) || decode(character_maximum_length,null,'', '('||cast(character_maximum_length as varchar(9))||')' )
-- default
|| decode(substr(column_default,2,8),'identity','',null,'',' default '||column_default||' ')
-- nullable
|| decode(is_nullable,'YES',' NULL ','NO',' NOT NULL ')
-- identity
|| decode(substr(column_default,2,8),'identity',' identity('||substr(column_default,(charindex('''',column_default)+1), (length(column_default)-charindex('''',reverse(column_default))-charindex('''',column_default) ) ) ||') ', '') as str
from
-- ci all the col info
(
select cast(t.tableid as int), cast(table_schema as varchar(100)), cast(table_name as varchar(100)), cast(column_name as varchar(100)),
cast(ordinal_position as int), cast(column_default as varchar(100)), cast(is_nullable as varchar(20)) , cast(udt_name as varchar(50)) ,cast(character_maximum_length as int),
sort_col_order , decode(d.colname,null,0,1) dist_key
from (select * from information_schema.columns c where c.table_schema= t.schemaname and c.table_name=t.tablename) c
left join
(-- gives sort cols
select attrelid as tableid, attname as colname, attsortkeyord as sort_col_order from pg_attribute a where
a.attnum > 0 AND NOT a.attisdropped AND a.attsortkeyord > 0
) s on t.tableid=s.tableid and c.column_name=s.colname
left join
-- gives dist col
(select attrelid as tableid, attname as colname from pg_attribute a where
a.attnum > 0 AND NOT a.attisdropped AND a.attisdistkey = 't'
) d on t.tableid=d.tableid and c.column_name=d.colname
order by ordinal_position
) ci
-- for the working array funct
), '') as str
from
(-- need tableid
SELECT substring(n.nspname,1,100) as schemaname, substring(c.relname,1,100) as tablename, c.oid as tableid
FROM pg_namespace n, pg_class c
WHERE n.oid = c.relnamespace
AND nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
) t
-- for the agg functions that dont exist
-- ) group by table_schema, table_name
)) cp on tm.tableid=cp.tableid
-- add in primary key query here
-- dist key
left join
( select
-- close off the col defs after the primary key
')' ||
' distkey('|| cast(column_name as varchar(100)) ||')' as distkey, t.tableid
from information_schema.columns c
join
(-- need tableid
SELECT substring(n.nspname,1,100) as schemaname, substring(c.relname,1,100) as tablename, c.oid as tableid
FROM pg_namespace n, pg_class c
WHERE n.oid = c.relnamespace
AND nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
) t on c.table_schema= t.schemaname and c.table_name=t.tablename
join
-- gives dist col
(select attrelid as tableid, attname as colname from pg_attribute a where
a.attnum > 0 AND NOT a.attisdropped AND a.attisdistkey = 't'
) d on t.tableid=d.tableid and c.column_name=d.colname
) d on tm.tableid=d.tableid

How to find the privileges granted to a user in AWS Redshift?

I am using a Redshift cluster.
Version:
PostgreSQL 8.0.2 on i686-pc-linux-gnu, compiled by GCC gcc (GCC) 3.4.2 20041017 (Red Hat 3.4.2-6.fc3), Redshift 1.0.735
I just need to drop a user but it gives the following error message:
redshiftpocdb=# drop user test_55;
ERROR: user "test_55" cannot be dropped because the user has a privilege on some object
Here is the output of the \dp command:
redshiftpocdb=# \dp
Access privileges
schema | name | type | access privileges
--------+---------+-------+-------------------
public | company | table |
public | test2 | table |
public | test22 | table |
public | test222 | table |
public | v_date | table |
(5 rows)
In a Postgresql environment, we have the command DROP OWNED BY but it does not work in Redshift.
How can I find out what privileges were granted to the TEST_55 user? Is there any system view we can query ( for e..g in Oracle, we have DBA_ROLE_PRIVS, DBA_TAB_PRIVS...DBA_SYS_PRIVS .etc )?
To be able to drop a user, you have to (at least)
if they own any objects, change the owner to a different user
remove grants from any objects
remove them from groups
remove grants from schemas
You can use this to find any tables they own (then run "alter table owner to "):
select * from pg_tables where tableowner = 'test_55'
You can use this to build the script to revoke any grants:
select relacl ,
'revoke ' || substring(
case when charindex('r',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',select ' else '' end
||case when charindex('w',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',update ' else '' end
||case when charindex('a',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',insert ' else '' end
||case when charindex('d',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',delete ' else '' end
||case when charindex('R',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',rule ' else '' end
||case when charindex('x',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',references ' else '' end
||case when charindex('t',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',trigger ' else '' end
||case when charindex('X',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',execute ' else '' end
||case when charindex('U',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',usage ' else '' end
||case when charindex('C',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',create ' else '' end
||case when charindex('T',split_part(split_part(array_to_string(relacl, '|'),pu.usename,2 ) ,'/',1)) > 0 then ',temporary ' else '' end
, 2,10000)
|| ' on '||namespace||'.'||item ||' from "'||pu.usename||'";' as grantsql
from
(SELECT
use.usename as subject,
nsp.nspname as namespace,
c.relname as item,
c.relkind as type,
use2.usename as owner,
c.relacl
FROM
pg_user use
cross join pg_class c
left join pg_namespace nsp on (c.relnamespace = nsp.oid)
left join pg_user use2 on (c.relowner = use2.usesysid)
WHERE
c.relowner = use.usesysid
and nsp.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
ORDER BY subject, namespace, item
) join pg_user pu on array_to_string(relacl, '|') like '%'||pu.usename||'%'
where relacl is not null
and pu.usename='test_55'
You can use a variation of this query to see if a user belongs to any groups (then use "alter group drop user "):
select usesysid, usename, nvl(groname,'default') from pg_user u
left join pg_group g on ','||array_to_string(grolist,',')||','
like '%,'||cast(usesysid as varchar(10))||',%'
where usename='test_55' order by 2,1;
You can use this query to see if they have any schema grants:
select * from pg_namespace where nspowner > 1 and array_to_string(nspacl,',') like '%test_55%';
Another variation, to get all users' privilege organized together:
WITH
usrs as (SELECT * FROM pg_user),
objs as (
SELECT
schemaname, 't' AS obj_type,
tablename AS objectname,
schemaname + '.' + tablename AS fullobj
FROM pg_tables
WHERE schemaname not in ('pg_internal')
UNION
SELECT
schemaname, 'v' AS obj_type,
viewname AS objectname,
schemaname + '.' + viewname AS fullobj
FROM pg_views
WHERE schemaname NOT IN ('pg_internal')
),
query as (
SELECT
schemaname,
objectname,
usename,
HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'select') AS sel,
HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'insert') AS ins,
HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'update') AS upd,
HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'delete') AS del,
HAS_TABLE_PRIVILEGE(usrs.usename, fullobj, 'references') AS ref
FROM objs, usrs
ORDER BY fullobj
)
SELECT * FROM query
WHERE (
sel = TRUE
OR ins = TRUE
OR upd = TRUE
OR del = TRUE
OR ref = TRUE
) AND schemaname='[optional schemaname]'
AND usename = '[optional username]';
I had to use || to concatenate strings, and a little difference as I have case-sensitive object names
'"' || schemaname || '"."' || tablename || '"' AS fullobj
'"' || schemaname || '"."' || viewname || '"' AS fullobj
instead of
schemaname + '.' + tablename AS fullobj
schemaname + '.' + viewname AS fullobj