From 68edbd43ca049ce8284fe34d9a71a0da785a0965 Mon Sep 17 00:00:00 2001 From: Jun Nemoto Date: Wed, 11 Jun 2025 17:53:38 +0900 Subject: [PATCH 1/2] Add PartiQL parser (GetHistory and ShowTables) --- .../table/v1_0_0/Constants.java | 6 +- .../GenericContractTableEndToEndTest.java | 29 ++- .../table/v1_0_0/GetHistory.java | 96 ++++++++-- .../table/v1_0_0/GetHistoryTest.java | 172 +++++++++++++++--- .../client/error/TableStoreClientError.java | 18 ++ .../partiql/parser/PartiqlParserVisitor.java | 120 +++++++++++- .../partiql/parser/ScalarPartiqlParser.java | 4 + .../statement/GetHistoryStatement.java | 72 ++++++++ .../statement/ShowTablesStatement.java | 69 +++++++ .../tablestore/client/util/JacksonUtils.java | 16 ++ .../parser/PartiqlParserVisitorTest.java | 86 +++++++++ .../statement/GetHistoryStatementTest.java | 52 ++++++ .../statement/ShowTablesStatementTest.java | 34 ++++ 13 files changed, 717 insertions(+), 57 deletions(-) create mode 100644 table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatement.java create mode 100644 table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatement.java create mode 100644 table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatementTest.java create mode 100644 table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatementTest.java diff --git a/common/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/Constants.java b/common/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/Constants.java index 83417948..091bb93f 100644 --- a/common/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/Constants.java +++ b/common/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/Constants.java @@ -11,6 +11,8 @@ public class Constants { public static final String CONTRACT_INSERT = PACKAGE + "." + VERSION + ".Insert"; public static final String CONTRACT_SELECT = PACKAGE + "." + VERSION + ".Select"; public static final String CONTRACT_UPDATE = PACKAGE + "." + VERSION + ".Update"; + public static final String CONTRACT_SHOW_TABLES = PACKAGE + "." + VERSION + ".ShowTables"; + public static final String CONTRACT_GET_HISTORY = PACKAGE + "." + VERSION + ".GetHistory"; public static final String CONTRACT_GET_ASSET_ID = PACKAGE + "." + VERSION + ".GetAssetId"; public static final String CONTRACT_SCAN = PACKAGE + "." + VERSION + ".Scan"; @@ -37,13 +39,13 @@ public class Constants { public static final String QUERY_JOINS = "joins"; public static final String QUERY_CONDITIONS = "conditions"; public static final String QUERY_PROJECTIONS = "projections"; + public static final String QUERY_LIMIT = "limit"; public static final String JOIN_TABLE = "table"; public static final String JOIN_LEFT_KEY = "left"; public static final String JOIN_RIGHT_KEY = "right"; public static final String UPDATE_TABLE = "table"; public static final String UPDATE_VALUES = "values"; public static final String UPDATE_CONDITIONS = "conditions"; - public static final String HISTORY_LIMIT = "limit"; public static final String HISTORY_ASSET_AGE = "age"; public static final String ALIAS_NAME = "name"; public static final String ALIAS_AS = "alias"; @@ -100,6 +102,8 @@ public class Constants { public static final String INVALID_JOIN_COLUMN = "The join column in the right table must be the primary key or index key. Column: "; public static final String INVALID_OBJECT_NAME = "The specified name is invalid: "; + public static final String INVALID_HISTORY_QUERY_CONDITION = + "The specified condition array is invalid. It must have a single equality condition for the primary key."; public static final String TABLE_ALREADY_EXISTS = "The specified table already exists."; public static final String TABLE_NOT_EXIST = "The specified table does not exist. Table: "; public static final String TABLE_AMBIGUOUS = diff --git a/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractTableEndToEndTest.java b/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractTableEndToEndTest.java index 87758d45..bf698526 100644 --- a/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractTableEndToEndTest.java +++ b/generic-contracts/src/integration-test/java/com/scalar/dl/genericcontracts/GenericContractTableEndToEndTest.java @@ -1945,15 +1945,17 @@ public void showTables_NonExistingTableNameGiven_ShouldThrowContractContextExcep @Test public void getHistory_WithoutLimitAndWithLimitGiven_ShouldReturnRecordAgesCorrectly() { // Arrange - String keyString = "key1"; + JsonNode key = TextNode.valueOf("key1"); JsonNode argumentsWithoutLimit = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, COMMON_TEST_TABLE) - .put(Constants.RECORD_KEY, keyString); + .put(Constants.QUERY_TABLE, COMMON_TEST_TABLE) + .set( + Constants.QUERY_CONDITIONS, + prepareArrayNode( + ImmutableList.of(prepareCondition(COLUMN_NAME_1, key, Constants.OPERATOR_EQ)))); ObjectNode argumentsWithLimit = argumentsWithoutLimit.deepCopy(); - argumentsWithLimit.put(Constants.HISTORY_LIMIT, 2); - JsonNode key = TextNode.valueOf(keyString); + argumentsWithLimit.put(Constants.QUERY_LIMIT, 2); JsonNode value0 = IntNode.valueOf(0); JsonNode value1 = IntNode.valueOf(1); JsonNode value2 = IntNode.valueOf(2); @@ -2006,8 +2008,13 @@ public void getHistory_NonExistingRecordGiven_ShouldReturnEmpty() { JsonNode arguments = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, COMMON_TEST_TABLE) - .put(Constants.RECORD_KEY, "key"); + .put(Constants.QUERY_TABLE, COMMON_TEST_TABLE) + .set( + Constants.QUERY_CONDITIONS, + prepareArrayNode( + ImmutableList.of( + prepareCondition( + COLUMN_NAME_1, TextNode.valueOf("key"), Constants.OPERATOR_EQ)))); // Act ContractExecutionResult actual = @@ -2026,8 +2033,12 @@ public void getHistory_InvalidKeyTypeGiven_ShouldReturnEmpty() { JsonNode arguments = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, COMMON_TEST_TABLE) - .set(Constants.RECORD_KEY, value); + .put(Constants.QUERY_TABLE, COMMON_TEST_TABLE) + .set( + Constants.QUERY_CONDITIONS, + prepareArrayNode( + ImmutableList.of( + prepareCondition(COLUMN_NAME_1, value, Constants.OPERATOR_EQ)))); // Act Assert assertThatThrownBy(() -> clientService.executeContract(CONTRACT_ID_GET_HISTORY, arguments)) diff --git a/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistory.java b/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistory.java index 8f994a9e..9dcf35f2 100644 --- a/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistory.java +++ b/generic-contracts/src/main/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistory.java @@ -19,20 +19,13 @@ public JsonNode invoke( Ledger ledger, JsonNode arguments, @Nullable JsonNode properties) { // Check the arguments - if (arguments.size() < 2 - || arguments.size() > 3 - || !arguments.has(Constants.RECORD_TABLE) - || !arguments.get(Constants.RECORD_TABLE).isTextual() - || !arguments.has(Constants.RECORD_KEY)) { - throw new ContractContextException(Constants.INVALID_CONTRACT_ARGUMENTS); - } + validateQuery(arguments); - JsonNode tableName = arguments.get(Constants.RECORD_TABLE); + JsonNode tableName = arguments.get(Constants.QUERY_TABLE); if (!isSupportedObjectName(tableName.asText())) { throw new ContractContextException(Constants.INVALID_OBJECT_NAME + tableName.asText()); } - // Get the table metadata JsonNode table = ledger .get(getAssetId(ledger, Constants.PREFIX_TABLE, tableName)) @@ -41,20 +34,25 @@ public JsonNode invoke( .data(); // Check the key type + JsonNode condition = arguments.get(Constants.QUERY_CONDITIONS).get(0); + String conditionColumn = getColumnName(condition.get(Constants.CONDITION_COLUMN).asText()); + JsonNode conditionValue = condition.get(Constants.CONDITION_VALUE); JsonNode keyColumn = table.get(Constants.TABLE_KEY); - JsonNode keyValue = arguments.get(Constants.RECORD_KEY); + if (!keyColumn.asText().equals(conditionColumn)) { + throw new ContractContextException(Constants.INVALID_HISTORY_QUERY_CONDITION); + } String keyType = table.get(Constants.TABLE_KEY_TYPE).asText(); - String givenType = keyValue.getNodeType().name(); + String givenType = conditionValue.getNodeType().name(); if (!givenType.equalsIgnoreCase(keyType)) { throw new ContractContextException(Constants.INVALID_KEY_TYPE + givenType); } // Prepare scan for the asset of the record String recordAssetId = - getAssetId(ledger, Constants.PREFIX_RECORD, tableName, keyColumn, keyValue); + getAssetId(ledger, Constants.PREFIX_RECORD, tableName, keyColumn, conditionValue); AssetFilter filter = new AssetFilter(recordAssetId).withAgeOrder(AgeOrder.DESC); - if (arguments.has(Constants.HISTORY_LIMIT)) { - filter.withLimit(validateAndGetLimit(arguments.get(Constants.HISTORY_LIMIT))); + if (arguments.has(Constants.QUERY_LIMIT)) { + filter.withLimit(validateAndGetLimit(arguments.get(Constants.QUERY_LIMIT))); } // Get history of the record @@ -72,6 +70,76 @@ public JsonNode invoke( return history; } + private void validateQuery(JsonNode arguments) { + // Check the required fields + if (arguments.size() < 2 + || arguments.size() > 3 + || !arguments.has(Constants.QUERY_TABLE) + || !arguments.get(Constants.QUERY_TABLE).isTextual() + || !arguments.has(Constants.QUERY_CONDITIONS)) { + throw new ContractContextException(Constants.INVALID_CONTRACT_ARGUMENTS); + } + + String tableReference = arguments.get(Constants.QUERY_TABLE).asText(); + validateConditions(tableReference, arguments.get(Constants.QUERY_CONDITIONS)); + } + + private void validateConditions(String tableReference, JsonNode conditions) { + if (!conditions.isArray() || conditions.size() != 1) { + throw new ContractContextException(Constants.INVALID_HISTORY_QUERY_CONDITION); + } + + JsonNode condition = conditions.get(0); + if (!condition.isObject() + || !condition.has(Constants.CONDITION_COLUMN) + || !condition.get(Constants.CONDITION_COLUMN).isTextual() + || !condition.has(Constants.CONDITION_OPERATOR) + || !condition.get(Constants.CONDITION_OPERATOR).isTextual()) { + throw new ContractContextException(Constants.INVALID_HISTORY_QUERY_CONDITION); + } + + if (!condition + .get(Constants.CONDITION_OPERATOR) + .asText() + .equalsIgnoreCase(Constants.OPERATOR_EQ)) { + throw new ContractContextException(Constants.INVALID_HISTORY_QUERY_CONDITION); + } + + // Check the column + validateColumn(tableReference, condition.get(Constants.CONDITION_COLUMN).asText()); + } + + private boolean isColumnName(String column) { + return Constants.OBJECT_NAME.matcher(column).matches(); + } + + private boolean isColumnReference(String column) { + return Constants.COLUMN_REFERENCE.matcher(column).matches(); + } + + private String getColumnName(String column) { + return isColumnReference(column) + ? column.substring(column.indexOf(Constants.COLUMN_SEPARATOR) + 1) + : column; + } + + private String getTableReference(String column) { + return column.substring(0, column.indexOf(Constants.COLUMN_SEPARATOR)); + } + + private void validateColumn(String tableReference, String column) { + if (isColumnReference(column)) { + String specifiedTableReference = getTableReference(column); + if (!specifiedTableReference.equals(tableReference)) { + throw new ContractContextException(Constants.UNKNOWN_TABLE + specifiedTableReference); + } + } else { + if (!isColumnName(column)) { + throw new ContractContextException(Constants.INVALID_COLUMN_FORMAT + column); + } + } + } + private Integer validateAndGetLimit(JsonNode limit) { if (!limit.isInt() || limit.asInt() < 0) { throw new ContractContextException(Constants.INVALID_CONTRACT_ARGUMENTS); diff --git a/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistoryTest.java b/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistoryTest.java index c73303e3..8c12c5b8 100644 --- a/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistoryTest.java +++ b/generic-contracts/src/test/java/com/scalar/dl/genericcontracts/table/v1_0_0/GetHistoryTest.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.TextNode; import com.google.common.collect.ImmutableList; @@ -20,6 +21,7 @@ import com.scalar.dl.ledger.exception.ContractContextException; import com.scalar.dl.ledger.statemachine.Asset; import com.scalar.dl.ledger.statemachine.Ledger; +import java.util.Arrays; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,7 +38,9 @@ public class GetHistoryTest { private static final String SOME_COLUMN = "column"; private static final String SOME_COLUMN_VALUE_1 = "value1"; private static final String SOME_COLUMN_VALUE_2 = "value2"; + private static final String SOME_UNKNOWN_TABLE_NAME = "unknown_table"; private static final String SOME_INVALID_TABLE_NAME = "invalid-table-name"; + private static final String SOME_INVALID_COLUMN_NAME = "column-name"; private static final String SOME_INVALID_FIELD = "filed"; private static final String SOME_INVALID_VALUE = "value"; private static final String SOME_KEY_TYPE_STRING = "string"; @@ -59,6 +63,24 @@ private static JsonNode createTable(String tableName) { .put(Constants.TABLE_KEY_TYPE, SOME_KEY_TYPE_STRING); } + private ArrayNode createArrayNode(JsonNode... jsonNodes) { + ArrayNode result = mapper.createArrayNode(); + Arrays.stream(jsonNodes).forEach(result::add); + return result; + } + + private static JsonNode createCondition(String column, String value, String operator) { + return createCondition(column, TextNode.valueOf(value), operator); + } + + private static JsonNode createCondition(String column, JsonNode value, String operator) { + return mapper + .createObjectNode() + .put(Constants.CONDITION_COLUMN, column) + .put(Constants.CONDITION_OPERATOR, operator) + .set(Constants.CONDITION_VALUE, value); + } + private String prepareTableAssetId(String tableName) { String assetId = GetAssetId.getAssetIdForTable(tableName); doReturn(assetId) @@ -86,9 +108,12 @@ public void invoke_CorrectArgumentsGiven_ShouldShowSingleTable() { JsonNode argument = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_TABLE_NAME) - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE) - .put(Constants.HISTORY_LIMIT, SOME_LIMIT); + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .put(Constants.QUERY_LIMIT, SOME_LIMIT) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); String recordAssetId = prepareRecordAssetId( SOME_TABLE_NAME, SOME_TABLE_KEY, TextNode.valueOf(SOME_RECORD_KEY_VALUE)); @@ -142,29 +167,35 @@ public void invoke_CorrectArgumentsGiven_ShouldShowSingleTable() { @Test public void invoke_InvalidArgumentsGiven_ShouldThrowContractContextException() { // Arrange - JsonNode argument1 = mapper.createObjectNode().put(Constants.RECORD_TABLE, SOME_TABLE_NAME); + JsonNode argument1 = mapper.createObjectNode().put(Constants.QUERY_TABLE, SOME_TABLE_NAME); JsonNode argument2 = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_TABLE_NAME) - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE) - .put(Constants.RECORD_VALUES, SOME_INVALID_VALUE) + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .put(Constants.QUERY_CONDITIONS, SOME_RECORD_KEY_VALUE) + .put(Constants.QUERY_LIMIT, SOME_INVALID_VALUE) .put(SOME_INVALID_FIELD, SOME_INVALID_VALUE); JsonNode argument3 = mapper .createObjectNode() - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE) - .put(Constants.HISTORY_LIMIT, 0); + .put(Constants.QUERY_LIMIT, 0) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); JsonNode argument4 = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, 0) - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE); + .put(Constants.QUERY_TABLE, 0) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); JsonNode argument5 = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_TABLE_NAME) - .put(Constants.HISTORY_LIMIT, 10); + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .put(Constants.QUERY_LIMIT, 10); // Act Assert assertThatThrownBy(() -> getHistory.invoke(ledger, argument1, null)) @@ -186,6 +217,83 @@ public void invoke_InvalidArgumentsGiven_ShouldThrowContractContextException() { verify(ledger, never()).scan(any()); } + @Test + public void invoke_InvalidConditionsGiven_ShouldThrowContractContextException() { + // Arrange + JsonNode argument1 = // Non-array conditions + mapper + .createObjectNode() + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .put(Constants.QUERY_CONDITIONS, SOME_RECORD_KEY_VALUE); + JsonNode argument2 = // Too many conditions + mapper + .createObjectNode() + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ), + createCondition(SOME_TABLE_KEY, SOME_INVALID_VALUE, Constants.OPERATOR_EQ))); + JsonNode argument3 = // Invalid condition format + mapper + .createObjectNode() + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + mapper.createObjectNode().put(Constants.CONDITION_COLUMN, SOME_COLUMN))); + JsonNode argument4 = // Non-equality condition + mapper + .createObjectNode() + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_INVALID_VALUE, Constants.OPERATOR_NE))); + JsonNode argument5 = // Unknown table reference in condition column + mapper + .createObjectNode() + .put(Constants.QUERY_TABLE, SOME_COLUMN) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition( + SOME_UNKNOWN_TABLE_NAME + Constants.COLUMN_SEPARATOR + SOME_TABLE_KEY, + SOME_INVALID_VALUE, + Constants.OPERATOR_EQ))); + JsonNode argument6 = // Invalid column name format + mapper + .createObjectNode() + .put(Constants.QUERY_TABLE, SOME_COLUMN) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition( + SOME_INVALID_COLUMN_NAME, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); + + // Act Assert + assertThatThrownBy(() -> getHistory.invoke(ledger, argument1, null)) + .isExactlyInstanceOf(ContractContextException.class) + .hasMessage(Constants.INVALID_HISTORY_QUERY_CONDITION); + assertThatThrownBy(() -> getHistory.invoke(ledger, argument2, null)) + .isExactlyInstanceOf(ContractContextException.class) + .hasMessage(Constants.INVALID_HISTORY_QUERY_CONDITION); + assertThatThrownBy(() -> getHistory.invoke(ledger, argument3, null)) + .isExactlyInstanceOf(ContractContextException.class) + .hasMessage(Constants.INVALID_HISTORY_QUERY_CONDITION); + assertThatThrownBy(() -> getHistory.invoke(ledger, argument4, null)) + .isExactlyInstanceOf(ContractContextException.class) + .hasMessage(Constants.INVALID_HISTORY_QUERY_CONDITION); + assertThatThrownBy(() -> getHistory.invoke(ledger, argument5, null)) + .isExactlyInstanceOf(ContractContextException.class) + .hasMessage(Constants.UNKNOWN_TABLE + SOME_UNKNOWN_TABLE_NAME); + assertThatThrownBy(() -> getHistory.invoke(ledger, argument6, null)) + .isExactlyInstanceOf(ContractContextException.class) + .hasMessage(Constants.INVALID_COLUMN_FORMAT + SOME_INVALID_COLUMN_NAME); + verify(ledger, never()).get(any()); + verify(ledger, never()).scan(any()); + } + @Test public void invoke_InvalidKeyTypeGiven_ShouldThrowContractContextException() { // Arrange @@ -193,8 +301,10 @@ public void invoke_InvalidKeyTypeGiven_ShouldThrowContractContextException() { JsonNode argument = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_TABLE_NAME) - .set(Constants.RECORD_KEY, key); + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode(createCondition(SOME_TABLE_KEY, key, Constants.OPERATOR_EQ))); String tableAssetId = prepareTableAssetId(SOME_TABLE_NAME); Asset tableAsset = (Asset) mock(Asset.class); when(tableAsset.data()).thenReturn(SOME_TABLE); @@ -214,15 +324,21 @@ public void invoke_InvalidLimitGiven_ShouldThrowContractContextException() { JsonNode argument1 = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_TABLE_NAME) - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE) - .put(Constants.HISTORY_LIMIT, 1.23); + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .put(Constants.QUERY_LIMIT, 1.23) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); JsonNode argument2 = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_TABLE_NAME) - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE) - .put(Constants.HISTORY_LIMIT, -1); + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .put(Constants.QUERY_LIMIT, -1) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); String tableAssetId = prepareTableAssetId(SOME_TABLE_NAME); Asset tableAsset = (Asset) mock(Asset.class); when(tableAsset.data()).thenReturn(SOME_TABLE); @@ -246,8 +362,11 @@ public void invoke_NonExistingTableGiven_ShouldThrowContractContextException() { JsonNode argument = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_TABLE_NAME) - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE); + .put(Constants.QUERY_TABLE, SOME_TABLE_NAME) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); String tableAssetId = prepareTableAssetId(SOME_TABLE_NAME); when(ledger.get(tableAssetId)).thenReturn(Optional.empty()); @@ -264,8 +383,11 @@ public void invoke_UnsupportedTableNameGiven_ShouldThrowContractContextException JsonNode argument = mapper .createObjectNode() - .put(Constants.RECORD_TABLE, SOME_INVALID_TABLE_NAME) - .put(Constants.RECORD_KEY, SOME_RECORD_KEY_VALUE); + .put(Constants.QUERY_TABLE, SOME_INVALID_TABLE_NAME) + .set( + Constants.QUERY_CONDITIONS, + createArrayNode( + createCondition(SOME_TABLE_KEY, SOME_RECORD_KEY_VALUE, Constants.OPERATOR_EQ))); // Act Assert assertThatThrownBy(() -> getHistory.invoke(ledger, argument, null)) diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java index 53c2d867..0a67ac0f 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java @@ -170,6 +170,24 @@ public enum TableStoreClientError implements ScalarDlError { "The limit clause is not supported except in the history query.", "", ""), + TABLE_ALIAS_NOT_SUPPORTED( + StatusCode.INVALID_ARGUMENT, + "028", + "The table alias not supported in the information schema and history query.", + "", + ""), + PROJECTION_NOT_SUPPORTED_FOR_INFORMATION_SCHEMA_QUERY( + StatusCode.INVALID_ARGUMENT, + "029", + "Projection is not supported for the information schema query. Specify '*' instead.", + "", + ""), + INVALID_CONDITION_FOR_INFORMATION_SCHEMA_QUERY( + StatusCode.INVALID_ARGUMENT, + "030", + "The specified condition for information schema query is invalid.", + "", + ""), ; private static final String COMPONENT_NAME = "DL-TABLE-STORE"; diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitor.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitor.java index 7ca834d9..c4d08013 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitor.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitor.java @@ -1,5 +1,9 @@ package com.scalar.dl.tablestore.client.partiql.parser; +import static com.scalar.dl.tablestore.client.partiql.parser.ScalarPartiqlParser.HISTORY_FUNCTION; +import static com.scalar.dl.tablestore.client.partiql.parser.ScalarPartiqlParser.INFORMATION_SCHEMA; +import static com.scalar.dl.tablestore.client.partiql.parser.ScalarPartiqlParser.INFORMATION_SCHEMA_TABLES; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.BigIntegerNode; @@ -15,13 +19,16 @@ import com.scalar.dl.tablestore.client.partiql.DataType; import com.scalar.dl.tablestore.client.partiql.statement.ContractStatement; import com.scalar.dl.tablestore.client.partiql.statement.CreateTableStatement; +import com.scalar.dl.tablestore.client.partiql.statement.GetHistoryStatement; import com.scalar.dl.tablestore.client.partiql.statement.InsertStatement; import com.scalar.dl.tablestore.client.partiql.statement.SelectStatement; +import com.scalar.dl.tablestore.client.partiql.statement.ShowTablesStatement; import com.scalar.dl.tablestore.client.partiql.statement.UpdateStatement; import com.scalar.dl.tablestore.client.util.JacksonUtils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; import java.util.List; import org.partiql.ast.AstNode; import org.partiql.ast.AstVisitor; @@ -50,6 +57,7 @@ import org.partiql.ast.expr.Expr; import org.partiql.ast.expr.ExprAnd; import org.partiql.ast.expr.ExprArray; +import org.partiql.ast.expr.ExprCall; import org.partiql.ast.expr.ExprLit; import org.partiql.ast.expr.ExprNullPredicate; import org.partiql.ast.expr.ExprOperator; @@ -211,15 +219,25 @@ private JsonNode getTable(FromExpr table) { } } - private String getColumnReference(ExprPath exprPath) { - if (exprPath.getSteps().size() == 1) { - String tableReference = extractNameFromExpr(exprPath.getRoot()); - PathStep step = exprPath.getSteps().get(0); + private List getPath(ExprPath exprPath) { + List path = new ArrayList<>(); + path.add(extractNameFromExpr(exprPath.getRoot())); + for (PathStep step : exprPath.getSteps()) { if (step instanceof PathStep.Field) { - String column = ((PathStep.Field) step).getField().getText(); - return JacksonUtils.buildColumnReference(tableReference, column); + path.add(((PathStep.Field) step).getField().getText()); + } else { + throw new IllegalArgumentException( + TableStoreClientError.SYNTAX_ERROR_INVALID_EXPRESSION.buildMessage(toSql(exprPath))); } } + return path; + } + + private String getColumnReference(ExprPath exprPath) { + List path = getPath(exprPath); + if (path.size() == 2) { + return JacksonUtils.buildColumnReference(path.get(0), path.get(1)); + } throw new IllegalArgumentException( TableStoreClientError.SYNTAX_ERROR_INVALID_COLUMN.buildMessage(toSql(exprPath))); @@ -288,6 +306,40 @@ private List getJoins(FromJoin fromJoin) { TableStoreClientError.SYNTAX_ERROR_INVALID_JOIN_TYPE.buildMessage()); } + private boolean isInformationSchemaQuery(FromTableRef tableRef) { + if (tableRef instanceof FromExpr) { + Expr expr = ((FromExpr) tableRef).getExpr(); + if (expr instanceof ExprPath) { + List path = getPath((ExprPath) expr); + return path.size() == 2 + && path.get(0).equals(INFORMATION_SCHEMA) + && path.get(1).equals(INFORMATION_SCHEMA_TABLES); + } + } + + return false; + } + + private boolean isHistoryQuery(Select select, FromTableRef from) { + if (select instanceof SelectList && from instanceof FromExpr) { + SelectList selectList = (SelectList) select; + if (selectList.getItems().size() == 1) { + SelectItem item = selectList.getItems().get(0); + if (item instanceof SelectItem.Expr) { + SelectItem.Expr selectItemExpr = (SelectItem.Expr) item; + Expr expr = selectItemExpr.getExpr(); + if (expr instanceof ExprCall && selectItemExpr.getAsAlias() == null) { + ExprCall call = (ExprCall) expr; + return call.getFunction().getIdentifier().getText().equals(HISTORY_FUNCTION) + && call.getArgs().isEmpty(); + } + } + } + } + + return false; + } + private List getProjections(Select select) { if (select instanceof SelectStar) { SelectStar selectStar = (SelectStar) select; @@ -321,6 +373,20 @@ private List getProjections(Select select) { TableStoreClientError.SYNTAX_ERROR_INVALID_SELECT_STATEMENT.buildMessage()); } + private String getTableNameForShowTables(List conditions) { + if (conditions.isEmpty()) { + return null; + } else if (conditions.size() == 1) { + JsonNode condition = conditions.get(0); + if (JacksonUtils.isConditionForShowTables(condition)) { + return JacksonUtils.getValueFromCondition(condition).asText(); + } + } + + throw new IllegalArgumentException( + TableStoreClientError.INVALID_CONDITION_FOR_INFORMATION_SCHEMA_QUERY.buildMessage()); + } + private int getLimit(Expr expr) { if (expr == null) { return 0; @@ -377,6 +443,25 @@ private void validateSfw(QueryBody.SFW sfw) { } } + private void validateInformationSchemaQuery(Select select, FromTableRef tableRef) { + FromExpr table = (FromExpr) tableRef; + if (table.getFromType().code() != FromType.SCAN || table.getAtAlias() != null) { + throw new IllegalArgumentException( + TableStoreClientError.SYNTAX_ERROR_INVALID_TABLE.buildMessage(INFORMATION_SCHEMA_TABLES)); + } + + if (table.getAsAlias() != null) { + throw new IllegalArgumentException( + TableStoreClientError.TABLE_ALIAS_NOT_SUPPORTED.buildMessage()); + } + + if (!(select instanceof SelectStar)) { + throw new IllegalArgumentException( + TableStoreClientError.PROJECTION_NOT_SUPPORTED_FOR_INFORMATION_SCHEMA_QUERY + .buildMessage()); + } + } + @Override public List visitExprQuerySet(ExprQuerySet astNode, Void context) { validateExprQuerySet(astNode); @@ -394,14 +479,33 @@ public List visitExprQuerySet(ExprQuerySet astNode, Void cont .buildMessage()); } + FromTableRef tableRef = from.getTableRefs().get(0); + List conditions = getConditions(sfw.getWhere()); + + if (isInformationSchemaQuery(tableRef)) { + validateInformationSchemaQuery(select, tableRef); + String tableName = getTableNameForShowTables(conditions); + return tableName == null + ? ImmutableList.of(ShowTablesStatement.create()) + : ImmutableList.of(ShowTablesStatement.create(tableName)); + } + int limit = getLimit(astNode.getLimit()); + if (isHistoryQuery(select, tableRef)) { + JsonNode table = getTable((FromExpr) tableRef); + if (table.isObject()) { + throw new IllegalArgumentException( + TableStoreClientError.TABLE_ALIAS_NOT_SUPPORTED.buildMessage()); + } + + return ImmutableList.of(GetHistoryStatement.create(table, conditions, limit)); + } + if (limit > 0) { throw new IllegalArgumentException( TableStoreClientError.LIMIT_CLAUSE_NOT_SUPPORTED.buildMessage()); } - FromTableRef tableRef = from.getTableRefs().get(0); - List conditions = getConditions(sfw.getWhere()); List projections = getProjections(select); if (tableRef instanceof FromJoin) { List joins = getJoins((FromJoin) tableRef); diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/ScalarPartiqlParser.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/ScalarPartiqlParser.java index 78edc3a9..2166b070 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/ScalarPartiqlParser.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/parser/ScalarPartiqlParser.java @@ -14,6 +14,10 @@ @ThreadSafe public class ScalarPartiqlParser { + public static final String HISTORY_FUNCTION = "history"; + public static final String INFORMATION_SCHEMA = "information_schema"; + public static final String INFORMATION_SCHEMA_TABLES = "tables"; + public static final String INFORMATION_SCHEMA_TABLE_NAME = "table_name"; private static final PartiQLParser parser = PartiQLParser.standard(); private static final PErrorListener errorListener = diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatement.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatement.java new file mode 100644 index 00000000..9d238cdb --- /dev/null +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatement.java @@ -0,0 +1,72 @@ +package com.scalar.dl.tablestore.client.partiql.statement; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.base.MoreObjects; +import com.scalar.dl.genericcontracts.table.v1_0_0.Constants; +import com.scalar.dl.tablestore.client.util.JacksonUtils; +import java.util.List; +import java.util.Objects; + +public class GetHistoryStatement extends AbstractJacksonBasedContractStatement { + + private final String contractId; + private final JsonNode arguments; + + private GetHistoryStatement(JsonNode arguments) { + this.contractId = Constants.CONTRACT_GET_HISTORY; + this.arguments = Objects.requireNonNull(arguments); + } + + @Override + public String getContractId() { + return contractId; + } + + @Override + public String getArguments() { + return jacksonSerDe.serialize(arguments); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("contractId", getContractId()) + .add("arguments", getArguments()) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof GetHistoryStatement)) { + return false; + } + GetHistoryStatement that = (GetHistoryStatement) o; + return Objects.equals(arguments, that.arguments); + } + + @Override + public int hashCode() { + return Objects.hash(arguments); + } + + private static JsonNode buildArguments(JsonNode table, List conditions, int limit) { + ObjectNode arguments = JacksonUtils.createObjectNode(); + arguments.set(Constants.QUERY_TABLE, table); + ArrayNode conditionArray = jacksonSerDe.getObjectMapper().createArrayNode(); + conditions.forEach(conditionArray::add); + arguments.set(Constants.QUERY_CONDITIONS, conditionArray); + if (limit > 0) { + arguments.put(Constants.QUERY_LIMIT, limit); + } + return arguments; + } + + public static GetHistoryStatement create(JsonNode table, List conditions, int limit) { + return new GetHistoryStatement(buildArguments(table, conditions, limit)); + } +} diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatement.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatement.java new file mode 100644 index 00000000..3c2718e7 --- /dev/null +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatement.java @@ -0,0 +1,69 @@ +package com.scalar.dl.tablestore.client.partiql.statement; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.base.MoreObjects; +import com.scalar.dl.genericcontracts.table.v1_0_0.Constants; +import java.util.Objects; + +public class ShowTablesStatement extends AbstractJacksonBasedContractStatement { + + private final String contractId; + private final JsonNode arguments; + + private ShowTablesStatement(JsonNode arguments) { + this.contractId = Constants.CONTRACT_SHOW_TABLES; + this.arguments = Objects.requireNonNull(arguments); + } + + @Override + public String getContractId() { + return contractId; + } + + @Override + public String getArguments() { + return jacksonSerDe.serialize(arguments); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("contractId", getContractId()) + .add("arguments", getArguments()) + .toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ShowTablesStatement)) { + return false; + } + ShowTablesStatement that = (ShowTablesStatement) o; + return Objects.equals(arguments, that.arguments); + } + + @Override + public int hashCode() { + return Objects.hash(arguments); + } + + private static ObjectNode buildArguments() { + return jacksonSerDe.getObjectMapper().createObjectNode(); + } + + private static JsonNode buildArguments(String table) { + return buildArguments().put(Constants.TABLE_NAME, table); + } + + public static ShowTablesStatement create() { + return new ShowTablesStatement(buildArguments()); + } + + public static ShowTablesStatement create(String table) { + return new ShowTablesStatement(buildArguments(table)); + } +} diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/util/JacksonUtils.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/util/JacksonUtils.java index 92f2f4d6..35335443 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/util/JacksonUtils.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/util/JacksonUtils.java @@ -1,5 +1,7 @@ package com.scalar.dl.tablestore.client.util; +import static com.scalar.dl.tablestore.client.partiql.parser.ScalarPartiqlParser.INFORMATION_SCHEMA_TABLE_NAME; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -64,6 +66,20 @@ public static String buildColumnReference(String tableReference, String column) return tableReference + Constants.COLUMN_SEPARATOR + column; } + public static JsonNode getValueFromCondition(JsonNode condition) { + return condition.get(Constants.CONDITION_VALUE); + } + + public static boolean isConditionForShowTables(JsonNode condition) { + return condition.isObject() + && condition.get(Constants.CONDITION_COLUMN).asText().equals(INFORMATION_SCHEMA_TABLE_NAME) + && condition + .get(Constants.CONDITION_OPERATOR) + .asText() + .equalsIgnoreCase(Constants.OPERATOR_EQ) + && condition.get(Constants.CONDITION_VALUE).isTextual(); + } + private static String toOperatorFrom(String symbol) { switch (symbol) { case "=": diff --git a/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitorTest.java b/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitorTest.java index 81d389dc..b0195688 100644 --- a/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitorTest.java +++ b/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/parser/PartiqlParserVisitorTest.java @@ -14,8 +14,10 @@ import com.scalar.dl.tablestore.client.partiql.DataType; import com.scalar.dl.tablestore.client.partiql.statement.ContractStatement; import com.scalar.dl.tablestore.client.partiql.statement.CreateTableStatement; +import com.scalar.dl.tablestore.client.partiql.statement.GetHistoryStatement; import com.scalar.dl.tablestore.client.partiql.statement.InsertStatement; import com.scalar.dl.tablestore.client.partiql.statement.SelectStatement; +import com.scalar.dl.tablestore.client.partiql.statement.ShowTablesStatement; import com.scalar.dl.tablestore.client.partiql.statement.UpdateStatement; import com.scalar.dl.tablestore.client.util.JacksonUtils; import java.math.BigDecimal; @@ -507,4 +509,88 @@ public void parse_InvalidSelectSqlForJoinGiven_ShouldThrowIllegalArgumentExcepti .isInstanceOf(IllegalArgumentException.class); } } + + @Test + public void parse_SelectSqlForGetHistoryGiven_ShouldParseCorrectly() { + // Arrange + JsonNode table = JacksonUtils.buildTable("tbl"); + + // Act + List statements = + ScalarPartiqlParser.parse( + "SELECT history() FROM tbl WHERE col1 = 'aaa';" + + "SELECT history() FROM tbl WHERE tbl.col1 = 'aaa';" + + "SELECT history() FROM tbl WHERE tbl.col1 = 'aaa' LIMIT 10;"); + + // Assert + assertThat(statements.size()).isEqualTo(3); + assertThat(statements.get(0)) + .isEqualTo( + GetHistoryStatement.create( + table, + ImmutableList.of(JacksonUtils.buildCondition("col1", "=", TextNode.valueOf("aaa"))), + 0)); + assertThat(statements.get(1)) + .isEqualTo( + GetHistoryStatement.create( + table, + ImmutableList.of( + JacksonUtils.buildCondition("tbl.col1", "=", TextNode.valueOf("aaa"))), + 0)); + assertThat(statements.get(2)) + .isEqualTo( + GetHistoryStatement.create( + table, + ImmutableList.of( + JacksonUtils.buildCondition("tbl.col1", "=", TextNode.valueOf("aaa"))), + 10)); + } + + @Test + public void parse_InvalidSelectSqlForGetHistoryGiven_ShouldThrowIllegalArgumentException() { + // Arrange + List sqlStatements = + ImmutableList.of( + "SELECT history() AS h FROM tbl WHERE col1 = 'aaa'", + "SELECT history() FROM tbl AS t WHERE t.col1 = 'aaa'", + "SELECT history(), col1 FROM tbl WHERE col1 = 'aaa'", + "SELECT history() FROM tbl WHERE col1 = 'aaa' ORDER BY age LIMIT 10"); + + // Act Assert + for (String sql : sqlStatements) { + assertThatThrownBy(() -> ScalarPartiqlParser.parse(sql), sql) + .isInstanceOf(IllegalArgumentException.class); + } + } + + @Test + public void parse_SelectSqlForShowTablesGiven_ShouldParseCorrectly() { + // Arrange Act + List statements = + ScalarPartiqlParser.parse( + "SELECT * FROM information_schema.tables;" + + "SELECT * FROM information_schema.tables WHERE table_name = 'aaa';"); + + // Assert + assertThat(statements.size()).isEqualTo(2); + assertThat(statements.get(0)).isEqualTo(ShowTablesStatement.create()); + assertThat(statements.get(1)).isEqualTo(ShowTablesStatement.create("aaa")); + } + + @Test + public void parse_InvalidSelectSqlForShowTablesGiven_ShouldThrowIllegalArgumentException() { + // Arrange + List sqlStatements = + ImmutableList.of( + "SELECT * FROM information_schema.tables AS tbl WHERE tbl.name = 'aaa'", + "SELECT * FROM information_schema.tables WHERE table_name = 'aaa' and col1 = 0", + "SELECT * FROM information_schema.tables WHERE col1 = 'aaa'", + "SELECT col1 FROM information_schema.tables"); + + // Act Assert + for (String sql : sqlStatements) { + assertThatThrownBy(() -> ScalarPartiqlParser.parse(sql), sql) + .isInstanceOf(IllegalArgumentException.class); + } + } } diff --git a/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatementTest.java b/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatementTest.java new file mode 100644 index 00000000..48950d01 --- /dev/null +++ b/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/GetHistoryStatementTest.java @@ -0,0 +1,52 @@ +package com.scalar.dl.tablestore.client.partiql.statement; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.node.IntNode; +import com.google.common.collect.ImmutableList; +import com.scalar.dl.tablestore.client.util.JacksonUtils; +import org.junit.jupiter.api.Test; + +public class GetHistoryStatementTest { + + @Test + public void getArguments_CorrectStatementWithoutLimitGiven_ShouldReturnCorrectArguments() { + // Arrange + GetHistoryStatement statement = + GetHistoryStatement.create( + JacksonUtils.buildTable("tbl"), + ImmutableList.of(JacksonUtils.buildCondition("col1", "=", IntNode.valueOf(1))), + 0); + String expected = + "{\"table\":\"tbl\"," + + "\"conditions\":[" + + "{\"column\":\"col1\",\"operator\":\"EQ\",\"value\":1}" + + "]}"; + + // Act + String arguments = statement.getArguments(); + + // Assert + assertThat(arguments).isEqualTo(expected); + } + + @Test + public void getArguments_CorrectStatementWithLimitGiven_ShouldReturnCorrectArguments() { + // Arrange + GetHistoryStatement statement = + GetHistoryStatement.create( + JacksonUtils.buildTable("tbl"), + ImmutableList.of(JacksonUtils.buildCondition("col1", "=", IntNode.valueOf(1))), + 10); + String expected = + "{\"table\":\"tbl\"," + + "\"conditions\":[{\"column\":\"col1\",\"operator\":\"EQ\",\"value\":1}]," + + "\"limit\":10}"; + + // Act + String arguments = statement.getArguments(); + + // Assert + assertThat(arguments).isEqualTo(expected); + } +} diff --git a/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatementTest.java b/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatementTest.java new file mode 100644 index 00000000..70e43903 --- /dev/null +++ b/table-store/src/test/java/com/scalar/dl/tablestore/client/partiql/statement/ShowTablesStatementTest.java @@ -0,0 +1,34 @@ +package com.scalar.dl.tablestore.client.partiql.statement; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class ShowTablesStatementTest { + + @Test + public void getArguments_CorrectStatementWithoutTableGiven_ShouldReturnCorrectArguments() { + // Arrange + ShowTablesStatement statement = ShowTablesStatement.create(); + String expected = "{}"; + + // Act + String arguments = statement.getArguments(); + + // Assert + assertThat(arguments).isEqualTo(expected); + } + + @Test + public void getArguments_CorrectStatementWithTableGiven_ShouldReturnCorrectArguments() { + // Arrange + ShowTablesStatement statement = ShowTablesStatement.create("tbl"); + String expected = "{\"name\":\"tbl\"}"; + + // Act + String arguments = statement.getArguments(); + + // Assert + assertThat(arguments).isEqualTo(expected); + } +} From 08420b91561fa66a696d65059e4a8613d467948b Mon Sep 17 00:00:00 2001 From: Jun Nemoto <35618893+jnmt@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:13:50 +0900 Subject: [PATCH 2/2] Update table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java Co-authored-by: Josh Wong --- .../dl/tablestore/client/error/TableStoreClientError.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java b/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java index 0a67ac0f..f7714f33 100644 --- a/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java +++ b/table-store/src/main/java/com/scalar/dl/tablestore/client/error/TableStoreClientError.java @@ -185,7 +185,7 @@ public enum TableStoreClientError implements ScalarDlError { INVALID_CONDITION_FOR_INFORMATION_SCHEMA_QUERY( StatusCode.INVALID_ARGUMENT, "030", - "The specified condition for information schema query is invalid.", + "The specified condition for the information schema query is invalid.", "", ""), ;