On Tue, Nov 1, 2011 at 4:31 PM, Guillaume Lelarge <[email protected]>wrote:

>
> OK, it works now. I understand a lot of improvements could come later,
> but there is one thing that bugs me. And I think it should be fixed
> before applying.
>
> Let's say I wrote this:
>
> SELECT * FROM person JOIN company ON
>
> and try autocompletion afterwards. I get the list of columns in person
> and company, which is great. The issue is that you only have the
> column's name, not the table's name. So you don't know if the column
> refers to the first table or the second. We could say it's not an issue
> if we were sure they were all different. But if I have two times the id
> column, it gets kinda weird. Maybe you should show the table's name in
> front of the column's name.
>

Allright, another week, another patch :-)

Changes since last time:
- Support for aliasses (autocompletion on "select * from person p join
company c on ...) will present a list of "p.id, p.name, c.id, c.name etc.)
- Support for queries without aliasses (select * from person where ...)
- Support for queries without aliasses but with joins (select * from person
join company on ...)
- Support for queries with mixed versions (select * from person p join
company on ...)
- Support for update statements (update person set ...)
- Support for autocompletion after SELECT (select ... from person)
- Fixed a crash

Regards,
Wander Winkelhorst.
diff -U2 -r -a ./old/pgadmin/ctl/ctlSQLBox.cpp ./new//pgadmin/ctl/ctlSQLBox.cpp
--- ./old/pgadmin/ctl/ctlSQLBox.cpp	2011-06-20 21:13:13.000000000 +0200
+++ ./new//pgadmin/ctl/ctlSQLBox.cpp	2011-11-10 14:11:18.188487304 +0100
@@ -620,5 +620,5 @@
 
 
-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 char *linestr, const int startptr, const int endptr, void *dbptr);
 void ctlSQLBox::OnAutoComplete(wxCommandEvent &rev)
 {
@@ -631,11 +631,12 @@
 
 	wxString what = GetCurLine().Left(GetCurrentPos() - PositionFromLine(GetCurrentLine()));;
+	wxString alltext = GetCurLine();
 	int spaceidx = what.Find(' ', true);
 
 	char *tab_ret;
 	if (spaceidx == -1)
-		tab_ret = tab_complete(what.mb_str(wxConvUTF8), 0, what.Len() + 1, m_database);
+		tab_ret = tab_complete(alltext.mb_str(wxConvUTF8), what.mb_str(wxConvUTF8), 0, what.Len() + 1, m_database);
 	else
-		tab_ret = tab_complete(what.mb_str(wxConvUTF8), spaceidx + 1, what.Len() + 1, m_database);
+		tab_ret = tab_complete(alltext.mb_str(wxConvUTF8), what.mb_str(wxConvUTF8), spaceidx + 1, what.Len() + 1, m_database);
 
 	if (tab_ret == NULL || tab_ret[0] == '\0')
diff -U2 -r -a ./old/pgadmin/utils/tabcomplete.c ./new//pgadmin/utils/tabcomplete.c
--- ./old/pgadmin/utils/tabcomplete.c	2011-06-20 21:13:22.000000000 +0200
+++ ./new//pgadmin/utils/tabcomplete.c	2011-11-09 15:06:25.915448665 +0100
@@ -34,4 +34,7 @@
 static char *rl_line_buffer;
 
+//This one contains all text; for autocompletion purposes:
+static char *rl_alltext_buffer;
+
 /*
  * Macros to ease typing present in psql, rewritten for our API
@@ -409,7 +412,8 @@
  * Entrypoint from the C++ world
  */
-char *tab_complete(const char *allstr, const int startptr, const int endptr, void *dbptr)
+char *tab_complete(const char *linestr, const char *allstr, const int startptr, const int endptr, void *dbptr)
 {
-	rl_line_buffer = (char *)allstr;
-	return psql_completion((char *)(allstr + startptr), startptr,endptr,dbptr);
+	rl_line_buffer = (char *)linestr;
+	rl_alltext_buffer = (char *)allstr;
+	return psql_completion((char *)(linestr + startptr), startptr,endptr,dbptr);
 }
diff -U2 -r -a ./old/pgadmin/utils/tab-complete.inc ./new//pgadmin/utils/tab-complete.inc
--- ./old/pgadmin/utils/tab-complete.inc	2011-06-20 21:13:22.000000000 +0200
+++ ./new//pgadmin/utils/tab-complete.inc	2011-11-10 14:35:23.307653263 +0100
@@ -229,4 +229,14 @@
 "   AND pg_catalog.pg_table_is_visible(c.oid)"
 
+#define Query_for_list_of_attributes_list \
+"SELECT pg_catalog.quote_ident(relname) || '.' || pg_catalog.quote_ident(attname) "\
+"  FROM pg_catalog.pg_attribute a, pg_catalog.pg_class c "\
+" WHERE c.oid = a.attrelid "\
+"   AND a.attnum > 0 "\
+"   AND NOT a.attisdropped "\
+"   AND substring(pg_catalog.quote_ident(attname),1,%d)='%s' "\
+"   AND pg_catalog.pg_table_is_visible(c.oid)"\
+"   AND pg_catalog.quote_ident(relname) in ("
+
 #define Query_for_list_of_databases \
 "SELECT pg_catalog.quote_ident(datname) FROM pg_catalog.pg_database "\
@@ -317,4 +327,15 @@
 
 /*
+ * This is a list of tables and their corrosponding tablealiasses
+ */
+
+typedef struct
+{
+	char *tablename;
+	char *tablealias;
+	void *nextAlias;
+} pgsql_alias_t;
+
+/*
  * 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.
@@ -362,4 +383,95 @@
 
 
+char * str_replace (const char *string, const char *substr, const char *replacement) {
+	char *tok = NULL;
+	char *newstr = NULL;
+	char *oldstr = NULL;
+	char *head = NULL;
+ 
+	/* if either substr or replacement is NULL, duplicate string a let caller handle it */
+	if ( (substr == NULL) || (replacement == NULL) ) return strdup(string);
+	newstr = strdup(string);
+	head = newstr;
+	while ( (tok = strstr(head, substr))) {
+		oldstr = newstr;
+		newstr = malloc( strlen(oldstr) - strlen(substr) + strlen(replacement) + 1 );
+		/*failed to alloc mem, free old string and return NULL */
+		if (newstr == NULL) {
+			free(oldstr);
+			return NULL;
+		}
+		memcpy(newstr, oldstr, tok - oldstr);
+		memcpy(newstr + (tok - oldstr), replacement, strlen(replacement) );
+		memcpy(newstr + (tok - oldstr) + strlen(replacement), tok + strlen(substr), strlen(oldstr) - strlen(substr) - (tok - oldstr) );
+		memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replacement) , 0, 1 );
+		/* move back head right after the last replacement */
+		head = newstr + (tok - oldstr) + strlen(replacement);
+		free(oldstr);
+	}
+	return newstr;
+}
+
+/*
+A function to get all attributes of a list of tables
+Used for autocompletion.
+*/
+static char * complete_with_attr_for_list(char *tablelist, char *text, void *dbptr, pgsql_alias_t *currentAlias) {
+	char* attrquery; 
+	char* result; 
+
+	char* textCopy = malloc(strlen(text) + 1);
+
+	char *p;
+	if (text[0] == ' ') {
+		//If it starts with a space, don't take the first word
+		p = "";
+	} else {
+		//It didn't start with a space, so the user must be in the middle of a word, and wants to autocomplete that.
+		strcpy(textCopy, text);
+		//Get first word of "text"
+		p = strtok(textCopy, " ");
+		if (p == NULL) {
+			p = "";
+		}
+	}
+
+	attrquery = malloc(strlen(Query_for_list_of_attributes_list) + strlen(tablelist) + 1);
+	strcpy(attrquery, Query_for_list_of_attributes_list);
+	strcat(attrquery, tablelist);
+	result = complete_from_query(p, attrquery, NULL, dbptr);
+
+	free(attrquery); 
+	free(textCopy); 
+
+	int multipleTables = 0;
+	
+	if (currentAlias != NULL) {
+		multipleTables = (currentAlias->nextAlias != NULL);
+	}
+
+	//Replace table names by their aliasses:
+	while (currentAlias != NULL) {
+		char *replaceThis = malloc(strlen(currentAlias->tablename) + 3);
+		strcpy(replaceThis, currentAlias->tablename);
+		strcat(replaceThis, ".");
+		if (currentAlias->tablealias == NULL) {
+			if (!multipleTables) {
+ 				//Not interested in table names; there is only one table in the query and it doesn't have an alias.
+				result = str_replace(result, replaceThis, "");
+			}
+		} else {
+			char *replaceWith = malloc(strlen(currentAlias->tablealias) + 3);
+			strcpy(replaceWith, currentAlias->tablealias);
+			strcat(replaceWith, ".");
+			result = str_replace(result, replaceThis, replaceWith);
+			free(replaceWith);
+		}
+		free(replaceThis);
+		currentAlias = currentAlias->nextAlias;
+	}
+
+	return result;
+}
+
 /* 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
@@ -401,4 +513,87 @@
 	(void) end;					/* not used */
 
+	char *buffer;
+	char *tablelist;
+	char *scratch;
+	buffer = malloc(strlen(rl_line_buffer) + 1);
+	tablelist = malloc(strlen(rl_line_buffer) * 2); // times 2 because stuff gets added to this.
+	strcpy (tablelist,"");
+	strcpy(buffer, rl_line_buffer);
+
+	#define START 1
+	#define TABLE 2
+	#define TABLEALIAS 3
+	int grammarState = START;
+	char *p = strtok(buffer, " ");
+
+	pgsql_alias_t *currentAlias = NULL;
+	pgsql_alias_t *firstAlias = NULL;
+
+	while (p) {
+		if (grammarState == TABLE) {
+			//Add tablename to list of tables
+			strcat(tablelist,"'");
+			scratch = malloc(strlen(p) * 2 + 1);
+			PQescapeString(scratch, p, strlen(p));
+			strcat(tablelist, scratch);
+			if (currentAlias != NULL) {
+				currentAlias->nextAlias = malloc(sizeof(pgsql_alias_t));
+				currentAlias = currentAlias->nextAlias;
+			} else {
+				currentAlias = malloc(sizeof(pgsql_alias_t));
+				firstAlias = currentAlias;
+			}
+			currentAlias->nextAlias = NULL;
+			currentAlias->tablealias = NULL;
+			currentAlias->tablename = scratch;
+			strcat(tablelist,"', ");
+
+			if (pg_strcasecmp(p, "JOIN") == 0) {
+				grammarState = TABLE;
+			} else if (pg_strcasecmp(p, "WHERE") == 0) {
+				grammarState = START;
+			} else {
+				//Must be alias :)
+				grammarState = TABLEALIAS;
+			}
+		} else if (grammarState == TABLEALIAS) {
+			if (pg_strcasecmp(p, "JOIN") == 0) {
+				grammarState = TABLE;
+			} else if (pg_strcasecmp(p, "ON") == 0) {
+				grammarState = START;
+			} else if (pg_strcasecmp(p, "WHERE") == 0) {
+				grammarState = START;
+			} else {
+				//Must be alias :)
+				if (currentAlias->tablealias == NULL) { //Only interrested in the first word after a tablename
+					scratch = malloc(strlen(p) * 2 + 1);
+					PQescapeString(scratch, p, strlen(p));
+					currentAlias->tablealias = scratch;
+				}
+
+				grammarState = TABLEALIAS;
+			}
+		} else {
+			if (pg_strcasecmp(p, "FROM") == 0) {
+				grammarState = TABLE;
+			} else if (pg_strcasecmp(p, "UPDATE") == 0) {
+				grammarState = TABLE;
+			} else if (pg_strcasecmp(p, "JOIN") == 0) {
+				grammarState = TABLE;
+			}
+		}
+			
+    		p = strtok(NULL, " ");
+	}
+
+	free(buffer);
+
+	currentAlias = firstAlias;
+	while (currentAlias != NULL) {
+		currentAlias = currentAlias->nextAlias;
+	}
+
+	strcat(tablelist,"' ')"); //Add dummy to mark end-of-list
+
 #ifdef HAVE_RL_COMPLETION_APPEND_CHARACTER
 	rl_completion_append_character = ' ';
@@ -1356,5 +1551,5 @@
 			 pg_strcasecmp(prev2_wd, "ORDER") == 0 &&
 			 pg_strcasecmp(prev_wd, "BY") == 0)
-		COMPLETE_WITH_ATTR(prev3_wd);
+		matches = complete_with_attr_for_list(tablelist, text, dbptr, firstAlias);
 
 /* PREPARE xx AS */
@@ -1389,5 +1584,8 @@
 
 /* SELECT */
-	/* naah . . . */
+	/* Sure, why not . . . */
+	else if (pg_strcasecmp(prev_wd, "SELECT") == 0) {
+		matches = complete_with_attr_for_list(tablelist, text, dbptr, firstAlias);
+	}
 
 /* SET, RESET, SHOW */
@@ -1621,5 +1819,5 @@
 	/* Simple case of the word before the where being the table name */
 	else if (pg_strcasecmp(prev_wd, "WHERE") == 0)
-		COMPLETE_WITH_ATTR(prev2_wd);
+		matches = complete_with_attr_for_list(tablelist, text, dbptr, firstAlias);
 
 /* ... FROM ... */
@@ -1630,4 +1828,25 @@
 		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
 
+/* ... JOIN ... */
+	else if (pg_strcasecmp(prev_wd, "JOIN") == 0 &&
+			 pg_strcasecmp(prev3_wd, "COPY") != 0 &&
+			 pg_strcasecmp(prev3_wd, "\\copy") != 0)
+		COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsv, NULL);
+
+/* ... ON ... */
+	else if (pg_strcasecmp(prev_wd, "ON") == 0)
+		matches = complete_with_attr_for_list(tablelist, text, dbptr, firstAlias);
+
+/* ... = ... */
+	else if (pg_strcasecmp(prev_wd, "=") == 0)
+		matches = complete_with_attr_for_list(tablelist, text, dbptr, firstAlias);
+
+/* ... AND ... */
+	else if (pg_strcasecmp(prev_wd, "AND") == 0)
+		matches = complete_with_attr_for_list(tablelist, text, dbptr, firstAlias);
+
+/* ... OR ... */
+	else if (pg_strcasecmp(prev_wd, "OR") == 0)
+		matches = complete_with_attr_for_list(tablelist, text, dbptr, firstAlias);
 
 /* Backslash commands */
@@ -1731,4 +1950,15 @@
 	free(prev4_wd);
 	free(prev5_wd);
+	free(tablelist);
+
+	currentAlias = firstAlias;
+	pgsql_alias_t *prevAlias = NULL;
+	while (currentAlias != NULL) {
+		free(currentAlias->tablename);
+		free(currentAlias->tablealias);
+		prevAlias = currentAlias;
+		currentAlias = currentAlias->nextAlias;
+		free(prevAlias);
+	}
 
 	/* Return our Grand List O' Matches */
-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

Reply via email to