On 2017-09-18 02:53:03 -0700, Andres Freund wrote:
> On 2017-09-13 23:39:21 -0400, Tom Lane wrote:
> > The real problem in this area, to my mind, is that we're not testing that
> > code --- either end of it --- in any systematic way.  If it's broken it
> > could take us quite a while to notice.
>
> Independent of the thrust of my question - why aren't we adding a
> 'force-v2' option to libpq?  A test that basically does something like
> postgres[22923][1]=# \setenv PGFORCEV2 1
> postgres[22923][1]=# \c
> You are now connected to database "postgres" as user "andres".
> postgres[22924][1]=>
> seems easy enough to add, in fact I tested the above.
>
> And the protocol coverage of the v2 protocol seems small enough that a
> single not too large file ought to cover most if it quite easily.

Here's what I roughly was thinking of.  I don't quite like the name, and
the way the version is specified for libpq (basically just the "raw"
integer).   Not sure if others have an opinion on that.  I personally
would lean towards not documenting this option...

There's a few things that I couldn't find easy ways to test:
- the v2 specific binary protocol - I don't quite see how we could test
  that without writing C
- version error checks - pg_regress/psql errors out in non-interactive
  mode if a connection fails to be established. This we could verify
  with a s simple tap test.

Coverage of the relevant files is a good bit higher afterwards. Although
our libpq coverage is generally pretty damn awful.

Regards,

Andres
>From d4b9fb485110a5a27d9f85eff23a8ffca550eaf2 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Wed, 20 Sep 2017 01:12:32 -0700
Subject: [PATCH 1/3] Add ability to force libpq to negotiate a specific
 version of the protocol.

Author: Andres Freund
Discussion: https://postgr.es/m/20170918095303.g766pdnvviqle...@alap3.anarazel.de
---
 src/interfaces/libpq/fe-connect.c | 13 ++++++++++++-
 src/interfaces/libpq/libpq-int.h  |  3 +++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index c580d91135..a9dd14d48b 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -323,6 +323,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
 	offsetof(struct pg_conn, target_session_attrs)},
 
+	{"forced_protocol_version", "PGFORCEPROTOCOLVERSION", NULL, NULL,
+	 "Forced-version", "D", sizeof("Forced-version"),
+	offsetof(struct pg_conn, forced_protocol_version)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
@@ -1803,7 +1807,14 @@ connectDBStart(PGconn *conn)
 	 */
 	conn->whichhost = 0;
 	conn->addr_cur = conn->connhost[0].addrlist;
-	conn->pversion = PG_PROTOCOL(3, 0);
+	if (conn->forced_protocol_version != NULL)
+	{
+		conn->pversion = atoi(conn->forced_protocol_version);
+	}
+	else
+	{
+		conn->pversion = PG_PROTOCOL(3, 0);
+	}
 	conn->send_appname = true;
 	conn->status = CONNECTION_NEEDED;
 
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 42913604e3..7bfc09ec71 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -364,6 +364,9 @@ struct pg_conn
 	/* Type of connection to make.  Possible values: any, read-write. */
 	char	   *target_session_attrs;
 
+	char	   *forced_protocol_version; /* for testing: force protocol to a
+										  * specific version */
+
 	/* Optional file to write trace info to */
 	FILE	   *Pfdebug;
 
-- 
2.14.1.536.g6867272d5b.dirty

>From da43188afcf150213212c6c419c5c87b003feb65 Mon Sep 17 00:00:00 2001
From: Andres Freund <and...@anarazel.de>
Date: Wed, 20 Sep 2017 01:10:17 -0700
Subject: [PATCH 2/3] Add minimal v2 protocol regression tests.

Author: Andres Freund
Discussion: https://postgr.es/m/20170918095303.g766pdnvviqle...@alap3.anarazel.de
---
 src/test/regress/expected/protocol.out | 252 +++++++++++++++++++++++++++++++++
 src/test/regress/parallel_schedule     |   2 +-
 src/test/regress/serial_schedule       |   1 +
 src/test/regress/sql/protocol.sql      | 124 ++++++++++++++++
 4 files changed, 378 insertions(+), 1 deletion(-)
 create mode 100644 src/test/regress/expected/protocol.out
 create mode 100644 src/test/regress/sql/protocol.sql

diff --git a/src/test/regress/expected/protocol.out b/src/test/regress/expected/protocol.out
new file mode 100644
index 0000000000..75e183ba78
--- /dev/null
+++ b/src/test/regress/expected/protocol.out
@@ -0,0 +1,252 @@
+\set ON_ERROR_STOP 0
+-- Function that returns the protocol version, as required for the protocol
+CREATE OR REPLACE FUNCTION pg_protocol(major int, minor int)
+RETURNS int
+LANGUAGE SQL
+IMMUTABLE
+AS $$
+    SELECT $1 << 16 | $2;
+$$;
+--
+-- first a few error checks about acceptance of various protocol versions
+--
+-- Can't execute failing tests these via psql -f / pg_regress, as they
+-- exit after a connection failure, therefore those are commented out.
+--
+-- -- too old protocol version
+-- SELECT pg_protocol(major => 1, minor => 0) \gset
+-- \setenv PGFORCEPROTOCOLVERSION :pg_protocol
+-- \c
+--
+-- -- check too low major, with high minor
+-- SELECT pg_protocol(major => 1, minor => 30) \gset
+-- \setenv PGFORCEPROTOCOLVERSION :pg_protocol
+-- \c
+--
+-- check 2.0 protocol, the earliest accepted
+SELECT pg_protocol(major => 2, minor => 0) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+-- check 2.10 protocol, an unknown minor
+SELECT pg_protocol(major => 2, minor => 10) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+-- check 3.0 protocol, the default version
+SELECT pg_protocol(major => 3, minor => 0) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+-- -- check 3.10 protocol, an unknown minor, will error out
+-- SELECT pg_protocol(major => 3, minor => 10) \gset
+-- \setenv PGFORCEPROTOCOLVERSION :pg_protocol
+-- \c
+--
+-- ensure some coverage for v2 protocol, which is otherwise untested
+--
+SELECT pg_protocol(major => 2, minor => 0) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+-- check that NOTICEs in the middle of a statement work
+CREATE OR REPLACE FUNCTION pg_temp.raise_notice(p_text text)
+RETURNS text
+LANGUAGE plpgsql AS $$
+BEGIN
+    RAISE NOTICE '%', p_text;
+    RETURN p_text;
+END$$;
+-- check that NOTICE in the middle of a normal statement works
+SELECT d, COALESCE(d, pg_temp.raise_notice('isnull'))
+FROM (VALUES ('notnull'), (NULL), ('notnullagain')) AS d(d);
+NOTICE:  isnull
+      d       |   coalesce   
+--------------+--------------
+ notnull      | notnull
+              | isnull
+ notnullagain | notnullagain
+(3 rows)
+
+-- check that NOTICE in the middle of a COPY works
+CREATE TEMPORARY TABLE bleat_on_null(d text CHECK(COALESCE(d, pg_temp.raise_notice('isnull')) IS NOT NULL));
+\copy bleat_on_null from stdin
+NOTICE:  isnull
+NOTICE:  isnull
+-- while at it, also test COPY OUT
+\copy bleat_on_null TO stdout
+notnull
+\N
+\N
+notnullagain
+COPY BLEAT_ON_NULL TO stdout;
+notnull
+\N
+\N
+notnullagain
+-- no columns
+SELECT;
+--
+(1 row)
+
+-- anonymous columns
+SELECT 1;
+ ?column? 
+----------
+        1
+(1 row)
+
+-- many columns with NULLs (for NULL bitmap)
+SELECT 1 c1, 2 c2, 3 c3, NULL::int c4, 5 c5, 6 c6, 7 c7, 8 c8, NULL::text c9, 10 c10;
+ c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 
+----+----+----+----+----+----+----+----+----+-----
+  1 |  2 |  3 |    |  5 |  6 |  7 |  8 |    |  10
+(1 row)
+
+-- send two sql queries in one protocol message
+SELECT 1\;SELECT 2;
+ ?column? 
+----------
+        2
+(1 row)
+
+-- a few more rows
+SELECT a, b
+FROM generate_series(1, 10) a, generate_series(1, 10) b;
+ a  | b  
+----+----
+  1 |  1
+  1 |  2
+  1 |  3
+  1 |  4
+  1 |  5
+  1 |  6
+  1 |  7
+  1 |  8
+  1 |  9
+  1 | 10
+  2 |  1
+  2 |  2
+  2 |  3
+  2 |  4
+  2 |  5
+  2 |  6
+  2 |  7
+  2 |  8
+  2 |  9
+  2 | 10
+  3 |  1
+  3 |  2
+  3 |  3
+  3 |  4
+  3 |  5
+  3 |  6
+  3 |  7
+  3 |  8
+  3 |  9
+  3 | 10
+  4 |  1
+  4 |  2
+  4 |  3
+  4 |  4
+  4 |  5
+  4 |  6
+  4 |  7
+  4 |  8
+  4 |  9
+  4 | 10
+  5 |  1
+  5 |  2
+  5 |  3
+  5 |  4
+  5 |  5
+  5 |  6
+  5 |  7
+  5 |  8
+  5 |  9
+  5 | 10
+  6 |  1
+  6 |  2
+  6 |  3
+  6 |  4
+  6 |  5
+  6 |  6
+  6 |  7
+  6 |  8
+  6 |  9
+  6 | 10
+  7 |  1
+  7 |  2
+  7 |  3
+  7 |  4
+  7 |  5
+  7 |  6
+  7 |  7
+  7 |  8
+  7 |  9
+  7 | 10
+  8 |  1
+  8 |  2
+  8 |  3
+  8 |  4
+  8 |  5
+  8 |  6
+  8 |  7
+  8 |  8
+  8 |  9
+  8 | 10
+  9 |  1
+  9 |  2
+  9 |  3
+  9 |  4
+  9 |  5
+  9 |  6
+  9 |  7
+  9 |  8
+  9 |  9
+  9 | 10
+ 10 |  1
+ 10 |  2
+ 10 |  3
+ 10 |  4
+ 10 |  5
+ 10 |  6
+ 10 |  7
+ 10 |  8
+ 10 |  9
+ 10 | 10
+(100 rows)
+
+-- check error responses
+SELECT "nonexistant-column";
+ERROR:  column "nonexistant-column" does not exist at character 8
+DO $$
+BEGIN
+    RAISE 'I am an error, what is your name?';
+END;
+$$;
+ERROR:  I am an error, what is your name?
+-- minimal largeobject test, also verifies PQfn()
+BEGIN;
+SELECT lo_creat(1) AS lo_oid \gset
+SELECT lo_open(:lo_oid, CAST(x'20000' | x'40000' AS integer)) as fd \gset
+SELECT lowrite(:fd, 'just some text');
+ lowrite 
+---------
+      14
+(1 row)
+
+SELECT loread(:fd, '100');
+ loread 
+--------
+ \x
+(1 row)
+
+SELECT lo_lseek(:fd, 0, 2);
+ lo_lseek 
+----------
+       14
+(1 row)
+
+COMMIT;
+\lo_unlink :lo_oid
+--
+-- Cleanup
+--
+DROP FUNCTION pg_protocol(int, int);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 2fd3f2b1b1..5db1a89b30 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -89,7 +89,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext
+test: alter_generic alter_operator misc psql async dbsize misc_functions sysviews tsrf tidscan stats_ext protocol
 
 # rules cannot run concurrently with any test that creates a view
 test: rules psql_crosstab amutils
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 76b0de30a7..0452943162 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -131,6 +131,7 @@ test: sysviews
 test: tsrf
 test: tidscan
 test: stats_ext
+test: protocol
 test: rules
 test: psql_crosstab
 test: select_parallel
diff --git a/src/test/regress/sql/protocol.sql b/src/test/regress/sql/protocol.sql
new file mode 100644
index 0000000000..e2f71dba25
--- /dev/null
+++ b/src/test/regress/sql/protocol.sql
@@ -0,0 +1,124 @@
+\set ON_ERROR_STOP 0
+
+-- Function that returns the protocol version, as required for the protocol
+CREATE OR REPLACE FUNCTION pg_protocol(major int, minor int)
+RETURNS int
+LANGUAGE SQL
+IMMUTABLE
+AS $$
+    SELECT $1 << 16 | $2;
+$$;
+
+--
+-- first a few error checks about acceptance of various protocol versions
+--
+-- Can't execute failing tests these via psql -f / pg_regress, as they
+-- exit after a connection failure, therefore those are commented out.
+--
+
+-- -- too old protocol version
+-- SELECT pg_protocol(major => 1, minor => 0) \gset
+-- \setenv PGFORCEPROTOCOLVERSION :pg_protocol
+-- \c
+--
+-- -- check too low major, with high minor
+-- SELECT pg_protocol(major => 1, minor => 30) \gset
+-- \setenv PGFORCEPROTOCOLVERSION :pg_protocol
+-- \c
+--
+
+-- check 2.0 protocol, the earliest accepted
+SELECT pg_protocol(major => 2, minor => 0) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+
+-- check 2.10 protocol, an unknown minor
+SELECT pg_protocol(major => 2, minor => 10) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+
+-- check 3.0 protocol, the default version
+SELECT pg_protocol(major => 3, minor => 0) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+
+-- -- check 3.10 protocol, an unknown minor, will error out
+-- SELECT pg_protocol(major => 3, minor => 10) \gset
+-- \setenv PGFORCEPROTOCOLVERSION :pg_protocol
+-- \c
+
+
+--
+-- ensure some coverage for v2 protocol, which is otherwise untested
+--
+
+SELECT pg_protocol(major => 2, minor => 0) \gset
+\setenv PGFORCEPROTOCOLVERSION :pg_protocol
+\c
+
+
+-- check that NOTICEs in the middle of a statement work
+CREATE OR REPLACE FUNCTION pg_temp.raise_notice(p_text text)
+RETURNS text
+LANGUAGE plpgsql AS $$
+BEGIN
+    RAISE NOTICE '%', p_text;
+    RETURN p_text;
+END$$;
+
+-- check that NOTICE in the middle of a normal statement works
+SELECT d, COALESCE(d, pg_temp.raise_notice('isnull'))
+FROM (VALUES ('notnull'), (NULL), ('notnullagain')) AS d(d);
+
+-- check that NOTICE in the middle of a COPY works
+CREATE TEMPORARY TABLE bleat_on_null(d text CHECK(COALESCE(d, pg_temp.raise_notice('isnull')) IS NOT NULL));
+\copy bleat_on_null from stdin
+notnull
+\N
+\N
+notnullagain
+\.
+-- while at it, also test COPY OUT
+\copy bleat_on_null TO stdout
+COPY BLEAT_ON_NULL TO stdout;
+
+-- no columns
+SELECT;
+
+-- anonymous columns
+SELECT 1;
+
+-- many columns with NULLs (for NULL bitmap)
+SELECT 1 c1, 2 c2, 3 c3, NULL::int c4, 5 c5, 6 c6, 7 c7, 8 c8, NULL::text c9, 10 c10;
+
+-- send two sql queries in one protocol message
+SELECT 1\;SELECT 2;
+
+-- a few more rows
+SELECT a, b
+FROM generate_series(1, 10) a, generate_series(1, 10) b;
+
+-- check error responses
+SELECT "nonexistant-column";
+
+DO $$
+BEGIN
+    RAISE 'I am an error, what is your name?';
+END;
+$$;
+
+
+-- minimal largeobject test, also verifies PQfn()
+BEGIN;
+SELECT lo_creat(1) AS lo_oid \gset
+SELECT lo_open(:lo_oid, CAST(x'20000' | x'40000' AS integer)) as fd \gset
+SELECT lowrite(:fd, 'just some text');
+SELECT loread(:fd, '100');
+SELECT lo_lseek(:fd, 0, 2);
+COMMIT;
+\lo_unlink :lo_oid
+
+--
+-- Cleanup
+--
+DROP FUNCTION pg_protocol(int, int);
-- 
2.14.1.536.g6867272d5b.dirty

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to