From 9603a32594d2f5e6d9a1f098bc554a68f44ccb3c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 17 Apr 2016 11:37:58 -0400
Subject: [PATCH] Avoid code duplication in \crosstabview.

In commit 6f0d6a507 I added a duplicate copy of psqlscanslash's identifier
downcasing code, but actually it's not hard to split that out as a callable
subroutine and avoid the duplication.
---
 src/bin/psql/crosstabview.c  | 30 ++-----------
 src/bin/psql/psqlscanslash.h |  2 +
 src/bin/psql/psqlscanslash.l | 84 ++++++++++++++++++++++--------------
 3 files changed, 56 insertions(+), 60 deletions(-)

diff --git a/src/bin/psql/crosstabview.c b/src/bin/psql/crosstabview.c
index 71abaf3a6f..7685c6e746 100644
--- a/src/bin/psql/crosstabview.c
+++ b/src/bin/psql/crosstabview.c
@@ -12,6 +12,7 @@
 #include "common.h"
 #include "crosstabview.h"
 #include "pqexpbuffer.h"
+#include "psqlscanslash.h"
 #include "settings.h"
 
 
@@ -648,39 +649,14 @@ indexOfColumn(char *arg, const PGresult *res)
 	}
 	else
 	{
-		bool		inquotes = false;
-		char	   *cp = arg;
 		int			i;
 
 		/*
 		 * Dequote and downcase the column name.  By checking for all-digits
 		 * before doing this, we can ensure that a quoted name is treated as a
-		 * name even if it's all digits.  This transformation should match
-		 * what psqlscanslash.l does in OT_SQLID mode.  (XXX ideally we would
-		 * let the lexer do this, but then we couldn't tell if the name was
-		 * quoted.)
+		 * name even if it's all digits.
 		 */
-		while (*cp)
-		{
-			if (*cp == '"')
-			{
-				if (inquotes && cp[1] == '"')
-				{
-					/* Keep the first quote, remove the second */
-					cp++;
-				}
-				inquotes = !inquotes;
-				/* Collapse out quote at *cp */
-				memmove(cp, cp + 1, strlen(cp));
-				/* do not advance cp */
-			}
-			else
-			{
-				if (!inquotes)
-					*cp = pg_tolower((unsigned char) *cp);
-				cp += PQmblen(cp, pset.encoding);
-			}
-		}
+		dequote_downcase_identifier(arg, true, pset.encoding);
 
 		/* Now look for match(es) among res' column names */
 		idx = -1;
diff --git a/src/bin/psql/psqlscanslash.h b/src/bin/psql/psqlscanslash.h
index 48553647a9..f078f698e8 100644
--- a/src/bin/psql/psqlscanslash.h
+++ b/src/bin/psql/psqlscanslash.h
@@ -32,4 +32,6 @@ extern char *psql_scan_slash_option(PsqlScanState state,
 
 extern void psql_scan_slash_command_end(PsqlScanState state);
 
+extern void dequote_downcase_identifier(char *str, bool downcase, int encoding);
+
 #endif   /* PSQLSCANSLASH_H */
diff --git a/src/bin/psql/psqlscanslash.l b/src/bin/psql/psqlscanslash.l
index e3e0db3b2f..90854afeb0 100644
--- a/src/bin/psql/psqlscanslash.l
+++ b/src/bin/psql/psqlscanslash.l
@@ -566,42 +566,15 @@ psql_scan_slash_option(PsqlScanState state,
 
 			/*
 			 * If SQL identifier processing was requested, then we strip out
-			 * excess double quotes and downcase unquoted letters.
-			 * Doubled double-quotes become output double-quotes, per spec.
-			 *
-			 * Note that a string like FOO"BAR"BAZ will be converted to
-			 * fooBARbaz; this is somewhat inconsistent with the SQL spec,
-			 * which would have us parse it as several identifiers.  But
-			 * for psql's purposes, we want a string like "foo"."bar" to
-			 * be treated as one option, so there's little choice.
+			 * excess double quotes and optionally downcase unquoted letters.
 			 */
 			if (type == OT_SQLID || type == OT_SQLIDHACK)
 			{
-				bool		inquotes = false;
-				char	   *cp = mybuf.data;
-
-				while (*cp)
-				{
-					if (*cp == '"')
-					{
-						if (inquotes && cp[1] == '"')
-						{
-							/* Keep the first quote, remove the second */
-							cp++;
-						}
-						inquotes = !inquotes;
-						/* Collapse out quote at *cp */
-						memmove(cp, cp + 1, strlen(cp));
-						mybuf.len--;
-						/* do not advance cp */
-					}
-					else
-					{
-						if (!inquotes && type == OT_SQLID)
-							*cp = pg_tolower((unsigned char) *cp);
-						cp += PQmblen(cp, state->encoding);
-					}
-				}
+				dequote_downcase_identifier(mybuf.data,
+											(type != OT_SQLIDHACK),
+											state->encoding);
+				/* update mybuf.len for possible shortening */
+				mybuf.len = strlen(mybuf.data);
 			}
 			break;
 		case xslashquote:
@@ -667,6 +640,51 @@ psql_scan_slash_command_end(PsqlScanState state)
 	psql_scan_reselect_sql_lexer(state);
 }
 
+/*
+ * De-quote and optionally downcase a SQL identifier.
+ *
+ * The string at *str is modified in-place; it can become shorter,
+ * but not longer.
+ *
+ * If downcase is true then non-quoted letters are folded to lower case.
+ * Ideally this behavior will match the backend's downcase_identifier();
+ * but note that it could differ if LC_CTYPE is different in the frontend.
+ *
+ * Note that a string like FOO"BAR"BAZ will be converted to fooBARbaz;
+ * this is somewhat inconsistent with the SQL spec, which would have us
+ * parse it as several identifiers.  But for psql's purposes, we want a
+ * string like "foo"."bar" to be treated as one option, so there's little
+ * choice; this routine doesn't get to change the token boundaries.
+ */
+void
+dequote_downcase_identifier(char *str, bool downcase, int encoding)
+{
+	bool		inquotes = false;
+	char	   *cp = str;
+
+	while (*cp)
+	{
+		if (*cp == '"')
+		{
+			if (inquotes && cp[1] == '"')
+			{
+				/* Keep the first quote, remove the second */
+				cp++;
+			}
+			inquotes = !inquotes;
+			/* Collapse out quote at *cp */
+			memmove(cp, cp + 1, strlen(cp));
+			/* do not advance cp */
+		}
+		else
+		{
+			if (downcase && !inquotes)
+				*cp = pg_tolower((unsigned char) *cp);
+			cp += PQmblen(cp, encoding);
+		}
+	}
+}
+
 /*
  * Evaluate a backticked substring of a slash command's argument.
  *
-- 
2.40.0