Repository: GoogleCloudPlatform/pgadapter
Pull request #60 (2022-03-10 10:15:11)

fix: remove all GSQL headers
The `/*GSQL*/` header will soon be unsupported and should be removed from
all clients.


Patch:
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java
index 4d5889f5..2ad32f46 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/utils/StatementParser.java
@@ -17,234 +17,6 @@ package com.google.cloud.spanner.pgadapter.utils;
 import com.google.common.base.Preconditions;
 
 public class StatementParser {
-  private static final String GSQL_STATEMENT = "/*GSQL*/";
-  private static final char SINGLE_QUOTE = '\'';
-  private static final char DOUBLE_QUOTE = '"';
-  private static final char BACKTICK_QUOTE = '`';
-  private static final char HYPHEN = '-';
-  private static final char DASH = '#';
-  private static final char SLASH = '/';
-  private static final char BACK_SLASH = '\\';
-  private static final char ASTERISK = '*';
-  private static final char DOLLAR = '$';
-  private static final char SEMICOLON = ';';
-  /**
-   * Removes comments from and trims the given sql statement. Spanner supports three types of
-   * comments:
-   *
-   * <ul>
-   *   <li>Single line comments starting with '--'
-   *   <li>Single line comments starting with '#'
-   *   <li>Multi line comments between '/&#42;' and '&#42;/'
-   * </ul>
-   *
-   * Reference: https://cloud.google.com/spanner/docs/lexical#comments
-   *
-   * @param sql The sql statement to remove comments from and to trim.
-   * @return the sql statement without the comments and leading and trailing spaces.
-   */
-  public static String removeCommentsAndTrim(String sql) {
-    if (sql.startsWith(GSQL_STATEMENT)) {
-      return removeCommentsAndTrimGSQL(sql);
-    }
-    return removeCommentsAndTrimPostgres(sql);
-  }
-
-  private static String removeCommentsAndTrimPostgres(String sql) {
-    Preconditions.checkNotNull(sql);
-    String currentTag = null;
-    boolean isInQuoted = false;
-    boolean isInSingleLineComment = false;
-    int multiLineCommentLevel = 0;
-    char startQuote = 0;
-    boolean lastCharWasEscapeChar = false;
-    StringBuilder res = new StringBuilder(sql.length());
-    int index = 0;
-    while (index < sql.length()) {
-      char c = sql.charAt(index);
-      if (isInQuoted) {
-        if ((c == '\n' || c == '\r') && startQuote != DOLLAR) {
-          throw new IllegalArgumentException("SQL statement contains an unclosed literal: " + sql);
-        } else if (c == startQuote) {
-          if (c == DOLLAR) {
-            // Check if this is the end of the current dollar quoted string.
-            String tag = parseDollarQuotedString(sql, index + 1);
-            if (tag != null && tag.equals(currentTag)) {
-              index += tag.length() + 1;
-              res.append(c);
-              res.append(tag);
-              isInQuoted = false;
-              startQuote = 0;
-            }
-          } else if (lastCharWasEscapeChar) {
-            lastCharWasEscapeChar = false;
-          } else {
-            isInQuoted = false;
-            startQuote = 0;
-          }
-        } else if (c == BACK_SLASH) {
-          lastCharWasEscapeChar = true;
-        } else {
-          lastCharWasEscapeChar = false;
-        }
-        res.append(c);
-      } else {
-        // We are not in a quoted string.
-        if (isInSingleLineComment) {
-          if (c == '\n') {
-            isInSingleLineComment = false;
-            // Include the line feed in the result.
-            res.append(c);
-          }
-        } else if (multiLineCommentLevel > 0) {
-          if (sql.length() > index + 1 && c == ASTERISK && sql.charAt(index + 1) == SLASH) {
-            multiLineCommentLevel--;
-            index++;
-          } else if (sql.length() > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERISK) {
-            multiLineCommentLevel++;
-            index++;
-          }
-        } else {
-          // Check for -- which indicates the start of a single-line comment.
-          if (sql.length() > index + 1 && c == HYPHEN && sql.charAt(index + 1) == HYPHEN) {
-            // This is a single line comment.
-            isInSingleLineComment = true;
-          } else if (sql.length() > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERISK) {
-            multiLineCommentLevel++;
-            index++;
-          } else {
-            if (c == SINGLE_QUOTE || c == DOUBLE_QUOTE) {
-              isInQuoted = true;
-              startQuote = c;
-            } else if (c == DOLLAR
-                && sql.length() > index + 1
-                && !Character.isDigit(sql.charAt(index + 1))) {
-              currentTag = parseDollarQuotedString(sql, index + 1);
-              if (currentTag != null) {
-                isInQuoted = true;
-                startQuote = DOLLAR;
-                index += currentTag.length() + 1;
-                res.append(c);
-                res.append(currentTag);
-              }
-            }
-            res.append(c);
-          }
-        }
-      }
-      index++;
-    }
-    if (isInQuoted) {
-      throw new IllegalArgumentException("SQL statement contains an unclosed literal: " + sql);
-    }
-    if (multiLineCommentLevel > 0) {
-      throw new IllegalArgumentException(
-          "SQL statement contains an unterminated block comment: " + sql);
-    }
-    return res.toString().trim();
-  }
-
-  static String parseDollarQuotedString(String sql, int index) {
-    // Look ahead to the next dollar sign (if any). Everything in between is the quote tag.
-    final char DOLLAR = '$';
-    StringBuilder tag = new StringBuilder();
-    while (index < sql.length()) {
-      char c = sql.charAt(index);
-      if (c == DOLLAR) {
-        return tag.toString();
-      }
-      if (!Character.isJavaIdentifierPart(c)) {
-        break;
-      }
-      tag.append(c);
-      index++;
-    }
-    return null;
-  }
-
-  private static String removeCommentsAndTrimGSQL(String sql) {
-    Preconditions.checkNotNull(sql);
-    boolean isInQuoted = false;
-    boolean isInSingleLineComment = false;
-    boolean isInMultiLineComment = false;
-    char startQuote = 0;
-    boolean lastCharWasEscapeChar = false;
-    boolean isTripleQuoted = false;
-    StringBuilder res = new StringBuilder(sql.length());
-    int index = 0;
-    while (index < sql.length()) {
-      char c = sql.charAt(index);
-      if (isInQuoted) {
-        if ((c == '\n' || c == '\r') && !isTripleQuoted) {
-          throw new IllegalArgumentException("SQL statement contains an unclosed literal: " + sql);
-        } else if (c == startQuote) {
-          if (lastCharWasEscapeChar) {
-            lastCharWasEscapeChar = false;
-          } else if (isTripleQuoted) {
-            if (sql.length() > index + 2
-                && sql.charAt(index + 1) == startQuote
-                && sql.charAt(index + 2) == startQuote) {
-              isInQuoted = false;
-              startQuote = 0;
-              isTripleQuoted = false;
-              res.append(c).append(c);
-              index += 2;
-            }
-          } else {
-            isInQuoted = false;
-            startQuote = 0;
-          }
-        } else if (c == BACK_SLASH) {
-          lastCharWasEscapeChar = true;
-        } else {
-          lastCharWasEscapeChar = false;
-        }
-        res.append(c);
-      } else {
-        // We are not in a quoted string.
-        if (isInSingleLineComment) {
-          if (c == '\n') {
-            isInSingleLineComment = false;
-            // Include the line feed in the result.
-            res.append(c);
-          }
-        } else if (isInMultiLineComment) {
-          if (sql.length() > index + 1 && c == ASTERISK && sql.charAt(index + 1) == SLASH) {
-            isInMultiLineComment = false;
-            index++;
-          }
-        } else {
-          if (c == DASH
-              || (sql.length() > index + 1 && c == HYPHEN && sql.charAt(index + 1) == HYPHEN)) {
-            // This is a single line comment.
-            isInSingleLineComment = true;
-          } else if (sql.length() > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERISK) {
-            isInMultiLineComment = true;
-            index++;
-          } else {
-            if (c == SINGLE_QUOTE || c == DOUBLE_QUOTE || c == BACKTICK_QUOTE) {
-              isInQuoted = true;
-              startQuote = c;
-              // Check whether it is a triple-quote.
-              if (sql.length() > index + 2
-                  && sql.charAt(index + 1) == startQuote
-                  && sql.charAt(index + 2) == startQuote) {
-                isTripleQuoted = true;
-                res.append(c).append(c);
-                index += 2;
-              }
-            }
-            res.append(c);
-          }
-        }
-      }
-      index++;
-    }
-    if (isInQuoted) {
-      throw new IllegalArgumentException("SQL statement contains an unclosed literal: " + sql);
-    }
-    return GSQL_STATEMENT + res.toString().trim();
-  }
 
   /**
    * Simple method to escape SQL. Ultimately it is preferred that a user uses PreparedStatements but
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
index bd13853c..7347d2ba 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
@@ -14,9 +14,10 @@
 
 package com.google.cloud.spanner.pgadapter.wireprotocol;
 
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.connection.AbstractStatementParser;
 import com.google.cloud.spanner.pgadapter.ConnectionHandler;
 import com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement;
-import com.google.cloud.spanner.pgadapter.utils.StatementParser;
 import com.google.cloud.spanner.pgadapter.wireoutput.ParseCompleteResponse;
 import com.google.common.base.Strings;
 import java.text.MessageFormat;
@@ -25,7 +26,8 @@ import java.util.List;
 
 /** Creates a prepared statement. */
 public class ParseMessage extends ControlMessage {
-
+  private static final AbstractStatementParser PARSER =
+      AbstractStatementParser.getInstance(Dialect.POSTGRESQL);
   protected static final char IDENTIFIER = 'P';
 
   private String name;
@@ -35,7 +37,7 @@ public class ParseMessage extends ControlMessage {
   public ParseMessage(ConnectionHandler connection) throws Exception {
     super(connection);
     this.name = this.readString();
-    String queryString = StatementParser.removeCommentsAndTrim(this.readString());
+    String queryString = PARSER.removeCommentsAndTrim(this.readString());
     this.parameterDataTypes = new ArrayList<>();
     short numberOfParameters = this.inputStream.readShort();
     for (int i = 0; i < numberOfParameters; i++) {
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java
index f5ca637f..075d9961 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/QueryMessage.java
@@ -14,6 +14,8 @@
 
 package com.google.cloud.spanner.pgadapter.wireprotocol;
 
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.connection.AbstractStatementParser;
 import com.google.cloud.spanner.pgadapter.ConnectionHandler;
 import com.google.cloud.spanner.pgadapter.ConnectionHandler.ConnectionStatus;
 import com.google.cloud.spanner.pgadapter.ConnectionHandler.QueryMode;
@@ -29,7 +31,8 @@ import java.text.MessageFormat;
 
 /** Executes a simple statement. */
 public class QueryMessage extends ControlMessage {
-
+  private static final AbstractStatementParser PARSER =
+      AbstractStatementParser.getInstance(Dialect.POSTGRESQL);
   protected static final char IDENTIFIER = 'Q';
   protected static final String COPY = "COPY";
 
@@ -37,7 +40,7 @@ public class QueryMessage extends ControlMessage {
 
   public QueryMessage(ConnectionHandler connection) throws Exception {
     super(connection);
-    String query = StatementParser.removeCommentsAndTrim(this.readAll());
+    String query = PARSER.removeCommentsAndTrim(this.readAll());
     String command = StatementParser.parseCommand(query);
     if (COPY.equalsIgnoreCase(command)) {
       this.statement = new CopyStatement(query, this.connection.getSpannerConnection());
diff --git a/src/main/resources/metadata/default_command_metadata.json b/src/main/resources/metadata/default_command_metadata.json
index 4ef94f7c..2064dfb5 100644
--- a/src/main/resources/metadata/default_command_metadata.json
+++ b/src/main/resources/metadata/default_command_metadata.json
@@ -1,13 +1,13 @@
 {
   "commands": [
     {
-      "input_pattern": "SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?,'v','m','S','f',''\\)[ \n]+AND n\\.nspname <> 'pg_catalog'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?n\\.nspname !~ '\\^pg_toast'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
+      "input_pattern": "SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?,'v','m','S','f',''\\)[ \n]+AND n\\.nspname <> 'pg_catalog'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?n\\.nspname !~ '\\^pg_toast'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;?$",
       "output_pattern": "SELECT t.table_schema as \"Schema\", t.table_name as \"Name\", 'table' as \"Type\", 'me' as \"Owner\" FROM information_schema.tables AS t WHERE t.table_schema = 'public';",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\d meta-command."
     },
     {
-      "input_pattern": "^SELECT c\\.oid,[ \n]+n\\.nspname,[ \n]+c\\.relname\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<tablename>.*)\\)\\$'[ \n]+AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 2, 3;$",
+      "input_pattern": "^SELECT c\\.oid,[ \n]+n\\.nspname,[ \n]+c\\.relname\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<tablename>.*)\\)\\$'[ \n]+AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 2, 3;?$",
       "output_pattern": "SELECT t.table_name as oid, 'public' as nspname, t.table_name as relname FROM information_schema.tables AS t WHERE t.table_schema='public' AND LOWER(t.table_name) = LOWER('%s');",
       "matcher_array": [
         "tablename"
@@ -15,13 +15,13 @@
       "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the first. Here we just fetch basic metadata used to fetch the table schema in earnest in the next step."
     },
     {
-      "input_pattern": "^SELECT relchecks, relkind, relhasindex, relhasrules, reltriggers <> 0, false, false, relhasoids, (?:false as relispartition, )?'', ''\nFROM pg_catalog\\.pg_class WHERE oid = '.*';$",
+      "input_pattern": "^SELECT relchecks, relkind, relhasindex, relhasrules, reltriggers <> 0, false, false, relhasoids, (?:false as relispartition, )?'', ''\nFROM pg_catalog\\.pg_class WHERE oid = '.*';?$",
       "output_pattern": "SELECT 0 as relcheck, 'r' as relkind, false as relhasindex, false as relhasrules, false as reltriggers, false as bool1, false as bool2, false as relhasoids, '' as str1, '' as str2;",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the second. Here normally would fetch catalogue metadata, but due to Spanner structure, we expect it to be static, and thus just hardcode it."
     },
     {
-      "input_pattern": "^SELECT a\\.attname,[ \n]+pg_catalog\\.format_type\\(a\\.atttypid, a\\.atttypmod\\),[ \n]+\\(SELECT (substring\\()?pg_catalog\\.pg_get_expr\\(d\\.adbin, d\\.adrelid(?:, true)?\\)( for 128\\))?[ \n]+FROM pg_catalog\\.pg_attrdef d[ \n]+WHERE d\\.adrelid = a\\.attrelid AND d\\.adnum = a\\.attnum AND a\\.atthasdef\\),[ \n]+a\\.attnotnull,(?: a\\.attnum,)?[ \n]+NULL AS attcollation,[ \n]+(?:''::pg_catalog\\.char AS attidentity,[ \n]+''::pg_catalog\\.char AS attgenerated\n|  NULL AS indexdef,[ \n]+NULL AS attfdwoptions\n)?([ ]*''::pg_catalog.char AS attidentity)?[ \n]*FROM pg_catalog\\.pg_attribute a\nWHERE a\\.attrelid = '(?<tablename>.*)' AND a\\.attnum > 0 AND NOT a\\.attisdropped\nORDER BY a\\.attnum;$",
+      "input_pattern": "^SELECT a\\.attname,[ \n]+pg_catalog\\.format_type\\(a\\.atttypid, a\\.atttypmod\\),[ \n]+\\(SELECT (substring\\()?pg_catalog\\.pg_get_expr\\(d\\.adbin, d\\.adrelid(?:, true)?\\)( for 128\\))?[ \n]+FROM pg_catalog\\.pg_attrdef d[ \n]+WHERE d\\.adrelid = a\\.attrelid AND d\\.adnum = a\\.attnum AND a\\.atthasdef\\),[ \n]+a\\.attnotnull,(?: a\\.attnum,)?[ \n]+NULL AS attcollation,[ \n]+(?:''::pg_catalog\\.char AS attidentity,[ \n]+''::pg_catalog\\.char AS attgenerated\n|  NULL AS indexdef,[ \n]+NULL AS attfdwoptions\n)?([ ]*''::pg_catalog.char AS attidentity)?[ \n]*FROM pg_catalog\\.pg_attribute a\nWHERE a\\.attrelid = '(?<tablename>.*)' AND a\\.attnum > 0 AND NOT a\\.attisdropped\nORDER BY a\\.attnum;?$",
       "output_pattern": "SELECT t.column_name as attname, t.data_type as format_type, '' as substring, t.is_nullable = 'NO' as attnotnull, null::INTEGER as attcollation, null::INTEGER as indexdef, null::INTEGER as attfdwoptions FROM information_schema.columns AS t WHERE t.table_schema='public' AND t.table_name = '%s';",
       "matcher_array": [
         "tablename"
@@ -29,25 +29,25 @@
       "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the third. Here we fetch the actual schema."
     },
     {
-      "input_pattern": "^SELECT c\\.oid::pg_catalog\\.regclass[ \n]+FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_inherits i[ \n]+WHERE c\\.oid( )?=( )?i\\.inhparent AND i\\.inhrelid = '.*'[ \n]+(?:AND c\\.relkind != 'p')?[ \n]+(?:AND c\\.relkind != 'I')?[ \n]*ORDER BY inhseqno;$",
+      "input_pattern": "^SELECT c\\.oid::pg_catalog\\.regclass[ \n]+FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_inherits i[ \n]+WHERE c\\.oid( )?=( )?i\\.inhparent AND i\\.inhrelid = '.*'[ \n]+(?:AND c\\.relkind != 'p')?[ \n]+(?:AND c\\.relkind != 'I')?[ \n]*ORDER BY inhseqno;?$",
       "output_pattern": "SELECT 1 LIMIT 0;",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the fourth. The Spanner fetch does not require a similar step so we just hardcode it."
     },
     {
-      "input_pattern": "^SELECT c\\.oid::pg_catalog\\.regclass(, c\\.relkind(, false AS inhdetachpending)?, NULL)?[ \n]+FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_inherits i[ \n]+WHERE c\\.oid( )?=( )?i\\.inhrelid AND i\\.inhparent = '.*'[ \n]+ORDER BY c\\.relname;$",
+      "input_pattern": "^SELECT c\\.oid::pg_catalog\\.regclass(, c\\.relkind(, false AS inhdetachpending)?, NULL)?[ \n]+FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_inherits i[ \n]+WHERE c\\.oid( )?=( )?i\\.inhrelid AND i\\.inhparent = '.*'[ \n]+ORDER BY c\\.relname;?$",
       "output_pattern": "SELECT 1 LIMIT 0;",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the fifth. The Spanner fetch does not require a similar step so we just hardcode it."
     },
     {
-      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?,''\\)[ \n]+AND n\\.nspname <> 'pg_catalog'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?n\\.nspname !~ '\\^pg_toast'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
+      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?,''\\)[ \n]+AND n\\.nspname <> 'pg_catalog'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?n\\.nspname !~ '\\^pg_toast'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;?$",
       "output_pattern": "SELECT * FROM information_schema.tables;",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\dt meta-command."
     },
     {
-      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?(?:,'t')?,'s',''\\)[ \n]+AND (?:n\\.nspname !~ '\\^pg_toast'[ \n]+AND )?c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<tablename>.*)\\)\\$'[ \n]+AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
+      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?(?:,'t')?,'s',''\\)[ \n]+AND (?:n\\.nspname !~ '\\^pg_toast'[ \n]+AND )?c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<tablename>.*)\\)\\$'[ \n]+AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;?$",
       "output_pattern": "SELECT * FROM information_schema.tables WHERE LOWER(table_name) = LOWER('%s');",
       "matcher_array": [
         "tablename"
@@ -55,13 +55,13 @@
       "blurb": "This command is equivalent to the PSQL \\dt <param> meta-command."
     },
     {
-      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\",[ \n]+c2\\.relname as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace[ \n]+LEFT JOIN pg_catalog\\.pg_index i ON i\\.indexrelid = c\\.oid[ \n]+LEFT JOIN pg_catalog\\.pg_class c2 ON i\\.indrelid = c2\\.oid\nWHERE c\\.relkind IN \\('i'(?:,'I')?,''\\)[ \n]+AND n\\.nspname <> 'pg_catalog'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?n\\.nspname !~ '\\^pg_toast'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
+      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\",[ \n]+c2\\.relname as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace[ \n]+LEFT JOIN pg_catalog\\.pg_index i ON i\\.indexrelid = c\\.oid[ \n]+LEFT JOIN pg_catalog\\.pg_class c2 ON i\\.indrelid = c2\\.oid\nWHERE c\\.relkind IN \\('i'(?:,'I')?,''\\)[ \n]+AND n\\.nspname <> 'pg_catalog'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?n\\.nspname !~ '\\^pg_toast'[ \n]+AND (?:n\\.nspname <> 'information_schema'[ \n]+AND )?pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;?$",
       "output_pattern": "SELECT table_catalog, table_schema, table_name, index_name, index_type, parent_table_name, is_unique, is_null_filtered, index_state, spanner_is_managed FROM information_schema.indexes;",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\di meta-command."
     },
     {
-      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\",[ \n]+c2\\.relname as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace[ \n]+LEFT JOIN pg_catalog\\.pg_index i ON i\\.indexrelid = c\\.oid[ \n]+LEFT JOIN pg_catalog\\.pg_class c2 ON i\\.indrelid = c2\\.oid\nWHERE c\\.relkind IN \\('i'(?:,'I')?,'s',''\\)[ \n]+AND (?:n\\.nspname !~ '\\^pg_toast'[ \n]+AND )?c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<indexname>.*)\\)\\$'[ \n]+AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
+      "input_pattern": "^SELECT n\\.nspname as \"[^\"]*\",[ \n]+c\\.relname as \"[^\"]*\",[ \n]+CASE c\\.relkind (WHEN '.' THEN '[^']*' )+END as \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"[^\"]*\",[ \n]+c2\\.relname as \"[^\"]*\"\nFROM pg_catalog\\.pg_class c[ \n]+LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace[ \n]+LEFT JOIN pg_catalog\\.pg_index i ON i\\.indexrelid = c\\.oid[ \n]+LEFT JOIN pg_catalog\\.pg_class c2 ON i\\.indrelid = c2\\.oid\nWHERE c\\.relkind IN \\('i'(?:,'I')?,'s',''\\)[ \n]+AND (?:n\\.nspname !~ '\\^pg_toast'[ \n]+AND )?c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<indexname>.*)\\)\\$'[ \n]+AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;?$",
       "output_pattern": "SELECT table_catalog, table_schema, table_name, index_name, index_type, parent_table_name, is_unique, is_null_filtered, index_state, spanner_is_managed FROM information_schema.indexes WHERE LOWER(index_name) = LOWER('%s');",
       "matcher_array": [
         "indexname"
@@ -69,13 +69,13 @@
       "blurb": "This command is equivalent to the PSQL \\di <param> meta-command."
     },
     {
-      "input_pattern": "^SELECT n\\.nspname AS \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(n\\.nspowner\\) AS \"[^\"]*\"\nFROM pg_catalog\\.pg_namespace n\nWHERE n\\.nspname !~ '\\^pg_' AND n\\.nspname <> 'information_schema'\nORDER BY 1;$",
+      "input_pattern": "^SELECT n\\.nspname AS \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(n\\.nspowner\\) AS \"[^\"]*\"\nFROM pg_catalog\\.pg_namespace n\nWHERE n\\.nspname !~ '\\^pg_' AND n\\.nspname <> 'information_schema'\nORDER BY 1;?$",
       "output_pattern": "SELECT * FROM information_schema.schemata;",
       "matcher_array": [],
       "blurb": "This command is equivalent to the PSQL \\dn meta-command."
     },
     {
-      "input_pattern": "^SELECT n\\.nspname AS \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(n\\.nspowner\\) AS \"[^\"]*\"\nFROM pg_catalog\\.pg_namespace n\nWHERE n\\.nspname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<schemaname>.*)\\)\\$'\nORDER BY 1;$",
+      "input_pattern": "^SELECT n\\.nspname AS \"[^\"]*\",[ \n]+pg_catalog\\.pg_get_userbyid\\(n\\.nspowner\\) AS \"[^\"]*\"\nFROM pg_catalog\\.pg_namespace n\nWHERE n\\.nspname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<schemaname>.*)\\)\\$'\nORDER BY 1;?$",
       "output_pattern": "SELECT * FROM information_schema.schemata WHERE LOWER(schema_name) = LOWER('%s');",
       "matcher_array": [
         "schemaname"
diff --git a/src/main/resources/metadata/strict_command_metadata.json b/src/main/resources/metadata/strict_command_metadata.json
deleted file mode 100644
index e19139aa..00000000
--- a/src/main/resources/metadata/strict_command_metadata.json
+++ /dev/null
@@ -1,119 +0,0 @@
-{
-  "commands":
-  [
-    {
-      "input_pattern": "^SELECT n\\.nspname as \"Schema\",\n  c\\.relname as \"Name\",\n  CASE c\\.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' (?:WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' )?END as \"Type\",\n  pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"Owner\"\nFROM pg_catalog\\.pg_class c\n     LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?,'v','m','S','f',''\\)\n      AND n\\.nspname <> 'pg_catalog'\n      AND n\\.nspname <> 'information_schema'\n      AND n\\.nspname !~ '\\^pg_toast'\n  AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
-      "output_pattern": "/*GSQL*/ SELECT t.table_schema as Schema, t.table_name as Name, \"table\" as Type, \"me\" as Owner FROM information_schema.tables AS t WHERE t.table_schema = ''",
-      "matcher_array": [],
-      "blurb": "This command is equivalent to the PSQL \\d meta-command."
-    },
-    {
-      "input_pattern": "^SELECT c\\.oid,\n  n\\.nspname,\n  c\\.relname\nFROM pg_catalog\\.pg_class c\n     LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<tablename>.*)\\)\\$'\n  AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 2, 3;$",
-      "output_pattern": "/*GSQL*/ SELECT farm_fingerprint(t.table_name) as oid, \"\" as nspname, t.table_name as relname FROM information_schema.tables AS t WHERE t.table_schema='' AND LOWER(t.table_name) = LOWER('%s');",
-      "matcher_array": ["tablename"],
-      "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the first. Here we just fetch basic metadata used to fetch the table schema in earnest in the next step."
-    },
-    {
-      "input_pattern": "^SELECT relchecks, relkind, relhasindex, relhasrules, reltriggers <> 0, false, false, relhasoids, (?:false as relispartition, )?'', ''\nFROM pg_catalog\\.pg_class WHERE oid = '.*';$",
-      "output_pattern": "/*GSQL*/ SELECT 0 as relcheck, \"r\" as relkind, false as relhasindex, false as relhasrules, false as reltriggers, false, false, false as relhasoids, \"\", \"\";",
-      "matcher_array": [],
-      "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the second. Here normally would fetch catalogue metadata, but due to Spanner structure, we expect it to be static, and thus just hardcode it."
-    },
-    {
-      "input_pattern": "^SELECT a\\.attname,\n  pg_catalog\\.format_type\\(a\\.atttypid, a\\.atttypmod\\),\n  \\(SELECT substring\\(pg_catalog\\.pg_get_expr\\(d\\.adbin, d\\.adrelid(?:, true)?\\) for 128\\)\n   FROM pg_catalog\\.pg_attrdef d\n   WHERE d\\.adrelid = a\\.attrelid AND d\\.adnum = a\\.attnum AND a\\.atthasdef\\),\n  a\\.attnotnull,(?: a\\.attnum,)?\n  NULL AS attcollation,\n(?:  ''::pg_catalog\\.char AS attidentity,\n  ''::pg_catalog\\.char AS attgenerated\n|  NULL AS indexdef,\n  NULL AS attfdwoptions\n)?FROM pg_catalog\\.pg_attribute a\nWHERE a\\.attrelid = '(?<tablename>.*)' AND a\\.attnum > 0 AND NOT a\\.attisdropped\nORDER BY a\\.attnum;$",
-      "output_pattern": "/*GSQL*/ SELECT t.column_name as attname, t.spanner_type as format_type, \"\" as substring, t.is_nullable = \"NO\" as attnotnull, 1 as attnum, null as attcollation, null as indexdef, null as attfdwoptions FROM information_schema.columns AS t WHERE t.table_schema='' AND farm_fingerprint(t.table_name) = %s;",
-      "matcher_array": ["tablename"],
-      "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the third. Here we fetch the actual schema."
-    },
-    {
-      "input_pattern": "^SELECT c\\.oid::pg_catalog\\.regclass FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_inherits i WHERE c\\.oid=i\\.inhparent AND i\\.inhrelid = '.*' (?:AND c\\.relkind != 'p' )?ORDER BY inhseqno;$",
-      "output_pattern": "/*GSQL*/ SELECT 1 as oid from UNNEST([]);",
-      "matcher_array": [],
-      "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the fourth. The Spanner fetch does not require a similar step so we just hardcode it."
-    },
-    {
-      "input_pattern": "^SELECT c\\.oid::pg_catalog\\.regclass FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_inherits i WHERE c\\.oid=i\\.inhrelid AND i\\.inhparent = '.*' ORDER BY c\\.relname;$",
-      "output_pattern": "/*GSQL*/ SELECT 1 as oid from UNNEST([]);",
-      "matcher_array": [],
-      "blurb": "This command is equivalent to the PSQL \\d <param> meta-command. This command is split up among multiple parts, this being the fifth. The Spanner fetch does not require a similar step so we just hardcode it."
-    },
-    {
-      "input_pattern": "^SELECT n\\.nspname as \"Schema\",\n  c\\.relname as \"Name\",\n  CASE c\\.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' (?:WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' )?END as \"Type\",\n  pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"Owner\"\nFROM pg_catalog\\.pg_class c\n     LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?,''\\)\n      AND n\\.nspname <> 'pg_catalog'\n      AND n\\.nspname <> 'information_schema'\n      AND n\\.nspname !~ '\\^pg_toast'\n  AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
-      "output_pattern": "/*GSQL*/ SELECT * FROM information_schema.tables;",
-      "matcher_array": [],
-      "blurb": "This command is equivalent to the PSQL \\dt meta-command."
-    },
-    {
-      "input_pattern": "^SELECT n\\.nspname as \"Schema\",\n  c\\.relname as \"Name\",\n  CASE c\\.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' (?:WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' )?END as \"Type\",\n  pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"Owner\"\nFROM pg_catalog\\.pg_class c\n     LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\nWHERE c\\.relkind IN \\('r'(?:,'p')?,'s',''\\)\n      AND n\\.nspname !~ '\\^pg_toast'\n  AND c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<tablename>.*)\\)\\$'\n  AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
-      "output_pattern": "/*GSQL*/ SELECT * FROM information_schema.tables WHERE LOWER(table_name) = LOWER('%s');",
-      "matcher_array": ["tablename"],
-      "blurb": "This command is equivalent to the PSQL \\dt <param> meta-command."
-    },
-    {
-      "input_pattern": "^SELECT n\\.nspname as \"Schema\",\n  c\\.relname as \"Name\",\n  CASE c\\.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' (?:WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' )?END as \"Type\",\n  pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"Owner\",\n c2\\.relname as \"Table\"\nFROM pg_catalog\\.pg_class c\n     LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\n     LEFT JOIN pg_catalog\\.pg_index i ON i\\.indexrelid = c\\.oid\n     LEFT JOIN pg_catalog\\.pg_class c2 ON i\\.indrelid = c2\\.oid\nWHERE c\\.relkind IN \\('i'(?:,'I')?,''\\)\n      AND n\\.nspname <> 'pg_catalog'\n      AND n\\.nspname <> 'information_schema'\n      AND n\\.nspname !~ '\\^pg_toast'\n  AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
-      "output_pattern": "/*GSQL*/ SELECT * FROM information_schema.indexes;",
-      "matcher_array": [],
-      "blurb": "This command is equivalent to the PSQL \\di meta-command."
-    },
-    {
-      "input_pattern": "^SELECT n\\.nspname as \"Schema\",\n  c\\.relname as \"Name\",\n  CASE c\\.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 's' THEN 'special' WHEN 'f' THEN 'foreign table' (?:WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' )?END as \"Type\",\n  pg_catalog\\.pg_get_userbyid\\(c\\.relowner\\) as \"Owner\",\n c2\\.relname as \"Table\"\nFROM pg_catalog\\.pg_class c\n     LEFT JOIN pg_catalog\\.pg_namespace n ON n\\.oid = c\\.relnamespace\n     LEFT JOIN pg_catalog\\.pg_index i ON i\\.indexrelid = c\\.oid\n     LEFT JOIN pg_catalog\\.pg_class c2 ON i\\.indrelid = c2\\.oid\nWHERE c\\.relkind IN \\('i'(?:,'I')?,'s',''\\)\n      AND n\\.nspname !~ '\\^pg_toast'\n  AND c\\.relname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<indexname>.*)\\)\\$'\n  AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nORDER BY 1,2;$",
-      "output_pattern": "/*GSQL*/ SELECT * FROM information_schema.indexes WHERE LOWER(index_name) = LOWER('%s');",
-      "matcher_array": ["indexname"],
-      "blurb": "This command is equivalent to the PSQL \\di <param> meta-command."
-    },
-    {
-      "input_pattern": "^SELECT n\\.nspname AS \"Name\",\n  pg_catalog\\.pg_get_userbyid\\(n\\.nspowner\\) AS \"Owner\"\nFROM pg_catalog\\.pg_namespace n\nWHERE n\\.nspname !~ '\\^pg_' AND n\\.nspname <> 'information_schema'\nORDER BY 1;$",
-      "output_pattern": "/*GSQL*/ SELECT * FROM information_schema.schemata;",
-      "matcher_array": [],
-      "blurb": "This command is equivalent to the PSQL \\dn meta-command."
-    },
-    {
-      "input_pattern": "^SELECT n\\.nspname AS \"Name\",\n  pg_catalog\\.pg_get_userbyid\\(n\\.nspowner\\) AS \"Owner\"\nFROM pg_catalog\\.pg_namespace n\nWHERE n\\.nspname OPERATOR\\(pg_catalog\\.~\\) '\\^\\((?<schemaname>.*)\\)\\$'\nORDER BY 1;$",
-      "output_pattern": "/*GSQL*/ SELECT * FROM information_schema.schemata WHERE LOWER(schema_name) = LOWER('%s');",
-      "matcher_array": ["schemaname"],
-      "blurb": "This command is equivalent to the PSQL \\dn <param> meta-command."
-    },
-    {
-      "input_pattern": "^SELECT pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c WHERE c\\.relkind IN \\('r', 'S', 'v', 'm', 'f'(?:, 'p')?\\) AND substring\\(pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(?<prefix>.*)' AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\) AND c\\.relnamespace <> \\(SELECT oid FROM pg_catalog\\.pg_namespace WHERE nspname = 'pg_catalog'\\)\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' FROM pg_catalog\\.pg_namespace n WHERE substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\)='.*' AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('.*',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) > \\d+\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_namespace n WHERE c\\.relnamespace = n\\.oid AND c\\.relkind IN \\('r', 'S', 'v', 'm', 'f'(?:, 'p')?\\) AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='.*' AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('.*',1,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(n\\.nspname\\)\\)\\+\\d+\\) AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('.*',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) = \\d+\nLIMIT \\d+$",
-      "output_pattern": "/*GSQL*/ SELECT table_name AS quote_ident FROM information_schema.tables WHERE table_catalog = '' and table_schema = '' and STARTS_WITH(LOWER(table_name), LOWER('%s')) LIMIT 1000;",
-      "matcher_array": ["prefix"],
-      "blurb": "Autocomplete: This replacement helps psql autocomplete table names in simple select queries."
-    },
-    {
-      "input_pattern": "^SELECT pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c WHERE c\\.relkind IN \\('r', 'f', 'v'(?:, 'p')?\\) AND substring\\(pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(?<prefix>.*)' AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\) AND c\\.relnamespace <> \\(SELECT oid FROM pg_catalog\\.pg_namespace WHERE nspname = 'pg_catalog'\\)\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' FROM pg_catalog\\.pg_namespace n WHERE substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\)='(.*)' AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*)',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) > \\d+\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_namespace n WHERE c\\.relnamespace = n\\.oid AND c\\.relkind IN \\('r', 'f', 'v'(?:, 'p')?\\) AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(.*)' AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*)',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(n\\.nspname\\)\\)\\+\\d+\\) AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*)',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) = \\d+\nLIMIT \\d+$",
-      "output_pattern": "/*GSQL*/ SELECT table_name AS quote_ident FROM information_schema.tables WHERE table_catalog = '' and table_schema = '' and STARTS_WITH(LOWER(table_name), LOWER('%s')) LIMIT 1000;",
-      "matcher_array": ["prefix"],
-      "blurb": "Autocomplete: This replacement helps psql autocomplete table names in simple insert queries."
-    },
-    {
-      "input_pattern": "^SELECT pg_catalog\\.quote_ident\\(attname\\)   FROM pg_catalog\\.pg_attribute a, pg_catalog\\.pg_class c  WHERE c\\.oid = a\\.attrelid    AND a\\.attnum > \\d+    AND NOT a\\.attisdropped    AND substring\\(pg_catalog\\.quote_ident\\(attname\\),\\d+,\\d+\\)='(?<attrprefix>.*)'    AND \\(pg_catalog\\.quote_ident\\(relname\\)='(?<tablename>.*)'         OR '\"' \\|\\| relname \\|\\| '\"'='.*'\\)    AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\)\nLIMIT 1000$",
-      "output_pattern": "/*GSQL*/ SELECT column_name AS quote_ident FROM information_schema.columns WHERE table_name = '%s' AND STARTS_WITH(LOWER(COLUMN_NAME), LOWER('%s')) LIMIT 1000;",
-      "matcher_array": ["tablename", "attrprefix"],
-      "blurb": "Autocomplete: This replacement helps psql autocomplete attribute names in simple queries."
-    },
-    {
-      "input_pattern": "^SELECT pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c WHERE substring\\(pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(?<prefix>.*)' AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\) AND c\\.relnamespace <> \\(SELECT oid FROM pg_catalog\\.pg_namespace WHERE nspname = 'pg_catalog'\\)\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' FROM pg_catalog\\.pg_namespace n WHERE substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\)='.*' AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('.*',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) > \\d+\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_namespace n WHERE c\\.relnamespace = n\\.oid AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='.*' AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('.*',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(n\\.nspname\\)\\)\\+\\d+\\) AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('.*',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) = \\d+\nLIMIT \\d+$",
-      "output_pattern": "/*GSQL*/ SELECT table_name AS quote_ident FROM information_schema.tables WHERE table_catalog = '' AND table_schema = '' AND STARTS_WITH(LOWER(table_name), LOWER('%s')) LIMIT 1000;",
-      "matcher_array": ["prefix"],
-      "blurb": "Autocomplete: This replacement helps psql autocomplete the \\d <table> command."
-    },
-    {
-      "input_pattern": "^SELECT pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c WHERE c\\.relkind IN \\('r', 'p'\\) AND substring\\(pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(?<prefix>.*)' AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\) AND c\\.relnamespace <> \\(SELECT oid FROM pg_catalog\\.pg_namespace WHERE nspname = 'pg_catalog'\\)\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' FROM pg_catalog\\.pg_namespace n WHERE substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\)='(.*) AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*),\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) > \\d+\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_namespace n WHERE c\\.relnamespace = n\\.oid AND c\\.relkind IN \\('r', 'p'\\) AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(.*) AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*),\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(n\\.nspname\\)\\)\\+\\d+\\) AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*),\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) = \\d+\nLIMIT \\d+$",
-      "output_pattern": "/*GSQL*/ SELECT table_name AS quote_ident FROM INFORMATION_SCHEMA.TABLES WHERE STARTS_WITH(LOWER(table_name), LOWER('%s')) LIMIT 1000;",
-      "matcher_array": ["prefix"],
-      "blurb": "Autocomplete: This replacement helps psql autocomplete the \\dt <table> command."
-    },
-    {
-      "input_pattern": "^SELECT pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c WHERE c\\.relkind IN \\('i', 'I'\\) AND substring\\(pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(?<prefix>.*)' AND pg_catalog\\.pg_table_is_visible\\(c\\.oid\\) AND c\\.relnamespace <> \\(SELECT oid FROM pg_catalog\\.pg_namespace WHERE nspname = 'pg_catalog'\\)\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' FROM pg_catalog\\.pg_namespace n WHERE substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\)='(.*)' AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*)',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) > \\d+\nUNION\nSELECT pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\) FROM pg_catalog\\.pg_class c, pg_catalog\\.pg_namespace n WHERE c\\.relnamespace = n\\.oid AND c\\.relkind IN \\('i', 'I'\\) AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.' \\|\\| pg_catalog\\.quote_ident\\(c\\.relname\\),\\d+,\\d+\\)='(.*)' AND substring\\(pg_catalog\\.quote_ident\\(n\\.nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*)',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(n\\.nspname\\)\\)\\+\\d+\\) AND \\(SELECT pg_catalog\\.count\\(\\*\\) FROM pg_catalog\\.pg_namespace WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\) \\|\\| '\\.',\\d+,\\d+\\) = substring\\('(.*)',\\d+,pg_catalog\\.length\\(pg_catalog\\.quote_ident\\(nspname\\)\\)\\+\\d+\\)\\) = \\d+\nLIMIT \\d+$",
-      "output_pattern": "/*GSQL*/ SELECT index_name AS quote_ident FROM INFORMATION_SCHEMA.INDEXES WHERE STARTS_WITH(LOWER(index_name), LOWER('%s')) LIMIT 1000;",
-      "matcher_array": ["prefix"],
-      "blurb": "Autocomplete: This replacement helps psql autocomplete the \\di <table> command."
-    },
-    {
-      "input_pattern": "SELECT pg_catalog\\.quote_ident\\(nspname\\) FROM pg_catalog\\.pg_namespace  WHERE substring\\(pg_catalog\\.quote_ident\\(nspname\\),\\d+,\\d+\\)='(?<prefix>.*)'\nLIMIT \\d+",
-      "output_pattern": "/*GSQL*/ SELECT schema_name AS quote_ident FROM INFORMATION_SCHEMA.SCHEMATA WHERE STARTS_WITH(LOWER(schema_name), LOWER('%s')) LIMIT 1000;",
-      "matcher_array": ["prefix"],
-      "blurb": "Autocomplete: This replacement helps psql autocomplete the \\dn <table> command."
-    }
-  ]
-}


Test patch:
diff --git a/.ci/e2e-expected/ddl-dml-batching.txt b/.ci/e2e-expected/ddl-dml-batching.txt
index f1244266..f5f0ce84 100644
--- a/.ci/e2e-expected/ddl-dml-batching.txt
+++ b/.ci/e2e-expected/ddl-dml-batching.txt
@@ -1,1 +1,1 @@
-ERROR: FAILED_PRECONDITION: Executing updates is not allowed for DDL batches. "CREATE TABLE testtable (id integer PRIMARY KEY, data integer); INSERT INTO testtable (id, data) VALUES (1, 2); INSERT INTO testtable (id, data) VALUES (3, 4);"
+ERROR: FAILED_PRECONDITION: Executing updates is not allowed for DDL batches. "CREATE TABLE testtable (id integer PRIMARY KEY, data integer); INSERT INTO testtable (id, data) VALUES (1, 2); INSERT INTO testtable (id, data) VALUES (3, 4)"
diff --git a/.ci/e2e-expected/invalid-commit-batching.txt b/.ci/e2e-expected/invalid-commit-batching.txt
index 78eddb5e..d4e73782 100644
--- a/.ci/e2e-expected/invalid-commit-batching.txt
+++ b/.ci/e2e-expected/invalid-commit-batching.txt
@@ -1,1 +1,1 @@
-ERROR: FAILED_PRECONDITION: This connection has an active batch and cannot begin a transaction "BEGIN TRANSACTION; INSERT INTO users (id, age, name) VALUES (99, 99, 'test'); COMMIT;"
+ERROR: FAILED_PRECONDITION: This connection has an active batch and cannot begin a transaction "BEGIN TRANSACTION; INSERT INTO users (id, age, name) VALUES (99, 99, 'test'); COMMIT"
diff --git a/.ci/e2e-expected/select-ddl-batching.txt b/.ci/e2e-expected/select-ddl-batching.txt
index fdfb07ae..0cebc3da 100644
--- a/.ci/e2e-expected/select-ddl-batching.txt
+++ b/.ci/e2e-expected/select-ddl-batching.txt
@@ -1,1 +1,1 @@
-ERROR: FAILED_PRECONDITION: Executing queries is not allowed for DDL batches. "CREATE TABLE test (id integer PRIMARY KEY); SELECT * FROM test; SELECT * FROM test;"
+ERROR: FAILED_PRECONDITION: Executing queries is not allowed for DDL batches. "CREATE TABLE test (id integer PRIMARY KEY); SELECT * FROM test; SELECT * FROM test"
diff --git a/.ci/e2e-expected/select-dml-batching.txt b/.ci/e2e-expected/select-dml-batching.txt
index 87a9f5d9..6ad7b053 100644
--- a/.ci/e2e-expected/select-dml-batching.txt
+++ b/.ci/e2e-expected/select-dml-batching.txt
@@ -1,1 +1,1 @@
-ERROR: INVALID_ARGUMENT: Statement type is not supported for batching "SELECT * FROM users; INSERT INTO users (id, age, name) VALUES (99, 99, 'person'); SELECT * FROM users;"
+ERROR: INVALID_ARGUMENT: Statement type is not supported for batching "SELECT * FROM users; INSERT INTO users (id, age, name) VALUES (99, 99, 'person'); SELECT * FROM users"
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java
index 13c2a9df..3a827452 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcSimpleModeMockServerTest.java
@@ -160,7 +160,7 @@ public class JdbcSimpleModeMockServerTest extends AbstractMockServerTest {
         assertEquals(
             String.format(
                 "ERROR: FAILED_PRECONDITION: Executing updates is not allowed for DDL batches. \"%s\"",
-                sql),
+                sql.substring(0, sql.length() - 1)),
             exception.getMessage());
       }
     }
@@ -177,7 +177,7 @@ public class JdbcSimpleModeMockServerTest extends AbstractMockServerTest {
         assertEquals(
             String.format(
                 "ERROR: FAILED_PRECONDITION: Executing queries is not allowed for DDL batches. \"%s\"",
-                sql),
+                sql.substring(0, sql.length() - 1)),
             exception.getMessage());
       }
     }
@@ -194,7 +194,7 @@ public class JdbcSimpleModeMockServerTest extends AbstractMockServerTest {
         assertEquals(
             String.format(
                 "ERROR: FAILED_PRECONDITION: This connection has an active batch and cannot begin a transaction \"%s\"",
-                sql),
+                sql.substring(0, sql.length() - 1)),
             exception.getMessage());
       }
     }
@@ -211,7 +211,7 @@ public class JdbcSimpleModeMockServerTest extends AbstractMockServerTest {
         assertEquals(
             String.format(
                 "ERROR: INVALID_ARGUMENT: Statement type is not supported for batching \"%s\"",
-                sql),
+                sql.substring(0, sql.length() - 1)),
             exception.getMessage());
       }
     }
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java
index 4f541ade..efa1142c 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ParserTest.java
@@ -28,9 +28,13 @@ import static org.mockito.Mockito.when;
 import com.google.cloud.ByteArray;
 import com.google.cloud.Date;
 import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.ErrorCode;
 import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.SpannerException;
 import com.google.cloud.spanner.Type;
 import com.google.cloud.spanner.Value;
+import com.google.cloud.spanner.connection.AbstractStatementParser;
 import com.google.cloud.spanner.pgadapter.ProxyServer.DataFormat;
 import com.google.cloud.spanner.pgadapter.parsers.ArrayParser;
 import com.google.cloud.spanner.pgadapter.parsers.BinaryParser;
@@ -66,6 +70,8 @@ import org.postgresql.util.ByteConverter;
  */
 @RunWith(JUnit4.class)
 public class ParserTest {
+  private static final AbstractStatementParser PARSER =
+      AbstractStatementParser.getInstance(Dialect.POSTGRESQL);
 
   private void validate(
       Parser parser, byte[] byteResult, byte[] stringResult, byte[] spannerResult) {
@@ -414,8 +420,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -427,8 +432,7 @@ public class ParserTest {
       String sql = "COPY Users FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -440,8 +444,7 @@ public class ParserTest {
       String sql = "COPY UsErS FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -453,8 +456,7 @@ public class ParserTest {
       String sql = "COPY USERS FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -466,8 +468,7 @@ public class ParserTest {
       String sql = "COPY \'Users\' FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("Users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -479,8 +480,7 @@ public class ParserTest {
       String sql = "COPY \"UsErS\" FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("UsErS")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -492,8 +492,7 @@ public class ParserTest {
       String sql = "COPY \'USERS\' FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("USERS")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -505,8 +504,7 @@ public class ParserTest {
       String sql = "COPY users (id) FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -518,8 +516,7 @@ public class ParserTest {
       String sql = "COPY users (id, age) FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -531,8 +528,7 @@ public class ParserTest {
       String sql = "COPY users (id, age, name) FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -544,8 +540,7 @@ public class ParserTest {
       String sql = "COPY users (ID, AGE, NAME) FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -557,8 +552,7 @@ public class ParserTest {
       String sql = "COPY users (Id, Age, Name) FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -570,8 +564,7 @@ public class ParserTest {
       String sql = "COPY users (iD, aGe, nAMe) FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -583,8 +576,7 @@ public class ParserTest {
       String sql = "COPY users (\'ID\', \"AGE\", \'NAME\') FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -596,8 +588,7 @@ public class ParserTest {
       String sql = "COPY users (\'Id\', \'Age\', \'Name\') FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -609,8 +600,7 @@ public class ParserTest {
       String sql = "COPY users (\"iD\", \"aGe\", \"nAMe\") FROM STDIN;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -625,8 +615,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN WITH FORMAT CSV;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.CSV)));
@@ -637,8 +626,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN WITH FORMAT BINARY;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.BINARY)));
@@ -649,8 +637,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN WITH FORMAT TEXT;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -661,8 +648,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN CSV;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.CSV)));
@@ -673,8 +659,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN BINARY;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.BINARY)));
@@ -685,8 +670,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN TEXT;";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -697,8 +681,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN DELIMITER '*';";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -710,9 +693,14 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN ESCAPE '\\';";
       parse(sql, options);
 
-      assertThrows(
-          IllegalArgumentException.class,
-          () -> StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)));
+      SpannerException exception =
+          assertThrows(
+              SpannerException.class,
+              () -> StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)));
+      assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode());
+      assertEquals(
+          "INVALID_ARGUMENT: SQL statement contains an unclosed literal: COPY users FROM STDIN ESCAPE '\\';",
+          exception.getMessage());
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -724,9 +712,14 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN QUOTE '\'';";
       parse(sql, options);
 
-      assertThrows(
-          IllegalArgumentException.class,
-          () -> StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)));
+      SpannerException exception =
+          assertThrows(
+              SpannerException.class,
+              () -> StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)));
+      assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode());
+      assertEquals(
+          "INVALID_ARGUMENT: SQL statement contains an unclosed literal: COPY users FROM STDIN QUOTE ''';",
+          exception.getMessage());
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -738,8 +731,7 @@ public class ParserTest {
       String sql = "COPY users FROM STDIN NULL 'null';";
       parse(sql, options);
 
-      assertEquals(
-          StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+      assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
       assertThat(options.getTableName(), is(equalTo("users")));
       assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
       assertThat(options.getFormat(), is(equalTo(Format.TEXT)));
@@ -756,12 +748,13 @@ public class ParserTest {
         // cases for their use: [' " \]  ('\'' and '\"' are actually allowed if they are wrapped by
         // the other character)
         if (value == '\'' || value == '\\') {
-          assertThrows(
-              IllegalArgumentException.class,
-              () -> StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)));
+          SpannerException exception =
+              assertThrows(
+                  SpannerException.class,
+                  () -> StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)));
+          assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode());
         } else {
-          assertEquals(
-              StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+          assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
         }
         assertThat(options.getTableName(), is(equalTo("users")));
         assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
@@ -786,12 +779,13 @@ public class ParserTest {
                 + "');";
         parse(sql, options);
         if (value >= '%' && value <= '\'' || value >= 'Z' && value <= '\\') {
-          assertThrows(
-              IllegalArgumentException.class,
-              () -> StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)));
+          SpannerException exception =
+              assertThrows(
+                  SpannerException.class,
+                  () -> StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)));
+          assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode());
         } else {
-          assertEquals(
-              StatementParser.parseCommand(StatementParser.removeCommentsAndTrim(sql)), "COPY");
+          assertEquals(StatementParser.parseCommand(PARSER.removeCommentsAndTrim(sql)), "COPY");
         }
         assertThat(options.getTableName(), is(equalTo("users")));
         assertThat(options.getFromTo(), is(equalTo(FromTo.FROM)));
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ProtocolTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ProtocolTest.java
index d0014cfb..2ba83bfe 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ProtocolTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ProtocolTest.java
@@ -235,10 +235,10 @@ public class ProtocolTest {
   }
 
   @Test
-  public void testParseMessageGsqlException() throws Exception {
+  public void testParseMessageException() throws Exception {
     byte[] messageMetadata = {'P'};
     String statementName = "some statement\0";
-    String payload = "/*GSQL*/ SELECT * FROM users WHERE name = $1\0";
+    String payload = "SELECT * FROM users WHERE name = $1\0";
 
     byte[] parameterCount = {0, 1};
     byte[] parameters = intToBytes(1002);
@@ -261,7 +261,7 @@ public class ProtocolTest {
             parameters);
 
     List<Integer> expectedParameterDataTypes = Arrays.asList(1002);
-    String expectedSQL = "/*GSQL*/SELECT * FROM users WHERE name = $1";
+    String expectedSQL = "SELECT * FROM users WHERE name = $1";
     String expectedMessageName = "some statement";
 
     DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(value));
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java
index 84cf0de4..6de3e89d 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/StatementParserTest.java
@@ -14,33 +14,42 @@
 
 package com.google.cloud.spanner.pgadapter;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.ErrorCode;
+import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.connection.AbstractStatementParser;
 import com.google.cloud.spanner.pgadapter.utils.StatementParser;
 import org.junit.Assert;
 import org.junit.Test;
 
 public class StatementParserTest {
+  private final AbstractStatementParser parser =
+      AbstractStatementParser.getInstance(Dialect.POSTGRESQL);
 
   @Test
   public void testRemoveCommentsAndTrim() {
 
     String sqlStatement = "-- This is a one line comment\nSELECT * FROM FOO";
     String expectedResult = "SELECT * FROM FOO";
-    String result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    String result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, expectedResult);
 
     sqlStatement = "/* This is a simple multi line comment */\nSELECT * FROM FOO";
     expectedResult = "SELECT * FROM FOO";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, expectedResult);
 
     sqlStatement = "/* This is a \nmulti line comment */\nSELECT * FROM FOO";
     expectedResult = "SELECT * FROM FOO";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, expectedResult);
 
     sqlStatement = "/* This\nis\na\nmulti\nline\ncomment */\nSELECT * FROM FOO";
     expectedResult = "SELECT * FROM FOO";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, expectedResult);
 
     sqlStatement =
@@ -68,67 +77,72 @@ public class StatementParserTest {
             + "  INSERT VALUES (v.column1, v.column2, v.column3)\n"
             + "WHEN MATCHED\n"
             + "  UPDATE SET FirstName = v.column2,\n"
-            + "             LastName = v.column3;";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+            + "             LastName = v.column3";
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, expectedResult);
 
     // Dollar Quoted
     sqlStatement = "$$--foo$$";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
     sqlStatement = "$$\nline 1\n--line2$$";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
     sqlStatement = "$bar$--foo$bar$";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
     sqlStatement = "SELECT FOO$BAR FROM SOME_TABLE";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
     sqlStatement = "SELECT FOO$BAR -- This is a comment\nFROM SOME_TABLE";
     expectedResult = "SELECT FOO$BAR \nFROM SOME_TABLE";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, expectedResult);
 
     // Embedded Comments
     sqlStatement =
         "/* This is a comment /* This is an embedded comment */ This is after the embedded comment */ SELECT 1";
     expectedResult = "SELECT 1";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, expectedResult);
 
     // No effect HashTag Comment
     sqlStatement = "# this is a comment\nselect * from foo";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
     sqlStatement = "select *\nfrom foo # this is a comment\nwhere bar=1";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
     // When parameters are mixed with dollar-quoted string
-    sqlStatement = "$1$$?it$?s$$$2";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    sqlStatement = "$1 $$?it$?s$$ $2";
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
-    sqlStatement = "$1$tag$?it$$?s$tag$$2";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    sqlStatement = "$1 $tag$?it$$?s$tag$ $2";
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
 
-    sqlStatement = "$1$$?it\\'?s \n ?it\\'?s$$$2";
-    result = StatementParser.removeCommentsAndTrim(sqlStatement);
+    sqlStatement = "$1 $$?it\\'?s \n ?it\\'?s$$ $2";
+    result = parser.removeCommentsAndTrim(sqlStatement);
     Assert.assertEquals(result, sqlStatement);
   }
 
-  @Test(expected = IllegalArgumentException.class)
+  @Test
   public void testRemoveCommentsAndTrimWithUnterminatedComment() {
     String sqlStatement =
         "/* This is a comment /* This is still a comment */ this is unterminated SELECT 1";
-    StatementParser.removeCommentsAndTrim(sqlStatement);
+    SpannerException exception =
+        assertThrows(SpannerException.class, () -> parser.removeCommentsAndTrim(sqlStatement));
+    assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode());
+    assertEquals(
+        "INVALID_ARGUMENT: SQL statement contains an unterminated block comment: /* This is a comment /* This is still a comment */ this is unterminated SELECT 1",
+        exception.getMessage());
   }
 
   @Test
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/StatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/StatementTest.java
index 03473fe7..00e0ba14 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/StatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/StatementTest.java
@@ -427,7 +427,7 @@ public class StatementTest {
     List<String> result = intermediateStatement.getStatements();
     assertEquals(result.size(), 2);
     assertEquals(result.get(0), "INSERT INTO users (name) VALUES (';;test;;');");
-    assertEquals(result.get(1), "INSERT INTO users (name1, name2) VALUES ('''''', ';'';');");
+    assertEquals(result.get(1), "INSERT INTO users (name1, name2) VALUES ('''''', ';'';')");
   }
 
   @Test
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/metadata/CommandMetadataParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/CommandMetadataParserTest.java
index 6f21988d..b3400664 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/metadata/CommandMetadataParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/CommandMetadataParserTest.java
@@ -64,7 +64,7 @@ public class CommandMetadataParserTest {
     commands.add(
         ImmutableMap.of(
             "input_pattern", "SELECT 1",
-            "output_pattern", "/*GSQL*/ SELECT 1",
+            "output_pattern", "SELECT 1",
             "matcher_array", new JSONArray(),
             "blurb", "test command"));
     final JSONObject expected = new JSONObject(ImmutableMap.of("commands", commands));