Repository: GoogleCloudPlatform/pgadapter
Pull request #691 (2023-02-21 14:55:36)

fix: timestamp arrays should use timestamptz_array OID
Timestamp arrays were returned to the client with the OID of _timestamp (timestamp[]), instead of _timestamptz (timestamptz[]). This meant that a client might not interpret the received value correctly.

codecov[bot]: # [Codecov](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/691?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) Report
> Merging [#691](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/691?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) (db142c0) into [postgresql-dialect](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/commit/ad74d74e17b23f715c17b6d8f6c39d693f2991a5?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) (ad74d74) will **increase** coverage by `0.05%`.
> The diff coverage is `100.00%`.

```diff
@@                   Coverage Diff                    @@
##             postgresql-dialect     #691      +/-   ##
========================================================
+ Coverage                 89.19%   89.24%   +0.05%     
- Complexity                 2271     2273       +2     
========================================================
  Files                       126      126              
  Lines                      7495     7495              
  Branches                   1068     1068              
========================================================
+ Hits                       6685     6689       +4     
+ Misses                      561      559       -2     
+ Partials                    249      247       -2     
```

| Flag | Coverage Δ | |
|---|---|---|
| all_tests | `89.24% <100.00%> (+0.05%)` | :arrow_up: |

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

| [Impacted Files](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/691?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) | Coverage Δ | |
|---|---|---|
| [...google/cloud/spanner/pgadapter/parsers/Parser.java](https://codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/691?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3BhcnNlcnMvUGFyc2VyLmphdmE=) | `93.89% <100.00%> (+3.05%)` | :arrow_up: |

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


Patch:
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
index a6c504fe..1021f125 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
@@ -15,9 +15,7 @@
 package com.google.cloud.spanner.pgadapter.parsers;
 
 import com.google.api.core.InternalApi;
-import com.google.cloud.spanner.ErrorCode;
 import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerExceptionFactory;
 import com.google.cloud.spanner.Statement;
 import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.Type.Code;
@@ -279,7 +277,7 @@ public abstract class Parser<T> {
           case BYTES:
             return Oid.BYTEA_ARRAY;
           case TIMESTAMP:
-            return Oid.TIMESTAMP_ARRAY;
+            return Oid.TIMESTAMPTZ_ARRAY;
           case DATE:
             return Oid.DATE_ARRAY;
           case NUMERIC:
@@ -287,15 +285,15 @@ public abstract class Parser<T> {
           case ARRAY:
           case STRUCT:
           default:
-            throw SpannerExceptionFactory.newSpannerException(
-                ErrorCode.INVALID_ARGUMENT, "Unsupported or unknown array type: " + type);
+            throw PGExceptionFactory.newPGException(
+                "Unsupported or unknown array type: " + type, SQLState.InternalError);
         }
       case NUMERIC:
       case JSON:
       case STRUCT:
       default:
-        throw SpannerExceptionFactory.newSpannerException(
-            ErrorCode.INVALID_ARGUMENT, "Unsupported or unknown type: " + type);
+        throw PGExceptionFactory.newPGException(
+            "Unsupported or unknown type: " + type, SQLState.InternalError);
     }
   }
 
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 1d5dce7a..825039b4 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
@@ -56,6 +56,16 @@ public class PgCatalog {
           .put(new TableOrIndexName(null, "pg_range"), new TableOrIndexName(null, "pg_range"))
           .put(new TableOrIndexName("pg_catalog", "pg_type"), new TableOrIndexName(null, "pg_type"))
           .put(new TableOrIndexName(null, "pg_type"), new TableOrIndexName(null, "pg_type"))
+          .put(
+              new TableOrIndexName("pg_catalog", "pg_sequence"),
+              new TableOrIndexName(null, "pg_sequence"))
+          .put(new TableOrIndexName(null, "pg_sequence"), new TableOrIndexName(null, "pg_sequence"))
+          .put(
+              new TableOrIndexName("pg_catalog", "pg_sequences"),
+              new TableOrIndexName(null, "pg_sequences"))
+          .put(
+              new TableOrIndexName(null, "pg_sequences"),
+              new TableOrIndexName(null, "pg_sequences"))
           .put(
               new TableOrIndexName("pg_catalog", "pg_settings"),
               new TableOrIndexName(null, "pg_settings"))
@@ -83,7 +93,9 @@ public class PgCatalog {
           new TableOrIndexName(null, "pg_class"), new PgClass(),
           new TableOrIndexName(null, "pg_proc"), new PgProc(),
           new TableOrIndexName(null, "pg_range"), new PgRange(),
-          new TableOrIndexName(null, "pg_type"), new PgType());
+          new TableOrIndexName(null, "pg_type"), new PgType(),
+          new TableOrIndexName(null, "pg_sequence"), new PgSequence(),
+          new TableOrIndexName(null, "pg_sequences"), new PgSequences());
   private final SessionState sessionState;
 
   public PgCatalog(@Nonnull SessionState sessionState, @Nonnull WellKnownClient wellKnownClient) {
@@ -472,4 +484,34 @@ public class PgCatalog {
       return PG_ENUM_CTE;
     }
   }
+
+  private static class PgSequences implements PgCatalogTable {
+    private static final String PG_SEQUENCES_CTE =
+        "pg_sequences as (\n"
+            + "select * from ("
+            + "select ''::varchar as schemaname, ''::varchar as sequencename, ''::varchar as sequenceowner, "
+            + "0::bigint as data_type, 0::bigint as start_value, 0::bigint as min_value, 0::bigint as max_value, "
+            + "0::bigint as increment_by, false::bool as cycle, 0::bigint as cache_size, 0::bigint as last_value\n"
+            + ") seq where false)";
+
+    @Override
+    public String getTableExpression() {
+      return PG_SEQUENCES_CTE;
+    }
+  }
+
+  private static class PgSequence implements PgCatalogTable {
+    private static final String PG_SEQUENCE_CTE =
+        "pg_sequence as (\n"
+            + "select * from ("
+            + "select 0::bigint as seqrelid, 0::bigint as seqtypid, 0::bigint as seqstart, "
+            + "0::bigint as seqincrement, 0::bigint as seqmax, 0::bigint as seqmin, 0::bigint as seqcache, "
+            + "false::bool as seqcycle\n"
+            + ") seq where false)";
+
+    @Override
+    public String getTableExpression() {
+      return PG_SEQUENCE_CTE;
+    }
+  }
 }


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcTest.java
index 63cd6a00..06e6f51b 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcTest.java
@@ -17,6 +17,7 @@ package com.google.cloud.spanner.pgadapter;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
@@ -206,6 +207,23 @@ public class ITJdbcTest implements IntegrationTest {
     }
   }
 
+  @Test
+  public void testPgCatalogViews() throws SQLException {
+    for (String view : new String[] {"pg_sequence", "pg_sequences"}) {
+      for (String prefix : new String[] {"", "pg_catalog."}) {
+        try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
+          try (ResultSet resultSet =
+              connection.createStatement().executeQuery("SELECT * FROM " + prefix + view)) {
+            while (resultSet.next()) {
+              assertNotNull(resultSet.getMetaData());
+              assertNotEquals(0, resultSet.getMetaData().getColumnCount());
+            }
+          }
+        }
+      }
+    }
+  }
+
   @Test
   public void testCreateTableIfNotExists() throws SQLException {
     try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java
index 846bb4c0..33b2e9bb 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java
@@ -713,6 +713,32 @@ public class ParserTest {
     assertEquals(Oid.UNSPECIFIED, getArrayElementOid(Integer.MAX_VALUE));
   }
 
+  @Test
+  public void testToOid() {
+    assertEquals(Oid.BOOL, toOid(Type.bool()));
+    assertEquals(Oid.BYTEA, toOid(Type.bytes()));
+    assertEquals(Oid.DATE, toOid(Type.date()));
+    assertEquals(Oid.FLOAT8, toOid(Type.float64()));
+    assertEquals(Oid.INT8, toOid(Type.int64()));
+    assertEquals(Oid.JSONB, toOid(Type.pgJsonb()));
+    assertEquals(Oid.NUMERIC, toOid(Type.pgNumeric()));
+    assertEquals(Oid.VARCHAR, toOid(Type.string()));
+    assertEquals(Oid.TIMESTAMPTZ, toOid(Type.timestamp()));
+
+    assertEquals(Oid.BOOL_ARRAY, toOid(Type.array(Type.bool())));
+    assertEquals(Oid.BYTEA_ARRAY, toOid(Type.array(Type.bytes())));
+    assertEquals(Oid.DATE_ARRAY, toOid(Type.array(Type.date())));
+    assertEquals(Oid.FLOAT8_ARRAY, toOid(Type.array(Type.float64())));
+    assertEquals(Oid.INT8_ARRAY, toOid(Type.array(Type.int64())));
+    assertEquals(Oid.JSONB_ARRAY, toOid(Type.array(Type.pgJsonb())));
+    assertEquals(Oid.NUMERIC_ARRAY, toOid(Type.array(Type.pgNumeric())));
+    assertEquals(Oid.VARCHAR_ARRAY, toOid(Type.array(Type.string())));
+    assertEquals(Oid.TIMESTAMPTZ_ARRAY, toOid(Type.array(Type.timestamp())));
+
+    assertThrows(PGException.class, () -> toOid(Type.array(Type.struct())));
+    assertThrows(PGException.class, () -> toOid(Type.struct()));
+  }
+
   static com.google.spanner.v1.Type createType(TypeCode code) {
     return com.google.spanner.v1.Type.newBuilder().setCode(code).build();
   }