Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions age--1.7.0--y.y.y.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1107,17 +1107,21 @@ COMMENT ON FUNCTION ag_catalog.create_subgraph(name, name, text, text) IS
-- Transition function for the age_reduce aggregate. The fold body is compiled
-- by transform_cypher_reduce() with the accumulator and element rewritten to
-- PARAM_EXEC params 0 and 1 and serialized into the text argument; the
-- transition evaluates it for each element in list order. It must be callable
-- with a NULL transition state (no initcond), so it is intentionally not STRICT.
CREATE FUNCTION ag_catalog.age_reduce_transfn(agtype, agtype, text, agtype)
-- transition evaluates it for each element in list order. The trailing
-- agtype[] argument carries the loop-invariant outer values (outer-query
-- variables and cypher() parameters) referenced by the body, bound to
-- PARAM_EXEC params 2, 3, ... It must be callable with a NULL transition state
-- (no initcond), so it is intentionally not STRICT.
CREATE FUNCTION ag_catalog.age_reduce_transfn(agtype, agtype, text, agtype, agtype[])
RETURNS agtype
LANGUAGE c
PARALLEL UNSAFE
AS 'MODULE_PATHNAME';

-- aggregate definition for reduce(); direct arguments are
-- (init, serialized-body, element), with the element fed ORDER BY ordinality.
CREATE AGGREGATE ag_catalog.age_reduce(agtype, text, agtype)
-- (init, serialized-body, element, captured-outer-values), with the element
-- fed ORDER BY ordinality.
CREATE AGGREGATE ag_catalog.age_reduce(agtype, text, agtype, agtype[])
(
stype = agtype,
sfunc = ag_catalog.age_reduce_transfn
Expand Down
258 changes: 251 additions & 7 deletions regress/expected/age_reduce.out
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,87 @@ $$) AS (result agtype);
[1, 4, 9]
(1 row)

--
-- Value types in the fold
--
-- a float accumulator and float elements
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0.0, x IN [1.5, 2.5, 3.0] | s + x)
$$) AS (result agtype);
result
--------
7.0
(1 row)

-- negative numbers
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN [-1, -2, -3] | s + x)
$$) AS (result agtype);
result
--------
-6
(1 row)

-- a map accumulator passed through unchanged
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = {n: 0}, x IN [1, 2, 3] | s)
$$) AS (result agtype);
result
----------
{"n": 0}
(1 row)

-- elements that are themselves lists, indexed in the body
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN [[1, 2], [3, 4], [5, 6]] | s + x[0])
$$) AS (result agtype);
result
--------
9
(1 row)

--
-- Function calls in the fold body
--
-- a scalar function applied to the element
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN ['a', 'bb', 'ccc'] | s + size(x))
$$) AS (result agtype);
result
--------
6
(1 row)

-- the list itself produced by a function
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN range(1, 5) | s + x)
$$) AS (result agtype);
result
--------
15
(1 row)

--
-- Composing reduce() with surrounding expressions
--
-- the reduce() result consumed by another function
SELECT * FROM cypher('reduce', $$
RETURN size(reduce(s = [], x IN [1, 2, 3, 4] | s + [x]))
$$) AS (result agtype);
result
--------
4
(1 row)

-- the reduce() result used in a comparison
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x) = 6
$$) AS (result agtype);
result
--------
true
(1 row)

--
-- A conditional body (CASE)
--
Expand Down Expand Up @@ -484,17 +565,129 @@ $$) AS (name agtype, total agtype);
(3 rows)

--
-- Not-yet-supported constructs raise a clean feature error
-- Outer references in the fold body
--
-- an outer variable referenced in the body
-- The body may reference loop-invariant values from the enclosing query: an
-- outer variable, a property of an outer variable, or a cypher() parameter.
-- a plain outer variable in the body
SELECT * FROM cypher('reduce', $$
WITH 5 AS w
RETURN reduce(s = 0, x IN [1, 2] | s + x + w)
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x + w)
$$) AS (result agtype);
ERROR: a reduce() expression may only reference its accumulator and element variables
LINE 1: SELECT * FROM cypher('reduce', $$
^
-- a nested reduce() in the body
result
--------
21
(1 row)

-- an outer variable used as a multiplier
SELECT * FROM cypher('reduce', $$
WITH 3 AS factor
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x * factor)
$$) AS (result agtype);
result
--------
18
(1 row)

-- two distinct outer variables in the body
SELECT * FROM cypher('reduce', $$
WITH 2 AS a, 100 AS b
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x * a + b)
$$) AS (result agtype);
result
--------
312
(1 row)

-- a property of an outer (graph) variable in the body
SELECT * FROM cypher('reduce', $$
MATCH (u:bag) WHERE u.name = 'mid'
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x + u.vals[0])
$$) AS (result agtype);
result
--------
21
(1 row)

-- the same outer variable referenced more than once in the body
SELECT * FROM cypher('reduce', $$
WITH 7 AS k
RETURN reduce(s = 0, x IN [1, 2, 3] | s + k + k)
$$) AS (result agtype);
result
--------
42
(1 row)

-- a property of an outer map referenced in the body
SELECT * FROM cypher('reduce', $$
WITH {factor: 10} AS m
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x * m.factor)
$$) AS (result agtype);
result
--------
60
(1 row)

-- a subexpression that mixes an outer reference with the element: only the
-- loop-invariant part (the outer list) is captured, the element index is not
SELECT * FROM cypher('reduce', $$
WITH [10, 20, 30] AS lookup
RETURN reduce(s = 0, x IN [1, 2, 3] | s + lookup[x - 1])
$$) AS (result agtype);
result
--------
60
(1 row)

-- an outer reference inside a CASE branch of the body is captured
SELECT * FROM cypher('reduce', $$
WITH 10 AS w
RETURN reduce(s = 0, x IN [1, 2, 3] | CASE WHEN x % 2 = 0 THEN s + w ELSE s + x END)
$$) AS (result agtype);
result
--------
14
(1 row)

-- a NULL outer value propagates through the fold
SELECT * FROM cypher('reduce', $$
WITH null AS w
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x + w)
$$) AS (result agtype);
result
--------
null
(1 row)

-- multiple outer captures with a mix of NULL and non-NULL: each is bound to its
-- own slot (the non-NULL multiplier is bound and the NULL still propagates)
SELECT * FROM cypher('reduce', $$
WITH 3 AS a, null AS b
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x * a + b)
$$) AS (result agtype);
result
--------
null
(1 row)

-- an outer variable that changes per row is captured per group
SELECT * FROM cypher('reduce', $$
UNWIND [1, 2, 3] AS m
RETURN reduce(s = 0, x IN [1, 2, 3, 4] | s + x * m) AS total
ORDER BY total
$$) AS (result agtype);
result
--------
10
20
30
(3 rows)

--
-- Not-yet-supported constructs raise a clean feature error
--
-- a nested reduce() in the body (any subquery in the body is unsupported)
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN [1, 2] | s + reduce(t = 0, y IN [x] | t + y))
$$) AS (result agtype);
Expand All @@ -509,6 +702,57 @@ ERROR: aggregate functions are not supported in a reduce() expression
LINE 1: SELECT * FROM cypher('reduce', $$
^
--
-- Syntax errors: each required piece of the reduce() form is enforced
--
-- missing "= init"
SELECT * FROM cypher('reduce', $$
RETURN reduce(s, x IN [1, 2] | s + x)
$$) AS (result agtype);
ERROR: syntax error at or near ","
LINE 2: RETURN reduce(s, x IN [1, 2] | s + x)
^
-- missing ", var IN list"
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0 | s)
$$) AS (result agtype);
ERROR: syntax error at or near "|"
LINE 2: RETURN reduce(s = 0 | s)
^
-- missing "| body"
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN [1, 2])
$$) AS (result agtype);
ERROR: syntax error at or near ")"
LINE 2: RETURN reduce(s = 0, x IN [1, 2])
^
-- a qualified iterator variable is not allowed
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x.y IN [1, 2] | s)
$$) AS (result agtype);
ERROR: syntax error at or near "."
LINE 2: RETURN reduce(s = 0, x.y IN [1, 2] | s)
^
--
-- cypher() parameter referenced in the fold body (via a prepared statement)
--
PREPARE reduce_param(agtype) AS
SELECT * FROM cypher('reduce', $$
RETURN reduce(s = 0, x IN [1, 2, 3] | s + x + $p)
$$, $1) AS (result agtype);
EXECUTE reduce_param('{"p": 10}');
result
--------
36
(1 row)

EXECUTE reduce_param('{"p": 100}');
result
--------
306
(1 row)

DEALLOCATE reduce_param;
--
-- "reduce" as a property key name (safe_keywords backward compatibility):
-- because reduce() introduced a reserved keyword, confirm the word is still
-- usable as a map key, the same way any/none/single are.
Expand Down
Loading
Loading