diff options
Diffstat (limited to 'src/bin')
| -rw-r--r-- | src/bin/pg_dump/common.c | 4 | ||||
| -rw-r--r-- | src/bin/pg_dump/pg_backup.h | 1 | ||||
| -rw-r--r-- | src/bin/pg_dump/pg_backup_archiver.c | 9 | ||||
| -rw-r--r-- | src/bin/pg_dump/pg_dump.c | 308 | ||||
| -rw-r--r-- | src/bin/pg_dump/pg_dump.h | 22 | ||||
| -rw-r--r-- | src/bin/pg_dump/pg_dump_sort.c | 11 | ||||
| -rw-r--r-- | src/bin/pg_dump/pg_dumpall.c | 23 | ||||
| -rw-r--r-- | src/bin/pg_dump/pg_restore.c | 4 | ||||
| -rw-r--r-- | src/bin/psql/describe.c | 150 | ||||
| -rw-r--r-- | src/bin/psql/tab-complete.c | 158 |
10 files changed, 664 insertions, 26 deletions
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 94e9147b13..2f855cf706 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -244,6 +244,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) write_msg(NULL, "reading rewrite rules\n"); getRules(fout, &numRules); + if (g_verbose) + write_msg(NULL, "reading row-security policies\n"); + getRowSecurity(fout, tblinfo, numTables); + *numTablesPtr = numTables; return tblinfo; } diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 25780cfc1a..921bc1ba36 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -150,6 +150,7 @@ typedef struct _restoreOptions bool single_txn; bool *idWanted; /* array showing which dump IDs to emit */ + int enable_row_security; } RestoreOptions; typedef void (*SetupWorkerPtr) (Archive *AH, RestoreOptions *ropt); diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index ded9135c36..5476a1e7e2 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -374,6 +374,14 @@ RestoreArchive(Archive *AHX) } /* + * Enable row-security if necessary. + */ + if (!ropt->enable_row_security) + ahprintf(AH, "SET row_security = off;\n"); + else + ahprintf(AH, "SET row_security = on;\n"); + + /* * Establish important parameter values right away. */ _doSetFixedOutputState(AH); @@ -3242,6 +3250,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat strcmp(te->desc, "INDEX") == 0 || strcmp(te->desc, "RULE") == 0 || strcmp(te->desc, "TRIGGER") == 0 || + strcmp(te->desc, "ROW SECURITY") == 0 || strcmp(te->desc, "USER MAPPING") == 0) { /* these object types don't have separate owners */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index c084ee9d9e..29153294e2 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -137,6 +137,7 @@ static int no_security_labels = 0; static int no_synchronized_snapshots = 0; static int no_unlogged_table_data = 0; static int serializable_deferrable = 0; +static int enable_row_security = 0; static void help(const char *progname); @@ -247,6 +248,7 @@ static char *myFormatType(const char *typname, int32 typmod); static void getBlobs(Archive *fout); static void dumpBlob(Archive *fout, BlobInfo *binfo); static int dumpBlobs(Archive *fout, void *arg); +static void dumpRowSecurity(Archive *fout, RowSecurityInfo *rsinfo); static void dumpDatabase(Archive *AH); static void dumpEncoding(Archive *AH); static void dumpStdStrings(Archive *AH); @@ -345,6 +347,7 @@ main(int argc, char **argv) {"column-inserts", no_argument, &column_inserts, 1}, {"disable-dollar-quoting", no_argument, &disable_dollar_quoting, 1}, {"disable-triggers", no_argument, &disable_triggers, 1}, + {"enable-row-security", no_argument, &enable_row_security, 1}, {"exclude-table-data", required_argument, NULL, 4}, {"if-exists", no_argument, &if_exists, 1}, {"inserts", no_argument, &dump_inserts, 1}, @@ -825,6 +828,7 @@ main(int argc, char **argv) ropt->noTablespace = outputNoTablespaces; ropt->disable_triggers = disable_triggers; ropt->use_setsessauth = use_setsessauth; + ropt->enable_row_security = enable_row_security; if (compressLevel == -1) ropt->compression = 0; @@ -897,6 +901,7 @@ help(const char *progname) printf(_(" --column-inserts dump data as INSERT commands with column names\n")); printf(_(" --disable-dollar-quoting disable dollar quoting, use SQL standard quoting\n")); printf(_(" --disable-triggers disable triggers during data-only restore\n")); + printf(_(" --enable-row-security enable row level security\n")); printf(_(" --exclude-table-data=TABLE do NOT dump data for the named table(s)\n")); printf(_(" --if-exists use IF EXISTS when dropping objects\n")); printf(_(" --inserts dump data as INSERT commands, rather than COPY\n")); @@ -1050,6 +1055,14 @@ setup_connection(Archive *AH, const char *dumpencoding, char *use_role) else AH->sync_snapshot_id = get_synchronized_snapshot(AH); } + + if (AH->remoteVersion >= 90500) + { + if (enable_row_security) + ExecuteSqlStatement(AH, "SET row_security TO ON"); + else + ExecuteSqlStatement(AH, "SET row_security TO OFF"); + } } static void @@ -2757,6 +2770,240 @@ dumpBlobs(Archive *fout, void *arg) return 1; } +/* + * getRowSecurity + * get information about every row-security policy on a dumpable table. + */ +void +getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables) +{ + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + RowSecurityInfo *rsinfo; + int i_oid; + int i_tableoid; + int i_rsecpolname; + int i_rseccmd; + int i_rsecroles; + int i_rsecqual; + int i_rsecwithcheck; + int i, j, ntups; + + if (fout->remoteVersion < 90500) + return; + + for (i = 0; i < numTables; i++) + { + TableInfo *tbinfo = &tblinfo[i]; + + /* Ignore row-security on tables not to be dumped */ + if (!tbinfo->dobj.dump) + continue; + + if (g_verbose) + write_msg(NULL, "reading row-security enabled for table \"%s\"", + tbinfo->dobj.name); + + /* + * Get row-security enabled information for the table. + * We represent RLS enabled on a table by creating RowSecurityInfo + * object with an empty policy. + */ + if (tbinfo->hasrowsec) + { + /* + * Note: use tableoid 0 so that this object won't be mistaken for + * something that pg_depend entries apply to. + */ + rsinfo = pg_malloc(sizeof(RowSecurityInfo)); + rsinfo->dobj.objType = DO_ROW_SECURITY; + rsinfo->dobj.catId.tableoid = 0; + rsinfo->dobj.catId.oid = tbinfo->dobj.catId.oid; + AssignDumpId(&rsinfo->dobj); + rsinfo->dobj.namespace = tbinfo->dobj.namespace; + rsinfo->dobj.name = pg_strdup(tbinfo->dobj.name); + rsinfo->rstable = tbinfo; + rsinfo->rsecpolname = NULL; + rsinfo->rseccmd = NULL; + rsinfo->rsecroles = NULL; + rsinfo->rsecqual = NULL; + rsinfo->rsecwithcheck = NULL; + } + + if (g_verbose) + write_msg(NULL, "reading row-security policies for table \"%s\"\n", + tbinfo->dobj.name); + + /* + * select table schema to ensure regproc name is qualified if needed + */ + selectSourceSchema(fout, tbinfo->dobj.namespace->dobj.name); + + resetPQExpBuffer(query); + + /* Get the policies for the table. */ + appendPQExpBuffer(query, + "SELECT oid, tableoid, s.rsecpolname, s.rseccmd, " + "CASE WHEN s.rsecroles = '{0}' THEN 'PUBLIC' ELSE " + " array_to_string(ARRAY(SELECT rolname from pg_roles WHERE oid = ANY(s.rsecroles)), ', ') END AS rsecroles, " + "pg_get_expr(s.rsecqual, s.rsecrelid) AS rsecqual, " + "pg_get_expr(s.rsecwithcheck, s.rsecrelid) AS rsecwithcheck " + "FROM pg_catalog.pg_rowsecurity s " + "WHERE rsecrelid = '%u'", + tbinfo->dobj.catId.oid); + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + if (ntups == 0) + { + /* + * No explicit policies to handle (only the default-deny policy, + * which is handled as part of the table definition. Clean up and + * return. + */ + PQclear(res); + continue; + } + + i_oid = PQfnumber(res, "oid"); + i_tableoid = PQfnumber(res, "tableoid"); + i_rsecpolname = PQfnumber(res, "rsecpolname"); + i_rseccmd = PQfnumber(res, "rseccmd"); + i_rsecroles = PQfnumber(res, "rsecroles"); + i_rsecqual = PQfnumber(res, "rsecqual"); + i_rsecwithcheck = PQfnumber(res, "rsecwithcheck"); + + rsinfo = pg_malloc(ntups * sizeof(RowSecurityInfo)); + + for (j = 0; j < ntups; j++) + { + rsinfo[j].dobj.objType = DO_ROW_SECURITY; + rsinfo[j].dobj.catId.tableoid = + atooid(PQgetvalue(res, j, i_tableoid)); + rsinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid)); + AssignDumpId(&rsinfo[j].dobj); + rsinfo[j].dobj.namespace = tbinfo->dobj.namespace; + rsinfo[j].rstable = tbinfo; + rsinfo[j].rsecpolname = pg_strdup(PQgetvalue(res, j, + i_rsecpolname)); + + rsinfo[j].dobj.name = pg_strdup(rsinfo[j].rsecpolname); + + if (PQgetisnull(res, j, i_rseccmd)) + rsinfo[j].rseccmd = NULL; + else + rsinfo[j].rseccmd = pg_strdup(PQgetvalue(res, j, i_rseccmd)); + + rsinfo[j].rsecroles = pg_strdup(PQgetvalue(res, j, i_rsecroles)); + + if (PQgetisnull(res, j, i_rsecqual)) + rsinfo[j].rsecqual = NULL; + else + rsinfo[j].rsecqual = pg_strdup(PQgetvalue(res, j, i_rsecqual)); + + if (PQgetisnull(res, j, i_rsecwithcheck)) + rsinfo[j].rsecwithcheck = NULL; + else + rsinfo[j].rsecwithcheck + = pg_strdup(PQgetvalue(res, j, i_rsecwithcheck)); + } + PQclear(res); + } + destroyPQExpBuffer(query); +} + +/* + * dumpRowSecurity + * dump the definition of the given row-security policy + */ +static void +dumpRowSecurity(Archive *fout, RowSecurityInfo *rsinfo) +{ + TableInfo *tbinfo = rsinfo->rstable; + PQExpBuffer query; + PQExpBuffer delqry; + const char *cmd; + + if (dataOnly) + return; + + /* + * If rsecpolname is NULL, then this record is just indicating that ROW + * LEVEL SECURITY is enabled for the table. + * Dump as ALTER TABLE <table> ENABLE ROW LEVEL SECURITY. + */ + if (rsinfo->rsecpolname == NULL) + { + query = createPQExpBuffer(); + + appendPQExpBuffer(query, "ALTER TABLE %s ENABLE ROW LEVEL SECURITY;", + fmtId(rsinfo->dobj.name)); + + ArchiveEntry(fout, rsinfo->dobj.catId, rsinfo->dobj.dumpId, + rsinfo->dobj.name, + rsinfo->dobj.namespace->dobj.name, + NULL, + tbinfo->rolname, false, + "ROW SECURITY", SECTION_NONE, + query->data, "", NULL, + NULL, 0, + NULL, NULL); + + destroyPQExpBuffer(query); + return; + } + + if (!rsinfo->rseccmd) + cmd = "ALL"; + else if (strcmp(rsinfo->rseccmd, "r") == 0) + cmd = "SELECT"; + else if (strcmp(rsinfo->rseccmd, "a") == 0) + cmd = "INSERT"; + else if (strcmp(rsinfo->rseccmd, "u") == 0) + cmd = "UPDATE"; + else if (strcmp(rsinfo->rseccmd, "d") == 0) + cmd = "DELETE"; + else + { + write_msg(NULL, "unexpected command type: '%s'\n", rsinfo->rseccmd); + exit_nicely(1); + } + + query = createPQExpBuffer(); + delqry = createPQExpBuffer(); + + appendPQExpBuffer(query, "CREATE POLICY %s ON %s FOR %s", + rsinfo->rsecpolname, fmtId(tbinfo->dobj.name), cmd); + + if (rsinfo->rsecroles != NULL) + appendPQExpBuffer(query, " TO %s", rsinfo->rsecroles); + + if (rsinfo->rsecqual != NULL) + appendPQExpBuffer(query, " USING %s", rsinfo->rsecqual); + + if (rsinfo->rsecwithcheck != NULL) + appendPQExpBuffer(query, " WITH CHECK %s", rsinfo->rsecwithcheck); + + appendPQExpBuffer(query, ";\n"); + + appendPQExpBuffer(delqry, "DROP POLICY %s ON %s;\n", + rsinfo->rsecpolname, fmtId(tbinfo->dobj.name)); + + ArchiveEntry(fout, rsinfo->dobj.catId, rsinfo->dobj.dumpId, + rsinfo->dobj.name, + rsinfo->dobj.namespace->dobj.name, + NULL, + tbinfo->rolname, false, + "ROW SECURITY", SECTION_POST_DATA, + query->data, delqry->data, NULL, + NULL, 0, + NULL, NULL); + + destroyPQExpBuffer(query); + destroyPQExpBuffer(delqry); +} + static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout, PQExpBuffer upgrade_buffer, @@ -4287,6 +4534,7 @@ getTables(Archive *fout, int *numTables) int i_relhastriggers; int i_relhasindex; int i_relhasrules; + int i_relhasrowsec; int i_relhasoids; int i_relfrozenxid; int i_relminmxid; @@ -4328,7 +4576,48 @@ getTables(Archive *fout, int *numTables) * we cannot correctly identify inherited columns, owned sequences, etc. */ - if (fout->remoteVersion >= 90400) + if (fout->remoteVersion >= 90500) + { + /* + * Left join to pick up dependency info linking sequences to their + * owning column, if any (note this dependency is AUTO as of 8.2) + */ + appendPQExpBuffer(query, + "SELECT c.tableoid, c.oid, c.relname, " + "c.relacl, c.relkind, c.relnamespace, " + "(%s c.relowner) AS rolname, " + "c.relchecks, c.relhastriggers, " + "c.relhasindex, c.relhasrules, c.relhasoids, " + "c.relhasrowsecurity, " + "c.relfrozenxid, c.relminmxid, tc.oid AS toid, " + "tc.relfrozenxid AS tfrozenxid, " + "tc.relminmxid AS tminmxid, " + "c.relpersistence, c.relispopulated, " + "c.relreplident, c.relpages, " + "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " + "d.refobjid AS owning_tab, " + "d.refobjsubid AS owning_col, " + "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " + "array_to_string(array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded'), ', ') AS reloptions, " + "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text " + "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, " + "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " + "FROM pg_class c " + "LEFT JOIN pg_depend d ON " + "(c.relkind = '%c' AND " + "d.classid = c.tableoid AND d.objid = c.oid AND " + "d.objsubid = 0 AND " + "d.refclassid = c.tableoid AND d.deptype = 'a') " + "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) " + "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') " + "ORDER BY c.oid", + username_subquery, + RELKIND_SEQUENCE, + RELKIND_RELATION, RELKIND_SEQUENCE, + RELKIND_VIEW, RELKIND_COMPOSITE_TYPE, + RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE); + } + else if (fout->remoteVersion >= 90400) { /* * Left join to pick up dependency info linking sequences to their @@ -4340,6 +4629,7 @@ getTables(Archive *fout, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " + "'f'::bool AS relhasrowsecurity, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "tc.relminmxid AS tminmxid, " @@ -4380,6 +4670,7 @@ getTables(Archive *fout, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " + "'f'::bool AS relhasrowsecurity, " "c.relfrozenxid, c.relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "tc.relminmxid AS tminmxid, " @@ -4420,6 +4711,7 @@ getTables(Archive *fout, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " + "'f'::bool AS relhasrowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4458,6 +4750,7 @@ getTables(Archive *fout, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " + "'f'::bool AS relhasrowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4495,6 +4788,7 @@ getTables(Archive *fout, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, c.relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " + "'f'::bool AS relhasrowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4532,6 +4826,7 @@ getTables(Archive *fout, int *numTables) "(%s c.relowner) AS rolname, " "c.relchecks, (c.reltriggers <> 0) AS relhastriggers, " "c.relhasindex, c.relhasrules, c.relhasoids, " + "'f'::bool AS relhasrowsecurity, " "c.relfrozenxid, 0 AS relminmxid, tc.oid AS toid, " "tc.relfrozenxid AS tfrozenxid, " "0 AS tminmxid, " @@ -4569,6 +4864,7 @@ getTables(Archive *fout, int *numTables) "(%s relowner) AS rolname, " "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, relhasoids, " + "'f'::bool AS relhasrowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4605,6 +4901,7 @@ getTables(Archive *fout, int *numTables) "(%s relowner) AS rolname, " "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, relhasoids, " + "'f'::bool AS relhasrowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4637,6 +4934,7 @@ getTables(Archive *fout, int *numTables) "(%s relowner) AS rolname, " "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, relhasoids, " + "'f'::bool AS relhasrowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4664,6 +4962,7 @@ getTables(Archive *fout, int *numTables) "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, " "'t'::bool AS relhasoids, " + "'f'::bool AS relhasrowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4701,6 +5000,7 @@ getTables(Archive *fout, int *numTables) "relchecks, (reltriggers <> 0) AS relhastriggers, " "relhasindex, relhasrules, " "'t'::bool AS relhasoids, " + "'f'::bool AS relhasrowsecurity, " "0 AS relfrozenxid, 0 AS relminmxid," "0 AS toid, " "0 AS tfrozenxid, 0 AS tminmxid," @@ -4748,6 +5048,7 @@ getTables(Archive *fout, int *numTables) i_relhastriggers = PQfnumber(res, "relhastriggers"); i_relhasindex = PQfnumber(res, "relhasindex"); i_relhasrules = PQfnumber(res, "relhasrules"); + i_relhasrowsec = PQfnumber(res, "relhasrowsecurity"); i_relhasoids = PQfnumber(res, "relhasoids"); i_relfrozenxid = PQfnumber(res, "relfrozenxid"); i_relminmxid = PQfnumber(res, "relminmxid"); @@ -4799,6 +5100,7 @@ getTables(Archive *fout, int *numTables) tblinfo[i].hasindex = (strcmp(PQgetvalue(res, i, i_relhasindex), "t") == 0); tblinfo[i].hasrules = (strcmp(PQgetvalue(res, i, i_relhasrules), "t") == 0); tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0); + tblinfo[i].hasrowsec = (strcmp(PQgetvalue(res, i, i_relhasrowsec), "t") == 0); tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0); tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0); tblinfo[i].relreplident = *(PQgetvalue(res, i, i_relreplident)); @@ -7930,6 +8232,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) NULL, 0, dumpBlobs, NULL); break; + case DO_ROW_SECURITY: + dumpRowSecurity(fout, (RowSecurityInfo *) dobj); + break; case DO_PRE_DATA_BOUNDARY: case DO_POST_DATA_BOUNDARY: /* never dumped, nothing to do */ @@ -15332,6 +15637,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs, case DO_TRIGGER: case DO_EVENT_TRIGGER: case DO_DEFAULT_ACL: + case DO_ROW_SECURITY: /* Post-data objects: must come after the post-data boundary */ addObjectDependency(dobj, postDataBound->dumpId); break; diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index d184187580..b5d820e761 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -111,7 +111,8 @@ typedef enum DO_PRE_DATA_BOUNDARY, DO_POST_DATA_BOUNDARY, DO_EVENT_TRIGGER, - DO_REFRESH_MATVIEW + DO_REFRESH_MATVIEW, + DO_ROW_SECURITY } DumpableObjectType; typedef struct _dumpableObject @@ -245,6 +246,7 @@ typedef struct _tableInfo bool hasindex; /* does it have any indexes? */ bool hasrules; /* does it have any rules? */ bool hastriggers; /* does it have any triggers? */ + bool hasrowsec; /* does it have any row-security policy? */ bool hasoids; /* does it have OIDs? */ uint32 frozenxid; /* for restore frozen xid */ uint32 minmxid; /* for restore min multi xid */ @@ -486,6 +488,23 @@ typedef struct _blobInfo char *blobacl; } BlobInfo; +/* + * The RowSecurityInfo struct is used to represent row policies on a table and + * to indicate if a table has RLS enabled (ENABLE ROW SECURITY). If + * rsecpolname is NULL, then the record indicates ENABLE ROW SECURITY, while if + * it's non-NULL then this is a regular policy definition. + */ +typedef struct _rowSecurityInfo +{ + DumpableObject dobj; + TableInfo *rstable; + char *rsecpolname; /* null indicates RLS is enabled on rel */ + char *rseccmd; + char *rsecroles; + char *rsecqual; + char *rsecwithcheck; +} RowSecurityInfo; + /* global decls */ extern bool force_quotes; /* double-quotes for identifiers flag */ extern bool g_verbose; /* verbose flag */ @@ -577,5 +596,6 @@ extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs); extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[], int numExtensions); extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers); +extern void getRowSecurity(Archive *fout, TableInfo tblinfo[], int numTables); #endif /* PG_DUMP_H */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index f0caa6b659..90aedee7d2 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -70,7 +70,8 @@ static const int oldObjectTypePriority[] = 10, /* DO_PRE_DATA_BOUNDARY */ 13, /* DO_POST_DATA_BOUNDARY */ 20, /* DO_EVENT_TRIGGER */ - 15 /* DO_REFRESH_MATVIEW */ + 15, /* DO_REFRESH_MATVIEW */ + 21 /* DO_ROW_SECURITY */ }; /* @@ -118,7 +119,8 @@ static const int newObjectTypePriority[] = 22, /* DO_PRE_DATA_BOUNDARY */ 25, /* DO_POST_DATA_BOUNDARY */ 32, /* DO_EVENT_TRIGGER */ - 33 /* DO_REFRESH_MATVIEW */ + 33, /* DO_REFRESH_MATVIEW */ + 34 /* DO_ROW_SECURITY */ }; static DumpId preDataBoundId; @@ -1434,6 +1436,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "BLOB DATA (ID %d)", obj->dumpId); return; + case DO_ROW_SECURITY: + snprintf(buf, bufsize, + "ROW-SECURITY POLICY (ID %d OID %u)", + obj->dumpId, obj->catId.oid); + return; case DO_PRE_DATA_BOUNDARY: snprintf(buf, bufsize, "PRE-DATA BOUNDARY (ID %d)", diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index b2b3e6feb7..c25ea851d3 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -663,17 +663,29 @@ dumpRoles(PGconn *conn) i_rolpassword, i_rolvaliduntil, i_rolreplication, + i_rolbypassrls, i_rolcomment, i_is_current_user; int i; /* note: rolconfig is dumped later */ - if (server_version >= 90100) + if (server_version >= 90500) + printfPQExpBuffer(buf, + "SELECT oid, rolname, rolsuper, rolinherit, " + "rolcreaterole, rolcreatedb, " + "rolcanlogin, rolconnlimit, rolpassword, " + "rolvaliduntil, rolreplication, rolbypassrls, " + "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, " + "rolname = current_user AS is_current_user " + "FROM pg_authid " + "ORDER BY 2"); + else if (server_version >= 90100) printfPQExpBuffer(buf, "SELECT oid, rolname, rolsuper, rolinherit, " "rolcreaterole, rolcreatedb, " "rolcanlogin, rolconnlimit, rolpassword, " "rolvaliduntil, rolreplication, " + "false as rolbypassrls, " "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, " "rolname = current_user AS is_current_user " "FROM pg_authid " @@ -684,6 +696,7 @@ dumpRoles(PGconn *conn) "rolcreaterole, rolcreatedb, " "rolcanlogin, rolconnlimit, rolpassword, " "rolvaliduntil, false as rolreplication, " + "false as rolbypassrls, " "pg_catalog.shobj_description(oid, 'pg_authid') as rolcomment, " "rolname = current_user AS is_current_user " "FROM pg_authid " @@ -694,6 +707,7 @@ dumpRoles(PGconn *conn) "rolcreaterole, rolcreatedb, " "rolcanlogin, rolconnlimit, rolpassword, " "rolvaliduntil, false as rolreplication, " + "false as rolbypassrls, " "null as rolcomment, " "rolname = current_user AS is_current_user " "FROM pg_authid " @@ -724,6 +738,7 @@ dumpRoles(PGconn *conn) "null::text as rolpassword, " "null::abstime as rolvaliduntil, " "false as rolreplication, " + "false as rolbypassrls, " "null as rolcomment, false " "FROM pg_group " "WHERE NOT EXISTS (SELECT 1 FROM pg_shadow " @@ -743,6 +758,7 @@ dumpRoles(PGconn *conn) i_rolpassword = PQfnumber(res, "rolpassword"); i_rolvaliduntil = PQfnumber(res, "rolvaliduntil"); i_rolreplication = PQfnumber(res, "rolreplication"); + i_rolbypassrls = PQfnumber(res, "rolbypassrls"); i_rolcomment = PQfnumber(res, "rolcomment"); i_is_current_user = PQfnumber(res, "is_current_user"); @@ -810,6 +826,11 @@ dumpRoles(PGconn *conn) else appendPQExpBufferStr(buf, " NOREPLICATION"); + if (strcmp(PQgetvalue(res, i, i_rolbypassrls), "t") == 0) + appendPQExpBufferStr(buf, " BYPASSRLS"); + else + appendPQExpBufferStr(buf, " NOBYPASSRLS"); + if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0) appendPQExpBuffer(buf, " CONNECTION LIMIT %s", PQgetvalue(res, i, i_rolconnlimit)); diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c index fdfdc19c3f..1c1b80f137 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -70,6 +70,7 @@ main(int argc, char **argv) Archive *AH; char *inputFileSpec; static int disable_triggers = 0; + static int enable_row_security = 0; static int if_exists = 0; static int no_data_for_failed_tables = 0; static int outputNoTablespaces = 0; @@ -111,6 +112,7 @@ main(int argc, char **argv) * the following options don't have an equivalent short option letter */ {"disable-triggers", no_argument, &disable_triggers, 1}, + {"enable-row-security", no_argument, &enable_row_security, 1}, {"if-exists", no_argument, &if_exists, 1}, {"no-data-for-failed-tables", no_argument, &no_data_for_failed_tables, 1}, {"no-tablespaces", no_argument, &outputNoTablespaces, 1}, @@ -333,6 +335,7 @@ main(int argc, char **argv) } opts->disable_triggers = disable_triggers; + opts->enable_row_security = enable_row_security; opts->noDataForFailedTables = no_data_for_failed_tables; opts->noTablespace = outputNoTablespaces; opts->use_setsessauth = use_setsessauth; @@ -460,6 +463,7 @@ usage(const char *progname) printf(_(" -x, --no-privileges skip restoration of access privileges (grant/revoke)\n")); printf(_(" -1, --single-transaction restore as a single transaction\n")); printf(_(" --disable-triggers disable triggers during data-only restore\n")); + printf(_(" --enable-row-security enable row level security\n")); printf(_(" --if-exists use IF EXISTS when dropping objects\n")); printf(_(" --no-data-for-failed-tables do not restore data of tables that could not be\n" " created\n")); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 282cd432a2..97dc2dded2 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -742,7 +742,7 @@ permissionsList(const char *pattern) PQExpBufferData buf; PGresult *res; printQueryOpt myopt = pset.popt; - static const bool translate_columns[] = {false, false, true, false, false}; + static const bool translate_columns[] = {false, false, true, false, false, false}; initPQExpBuffer(&buf); @@ -778,7 +778,38 @@ permissionsList(const char *pattern) " FROM pg_catalog.pg_attribute a\n" " WHERE attrelid = c.oid AND NOT attisdropped AND attacl IS NOT NULL\n" " ), E'\\n') AS \"%s\"", - gettext_noop("Column access privileges")); + gettext_noop("Column privileges")); + + if (pset.sversion >= 90500) + appendPQExpBuffer(&buf, + ",\n pg_catalog.array_to_string(ARRAY(\n" + " SELECT rsecpolname\n" + " || CASE WHEN rseccmd IS NOT NULL THEN\n" + " E' (' || rseccmd || E')'\n" + " ELSE E':' \n" + " END\n" + " || CASE WHEN rs.rsecqual IS NOT NULL THEN\n" + " E'\\n (u): ' || pg_catalog.pg_get_expr(rsecqual, rsecrelid)\n" + " ELSE E''\n" + " END\n" + " || CASE WHEN rsecwithcheck IS NOT NULL THEN\n" + " E'\\n (c): ' || pg_catalog.pg_get_expr(rsecwithcheck, rsecrelid)\n" + " ELSE E''\n" + " END" + " || CASE WHEN rs.rsecroles <> '{0}' THEN\n" + " E'\\n to: ' || pg_catalog.array_to_string(\n" + " ARRAY(\n" + " SELECT rolname\n" + " FROM pg_catalog.pg_roles\n" + " WHERE oid = ANY (rs.rsecroles)\n" + " ORDER BY 1\n" + " ), E', ')\n" + " ELSE E''\n" + " END\n" + " FROM pg_catalog.pg_rowsecurity rs\n" + " WHERE rsecrelid = c.oid), E'\\n')\n" + " AS \"%s\"", + gettext_noop("Policies")); appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_class c\n" " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" @@ -1173,6 +1204,7 @@ describeOneTableDetails(const char *schemaname, bool hasindex; bool hasrules; bool hastriggers; + bool hasrowsecurity; bool hasoids; Oid tablespace; char *reloptions; @@ -1194,11 +1226,28 @@ describeOneTableDetails(const char *schemaname, initPQExpBuffer(&tmpbuf); /* Get general table info */ - if (pset.sversion >= 90400) + if (pset.sversion >= 90500) { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, c.relhasoids, " + "c.relhastriggers, c.relhasrowsecurity, c.relhasoids, " + "%s, c.reltablespace, " + "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " + "c.relpersistence, c.relreplident\n" + "FROM pg_catalog.pg_class c\n " + "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" + "WHERE c.oid = '%s';", + (verbose ? + "pg_catalog.array_to_string(c.reloptions || " + "array(select 'toast.' || x from pg_catalog.unnest(tc.reloptions) x), ', ')\n" + : "''"), + oid); + } + else if (pset.sversion >= 90400) + { + printfPQExpBuffer(&buf, + "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " + "c.relhastriggers, false, c.relhasoids, " "%s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence, c.relreplident\n" @@ -1215,7 +1264,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, c.relhasoids, " + "c.relhastriggers, false, c.relhasoids, " "%s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END, " "c.relpersistence\n" @@ -1232,7 +1281,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, c.relhasoids, " + "c.relhastriggers, false, c.relhasoids, " "%s, c.reltablespace, " "CASE WHEN c.reloftype = 0 THEN '' ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text END\n" "FROM pg_catalog.pg_class c\n " @@ -1248,7 +1297,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT c.relchecks, c.relkind, c.relhasindex, c.relhasrules, " - "c.relhastriggers, c.relhasoids, " + "c.relhastriggers, false, c.relhasoids, " "%s, c.reltablespace\n" "FROM pg_catalog.pg_class c\n " "LEFT JOIN pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)\n" @@ -1263,7 +1312,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " - "reltriggers <> 0, relhasoids, " + "reltriggers <> 0, false, relhasoids, " "%s, reltablespace\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", (verbose ? @@ -1274,7 +1323,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " - "reltriggers <> 0, relhasoids, " + "reltriggers <> 0, false, relhasoids, " "'', reltablespace\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", oid); @@ -1283,7 +1332,7 @@ describeOneTableDetails(const char *schemaname, { printfPQExpBuffer(&buf, "SELECT relchecks, relkind, relhasindex, relhasrules, " - "reltriggers <> 0, relhasoids, " + "reltriggers <> 0, false, relhasoids, " "'', ''\n" "FROM pg_catalog.pg_class WHERE oid = '%s';", oid); @@ -1306,18 +1355,19 @@ describeOneTableDetails(const char *schemaname, tableinfo.hasindex = strcmp(PQgetvalue(res, 0, 2), "t") == 0; tableinfo.hasrules = strcmp(PQgetvalue(res, 0, 3), "t") == 0; tableinfo.hastriggers = strcmp(PQgetvalue(res, 0, 4), "t") == 0; - tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 5), "t") == 0; + tableinfo.hasrowsecurity = strcmp(PQgetvalue(res, 0, 5), "t") == 0; + tableinfo.hasoids = strcmp(PQgetvalue(res, 0, 6), "t") == 0; tableinfo.reloptions = (pset.sversion >= 80200) ? - pg_strdup(PQgetvalue(res, 0, 6)) : NULL; + pg_strdup(PQgetvalue(res, 0, 7)) : NULL; tableinfo.tablespace = (pset.sversion >= 80000) ? - atooid(PQgetvalue(res, 0, 7)) : 0; + atooid(PQgetvalue(res, 0, 8)) : 0; tableinfo.reloftype = (pset.sversion >= 90000 && - strcmp(PQgetvalue(res, 0, 8), "") != 0) ? - pg_strdup(PQgetvalue(res, 0, 8)) : NULL; + strcmp(PQgetvalue(res, 0, 9), "") != 0) ? + pg_strdup(PQgetvalue(res, 0, 9)) : NULL; tableinfo.relpersistence = (pset.sversion >= 90100) ? - *(PQgetvalue(res, 0, 9)) : 0; + *(PQgetvalue(res, 0, 10)) : 0; tableinfo.relreplident = (pset.sversion >= 90400) ? - *(PQgetvalue(res, 0, 10)) : 'd'; + *(PQgetvalue(res, 0, 11)) : 'd'; PQclear(res); res = NULL; @@ -1948,6 +1998,67 @@ describeOneTableDetails(const char *schemaname, PQclear(result); } + + if (pset.sversion >= 90500) + appendPQExpBuffer(&buf, + ",\n pg_catalog.pg_get_expr(rs.rsecqual, c.oid) as \"%s\"", + gettext_noop("Row-security")); + if (verbose && pset.sversion >= 90500) + appendPQExpBuffer(&buf, + "\n LEFT JOIN pg_rowsecurity rs ON rs.rsecrelid = c.oid"); + + /* print any row-level policies */ + if (tableinfo.hasrowsecurity) + { + printfPQExpBuffer(&buf, + "SELECT rs.rsecpolname,\n" + "CASE WHEN rs.rsecroles = '{0}' THEN NULL ELSE array(select rolname from pg_roles where oid = any (rs.rsecroles) order by 1) END,\n" + "pg_catalog.pg_get_expr(rs.rsecqual, rs.rsecrelid),\n" + "pg_catalog.pg_get_expr(rs.rsecwithcheck, rs.rsecrelid),\n" + "rs.rseccmd AS cmd\n" + "FROM pg_catalog.pg_rowsecurity rs\n" + "WHERE rs.rsecrelid = '%s' ORDER BY 1;", + oid); + result = PSQLexec(buf.data, false); + if (!result) + goto error_return; + else + tuples = PQntuples(result); + + if (tuples > 0) + { + printTableAddFooter(&cont, _("Policies:")); + for (i = 0; i < tuples; i++) + { + printfPQExpBuffer(&buf, " POLICY \"%s\"", + PQgetvalue(result, i, 0)); + + if (!PQgetisnull(result, i, 4)) + appendPQExpBuffer(&buf, " (%s)", + PQgetvalue(result, i, 4)); + + if (!PQgetisnull(result, i, 2)) + appendPQExpBuffer(&buf, " EXPRESSION %s", + PQgetvalue(result, i, 2)); + + if (!PQgetisnull(result, i, 3)) + appendPQExpBuffer(&buf, " WITH CHECK %s", + PQgetvalue(result, i, 3)); + + printTableAddFooter(&cont, buf.data); + + if (!PQgetisnull(result, i, 1)) + { + printfPQExpBuffer(&buf, " APPLIED TO %s", + PQgetvalue(result, i, 1)); + + printTableAddFooter(&cont, buf.data); + } + } + } + PQclear(result); + } + /* print rules */ if (tableinfo.hasrules && tableinfo.relkind != 'm') { @@ -2529,6 +2640,11 @@ describeRoles(const char *pattern, bool verbose) appendPQExpBufferStr(&buf, "\n, r.rolreplication"); } + if (pset.sversion >= 90500) + { + appendPQExpBufferStr(&buf, "\n, r.rolbypassrls"); + } + appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n"); processSQLNamePattern(pset.db, &buf, pattern, false, false, diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index b80fe13168..a4594b6783 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -782,6 +782,7 @@ static const pgsql_thing_t words_after_create[] = { * good idea. */ {"OWNED", NULL, NULL, THING_NO_CREATE}, /* for DROP OWNED BY ... */ {"PARSER", Query_for_list_of_ts_parsers, NULL, THING_NO_SHOW}, + {"POLICY", NULL, NULL}, {"ROLE", Query_for_list_of_roles}, {"RULE", "SELECT pg_catalog.quote_ident(rulename) FROM pg_catalog.pg_rules WHERE substring(pg_catalog.quote_ident(rulename),1,%d)='%s'"}, {"SCHEMA", Query_for_list_of_schemas}, @@ -971,7 +972,7 @@ psql_completion(const char *text, int start, int end) {"AGGREGATE", "COLLATION", "CONVERSION", "DATABASE", "DEFAULT PRIVILEGES", "DOMAIN", "EVENT TRIGGER", "EXTENSION", "FOREIGN DATA WRAPPER", "FOREIGN TABLE", "FUNCTION", "GROUP", "INDEX", "LANGUAGE", "LARGE OBJECT", "MATERIALIZED VIEW", "OPERATOR", - "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE", + "POLICY", "ROLE", "RULE", "SCHEMA", "SERVER", "SEQUENCE", "SYSTEM", "TABLE", "TABLESPACE", "TEXT SEARCH", "TRIGGER", "TYPE", "USER", "USER MAPPING FOR", "VIEW", NULL}; @@ -1398,6 +1399,44 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_LIST(list_ALTERMATVIEW); } + /* ALTER POLICY <name> ON */ + else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && + pg_strcasecmp(prev2_wd, "POLICY") == 0) + COMPLETE_WITH_CONST("ON"); + /* ALTER POLICY <name> ON <table> */ + else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && + pg_strcasecmp(prev3_wd, "POLICY") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + /* ALTER POLICY <name> ON <table> - show options */ + else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 && + pg_strcasecmp(prev4_wd, "POLICY") == 0 && + pg_strcasecmp(prev2_wd, "ON") == 0) + { + static const char *const list_ALTERPOLICY[] = + {"RENAME TO", "TO", "USING", "WITH CHECK", NULL}; + + COMPLETE_WITH_LIST(list_ALTERPOLICY); + } + /* ALTER POLICY <name> ON <table> TO <role> */ + else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 && + pg_strcasecmp(prev5_wd, "POLICY") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "TO") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles); + /* ALTER POLICY <name> ON <table> USING ( */ + else if (pg_strcasecmp(prev6_wd, "ALTER") == 0 && + pg_strcasecmp(prev5_wd, "POLICY") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "USING") == 0) + COMPLETE_WITH_CONST("("); + /* ALTER POLICY <name> ON <table> WITH CHECK ( */ + else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 && + pg_strcasecmp(prev4_wd, "ON") == 0 && + pg_strcasecmp(prev2_wd, "WITH") == 0 && + pg_strcasecmp(prev_wd, "CHECK") == 0) + COMPLETE_WITH_CONST("("); + /* ALTER RULE <name>, add ON */ else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 && pg_strcasecmp(prev2_wd, "RULE") == 0) @@ -1462,7 +1501,7 @@ psql_completion(const char *text, int start, int end) pg_strcasecmp(prev_wd, "ENABLE") == 0) { static const char *const list_ALTERENABLE[] = - {"ALWAYS", "REPLICA", "RULE", "TRIGGER", NULL}; + {"ALWAYS", "REPLICA", "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL}; COMPLETE_WITH_LIST(list_ALTERENABLE); } @@ -1529,7 +1568,7 @@ psql_completion(const char *text, int start, int end) pg_strcasecmp(prev_wd, "DISABLE") == 0) { static const char *const list_ALTERDISABLE[] = - {"RULE", "TRIGGER", NULL}; + { "ROW LEVEL SECURITY", "RULE", "TRIGGER", NULL}; COMPLETE_WITH_LIST(list_ALTERDISABLE); } @@ -1549,6 +1588,16 @@ psql_completion(const char *text, int start, int end) completion_info_charp = prev3_wd; COMPLETE_WITH_QUERY(Query_for_trigger_of_table); } + else if (pg_strcasecmp(prev4_wd, "DISABLE") == 0 && + pg_strcasecmp(prev3_wd, "ROW") == 0 && + pg_strcasecmp(prev2_wd, "LEVEL") == 0 && + pg_strcasecmp(prev_wd, "SECURITY") == 0) + { + static const char *const list_DISABLERLS[] = + { "CASCADE", NULL}; + + COMPLETE_WITH_LIST(list_DISABLERLS); + } /* ALTER TABLE xxx ALTER */ else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 && @@ -2251,12 +2300,103 @@ psql_completion(const char *text, int start, int end) pg_strcasecmp(prev_wd, "(") == 0) COMPLETE_WITH_ATTR(prev4_wd, ""); /* Complete USING with an index method */ - else if (pg_strcasecmp(prev_wd, "USING") == 0) + else if ((pg_strcasecmp(prev6_wd, "INDEX") == 0 || + pg_strcasecmp(prev5_wd, "INDEX") == 0 || + pg_strcasecmp(prev4_wd, "INDEX") == 0) && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "USING") == 0) COMPLETE_WITH_QUERY(Query_for_list_of_access_methods); else if (pg_strcasecmp(prev4_wd, "ON") == 0 && + (!(pg_strcasecmp(prev6_wd, "POLICY") == 0) && + !(pg_strcasecmp(prev4_wd, "FOR") == 0)) && pg_strcasecmp(prev2_wd, "USING") == 0) COMPLETE_WITH_CONST("("); + /* CREATE POLICY */ + /* Complete "CREATE POLICY <name> ON" */ + else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && + pg_strcasecmp(prev2_wd, "POLICY") == 0) + COMPLETE_WITH_CONST("ON"); + /* Complete "CREATE POLICY <name> ON <table>" */ + else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 && + pg_strcasecmp(prev3_wd, "POLICY") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + /* Complete "CREATE POLICY <name> ON <table> FOR|TO|USING|WITH CHECK" */ + else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 && + pg_strcasecmp(prev4_wd, "POLICY") == 0 && + pg_strcasecmp(prev2_wd, "ON") == 0) + { + static const char *const list_POLICYOPTIONS[] = + {"FOR", "TO", "USING", "WITH CHECK", NULL}; + + COMPLETE_WITH_LIST(list_POLICYOPTIONS); + } + /* Complete "CREATE POLICY <name> ON <table> FOR ALL|SELECT|INSERT|UPDATE|DELETE" */ + else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 && + pg_strcasecmp(prev5_wd, "POLICY") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "FOR") == 0) + { + static const char *const list_POLICYCMDS[] = + {"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL}; + + COMPLETE_WITH_LIST(list_POLICYCMDS); + } + /* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */ + else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 && + pg_strcasecmp(prev4_wd, "ON") == 0 && + pg_strcasecmp(prev2_wd, "FOR") == 0 && + pg_strcasecmp(prev_wd, "INSERT") == 0) + { + static const char *const list_POLICYOPTIONS[] = + {"TO", "WITH CHECK", NULL}; + + COMPLETE_WITH_LIST(list_POLICYOPTIONS); + } + /* + * Complete "CREATE POLICY <name> ON <table> FOR SELECT TO|USING" + * Complete "CREATE POLICY <name> ON <table> FOR DELETE TO|USING" + */ + else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 && + pg_strcasecmp(prev4_wd, "ON") == 0 && + pg_strcasecmp(prev2_wd, "FOR") == 0 && + (pg_strcasecmp(prev_wd, "SELECT") == 0 || + pg_strcasecmp(prev_wd, "DELETE") == 0)) + { + static const char *const list_POLICYOPTIONS[] = + {"TO", "USING", NULL}; + + COMPLETE_WITH_LIST(list_POLICYOPTIONS); + } + /* + * Complete "CREATE POLICY <name> ON <table> FOR ALL TO|USING|WITH CHECK" + * Complete "CREATE POLICY <name> ON <table> FOR UPDATE TO|USING|WITH CHECK" + */ + else if (pg_strcasecmp(prev6_wd, "POLICY") == 0 && + pg_strcasecmp(prev4_wd, "ON") == 0 && + pg_strcasecmp(prev2_wd, "FOR") == 0 && + (pg_strcasecmp(prev_wd, "ALL") == 0 || + pg_strcasecmp(prev_wd, "UPDATE") == 0)) + { + static const char *const list_POLICYOPTIONS[] = + {"TO", "USING", "WITH CHECK", NULL}; + + COMPLETE_WITH_LIST(list_POLICYOPTIONS); + } + /* Complete "CREATE POLICY <name> ON <table> TO <role>" */ + else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 && + pg_strcasecmp(prev5_wd, "POLICY") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "TO") == 0) + COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles); + /* Complete "CREATE POLICY <name> ON <table> USING (" */ + else if (pg_strcasecmp(prev6_wd, "CREATE") == 0 && + pg_strcasecmp(prev5_wd, "POLICY") == 0 && + pg_strcasecmp(prev3_wd, "ON") == 0 && + pg_strcasecmp(prev_wd, "USING") == 0) + COMPLETE_WITH_CONST("("); + /* CREATE RULE */ /* Complete "CREATE RULE <sth>" with "AS" */ else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 && @@ -2726,6 +2866,16 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_event_triggers); } + /* DROP POLICY <name> ON */ + else if (pg_strcasecmp(prev3_wd, "DROP") == 0 && + pg_strcasecmp(prev2_wd, "POLICY") == 0) + COMPLETE_WITH_CONST("ON"); + /* DROP POLICY <name> ON <table> */ + else if (pg_strcasecmp(prev4_wd, "DROP") == 0 && + pg_strcasecmp(prev3_wd, "POLICY") == 0 && + pg_strcasecmp(prev_wd, "ON") == 0) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL); + /* DROP RULE */ else if (pg_strcasecmp(prev3_wd, "DROP") == 0 && pg_strcasecmp(prev2_wd, "RULE") == 0) |
