Skip to content

Commit 8375ac6

Browse files
committed
Introduce flat collection types
1 parent df5c5f6 commit 8375ac6

File tree

17 files changed

+311
-98
lines changed

17 files changed

+311
-98
lines changed

document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@
9090
import org.hypertrace.core.documentstore.expression.impl.AliasedIdentifierExpression;
9191
import org.hypertrace.core.documentstore.expression.impl.ArrayIdentifierExpression;
9292
import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression;
93-
import org.hypertrace.core.documentstore.expression.impl.ArrayType;
9493
import org.hypertrace.core.documentstore.expression.impl.ConstantExpression;
9594
import org.hypertrace.core.documentstore.expression.impl.FunctionExpression;
9695
import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression;
@@ -4171,7 +4170,7 @@ void testInStringArray(String dataStoreName) throws JsonProcessingException {
41714170
.addSelection(ArrayIdentifierExpression.of("tags"))
41724171
.setFilter(
41734172
RelationalExpression.of(
4174-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4173+
ArrayIdentifierExpression.ofStrings("tags"),
41754174
IN,
41764175
ConstantExpression.ofStrings(List.of("hygiene", "grooming"))))
41774176
.build();
@@ -4223,7 +4222,7 @@ void testNotInStringArray(String dataStoreName) throws JsonProcessingException {
42234222
.addSelection(ArrayIdentifierExpression.of("tags"))
42244223
.setFilter(
42254224
RelationalExpression.of(
4226-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4225+
ArrayIdentifierExpression.ofStrings("tags"),
42274226
NOT_IN,
42284227
ConstantExpression.ofStrings(List.of("premium", "hygiene"))))
42294228
.build();
@@ -4271,7 +4270,7 @@ void testInIntArray(String dataStoreName) throws JsonProcessingException {
42714270
.addSelection(ArrayIdentifierExpression.of("numbers"))
42724271
.setFilter(
42734272
RelationalExpression.of(
4274-
ArrayIdentifierExpression.of("numbers", ArrayType.INTEGER),
4273+
ArrayIdentifierExpression.ofInts("numbers"),
42754274
IN,
42764275
ConstantExpression.ofNumbers(List.of(1, 10, 20))))
42774276
.build();
@@ -4318,7 +4317,7 @@ void testInDoubleArray(String dataStoreName) throws JsonProcessingException {
43184317
.addSelection(ArrayIdentifierExpression.of("scores"))
43194318
.setFilter(
43204319
RelationalExpression.of(
4321-
ArrayIdentifierExpression.of("scores", ArrayType.DOUBLE_PRECISION),
4320+
ArrayIdentifierExpression.ofDoubles("scores"),
43224321
IN,
43234322
ConstantExpression.ofNumbers(List.of(3.14, 5.0))))
43244323
.build();
@@ -4365,12 +4364,11 @@ void testInWithUnnest(String dataStoreName) {
43654364
.addSelection(ArrayIdentifierExpression.of("tags"))
43664365
.addFromClause(
43674366
UnnestExpression.of(
4368-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4369-
preserveNullAndEmptyArrays))
4367+
ArrayIdentifierExpression.ofStrings("tags"), preserveNullAndEmptyArrays))
43704368
// Should return unnested tag elements that match 'hygiene' OR 'grooming'
43714369
.setFilter(
43724370
RelationalExpression.of(
4373-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4371+
ArrayIdentifierExpression.ofStrings("tags"),
43744372
IN,
43754373
ConstantExpression.ofStrings(List.of("hygiene", "grooming"))))
43764374
.build();
@@ -4403,11 +4401,10 @@ void testNotInWithUnnest(String dataStoreName) {
44034401
.addSelection(ArrayIdentifierExpression.of("tags"))
44044402
.addFromClause(
44054403
UnnestExpression.of(
4406-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4407-
preserveNullAndEmptyArrays))
4404+
ArrayIdentifierExpression.ofStrings("tags"), preserveNullAndEmptyArrays))
44084405
.setFilter(
44094406
RelationalExpression.of(
4410-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4407+
ArrayIdentifierExpression.ofStrings("tags"),
44114408
NOT_IN,
44124409
ConstantExpression.ofStrings(List.of("hygiene", "grooming"))))
44134410
.build();
@@ -4439,16 +4436,15 @@ void testEmptyWithUnnest(String dataStoreName) {
44394436
Query.builder()
44404437
.addSelection(IdentifierExpression.of("item"))
44414438
.addSelection(ArrayIdentifierExpression.of("tags"))
4442-
.addFromClause(
4443-
UnnestExpression.of(ArrayIdentifierExpression.of("tags", ArrayType.TEXT), true))
4439+
.addFromClause(UnnestExpression.of(ArrayIdentifierExpression.ofStrings("tags"), true))
44444440
// Only include tags[] that are either NULL or empty (we have one row with NULL tag
44454441
// and one with empty tag. Unnest will result in two rows with NULL for
44464442
// "tags_unnested"). Note that this behavior will change with
44474443
// preserveNulLAndEmptyArrays = false. This is because unnest won't preserve those
44484444
// rows for which the unnested column is NULL then.
44494445
.setFilter(
44504446
RelationalExpression.of(
4451-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4447+
ArrayIdentifierExpression.ofStrings("tags"),
44524448
NOT_EXISTS,
44534449
ConstantExpression.of("null")))
44544450
.build();
@@ -4474,13 +4470,12 @@ void testNotEmptyWithUnnest(String dataStoreName) {
44744470
Query.builder()
44754471
.addSelection(IdentifierExpression.of("item"))
44764472
.addSelection(ArrayIdentifierExpression.of("tags"))
4477-
.addFromClause(
4478-
UnnestExpression.of(ArrayIdentifierExpression.of("tags", ArrayType.TEXT), true))
4473+
.addFromClause(UnnestExpression.of(ArrayIdentifierExpression.ofStrings("tags"), true))
44794474
// Only include tags[] that have at least 1 element, all rows with NULL or empty tags
44804475
// should be excluded.
44814476
.setFilter(
44824477
RelationalExpression.of(
4483-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4478+
ArrayIdentifierExpression.ofStrings("tags"),
44844479
EXISTS,
44854480
ConstantExpression.of("null")))
44864481
.build();
@@ -4505,11 +4500,10 @@ void testContainsStrArrayWithUnnest(String dataStoreName) {
45054500
Query query =
45064501
Query.builder()
45074502
.addSelection(IdentifierExpression.of("item"))
4508-
.addFromClause(
4509-
UnnestExpression.of(ArrayIdentifierExpression.of("tags", ArrayType.TEXT), true))
4503+
.addFromClause(UnnestExpression.of(ArrayIdentifierExpression.ofStrings("tags"), true))
45104504
.setFilter(
45114505
RelationalExpression.of(
4512-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4506+
ArrayIdentifierExpression.ofStrings("tags"),
45134507
CONTAINS,
45144508
ConstantExpression.ofStrings(List.of("hygiene", "premium"))))
45154509
.build();
@@ -4532,10 +4526,10 @@ void testContainsStrArray(String dataStoreName) throws JsonProcessingException {
45324526
Query query =
45334527
Query.builder()
45344528
.addSelection(IdentifierExpression.of("item"))
4535-
.addSelection(ArrayIdentifierExpression.of("tags", ArrayType.TEXT))
4529+
.addSelection(ArrayIdentifierExpression.ofStrings("tags"))
45364530
.setFilter(
45374531
RelationalExpression.of(
4538-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4532+
ArrayIdentifierExpression.ofStrings("tags"),
45394533
CONTAINS,
45404534
ConstantExpression.ofStrings(List.of("hygiene", "personal-care"))))
45414535
.build();
@@ -4584,7 +4578,7 @@ void testNotContainsStrArray(String dataStoreName) throws JsonProcessingExceptio
45844578
.addSelection(ArrayIdentifierExpression.of("tags"))
45854579
.setFilter(
45864580
RelationalExpression.of(
4587-
ArrayIdentifierExpression.of("tags", ArrayType.TEXT),
4581+
ArrayIdentifierExpression.ofStrings("tags"),
45884582
NOT_CONTAINS,
45894583
ConstantExpression.ofStrings(List.of("hair-care", "personal-care"))))
45904584
.build();
@@ -4628,10 +4622,10 @@ void testContainsOnIntArray(String dataStoreName) throws JsonProcessingException
46284622
Query query =
46294623
Query.builder()
46304624
.addSelection(IdentifierExpression.of("item"))
4631-
.addSelection(ArrayIdentifierExpression.of("numbers", ArrayType.INTEGER))
4625+
.addSelection(ArrayIdentifierExpression.ofInts("numbers"))
46324626
.setFilter(
46334627
RelationalExpression.of(
4634-
ArrayIdentifierExpression.of("numbers", ArrayType.INTEGER),
4628+
ArrayIdentifierExpression.ofInts("numbers"),
46354629
CONTAINS,
46364630
ConstantExpression.ofNumbers(List.of(1, 2))))
46374631
.build();
@@ -4675,10 +4669,10 @@ void testNotContainsOnIntArray(String dataStoreName) throws JsonProcessingExcept
46754669
Query query =
46764670
Query.builder()
46774671
.addSelection(IdentifierExpression.of("item"))
4678-
.addSelection(ArrayIdentifierExpression.of("numbers", ArrayType.INTEGER))
4672+
.addSelection(ArrayIdentifierExpression.ofInts("numbers"))
46794673
.setFilter(
46804674
RelationalExpression.of(
4681-
ArrayIdentifierExpression.of("numbers", ArrayType.INTEGER),
4675+
ArrayIdentifierExpression.ofInts("numbers"),
46824676
NOT_CONTAINS,
46834677
ConstantExpression.ofNumbers(List.of(10, 20))))
46844678
.build();
@@ -4724,10 +4718,10 @@ void testContainsOnDoubleArray(String dataStoreName) throws JsonProcessingExcept
47244718
Query query =
47254719
Query.builder()
47264720
.addSelection(IdentifierExpression.of("item"))
4727-
.addSelection(ArrayIdentifierExpression.of("scores", ArrayType.DOUBLE_PRECISION))
4721+
.addSelection(ArrayIdentifierExpression.ofDoubles("scores"))
47284722
.setFilter(
47294723
RelationalExpression.of(
4730-
ArrayIdentifierExpression.of("scores", ArrayType.DOUBLE_PRECISION),
4724+
ArrayIdentifierExpression.ofDoubles("scores"),
47314725
CONTAINS,
47324726
ConstantExpression.ofNumbers(List.of(3.14, 2.71))))
47334727
.build();

document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/AliasedIdentifierExpression.java

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
package org.hypertrace.core.documentstore.expression.impl;
22

33
import com.google.common.base.Preconditions;
4+
import lombok.EqualsAndHashCode;
45
import lombok.Value;
56
import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor;
67

78
/**
89
* Expression for referencing an identifier/column name within a context having an alias.
910
*
1011
* <p>Example: In this query: <code>
11-
* SELECT item, quantity, date
12-
* FROM <implicit_collection>
13-
* JOIN (
14-
* SELECT item, MAX(date) AS latest_date
15-
* FROM <implicit_collection>
16-
* GROUP BY item
17-
* ) AS latest
18-
* ON item = latest.item
19-
* ORDER BY `item` ASC;
12+
* SELECT item, quantity, date FROM <implicit_collection> JOIN ( SELECT item, MAX(date) AS
13+
* latest_date FROM <implicit_collection> GROUP BY item ) AS latest ON item = latest.item ORDER BY
14+
* `item` ASC;
2015
* </code> the rhs of the join condition "latest.item" can be expressed as: <code>
21-
* AliasedIdentifierExpression.builder().name("item").alias("alias1").build() </code>
16+
* AliasedIdentifierExpression.builder().name("item").alias("alias1").build() </code>
2217
*/
18+
@EqualsAndHashCode(callSuper = true)
2319
@Value
2420
public class AliasedIdentifierExpression extends IdentifierExpression {
21+
2522
String contextAlias;
2623

2724
private AliasedIdentifierExpression(final String name, final String contextAlias) {
28-
super(name);
25+
super(name, null);
2926
this.contextAlias = contextAlias;
3027
}
3128

@@ -44,6 +41,7 @@ public static AliasedIdentifierExpressionBuilder builder() {
4441
}
4542

4643
public static class AliasedIdentifierExpressionBuilder {
44+
4745
private String name;
4846
private String contextAlias;
4947

document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayIdentifierExpression.java

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,126 @@
1414
@EqualsAndHashCode(callSuper = true)
1515
public class ArrayIdentifierExpression extends IdentifierExpression {
1616

17-
private final ArrayType arrayType;
17+
private final FlatCollectionDataType arrayElementType;
1818

1919
public ArrayIdentifierExpression(String name) {
2020
this(name, null);
2121
}
2222

23-
public ArrayIdentifierExpression(String name, ArrayType arrayType) {
24-
super(name);
25-
this.arrayType = arrayType;
23+
// Package-private: used internally by factory methods
24+
ArrayIdentifierExpression(String name, FlatCollectionDataType arrayElementType) {
25+
super(name, null);
26+
this.arrayElementType = arrayElementType;
2627
}
2728

2829
public static ArrayIdentifierExpression of(String name) {
2930
return new ArrayIdentifierExpression(name);
3031
}
3132

32-
public static ArrayIdentifierExpression of(String name, ArrayType arrayType) {
33-
return new ArrayIdentifierExpression(name, arrayType);
33+
// Package-private: used internally by factory methods
34+
static ArrayIdentifierExpression of(String name, FlatCollectionDataType arrayElementType) {
35+
return new ArrayIdentifierExpression(name, arrayElementType);
3436
}
3537

36-
/** Returns the array type if specified, empty otherwise */
37-
public Optional<ArrayType> getArrayType() {
38-
return Optional.ofNullable(arrayType);
38+
public static ArrayIdentifierExpression ofStrings(final String name) {
39+
return of(name, PostgresDataType.TEXT);
40+
}
41+
42+
public static ArrayIdentifierExpression ofInts(final String name) {
43+
return of(name, PostgresDataType.INTEGER);
44+
}
45+
46+
public static ArrayIdentifierExpression ofLongs(final String name) {
47+
return of(name, PostgresDataType.BIGINT);
48+
}
49+
50+
public static ArrayIdentifierExpression ofShorts(final String name) {
51+
return of(name, PostgresDataType.SMALLINT);
52+
}
53+
54+
public static ArrayIdentifierExpression ofFloats(final String name) {
55+
return of(name, PostgresDataType.FLOAT);
56+
}
57+
58+
public static ArrayIdentifierExpression ofDoubles(final String name) {
59+
return of(name, PostgresDataType.DOUBLE);
60+
}
61+
62+
public static ArrayIdentifierExpression ofDecimals(final String name) {
63+
return of(name, PostgresDataType.NUMERIC);
64+
}
65+
66+
public static ArrayIdentifierExpression ofBooleans(final String name) {
67+
return of(name, PostgresDataType.BOOLEAN);
68+
}
69+
70+
public static ArrayIdentifierExpression ofTimestamps(final String name) {
71+
return of(name, PostgresDataType.TIMESTAMP);
72+
}
73+
74+
public static ArrayIdentifierExpression ofTimestampsTz(final String name) {
75+
return of(name, PostgresDataType.TIMESTAMPTZ);
76+
}
77+
78+
public static ArrayIdentifierExpression ofDates(final String name) {
79+
return of(name, PostgresDataType.DATE);
80+
}
81+
82+
public static ArrayIdentifierExpression ofUuids(final String name) {
83+
return of(name, PostgresDataType.UUID);
84+
}
85+
86+
// Package-private: used internally by getPostgresArrayTypeString()
87+
Optional<FlatCollectionDataType> getArrayElementType() {
88+
return Optional.ofNullable(arrayElementType);
89+
}
90+
91+
/**
92+
* Returns the PostgreSQL array type string for this expression, if the element type is specified.
93+
*
94+
* <p>Examples:
95+
*
96+
* <ul>
97+
* <li>TEXT → "text[]"
98+
* <li>INTEGER → "integer[]"
99+
* <li>BOOLEAN → "boolean[]"
100+
* <li>FLOAT → "real[]"
101+
* <li>DOUBLE → "double precision[]"
102+
* </ul>
103+
*
104+
* @return Optional containing the PostgreSQL array type string, or empty if no type is specified
105+
* @throws IllegalArgumentException if the element type is not a PostgresDataType
106+
*/
107+
public Optional<String> getPostgresArrayTypeString() {
108+
return getArrayElementType().map(this::toPostgresArrayType);
109+
}
110+
111+
private String toPostgresArrayType(FlatCollectionDataType type) {
112+
if (!(type instanceof PostgresDataType)) {
113+
throw new IllegalArgumentException(
114+
"Only PostgresDataType is currently supported, got: " + type.getClass());
115+
}
116+
PostgresDataType postgresType = (PostgresDataType) type;
117+
String baseType = postgresType.getPgTypeName();
118+
119+
// Map internal type names to SQL array type names
120+
switch (baseType) {
121+
case "int4":
122+
return "integer[]";
123+
case "int8":
124+
return "bigint[]";
125+
case "int2":
126+
return "smallint[]";
127+
case "float4":
128+
return "real[]";
129+
case "float8":
130+
return "double precision[]";
131+
case "bool":
132+
return "boolean[]";
133+
default:
134+
// For most types, just append []
135+
return baseType + "[]";
136+
}
39137
}
40138

41139
/**

document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayType.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)