Repository: GoogleCloudPlatform/pgadapter
Pull request #1875 (2024-05-27 07:01:33)

build: drop stale test databases


Patch:
diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml
index 8195c7c3..a54be1fa 100644
--- a/.github/workflows/integration.yaml
+++ b/.github/workflows/integration.yaml
@@ -21,6 +21,7 @@ env:
   GOOGLE_CLOUD_INSTANCE: "pgadapter-testing"
   GOOGLE_CLOUD_DATABASE: "testdb_integration"
   GOOGLE_CLOUD_ENDPOINT: "spanner.googleapis.com"
+  PG_ADAPTER_DROP_STALE_DATABASES: true
 jobs:
   check-env:
     outputs:
diff --git a/benchmarks/latency-comparison/golang/go.mod b/benchmarks/latency-comparison/golang/go.mod
index 541eac16..f059e3d3 100644
--- a/benchmarks/latency-comparison/golang/go.mod
+++ b/benchmarks/latency-comparison/golang/go.mod
@@ -5,10 +5,10 @@ go 1.21
 toolchain go1.22.3
 
 require (
-	cloud.google.com/go/spanner v1.62.0
+	cloud.google.com/go/spanner v1.63.0
 	github.com/docker/docker v26.1.3+incompatible
 	github.com/jackc/pgx/v4 v4.18.3
-	github.com/jackc/pgx/v5 v5.5.5
+	github.com/jackc/pgx/v5 v5.6.0
 	github.com/montanaflynn/stats v0.7.0
 	github.com/testcontainers/testcontainers-go v0.31.0
 	golang.org/x/oauth2 v0.20.0
@@ -16,7 +16,7 @@ require (
 )
 
 require (
-	cloud.google.com/go v0.113.0 // indirect
+	cloud.google.com/go v0.114.0 // indirect
 	cloud.google.com/go/auth v0.4.1 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
 	cloud.google.com/go/compute/metadata v0.3.0 // indirect
@@ -28,7 +28,7 @@ require (
 	github.com/cenkalti/backoff/v4 v4.2.1 // indirect
 	github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
-	github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect
+	github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect
 	github.com/containerd/containerd v1.7.15 // indirect
 	github.com/containerd/log v0.1.0 // indirect
 	github.com/cpuguy83/dockercfg v0.3.1 // indirect
@@ -94,6 +94,6 @@ require (
 	golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
-	google.golang.org/grpc v1.63.2 // indirect
+	google.golang.org/grpc v1.64.0 // indirect
 	google.golang.org/protobuf v1.34.1 // indirect
 )
diff --git a/benchmarks/latency-comparison/golang/go.sum b/benchmarks/latency-comparison/golang/go.sum
index e5c1be14..c3f96377 100644
--- a/benchmarks/latency-comparison/golang/go.sum
+++ b/benchmarks/latency-comparison/golang/go.sum
@@ -40,6 +40,8 @@ cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw=
 cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms=
 cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA=
 cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8=
+cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY=
+cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E=
 cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
 cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
 cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -530,6 +532,8 @@ cloud.google.com/go/spanner v1.61.0 h1:P7XRZDjBnNw+3tHkPrtWzcxtC3Cqhm+X0vWrO61Ry
 cloud.google.com/go/spanner v1.61.0/go.mod h1:+hdNE+zL7EWNfOWRetw01jxz8H5qsE/ayZvF/pfrAl8=
 cloud.google.com/go/spanner v1.62.0 h1:AHjSLrsGYIEzVQLQkF/OBgObSbXlnMpdovai8x+owco=
 cloud.google.com/go/spanner v1.62.0/go.mod h1:ej/7etuxlyxOhfudKk9nUXBVyxr4kK39LMbmSjOeDEM=
+cloud.google.com/go/spanner v1.63.0 h1:P6+BY70Wtol4MtryBgnXZVTZfsdySEvWfz0EpyLwHi4=
+cloud.google.com/go/spanner v1.63.0/go.mod h1:iqDx7urZpgD7RekZ+CFvBRH6kVTW1ZSEb2HMDKOp5Cc=
 cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
 cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
 cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
@@ -672,6 +676,8 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ=
 github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM=
+github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc=
+github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
 github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
@@ -926,6 +932,8 @@ github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
 github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
 github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
 github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
+github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
 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=
@@ -1790,6 +1798,8 @@ google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3
 google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
 google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
 google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
+google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
+google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
diff --git a/benchmarks/tpcc/pom.xml b/benchmarks/tpcc/pom.xml
index 52b07a3a..c496d191 100644
--- a/benchmarks/tpcc/pom.xml
+++ b/benchmarks/tpcc/pom.xml
@@ -79,7 +79,7 @@
       <plugin>
         <groupId>com.spotify.fmt</groupId>
         <artifactId>fmt-maven-plugin</artifactId>
-        <version>2.21.1</version>
+        <version>2.23</version>
         <executions>
           <execution>
             <goals>
diff --git a/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/dataloader/NewOrderRowProducer.java b/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/dataloader/NewOrderRowProducer.java
index 2e9885fc..1680c02a 100644
--- a/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/dataloader/NewOrderRowProducer.java
+++ b/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/dataloader/NewOrderRowProducer.java
@@ -18,7 +18,8 @@ import com.google.common.collect.ImmutableList;
 
 class NewOrderRowProducer extends AbstractOrderedIdRowProducer {
   private static final String TABLE = "new_orders";
-  private static final String COLUMNS = """
+  private static final String COLUMNS =
+      """
     o_id,
     c_id,
     d_id,
diff --git a/pom.xml b/pom.xml
index e26f05e3..89c033d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -200,7 +200,7 @@
     <dependency>
       <groupId>org.apache.maven</groupId>
       <artifactId>maven-model</artifactId>
-      <version>3.9.6</version>
+      <version>3.9.7</version>
     </dependency>
     <dependency>
       <groupId>org.bouncycastle</groupId>
diff --git a/samples/cloud-run/golang/go.mod b/samples/cloud-run/golang/go.mod
index c9937743..8c8a71ec 100644
--- a/samples/cloud-run/golang/go.mod
+++ b/samples/cloud-run/golang/go.mod
@@ -2,7 +2,7 @@ module pgadapter-cloud-run
 
 go 1.19
 
-require github.com/jackc/pgx/v5 v5.5.5
+require github.com/jackc/pgx/v5 v5.6.0
 
 require (
 	github.com/jackc/pgpassfile v1.0.0 // indirect
diff --git a/samples/cloud-run/golang/go.sum b/samples/cloud-run/golang/go.sum
index a6d78a0c..7d1c918a 100644
--- a/samples/cloud-run/golang/go.sum
+++ b/samples/cloud-run/golang/go.sum
@@ -8,6 +8,8 @@ github.com/jackc/pgx/v5 v5.5.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
 github.com/jackc/pgx/v5 v5.5.4/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
 github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
 github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
+github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
 github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
diff --git a/samples/golang/pgx/go.mod b/samples/golang/pgx/go.mod
index 3ac601f1..23e2423f 100644
--- a/samples/golang/pgx/go.mod
+++ b/samples/golang/pgx/go.mod
@@ -6,7 +6,7 @@ toolchain go1.22.3
 
 require (
 	github.com/GoogleCloudPlatform/pgadapter/wrappers/golang v0.0.0-20240523180507-ef04d3b56a37
-	github.com/jackc/pgx/v5 v5.5.5
+	github.com/jackc/pgx/v5 v5.6.0
 )
 
 require (
diff --git a/samples/golang/pgx/go.sum b/samples/golang/pgx/go.sum
index cc38e067..9ffcac2f 100644
--- a/samples/golang/pgx/go.sum
+++ b/samples/golang/pgx/go.sum
@@ -83,6 +83,8 @@ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
 github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
 github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
+github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
 github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
diff --git a/samples/python/django/requirements.txt b/samples/python/django/requirements.txt
index 8f96092d..742372ee 100644
--- a/samples/python/django/requirements.txt
+++ b/samples/python/django/requirements.txt
@@ -3,5 +3,5 @@ pytz==2024.1
 Django==5.0.6
 google==3.0.0
 google.auth==2.29.0
-testcontainers==4.4.1
+testcontainers==4.5.0
 requests<2.32.0  # `requests` renamed `get_connection` to `_get_connection`, which is a method docker-py monkeypatches. See https://github.com/docker/docker-py/issues/3256 Should be able to un-pin when docker-py hotfix is out.
diff --git a/samples/python/psycopg3/requirements.txt b/samples/python/psycopg3/requirements.txt
index ec3d405a..f80aadb4 100644
--- a/samples/python/psycopg3/requirements.txt
+++ b/samples/python/psycopg3/requirements.txt
@@ -1,5 +1,5 @@
 psycopg[binary]~=3.1.19
-testcontainers~=4.4.1
+testcontainers~=4.5.0
 requests<2.32.0  # `requests` renamed `get_connection` to `_get_connection`, which is a method docker-py monkeypatches. See https://github.com/docker/docker-py/issues/3256 Should be able to un-pin when docker-py hotfix is out.
 google~=3.0.0
 google.auth~=2.29.0
\ No newline at end of file
diff --git a/samples/python/sqlalchemy2-sample/requirements.txt b/samples/python/sqlalchemy2-sample/requirements.txt
index 86f70c6a..ab69bd17 100644
--- a/samples/python/sqlalchemy2-sample/requirements.txt
+++ b/samples/python/sqlalchemy2-sample/requirements.txt
@@ -3,5 +3,5 @@ pytz==2024.1
 sqlalchemy==2.0.27
 google~=3.0.0
 google.auth~=2.29.0
-testcontainers~=4.4.0
+testcontainers~=4.5.0
 requests<2.32.0  # `requests` renamed `get_connection` to `_get_connection`, which is a method docker-py monkeypatches. See https://github.com/docker/docker-py/issues/3256 Should be able to un-pin when docker-py hotfix is out.
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 932e2169..aab09343 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/ConnectionHandler.java
@@ -665,7 +665,8 @@ public class ConnectionHandler implements Runnable {
   public IntermediatePreparedStatement getStatement(String statementName) {
     if (!hasStatement(statementName)) {
       throw PGExceptionFactory.newPGException(
-          "prepared statement " + statementName + " does not exist");
+          "prepared statement " + statementName + " does not exist",
+          SQLState.InvalidSqlStatementName);
     }
     return this.statementsMap.get(statementName);
   }
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/error/SQLState.java b/src/main/java/com/google/cloud/spanner/pgadapter/error/SQLState.java
index 724cd1fb..b2afd47e 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/error/SQLState.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/error/SQLState.java
@@ -158,6 +158,9 @@ public enum SQLState {
   InFailedSqlTransaction("25P02"),
   IdleInTransactionSessionTimeout("25P03"),
 
+  // Class 26 - Invalid SQL statement name
+  InvalidSqlStatementName("26000"),
+
   // Class 34 - Cursor
   InvalidCursorName("34000"),
 
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/DiscardStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/DiscardStatement.java
new file mode 100644
index 00000000..09b1d9ab
--- /dev/null
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/DiscardStatement.java
@@ -0,0 +1,152 @@
+// Copyright 2024 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.statements;
+
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
+import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType;
+import com.google.cloud.spanner.connection.StatementResult;
+import com.google.cloud.spanner.pgadapter.ConnectionHandler;
+import com.google.cloud.spanner.pgadapter.error.PGExceptionFactory;
+import com.google.cloud.spanner.pgadapter.error.SQLState;
+import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
+import com.google.cloud.spanner.pgadapter.statements.BackendConnection.ConnectionState;
+import com.google.cloud.spanner.pgadapter.statements.BackendConnection.NoResult;
+import com.google.cloud.spanner.pgadapter.statements.DiscardStatement.ParsedDiscardStatement.DiscardType;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import java.util.List;
+import java.util.concurrent.Future;
+
+public class DiscardStatement extends IntermediatePortalStatement {
+  static final class ParsedDiscardStatement {
+    enum DiscardType {
+      PLANS,
+      SEQUENCES,
+      TEMPORARY,
+      ALL,
+    }
+
+    final DiscardType type;
+
+    private ParsedDiscardStatement(DiscardType type) {
+      this.type = type;
+    }
+  }
+
+  private final ParsedDiscardStatement discardStatement;
+
+  public DiscardStatement(
+      ConnectionHandler connectionHandler,
+      OptionsMetadata options,
+      String name,
+      ParsedStatement parsedStatement,
+      Statement originalStatement) {
+    super(
+        name,
+        new IntermediatePreparedStatement(
+            connectionHandler,
+            options,
+            name,
+            NO_PARAMETER_TYPES,
+            parsedStatement,
+            originalStatement),
+        NO_PARAMS,
+        ImmutableList.of(),
+        ImmutableList.of());
+    this.discardStatement = parse(originalStatement.getSql());
+  }
+
+  @Override
+  public String getCommandTag() {
+    return "DISCARD";
+  }
+
+  @Override
+  public StatementType getStatementType() {
+    return StatementType.CLIENT_SIDE;
+  }
+
+  @Override
+  public void executeAsync(BackendConnection backendConnection) {
+    this.executed = true;
+    try {
+      switch (discardStatement.type) {
+        case ALL:
+          if (backendConnection.getConnectionState() == ConnectionState.TRANSACTION) {
+            setFutureStatementResult(
+                Futures.immediateFailedFuture(
+                    PGExceptionFactory.newPGException(
+                        "DISCARD ALL cannot run inside a transaction block",
+                        SQLState.ActiveSqlTransaction)));
+            return;
+          }
+          connectionHandler.closeAllStatements();
+          break;
+        case PLANS:
+        case SEQUENCES:
+        case TEMPORARY:
+        default:
+          // The default is no-op as PGAdapter does not support clearing plans, sequences or
+          // temporary tables.
+      }
+    } catch (Exception exception) {
+      setFutureStatementResult(Futures.immediateFailedFuture(exception));
+      return;
+    }
+    setFutureStatementResult(Futures.immediateFuture(new NoResult()));
+  }
+
+  @Override
+  public Future<StatementResult> describeAsync(BackendConnection backendConnection) {
+    // Return null to indicate that this DISCARD statement does not return any
+    // RowDescriptionResponse.
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public IntermediatePortalStatement createPortal(
+      String name,
+      byte[][] parameters,
+      List<Short> parameterFormatCodes,
+      List<Short> resultFormatCodes) {
+    // DISCARD does not support binding any parameters, so we just return the same statement.
+    return this;
+  }
+
+  static ParsedDiscardStatement parse(String sql) {
+    Preconditions.checkNotNull(sql);
+
+    SimpleParser parser = new SimpleParser(sql);
+    if (!parser.eatKeyword("discard")) {
+      throw PGExceptionFactory.newPGException("not a valid DISCARD statement: " + sql);
+    }
+    DiscardType type;
+    if (parser.eatKeyword("all")) {
+      type = DiscardType.ALL;
+    } else if (parser.eatKeyword("plans")) {
+      type = DiscardType.PLANS;
+    } else if (parser.eatKeyword("sequences")) {
+      type = DiscardType.SEQUENCES;
+    } else if (parser.eatKeyword("temp") || parser.eatKeyword("temporary")) {
+      type = DiscardType.TEMPORARY;
+    } else {
+      throw PGExceptionFactory.newPGException("Invalid DISCARD statement: " + parser.getSql());
+    }
+    parser.throwIfHasMoreTokens();
+    return new ParsedDiscardStatement(type);
+  }
+}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
index a10a767a..b01fc53f 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
@@ -19,6 +19,7 @@ import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.CLOSE
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.COPY;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.DEALLOCATE;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.DECLARE;
+import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.DISCARD;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.EXECUTE;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.FETCH;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.MOVE;
@@ -44,6 +45,7 @@ import com.google.cloud.spanner.pgadapter.statements.CloseStatement;
 import com.google.cloud.spanner.pgadapter.statements.CopyStatement;
 import com.google.cloud.spanner.pgadapter.statements.DeallocateStatement;
 import com.google.cloud.spanner.pgadapter.statements.DeclareStatement;
+import com.google.cloud.spanner.pgadapter.statements.DiscardStatement;
 import com.google.cloud.spanner.pgadapter.statements.ExecuteStatement;
 import com.google.cloud.spanner.pgadapter.statements.FetchStatement;
 import com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement;
@@ -148,6 +150,13 @@ public class ParseMessage extends AbstractQueryProtocolMessage {
             name,
             parsedStatement,
             originalStatement);
+      } else if (isCommand(DISCARD, originalStatement.getSql())) {
+        return new DiscardStatement(
+            connectionHandler,
+            connectionHandler.getServer().getOptions(),
+            name,
+            parsedStatement,
+            originalStatement);
       } else if (isCommand(DECLARE, originalStatement.getSql())) {
         return new DeclareStatement(
             connectionHandler,
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java
index 454288a5..61135469 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java
@@ -29,6 +29,7 @@ public class QueryMessage extends ControlMessage {
   public static final String PREPARE = "PREPARE";
   public static final String EXECUTE = "EXECUTE";
   public static final String DEALLOCATE = "DEALLOCATE";
+  public static final String DISCARD = "DISCARD";
   public static final String VACUUM = "VACUUM";
   public static final String TRUNCATE = "TRUNCATE";
   public static final String SAVEPOINT = "SAVEPOINT";


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java
index fe173f6e..f1620f7e 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java
@@ -16,12 +16,14 @@ package com.google.cloud.spanner.pgadapter;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 
 import com.google.cloud.spanner.Dialect;
 import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
 import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.pgadapter.error.SQLState;
 import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
 import com.google.common.collect.ImmutableList;
 import com.google.protobuf.ListValue;
@@ -54,6 +56,7 @@ import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
 import org.postgresql.jdbc.TimestampUtils;
+import org.postgresql.util.PSQLException;
 
 /**
  * Tests the native PG JDBC driver in simple query mode. This is similar to the protocol that is
@@ -571,6 +574,41 @@ public class JdbcSimpleModeMockServerTest extends AbstractMockServerTest {
     }
   }
 
+  @Test
+  public void testDiscard() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      connection.setAutoCommit(true);
+      // Verify that all variants are supported.
+      connection.createStatement().execute("discard all");
+      connection.createStatement().execute("discard plans");
+      connection.createStatement().execute("discard sequences");
+      connection.createStatement().execute("discard temp");
+      connection.createStatement().execute("discard temporary");
+
+      // Create a prepared statement verify that it is dropped by DISCARD ALL.
+      connection.createStatement().execute("prepare foo as SELECT 1");
+      connection.createStatement().execute("execute foo");
+      connection.createStatement().execute("discard all");
+      PSQLException exception =
+          assertThrows(
+              PSQLException.class, () -> connection.createStatement().execute("execute foo"));
+      assertNotNull(exception.getServerErrorMessage());
+      assertEquals(
+          SQLState.InvalidSqlStatementName.toString(),
+          exception.getServerErrorMessage().getSQLState());
+
+      // Verify that 'discard all' is not accepted in a transaction block.
+      connection.setAutoCommit(false);
+      exception =
+          assertThrows(
+              PSQLException.class, () -> connection.createStatement().execute("discard all"));
+      assertNotNull(exception.getServerErrorMessage());
+      assertEquals(
+          SQLState.ActiveSqlTransaction.toString(),
+          exception.getServerErrorMessage().getSQLState());
+    }
+  }
+
   @Test
   public void testGetTimezoneStringUtc() throws SQLException {
     String sql = "select '2022-01-01 10:00:00+01'::timestamptz";
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 9d3281de..087a61ed 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/PgAdapterTestEnv.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/PgAdapterTestEnv.java
@@ -50,6 +50,8 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.URL;
 import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -58,6 +60,7 @@ import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -67,6 +70,7 @@ public class PgAdapterTestEnv {
   private static final int INIT_PROTOCOL = 80877103;
   private static final int OPTIONS_PROTOCOL = 196608;
   private static final String CHANNEL_PROVIDER_PROPERTY = "CHANNEL_PROVIDER";
+  private static final AtomicBoolean DELETED_STALE_DATABASES = new AtomicBoolean();
 
   // GCP credentials file should be set through the 'GOOGLE_APPLICATION_CREDENTIALS' environment
   // variable.
@@ -189,6 +193,26 @@ public class PgAdapterTestEnv {
     spannerHost = getSpannerUrl();
     logger.info("Using Spanner host: " + spannerHost);
     options = createSpannerOptions();
+    deleteStaleTestDatabases();
+  }
+
+  private void deleteStaleTestDatabases() {
+    if (isDropStaleTestDatabases() && DELETED_STALE_DATABASES.compareAndSet(false, true)) {
+      DatabaseAdminClient client = getSpanner().getDatabaseAdminClient();
+      long thresholdTime = Instant.now().getEpochSecond() - Duration.ofHours(6L).getSeconds();
+      for (Database database : client.listDatabases(getInstanceId()).iterateAll()) {
+        if ((database.getId().getDatabase().startsWith("testdb_")
+                || database.getId().getDatabase().startsWith(getDatabaseId()))
+            && database.getCreateTime().getSeconds() <= thresholdTime) {
+          logger.info("Dropping stale test database " + database.getId().getName());
+          database.drop();
+        }
+      }
+    }
+  }
+
+  private boolean isDropStaleTestDatabases() {
+    return Boolean.parseBoolean(System.getenv("PG_ADAPTER_DROP_STALE_DATABASES"));
   }
 
   public void startPGAdapterServer(Iterable<String> additionalPGAdapterOptions) {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/DiscardStatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/DiscardStatementTest.java
new file mode 100644
index 00000000..331c61c4
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/DiscardStatementTest.java
@@ -0,0 +1,44 @@
+// 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.statements;
+
+import static com.google.cloud.spanner.pgadapter.statements.DiscardStatement.parse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.google.cloud.spanner.pgadapter.error.PGException;
+import com.google.cloud.spanner.pgadapter.statements.DiscardStatement.ParsedDiscardStatement.DiscardType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class DiscardStatementTest {
+
+  @Test
+  public void testParse() {
+    assertEquals(DiscardType.ALL, parse("discard all").type);
+    assertEquals(DiscardType.PLANS, parse("discard plans").type);
+    assertEquals(DiscardType.SEQUENCES, parse("discard sequences").type);
+    assertEquals(DiscardType.TEMPORARY, parse("discard temp").type);
+    assertEquals(DiscardType.TEMPORARY, parse("discard temporary").type);
+    assertEquals(DiscardType.TEMPORARY, parse("discard/*comment*/temporary").type);
+
+    assertThrows(PGException.class, () -> parse("discard foo"));
+    assertThrows(PGException.class, () -> parse("discard"));
+    assertThrows(PGException.class, () -> parse("deallocate all foo"));
+    assertThrows(PGException.class, () -> parse("discard temp all"));
+  }
+}