1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Aggregation IR for extension.
//!
//! Datalog with aggregation extends pure Datalog by allowing a
//! body literal that folds a set of inner bindings into a single
//! aggregate value, optionally grouped by a chosen set of
//! variables. The aggregate result is bound to a fresh variable
//! that downstream body literals (and the rule head) can consume.
//!
//! This module defines the IR data types only:
//! [`Aggregator`] (the SQL/SPARQL aggregator vocabulary) and
//! [`AggregateBody`] (an aggregation sub-query). The
//! corresponding body-literal variant is
//! [`Literal::Aggregate`](super::literal::Literal::Aggregate),
//! and rule-level safety analysis is in
//! [`Rule::unsafe_aggregate_variables`](super::rule::Rule::unsafe_aggregate_variables).
//!
//! **Stratification**: an aggregation depends on the *complete*
//! extension of its inner predicates, so each predicate referenced
//! from the aggregate's inner body forms a polarity-bearing
//! dependency edge analogous to a negative literal — those
//! predicates must sit in a strictly lower stratum than the
//! rule's head predicate.
use BTreeSet;
use Literal;
use VariableSymbol;
/// SQL/SPARQL aggregator vocabulary supported in rule bodies.
///
/// Mirrors the subset called out in the extension deliverable
/// (the rule validation specification):
/// MIN, MAX, SUM, COUNT, AVG. SPARQL's `SAMPLE` and
/// `GROUP_CONCAT` are not in scope for extension — they map onto
/// SPARQL's `Group` operator rather than a Datalog rule body.
/// An aggregation sub-query inside a rule body.
///
/// Semantically equivalent to a SPARQL `Group { ... } over { ... }`
/// projection: evaluate `inner_body` as a sub-query, group the
/// results by `group_by`, fold each group's `input_var` values
/// with `aggregator`, and bind the per-group result to
/// `result_var`. The aggregate then participates in the outer
/// rule body as if `result_var` were produced by a positive
/// atom that ranges over the per-group results.
///
/// # Variable scoping
///
/// `inner_body` is its own scope: positive atoms and `BIND`
/// outputs inside the inner body bind variables that are visible
/// only within the aggregation. The three sets of variables that
/// leak out are `group_by`, `input_var`, and (after the
/// aggregation fires) `result_var`. Concretely:
///
/// - `group_by` and `input_var` MUST be bound by some positive atom or `BIND`
/// inside `inner_body`.
/// - `result_var` MUST be fresh — it must not appear anywhere else in the outer
/// rule (head or body).
///
/// # Inner-body literal kinds
///
/// `inner_body` accepts the same four literal kinds the outer
/// rule body accepts — [`Literal::Positive`], [`Literal::Negative`],
/// [`Literal::Filter`], and [`Literal::Bind`] — under the usual
/// safety rules (negated atoms and filter expressions reference
/// only inner-body-bound variables; `BIND` expressions reference
/// only earlier inner positives / binds and bind a fresh variable).
/// Nested aggregates inside an aggregate's inner body are rejected
/// at validation time as
/// [`super::error::ProgramValidationError::NestedAggregateInInnerBody`].