feat: Where condition helper library using H2 in memory database (#7592)
This commit is contained in:
parent
eaa9d783df
commit
659d7c3866
|
|
@ -126,6 +126,12 @@
|
||||||
<version>2.4.7</version>
|
<version>2.4.7</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<version>1.4.200</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.external.plugins;
|
package com.appsmith.external.constants;
|
||||||
|
|
||||||
public enum Op {
|
public enum ConditionalOperator {
|
||||||
LT,
|
LT,
|
||||||
LTE,
|
LTE,
|
||||||
EQ,
|
EQ,
|
||||||
61
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Condition.java
vendored
Normal file
61
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/Condition.java
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.appsmith.external.models;
|
||||||
|
|
||||||
|
import com.appsmith.external.constants.ConditionalOperator;
|
||||||
|
import com.appsmith.external.constants.DataType;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.appsmith.external.helpers.DataTypeStringUtils.stringToKnownDataTypeConverter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Condition {
|
||||||
|
|
||||||
|
String path;
|
||||||
|
|
||||||
|
ConditionalOperator operator;
|
||||||
|
|
||||||
|
String value;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
DataType valueDataType;
|
||||||
|
|
||||||
|
public Condition(String path, String operator, String value) {
|
||||||
|
this.path = path;
|
||||||
|
this.operator = ConditionalOperator.valueOf(operator);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<Condition> addValueDataType(List<Condition> conditionList) {
|
||||||
|
|
||||||
|
return conditionList
|
||||||
|
.stream()
|
||||||
|
.map(condition -> {
|
||||||
|
String value = condition.getValue();
|
||||||
|
DataType dataType = stringToKnownDataTypeConverter(value);
|
||||||
|
condition.setValueDataType(dataType);
|
||||||
|
return condition;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean isValid(Condition condition) {
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(condition.getPath()) ||
|
||||||
|
(condition.getOperator() == null) ||
|
||||||
|
StringUtils.isEmpty(condition.getValue())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
431
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/services/FilterDataService.java
vendored
Normal file
431
app/server/appsmith-interfaces/src/main/java/com/appsmith/external/services/FilterDataService.java
vendored
Normal file
|
|
@ -0,0 +1,431 @@
|
||||||
|
package com.appsmith.external.services;
|
||||||
|
|
||||||
|
import com.appsmith.external.constants.ConditionalOperator;
|
||||||
|
import com.appsmith.external.constants.DataType;
|
||||||
|
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||||
|
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||||
|
import com.appsmith.external.models.Condition;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bson.types.ObjectId;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.DriverManager;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.ResultSetMetaData;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.appsmith.external.helpers.DataTypeStringUtils.stringToKnownDataTypeConverter;
|
||||||
|
import static com.appsmith.external.models.Condition.addValueDataType;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class FilterDataService {
|
||||||
|
|
||||||
|
private static FilterDataService instance = null;
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
private Connection connection;
|
||||||
|
|
||||||
|
private static final String URL = "jdbc:h2:mem:filterDb";
|
||||||
|
|
||||||
|
private static final Map<DataType, String> SQL_DATATYPE_MAP = Map.of(
|
||||||
|
DataType.INTEGER, "INT",
|
||||||
|
DataType.LONG, "BIGINT",
|
||||||
|
DataType.FLOAT, "REAL",
|
||||||
|
DataType.DOUBLE, "DOUBLE",
|
||||||
|
DataType.BOOLEAN, "BOOLEAN",
|
||||||
|
DataType.STRING, "VARCHAR"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Map<ConditionalOperator, String> SQL_OPERATOR_MAP = Map.of(
|
||||||
|
ConditionalOperator.LT, "<",
|
||||||
|
ConditionalOperator.LTE, "<=",
|
||||||
|
ConditionalOperator.EQ, "=",
|
||||||
|
ConditionalOperator.NOT_EQ, "<>",
|
||||||
|
ConditionalOperator.GT, ">",
|
||||||
|
ConditionalOperator.GTE, ">=",
|
||||||
|
ConditionalOperator.IN, "IN",
|
||||||
|
ConditionalOperator.NOT_IN, "NOT IN"
|
||||||
|
);
|
||||||
|
|
||||||
|
private FilterDataService() {
|
||||||
|
|
||||||
|
objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
|
try {
|
||||||
|
connection = DriverManager.getConnection(URL);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Failed to connect to the in memory database. Unable to perform filtering");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FilterDataService getInstance() {
|
||||||
|
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new FilterDataService();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArrayNode filterData(ArrayNode items, List<Condition> conditionList) {
|
||||||
|
|
||||||
|
if (items == null || items.size() == 0) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validConditionList(conditionList)) {
|
||||||
|
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Conditions for filtering were incomplete or incorrect.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Condition> conditions = addValueDataType(conditionList);
|
||||||
|
|
||||||
|
// Generate the schema of the table using the first object
|
||||||
|
JsonNode jsonNode = items.get(0);
|
||||||
|
|
||||||
|
Map<String, DataType> schema = generateSchema(jsonNode);
|
||||||
|
|
||||||
|
String tableName = generateTable(schema);
|
||||||
|
|
||||||
|
// insert the data
|
||||||
|
insertData(tableName, items, schema);
|
||||||
|
|
||||||
|
// Filter the data
|
||||||
|
List<Map<String, Object>> finalResults = executeFilterQuery(tableName, conditions);
|
||||||
|
|
||||||
|
// Now that the data has been filtered. Clean Up. Drop the table
|
||||||
|
dropTable(tableName);
|
||||||
|
|
||||||
|
ArrayNode finalResultsNode = objectMapper.valueToTree(finalResults);
|
||||||
|
|
||||||
|
return finalResultsNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> executeFilterQuery(String tableName, List<Condition> conditions) {
|
||||||
|
StringBuilder sb = new StringBuilder("SELECT * FROM " + tableName);
|
||||||
|
|
||||||
|
String whereClause = generateWhereClause(conditions);
|
||||||
|
sb.append(whereClause);
|
||||||
|
|
||||||
|
sb.append(";");
|
||||||
|
|
||||||
|
String selectQuery = sb.toString();
|
||||||
|
log.debug("{} : Executing Query on H2 : {}", Thread.currentThread().getName(), selectQuery);
|
||||||
|
|
||||||
|
List<Map<String, Object>> rowsList = new ArrayList<>(50);
|
||||||
|
|
||||||
|
Connection conn = checkAndGetConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Statement statement = conn.createStatement();
|
||||||
|
ResultSet resultSet = statement.executeQuery(selectQuery);
|
||||||
|
|
||||||
|
ResultSetMetaData metaData = resultSet.getMetaData();
|
||||||
|
int colCount = metaData.getColumnCount();
|
||||||
|
|
||||||
|
while (resultSet.next()) {
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>(colCount);
|
||||||
|
for (int i = 1; i <= colCount; i++) {
|
||||||
|
row.put(metaData.getColumnName(i), resultSet.getObject(i));
|
||||||
|
}
|
||||||
|
rowsList.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
// Getting a SQL Exception here means that our generated query is incorrect. Raise an alarm!
|
||||||
|
log.error(e.getMessage());
|
||||||
|
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Filtering failure seen : " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return rowsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateWhereClause(List<Condition> conditions) {
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
Boolean firstCondition = true;
|
||||||
|
for (Condition condition : conditions) {
|
||||||
|
|
||||||
|
if (firstCondition) {
|
||||||
|
// Append the WHERE keyword before adding the conditions
|
||||||
|
sb.append(" WHERE ");
|
||||||
|
firstCondition = false;
|
||||||
|
} else {
|
||||||
|
// This is not the first condition. Append an `AND` before adding the next condition
|
||||||
|
sb.append(" AND ");
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = condition.getPath();
|
||||||
|
ConditionalOperator operator = condition.getOperator();
|
||||||
|
String value = condition.getValue();
|
||||||
|
|
||||||
|
String sqlOp = SQL_OPERATOR_MAP.get(operator);
|
||||||
|
if (sqlOp == null) {
|
||||||
|
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||||
|
operator.toString() + " is not supported currently for filtering.");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(path);
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(sqlOp);
|
||||||
|
sb.append(" ");
|
||||||
|
|
||||||
|
// These are array operations. Convert value into appropriate format and then append
|
||||||
|
if (operator == ConditionalOperator.IN || operator == ConditionalOperator.NOT_IN) {
|
||||||
|
|
||||||
|
StringBuilder valueBuilder = new StringBuilder("(");
|
||||||
|
|
||||||
|
// The array could be an array of Strings
|
||||||
|
if (value.contains("\"")) {
|
||||||
|
try {
|
||||||
|
List<String> stringValues = objectMapper.readValue(value, List.class);
|
||||||
|
List<String> updatedStringValues = stringValues.stream().map(stringValue -> "\'" + stringValue + "\'").collect(Collectors.toList());
|
||||||
|
String finalValues = String.join(",", updatedStringValues);
|
||||||
|
valueBuilder.append(finalValues);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Removes the outer square brackets from the string to leave behind just the values separated by comma
|
||||||
|
String trimmedValue = value.replaceAll("^\\[|]$", "");
|
||||||
|
valueBuilder.append(trimmedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
valueBuilder.append(")");
|
||||||
|
value = valueBuilder.toString();
|
||||||
|
sb.append(value);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Since the value is not an array, surround the same with single quotes and append
|
||||||
|
sb.append("'");
|
||||||
|
sb.append(value);
|
||||||
|
sb.append("'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSERT INTO tableName (columnName1, columnName2) VALUES (data1, data2)
|
||||||
|
public void insertData(String tableName, ArrayNode items, Map<String, DataType> schema) {
|
||||||
|
|
||||||
|
List<String> columnNames = schema.keySet().stream().collect(Collectors.toList());
|
||||||
|
|
||||||
|
StringBuilder insertQueryBuilder = new StringBuilder("INSERT INTO ");
|
||||||
|
insertQueryBuilder.append(tableName);
|
||||||
|
|
||||||
|
StringBuilder columnNamesBuilder = new StringBuilder("(");
|
||||||
|
columnNamesBuilder.append(String.join(", ", columnNames));
|
||||||
|
columnNamesBuilder.append(")");
|
||||||
|
|
||||||
|
insertQueryBuilder.append(columnNamesBuilder);
|
||||||
|
insertQueryBuilder.append(" VALUES ");
|
||||||
|
|
||||||
|
StringBuilder valuesMasterBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
int counter = 0;
|
||||||
|
for (JsonNode item : items) {
|
||||||
|
|
||||||
|
// If the number of values inserted is greater than 1000, the insert would fail. Once we have reached 1000
|
||||||
|
// rows, execute the insert for rows so far and start afresh for the rest of the rows
|
||||||
|
if (counter == 1000) {
|
||||||
|
String insertQueryString = finalInsertQueryString(insertQueryBuilder.toString(), valuesMasterBuilder);
|
||||||
|
executeDbQuery(insertQueryString);
|
||||||
|
|
||||||
|
// Reset the values builder and counter for new insert queries.
|
||||||
|
valuesMasterBuilder = new StringBuilder();
|
||||||
|
counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder valuesBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
if (counter != 0) {
|
||||||
|
// If not the first row, add a separator between rows
|
||||||
|
valuesBuilder.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the row
|
||||||
|
valuesBuilder.append("(");
|
||||||
|
|
||||||
|
Boolean firstEntry = true;
|
||||||
|
for (String columnName : columnNames) {
|
||||||
|
|
||||||
|
if (!firstEntry) {
|
||||||
|
// Add a separator before adding a new entry
|
||||||
|
valuesBuilder.append(",");
|
||||||
|
} else {
|
||||||
|
// For future iterations, set flag to false
|
||||||
|
firstEntry = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode fieldNode = item.get(columnName);
|
||||||
|
if (fieldNode != null) {
|
||||||
|
valuesBuilder.append("'");
|
||||||
|
valuesBuilder.append(fieldNode.asText());
|
||||||
|
valuesBuilder.append("'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the row
|
||||||
|
valuesBuilder.append(")");
|
||||||
|
|
||||||
|
valuesMasterBuilder.append(valuesBuilder);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valuesMasterBuilder.length() > 0) {
|
||||||
|
String insertQueryString = finalInsertQueryString(insertQueryBuilder.toString(), valuesMasterBuilder);
|
||||||
|
executeDbQuery(insertQueryString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeDbQuery(String query) {
|
||||||
|
|
||||||
|
Connection conn = checkAndGetConnection();
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn.createStatement().execute(query);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
// Getting a SQL Exception here means that our generated query is incorrect. Raise an alarm!
|
||||||
|
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Filtering failure seen during insertion of data : " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String finalInsertQueryString(String partialInsertQuery, StringBuilder valuesBuilder) {
|
||||||
|
|
||||||
|
StringBuilder insertQueryBuilder = new StringBuilder(partialInsertQuery);
|
||||||
|
|
||||||
|
insertQueryBuilder.append(valuesBuilder);
|
||||||
|
insertQueryBuilder.append(";");
|
||||||
|
|
||||||
|
String finalInsertQuery = insertQueryBuilder.toString();
|
||||||
|
|
||||||
|
return finalInsertQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Connection checkAndGetConnection() {
|
||||||
|
try {
|
||||||
|
if (connection == null || connection.isClosed() || !connection.isValid(5)) {
|
||||||
|
connection = DriverManager.getConnection(URL);
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Failed to connect to the in memory database. Unable to perform filtering");
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateTable(Map<String, DataType> schema) {
|
||||||
|
|
||||||
|
// Generate table name
|
||||||
|
String generateUniqueId = new ObjectId().toString().toUpperCase();
|
||||||
|
|
||||||
|
// Appending tbl_ before the generated unique id since using the string directly was throwing a SQL error
|
||||||
|
// which I couldnt solve. Just appending a string to it though works perfectly.
|
||||||
|
String tableName = new StringBuilder("tbl_").append(generateUniqueId).toString();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder("CREATE TABLE ");
|
||||||
|
|
||||||
|
sb.append(tableName);
|
||||||
|
|
||||||
|
sb.append(" (");
|
||||||
|
|
||||||
|
Boolean columnsAdded = false;
|
||||||
|
for (Map.Entry<String, DataType> entry : schema.entrySet()) {
|
||||||
|
|
||||||
|
if (columnsAdded) {
|
||||||
|
// If columns have been added before, add a separator
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
String fieldName = entry.getKey();
|
||||||
|
DataType dataType = entry.getValue();
|
||||||
|
|
||||||
|
String sqlDataType = SQL_DATATYPE_MAP.get(dataType);
|
||||||
|
if (sqlDataType == null) {
|
||||||
|
// the data type recognized does not have a native support in appsmith right now
|
||||||
|
// default to String
|
||||||
|
sqlDataType = SQL_DATATYPE_MAP.get(DataType.STRING);
|
||||||
|
}
|
||||||
|
columnsAdded = true;
|
||||||
|
sb.append(fieldName);
|
||||||
|
sb.append(" ");
|
||||||
|
sb.append(sqlDataType);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(");");
|
||||||
|
|
||||||
|
String createTableQuery = sb.toString();
|
||||||
|
|
||||||
|
executeDbQuery(createTableQuery);
|
||||||
|
|
||||||
|
return tableName;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dropTable(String tableName) {
|
||||||
|
|
||||||
|
String dropTableQuery = "DROP TABLE " + tableName + ";";
|
||||||
|
|
||||||
|
executeDbQuery(dropTableQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Map<String, DataType> generateSchema(JsonNode jsonNode) {
|
||||||
|
|
||||||
|
Iterator<String> fieldNamesIterator = jsonNode.fieldNames();
|
||||||
|
/*
|
||||||
|
* For an object of the following type :
|
||||||
|
* {
|
||||||
|
* "field1" : "stringValue",
|
||||||
|
* "field2" : "true",
|
||||||
|
* "field3" : "12"
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* The schema generated would be a Map as follows :
|
||||||
|
* {
|
||||||
|
* field1 : DataType.STRING
|
||||||
|
* field2 : DataType.BOOLEAN
|
||||||
|
* field3 : DataType.INTEGER
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
Map<String, DataType> schema = Stream.generate(() -> null)
|
||||||
|
.takeWhile(x -> fieldNamesIterator.hasNext())
|
||||||
|
.map(n -> fieldNamesIterator.next())
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
Function.identity(),
|
||||||
|
name -> {
|
||||||
|
String value = jsonNode.get(name).asText();
|
||||||
|
DataType dataType = stringToKnownDataTypeConverter(value);
|
||||||
|
return dataType;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean validConditionList(List<Condition> conditionList) {
|
||||||
|
|
||||||
|
return conditionList
|
||||||
|
.stream()
|
||||||
|
.allMatch(condition -> Condition.isValid(condition));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
package com.appsmith.external.services;
|
||||||
|
|
||||||
|
import com.appsmith.external.constants.DataType;
|
||||||
|
import com.appsmith.external.models.Condition;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class FilterDataServiceTest {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
private final FilterDataService filterDataService = FilterDataService.getInstance();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateTable() {
|
||||||
|
Map<String, DataType> schema = Map.of(
|
||||||
|
"id", DataType.INTEGER,
|
||||||
|
"name", DataType.STRING,
|
||||||
|
"status", DataType.BOOLEAN
|
||||||
|
);
|
||||||
|
|
||||||
|
String table = filterDataService.generateTable(schema);
|
||||||
|
|
||||||
|
assertThat(table).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilterSingleCondition() {
|
||||||
|
String data = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2381224,\n" +
|
||||||
|
" \"email\": \"michael.lawson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Michael Lawson\",\n" +
|
||||||
|
" \"productName\": \"Chicken Sandwich\",\n" +
|
||||||
|
" \"orderAmount\": 4.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2736212,\n" +
|
||||||
|
" \"email\": \"lindsay.ferguson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Lindsay Ferguson\",\n" +
|
||||||
|
" \"productName\": \"Tuna Salad\",\n" +
|
||||||
|
" \"orderAmount\": 9.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 6788734,\n" +
|
||||||
|
" \"email\": \"tobias.funke@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Tobias Funke\",\n" +
|
||||||
|
" \"productName\": \"Beef steak\",\n" +
|
||||||
|
" \"orderAmount\": 19.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
try {
|
||||||
|
ArrayNode items = (ArrayNode) objectMapper.readTree(data);
|
||||||
|
|
||||||
|
List<Condition> conditionList = new ArrayList<>();
|
||||||
|
|
||||||
|
Condition condition = new Condition("orderAmount", "LT", "15");
|
||||||
|
conditionList.add(condition);
|
||||||
|
|
||||||
|
ArrayNode filteredData = filterDataService.filterData(items, conditionList);
|
||||||
|
|
||||||
|
assertEquals(filteredData.size(), 2);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilterMultipleConditions() {
|
||||||
|
String data = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2381224,\n" +
|
||||||
|
" \"email\": \"michael.lawson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Michael Lawson\",\n" +
|
||||||
|
" \"productName\": \"Chicken Sandwich\",\n" +
|
||||||
|
" \"orderAmount\": 4.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2736212,\n" +
|
||||||
|
" \"email\": \"lindsay.ferguson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Lindsay Ferguson\",\n" +
|
||||||
|
" \"productName\": \"Tuna Salad\",\n" +
|
||||||
|
" \"orderAmount\": 9.99,\n" +
|
||||||
|
" \"orderStatus\": \"NOT READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 6788734,\n" +
|
||||||
|
" \"email\": \"tobias.funke@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Tobias Funke\",\n" +
|
||||||
|
" \"productName\": \"Beef steak\",\n" +
|
||||||
|
" \"orderAmount\": 19.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
try {
|
||||||
|
ArrayNode items = (ArrayNode) objectMapper.readTree(data);
|
||||||
|
|
||||||
|
List<Condition> conditionList = new ArrayList<>();
|
||||||
|
|
||||||
|
Condition condition = new Condition("orderAmount", "LT", "15");
|
||||||
|
conditionList.add(condition);
|
||||||
|
|
||||||
|
Condition condition1 = new Condition("orderStatus", "EQ", "READY");
|
||||||
|
conditionList.add(condition1);
|
||||||
|
|
||||||
|
ArrayNode filteredData = filterDataService.filterData(items, conditionList);
|
||||||
|
|
||||||
|
assertEquals(filteredData.size(), 1);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilterInConditionForStrings() {
|
||||||
|
String data = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2381224,\n" +
|
||||||
|
" \"email\": \"michael.lawson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Michael Lawson\",\n" +
|
||||||
|
" \"productName\": \"Chicken Sandwich\",\n" +
|
||||||
|
" \"orderAmount\": 4.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2736212,\n" +
|
||||||
|
" \"email\": \"lindsay.ferguson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Lindsay Ferguson\",\n" +
|
||||||
|
" \"productName\": \"Tuna Salad\",\n" +
|
||||||
|
" \"orderAmount\": 9.99,\n" +
|
||||||
|
" \"orderStatus\": \"NOT READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 6788734,\n" +
|
||||||
|
" \"email\": \"tobias.funke@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Tobias Funke\",\n" +
|
||||||
|
" \"productName\": \"Beef steak\",\n" +
|
||||||
|
" \"orderAmount\": 19.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
try {
|
||||||
|
ArrayNode items = (ArrayNode) objectMapper.readTree(data);
|
||||||
|
|
||||||
|
List<Condition> conditionList = new ArrayList<>();
|
||||||
|
|
||||||
|
Condition condition = new Condition("orderAmount", "LT", "15");
|
||||||
|
conditionList.add(condition);
|
||||||
|
|
||||||
|
Condition condition1 = new Condition("orderStatus", "IN", "[\"READY\", \"NOT READY\"]");
|
||||||
|
conditionList.add(condition1);
|
||||||
|
|
||||||
|
ArrayNode filteredData = filterDataService.filterData(items, conditionList);
|
||||||
|
|
||||||
|
assertEquals(filteredData.size(), 2);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilterInConditionForNumbers() {
|
||||||
|
String data = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2381224,\n" +
|
||||||
|
" \"email\": \"michael.lawson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Michael Lawson\",\n" +
|
||||||
|
" \"productName\": \"Chicken Sandwich\",\n" +
|
||||||
|
" \"orderAmount\": 4.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2736212,\n" +
|
||||||
|
" \"email\": \"lindsay.ferguson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Lindsay Ferguson\",\n" +
|
||||||
|
" \"productName\": \"Tuna Salad\",\n" +
|
||||||
|
" \"orderAmount\": 9.99,\n" +
|
||||||
|
" \"orderStatus\": \"NOT READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 6788734,\n" +
|
||||||
|
" \"email\": \"tobias.funke@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Tobias Funke\",\n" +
|
||||||
|
" \"productName\": \"Beef steak\",\n" +
|
||||||
|
" \"orderAmount\": 19.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
try {
|
||||||
|
ArrayNode items = (ArrayNode) objectMapper.readTree(data);
|
||||||
|
|
||||||
|
List<Condition> conditionList = new ArrayList<>();
|
||||||
|
|
||||||
|
Condition condition = new Condition("orderAmount", "LT", "15");
|
||||||
|
conditionList.add(condition);
|
||||||
|
|
||||||
|
Condition condition1 = new Condition("orderAmount", "IN", "[4.99, 19.99]");
|
||||||
|
conditionList.add(condition1);
|
||||||
|
|
||||||
|
ArrayNode filteredData = filterDataService.filterData(items, conditionList);
|
||||||
|
|
||||||
|
assertEquals(filteredData.size(), 1);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFilterNotInConditionForNumbers() {
|
||||||
|
String data = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2381224,\n" +
|
||||||
|
" \"email\": \"michael.lawson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Michael Lawson\",\n" +
|
||||||
|
" \"productName\": \"Chicken Sandwich\",\n" +
|
||||||
|
" \"orderAmount\": 4.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 2736212,\n" +
|
||||||
|
" \"email\": \"lindsay.ferguson@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Lindsay Ferguson\",\n" +
|
||||||
|
" \"productName\": \"Tuna Salad\",\n" +
|
||||||
|
" \"orderAmount\": 9.99,\n" +
|
||||||
|
" \"orderStatus\": \"NOT READY\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": 6788734,\n" +
|
||||||
|
" \"email\": \"tobias.funke@reqres.in\",\n" +
|
||||||
|
" \"userName\": \"Tobias Funke\",\n" +
|
||||||
|
" \"productName\": \"Beef steak\",\n" +
|
||||||
|
" \"orderAmount\": 19.99,\n" +
|
||||||
|
" \"orderStatus\": \"READY\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
try {
|
||||||
|
ArrayNode items = (ArrayNode) objectMapper.readTree(data);
|
||||||
|
|
||||||
|
List<Condition> conditionList = new ArrayList<>();
|
||||||
|
|
||||||
|
Condition condition = new Condition("orderAmount", "LT", "15");
|
||||||
|
conditionList.add(condition);
|
||||||
|
|
||||||
|
Condition condition1 = new Condition("orderAmount", "NOT_IN", "[5.99, 19.00]");
|
||||||
|
conditionList.add(condition1);
|
||||||
|
|
||||||
|
ArrayNode filteredData = filterDataService.filterData(items, conditionList);
|
||||||
|
|
||||||
|
assertEquals(filteredData.size(), 2);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package com.external.utils;
|
||||||
|
|
||||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||||
import com.external.plugins.Op;
|
import com.appsmith.external.constants.ConditionalOperator;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.cloud.firestore.FieldPath;
|
import com.google.cloud.firestore.FieldPath;
|
||||||
import com.google.cloud.firestore.Query;
|
import com.google.cloud.firestore.Query;
|
||||||
|
|
@ -26,9 +26,9 @@ public class WhereConditionUtils {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Op operator;
|
ConditionalOperator operator;
|
||||||
try {
|
try {
|
||||||
operator = StringUtils.isEmpty(operatorString) ? null : Op.valueOf(operatorString);
|
operator = StringUtils.isEmpty(operatorString) ? null : ConditionalOperator.valueOf(operatorString);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new AppsmithPluginException(
|
throw new AppsmithPluginException(
|
||||||
AppsmithPluginError.PLUGIN_ERROR,
|
AppsmithPluginError.PLUGIN_ERROR,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user