Repository: GoogleCloudPlatform/pgadapter
Pull request #333 (2022-08-23 09:20:11)

test: add PostgreSQL JDBC 42.4.2 to the list of tested versions
Adds version 42.4.2 to the list of PostgreSQL JDBC versions that are tested with the metadata queries.


Patch:
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParser.java
index 6d6f6941..f5b013fd 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParser.java
@@ -37,7 +37,7 @@ public class BooleanParser extends Parser<Boolean> {
   private static final Set<String> TRUE_VALUES =
       ImmutableSet.of("t", "tr", "tru", "true", "y", "ye", "yes", "on", "1");
   private static final Set<String> FALSE_VALUES =
-      ImmutableSet.of("f", "fa", "fal", "fals", "false", "n", "no", "of", "off");
+      ImmutableSet.of("f", "fa", "fal", "fals", "false", "n", "no", "of", "off", "0");
 
   BooleanParser(ResultSet item, int position) {
     this.item = item.getBoolean(position);
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParser.java
index 69ce0930..c2a77ac9 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParser.java
@@ -57,7 +57,7 @@ public class TimestampParser extends Parser<Timestamp> {
           .parseLenient()
           .parseCaseInsensitive()
           .appendPattern("yyyy-MM-dd HH:mm:ss")
-          .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 9, true)
+          .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
           .appendOffset("+HH:mm", "Z")
           .toFormatter();
 
@@ -73,12 +73,7 @@ public class TimestampParser extends Parser<Timestamp> {
     if (item != null) {
       switch (formatCode) {
         case TEXT:
-          String stringValue = toPGString(new String(item, StandardCharsets.UTF_8));
-          TemporalAccessor temporalAccessor = TIMESTAMP_FORMATTER.parse(stringValue);
-          this.item =
-              Timestamp.ofTimeSecondsAndNanos(
-                  temporalAccessor.getLong(ChronoField.INSTANT_SECONDS),
-                  temporalAccessor.get(ChronoField.NANO_OF_SECOND));
+          this.item = toTimestamp(new String(item, StandardCharsets.UTF_8));
           break;
         case BINARY:
           this.item = toTimestamp(item);
@@ -102,6 +97,15 @@ public class TimestampParser extends Parser<Timestamp> {
     return Timestamp.ofTimeSecondsAndNanos(javaSeconds, javaNanos);
   }
 
+  /** Converts the given string value to a {@link Timestamp}. */
+  public static Timestamp toTimestamp(String value) {
+    String stringValue = toPGString(value);
+    TemporalAccessor temporalAccessor = TIMESTAMP_FORMATTER.parse(stringValue);
+    return Timestamp.ofTimeSecondsAndNanos(
+        temporalAccessor.getLong(ChronoField.INSTANT_SECONDS),
+        temporalAccessor.get(ChronoField.NANO_OF_SECOND));
+  }
+
   /**
    * Checks whether the given text contains a timestamp that can be parsed by PostgreSQL.
    *
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/utils/CopyInParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/utils/CopyInParser.java
index 8a701a30..b3072d7b 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/utils/CopyInParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/utils/CopyInParser.java
@@ -23,7 +23,6 @@ import java.io.PipedOutputStream;
 import java.util.Iterator;
 import javax.annotation.Nullable;
 import org.apache.commons.csv.CSVFormat;
-import org.postgresql.jdbc.TimestampUtils;
 
 /** Common interface for parsers that implement one or more of the PostgreSQL COPY formats. */
 @InternalApi
@@ -51,10 +50,6 @@ public interface CopyInParser {
     }
   }
 
-  static TimestampUtils createDefaultTimestampUtils() {
-    return new TimestampUtils(false, () -> null);
-  }
-
   /** Returns an iterator of COPY records. */
   Iterator<CopyRecord> iterator();
 
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParser.java
index 6db275bf..ba6c19bb 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParser.java
@@ -16,12 +16,14 @@ package com.google.cloud.spanner.pgadapter.utils;
 
 import com.google.cloud.ByteArray;
 import com.google.cloud.Date;
+import com.google.cloud.Timestamp;
 import com.google.cloud.spanner.ErrorCode;
 import com.google.cloud.spanner.SpannerException;
 import com.google.cloud.spanner.SpannerExceptionFactory;
 import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.Value;
 import com.google.cloud.spanner.pgadapter.parsers.BooleanParser;
+import com.google.cloud.spanner.pgadapter.parsers.TimestampParser;
 import com.google.common.collect.Iterators;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -29,7 +31,6 @@ import java.io.PipedInputStream;
 import java.io.PipedOutputStream;
 import java.io.Reader;
 import java.nio.charset.StandardCharsets;
-import java.sql.Timestamp;
 import java.time.format.DateTimeParseException;
 import java.util.Iterator;
 import java.util.logging.Level;
@@ -38,13 +39,11 @@ import org.apache.commons.codec.binary.Hex;
 import org.apache.commons.csv.CSVFormat;
 import org.apache.commons.csv.CSVParser;
 import org.apache.commons.csv.CSVRecord;
-import org.postgresql.jdbc.TimestampUtils;
 
 /** Implementation of {@link CopyInParser} for the TEXT and CSV formats. */
 class CsvCopyParser implements CopyInParser {
   private static final Logger logger = Logger.getLogger(CsvCopyParser.class.getName());
 
-  private final TimestampUtils timestampUtils = CopyInParser.createDefaultTimestampUtils();
   private final CSVFormat format;
   private final PipedOutputStream payload;
   private final int pipeBufferSize;
@@ -64,8 +63,7 @@ class CsvCopyParser implements CopyInParser {
   @Override
   public Iterator<CopyRecord> iterator() {
     return Iterators.transform(
-        parser.iterator(),
-        record -> new CsvCopyRecord(this.timestampUtils, record, this.hasHeader));
+        parser.iterator(), record -> new CsvCopyRecord(record, this.hasHeader));
   }
 
   @Override
@@ -91,12 +89,10 @@ class CsvCopyParser implements CopyInParser {
   }
 
   static class CsvCopyRecord implements CopyRecord {
-    private final TimestampUtils timestampUtils;
     private final CSVRecord record;
     private final boolean hasHeader;
 
-    CsvCopyRecord(TimestampUtils timestampUtils, CSVRecord record, boolean hasHeader) {
-      this.timestampUtils = timestampUtils;
+    CsvCopyRecord(CSVRecord record, boolean hasHeader) {
       this.record = record;
       this.hasHeader = hasHeader;
     }
@@ -114,17 +110,16 @@ class CsvCopyParser implements CopyInParser {
     @Override
     public Value getValue(Type type, String columnName) throws SpannerException {
       String recordValue = record.get(columnName);
-      return getSpannerValue(type, recordValue, this.timestampUtils);
+      return getSpannerValue(type, recordValue);
     }
 
     @Override
     public Value getValue(Type type, int columnIndex) throws SpannerException {
       String recordValue = record.get(columnIndex);
-      return getSpannerValue(type, recordValue, this.timestampUtils);
+      return getSpannerValue(type, recordValue);
     }
 
-    static Value getSpannerValue(Type type, String recordValue, TimestampUtils timestampUtils)
-        throws SpannerException {
+    static Value getSpannerValue(Type type, String recordValue) throws SpannerException {
       try {
         switch (type.getCode()) {
           case STRING:
@@ -152,9 +147,9 @@ class CsvCopyParser implements CopyInParser {
           case DATE:
             return Value.date(recordValue == null ? null : Date.parseDate(recordValue));
           case TIMESTAMP:
-            Timestamp timestamp = timestampUtils.toTimestamp(null, recordValue);
-            return Value.timestamp(
-                timestamp == null ? null : com.google.cloud.Timestamp.of(timestamp));
+            Timestamp timestamp =
+                recordValue == null ? null : TimestampParser.toTimestamp(recordValue);
+            return Value.timestamp(timestamp);
           default:
             SpannerException spannerException =
                 SpannerExceptionFactory.newSpannerException(


Test patch:
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 3d0ffcf3..96c2fe90 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcMetadataTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcMetadataTest.java
@@ -52,11 +52,11 @@ import org.junit.runners.JUnit4;
 public class ITJdbcMetadataTest implements IntegrationTest {
   private static final String[] VERSIONS =
       new String[] {
-        "42.4.1", "42.4.0", "42.3.6", "42.3.5", "42.3.4", "42.3.3", "42.3.2", "42.3.1", "42.3.0",
-        "42.2.25", "42.2.24", "42.2.23", "42.2.22", "42.2.21", "42.2.20", "42.2.19", "42.2.18",
-        "42.2.17", "42.2.16", "42.2.15", "42.2.14", "42.2.13", "42.2.12", "42.2.11", "42.2.10",
-        "42.2.9", "42.2.8", "42.2.7", "42.2.6", "42.2.5", "42.2.4", "42.2.3", "42.2.2", "42.2.1",
-        "42.2.0", "42.1.4", "42.1.3", "42.1.2", "42.1.1", "42.1.0", "42.0.0"
+        "42.4.2", "42.4.1", "42.4.0", "42.3.6", "42.3.5", "42.3.4", "42.3.3", "42.3.2", "42.3.1",
+        "42.3.0", "42.2.25", "42.2.24", "42.2.23", "42.2.22", "42.2.21", "42.2.20", "42.2.19",
+        "42.2.18", "42.2.17", "42.2.16", "42.2.15", "42.2.14", "42.2.13", "42.2.12", "42.2.11",
+        "42.2.10", "42.2.9", "42.2.8", "42.2.7", "42.2.6", "42.2.5", "42.2.4", "42.2.3", "42.2.2",
+        "42.2.1", "42.2.0", "42.1.4", "42.1.3", "42.1.2", "42.1.1", "42.1.0", "42.0.0"
       };
 
   private static final PgAdapterTestEnv testEnv = new PgAdapterTestEnv();
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParserTest.java
index 920da9e5..feb47cfd 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/BooleanParserTest.java
@@ -14,6 +14,7 @@
 
 package com.google.cloud.spanner.pgadapter.parsers;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -61,10 +62,24 @@ public class BooleanParserTest {
     assertThrows(IllegalArgumentException.class, () -> BooleanParser.toBoolean("foo"));
   }
 
+  @Test
+  public void testBinaryParse() {
+    assertArrayEquals(new byte[] {1}, new BooleanParser(Boolean.TRUE).binaryParse());
+    assertArrayEquals(new byte[] {0}, new BooleanParser(Boolean.FALSE).binaryParse());
+    assertNull(new BooleanParser(null).binaryParse());
+  }
+
   @Test
   public void testStringParse() {
     assertEquals("t", new BooleanParser(Boolean.TRUE).stringParse());
     assertEquals("f", new BooleanParser(Boolean.FALSE).stringParse());
     assertNull(new BooleanParser(null).stringParse());
   }
+
+  @Test
+  public void testSpannerParse() {
+    assertEquals("true", new BooleanParser(Boolean.TRUE).spannerParse());
+    assertEquals("false", new BooleanParser(Boolean.FALSE).spannerParse());
+    assertNull(new BooleanParser(null).spannerParse());
+  }
 }
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParserTest.java
index 21235e0a..8a4c7a79 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/TimestampParserTest.java
@@ -15,6 +15,7 @@
 package com.google.cloud.spanner.pgadapter.parsers;
 
 import static com.google.cloud.spanner.pgadapter.parsers.Parser.PG_EPOCH_SECONDS;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
@@ -22,6 +23,8 @@ import static org.junit.Assert.assertThrows;
 import com.google.cloud.Timestamp;
 import com.google.cloud.spanner.ErrorCode;
 import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.pgadapter.parsers.Parser.FormatCode;
+import java.nio.charset.StandardCharsets;
 import java.util.Random;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,6 +49,20 @@ public class TimestampParserTest {
     SpannerException spannerException =
         assertThrows(SpannerException.class, () -> TimestampParser.toTimestamp(new byte[4]));
     assertEquals(ErrorCode.INVALID_ARGUMENT, spannerException.getErrorCode());
+
+    assertArrayEquals(data, new TimestampParser(TimestampParser.toTimestamp(data)).binaryParse());
+    assertNull(new TimestampParser(null).binaryParse());
+  }
+
+  @Test
+  public void testSpannerParse() {
+    assertEquals(
+        "2022-07-08T07:22:59.123456789Z",
+        new TimestampParser(
+                "2022-07-08 07:22:59.123456789+00".getBytes(StandardCharsets.UTF_8),
+                FormatCode.TEXT)
+            .spannerParse());
+    assertNull(new TimestampParser(null).spannerParse());
   }
 
   @Test
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/utils/CopyInParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/utils/CopyInParserTest.java
index 00e96036..5d772087 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/utils/CopyInParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/utils/CopyInParserTest.java
@@ -14,7 +14,6 @@
 
 package com.google.cloud.spanner.pgadapter.utils;
 
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import com.google.cloud.spanner.pgadapter.parsers.copy.CopyTreeParser.CopyOptions.Format;
@@ -50,9 +49,4 @@ public class CopyInParserTest {
         CopyInParser.create(Format.BINARY, null, new PipedOutputStream(), 256, false);
     assertTrue(parser instanceof BinaryCopyParser);
   }
-
-  @Test
-  public void testCreateTimestampUtils() {
-    assertNotNull(CopyInParser.createDefaultTimestampUtils());
-  }
 }
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParserTest.java
index f6290d69..18876d5c 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/utils/CsvCopyParserTest.java
@@ -14,12 +14,17 @@
 
 package com.google.cloud.spanner.pgadapter.utils;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 
+import com.google.cloud.ByteArray;
+import com.google.cloud.Date;
+import com.google.cloud.Timestamp;
 import com.google.cloud.spanner.SpannerException;
 import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.Type.StructField;
+import com.google.cloud.spanner.Value;
 import com.google.cloud.spanner.pgadapter.utils.CsvCopyParser.CsvCopyRecord;
 import java.io.DataOutputStream;
 import java.io.IOException;
@@ -66,49 +71,151 @@ public class CsvCopyParserTest {
     parser.close();
   }
 
+  @Test
+  public void testGetSpannerValueBool() {
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "t"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "tr"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "tru"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "true"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "1"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "on"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "y"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "ye"));
+    assertEquals(Value.bool(true), CsvCopyRecord.getSpannerValue(Type.bool(), "yes"));
+
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "f"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "fa"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "fal"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "fals"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "false"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "0"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "off"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "of"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "n"));
+    assertEquals(Value.bool(false), CsvCopyRecord.getSpannerValue(Type.bool(), "no"));
+
+    assertEquals(Value.bool(null), CsvCopyRecord.getSpannerValue(Type.bool(), null));
+  }
+
+  @Test
+  public void testGetSpannerValueBytes() {
+    assertEquals(
+        Value.bytes(ByteArray.copyFrom("test")),
+        CsvCopyRecord.getSpannerValue(Type.bytes(), "\\x74657374"));
+    assertEquals(
+        Value.bytes(ByteArray.copyFrom("")), CsvCopyRecord.getSpannerValue(Type.bytes(), "\\x"));
+    assertEquals(Value.bytes(null), CsvCopyRecord.getSpannerValue(Type.bytes(), null));
+  }
+
+  @Test
+  public void testGetSpannerValueInt64() {
+    assertEquals(Value.int64(-1L), CsvCopyRecord.getSpannerValue(Type.int64(), "-1"));
+    assertEquals(Value.int64(1L), CsvCopyRecord.getSpannerValue(Type.int64(), "1"));
+    assertEquals(Value.int64(0L), CsvCopyRecord.getSpannerValue(Type.int64(), "0"));
+    assertEquals(
+        Value.int64(Long.MAX_VALUE),
+        CsvCopyRecord.getSpannerValue(Type.int64(), "9223372036854775807"));
+    assertEquals(
+        Value.int64(Long.MIN_VALUE),
+        CsvCopyRecord.getSpannerValue(Type.int64(), "-9223372036854775808"));
+    assertEquals(Value.int64(null), CsvCopyRecord.getSpannerValue(Type.int64(), null));
+  }
+
+  @Test
+  public void testGetSpannerValueFloat64() {
+    assertEquals(Value.float64(-1.0D), CsvCopyRecord.getSpannerValue(Type.float64(), "-1.0"));
+    assertEquals(Value.float64(0.0D), CsvCopyRecord.getSpannerValue(Type.float64(), "0.0"));
+    assertEquals(Value.float64(1.0D), CsvCopyRecord.getSpannerValue(Type.float64(), "1.0"));
+    assertEquals(Value.float64(null), CsvCopyRecord.getSpannerValue(Type.float64(), null));
+  }
+
+  @Test
+  public void testGetSpannerValueNumeric() {
+    assertEquals(Value.pgNumeric("-1.0"), CsvCopyRecord.getSpannerValue(Type.pgNumeric(), "-1.0"));
+    assertEquals(Value.pgNumeric("0.0"), CsvCopyRecord.getSpannerValue(Type.pgNumeric(), "0.0"));
+    assertEquals(Value.pgNumeric("1.0"), CsvCopyRecord.getSpannerValue(Type.pgNumeric(), "1.0"));
+    assertEquals(Value.pgNumeric(null), CsvCopyRecord.getSpannerValue(Type.pgNumeric(), null));
+  }
+
+  @Test
+  public void testGetSpannerValueString() {
+    assertEquals(Value.string("test"), CsvCopyRecord.getSpannerValue(Type.string(), "test"));
+    assertEquals(Value.string(""), CsvCopyRecord.getSpannerValue(Type.string(), ""));
+    assertEquals(Value.string(null), CsvCopyRecord.getSpannerValue(Type.string(), null));
+  }
+
+  @Test
+  public void testGetSpannerValueDate() {
+    assertEquals(
+        Value.date(Date.parseDate("2022-08-17")),
+        CsvCopyRecord.getSpannerValue(Type.date(), "2022-08-17"));
+    assertEquals(Value.date(null), CsvCopyRecord.getSpannerValue(Type.date(), null));
+  }
+
+  @Test
+  public void testGetSpannerValueTimestamp() {
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2093-08-02T14:53:40.481913Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2093-08-02T14:53:40.481913+00"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17T10:11:12.123456789Z"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17 10:11:12.123456789Z"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17 10:11:12.123456789+00"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17 10:11:12.123456789+00:00"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17T10:11:12.123456789+00"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17T10:11:12.123456789+00:00"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17 12:11:12.123456789+02"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17T12:11:12.123456789+02"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17 08:11:12.123456789-02"));
+    assertEquals(
+        Value.timestamp(Timestamp.parseTimestamp("2022-08-17T10:11:12.123456789Z")),
+        CsvCopyRecord.getSpannerValue(Type.timestamp(), "2022-08-17T08:11:12.123456789-02"));
+    assertEquals(Value.date(null), CsvCopyRecord.getSpannerValue(Type.date(), null));
+  }
+
   @Test
   public void testGetSpannerValue_InvalidBytesValue() {
     assertThrows(
-        SpannerException.class,
-        () ->
-            CsvCopyRecord.getSpannerValue(
-                Type.bytes(), "value", CopyInParser.createDefaultTimestampUtils()));
+        SpannerException.class, () -> CsvCopyRecord.getSpannerValue(Type.bytes(), "value"));
   }
 
   @Test
   public void testGetSpannerValue_InvalidNumberValue() {
     assertThrows(
-        SpannerException.class,
-        () ->
-            CsvCopyRecord.getSpannerValue(
-                Type.int64(), "value", CopyInParser.createDefaultTimestampUtils()));
+        SpannerException.class, () -> CsvCopyRecord.getSpannerValue(Type.int64(), "value"));
   }
 
   @Test
   public void testGetSpannerValue_InvalidBoolValue() {
-    assertThrows(
-        SpannerException.class,
-        () ->
-            CsvCopyRecord.getSpannerValue(
-                Type.bool(), "value", CopyInParser.createDefaultTimestampUtils()));
+    assertThrows(SpannerException.class, () -> CsvCopyRecord.getSpannerValue(Type.bool(), "value"));
   }
 
   @Test
   public void testGetSpannerValue_InvalidDateValue() {
-    assertThrows(
-        SpannerException.class,
-        () ->
-            CsvCopyRecord.getSpannerValue(
-                Type.date(), "value", CopyInParser.createDefaultTimestampUtils()));
+    assertThrows(SpannerException.class, () -> CsvCopyRecord.getSpannerValue(Type.date(), "value"));
   }
 
   @Test
   public void testGetSpannerValue_InvalidTimestampValue() {
     assertThrows(
-        SpannerException.class,
-        () ->
-            CsvCopyRecord.getSpannerValue(
-                Type.timestamp(), "value", CopyInParser.createDefaultTimestampUtils()));
+        SpannerException.class, () -> CsvCopyRecord.getSpannerValue(Type.timestamp(), "value"));
   }
 
   @Test
@@ -117,8 +224,6 @@ public class CsvCopyParserTest {
         SpannerException.class,
         () ->
             CsvCopyRecord.getSpannerValue(
-                Type.struct(StructField.of("f1", Type.string())),
-                "value",
-                CopyInParser.createDefaultTimestampUtils()));
+                Type.struct(StructField.of("f1", Type.string())), "value"));
   }
 }
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/utils/MutationWriterTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/utils/MutationWriterTest.java
index bb98626b..e6512c23 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/utils/MutationWriterTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/utils/MutationWriterTest.java
@@ -14,7 +14,6 @@
 
 package com.google.cloud.spanner.pgadapter.utils;
 
-import static com.google.cloud.spanner.pgadapter.utils.CopyInParser.createDefaultTimestampUtils;
 import static com.google.cloud.spanner.pgadapter.utils.MutationWriter.calculateSize;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -538,9 +537,7 @@ public class MutationWriterTest {
               new StringReader("col\n\\N\n"), CSVFormat.POSTGRESQL_TEXT.withFirstRecordAsHeader());
       CSVRecord record = parser.getRecords().get(0);
 
-      Mutation mutation =
-          mutationWriter.buildMutation(
-              new CsvCopyRecord(createDefaultTimestampUtils(), record, true));
+      Mutation mutation = mutationWriter.buildMutation(new CsvCopyRecord(record, true));
 
       assertEquals(String.format("Type: %s", type), 1, mutation.asMap().size());
     }