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 0000000000..2943da2972 --- /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 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 parameterFormatCodes, + List 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 0000000000..0a949eebb8 --- /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 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 parameterFormatCodes, + List 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 2bfc7a0a3d..63ca472a4d 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 String toString() { } 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 eac80132cb..f926e0867f 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 @@ boolean skipNumericLiteral() { 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 08a4edafd2..a10a767a78 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.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.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 @@ static IntermediatePreparedStatement createStatement( 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 @@ static IntermediatePreparedStatement createStatement( } @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 0af8d90d87..454288a5a6 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 SHOW_DATABASE_DDL = ImmutableList.of("SHOW", "DATABASE", "DDL"); + public static final ImmutableList SELECT_CURRENT_SETTING = + ImmutableList.of("SELECT", "CURRENT_SETTING"); + public static final ImmutableList SELECT_SET_CONFIG = + ImmutableList.of("SELECT", "SET_CONFIG"); private final Statement originalStatement; private final SimpleQueryStatement simpleQueryStatement; 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 89d2390688..3b1ca19ef9 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 void testSetTimeZoneWithTransactionRollback() throws SQLException { } } + @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 0000000000..6dfbd3287f --- /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 0000000000..71be39f38b --- /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)")); + } +}