commit 89afdd3b4593a01e06095aed6ce49ac86ce2d8d1
Author: kuroda.hayato%40jp.fujitsu.com <kuroda.hayato@jp.fujitsu.com>
Date:   Fri Mar 5 01:44:44 2021 +0000

    allow ipv6

diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml
index 9310a71166..4708974fb2 100644
--- a/doc/src/sgml/ecpg.sgml
+++ b/doc/src/sgml/ecpg.sgml
@@ -163,6 +163,15 @@ EXEC SQL CONNECT TO <replaceable>target</replaceable> <optional>AS <replaceable>
    target directly.
   </para>
 
+  <para>
+   Same as libpq, the host part can be specified an IPv6 address.
+   To specify an IPv6 address, enclose it in square brackets:
+  </para>
+
+<programlisting>
+EXEC SQL CONNECT TO 'tcp:postgresql://[2001:db8::1234]/database'
+</programlisting>
+
   <para>
    There are also different ways to specify the user name:
 
diff --git a/src/interfaces/ecpg/ecpglib/connect.c b/src/interfaces/ecpg/ecpglib/connect.c
index d2a326c04a..1d8ecd5e8d 100644
--- a/src/interfaces/ecpg/ecpglib/connect.c
+++ b/src/interfaces/ecpg/ecpglib/connect.c
@@ -295,9 +295,44 @@ parse_newstyle(int lineno, char *buf, char **host, char **dbname, char **port, c
 	start = buf + prefix_len + strlen("postgresql://");
 	p = start;
 
-	/* Look ahead for possible user credentials designator */
-	while (*p && *p != ':' && *p != '/' && *p != '?')
-		++p;
+
+	/*
+	 * Look for IPv6 address.
+	 */
+	if (*p == '[')
+	{
+		start = ++p;
+		while (*p && *p != ']')
+			++p;
+		if (!*p)
+		{
+			ecpg_log("end of string reached when looking for matching \"]\" in IPv6 host address: \"%s\"\n", buf);
+			return -1;
+		}
+		if (p == start)
+		{
+			ecpg_log("IPv6 host address may not be empty: \"%s\"\n", buf);
+			return -1;
+		}
+		/* Cut off the bracket and advance */
+		*(p++) = '\0';
+
+		/*
+		 * The address may be followed by a port specifier or a slash or a
+		 * query.
+		 */
+		if (*p && *p != ':' && *p != '/' && *p != '?')
+		{
+			ecpg_log("unexpected character \"%c\" at position %d (expected \":\", \"/\" or \"?\"): \"%s]%s\"\n", *p, (int) (p - buf + 1), buf, p);
+			return -1;
+		}
+	}
+	else
+	{
+		/* Look ahead for possible user credentials designator */
+		while (*p && *p != ':' && *p != '/' && *p != '?')
+			++p;
+	}
 
 	/* Save the hostname terminator before we null it */
 	prevchar = *p;
@@ -328,7 +363,8 @@ parse_newstyle(int lineno, char *buf, char **host, char **dbname, char **port, c
 		*port = ecpg_strdup(start, lineno);
 		if (!(*port))
 		{
-			ecpg_free(*host);
+			if (!is_unix)
+				ecpg_free(*host);
 			return -1;
 		}
 		connect_params++;
@@ -352,7 +388,8 @@ parse_newstyle(int lineno, char *buf, char **host, char **dbname, char **port, c
 			*dbname = ecpg_strdup(start, lineno);
 			if (!(*dbname))
 			{
-				ecpg_free(*host);
+				if (!is_unix)
+					ecpg_free(*host);
 				ecpg_free(*port);
 				return -1;
 			}
@@ -368,7 +405,8 @@ parse_newstyle(int lineno, char *buf, char **host, char **dbname, char **port, c
 			*options = ecpg_strdup(start, lineno);
 			if (!(*options))
 			{
-				ecpg_free(*host);
+				if (!is_unix)
+					ecpg_free(*host);
 				ecpg_free(*port);
 				ecpg_free(*dbname);
 				return -1;
@@ -379,12 +417,13 @@ parse_newstyle(int lineno, char *buf, char **host, char **dbname, char **port, c
 	if (is_unix)
 	{
 		if (strcmp(*host, "localhost") &&
-			strcmp(*host, "127.0.0.1"))
+			strcmp(*host, "127.0.0.1") &&
+			strcmp(*host, "::1"))
 		{
 			/*
 			 * The alternative of using "127.0.0.1" here is deprecated
 			 * and undocumented; we'll keep it for backward
-			 * compatibility's sake, but not extend it to allow IPv6.
+			 * compatibility's sake.
 			 */
 			ecpg_log("ECPGconnect: non-localhost access via sockets on line %d\n", lineno);
 			ecpg_raise(lineno, ECPG_CONNECT, ECPG_SQLSTATE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, *dbname ? *dbname : ecpg_gettext("<DEFAULT>"));
@@ -435,8 +474,43 @@ parse_oldstyle(int lineno, char *buf, char **host, char **dbname, char **port)
 	{
 		/* hostname is found */
 		start = ++p;
-		while(*p && *p != ':')
-			++p;
+		/*
+		 * Look for IPv6 address.
+		 */
+		if (*p == '[')
+		{
+			start = ++p;
+			while (*p && *p != ']')
+				++p;
+			if (!*p)
+			{
+				ecpg_log("end of string reached when looking for matching \"]\" in IPv6 host address: \"%s\"\n", buf);
+				return -1;
+			}
+			if (p == start)
+			{
+				ecpg_log("IPv6 host address may not be empty: \"%s\"\n", buf);
+				return -1;
+			}
+			/* Cut off the bracket and advance */
+			*(p++) = '\0';
+
+			/*
+			* The address may be followed by a port specifier or a slash or a
+			* query.
+			*/
+			if (*p && *p != ':')
+			{
+				ecpg_log("unexpected character \"%c\": \"%s]%s\"\n", *p, buf, p);
+				return -1;
+			}
+		}
+		else
+		{
+			while(*p && *p != ':')
+				++p;
+
+		}
 		prevchar = *p;
 		*p = '\0';
 		if (strcmp(start, "localhost") && strcmp(start, "127.0.0.1"))
diff --git a/src/interfaces/ecpg/preproc/ecpg.tokens b/src/interfaces/ecpg/preproc/ecpg.tokens
index 8e0527fdb7..def81d453f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.tokens
+++ b/src/interfaces/ecpg/preproc/ecpg.tokens
@@ -23,4 +23,4 @@
                 S_STATIC S_SUB S_VOLATILE
                 S_TYPEDEF
 
-%token CSTRING CVARIABLE CPP_LINE IP
+%token CSTRING CVARIABLE CPP_LINE IPV4 ENCLOSEDIPV6
diff --git a/src/interfaces/ecpg/preproc/ecpg.trailer b/src/interfaces/ecpg/preproc/ecpg.trailer
index 0e4a041393..0f3ad6ef3f 100644
--- a/src/interfaces/ecpg/preproc/ecpg.trailer
+++ b/src/interfaces/ecpg/preproc/ecpg.trailer
@@ -94,7 +94,8 @@ connection_target: opt_database_name opt_server opt_port
 
 			if (strncmp($1, "unix", strlen("unix")) == 0 &&
 				strncmp($3 + strlen("//"), "localhost", strlen("localhost")) != 0 &&
-				strncmp($3 + strlen("//"), "127.0.0.1", strlen("127.0.0.1")) != 0)
+				strncmp($3 + strlen("//"), "127.0.0.1", strlen("127.0.0.1")) != 0 &&
+				strncmp($3 + strlen("//"), "[::1]", strlen("[::1]")) != 0)
 				mmerror(PARSE_ERROR, ET_ERROR, "Unix-domain sockets only work on \"localhost\" but not on \"%s\"", $3 + strlen("//"));
 
 			$$ = make3_str(make3_str(mm_strdup("\""), $1, mm_strdup(":")), $3, make3_str(make3_str($4, mm_strdup("/"), $6), $7, mm_strdup("\"")));
@@ -145,7 +146,8 @@ opt_server: server			{ $$ = $1; }
 
 server_name: ColId					{ $$ = $1; }
 		| ColId '.' server_name		{ $$ = make3_str($1, mm_strdup("."), $3); }
-		| IP						{ $$ = make_name(); }
+		| IPV4						{ $$ = make_name(); }
+		| ENCLOSEDIPV6				{ $$ = make_name(); }
 		;
 
 opt_port: ':' Iconst		{ $$ = make2_str(mm_strdup(":"), $2); }
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index 7a0356638d..e75e2d5337 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -409,7 +409,13 @@ struct			[sS][tT][rR][uU][cC][tT]
 
 exec_sql		{exec}{space}*{sql}{space}*
 ipdigit			({digit}|{digit}{digit}|{digit}{digit}{digit})
-ip				{ipdigit}\.{ipdigit}\.{ipdigit}\.{ipdigit}
+ipv4				{ipdigit}\.{ipdigit}\.{ipdigit}\.{ipdigit}
+
+/* for ipv6 */
+hexadecimal		[0-9A-Fa-f]
+onegroup		{hexadecimal}{1,4}
+ipv6			({onegroup}:){7,7}{onegroup}|({onegroup}:){1,7}:|({onegroup}:){1,6}:{onegroup}|({onegroup}:){1,5}(:{onegroup}){1,2}|({onegroup}:){1,4}(:{onegroup}){1,3}|({onegroup}:){1,3}(:{onegroup}){1,4}|({onegroup}:){1,2}(:{onegroup}){1,5}|{onegroup}:((:{onegroup}){1,6})|:((:{onegroup}){1,7}|:)|fe80:(:{hexadecimal}{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|({onegroup}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])
+enclosedipv6		"\["{ipv6}"\]"
 
 /* we might want to parse all cpp include files */
 cppinclude		{space}*#{include}{space}*
@@ -916,9 +922,13 @@ cppline			{space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+
 					return PARAM;
 				}
 
-{ip}			{
+{ipv4}			{
+					base_yylval.str = mm_strdup(yytext);
+					return IPV4;
+				}
+{enclosedipv6}	{
 					base_yylval.str = mm_strdup(yytext);
-					return IP;
+					return ENCLOSEDIPV6;
 				}
 }  /* <SQL> */
 
diff --git a/src/interfaces/ecpg/test/connect/test5.pgc b/src/interfaces/ecpg/test/connect/test5.pgc
index e712fa8778..e001997ef4 100644
--- a/src/interfaces/ecpg/test/connect/test5.pgc
+++ b/src/interfaces/ecpg/test/connect/test5.pgc
@@ -72,5 +72,18 @@ exec sql end declare section;
 	/* not connected */
 	exec sql disconnect nonexistent;
 
+	/* use IPv6 */
+	exec sql connect to 'unix:postgresql://[::1]/ecpg2_regression' as main;
+	exec sql disconnect main;
+
+	exec sql connect to unix:postgresql://[::1]/ecpg2_regression as main;
+	exec sql disconnect main;
+
+	exec sql connect to 'unix:postgresql://[::1/ecpg2_regression' as main;
+
+	exec sql connect to 'unix:postgresql://[]/ecpg2_regression' as main;
+
+	exec sql connect to 'unix:postgresql://[::1]&ecpg2_regression' as main;
+
 	return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/connect-test5.c b/src/interfaces/ecpg/test/expected/connect-test5.c
index 6ae5b589de..f3f574ac86 100644
--- a/src/interfaces/ecpg/test/expected/connect-test5.c
+++ b/src/interfaces/ecpg/test/expected/connect-test5.c
@@ -158,5 +158,32 @@ main(void)
 #line 73 "test5.pgc"
 
 
+	/* use IPv6 */
+	{ ECPGconnect(__LINE__, 0, "unix:postgresql://[::1]/ecpg2_regression" , NULL, NULL , "main", 0); }
+#line 76 "test5.pgc"
+
+	{ ECPGdisconnect(__LINE__, "main");}
+#line 77 "test5.pgc"
+
+
+	{ ECPGconnect(__LINE__, 0, "unix:postgresql://[::1]/ecpg2_regression" , NULL, NULL , "main", 0); }
+#line 79 "test5.pgc"
+
+	{ ECPGdisconnect(__LINE__, "main");}
+#line 80 "test5.pgc"
+
+
+	{ ECPGconnect(__LINE__, 0, "unix:postgresql://[::1/ecpg2_regression" , NULL, NULL , "main", 0); }
+#line 82 "test5.pgc"
+
+
+	{ ECPGconnect(__LINE__, 0, "unix:postgresql://[]/ecpg2_regression" , NULL, NULL , "main", 0); }
+#line 84 "test5.pgc"
+
+
+	{ ECPGconnect(__LINE__, 0, "unix:postgresql://[::1]&ecpg2_regression" , NULL, NULL , "main", 0); }
+#line 86 "test5.pgc"
+
+
 	return 0;
 }
diff --git a/src/interfaces/ecpg/test/expected/connect-test5.stderr b/src/interfaces/ecpg/test/expected/connect-test5.stderr
index a15f344320..dbfe31cf79 100644
--- a/src/interfaces/ecpg/test/expected/connect-test5.stderr
+++ b/src/interfaces/ecpg/test/expected/connect-test5.stderr
@@ -88,3 +88,17 @@
 [NO_PID]: sqlca: code: 0, state: 00000
 [NO_PID]: raising sqlcode -220 on line 73: connection "nonexistent" does not exist on line 73
 [NO_PID]: sqlca: code: -220, state: 08003
+[NO_PID]: ECPGconnect: opening database ecpg2_regression on <DEFAULT> port <DEFAULT>  
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_finish: connection main closed
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ECPGconnect: opening database ecpg2_regression on <DEFAULT> port <DEFAULT>  
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_finish: connection main closed
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: end of string reached when looking for matching "]" in IPv6 host address: "unix:postgresql://[::1/ecpg2_regression"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: IPv6 host address may not be empty: "unix:postgresql://[]/ecpg2_regression"
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: unexpected character "&" at position 24 (expected ":", "/" or "?"): "unix:postgresql://[::1]&ecpg2_regression"
+[NO_PID]: sqlca: code: 0, state: 00000
