Skip to content

Commit 211fb41

Browse files
committed
Remove instanceof checks by using compile-type type
1 parent b3effa7 commit 211fb41

File tree

11 files changed

+205
-250
lines changed

11 files changed

+205
-250
lines changed

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresInRelationalFilterParserJsonPrimitive.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import org.hypertrace.core.documentstore.expression.impl.JsonIdentifierExpression;
66
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
77
import org.hypertrace.core.documentstore.postgres.Params;
8-
import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils;
98

109
/**
1110
* Optimized parser for IN operations on JSON primitive fields (string, number, boolean) with proper
@@ -69,8 +68,8 @@ private String prepareFilterStringForInOperator(
6968
return "1 = 0";
7069
}
7170

72-
// Add as single array parameter
73-
paramsBuilder.addArrayParam(values, PostgresUtils.inferSqlTypeFromValue(values));
71+
String sqlType = mapJsonFieldTypeToSqlType(fieldType);
72+
paramsBuilder.addArrayParam(values, sqlType);
7473

7574
// Apply appropriate casting based on field type
7675
String lhsWithCast = parsedLhs;
@@ -81,4 +80,16 @@ private String prepareFilterStringForInOperator(
8180
}
8281
return String.format("%s = ANY(?)", lhsWithCast);
8382
}
83+
84+
private String mapJsonFieldTypeToSqlType(JsonFieldType fieldType) {
85+
switch (fieldType) {
86+
case NUMBER:
87+
return "float8";
88+
case BOOLEAN:
89+
return "bool";
90+
case STRING:
91+
default:
92+
return "text";
93+
}
94+
}
8495
}

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresTopLevelArrayEqualityFilterParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.util.stream.StreamSupport;
66
import org.hypertrace.core.documentstore.expression.impl.ArrayIdentifierExpression;
77
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
8-
import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.nonjson.field.PostgresArrayTypeExtractor;
8+
import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.nonjson.field.PostgresTypeExtractor;
99

1010
/**
1111
* Handles EQ/NEQ operations on top-level array columns when RHS is also an array, using exact
@@ -43,7 +43,7 @@ public String parse(
4343
.collect(Collectors.joining(", "));
4444

4545
ArrayIdentifierExpression arrayExpr = (ArrayIdentifierExpression) expression.getLhs();
46-
String arrayTypeCast = arrayExpr.accept(new PostgresArrayTypeExtractor());
46+
String arrayTypeCast = arrayExpr.accept(PostgresTypeExtractor.arrayType());
4747

4848
// Generate: tags = ARRAY[?, ?]::text[]
4949
if (arrayTypeCast != null) {

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/nonjson/field/PostgresContainsRelationalFilterParserNonJsonField.java

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,11 @@ public String parse(
5050
}
5151

5252
// Field is NOT unnested - use array containment operator
53-
String arrayTypeCast = expression.getLhs().accept(new PostgresArrayTypeExtractor());
53+
String arrayType = expression.getLhs().accept(PostgresTypeExtractor.arrayType());
54+
// Fallback to text[] if type is unknown
55+
String typeCast = (arrayType != null) ? arrayType : "text[]";
5456

55-
// Use ARRAY[?, ?, ...] syntax with appropriate type cast
56-
if (arrayTypeCast != null && arrayTypeCast.equals("text[]")) {
57-
return String.format("%s @> ARRAY[%s]::text[]", parsedLhs, placeholders);
58-
} else if (arrayTypeCast != null) {
59-
// INTEGER/BOOLEAN/DOUBLE arrays: Use the correct type cast
60-
return String.format("%s @> ARRAY[%s]::%s", parsedLhs, placeholders, arrayTypeCast);
61-
} else {
62-
// Fallback: use text[] cast
63-
return String.format("%s @> ARRAY[%s]::text[]", parsedLhs, placeholders);
64-
}
57+
return String.format("%s @> ARRAY[%s]::%s", parsedLhs, placeholders, typeCast);
6558
}
6659

6760
private Iterable<Object> normalizeToIterable(final Object value) {

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/nonjson/field/PostgresDataType.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,36 @@
55
/**
66
* PostgreSQL-specific data types with their SQL type strings.
77
*
8-
* <p>This enum maps generic {@link DataType} values to PostgreSQL-specific type strings used in SQL
9-
* queries for type casting.
8+
* <p>This enum maps generic {@link DataType} values to PostgreSQL internal type names, which work
9+
* for both JDBC's {@code Connection.createArrayOf()} and SQL type casting.
1010
*/
1111
public enum PostgresDataType {
1212
TEXT("text"),
13-
INTEGER("integer"),
14-
BIGINT("bigint"),
15-
REAL("real"),
16-
DOUBLE_PRECISION("double precision"),
17-
BOOLEAN("boolean"),
13+
INTEGER("int4"),
14+
BIGINT("int8"),
15+
REAL("float4"),
16+
DOUBLE_PRECISION("float8"),
17+
BOOLEAN("bool"),
1818
TIMESTAMPTZ("timestamptz"),
1919
DATE("date"),
20-
UNKNOWN("unknown");
20+
UNKNOWN(null);
2121

2222
private final String sqlType;
2323

2424
PostgresDataType(String sqlType) {
2525
this.sqlType = sqlType;
2626
}
2727

28+
/**
29+
* Returns the PostgreSQL type name for use with JDBC's createArrayOf() and SQL casting.
30+
*
31+
* @return The type name (e.g., "int4", "float8", "text")
32+
*/
2833
public String getSqlType() {
2934
return sqlType;
3035
}
3136

37+
/** Returns the array type for SQL casting (e.g., "int4[]", "text[]"). */
3238
public String getArraySqlType() {
3339
return sqlType + "[]";
3440
}
@@ -38,7 +44,6 @@ public String getArraySqlType() {
3844
*
3945
* @param dataType the generic data type
4046
* @return the corresponding PostgresDataType, or null if UNSPECIFIED
41-
* @throws IllegalArgumentException if the DataType is unknown
4247
*/
4348
public static PostgresDataType fromDataType(DataType dataType) {
4449
switch (dataType) {
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.nonjson.field;
22

3+
import java.util.stream.Collectors;
34
import java.util.stream.StreamSupport;
45
import org.hypertrace.core.documentstore.expression.impl.ArrayIdentifierExpression;
56
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
67
import org.hypertrace.core.documentstore.postgres.Params;
78
import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresInRelationalFilterParserInterface;
89
import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParser;
9-
import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils;
1010

1111
/**
1212
* Implementation of PostgresInRelationalFilterParserInterface for handling IN operations on array
@@ -31,20 +31,22 @@ public String parse(
3131
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
3232
final Iterable<Object> parsedRhs = expression.getRhs().accept(context.rhsParser());
3333

34+
// Extract element type from expression metadata for type-safe query generation
35+
String sqlType = expression.getLhs().accept(PostgresTypeExtractor.scalarType());
36+
3437
// Check if this field has been unnested - if so, treat it as a scalar
3538
ArrayIdentifierExpression arrayExpr = (ArrayIdentifierExpression) expression.getLhs();
3639
String fieldName = arrayExpr.getName();
3740
if (context.getPgColumnNames().containsKey(fieldName)) {
3841
// Field is unnested - each element is now a scalar, not an array
3942
// Use scalar IN operator instead of array overlap
4043
return prepareFilterStringForScalarInOperator(
41-
parsedLhs, parsedRhs, context.getParamsBuilder());
44+
parsedLhs, parsedRhs, sqlType, context.getParamsBuilder());
4245
}
4346

4447
// Field is NOT unnested - use array overlap logic
45-
String arrayTypeCast = expression.getLhs().accept(new PostgresArrayTypeExtractor());
4648
return prepareFilterStringForArrayInOperator(
47-
parsedLhs, parsedRhs, arrayTypeCast, context.getParamsBuilder());
49+
parsedLhs, parsedRhs, sqlType, context.getParamsBuilder());
4850
}
4951

5052
/**
@@ -54,19 +56,22 @@ public String parse(
5456
private String prepareFilterStringForScalarInOperator(
5557
final String parsedLhs,
5658
final Iterable<Object> parsedRhs,
59+
final String sqlType,
5760
final Params.Builder paramsBuilder) {
58-
59-
Object[] values = StreamSupport.stream(parsedRhs.spliterator(), false).toArray();
60-
61-
if (values.length == 0) {
62-
// return FALSE
63-
return "1 = 0";
61+
// If type is specified, use optimized ANY(ARRAY[]) syntax
62+
// Otherwise, fall back to traditional IN (?, ?, ?) for backward compatibility
63+
if (sqlType != null) {
64+
Object[] values = StreamSupport.stream(parsedRhs.spliterator(), false).toArray();
65+
66+
if (values.length == 0) {
67+
return "1 = 0";
68+
}
69+
70+
paramsBuilder.addArrayParam(values, sqlType);
71+
return String.format("%s = ANY(?)", parsedLhs);
72+
} else {
73+
return prepareFilterStringFallback(parsedLhs, parsedRhs, paramsBuilder, "%s IN (%s)");
6474
}
65-
66-
// SQL type is needed during parameter binding
67-
paramsBuilder.addArrayParam(values, PostgresUtils.inferSqlTypeFromValue(values));
68-
69-
return String.format("%s = ANY(?)", parsedLhs);
7075
}
7176

7277
/**
@@ -78,45 +83,48 @@ private String prepareFilterStringForScalarInOperator(
7883
private String prepareFilterStringForArrayInOperator(
7984
final String parsedLhs,
8085
final Iterable<Object> parsedRhs,
81-
final String arrayType,
86+
final String sqlType,
8287
final Params.Builder paramsBuilder) {
83-
84-
// Collect all values into an array
85-
Object[] values = StreamSupport.stream(parsedRhs.spliterator(), false).toArray();
86-
87-
if (values.length == 0) {
88-
return "1 = 0";
88+
// If type is specified, use optimized array overlap with typed array
89+
// Otherwise, fall back to jsonb-based approach for backward compatibility
90+
if (sqlType != null) {
91+
Object[] values = StreamSupport.stream(parsedRhs.spliterator(), false).toArray();
92+
93+
if (values.length == 0) {
94+
return "1 = 0";
95+
}
96+
97+
paramsBuilder.addArrayParam(values, sqlType);
98+
return String.format("%s && ?", parsedLhs);
99+
} else {
100+
// Fallback: use jsonb array contains approach
101+
return prepareFilterStringFallback(parsedLhs, parsedRhs, paramsBuilder, "%s @> ARRAY[%s]");
89102
}
90-
91-
// Infer SQL type from first value or array type hint
92-
String sqlType =
93-
arrayType != null
94-
? mapArrayTypeToSqlType(arrayType)
95-
: PostgresUtils.inferSqlTypeFromValue(values);
96-
97-
// Add as single array parameter
98-
paramsBuilder.addArrayParam(values, sqlType);
99-
100-
// Use array overlap operator with single parameter
101-
return String.format("%s && ?", parsedLhs);
102103
}
103104

104-
private String mapArrayTypeToSqlType(String arrayType) {
105-
// Remove [] suffix
106-
String baseType = arrayType.replace("[]", "");
107-
108-
// Map to internal type names for createArrayOf()
109-
switch (baseType) {
110-
case "double precision":
111-
return "float8";
112-
case "integer":
113-
return "int4";
114-
case "boolean":
115-
return "bool";
116-
case "text":
117-
return "text";
118-
default:
119-
return baseType;
105+
/**
106+
* Fallback method using traditional (?, ?, ?) syntax for backward compatibility when type
107+
* information is not available.
108+
*/
109+
private String prepareFilterStringFallback(
110+
final String parsedLhs,
111+
final Iterable<Object> parsedRhs,
112+
final Params.Builder paramsBuilder,
113+
final String formatPattern) {
114+
115+
String collect =
116+
StreamSupport.stream(parsedRhs.spliterator(), false)
117+
.map(
118+
val -> {
119+
paramsBuilder.addObjectParam(val);
120+
return "?";
121+
})
122+
.collect(Collectors.joining(", "));
123+
124+
if (collect.isEmpty()) {
125+
return "1 = 0";
120126
}
127+
128+
return String.format(formatPattern, parsedLhs, collect);
121129
}
122130
}

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/nonjson/field/PostgresInRelationalFilterParserScalarField.java

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.nonjson.field;
22

3+
import java.util.stream.Collectors;
34
import java.util.stream.StreamSupport;
45
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
56
import org.hypertrace.core.documentstore.postgres.Params;
67
import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresInRelationalFilterParserInterface;
78
import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParser;
8-
import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils;
99

1010
/**
1111
* Implementation of PostgresInRelationalFilterParserInterface for handling IN operations on
@@ -21,23 +21,61 @@ public String parse(
2121
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
2222
final Iterable<Object> parsedRhs = expression.getRhs().accept(context.rhsParser());
2323

24-
return prepareFilterStringForInOperator(parsedLhs, parsedRhs, context.getParamsBuilder());
24+
// Extract type from expression metadata
25+
String sqlType = expression.getLhs().accept(PostgresTypeExtractor.scalarType());
26+
27+
// If type is specified, use optimized ANY(ARRAY[]) syntax
28+
// Otherwise, fall back to traditional IN (?, ?, ?) for backward compatibility
29+
if (sqlType != null) {
30+
return prepareFilterStringForInOperatorWithArray(
31+
parsedLhs, parsedRhs, sqlType, context.getParamsBuilder());
32+
} else {
33+
return prepareFilterStringForInOperatorFallback(
34+
parsedLhs, parsedRhs, context.getParamsBuilder());
35+
}
2536
}
2637

27-
private String prepareFilterStringForInOperator(
38+
/** Optimized IN using = ANY(?) with typed array parameter. */
39+
private String prepareFilterStringForInOperatorWithArray(
2840
final String parsedLhs,
2941
final Iterable<Object> parsedRhs,
42+
final String sqlType,
3043
final Params.Builder paramsBuilder) {
3144

3245
Object[] values = StreamSupport.stream(parsedRhs.spliterator(), false).toArray();
3346

3447
if (values.length == 0) {
35-
// return FALSE
48+
// Evaluates to FALSE
3649
return "1 = 0";
3750
}
3851

39-
paramsBuilder.addArrayParam(values, PostgresUtils.inferSqlTypeFromValue(values));
52+
paramsBuilder.addArrayParam(values, sqlType);
4053

4154
return String.format("%s = ANY(?)", parsedLhs);
4255
}
56+
57+
/**
58+
* Fallback IN using traditional IN (?, ?, ?) syntax for backward compatibility when type
59+
* information is not available.
60+
*/
61+
private String prepareFilterStringForInOperatorFallback(
62+
final String parsedLhs,
63+
final Iterable<Object> parsedRhs,
64+
final Params.Builder paramsBuilder) {
65+
66+
String collect =
67+
StreamSupport.stream(parsedRhs.spliterator(), false)
68+
.map(
69+
val -> {
70+
paramsBuilder.addObjectParam(val);
71+
return "?";
72+
})
73+
.collect(Collectors.joining(", "));
74+
75+
if (collect.isEmpty()) {
76+
return "1 = 0";
77+
}
78+
79+
return String.format("%s IN (%s)", parsedLhs, collect);
80+
}
4381
}

0 commit comments

Comments
 (0)