From 145c9d04f807ebd4f85601420b21535657076db6 Mon Sep 17 00:00:00 2001
From: Alexander Polyakov <alexplkv@yandex.ru>
Date: Tue, 5 Apr 2016 17:09:57 +0400
Subject: [PATCH] Basic tab-completion for policies

---
 pgadmin/ctl/ctlSQLBox.cpp      |   6 +-
 pgadmin/utils/tab-complete.inc | 164 ++++++++++++++++++++++++++++++++++++++++-
 pgadmin/utils/tabcomplete.c    |   4 +-
 3 files changed, 165 insertions(+), 9 deletions(-)

diff --git a/pgadmin/ctl/ctlSQLBox.cpp b/pgadmin/ctl/ctlSQLBox.cpp
index a45bb5e..37967d6 100644
--- a/pgadmin/ctl/ctlSQLBox.cpp
+++ b/pgadmin/ctl/ctlSQLBox.cpp
@@ -828,7 +828,7 @@ void ctlSQLBox::OnMarginClick(wxStyledTextEvent &event)
 }
 
 
-extern "C" char *tab_complete(const char *allstr, const int startptr, const int endptr, void *dbptr);
+extern "C" char *tab_complete(const char *allstr, const int startptr, const int endptr, void *dbptr, int dbvmajor, int dbvminor);
 void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
 {
 	if (GetReadOnly())
@@ -843,9 +843,9 @@ void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
 
 	char *tab_ret;
 	if (spaceidx == -1)
-		tab_ret = tab_complete(what.mb_str(wxConvUTF8), 0, what.Len() + 1, m_database);
+		tab_ret = tab_complete(what.mb_str(wxConvUTF8), 0, what.Len() + 1, m_database, m_database->GetMajorVersion(), m_database->GetMinorVersion());
 	else
-		tab_ret = tab_complete(what.mb_str(wxConvUTF8), spaceidx + 1, what.Len() + 1, m_database);
+		tab_ret = tab_complete(what.mb_str(wxConvUTF8), spaceidx + 1, what.Len() + 1, m_database, m_database->GetMajorVersion(), m_database->GetMinorVersion());
 
 	if (tab_ret == NULL || tab_ret[0] == '\0')
 		return; /* No autocomplete available for this string */
diff --git a/pgadmin/utils/tab-complete.inc b/pgadmin/utils/tab-complete.inc
index 98b7a35..2a42f85 100644
--- a/pgadmin/utils/tab-complete.inc
+++ b/pgadmin/utils/tab-complete.inc
@@ -315,6 +315,19 @@ static const SchemaQuery Query_for_list_of_views = {
 "       (SELECT tgrelid FROM pg_catalog.pg_trigger "\
 "         WHERE pg_catalog.quote_ident(tgname)='%s')"
 
+#define Query_for_list_of_policies \
+" SELECT pg_catalog.quote_ident(polname) "\
+"   FROM pg_catalog.pg_policy "\
+"  WHERE substring(pg_catalog.quote_ident(polname),1,%d)='%s'"
+
+#define Query_for_list_of_tables_for_policy \
+"SELECT pg_catalog.quote_ident(relname) "\
+"  FROM pg_catalog.pg_class"\
+" WHERE (%d = pg_catalog.length('%s'))"\
+"   AND oid IN "\
+"       (SELECT polrelid FROM pg_catalog.pg_policy "\
+"         WHERE pg_catalog.quote_ident(polname)='%s')"
+
 /*
  * This is a list of all "things" in Pgsql, which can show up after CREATE or
  * DROP; and there is also a query to get a list of them.
@@ -345,6 +358,7 @@ static const pgsql_thing_t words_after_create[] = {
 	{"INDEX", NULL, &Query_for_list_of_indexes},
 	{"OPERATOR", NULL, NULL},	/* Querying for this is probably not such a
 								 * good idea. */
+	{"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},
@@ -360,13 +374,14 @@ static const pgsql_thing_t words_after_create[] = {
 	{NULL, NULL, NULL}			/* end of list */
 };
 
+#define BACKEND_MINIMUM(major, minor)		(dbvmajor > major || (dbvmajor == major && dbvminor >= minor))
 
 /* The completion function. Acc. to readline spec this gets passed the text
    entered to far and its start and end in the readline buffer. The return value
    is some partially obscure list format that can be generated by the readline
    libraries completion_matches() function, so we don't have to worry about it.
 */
-static char * psql_completion(char *text, int start, int end, void *dbptr)
+static char * psql_completion(char *text, int start, int end, void *dbptr, int dbvmajor, int dbvminor)
 {
 	/* This is the variable we'll return. */
 	char *matches = NULL;
@@ -376,7 +391,9 @@ static char * psql_completion(char *text, int start, int end, void *dbptr)
 			   *prev2_wd,
 			   *prev3_wd,
 			   *prev4_wd,
-			   *prev5_wd;
+			   *prev5_wd,
+			   *prev6_wd,
+			   *prev7_wd;
 
 	static const char *const sql_commands[] = {
 		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT",
@@ -419,6 +436,8 @@ static char * psql_completion(char *text, int start, int end, void *dbptr)
 	prev3_wd = previous_word(start, 2);
 	prev4_wd = previous_word(start, 3);
 	prev5_wd = previous_word(start, 4);
+	prev6_wd = previous_word(start, 5);
+	prev7_wd = previous_word(start, 6);
 
 	/* If a backslash command was started, continue */
 	if (text[0] == '\\')
@@ -448,7 +467,7 @@ static char * psql_completion(char *text, int start, int end, void *dbptr)
 	{
 		static const char *const list_ALTER[] =
 		{"AGGREGATE", "CONVERSION", "DATABASE", "DOMAIN", "FUNCTION",
-			"GROUP", "INDEX", "LANGUAGE", "OPERATOR", "ROLE", "SCHEMA", "SEQUENCE", "TABLE",
+			"GROUP", "INDEX", "LANGUAGE", "OPERATOR", "POLICY", "ROLE", "SCHEMA", "SEQUENCE", "TABLE",
 		"TABLESPACE", "TRIGGER", "TYPE", "USER", NULL};
 
 		COMPLETE_WITH_LIST(list_ALTER);
@@ -781,7 +800,7 @@ static char * psql_completion(char *text, int start, int end, void *dbptr)
 			 pg_strcasecmp(prev_wd, "ON") == 0)
 	{
 		static const char *const list_COMMENT[] =
-		{"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "RULE", "SCHEMA",
+		{"CAST", "CONVERSION", "DATABASE", "INDEX", "LANGUAGE", "POLICY", "RULE", "SCHEMA",
 			"SEQUENCE", "TABLE", "TYPE", "VIEW", "COLUMN", "AGGREGATE", "FUNCTION",
 		"OPERATOR", "TRIGGER", "CONSTRAINT", "DOMAIN", "LARGE OBJECT", NULL};
 
@@ -1265,6 +1284,141 @@ static char * psql_completion(char *text, int start, int end, void *dbptr)
 			 pg_strcasecmp(prev_wd, "GROUP") == 0)
 		COMPLETE_WITH_CONST("BY");
 
+/* 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_CREATEPOLICY[] =
+			{"FOR", "TO", "USING (", "WITH CHECK (", NULL};
+		COMPLETE_WITH_LIST(list_CREATEPOLICY);
+	}
+	/* 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_CREATEPOLICYFOR[] =
+			{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", NULL};
+		COMPLETE_WITH_LIST(list_CREATEPOLICYFOR);
+	}
+	/* Complete "CREATE POLICY <name> ON <table> FOR INSERT TO|WITH CHECK" */
+	/* Note this should happen before INSERT INTO/UPDATE checks*/
+	else if (pg_strcasecmp(prev7_wd, "CREATE") == 0 &&
+			 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_CREATEPOLICYFORINSERT[] =
+			{"TO", "WITH CHECK (", NULL};
+		COMPLETE_WITH_LIST(list_CREATEPOLICYFORINSERT);
+	}
+	/* Complete "CREATE POLICY <name> ON <table> FOR SELECT|DELETE TO|USING" */
+	else if (pg_strcasecmp(prev7_wd, "CREATE") == 0 &&
+			 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_CREATEPOLICYFORSELECT[] =
+			{"TO", "USING (", NULL};
+		COMPLETE_WITH_LIST(list_CREATEPOLICYFORSELECT);
+	}
+	/* CREATE POLICY <name> ON <table> FOR ALL|UPDATE TO|USING|WITH CHECK */
+	else if (pg_strcasecmp(prev7_wd, "CREATE") == 0 &&
+			 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_CREATEPOLICYFORSELECT[] =
+			{"TO", "USING (", "WITH CHECK (", NULL};
+		COMPLETE_WITH_LIST(list_CREATEPOLICYFORSELECT);
+	}
+	/* 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);
+	/* 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("(");
+
+	/* ALTER POLICY <name> */
+	/* Should check db version to avoid errors with nonexistent catalogs */
+	else if (pg_strcasecmp(prev2_wd, "ALTER") == 0 &&
+			 pg_strcasecmp(prev_wd, "POLICY") == 0 &&
+			 BACKEND_MINIMUM(9, 5))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+	/* 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(prev7_wd, "CREATE") == 0 &&
+			 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("(");
+
+	/* DROP POLICY <name> */
+	else if (pg_strcasecmp(prev2_wd, "DROP") == 0 &&
+			 pg_strcasecmp(prev_wd, "POLICY") == 0 &&
+			 BACKEND_MINIMUM(9, 5))
+		COMPLETE_WITH_QUERY(Query_for_list_of_policies);
+	/* 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);
+
 /* INSERT */
 	/* Complete INSERT with "INTO" */
 	else if (pg_strcasecmp(prev_wd, "INSERT") == 0)
@@ -1730,6 +1884,8 @@ static char * psql_completion(char *text, int start, int end, void *dbptr)
 	free(prev3_wd);
 	free(prev4_wd);
 	free(prev5_wd);
+	free(prev6_wd);
+	free(prev7_wd);
 
 	/* Return our Grand List O' Matches */
 	return matches;
diff --git a/pgadmin/utils/tabcomplete.c b/pgadmin/utils/tabcomplete.c
index f8cba05..853915f 100644
--- a/pgadmin/utils/tabcomplete.c
+++ b/pgadmin/utils/tabcomplete.c
@@ -408,8 +408,8 @@ static char *complete_filename()
 /*
  * Entrypoint from the C++ world
  */
-char *tab_complete(const char *allstr, const int startptr, const int endptr, void *dbptr)
+char *tab_complete(const char *allstr, const int startptr, const int endptr, void *dbptr, int dbvmajor, int dbvminor)
 {
 	rl_line_buffer = (char *)allstr;
-	return psql_completion((char *)(allstr + startptr), startptr,endptr,dbptr);
+	return psql_completion((char *)(allstr + startptr), startptr,endptr,dbptr,dbvmajor,dbvminor);
 }
-- 
2.7.2.windows.1

