Repository: GoogleCloudPlatform/pgadapter
Pull request #893 (2023-06-22 10:04:56)

feat: support replacement of DDL statements
Adds support for replacing DDL statements on a per-client basis. This allows us to automatically replace well-known DDL statements that are not supported with supported statements. This change also includes replacements for the Liquibase changelog tables.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/893?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) Report
> Merging [#893](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/893?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) (dea3ad4) into [postgresql-dialect](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/commit/62afdf9f479b08b12c2df528480995b6e088bfee?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) (62afdf9) will **increase** coverage by `0.00%`.
> The diff coverage is `90.90%`.

```diff
@@                  Coverage Diff                  @@
##             postgresql-dialect     #893   +/-   ##
=====================================================
  Coverage                 89.97%   89.97%           
- Complexity                 2484     2489    +5     
=====================================================
  Files                       139      139           
  Lines                      8233     8253   +20     
  Branches                   1206     1208    +2     
=====================================================
+ Hits                       7408     7426   +18     
- Misses                      570      571    +1     
- Partials                    255      256    +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| all_tests | `89.97% <90.90%> (+<0.01%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/893?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) | Coverage Δ | |
|---|---|---|
| [...loud/spanner/pgadapter/statements/DdlExecutor.java](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/893?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3N0YXRlbWVudHMvRGRsRXhlY3V0b3IuamF2YQ==) | `93.10% <85.71%> (-2.46%)` | :arrow_down: |
| [...panner/pgadapter/statements/BackendConnection.java](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/893?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3N0YXRlbWVudHMvQmFja2VuZENvbm5lY3Rpb24uamF2YQ==) | `92.34% <100.00%> (+0.01%)` | :arrow_up: |
| [...ud/spanner/pgadapter/utils/ClientAutoDetector.java](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/893?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3V0aWxzL0NsaWVudEF1dG9EZXRlY3Rvci5qYXZh) | `93.87% <100.00%> (+0.19%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform)


Patch:
diff --git a/samples/golang/pgx/go.mod b/samples/golang/pgx/go.mod
index de06c993..46cf9d29 100644
--- a/samples/golang/pgx/go.mod
+++ b/samples/golang/pgx/go.mod
@@ -3,8 +3,8 @@ module pgadapter-pgx-sample
 go 1.20
 
 require (
-	github.com/jackc/pgx/v5 v5.3.1
-	github.com/testcontainers/testcontainers-go v0.20.1
+	github.com/jackc/pgx/v5 v5.4.1
+	github.com/testcontainers/testcontainers-go v0.21.0
 	golang.org/x/oauth2 v0.8.0
 )
 
@@ -22,21 +22,22 @@ require (
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/uuid v1.3.0 // indirect
-	github.com/imdario/mergo v0.3.12 // indirect
+	github.com/imdario/mergo v0.3.15 // indirect
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
 	github.com/klauspost/compress v1.11.13 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/moby/patternmatcher v0.5.0 // indirect
 	github.com/moby/sys/sequential v0.5.0 // indirect
-	github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f // indirect
+	github.com/moby/term v0.5.0 // indirect
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
 	github.com/opencontainers/runc v1.1.5 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
-	golang.org/x/crypto v0.6.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
 	golang.org/x/net v0.10.0 // indirect
 	golang.org/x/sys v0.8.0 // indirect
 	golang.org/x/text v0.9.0 // indirect
diff --git a/samples/golang/pgx/go.sum b/samples/golang/pgx/go.sum
index 1939a6b9..5d85622f 100644
--- a/samples/golang/pgx/go.sum
+++ b/samples/golang/pgx/go.sum
@@ -30,7 +30,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
 github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
 github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
+github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -86,14 +86,14 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
-github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
+github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
-github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
+github.com/jackc/pgx/v5 v5.4.1 h1:oKfB/FhuVtit1bBM3zNRRsZ925ZkMN3HXL+LgLUM9lE=
+github.com/jackc/pgx/v5 v5.4.1/go.mod h1:q6iHT8uDNXWiFNOlRqJzBTaSH3+2xCXkokxHZC5qWFY=
 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=
 github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
@@ -108,8 +108,8 @@ github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YO
 github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
 github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
 github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
-github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f h1:J/7hjLaHLD7epG0m6TBMGmp4NQ+ibBYLfeyJWdAIFLA=
-github.com/moby/term v0.0.0-20221128092401-c43b287e0e0f/go.mod h1:15ce4BGCFxt7I5NQKT+HV0yEDxmf6fSysfEDiVo3zFM=
+github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
+github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
 github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
 github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
@@ -139,10 +139,10 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/testcontainers/testcontainers-go v0.20.1 h1:mK15UPJ8c5P+NsQKmkqzs/jMdJt6JMs5vlw2y4j92c0=
-github.com/testcontainers/testcontainers-go v0.20.1/go.mod h1:zb+NOlCQBkZ7RQp4QI+YMIHyO2CQ/qsXzNF5eLJ24SY=
+github.com/testcontainers/testcontainers-go v0.21.0 h1:syePAxdeTzfkap+RrJaQZpJQ/s/fsUgn11xIvHrOE9U=
+github.com/testcontainers/testcontainers-go v0.21.0/go.mod h1:c1ez3WVRHq7T/Aj+X3TIipFBwkBaNT5iNCY8+1b83Ng=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
@@ -152,9 +152,11 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
+golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -260,10 +262,9 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
index 12d59d61..d2b48c70 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
@@ -737,7 +737,8 @@ public class BackendConnection {
             () -> new PgCatalog(BackendConnection.this.sessionState, wellKnownClient.get()));
     this.spannerConnection = spannerConnection;
     this.databaseId = databaseId;
-    this.ddlExecutor = new DdlExecutor(this);
+    this.ddlExecutor =
+        new DdlExecutor(this, Suppliers.memoize(() -> wellKnownClient.get().getDdlReplacements()));
     this.localStatements =
         Suppliers.memoize(
             () -> {
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutor.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutor.java
index 8f69855b..e3e41b77 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutor.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutor.java
@@ -14,6 +14,7 @@
 
 package com.google.cloud.spanner.pgadapter.statements;
 
+import com.google.cloud.Tuple;
 import com.google.cloud.spanner.Dialect;
 import com.google.cloud.spanner.Statement;
 import com.google.cloud.spanner.connection.AbstractStatementParser;
@@ -21,6 +22,11 @@ import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStateme
 import com.google.cloud.spanner.connection.Connection;
 import com.google.cloud.spanner.connection.StatementResult;
 import com.google.cloud.spanner.pgadapter.statements.SimpleParser.TableOrIndexName;
+import com.google.cloud.spanner.pgadapter.utils.QueryPartReplacer;
+import com.google.cloud.spanner.pgadapter.utils.QueryPartReplacer.ReplacementStatus;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import java.util.function.Supplier;
 
 /**
  * {@link DdlExecutor} inspects DDL statements before executing these to support commonly used DDL
@@ -30,11 +36,15 @@ class DdlExecutor {
 
   private final BackendConnection backendConnection;
   private final Connection connection;
+  private final Supplier<ImmutableList<QueryPartReplacer>> ddlStatementReplacements;
 
   /** Constructor for a {@link DdlExecutor} for the given connection. */
-  DdlExecutor(BackendConnection backendConnection) {
+  DdlExecutor(
+      BackendConnection backendConnection,
+      Supplier<ImmutableList<QueryPartReplacer>> ddlStatementReplacements) {
     this.backendConnection = backendConnection;
     this.connection = backendConnection.getSpannerConnection();
+    this.ddlStatementReplacements = ddlStatementReplacements;
   }
 
   /**
@@ -56,9 +66,28 @@ class DdlExecutor {
    * make the statement compatible with Cloud Spanner.
    */
   StatementResult execute(ParsedStatement parsedStatement, Statement statement) {
+    if (!ddlStatementReplacements.get().isEmpty()) {
+      String replaced = applyReplacers(statement.getSql());
+      if (!replaced.equals(statement.getSql())) {
+        statement = Statement.of(replaced);
+        parsedStatement = AbstractStatementParser.getInstance(Dialect.POSTGRESQL).parse(statement);
+      }
+    }
     return connection.execute(translate(parsedStatement, statement));
   }
 
+  @VisibleForTesting
+  String applyReplacers(String sql) {
+    for (QueryPartReplacer functionReplacement : ddlStatementReplacements.get()) {
+      Tuple<String, ReplacementStatus> result = functionReplacement.replace(sql);
+      if (result.y() == ReplacementStatus.STOP) {
+        return result.x();
+      }
+      sql = result.x();
+    }
+    return sql;
+  }
+
   /**
    * Translates a DDL statement that can contain 'create table' statement with a named primary key
    * into one that is supported by the backend.
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/utils/ClientAutoDetector.java b/src/main/java/com/google/cloud/spanner/pgadapter/utils/ClientAutoDetector.java
index 498fb799..8b0c101c 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/utils/ClientAutoDetector.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/utils/ClientAutoDetector.java
@@ -308,6 +308,63 @@ public class ClientAutoDetector {
         return ImmutableMap.of(
             "spanner.guess_types", String.format("%d,%d", Oid.TIMESTAMPTZ, Oid.DATE));
       }
+
+      @Override
+      public ImmutableList<QueryPartReplacer> getDdlReplacements() {
+        // Replace known metadata tables for Liquibase.
+        return ImmutableList.of(
+            RegexQueryPartReplacer.replace(
+                Pattern.compile(
+                    "CREATE\\s+TABLE\\s+(?:.*\\.)?databasechangeloglock\\s*\\(\\s*"
+                        + "ID\\s+INTEGER\\s+NOT\\s+NULL\\s*,\\s*"
+                        + "LOCKED\\s+BOOLEAN\\s+NOT\\s+NULL\\s*,\\s*"
+                        + "LOCKGRANTED\\s+TIMESTAMP\\s+WITHOUT\\s+TIME\\s+ZONE\\s*,\\s*"
+                        + "LOCKEDBY\\s+VARCHAR\\s*\\(255\\)\\s*,\\s*"
+                        + "CONSTRAINT\\s*databasechangeloglock_pkey\\s*PRIMARY\\s+KEY\\s*\\(ID\\)\\s*\\s*"
+                        + "\\)\\s*",
+                    Pattern.CASE_INSENSITIVE),
+                "CREATE TABLE databasechangeloglock (\n"
+                    + "    ID INTEGER NOT NULL,\n"
+                    + "    LOCKED BOOLEAN NOT NULL,\n"
+                    + "    LOCKGRANTED TIMESTAMPTZ,\n"
+                    + "    LOCKEDBY VARCHAR(255),\n"
+                    + "    PRIMARY KEY (ID)\n"
+                    + ")"),
+            RegexQueryPartReplacer.replace(
+                Pattern.compile(
+                    "CREATE\\s+TABLE\\s+(?:.*\\.)?databasechangelog\\s*\\(\\s*"
+                        + "ID\\s+VARCHAR\\s*\\(255\\)\\s*NOT\\s+NULL\\s*,\\s*"
+                        + "AUTHOR\\s+VARCHAR\\s*\\(255\\)\\s*NOT\\s+NULL\\s*,\\s*"
+                        + "FILENAME\\s+VARCHAR\\s*\\(255\\)\\s*NOT\\s+NULL\\s*,\\s*"
+                        + "DATEEXECUTED\\s+TIMESTAMP(?:.*)?\\s+NOT\\s+NULL\\s*,\\s*"
+                        + "ORDEREXECUTED\\s+INTEGER\\s+NOT\\s+NULL\\s*,\\s*"
+                        + "EXECTYPE\\s+VARCHAR\\s*\\(10\\)\\s*NOT\\s+NULL\\s*,\\s*"
+                        + "MD5SUM\\s+VARCHAR\\s*\\(35\\)\\s*,\\s*"
+                        + "DESCRIPTION\\s+VARCHAR\\s*\\(255\\)\\s*,\\s*"
+                        + "COMMENTS\\s+VARCHAR\\s*\\(255\\)\\s*,\\s*"
+                        + "TAG\\s+VARCHAR\\s*\\(255\\)\\s*,\\s*"
+                        + "LIQUIBASE\\s+VARCHAR\\s*\\(20\\)\\s*,\\s*"
+                        + "CONTEXTS\\s+VARCHAR\\s*\\(255\\)\\s*,\\s*"
+                        + "LABELS\\s+VARCHAR\\s*\\(255\\)\\s*,\\s*"
+                        + "DEPLOYMENT_ID\\s+VARCHAR\\s*\\(10\\)\\s*"
+                        + "\\)\\s*"),
+                "CREATE TABLE databasechangelog (\n"
+                    + "    ID VARCHAR(255) NOT NULL PRIMARY KEY,\n"
+                    + "    AUTHOR VARCHAR(255) NOT NULL,\n"
+                    + "    FILENAME VARCHAR(255) NOT NULL,\n"
+                    + "    DATEEXECUTED TIMESTAMPTZ NOT NULL,\n"
+                    + "    ORDEREXECUTED INTEGER NOT NULL,\n"
+                    + "    EXECTYPE VARCHAR(10) NOT NULL,\n"
+                    + "    MD5SUM VARCHAR(35),\n"
+                    + "    DESCRIPTION VARCHAR(255),\n"
+                    + "    COMMENTS VARCHAR(255),\n"
+                    + "    TAG VARCHAR(255),\n"
+                    + "    LIQUIBASE VARCHAR(20),\n"
+                    + "    CONTEXTS VARCHAR(255),\n"
+                    + "    LABELS VARCHAR(255),\n"
+                    + "    DEPLOYMENT_ID VARCHAR(10)\n"
+                    + ")"));
+      }
     },
     PGX {
       @Override
@@ -471,6 +528,10 @@ public class ClientAutoDetector {
       return ImmutableList.of();
     }
 
+    public ImmutableList<QueryPartReplacer> getDdlReplacements() {
+      return ImmutableList.of();
+    }
+
     /** Creates specific notice messages for a client after startup. */
     public ImmutableList<NoticeResponse> createStartupNoticeResponses(
         ConnectionHandler connection) {


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/liquibase/ITLiquibaseTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/liquibase/ITLiquibaseTest.java
index 346785f3..9fcb3ead 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/liquibase/ITLiquibaseTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/liquibase/ITLiquibaseTest.java
@@ -33,7 +33,6 @@ import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.sql.Statement;
 import java.util.Scanner;
 import java.util.logging.Logger;
 import java.util.stream.Collectors;
@@ -63,27 +62,6 @@ public class ITLiquibaseTest {
     testEnv.setUp();
     database = testEnv.createDatabase(ImmutableList.of());
     testEnv.startPGAdapterServer(ImmutableList.of());
-    // Create databasechangelog and databasechangeloglock tables.
-    StringBuilder builder = new StringBuilder();
-    try (Scanner scanner = new Scanner(new FileReader(LIQUIBASE_DB_CHANGELOG_DDL_FILE))) {
-      while (scanner.hasNextLine()) {
-        builder.append(scanner.nextLine()).append("\n");
-      }
-    }
-    // Note: We know that all semicolons in this file are outside of literals etc.
-    String[] ddl = builder.toString().split(";");
-    String url =
-        String.format(
-            "jdbc:postgresql://localhost:%d/%s?options=-c%%20spanner.ddl_transaction_mode=AutocommitExplicitTransaction",
-            testEnv.getPGAdapterPort(), database.getId().getDatabase());
-    try (Connection connection = DriverManager.getConnection(url)) {
-      try (Statement statement = connection.createStatement()) {
-        for (String sql : ddl) {
-          LOGGER.info("Executing " + sql);
-          statement.execute(sql);
-        }
-      }
-    }
 
     // Write liquibase.properties
     StringBuilder original = new StringBuilder();
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutorTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutorTest.java
index 3ff105fd..dde07792 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutorTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/DdlExecutorTest.java
@@ -16,11 +16,16 @@ package com.google.cloud.spanner.pgadapter.statements;
 
 import static com.google.cloud.spanner.pgadapter.statements.DdlExecutor.unquoteIdentifier;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.mock;
 
 import com.google.cloud.spanner.Dialect;
 import com.google.cloud.spanner.Statement;
 import com.google.cloud.spanner.connection.AbstractStatementParser;
+import com.google.cloud.spanner.pgadapter.utils.RegexQueryPartReplacer;
+import com.google.common.base.Suppliers;
+import com.google.common.collect.ImmutableList;
+import java.util.regex.Pattern;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -52,7 +57,8 @@ public class DdlExecutorTest {
 
   @Test
   public void testMaybeRemovePrimaryKeyConstraintName() {
-    DdlExecutor ddlExecutor = new DdlExecutor(mock(BackendConnection.class));
+    DdlExecutor ddlExecutor =
+        new DdlExecutor(mock(BackendConnection.class), Suppliers.ofInstance(ImmutableList.of()));
 
     assertEquals(
         Statement.of("create table foo (id bigint primary key, value text)"),
@@ -107,4 +113,43 @@ public class DdlExecutorTest {
             Statement.of(
                 "CREATE TABLE \"user\" (\"id\" integer NOT NULL, \"firstName\" character varying NOT NULL, \"lastName\" character varying NOT NULL, \"age\" integer NOT NULL, CONSTRAINT \"PK_user\" PRIMARY KEY (\"id\"))")));
   }
+
+  @Test
+  public void testDdlReplacements() {
+    DdlExecutor ddlExecutorWithoutReplacements =
+        new DdlExecutor(mock(BackendConnection.class), Suppliers.ofInstance(ImmutableList.of()));
+    String sql = "create table my_table (id bigint primary key, value varchar)";
+    assertSame(sql, ddlExecutorWithoutReplacements.applyReplacers(sql));
+
+    DdlExecutor ddlExecutorWithReplacements =
+        new DdlExecutor(
+            mock(BackendConnection.class),
+            Suppliers.ofInstance(
+                ImmutableList.of(
+                    RegexQueryPartReplacer.replace(Pattern.compile("timestamp"), "timestamptz"),
+                    RegexQueryPartReplacer.replaceAndStop(
+                        Pattern.compile(
+                            "create table databasechangelog \\(id bigint primary key\\)"),
+                        "create table databasechangelog_replaced (id bigint primary key)"),
+                    RegexQueryPartReplacer.replace(
+                        Pattern.compile("create table databasechangelog_replaced"),
+                        "create table databasechangelog_reverted"))));
+    assertSame(sql, ddlExecutorWithReplacements.applyReplacers(sql));
+    assertEquals(
+        "create table my_table (id bigint primary key, value timestamptz)",
+        ddlExecutorWithReplacements.applyReplacers(
+            "create table my_table (id bigint primary key, value timestamp)"));
+    assertEquals(
+        "create table databasechangelog_replaced (id bigint primary key)",
+        ddlExecutorWithReplacements.applyReplacers(
+            "create table databasechangelog (id bigint primary key)"));
+    assertEquals(
+        "create table databasechangelog_replaced (id bigint primary key); create table databasechangelog_replaced (id bigint primary key)",
+        ddlExecutorWithReplacements.applyReplacers(
+            "create table databasechangelog (id bigint primary key); create table databasechangelog (id bigint primary key)"));
+    assertEquals(
+        "create table databasechangelog_reverted (id bigint primary key)",
+        ddlExecutorWithReplacements.applyReplacers(
+            "create table databasechangelog_replaced (id bigint primary key)"));
+  }
 }
diff --git a/src/test/nodejs/typeorm/data-test/package.json b/src/test/nodejs/typeorm/data-test/package.json
index bbb920c3..1a93446c 100644
--- a/src/test/nodejs/typeorm/data-test/package.json
+++ b/src/test/nodejs/typeorm/data-test/package.json
@@ -9,7 +9,7 @@
       "typescript": "5.1.3"
    },
    "dependencies": {
-      "typeorm": "0.3.16",
+      "typeorm": "0.3.17",
       "reflect-metadata": "^0.1.13",
       "pg": "^8.9.0",
       "yargs": "^17.5.1"