diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c
index 2bc065adcff044155b04dc65eafc65aa50592db0..ccd9a3ef611a5fd2f0b48af4c76de34b2b0a7a9d 100644
--- a/src/bin/psql/input.c
+++ b/src/bin/psql/input.c
@@ -53,12 +53,17 @@ static void finishInput(void);
  * gets_interactive()
  *
  * Gets a line of interactive input, using readline if desired.
+ *
+ * prompt: the prompt string to be used
+ * query_buf: buffer containing lines already read in the current command
+ * (query_buf is not modified here, but may be consulted for tab completion)
+ *
  * The result is a malloc'd string.
  *
  * Caller *must* have set up sigint_interrupt_jmp before calling.
  */
 char *
-gets_interactive(const char *prompt)
+gets_interactive(const char *prompt, PQExpBuffer query_buf)
 {
 #ifdef USE_READLINE
 	if (useReadline)
@@ -76,6 +81,9 @@ gets_interactive(const char *prompt)
 		rl_reset_screen_size();
 #endif
 
+		/* Make current query_buf available to tab completion callback */
+		tab_completion_query_buf = query_buf;
+
 		/* Enable SIGINT to longjmp to sigint_interrupt_jmp */
 		sigint_interrupt_enabled = true;
 
@@ -85,6 +93,9 @@ gets_interactive(const char *prompt)
 		/* Disable SIGINT again */
 		sigint_interrupt_enabled = false;
 
+		/* Pure neatnik-ism */
+		tab_completion_query_buf = NULL;
+
 		return result;
 	}
 #endif
diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h
index abd7012d1083fb76bf525945713978c0136f9e86..5464cd6b854b1d49908e79783527a391c70130d3 100644
--- a/src/bin/psql/input.h
+++ b/src/bin/psql/input.h
@@ -12,7 +12,7 @@
  * If some other file needs to have access to readline/history, include this
  * file and save yourself all this work.
  *
- * USE_READLINE is the definite pointers regarding existence or not.
+ * USE_READLINE is what to conditionalize readline-dependent code on.
  */
 #ifdef HAVE_LIBREADLINE
 #define USE_READLINE 1
@@ -38,14 +38,14 @@
 #include "pqexpbuffer.h"
 
 
-char	   *gets_interactive(const char *prompt);
-char	   *gets_fromFile(FILE *source);
+extern char *gets_interactive(const char *prompt, PQExpBuffer query_buf);
+extern char *gets_fromFile(FILE *source);
 
-void		initializeInput(int flags);
+extern void initializeInput(int flags);
 
-bool		printHistory(const char *fname, unsigned short int pager);
+extern bool printHistory(const char *fname, unsigned short int pager);
 
-void		pg_append_history(const char *s, PQExpBuffer history_buf);
-void		pg_send_history(PQExpBuffer history_buf);
+extern void pg_append_history(const char *s, PQExpBuffer history_buf);
+extern void pg_send_history(PQExpBuffer history_buf);
 
 #endif   /* INPUT_H */
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
index b6cef94272c1646f367bd114c4b6c4e00f5352a4..cb864579f329d8f2cedd5496a31add58d49b1159 100644
--- a/src/bin/psql/mainloop.c
+++ b/src/bin/psql/mainloop.c
@@ -133,7 +133,7 @@ MainLoop(FILE *source)
 			/* May need to reset prompt, eg after \r command */
 			if (query_buf->len == 0)
 				prompt_status = PROMPT_READY;
-			line = gets_interactive(get_prompt(prompt_status));
+			line = gets_interactive(get_prompt(prompt_status), query_buf);
 		}
 		else
 		{
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 731df2a5c06ed08997b3fe2ecae867a77624d0d1..1db56ee8fffaf22a0ac975e7c312b819e2c9bd27 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -24,19 +24,10 @@
  *	 set disable-completion on
  *	 $endif
  *
- * See `man 3 readline' or `info readline' for the full details. Also,
- * hence the
+ * See `man 3 readline' or `info readline' for the full details.
  *
  * BUGS:
- *
- * - If you split your queries across lines, this whole thing gets
- *	 confused. (To fix this, one would have to read psql's query
- *	 buffer rather than readline's line buffer, which would require
- *	 some major revisions of things.)
- *
- * - Table or attribute names with spaces in it may confuse it.
- *
- * - Quotes, parenthesis, and other funny characters are not handled
+ * - Quotes, parentheses, and other funny characters are not handled
  *	 all that gracefully.
  *----------------------------------------------------------------------
  */
@@ -69,6 +60,13 @@ extern char *filename_completion_function();
 /* word break characters */
 #define WORD_BREAKS		"\t\n@$><=;|&{() "
 
+/*
+ * Since readline doesn't let us pass any state through to the tab completion
+ * callback, we have to use this global variable to let get_previous_words()
+ * get at the previous lines of the current command.  Ick.
+ */
+PQExpBuffer tab_completion_query_buf = NULL;
+
 /*
  * This struct is used to define "schema queries", which are custom-built
  * to obtain possibly-schema-qualified names of database objects.  There is
@@ -923,7 +921,7 @@ static char *pg_strdup_keyword_case(const char *s, const char *ref);
 static char *escape_string(const char *text);
 static PGresult *exec_query(const char *query);
 
-static int	get_previous_words(int point, char **previous_words, int nwords);
+static char **get_previous_words(int point, char **buffer, int *nwords);
 
 static char *get_guctype(const char *varname);
 
@@ -1027,13 +1025,22 @@ psql_completion(const char *text, int start, int end)
 	/* This is the variable we'll return. */
 	char	  **matches = NULL;
 
-	/* This array will contain some scannage of the input line. */
-	char	   *previous_words[9];
+	/* Workspace for parsed words. */
+	char	   *words_buffer;
+
+	/* This array will contain pointers to parsed words. */
+	char	  **previous_words;
 
 	/* The number of words found on the input line. */
 	int			previous_words_count;
 
-	/* For compactness, we use these macros to reference previous_words[]. */
+	/*
+	 * For compactness, we use these macros to reference previous_words[].
+	 * Caution: do not access a previous_words[] entry without having checked
+	 * previous_words_count to be sure it's valid.  In most cases below, that
+	 * check is implicit in a TailMatches() or similar macro, but in some
+	 * places we have to check it explicitly.
+	 */
 #define prev_wd   (previous_words[0])
 #define prev2_wd  (previous_words[1])
 #define prev3_wd  (previous_words[2])
@@ -1133,9 +1140,7 @@ psql_completion(const char *text, int start, int end)
 
 	/*
 	 * Macros for matching N words at the start of the line, regardless of
-	 * what is after them.  These are pretty broken because they can only look
-	 * back lengthof(previous_words) words, but we'll worry about improving
-	 * their implementation some other day.
+	 * what is after them.
 	 */
 #define HeadMatches1(p1) \
 	(previous_words_count >= 1 && \
@@ -1194,13 +1199,13 @@ psql_completion(const char *text, int start, int end)
 	completion_info_charp2 = NULL;
 
 	/*
-	 * Scan the input line before our current position for the last few words.
+	 * Scan the input line to extract the words before our current position.
 	 * According to those we'll make some smart decisions on what the user is
 	 * probably intending to type.
 	 */
-	previous_words_count = get_previous_words(start,
-											  previous_words,
-											  lengthof(previous_words));
+	previous_words = get_previous_words(start,
+										&words_buffer,
+										&previous_words_count);
 
 	/* If current word is a backslash command, offer completions for that */
 	if (text[0] == '\\')
@@ -1692,7 +1697,7 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST(list_TABLEOPTIONS);
 	}
-	else if (TailMatches4("REPLICA", "IDENTITY", "USING", "INDEX"))
+	else if (TailMatches5(MatchAny, "REPLICA", "IDENTITY", "USING", "INDEX"))
 	{
 		completion_info_charp = prev5_wd;
 		COMPLETE_WITH_QUERY(Query_for_index_of_table);
@@ -2806,7 +2811,9 @@ psql_completion(const char *text, int start, int end)
 		if (!recognized_connection_string(text))
 			COMPLETE_WITH_QUERY(Query_for_list_of_databases);
 	}
-	else if (strcmp(prev2_wd, "\\connect") == 0 || strcmp(prev2_wd, "\\c") == 0)
+	else if (previous_words_count >= 2 &&
+			 (strcmp(prev2_wd, "\\connect") == 0 ||
+			  strcmp(prev2_wd, "\\c") == 0))
 	{
 		if (!recognized_connection_string(prev_wd))
 			COMPLETE_WITH_QUERY(Query_for_list_of_roles);
@@ -2891,7 +2898,8 @@ psql_completion(const char *text, int start, int end)
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
-	else if (strcmp(prev2_wd, "\\pset") == 0)
+	else if (previous_words_count >= 2 &&
+			 strcmp(prev2_wd, "\\pset") == 0)
 	{
 		if (strcmp(prev_wd, "format") == 0)
 		{
@@ -2927,7 +2935,8 @@ psql_completion(const char *text, int start, int end)
 	{
 		matches = complete_from_variables(text, "", "", false);
 	}
-	else if (strcmp(prev2_wd, "\\set") == 0)
+	else if (previous_words_count >= 2 &&
+			 strcmp(prev2_wd, "\\set") == 0)
 	{
 		static const char *const boolean_value_list[] =
 		{"on", "off", NULL};
@@ -3048,12 +3057,8 @@ psql_completion(const char *text, int start, int end)
 	}
 
 	/* free storage */
-	{
-		int			i;
-
-		for (i = 0; i < lengthof(previous_words); i++)
-			free(previous_words[i]);
-	}
+	free(previous_words);
+	free(words_buffer);
 
 	/* Return our Grand List O' Matches */
 	return matches;
@@ -3632,30 +3637,80 @@ exec_query(const char *query)
 
 
 /*
- * Return the nwords word(s) before point.  Words are returned right to left,
- * that is, previous_words[0] gets the last word before point.
- * If we run out of words, remaining array elements are set to empty strings.
- * Each array element is filled with a malloc'd string.
- * Returns the number of words that were actually found (up to nwords).
+ * Parse all the word(s) before point.
+ *
+ * Returns a malloc'd array of character pointers that point into the malloc'd
+ * data array returned to *buffer; caller must free() both of these when done.
+ * *nwords receives the number of words found, ie, the valid length of the
+ * return array.
+ *
+ * Words are returned right to left, that is, previous_words[0] gets the last
+ * word before point, previous_words[1] the next-to-last, etc.
  */
-static int
-get_previous_words(int point, char **previous_words, int nwords)
+static char **
+get_previous_words(int point, char **buffer, int *nwords)
 {
-	const char *buf = rl_line_buffer;	/* alias */
+	char	  **previous_words;
+	char	   *buf;
+	int			buflen;
 	int			words_found = 0;
 	int			i;
 
-	/* first we look for a non-word char before the current point */
+	/*
+	 * Construct a writable buffer including both preceding and current lines
+	 * of the query, up to "point" which is where the currently completable
+	 * word begins.  Because our definition of "word" is such that a non-word
+	 * character must end each word, we can use this buffer to return the word
+	 * data as-is, by placing a '\0' after each word.
+	 */
+	buflen = point + 1;
+	if (tab_completion_query_buf)
+		buflen += tab_completion_query_buf->len + 1;
+	*buffer = buf = pg_malloc(buflen);
+	i = 0;
+	if (tab_completion_query_buf)
+	{
+		memcpy(buf, tab_completion_query_buf->data,
+			   tab_completion_query_buf->len);
+		i += tab_completion_query_buf->len;
+		buf[i++] = '\n';
+	}
+	memcpy(buf + i, rl_line_buffer, point);
+	i += point;
+	buf[i] = '\0';
+
+	/* Readjust point to reference appropriate offset in buf */
+	point = i;
+
+	/*
+	 * Allocate array of word start points.  There can be at most length/2 + 1
+	 * words in the buffer.
+	 */
+	previous_words = (char **) pg_malloc((point / 2 + 1) * sizeof(char *));
+
+	/*
+	 * First we look for a non-word char before the current point.  (This is
+	 * probably useless, if readline is on the same page as we are about what
+	 * is a word, but if so it's cheap.)
+	 */
 	for (i = point - 1; i >= 0; i--)
+	{
 		if (strchr(WORD_BREAKS, buf[i]))
 			break;
+	}
 	point = i;
 
-	while (nwords-- > 0)
+	/*
+	 * Now parse words, working backwards, until we hit start of line.  The
+	 * backwards scan has some interesting but intentional properties
+	 * concerning parenthesis handling.
+	 */
+	while (point >= 0)
 	{
 		int			start,
 					end;
-		char	   *s;
+		bool		inquotes = false;
+		int			parentheses = 0;
 
 		/* now find the first non-space which then constitutes the end */
 		end = -1;
@@ -3667,59 +3722,46 @@ get_previous_words(int point, char **previous_words, int nwords)
 				break;
 			}
 		}
+		/* if no end found, we're done */
+		if (end < 0)
+			break;
 
 		/*
-		 * If no end found we return an empty string, because there is no word
-		 * before the point
+		 * Otherwise we now look for the start.  The start is either the last
+		 * character before any word-break character going backwards from the
+		 * end, or it's simply character 0.  We also handle open quotes and
+		 * parentheses.
 		 */
-		if (end < 0)
+		for (start = end; start > 0; start--)
 		{
-			point = end;
-			s = pg_strdup("");
-		}
-		else
-		{
-			/*
-			 * Otherwise we now look for the start. The start is either the
-			 * last character before any word-break character going backwards
-			 * from the end, or it's simply character 0. We also handle open
-			 * quotes and parentheses.
-			 */
-			bool		inquotes = false;
-			int			parentheses = 0;
-
-			for (start = end; start > 0; start--)
+			if (buf[start] == '"')
+				inquotes = !inquotes;
+			if (!inquotes)
 			{
-				if (buf[start] == '"')
-					inquotes = !inquotes;
-				if (!inquotes)
+				if (buf[start] == ')')
+					parentheses++;
+				else if (buf[start] == '(')
 				{
-					if (buf[start] == ')')
-						parentheses++;
-					else if (buf[start] == '(')
-					{
-						if (--parentheses <= 0)
-							break;
-					}
-					else if (parentheses == 0 &&
-							 strchr(WORD_BREAKS, buf[start - 1]))
+					if (--parentheses <= 0)
 						break;
 				}
+				else if (parentheses == 0 &&
+						 strchr(WORD_BREAKS, buf[start - 1]))
+					break;
 			}
-
-			point = start - 1;
-
-			/* make a copy of chars from start to end inclusive */
-			s = pg_malloc(end - start + 2);
-			strlcpy(s, &buf[start], end - start + 2);
-
-			words_found++;
 		}
 
-		*previous_words++ = s;
+		/* Return the word located at start to end inclusive */
+		previous_words[words_found] = &buf[start];
+		buf[end + 1] = '\0';
+		words_found++;
+
+		/* Continue searching */
+		point = start - 1;
 	}
 
-	return words_found;
+	*nwords = words_found;
+	return previous_words;
 }
 
 /*
diff --git a/src/bin/psql/tab-complete.h b/src/bin/psql/tab-complete.h
index 9dcd7e742db244218503fe0eb0743556b413e441..0e9a4302c70885168b035f8d6f7d75920414be0c 100644
--- a/src/bin/psql/tab-complete.h
+++ b/src/bin/psql/tab-complete.h
@@ -8,8 +8,10 @@
 #ifndef TAB_COMPLETE_H
 #define TAB_COMPLETE_H
 
-#include "postgres_fe.h"
+#include "pqexpbuffer.h"
 
-void		initialize_readline(void);
+extern PQExpBuffer tab_completion_query_buf;
 
-#endif
+extern void initialize_readline(void);
+
+#endif   /* TAB_COMPLETE_H */