Repository: GoogleCloudPlatform/pgadapter
Pull request #2376 (2024-09-25 14:00:37)

deps: update module cloud.google.com/go/spanner to v1.68.0
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [cloud.google.com/go/spanner](https://redirect.github.com/googleapis/google-cloud-go) | `v1.67.0` -> `v1.68.0` | [![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2fspanner/v1.68.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/go/cloud.google.com%2fgo%2fspanner/v1.68.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/go/cloud.google.com%2fgo%2fspanner/v1.67.0/v1.68.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/go/cloud.google.com%2fgo%2fspanner/v1.67.0/v1.68.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/GoogleCloudPlatform/pgadapter).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOC45NC4zIiwidXBkYXRlZEluVmVyIjoiMzguOTQuMyIsInRhcmdldEJyYW5jaCI6InBvc3RncmVzcWwtZGlhbGVjdCIsImxhYmVscyI6W119-->



Patch:
diff --git a/.ci/evaluate-with-psql.sh b/.ci/evaluate-with-psql.sh
index 2d0dec1a..8aa9fe24 100644
--- a/.ci/evaluate-with-psql.sh
+++ b/.ci/evaluate-with-psql.sh
@@ -86,43 +86,43 @@ diff -i -w -s .ci/e2e-result/backslash-l.txt .ci/e2e-expected/backslash-l.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"-c option dml batching\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "$(cat .ci/e2e-batching/dml-batch.txt)" > .ci/e2e-result/dml-batching.txt
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "$(cat .ci/e2e-batching/dml-batch.txt)" > .ci/e2e-result/dml-batching.txt
 diff -i -w -s .ci/e2e-result/dml-batching.txt .ci/e2e-expected/dml-batching.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"-c option ddl batching\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "$(cat .ci/e2e-batching/ddl-batch.txt)"
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "$(cat .ci/e2e-batching/ddl-batch.txt)"
 echo "\d" | /usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" > .ci/e2e-result/ddl-batching.txt
 diff -i -w -s .ci/e2e-result/ddl-batching.txt .ci/e2e-expected/ddl-batching.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"-c option ddl-dml mix batching\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "$(cat .ci/e2e-batching/ddl-dml-batch.txt)" &> .ci/e2e-result/ddl-dml-batching.txt
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "$(cat .ci/e2e-batching/ddl-dml-batch.txt)" &> .ci/e2e-result/ddl-dml-batching.txt
 diff -i -w -s .ci/e2e-result/ddl-dml-batching.txt .ci/e2e-expected/ddl-dml-batching.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"-c option SELECT DML batching\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "$(cat .ci/e2e-batching/select-dml-batch.txt)" &> .ci/e2e-result/select-dml-batching.txt
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "$(cat .ci/e2e-batching/select-dml-batch.txt)" &> .ci/e2e-result/select-dml-batching.txt
 diff -i -w -s .ci/e2e-result/select-dml-batching.txt .ci/e2e-expected/select-dml-batching.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"-c option SELECT DDL batching\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "$(cat .ci/e2e-batching/select-ddl-batch.txt)" &> .ci/e2e-result/select-ddl-batching.txt
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "$(cat .ci/e2e-batching/select-ddl-batch.txt)" &> .ci/e2e-result/select-ddl-batching.txt
 diff -i -w -s .ci/e2e-result/select-ddl-batching.txt .ci/e2e-expected/select-ddl-batching.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"-c option WITH DML batching\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "$(cat .ci/e2e-batching/with-dml-batch.txt)" &> .ci/e2e-result/with-dml-batching.txt
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "$(cat .ci/e2e-batching/with-dml-batch.txt)" &> .ci/e2e-result/with-dml-batching.txt
 diff -i -w -s .ci/e2e-result/with-dml-batching.txt .ci/e2e-expected/with-dml-batching.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"-c option begin/commit batching\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "$(cat .ci/e2e-batching/begin-commit-batch.txt)" &> .ci/e2e-result/begin-commit-batching.txt
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "$(cat .ci/e2e-batching/begin-commit-batch.txt)" &> .ci/e2e-result/begin-commit-batching.txt
 diff -i -w -s .ci/e2e-result/begin-commit-batching.txt .ci/e2e-expected/begin-commit-batching.txt
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"COPY FROM STDIN\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "COPY users FROM STDIN;" <<EOF
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "COPY users FROM STDIN;" <<EOF
 12	12	12
 13	13	13
 14	14	14
@@ -136,7 +136,7 @@ diff -i -w -s .ci/e2e-result/copy-from-stdin.txt .ci/e2e-expected/copy-from-stdi
 RETURN_CODE=$((${RETURN_CODE}||$?))
 
 echo "------Test \"COPY FROM STDIN in batch\"------"
-/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" -c "select 0; COPY users FROM STDIN; COPY users FROM STDIN; select 1;" <<EOF
+/usr/lib/postgresql/"${PSQL_VERSION}"/bin/psql -h localhost -p 4242 -d "${GOOGLE_CLOUD_DATABASE_WITH_VERSION}" --set=SHOW_ALL_RESULTS=off -c "select 0; COPY users FROM STDIN; COPY users FROM STDIN; select 1;" <<EOF
 18	18	18
 \.
 19	19	19
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 635eb841..ba1cd26a 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -32,7 +32,7 @@ jobs:
         distribution: 'zulu'
     - run: java -version
     - run: .ci/run-with-credentials.sh lint
-  e2e-psql-v11-v14:
+  e2e-psql-v12-v14:
     needs: [check-env]
     if: needs.check-env.outputs.has-key == 'true'
     runs-on: ubuntu-latest
@@ -43,12 +43,12 @@ jobs:
           java-version: 11
           distribution: 'zulu'
       - run: java -version
-      - name: "Install postgresql-client-11"
+      - name: "Install postgresql-client-12"
         run: |
           sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
           wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
           sudo apt-get update
-          sudo apt-get install postgresql-client-11
+          sudo apt-get install postgresql-client-12
       - id: 'auth'
         uses: google-github-actions/auth@v2
         with:
@@ -60,8 +60,8 @@ jobs:
       - name: "Install gcloud alpha component"
         run: gcloud components install alpha
       - run: .ci/run-with-credentials.sh uber-jar-build
-      - run: .ci/run-with-credentials.sh e2e-psql 11 14.1
-  e2e-psql-v12-v1-v14:
+      - run: .ci/run-with-credentials.sh e2e-psql 12 14.1
+  e2e-psql-v13-v14:
     needs: [check-env]
     if: needs.check-env.outputs.has-key == 'true'
     runs-on: ubuntu-latest
@@ -72,12 +72,12 @@ jobs:
           java-version: 11
           distribution: 'zulu'
       - run: java -version
-      - name: "Install postgresql-client-12"
+      - name: "Install postgresql-client-13"
         run: |
           sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
           wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
           sudo apt-get update
-          sudo apt-get install postgresql-client-12
+          sudo apt-get install postgresql-client-13
       - id: 'auth'
         uses: google-github-actions/auth@v2
         with:
@@ -89,8 +89,8 @@ jobs:
       - name: "Install gcloud alpha component"
         run: gcloud components install alpha
       - run: .ci/run-with-credentials.sh uber-jar-build
-      - run: .ci/run-with-credentials.sh e2e-psql 12 14.1
-  e2e-psql-v13-v14:
+      - run: .ci/run-with-credentials.sh e2e-psql 13 14.1
+  e2e-psql-v14-v14:
     needs: [check-env]
     if: needs.check-env.outputs.has-key == 'true'
     runs-on: ubuntu-latest
@@ -101,12 +101,12 @@ jobs:
           java-version: 11
           distribution: 'zulu'
       - run: java -version
-      - name: "Install postgresql-client-13"
+      - name: "Install postgresql-client-14"
         run: |
           sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
           wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
           sudo apt-get update
-          sudo apt-get install postgresql-client-13
+          sudo apt-get install postgresql-client-14
       - id: 'auth'
         uses: google-github-actions/auth@v2
         with:
@@ -118,8 +118,8 @@ jobs:
       - name: "Install gcloud alpha component"
         run: gcloud components install alpha
       - run: .ci/run-with-credentials.sh uber-jar-build
-      - run: .ci/run-with-credentials.sh e2e-psql 13 14.1
-  e2e-psql-v14-v14:
+      - run: .ci/run-with-credentials.sh e2e-psql 14 14.1
+  e2e-psql-v15-v14:
     needs: [check-env]
     if: needs.check-env.outputs.has-key == 'true'
     runs-on: ubuntu-latest
@@ -130,12 +130,12 @@ jobs:
           java-version: 11
           distribution: 'zulu'
       - run: java -version
-      - name: "Install postgresql-client-14"
+      - name: "Install postgresql-client-15"
         run: |
           sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
           wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
           sudo apt-get update
-          sudo apt-get install postgresql-client-14
+          sudo apt-get install postgresql-client-15
       - id: 'auth'
         uses: google-github-actions/auth@v2
         with:
@@ -147,4 +147,62 @@ jobs:
       - name: "Install gcloud alpha component"
         run: gcloud components install alpha
       - run: .ci/run-with-credentials.sh uber-jar-build
-      - run: .ci/run-with-credentials.sh e2e-psql 14 14.1
+      - run: .ci/run-with-credentials.sh e2e-psql 15 14.1
+  e2e-psql-v16-v14:
+    needs: [check-env]
+    if: needs.check-env.outputs.has-key == 'true'
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-java@v4
+        with:
+          java-version: 11
+          distribution: 'zulu'
+      - run: java -version
+      - name: "Install postgresql-client-16"
+        run: |
+          sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+          wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+          sudo apt-get update
+          sudo apt-get install postgresql-client-16
+      - id: 'auth'
+        uses: google-github-actions/auth@v2
+        with:
+          credentials_json: '${{ secrets.JSON_SERVICE_ACCOUNT_CREDENTIALS }}'
+      - id: 'setup-gcloud'
+        uses: google-github-actions/setup-gcloud@v2
+        with:
+          project_id: ${{ env.GOOGLE_CLOUD_PROJECT }}
+      - name: "Install gcloud alpha component"
+        run: gcloud components install alpha
+      - run: .ci/run-with-credentials.sh uber-jar-build
+      - run: .ci/run-with-credentials.sh e2e-psql 16 14.1
+  e2e-psql-v17-v14:
+    needs: [check-env]
+    if: needs.check-env.outputs.has-key == 'true'
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-java@v4
+        with:
+          java-version: 11
+          distribution: 'zulu'
+      - run: java -version
+      - name: "Install postgresql-client-17"
+        run: |
+          sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
+          wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
+          sudo apt-get update
+          sudo apt-get install postgresql-client-17
+      - id: 'auth'
+        uses: google-github-actions/auth@v2
+        with:
+          credentials_json: '${{ secrets.JSON_SERVICE_ACCOUNT_CREDENTIALS }}'
+      - id: 'setup-gcloud'
+        uses: google-github-actions/setup-gcloud@v2
+        with:
+          project_id: ${{ env.GOOGLE_CLOUD_PROJECT }}
+      - name: "Install gcloud alpha component"
+        run: gcloud components install alpha
+      - run: .ci/run-with-credentials.sh uber-jar-build
+      - run: .ci/run-with-credentials.sh e2e-psql 17 14.1
diff --git a/README.md b/README.md
index 806f3c7b..cc65189b 100644
--- a/README.md
+++ b/README.md
@@ -271,6 +271,17 @@ The following list contains the most frequently used startup options for PGAdapt
     Use the -x switch to turn off the localhost check. This is required when running PGAdapter in a
     Docker container, as the connections from the host machine will not be seen as a connection from
     localhost in the container.
+
+--allow_shutdown_command
+  * Enables the use of the custom SQL command `SHUTDOWN [SMART | FAST | IMMEDIATE]`. This command can
+    be used to shut down PGAdapter by just sending it a SQL statement. This option should only be enabled
+    when PGAdapter runs in a trusted environment, for example as a side-car container. The default
+    shutdown mode is `FAST`, which terminates all existing connections and then shuts down PGAdapter.
+    Use shutdown mode `SMART` to instruct PGAdapter to wait until all existing connections have been
+    terminated by the client before shutting down. See also https://www.postgresql.org/docs/current/server-shutdown.html
+    for more information about shutdown modes.
+  * Note that `SHUTDOWN [SMART | FAST | IMMEDIATE]` only works on PGAdapter. This command is not
+    supported by PostgreSQL.
 ```
 
 * See [command line arguments](docs/command_line_arguments.md) for a list of all supported arguments.
diff --git a/benchmarks/latency-comparison/golang/go.mod b/benchmarks/latency-comparison/golang/go.mod
index b2286ced..836fe7de 100644
--- a/benchmarks/latency-comparison/golang/go.mod
+++ b/benchmarks/latency-comparison/golang/go.mod
@@ -3,7 +3,7 @@ module cloud.google.com/pgadapter-latency-benchmark
 go 1.21
 
 require (
-	cloud.google.com/go/spanner v1.67.0
+	cloud.google.com/go/spanner v1.68.0
 	github.com/docker/docker v27.3.1+incompatible
 	github.com/jackc/pgx/v4 v4.18.3
 	github.com/jackc/pgx/v5 v5.7.1
@@ -14,20 +14,22 @@ require (
 )
 
 require (
-	cel.dev/expr v0.15.0 // indirect
+	cel.dev/expr v0.16.0 // indirect
 	cloud.google.com/go v0.115.1 // indirect
 	cloud.google.com/go/auth v0.9.4 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
 	cloud.google.com/go/compute/metadata v0.5.1 // indirect
+	cloud.google.com/go/monitoring v1.21.0 // indirect
 	dario.cat/mergo v1.0.0 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 	github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 // indirect
+	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 // indirect
 	github.com/Microsoft/go-winio v0.6.2 // indirect
 	github.com/Microsoft/hcsshim v0.11.5 // indirect
 	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.3.0 // indirect
-	github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
+	github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 // indirect
 	github.com/containerd/containerd v1.7.18 // indirect
 	github.com/containerd/errdefs v0.1.0 // indirect
 	github.com/containerd/log v0.1.0 // indirect
@@ -36,8 +38,8 @@ require (
 	github.com/distribution/reference v0.6.0 // indirect
 	github.com/docker/go-connections v0.5.0 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
-	github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 // indirect
-	github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
+	github.com/envoyproxy/go-control-plane v0.13.0 // indirect
+	github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/go-logr/logr v1.4.2 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
@@ -78,10 +80,13 @@ require (
 	github.com/tklauser/numcpus v0.6.1 // indirect
 	github.com/yusufpapurcu/wmi v1.2.3 // indirect
 	go.opencensus.io v0.24.0 // indirect
+	go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
 	go.opentelemetry.io/otel v1.29.0 // indirect
 	go.opentelemetry.io/otel/metric v1.29.0 // indirect
+	go.opentelemetry.io/otel/sdk v1.29.0 // indirect
+	go.opentelemetry.io/otel/sdk/metric v1.29.0 // indirect
 	go.opentelemetry.io/otel/trace v1.29.0 // indirect
 	golang.org/x/crypto v0.27.0 // indirect
 	golang.org/x/net v0.29.0 // indirect
@@ -90,7 +95,8 @@ require (
 	golang.org/x/text v0.18.0 // indirect
 	golang.org/x/time v0.6.0 // indirect
 	golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
+	google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
 	google.golang.org/grpc v1.66.2 // indirect
 	google.golang.org/protobuf v1.34.2 // indirect
diff --git a/benchmarks/latency-comparison/golang/go.sum b/benchmarks/latency-comparison/golang/go.sum
index ead6fa9d..c305f9d8 100644
--- a/benchmarks/latency-comparison/golang/go.sum
+++ b/benchmarks/latency-comparison/golang/go.sum
@@ -1,5 +1,7 @@
 cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
 cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
+cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y=
+cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -402,6 +404,8 @@ cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhI
 cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4=
 cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w=
 cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw=
+cloud.google.com/go/monitoring v1.21.0 h1:EMc0tB+d3lUewT2NzKC/hr8cSR9WsUieVywzIHetGro=
+cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4=
 cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA=
 cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o=
 cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM=
@@ -554,6 +558,8 @@ cloud.google.com/go/spanner v1.65.0 h1:XK15cs9lFFQo5n4Wh9nfrcPXAxWln6NdodDiQKmoD
 cloud.google.com/go/spanner v1.65.0/go.mod h1:dQGB+w5a67gtyE3qSKPPxzniedrnAmV6tewQeBY7Hxs=
 cloud.google.com/go/spanner v1.67.0 h1:h8xfobxh5lQu4qJVMPH+wSiyU+ZM6ZTxRNqGeu9iIVA=
 cloud.google.com/go/spanner v1.67.0/go.mod h1:Um+TNmxfcCHqNCKid4rmAMvoe/Iu1vdz6UfxJ9GPxRQ=
+cloud.google.com/go/spanner v1.68.0 h1:54nL8lEUj5A6frSGnL9VPmHDYbV5UwVkK8fbdnZyDCs=
+cloud.google.com/go/spanner v1.68.0/go.mod h1:X5T0XftydYp0K1adeJQDJtdWpbrOeJ7wHecM4tK6FiE=
 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=
@@ -649,6 +655,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0 h1:oVLqHXhnYtUwM89y9T1fXGaK9wTkXHgNp8/ZNMQzUxE=
 github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.0/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1 h1:pB2F2JKCj1Znmp2rwxxt1J0Fg0wezTMgWYk5Mpbi1kg=
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.24.1/go.mod h1:itPGVDKf9cC/ov4MdvJ2QZ0khw4bfoo9jzwTJlaxy2k=
 github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
@@ -700,6 +708,8 @@ github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipa
 github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
 github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
 github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59 h1:fLZ97KE86ELjEYJCEUVzmbhfzDxHHGwBrDVMd4XL6Bs=
+github.com/cncf/xds/go v0.0.0-20240822171458-6449f94b4d59/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
 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.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
@@ -758,12 +768,16 @@ github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjl
 github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
 github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc=
 github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
+github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les=
+github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
 github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
 github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
 github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
 github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
+github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
+github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
@@ -1140,6 +1154,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/detectors/gcp v1.29.0 h1:TiaiXB4DpGD3sdzNlYQxruQngn5Apwzi1X0DRhuGvDQ=
+go.opentelemetry.io/contrib/detectors/gcp v1.29.0/go.mod h1:GW2aWZNwR2ZxDLdv8OyC2G8zkRoQBuURgV7RPQgcPoU=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
@@ -1162,6 +1178,10 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2
 go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
 go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
 go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
+go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
+go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
+go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY=
+go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ=
 go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
 go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
 go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
@@ -1827,6 +1847,8 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
 google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
 google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls=
 google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M=
+google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU=
+google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4=
 google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc=
 google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c=
 google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
@@ -1835,6 +1857,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade h1:
 google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
 google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
 google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0=
diff --git a/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/BenchmarkApplication.java b/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/BenchmarkApplication.java
index 1f2f704e..996e859f 100644
--- a/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/BenchmarkApplication.java
+++ b/benchmarks/tpcc/src/main/java/com/google/cloud/pgadapter/tpcc/BenchmarkApplication.java
@@ -245,7 +245,6 @@ public class BenchmarkApplication implements CommandLineRunner {
 
   static Attributes createMetricAttributes(SpannerConfiguration spannerConfiguration) {
     AttributesBuilder attributesBuilder = Attributes.builder();
-    attributesBuilder.put("connection_id", UUID.randomUUID().toString());
     attributesBuilder.put("database", spannerConfiguration.getDatabase());
     attributesBuilder.put("instance_id", spannerConfiguration.getInstance());
     attributesBuilder.put("project_id", spannerConfiguration.getProject());
diff --git a/docs/command_line_arguments.md b/docs/command_line_arguments.md
index 40d5048a..cc1784ca 100644
--- a/docs/command_line_arguments.md
+++ b/docs/command_line_arguments.md
@@ -70,6 +70,17 @@
     Docker container, as the connections from the host machine will not be seen as a connection from
     localhost in the container.
 
+--allow_shutdown_command
+  * Enables the use of the custom SQL command `SHUTDOWN [SMART | FAST | IMMEDIATE]`. This command can
+    be used to shut down PGAdapter by just sending it a SQL statement. This option should only be enabled
+    when PGAdapter runs in a trusted environment, for example as a side-car container. The default
+    shutdown mode is `FAST`, which terminates all existing connections and then shuts down PGAdapter.
+    Use shutdown mode `SMART` to instruct PGAdapter to wait until all existing connections have been
+    terminated by the client before shutting down. See also https://www.postgresql.org/docs/current/server-shutdown.html
+    for more information about shutdown modes.
+  * Note that `SHUTDOWN [SMART | FAST | IMMEDIATE]` only works on PGAdapter. This command is not
+    supported by PostgreSQL.
+
 -r
   * PGAdapter internally uses the core engine of the Cloud Spanner JDBC driver. This option specifies
     additional properties that will be used with that internal JDBC connection. All connection properties
diff --git a/samples/java/hibernate/pom.xml b/samples/java/hibernate/pom.xml
index 49452f8c..a3f49a17 100644
--- a/samples/java/hibernate/pom.xml
+++ b/samples/java/hibernate/pom.xml
@@ -69,7 +69,7 @@
     <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-databind</artifactId>
-      <version>2.17.2</version>
+      <version>2.18.0</version>
     </dependency>
     
     <!-- NOTE: This dependency is deliberately not defined with scope <test>, as that breaks the use of testcontainers in the sample application. -->
diff --git a/samples/python/django/requirements.txt b/samples/python/django/requirements.txt
index 58535d86..dc4afce7 100644
--- a/samples/python/django/requirements.txt
+++ b/samples/python/django/requirements.txt
@@ -1,4 +1,4 @@
-psycopg-binary==3.2.2
+psycopg-binary==3.2.3
 pytz==2024.2
 Django==5.1.1
 google==3.0.0
diff --git a/samples/python/sqlalchemy2-sample/requirements.txt b/samples/python/sqlalchemy2-sample/requirements.txt
index 2e85d0b0..3419a0a8 100644
--- a/samples/python/sqlalchemy2-sample/requirements.txt
+++ b/samples/python/sqlalchemy2-sample/requirements.txt
@@ -1,4 +1,4 @@
-psycopg[binary]==3.2.2
+psycopg[binary]==3.2.3
 pytz==2024.2
 sqlalchemy==2.0.35
 google~=3.0.0
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java
index 72166cd4..f38ee4c9 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/ProxyServer.java
@@ -114,6 +114,8 @@ public class ProxyServer extends AbstractApiService {
     IMMEDIATE,
   }
 
+  private final AtomicReference<ShutdownHandler> shutdownHandler = new AtomicReference<>();
+
   private final AtomicReference<ShutdownMode> shutdownMode = new AtomicReference<>();
 
   private final AtomicReference<CountDownLatch> allHandlersTerminatedLatch =
@@ -322,6 +324,13 @@ public class ProxyServer extends AbstractApiService {
     awaitTerminated();
   }
 
+  public synchronized ShutdownHandler getOrCreateShutdownHandler() {
+    if (this.shutdownHandler.get() == null) {
+      this.shutdownHandler.set(ShutdownHandler.createForServer(this));
+    }
+    return this.shutdownHandler.get();
+  }
+
   void setShutdownMode(ShutdownMode shutdownMode) {
     logger.log(Level.INFO, "Setting shutdown mode to {0}", shutdownMode);
     this.shutdownMode.set(shutdownMode);
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/Server.java b/src/main/java/com/google/cloud/spanner/pgadapter/Server.java
index 958a9a3b..361d6c39 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/Server.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/Server.java
@@ -62,7 +62,7 @@ import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
 public class Server {
   private static final Logger logger = Logger.getLogger(Server.class.getName());
 
-  private static ShutdownHandler shutdownHandler;
+  private static volatile ShutdownHandler shutdownHandler;
 
   /**
    * Main method for running a Spanner PostgreSQL Adapter {@link Server} as a stand-alone
@@ -78,7 +78,7 @@ public class Server {
 
       // Create a shutdown handler and register signal handlers for the signals that should
       // terminate the server.
-      Server.shutdownHandler = ShutdownHandler.createForServer(proxyServer);
+      Server.shutdownHandler = proxyServer.getOrCreateShutdownHandler();
       registerSignalHandlers();
     } catch (Exception e) {
       printError(e, System.err, System.out);
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/ShutdownHandler.java b/src/main/java/com/google/cloud/spanner/pgadapter/ShutdownHandler.java
index 4a991404..80037820 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/ShutdownHandler.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/ShutdownHandler.java
@@ -14,6 +14,7 @@
 
 package com.google.cloud.spanner.pgadapter;
 
+import com.google.api.core.InternalApi;
 import com.google.cloud.spanner.pgadapter.ProxyServer.ShutdownMode;
 import com.google.common.base.Preconditions;
 import java.util.concurrent.atomic.AtomicReference;
@@ -28,6 +29,7 @@ public class ShutdownHandler {
   private final Thread shutdownThread;
   private final AtomicReference<ShutdownMode> shutdownMode = new AtomicReference<>();
 
+  @InternalApi
   static ShutdownHandler createForServer(@Nonnull ProxyServer proxyServer) {
     return new ShutdownHandler(proxyServer);
   }
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/commands/ListCommand.java b/src/main/java/com/google/cloud/spanner/pgadapter/commands/ListCommand.java
index e5ac8da5..db771778 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/commands/ListCommand.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/commands/ListCommand.java
@@ -24,14 +24,13 @@ public class ListCommand extends Command {
 
   private static final Pattern INPUT_REGEX =
       Pattern.compile(
-          "^SELECT d\\.datname as \"Name\",\n"
-              + "       pg_catalog\\.pg_get_userbyid\\(d.datdba\\) as \"Owner\",\n"
-              + "       pg_catalog\\.pg_encoding_to_char\\(d\\.encoding\\) as \"Encoding\",\n"
-              + "(?:\\s*d\\.datcollate as \"Collate\",\n)?"
-              + "(?:\\s*d\\.datctype as \"Ctype\",\n)?"
-              + "       pg_catalog\\.array_to_string\\(d\\.datacl, .*\\) AS \"Access privileges\"\n"
-              + "FROM pg_catalog\\.pg_database d\n.*\n?"
-              + "ORDER BY 1;?$");
+          "^SELECT\\s+d\\.datname as \"Name\",\n"
+              + "\\s*pg_catalog\\.pg_get_userbyid\\(d.datdba\\) as \"Owner\",\n"
+              + ".*"
+              + "\\s*pg_catalog\\.array_to_string\\(d\\.datacl, .*\\)(:? END)? AS \"Access privileges\"\n"
+              + "FROM (?:pg_catalog\\.)?pg_database d\n.*\n?"
+              + "ORDER BY 1;?$",
+          Pattern.DOTALL);
 
   ListCommand(String sql) {
     super(sql);
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java
index 1942510e..1249305d 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/OptionsMetadata.java
@@ -100,6 +100,7 @@ public class OptionsMetadata {
     private String unixDomainSocketDirectory;
     private boolean autoConfigEmulator;
     private boolean logGrpcMessages;
+    private boolean allowShutdownStatement;
     private boolean debugMode;
     private String endpoint;
     private boolean usePlainText;
@@ -369,6 +370,15 @@ public class OptionsMetadata {
       return this;
     }
 
+    /**
+     * Enables the use of the SHUTDOWN [SMART | FAST | IMMEDIATE] statement to shutdown the proxy
+     * server.
+     */
+    public Builder setAllowShutdownStatement(boolean allowShutdownStatement) {
+      this.allowShutdownStatement = allowShutdownStatement;
+      return this;
+    }
+
     Builder enableDebugMode() {
       this.debugMode = true;
       return this;
@@ -486,6 +496,9 @@ public class OptionsMetadata {
       if (logGrpcMessages) {
         addOption(args, OPTION_LOG_GRPC_MESSAGES);
       }
+      if (allowShutdownStatement) {
+        addOption(args, OPTION_ALLOW_SHUTDOWN_STATEMENT);
+      }
       if (debugMode) {
         addOption(args, OPTION_INTERNAL_DEBUG_MODE);
         addOption(args, OPTION_SKIP_INTERNAL_DEBUG_MODE_WARNING);
@@ -604,6 +617,7 @@ public class OptionsMetadata {
   private static final String OPTION_DEBUG_MODE = "debug";
   private static final String OPTION_LEGACY_LOGGING = "legacy_logging";
   private static final String OPTION_LOG_GRPC_MESSAGES = "log_grpc_messages";
+  private static final String OPTION_ALLOW_SHUTDOWN_STATEMENT = "allow_shutdown_statement";
 
   private final Map<String, String> environment;
   private final String osName;
@@ -635,6 +649,7 @@ public class OptionsMetadata {
   private final boolean debugMode;
   private final Duration startupTimeout;
   private final boolean logGrpcMessages;
+  private final boolean allowShutdownStatement;
 
   /**
    * Creates a new instance of {@link OptionsMetadata} from the given arguments.
@@ -728,6 +743,7 @@ public class OptionsMetadata {
     this.serverVersion = commandLine.getOptionValue(OPTION_SERVER_VERSION, DEFAULT_SERVER_VERSION);
     this.debugMode = commandLine.hasOption(OPTION_INTERNAL_DEBUG_MODE);
     this.logGrpcMessages = commandLine.hasOption(OPTION_LOG_GRPC_MESSAGES);
+    this.allowShutdownStatement = commandLine.hasOption(OPTION_ALLOW_SHUTDOWN_STATEMENT);
     this.startupTimeout = startupTimeout;
   }
 
@@ -802,6 +818,7 @@ public class OptionsMetadata {
     this.serverVersion = DEFAULT_SERVER_VERSION;
     this.debugMode = false;
     this.logGrpcMessages = false;
+    this.allowShutdownStatement = false;
     this.startupTimeout = DEFAULT_STARTUP_TIMEOUT;
   }
 
@@ -1292,6 +1309,11 @@ public class OptionsMetadata {
         "Logs all gRPC messages that are sent by PGAdapter to Spanner to the default log handler (stdout).\n"
             + "This option should only be enabled to debug problems and/or to determine exactly which gRPC messages\n"
             + "are being sent to Spanner. Enabling this in production will cause a large number of messages to be logged.");
+    options.addOption(
+        OPTION_ALLOW_SHUTDOWN_STATEMENT,
+        "allow-shutdown-statement",
+        false,
+        "Allows the proxy server to be shutdown by executing the SHUTDOWN [SMART | FAST | IMMEDIATE] statement.");
     options.addOption(
         OPTION_INTERNAL_DEBUG_MODE,
         "internal-debug-mode",
@@ -1392,6 +1414,10 @@ public class OptionsMetadata {
     return this.logGrpcMessages;
   }
 
+  public boolean isAllowShutdownStatement() {
+    return this.allowShutdownStatement;
+  }
+
   public boolean isDebugMode() {
     return this.debugMode;
   }
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java
index 3c74fce1..1cef475e 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/ExtendedQueryProtocolHandler.java
@@ -66,9 +66,7 @@ public class ExtendedQueryProtocolHandler {
                 .getServer()
                 .getTracer(ConnectionHandler.class.getName(), getVersion()),
             connectionHandler.getServer().getMetrics(),
-            createMetricAttributes(
-                connectionHandler.getDatabaseId(),
-                connectionHandler.getTraceConnectionId().toString()),
+            createMetricAttributes(connectionHandler.getDatabaseId()),
             connectionHandler.getTraceConnectionId().toString(),
             connectionHandler::closeAllPortals,
             connectionHandler.getDatabaseId(),
@@ -97,9 +95,8 @@ public class ExtendedQueryProtocolHandler {
   }
 
   @VisibleForTesting
-  static Attributes createMetricAttributes(DatabaseId databaseId, String connectionId) {
+  static Attributes createMetricAttributes(DatabaseId databaseId) {
     AttributesBuilder attributesBuilder = Attributes.builder();
-    attributesBuilder.put("pgadapter.connection_id", connectionId);
     attributesBuilder.put("database", databaseId.getDatabase());
     attributesBuilder.put("instance_id", databaseId.getInstanceId().getInstance());
     attributesBuilder.put("project_id", databaseId.getInstanceId().getProject());
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/ShutdownStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/ShutdownStatement.java
new file mode 100644
index 00000000..329b3e25
--- /dev/null
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/ShutdownStatement.java
@@ -0,0 +1,156 @@
+// 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.ProxyServer;
+import com.google.cloud.spanner.pgadapter.ProxyServer.ShutdownMode;
+import com.google.cloud.spanner.pgadapter.ShutdownHandler;
+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.NoResult;
+import com.google.cloud.spanner.pgadapter.statements.ShutdownStatement.ParsedShutdownStatement.Builder;
+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;
+
+/**
+ * <code>SHUTDOWN [FAST | SMART | IMMEDIATE]</code>
+ *
+ * <p>Shuts down the proxy server. This statement is a PGAdapter-only statement, and is not
+ * supported on normal PostgreSQL.
+ */
+public class ShutdownStatement extends IntermediatePortalStatement {
+  static final class ParsedShutdownStatement {
+    static final class Builder {
+      ShutdownMode shutdownMode = ShutdownMode.FAST;
+    }
+
+    /** The server that will be shut down. */
+    final ProxyServer server;
+
+    final ShutdownMode shutdownMode;
+
+    private ParsedShutdownStatement(Builder builder, ProxyServer server) {
+      this.server = server;
+      this.shutdownMode = builder.shutdownMode;
+    }
+  }
+
+  private final ParsedShutdownStatement parsedShutdownStatement;
+
+  private ShutdownHandler shutdownHandler;
+
+  public ShutdownStatement(
+      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.parsedShutdownStatement = parse(originalStatement.getSql(), connectionHandler.getServer());
+  }
+
+  @Override
+  public String getCommandTag() {
+    return "SHUTDOWN";
+  }
+
+  @Override
+  public StatementType getStatementType() {
+    return StatementType.UNKNOWN;
+  }
+
+  @Override
+  public void close() throws Exception {
+    super.close();
+    if (this.executed && this.shutdownHandler != null) {
+      this.shutdownHandler.shutdown(this.parsedShutdownStatement.shutdownMode);
+    }
+  }
+
+  @Override
+  public void executeAsync(BackendConnection backendConnection) {
+    if (!this.executed) {
+      this.executed = true;
+      if (options.isAllowShutdownStatement()) {
+        this.shutdownHandler = this.parsedShutdownStatement.server.getOrCreateShutdownHandler();
+        setFutureStatementResult(Futures.immediateFuture(new NoResult(getCommandTag())));
+      } else {
+        setFutureStatementResult(
+            Futures.immediateFailedFuture(
+                PGExceptionFactory.newPGException(
+                    "SHUTDOWN [SMART | FAST | IMMEDIATE] statement is not enabled for this server. Start PGAdapter with --allow_shutdown_statement to enable the use of the SHUTDOWN statement.",
+                    SQLState.FeatureNotSupported)));
+      }
+    }
+  }
+
+  @Override
+  public Future<StatementResult> describeAsync(BackendConnection backendConnection) {
+    // Return null to indicate that this SHUTDOWN statement does not return any
+    // RowDescriptionResponse.
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public IntermediatePortalStatement createPortal(
+      String name,
+      byte[][] parameters,
+      List<Short> parameterFormatCodes,
+      List<Short> resultFormatCodes) {
+    // SHUTDOWN does not support binding any parameters, so we just return the same statement.
+    return this;
+  }
+
+  static ParsedShutdownStatement parse(String sql, ProxyServer server) {
+    Preconditions.checkNotNull(sql);
+    Preconditions.checkNotNull(server);
+
+    SimpleParser parser = new SimpleParser(sql);
+    ParsedShutdownStatement.Builder builder = new Builder();
+    if (!parser.eatKeyword("shutdown")) {
+      throw PGExceptionFactory.newPGException(
+          "not a valid SHUTDOWN statement: " + sql, SQLState.SyntaxError);
+    }
+    if (parser.eatKeyword("fast")) {
+      builder.shutdownMode = ShutdownMode.FAST;
+    } else if (parser.eatKeyword("smart")) {
+      builder.shutdownMode = ShutdownMode.SMART;
+    } else if (parser.eatKeyword("immediate")) {
+      builder.shutdownMode = ShutdownMode.IMMEDIATE;
+    }
+    parser.throwIfHasMoreTokens();
+    return new ParsedShutdownStatement(builder, server);
+  }
+}
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 b01fc53f..fcdc30a9 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
@@ -30,6 +30,7 @@ import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SAVEP
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SELECT_CURRENT_SETTING;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SELECT_SET_CONFIG;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SHOW_DATABASE_DDL;
+import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SHUTDOWN;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.TRUNCATE;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.VACUUM;
 
@@ -58,6 +59,7 @@ import com.google.cloud.spanner.pgadapter.statements.SavepointStatement;
 import com.google.cloud.spanner.pgadapter.statements.SelectCurrentSettingStatement;
 import com.google.cloud.spanner.pgadapter.statements.SelectSetConfigStatement;
 import com.google.cloud.spanner.pgadapter.statements.ShowDatabaseDdlStatement;
+import com.google.cloud.spanner.pgadapter.statements.ShutdownStatement;
 import com.google.cloud.spanner.pgadapter.statements.TruncateStatement;
 import com.google.cloud.spanner.pgadapter.statements.VacuumStatement;
 import com.google.cloud.spanner.pgadapter.wireoutput.ParseCompleteResponse;
@@ -244,6 +246,13 @@ public class ParseMessage extends AbstractQueryProtocolMessage {
             name,
             parsedStatement,
             originalStatement);
+      } else if (isCommand(SHUTDOWN, originalStatement.getSql())) {
+        return new ShutdownStatement(
+            connectionHandler,
+            connectionHandler.getServer().getOptions(),
+            name,
+            parsedStatement,
+            originalStatement);
       } else {
         return new IntermediatePreparedStatement(
             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 4f417793..02bd901c 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
@@ -41,6 +41,7 @@ public class QueryMessage extends ControlMessage {
   public static final String FETCH = "FETCH";
   public static final String MOVE = "MOVE";
   public static final String CLOSE = "CLOSE";
+  public static final String SHUTDOWN = "SHUTDOWN";
   public static final ImmutableList<String> SHOW_DATABASE_DDL =
       ImmutableList.of("SHOW", "DATABASE", "DDL");
   public static final ImmutableList<String> SELECT_CURRENT_SETTING =
diff --git a/src/main/resources/metadata/default_command_metadata.json b/src/main/resources/metadata/default_command_metadata.json
index 7e711d23..647b591e 100644
--- a/src/main/resources/metadata/default_command_metadata.json
+++ b/src/main/resources/metadata/default_command_metadata.json
@@ -123,7 +123,7 @@
       "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the seventh. The Spanner fetch does not require a similar step so we just hardcode it."
     },
     {
-      "input_pattern": "(?s)^SELECT pubname\\s+FROM pg_catalog\\.pg_publication p\\s+JOIN pg_catalog\\.pg_publication_rel pr ON p.oid = pr\\.prpubid\\s+WHERE pr\\.prrelid = '.*'\\s+UNION ALL\\s+SELECT pubname\\s+FROM pg_catalog\\.pg_publication p\\s+WHERE p\\.puballtables AND pg_catalog\\.pg_relation_is_publishable\\('.*'\\)\\s+ORDER BY 1;?$",
+      "input_pattern": "(?s)^SELECT pubname.*\\s+FROM pg_catalog\\.pg_publication p\\s+JOIN pg_catalog\\.pg_publication_rel pr ON p.oid = pr\\.prpubid\\s+WHERE pr\\.prrelid = '.*'\\s+UNION ALL\\s+SELECT pubname.*\\s+FROM pg_catalog\\.pg_publication p\\s+WHERE p\\.puballtables AND pg_catalog\\.pg_relation_is_publishable\\('.*'\\)\\s+ORDER BY 1;?$",
       "output_pattern": "SELECT 1 LIMIT 0;",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the eighth. The Spanner fetch does not require a similar step so we just hardcode it."


Test patch:
diff --git a/.ci/e2e-expected/backslash-dt.txt b/.ci/e2e-expected/backslash-dt.txt
index 9c3fc099..35c3ccfa 100644
--- a/.ci/e2e-expected/backslash-dt.txt
+++ b/.ci/e2e-expected/backslash-dt.txt
@@ -65,9 +65,8 @@
  testdb_e2e_psql_v?? | spanner_sys        | read_stats_total_hour            | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | read_stats_total_minute          | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | row_deletion_policies            | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 |
- testdb_e2e_psql_v?? | spanner_sys        | split_hotness_stats_top_10minute | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
- testdb_e2e_psql_v?? | spanner_sys        | split_hotness_stats_top_hour     | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | split_hotness_stats_top_minute   | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
+ testdb_e2e_psql_v?? | spanner_sys        | split_stats_top_minute           | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | supported_optimizer_versions     | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | table_operations_stats_10minute  | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | table_operations_stats_hour      | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
@@ -79,5 +78,5 @@
  testdb_e2e_psql_v?? | spanner_sys        | txn_stats_total_10minute         | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | txn_stats_total_hour             | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
  testdb_e2e_psql_v?? | spanner_sys        | txn_stats_total_minute           | VIEW       |                              |                      |                           |                          |                        |                    |          |               |                   |                  |               |                 | 
-(78 rows)
+(77 rows)
 
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
index 61e783fc..f4f8eb86 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
@@ -5596,6 +5596,19 @@ public class JdbcMockServerTest extends AbstractMockServerTest {
         exception.getMessage().contains("The server does not support GSS Encryption"));
   }
 
+  @Test
+  public void testShutdown_failsByDefault() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl());
+        java.sql.Statement statement = connection.createStatement()) {
+      PSQLException exception =
+          assertThrows(PSQLException.class, () -> statement.execute("shutdown"));
+      assertEquals(
+          "ERROR: SHUTDOWN [SMART | FAST | IMMEDIATE] statement is not enabled for this server. "
+              + "Start PGAdapter with --allow_shutdown_statement to enable the use of the SHUTDOWN statement.",
+          exception.getMessage());
+    }
+  }
+
   private void verifySelect1(java.sql.Statement statement) throws SQLException {
     try (ResultSet resultSet = statement.executeQuery("SELECT 1")) {
       assertTrue(resultSet.next());
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ShutdownModeMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ShutdownModeMockServerTest.java
index e7d3d61b..ff3e5ca8 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ShutdownModeMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ShutdownModeMockServerTest.java
@@ -32,15 +32,26 @@ import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Statement;
 import java.util.concurrent.TimeUnit;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
-@RunWith(JUnit4.class)
+@RunWith(Parameterized.class)
 public class ShutdownModeMockServerTest extends AbstractMockServerTest {
+
+  @Parameter public boolean useSqlStatement;
+
+  @Parameters(name = "useSqlStatement = {0}")
+  public static Object[] data() {
+    return new Object[] {false, true};
+  }
+
   private ProxyServer proxyServer;
 
   @Before
@@ -51,7 +62,8 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
         .enableDebugMode()
         .setUsePlainText()
         .setEndpoint(String.format("localhost:%d", spannerServer.getPort()))
-        .setCredentials(NoCredentials.getInstance());
+        .setCredentials(NoCredentials.getInstance())
+        .setAllowShutdownStatement(true);
     proxyServer = new ProxyServer(builder.build(), OpenTelemetry.noop());
     proxyServer.startServer();
   }
@@ -71,7 +83,7 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
   public void testSmartShutdown() throws SQLException {
     String sql = "SELECT 1";
 
-    ShutdownHandler shutdownHandler = ShutdownHandler.createForServer(proxyServer);
+    ShutdownHandler shutdownHandler = createShutdownHandler();
     // Verify that the server is running.
     assertEquals(State.RUNNING, proxyServer.state());
 
@@ -83,7 +95,11 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
         assertFalse(resultSet.next());
       }
       // Initiate a smart shutdown.
-      shutdownHandler.shutdown(ShutdownMode.SMART);
+      if (useSqlStatement) {
+        connection.createStatement().execute("shutdown smart");
+      } else {
+        shutdownHandler.shutdown(ShutdownMode.SMART);
+      }
       // Wait until the server port is no longer open.
       Stopwatch stopwatch = Stopwatch.createStarted();
       while (isPortListening(proxyServer.getLocalPort())
@@ -127,7 +143,7 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
   public void testSmartShutdownWithoutConnections() throws SQLException {
     String sql = "SELECT 1";
 
-    ShutdownHandler shutdownHandler = ShutdownHandler.createForServer(proxyServer);
+    ShutdownHandler shutdownHandler = createShutdownHandler();
     // Verify that the server is running.
     assertEquals(State.RUNNING, proxyServer.state());
 
@@ -140,7 +156,14 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
       }
     }
     // Initiate a smart shutdown.
-    shutdownHandler.shutdown(ShutdownMode.SMART);
+    if (useSqlStatement) {
+      try (Connection connection = DriverManager.getConnection(createUrl());
+          Statement statement = connection.createStatement()) {
+        statement.execute("shutdown smart");
+      }
+    } else {
+      shutdownHandler.shutdown(ShutdownMode.SMART);
+    }
     // Wait until the server port is no longer open.
     Stopwatch stopwatch = Stopwatch.createStarted();
     while (isPortListening(proxyServer.getLocalPort())
@@ -169,7 +192,7 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
   public void testFastShutdown() throws SQLException {
     String sql = "SELECT 1";
 
-    ShutdownHandler shutdownHandler = ShutdownHandler.createForServer(proxyServer);
+    ShutdownHandler shutdownHandler = createShutdownHandler();
     // Verify that the server is running.
     assertEquals(State.RUNNING, proxyServer.state());
 
@@ -181,7 +204,11 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
         assertFalse(resultSet.next());
       }
       // Initiate a fast shutdown.
-      shutdownHandler.shutdown(ShutdownMode.FAST);
+      if (useSqlStatement) {
+        connection.createStatement().execute("shutdown fast");
+      } else {
+        shutdownHandler.shutdown(ShutdownMode.FAST);
+      }
       // Verify that the server stops.
       Stopwatch stopwatch = Stopwatch.createStarted();
       while (proxyServer.state() != State.TERMINATED
@@ -209,7 +236,7 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
   public void testSmartShutdownFollowedByFastShutdown() throws SQLException {
     String sql = "SELECT 1";
 
-    ShutdownHandler shutdownHandler = ShutdownHandler.createForServer(proxyServer);
+    ShutdownHandler shutdownHandler = createShutdownHandler();
     // Verify that the server is running.
     assertEquals(State.RUNNING, proxyServer.state());
 
@@ -221,7 +248,11 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
         assertFalse(resultSet.next());
       }
       // Initiate a smart shutdown.
-      shutdownHandler.shutdown(ShutdownMode.SMART);
+      if (useSqlStatement) {
+        connection.createStatement().execute("shutdown smart");
+      } else {
+        shutdownHandler.shutdown(ShutdownMode.SMART);
+      }
       // Wait until the server port is no longer open.
       Stopwatch stopwatch = Stopwatch.createStarted();
       while (isPortListening(proxyServer.getLocalPort())
@@ -248,7 +279,11 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
       assertEquals(State.STOPPING, proxyServer.state());
 
       // Now force the server to stop by initiating a fast shutdown.
-      shutdownHandler.shutdown(ShutdownMode.FAST);
+      if (useSqlStatement) {
+        connection.createStatement().execute("shutdown fast");
+      } else {
+        shutdownHandler.shutdown(ShutdownMode.FAST);
+      }
       stopwatch = Stopwatch.createStarted();
       while (proxyServer.state() != State.TERMINATED
           && stopwatch.elapsed(TimeUnit.MILLISECONDS) < 2000) {
@@ -263,6 +298,10 @@ public class ShutdownModeMockServerTest extends AbstractMockServerTest {
     }
   }
 
+  ShutdownHandler createShutdownHandler() {
+    return useSqlStatement ? null : ShutdownHandler.createForServer(this.proxyServer);
+  }
+
   boolean isPortListening(int port) {
     try (Socket ignored = new Socket("localhost", port)) {
       return true;
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
index 58a06cd4..7edfa78a 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
@@ -87,7 +87,7 @@ public class BackendConnectionTest {
   private static final Runnable DO_NOTHING = () -> {};
   private static final DatabaseId DATABASE_ID = DatabaseId.of("p", "i", "d");
   private static final Attributes METRIC_ATTRIBUTES =
-      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID, "test-id");
+      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID);
 
   @Test
   public void testExtractDdlUpdateCounts() {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/ShutdownStatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/ShutdownStatementTest.java
new file mode 100644
index 00000000..f3369114
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/ShutdownStatementTest.java
@@ -0,0 +1,163 @@
+// 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.ShutdownStatement.parse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.connection.AbstractStatementParser;
+import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
+import com.google.cloud.spanner.pgadapter.ConnectionHandler;
+import com.google.cloud.spanner.pgadapter.ProxyServer;
+import com.google.cloud.spanner.pgadapter.ProxyServer.ShutdownMode;
+import com.google.cloud.spanner.pgadapter.ShutdownHandler;
+import com.google.cloud.spanner.pgadapter.error.PGException;
+import com.google.cloud.spanner.pgadapter.error.SQLState;
+import com.google.cloud.spanner.pgadapter.metadata.ConnectionMetadata;
+import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
+import com.google.cloud.spanner.pgadapter.statements.IntermediateStatement.ResultNotReadyBehavior;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ShutdownStatementTest {
+
+  @Test
+  public void testParseShutdown() {
+    ProxyServer server = mock(ProxyServer.class);
+
+    assertEquals(ShutdownMode.FAST, parse("shutdown", server).shutdownMode);
+    assertEquals(ShutdownMode.FAST, parse("Shutdown", server).shutdownMode);
+    assertEquals(ShutdownMode.FAST, parse("SHUTDOWN", server).shutdownMode);
+    assertEquals(ShutdownMode.FAST, parse("\t shutdown \n", server).shutdownMode);
+    assertEquals(ShutdownMode.IMMEDIATE, parse("shutdown immediate", server).shutdownMode);
+    assertEquals(ShutdownMode.SMART, parse("shutdown smart", server).shutdownMode);
+    assertEquals(ShutdownMode.FAST, parse("shutdown fast", server).shutdownMode);
+    assertEquals(ShutdownMode.FAST, parse("shutdown\tfast", server).shutdownMode);
+    assertEquals(ShutdownMode.FAST, parse("shutdown\nfast", server).shutdownMode);
+    assertEquals(ShutdownMode.FAST, parse("shutdown\n\t   fast", server).shutdownMode);
+
+    PGException exception;
+    exception = assertThrows(PGException.class, () -> parse("shutdown fast immediate", server));
+    assertEquals(SQLState.SyntaxError, exception.getSQLState());
+    exception = assertThrows(PGException.class, () -> parse("shutdown foo", server));
+    assertEquals(SQLState.SyntaxError, exception.getSQLState());
+    exception = assertThrows(PGException.class, () -> parse("shutdown foo fast", server));
+    assertEquals(SQLState.SyntaxError, exception.getSQLState());
+    exception = assertThrows(PGException.class, () -> parse("shutdown fast,", server));
+    assertEquals(SQLState.SyntaxError, exception.getSQLState());
+    exception = assertThrows(PGException.class, () -> parse("fast shutdown", server));
+    assertEquals(SQLState.SyntaxError, exception.getSQLState());
+  }
+
+  @Test
+  public void testShutdownStatement_usesDefaultShutdownMode() throws Exception {
+    OptionsMetadata options = mock(OptionsMetadata.class);
+    when(options.isAllowShutdownStatement()).thenReturn(true);
+    ConnectionHandler connectionHandler = mock(ConnectionHandler.class);
+    ProxyServer server = mock(ProxyServer.class);
+    when(connectionHandler.getServer()).thenReturn(server);
+    when(connectionHandler.getConnectionMetadata()).thenReturn(mock(ConnectionMetadata.class));
+    ShutdownHandler shutdownHandler = mock(ShutdownHandler.class);
+    when(server.getOrCreateShutdownHandler()).thenReturn(shutdownHandler);
+
+    String sql = "shutdown";
+    Statement originalStatement = Statement.of(sql);
+    ParsedStatement parsedStatement =
+        AbstractStatementParser.getInstance(Dialect.POSTGRESQL).parse(originalStatement);
+    ShutdownStatement statement =
+        new ShutdownStatement(
+            connectionHandler, options, /* name= */ "", parsedStatement, originalStatement);
+
+    statement.executeAsync(mock(BackendConnection.class));
+    statement.close();
+
+    // PGAdapter uses FAST shutdown mode by default.
+    // We need to use a timeout, as the shutdown handler runs async. The wait time is normally much
+    // less than the 1000 milliseconds that is used as the timeout value.
+    verify(shutdownHandler, timeout(1000L)).shutdown(ShutdownMode.FAST);
+  }
+
+  @Test
+  public void testShutdownStatement_usesParsedShutdownMode() throws Exception {
+    OptionsMetadata options = mock(OptionsMetadata.class);
+    when(options.isAllowShutdownStatement()).thenReturn(true);
+    ConnectionHandler connectionHandler = mock(ConnectionHandler.class);
+    ProxyServer server = mock(ProxyServer.class);
+    when(connectionHandler.getServer()).thenReturn(server);
+    when(connectionHandler.getConnectionMetadata()).thenReturn(mock(ConnectionMetadata.class));
+    ShutdownHandler shutdownHandler = mock(ShutdownHandler.class);
+    when(server.getOrCreateShutdownHandler()).thenReturn(shutdownHandler);
+
+    String sql = "shutdown smart";
+    Statement originalStatement = Statement.of(sql);
+    ParsedStatement parsedStatement =
+        AbstractStatementParser.getInstance(Dialect.POSTGRESQL).parse(originalStatement);
+    ShutdownStatement statement =
+        new ShutdownStatement(
+            connectionHandler, options, /* name= */ "", parsedStatement, originalStatement);
+
+    statement.executeAsync(mock(BackendConnection.class));
+    statement.close();
+
+    // The shutdown mode from the SQL statement should be used.
+    // We need to use a timeout, as the shutdown handler runs async. The wait time is normally much
+    // less than the 1000 milliseconds that is used as the timeout value.
+    verify(shutdownHandler, timeout(1000L)).shutdown(ShutdownMode.SMART);
+  }
+
+  @Test
+  public void testShutdownStatement_returnsErrorIfNotEnabled() throws Exception {
+    OptionsMetadata options = mock(OptionsMetadata.class);
+    when(options.isAllowShutdownStatement()).thenReturn(false);
+    ConnectionHandler connectionHandler = mock(ConnectionHandler.class);
+    ProxyServer server = mock(ProxyServer.class);
+    when(connectionHandler.getServer()).thenReturn(server);
+    when(connectionHandler.getConnectionMetadata()).thenReturn(mock(ConnectionMetadata.class));
+
+    String sql = "shutdown";
+    Statement originalStatement = Statement.of(sql);
+    ParsedStatement parsedStatement =
+        AbstractStatementParser.getInstance(Dialect.POSTGRESQL).parse(originalStatement);
+    ShutdownStatement statement =
+        new ShutdownStatement(
+            connectionHandler, options, /* name= */ "", parsedStatement, originalStatement);
+
+    // Execute the statement and then get the result.
+    statement.executeAsync(mock(BackendConnection.class));
+    statement.close();
+    // The result should be an exception.
+    statement.initFutureResult(ResultNotReadyBehavior.FAIL);
+    assertNotNull(statement.getException());
+    assertEquals(
+        "SHUTDOWN [SMART | FAST | IMMEDIATE] statement is not enabled for this server. "
+            + "Start PGAdapter with --allow_shutdown_statement to enable the use of the SHUTDOWN statement.",
+        statement.getException().getMessage());
+
+    // The server should not be shut down.
+    verify(server, never()).stopServer(any(ShutdownMode.class));
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
index 9cac0270..a5513ef5 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
@@ -97,7 +97,7 @@ public class StatementTest {
   private static final Runnable DO_NOTHING = () -> {};
   private static final DatabaseId DATABASE_ID = DatabaseId.of("p", "i", "d");
   private static final Attributes METRIC_ATTRIBUTES =
-      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID, "test-connection");
+      ExtendedQueryProtocolHandler.createMetricAttributes(DATABASE_ID);
 
   private static ParsedStatement parse(String sql) {
     return PARSER.parse(Statement.of(sql));
@@ -521,7 +521,7 @@ public class StatementTest {
         new BackendConnection(
             NOOP_OTEL,
             NOOP_OTEL_METER,
-            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId, "test-connection"),
+            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId),
             UUID.randomUUID().toString(),
             DO_NOTHING,
             databaseId,
@@ -629,7 +629,7 @@ public class StatementTest {
         new BackendConnection(
             NOOP_OTEL,
             NOOP_OTEL_METER,
-            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId, "test-connection"),
+            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId),
             UUID.randomUUID().toString(),
             DO_NOTHING,
             databaseId,
@@ -688,7 +688,7 @@ public class StatementTest {
         new BackendConnection(
             NOOP_OTEL,
             NOOP_OTEL_METER,
-            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId, "test-connection"),
+            ExtendedQueryProtocolHandler.createMetricAttributes(databaseId),
             UUID.randomUUID().toString(),
             DO_NOTHING,
             databaseId,