Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,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";
Expand Down Expand Up @@ -102,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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 =
Expand All @@ -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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,13 @@ public JsonNode invoke(
Ledger<JsonNode> 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))
Expand All @@ -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
Expand All @@ -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);
Expand Down
Loading