Repository: GoogleCloudPlatform/pgadapter
Pull request #766 (2023-04-08 06:18:17)

feat: emulate pg_class and related tables
Adds common table expressions for pg_class and related tables. The OID of a table is set as the fully qualified name of the table. This is not in line with OIDs in PostgreSQL, as those are numeric, but most tools do not read the OID anyways. Instead, the OID is used for joining the different pg_class tables to get the relevant information.

This improves the compatibility for ORMs and tools that query pg_catalog for information about the current objects in the schema, and is a requirement for Ruby ActiveRecord. The feature can be turned off using the pg_setting entry `spanner.emulate_pg_class_tables`.

codecov[bot]: ## [Codecov](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/766?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) Report
> Merging [#766](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/766?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) (d35174d) into [postgresql-dialect](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/commit/c2aaa363a23c213074c8071dfe39c6bbd4448d96?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) (c2aaa36) will **decrease** coverage by `0.12%`.
> The diff coverage is `74.00%`.

```diff
@@                   Coverage Diff                    @@
##             postgresql-dialect     #766      +/-   ##
========================================================
- Coverage                 89.54%   89.42%   -0.12%     
  Complexity                 2354     2354              
========================================================
  Files                       129      129              
  Lines                      7767     7803      +36     
  Branches                   1112     1119       +7     
========================================================
+ Hits                       6955     6978      +23     
- Misses                      564      573       +9     
- Partials                    248      252       +4     
```

| Flag | Coverage Δ | |
|---|---|---|
| all_tests | `89.42% <74.00%> (-0.12%)` | :arrow_down: |

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://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/766?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) | Coverage Δ | |
|---|---|---|
| [...dapter/statements/JdbcMetadataStatementHelper.java](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/766?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3N0YXRlbWVudHMvSmRiY01ldGFkYXRhU3RhdGVtZW50SGVscGVyLmphdmE=) | `83.85% <ø> (ø)` | |
| [...ud/spanner/pgadapter/utils/ClientAutoDetector.java](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/766?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3V0aWxzL0NsaWVudEF1dG9EZXRlY3Rvci5qYXZh) | `92.79% <ø> (-0.37%)` | :arrow_down: |
| [.../cloud/spanner/pgadapter/session/SessionState.java](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/766?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3Nlc3Npb24vU2Vzc2lvblN0YXRlLmphdmE=) | `93.84% <50.00%> (-1.88%)` | :arrow_down: |
| [.../cloud/spanner/pgadapter/statements/PgCatalog.java](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/766?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3N0YXRlbWVudHMvUGdDYXRhbG9nLmphdmE=) | `92.35% <78.57%> (-4.71%)` | :arrow_down: |

: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/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java b/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java
index ca37be80..d443e974 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java
@@ -113,6 +113,11 @@ public class SessionState {
     initCopySettings(this.settings);
   }
 
+  @VisibleForTesting
+  Map<String, PGSetting> getSettings() {
+    return this.settings;
+  }
+
   void initSettingValue(String key, String value) {
     PGSetting setting = this.settings.get(key);
     if (setting != null) {
@@ -400,6 +405,22 @@ public class SessionState {
         () -> BooleanParser.toBoolean(setting.getBootVal()));
   }
 
+  /**
+   * Returns the current setting for replacing pg_class tables with common table expressions that
+   * use the object name as OID.
+   */
+  public boolean isEmulatePgClassTables() {
+    PGSetting setting = internalGet(toKey("spanner", "emulate_pg_class_tables"), false);
+    if (setting == null) {
+      return false;
+    }
+    return tryGetFirstNonNull(
+        true,
+        () -> BooleanParser.toBoolean(setting.getSetting()),
+        () -> BooleanParser.toBoolean(setting.getResetVal()),
+        () -> BooleanParser.toBoolean(setting.getBootVal()));
+  }
+
   /** Returns the {@link DdlTransactionMode} that is used for this connection at this time. */
   public DdlTransactionMode getDdlTransactionMode() {
     PGSetting setting = internalGet(toKey("spanner", "ddl_transaction_mode"), false);
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/JdbcMetadataStatementHelper.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/JdbcMetadataStatementHelper.java
index 1fe63138..86746d0a 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/JdbcMetadataStatementHelper.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/JdbcMetadataStatementHelper.java
@@ -167,8 +167,8 @@ class JdbcMetadataStatementHelper {
             " ON (a.atttypid = t.oid)",
             " ON (coalesce(a.atttypid, a_spanner.spanner_atttypid) = t.oid)")
         .replace(
-            " OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)",
-            " OR FALSE ")
+            " (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))",
+            " (t.typrelid::bigint = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid))")
 
         // Replace function calls for DDL definitions with null literals.
         .replaceAll("pg_catalog\\.pg_get_expr\\(.+?\\)", "null")
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java
index 01992393..2ece2e5e 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java
@@ -45,10 +45,34 @@ public class PgCatalog {
           .put(
               new TableOrIndexName(null, "pg_namespace"),
               new TableOrIndexName(null, "pg_namespace"))
+          .put(
+              new TableOrIndexName("pg_collation", "pg_collation"),
+              new TableOrIndexName(null, "pg_collation"))
+          .put(
+              new TableOrIndexName(null, "pg_collation"),
+              new TableOrIndexName(null, "pg_collation"))
           .put(
               new TableOrIndexName("pg_catalog", "pg_class"),
               new TableOrIndexName(null, "pg_class"))
           .put(new TableOrIndexName(null, "pg_class"), new TableOrIndexName(null, "pg_class"))
+          .put(
+              new TableOrIndexName("pg_catalog", "pg_attribute"),
+              new TableOrIndexName(null, "pg_attribute"))
+          .put(
+              new TableOrIndexName(null, "pg_attribute"),
+              new TableOrIndexName(null, "pg_attribute"))
+          .put(
+              new TableOrIndexName("pg_catalog", "pg_attrdef"),
+              new TableOrIndexName(null, "pg_attrdef"))
+          .put(new TableOrIndexName(null, "pg_attrdef"), new TableOrIndexName(null, "pg_attrdef"))
+          .put(
+              new TableOrIndexName("pg_catalog", "pg_constraint"),
+              new TableOrIndexName(null, "pg_constraint"))
+          .put(
+              new TableOrIndexName(null, "pg_constraint"),
+              new TableOrIndexName(null, "pg_constraint"))
+          .put(new TableOrIndexName("pg_index", "pg_index"), new TableOrIndexName(null, "pg_index"))
+          .put(new TableOrIndexName(null, "pg_index"), new TableOrIndexName(null, "pg_index"))
           .put(new TableOrIndexName("pg_catalog", "pg_proc"), new TableOrIndexName(null, "pg_proc"))
           .put(new TableOrIndexName(null, "pg_proc"), new TableOrIndexName(null, "pg_proc"))
           .put(new TableOrIndexName("pg_catalog", "pg_enum"), new TableOrIndexName(null, "pg_enum"))
@@ -72,6 +96,12 @@ public class PgCatalog {
           .put(
               new TableOrIndexName("information_schema", "sequences"),
               new TableOrIndexName(null, "pg_information_schema_sequences"))
+          .put(
+              new TableOrIndexName("pg_catalog", "pg_extension"),
+              new TableOrIndexName(null, "pg_extension"))
+          .put(
+              new TableOrIndexName(null, "pg_extension"),
+              new TableOrIndexName(null, "pg_extension"))
           .put(
               new TableOrIndexName("pg_catalog", "pg_settings"),
               new TableOrIndexName(null, "pg_settings"))
@@ -97,16 +127,16 @@ public class PgCatalog {
   private static final Map<TableOrIndexName, PgCatalogTable> DEFAULT_PG_CATALOG_TABLES =
       ImmutableMap.<TableOrIndexName, PgCatalogTable>builder()
           .put(new TableOrIndexName(null, "pg_namespace"), new PgNamespace())
-          .put(new TableOrIndexName(null, "pg_class"), new PgClass())
+          .put(new TableOrIndexName(null, "pg_collation"), new PgCollation())
           .put(new TableOrIndexName(null, "pg_proc"), new PgProc())
           .put(new TableOrIndexName(null, "pg_enum"), new EmptyPgEnum())
           .put(new TableOrIndexName(null, "pg_range"), new PgRange())
-          .put(new TableOrIndexName(null, "pg_type"), new PgType())
           .put(new TableOrIndexName(null, "pg_sequence"), new PgSequence())
           .put(new TableOrIndexName(null, "pg_sequences"), new PgSequences())
           .put(
               new TableOrIndexName(null, "pg_information_schema_sequences"),
               new InformationSchemaSequences())
+          .put(new TableOrIndexName(null, "pg_extension"), new PgExtension())
           .build();
   private final SessionState sessionState;
 
@@ -124,6 +154,12 @@ public class PgCatalog {
     ImmutableMap.Builder<TableOrIndexName, PgCatalogTable> pgCatalogTablesBuilder =
         ImmutableMap.<TableOrIndexName, PgCatalogTable>builder()
             .putAll(DEFAULT_PG_CATALOG_TABLES)
+            .put(new TableOrIndexName(null, "pg_class"), new PgClass())
+            .put(new TableOrIndexName(null, "pg_attribute"), new PgAttribute())
+            .put(new TableOrIndexName(null, "pg_attrdef"), new PgAttrdef())
+            .put(new TableOrIndexName(null, "pg_constraint"), new PgConstraint())
+            .put(new TableOrIndexName(null, "pg_index"), new PgIndex())
+            .put(new TableOrIndexName(null, "pg_type"), new PgType())
             .put(new TableOrIndexName(null, "pg_settings"), new PgSettings());
     wellKnownClient
         .getPgCatalogTables()
@@ -237,8 +273,9 @@ public class PgCatalog {
     }
   }
 
-  private static class PgNamespace implements PgCatalogTable {
-    private static final String PG_NAMESPACE_CTE =
+  @InternalApi
+  public static class PgNamespace implements PgCatalogTable {
+    public static final String PG_NAMESPACE_CTE =
         "pg_namespace as (\n"
             + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
             + "        schema_name as nspname, null as nspowner, null as nspacl\n"
@@ -251,8 +288,31 @@ public class PgCatalog {
     }
   }
 
-  private static class PgType implements PgCatalogTable {
-    private static final ImmutableSet<TableOrIndexName> DEPENDENCIES =
+  @InternalApi
+  public static class PgCollation implements PgCatalogTable {
+    public static final String PG_COLLATION_CTE =
+        "pg_collation as (\n"
+            + "  select 100::bigint as oid, 'default' as collname, 11 as collnamespace,\n"
+            + "         null::bigint as collowner, 'd'::varchar as collprovider, true::bool as collisdeterministic,\n"
+            + "         -1::bigint as collencoding, null::varchar as collcollate, null::varchar as collctype,\n"
+            + "         null::bigint as collversion\n"
+            + ")";
+
+    @Override
+    public String getTableExpression() {
+      return PG_COLLATION_CTE;
+    }
+  }
+
+  // This is defined outside the PgType class, because Java 8 does not allow static initialization
+  // inside inner classes.
+  @InternalApi
+  public static final String PG_TYPE_CTE_EMULATED =
+      PgType.PG_TYPE_CTE.replace("0 as typrelid", "'0'::varchar as typrelid");
+
+  @InternalApi
+  public class PgType implements PgCatalogTable {
+    private final ImmutableSet<TableOrIndexName> DEPENDENCIES =
         ImmutableSet.of(new TableOrIndexName(null, "pg_namespace"));
     private static final String GENERATION_SQL =
         "select 'select '\n"
@@ -298,41 +358,41 @@ public class PgCatalog {
             + "where typname in ('bool', 'bytea', 'int2', 'int4', 'int8', 'float4', 'float8',\n"
             + "                  'numeric', 'varchar', 'text', 'jsonb', 'timestamp', 'timestamptz', 'date')\n"
             + ";\n";
-    private static final String PG_TYPE_CTE =
+    public static final String PG_TYPE_CTE =
         "pg_type as (\n"
-            + "  select 16 as oid, 'bool' as typname, 11 as typnamespace, null as typowner, 1 as typlen, true as typbyval, 'b' as typtype, 'B' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1000 as typarray, 'boolin' as typinput, 'boolout' as typoutput, 'boolrecv' as typreceive, 'boolsend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'c' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 17 as oid, 'bytea' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1001 as typarray, 'byteain' as typinput, 'byteaout' as typoutput, 'bytearecv' as typreceive, 'byteasend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 20 as oid, 'int8' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1016 as typarray, 'int8in' as typinput, 'int8out' as typoutput, 'int8recv' as typreceive, 'int8send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 21 as oid, 'int2' as typname, 11 as typnamespace, null as typowner, 2 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1005 as typarray, 'int2in' as typinput, 'int2out' as typoutput, 'int2recv' as typreceive, 'int2send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 's' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 23 as oid, 'int4' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1007 as typarray, 'int4in' as typinput, 'int4out' as typoutput, 'int4recv' as typreceive, 'int4send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 25 as oid, 'text' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'S' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1009 as typarray, 'textin' as typinput, 'textout' as typoutput, 'textrecv' as typreceive, 'textsend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 700 as oid, 'float4' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1021 as typarray, 'float4in' as typinput, 'float4out' as typoutput, 'float4recv' as typreceive, 'float4send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 701 as oid, 'float8' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1022 as typarray, 'float8in' as typinput, 'float8out' as typoutput, 'float8recv' as typreceive, 'float8send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1043 as oid, 'varchar' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'S' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1015 as typarray, 'varcharin' as typinput, 'varcharout' as typoutput, 'varcharrecv' as typreceive, 'varcharsend' as typsend, 'varchartypmodin' as typmodin, 'varchartypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1082 as oid, 'date' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1182 as typarray, 'date_in' as typinput, 'date_out' as typoutput, 'date_recv' as typreceive, 'date_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1114 as oid, 'timestamp' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1115 as typarray, 'timestamp_in' as typinput, 'timestamp_out' as typoutput, 'timestamp_recv' as typreceive, 'timestamp_send' as typsend, 'timestamptypmodin' as typmodin, 'timestamptypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1184 as oid, 'timestamptz' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1185 as typarray, 'timestamptz_in' as typinput, 'timestamptz_out' as typoutput, 'timestamptz_recv' as typreceive, 'timestamptz_send' as typsend, 'timestamptztypmodin' as typmodin, 'timestamptztypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1700 as oid, 'numeric' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1231 as typarray, 'numeric_in' as typinput, 'numeric_out' as typoutput, 'numeric_recv' as typreceive, 'numeric_send' as typsend, 'numerictypmodin' as typmodin, 'numerictypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'm' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 3802 as oid, 'jsonb' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 3807 as typarray, 'jsonb_in' as typinput, 'jsonb_out' as typoutput, 'jsonb_recv' as typreceive, 'jsonb_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1000 as oid, '_bool' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 16 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1001 as oid, '_bytea' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 17 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1016 as oid, '_int8' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 20 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1005 as oid, '_int2' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 21 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1007 as oid, '_int4' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 23 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1009 as oid, '_text' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 25 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1021 as oid, '_float4' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 700 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1022 as oid, '_float8' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 701 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1015 as oid, '_varchar' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1043 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1182 as oid, '_date' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1082 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1115 as oid, '_timestamp' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 1114 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1185 as oid, '_timestamptz' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1184 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 1231 as oid, '_numeric' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1700 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-            + "  select 3807 as oid, '_jsonb' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 3802 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl\n"
+            + "  select 16 as oid, 'bool' as typname, 11 as typnamespace, null as typowner, 1 as typlen, true as typbyval, 'b' as typtype, 'B' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1000 as typarray, 'boolin' as typinput, 'boolout' as typoutput, 'boolrecv' as typreceive, 'boolsend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'c' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'boolean' as spanner_type union all\n"
+            + "  select 17 as oid, 'bytea' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1001 as typarray, 'byteain' as typinput, 'byteaout' as typoutput, 'bytearecv' as typreceive, 'byteasend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'bytea' as spanner_type union all\n"
+            + "  select 20 as oid, 'int8' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1016 as typarray, 'int8in' as typinput, 'int8out' as typoutput, 'int8recv' as typreceive, 'int8send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'bigint' as spanner_type union all\n"
+            + "  select 21 as oid, 'int2' as typname, 11 as typnamespace, null as typowner, 2 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1005 as typarray, 'int2in' as typinput, 'int2out' as typoutput, 'int2recv' as typreceive, 'int2send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 's' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 23 as oid, 'int4' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1007 as typarray, 'int4in' as typinput, 'int4out' as typoutput, 'int4recv' as typreceive, 'int4send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 25 as oid, 'text' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'S' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1009 as typarray, 'textin' as typinput, 'textout' as typoutput, 'textrecv' as typreceive, 'textsend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 700 as oid, 'float4' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1021 as typarray, 'float4in' as typinput, 'float4out' as typoutput, 'float4recv' as typreceive, 'float4send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 701 as oid, 'float8' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1022 as typarray, 'float8in' as typinput, 'float8out' as typoutput, 'float8recv' as typreceive, 'float8send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'double precision' as spanner_type union all\n"
+            + "  select 1043 as oid, 'varchar' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'S' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1015 as typarray, 'varcharin' as typinput, 'varcharout' as typoutput, 'varcharrecv' as typreceive, 'varcharsend' as typsend, 'varchartypmodin' as typmodin, 'varchartypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'character varying' as spanner_type union all\n"
+            + "  select 1082 as oid, 'date' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1182 as typarray, 'date_in' as typinput, 'date_out' as typoutput, 'date_recv' as typreceive, 'date_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'date' as spanner_type union all\n"
+            + "  select 1114 as oid, 'timestamp' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1115 as typarray, 'timestamp_in' as typinput, 'timestamp_out' as typoutput, 'timestamp_recv' as typreceive, 'timestamp_send' as typsend, 'timestamptypmodin' as typmodin, 'timestamptypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 1184 as oid, 'timestamptz' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1185 as typarray, 'timestamptz_in' as typinput, 'timestamptz_out' as typoutput, 'timestamptz_recv' as typreceive, 'timestamptz_send' as typsend, 'timestamptztypmodin' as typmodin, 'timestamptztypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'timestamp with time zone' as spanner_type union all\n"
+            + "  select 1700 as oid, 'numeric' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1231 as typarray, 'numeric_in' as typinput, 'numeric_out' as typoutput, 'numeric_recv' as typreceive, 'numeric_send' as typsend, 'numerictypmodin' as typmodin, 'numerictypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'm' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'numeric' as spanner_type union all\n"
+            + "  select 3802 as oid, 'jsonb' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 3807 as typarray, 'jsonb_in' as typinput, 'jsonb_out' as typoutput, 'jsonb_recv' as typreceive, 'jsonb_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'jsonb' as spanner_type union all\n"
+            + "  select 1000 as oid, '_bool' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 16 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'boolean[]' as spanner_type union all\n"
+            + "  select 1001 as oid, '_bytea' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 17 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'bytea[]' as spanner_type union all\n"
+            + "  select 1016 as oid, '_int8' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 20 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'bigint[]' as spanner_type union all\n"
+            + "  select 1005 as oid, '_int2' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 21 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 1007 as oid, '_int4' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 23 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 1009 as oid, '_text' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 25 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 1021 as oid, '_float4' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 700 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 1022 as oid, '_float8' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 701 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'double precision[]' as spanner_type union all\n"
+            + "  select 1015 as oid, '_varchar' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1043 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'character varying[]' as spanner_type union all\n"
+            + "  select 1182 as oid, '_date' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1082 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'date[]' as spanner_type union all\n"
+            + "  select 1115 as oid, '_timestamp' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 1114 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, null as spanner_type union all\n"
+            + "  select 1185 as oid, '_timestamptz' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1184 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'timestamp with time zone[]' as spanner_type union all\n"
+            + "  select 1231 as oid, '_numeric' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1700 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'numeric[]' as spanner_type union all\n"
+            + "  select 3807 as oid, '_jsonb' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 3802 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl, 'jsonb[]' as spanner_type\n"
             + ")";
 
     @Override
     public String getTableExpression() {
-      return PG_TYPE_CTE;
+      return sessionState.isEmulatePgClassTables() ? PG_TYPE_CTE_EMULATED : PG_TYPE_CTE;
     }
 
     @Override
@@ -349,11 +409,12 @@ public class PgCatalog {
     }
   }
 
-  private static class PgClass implements PgCatalogTable {
-    private static final String PG_CLASS_CTE =
+  @InternalApi
+  public class PgClass implements PgCatalogTable {
+    public static final String PG_CLASS_CTE =
         "pg_class as (\n"
             + "  select\n"
-            + "  -1 as oid,\n"
+            + "  %s as oid,\n"
             + "  table_name as relname,\n"
             + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
             + "  0 as reltype,\n"
@@ -369,7 +430,7 @@ public class PgCatalog {
             + "  false as relhasindex,\n"
             + "  false as relisshared,\n"
             + "  'p' as relpersistence,\n"
-            + "  'r' as relkind,\n"
+            + "  CASE table_type WHEN 'BASE TABLE' THEN 'r' WHEN 'VIEW' THEN 'v' ELSE '' END as relkind,\n"
             + "  count(*) as relnatts,\n"
             + "  0 as relchecks,\n"
             + "  false as relhasrules,\n"
@@ -388,10 +449,10 @@ public class PgCatalog {
             + "  0 as relpartbound\n"
             + "from information_schema.tables t\n"
             + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-            + "group by t.table_name, t.table_schema\n"
+            + "group by t.table_name, t.table_schema, t.table_type\n"
             + "union all\n"
             + "select\n"
-            + "    -1 as oid,\n"
+            + "    %s as oid,\n"
             + "    i.index_name as relname,\n"
             + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
             + "    0 as reltype,\n"
@@ -407,7 +468,7 @@ public class PgCatalog {
             + "    false as relhasindex,\n"
             + "    false as relisshared,\n"
             + "    'p' as relpersistence,\n"
-            + "    'r' as relkind,\n"
+            + "    'i' as relkind,\n"
             + "    count(*) as relnatts,\n"
             + "    0 as relchecks,\n"
             + "    false as relhasrules,\n"
@@ -425,13 +486,19 @@ public class PgCatalog {
             + "    '{}'::text[] as reloptions,\n"
             + "    0 as relpartbound\n"
             + "from information_schema.indexes i\n"
-            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-            + "group by i.index_name, i.table_schema\n"
+            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name, index_name)\n"
+            + "group by i.index_name, i.table_name, i.table_schema\n"
             + ")";
 
     @Override
     public String getTableExpression() {
-      return PG_CLASS_CTE;
+      if (sessionState.isEmulatePgClassTables()) {
+        return String.format(
+            PG_CLASS_CTE,
+            "'''\"' || t.table_schema || '\".\"' || t.table_name || '\"'''",
+            "'''\"' || i.table_schema || '\".\"' || i.table_name || '\".\"' || i.index_name || '\"'''");
+      }
+      return String.format(PG_CLASS_CTE, "-1", "-1");
     }
   }
 
@@ -455,8 +522,9 @@ public class PgCatalog {
     }
   }
 
-  private static class PgRange implements PgCatalogTable {
-    private static final String PG_RANGE_CTE =
+  @InternalApi
+  public static class PgRange implements PgCatalogTable {
+    public static final String PG_RANGE_CTE =
         "pg_range as (\n"
             + "select * from ("
             + "select 0::bigint as rngtypid, 0::bigint as rngsubtype, 0::bigint as rngmultitypid, "
@@ -470,8 +538,8 @@ public class PgCatalog {
   }
 
   @InternalApi
-  public static class EmptyPgAttribute implements PgCatalogTable {
-    private static final String PG_ATTRIBUTE_CTE =
+  public class PgAttribute implements PgCatalogTable {
+    public static final String EMPTY_PG_ATTRIBUTE_CTE =
         "pg_attribute as (\n"
             + "select * from ("
             + "select 0::bigint as attrelid, '' as attname, 0::bigint as atttypid, 0::bigint as attstattarget, "
@@ -482,9 +550,252 @@ public class PgCatalog {
             + "'{}'::text[] as attoptions, '{}'::text[] as attfdwoptions, null as attmissingval\n"
             + ") a where false)";
 
+    public static final String PG_ATTRIBUTE_CTE =
+        "pg_attribute as (\n"
+            + "select  '''\"' || table_schema || '\".\"' || table_name || '\"''' as attrelid,\n"
+            + "        column_name as attname,\n"
+            + "        case regexp_replace(c.spanner_type, '\\(.*\\)', '')\n"
+            + "            when 'boolean' then 16\n"
+            + "            when 'bytea' then 17\n"
+            + "            when 'bigint' then 20\n"
+            + "            when 'double precision' then 701\n"
+            + "            when 'character varying' then 1043\n"
+            + "            when 'date' then 1082\n"
+            + "            when 'timestamp with time zone' then 1184\n"
+            + "            when 'numeric' then 1700\n"
+            + "            when 'jsonb' then 3802\n"
+            + "            when 'boolean[]' then 1000\n"
+            + "            when 'bytea[]' then 1001\n"
+            + "            when 'bigint[]' then 1016\n"
+            + "            when 'double precision[]' then 1022\n"
+            + "            when 'character varying[]' then 1015\n"
+            + "            when 'date[]' then 1182\n"
+            + "            when 'timestamp with time zone[]' then 1185\n"
+            + "            when 'numeric[]' then 1231\n"
+            + "            when 'jsonb[]' then 3807\n"
+            + "            else 0\n"
+            + "        end as atttypid,\n"
+            + "        0::bigint as attstattarget,\n"
+            + "        character_maximum_length as attlen, c.ordinal_position as attnum,\n"
+            + "        case data_type when 'ARRAY' then 1::bigint else 0::bigint end as attndims,\n"
+            + "        -1::bigint as attcacheoff,\n"
+            + "        coalesce(c.character_maximum_length, -1::bigint) as atttypmod, true as attbyval,\n"
+            + "        'i' as attalign, 'p' as attstorage, ''::varchar as attcompression,\n"
+            + "        c.is_nullable='NO' as attnotnull,\n"
+            + "        (c.column_default is not null or c.generation_expression is not null) as atthasdef,\n"
+            + "        false as atthasmissing,'' as attidentity,\n"
+            + "        case c.generation_expression is not null when true then 's' else '' end as attgenerated,\n"
+            + "        false as attisdropped, true as attislocal, 0 as attinhcount, null::bigint as attcollation, '{}'::bigint[] as attacl,\n"
+            + "        '{}'::text[] as attoptions, '{}'::text[] as attfdwoptions, null as attmissingval,\n"
+            + "        c.spanner_type\n"
+            + "from information_schema.columns c\n"
+            + "union all\n"
+            + "select  '''\"' || i.table_schema || '\".\"' || i.table_name || '\".\"' || i.index_name || '\"''' as attrelid,\n"
+            + "        i.column_name as attname,\n"
+            + "        case regexp_replace(c.spanner_type, '\\(.*\\)', '')\n"
+            + "            when 'boolean' then 16\n"
+            + "            when 'bytea' then 17\n"
+            + "            when 'bigint' then 20\n"
+            + "            when 'double precision' then 701\n"
+            + "            when 'character varying' then 1043\n"
+            + "            when 'date' then 1082\n"
+            + "            when 'timestamp with time zone' then 1184\n"
+            + "            when 'numeric' then 1700\n"
+            + "            when 'jsonb' then 3802\n"
+            + "            when 'boolean[]' then 1000\n"
+            + "            when 'bytea[]' then 1001\n"
+            + "            when 'bigint[]' then 1016\n"
+            + "            when 'double precision[]' then 1022\n"
+            + "            when 'character varying[]' then 1015\n"
+            + "            when 'date[]' then 1182\n"
+            + "            when 'timestamp with time zone[]' then 1185\n"
+            + "            when 'numeric[]' then 1231\n"
+            + "            when 'jsonb[]' then 3807\n"
+            + "            else 0\n"
+            + "        end as atttypid,\n"
+            + "        0::bigint as attstattarget,\n"
+            + "        character_maximum_length as attlen, c.ordinal_position as attnum,\n"
+            + "        case data_type when 'ARRAY' then 1::bigint else 0::bigint end as attndims,\n"
+            + "        -1::bigint as attcacheoff,\n"
+            + "        coalesce(c.character_maximum_length, -1::bigint) as atttypmod, true as attbyval,\n"
+            + "        'i' as attalign, 'p' as attstorage, ''::varchar as attcompression,\n"
+            + "        c.is_nullable='NO' as attnotnull,\n"
+            + "        (c.column_default is not null or c.generation_expression is not null) as atthasdef,\n"
+            + "        false as atthasmissing,'' as attidentity,\n"
+            + "        case c.generation_expression is not null when true then 's' else '' end as attgenerated,\n"
+            + "        false as attisdropped, true as attislocal, 0 as attinhcount, null::bigint as attcollation, '{}'::bigint[] as attacl,\n"
+            + "        '{}'::text[] as attoptions, '{}'::text[] as attfdwoptions, null as attmissingval,\n"
+            + "        c.spanner_type\n"
+            + "from information_schema.index_columns i\n"
+            + "inner join information_schema.columns c using (table_catalog, table_schema, table_name, column_name)"
+            + ")";
+
+    @Override
+    public String getTableExpression() {
+      return sessionState.isEmulatePgClassTables() ? PG_ATTRIBUTE_CTE : EMPTY_PG_ATTRIBUTE_CTE;
+    }
+  }
+
+  @InternalApi
+  public class PgAttrdef implements PgCatalogTable {
+    public static final String EMPTY_PG_ATTRDEF_CTE =
+        "pg_attrdef as (\n"
+            + "select * from ("
+            + "select 0::bigint as oid, 0::bigint as adrelid, 0::bigint as adnum, ''::varchar as adbin"
+            + ") d where false)";
+
+    public static final String PG_ATTRDEF_CTE =
+        "pg_attrdef as (\n"
+            + "select  '''\"' || table_schema || '\".\"' || table_name || '\".\"' || column_name || '\"''' as oid,\n"
+            + "        '''\"' || table_schema || '\".\"' || table_name || '\"''' as adrelid,\n"
+            + "        ordinal_position as adnum,\n"
+            + "        coalesce(column_default, generation_expression) as adbin\n"
+            + "from information_schema.columns c\n"
+            + "where coalesce(column_default, generation_expression) is not null\n"
+            + ")";
+
+    @Override
+    public String getTableExpression() {
+      return sessionState.isEmulatePgClassTables() ? PG_ATTRDEF_CTE : EMPTY_PG_ATTRDEF_CTE;
+    }
+  }
+
+  public class PgConstraint implements PgCatalogTable {
+    public static final String PG_CONSTRAINT_CTE =
+        "pg_constraint as (\n"
+            + "select\n"
+            + "    '''\"' || tc.constraint_schema || '\".\"' || tc.constraint_name || '\"''' as oid,\n"
+            + "    tc.constraint_name as conname, 2200 as connamespace,\n"
+            + "    case tc.constraint_type\n"
+            + "        when 'PRIMARY KEY' then 'p'\n"
+            + "        when 'CHECK' then 'c'\n"
+            + "        when 'FOREIGN KEY' then 'f'\n"
+            + "        else ''\n"
+            + "    end as contype, false as condeferrable, false as condeferred, true as convalidated,\n"
+            + "    '''\"' || tc.table_schema || '\".\"' || tc.table_name || '\"''' as conrelid,\n"
+            + "    0::bigint as contypid, '0'::varchar as conindid, '0'::varchar as conparentid,\n"
+            + "    '''\"' || uc.table_schema || '\".\"' || uc.table_name || '\"''' as confrelid,\n"
+            + "    case rc.update_rule\n"
+            + "        when 'CASCADE' then 'c'\n"
+            + "        when 'NO ACTION' then 'a'\n"
+            + "    end as confupdtype,\n"
+            + "    case rc.delete_rule\n"
+            + "        when 'CASCADE' then 'c'\n"
+            + "        when 'NO ACTION' then 'a'\n"
+            + "    end as confdeltype, 's' as confmatchtype, true as conislocal, 0 as coninhcount,\n"
+            + "    true as connoinherit, cck.column_keys as conkey, fck.column_keys as confkey,\n"
+            + "    null::bigint[] as conpfeqop, null::bigint[] as conppeqop, null::bigint[] as conffeqop,\n"
+            + "    null::bigint[] as confdelsetcols, null::bigint[] as conexclop, check_clause as conbin\n"
+            + "from information_schema.table_constraints tc\n"
+            + "left outer join information_schema.referential_constraints rc using (constraint_catalog, constraint_schema, constraint_name)\n"
+            + "left outer join information_schema.table_constraints uc on uc.constraint_catalog=rc.unique_constraint_catalog and uc.constraint_schema=rc.unique_constraint_schema and uc.constraint_name=rc.unique_constraint_name\n"
+            + "left outer join (select c.constraint_catalog,\n"
+            + "                        c.constraint_schema,\n"
+            + "                        c.constraint_name,\n"
+            + "                        array_agg(col.ordinal_position) as column_keys\n"
+            + "                 from information_schema.table_constraints c\n"
+            + "                          inner join information_schema.constraint_column_usage ccu\n"
+            + "                                     using (constraint_catalog, constraint_schema, constraint_name)\n"
+            + "                          inner join information_schema.columns col\n"
+            + "                                     on ccu.table_catalog = col.table_catalog and\n"
+            + "                                        ccu.table_schema = col.table_schema and\n"
+            + "                                        ccu.table_name = col.table_name and\n"
+            + "                                        ccu.column_name = col.column_name\n"
+            + "                 where not c.constraint_type='FOREIGN KEY'\n"
+            + "                 group by c.constraint_catalog, c.constraint_schema,\n"
+            + "                          c.constraint_name\n"
+            + "                 union all\n"
+            + "                 select c.constraint_catalog,\n"
+            + "                        c.constraint_schema,\n"
+            + "                        c.constraint_name,\n"
+            + "                        array_agg(col.ordinal_position) as column_keys\n"
+            + "                 from information_schema.table_constraints c\n"
+            + "                          inner join information_schema.key_column_usage kcu\n"
+            + "                                     using (constraint_catalog, constraint_schema, constraint_name)\n"
+            + "                          inner join information_schema.columns col\n"
+            + "                                     on kcu.table_catalog = col.table_catalog and\n"
+            + "                                        kcu.table_schema = col.table_schema and\n"
+            + "                                        kcu.table_name = col.table_name and\n"
+            + "                                        kcu.column_name = col.column_name\n"
+            + "                 where c.constraint_type='FOREIGN KEY'\n"
+            + "                 group by c.constraint_catalog, c.constraint_schema,\n"
+            + "                          c.constraint_name) cck on tc.constraint_catalog=cck.constraint_catalog and tc.constraint_schema=cck.constraint_schema and tc.constraint_name=cck.constraint_name\n"
+            + "left outer join (select c.constraint_catalog,\n"
+            + "                        c.constraint_schema,\n"
+            + "                        c.constraint_name,\n"
+            + "                        array_agg(col.ordinal_position) as column_keys\n"
+            + "                 from information_schema.referential_constraints c\n"
+            + "                          inner join information_schema.constraint_column_usage ccu\n"
+            + "                                     on  c.unique_constraint_catalog=ccu.constraint_catalog\n"
+            + "                                         and c.unique_constraint_schema=ccu.constraint_schema\n"
+            + "                                         and c.unique_constraint_name=ccu.constraint_name\n"
+            + "                          inner join information_schema.columns col\n"
+            + "                                     on ccu.table_catalog = col.table_catalog and\n"
+            + "                                        ccu.table_schema = col.table_schema and\n"
+            + "                                        ccu.table_name = col.table_name and\n"
+            + "                                        ccu.column_name = col.column_name\n"
+            + "                 group by c.constraint_catalog, c.constraint_schema,\n"
+            + "                          c.constraint_name) fck on tc.constraint_catalog=fck.constraint_catalog and tc.constraint_schema=fck.constraint_schema and tc.constraint_name=fck.constraint_name\n"
+            + "left outer join information_schema.check_constraints cc on cc.constraint_catalog=tc.constraint_catalog and cc.constraint_schema=tc.constraint_schema and cc.constraint_name=tc.constraint_name\n"
+            + "where tc.constraint_schema='public' and not substr(tc.constraint_name, 1, length('CK_IS_NOT_NULL_')) = 'CK_IS_NOT_NULL_'\n"
+            + ")";
+
+    public static final String EMPTY_PG_CONSTRAINT_CTE =
+        "pg_constraint as (\n"
+            + "select * from ("
+            + "select    0::bigint as oid, ''::varchar as conname, 0::bigint as connamespace, ''  as contype,\n"
+            + "          false as condeferrable, false as condeferred, true as convalidated,\n"
+            + "          0::bigint as conrelid, 0::bigint as contypid, 0::bigint as conindid, 0::bigint as conparentid,\n"
+            + "          0::bigint as confrelid, ''::varchar as confupdtype, ''::varchar as confdeltype, '' as confmatchtype,\n"
+            + "          true as conislocal, 0 as coninhcount, true as connoinherit, null::bigint[] as conkey,\n"
+            + "          null::bigint[] as confkey, null::bigint[] as conpfeqop, null::bigint[] as conppeqop,\n"
+            + "          null::bigint[] as conffeqop, null::bigint[] as confdelsetcols, null::bigint[] as conexclop,\n"
+            + "          '' as conbin\n"
+            + ") c where false)";
+
+    @Override
+    public String getTableExpression() {
+      return sessionState.isEmulatePgClassTables() ? PG_CONSTRAINT_CTE : EMPTY_PG_CONSTRAINT_CTE;
+    }
+  }
+
+  @InternalApi
+  public class PgIndex implements PgCatalogTable {
+    public static final String PG_INDEX_CTE =
+        "pg_index as (\n"
+            + "select '''\"' || i.table_schema || '\".\"' || i.table_name || '\".\"' || i.index_name || '\"''' as indexrelid,\n"
+            + "       '''\"' || i.table_schema || '\".\"' || i.table_name || '\"''' as indrelid,\n"
+            + "       count(1) as indnatts, sum(case ic.ordinal_position is null when true then 0 else 1 end) as indnkeyatts,\n"
+            + "       i.is_unique='YES' as indisunique, i.is_unique='YES' and i.is_null_filtered='NO' as indnullsnotdistinct,\n"
+            + "       i.index_type='PRIMARY_KEY' as indisprimary, false as indisexclusion, true as indimmediate,\n"
+            + "       false as indisclustered, true as indisvalid, false as indcheckxmin, true as indisready,\n"
+            + "       true as indislive, false as indisreplident, string_agg(c.ordinal_position::varchar, ' ') as indkey,\n"
+            + "       ''::varchar as indcollation, ''::varchar as indclass, ''::varchar as indoption,"
+            + "       null::varchar as indexprs, i.filter as indpred\n"
+            + "from information_schema.indexes i\n"
+            + "inner join information_schema.index_columns ic using (table_catalog, table_schema, table_name, index_name)\n"
+            + "inner join information_schema.columns c\n"
+            + "           on ic.table_catalog=c.table_catalog and ic.table_schema=c.table_schema and ic.table_name=c.table_name and ic.column_name=c.column_name\n"
+            + "group by i.table_schema, i.table_name, i.index_name, i.is_unique, i.is_null_filtered, i.index_type, i.filter\n"
+            + ")";
+
+    public static final String EMPTY_PG_INDEX_CTE =
+        "pg_index as (\n"
+            + "select * from (\n"
+            + "select 0::bigint as indexrelid,\n"
+            + "       0::bigint as indrelid,\n"
+            + "       0::bigint as indnatts, 0::bigint as indnkeyatts,\n"
+            + "       false as indisunique, false as indnullsnotdistinct,\n"
+            + "       false as indisprimary, false as indisexclusion, true as indimmediate,\n"
+            + "       false as indisclustered, true as indisvalid, false as indcheckxmin, true as indisready,\n"
+            + "       true as indislive, false as indisreplident, null::bigint[] as indkey,\n"
+            + "       null::bigint[] as indcollation, null::bigint[] as indclass, null::bigint[] as indoption,\n"
+            + "       null::varchar as indexprs, null::varchar as indpred\n"
+            + ") i where false)";
+
     @Override
     public String getTableExpression() {
-      return PG_ATTRIBUTE_CTE;
+      return sessionState.isEmulatePgClassTables() ? PG_INDEX_CTE : EMPTY_PG_INDEX_CTE;
     }
   }
 
@@ -549,4 +860,20 @@ public class PgCatalog {
       return INFORMATION_SCHEMA_SEQUENCES_CTE;
     }
   }
+
+  @InternalApi
+  public static class PgExtension implements PgCatalogTable {
+    public static final String PG_EXTENSION_CTE =
+        "pg_extension as (\n"
+            + "select * from ("
+            + "select 0::bigint as oid, ''::varchar as extname, 0::bigint as extowner, "
+            + "0::bigint as extnamespace, false::bool as extrelocatable, ''::varchar as extversion, "
+            + "'{}'::bigint[] as extconfig, '{}'::text[] as extcondition\n"
+            + ") ext where false)";
+
+    @Override
+    public String getTableExpression() {
+      return PG_EXTENSION_CTE;
+    }
+  }
 }
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 1483205d..4909e310 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
@@ -19,7 +19,6 @@ import com.google.cloud.spanner.Statement;
 import com.google.cloud.spanner.pgadapter.ConnectionHandler;
 import com.google.cloud.spanner.pgadapter.error.PGException;
 import com.google.cloud.spanner.pgadapter.error.SQLState;
-import com.google.cloud.spanner.pgadapter.statements.PgCatalog.EmptyPgAttribute;
 import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgCatalogTable;
 import com.google.cloud.spanner.pgadapter.statements.local.DjangoGetTableNamesStatement;
 import com.google.cloud.spanner.pgadapter.statements.local.ListDatabasesStatement;
@@ -181,12 +180,6 @@ public class ClientAutoDetector {
       }
     },
     NPGSQL {
-      final ImmutableMap<String, String> tableReplacements =
-          ImmutableMap.of(
-              "pg_catalog.pg_attribute", "pg_attribute", "pg_attribute", "pg_attribute");
-      final ImmutableMap<String, PgCatalogTable> pgCatalogTables =
-          ImmutableMap.of("pg_attribute", new EmptyPgAttribute());
-
       final ImmutableMap<Pattern, Supplier<String>> functionReplacements =
           ImmutableMap.of(
               Pattern.compile("elemproc\\.oid = elemtyp\\.typreceive"),
@@ -220,16 +213,6 @@ public class ClientAutoDetector {
                         + "SELECT ns.nspname, t.oid, t.typname, t.typtype, t.typnotnull, t.elemtypoid\n");
       }
 
-      @Override
-      public ImmutableMap<String, String> getTableReplacements() {
-        return tableReplacements;
-      }
-
-      @Override
-      public ImmutableMap<String, PgCatalogTable> getPgCatalogTables() {
-        return pgCatalogTables;
-      }
-
       @Override
       public ImmutableMap<Pattern, Supplier<String>> getFunctionReplacements() {
         return functionReplacements;
diff --git a/src/main/resources/com/google/cloud/spanner/pgadapter/session/pg_settings.txt b/src/main/resources/com/google/cloud/spanner/pgadapter/session/pg_settings.txt
index d5696308..439e4253 100644
--- a/src/main/resources/com/google/cloud/spanner/pgadapter/session/pg_settings.txt
+++ b/src/main/resources/com/google/cloud/spanner/pgadapter/session/pg_settings.txt
@@ -354,5 +354,6 @@ spanner.copy_max_non_atomic_commit_size	5000000	\N	COPY / The max number of byte
 spanner.copy_commit_size_multiplier_factor	2.0	\N	COPY / Factor for estimating COPY commit size	The factor that the size of the incoming payload is multiplied with to estimate whether the current commit size will exceed the maximum commit size.	\N	user	real	default	\N	\N	\N	2.0	2.0	\N	\N	f
 spanner.copy_pipe_buffer_size	65536	\N	COPY / Buffer size for incoming COPY data messages	Internal buffer size for incoming COPY data messages.	\N	internal	integer	default	\N	\N	\N	100000000	100000000	\N	\N	f
 spanner.ddl_transaction_mode	Batch	\N	PGAdapter Options	DDL transaction mode.	\N	user	enum	default	\N	\N	{Single,Batch,AutocommitImplicitTransaction,AutocommitExplicitTransaction}	Batch	Batch	\N	\N	f
+spanner.emulate_pg_class_tables	on	\N	PGAdapter Options Emulate pg_class and related tables using common table expressions and textual OIDs	Generate commont table expressions for pg_class and related tables. The OID of an object is set to its fully qualified name.	\N	user	bool	default	\N	\N	\N	off	off	\N	\N	f
 spanner.force_autocommit	off	\N	PGAdapter Options Execute all statements in autocommit mode	Ignore all transaction statements (BEGIN / COMMIT / ROLLBACK) and execute all statements in autocommit mode.	\N	user	bool	default	\N	\N	\N	off	off	\N	\N	f
 spanner.replace_pg_catalog_tables	on	\N	PGAdapter Options	Automatically replace pg_catalog table references with Common Table Expressions.	\N	user	bool	default	\N	\N	\N	on	on	\N	\N	f


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java
index 307e8c8b..d22ccda7 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java
@@ -1319,7 +1319,7 @@ public class AbortedMockServerTest extends AbstractMockServerTest {
           }
           count++;
         }
-        assertEquals(359, count);
+        assertEquals(360, count);
       }
     }
   }
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
index 1fb53485..04aab9a4 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
@@ -14,6 +14,8 @@
 
 package com.google.cloud.spanner.pgadapter;
 
+import static com.google.cloud.spanner.pgadapter.statements.PgCatalog.PG_TYPE_CTE_EMULATED;
+import static com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgNamespace.PG_NAMESPACE_CTE;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -28,6 +30,9 @@ import com.google.cloud.spanner.admin.database.v1.MockDatabaseAdminImpl;
 import com.google.cloud.spanner.admin.instance.v1.MockInstanceAdminImpl;
 import com.google.cloud.spanner.connection.SpannerPool;
 import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgAttrdef;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgAttribute;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgClass;
 import com.google.cloud.spanner.pgadapter.wireprotocol.WireMessage;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
@@ -95,42 +100,15 @@ import org.postgresql.util.PGobject;
 public abstract class AbstractMockServerTest {
   private static final Logger logger = Logger.getLogger(AbstractMockServerTest.class.getName());
 
-  public static final String PG_TYPE_PREFIX =
-      "pg_namespace as (\n"
-          + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
-          + "        schema_name as nspname, null as nspowner, null as nspacl\n"
-          + "  from information_schema.schemata\n"
-          + "),\n"
-          + "pg_type as (\n"
-          + "  select 16 as oid, 'bool' as typname, 11 as typnamespace, null as typowner, 1 as typlen, true as typbyval, 'b' as typtype, 'B' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1000 as typarray, 'boolin' as typinput, 'boolout' as typoutput, 'boolrecv' as typreceive, 'boolsend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'c' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 17 as oid, 'bytea' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1001 as typarray, 'byteain' as typinput, 'byteaout' as typoutput, 'bytearecv' as typreceive, 'byteasend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 20 as oid, 'int8' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1016 as typarray, 'int8in' as typinput, 'int8out' as typoutput, 'int8recv' as typreceive, 'int8send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 21 as oid, 'int2' as typname, 11 as typnamespace, null as typowner, 2 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1005 as typarray, 'int2in' as typinput, 'int2out' as typoutput, 'int2recv' as typreceive, 'int2send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 's' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 23 as oid, 'int4' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1007 as typarray, 'int4in' as typinput, 'int4out' as typoutput, 'int4recv' as typreceive, 'int4send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 25 as oid, 'text' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'S' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1009 as typarray, 'textin' as typinput, 'textout' as typoutput, 'textrecv' as typreceive, 'textsend' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 700 as oid, 'float4' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1021 as typarray, 'float4in' as typinput, 'float4out' as typoutput, 'float4recv' as typreceive, 'float4send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 701 as oid, 'float8' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'N' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1022 as typarray, 'float8in' as typinput, 'float8out' as typoutput, 'float8recv' as typreceive, 'float8send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1043 as oid, 'varchar' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'S' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1015 as typarray, 'varcharin' as typinput, 'varcharout' as typoutput, 'varcharrecv' as typreceive, 'varcharsend' as typsend, 'varchartypmodin' as typmodin, 'varchartypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1082 as oid, 'date' as typname, 11 as typnamespace, null as typowner, 4 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1182 as typarray, 'date_in' as typinput, 'date_out' as typoutput, 'date_recv' as typreceive, 'date_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1114 as oid, 'timestamp' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1115 as typarray, 'timestamp_in' as typinput, 'timestamp_out' as typoutput, 'timestamp_recv' as typreceive, 'timestamp_send' as typsend, 'timestamptypmodin' as typmodin, 'timestamptypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1184 as oid, 'timestamptz' as typname, 11 as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1185 as typarray, 'timestamptz_in' as typinput, 'timestamptz_out' as typoutput, 'timestamptz_recv' as typreceive, 'timestamptz_send' as typsend, 'timestamptztypmodin' as typmodin, 'timestamptztypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1700 as oid, 'numeric' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1231 as typarray, 'numeric_in' as typinput, 'numeric_out' as typoutput, 'numeric_recv' as typreceive, 'numeric_send' as typsend, 'numerictypmodin' as typmodin, 'numerictypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'm' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 3802 as oid, 'jsonb' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 3807 as typarray, 'jsonb_in' as typinput, 'jsonb_out' as typoutput, 'jsonb_recv' as typreceive, 'jsonb_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1000 as oid, '_bool' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 16 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1001 as oid, '_bytea' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 17 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1016 as oid, '_int8' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 20 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1005 as oid, '_int2' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 21 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1007 as oid, '_int4' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 23 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1009 as oid, '_text' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 25 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1021 as oid, '_float4' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 700 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1022 as oid, '_float8' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 701 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1015 as oid, '_varchar' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1043 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 100 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1182 as oid, '_date' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1082 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1115 as oid, '_timestamp' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, false as typisdefined, ',' as typdelim, 0 as typrelid, 1114 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1185 as oid, '_timestamptz' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1184 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 1231 as oid, '_numeric' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 1700 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
-          + "  select 3807 as oid, '_jsonb' as typname, 11 as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'A' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 3802 as typelem, 0 as typarray, 'array_in' as typinput, 'array_out' as typoutput, 'array_recv' as typreceive, 'array_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl\n"
-          + ")";
+  public static final String PG_TYPE_PREFIX = PG_NAMESPACE_CTE + ",\n" + PG_TYPE_CTE_EMULATED;
+  public static final String PG_CLASS_PREFIX = String.format(PgClass.PG_CLASS_CTE, "-1", "-1");
+  public static final String EMULATED_PG_CLASS_PREFIX =
+      String.format(
+          PgClass.PG_CLASS_CTE,
+          "'''\"' || t.table_schema || '\".\"' || t.table_name || '\"'''",
+          "'''\"' || i.table_schema || '\".\"' || i.table_name || '\".\"' || i.index_name || '\"'''");
+  public static final String EMULATED_PG_ATTRIBUTE_PREFIX = PgAttribute.PG_ATTRIBUTE_CTE;
+  public static final String EMULATED_PG_ATTRDEF_PREFIX = PgAttrdef.PG_ATTRDEF_CTE;
 
   protected static final Statement SELECT_JSONB_TYPE_BY_OID =
       Statement.newBuilder(
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcMetadataTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcMetadataTest.java
index 4444efea..02dd7ac9 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcMetadataTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcMetadataTest.java
@@ -85,7 +85,7 @@ public class ITJdbcMetadataTest implements IntegrationTest {
     testEnv.cleanUp();
   }
 
-  private static Iterable<String> getDdlStatements() {
+  static Iterable<String> getDdlStatements() {
     ImmutableList.Builder<String> builder = ImmutableList.builder();
     builder.addAll(PgAdapterTestEnv.DEFAULT_DATA_MODEL);
     builder.addAll(
@@ -108,9 +108,10 @@ public class ITJdbcMetadataTest implements IntegrationTest {
                 + "\talbum int8 not null references albums (album_id),\n"
                 + "\ttrack int8 not null,\n"
                 + "\tattempt int8 not null,\n"
-                + "\trecording_time timestamptz not null,\n"
+                + "\trecording_time timestamptz not null default (now()),\n"
                 + "\tprimary key (album, track, attempt),\n"
-                + "\tforeign key (album, track) references tracks (album_id, track_number)\n"
+                + "\tforeign key (album, track) references tracks (album_id, track_number),\n"
+                + "\tconstraint recording_attempt_greater_than_zero check (attempt > 0)\n"
                 + ")"));
     return builder.build();
   }
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ITPgClassTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ITPgClassTest.java
new file mode 100644
index 00000000..19b36972
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ITPgClassTest.java
@@ -0,0 +1,625 @@
+// 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;
+
+import static com.google.cloud.spanner.pgadapter.ITJdbcMetadataTest.getDdlStatements;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.spanner.Database;
+import com.google.common.collect.ImmutableList;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@Category(IntegrationTest.class)
+@RunWith(JUnit4.class)
+public class ITPgClassTest implements IntegrationTest {
+  private static final PgAdapterTestEnv testEnv = new PgAdapterTestEnv();
+
+  @BeforeClass
+  public static void setup() {
+    testEnv.setUp();
+    Database database = testEnv.createDatabase(getDdlStatements());
+    testEnv.startPGAdapterServerWithDefaultDatabase(database.getId(), ImmutableList.of());
+  }
+
+  @AfterClass
+  public static void teardown() {
+    testEnv.stopPGAdapterServer();
+    testEnv.cleanUp();
+  }
+
+  private String getConnectionUrl() {
+    return String.format("jdbc:postgresql://%s/", testEnv.getPGAdapterHostAndPort());
+  }
+
+  private static class PgClassRow {
+    final String oid;
+    final String relname;
+    final String relkind;
+    final int relnatts;
+    // Relchecks is not implemented and always returns zero.
+    final int relchecks;
+
+    PgClassRow(String oid, String relname, String relkind, int relnatts, int relchecks) {
+      this.oid = oid;
+      this.relname = relname;
+      this.relkind = relkind;
+      this.relnatts = relnatts;
+      this.relchecks = relchecks;
+    }
+  }
+
+  @Test
+  public void testPgClass() throws SQLException {
+    ImmutableList<PgClassRow> expectedRows =
+        ImmutableList.of(
+            new PgClassRow("'\"public\".\"albums\"'", "albums", "r", 3, 0),
+            new PgClassRow(
+                "'\"public\".\"albums\".\"IDX_albums_singer_id_%",
+                "IDX_albums_singer_id_%", "i", 1, 0),
+            new PgClassRow("'\"public\".\"albums\".\"PRIMARY_KEY\"'", "PRIMARY_KEY", "i", 1, 0),
+            new PgClassRow("'\"public\".\"all_types\"'", "all_types", "r", 20, 0),
+            new PgClassRow("'\"public\".\"all_types\".\"PRIMARY_KEY\"'", "PRIMARY_KEY", "i", 1, 0),
+            new PgClassRow(
+                "'\"public\".\"all_types\".\"idx_col_varchar_int\"'",
+                "idx_col_varchar_int",
+                "i",
+                2,
+                0),
+            new PgClassRow("'\"public\".\"numbers\"'", "numbers", "r", 2, 0),
+            new PgClassRow("'\"public\".\"numbers\".\"PRIMARY_KEY\"'", "PRIMARY_KEY", "i", 1, 0),
+            new PgClassRow(
+                "'\"public\".\"numbers\".\"idx_numbers_name\"'", "idx_numbers_name", "i", 1, 0),
+            new PgClassRow("'\"public\".\"recording_attempt\"'", "recording_attempt", "r", 4, 0),
+            new PgClassRow(
+                "'\"public\".\"recording_attempt\".\"PRIMARY_KEY\"'", "PRIMARY_KEY", "i", 3, 0),
+            new PgClassRow("'\"public\".\"singers\"'", "singers", "r", 2, 0),
+            new PgClassRow("'\"public\".\"singers\".\"PRIMARY_KEY\"'", "PRIMARY_KEY", "i", 1, 0),
+            new PgClassRow("'\"public\".\"tracks\"'", "tracks", "r", 3, 0),
+            new PgClassRow("'\"public\".\"tracks\".\"PRIMARY_KEY\"'", "PRIMARY_KEY", "i", 2, 0));
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      try (ResultSet resultSet =
+          connection
+              .createStatement()
+              .executeQuery(
+                  "select oid, relname, relkind, relnatts, relchecks "
+                      + "from pg_class "
+                      + "where relnamespace=2200 "
+                      + "order by oid")) {
+        int index = 0;
+        while (resultSet.next()) {
+          assertTrue(index < expectedRows.size());
+          PgClassRow expected = expectedRows.get(index);
+          if (expected.oid.endsWith("%")) {
+            assertTrue(
+                resultSet.getString("oid"),
+                resultSet
+                    .getString("oid")
+                    .startsWith(expected.oid.substring(0, expected.oid.length() - 1)));
+            assertTrue(
+                resultSet.getString("relname"),
+                resultSet
+                    .getString("relname")
+                    .startsWith(expected.relname.substring(0, expected.relname.length() - 1)));
+          } else {
+            assertEquals(expected.oid, resultSet.getString("oid"));
+            assertEquals(expected.relname, resultSet.getString("relname"));
+          }
+          assertEquals(expected.oid, expected.relkind, resultSet.getString("relkind"));
+          assertEquals(expected.oid, expected.relnatts, resultSet.getInt("relnatts"));
+          assertEquals(expected.oid, expected.relchecks, resultSet.getInt("relchecks"));
+          index++;
+        }
+        assertEquals(index, expectedRows.size());
+      }
+    }
+  }
+
+  private static class PgIndexRow {
+    final String indexrelid;
+    final String indrelid;
+    final int indnatts;
+    final int indnkeyatts;
+    final boolean indisunique;
+    final boolean indnullsnotdistinct;
+    final boolean indisprimary;
+    final String indpred;
+
+    PgIndexRow(
+        String indexrelid,
+        String indrelid,
+        int indnatts,
+        int indnkeyatts,
+        boolean indisunique,
+        boolean indnullsnotdistinct,
+        boolean indisprimary,
+        String indpred) {
+      this.indexrelid = indexrelid;
+      this.indrelid = indrelid;
+      this.indnatts = indnatts;
+      this.indnkeyatts = indnkeyatts;
+      this.indisunique = indisunique;
+      this.indnullsnotdistinct = indnullsnotdistinct;
+      this.indisprimary = indisprimary;
+      this.indpred = indpred;
+    }
+  }
+
+  @Test
+  public void testPgIndex() throws SQLException {
+    String sql =
+        "select indexrelid, indrelid, indnatts, indnkeyatts, indisunique, "
+            + "indnullsnotdistinct, indisprimary, indpred "
+            + "from pg_index "
+            + "order by indexrelid";
+    ImmutableList<PgIndexRow> expectedRows =
+        ImmutableList.of(
+            new PgIndexRow(
+                "'\"public\".\"albums\".\"IDX_albums_singer_id_%",
+                "'\"public\".\"albums\"'", 1, 1, false, false, false, null),
+            new PgIndexRow(
+                "'\"public\".\"albums\".\"PRIMARY_KEY\"'",
+                "'\"public\".\"albums\"'",
+                1,
+                1,
+                true,
+                true,
+                true,
+                null),
+            new PgIndexRow(
+                "'\"public\".\"all_types\".\"PRIMARY_KEY\"'",
+                "'\"public\".\"all_types\"'",
+                1,
+                1,
+                true,
+                true,
+                true,
+                null),
+            new PgIndexRow(
+                "'\"public\".\"all_types\".\"idx_col_varchar_int\"'",
+                "'\"public\".\"all_types\"'",
+                2,
+                2,
+                false,
+                false,
+                false,
+                null),
+            new PgIndexRow(
+                "'\"public\".\"numbers\".\"PRIMARY_KEY\"'",
+                "'\"public\".\"numbers\"'",
+                1,
+                1,
+                true,
+                true,
+                true,
+                null),
+            new PgIndexRow(
+                "'\"public\".\"numbers\".\"idx_numbers_name\"'",
+                "'\"public\".\"numbers\"'",
+                1,
+                1,
+                true,
+                true,
+                false,
+                "name IS NOT NULL"),
+            new PgIndexRow(
+                "'\"public\".\"recording_attempt\".\"PRIMARY_KEY\"'",
+                "'\"public\".\"recording_attempt\"'",
+                3,
+                3,
+                true,
+                true,
+                true,
+                null),
+            new PgIndexRow(
+                "'\"public\".\"singers\".\"PRIMARY_KEY\"'",
+                "'\"public\".\"singers\"'",
+                1,
+                1,
+                true,
+                true,
+                true,
+                null),
+            new PgIndexRow(
+                "'\"public\".\"tracks\".\"PRIMARY_KEY\"'",
+                "'\"public\".\"tracks\"'",
+                2,
+                2,
+                true,
+                true,
+                true,
+                null));
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+        int index = 0;
+        while (resultSet.next()) {
+          assertTrue(index < expectedRows.size());
+          PgIndexRow expected = expectedRows.get(index);
+          if (expected.indexrelid.endsWith("%")) {
+            assertTrue(
+                resultSet.getString("indexrelid"),
+                resultSet
+                    .getString("indexrelid")
+                    .startsWith(
+                        expected.indexrelid.substring(0, expected.indexrelid.length() - 1)));
+          } else {
+            assertEquals(expected.indexrelid, resultSet.getString("indexrelid"));
+          }
+          assertEquals(expected.indexrelid, expected.indrelid, resultSet.getString("indrelid"));
+          assertEquals(expected.indexrelid, expected.indnatts, resultSet.getInt("indnatts"));
+          assertEquals(expected.indexrelid, expected.indnkeyatts, resultSet.getInt("indnkeyatts"));
+          assertEquals(
+              expected.indexrelid, expected.indisunique, resultSet.getBoolean("indisunique"));
+          assertEquals(
+              expected.indexrelid,
+              expected.indnullsnotdistinct,
+              resultSet.getBoolean("indnullsnotdistinct"));
+          assertEquals(
+              expected.indexrelid, expected.indisprimary, resultSet.getBoolean("indisprimary"));
+          assertEquals(expected.indexrelid, expected.indpred, resultSet.getString("indpred"));
+          index++;
+        }
+        assertEquals(index, expectedRows.size());
+      }
+    }
+  }
+
+  @Test
+  public void testPgAttribute() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      try (PreparedStatement statement =
+          connection.prepareStatement(
+              "select count(*) " + "from pg_attribute " + "where attrelid=?")) {
+        try (ResultSet pgClass =
+            connection
+                .createStatement()
+                .executeQuery(
+                    "select oid, relnatts " + "from pg_class " + "where relnamespace=2200")) {
+          while (pgClass.next()) {
+            statement.setObject(1, pgClass.getObject(1));
+            try (ResultSet numAttributes = statement.executeQuery()) {
+              assertTrue(numAttributes.next());
+              assertEquals(pgClass.getLong(2), numAttributes.getLong(1));
+              assertFalse(numAttributes.next());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testEmulatePgAttribute() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      connection.createStatement().execute("set spanner.emulate_pg_class_tables=true");
+      try (ResultSet resultSet =
+          connection.createStatement().executeQuery("select * from pg_attribute limit 1")) {
+        assertTrue(resultSet.next());
+        assertFalse(resultSet.next());
+      }
+      connection.createStatement().execute("set spanner.emulate_pg_class_tables=false");
+      try (ResultSet resultSet =
+          connection.createStatement().executeQuery("select * from pg_attribute limit 1")) {
+        assertFalse(resultSet.next());
+      }
+    }
+  }
+
+  @Test
+  public void testPgCollation() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      try (ResultSet collations =
+          connection.createStatement().executeQuery("select * from pg_collation")) {
+        assertTrue(collations.next());
+        assertEquals(100, collations.getInt("oid"));
+        assertEquals("default", collations.getString("collname"));
+        assertFalse(collations.next());
+      }
+    }
+  }
+
+  @Test
+  public void testPgExtension() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      try (ResultSet extensions =
+          connection.createStatement().executeQuery("select * from pg_extension")) {
+        assertEquals(8, extensions.getMetaData().getColumnCount());
+        assertFalse(extensions.next());
+      }
+    }
+  }
+
+  @Test
+  public void testPgType() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      for (boolean emulate : new boolean[] {true, false}) {
+        connection.createStatement().execute("set spanner.emulate_pg_class_tables=" + emulate);
+        try (ResultSet types = connection.createStatement().executeQuery("select * from pg_type")) {
+          int count = 0;
+          while (types.next()) {
+            assertEquals(0, types.getInt("typrelid"));
+            count++;
+          }
+          assertEquals(28, count);
+        }
+      }
+    }
+  }
+
+  @Test
+  public void testPgAttrdef() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      try (ResultSet resultSet =
+          connection.createStatement().executeQuery("select * from pg_attrdef")) {
+        assertTrue(resultSet.next());
+        assertEquals(
+            "'\"public\".\"recording_attempt\".\"recording_time\"'", resultSet.getString("oid"));
+        assertEquals("'\"public\".\"recording_attempt\"'", resultSet.getString("adrelid"));
+        assertEquals(4, resultSet.getInt("adnum"));
+        assertEquals("now()", resultSet.getString("adbin"));
+        assertFalse(resultSet.next());
+      }
+    }
+  }
+
+  private static class PgConstraintRow {
+    final String oid;
+    final String conname;
+    final char contype;
+    final String conrelid;
+    final String conindid; // Currently not implemented
+    final String confrelid;
+    final String confupdtype;
+    final String confdeltype;
+    final String confmatchtype;
+    final Long[] conkey;
+    final Long[] confkey;
+    final String conbin;
+
+    private PgConstraintRow(
+        String oid,
+        String conname,
+        char contype,
+        String conrelid,
+        String conindid,
+        String confrelid,
+        String confupdtype,
+        String confdeltype,
+        String confmatchtype,
+        Long[] conkey,
+        Long[] confkey,
+        String conbin) {
+      this.oid = oid;
+      this.conname = conname;
+      this.contype = contype;
+      this.conrelid = conrelid;
+      this.conindid = conindid;
+      this.confrelid = confrelid;
+      this.confupdtype = confupdtype;
+      this.confdeltype = confdeltype;
+      this.confmatchtype = confmatchtype;
+      this.conkey = conkey;
+      this.confkey = confkey;
+      this.conbin = conbin;
+    }
+  }
+
+  @Test
+  public void testPgConstraint() throws SQLException {
+    ImmutableList<PgConstraintRow> expectedRows =
+        ImmutableList.of(
+            new PgConstraintRow(
+                "'\"public\".\"FK_albums_singers_%",
+                "FK_albums_singers_%",
+                'f',
+                "'\"public\".\"albums\"'",
+                "0",
+                "'\"public\".\"singers\"'",
+                "a",
+                "a",
+                "s",
+                new Long[] {2L},
+                new Long[] {1L},
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"FK_recording_attempt_albums_%",
+                "FK_recording_attempt_albums_%",
+                'f',
+                "'\"public\".\"recording_attempt\"'",
+                "0",
+                "'\"public\".\"albums\"'",
+                "a",
+                "a",
+                "s",
+                new Long[] {1L},
+                new Long[] {1L},
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"FK_recording_attempt_tracks_%",
+                "FK_recording_attempt_tracks_%",
+                'f',
+                "'\"public\".\"recording_attempt\"'",
+                "0",
+                "'\"public\".\"tracks\"'",
+                "a",
+                "a",
+                "s",
+                new Long[] {1L, 2L},
+                new Long[] {1L, 2L},
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"FK_tracks_albums_%",
+                "FK_tracks_albums_%",
+                'f',
+                "'\"public\".\"tracks\"'",
+                "0",
+                "'\"public\".\"albums\"'",
+                "a",
+                "a",
+                "s",
+                new Long[] {1L},
+                new Long[] {1L},
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"PK_albums\"'",
+                "PK_albums",
+                'p',
+                "'\"public\".\"albums\"'",
+                "0",
+                null,
+                null,
+                null,
+                "s",
+                new Long[] {1L},
+                null,
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"PK_all_types\"'",
+                "PK_all_types",
+                'p',
+                "'\"public\".\"all_types\"'",
+                "0",
+                null,
+                null,
+                null,
+                "s",
+                new Long[] {1L},
+                null,
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"PK_numbers\"'",
+                "PK_numbers",
+                'p',
+                "'\"public\".\"numbers\"'",
+                "0",
+                null,
+                null,
+                null,
+                "s",
+                new Long[] {1L},
+                null,
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"PK_recording_attempt\"'",
+                "PK_recording_attempt",
+                'p',
+                "'\"public\".\"recording_attempt\"'",
+                "0",
+                null,
+                null,
+                null,
+                "s",
+                new Long[] {1L, 2L, 3L},
+                null,
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"PK_singers\"'",
+                "PK_singers",
+                'p',
+                "'\"public\".\"singers\"'",
+                "0",
+                null,
+                null,
+                null,
+                "s",
+                new Long[] {1L},
+                null,
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"PK_tracks\"'",
+                "PK_tracks",
+                'p',
+                "'\"public\".\"tracks\"'",
+                "0",
+                null,
+                null,
+                null,
+                "s",
+                new Long[] {1L, 2L},
+                null,
+                null),
+            new PgConstraintRow(
+                "'\"public\".\"recording_attempt_greater_than_zero\"'",
+                "recording_attempt_greater_than_zero",
+                'c',
+                "'\"public\".\"recording_attempt\"'",
+                "0",
+                null,
+                null,
+                null,
+                "s",
+                new Long[] {3L},
+                null,
+                "(attempt > '0'::bigint)"));
+
+    String sql =
+        "select oid, conname, contype, conrelid, conindid, confrelid, confupdtype, confdeltype, "
+            + "confmatchtype, conkey, confkey, conbin "
+            + "from pg_constraint "
+            + "order by oid";
+    try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+      try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+        int index = 0;
+        while (resultSet.next()) {
+          assertTrue(resultSet.getString("oid"), index < expectedRows.size());
+          PgConstraintRow expected = expectedRows.get(index);
+          if (expected.oid.endsWith("%")) {
+            assertTrue(
+                expected.oid,
+                resultSet
+                    .getString("oid")
+                    .startsWith(expected.oid.substring(0, expected.oid.length() - 1)));
+            assertTrue(
+                expected.conname,
+                resultSet
+                    .getString("conname")
+                    .startsWith(expected.conname.substring(0, expected.conname.length() - 1)));
+          } else {
+            assertEquals(expected.oid, resultSet.getString("oid"));
+            assertEquals(expected.conname, resultSet.getString("conname"));
+          }
+          assertEquals(expected.contype, resultSet.getString("contype").charAt(0));
+          assertEquals(expected.conrelid, resultSet.getString("conrelid"));
+          assertEquals(expected.conindid, resultSet.getString("conindid"));
+          assertEquals(expected.confrelid, resultSet.getString("confrelid"));
+          assertEquals(expected.confupdtype, resultSet.getString("confupdtype"));
+          assertEquals(expected.confdeltype, resultSet.getString("confdeltype"));
+          assertEquals(expected.confmatchtype, resultSet.getString("confmatchtype"));
+          assertArrayEquals(expected.conkey, (Long[]) resultSet.getArray("conkey").getArray());
+          assertArrayEquals(
+              expected.confkey,
+              resultSet.getArray("confkey") == null
+                  ? null
+                  : (Long[]) resultSet.getArray("confkey").getArray());
+          assertEquals(expected.conbin, resultSet.getString("conbin"));
+          index++;
+        }
+        assertEquals(expectedRows.size(), index);
+      }
+    }
+  }
+}
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 87fecdab..5888e540 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
@@ -15,6 +15,7 @@
 package com.google.cloud.spanner.pgadapter;
 
 import static com.google.cloud.spanner.pgadapter.statements.BackendConnection.TRANSACTION_ABORTED_ERROR;
+import static com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgNamespace.PG_NAMESPACE_CTE;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -40,6 +41,12 @@ import com.google.cloud.spanner.Statement;
 import com.google.cloud.spanner.connection.RandomResultSetGenerator;
 import com.google.cloud.spanner.pgadapter.error.SQLState;
 import com.google.cloud.spanner.pgadapter.statements.PgCatalog.EmptyPgEnum;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgAttrdef;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgAttribute;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgCollation;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgConstraint;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgExtension;
+import com.google.cloud.spanner.pgadapter.statements.PgCatalog.PgIndex;
 import com.google.cloud.spanner.pgadapter.wireprotocol.ControlMessage.PreparedType;
 import com.google.cloud.spanner.pgadapter.wireprotocol.DescribeMessage;
 import com.google.cloud.spanner.pgadapter.wireprotocol.ExecuteMessage;
@@ -3345,13 +3352,7 @@ public class JdbcMockServerTest extends AbstractMockServerTest {
   public void testInformationSchemaQueryInTransactionWithReplacedPgCatalogTables()
       throws SQLException {
     String sql = "SELECT 1 FROM pg_namespace";
-    String replacedSql =
-        "with pg_namespace as (\n"
-            + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
-            + "        schema_name as nspname, null as nspowner, null as nspacl\n"
-            + "  from information_schema.schemata\n"
-            + ")\n"
-            + "SELECT 1 FROM pg_namespace";
+    String replacedSql = "with " + PG_NAMESPACE_CTE + "\nSELECT 1 FROM pg_namespace";
     // Register a result for the query. Note that we don't really care what the result is, just that
     // there is a result.
     mockSpanner.putStatementResult(
@@ -3834,7 +3835,7 @@ public class JdbcMockServerTest extends AbstractMockServerTest {
           }
           count++;
         }
-        assertEquals(359, count);
+        assertEquals(360, count);
       }
     }
   }
@@ -4117,13 +4118,7 @@ public class JdbcMockServerTest extends AbstractMockServerTest {
       }
       mockSpanner.putStatementResult(
           StatementResult.query(
-              Statement.of(
-                  "with pg_namespace as (\n"
-                      + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
-                      + "        schema_name as nspname, null as nspowner, null as nspacl\n"
-                      + "  from information_schema.schemata\n"
-                      + ")\n"
-                      + "select * from pg_namespace"),
+              Statement.of("with " + PG_NAMESPACE_CTE + "\n" + "select * from pg_namespace"),
               SELECT1_RESULTSET));
       // Just verify that this works, we don't care about the result.
       try (ResultSet resultSet =
@@ -4736,6 +4731,133 @@ public class JdbcMockServerTest extends AbstractMockServerTest {
     assertEquals(sql, request.getStatements(0).getSql());
   }
 
+  @Test
+  public void testEmulatePgClass() throws SQLException {
+    String withEmulation = "with " + EMULATED_PG_CLASS_PREFIX + "\nselect 1 from pg_class";
+    mockSpanner.putStatementResult(
+        StatementResult.query(Statement.of(withEmulation), SELECT1_RESULTSET));
+    String withoutEmulation = "with " + PG_CLASS_PREFIX + "\nselect 1 from pg_class";
+    mockSpanner.putStatementResult(
+        StatementResult.query(Statement.of(withoutEmulation), EMPTY_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of("with " + PgCollation.PG_COLLATION_CTE + "\nselect 1 from pg_collation"),
+            SELECT1_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of("with " + PgExtension.PG_EXTENSION_CTE + "\nselect 1 from pg_extension"),
+            SELECT1_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of(
+                "with " + PgAttribute.EMPTY_PG_ATTRIBUTE_CTE + "\nselect 1 from pg_attribute"),
+            EMPTY_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of("with " + EMULATED_PG_ATTRIBUTE_PREFIX + "\nselect 1 from pg_attribute"),
+            SELECT1_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of("with " + PgAttrdef.EMPTY_PG_ATTRDEF_CTE + "\nselect 1 from pg_attrdef"),
+            EMPTY_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of("with " + PgAttrdef.PG_ATTRDEF_CTE + "\nselect 1 from pg_attrdef"),
+            SELECT1_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of(
+                "with " + PgConstraint.EMPTY_PG_CONSTRAINT_CTE + "\nselect 1 from pg_constraint"),
+            EMPTY_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of(
+                "with " + PgConstraint.PG_CONSTRAINT_CTE + "\nselect 1 from pg_constraint"),
+            SELECT1_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of("with " + PgIndex.EMPTY_PG_INDEX_CTE + "\nselect 1 from pg_index"),
+            EMPTY_RESULTSET));
+    mockSpanner.putStatementResult(
+        StatementResult.query(
+            Statement.of("with " + PgIndex.PG_INDEX_CTE + "\nselect 1 from pg_index"),
+            SELECT1_RESULTSET));
+
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      for (boolean emulate : new boolean[] {true, false}) {
+        connection.createStatement().execute("set spanner.emulate_pg_class_tables=" + emulate);
+        try (ResultSet resultSet =
+            connection.createStatement().executeQuery("select 1 from pg_class")) {
+          if (emulate) {
+            assertTrue(resultSet.next());
+            assertEquals(1L, resultSet.getLong(1));
+          }
+          assertFalse(resultSet.next());
+        }
+
+        try (ResultSet resultSet =
+            connection.createStatement().executeQuery("select 1 from pg_collation")) {
+          assertTrue(resultSet.next());
+          assertEquals(1L, resultSet.getLong(1));
+          assertFalse(resultSet.next());
+        }
+
+        try (ResultSet resultSet =
+            connection.createStatement().executeQuery("select 1 from pg_extension")) {
+          assertTrue(resultSet.next());
+          assertEquals(1L, resultSet.getLong(1));
+          assertFalse(resultSet.next());
+        }
+
+        try (ResultSet resultSet =
+            connection.createStatement().executeQuery("select 1 from pg_attribute")) {
+          if (emulate) {
+            assertTrue(resultSet.next());
+            assertEquals(1L, resultSet.getLong(1));
+          }
+          assertFalse(resultSet.next());
+        }
+
+        try (ResultSet resultSet =
+            connection.createStatement().executeQuery("select 1 from pg_attrdef")) {
+          if (emulate) {
+            assertTrue(resultSet.next());
+            assertEquals(1L, resultSet.getLong(1));
+          }
+          assertFalse(resultSet.next());
+        }
+
+        try (ResultSet resultSet =
+            connection.createStatement().executeQuery("select 1 from pg_constraint")) {
+          if (emulate) {
+            assertTrue(resultSet.next());
+            assertEquals(1L, resultSet.getLong(1));
+          }
+          assertFalse(resultSet.next());
+        }
+
+        try (ResultSet resultSet =
+            connection.createStatement().executeQuery("select 1 from pg_index")) {
+          if (emulate) {
+            assertTrue(resultSet.next());
+            assertEquals(1L, resultSet.getLong(1));
+          }
+          assertFalse(resultSet.next());
+        }
+      }
+    }
+    assertEquals(
+        1,
+        mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
+            .filter(request -> request.getSql().equals(withEmulation))
+            .count());
+    assertEquals(
+        1,
+        mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
+            .filter(request -> request.getSql().equals(withoutEmulation))
+            .count());
+  }
+
   @Ignore("Only used for manual performance testing")
   @Test
   public void testBasePerformance() throws SQLException {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java
index f9d744b1..e5f61043 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java
@@ -64,83 +64,9 @@ public abstract class AbstractNpgsqlMockServerTest extends AbstractMockServerTes
               + "select * from (select 0::bigint as rngtypid, 0::bigint as rngsubtype, 0::bigint as rngmultitypid, 0::bigint as rngcollation, 0::bigint as rngsubopc, ''::varchar as rngcanonical, ''::varchar as rngsubdiff\n"
               + ") range where false),\n"
               + PG_TYPE_PREFIX
-              + ",\npg_class as (\n"
-              + "  select\n"
-              + "  -1 as oid,\n"
-              + "  table_name as relname,\n"
-              + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-              + "  0 as reltype,\n"
-              + "  0 as reloftype,\n"
-              + "  0 as relowner,\n"
-              + "  1 as relam,\n"
-              + "  0 as relfilenode,\n"
-              + "  0 as reltablespace,\n"
-              + "  0 as relpages,\n"
-              + "  0.0::float8 as reltuples,\n"
-              + "  0 as relallvisible,\n"
-              + "  0 as reltoastrelid,\n"
-              + "  false as relhasindex,\n"
-              + "  false as relisshared,\n"
-              + "  'p' as relpersistence,\n"
-              + "  'r' as relkind,\n"
-              + "  count(*) as relnatts,\n"
-              + "  0 as relchecks,\n"
-              + "  false as relhasrules,\n"
-              + "  false as relhastriggers,\n"
-              + "  false as relhassubclass,\n"
-              + "  false as relrowsecurity,\n"
-              + "  false as relforcerowsecurity,\n"
-              + "  true as relispopulated,\n"
-              + "  'n' as relreplident,\n"
-              + "  false as relispartition,\n"
-              + "  0 as relrewrite,\n"
-              + "  0 as relfrozenxid,\n"
-              + "  0 as relminmxid,\n"
-              + "  '{}'::bigint[] as relacl,\n"
-              + "  '{}'::text[] as reloptions,\n"
-              + "  0 as relpartbound\n"
-              + "from information_schema.tables t\n"
-              + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-              + "group by t.table_name, t.table_schema\n"
-              + "union all\n"
-              + "select\n"
-              + "    -1 as oid,\n"
-              + "    i.index_name as relname,\n"
-              + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-              + "    0 as reltype,\n"
-              + "    0 as reloftype,\n"
-              + "    0 as relowner,\n"
-              + "    1 as relam,\n"
-              + "    0 as relfilenode,\n"
-              + "    0 as reltablespace,\n"
-              + "    0 as relpages,\n"
-              + "    0.0::float8 as reltuples,\n"
-              + "    0 as relallvisible,\n"
-              + "    0 as reltoastrelid,\n"
-              + "    false as relhasindex,\n"
-              + "    false as relisshared,\n"
-              + "    'p' as relpersistence,\n"
-              + "    'r' as relkind,\n"
-              + "    count(*) as relnatts,\n"
-              + "    0 as relchecks,\n"
-              + "    false as relhasrules,\n"
-              + "    false as relhastriggers,\n"
-              + "    false as relhassubclass,\n"
-              + "    false as relrowsecurity,\n"
-              + "    false as relforcerowsecurity,\n"
-              + "    true as relispopulated,\n"
-              + "    'n' as relreplident,\n"
-              + "    false as relispartition,\n"
-              + "    0 as relrewrite,\n"
-              + "    0 as relfrozenxid,\n"
-              + "    0 as relminmxid,\n"
-              + "    '{}'::bigint[] as relacl,\n"
-              + "    '{}'::text[] as reloptions,\n"
-              + "    0 as relpartbound\n"
-              + "from information_schema.indexes i\n"
-              + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-              + "group by i.index_name, i.table_schema\n"
-              + "),\n"
+              + ",\n"
+              + EMULATED_PG_CLASS_PREFIX
+              + ",\n"
               + "pg_proc as (\n"
               + "select * from (select 0::bigint as oid, ''::varchar as proname, 0::bigint as pronamespace, 0::bigint as proowner, 0::bigint as prolang, 0.0::float8 as procost, 0.0::float8 as prorows, 0::bigint as provariadic, ''::varchar as prosupport, ''::varchar as prokind, false::bool as prosecdef, false::bool as proleakproof, false::bool as proisstrict, false::bool as proretset, ''::varchar as provolatile, ''::varchar as proparallel, 0::bigint as pronargs, 0::bigint as pronargdefaults, 0::bigint as prorettype, 0::bigint as proargtypes, '{}'::bigint[] as proallargtypes, '{}'::varchar[] as proargmodes, '{}'::text[] as proargnames, ''::varchar as proargdefaults, '{}'::bigint[] as protrftypes, ''::text as prosrc, ''::text as probin, ''::varchar as prosqlbody, '{}'::text[] as proconfig, '{}'::bigint[] as proacl\n"
               + ") proc where false)\n"
@@ -504,86 +430,11 @@ public abstract class AbstractNpgsqlMockServerTest extends AbstractMockServerTes
       Statement.of(
           "with "
               + PG_TYPE_PREFIX
-              + ",\npg_class as (\n"
-              + "  select\n"
-              + "  -1 as oid,\n"
-              + "  table_name as relname,\n"
-              + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-              + "  0 as reltype,\n"
-              + "  0 as reloftype,\n"
-              + "  0 as relowner,\n"
-              + "  1 as relam,\n"
-              + "  0 as relfilenode,\n"
-              + "  0 as reltablespace,\n"
-              + "  0 as relpages,\n"
-              + "  0.0::float8 as reltuples,\n"
-              + "  0 as relallvisible,\n"
-              + "  0 as reltoastrelid,\n"
-              + "  false as relhasindex,\n"
-              + "  false as relisshared,\n"
-              + "  'p' as relpersistence,\n"
-              + "  'r' as relkind,\n"
-              + "  count(*) as relnatts,\n"
-              + "  0 as relchecks,\n"
-              + "  false as relhasrules,\n"
-              + "  false as relhastriggers,\n"
-              + "  false as relhassubclass,\n"
-              + "  false as relrowsecurity,\n"
-              + "  false as relforcerowsecurity,\n"
-              + "  true as relispopulated,\n"
-              + "  'n' as relreplident,\n"
-              + "  false as relispartition,\n"
-              + "  0 as relrewrite,\n"
-              + "  0 as relfrozenxid,\n"
-              + "  0 as relminmxid,\n"
-              + "  '{}'::bigint[] as relacl,\n"
-              + "  '{}'::text[] as reloptions,\n"
-              + "  0 as relpartbound\n"
-              + "from information_schema.tables t\n"
-              + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-              + "group by t.table_name, t.table_schema\n"
-              + "union all\n"
-              + "select\n"
-              + "    -1 as oid,\n"
-              + "    i.index_name as relname,\n"
-              + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-              + "    0 as reltype,\n"
-              + "    0 as reloftype,\n"
-              + "    0 as relowner,\n"
-              + "    1 as relam,\n"
-              + "    0 as relfilenode,\n"
-              + "    0 as reltablespace,\n"
-              + "    0 as relpages,\n"
-              + "    0.0::float8 as reltuples,\n"
-              + "    0 as relallvisible,\n"
-              + "    0 as reltoastrelid,\n"
-              + "    false as relhasindex,\n"
-              + "    false as relisshared,\n"
-              + "    'p' as relpersistence,\n"
-              + "    'r' as relkind,\n"
-              + "    count(*) as relnatts,\n"
-              + "    0 as relchecks,\n"
-              + "    false as relhasrules,\n"
-              + "    false as relhastriggers,\n"
-              + "    false as relhassubclass,\n"
-              + "    false as relrowsecurity,\n"
-              + "    false as relforcerowsecurity,\n"
-              + "    true as relispopulated,\n"
-              + "    'n' as relreplident,\n"
-              + "    false as relispartition,\n"
-              + "    0 as relrewrite,\n"
-              + "    0 as relfrozenxid,\n"
-              + "    0 as relminmxid,\n"
-              + "    '{}'::bigint[] as relacl,\n"
-              + "    '{}'::text[] as reloptions,\n"
-              + "    0 as relpartbound\n"
-              + "from information_schema.indexes i\n"
-              + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-              + "group by i.index_name, i.table_schema\n"
-              + "),\n"
-              + "pg_attribute as (\n"
-              + "select * from (select 0::bigint as attrelid, '' as attname, 0::bigint as atttypid, 0::bigint as attstattarget, 0::bigint as attlen, 0::bigint as attnum, 0::bigint as attndims, -1::bigint as attcacheoff, 0::bigint as atttypmod, true as attbyval, '' as attalign, '' as attstorage, '' as attcompression, false as attnotnull, true as atthasdef, false as atthasmissing, '' as attidentity, '' as attgenerated, false as attisdropped, true as attislocal, 0 as attinhcount, 0 as attcollation, '{}'::bigint[] as attacl, '{}'::text[] as attoptions, '{}'::text[] as attfdwoptions, null as attmissingval\n"
-              + ") a where false)\n"
+              + ",\n"
+              + EMULATED_PG_CLASS_PREFIX
+              + ",\n"
+              + EMULATED_PG_ATTRIBUTE_PREFIX
+              + "\n"
               + "-- Load field definitions for (free-standing) composite types\n"
               + "SELECT typ.oid, att.attname, att.atttypid\n"
               + "FROM pg_type AS typ\n"
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemyBasicsTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemyBasicsTest.java
index 8094633d..32fbd629 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemyBasicsTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemyBasicsTest.java
@@ -196,83 +196,9 @@ public class SqlAlchemyBasicsTest extends AbstractMockServerTest {
   @Test
   public void testSimpleMetadata() throws Exception {
     String checkTableExistsSql =
-        "with pg_class as (\n"
-            + "  select\n"
-            + "  -1 as oid,\n"
-            + "  table_name as relname,\n"
-            + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "  0 as reltype,\n"
-            + "  0 as reloftype,\n"
-            + "  0 as relowner,\n"
-            + "  1 as relam,\n"
-            + "  0 as relfilenode,\n"
-            + "  0 as reltablespace,\n"
-            + "  0 as relpages,\n"
-            + "  0.0::float8 as reltuples,\n"
-            + "  0 as relallvisible,\n"
-            + "  0 as reltoastrelid,\n"
-            + "  false as relhasindex,\n"
-            + "  false as relisshared,\n"
-            + "  'p' as relpersistence,\n"
-            + "  'r' as relkind,\n"
-            + "  count(*) as relnatts,\n"
-            + "  0 as relchecks,\n"
-            + "  false as relhasrules,\n"
-            + "  false as relhastriggers,\n"
-            + "  false as relhassubclass,\n"
-            + "  false as relrowsecurity,\n"
-            + "  false as relforcerowsecurity,\n"
-            + "  true as relispopulated,\n"
-            + "  'n' as relreplident,\n"
-            + "  false as relispartition,\n"
-            + "  0 as relrewrite,\n"
-            + "  0 as relfrozenxid,\n"
-            + "  0 as relminmxid,\n"
-            + "  '{}'::bigint[] as relacl,\n"
-            + "  '{}'::text[] as reloptions,\n"
-            + "  0 as relpartbound\n"
-            + "from information_schema.tables t\n"
-            + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-            + "group by t.table_name, t.table_schema\n"
-            + "union all\n"
-            + "select\n"
-            + "    -1 as oid,\n"
-            + "    i.index_name as relname,\n"
-            + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "    0 as reltype,\n"
-            + "    0 as reloftype,\n"
-            + "    0 as relowner,\n"
-            + "    1 as relam,\n"
-            + "    0 as relfilenode,\n"
-            + "    0 as reltablespace,\n"
-            + "    0 as relpages,\n"
-            + "    0.0::float8 as reltuples,\n"
-            + "    0 as relallvisible,\n"
-            + "    0 as reltoastrelid,\n"
-            + "    false as relhasindex,\n"
-            + "    false as relisshared,\n"
-            + "    'p' as relpersistence,\n"
-            + "    'r' as relkind,\n"
-            + "    count(*) as relnatts,\n"
-            + "    0 as relchecks,\n"
-            + "    false as relhasrules,\n"
-            + "    false as relhastriggers,\n"
-            + "    false as relhassubclass,\n"
-            + "    false as relrowsecurity,\n"
-            + "    false as relforcerowsecurity,\n"
-            + "    true as relispopulated,\n"
-            + "    'n' as relreplident,\n"
-            + "    false as relispartition,\n"
-            + "    0 as relrewrite,\n"
-            + "    0 as relfrozenxid,\n"
-            + "    0 as relminmxid,\n"
-            + "    '{}'::bigint[] as relacl,\n"
-            + "    '{}'::text[] as reloptions,\n"
-            + "    0 as relpartbound\n"
-            + "from information_schema.indexes i\n"
-            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-            + "group by i.index_name, i.table_schema\n"
-            + "),\n"
+        "with "
+            + EMULATED_PG_CLASS_PREFIX
+            + ",\n"
             + "pg_namespace as (\n"
             + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
             + "        schema_name as nspname, null as nspowner, null as nspacl\n"
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemySampleTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemySampleTest.java
index b7c9ef1c..6882e556 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemySampleTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy/SqlAlchemySampleTest.java
@@ -730,83 +730,9 @@ public class SqlAlchemySampleTest extends AbstractMockServerTest {
   @Test
   public void testCreateDataModel() throws Exception {
     String checkTableExistsSql =
-        "with pg_class as (\n"
-            + "  select\n"
-            + "  -1 as oid,\n"
-            + "  table_name as relname,\n"
-            + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "  0 as reltype,\n"
-            + "  0 as reloftype,\n"
-            + "  0 as relowner,\n"
-            + "  1 as relam,\n"
-            + "  0 as relfilenode,\n"
-            + "  0 as reltablespace,\n"
-            + "  0 as relpages,\n"
-            + "  0.0::float8 as reltuples,\n"
-            + "  0 as relallvisible,\n"
-            + "  0 as reltoastrelid,\n"
-            + "  false as relhasindex,\n"
-            + "  false as relisshared,\n"
-            + "  'p' as relpersistence,\n"
-            + "  'r' as relkind,\n"
-            + "  count(*) as relnatts,\n"
-            + "  0 as relchecks,\n"
-            + "  false as relhasrules,\n"
-            + "  false as relhastriggers,\n"
-            + "  false as relhassubclass,\n"
-            + "  false as relrowsecurity,\n"
-            + "  false as relforcerowsecurity,\n"
-            + "  true as relispopulated,\n"
-            + "  'n' as relreplident,\n"
-            + "  false as relispartition,\n"
-            + "  0 as relrewrite,\n"
-            + "  0 as relfrozenxid,\n"
-            + "  0 as relminmxid,\n"
-            + "  '{}'::bigint[] as relacl,\n"
-            + "  '{}'::text[] as reloptions,\n"
-            + "  0 as relpartbound\n"
-            + "from information_schema.tables t\n"
-            + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-            + "group by t.table_name, t.table_schema\n"
-            + "union all\n"
-            + "select\n"
-            + "    -1 as oid,\n"
-            + "    i.index_name as relname,\n"
-            + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "    0 as reltype,\n"
-            + "    0 as reloftype,\n"
-            + "    0 as relowner,\n"
-            + "    1 as relam,\n"
-            + "    0 as relfilenode,\n"
-            + "    0 as reltablespace,\n"
-            + "    0 as relpages,\n"
-            + "    0.0::float8 as reltuples,\n"
-            + "    0 as relallvisible,\n"
-            + "    0 as reltoastrelid,\n"
-            + "    false as relhasindex,\n"
-            + "    false as relisshared,\n"
-            + "    'p' as relpersistence,\n"
-            + "    'r' as relkind,\n"
-            + "    count(*) as relnatts,\n"
-            + "    0 as relchecks,\n"
-            + "    false as relhasrules,\n"
-            + "    false as relhastriggers,\n"
-            + "    false as relhassubclass,\n"
-            + "    false as relrowsecurity,\n"
-            + "    false as relforcerowsecurity,\n"
-            + "    true as relispopulated,\n"
-            + "    'n' as relreplident,\n"
-            + "    false as relispartition,\n"
-            + "    0 as relrewrite,\n"
-            + "    0 as relfrozenxid,\n"
-            + "    0 as relminmxid,\n"
-            + "    '{}'::bigint[] as relacl,\n"
-            + "    '{}'::text[] as reloptions,\n"
-            + "    0 as relpartbound\n"
-            + "from information_schema.indexes i\n"
-            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-            + "group by i.index_name, i.table_schema\n"
-            + "),\n"
+        "with "
+            + EMULATED_PG_CLASS_PREFIX
+            + ",\n"
             + "pg_namespace as (\n"
             + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
             + "        schema_name as nspname, null as nspowner, null as nspacl\n"
@@ -922,83 +848,9 @@ public class SqlAlchemySampleTest extends AbstractMockServerTest {
   @Test
   public void testMetadataReflect() throws Exception {
     String sql =
-        "with pg_class as (\n"
-            + "  select\n"
-            + "  -1 as oid,\n"
-            + "  table_name as relname,\n"
-            + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "  0 as reltype,\n"
-            + "  0 as reloftype,\n"
-            + "  0 as relowner,\n"
-            + "  1 as relam,\n"
-            + "  0 as relfilenode,\n"
-            + "  0 as reltablespace,\n"
-            + "  0 as relpages,\n"
-            + "  0.0::float8 as reltuples,\n"
-            + "  0 as relallvisible,\n"
-            + "  0 as reltoastrelid,\n"
-            + "  false as relhasindex,\n"
-            + "  false as relisshared,\n"
-            + "  'p' as relpersistence,\n"
-            + "  'r' as relkind,\n"
-            + "  count(*) as relnatts,\n"
-            + "  0 as relchecks,\n"
-            + "  false as relhasrules,\n"
-            + "  false as relhastriggers,\n"
-            + "  false as relhassubclass,\n"
-            + "  false as relrowsecurity,\n"
-            + "  false as relforcerowsecurity,\n"
-            + "  true as relispopulated,\n"
-            + "  'n' as relreplident,\n"
-            + "  false as relispartition,\n"
-            + "  0 as relrewrite,\n"
-            + "  0 as relfrozenxid,\n"
-            + "  0 as relminmxid,\n"
-            + "  '{}'::bigint[] as relacl,\n"
-            + "  '{}'::text[] as reloptions,\n"
-            + "  0 as relpartbound\n"
-            + "from information_schema.tables t\n"
-            + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-            + "group by t.table_name, t.table_schema\n"
-            + "union all\n"
-            + "select\n"
-            + "    -1 as oid,\n"
-            + "    i.index_name as relname,\n"
-            + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "    0 as reltype,\n"
-            + "    0 as reloftype,\n"
-            + "    0 as relowner,\n"
-            + "    1 as relam,\n"
-            + "    0 as relfilenode,\n"
-            + "    0 as reltablespace,\n"
-            + "    0 as relpages,\n"
-            + "    0.0::float8 as reltuples,\n"
-            + "    0 as relallvisible,\n"
-            + "    0 as reltoastrelid,\n"
-            + "    false as relhasindex,\n"
-            + "    false as relisshared,\n"
-            + "    'p' as relpersistence,\n"
-            + "    'r' as relkind,\n"
-            + "    count(*) as relnatts,\n"
-            + "    0 as relchecks,\n"
-            + "    false as relhasrules,\n"
-            + "    false as relhastriggers,\n"
-            + "    false as relhassubclass,\n"
-            + "    false as relrowsecurity,\n"
-            + "    false as relforcerowsecurity,\n"
-            + "    true as relispopulated,\n"
-            + "    'n' as relreplident,\n"
-            + "    false as relispartition,\n"
-            + "    0 as relrewrite,\n"
-            + "    0 as relfrozenxid,\n"
-            + "    0 as relminmxid,\n"
-            + "    '{}'::bigint[] as relacl,\n"
-            + "    '{}'::text[] as reloptions,\n"
-            + "    0 as relpartbound\n"
-            + "from information_schema.indexes i\n"
-            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-            + "group by i.index_name, i.table_schema\n"
-            + "),\n"
+        "with "
+            + EMULATED_PG_CLASS_PREFIX
+            + ",\n"
             + "pg_namespace as (\n"
             + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
             + "        schema_name as nspname, null as nspowner, null as nspacl\n"
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2BasicsTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2BasicsTest.java
index 58badf6c..4dad3a6f 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2BasicsTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2BasicsTest.java
@@ -291,83 +291,9 @@ public class SqlAlchemy2BasicsTest extends AbstractMockServerTest {
   @Test
   public void testSimpleMetadata() throws Exception {
     String checkTableExistsSql =
-        "with pg_class as (\n"
-            + "  select\n"
-            + "  -1 as oid,\n"
-            + "  table_name as relname,\n"
-            + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "  0 as reltype,\n"
-            + "  0 as reloftype,\n"
-            + "  0 as relowner,\n"
-            + "  1 as relam,\n"
-            + "  0 as relfilenode,\n"
-            + "  0 as reltablespace,\n"
-            + "  0 as relpages,\n"
-            + "  0.0::float8 as reltuples,\n"
-            + "  0 as relallvisible,\n"
-            + "  0 as reltoastrelid,\n"
-            + "  false as relhasindex,\n"
-            + "  false as relisshared,\n"
-            + "  'p' as relpersistence,\n"
-            + "  'r' as relkind,\n"
-            + "  count(*) as relnatts,\n"
-            + "  0 as relchecks,\n"
-            + "  false as relhasrules,\n"
-            + "  false as relhastriggers,\n"
-            + "  false as relhassubclass,\n"
-            + "  false as relrowsecurity,\n"
-            + "  false as relforcerowsecurity,\n"
-            + "  true as relispopulated,\n"
-            + "  'n' as relreplident,\n"
-            + "  false as relispartition,\n"
-            + "  0 as relrewrite,\n"
-            + "  0 as relfrozenxid,\n"
-            + "  0 as relminmxid,\n"
-            + "  '{}'::bigint[] as relacl,\n"
-            + "  '{}'::text[] as reloptions,\n"
-            + "  0 as relpartbound\n"
-            + "from information_schema.tables t\n"
-            + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-            + "group by t.table_name, t.table_schema\n"
-            + "union all\n"
-            + "select\n"
-            + "    -1 as oid,\n"
-            + "    i.index_name as relname,\n"
-            + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "    0 as reltype,\n"
-            + "    0 as reloftype,\n"
-            + "    0 as relowner,\n"
-            + "    1 as relam,\n"
-            + "    0 as relfilenode,\n"
-            + "    0 as reltablespace,\n"
-            + "    0 as relpages,\n"
-            + "    0.0::float8 as reltuples,\n"
-            + "    0 as relallvisible,\n"
-            + "    0 as reltoastrelid,\n"
-            + "    false as relhasindex,\n"
-            + "    false as relisshared,\n"
-            + "    'p' as relpersistence,\n"
-            + "    'r' as relkind,\n"
-            + "    count(*) as relnatts,\n"
-            + "    0 as relchecks,\n"
-            + "    false as relhasrules,\n"
-            + "    false as relhastriggers,\n"
-            + "    false as relhassubclass,\n"
-            + "    false as relrowsecurity,\n"
-            + "    false as relforcerowsecurity,\n"
-            + "    true as relispopulated,\n"
-            + "    'n' as relreplident,\n"
-            + "    false as relispartition,\n"
-            + "    0 as relrewrite,\n"
-            + "    0 as relfrozenxid,\n"
-            + "    0 as relminmxid,\n"
-            + "    '{}'::bigint[] as relacl,\n"
-            + "    '{}'::text[] as reloptions,\n"
-            + "    0 as relpartbound\n"
-            + "from information_schema.indexes i\n"
-            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-            + "group by i.index_name, i.table_schema\n"
-            + "),\n"
+        "with "
+            + EMULATED_PG_CLASS_PREFIX
+            + ",\n"
             + "pg_namespace as (\n"
             + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
             + "        schema_name as nspname, null as nspowner, null as nspacl\n"
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2SampleTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2SampleTest.java
index db5f191e..fa9dec01 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2SampleTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/python/sqlalchemy2/SqlAlchemy2SampleTest.java
@@ -1075,83 +1075,9 @@ public class SqlAlchemy2SampleTest extends AbstractMockServerTest {
   @Test
   public void testCreateDataModel() throws Exception {
     String checkTableExistsSql =
-        "with pg_class as (\n"
-            + "  select\n"
-            + "  -1 as oid,\n"
-            + "  table_name as relname,\n"
-            + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "  0 as reltype,\n"
-            + "  0 as reloftype,\n"
-            + "  0 as relowner,\n"
-            + "  1 as relam,\n"
-            + "  0 as relfilenode,\n"
-            + "  0 as reltablespace,\n"
-            + "  0 as relpages,\n"
-            + "  0.0::float8 as reltuples,\n"
-            + "  0 as relallvisible,\n"
-            + "  0 as reltoastrelid,\n"
-            + "  false as relhasindex,\n"
-            + "  false as relisshared,\n"
-            + "  'p' as relpersistence,\n"
-            + "  'r' as relkind,\n"
-            + "  count(*) as relnatts,\n"
-            + "  0 as relchecks,\n"
-            + "  false as relhasrules,\n"
-            + "  false as relhastriggers,\n"
-            + "  false as relhassubclass,\n"
-            + "  false as relrowsecurity,\n"
-            + "  false as relforcerowsecurity,\n"
-            + "  true as relispopulated,\n"
-            + "  'n' as relreplident,\n"
-            + "  false as relispartition,\n"
-            + "  0 as relrewrite,\n"
-            + "  0 as relfrozenxid,\n"
-            + "  0 as relminmxid,\n"
-            + "  '{}'::bigint[] as relacl,\n"
-            + "  '{}'::text[] as reloptions,\n"
-            + "  0 as relpartbound\n"
-            + "from information_schema.tables t\n"
-            + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-            + "group by t.table_name, t.table_schema\n"
-            + "union all\n"
-            + "select\n"
-            + "    -1 as oid,\n"
-            + "    i.index_name as relname,\n"
-            + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "    0 as reltype,\n"
-            + "    0 as reloftype,\n"
-            + "    0 as relowner,\n"
-            + "    1 as relam,\n"
-            + "    0 as relfilenode,\n"
-            + "    0 as reltablespace,\n"
-            + "    0 as relpages,\n"
-            + "    0.0::float8 as reltuples,\n"
-            + "    0 as relallvisible,\n"
-            + "    0 as reltoastrelid,\n"
-            + "    false as relhasindex,\n"
-            + "    false as relisshared,\n"
-            + "    'p' as relpersistence,\n"
-            + "    'r' as relkind,\n"
-            + "    count(*) as relnatts,\n"
-            + "    0 as relchecks,\n"
-            + "    false as relhasrules,\n"
-            + "    false as relhastriggers,\n"
-            + "    false as relhassubclass,\n"
-            + "    false as relrowsecurity,\n"
-            + "    false as relforcerowsecurity,\n"
-            + "    true as relispopulated,\n"
-            + "    'n' as relreplident,\n"
-            + "    false as relispartition,\n"
-            + "    0 as relrewrite,\n"
-            + "    0 as relfrozenxid,\n"
-            + "    0 as relminmxid,\n"
-            + "    '{}'::bigint[] as relacl,\n"
-            + "    '{}'::text[] as reloptions,\n"
-            + "    0 as relpartbound\n"
-            + "from information_schema.indexes i\n"
-            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-            + "group by i.index_name, i.table_schema\n"
-            + "),\n"
+        "with "
+            + EMULATED_PG_CLASS_PREFIX
+            + ",\n"
             + "pg_namespace as (\n"
             + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
             + "        schema_name as nspname, null as nspowner, null as nspacl\n"
@@ -1364,83 +1290,9 @@ public class SqlAlchemy2SampleTest extends AbstractMockServerTest {
   @Test
   public void testMetadataReflect() throws Exception {
     String sql =
-        "with pg_class as (\n"
-            + "  select\n"
-            + "  -1 as oid,\n"
-            + "  table_name as relname,\n"
-            + "  case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "  0 as reltype,\n"
-            + "  0 as reloftype,\n"
-            + "  0 as relowner,\n"
-            + "  1 as relam,\n"
-            + "  0 as relfilenode,\n"
-            + "  0 as reltablespace,\n"
-            + "  0 as relpages,\n"
-            + "  0.0::float8 as reltuples,\n"
-            + "  0 as relallvisible,\n"
-            + "  0 as reltoastrelid,\n"
-            + "  false as relhasindex,\n"
-            + "  false as relisshared,\n"
-            + "  'p' as relpersistence,\n"
-            + "  'r' as relkind,\n"
-            + "  count(*) as relnatts,\n"
-            + "  0 as relchecks,\n"
-            + "  false as relhasrules,\n"
-            + "  false as relhastriggers,\n"
-            + "  false as relhassubclass,\n"
-            + "  false as relrowsecurity,\n"
-            + "  false as relforcerowsecurity,\n"
-            + "  true as relispopulated,\n"
-            + "  'n' as relreplident,\n"
-            + "  false as relispartition,\n"
-            + "  0 as relrewrite,\n"
-            + "  0 as relfrozenxid,\n"
-            + "  0 as relminmxid,\n"
-            + "  '{}'::bigint[] as relacl,\n"
-            + "  '{}'::text[] as reloptions,\n"
-            + "  0 as relpartbound\n"
-            + "from information_schema.tables t\n"
-            + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
-            + "group by t.table_name, t.table_schema\n"
-            + "union all\n"
-            + "select\n"
-            + "    -1 as oid,\n"
-            + "    i.index_name as relname,\n"
-            + "    case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
-            + "    0 as reltype,\n"
-            + "    0 as reloftype,\n"
-            + "    0 as relowner,\n"
-            + "    1 as relam,\n"
-            + "    0 as relfilenode,\n"
-            + "    0 as reltablespace,\n"
-            + "    0 as relpages,\n"
-            + "    0.0::float8 as reltuples,\n"
-            + "    0 as relallvisible,\n"
-            + "    0 as reltoastrelid,\n"
-            + "    false as relhasindex,\n"
-            + "    false as relisshared,\n"
-            + "    'p' as relpersistence,\n"
-            + "    'r' as relkind,\n"
-            + "    count(*) as relnatts,\n"
-            + "    0 as relchecks,\n"
-            + "    false as relhasrules,\n"
-            + "    false as relhastriggers,\n"
-            + "    false as relhassubclass,\n"
-            + "    false as relrowsecurity,\n"
-            + "    false as relforcerowsecurity,\n"
-            + "    true as relispopulated,\n"
-            + "    'n' as relreplident,\n"
-            + "    false as relispartition,\n"
-            + "    0 as relrewrite,\n"
-            + "    0 as relfrozenxid,\n"
-            + "    0 as relminmxid,\n"
-            + "    '{}'::bigint[] as relacl,\n"
-            + "    '{}'::text[] as reloptions,\n"
-            + "    0 as relpartbound\n"
-            + "from information_schema.indexes i\n"
-            + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
-            + "group by i.index_name, i.table_schema\n"
-            + "),\n"
+        "with "
+            + EMULATED_PG_CLASS_PREFIX
+            + ",\n"
             + "pg_namespace as (\n"
             + "  select case schema_name when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as oid,\n"
             + "        schema_name as nspname, null as nspowner, null as nspacl\n"
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java
index d67eb144..e69b5744 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java
@@ -161,7 +161,7 @@ public class SessionStateTest {
   public void testGetAll() {
     SessionState state = new SessionState(mock(OptionsMetadata.class));
     List<PGSetting> allSettings = state.getAll();
-    assertEquals(358, allSettings.size());
+    assertEquals(359, allSettings.size());
   }
 
   @Test
@@ -199,7 +199,7 @@ public class SessionStateTest {
     state.setLocal("spanner", "custom_local_setting", "value2");
 
     List<PGSetting> allSettings = state.getAll();
-    assertEquals(360, allSettings.size());
+    assertEquals(361, allSettings.size());
 
     PGSetting applicationName =
         allSettings.stream()
@@ -716,6 +716,76 @@ public class SessionStateTest {
     assertFalse(state.isReplacePgCatalogTables());
   }
 
+  @Test
+  public void testIsEmulatePgClassTables_resetVal() {
+    OptionsMetadata optionsMetadata = mock(OptionsMetadata.class);
+    PGSetting setting =
+        new PGSetting(
+            "spanner",
+            "emulate_pg_class_tables",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            /* resetVal = */ "off",
+            null,
+            null,
+            false);
+    SessionState state =
+        new SessionState(
+            ImmutableMap.of("spanner.emulate_pg_class_tables", setting), optionsMetadata);
+    state.set("spanner", "emulate_pg_class_tables", null);
+
+    assertFalse(state.isEmulatePgClassTables());
+  }
+
+  @Test
+  public void testIsEmulatePgClassTables_bootVal() {
+    OptionsMetadata optionsMetadata = mock(OptionsMetadata.class);
+    PGSetting setting =
+        new PGSetting(
+            "spanner",
+            "emulate_pg_class_tables",
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            /* bootVal = */ "off",
+            null,
+            null,
+            null,
+            false);
+    SessionState state =
+        new SessionState(
+            ImmutableMap.of("spanner.emulate_pg_class_tables", setting), optionsMetadata);
+    state.set("spanner", "emulate_pg_class_tables", null);
+
+    assertFalse(state.isEmulatePgClassTables());
+  }
+
+  @Test
+  public void testIsEmulatePgClassTables_NoValue() {
+    OptionsMetadata optionsMetadata = mock(OptionsMetadata.class);
+    SessionState state = new SessionState(ImmutableMap.of(), optionsMetadata);
+    state.getSettings().clear();
+    assertFalse(state.isEmulatePgClassTables());
+  }
+
   @Test
   public void testCopyCommitPriority() {
     SessionState state = new SessionState(ImmutableMap.of(), mock(OptionsMetadata.class));