diff --git a/document-store/build.gradle.kts b/document-store/build.gradle.kts index cc0028a4..c609baaf 100644 --- a/document-store/build.gradle.kts +++ b/document-store/build.gradle.kts @@ -8,6 +8,7 @@ plugins { dependencies { api("com.typesafe:config:1.4.2") + implementation("org.projectlombok:lombok:1.18.18") annotationProcessor("org.projectlombok:lombok:1.18.22") compileOnly("org.projectlombok:lombok:1.18.22") implementation("org.apache.commons:commons-collections4:4.4") diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreTest.java index 035b0051..a692808f 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreTest.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreTest.java @@ -32,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -56,6 +57,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.shaded.com.google.common.collect.Maps; +import org.testcontainers.shaded.org.apache.commons.lang.StringEscapeUtils; import org.testcontainers.utility.DockerImageName; public class DocStoreTest { @@ -85,22 +87,25 @@ public static void init() { Datastore mongoDatastore = DatastoreProvider.getDatastore("Mongo", config); System.out.println(mongoDatastore.listCollections()); - postgres = - new GenericContainer<>(DockerImageName.parse("postgres:13.1")) - .withEnv("POSTGRES_PASSWORD", "postgres") - .withEnv("POSTGRES_USER", "postgres") - .withExposedPorts(5432) - .waitingFor(Wait.forListeningPort()); - postgres.start(); +// postgres = +// new GenericContainer<>(DockerImageName.parse("postgres:13.1")) +// .withEnv("POSTGRES_PASSWORD", "postgres") +// .withEnv("POSTGRES_USER", "postgres") +// .withExposedPorts(5432) +// .waitingFor(Wait.forListeningPort()); +// postgres.start(); + +// String postgresConnectionUrl = +// String.format("jdbc:postgresql://localhost:%s/", postgres.getMappedPort(5432)); String postgresConnectionUrl = - String.format("jdbc:postgresql://localhost:%s/", postgres.getMappedPort(5432)); + String.format("jdbc:postgresql://localhost:%s/", "5432"); DatastoreProvider.register("POSTGRES", PostgresDatastore.class); Map postgresConfig = new HashMap<>(); postgresConfig.putIfAbsent("url", postgresConnectionUrl); - postgresConfig.putIfAbsent("user", "postgres"); - postgresConfig.putIfAbsent("password", "postgres"); + postgresConfig.putIfAbsent("user", "keycloak"); + postgresConfig.putIfAbsent("password", "keycloak"); Datastore postgresDatastore = DatastoreProvider.getDatastore("Postgres", ConfigFactory.parseMap(postgresConfig)); System.out.println(postgresDatastore.listCollections()); @@ -139,6 +144,47 @@ private static Stream databaseContextMongo() { return Stream.of(Arguments.of(MONGO_STORE)); } + @ParameterizedTest + @MethodSource("databaseContextPostgres") + public void debugSearch(String dataStoreName) throws Exception { + /* + SELECT document->'identifyingAttributes' AS "identifyingAttributes", + document->'tenantId' AS "tenantId", + document->'type' AS "type",id AS "id", + document->'attributes' AS "attributes" + FROM insights WHERE ((document->>'tenantId' = '14d8d0d8-c1a9-4100-83a4-97edfeb85606') AND (document->>'type' = 'API')) + AND (document->'identifyingAttributes'->>'api_id' = '5e6f57d7-313d-34e1-a37e-a338c448c271') + */ + + Datastore datastore = datastoreMap.get(dataStoreName); + Collection collection = datastore.getCollection("insights"); + + Query query = new Query(); + query.addSelection("identifyingAttributes"); + query.addSelection("tenantId"); + query.addSelection("id"); + query.addSelection("attributes"); + query.addSelection("type"); + + Filter f1 = Filter.eq("tenantId", "14d8d0d8-c1a9-4100-83a4-97edfeb85606"); + Filter f2 = f1.and(Filter.eq("type", "API")); + Filter f3 = f2.and(Filter.eq("identifyingAttributes.api_id", "5e6f57d7-313d-34e1-a37e-a338c448c271")); + query.setFilter(f3); + + Iterator results = collection.search(query); + List documents = new ArrayList<>(); + while (results.hasNext()) { + Document document = results.next(); + String insightDocumentJson = document.toJson(); + String processed = StringEscapeUtils.unescapeJava(insightDocumentJson); + documents.add(document); + Optional mayBeInsightDto = + Optional.ofNullable(OBJECT_MAPPER.readValue(processed, InsightDto.class)); + Assertions.assertNotNull(mayBeInsightDto.get()); + } + Assertions.assertFalse(documents.isEmpty()); + } + @ParameterizedTest @MethodSource("databaseContextProvider") public void testUpsert(String dataStoreName) throws Exception { diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/InsightDto.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/InsightDto.java new file mode 100644 index 00000000..e0a2fb55 --- /dev/null +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/InsightDto.java @@ -0,0 +1,35 @@ +package org.hypertrace.core.documentstore; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class InsightDto { + public static final String ID_FIELD = "id"; + public static final String TYPE_FIELD = "type"; + public static final String ATTRIBUTES_FIELD = "attributes"; + public static final String IDENTIFYING_ATTRIBUTES_FIELD = "identifyingAttributes"; + public static final String TENANT_ID_FIELD = "tenantId"; + + @JsonProperty(value = ID_FIELD) + private String id; + + @JsonProperty(value = TYPE_FIELD) + private String type; + + @JsonProperty(value = ATTRIBUTES_FIELD) + private Map attributes; + + @JsonProperty(value = IDENTIFYING_ATTRIBUTES_FIELD) + private Map identifyingAttributes; + + @JsonProperty(value = TENANT_ID_FIELD) + private String tenantId; +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java index 10b45926..aee5f7c1 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresCollection.java @@ -1,6 +1,7 @@ package org.hypertrace.core.documentstore.postgres; import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -324,8 +325,11 @@ public BulkUpdateResult bulkOperationOnArrayValue(BulkArrayValueUpdateRequest re @Override public CloseableIterator search(Query query) { + String selection = PostgresQueryParser.parseSelections(query.getSelections()); + StringBuilder sqlBuilder = + new StringBuilder(String.format("SELECT %s FROM ", selection)).append(collectionName); + String filters = null; - StringBuilder sqlBuilder = new StringBuilder("SELECT * FROM ").append(collectionName); Params.Builder paramsBuilder = Params.newBuilder(); // If there is a filter in the query, parse it fully. @@ -333,8 +337,6 @@ public CloseableIterator search(Query query) { filters = PostgresQueryParser.parseFilter(query.getFilter(), paramsBuilder); } - LOGGER.debug("Sending query to PostgresSQL: {} : {}", collectionName, filters); - if (filters != null) { sqlBuilder.append(" WHERE ").append(filters); } @@ -354,13 +356,20 @@ public CloseableIterator search(Query query) { sqlBuilder.append(" OFFSET ").append(offset); } + String pgSqlQuery = sqlBuilder.toString(); try { PreparedStatement preparedStatement = - buildPreparedStatement(sqlBuilder.toString(), paramsBuilder.build()); + buildPreparedStatement(pgSqlQuery, paramsBuilder.build()); + LOGGER.warn("Executing search query to PostgresSQL:{}", preparedStatement.toString()); ResultSet resultSet = preparedStatement.executeQuery(); - return new PostgresResultIterator(resultSet); + CloseableIterator closeableIterator = + query.getSelections().size() > 0 + ? new PostgresResultIteratorWithMetaData(resultSet) + : new PostgresResultIterator(resultSet); + return closeableIterator; } catch (SQLException e) { - LOGGER.error("SQLException querying documents. query: {}", query, e); + LOGGER.error( + "SQLException in querying documents - query: {}, sqlQuery:{}", query, pgSqlQuery, e); } return EMPTY_ITERATOR; @@ -739,8 +748,7 @@ private Optional getCreatedTime(Key key) throws IOException { private CloseableIterator searchDocsForKeys(Set keys) { List keysAsStr = keys.stream().map(Key::toString).collect(Collectors.toList()); - Query query = - new Query().withSelection("*").withFilter(new Filter(Filter.Op.IN, ID, keysAsStr)); + Query query = new Query().withFilter(new Filter(Filter.Op.IN, ID, keysAsStr)); return search(query); } @@ -778,6 +786,7 @@ private CloseableIterator executeQueryV1( try { PreparedStatement preparedStatement = buildPreparedStatement(sqlQuery, queryParser.getParamsBuilder().build()); + LOGGER.warn("Executing executeQueryV1 sqlQuery:{}", preparedStatement.toString()); ResultSet resultSet = preparedStatement.executeQuery(); CloseableIterator closeableIterator = query.getSelections().size() > 0 @@ -1075,11 +1084,7 @@ protected Document prepareDocument() throws SQLException, IOException { Map jsonNode = new HashMap(); for (int i = 1; i <= columnCount; i++) { String columnName = resultSetMetaData.getColumnName(i); - int columnType = resultSetMetaData.getColumnType(i); - String columnValue = - columnType == Types.ARRAY - ? MAPPER.writeValueAsString(resultSet.getArray(i).getArray()) - : resultSet.getString(i); + String columnValue = getColumnValue(resultSetMetaData, columnName, i); if (StringUtils.isNotEmpty(columnValue)) { JsonNode leafNodeValue = MAPPER.readTree(columnValue); if (PostgresUtils.isEncodedNestedField(columnName)) { @@ -1093,6 +1098,23 @@ protected Document prepareDocument() throws SQLException, IOException { return new JSONDocument(MAPPER.writeValueAsString(jsonNode)); } + private String getColumnValue(ResultSetMetaData resultSetMetaData, String columnName, int columnIndex) + throws SQLException, JsonProcessingException { + int columnType = resultSetMetaData.getColumnType(columnIndex); + // check for array + if (columnType == Types.ARRAY) { + return MAPPER.writeValueAsString(resultSet.getArray(columnIndex).getArray()); + } + + // check for ID column + if (PostgresUtils.OUTER_COLUMNS.contains(columnName) && columnName.equals(PostgresUtils.ID_COLUMN)) { + return MAPPER.writeValueAsString(resultSet.getString(columnIndex)); + } + + // rest of the columns + return resultSet.getString(columnIndex); + } + private void handleNestedField( String columnName, Map rootNode, JsonNode leafNodeValue) { List keys = PostgresUtils.splitNestedField(columnName); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParser.java index 1e002fd9..cdb12b2c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParser.java @@ -1,149 +1,43 @@ package org.hypertrace.core.documentstore.postgres; -import static org.hypertrace.core.documentstore.Collection.UNSUPPORTED_QUERY_OPERATION; -import static org.hypertrace.core.documentstore.postgres.PostgresCollection.CREATED_AT; -import static org.hypertrace.core.documentstore.postgres.PostgresCollection.DOCUMENT; -import static org.hypertrace.core.documentstore.postgres.PostgresCollection.DOC_PATH_SEPARATOR; -import static org.hypertrace.core.documentstore.postgres.PostgresCollection.ID; -import static org.hypertrace.core.documentstore.postgres.PostgresCollection.UPDATED_AT; - import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hypertrace.core.documentstore.Filter; import org.hypertrace.core.documentstore.OrderBy; import org.hypertrace.core.documentstore.postgres.Params.Builder; +import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils; class PostgresQueryParser { - private static final String QUESTION_MARK = "?"; - // postgres jsonb uses `->` instead of `.` for json field access - private static final String JSON_FIELD_ACCESSOR = "->"; - // postgres operator to fetch the value of json object as text. - private static final String JSON_DATA_ACCESSOR = "->>"; - private static final Set OUTER_COLUMNS = - new HashSet<>() { - { - add(CREATED_AT); - add(ID); - add(UPDATED_AT); - } - }; + static String parseSelections(List selections) { + return Optional.of( + selections.stream() + .map( + selection -> + String.format( + "%s AS \"%s\"", + PostgresUtils.prepareFieldAccessorExpr( + selection, PostgresUtils.DOCUMENT_COLUMN), + selection)) + .collect(Collectors.joining(","))) + .filter(str -> StringUtils.isNotBlank(str)) + .orElse("*"); + } static String parseFilter(Filter filter, Builder paramsBuilder) { if (filter.isComposite()) { return parseCompositeFilter(filter, paramsBuilder); } else { - return parseNonCompositeFilter(filter, paramsBuilder); - } - } - - static String parseNonCompositeFilter(Filter filter, Builder paramsBuilder) { - Filter.Op op = filter.getOp(); - Object value = filter.getValue(); - String fieldName = filter.getFieldName(); - String fullFieldName = prepareCast(prepareFieldDataAccessorExpr(fieldName), value); - StringBuilder filterString = new StringBuilder(fullFieldName); - String sqlOperator; - Boolean isMultiValued = false; - switch (op) { - case EQ: - sqlOperator = " = "; - break; - case GT: - sqlOperator = " > "; - break; - case LT: - sqlOperator = " < "; - break; - case GTE: - sqlOperator = " >= "; - break; - case LTE: - sqlOperator = " <= "; - break; - case LIKE: - // Case insensitive regex search, Append % at beginning and end of value to do a regex - // search - sqlOperator = " ILIKE "; - value = "%" + value + "%"; - break; - case NOT_IN: - // NOTE: Below two points - // 1. both NOT_IN and IN filter currently limited to non-array field - // - https://github.com/hypertrace/document-store/issues/32#issuecomment-781411676 - // 2. To make semantically opposite filter of IN, we need to check for if key is not present - // Ref in context of NEQ - - // https://github.com/hypertrace/document-store/pull/20#discussion_r547101520Other - // so, we need - "document->key IS NULL OR document->key->> NOT IN (v1, v2)" - StringBuilder notInFilterString = prepareFieldAccessorExpr(fieldName); - if (notInFilterString != null) { - filterString = notInFilterString.append(" IS NULL OR ").append(fullFieldName); - } - sqlOperator = " NOT IN "; - isMultiValued = true; - value = prepareParameterizedStringForList((List) value, paramsBuilder); - break; - case IN: - // NOTE: both NOT_IN and IN filter currently limited to non-array field - // - https://github.com/hypertrace/document-store/issues/32#issuecomment-781411676 - sqlOperator = " IN "; - isMultiValued = true; - value = prepareParameterizedStringForList((List) value, paramsBuilder); - break; - case NOT_EXISTS: - sqlOperator = " IS NULL "; - value = null; - // For fields inside jsonb - StringBuilder notExists = prepareFieldAccessorExpr(fieldName); - if (notExists != null) { - filterString = notExists; - } - break; - case EXISTS: - sqlOperator = " IS NOT NULL "; - value = null; - // For fields inside jsonb - StringBuilder exists = prepareFieldAccessorExpr(fieldName); - if (exists != null) { - filterString = exists; - } - break; - case NEQ: - sqlOperator = " != "; - // https://github.com/hypertrace/document-store/pull/20#discussion_r547101520 - // The expected behaviour is to get all documents which either satisfy non equality - // condition - // or the key doesn't exist in them - // Semantics for handling if key not exists and if it exists, its value - // doesn't equal to the filter for Jsonb document will be done as: - // "document->key IS NULL OR document->key->> != value" - StringBuilder notEquals = prepareFieldAccessorExpr(fieldName); - // For fields inside jsonb - if (notEquals != null) { - filterString = notEquals.append(" IS NULL OR ").append(fullFieldName); - } - break; - case CONTAINS: - // TODO: Matches condition inside an array of documents - default: - throw new UnsupportedOperationException(UNSUPPORTED_QUERY_OPERATION); - } - - filterString.append(sqlOperator); - if (value != null) { - if (isMultiValued) { - filterString.append(value); - } else { - filterString.append(QUESTION_MARK); - paramsBuilder.addObjectParam(value); - } + return PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + paramsBuilder); } - String filters = filterString.toString(); - return filters; } static String parseCompositeFilter(Filter filter, Builder paramsBuilder) { @@ -179,82 +73,11 @@ static String parseOrderBys(List orderBys) { return orderBys.stream() .map( orderBy -> - prepareFieldDataAccessorExpr(orderBy.getField()) + PostgresUtils.prepareFieldDataAccessorExpr( + orderBy.getField(), PostgresUtils.DOCUMENT_COLUMN) + " " + (orderBy.isAsc() ? "ASC" : "DESC")) .filter(str -> !StringUtils.isEmpty(str)) .collect(Collectors.joining(" , ")); } - - private static String prepareParameterizedStringForList( - List values, Params.Builder paramsBuilder) { - String collect = - values.stream() - .map( - val -> { - paramsBuilder.addObjectParam(val); - return QUESTION_MARK; - }) - .collect(Collectors.joining(", ")); - return "(" + collect + ")"; - } - - private static StringBuilder prepareFieldAccessorExpr(String fieldName) { - // Generate json field accessor statement - if (!OUTER_COLUMNS.contains(fieldName)) { - StringBuilder filterString = new StringBuilder(DOCUMENT); - String[] nestedFields = fieldName.split(DOC_PATH_SEPARATOR); - for (String nestedField : nestedFields) { - filterString.append(JSON_FIELD_ACCESSOR).append("'").append(nestedField).append("'"); - } - return filterString; - } - // Field accessor is only applicable to jsonb fields, return null otherwise - return null; - } - - /** - * Add field prefix for searching into json document based on postgres syntax, handles nested - * keys. Note: It doesn't handle array elements in json document. e.g SELECT * FROM TABLE where - * document ->> 'first' = 'name' and document -> 'address' ->> 'pin' = "00000" - */ - private static String prepareFieldDataAccessorExpr(String fieldName) { - StringBuilder fieldPrefix = new StringBuilder(fieldName); - if (!OUTER_COLUMNS.contains(fieldName)) { - fieldPrefix = new StringBuilder(DOCUMENT); - String[] nestedFields = fieldName.split(DOC_PATH_SEPARATOR); - for (int i = 0; i < nestedFields.length - 1; i++) { - fieldPrefix.append(JSON_FIELD_ACCESSOR).append("'").append(nestedFields[i]).append("'"); - } - fieldPrefix - .append(JSON_DATA_ACCESSOR) - .append("'") - .append(nestedFields[nestedFields.length - 1]) - .append("'"); - } - return fieldPrefix.toString(); - } - - private static String prepareCast(String field, Object value) { - String fmt = "CAST (%s AS %s)"; - - // handle the case if the value type is collection for filter operator - `IN` - // Currently, for `IN` operator, we are considering List collection, and it is fair - // assumption that all its value of the same types. Based on that and for consistency - // we will use CAST ( as ) for all non string operator. - // Ref : https://github.com/hypertrace/document-store/pull/30#discussion_r571782575 - - if (value instanceof List && ((List) value).size() > 0) { - List listValue = (List) value; - value = listValue.get(0); - } - - if (value instanceof Number) { - return String.format(fmt, field, "NUMERIC"); - } else if (value instanceof Boolean) { - return String.format(fmt, field, "BOOLEAN"); - } else /* default is string */ { - return field; - } - } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java index 9448f322..5e992def 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java @@ -15,11 +15,8 @@ import org.apache.commons.lang3.StringUtils; import org.hypertrace.core.documentstore.postgres.Params; import org.hypertrace.core.documentstore.postgres.Params.Builder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class PostgresUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(PostgresUtils.class); private static final String QUESTION_MARK = "?"; private static final String JSON_FIELD_ACCESSOR = "->"; private static final String JSON_DATA_ACCESSOR = "->>"; @@ -28,6 +25,8 @@ public class PostgresUtils { public static final Set OUTER_COLUMNS = new TreeSet<>(List.of(ID, CREATED_AT, UPDATED_AT)); + public static final String ID_COLUMN = ID; + public static final String DOCUMENT_COLUMN = DOCUMENT; public enum Type { @@ -47,10 +46,8 @@ public static StringBuilder prepareFieldAccessorExpr(String fieldName, String co } return filterString; } - // Field accessor is only applicable to jsonb fields, return null otherwise - LOGGER.warn( - "Returning null string for field name {} and column name {}", fieldName, columnName); - return null; + // There is no need of field accessor in case of outer column. + return new StringBuilder(fieldName); } /** @@ -187,7 +184,7 @@ public static String parseNonCompositeFilter( // https://github.com/hypertrace/document-store/pull/20#discussion_r547101520Other // so, we need - "document->key IS NULL OR document->key->> NOT IN (v1, v2)" StringBuilder notInFilterString = prepareFieldAccessorExpr(fieldName, columnName); - if (notInFilterString != null) { + if (notInFilterString != null && !OUTER_COLUMNS.contains(fieldName)) { filterString = notInFilterString.append(" IS NULL OR ").append(fullFieldName); } sqlOperator = " NOT IN "; @@ -206,7 +203,7 @@ public static String parseNonCompositeFilter( value = null; // For fields inside jsonb StringBuilder notExists = prepareFieldAccessorExpr(fieldName, columnName); - if (notExists != null) { + if (notExists != null && !OUTER_COLUMNS.contains(fieldName)) { filterString = notExists; } break; @@ -215,7 +212,7 @@ public static String parseNonCompositeFilter( value = null; // For fields inside jsonb StringBuilder exists = prepareFieldAccessorExpr(fieldName, columnName); - if (exists != null) { + if (exists != null && !OUTER_COLUMNS.contains(fieldName)) { filterString = exists; } break; @@ -230,7 +227,7 @@ public static String parseNonCompositeFilter( // "document->key IS NULL OR document->key->> != value" StringBuilder notEquals = prepareFieldAccessorExpr(fieldName, columnName); // For fields inside jsonb - if (notEquals != null) { + if (notEquals != null && !OUTER_COLUMNS.contains(fieldName)) { filterString = notEquals.append(" IS NULL OR ").append(fullFieldName); } break; diff --git a/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParserTest.java b/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParserTest.java index a83773f3..3979c76e 100644 --- a/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParserTest.java +++ b/document-store/src/test/java/org/hypertrace/core/documentstore/postgres/PostgresQueryParserTest.java @@ -10,6 +10,7 @@ import org.hypertrace.core.documentstore.Filter; import org.hypertrace.core.documentstore.Filter.Op; import org.hypertrace.core.documentstore.OrderBy; +import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -19,49 +20,97 @@ class PostgresQueryParserTest { void testParseNonCompositeFilter() { { Filter filter = new Filter(Filter.Op.EQ, ID, "val1"); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals(ID + " = ?", query); } { Filter filter = new Filter(Filter.Op.NEQ, ID, "val1"); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals(ID + " != ?", query); } { Filter filter = new Filter(Filter.Op.GT, ID, 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals("CAST (" + ID + " AS NUMERIC) > ?", query); } { Filter filter = new Filter(Filter.Op.GTE, ID, 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals("CAST (" + ID + " AS NUMERIC) >= ?", query); } { Filter filter = new Filter(Filter.Op.LT, ID, 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals("CAST (" + ID + " AS NUMERIC) < ?", query); } { Filter filter = new Filter(Filter.Op.LTE, ID, 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals("CAST (" + ID + " AS NUMERIC) <= ?", query); } { Filter filter = new Filter(Filter.Op.LIKE, ID, "abc"); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals(ID + " ILIKE ?", query); } { Filter filter = new Filter(Filter.Op.IN, ID, List.of("abc", "xyz")); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = + PostgresUtils.parseNonCompositeFilter( + filter.getFieldName(), + PostgresUtils.DOCUMENT_COLUMN, + filter.getOp().toString(), + filter.getValue(), + initParams()); Assertions.assertEquals(ID + " IN (?, ?)", query); } } @@ -70,74 +119,74 @@ void testParseNonCompositeFilter() { void testParseNonCompositeFilterForJsonField() { { Filter filter = new Filter(Filter.Op.EQ, "key1", "val1"); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("document->>'key1' = ?", query); } { Filter filter = new Filter(Filter.Op.NEQ, "key1", "val1"); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("document->'key1' IS NULL OR document->>'key1' != ?", query); } { Filter filter = new Filter(Filter.Op.GT, "key1", 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("CAST (document->>'key1' AS NUMERIC) > ?", query); } { Filter filter = new Filter(Filter.Op.GTE, "key1", 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("CAST (document->>'key1' AS NUMERIC) >= ?", query); } { Filter filter = new Filter(Filter.Op.LT, "key1", 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("CAST (document->>'key1' AS NUMERIC) < ?", query); } { Filter filter = new Filter(Filter.Op.LTE, "key1", 5); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("CAST (document->>'key1' AS NUMERIC) <= ?", query); } { Filter filter = new Filter(Filter.Op.LIKE, "key1", "abc"); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("document->>'key1' ILIKE ?", query); } { Filter filter = new Filter(Filter.Op.IN, "key1", List.of("abc", "xyz")); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("document->>'key1' IN (?, ?)", query); } { Filter filter = new Filter(Op.NOT_IN, "key1", List.of("abc", "xyz")); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("document->'key1' IS NULL OR document->>'key1' NOT IN (?, ?)", query); } { Filter filter = new Filter(Filter.Op.EQ, DOCUMENT_ID, "k1:k2"); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("document->>'_id' = ?", query); } { Filter filter = new Filter(Filter.Op.EXISTS, "key1.key2", null); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); System.err.println(query); Assertions.assertEquals("document->'key1'->'key2' IS NOT NULL ", query); } { Filter filter = new Filter(Filter.Op.NOT_EXISTS, "key1", null); - String query = PostgresQueryParser.parseNonCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("document->'key1' IS NULL ", query); } } @@ -151,7 +200,7 @@ void testNonCompositeFilterUnsupportedException() { Exception exception = assertThrows( UnsupportedOperationException.class, - () -> PostgresQueryParser.parseNonCompositeFilter(filter, initParams())); + () -> PostgresQueryParser.parseFilter(filter, initParams())); String actualMessage = exception.getMessage(); Assertions.assertTrue(actualMessage.contains(expected)); } @@ -174,7 +223,7 @@ void testParseQueryForCompositeFilter() { { Filter filter = new Filter(Filter.Op.EQ, ID, "val1").and(new Filter(Filter.Op.EQ, CREATED_AT, "val2")); - String query = PostgresQueryParser.parseCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals(String.format("(%s = ?) AND (%s = ?)", ID, CREATED_AT), query); } @@ -182,7 +231,7 @@ void testParseQueryForCompositeFilter() { Filter filter = new Filter(Filter.Op.EQ, ID, "val1").or(new Filter(Filter.Op.EQ, CREATED_AT, "val2")); - String query = PostgresQueryParser.parseCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals(String.format("(%s = ?) OR (%s = ?)", ID, CREATED_AT), query); } } @@ -192,7 +241,7 @@ void testParseQueryForCompositeFilterForJsonField() { { Filter filter = new Filter(Filter.Op.EQ, "key1", "val1").and(new Filter(Filter.Op.EQ, "key2", "val2")); - String query = PostgresQueryParser.parseCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("(document->>'key1' = ?) AND (document->>'key2' = ?)", query); } @@ -200,7 +249,7 @@ void testParseQueryForCompositeFilterForJsonField() { Filter filter = new Filter(Filter.Op.EQ, "key1", "val1").or(new Filter(Filter.Op.EQ, "key2", "val2")); - String query = PostgresQueryParser.parseCompositeFilter(filter, initParams()); + String query = PostgresQueryParser.parseFilter(filter, initParams()); Assertions.assertEquals("(document->>'key1' = ?) OR (document->>'key2' = ?)", query); } }