psql tab-completion for new syntax

Started by Takahiro Itagakialmost 16 years ago2 messages
#1Takahiro Itagaki
itagaki.takahiro@oss.ntt.co.jp
1 attachment(s)

We've added some new syntax in HEAD, but psql tab-completion is out of sync.
Here is a patch to support the following syntax in tab-completion:

- top-level DO
- ALTER TABLE/INDEX/TABLESPACE SET/RESET with options
- ALTER TABLE ALTER COLUMN SET/RESET with options
- CREATE TRIGGER with events

The fix is not a stopper to alpha release, but I'd like to
add it before beta release. Comments welcome.

Regards,
---
Takahiro Itagaki
NTT Open Source Software Center

Attachments:

psql-tab-completion_20100210.patchapplication/octet-stream; name=psql-tab-completion_20100210.patchDownload
diff -cprN head/src/bin/psql/tab-complete.c work/src/bin/psql/tab-complete.c
*** head/src/bin/psql/tab-complete.c	Tue Jan 26 11:47:08 2010
--- work/src/bin/psql/tab-complete.c	Wed Feb 10 10:57:29 2010
*************** psql_completion(char *text, int start, i
*** 618,624 ****
  	static const char *const sql_commands[] = {
  		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
! 		"DELETE FROM", "DISCARD", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
  		"REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
  		"SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TABLE", "TRUNCATE", "UNLISTEN",
--- 618,624 ----
  	static const char *const sql_commands[] = {
  		"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER",
  		"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
! 		"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", "FETCH",
  		"GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE",
  		"REASSIGN", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK",
  		"SAVEPOINT", "SELECT", "SET", "SHOW", "START", "TABLE", "TRUNCATE", "UNLISTEN",
*************** psql_completion(char *text, int start, i
*** 749,754 ****
--- 749,788 ----
  
  		COMPLETE_WITH_LIST(list_ALTERINDEX);
  	}
+ 	/* ALTER INDEX <name> SET */
+ 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "SET") == 0)
+ 	{
+ 		if (find_open_parenthesis(end))
+ 			COMPLETE_WITH_CONST("");
+ 		else
+ 		{
+ 			static const char *const list_ALTERINDEXSET[] =
+ 			{"(", "TABLESPACE", NULL};
+ 
+ 			COMPLETE_WITH_LIST(list_ALTERINDEXSET);
+ 		}
+ 	}
+ 	/* ALTER INDEX <name> RESET */
+ 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "RESET") == 0)
+ 	{
+ 		COMPLETE_WITH_CONST("(");
+ 	}
+ 	/* ALTER INDEX <foo> SET|RESET ( */
+ 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
+ 			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+ 			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+ 			 pg_strcasecmp(prev_wd, "(") == 0)
+ 	{
+ 		static const char *const list_INDEXOPTIONS[] =
+ 		{"fillfactor", "fastupdate", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
+ 	}
  
  	/* ALTER LANGUAGE <name> */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
*************** psql_completion(char *text, int start, i
*** 977,997 ****
  			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
  	{
  		static const char *const list_COLUMNALTER[] =
! 		{"TYPE", "SET", "DROP", NULL};
  
  		COMPLETE_WITH_LIST(list_COLUMNALTER);
  	}
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
  			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
! 		static const char *const list_COLUMNSET[] =
! 		{"DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
  
! 		COMPLETE_WITH_LIST(list_COLUMNSET);
  	}
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
--- 1011,1062 ----
  			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
  	{
  		static const char *const list_COLUMNALTER[] =
! 		{"TYPE", "SET", "RESET", "DROP", NULL};
  
  		COMPLETE_WITH_LIST(list_COLUMNALTER);
  	}
+ 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
  			   pg_strcasecmp(prev3_wd, "ALTER") == 0)) &&
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
! 		if (find_open_parenthesis(end))
! 			COMPLETE_WITH_CONST("");
! 		else
! 		{
! 			static const char *const list_COLUMNSET[] =
! 			{"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
! 
! 			COMPLETE_WITH_LIST(list_COLUMNSET);
! 		}
! 	}
! 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
! 	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
! 			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
! 			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
! 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
! 			 pg_strcasecmp(prev_wd, "(") == 0)
! 	{
! 		static const char *const list_COLUMNOPTIONS[] =
! 		{"n_distinct", "n_distinct_inherited", NULL};
! 
! 		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
! 	}
! 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
! 	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
! 			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
! 			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
! 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
! 			 pg_strcasecmp(prev_wd, "STORAGE") == 0)
! 	{
! 		static const char *const list_COLUMNSTORAGE[] =
! 		{"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
  
! 		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
  	}
+ 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1003,1011 ****
--- 1068,1078 ----
  
  		COMPLETE_WITH_LIST(list_COLUMNDROP);
  	}
+ 	/* ALTER TABLE <foo> CLUSTER */
  	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
  			 pg_strcasecmp(prev_wd, "CLUSTER") == 0)
  		COMPLETE_WITH_CONST("ON");
+ 	/* ALTER TABLE <foo> CLUSTER ON */
  	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
  			 pg_strcasecmp(prev2_wd, "CLUSTER") == 0 &&
  			 pg_strcasecmp(prev_wd, "ON") == 0)
*************** psql_completion(char *text, int start, i
*** 1017,1026 ****
  	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
! 		static const char *const list_TABLESET[] =
! 		{"WITHOUT", "TABLESPACE", "SCHEMA", NULL};
  
! 		COMPLETE_WITH_LIST(list_TABLESET);
  	}
  	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
  	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
--- 1084,1098 ----
  	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
! 		if (find_open_parenthesis(end))
! 			COMPLETE_WITH_CONST("");
! 		else
! 		{
! 			static const char *const list_TABLESET[] =
! 			{"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
  
! 			COMPLETE_WITH_LIST(list_TABLESET);
! 		}
  	}
  	/* If we have TABLE <sth> SET TABLESPACE provide a list of tablespaces */
  	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1037,1051 ****
  
  		COMPLETE_WITH_LIST(list_TABLESET2);
  	}
! 	/* we have ALTER TABLESPACE, so suggest RENAME TO, OWNER TO */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
  	{
  		static const char *const list_ALTERTSPC[] =
! 		{"RENAME TO", "OWNER TO", NULL};
  
  		COMPLETE_WITH_LIST(list_ALTERTSPC);
  	}
  	/* ALTER TEXT SEARCH */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
--- 1109,1176 ----
  
  		COMPLETE_WITH_LIST(list_TABLESET2);
  	}
! 	/* ALTER TABLE <foo> RESET */
! 	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
! 			 pg_strcasecmp(prev_wd, "RESET") == 0)
! 	{
! 		COMPLETE_WITH_CONST("(");
! 	}
! 	/* ALTER TABLE <foo> SET|RESET ( */
! 	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
! 			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
! 			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
! 			 pg_strcasecmp(prev_wd, "(") == 0)
! 	{
! 		static const char *const list_TABLEOPTIONS[] =
! 		{"fillfactor",
! 		 "autovacuum_enabled", "toast.autovacuum_enabled",
! 		 "autovacuum_vacuum_threshold", "toast.autovacuum_vacuum_threshold",
! 		 "autovacuum_vacuum_scale_factor", "toast.autovacuum_vacuum_scale_factor",
! 		 "autovacuum_analyze_threshold", "toast.autovacuum_analyze_threshold",
! 		 "autovacuum_analyze_scale_factor", "toast.autovacuum_analyze_scale_factor",
! 		 "autovacuum_vacuum_cost_delay", "toast.autovacuum_vacuum_cost_delay",
! 		 "autovacuum_vacuum_cost_limit", "toast.autovacuum_vacuum_cost_limit",
! 		 "autovacuum_freeze_min_age", "toast.autovacuum_freeze_min_age",
! 		 "autovacuum_freeze_max_age", "toast.autovacuum_freeze_max_age",
! 		 "autovacuum_freeze_table_age", "toast.autovacuum_freeze_table_age",
! 		 NULL};
! 
! 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
! 	}
! 
! 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
  	{
  		static const char *const list_ALTERTSPC[] =
! 		{"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
  
  		COMPLETE_WITH_LIST(list_ALTERTSPC);
  	}
+ 	/* ALTER TABLESPACE <foo> SET|RESET */
+ 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
+ 			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
+ 			  pg_strcasecmp(prev_wd, "RESET") == 0))
+ 	{
+ 		if (find_open_parenthesis(end))
+ 			COMPLETE_WITH_CONST("");
+ 		else
+ 			COMPLETE_WITH_CONST("(");
+ 	}
+ 	/* ALTER TABLESPACE <foo> SET|RESET ( */
+ 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
+ 			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+ 			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+ 			 pg_strcasecmp(prev_wd, "(") == 0)
+ 	{
+ 		static const char *const list_TABLESPACEOPTIONS[] =
+ 		{"seq_page_cost", "random_page_cost", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
+ 	}
+ 
  	/* ALTER TEXT SEARCH */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1417,1422 ****
--- 1542,1558 ----
  
  		COMPLETE_WITH_LIST(list_CREATETRIGGER);
  	}
+ 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
+ 	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
+ 			  pg_strcasecmp(prev_wd, "AFTER") == 0))
+ 	{
+ 		static const char *const list_CREATETRIGGER_EVENTS[] =
+ 		{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
+ 	}
  	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
  	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
  			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1428,1433 ****
--- 1564,1578 ----
  
  		COMPLETE_WITH_LIST(list_CREATETRIGGER2);
  	}
+ 	/* complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of tables */
+ 	else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
+ 			 (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
+ 			  pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
+ 	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0)
+ 		COMPLETE_WITH_CONST("PROCEDURE");
  
  /* CREATE ROLE,USER,GROUP */
  	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1487,1492 ****
--- 1632,1638 ----
  		COMPLETE_WITH_LIST(list_DECLARE);
  	}
  
+ /* CURSOR */
  	else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
  	{
  		static const char *const list_DECLARECURSOR[] =
*************** psql_completion(char *text, int start, i
*** 1700,1711 ****
  	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
  			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
  	{
! 		static const char *const list_privileg[] =
  		{"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
  			"TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
  		"ALL", NULL};
  
! 		COMPLETE_WITH_LIST(list_privileg);
  	}
  	/* Complete GRANT/REVOKE <sth> with "ON" */
  	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
--- 1846,1857 ----
  	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
  			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
  	{
! 		static const char *const list_privilege[] =
  		{"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
  			"TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
  		"ALL", NULL};
  
! 		COMPLETE_WITH_LIST(list_privilege);
  	}
  	/* Complete GRANT/REVOKE <sth> with "ON" */
  	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
*************** psql_completion(char *text, int start, i
*** 1735,1742 ****
  								   " UNION SELECT 'LARGE OBJECT'"
  								   " UNION SELECT 'SCHEMA'"
  								   " UNION SELECT 'TABLESPACE'");
  
! 	/* Complete "GRANT/REVOKE * ON * " with "TO" */
  	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
  			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
  			 pg_strcasecmp(prev2_wd, "ON") == 0)
--- 1881,1898 ----
  								   " UNION SELECT 'LARGE OBJECT'"
  								   " UNION SELECT 'SCHEMA'"
  								   " UNION SELECT 'TABLESPACE'");
+ 	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
+ 			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
+ 			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+ 	{
+ 		static const char *const list_privilege_foreign[] =
+ 		{"DATA WRAPPER", "SERVER", NULL};
  
! 		COMPLETE_WITH_LIST(list_privilege_foreign);
! 	}
! 
! 	/* Complete "GRANT/REVOKE * ON * " with "TO/FROM" */
  	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
  			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
  			 pg_strcasecmp(prev2_wd, "ON") == 0)
*************** psql_completion(char *text, int start, i
*** 1758,1769 ****
  	}
  
  	/* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
! 	else if (pg_strcasecmp(prev3_wd, "ON") == 0 &&
! 			 ((pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
! 			   pg_strcasecmp(prev_wd, "TO") == 0) ||
! 			  (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
! 			   pg_strcasecmp(prev_wd, "FROM") == 0)))
! 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
  
  /* GROUP BY */
  	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
--- 1914,1935 ----
  	}
  
  	/* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
! 	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
! 			 pg_strcasecmp(prev3_wd, "ON") == 0)
! 	{
! 		if (pg_strcasecmp(prev_wd, "TO") == 0)
! 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
! 		else
! 			COMPLETE_WITH_CONST("TO");
! 	}
! 	else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
! 			 pg_strcasecmp(prev3_wd, "ON") == 0)
! 	{
! 		if (pg_strcasecmp(prev_wd, "FROM") == 0)
! 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
! 		else
! 			COMPLETE_WITH_CONST("FROM");
! 	}
  
  /* GROUP BY */
  	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
*************** psql_completion(char *text, int start, i
*** 2044,2049 ****
--- 2210,2216 ----
  	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
  			 pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
  			 pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
+ 			 pg_strcasecmp(prev_wd, "(") != 0 &&
  			 pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
  		COMPLETE_WITH_CONST("TO");
  	/* Suggest possible variable values */
#2Takahiro Itagaki
itagaki.takahiro@oss.ntt.co.jp
In reply to: Takahiro Itagaki (#1)
1 attachment(s)
Re: psql tab-completion for new syntax

Here is a patch to support new syntax in psql tab completion
and fix bugs to complete after an open parenthesis.

Supported additonal syntax are:
- ALTER TABLE/INDEX/TABLESPACE SET/RESET with options
- ALTER TABLE ALTER COLUMN SET/RESET with options
- ALTER TABLE ALTER COLUMN SET STORAGE
- CREATE TRIGGER with events
- CREATE INDEX CONCURRENTLY
- CREATE INDEX ON (without name)
- CREATE INDEX ... USING with pg_am.amname instead of hard-corded names.

Fixes bugs are:
Bug 1: Double parenthesis
=# INSERT INTO pgbench_history VALUES (<TAB>
=# INSERT INTO pgbench_history VALUES (( <= wrong

Bug 2: We cannot complete words if no whitespaces around a parenthesis.
=# CREATE INDEX idx ON pgbench_history( <TAB>
^ no whitespace here

Bug 3: should be completed with "(" before columns.
=# CREATE INDEX foo ON pgbench_accounts USING BTREE <TAB>
abalance aid bid filler <= wrong, should be "("

I adjusted previous_word() to split words not only with spaces but also
with non-alphabets, and removed a hack with find_open_parenthesis().

Comments?

Regards,
---
Takahiro Itagaki
NTT Open Source Software Center

Attachments:

psql-tab-completion_20100216.patchapplication/octet-stream; name=psql-tab-completion_20100216.patchDownload
diff -cprN head/src/bin/psql/tab-complete.c work/src/bin/psql/tab-complete.c
*** head/src/bin/psql/tab-complete.c	2010-02-15 11:56:18.651089000 +0900
--- work/src/bin/psql/tab-complete.c	2010-02-16 14:23:09.274053000 +0900
*************** static const SchemaQuery Query_for_list_
*** 500,505 ****
--- 500,510 ----
  "   FROM pg_catalog.pg_user_mappings "\
  "  WHERE substring(pg_catalog.quote_ident(usename),1,%d)='%s'"
  
+ #define Query_for_list_of_access_methods \
+ " SELECT pg_catalog.quote_ident(amname) "\
+ "   FROM pg_catalog.pg_am "\
+ "  WHERE substring(pg_catalog.quote_ident(amname),1,%d)='%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.
*************** static PGresult *exec_query(const char *
*** 571,578 ****
  
  static char *previous_word(int point, int skip);
  
- static int	find_open_parenthesis(int end);
- 
  #if 0
  static char *quote_file_name(char *text, int match_type, char *quote_pointer);
  static char *dequote_file_name(char *text, char quote_char);
--- 576,581 ----
*************** psql_completion(char *text, int start, i
*** 638,645 ****
  		"\\timing", "\\unset", "\\x", "\\w", "\\z", "\\!", NULL
  	};
  
- 	(void) end;					/* not used */
- 
  #ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
  	rl_completion_append_character = ' ';
  #endif
--- 641,646 ----
*************** psql_completion(char *text, int start, i
*** 749,754 ****
--- 750,782 ----
  
  		COMPLETE_WITH_LIST(list_ALTERINDEX);
  	}
+ 	/* ALTER INDEX <name> SET */
+ 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "SET") == 0)
+ 	{
+ 		static const char *const list_ALTERINDEXSET[] =
+ 		{"(", "TABLESPACE", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_ALTERINDEXSET);
+ 	}
+ 	/* ALTER INDEX <name> RESET */
+ 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "RESET") == 0)
+ 		COMPLETE_WITH_CONST("(");
+ 	/* ALTER INDEX <foo> SET|RESET ( */
+ 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
+ 			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+ 			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+ 			 pg_strcasecmp(prev_wd, "(") == 0)
+ 	{
+ 		static const char *const list_INDEXOPTIONS[] =
+ 		{"fillfactor", "fastupdate", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_INDEXOPTIONS);
+ 	}
  
  	/* ALTER LANGUAGE <name> */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
*************** psql_completion(char *text, int start, i
*** 977,986 ****
  			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
  	{
  		static const char *const list_COLUMNALTER[] =
! 		{"TYPE", "SET", "DROP", NULL};
  
  		COMPLETE_WITH_LIST(list_COLUMNALTER);
  	}
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
--- 1005,1015 ----
  			  pg_strcasecmp(prev2_wd, "ALTER") == 0))
  	{
  		static const char *const list_COLUMNALTER[] =
! 		{"TYPE", "SET", "RESET", "DROP", NULL};
  
  		COMPLETE_WITH_LIST(list_COLUMNALTER);
  	}
+ 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
*************** psql_completion(char *text, int start, i
*** 988,997 ****
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
  		static const char *const list_COLUMNSET[] =
! 		{"DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
  
  		COMPLETE_WITH_LIST(list_COLUMNSET);
  	}
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
--- 1017,1051 ----
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
  		static const char *const list_COLUMNSET[] =
! 		{"(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE", NULL};
  
  		COMPLETE_WITH_LIST(list_COLUMNSET);
  	}
+ 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
+ 	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+ 			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
+ 			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
+ 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "(") == 0)
+ 	{
+ 		static const char *const list_COLUMNOPTIONS[] =
+ 		{"n_distinct", "n_distinct_inherited", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_COLUMNOPTIONS);
+ 	}
+ 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
+ 	else if (((pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+ 			   pg_strcasecmp(prev4_wd, "COLUMN") == 0) ||
+ 			  pg_strcasecmp(prev4_wd, "ALTER") == 0) &&
+ 			 pg_strcasecmp(prev2_wd, "SET") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "STORAGE") == 0)
+ 	{
+ 		static const char *const list_COLUMNSTORAGE[] =
+ 		{"PLAIN", "EXTERNAL", "EXTENDED", "MAIN", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_COLUMNSTORAGE);
+ 	}
+ 	/* ALTER TABLE ALTER [COLUMN] <foo> DROP */
  	else if (((pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
  			   pg_strcasecmp(prev3_wd, "COLUMN") == 0) ||
  			  (pg_strcasecmp(prev5_wd, "TABLE") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1018,1024 ****
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
  		static const char *const list_TABLESET[] =
! 		{"WITHOUT", "TABLESPACE", "SCHEMA", NULL};
  
  		COMPLETE_WITH_LIST(list_TABLESET);
  	}
--- 1072,1078 ----
  			 pg_strcasecmp(prev_wd, "SET") == 0)
  	{
  		static const char *const list_TABLESET[] =
! 		{"(", "WITHOUT", "TABLESPACE", "SCHEMA", NULL};
  
  		COMPLETE_WITH_LIST(list_TABLESET);
  	}
*************** psql_completion(char *text, int start, i
*** 1037,1051 ****
  
  		COMPLETE_WITH_LIST(list_TABLESET2);
  	}
! 	/* we have ALTER TABLESPACE, so suggest RENAME TO, OWNER TO */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
  	{
  		static const char *const list_ALTERTSPC[] =
! 		{"RENAME TO", "OWNER TO", NULL};
  
  		COMPLETE_WITH_LIST(list_ALTERTSPC);
  	}
  	/* ALTER TEXT SEARCH */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
--- 1091,1163 ----
  
  		COMPLETE_WITH_LIST(list_TABLESET2);
  	}
! 	/* ALTER TABLE <foo> RESET */
! 	else if (pg_strcasecmp(prev3_wd, "TABLE") == 0 &&
! 			 pg_strcasecmp(prev_wd, "RESET") == 0)
! 		COMPLETE_WITH_CONST("(");
! 	/* ALTER TABLE <foo> SET|RESET ( */
! 	else if (pg_strcasecmp(prev4_wd, "TABLE") == 0 &&
! 			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
! 			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
! 			 pg_strcasecmp(prev_wd, "(") == 0)
! 	{
! 		static const char *const list_TABLEOPTIONS[] =
! 		{
! 			"autovacuum_analyze_scale_factor",
! 			"autovacuum_analyze_threshold",
! 			"autovacuum_enabled",
! 			"autovacuum_freeze_max_age",
! 			"autovacuum_freeze_min_age",
! 			"autovacuum_freeze_table_age",
! 			"autovacuum_vacuum_cost_delay",
! 			"autovacuum_vacuum_cost_limit",
! 			"autovacuum_vacuum_scale_factor",
! 			"autovacuum_vacuum_threshold",
! 			"fillfactor",
! 			"toast.autovacuum_analyze_scale_factor",
! 			"toast.autovacuum_analyze_threshold",
! 			"toast.autovacuum_enabled",
! 			"toast.autovacuum_freeze_max_age",
! 			"toast.autovacuum_freeze_min_age",
! 			"toast.autovacuum_freeze_table_age",
! 			"toast.autovacuum_vacuum_cost_delay",
! 			"toast.autovacuum_vacuum_cost_limit",
! 			"toast.autovacuum_vacuum_scale_factor",
! 			"toast.autovacuum_vacuum_threshold",
! 			NULL
! 		};
! 
! 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
! 	}
! 
! 	/* ALTER TABLESPACE <foo> with RENAME TO, OWNER TO, SET, RESET */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TABLESPACE") == 0)
  	{
  		static const char *const list_ALTERTSPC[] =
! 		{"RENAME TO", "OWNER TO", "SET", "RESET", NULL};
  
  		COMPLETE_WITH_LIST(list_ALTERTSPC);
  	}
+ 	/* ALTER TABLESPACE <foo> SET|RESET */
+ 	else if (pg_strcasecmp(prev4_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TABLESPACE") == 0 &&
+ 			 (pg_strcasecmp(prev_wd, "SET") == 0 ||
+ 			  pg_strcasecmp(prev_wd, "RESET") == 0))
+ 		COMPLETE_WITH_CONST("(");
+ 	/* ALTER TABLESPACE <foo> SET|RESET ( */
+ 	else if (pg_strcasecmp(prev5_wd, "ALTER") == 0 &&
+ 			 pg_strcasecmp(prev4_wd, "TABLESPACE") == 0 &&
+ 			 (pg_strcasecmp(prev2_wd, "SET") == 0 ||
+ 			  pg_strcasecmp(prev2_wd, "RESET") == 0) &&
+ 			 pg_strcasecmp(prev_wd, "(") == 0)
+ 	{
+ 		static const char *const list_TABLESPACEOPTIONS[] =
+ 		{"seq_page_cost", "random_page_cost", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_TABLESPACEOPTIONS);
+ 	}
+ 
  	/* ALTER TEXT SEARCH */
  	else if (pg_strcasecmp(prev3_wd, "ALTER") == 0 &&
  			 pg_strcasecmp(prev2_wd, "TEXT") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1282,1325 ****
  	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
  			 pg_strcasecmp(prev_wd, "UNIQUE") == 0)
  		COMPLETE_WITH_CONST("INDEX");
! 	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" */
! 	else if (pg_strcasecmp(prev2_wd, "INDEX") == 0 &&
! 			 (pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
! 			  pg_strcasecmp(prev3_wd, "UNIQUE") == 0))
! 		COMPLETE_WITH_CONST("ON");
! 	/* Complete ... INDEX <name> ON with a list of tables  */
! 	else if (pg_strcasecmp(prev3_wd, "INDEX") == 0 &&
  			 pg_strcasecmp(prev_wd, "ON") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
  
  	/*
  	 * Complete INDEX <name> ON <table> with a list of table columns (which
  	 * should really be in parens)
  	 */
! 	else if (pg_strcasecmp(prev4_wd, "INDEX") == 0 &&
  			 pg_strcasecmp(prev2_wd, "ON") == 0)
  	{
! 		if (find_open_parenthesis(end))
! 			COMPLETE_WITH_ATTR(prev_wd, "");
! 		else
! 			COMPLETE_WITH_CONST("(");
  	}
! 	else if (pg_strcasecmp(prev5_wd, "INDEX") == 0 &&
  			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
  			 pg_strcasecmp(prev_wd, "(") == 0)
  		COMPLETE_WITH_ATTR(prev2_wd, "");
  	/* same if you put in USING */
! 	else if (pg_strcasecmp(prev4_wd, "ON") == 0 &&
! 			 pg_strcasecmp(prev2_wd, "USING") == 0)
! 		COMPLETE_WITH_ATTR(prev3_wd, "");
  	/* Complete USING with an index method */
  	else if (pg_strcasecmp(prev_wd, "USING") == 0)
! 	{
! 		static const char *const index_mth[] =
! 		{"BTREE", "HASH", "GIN", "GIST", NULL};
! 
! 		COMPLETE_WITH_LIST(index_mth);
! 	}
  
  /* CREATE RULE */
  	/* Complete "CREATE RULE <sth>" with "AS" */
--- 1394,1459 ----
  	else if (pg_strcasecmp(prev2_wd, "CREATE") == 0 &&
  			 pg_strcasecmp(prev_wd, "UNIQUE") == 0)
  		COMPLETE_WITH_CONST("INDEX");
! 	/* If we have CREATE|UNIQUE INDEX, then add "ON" and existing indexes */
! 	else if (pg_strcasecmp(prev_wd, "INDEX") == 0 &&
! 			 (pg_strcasecmp(prev2_wd, "CREATE") == 0 ||
! 			  pg_strcasecmp(prev2_wd, "UNIQUE") == 0))
! 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
! 								   " UNION SELECT 'ON'"
! 								   " UNION SELECT 'CONCURRENTLY'");
! 	/* Complete ... INDEX [<name>] ON with a list of tables  */
! 	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
! 			  pg_strcasecmp(prev2_wd, "INDEX") == 0 ||
! 			  pg_strcasecmp(prev2_wd, "CONCURRENTLY") == 0) &&
  			 pg_strcasecmp(prev_wd, "ON") == 0)
  		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ 	/* If we have CREATE|UNIQUE INDEX <sth> CONCURRENTLY, then add "ON" */
+ 	else if ((pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
+ 			  pg_strcasecmp(prev2_wd, "INDEX") == 0) &&
+ 			 pg_strcasecmp(prev_wd, "CONCURRENTLY") == 0)
+ 		COMPLETE_WITH_CONST("ON");
+ 	/* If we have CREATE|UNIQUE INDEX <sth>, then add "ON" or "CONCURRENTLY" */
+ 	else if ((pg_strcasecmp(prev3_wd, "CREATE") == 0 ||
+ 			  pg_strcasecmp(prev3_wd, "UNIQUE") == 0) &&
+ 			 pg_strcasecmp(prev2_wd, "INDEX") == 0)
+ 	{
+ 		static const char *const list_CREATE_INDEX[] =
+ 		{"CONCURRENTLY", "ON", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CREATE_INDEX);
+ 	}
  
  	/*
  	 * Complete INDEX <name> ON <table> with a list of table columns (which
  	 * should really be in parens)
  	 */
! 	else if ((pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
! 			  pg_strcasecmp(prev3_wd, "INDEX") == 0 ||
! 			  pg_strcasecmp(prev3_wd, "CONCURRENTLY") == 0) &&
  			 pg_strcasecmp(prev2_wd, "ON") == 0)
  	{
! 		static const char *const list_CREATE_INDEX2[] =
! 		{"(", "USING",  NULL};
! 
! 		COMPLETE_WITH_LIST(list_CREATE_INDEX2);
  	}
! 	else if ((pg_strcasecmp(prev5_wd, "INDEX") == 0 ||
! 			  pg_strcasecmp(prev4_wd, "INDEX") == 0 ||
! 			  pg_strcasecmp(prev4_wd, "CONCURRENTLY") == 0) &&
  			 pg_strcasecmp(prev3_wd, "ON") == 0 &&
  			 pg_strcasecmp(prev_wd, "(") == 0)
  		COMPLETE_WITH_ATTR(prev2_wd, "");
  	/* same if you put in USING */
! 	else if (pg_strcasecmp(prev5_wd, "ON") == 0 &&
! 			 pg_strcasecmp(prev3_wd, "USING") == 0 &&
! 			 pg_strcasecmp(prev_wd, "(") == 0)
! 		COMPLETE_WITH_ATTR(prev4_wd, "");
  	/* Complete USING with an index method */
  	else if (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(prev2_wd, "USING") == 0)
! 		COMPLETE_WITH_CONST("(");
  
  /* CREATE RULE */
  	/* Complete "CREATE RULE <sth>" with "AS" */
*************** psql_completion(char *text, int start, i
*** 1417,1422 ****
--- 1551,1567 ----
  
  		COMPLETE_WITH_LIST(list_CREATETRIGGER);
  	}
+ 	/* complete CREATE TRIGGER <name> BEFORE,AFTER with an event */
+ 	else if (pg_strcasecmp(prev4_wd, "CREATE") == 0 &&
+ 			 pg_strcasecmp(prev3_wd, "TRIGGER") == 0 &&
+ 			 (pg_strcasecmp(prev_wd, "BEFORE") == 0 ||
+ 			  pg_strcasecmp(prev_wd, "AFTER") == 0))
+ 	{
+ 		static const char *const list_CREATETRIGGER_EVENTS[] =
+ 		{"INSERT", "DELETE", "UPDATE", "TRUNCATE", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_CREATETRIGGER_EVENTS);
+ 	}
  	/* complete CREATE TRIGGER <name> BEFORE,AFTER sth with OR,ON */
  	else if (pg_strcasecmp(prev5_wd, "CREATE") == 0 &&
  			 pg_strcasecmp(prev4_wd, "TRIGGER") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1428,1433 ****
--- 1573,1587 ----
  
  		COMPLETE_WITH_LIST(list_CREATETRIGGER2);
  	}
+ 	/* complete CREATE TRIGGER <name> BEFORE,AFTER event ON with a list of tables */
+ 	else if (pg_strcasecmp(prev5_wd, "TRIGGER") == 0 &&
+ 			 (pg_strcasecmp(prev3_wd, "BEFORE") == 0 ||
+ 			  pg_strcasecmp(prev3_wd, "AFTER") == 0) &&
+ 			 pg_strcasecmp(prev_wd, "ON") == 0)
+ 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables, NULL);
+ 	/* complete CREATE TRIGGER ... EXECUTE with PROCEDURE */
+ 	else if (pg_strcasecmp(prev_wd, "EXECUTE") == 0)
+ 		COMPLETE_WITH_CONST("PROCEDURE");
  
  /* CREATE ROLE,USER,GROUP */
  	else if (pg_strcasecmp(prev3_wd, "CREATE") == 0 &&
*************** psql_completion(char *text, int start, i
*** 1487,1492 ****
--- 1641,1647 ----
  		COMPLETE_WITH_LIST(list_DECLARE);
  	}
  
+ /* CURSOR */
  	else if (pg_strcasecmp(prev_wd, "CURSOR") == 0)
  	{
  		static const char *const list_DECLARECURSOR[] =
*************** psql_completion(char *text, int start, i
*** 1579,1599 ****
  			   pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
  		)
  	{
! 		if ((pg_strcasecmp(prev3_wd, "DROP") == 0) && (pg_strcasecmp(prev2_wd, "FUNCTION") == 0))
  		{
! 			if (find_open_parenthesis(end))
! 			{
! 				static const char func_args_query[] = "select pg_catalog.oidvectortypes(proargtypes)||')' from pg_proc where proname='%s'";
! 				char	   *tmp_buf = malloc(strlen(func_args_query) + strlen(prev_wd));
! 
! 				sprintf(tmp_buf, func_args_query, prev_wd);
! 				COMPLETE_WITH_QUERY(tmp_buf);
! 				free(tmp_buf);
! 			}
! 			else
! 			{
! 				COMPLETE_WITH_CONST("(");
! 			}
  		}
  		else
  		{
--- 1734,1743 ----
  			   pg_strcasecmp(prev2_wd, "TEMPLATE") == 0))
  		)
  	{
! 		if (pg_strcasecmp(prev3_wd, "DROP") == 0 &&
! 			pg_strcasecmp(prev2_wd, "FUNCTION") == 0)
  		{
! 			COMPLETE_WITH_CONST("(");
  		}
  		else
  		{
*************** psql_completion(char *text, int start, i
*** 1712,1723 ****
  	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
  			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
  	{
! 		static const char *const list_privileg[] =
  		{"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
  			"TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
  		"ALL", NULL};
  
! 		COMPLETE_WITH_LIST(list_privileg);
  	}
  	/* Complete GRANT/REVOKE <sth> with "ON" */
  	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
--- 1856,1867 ----
  	else if (pg_strcasecmp(prev_wd, "GRANT") == 0 ||
  			 pg_strcasecmp(prev_wd, "REVOKE") == 0)
  	{
! 		static const char *const list_privilege[] =
  		{"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES",
  			"TRIGGER", "CREATE", "CONNECT", "TEMPORARY", "EXECUTE", "USAGE",
  		"ALL", NULL};
  
! 		COMPLETE_WITH_LIST(list_privilege);
  	}
  	/* Complete GRANT/REVOKE <sth> with "ON" */
  	else if (pg_strcasecmp(prev2_wd, "GRANT") == 0 ||
*************** psql_completion(char *text, int start, i
*** 1747,1754 ****
  								   " UNION SELECT 'LARGE OBJECT'"
  								   " UNION SELECT 'SCHEMA'"
  								   " UNION SELECT 'TABLESPACE'");
  
! 	/* Complete "GRANT/REVOKE * ON * " with "TO" */
  	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
  			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
  			 pg_strcasecmp(prev2_wd, "ON") == 0)
--- 1891,1908 ----
  								   " UNION SELECT 'LARGE OBJECT'"
  								   " UNION SELECT 'SCHEMA'"
  								   " UNION SELECT 'TABLESPACE'");
+ 	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
+ 			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
+ 			 pg_strcasecmp(prev2_wd, "ON") == 0 &&
+ 			 pg_strcasecmp(prev_wd, "FOREIGN") == 0)
+ 	{
+ 		static const char *const list_privilege_foreign[] =
+ 		{"DATA WRAPPER", "SERVER", NULL};
+ 
+ 		COMPLETE_WITH_LIST(list_privilege_foreign);
+ 	}
  
! 	/* Complete "GRANT/REVOKE * ON * " with "TO/FROM" */
  	else if ((pg_strcasecmp(prev4_wd, "GRANT") == 0 ||
  			  pg_strcasecmp(prev4_wd, "REVOKE") == 0) &&
  			 pg_strcasecmp(prev2_wd, "ON") == 0)
*************** psql_completion(char *text, int start, i
*** 1770,1781 ****
  	}
  
  	/* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
! 	else if (pg_strcasecmp(prev3_wd, "ON") == 0 &&
! 			 ((pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
! 			   pg_strcasecmp(prev_wd, "TO") == 0) ||
! 			  (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
! 			   pg_strcasecmp(prev_wd, "FROM") == 0)))
! 		COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
  
  /* GROUP BY */
  	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
--- 1924,1945 ----
  	}
  
  	/* Complete "GRANT/REVOKE * ON * TO/FROM" with username, GROUP, or PUBLIC */
! 	else if (pg_strcasecmp(prev5_wd, "GRANT") == 0 &&
! 			 pg_strcasecmp(prev3_wd, "ON") == 0)
! 	{
! 		if (pg_strcasecmp(prev_wd, "TO") == 0)
! 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
! 		else
! 			COMPLETE_WITH_CONST("TO");
! 	}
! 	else if (pg_strcasecmp(prev5_wd, "REVOKE") == 0 &&
! 			 pg_strcasecmp(prev3_wd, "ON") == 0)
! 	{
! 		if (pg_strcasecmp(prev_wd, "FROM") == 0)
! 			COMPLETE_WITH_QUERY(Query_for_list_of_grant_roles);
! 		else
! 			COMPLETE_WITH_CONST("FROM");
! 	}
  
  /* GROUP BY */
  	else if (pg_strcasecmp(prev3_wd, "FROM") == 0 &&
*************** psql_completion(char *text, int start, i
*** 2056,2061 ****
--- 2220,2226 ----
  	else if (pg_strcasecmp(prev2_wd, "SET") == 0 &&
  			 pg_strcasecmp(prev4_wd, "UPDATE") != 0 &&
  			 pg_strcasecmp(prev_wd, "TABLESPACE") != 0 &&
+ 			 pg_strcasecmp(prev_wd, "(") != 0 &&
  			 pg_strcasecmp(prev4_wd, "DOMAIN") != 0)
  		COMPLETE_WITH_CONST("TO");
  	/* Suggest possible variable values */
*************** exec_query(const char *query)
*** 2767,2772 ****
--- 2932,2940 ----
   * skip that many words; e.g. skip=1 finds the word before the
   * previous one. Return value is NULL or a malloc'ed string.
   */
+ #define issep(c) \
+ 	(isspace((unsigned char) (c)) || (c) == '(' || (c) == ')')
+ 
  static char *
  previous_word(int point, int skip)
  {
*************** previous_word(int point, int skip)
*** 2776,2791 ****
  				inquotes = 0;
  	char	   *s;
  
  	while (skip-- >= 0)
  	{
- 		/* first we look for a space before the current word */
- 		for (i = point; i >= 0; i--)
- 			if (rl_line_buffer[i] == ' ')
- 				break;
- 
  		/* now find the first non-space which then constitutes the end */
! 		for (; i >= 0; i--)
! 			if (rl_line_buffer[i] != ' ')
  			{
  				end = i;
  				break;
--- 2944,2960 ----
  				inquotes = 0;
  	char	   *s;
  
+ 	/* first we look for a space before the current word */
+ 	for (i = point - 1; i >= 0; i--)
+ 		if (issep(rl_line_buffer[i]))
+ 			break;
+ 	point = i;
+ 
  	while (skip-- >= 0)
  	{
  		/* now find the first non-space which then constitutes the end */
! 		for (i = point; i >= 0; i--)
! 			if (!isspace((unsigned char) rl_line_buffer[i]))
  			{
  				end = i;
  				break;
*************** previous_word(int point, int skip)
*** 2798,2817 ****
  		if (end == -1)
  			return NULL;
  
! 		/*
! 		 * Otherwise we now look for the start. The start is either the last
! 		 * character before any space going backwards from the end, or it's
! 		 * simply character 0
! 		 */
! 		for (start = end; start > 0; start--)
  		{
! 			if (rl_line_buffer[start] == '"')
! 				inquotes = !inquotes;
! 			if ((rl_line_buffer[start - 1] == ' ') && inquotes == 0)
! 				break;
  		}
! 
! 		point = start;
  	}
  
  	/* make a copy */
--- 2967,2994 ----
  		if (end == -1)
  			return NULL;
  
! 		if (issep(rl_line_buffer[end]))
  		{
! 			/* non-alphabet separator is a single-character word */
! 			start = end;
! 			point = start - 1;
  		}
! 		else
! 		{
! 			/*
! 			 * Otherwise we now look for the start. The start is either the
! 			 * last character before any space going backwards from the end,
! 			 * or it's simply character 0
! 			 */
! 			for (start = end; start > 0; start--)
! 			{
! 				if (rl_line_buffer[start] == '"')
! 					inquotes = !inquotes;
! 				if (inquotes == 0 && issep(rl_line_buffer[start - 1]))
! 					break;
! 			}
! 		}
! 		point = start - 1;
  	}
  
  	/* make a copy */
*************** previous_word(int point, int skip)
*** 2821,2852 ****
  	return s;
  }
  
- /* Find the parenthesis after the last word */
- 
- 
- static int
- find_open_parenthesis(int end)
- {
- 	int			i = end - 1;
- 
- 	while ((rl_line_buffer[i] != ' ') && (i >= 0))
- 	{
- 		if (rl_line_buffer[i] == '(')
- 			return 1;
- 		i--;
- 	}
- 	while ((rl_line_buffer[i] == ' ') && (i >= 0))
- 	{
- 		i--;
- 	}
- 	if (rl_line_buffer[i] == '(')
- 	{
- 		return 1;
- 	}
- 	return 0;
- 
- }
- 
  #if 0
  
  /*
--- 2998,3003 ----