From 93fce2a68674e1f3ec8999b15c8526535e12fffa Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@2ndquadrant.com>
Date: Mon, 14 Sep 2020 11:29:43 -0400
Subject: [PATCH] Unify scanstr() implementations

Use single-quoted escaped strings in the BKI file, the same as those
used for GUC parameters. Rewrite scanstr() to match GUC_scanstr() and
remove the latter. Get rid of special case coding around double quotes
both in the callers of scanstr() and while formatting the input.
---
 doc/src/sgml/bki.sgml               |  6 +-
 src/backend/bootstrap/bootscanner.l |  9 +--
 src/backend/catalog/genbki.pl       |  6 +-
 src/backend/parser/scansup.c        | 34 +++++-----
 src/backend/utils/misc/guc-file.l   | 97 +----------------------------
 src/bin/initdb/initdb.c             | 34 +++-------
 6 files changed, 35 insertions(+), 151 deletions(-)

diff --git a/doc/src/sgml/bki.sgml b/doc/src/sgml/bki.sgml
index 4e696d1d3e..b7f0bccf35 100644
--- a/doc/src/sgml/bki.sgml
+++ b/doc/src/sgml/bki.sgml
@@ -752,7 +752,7 @@ $ perl  rewrite_dat_with_prokind.pl  pg_proc.dat
    next token that syntactically cannot belong to the preceding
    command starts a new one.  (Usually you would put a new command on
    a new line, for clarity.)  Tokens can be certain key words, special
-   characters (parentheses, commas, etc.), numbers, or double-quoted
+   characters (parentheses, commas, etc.), numbers, or single-quoted
    strings.  Everything is case sensitive.
   </para>
 
@@ -876,7 +876,7 @@ $ perl  rewrite_dat_with_prokind.pl  pg_proc.dat
      <para>
       NULL values can be specified using the special key word
       <literal>_null_</literal>.  Values that do not look like
-      identifiers or digit strings must be double quoted.
+      identifiers or digit strings must be single-quoted.
      </para>
     </listitem>
    </varlistentry>
@@ -1046,7 +1046,7 @@ $ perl  rewrite_dat_with_prokind.pl  pg_proc.dat
 <programlisting>
 create test_table 420 (oid = oid, cola = int4, colb = text)
 open test_table
-insert ( 421 1 "value1" )
+insert ( 421 1 'value 1' )
 insert ( 422 2 _null_ )
 close test_table
 </programlisting>
diff --git a/src/backend/bootstrap/bootscanner.l b/src/backend/bootstrap/bootscanner.l
index 1048e70d05..452dc067d0 100644
--- a/src/backend/bootstrap/bootscanner.l
+++ b/src/backend/bootstrap/bootscanner.l
@@ -66,7 +66,7 @@ static int	yyline = 1;			/* line number for error reporting */
 
 
 id		[-A-Za-z0-9_]+
-sid		\"([^\"])*\"
+sid		\'([^']|\'\')*\'
 
 /*
  * Keyword tokens return the keyword text (as a constant string) in yylval.kw,
@@ -120,14 +120,11 @@ NOT				{ yylval.kw = "NOT"; return XNOT; }
 NULL			{ yylval.kw = "NULL"; return XNULL; }
 
 {id}			{
-					yylval.str = scanstr(yytext);
+					yylval.str = pstrdup(yytext);
 					return ID;
 				}
 {sid}			{
-					/* leading and trailing quotes are not passed to scanstr */
-					yytext[strlen(yytext) - 1] = '\0';
-					yylval.str = scanstr(yytext+1);
-					yytext[strlen(yytext)] = '"';	/* restore yytext */
+					yylval.str = scanstr(yytext);
 					return ID;
 				}
 
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index dc5f442397..ef3105af44 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -845,17 +845,15 @@ sub print_bki_insert
 		# since that represents a NUL char in C code.
 		$bki_value = '' if $bki_value eq '\0';
 
-		# Handle single quotes by doubling them, and double quotes by
-		# converting them to octal escapes, because that's what the
+		# Handle single quotes by doubling them, because that's what the
 		# bootstrap scanner requires.  We do not process backslashes
 		# specially; this allows escape-string-style backslash escapes
 		# to be used in catalog data.
 		$bki_value =~ s/'/''/g;
-		$bki_value =~ s/"/\\042/g;
 
 		# Quote value if needed.  We need not quote values that satisfy
 		# the "id" pattern in bootscanner.l, currently "[-A-Za-z0-9_]+".
-		$bki_value = sprintf(qq'"%s"', $bki_value)
+		$bki_value = sprintf("'%s'", $bki_value)
 		  if length($bki_value) == 0
 		  or $bki_value =~ /[^-A-Za-z0-9_]/;
 
diff --git a/src/backend/parser/scansup.c b/src/backend/parser/scansup.c
index cac70d5df7..f35ee91d64 100644
--- a/src/backend/parser/scansup.c
+++ b/src/backend/parser/scansup.c
@@ -23,8 +23,8 @@
 /* ----------------
  *		scanstr
  *
- * if the string passed in has escaped codes, map the escape codes to actual
- * chars
+ * Strip the quotes surrounding the given string, and collapse any embedded
+ * '' sequences and backslash escapes.
  *
  * the string returned is palloc'd and should eventually be pfree'd by the
  * caller!
@@ -39,25 +39,23 @@ scanstr(const char *s)
 				i,
 				j;
 
-	if (s == NULL || s[0] == '\0')
-		return pstrdup("");
-
+	Assert(s != NULL && s[0] == '\'');
 	len = strlen(s);
+	Assert(len >= 2);
+	Assert(s[len - 1] == '\'');
+
+	/* Skip the leading quote; we'll handle the trailing quote below */
+	s++, len--;
 
-	newStr = palloc(len + 1);	/* string cannot get longer */
+	/* Since len still includes trailing quote, this is enough space */
+	newStr = palloc(len);
 
 	for (i = 0, j = 0; i < len; i++)
 	{
-		if (s[i] == '\'')
+		if (s[i] == '\'' && s[i + 1] == '\'')
 		{
-			/*
-			 * Note: if scanner is working right, unescaped quotes can only
-			 * appear in pairs, so there should be another character.
-			 */
-			i++;
-			/* The bootstrap parser is not as smart, so check here. */
-			Assert(s[i] == '\'');
-			newStr[j] = s[i];
+			/* doubled quote becomes just one quote */
+			newStr[j] = s[++i];
 		}
 		else if (s[i] == '\\')
 		{
@@ -108,7 +106,11 @@ scanstr(const char *s)
 			newStr[j] = s[i];
 		j++;
 	}
-	newStr[j] = '\0';
+
+	/* We copied the ending quote to newStr, so replace with \0 */
+	Assert(j > 0 && j <= len);
+	newStr[--j] = '\0';
+
 	return newStr;
 }
 
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index 268b745528..a73f0171b0 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -16,6 +16,7 @@
 
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "parser/scansup.h"
 #include "storage/fd.h"
 #include "utils/guc.h"
 
@@ -55,7 +56,6 @@ static void record_config_file_error(const char *errmsg,
 						 ConfigVariable **tail_p);
 
 static int	GUC_flex_fatal(const char *msg);
-static char *GUC_scanstr(const char *s);
 
 /* LCOV_EXCL_START */
 
@@ -797,7 +797,7 @@ ParseConfigFp(FILE *fp, const char *config_file, int depth, int elevel,
 			token != GUC_UNQUOTED_STRING)
 			goto parse_error;
 		if (token == GUC_STRING)	/* strip quotes and escapes */
-			opt_value = GUC_scanstr(yytext);
+			opt_value = scanstr(yytext);
 		else
 			opt_value = pstrdup(yytext);
 
@@ -1129,96 +1129,3 @@ FreeConfigVariable(ConfigVariable *item)
 		pfree(item->filename);
 	pfree(item);
 }
-
-
-/*
- *		scanstr
- *
- * Strip the quotes surrounding the given string, and collapse any embedded
- * '' sequences and backslash escapes.
- *
- * the string returned is palloc'd and should eventually be pfree'd by the
- * caller.
- */
-static char *
-GUC_scanstr(const char *s)
-{
-	char	   *newStr;
-	int			len,
-				i,
-				j;
-
-	Assert(s != NULL && s[0] == '\'');
-	len = strlen(s);
-	Assert(len >= 2);
-	Assert(s[len - 1] == '\'');
-
-	/* Skip the leading quote; we'll handle the trailing quote below */
-	s++, len--;
-
-	/* Since len still includes trailing quote, this is enough space */
-	newStr = palloc(len);
-
-	for (i = 0, j = 0; i < len; i++)
-	{
-		if (s[i] == '\\')
-		{
-			i++;
-			switch (s[i])
-			{
-				case 'b':
-					newStr[j] = '\b';
-					break;
-				case 'f':
-					newStr[j] = '\f';
-					break;
-				case 'n':
-					newStr[j] = '\n';
-					break;
-				case 'r':
-					newStr[j] = '\r';
-					break;
-				case 't':
-					newStr[j] = '\t';
-					break;
-				case '0':
-				case '1':
-				case '2':
-				case '3':
-				case '4':
-				case '5':
-				case '6':
-				case '7':
-					{
-						int			k;
-						long		octVal = 0;
-
-						for (k = 0;
-							 s[i + k] >= '0' && s[i + k] <= '7' && k < 3;
-							 k++)
-							octVal = (octVal << 3) + (s[i + k] - '0');
-						i += k - 1;
-						newStr[j] = ((char) octVal);
-					}
-					break;
-				default:
-					newStr[j] = s[i];
-					break;
-			}					/* switch */
-		}
-		else if (s[i] == '\'' && s[i + 1] == '\'')
-		{
-			/* doubled quote becomes just one quote */
-			newStr[j] = s[++i];
-		}
-		else
-			newStr[j] = s[i];
-		j++;
-	}
-
-	/* We copied the ending quote to newStr, so replace with \0 */
-	Assert(j > 0 && j <= len);
-	newStr[--j] = '\0';
-
-	return newStr;
-}
diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c
index 37e0d7ceab..e4d45f5b2f 100644
--- a/src/bin/initdb/initdb.c
+++ b/src/bin/initdb/initdb.c
@@ -331,12 +331,9 @@ escape_quotes(const char *src)
 
 /*
  * Escape a field value to be inserted into the BKI data.
- * Here, we first run the value through escape_quotes (which
- * will be inverted by the backend's scanstr() function) and
- * then overlay special processing of double quotes, which
- * bootscanner.l will only accept as data if converted to octal
- * representation ("\042").  We always wrap the value in double
- * quotes, even if that isn't strictly necessary.
+ * Run the value through escape_quotes (which will be inverted
+ * by the backend's scanstr() function), then wrap the value in
+ * single quotes, even if that isn't strictly necessary.
  */
 static char *
 escape_quotes_bki(const char *src)
@@ -345,30 +342,13 @@ escape_quotes_bki(const char *src)
 	char	   *data = escape_quotes(src);
 	char	   *resultp;
 	char	   *datap;
-	int			nquotes = 0;
 
-	/* count double quotes in data */
-	datap = data;
-	while ((datap = strchr(datap, '"')) != NULL)
-	{
-		nquotes++;
-		datap++;
-	}
-
-	result = (char *) pg_malloc(strlen(data) + 3 + nquotes * 3);
+	result = (char *) pg_malloc(strlen(data) + 3);
 	resultp = result;
-	*resultp++ = '"';
+	*resultp++ = '\'';
 	for (datap = data; *datap; datap++)
-	{
-		if (*datap == '"')
-		{
-			strcpy(resultp, "\\042");
-			resultp += 4;
-		}
-		else
-			*resultp++ = *datap;
-	}
-	*resultp++ = '"';
+		*resultp++ = *datap;
+	*resultp++ = '\'';
 	*resultp = '\0';
 
 	free(data);
-- 
2.22.0

