Repository: GoogleCloudPlatform/pgadapter
Pull request #82 (2022-03-25 18:46:36)

feat: support pgx in extended mode
Adds support and tests for PGX in extended query mode. This also fixes
some minor issues with sending bytea values in text format. bytea values
in text format should use the newer hex format, as some newer drivers
will not understand the older octal format. Also, any '\' characters in
a literal in an array must be escaped.

The Golang PGX driver will by default use extended mode in combination
with DescribeStatement. The problem with DescribeStatement is that we
currently do not have any way of really supporting it. We therefore need
a patch in PGX to be able to work with it. However, that patch is also
valuable for other users of PGX, and hopefully we will be able to get it
merged in the main line of PGX.


Patch:
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java
index 78d1a5fe..f0d9116b 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java
@@ -31,6 +31,7 @@ import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.EOFException;
 import java.io.IOException;
 import java.net.Socket;
 import java.security.SecureRandom;
@@ -137,6 +138,18 @@ public class ConnectionHandler extends Thread {
         this.connectionMetadata = new ConnectionMetadata(input, output);
         this.message = BootstrapMessage.create(this);
         this.message.send();
+        while (this.status == ConnectionStatus.UNAUTHENTICATED) {
+          try {
+            message.nextHandler();
+            message.send();
+          } catch (EOFException eofException) {
+            // This indicates that the frontend terminated the connection before we got
+            // authenticated. This is in most cases an indication that the frontend killed the
+            // connection after having requested SSL and gotten an SSL denied message.
+            this.status = ConnectionStatus.TERMINATED;
+            break;
+          }
+        }
         while (this.status != ConnectionStatus.TERMINATED) {
           try {
             message.nextHandler();
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/ArrayParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/ArrayParser.java
index 4ebb53c0..b9e8ab45 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/ArrayParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/ArrayParser.java
@@ -122,6 +122,9 @@ public class ArrayParser extends Parser<List<?>> {
       return "NULL";
     }
     if (this.isStringEquivalent) {
+      if (value.indexOf('\\') > -1) {
+        value = value.replace("\\", "\\\\");
+      }
       return STRING_TOGGLE + value + STRING_TOGGLE;
     }
     return value;
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BinaryParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BinaryParser.java
index 878639f5..f2ad7262 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BinaryParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BinaryParser.java
@@ -20,6 +20,7 @@ import com.google.cloud.spanner.Statement;
 import java.nio.charset.StandardCharsets;
 import java.sql.SQLException;
 import java.sql.Types;
+import org.postgresql.core.Utils;
 import org.postgresql.util.PGbytea;
 
 /**
@@ -63,7 +64,7 @@ public class BinaryParser extends Parser<ByteArray> {
 
   @Override
   protected String stringParse() {
-    return this.item == null ? null : PGbytea.toPGString(this.item.toByteArray());
+    return this.item == null ? null : "\\x" + Utils.toHexString(this.item.toByteArray());
   }
 
   @Override
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
index be70082a..2098c394 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
@@ -213,7 +213,7 @@ public abstract class Parser<T> {
       case BYTES:
         return Oid.BYTEA;
       case TIMESTAMP:
-        return Oid.TIMESTAMP;
+        return Oid.TIMESTAMPTZ;
       case DATE:
         return Oid.DATE;
       case ARRAY:
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediatePortalStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediatePortalStatement.java
index 2c1ecd2d..680ac061 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediatePortalStatement.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediatePortalStatement.java
@@ -62,7 +62,7 @@ public class IntermediatePortalStatement extends IntermediatePreparedStatement {
   @Override
   public short getResultFormatCode(int index) {
     if (this.resultFormatCodes == null || this.resultFormatCodes.isEmpty()) {
-      return 0;
+      return super.getResultFormatCode(index);
     } else if (this.resultFormatCodes.size() == 1) {
       return this.resultFormatCodes.get(0);
     } else {
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionResponse.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionResponse.java
index c386a9c1..7ceb678b 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionResponse.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionResponse.java
@@ -162,7 +162,7 @@ public class RowDescriptionResponse extends WireOutput {
         return 8;
       case Oid.TIME:
         return 8;
-      case Oid.TIMESTAMP:
+      case Oid.TIMESTAMPTZ:
         return 12;
       default:
         return -1;


Test patch:
diff --git a/src/test/golang/pgadapter_pgx_tests/go.mod b/src/test/golang/pgadapter_pgx_tests/go.mod
index e1587c27..f91d689c 100644
--- a/src/test/golang/pgadapter_pgx_tests/go.mod
+++ b/src/test/golang/pgadapter_pgx_tests/go.mod
@@ -15,3 +15,5 @@ require (
 	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
 	golang.org/x/text v0.3.6 // indirect
 )
+
+replace github.com/jackc/pgx/v4 => github.com/olavloite/pgx/v4 v4.15.1
diff --git a/src/test/golang/pgadapter_pgx_tests/go.sum b/src/test/golang/pgadapter_pgx_tests/go.sum
index 6901d1f2..f5897e52 100644
--- a/src/test/golang/pgadapter_pgx_tests/go.sum
+++ b/src/test/golang/pgadapter_pgx_tests/go.sum
@@ -2,8 +2,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -11,12 +9,9 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
 github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
 github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
 github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
-github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
-github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
 github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
 github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
 github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
@@ -30,9 +25,6 @@ github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZ
 github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
-github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
 github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
 github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
@@ -41,74 +33,43 @@ github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5
 github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
 github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
-github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
-github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
 github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
-github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
 github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
 github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
-github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
-github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
-github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
-github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
-github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
-github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
-github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
-github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
-github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
-github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/olavloite/pgx/v4 v4.15.1 h1:k/TjekuPmYMZ1ah/MhBIf98eeOD1MZ7wgFJJcbp9cLI=
+github.com/olavloite/pgx/v4 v4.15.1/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
-github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
 github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -116,29 +77,21 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
@@ -147,14 +100,10 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/src/test/golang/pgadapter_pgx_tests/pgx.go b/src/test/golang/pgadapter_pgx_tests/pgx.go
index c0250982..66f6cab3 100644
--- a/src/test/golang/pgadapter_pgx_tests/pgx.go
+++ b/src/test/golang/pgadapter_pgx_tests/pgx.go
@@ -18,6 +18,7 @@ import "C"
 import (
 	"context"
 	"fmt"
+	"github.com/jackc/pgconn"
 	"github.com/jackc/pgtype"
 	"github.com/jackc/pgx/v4"
 	"reflect"
@@ -111,16 +112,18 @@ func TestQueryAllDataTypes(connString string) *C.char {
 	var boolValue bool
 	var byteaValue []byte
 	var float8Value float64
+	var intValue int
 	var numericValue pgtype.Numeric // pgx default maps numeric to string
 	var timestamptzValue time.Time
 	var dateValue time.Time
 	var varcharValue string
 
-	err = conn.QueryRow(ctx, "SELECT * FROM AllTypes").Scan(
+	err = conn.QueryRow(ctx, "SELECT * FROM all_types WHERE col_bigint=1").Scan(
 		&bigintValue,
 		&boolValue,
 		&byteaValue,
 		&float8Value,
+		&intValue,
 		&numericValue,
 		&timestamptzValue,
 		&dateValue,
@@ -141,6 +144,9 @@ func TestQueryAllDataTypes(connString string) *C.char {
 	if g, w := float8Value, 3.14; g != w {
 		return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
 	}
+	if g, w := intValue, 100; g != w {
+		return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+	}
 	var wantNumericValue pgtype.Numeric
 	_ = wantNumericValue.Scan("6.626")
 	if g, w := numericValue, wantNumericValue; !reflect.DeepEqual(g, w) {
@@ -150,8 +156,8 @@ func TestQueryAllDataTypes(connString string) *C.char {
 	if g, w := dateValue, wantDateValue; !reflect.DeepEqual(g, w) {
 		return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
 	}
-	wantTimestamptzValue, _ := time.Parse(time.RFC3339Nano, "2022-02-16T13:18:02.123456000Z")
-	if g, w := timestamptzValue, wantTimestamptzValue; !reflect.DeepEqual(g, w) {
+	wantTimestamptzValue, _ := time.Parse(time.RFC3339Nano, "2022-02-16T13:18:02.123456789+00:00")
+	if g, w := timestamptzValue.String(), wantTimestamptzValue.String(); g != w {
 		return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
 	}
 	if g, w := varcharValue, "test"; g != w {
@@ -162,7 +168,7 @@ func TestQueryAllDataTypes(connString string) *C.char {
 }
 
 //export TestInsertAllDataTypes
-func TestInsertAllDataTypes(connString string) *C.char {
+func TestInsertAllDataTypes(connString string, dateSupported bool) *C.char {
 	ctx := context.Background()
 	conn, err := pgx.Connect(ctx, connString)
 	if err != nil {
@@ -170,11 +176,65 @@ func TestInsertAllDataTypes(connString string) *C.char {
 	}
 	defer conn.Close(ctx)
 
-	sql := "INSERT INTO AllTypes (col_bigint, col_bool, col_bytea, col_float8, col_numeric, col_timestamp, col_varchar) values ($1, $2, $3, $4, $5, $6, $7)"
+	// There is no data type registered by default for NUMERIC in pgx, as there is no native
+	// decimal/numeric data in Go. We therefore need to register the default that we want to use.
+	if dt, ok := conn.ConnInfo().DataTypeForOID(pgtype.NumericOID); ok {
+		conn.ConnInfo().RegisterDefaultPgType(pgtype.Numeric{}, dt.Name)
+	} else {
+		return C.CString("could not register default type for numeric")
+	}
+	if dt, ok := conn.ConnInfo().DataTypeForOID(pgtype.DateOID); ok {
+		conn.ConnInfo().RegisterDefaultPgType(pgtype.Date{}, dt.Name)
+	} else {
+		return C.CString("could not register default type for date")
+	}
+
+	sql := "INSERT INTO all_types (col_bigint, col_bool, col_bytea, col_float8, col_numeric, col_timestamptz, col_date, col_varchar) values ($1, $2, $3, $4, $5, $6, $7, $8)"
+	if !dateSupported {
+		sql = "INSERT INTO all_types (col_bigint, col_bool, col_bytea, col_float8, col_numeric, col_timestamptz, col_varchar) values ($1, $2, $3, $4, $5, $6, $7)"
+	}
 	numeric := pgtype.Numeric{}
-	numeric.Scan("6.626")
+	_ = numeric.Set("6.626")
 	timestamptz, _ := time.Parse(time.RFC3339Nano, "2022-03-24T07:39:10.123456789+01:00")
-	tag, err := conn.Exec(ctx, sql, 100, true, []byte("test_bytes"), 3.14, numeric, timestamptz, "test_string")
+	var tag pgconn.CommandTag
+	if dateSupported {
+		date := pgtype.Date{}
+		_ = date.Set("2022-04-02")
+		tag, err = conn.Exec(ctx, sql, 100, true, []byte("test_bytes"), 3.14, numeric, timestamptz, date, "test_string")
+	} else {
+		tag, err = conn.Exec(ctx, sql, 100, true, []byte("test_bytes"), 3.14, numeric, timestamptz, "test_string")
+	}
+	if err != nil {
+		return C.CString(fmt.Sprintf("failed to execute insert statement: %v", err))
+	}
+	if !tag.Insert() {
+		return C.CString("statement was not recognized as an insert")
+	}
+	if g, w := tag.RowsAffected(), int64(1); g != w {
+		return C.CString(fmt.Sprintf("rows affected mismatch:\n Got: %v\nWant: %v", g, w))
+	}
+
+	return nil
+}
+
+//export TestInsertNullsAllDataTypes
+func TestInsertNullsAllDataTypes(connString string, dateSupported bool) *C.char {
+	ctx := context.Background()
+	conn, err := pgx.Connect(ctx, connString)
+	if err != nil {
+		return C.CString(err.Error())
+	}
+	defer conn.Close(ctx)
+
+	var tag pgconn.CommandTag
+	if dateSupported {
+		sql := "INSERT INTO all_types (col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)"
+		tag, err = conn.Exec(ctx, sql, int64(100), nil, nil, nil, nil, nil, nil, nil, nil)
+	} else {
+		var b *bool
+		sql := "INSERT INTO all_types (col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_varchar) values ($1, $2, $3, $4, $5, $6, $7, $8)"
+		tag, err = conn.Exec(ctx, sql, int64(100), b, nil, nil, nil, nil, nil, nil, nil)
+	}
 	if err != nil {
 		return C.CString(fmt.Sprintf("failed to execute insert statement: %v", err))
 	}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
index bec5393f..18d8a098 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
@@ -146,6 +146,10 @@ public abstract class AbstractMockServerTest {
                       Field.newBuilder()
                           .setName("col_float8")
                           .setType(Type.newBuilder().setCode(TypeCode.FLOAT64).build()))
+                  .addFields(
+                      Field.newBuilder()
+                          .setName("col_int")
+                          .setType(Type.newBuilder().setCode(TypeCode.INT64).build()))
                   .addFields(
                       Field.newBuilder()
                           .setName("col_numeric")
@@ -182,6 +186,7 @@ public abstract class AbstractMockServerTest {
                                   .encodeToString("test".getBytes(StandardCharsets.UTF_8)))
                           .build())
                   .addValues(Value.newBuilder().setNumberValue(3.14d).build())
+                  .addValues(Value.newBuilder().setStringValue("100").build())
                   .addValues(Value.newBuilder().setStringValue("6.626").build())
                   .addValues(
                       Value.newBuilder().setStringValue("2022-02-16T13:18:02.123456789Z").build())
@@ -202,6 +207,7 @@ public abstract class AbstractMockServerTest {
                   .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build())
                   .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build())
                   .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build())
+                  .addValues(Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build())
                   .build())
           .build();
 
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java
index 986c37ac..23c8d08d 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java
@@ -305,7 +305,7 @@ public class ParserTest {
   public void testBinaryParsing() {
     ByteArray value = ByteArray.copyFrom(new byte[] {(byte) 0b01010101, (byte) 0b10101010});
     byte[] byteResult = {(byte) 0b01010101, (byte) 0b10101010};
-    byte[] stringResult = {'U', '\\', '2', '5', '2'};
+    byte[] stringResult = {'\\', 'x', '5', '5', 'a', 'a'};
 
     BinaryParser parsedValue = new BinaryParser(value);
 
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/PgAdapterTestEnv.java b/src/test/java/com/google/cloud/spanner/pgadapter/PgAdapterTestEnv.java
index 4412daad..71b8b19a 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/PgAdapterTestEnv.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/PgAdapterTestEnv.java
@@ -87,8 +87,7 @@ public final class PgAdapterTestEnv {
   private static final String DEFAULT_DATABASE_ID = "pgtest-db";
 
   // Default host url
-  private static final String DEFAULT_HOST_URL =
-      "https://staging-wrenchworks.sandbox.googleapis.com:443";
+  private static final String DEFAULT_HOST_URL = "https://spanner.googleapis.com:443";
 
   // The project Id. This can be overwritten.
   private String projectId;
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/GoString.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/GoString.java
new file mode 100644
index 00000000..064ff899
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/GoString.java
@@ -0,0 +1,36 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.golang;
+
+import com.google.common.base.Preconditions;
+import com.sun.jna.Structure;
+import java.util.Arrays;
+import java.util.List;
+
+public class GoString extends Structure implements Structure.ByValue {
+
+  // JNA does not allow these fields to be final.
+  public String p;
+  public long n;
+
+  public GoString(String p) {
+    this.p = Preconditions.checkNotNull(p);
+    this.n = p.length();
+  }
+
+  protected List<String> getFieldOrder() {
+    return Arrays.asList("p", "n");
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/ITPgxTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/ITPgxTest.java
new file mode 100644
index 00000000..ae0273c4
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/ITPgxTest.java
@@ -0,0 +1,167 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.golang;
+
+import static org.junit.Assert.assertNull;
+
+import com.google.cloud.ByteArray;
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Database;
+import com.google.cloud.spanner.Key;
+import com.google.cloud.spanner.Mutation;
+import com.google.cloud.spanner.pgadapter.IntegrationTest;
+import com.google.cloud.spanner.pgadapter.PgAdapterTestEnv;
+import com.google.cloud.spanner.pgadapter.ProxyServer;
+import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
+import com.google.common.collect.ImmutableList;
+import java.math.BigDecimal;
+import java.util.Collections;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+public class ITPgxTest implements IntegrationTest {
+  private static final PgAdapterTestEnv testEnv = new PgAdapterTestEnv();
+  private static ProxyServer server;
+  private static Database database;
+  private static PgxTest pgxTest;
+
+  @Parameter public String preferQueryMode;
+
+  @Parameters(name = "preferQueryMode = {0}")
+  public static Object[] data() {
+    return new Object[] {"extended", "simple"};
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    pgxTest = PgxTest.compile();
+
+    // TODO: Refactor the integration tests to use a common subclass, as this is repeated in each
+    // class.
+    testEnv.setUp();
+    if (testEnv.isUseExistingDb()) {
+      database = testEnv.getExistingDatabase();
+    } else {
+      database = testEnv.createDatabase();
+      testEnv.updateDdl(
+          database.getId().getDatabase(),
+          Collections.singletonList(
+              "create table all_types ("
+                  + "col_bigint bigint not null primary key, "
+                  + "col_bool bool, "
+                  + "col_bytea bytea, "
+                  + "col_float8 float8, "
+                  + "col_int int, "
+                  + "col_numeric numeric, "
+                  + "col_timestamptz timestamptz, "
+                  + "col_varchar varchar(100))"));
+    }
+    String credentials = testEnv.getCredentials();
+    ImmutableList.Builder<String> argsListBuilder =
+        ImmutableList.<String>builder()
+            .add(
+                "-p",
+                testEnv.getProjectId(),
+                "-i",
+                testEnv.getInstanceId(),
+                "-d",
+                database.getId().getDatabase(),
+                "-s",
+                String.valueOf(testEnv.getPort()),
+                "-e",
+                testEnv.getUrl().getHost());
+    if (credentials != null) {
+      argsListBuilder.add("-c", testEnv.getCredentials());
+    }
+    String[] args = argsListBuilder.build().toArray(new String[0]);
+    server = new ProxyServer(new OptionsMetadata(args));
+    server.startServer();
+  }
+
+  @AfterClass
+  public static void teardown() {
+    if (server != null) {
+      server.stopServer();
+    }
+    testEnv.cleanUp();
+  }
+
+  private GoString createConnString() {
+    return new GoString(
+        String.format(
+            "postgres://uid:pwd@localhost:%d/?statement_cache_capacity=0&sslmode=disable",
+            server.getLocalPort()));
+  }
+
+  @Before
+  public void insertTestData() {
+    String databaseId = database.getId().getDatabase();
+    testEnv.write(
+        databaseId,
+        Collections.singletonList(
+            Mutation.newInsertOrUpdateBuilder("all_types")
+                .set("col_bigint")
+                .to(1L)
+                .set("col_bool")
+                .to(true)
+                .set("col_bytea")
+                .to(ByteArray.copyFrom("test"))
+                .set("col_float8")
+                .to(3.14d)
+                .set("col_int")
+                .to(100)
+                .set("col_numeric")
+                .to(new BigDecimal("6.626"))
+                .set("col_timestamptz")
+                .to(Timestamp.parseTimestamp("2022-02-16T14:18:02.123456789+01:00"))
+                .set("col_varchar")
+                .to("test")
+                .build()));
+  }
+
+  @Test
+  public void testSelectHelloWorld() {
+    assertNull(pgxTest.TestHelloWorld(createConnString()));
+  }
+
+  @Test
+  public void testQueryAllDataTypes() {
+    assertNull(pgxTest.TestQueryAllDataTypes(createConnString()));
+  }
+
+  @Test
+  public void testInsertAllDataTypes() {
+    // Make sure there is no row that already exists with col_bigint=100
+    String databaseId = database.getId().getDatabase();
+    testEnv.write(
+        databaseId, Collections.singletonList(Mutation.delete("all_types", Key.of(100L))));
+
+    assertNull(pgxTest.TestInsertAllDataTypes(createConnString(), false));
+  }
+
+  @Test
+  public void testInsertNullsAllDataTypes() {
+    // Make sure there is no row that already exists with col_bigint=100
+    String databaseId = database.getId().getDatabase();
+    testEnv.write(
+        databaseId, Collections.singletonList(Mutation.delete("all_types", Key.of(100L))));
+
+    assertNull(pgxTest.TestInsertNullsAllDataTypes(createConnString(), false));
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
index ac2326f6..c848873f 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
@@ -20,12 +20,12 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import com.google.cloud.ByteArray;
+import com.google.cloud.Date;
 import com.google.cloud.Timestamp;
 import com.google.cloud.spanner.Dialect;
 import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
 import com.google.cloud.spanner.Statement;
 import com.google.cloud.spanner.pgadapter.AbstractMockServerTest;
-import com.google.common.base.Preconditions;
 import com.google.protobuf.ListValue;
 import com.google.protobuf.Value;
 import com.google.spanner.v1.ExecuteSqlRequest;
@@ -36,15 +36,9 @@ import com.google.spanner.v1.StructType;
 import com.google.spanner.v1.StructType.Field;
 import com.google.spanner.v1.Type;
 import com.google.spanner.v1.TypeCode;
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-import com.sun.jna.Structure;
-import java.io.File;
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.List;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.runner.RunWith;
@@ -52,65 +46,23 @@ import org.junit.runners.JUnit4;
 
 /**
  * Tests PGAdapter using the native Go pgx driver. The Go code can be found in
- * src/test/golang/pgx.go.
+ * src/test/golang/pgadapter_pgx_tests/pgx.go.
  */
 @Category(GolangTest.class)
 @RunWith(JUnit4.class)
 public class PgxMockServerTest extends AbstractMockServerTest {
-  public static class GoString extends Structure implements Structure.ByValue {
-    // JNA does not allow these fields to be final.
-    public String p;
-    public long n;
-
-    public GoString(String p) {
-      this.p = Preconditions.checkNotNull(p);
-      this.n = p.length();
-    }
-
-    protected List<String> getFieldOrder() {
-      return Arrays.asList("p", "n");
-    }
-  }
-
-  public interface PgxTest extends Library {
-    String TestHelloWorld(GoString connString);
-
-    String TestSelect1(GoString connString);
-
-    String TestQueryWithParameter(GoString connString);
-
-    String TestQueryAllDataTypes(GoString connString);
-
-    String TestInsertAllDataTypes(GoString connString);
-
-    String TestWrongDialect(GoString connString);
-  }
-
   private static PgxTest pgxTest;
 
   @BeforeClass
   public static void compile() throws IOException, InterruptedException {
-    // Compile the Go code to ensure that we always have the most recent test code.
-    ProcessBuilder builder = new ProcessBuilder();
-    String[] compileCommand = "go build -o pgx_test.so -buildmode=c-shared pgx.go".split(" ");
-    builder.command(compileCommand);
-    builder.directory(new File("./src/test/golang/pgadapter_pgx_tests"));
-    Process process = builder.start();
-    int res = process.waitFor();
-    assertEquals(0, res);
-
-    // We explicitly use the full path to force JNA to look in a specific directory, instead of in
-    // standard library directories.
-    String currentPath = new java.io.File(".").getCanonicalPath();
-    pgxTest =
-        Native.load(
-            String.format("%s/src/test/golang/pgadapter_pgx_tests/pgx_test.so", currentPath),
-            PgxTest.class);
+    pgxTest = PgxTest.compile();
   }
 
   private GoString createConnString() {
     return new GoString(
-        String.format("postgres://uid:pwd@localhost:%d/test-db", pgServer.getLocalPort()));
+        String.format(
+            "postgres://uid:pwd@localhost:%d/?statement_cache_capacity=0&sslmode=disable",
+            pgServer.getLocalPort()));
   }
 
   @Test
@@ -142,20 +94,11 @@ public class PgxMockServerTest extends AbstractMockServerTest {
 
     assertNull(res);
     List<ExecuteSqlRequest> requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
-    // pgx sends the query two times:
-    // 1. DESCRIBE statement
-    // 2. DESCRIBE/EXECUTE portal
-    assertEquals(2, requests.size());
-    int index = 0;
-    for (ExecuteSqlRequest request : requests) {
-      assertEquals(sql, request.getSql());
-      if (index < 1) {
-        assertEquals(QueryMode.PLAN, request.getQueryMode());
-      } else {
-        assertEquals(QueryMode.NORMAL, request.getQueryMode());
-      }
-      index++;
-    }
+    // The patched version of pgx sends the query one time.
+    assertEquals(1, requests.size());
+    ExecuteSqlRequest request = requests.get(0);
+    assertEquals(sql, request.getSql());
+    assertEquals(QueryMode.NORMAL, request.getQueryMode());
   }
 
   @Test
@@ -166,20 +109,11 @@ public class PgxMockServerTest extends AbstractMockServerTest {
 
     assertNull(res);
     List<ExecuteSqlRequest> requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
-    // pgx sends the query two times:
-    // 1. DESCRIBE statement
-    // 2. DESCRIBE/EXECUTE portal
-    assertEquals(2, requests.size());
-    int index = 0;
-    for (ExecuteSqlRequest request : requests) {
-      assertEquals(sql, request.getSql());
-      if (index < 1) {
-        assertEquals(QueryMode.PLAN, request.getQueryMode());
-      } else {
-        assertEquals(QueryMode.NORMAL, request.getQueryMode());
-      }
-      index++;
-    }
+    // The patched version of pgx sends the query one time.
+    assertEquals(1, requests.size());
+    ExecuteSqlRequest request = requests.get(0);
+    assertEquals(sql, request.getSql());
+    assertEquals(QueryMode.NORMAL, request.getQueryMode());
   }
 
   @Test
@@ -219,55 +153,34 @@ public class PgxMockServerTest extends AbstractMockServerTest {
 
     assertNull(res);
     List<ExecuteSqlRequest> requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
-    // pgx sends the query two times:
-    // 1. DESCRIBE statement
-    // 2. DESCRIBE/EXECUTE portal
-    assertEquals(2, requests.size());
-    int index = 0;
-    for (ExecuteSqlRequest request : requests) {
-      assertEquals(sql, request.getSql());
-      if (index < 1) {
-        assertEquals(QueryMode.PLAN, request.getQueryMode());
-      } else {
-        assertEquals(QueryMode.NORMAL, request.getQueryMode());
-      }
-      index++;
-    }
+    // The patched version of pgx sends the query one time.
+    assertEquals(1, requests.size());
+    ExecuteSqlRequest request = requests.get(0);
+    assertEquals(sql, request.getSql());
+    assertEquals(QueryMode.NORMAL, request.getQueryMode());
   }
 
   @Test
   public void testQueryAllDataTypes() {
-    String sql = "SELECT * FROM AllTypes";
+    String sql = "SELECT * FROM all_types WHERE col_bigint=1";
     mockSpanner.putStatementResult(StatementResult.query(Statement.of(sql), ALL_TYPES_RESULTSET));
 
     String res = pgxTest.TestQueryAllDataTypes(createConnString());
 
     assertNull(res);
     List<ExecuteSqlRequest> requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
-    // pgx sends the query two times:
-    // 1. DESCRIBE statement
-    // 2. EXECUTE portal
-    assertEquals(2, requests.size());
-    int index = 0;
-    for (ExecuteSqlRequest request : requests) {
-      assertEquals(sql, request.getSql());
-      if (index < 1) {
-        assertEquals(QueryMode.PLAN, request.getQueryMode());
-      } else {
-        assertEquals(QueryMode.NORMAL, request.getQueryMode());
-      }
-      index++;
-    }
+    // The patched version of pgx sends the query one time.
+    assertEquals(1, requests.size());
+    ExecuteSqlRequest request = requests.get(0);
+    assertEquals(QueryMode.NORMAL, request.getQueryMode());
   }
 
   @Test
-  @Ignore("Needs a patch in the pgx driver")
   public void testInsertAllDataTypes() {
     String sql =
-        "INSERT INTO AllTypes "
-            + "(col_bigint, col_bool, col_bytea, col_float8, col_numeric, col_timestamp, col_varchar) "
-            + "values ($1, $2, $3, $4, $5, $6, $7)";
-    mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 1L));
+        "INSERT INTO all_types "
+            + "(col_bigint, col_bool, col_bytea, col_float8, col_numeric, col_timestamptz, col_date, col_varchar) "
+            + "values ($1, $2, $3, $4, $5, $6, $7, $8)";
     mockSpanner.putStatementResult(
         StatementResult.update(
             Statement.newBuilder(sql)
@@ -282,31 +195,62 @@ public class PgxMockServerTest extends AbstractMockServerTest {
                 .bind("p5")
                 .to(com.google.cloud.spanner.Value.pgNumeric("6.626"))
                 .bind("p6")
-                .to(Timestamp.parseTimestamp("2022-03-24T07:39:10.123456789+01:00"))
+                .to(Timestamp.parseTimestamp("2022-03-24T06:39:10.123456000Z"))
                 .bind("p7")
+                .to(Date.parseDate("2022-04-02"))
+                .bind("p8")
                 .to("test_string")
                 .build(),
             1L));
 
-    String res = pgxTest.TestInsertAllDataTypes(createConnString());
+    String res = pgxTest.TestInsertAllDataTypes(createConnString(), true);
 
     assertNull(res);
     List<ExecuteSqlRequest> requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
-    // pgx sends the query three times:
-    // 1. DESCRIBE statement
-    // 2. DESCRIBE portal
-    // 3. EXECUTE portal
-    assertEquals(3, requests.size());
-    int index = 0;
-    for (ExecuteSqlRequest request : requests) {
-      assertEquals(sql, request.getSql());
-      if (index < 2) {
-        assertEquals(QueryMode.PLAN, request.getQueryMode());
-      } else {
-        assertEquals(QueryMode.NORMAL, request.getQueryMode());
-      }
-      index++;
-    }
+    // The patched version of pgx sends the query only once!
+    assertEquals(1, requests.size());
+    ExecuteSqlRequest request = requests.get(0);
+    assertEquals(QueryMode.NORMAL, request.getQueryMode());
+  }
+
+  @Test
+  public void testInsertNullsAllDataTypes() {
+    String sql =
+        "INSERT INTO all_types "
+            + "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar) "
+            + "values ($1, $2, $3, $4, $5, $6, $7, $8, $9)";
+    mockSpanner.putStatementResult(
+        StatementResult.update(
+            Statement.newBuilder(sql)
+                .bind("p1")
+                .to(100L)
+                .bind("p2")
+                .to((com.google.cloud.spanner.Value) null)
+                .bind("p3")
+                .to((com.google.cloud.spanner.Value) null)
+                .bind("p4")
+                .to((com.google.cloud.spanner.Value) null)
+                .bind("p5")
+                .to((com.google.cloud.spanner.Value) null)
+                .bind("p6")
+                .to((com.google.cloud.spanner.Value) null)
+                .bind("p7")
+                .to((com.google.cloud.spanner.Value) null)
+                .bind("p8")
+                .to((com.google.cloud.spanner.Value) null)
+                .bind("p9")
+                .to((com.google.cloud.spanner.Value) null)
+                .build(),
+            1L));
+
+    String res = pgxTest.TestInsertNullsAllDataTypes(createConnString(), true);
+
+    assertNull(res);
+    List<ExecuteSqlRequest> requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+    // The patched version of pgx sends the query only once!
+    assertEquals(1, requests.size());
+    ExecuteSqlRequest request = requests.get(0);
+    assertEquals(QueryMode.NORMAL, request.getQueryMode());
   }
 
   @Test
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
new file mode 100644
index 00000000..f5d1c10c
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
@@ -0,0 +1,57 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.golang;
+
+import static org.junit.Assert.assertEquals;
+
+import com.sun.jna.Library;
+import com.sun.jna.Native;
+import java.io.File;
+import java.io.IOException;
+
+public interface PgxTest extends Library {
+
+  String TestHelloWorld(GoString connString);
+
+  String TestSelect1(GoString connString);
+
+  String TestQueryWithParameter(GoString connString);
+
+  String TestQueryAllDataTypes(GoString connString);
+
+  String TestInsertAllDataTypes(GoString connString, boolean dateSupported);
+
+  String TestInsertNullsAllDataTypes(GoString connString, boolean dateSupported);
+
+  String TestWrongDialect(GoString connString);
+
+  static PgxTest compile() throws IOException, InterruptedException {
+    // Compile the Go code to ensure that we always have the most recent test code.
+    ProcessBuilder builder = new ProcessBuilder();
+    String[] compileCommand = "go build -o pgx_test.so -buildmode=c-shared pgx.go".split(" ");
+    builder.command(compileCommand);
+    builder.directory(new File("./src/test/golang/pgadapter_pgx_tests"));
+    Process process = builder.start();
+    int res = process.waitFor();
+    assertEquals(0, res);
+
+    // We explicitly use the full path to force JNA to look in a specific directory, instead of in
+    // standard library directories.
+    String currentPath = new java.io.File(".").getCanonicalPath();
+    return Native.load(
+        String.format("%s/src/test/golang/pgadapter_pgx_tests/pgx_test.so", currentPath),
+        PgxTest.class);
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionTest.java
index cd8a7145..42a88363 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/wireoutput/RowDescriptionTest.java
@@ -105,8 +105,8 @@ public final class RowDescriptionTest {
     assertEquals(Oid.DATE, response.getOidType(6));
     assertEquals(8, response.getOidTypeSize(Oid.DATE));
     // Types.TIMESTAMP
-    assertEquals(Oid.TIMESTAMP, response.getOidType(7));
-    assertEquals(12, response.getOidTypeSize(Oid.TIMESTAMP));
+    assertEquals(Oid.TIMESTAMPTZ, response.getOidType(7));
+    assertEquals(12, response.getOidTypeSize(Oid.TIMESTAMPTZ));
   }
 
   @Test