Skip to content

Commit 9596bf2

Browse files
committed
Translate MAP ctor and element access to JSONB for Postgres
1 parent 5e4486c commit 9596bf2

7 files changed

Lines changed: 136 additions & 7 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright © 2021 DataSQRL (contact@datasqrl.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.datasqrl.function.translation.postgres.builtinflink;
17+
18+
import static com.datasqrl.function.CalciteFunctionUtil.lightweightOp;
19+
20+
import com.datasqrl.function.translation.PostgresSqlTranslation;
21+
import com.datasqrl.function.translation.SqlTranslation;
22+
import com.google.auto.service.AutoService;
23+
import org.apache.calcite.sql.SqlCall;
24+
import org.apache.calcite.sql.SqlWriter;
25+
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
26+
import org.apache.calcite.sql.parser.SqlParserPos;
27+
28+
@AutoService(SqlTranslation.class)
29+
public class MapConstructorSqlTranslation extends PostgresSqlTranslation {
30+
31+
public MapConstructorSqlTranslation() {
32+
super(SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR);
33+
}
34+
35+
@Override
36+
public void unparse(SqlCall call, SqlWriter writer, int leftPrec, int rightPrec) {
37+
// Translate MAP['k1', v1, 'k2', v2] to jsonb_build_object('k1', v1, 'k2', v2)
38+
lightweightOp("jsonb_build_object")
39+
.createCall(SqlParserPos.ZERO, call.getOperandList())
40+
.unparse(writer, leftPrec, rightPrec);
41+
}
42+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright © 2021 DataSQRL (contact@datasqrl.com)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.datasqrl.function.translation.postgres.builtinflink;
17+
18+
import com.datasqrl.function.translation.PostgresSqlTranslation;
19+
import com.datasqrl.function.translation.SqlTranslation;
20+
import com.google.auto.service.AutoService;
21+
import org.apache.calcite.sql.SqlCall;
22+
import org.apache.calcite.sql.SqlIdentifier;
23+
import org.apache.calcite.sql.SqlKind;
24+
import org.apache.calcite.sql.SqlLiteral;
25+
import org.apache.calcite.sql.SqlWriter;
26+
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
27+
import org.apache.calcite.sql.type.SqlTypeName;
28+
29+
@AutoService(SqlTranslation.class)
30+
public class MapElementSqlTranslation extends PostgresSqlTranslation {
31+
32+
public MapElementSqlTranslation() {
33+
super(SqlStdOperatorTable.ITEM);
34+
}
35+
36+
@Override
37+
public void unparse(SqlCall call, SqlWriter writer, int leftPrec, int rightPrec) {
38+
if (call.operandCount() == 2) {
39+
var baseOperand = call.operand(0);
40+
var indexOrKey = call.operand(1);
41+
42+
var isMapBase =
43+
baseOperand instanceof SqlIdentifier
44+
|| baseOperand.getKind() == SqlKind.MAP_VALUE_CONSTRUCTOR;
45+
46+
// Heuristic: If the index/key is a string literal, treat as map access
47+
// Maps use string keys: map['name'], arrays use numeric indices: array[1]
48+
var isLikelyMapAccess =
49+
indexOrKey instanceof SqlLiteral literal && literal.getTypeName() == SqlTypeName.CHAR;
50+
51+
if (isMapBase && isLikelyMapAccess) {
52+
// Translate map['key'] to map->>'key' for PostgreSQL JSONB access
53+
call.operand(0).unparse(writer, 0, 0);
54+
writer.print("->>");
55+
call.operand(1).unparse(writer, 0, 0);
56+
return;
57+
}
58+
}
59+
60+
call.getOperator().unparse(writer, call, leftPrec, rightPrec);
61+
}
62+
}

sqrl-testing/sqrl-testing-integration/src/test/java/com/datasqrl/FullUseCaseIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class FullUseCaseIT extends AbstractFullUseCaseTest {
4949
@Test
5050
@Disabled("Intended for manual usage")
5151
void specificUseCase() {
52-
var pkg = USE_CASES.resolve("duckdb").resolve("package.json");
52+
var pkg = USE_CASES.resolve("function-translation").resolve("postgres").resolve("package.json");
5353

5454
var param = new UseCaseParam(pkg);
5555
fullUseCaseTest(param);

sqrl-testing/sqrl-testing-integration/src/test/resources/snapshots/com/datasqrl/UseCaseCompileTest/postgres-package.txt

Lines changed: 12 additions & 5 deletions
Large diffs are not rendered by default.

sqrl-testing/sqrl-testing-integration/src/test/resources/usecases/function-translation/postgres/pg-translation.sqrl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ FunctionCalls := SELECT
8383

8484
ELEMENT(ARRAY[3]) AS element_example,
8585
-- CARDINALITY(MAP['a', 1, 'b', 2]) AS cardinality_example,
86-
-- MAP['a', 1, 'b', 2]['a'] AS map_index_example,
86+
MAP['a', 1, 'b', 2]['a'] AS map_elem_access_example,
8787
ARRAY_CONTAINS(ARRAY[1, 2, 3], 2) AS array_contains_example,
8888
-- ARRAY_DISTINCT(ARRAY['a', 'b', 'b', 'c']) AS array_distinct_example,
8989
ARRAY_PREPEND(ARRAY['b', 'c'], 'a') AS array_prepend_example,
@@ -141,3 +141,11 @@ ArrayFunctionsTest :=
141141
ARRAY_POSITION(arrayagg_example, 'y') IS NOT NULL AS array_has_y,
142142
ARRAY_POSITION(arrayagg_example, 'z') IS NOT NULL AS array_has_z
143143
FROM ArrayFunctions;
144+
145+
/*+engine(postgres)*/
146+
_MapData := SELECT MAP['name', name,'city', city] AS map_elems
147+
FROM (VALUES ('Alice', 'Berlin'), ('Bob', 'Paris')) AS t(name, city);
148+
149+
/*+test */
150+
MapTest :=
151+
SELECT map_elems['name'] AS elem_name FROM _MapData;

sqrl-testing/sqrl-testing-integration/src/test/resources/usecases/function-translation/postgres/snapshots/FunctionCallsTest.snapshot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"is_decimal_example" : true,
5353
"is_digit_example" : true,
5454
"element_example" : 3,
55+
"map_elem_access_example" : 1,
5556
"array_contains_example" : true,
5657
"array_prepend_example" : [ "a", "b", "c" ],
5758
"array_concat_example" : [ "a", "b", "c", "d", "e" ],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"data" : {
3+
"MapTest" : [ {
4+
"elem_name" : "Alice"
5+
}, {
6+
"elem_name" : "Bob"
7+
} ]
8+
}
9+
}

0 commit comments

Comments
 (0)