From 58ca77f2af6a7b2cf7b8a510f3041d1c4b7afaea Mon Sep 17 00:00:00 2001 From: Mihai Budiu Date: Sat, 15 Nov 2025 16:15:20 -0800 Subject: [PATCH] [SQL] Implement MAP_VALUES function Signed-off-by: Mihai Budiu --- crates/sqllib/src/map.rs | 18 +++++++++ docs.feldera.com/docs/sql/function-index.md | 1 + docs.feldera.com/docs/sql/map.md | 1 + .../compiler/frontend/ExpressionCompiler.java | 1 + .../calciteCompiler/CalciteFunctions.java | 4 +- .../FunctionDocumentation.java | 2 +- .../compiler/sql/simple/MapTests.java | 40 ++++++++++++++++++- .../compiler/sql/simple/Regression1Tests.java | 1 + 8 files changed, 65 insertions(+), 3 deletions(-) diff --git a/crates/sqllib/src/map.rs b/crates/sqllib/src/map.rs index 457ec217ac1..04677cbc95e 100644 --- a/crates/sqllib/src/map.rs +++ b/crates/sqllib/src/map.rs @@ -290,3 +290,21 @@ where { value.map(|value| map_keys_(value)) } + +#[doc(hidden)] +pub fn map_values_(value: Map) -> Array +where + I: Ord + Clone, + T: Clone, +{ + Arc::new(value.values().cloned().collect()) +} + +#[doc(hidden)] +pub fn map_valuesN(value: Option>) -> Option> +where + I: Ord + Clone, + T: Clone, +{ + value.map(|value| map_values_(value)) +} diff --git a/docs.feldera.com/docs/sql/function-index.md b/docs.feldera.com/docs/sql/function-index.md index 0d02981b9b4..fb498dbd02d 100644 --- a/docs.feldera.com/docs/sql/function-index.md +++ b/docs.feldera.com/docs/sql/function-index.md @@ -151,6 +151,7 @@ * `MAP`: [map](map.md#map-literals) * `MAP_CONTAINS_KEY`: [map](map.md#map_contains_key) * `MAP_KEYS`: [map](map.md#map_keys) +* `MAP_VALUES`: [map](map.md#map_values) * `MAX` (aggregate): [aggregates](aggregates.md#max), [aggregates](aggregates.md#window-max) * `MD5`: [string](string.md#md5), [binary](binary.md#md5) * `MIN` (aggregate): [aggregates](aggregates.md#min), [aggregates](aggregates.md#window-min) diff --git a/docs.feldera.com/docs/sql/map.md b/docs.feldera.com/docs/sql/map.md index 90d844d71b9..6e70b523e53 100644 --- a/docs.feldera.com/docs/sql/map.md +++ b/docs.feldera.com/docs/sql/map.md @@ -49,6 +49,7 @@ Comparison operations (`=`, `<>`, `!=`, `>`, `<`, `>=`, `<=`) can be applied to | `CARDINALITY`(map) | Returns the number of key-value pairs in the map. | `CARDINALITY(MAP['x', 4])` => 1 | | `MAP_CONTAINS_KEY`(map, key) | Returns true when the map has an item with the specified key; `NULL` if any argument is `NULL`. | `MAP_CONTAINS_KEY(MAP['x', 4], 'x')` => `true` | | `MAP_KEYS`(map) | Returns a sorted ARRAY of the appropriate type with all the keys of the MAP. | `MAP_KEYS(MAP['x', 4, 'y', 5])` => `['x', 'y']` | +| `MAP_VALUES`(map) | Returns an ARRAY of the appropriate type with all the values of the MAP. | `MAP_VALUES(MAP['x', 4, 'y', 5])` => `[4, 5]` | ## The `UNNEST` Operator diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/ExpressionCompiler.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/ExpressionCompiler.java index fe1a17dcdee..94e25b46fc4 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/ExpressionCompiler.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/ExpressionCompiler.java @@ -1892,6 +1892,7 @@ else if (arg0Type.is(DBSPTypeMap.class)) return next; } case MAP_KEYS: + case MAP_VALUES: validateArgCount(node, operationName, ops.size(), 1); DBSPExpression arg0 = ops.get(0); String method = getArrayOrMapCallName(call, arg0); diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/CalciteFunctions.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/CalciteFunctions.java index 2cedaf63a2f..336ace9c67a 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/CalciteFunctions.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/CalciteFunctions.java @@ -340,7 +340,9 @@ record Func(SqlOperator function, String functionName, SqlLibrary library, new Func(SqlLibraryOperators.IFNULL, "IFNULL", SqlLibrary.BIG_QUERY, "comparisons#ifnull", FunctionDocumentation.NO_FILE, false), new Func(SqlLibraryOperators.MAP_KEYS, "MAP_KEYS", SqlLibrary.SPARK, - "map#map_keys", FunctionDocumentation.NO_FILE, false) + "map#map_keys", FunctionDocumentation.NO_FILE, false), + new Func(SqlLibraryOperators.MAP_VALUES, "MAP_VALUES", SqlLibrary.SPARK, + "map#map_values", FunctionDocumentation.NO_FILE, false) // new Func(SqlLibraryOperators.SAFE_ORDINAL, "SAFE_ORDINAL", SqlLibrary.BIG_QUERY, "array", FunctionDocumentation.NO_FILE, false), }; diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/FunctionDocumentation.java b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/FunctionDocumentation.java index 41ab3b2829c..2f9fbfa9e7e 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/FunctionDocumentation.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/main/java/org/dbsp/sqlCompiler/compiler/frontend/calciteCompiler/FunctionDocumentation.java @@ -132,7 +132,7 @@ static boolean sameFunction(FunctionDescription left, FunctionDescription right) static void checkTestedBy(FunctionDescription description, TextFileCache fileContents) throws IOException { // A list of file names separated by pipe symbols - String[] patterns = description.testedBy().split("\\|"); + String[] patterns = description.testedBy().replace("\n", "").split("\\|"); Path pythonTests = Paths.get(PYTHON_TESTS); for (String pattern: patterns) { if (pattern.equalsIgnoreCase("nofile.py")) diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/MapTests.java b/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/MapTests.java index 78dcd41c900..279f105f822 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/MapTests.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/MapTests.java @@ -10,6 +10,7 @@ import org.dbsp.sqlCompiler.ir.expression.DBSPTupleExpression; import org.dbsp.sqlCompiler.ir.expression.literal.DBSPI32Literal; import org.dbsp.sqlCompiler.ir.expression.DBSPMapExpression; +import org.dbsp.sqlCompiler.ir.expression.literal.DBSPLiteral; import org.dbsp.sqlCompiler.ir.expression.literal.DBSPStringLiteral; import org.dbsp.sqlCompiler.ir.expression.DBSPZSetExpression; import org.dbsp.sqlCompiler.ir.type.DBSPType; @@ -184,7 +185,7 @@ public void testMapKeys() { } @Test - public void mapVariant() { + public void mapKeyVariant() { var ccs = this.getCCS(""" create table j(j VARCHAR); @@ -207,4 +208,41 @@ SELECT cast(contacts as MAP) contacts d| 1 e| 1"""); } + + @Test + public void testMapValues() { + String sql = "SELECT map_values(map['foo', 1, 'bar', 2])"; + DBSPZSetExpression result = new DBSPZSetExpression( + new DBSPTupleExpression(new DBSPArrayExpression( + false, + new DBSPI32Literal(2), + new DBSPI32Literal(1)))); + this.testQuery("", sql, new InputOutputChangeStream() + .addPair(new Change(), new Change("V", result))); + } + + @Test + public void mapValuesVariant() { + var ccs = this.getCCS(""" + create table j(j VARCHAR); + + create LOCAL view user_props AS + SELECT PARSE_JSON(j) AS contacts FROM j; + + create view abc as + WITH ref_profile AS ( + SELECT cast(contacts as MAP) contacts + FROM user_props + ) SELECT TO_JSON(value) + FROM ref_profile profile_0, UNNEST(MAP_VALUES(profile_0.contacts)) AS t(value)"""); + ccs.step(""" + INSERT INTO j VALUES('{ "a": "1", "b": 2, "c": [1, 2, 3], "d": null, "e": { "f": 1 } }');""", """ + key | weight + ------------------------ + "1"| 1 + 2| 1 + [1,2,3]| 1 + null| 1 + {"f":1}| 1"""); + } } diff --git a/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression1Tests.java b/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression1Tests.java index 9dc9e403197..a73e25adb35 100644 --- a/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression1Tests.java +++ b/sql-to-dbsp-compiler/SQL-compiler/src/test/java/org/dbsp/sqlCompiler/compiler/sql/simple/Regression1Tests.java @@ -19,6 +19,7 @@ import org.dbsp.util.Linq; import org.dbsp.util.Logger; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.sql.SQLException;