diff --git a/docs/functions.md b/docs/functions.md index 7a92fcb8..aa8d728b 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -27,6 +27,25 @@ All of the DuckDB [json functions and aggregates](https://duckdb.org/docs/data/j | [`union_extract`](#union_extract) | Extracts a value from a union type by tag name. | | [`union_tag`](#union_tag) | Gets the tag name of the active member in a union type. | +## MAP Functions + +All of the DuckDB [map functions](https://duckdb.org/docs/sql/data_types/map.html#map-functions). + +| Name | Description | +| :--- | :---------- | +| [`cardinality`](#cardinality) | Return the size of the map | +| [`element_at`](#element_at) | Return the value for a given key as a list | +| [`map_concat`](#map_concat) | Merge multiple maps | +| [`map_contains`](#map_contains) | Check if a map contains a given key | +| [`map_contains_entry`](#map_contains_entry) | Check if a map contains a given key-value pair | +| [`map_contains_value`](#map_contains_value) | Check if a map contains a given value | +| [`map_entries`](#map_entries) | Return a list of struct(k, v) for each key-value pair | +| [`map_extract`](#map_extract) | Extract a value from a map using a key | +| [`map_extract_value`](#map_extract_value) | Return the value for a given key or NULL | +| [`map_from_entries`](#map_from_entries) | Create a map from an array of struct(k, v) | +| [`map_keys`](#map_keys) | Get all keys from a map as a list | +| [`map_values`](#map_values) | Get all values from a map as a list | + ## Aggregates |Name|Description| @@ -672,6 +691,277 @@ WHERE epoch(created_at) > 1640995200; -- After 2022-01-01 | :--- | :--- | :---------- | | timestamp_expr | timestamp | The timestamp to convert to epoch seconds | +#### `map_extract(map_col duckdb.map, key duckdb.unresolved_type) -> duckdb.unresolved_type` + +Extracts a value from a map using the specified key. If the key doesn't exist, returns an empty array. + +```sql +-- Extract value from a map +SELECT map_extract(r['map_col'], 'a') as value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: {1} + +-- Extract non-existent key +SELECT map_extract(r['map_col'], 'c') as value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: {} +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to extract from | +| key | duckdb.unresolved_type | The key to look up in the map | + +#### `map_keys(map_col duckdb.map) -> duckdb.unresolved_type` + +Returns all keys from a map as an array. + +```sql +-- Get all keys from a map +SELECT map_keys(r['map_col']) as keys +FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; +-- Returns: {a,b,c} + +-- Empty map +SELECT map_keys(r['map_col']) as keys +FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; +-- Returns: {} +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to extract keys from | + +#### `map_values(map_col duckdb.map) -> duckdb.unresolved_type` + +Returns all values from a map as an array. + +```sql +-- Get all values from a map +SELECT map_values(r['map_col']) as values +FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; +-- Returns: {1,2,3} + +-- Empty map +SELECT map_values(r['map_col']) as values +FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; +-- Returns: {} +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to extract values from | + +#### `cardinality(map_col duckdb.map) -> numeric` + +Returns the size of the map (number of key-value pairs). + +```sql +-- Get the number of entries in a map +SELECT cardinality(r['map_col']) as size +FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; +-- Returns: 3 + +-- Empty map +SELECT cardinality(r['map_col']) as size +FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; +-- Returns: 0 +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to get the size of | + +#### `element_at(map_col duckdb.map, key duckdb.unresolved_type) -> duckdb.unresolved_type` + +Returns the value for a given key as an array. + +```sql +-- Get value for a specific key +SELECT element_at(r['map_col'], 'a') as value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: {1} + +-- Non-existent key +SELECT element_at(r['map_col'], 'c') as value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: {} +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to extract from | +| key | duckdb.unresolved_type | The key to look up in the map | + +#### `map_concat(map_col duckdb.map, map_col2 duckdb.map) -> duckdb.map` + +Merges multiple maps. On key collision, the value is taken from the last map. + +```sql +-- Merge two maps +SELECT map_concat(r1['map1'], r2['map2']) as merged +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map1 $$) r1, + duckdb.query($$ SELECT MAP(['b', 'c'], [3, 4]) as map2 $$) r2; +-- Returns: {a=1, b=3, c=4} + +-- Note: 'b' value from map2 (3) overwrites map1's value (2) +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The first map | +| map_col2 | duckdb.map | The second map to merge | + +#### `map_contains(map_col duckdb.map, key duckdb.unresolved_type) -> boolean` + +Checks if a map contains a given key. + +```sql +-- Check if key exists +SELECT map_contains(r['map_col'], 'a') as has_key +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: t (true) + +-- Check for non-existent key +SELECT map_contains(r['map_col'], 'c') as has_key +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: f (false) +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to check | +| key | duckdb.unresolved_type | The key to search for | + +#### `map_contains_entry(map_col duckdb.map, key duckdb.unresolved_type, value duckdb.unresolved_type) -> boolean` + +Checks if a map contains a given key-value pair. + +```sql +-- Check if key-value pair exists +SELECT map_contains_entry(r['map_col'], 'a', 1) as has_entry +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: t (true) + +-- Check with wrong value for existing key +SELECT map_contains_entry(r['map_col'], 'a', 2) as has_entry +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: f (false) +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to check | +| key | duckdb.unresolved_type | The key to search for | +| value | duckdb.unresolved_type | The value to match with the key | + +#### `map_contains_value(map_col duckdb.map, value duckdb.unresolved_type) -> boolean` + +Checks if a map contains a given value. + +```sql +-- Check if value exists +SELECT map_contains_value(r['map_col'], 1) as has_value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: t (true) + +-- Check for non-existent value +SELECT map_contains_value(r['map_col'], 3) as has_value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: f (false) +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to check | +| value | duckdb.unresolved_type | The value to search for | + +#### `map_entries(map_col duckdb.map) -> duckdb.struct[]` + +Returns an array of struct(key, value) for each key-value pair in the map. + +```sql +-- Get all key-value pairs as structs +SELECT map_entries(r['map_col']) as entries +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: {"(a,1)","(b,2)"} + +-- Access individual struct fields +SELECT unnest(map_entries(r['map_col'])) as entry +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to extract entries from | + +#### `map_extract_value(map_col duckdb.map, key duckdb.unresolved_type) -> duckdb.unresolved_type` + +Returns the value for a given key or NULL if the key is not contained in the map. + +```sql +-- Extract single value (not as array) +SELECT map_extract_value(r['map_col'], 'a') as value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: 1 + +-- Non-existent key returns NULL +SELECT map_extract_value(r['map_col'], 'c') as value +FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map_col $$) r; +-- Returns: NULL +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| map_col | duckdb.map | The map to extract from | +| key | duckdb.unresolved_type | The key to look up in the map | + +#### `map_from_entries(entries duckdb.struct[]) -> duckdb.map` + +Creates a map from an array of struct(k, v). + +```sql +-- Create map from array of structs +SELECT map_from_entries(r['entries']) as new_map +FROM duckdb.query($$ + SELECT [{'k': 'a', 'v': 1}, {'k': 'b', 'v': 2}] as entries +$$) r; +-- Returns: {a=1, b=2} + +-- This is the inverse operation of map_entries +SELECT map_from_entries(map_entries(r['map_col'])) as reconstructed +FROM duckdb.query($$ SELECT MAP(['x', 'y'], [10, 20]) as map_col $$) r; +-- Returns: {x=10, y=20} +``` + +##### Required Arguments + +| Name | Type | Description | +| :--- | :--- | :---------- | +| entries | duckdb.struct[] | Array of structs with 'k' (key) and 'v' (value) fields | + #### `epoch_ms(timestamp_expr)` -> `BIGINT` Converts timestamps to Unix epoch milliseconds. diff --git a/sql/pg_duckdb--1.0.0--1.1.0.sql b/sql/pg_duckdb--1.0.0--1.1.0.sql index e69de29b..5db78578 100644 --- a/sql/pg_duckdb--1.0.0--1.1.0.sql +++ b/sql/pg_duckdb--1.0.0--1.1.0.sql @@ -0,0 +1,140 @@ +-- Add MAP functions support +-- Extract value from map using key +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.map, key duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.unresolved_type, key duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.map, key text) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.unresolved_type, key text) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.map, key integer) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.unresolved_type, key integer) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.map, key bigint) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract(map_col duckdb.unresolved_type, key bigint) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Get all keys from map +CREATE FUNCTION @extschema@.map_keys(map_col duckdb.map) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_keys(map_col duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Get all values from map +CREATE FUNCTION @extschema@.map_values(map_col duckdb.map) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_values(map_col duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Get map size +CREATE FUNCTION @extschema@.cardinality(map_col duckdb.map) +RETURNS numeric AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.cardinality(map_col duckdb.unresolved_type) +RETURNS numeric AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Get element at key (alias for map_extract) +CREATE FUNCTION @extschema@.element_at(map_col duckdb.map, key duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.element_at(map_col duckdb.unresolved_type, key duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.element_at(map_col duckdb.map, key text) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.element_at(map_col duckdb.unresolved_type, key text) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.element_at(map_col duckdb.map, key integer) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.element_at(map_col duckdb.unresolved_type, key integer) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.element_at(map_col duckdb.map, key bigint) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.element_at(map_col duckdb.unresolved_type, key bigint) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Merge multiple maps +CREATE FUNCTION @extschema@.map_concat(map_col duckdb.map, map_col2 duckdb.map) +RETURNS duckdb.map AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_concat(map_col duckdb.unresolved_type, map_col2 duckdb.map) +RETURNS duckdb.map AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_concat(map_col duckdb.map, map_col2 duckdb.unresolved_type) +RETURNS duckdb.map AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_concat(map_col duckdb.unresolved_type, map_col2 duckdb.unresolved_type) +RETURNS duckdb.map AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Check if map contains key +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.map, key duckdb.unresolved_type) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.unresolved_type, key duckdb.unresolved_type) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.map, key text) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.unresolved_type, key text) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.map, key integer) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.unresolved_type, key integer) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.map, key bigint) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains(map_col duckdb.unresolved_type, key bigint) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Check if map contains key-value pair +CREATE FUNCTION @extschema@.map_contains_entry(map_col duckdb.map, key duckdb.unresolved_type, value duckdb.unresolved_type) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_entry(map_col duckdb.unresolved_type, key duckdb.unresolved_type, value duckdb.unresolved_type) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_entry(map_col duckdb.map, key text, value text) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_entry(map_col duckdb.unresolved_type, key text, value text) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Check if map contains value +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.map, value duckdb.unresolved_type) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.unresolved_type, value duckdb.unresolved_type) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.map, value text) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.unresolved_type, value text) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.map, value integer) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.unresolved_type, value integer) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.map, value bigint) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_contains_value(map_col duckdb.unresolved_type, value bigint) +RETURNS boolean AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Get all key-value pairs as structs +CREATE FUNCTION @extschema@.map_entries(map_col duckdb.map) +RETURNS duckdb.struct[] AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_entries(map_col duckdb.unresolved_type) +RETURNS duckdb.struct[] AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Extract single value from map (not as list) +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.map, key duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.unresolved_type, key duckdb.unresolved_type) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.map, key text) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.unresolved_type, key text) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.map, key integer) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.unresolved_type, key integer) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.map, key bigint) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_extract_value(map_col duckdb.unresolved_type, key bigint) +RETURNS duckdb.unresolved_type AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; + +-- Create map from array of struct(k, v) +CREATE FUNCTION @extschema@.map_from_entries(entries duckdb.struct[]) +RETURNS duckdb.map AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +CREATE FUNCTION @extschema@.map_from_entries(entries duckdb.unresolved_type) +RETURNS duckdb.map AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; diff --git a/src/pgduckdb_metadata_cache.cpp b/src/pgduckdb_metadata_cache.cpp index 0492b879..b11543da 100644 --- a/src/pgduckdb_metadata_cache.cpp +++ b/src/pgduckdb_metadata_cache.cpp @@ -183,7 +183,19 @@ BuildDuckdbOnlyFunctions() { "make_timestamptz", "time_bucket", "union_extract", - "union_tag"}; + "union_tag", + "cardinality", + "element_at", + "map_concat", + "map_contains", + "map_contains_entry", + "map_contains_value", + "map_entries", + "map_extract", + "map_extract_value", + "map_from_entries", + "map_keys", + "map_values"}; for (uint32_t i = 0; i < lengthof(function_names); i++) { CatCList *catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(function_names[i])); diff --git a/test/regression/expected/map_functions.out b/test/regression/expected/map_functions.out new file mode 100644 index 00000000..e5f397cd --- /dev/null +++ b/test/regression/expected/map_functions.out @@ -0,0 +1,66 @@ +-- Test MAP functions +-- These tests verify that the MAP functions are properly exposed and working +-- Test map_extract function +SELECT map_extract(r['map_col'], 'a') as extracted_value FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; + extracted_value +----------------- + {1} +(1 row) + +SELECT map_extract(r['map_col'], 'd') as missing_key FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; + missing_key +------------- + {} +(1 row) + +SELECT map_extract(r['map_col'], 'key1') as string_value FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + string_value +-------------- + {value1} +(1 row) + +-- Test map_keys function +SELECT map_keys(r['map_col']) as keys FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; + keys +--------- + {a,b,c} +(1 row) + +SELECT map_keys(r['map_col']) as empty_keys FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; + empty_keys +------------ + {} +(1 row) + +-- Test map_values function +SELECT map_values(r['map_col']) as values FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; + values +--------- + {1,2,3} +(1 row) + +SELECT map_values(r['map_col']) as empty_values FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; + empty_values +-------------- + {} +(1 row) + +-- Test with unresolved_type for flexibility +SELECT map_extract(r['map_col']::duckdb.unresolved_type, 'x') as flexible_extract FROM duckdb.query($$ SELECT MAP(['x', 'y'], [10, 20]) as map_col $$) r; + flexible_extract +------------------ + {10} +(1 row) + +SELECT map_keys(r['map_col']::duckdb.unresolved_type) as flexible_keys FROM duckdb.query($$ SELECT MAP(['p', 'q'], [100, 200]) as map_col $$) r; + flexible_keys +--------------- + {p,q} +(1 row) + +SELECT map_values(r['map_col']::duckdb.unresolved_type) as flexible_values FROM duckdb.query($$ SELECT MAP(['m', 'n'], [1000, 2000]) as map_col $$) r; + flexible_values +----------------- + {1000,2000} +(1 row) + diff --git a/test/regression/expected/map_functions_comprehensive.out b/test/regression/expected/map_functions_comprehensive.out new file mode 100644 index 00000000..9dee1b83 --- /dev/null +++ b/test/regression/expected/map_functions_comprehensive.out @@ -0,0 +1,140 @@ +-- Test MAP functions +SET duckdb.force_execution = false; +-- Basic functions +SELECT cardinality(r['map_col']) as cardinality_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2', 'key3'], ['value1', 'value2', 'value3']) as map_col $$) r; + cardinality_result +-------------------- + 3 +(1 row) + +SELECT element_at(r['map_col'], 'key1') as element_at_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + element_at_result +------------------- + {value1} +(1 row) + +SELECT map_concat(r1['map1'], r2['map2']) as map_concat_result FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map1 $$) r1, duckdb.query($$ SELECT MAP(['b', 'c'], [3, 4]) as map2 $$) r2; + map_concat_result +------------------- + {a=1, b=3, c=4} +(1 row) + +-- Contains functions +SELECT map_contains(r['map_col'], 'key1') as contains_key_true FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + contains_key_true +------------------- + t +(1 row) + +SELECT map_contains(r['map_col'], 'key3') as contains_key_false FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + contains_key_false +-------------------- + f +(1 row) + +SELECT map_contains_entry(r['map_col'], 'key1', 'value1') as contains_entry_true FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + contains_entry_true +--------------------- + t +(1 row) + +SELECT map_contains_entry(r['map_col'], 'key1', 'value2') as contains_entry_false FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + contains_entry_false +---------------------- + f +(1 row) + +SELECT map_contains_value(r['map_col'], 'value1') as contains_value_true FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + contains_value_true +--------------------- + t +(1 row) + +SELECT map_contains_value(r['map_col'], 'value3') as contains_value_false FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + contains_value_false +---------------------- + f +(1 row) + +-- Extract functions +SELECT map_extract(r['map_col'], 'key1') as map_extract_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + map_extract_result +-------------------- + {value1} +(1 row) + +SELECT map_extract_value(r['map_col'], 'key1') as map_extract_value_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + map_extract_value_result +-------------------------- + value1 +(1 row) + +SELECT map_extract_value(r['map_col'], 'key3') as map_extract_value_null FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + map_extract_value_null +------------------------ + +(1 row) + +-- Keys and values +SELECT map_from_entries(r['entries']) as map_from_entries_result FROM duckdb.query($$ SELECT [{'k': 'a', 'v': 1}, {'k': 'b', 'v': 2}] as entries $$) r; + map_from_entries_result +------------------------- + {a=1, b=2} +(1 row) + +SELECT map_keys(r['map_col']) as map_keys_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2', 'key3'], ['value1', 'value2', 'value3']) as map_col $$) r; + map_keys_result +------------------ + {key1,key2,key3} +(1 row) + +SELECT map_values(r['map_col']) as map_values_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2', 'key3'], ['value1', 'value2', 'value3']) as map_col $$) r; + map_values_result +------------------------ + {value1,value2,value3} +(1 row) + +-- Different data types +SELECT cardinality(r['map_col']) as cardinality_numeric_keys FROM duckdb.query($$ SELECT MAP([1, 2, 3], ['a', 'b', 'c']) as map_col $$) r; + cardinality_numeric_keys +-------------------------- + 3 +(1 row) + +SELECT map_contains(r['map_col'], 1) as contains_numeric_key FROM duckdb.query($$ SELECT MAP([1, 2, 3], ['a', 'b', 'c']) as map_col $$) r; + contains_numeric_key +---------------------- + t +(1 row) + +-- Edge cases +SELECT cardinality(r['map_col']) as cardinality_empty FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; + cardinality_empty +------------------- + 0 +(1 row) + +SELECT map_keys(r['map_col']) as keys_empty FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; + keys_empty +------------ + {} +(1 row) + +SELECT map_values(r['map_col']) as values_empty FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; + values_empty +-------------- + {} +(1 row) + +SELECT map_extract(r['map_col'], 'non_existent') as extract_non_existent FROM duckdb.query($$ SELECT MAP(['key1'], ['value1']) as map_col $$) r; + extract_non_existent +---------------------- + {} +(1 row) + +SELECT map_contains(r['map_col'], 'non_existent') as contains_non_existent FROM duckdb.query($$ SELECT MAP(['key1'], ['value1']) as map_col $$) r; + contains_non_existent +----------------------- + f +(1 row) + diff --git a/test/regression/schedule b/test/regression/schedule index 7f646e48..32a120cd 100644 --- a/test/regression/schedule +++ b/test/regression/schedule @@ -52,6 +52,8 @@ test: transaction_isolation test: transactions test: type_support test: union_functions +test: map_functions +test: map_functions_comprehensive test: unresolved_type test: views test: parallel_postgres_scan diff --git a/test/regression/sql/map_functions.sql b/test/regression/sql/map_functions.sql new file mode 100644 index 00000000..f45e3b08 --- /dev/null +++ b/test/regression/sql/map_functions.sql @@ -0,0 +1,20 @@ +-- Test MAP functions +-- These tests verify that the MAP functions are properly exposed and working + +-- Test map_extract function +SELECT map_extract(r['map_col'], 'a') as extracted_value FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; +SELECT map_extract(r['map_col'], 'd') as missing_key FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; +SELECT map_extract(r['map_col'], 'key1') as string_value FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + +-- Test map_keys function +SELECT map_keys(r['map_col']) as keys FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; +SELECT map_keys(r['map_col']) as empty_keys FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; + +-- Test map_values function +SELECT map_values(r['map_col']) as values FROM duckdb.query($$ SELECT MAP(['a', 'b', 'c'], [1, 2, 3]) as map_col $$) r; +SELECT map_values(r['map_col']) as empty_values FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; + +-- Test with unresolved_type for flexibility +SELECT map_extract(r['map_col']::duckdb.unresolved_type, 'x') as flexible_extract FROM duckdb.query($$ SELECT MAP(['x', 'y'], [10, 20]) as map_col $$) r; +SELECT map_keys(r['map_col']::duckdb.unresolved_type) as flexible_keys FROM duckdb.query($$ SELECT MAP(['p', 'q'], [100, 200]) as map_col $$) r; +SELECT map_values(r['map_col']::duckdb.unresolved_type) as flexible_values FROM duckdb.query($$ SELECT MAP(['m', 'n'], [1000, 2000]) as map_col $$) r; diff --git a/test/regression/sql/map_functions_comprehensive.sql b/test/regression/sql/map_functions_comprehensive.sql new file mode 100644 index 00000000..a5a3b130 --- /dev/null +++ b/test/regression/sql/map_functions_comprehensive.sql @@ -0,0 +1,36 @@ +-- Test MAP functions +SET duckdb.force_execution = false; + +-- Basic functions +SELECT cardinality(r['map_col']) as cardinality_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2', 'key3'], ['value1', 'value2', 'value3']) as map_col $$) r; +SELECT element_at(r['map_col'], 'key1') as element_at_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_concat(r1['map1'], r2['map2']) as map_concat_result FROM duckdb.query($$ SELECT MAP(['a', 'b'], [1, 2]) as map1 $$) r1, duckdb.query($$ SELECT MAP(['b', 'c'], [3, 4]) as map2 $$) r2; + +-- Contains functions +SELECT map_contains(r['map_col'], 'key1') as contains_key_true FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_contains(r['map_col'], 'key3') as contains_key_false FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_contains_entry(r['map_col'], 'key1', 'value1') as contains_entry_true FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_contains_entry(r['map_col'], 'key1', 'value2') as contains_entry_false FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_contains_value(r['map_col'], 'value1') as contains_value_true FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_contains_value(r['map_col'], 'value3') as contains_value_false FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + +-- Extract functions +SELECT map_extract(r['map_col'], 'key1') as map_extract_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_extract_value(r['map_col'], 'key1') as map_extract_value_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; +SELECT map_extract_value(r['map_col'], 'key3') as map_extract_value_null FROM duckdb.query($$ SELECT MAP(['key1', 'key2'], ['value1', 'value2']) as map_col $$) r; + +-- Keys and values +SELECT map_from_entries(r['entries']) as map_from_entries_result FROM duckdb.query($$ SELECT [{'k': 'a', 'v': 1}, {'k': 'b', 'v': 2}] as entries $$) r; +SELECT map_keys(r['map_col']) as map_keys_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2', 'key3'], ['value1', 'value2', 'value3']) as map_col $$) r; +SELECT map_values(r['map_col']) as map_values_result FROM duckdb.query($$ SELECT MAP(['key1', 'key2', 'key3'], ['value1', 'value2', 'value3']) as map_col $$) r; + +-- Different data types +SELECT cardinality(r['map_col']) as cardinality_numeric_keys FROM duckdb.query($$ SELECT MAP([1, 2, 3], ['a', 'b', 'c']) as map_col $$) r; +SELECT map_contains(r['map_col'], 1) as contains_numeric_key FROM duckdb.query($$ SELECT MAP([1, 2, 3], ['a', 'b', 'c']) as map_col $$) r; + +-- Edge cases +SELECT cardinality(r['map_col']) as cardinality_empty FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; +SELECT map_keys(r['map_col']) as keys_empty FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; +SELECT map_values(r['map_col']) as values_empty FROM duckdb.query($$ SELECT MAP([], []) as map_col $$) r; +SELECT map_extract(r['map_col'], 'non_existent') as extract_non_existent FROM duckdb.query($$ SELECT MAP(['key1'], ['value1']) as map_col $$) r; +SELECT map_contains(r['map_col'], 'non_existent') as contains_non_existent FROM duckdb.query($$ SELECT MAP(['key1'], ['value1']) as map_col $$) r;