Repository: GoogleCloudPlatform/pgadapter
Pull request #1437 (2024-02-20 15:50:54)

feat: support set_config and current_setting
Support the following specific query constructs:
1. select set_config('setting_name', 'setting_value', is_local)
2. select current_setting('setting_name'[, missing_ok])

The `set_config` statement is required for supporting Django with psycopg3.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) Report
Attention: `13 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`cd6c9dc`)](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/commit/cd6c9dc9ae3492b9c561ca892cbbf518645be623?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) 90.86% compared to head [(`16a7f66`)](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) 90.82%.
> Report is 1 commits behind head on postgresql-dialect.

| [Files](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) | Patch % | Lines |
|---|---|---|
| [...pgadapter/statements/SelectSetConfigStatement.java](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3N0YXRlbWVudHMvU2VsZWN0U2V0Q29uZmlnU3RhdGVtZW50LmphdmE=) | 86.27% | [5 Missing and 2 partials :warning: ](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) |
| [...r/pgadapter/statements/SessionStatementParser.java](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3N0YXRlbWVudHMvU2Vzc2lvblN0YXRlbWVudFBhcnNlci5qYXZh) | 60.00% | [3 Missing and 1 partial :warning: ](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) |
| [...pter/statements/SelectCurrentSettingStatement.java](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform#diff-c3JjL21haW4vamF2YS9jb20vZ29vZ2xlL2Nsb3VkL3NwYW5uZXIvcGdhZGFwdGVyL3N0YXRlbWVudHMvU2VsZWN0Q3VycmVudFNldHRpbmdTdGF0ZW1lbnQuamF2YQ==) | 94.59% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) |

<details><summary>Additional details and impacted files</summary>


```diff
@@                   Coverage Diff                    @@
##             postgresql-dialect    #1437      +/-   ##
========================================================
- Coverage                 90.86%   90.82%   -0.04%     
- Complexity                 2658     2683      +25     
========================================================
  Files                       141      143       +2     
  Lines                      9063     9166     +103     
  Branches                   1323     1339      +16     
========================================================
+ Hits                       8235     8325      +90     
- Misses                      555      564       +9     
- Partials                    273      277       +4     
```

| [Flag](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) | Coverage Δ | |
|---|---|---|
| [all_tests](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform) | `90.82% <87.96%> (-0.04%)` | :arrow_down: |

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


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/GoogleCloudPlatform/pgadapter/pull/1437?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=GoogleCloudPlatform).


Patch:
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SelectCurrentSettingStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SelectCurrentSettingStatement.java
new file mode 100644
index 00000000..2943da29
--- /dev/null
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SelectCurrentSettingStatement.java
@@ -0,0 +1,135 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.statements;
+
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.Type;
+import com.google.cloud.spanner.Type.StructField;
+import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
+import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType;
+import com.google.cloud.spanner.connection.StatementResult;
+import com.google.cloud.spanner.pgadapter.ConnectionHandler;
+import com.google.cloud.spanner.pgadapter.error.PGExceptionFactory;
+import com.google.cloud.spanner.pgadapter.error.SQLState;
+import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
+import com.google.cloud.spanner.pgadapter.parsers.BooleanParser;
+import com.google.cloud.spanner.pgadapter.statements.BackendConnection.QueryResult;
+import com.google.cloud.spanner.pgadapter.statements.SessionStatementParser.ShowStatement;
+import com.google.cloud.spanner.pgadapter.statements.SimpleParser.QuotedString;
+import com.google.cloud.spanner.pgadapter.statements.SimpleParser.TableOrIndexName;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Future;
+
+/**
+ * SELECT CURRENT_SETTING(setting_name text [, missing_ok boolean ] ) is translated to the
+ * equivalent SHOW statement.
+ */
+public class SelectCurrentSettingStatement extends IntermediatePortalStatement {
+  private final ShowStatement showStatement;
+
+  public SelectCurrentSettingStatement(
+      ConnectionHandler connectionHandler,
+      OptionsMetadata options,
+      String name,
+      ParsedStatement parsedStatement,
+      Statement originalStatement) {
+    super(
+        name,
+        new IntermediatePreparedStatement(
+            connectionHandler,
+            options,
+            name,
+            NO_PARAMETER_TYPES,
+            parsedStatement,
+            originalStatement),
+        NO_PARAMS,
+        ImmutableList.of(),
+        ImmutableList.of());
+    this.showStatement = parse(originalStatement.getSql());
+  }
+
+  static ShowStatement parse(String sql) {
+    Preconditions.checkNotNull(sql);
+
+    // SELECT CURRENT_SETTING('setting_name'[, missing_ok])
+    SimpleParser parser = new SimpleParser(sql);
+    if (!parser.eatKeyword("select", "current_setting")) {
+      throw PGExceptionFactory.newPGException(
+          "not a valid SELECT CURRENT_SETTING statement: " + sql, SQLState.SyntaxError);
+    }
+    if (!parser.eatToken("(")) {
+      throw PGExceptionFactory.newPGException(
+          "missing '(' for current_setting", SQLState.SyntaxError);
+    }
+    QuotedString setting = parser.readSingleQuotedString();
+    boolean missingOk = false;
+    if (parser.eatToken(",")) {
+      String missingOkString = parser.readKeyword().toLowerCase(Locale.ENGLISH);
+      missingOk = BooleanParser.toBoolean(missingOkString);
+    }
+    if (!parser.eatToken(")")) {
+      throw PGExceptionFactory.newPGException(
+          "missing ')' for current_setting", SQLState.SyntaxError);
+    }
+    parser.throwIfHasMoreTokens();
+
+    try {
+      TableOrIndexName settingName = TableOrIndexName.parse(setting.getValue());
+      return new ShowStatement(settingName, "current_setting", missingOk);
+    } catch (IllegalArgumentException illegalArgumentException) {
+      throw PGExceptionFactory.newPGException(
+          illegalArgumentException.getMessage(), SQLState.SyntaxError);
+    }
+  }
+
+  @Override
+  public String getCommandTag() {
+    return "SELECT";
+  }
+
+  @Override
+  public StatementType getStatementType() {
+    return StatementType.CLIENT_SIDE;
+  }
+
+  @Override
+  public void executeAsync(BackendConnection backendConnection) {
+    this.executed = true;
+    setFutureStatementResult(
+        Futures.immediateFuture(showStatement.execute(backendConnection.getSessionState())));
+  }
+
+  @Override
+  public Future<StatementResult> describeAsync(BackendConnection backendConnection) {
+    ResultSet resultSet =
+        ClientSideResultSet.forRows(
+            Type.struct(StructField.of("current_setting", Type.string())), ImmutableList.of());
+    return Futures.immediateFuture(new QueryResult(resultSet));
+  }
+
+  @Override
+  public IntermediatePortalStatement createPortal(
+      String name,
+      byte[][] parameters,
+      List<Short> parameterFormatCodes,
+      List<Short> resultFormatCodes) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SelectSetConfigStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SelectSetConfigStatement.java
new file mode 100644
index 00000000..0a949eeb
--- /dev/null
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SelectSetConfigStatement.java
@@ -0,0 +1,151 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.statements;
+
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.Struct;
+import com.google.cloud.spanner.Type;
+import com.google.cloud.spanner.Type.StructField;
+import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
+import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType;
+import com.google.cloud.spanner.connection.StatementResult;
+import com.google.cloud.spanner.pgadapter.ConnectionHandler;
+import com.google.cloud.spanner.pgadapter.error.PGExceptionFactory;
+import com.google.cloud.spanner.pgadapter.error.SQLState;
+import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
+import com.google.cloud.spanner.pgadapter.parsers.BooleanParser;
+import com.google.cloud.spanner.pgadapter.statements.BackendConnection.QueryResult;
+import com.google.cloud.spanner.pgadapter.statements.SessionStatementParser.SetStatement;
+import com.google.cloud.spanner.pgadapter.statements.SimpleParser.QuotedString;
+import com.google.cloud.spanner.pgadapter.statements.SimpleParser.TableOrIndexName;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.Future;
+
+/**
+ * SELECT SET_CONFIG(setting_name text, new_value text, is_local boolean) is translated to the
+ * equivalent SET statement.
+ */
+public class SelectSetConfigStatement extends IntermediatePortalStatement {
+  private final SetStatement setStatement;
+
+  public SelectSetConfigStatement(
+      ConnectionHandler connectionHandler,
+      OptionsMetadata options,
+      String name,
+      ParsedStatement parsedStatement,
+      Statement originalStatement) {
+    super(
+        name,
+        new IntermediatePreparedStatement(
+            connectionHandler,
+            options,
+            name,
+            NO_PARAMETER_TYPES,
+            parsedStatement,
+            originalStatement),
+        NO_PARAMS,
+        ImmutableList.of(),
+        ImmutableList.of());
+    this.setStatement = parse(originalStatement.getSql());
+  }
+
+  static SetStatement parse(String sql) {
+    Preconditions.checkNotNull(sql);
+
+    // SELECT SET_CONFIG(setting_name text, new_value text, is_local boolean)
+    SimpleParser parser = new SimpleParser(sql);
+    if (!parser.eatKeyword("select", "set_config")) {
+      throw PGExceptionFactory.newPGException(
+          "not a valid SELECT SET_CONFIG statement: " + sql, SQLState.SyntaxError);
+    }
+    if (!parser.eatToken("(")) {
+      throw PGExceptionFactory.newPGException("missing '(' for set_config", SQLState.SyntaxError);
+    }
+    QuotedString setting = parser.readSingleQuotedString();
+    if (!parser.eatToken(",")) {
+      throw PGExceptionFactory.newPGException(
+          "missing ',' after setting name", SQLState.SyntaxError);
+    }
+    QuotedString value = parser.readSingleQuotedString();
+    if (!parser.eatToken(",")) {
+      throw PGExceptionFactory.newPGException(
+          "missing ',' after setting value", SQLState.SyntaxError);
+    }
+    String localString = parser.readKeyword().toLowerCase(Locale.ENGLISH);
+    boolean local = BooleanParser.toBoolean(localString);
+    if (!parser.eatToken(")")) {
+      throw PGExceptionFactory.newPGException("missing ')' for set_config", SQLState.SyntaxError);
+    }
+    parser.throwIfHasMoreTokens();
+
+    try {
+      TableOrIndexName settingName = TableOrIndexName.parse(setting.getValue());
+      return new SetStatement(local, settingName, value.rawValue);
+    } catch (IllegalArgumentException illegalArgumentException) {
+      throw PGExceptionFactory.newPGException(
+          illegalArgumentException.getMessage(), SQLState.SyntaxError);
+    }
+  }
+
+  @Override
+  public String getCommandTag() {
+    return "SELECT";
+  }
+
+  @Override
+  public StatementType getStatementType() {
+    return StatementType.CLIENT_SIDE;
+  }
+
+  @Override
+  public void executeAsync(BackendConnection backendConnection) {
+    this.executed = true;
+    try {
+      setStatement.execute(backendConnection.getSessionState());
+    } catch (Throwable throwable) {
+      setFutureStatementResult(Futures.immediateFailedFuture(throwable));
+      return;
+    }
+    setFutureStatementResult(
+        Futures.immediateFuture(
+            new QueryResult(
+                ClientSideResultSet.forRows(
+                    Type.struct(StructField.of("set_config", Type.string())),
+                    ImmutableList.of(
+                        Struct.newBuilder().set("set_config").to(setStatement.value).build())))));
+  }
+
+  @Override
+  public Future<StatementResult> describeAsync(BackendConnection backendConnection) {
+    ResultSet resultSet =
+        ClientSideResultSet.forRows(
+            Type.struct(StructField.of("set_config", Type.string())), ImmutableList.of());
+    return Futures.immediateFuture(new QueryResult(resultSet));
+  }
+
+  @Override
+  public IntermediatePortalStatement createPortal(
+      String name,
+      byte[][] parameters,
+      List<Short> parameterFormatCodes,
+      List<Short> resultFormatCodes) {
+    return this;
+  }
+}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SessionStatementParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SessionStatementParser.java
index 2bfc7a0a..63ca472a 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SessionStatementParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SessionStatementParser.java
@@ -172,26 +172,37 @@ public class SessionStatementParser {
   }
 
   static class ShowStatement extends SessionStatement {
+    final String header;
+    final boolean missingOk;
 
     static ShowStatement createShowAll() {
       return new ShowStatement(null);
     }
 
     ShowStatement(TableOrIndexName name) {
+      this(name, null, false);
+    }
+
+    ShowStatement(TableOrIndexName name, String header, boolean missingOk) {
       super(name);
+      this.header = header;
+      this.missingOk = missingOk;
     }
 
     @Override
     public StatementResult execute(SessionState sessionState) {
       if (name != null) {
+        String value;
+        if (missingOk) {
+          PGSetting pgSetting = sessionState.tryGet(extension, name);
+          value = pgSetting == null ? null : pgSetting.getSetting();
+        } else {
+          value = sessionState.get(extension, name).getSetting();
+        }
         return new QueryResult(
             ClientSideResultSet.forRows(
                 Type.struct(StructField.of(getKey(), Type.string())),
-                ImmutableList.of(
-                    Struct.newBuilder()
-                        .set(getKey())
-                        .to(sessionState.get(extension, name).getSetting())
-                        .build())));
+                ImmutableList.of(Struct.newBuilder().set(getKey()).to(value).build())));
       }
       return new QueryResult(
           ClientSideResultSet.forRows(
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleParser.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleParser.java
index eac80132..f926e086 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleParser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/SimpleParser.java
@@ -960,18 +960,22 @@ public class SimpleParser {
     return pos > startPos;
   }
 
+  @Nonnull
   QuotedString readSingleQuotedString() {
     return readQuotedString(SINGLE_QUOTE, false);
   }
 
+  @Nonnull
   QuotedString readDoubleQuotedString() {
     return readQuotedString(DOUBLE_QUOTE, false);
   }
 
+  @Nonnull
   QuotedString readQuotedString(char quote) {
     return readQuotedString(quote, false);
   }
 
+  @Nonnull
   QuotedString readQuotedString(char quote, boolean alwaysEscaped) {
     skipWhitespaces();
     if (pos >= sql.length()) {
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 08a4edaf..a10a767a 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
@@ -26,6 +26,8 @@ import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.PREPA
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.RELEASE;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.ROLLBACK;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SAVEPOINT;
+import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SELECT_CURRENT_SETTING;
+import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SELECT_SET_CONFIG;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.SHOW_DATABASE_DDL;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.TRUNCATE;
 import static com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage.VACUUM;
@@ -51,6 +53,8 @@ import com.google.cloud.spanner.pgadapter.statements.PrepareStatement;
 import com.google.cloud.spanner.pgadapter.statements.ReleaseStatement;
 import com.google.cloud.spanner.pgadapter.statements.RollbackToStatement;
 import com.google.cloud.spanner.pgadapter.statements.SavepointStatement;
+import com.google.cloud.spanner.pgadapter.statements.SelectCurrentSettingStatement;
+import com.google.cloud.spanner.pgadapter.statements.SelectSetConfigStatement;
 import com.google.cloud.spanner.pgadapter.statements.ShowDatabaseDdlStatement;
 import com.google.cloud.spanner.pgadapter.statements.TruncateStatement;
 import com.google.cloud.spanner.pgadapter.statements.VacuumStatement;
@@ -217,6 +221,20 @@ public class ParseMessage extends AbstractQueryProtocolMessage {
             name,
             parsedStatement,
             originalStatement);
+      } else if (isCommand(SELECT_CURRENT_SETTING, originalStatement.getSql())) {
+        return new SelectCurrentSettingStatement(
+            connectionHandler,
+            connectionHandler.getServer().getOptions(),
+            name,
+            parsedStatement,
+            originalStatement);
+      } else if (isCommand(SELECT_SET_CONFIG, originalStatement.getSql())) {
+        return new SelectSetConfigStatement(
+            connectionHandler,
+            connectionHandler.getServer().getOptions(),
+            name,
+            parsedStatement,
+            originalStatement);
       } else {
         return new IntermediatePreparedStatement(
             connectionHandler,
@@ -238,11 +256,15 @@ public class ParseMessage extends AbstractQueryProtocolMessage {
   }
 
   @Override
-  void buffer(BackendConnection backendConnection) {
+  void buffer(BackendConnection backendConnection) throws Exception {
     if (!Strings.isNullOrEmpty(this.name) && this.connection.hasStatement(this.name)) {
       throw new IllegalStateException("Must close statement before reusing name.");
     }
-    this.connection.registerStatement(this.name, this.statement);
+    if (this.statement.hasException()) {
+      handleError(statement.getException());
+    } else {
+      this.connection.registerStatement(this.name, this.statement);
+    }
   }
 
   @Override
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 0af8d90d..454288a5 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
@@ -40,6 +40,10 @@ public class QueryMessage extends ControlMessage {
   public static final String CLOSE = "CLOSE";
   public static final ImmutableList<String> SHOW_DATABASE_DDL =
       ImmutableList.of("SHOW", "DATABASE", "DDL");
+  public static final ImmutableList<String> SELECT_CURRENT_SETTING =
+      ImmutableList.of("SELECT", "CURRENT_SETTING");
+  public static final ImmutableList<String> SELECT_SET_CONFIG =
+      ImmutableList.of("SELECT", "SET_CONFIG");
   private final Statement originalStatement;
   private final SimpleQueryStatement simpleQueryStatement;
 


Test patch:
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
index 89d23906..3b1ca19e 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
@@ -4232,6 +4232,106 @@ public class JdbcMockServerTest extends AbstractMockServerTest {
     }
   }
 
+  @Test
+  public void testSelectSetConfigTimezone() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      try (ResultSet resultSet =
+          connection
+              .createStatement()
+              .executeQuery("select set_config('timezone', 'ist', false)")) {
+        assertTrue(resultSet.next());
+        assertEquals("ist", resultSet.getString("set_config"));
+        assertFalse(resultSet.next());
+      }
+      verifySettingValue(connection, "timezone", "Asia/Kolkata");
+    }
+  }
+
+  @Test
+  public void testSelectSetConfigInvalidTimezone() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      PSQLException exception =
+          assertThrows(
+              PSQLException.class,
+              () ->
+                  connection
+                      .createStatement()
+                      .executeQuery(
+                          "select set_config('timezone', 'non-existent-timezone', false)"));
+      assertNotNull(exception.getServerErrorMessage());
+      assertEquals(
+          exception.getServerErrorMessage().getSQLState(), SQLState.RaiseException.toString());
+    }
+  }
+
+  @Test
+  public void testSelectCurrentSettingTimezone() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      connection.createStatement().execute("set time zone 'IST'");
+      try (ResultSet resultSet =
+          connection.createStatement().executeQuery("select current_setting('timezone')")) {
+        assertTrue(resultSet.next());
+        assertEquals("Asia/Kolkata", resultSet.getString("current_setting"));
+        assertFalse(resultSet.next());
+      }
+    }
+  }
+
+  @Test
+  public void testSelectCurrentSettingInvalidName() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      PSQLException exception =
+          assertThrows(
+              PSQLException.class,
+              () ->
+                  connection
+                      .createStatement()
+                      .executeQuery("select current_setting('invalid-setting-name')"));
+      assertEquals(SQLState.SyntaxError.toString(), exception.getSQLState());
+    }
+  }
+
+  @Test
+  public void testSelectCurrentSettingMissingNotOk() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      PSQLException exception =
+          assertThrows(
+              PSQLException.class,
+              () ->
+                  connection
+                      .createStatement()
+                      .executeQuery("select current_setting('non_existing_setting')"));
+      assertEquals(SQLState.RaiseException.toString(), exception.getSQLState());
+    }
+  }
+
+  @Test
+  public void testSelectCurrentSettingMissingOk_settingIsMissing() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      try (ResultSet resultSet =
+          connection
+              .createStatement()
+              .executeQuery("select current_setting('non_existing_setting', true)")) {
+        assertTrue(resultSet.next());
+        assertNull(resultSet.getString("current_setting"));
+        assertFalse(resultSet.next());
+      }
+    }
+  }
+
+  @Test
+  public void testSelectCurrentSettingMissingOk_settingIsPresent() throws SQLException {
+    try (Connection connection = DriverManager.getConnection(createUrl())) {
+      connection.createStatement().execute("set time zone 'IST'");
+      try (ResultSet resultSet =
+          connection.createStatement().executeQuery("select current_setting('timezone', true)")) {
+        assertTrue(resultSet.next());
+        assertEquals("Asia/Kolkata", resultSet.getString("current_setting"));
+        assertFalse(resultSet.next());
+      }
+    }
+  }
+
   @Test
   public void testSetNames() throws SQLException {
     try (Connection connection = DriverManager.getConnection(createUrl())) {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/SelectCurrentSettingStatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/SelectCurrentSettingStatementTest.java
new file mode 100644
index 00000000..6dfbd328
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/SelectCurrentSettingStatementTest.java
@@ -0,0 +1,55 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.statements;
+
+import static com.google.cloud.spanner.pgadapter.statements.SelectCurrentSettingStatement.parse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.spanner.pgadapter.error.PGException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SelectCurrentSettingStatementTest {
+
+  @Test
+  public void testParse() {
+    assertEquals("foo", parse("select current_setting('foo')").name);
+    assertFalse(parse("select current_setting('foo')").missingOk);
+    assertFalse(parse("select current_setting('foo', off)").missingOk);
+    assertTrue(parse("select current_setting('foo', t)").missingOk);
+
+    assertEquals(
+        "spanner", parse("select current_setting('spanner.autocommit_dml_mode')").extension);
+    assertEquals(
+        "autocommit_dml_mode", parse("select current_setting('spanner.autocommit_dml_mode')").name);
+
+    assertThrows(PGException.class, () -> parse("select"));
+    assertThrows(PGException.class, () -> parse("select foo"));
+    assertThrows(PGException.class, () -> parse("select current_setting"));
+    assertThrows(PGException.class, () -> parse("select current_setting ("));
+    assertThrows(PGException.class, () -> parse("select current_setting ('foo', 'bar')"));
+    assertThrows(PGException.class, () -> parse("select current_setting ('foo', true, true)"));
+    assertThrows(PGException.class, () -> parse("select current_setting ('foo', true"));
+    assertThrows(PGException.class, () -> parse("select current_setting ('foo', true, 1)"));
+    assertThrows(PGException.class, () -> parse("select current_setting ('foo', true), 1"));
+    assertThrows(PGException.class, () -> parse("select current_setting ('foo.bar.baz', true)"));
+    assertThrows(PGException.class, () -> parse("select current_setting('foo-bar')"));
+  }
+}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/SelectSetConfigStatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/SelectSetConfigStatementTest.java
new file mode 100644
index 00000000..71be39f3
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/SelectSetConfigStatementTest.java
@@ -0,0 +1,63 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.statements;
+
+import static com.google.cloud.spanner.pgadapter.statements.SelectSetConfigStatement.parse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import com.google.cloud.spanner.pgadapter.error.PGException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SelectSetConfigStatementTest {
+
+  @Test
+  public void testParse() {
+    assertEquals("foo", parse("select set_config('foo', 'bar', false)").name);
+    assertEquals("bar", parse("select set_config('foo', 'bar', false)").value);
+    assertFalse(parse("select set_config('foo', 'bar', false)").local);
+    assertTrue(parse("select set_config('foo', 'bar', 1)").local);
+
+    assertEquals(
+        "spanner",
+        parse("select set_config('spanner.autocommit_dml_mode', 'partitioned_non_atomic', false)")
+            .extension);
+    assertEquals(
+        "autocommit_dml_mode",
+        parse("select set_config('spanner.autocommit_dml_mode', 'partitioned_non_atomic', false)")
+            .name);
+    assertEquals(
+        "partitioned_non_atomic",
+        parse("select set_config('spanner.autocommit_dml_mode', 'partitioned_non_atomic', false)")
+            .value);
+
+    assertThrows(PGException.class, () -> parse("select"));
+    assertThrows(PGException.class, () -> parse("select foo"));
+    assertThrows(PGException.class, () -> parse("select set_config"));
+    assertThrows(PGException.class, () -> parse("select set_config ("));
+    assertThrows(PGException.class, () -> parse("select set_config ('foo')"));
+    assertThrows(PGException.class, () -> parse("select set_config ('foo', 'bar')"));
+    assertThrows(PGException.class, () -> parse("select set_config ('foo', true, true)"));
+    assertThrows(PGException.class, () -> parse("select set_config ('foo', 'bar', true"));
+    assertThrows(PGException.class, () -> parse("select set_config ('foo', 'bar', true, 1)"));
+    assertThrows(PGException.class, () -> parse("select set_config ('foo', 'bar', true), 1"));
+    assertThrows(PGException.class, () -> parse("select set_config ('foo.bar.baz', 'bar', true)"));
+  }
+}