ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
having_semantics.hpp
Go to the documentation of this file.
1/**
2 * @file having_semantics.hpp
3 * @brief Provenance evaluation helper for HAVING-clause circuits.
4 *
5 * When a query includes a HAVING clause, ProvSQL creates a special
6 * sub-circuit that encodes the aggregate predicate. Before the main
7 * provenance circuit can be evaluated over a semiring, these HAVING
8 * sub-circuits must first be evaluated to determine which groups
9 * pass the filter.
10 *
11 * The single public entry point @c provsql_having() rewrites HAVING
12 * comparison gates in the circuit, for any compiled semiring, by
13 * enumerating possible worlds -- or, for @c MIN / @c MAX against a
14 * constant, by the equivalent single-scan closed form: in every
15 * absorptive semiring for the existential comparisons (@c MIN below,
16 * @c MAX above a constant), and when ⊗ moreover distributes over ⊖ for
17 * the others. If the HAVING gate type is incompatible with the
18 * requested semiring, the function is a no-op.
19 */
20#ifndef PROVSQL_HAVING_SEMANTICS_HPP
21#define PROVSQL_HAVING_SEMANTICS_HPP
22
23#include <algorithm>
24#include <cstdint>
25#include <cmath>
26#include <functional>
27#include <map>
28#include <stdexcept>
29#include <string>
30#include <vector>
31
32#include "GenericCircuit.hpp"
33#include "provsql_utils.h"
34#include "subset.hpp"
35#include "Aggregation.h"
36
37/** @cond INTERNAL */
38namespace provsql_having_detail {
39std::vector<gate_t> collect_sp_cmp_gates(GenericCircuit &c, gate_t start);
40bool extract_constant_string(GenericCircuit &c, gate_t x, std::string &C_out);
41bool semimod_extract_string_and_K(GenericCircuit &c, gate_t semimod_gate, std::string &m_out, gate_t &k_gate_out);
42bool aggtype_is_text(unsigned oid);
43bool aggtype_is_integer(unsigned oid);
44bool aggtype_is_boolean(unsigned oid);
45// True if @p oid is boolean, or an array whose element type is boolean. Used
46// by the array_agg comparison to reconcile PostgreSQL's two boolean text forms
47// (scalar 'true'/'false' vs array-element 't'/'f').
48bool aggtype_elem_is_boolean(unsigned oid);
49bool aggtype_is_numeric(unsigned oid);
50
51/**
52 * @brief Map values of type @p typoid, given as text, and a threshold to
53 * their dense ranks under the type's comparison function.
54 *
55 * @return @c false when the type has no comparison function.
56 */
57bool rank_values_by_type(unsigned typoid, const std::vector<std::string> &vals,
58 const std::string &threshold,
59 std::vector<long> &ranks, long &threshold_rank);
60/** @brief @c PROVSQL_GATE_NULL_UUID, the value gate of the NULL value. */
61constexpr const char *GATE_NULL_UUID = PROVSQL_GATE_NULL_UUID;
62
63/**
64 * @brief The text standing for a NULL array element, on both sides of an
65 * @c array_agg comparison.
66 *
67 * It starts with a NUL byte, which no PostgreSQL text value contains, so it
68 * equals itself and no actual element: exactly how array equality treats
69 * NULL elements.
70 */
71inline const std::string &array_null_element() {
72 static const std::string null_element("\0NULL", 5);
73 return null_element;
74}
75
76bool parse_array_literal(const std::string &s, std::vector<std::string> &out);
77bool parse_decimal_scaled(const std::string &s, long &mantissa, int &scale);
78bool rescale_to(long mantissa, int scale, int target_scale, long &out);
79ComparisonOperator map_cmp_op(GenericCircuit &c, gate_t cmp_gate, bool &ok);
81}
82/** @endcond */
83
84/**
85 * @brief Rewrite HAVING comparison gates in the circuit into their
86 * possible-world provenance (world enumeration, or the MIN / MAX
87 * single-scan closed form where the semiring allows it).
88 *
89 * @tparam SemiringT The semiring type used for evaluation.
90 * @tparam MapT The provenance mapping type (gate_t → semiring value).
91 * @param c Circuit to rewrite.
92 * @param g Root gate of the sub-circuit to inspect.
93 * @param mapping Provenance mapping updated in place.
94 * @param S Semiring instance (default-constructed for stateless semirings).
95 */
96template <typename SemiringT, typename MapT>
99 gate_t g,
100 MapT &mapping,
101 SemiringT S = SemiringT{})
102{
103 using namespace provsql_having_detail;
104
105 std::vector<gate_t> cmp_gates = collect_sp_cmp_gates(c, g);
106
107 if (cmp_gates.empty())
108 return;
109
110 // Whether the world enumeration over these contributor annotations can
111 // be built *certified*: the semiring persists d-DNNF certificates
112 // (circuit-building BoolExpr), every contributor is an independent
113 // literal (base Bernoulli or constant) so world terms are decomposable,
114 // no literal repeats (a repeat would couple two contributors), and the
115 // contributor count is small enough that the complete-worlds
116 // enumeration the certificate requires stays affordable.
117 auto certifiable_contributors =
118 [&](const std::vector<typename SemiringT::value_type> &kv) -> bool {
119 if (!S.certifying())
120 return false;
121 if (kv.size() > 16)
122 return false;
123 for (size_t i = 0; i < kv.size(); ++i) {
124 if (!S.independent_literal(kv[i]))
125 return false;
126 // Distinctness among non-constant literals (constants are
127 // variable-free, so repeats of one() / zero() are harmless).
128 if (kv[i] == S.one() || kv[i] == S.zero())
129 continue;
130 for (size_t j = 0; j < i; ++j)
131 if (kv[j] == kv[i])
132 return false;
133 }
134 return true;
135 };
136
137 // Combine a set of valid-world bitmasks (from an exhaustive enumeration) into
138 // the m-semiring predicate-provenance. Shared by every HAVING domain that
139 // enumerates exact worlds (the numeric comparison, the boolean aggregates
140 // and array_agg): a certifying semiring gets one certified world term per
141 // world under a deterministic OR; otherwise each world contributes
142 // ∏present ⊗ (1 ⊖ ⊕missing). The monus factor is dropped when the
143 // enumeration is an @p upset (an absorptive semiring pruned the family to
144 // a generating subfamily of minimal worlds) or when the family is
145 // @p monotone -- closed under supersets, i.e. the complete valid worlds of
146 // a comparison preserved as the group grows -- and the semiring is
147 // idempotent: over such a family the monus cancels from the possible-world
148 // sum, ⊕_W ann_U(W) = ⊕_W ∏present (Lean Having.witness_identity, which
149 // needs idempotence only; absorptivity further collapses the sum to the
150 // minimal worlds, Having.witness_minimal, which is what the upset prune
151 // exploits).
152 auto combine_exhaustive_worlds =
153 [&](const std::vector<mask_t> &worlds,
154 const std::vector<typename SemiringT::value_type> &kvals,
155 bool upset, bool monotone) -> typename SemiringT::value_type {
156 if (worlds.empty())
157 return S.zero();
158
159 if (certifiable_contributors(kvals)) {
160 std::vector<typename SemiringT::value_type> disjuncts;
161 disjuncts.reserve(worlds.size());
162 for (const auto &mask : worlds) {
163 std::vector<typename SemiringT::value_type> present, missing;
164 for (size_t i = 0; i < kvals.size(); ++i)
165 (mask[i] ? present : missing).push_back(kvals[i]);
166 disjuncts.push_back(S.certified_world_term(present, missing));
167 }
168 return S.certified_exclusive_plus(disjuncts);
169 }
170
171 std::vector<typename SemiringT::value_type> disjuncts;
172 disjuncts.reserve(worlds.size());
173 const size_t n = kvals.size();
174 const bool no_monus = upset || (monotone && S.idempotent());
175
176 for (const auto &mask : worlds) {
177 std::vector<typename SemiringT::value_type> present, missing;
178 present.reserve(n);
179 missing.reserve(n);
180
181 for (size_t i = 0; i < n; ++i) {
182 if (mask[i]) {
183 if (kvals[i] != S.one())
184 present.push_back(kvals[i]);
185 } else if (!no_monus) {
186 if (kvals[i] != S.zero())
187 missing.push_back(kvals[i]);
188 }
189 }
190
191 auto present_prod = S.times(present);
192
193 if (missing.empty()) {
194 disjuncts.push_back(std::move(present_prod));
195 } else {
196 auto monus_factor = S.monus(S.one(), S.plus(missing));
197 auto term = monus_factor;
198 if (present_prod != S.one())
199 term = S.times(std::vector<typename SemiringT::value_type>{
200 present_prod, monus_factor});
201 disjuncts.push_back(std::move(term));
202 }
203 }
204
205 return S.plus(disjuncts);
206 };
207
208 // Single-scan closed form for MIN / MAX against a constant in an
209 // absorptive m-semiring. Writing L, L', G, G', E for the ⊕-sums of
210 // the contributors whose value is <, <=, >=, >, = C, the possible-world
211 // provenance of MIN(a) op C is
212 // MIN < C : L MIN >= C : (1 ⊖ L) ⊗ G
213 // MIN <= C : L' MIN > C : (1 ⊖ L') ⊗ G'
214 // MIN = C : (1 ⊖ L) ⊗ E MIN <> C : L ⊕ (1 ⊖ L') ⊗ G'
215 // and MAX is the mirror image (exchange < and >). The existential
216 // comparisons (MIN <, <=; MAX >, >=) hold in the worlds meeting the set
217 // of witnesses, an upward-closed family whose minimal elements are the
218 // singletons: the monus cancels from the possible-world sum and
219 // absorptivity alone makes the ⊕-sum over the witnesses exact (Lean
220 // Having.sum_ann_meet, HavingMonotone.minLe_site_rewrite and its three
221 // siblings). The other comparisons carry the 1 ⊖ factor excluding
222 // every world containing a spoiler, and their closed form is proved
223 // only when ⊗ moreover distributes over ⊖, which combines that factor
224 // with the witnesses by one ⊗ (Lean Having.minScan_correct /
225 // Having.maxScan_correct); without distributivity they are not
226 // proved, and not taken. The empty world never satisfies the
227 // comparison (MIN / MAX of an empty group is NULL), so scalar
228 // aggregation needs no special case.
229 auto min_max_scan =
230 [&](const std::vector<long> &mvals, long C,
231 const std::vector<typename SemiringT::value_type> &kvals,
233 AggregationOperator agg_kind) -> typename SemiringT::value_type {
234 using V = typename SemiringT::value_type;
235 std::vector<V> lt, le, ge, gt, eq;
236 for (size_t i = 0; i < kvals.size(); ++i) {
237 if (kvals[i] == S.zero()) continue;
238 if (mvals[i] < C) lt.push_back(kvals[i]);
239 if (mvals[i] <= C) le.push_back(kvals[i]);
240 if (mvals[i] >= C) ge.push_back(kvals[i]);
241 if (mvals[i] > C) gt.push_back(kvals[i]);
242 if (mvals[i] == C) eq.push_back(kvals[i]);
243 }
244 auto sum = [&](const std::vector<V> &v) -> V {
245 return v.empty() ? S.zero() : S.plus(v);
246 };
247 // (1 ⊖ ⊕spoilers) ⊗ ⊕witnesses
248 auto guarded = [&](const std::vector<V> &spoilers,
249 const std::vector<V> &witnesses) -> V {
250 if (witnesses.empty()) return S.zero();
251 V w = S.plus(witnesses);
252 if (spoilers.empty()) return w;
253 return S.times(std::vector<V>{S.monus(S.one(), S.plus(spoilers)), w});
254 };
255
256 // The MAX table is the MIN table with < and > exchanged: flip the
257 // operator and swap the roles of the below / above sets.
258 const bool is_max = (agg_kind == AggregationOperator::MAX);
259 const std::vector<V> &below = is_max ? gt : lt; // strictly on the "losing" side
260 const std::vector<V> &below_eq = is_max ? ge : le; // losing side or equal
261 const std::vector<V> &above = is_max ? lt : gt; // strictly on the "winning" side
262 const std::vector<V> &above_eq = is_max ? le : ge; // winning side or equal
263 if (is_max) op = flip_op(op);
264
265 switch (op) {
266 case ComparisonOperator::LT: return sum(below);
267 case ComparisonOperator::LE: return sum(below_eq);
268 case ComparisonOperator::GE: return guarded(below, above_eq);
269 case ComparisonOperator::GT: return guarded(below_eq, above);
270 case ComparisonOperator::EQ: return guarded(below, eq);
272 V a = sum(below);
273 V b = guarded(below_eq, above);
274 if (a == S.zero()) return b;
275 if (b == S.zero()) return a;
276 return S.plus(std::vector<V>{a, b});
277 }
278 }
279 return S.zero();
280 };
281
282 // Provenance of a predicate decided by the *first present* occurrence:
283 // the valid worlds partition into fibers, one per candidate first
284 // occurrence i, each fixing i present, a set A_i of occurrences absent
285 // (for choose(): every occurrence before i; for bool_or / bool_and: the
286 // earlier candidates and the class that must be wholly absent) and
287 // leaving the rest free. In an absorptive m-semiring whose ⊗
288 // distributes over ⊖ the free suffix sums to 𝟙 and each fiber is
289 // (1 ⊖ ⊕_{A_i} k) ⊗ k_i (Lean Having.firstScan_correct; the same
290 // hypotheses as the MIN / MAX scan). Elsewhere -- Why, Which, How,
291 // Formula, counting, the security semiring -- the free part does not
292 // collapse, so every world of every fiber is enumerated and annotated
293 // ∏present ⊗ (1 ⊖ ⊕missing), exponential in the number of free
294 // occurrences; @p monotone says the union of the fibers is closed under
295 // supersets (no class has to be wholly absent), which lets an
296 // idempotent semiring drop the monus factors.
297 using fiber_t = std::pair<size_t, std::vector<size_t>>; // (present, absent set)
298 auto first_present_provenance =
299 [&](const std::vector<fiber_t> &fibers,
300 const std::vector<typename SemiringT::value_type> &kvals,
301 bool monotone) -> typename SemiringT::value_type {
302 using V = typename SemiringT::value_type;
303 if (fibers.empty()) return S.zero();
304
305 if (S.absorptive() && S.mul_sub_left_distributive()) {
306 std::vector<V> terms;
307 for (const auto &fb : fibers) {
308 const V &k = kvals[fb.first];
309 if (k == S.zero()) continue;
310 std::vector<V> absent;
311 for (size_t j : fb.second)
312 if (kvals[j] != S.zero()) absent.push_back(kvals[j]);
313 if (absent.empty()) { terms.push_back(k); continue; }
314 V guard = S.monus(S.one(), S.plus(absent));
315 terms.push_back(k == S.one() ? guard : S.times(std::vector<V>{guard, k}));
316 }
317 return terms.empty() ? S.zero() : S.plus(terms);
318 }
319
320 const size_t n = kvals.size();
321 std::vector<mask_t> worlds;
322 for (const auto &fb : fibers) {
323 std::vector<bool> fixed(n, false);
324 fixed[fb.first] = true;
325 for (size_t j : fb.second) fixed[j] = true;
326 std::vector<size_t> free_idx;
327 for (size_t j = 0; j < n; ++j)
328 if (!fixed[j]) free_idx.push_back(j);
329 const size_t m = free_idx.size();
330 for (size_t sub = 0; sub < (size_t(1) << m); ++sub) {
331 mask_t mask(n, false);
332 mask[fb.first] = true;
333 for (size_t b = 0; b < m; ++b)
334 if (sub & (size_t(1) << b)) mask[free_idx[b]] = true;
335 worlds.push_back(std::move(mask));
336 }
337 }
338 return combine_exhaustive_worlds(worlds, kvals, /*upset=*/false, monotone);
339 };
340
341 auto pw_from_cmp_gate = [&](gate_t cmp_gate, typename SemiringT::value_type &pw_out) -> bool {
342 const auto &cw = c.getWires(cmp_gate);
343 if (cw.size() != 2) return false;
344
345 gate_t L = cw[0];
346 gate_t R = cw[1];
347
348 bool okop = false;
349 ComparisonOperator op = map_cmp_op(c, cmp_gate, okop);
350 if (!okop) return false;
351
352 auto build_from = [&](gate_t agg_side, gate_t const_side, ComparisonOperator effective_op) -> bool {
353 if (c.getGateType(agg_side) != gate_agg) return false;
354
355 // info2 of the gate_agg is the aggregate's result type -- the
356 // comparison domain (int / numeric / float / text) -- in its low 31 bits;
357 // the high bit is the scalar-aggregation flag, masked off here.
358 const unsigned aggtype = c.getInfos(agg_side).second & PROVSQL_AGG_TYPE_MASK;
359 const bool is_scalar =
360 (c.getInfos(agg_side).second & PROVSQL_AGG_SCALAR_FLAG) != 0;
361 AggregationOperator agg_kind = getAggregationOperator(c.getInfos(agg_side).first);
362 const auto &children = c.getWires(agg_side);
363
364 // ---- Value-as-text domain: choose() over a non-numeric scalar constant.
365 // Covers text and any other type whose values round-trip through
366 // their text representation (bool, date, uuid, enum, ...); numeric
367 // choose() goes to the numeric domain below (which also supports the
368 // ordering comparisons). Only = / <> are exposed here. ----
369 if ((aggtype_is_text(aggtype) &&
370 agg_kind != AggregationOperator::MIN &&
371 agg_kind != AggregationOperator::MAX) ||
372 (agg_kind == AggregationOperator::CHOOSE &&
373 !aggtype_is_numeric(aggtype))) {
374 std::string C_str;
375 if (!extract_constant_string(c, const_side, C_str)) return false;
376
377 std::vector<std::string> mvals_str;
378 std::vector<typename SemiringT::value_type> kvals;
379 mvals_str.reserve(children.size());
380 kvals.reserve(children.size());
381 for (gate_t ch : children) {
382 if (c.getGateType(ch) != gate_semimod) return false;
383 std::string m_str;
384 gate_t k_gate{};
385 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
386 mvals_str.push_back(m_str);
387 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
388 }
389
390 // Only choose() is supported (the only text-valued aggregate whose
391 // possible-world value is decided occurrence-by-occurrence), and
392 // only = / <> are exposed for text.
393 if (agg_kind != AggregationOperator::CHOOSE)
394 throw std::runtime_error(
395 "comparing an aggregate with a text constant in HAVING "
396 "is only implemented for choose()");
397 if (effective_op != ComparisonOperator::EQ &&
398 effective_op != ComparisonOperator::NE)
399 throw std::runtime_error(
400 "only = and <> are supported when comparing choose() "
401 "with a text constant");
402
403 // choose() is PICKFIRST: in a world W its value is that of the
404 // lowest-index present element. So choose(W) op C holds iff the
405 // first present element matches: one fiber per matching index i,
406 // with every occurrence before i absent and the suffix free.
407 //
408 // The fibers are mutually exclusive by construction (they differ
409 // on the first present index): when the semiring certifies
410 // enumerations, build them as certified world terms under a
411 // certified deterministic OR instead.
412 if (certifiable_contributors(kvals)) {
413 std::vector<typename SemiringT::value_type> disjuncts;
414 std::vector<typename SemiringT::value_type> before;
415 for (size_t i = 0; i < kvals.size(); ++i) {
416 bool match = (effective_op == ComparisonOperator::EQ)
417 ? (mvals_str[i] == C_str)
418 : (mvals_str[i] != C_str);
419 if (match)
420 disjuncts.push_back(S.certified_world_term(
421 std::vector<typename SemiringT::value_type>{kvals[i]},
422 before));
423 before.push_back(kvals[i]);
424 }
425 pw_out = disjuncts.empty() ? S.zero()
426 : S.certified_exclusive_plus(disjuncts);
427 return true;
428 }
429 std::vector<fiber_t> fibers;
430 std::vector<size_t> before;
431 for (size_t i = 0; i < kvals.size(); ++i) {
432 bool match = (effective_op == ComparisonOperator::EQ)
433 ? (mvals_str[i] == C_str)
434 : (mvals_str[i] != C_str);
435 if (match) fibers.emplace_back(i, before);
436 before.push_back(i);
437 }
438 pw_out = first_present_provenance(fibers, kvals, /*monotone=*/false);
439 return true;
440 }
441
442 // ---- Boolean comparison domain: bool_or / bool_and (and the every
443 // alias for bool_and) compared with a boolean constant. A boolean
444 // aggregate has only two possible values, so the worlds yielding the
445 // wanted value are characterised directly in the m-semiring rather
446 // than by a 2^n enumeration. The rows partition by their value into
447 // a class that must have at least one present member ("someE") and a
448 // class that must be wholly absent ("noneF"):
449 // bool_or = true : someE = true-rows (false free)
450 // bool_or = false : someE = false-rows, noneF = true-rows
451 // bool_and = true : someE = true-rows, noneF = false-rows
452 // bool_and = false : someE = false-rows (true free)
453 // "at least one of someE present" splits by the first present
454 // someE index (the choose pattern), each fiber also requiring
455 // every noneF row absent; see first_present_provenance for when
456 // that is a closed form and when it is enumerated. Non-empty
457 // groups are enforced by someE being non-empty. ----
458 if (aggtype_is_boolean(aggtype)) {
459 if (agg_kind != AggregationOperator::OR &&
460 agg_kind != AggregationOperator::AND)
461 return false;
462 if (effective_op != ComparisonOperator::EQ &&
463 effective_op != ComparisonOperator::NE)
464 throw std::runtime_error(
465 "only = and <> are supported when comparing a boolean aggregate "
466 "(bool_or / bool_and / every) with a constant in HAVING");
467
468 auto parse_bool = [](const std::string &s, bool &ok) -> bool {
469 ok = true;
470 if (s == "t" || s == "true" || s == "1") return true;
471 if (s == "f" || s == "false" || s == "0") return false;
472 ok = false; return false;
473 };
474
475 std::string C_str;
476 if (!extract_constant_string(c, const_side, C_str)) return false;
477 bool okc = false;
478 const bool C = parse_bool(C_str, okc);
479 if (!okc) return false;
480 // The aggregate value the satisfying worlds must produce.
481 const bool target = (effective_op == ComparisonOperator::EQ) ? C : !C;
482
483 std::vector<bool> vals;
484 std::vector<typename SemiringT::value_type> kvals;
485 vals.reserve(children.size());
486 kvals.reserve(children.size());
487 for (gate_t ch : children) {
488 if (c.getGateType(ch) != gate_semimod) return false;
489 std::string m_str;
490 gate_t k_gate{};
491 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
492 bool okv = false;
493 const bool b = parse_bool(m_str, okv);
494 if (!okv) return false;
495 vals.push_back(b);
496 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
497 }
498
499 const bool want_or = (agg_kind == AggregationOperator::OR);
500 std::vector<size_t> someE; // at least one of these must be present
501 std::vector<size_t> noneF; // all of these must be absent
502 if (want_or == target) {
503 // bool_or=true / bool_and=false: one trigger row suffices, the other
504 // class is free.
505 for (size_t i = 0; i < vals.size(); ++i)
506 if (vals[i] == want_or) someE.push_back(i);
507 } else {
508 // bool_or=false / bool_and=true: the trigger class must be wholly
509 // absent and the opposite class must have at least one present row.
510 for (size_t i = 0; i < vals.size(); ++i)
511 (vals[i] == want_or ? noneF : someE).push_back(i);
512 }
513
514 if (someE.empty()) { pw_out = S.zero(); return true; }
515
516 if (certifiable_contributors(kvals)) {
517 std::vector<typename SemiringT::value_type> disjuncts;
518 std::vector<typename SemiringT::value_type> before; // someE rows before i
519 for (size_t e : someE) {
520 std::vector<typename SemiringT::value_type> missing = before;
521 for (size_t f : noneF) missing.push_back(kvals[f]);
522 disjuncts.push_back(S.certified_world_term(
523 std::vector<typename SemiringT::value_type>{kvals[e]}, missing));
524 before.push_back(kvals[e]);
525 }
526 pw_out = S.certified_exclusive_plus(disjuncts);
527 return true;
528 }
529
530 // With no class to exclude (bool_or = true, bool_and = false) the
531 // predicate is existential: it holds in the worlds meeting someE,
532 // an upward-closed family whose minimal elements are the
533 // singletons, so in an absorptive semiring the ⊕-sum of the
534 // someE annotations is exact, with no monus and no distributivity
535 // (Lean Having.sum_ann_meet, the MIN <= C argument). An
536 // idempotent semiring still enumerates the family but, it being
537 // monotone, without the monus factors.
538 if (noneF.empty() && S.absorptive()) {
539 std::vector<typename SemiringT::value_type> witnesses;
540 for (size_t e : someE)
541 if (kvals[e] != S.zero()) witnesses.push_back(kvals[e]);
542 pw_out = witnesses.empty() ? S.zero() : S.plus(witnesses);
543 return true;
544 }
545
546 std::vector<fiber_t> fibers;
547 std::vector<size_t> absent = noneF; // grows with the earlier someE rows
548 for (size_t e : someE) {
549 fibers.emplace_back(e, absent);
550 absent.push_back(e);
551 }
552 pw_out = first_present_provenance(fibers, kvals,
553 /*monotone=*/noneF.empty());
554 return true;
555 }
556
557 // ---- Array comparison domain: array_agg(x) against a constant array.
558 // No aggregate-specific optimization (the general pipeline): scan the
559 // non-empty worlds whose ordered present elements equal (=) or differ
560 // (<>) from the constant array, then combine those worlds in the
561 // m-semiring. Elements are compared as their text representations,
562 // so any element type works. ----
563 if (agg_kind == AggregationOperator::ARRAY_AGG) {
564 if (effective_op != ComparisonOperator::EQ &&
565 effective_op != ComparisonOperator::NE)
566 throw std::runtime_error(
567 "only = and <> are supported when comparing array_agg() with a "
568 "constant array in HAVING");
569
570 std::string C_str;
571 if (!extract_constant_string(c, const_side, C_str)) return false;
572 std::vector<std::string> target;
573 if (!parse_array_literal(C_str, target)) return false;
574
575 std::vector<std::string> vals;
576 std::vector<typename SemiringT::value_type> kvals;
577 vals.reserve(children.size());
578 kvals.reserve(children.size());
579 for (gate_t ch : children) {
580 if (c.getGateType(ch) != gate_semimod) return false;
581 std::string m_str;
582 gate_t k_gate{};
583 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
584 // A NULL input of the aggregate: its value gate is gate_null().
585 if (c.getUUID(c.getWires(ch)[1]) == GATE_NULL_UUID)
586 m_str = array_null_element();
587 vals.push_back(m_str);
588 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
589 }
590
591 // Boolean elements: the row values carry the scalar bool text
592 // ('true'/'false') while the constant array's elements come back in
593 // PostgreSQL's array form ('t'/'f'). Canonicalise both sides so the
594 // text comparison below sees the same representation.
595 if (aggtype_elem_is_boolean(aggtype)) {
596 auto canon_bool = [](std::string &s) {
597 if (s == "t" || s == "true" || s == "1") s = "true";
598 else if (s == "f" || s == "false" || s == "0") s = "false";
599 };
600 for (auto &e : target) canon_bool(e);
601 for (auto &v : vals) canon_bool(v);
602 }
603
604 auto worlds = enumerate_array_agg_worlds(
605 vals, target, effective_op == ComparisonOperator::EQ);
606 pw_out = combine_exhaustive_worlds(worlds, kvals, /*upset=*/false,
607 /*monotone=*/false);
608 return true;
609 }
610
611 // The possible worlds of an aggregate over integer-mapped values
612 // compared with an integer threshold: the closed forms, or the
613 // enumeration.
614 auto finish_domain = [&](const std::vector<long> &mvals, long C,
615 const std::vector<typename SemiringT::value_type> &kvals)
616 -> bool {
617 // A certified enumeration needs the *complete* valid worlds (every
618 // contributor present or explicitly negated): the upset shortcut
619 // and the monotone MIN/MAX skips below produce overlapping,
620 // non-exclusive disjuncts, sound for absorptive evaluation but
621 // unmarkable. When certifying, request the full enumeration --
622 // the same one non-absorptive semirings already use.
623 const bool certify = certifiable_contributors(kvals);
624
625 // MIN / MAX in an absorptive semiring: the single-scan closed form
626 // replaces the 2^n world enumeration -- for the existential
627 // comparisons (MIN below, MAX above the constant) in every
628 // absorptive semiring, for the others only when ⊗ distributes over
629 // ⊖ (see min_max_scan). A certifying semiring keeps the
630 // enumeration, whose exclusive world terms its certificate needs.
631 if ((agg_kind == AggregationOperator::MIN ||
632 agg_kind == AggregationOperator::MAX) &&
633 S.absorptive() && !certify) {
634 const bool existential =
635 (agg_kind == AggregationOperator::MIN)
636 ? (effective_op == ComparisonOperator::LT ||
637 effective_op == ComparisonOperator::LE)
638 : (effective_op == ComparisonOperator::GT ||
639 effective_op == ComparisonOperator::GE);
640 if (existential || S.mul_sub_left_distributive()) {
641 pw_out = min_max_scan(mvals, C, kvals, effective_op, agg_kind);
642 return true;
643 }
644 }
645
646 bool upset = false;
647 auto worlds = enumerate_valid_worlds(mvals, C, effective_op, agg_kind,
648 certify ? false : S.absorptive(),
649 upset, is_scalar);
650
651 // Comparisons preserved as the group grows, whose valid worlds are
652 // therefore closed under supersets: MIN below and MAX above the
653 // constant, COUNT above it (count(col) carries 0/1 values), SUM
654 // above it when no contributor is negative.
655 const bool above = (effective_op == ComparisonOperator::GT ||
656 effective_op == ComparisonOperator::GE);
657 const bool below = (effective_op == ComparisonOperator::LT ||
658 effective_op == ComparisonOperator::LE);
659 bool monotone = false;
660 switch (agg_kind) {
661 case AggregationOperator::MIN: monotone = below; break;
662 case AggregationOperator::MAX: monotone = above; break;
663 case AggregationOperator::COUNT: monotone = above; break;
665 monotone = above &&
666 std::all_of(mvals.begin(), mvals.end(), [](long v) { return v >= 0; });
667 break;
668 default: break;
669 }
670
671 pw_out = combine_exhaustive_worlds(worlds, kvals, upset, monotone);
672 return true;
673 };
674
675 // ---- Ordered domain: MIN / MAX over a type that is not numeric (text,
676 // date, timestamp, ...). Their value in a world only depends on
677 // the order of the values, so values and threshold are mapped to
678 // their ranks under the type's own comparison function (with its
679 // default collation, for text), and the integer machinery below
680 // applies. ----
681 if (!aggtype_is_numeric(aggtype) &&
682 (agg_kind == AggregationOperator::MIN ||
683 agg_kind == AggregationOperator::MAX)) {
684 std::string C_str;
685 if (!extract_constant_string(c, const_side, C_str)) return false;
686 std::vector<std::string> m_strs;
687 std::vector<typename SemiringT::value_type> kvals;
688 for (gate_t ch : children) {
689 if (c.getGateType(ch) != gate_semimod) return false;
690 std::string m_str;
691 gate_t k_gate{};
692 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
693 m_strs.push_back(m_str);
694 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
695 }
696 std::vector<long> ranks;
697 long C_rank = 0;
698 if (!rank_values_by_type(aggtype, m_strs, C_str, ranks, C_rank))
699 return false;
700 return finish_domain(ranks, C_rank, kvals);
701 }
702
703 // ---- Numeric comparison domain (int / numeric / float): unify by
704 // scaling every value and the threshold to a common integer
705 // grid by their decimal text, so a numeric(p,d) / finite-decimal
706 // float column is evaluated exactly and fractional thresholds
707 // work. Integer is the scale-0 case. ----
708 std::string C_str;
709 if (!extract_constant_string(c, const_side, C_str)) return false;
710 long C_mant = 0; int C_scale = 0;
711 if (!parse_decimal_scaled(C_str, C_mant, C_scale)) return false;
712
713 std::vector<long> m_mant;
714 std::vector<int> m_scale;
715 std::vector<typename SemiringT::value_type> kvals;
716 m_mant.reserve(children.size());
717 m_scale.reserve(children.size());
718 kvals.reserve(children.size());
719 for (gate_t ch : children) {
720 if (c.getGateType(ch) != gate_semimod) return false;
721 std::string m_str;
722 gate_t k_gate{};
723 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
724 long mm = 0; int ms = 0;
725 if (!parse_decimal_scaled(m_str, mm, ms)) return false;
726 m_mant.push_back(mm);
727 m_scale.push_back(ms);
728 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
729 }
730
731 // Common scale: rescale every value and the threshold to integers.
732 int target_scale = C_scale;
733 for (int ms : m_scale) target_scale = std::max(target_scale, ms);
734 long C = 0;
735 if (!rescale_to(C_mant, C_scale, target_scale, C)) return false;
736 std::vector<long> mvals(m_mant.size());
737 for (size_t i = 0; i < m_mant.size(); ++i)
738 if (!rescale_to(m_mant[i], m_scale[i], target_scale, mvals[i])) return false;
739
740 return finish_domain(mvals, C, kvals);
741 };
742
743 // General possible-worlds evaluation for a comparison over an *arithmetic
744 // expression of one or more aggregates* (sum(x)*sum(y) > k, sum(x) > sum(y),
745 // 100/sum(x) > 5, ...), which the single-aggregate path above cannot fold.
746 // We enumerate the joint possible worlds over the union of the aggregates'
747 // contributors, evaluate the arithmetic numerically in each world, and
748 // combine the worlds where the comparison holds in the semiring exactly as
749 // the single-aggregate path does (present_prod ⊗ (1 ⊖ missing_sum)). This
750 // is exponential in the number of distinct contributors, so it bails out
751 // (returning false, leaving the gate unresolved) beyond a small bound.
752 auto build_general = [&](gate_t Lx, gate_t Rx,
753 ComparisonOperator opx) -> bool {
754 struct AggInfo {
756 bool is_int; // integer-typed result?
757 bool is_scalar; // aggregation without GROUP BY?
758 std::vector<std::pair<int, double> > contribs; // (distinct-K index, value)
759 };
760 // A decimal text denotes an integer iff it has no fractional/exponent part.
761 auto text_is_int = [](const std::string &s) -> bool {
762 if (s.empty()) return false;
763 for (char ch : s)
764 if (!((ch >= '0' && ch <= '9') || ch == '+' || ch == '-'))
765 return false;
766 return true;
767 };
768 std::map<gate_t, AggInfo> aggs;
769 std::map<gate_t, int> kindex;
770 std::vector<gate_t> kgates;
771
772 // A number, the whole text of it: a date such as 2022-08-05 is not
773 // 2022.
774 auto parse_number = [](const std::string &s, double &out) -> bool {
775 try {
776 std::size_t pos = 0;
777 out = std::stod(s, &pos);
778 return pos == s.size();
779 } catch (...) {
780 return false;
781 }
782 };
783
784 std::function<bool(gate_t)> collect = [&](gate_t gx) -> bool {
785 gate_type gt = c.getGateType(gx);
786 if (gt == gate_agg) {
787 if (aggs.count(gx))
788 return true;
789 AggInfo ai;
790 ai.kind = getAggregationOperator(c.getInfos(gx).first);
791 ai.is_int = aggtype_is_integer(c.getInfos(gx).second & PROVSQL_AGG_TYPE_MASK);
792 ai.is_scalar =
793 (c.getInfos(gx).second & PROVSQL_AGG_SCALAR_FLAG) != 0;
794 // Only the kinds eval reads a value of: any other declines the
795 // route here, rather than reading there as "no value" in every
796 // world -- which is a comparison that never holds, a probability of
797 // zero with nothing to say so. bool_and / bool_or / array_agg were
798 // kept out only by their values failing to parse as numbers.
799 switch (ai.kind) {
806 break;
807 default:
808 return false;
809 }
810 for (gate_t ch : c.getWires(gx)) {
811 if (c.getGateType(ch) != gate_semimod)
812 return false;
813 std::string ms;
814 gate_t kg{};
815 if (!semimod_extract_string_and_K(c, ch, ms, kg))
816 return false;
817 int idx;
818 auto it = kindex.find(kg);
819 if (it == kindex.end()) {
820 idx = static_cast<int>(kgates.size());
821 kindex[kg] = idx;
822 kgates.push_back(kg);
823 } else
824 idx = it->second;
825 double m;
826 if (!parse_number(ms, m)) return false;
827 ai.contribs.emplace_back(idx, m);
828 }
829 aggs.emplace(gx, std::move(ai));
830 return true;
831 }
832 if (gt == gate_arith) {
833 for (gate_t ch : c.getWires(gx))
834 if (!collect(ch))
835 return false;
836 return true;
837 }
838 if (gt == gate_value)
839 return true;
840 if (gt == gate_semimod) { // a constant threshold: semimod(1, value)
841 std::string ms;
842 gate_t kg{};
843 if (!semimod_extract_string_and_K(c, gx, ms, kg))
844 return false;
845 return c.getGateType(kg) == gate_one;
846 }
847 return false;
848 };
849
850 if (!collect(Lx) || !collect(Rx))
851 return false;
852 if (aggs.empty())
853 return false;
854 const size_t n = kgates.size();
855 if (n > 24) // 2^n enumeration: keep it bounded
856 return false;
857
858 // Numeric value of a subexpression in a given world, tracking whether it
859 // is integer-valued so that division floors as SQL does (NULL -> false).
860 std::function<bool(gate_t, uint64_t, double &, bool &)> eval =
861 [&](gate_t gx, uint64_t world, double &out, bool &is_int) -> bool {
862 gate_type gt = c.getGateType(gx);
863 if (gt == gate_value) {
864 std::string s = c.getExtra(gx);
865 if (!parse_number(s, out)) return false;
866 is_int = text_is_int(s);
867 return true;
868 }
869 if (gt == gate_semimod) { // constant threshold
870 std::string ms; gate_t kg{};
871 if (!semimod_extract_string_and_K(c, gx, ms, kg)) return false;
872 if (!parse_number(ms, out)) return false;
873 is_int = text_is_int(ms);
874 return true;
875 }
876 if (gt == gate_agg) {
877 const AggInfo &ai = aggs.at(gx);
878 double acc = 0, mn = 0, mx = 0, fst = 0;
879 long cnt = 0;
880 bool first = true;
881 for (const auto &pr : ai.contribs)
882 if (world & (uint64_t(1) << pr.first)) {
883 double m = pr.second;
884 acc += m; ++cnt;
885 if (first) { mn = mx = fst = m; first = false; }
886 else { mn = std::min(mn, m); mx = std::max(mx, m); }
887 }
888 is_int = ai.is_int;
889 // An aggregate with no surviving contributor in this world declines
890 // the world (returning false), which the caller reads as "the
891 // comparison does not hold here". Two situations produce cnt == 0
892 // and both want that answer: the group has no row present, so the
893 // group's row does not exist; or the group is non-empty but every
894 // contributed value was NULL, so the aggregate is SQL NULL and the
895 // comparison is NULL, hence false. (A NULL-valued row never reaches
896 // contribs -- the aggregation rewriting drops it -- which is why the
897 // two are indistinguishable here, and why they need not be.)
898 //
899 // COUNT is the exception: an empty set genuinely counts 0, so the
900 // world has a value whenever a row exists to carry it -- which is
901 // the case for a scalar aggregation, and not for a grouped one whose
902 // empty group is no row at all. The gate records COUNT for both
903 // count(*) and count(expr) (see make_aggregation_expression), so no
904 // guessing from the values is needed here.
905 switch (ai.kind) {
906 case AggregationOperator::SUM: if (cnt == 0) return false; out = acc; return true;
907 case AggregationOperator::COUNT: // values 1 or 0/1
908 if (cnt == 0 && !ai.is_scalar) return false;
909 out = acc; return true;
910 case AggregationOperator::AVG: if (cnt == 0) return false; out = acc / cnt; return true;
911 case AggregationOperator::MIN: if (cnt == 0) return false; out = mn; return true;
912 case AggregationOperator::MAX: if (cnt == 0) return false; out = mx; return true;
913 // choose() is PICKFIRST: the value of the first present contributor,
914 // in the order of the gate's wires (see ChooseAgg).
915 case AggregationOperator::CHOOSE: if (cnt == 0) return false; out = fst; return true;
916 default:
917 // collect admits no other kind: one reaching here would read as
918 // "no value" in every world, so it raises instead.
919 throw CircuitException(
920 "having_semantics: aggregate kind not read per world");
921 }
922 }
923 if (gt == gate_arith) {
924 const auto &w = c.getWires(gx);
925 unsigned aop = static_cast<unsigned>(c.getInfos(gx).first);
926 if (aop == PROVSQL_ARITH_PLUS || aop == PROVSQL_ARITH_TIMES) {
927 double r = (aop == PROVSQL_ARITH_PLUS) ? 0 : 1;
928 bool all_int = true;
929 for (gate_t ch : w) {
930 double v; bool vi;
931 if (!eval(ch, world, v, vi)) return false;
932 if (aop == PROVSQL_ARITH_PLUS) r += v; else r *= v;
933 all_int = all_int && vi;
934 }
935 out = r; is_int = all_int; return true;
936 }
937 if (aop == PROVSQL_ARITH_MINUS) {
938 if (w.size() != 2) return false;
939 double a, b; bool ai, bi;
940 if (!eval(w[0], world, a, ai) || !eval(w[1], world, b, bi)) return false;
941 out = a - b; is_int = ai && bi; return true;
942 }
943 if (aop == PROVSQL_ARITH_DIV || aop == PROVSQL_ARITH_INTDIV) {
944 if (w.size() != 2) return false;
945 double a, b; bool ai, bi;
946 if (!eval(w[0], world, a, ai) || !eval(w[1], world, b, bi)) return false;
947 if (b == 0) return false;
948 if (aop == PROVSQL_ARITH_INTDIV) { // SQL division of integers
949 out = std::trunc(a / b);
950 is_int = true;
951 } else {
952 /* A real division, whatever the operands happen to be in this
953 * world. Which of the two divisions SQL means is the gate's to
954 * say -- the rewriting emits INTDIV exactly where the
955 * expression's type is an integer one -- and deciding it again
956 * from the values truncated a division the query wrote over
957 * numbers: count(*) OVER w / count(*) OVER w0 came out 1 or 0
958 * per world, so a comparison over it answered the probability
959 * that the two counts are equal. The other five evaluators
960 * (Monte Carlo, RangeCheck, Expectation, the simplifier, the
961 * collapsed moment) all read the op and not the values. */
962 out = a / b;
963 is_int = false;
964 }
965 return true;
966 }
967 if (aop == PROVSQL_ARITH_NEG) {
968 if (w.size() != 1) return false;
969 double a; bool ai;
970 if (!eval(w[0], world, a, ai)) return false;
971 out = -a; is_int = ai; return true;
972 }
973 if (aop == PROVSQL_ARITH_ASFLOAT8 ||
974 aop == PROVSQL_ARITH_ASFLOAT4) {
975 /* The value read in its own type: a double is what this carries,
976 * and a real rounds it to seven digits. */
977 double a;
978 bool ai;
979 if (w.size() != 1 || !eval(w[0], world, a, ai)) return false;
980 out = aop == PROVSQL_ARITH_ASFLOAT8
981 ? a : static_cast<double>(static_cast<float>(a));
982 is_int = ai;
983 return true;
984 }
985 if (aop == PROVSQL_ARITH_ROUND || aop == PROVSQL_ARITH_FLOOR ||
986 aop == PROVSQL_ARITH_CEIL || aop == PROVSQL_ARITH_ABS) {
987 /* Read in the world, on the value the world gives: rounding and
988 * absolute value of a value, never of an expectation. */
989 double a;
990 bool ai;
991 if (w.empty() || !eval(w[0], world, a, ai)) return false;
992 if (aop == PROVSQL_ARITH_FLOOR) out = std::floor(a);
993 else if (aop == PROVSQL_ARITH_CEIL) out = std::ceil(a);
994 else if (aop == PROVSQL_ARITH_ABS) out = std::fabs(a);
995 else if (w.size() == 1) out = std::round(a);
996 else {
997 double d;
998 bool di;
999 if (!eval(w[1], world, d, di)) return false;
1000 const double f = std::pow(10.0, d);
1001 out = std::round(a * f) / f;
1002 }
1003 /* An integer in, an integer out, except a rounding to digits of a
1004 * value that was not one. */
1005 is_int = ai || aop != PROVSQL_ARITH_ROUND || w.size() == 1;
1006 return true;
1007 }
1008 if (aop == PROVSQL_ARITH_POW || aop == PROVSQL_ARITH_LN ||
1009 aop == PROVSQL_ARITH_EXP) {
1010 /* The transform of the value this world gives. Without these
1011 * three the switch fell through to "no value in this world", which
1012 * the caller reads as "the comparison does not hold here" -- in
1013 * every world, so a HAVING over sqrt(sum(x)) (a POW of one half),
1014 * over ln or exp, or over an expression holding one, answered
1015 * probability 0 whatever the threshold. The other five evaluators
1016 * read them, or decline the shape and leave it to this one.
1017 *
1018 * Where the transform is undefined ON the value the world gives --
1019 * the logarithm of a nonpositive number, a negative base raised to
1020 * a fraction -- the world has no value, the reading a divisor of
1021 * zero gets (see the DIV arm) rather than the error SQL raises
1022 * there: raising in one world would take every other world's
1023 * answer with it. */
1024 double a;
1025 bool ai;
1026
1027 if (w.empty() || !eval(w[0], world, a, ai)) return false;
1028 if (aop == PROVSQL_ARITH_LN) {
1029 if (!(a > 0)) return false;
1030 out = std::log(a);
1031 } else if (aop == PROVSQL_ARITH_EXP) {
1032 out = std::exp(a);
1033 } else {
1034 double e;
1035 bool ei;
1036
1037 if (w.size() != 2 || !eval(w[1], world, e, ei)) return false;
1038 out = std::pow(a, e);
1039 if (std::isnan(out)) return false;
1040 }
1041 if (!std::isfinite(out)) return false;
1042 is_int = false;
1043 return true;
1044 }
1045 if (aop == PROVSQL_ARITH_MAX || aop == PROVSQL_ARITH_MIN) {
1046 if (w.empty()) return false;
1047 double r = 0; bool all_int = true, first = true;
1048 for (gate_t ch : w) {
1049 double v; bool vi;
1050 if (!eval(ch, world, v, vi)) return false;
1051 if (first) { r = v; first = false; }
1052 else r = (aop == PROVSQL_ARITH_MAX) ? std::max(r, v)
1053 : std::min(r, v);
1054 all_int = all_int && vi;
1055 }
1056 out = r; is_int = all_int; return true;
1057 }
1058 if (aop == PROVSQL_ARITH_PERCENTILE) {
1059 /* A continuous percentile over the group's rows: the wires are
1060 * interleaved [ind_1, x_1, ...] with ind_i the row's 0/1 presence,
1061 * and the fraction is the gate's own text. The values the world
1062 * holds are sorted and interpolated at the fraction, as
1063 * MonteCarloSampler reads them; a world holding none of them has no
1064 * value, like an aggregate with no contributing row. No comparison
1065 * reaches this today -- the gate is built for percentile_cont over
1066 * a random variable, whose circuits the sampler reads -- so it is
1067 * written to the sampler's reading rather than to a measurement. */
1068 std::vector<double> members;
1069 double fraction, pos, fp;
1070 std::size_t lo;
1071
1072 if (w.empty() || w.size() % 2 != 0) return false;
1073 try {
1074 fraction = std::stod(c.getExtra(gx));
1075 } catch (const std::exception &) {
1076 return false;
1077 }
1078 for (std::size_t i = 0; i < w.size(); i += 2) {
1079 double ind, x;
1080 bool ii, xi;
1081
1082 if (!eval(w[i], world, ind, ii)) return false;
1083 if (ind < 0.5) continue; /* the row is not here */
1084 if (!eval(w[i + 1], world, x, xi)) return false;
1085 members.push_back(x);
1086 }
1087 if (members.empty()) return false;
1088 std::sort(members.begin(), members.end());
1089 pos = fraction * static_cast<double>(members.size() - 1);
1090 lo = static_cast<std::size_t>(pos);
1091 fp = pos - static_cast<double>(lo);
1092 out = (lo + 1 < members.size())
1093 ? members[lo] + fp * (members[lo + 1] - members[lo])
1094 : members[lo];
1095 is_int = false;
1096 return true;
1097 }
1098 /* An arithmetic this evaluator does not know. Falling through to
1099 * "this world has no value" is what made a HAVING over sqrt, ln or
1100 * exp answer probability 0 in every world, silently and with
1101 * confidence, for as long as those three arms were missing: the
1102 * caller reads no value as "the comparison does not hold here". An
1103 * unimplemented operator has to be told apart from a world that
1104 * genuinely has no value, so it raises, as every other evaluator's
1105 * default does (MonteCarloSampler, Expectation). A new gate_arith
1106 * operator therefore fails loudly here until it is read. */
1107 throw CircuitException(
1108 "having_semantics: unknown gate_arith operator tag: " +
1109 std::to_string(aop));
1110 }
1111 return false;
1112 };
1113
1114 std::vector<typename SemiringT::value_type> kval(n);
1115 for (size_t i = 0; i < n; ++i)
1116 kval[i] = c.evaluate<SemiringT>(kgates[i], mapping, S);
1117
1118 // The joint enumeration below is over complete worlds already:
1119 // certify the disjuncts when the semiring and contributors allow.
1120 const bool certify = certifiable_contributors(kval);
1121
1122 // Whether the world in which no contributor is present is itself a
1123 // valid world. For a grouped aggregation it is not: an empty group is
1124 // no row, so there is nothing for the comparison to hold of. A scalar
1125 // aggregation always yields its single row, empty input included, so
1126 // that world is real and a predicate true on it (count(*) = 0, and
1127 // anything the empty-input values satisfy) must pick it up. Mixed
1128 // shapes follow the grouped reading: whichever side is grouped has no
1129 // row there, so the joined row does not exist either.
1130 const bool empty_world_valid =
1131 !aggs.empty() &&
1132 std::all_of(aggs.begin(), aggs.end(),
1133 [](const std::pair<const gate_t, AggInfo> &e) {
1134 return e.second.is_scalar;
1135 });
1136 // No contributor at all: the empty world is the only one, and it is
1137 // only a world for scalar aggregations (a window frame that may be
1138 // empty while its row exists).
1139 if (n == 0 && !empty_world_valid)
1140 return false;
1141
1142 std::vector<typename SemiringT::value_type> disjuncts;
1143 const uint64_t total = uint64_t(1) << n;
1144 for (uint64_t world = empty_world_valid ? 0 : 1; world < total; ++world) {
1145 double lv, rv;
1146 bool lint, rint;
1147 if (!eval(Lx, world, lv, lint) || !eval(Rx, world, rv, rint))
1148 continue; // NULL comparison: false
1149 bool holds = false;
1150 switch (opx) {
1151 case ComparisonOperator::EQ: holds = (lv == rv); break;
1152 case ComparisonOperator::NE: holds = (lv != rv); break;
1153 case ComparisonOperator::LT: holds = (lv < rv); break;
1154 case ComparisonOperator::LE: holds = (lv <= rv); break;
1155 case ComparisonOperator::GT: holds = (lv > rv); break;
1156 case ComparisonOperator::GE: holds = (lv >= rv); break;
1157 }
1158 if (!holds)
1159 continue;
1160
1161 std::vector<typename SemiringT::value_type> present, missing;
1162 for (size_t i = 0; i < n; ++i) {
1163 if (world & (uint64_t(1) << i)) {
1164 if (certify || kval[i] != S.one()) present.push_back(kval[i]);
1165 } else {
1166 if (certify || kval[i] != S.zero()) missing.push_back(kval[i]);
1167 }
1168 }
1169 if (certify) {
1170 disjuncts.push_back(S.certified_world_term(present, missing));
1171 continue;
1172 }
1173 auto present_prod = S.times(present);
1174 if (missing.empty())
1175 disjuncts.push_back(std::move(present_prod));
1176 else {
1177 auto monus_factor = S.monus(S.one(), S.plus(missing));
1178 disjuncts.push_back(
1179 present_prod == S.one()
1180 ? monus_factor
1181 : S.times(std::vector<typename SemiringT::value_type>{
1182 present_prod, monus_factor}));
1183 }
1184 }
1185
1186 pw_out = disjuncts.empty() ? S.zero()
1187 : certify ? S.certified_exclusive_plus(disjuncts)
1188 : S.plus(disjuncts);
1189 return true;
1190 };
1191
1192 // ---- Two array_agg() aggregates compared with each other (a join on
1193 // two aggregated arrays): the worlds of the tuples of both, which
1194 // may be shared, where the arrays are equal (=) or differ (<>).
1195 // Elements are compared as their texts, as against a constant. ----
1196 auto build_array_pair = [&](gate_t Lx, gate_t Rx,
1197 ComparisonOperator opx) -> bool {
1199 return false;
1200 std::map<gate_t, std::size_t> bit;
1201 std::vector<typename SemiringT::value_type> kvals;
1202 std::vector<std::size_t> bits[2];
1203 std::vector<std::string> vals[2];
1204 const gate_t sides[2] = {Lx, Rx};
1205
1206 for (int side = 0; side < 2; ++side) {
1207 if (getAggregationOperator(c.getInfos(sides[side]).first) !=
1209 return false;
1210 for (gate_t ch : c.getWires(sides[side])) {
1211 if (c.getGateType(ch) != gate_semimod) return false;
1212 std::string m_str;
1213 gate_t k_gate{};
1214 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
1215 if (c.getUUID(c.getWires(ch)[1]) == GATE_NULL_UUID)
1216 m_str = array_null_element();
1217 auto it = bit.find(k_gate);
1218 if (it == bit.end()) {
1219 if (kvals.size() >= 20)
1220 return false; // 2^n enumeration: keep it bounded
1221 it = bit.emplace(k_gate, kvals.size()).first;
1222 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
1223 }
1224 bits[side].push_back(it->second);
1225 vals[side].push_back(m_str);
1226 }
1227 }
1228 if (kvals.empty())
1229 return false;
1230
1231 auto worlds = enumerate_array_agg_pair_worlds(
1232 bits[0], vals[0], bits[1], vals[1], kvals.size(),
1233 opx == ComparisonOperator::EQ);
1234 pw_out = combine_exhaustive_worlds(worlds, kvals, /*upset=*/false,
1235 /*monotone=*/false);
1236 return true;
1237 };
1238
1239 if (c.getGateType(L) == gate_agg && build_from(L, R, op))
1240 return true;
1241 if (c.getGateType(R) == gate_agg && build_from(R, L, flip_op(op)))
1242 return true;
1243 if (c.getGateType(L) == gate_agg && c.getGateType(R) == gate_agg &&
1244 build_array_pair(L, R, op))
1245 return true;
1246
1247 return build_general(L, R, op);
1248 };
1249
1250 // Each comparison gate is resolved on its own: its possible-world
1251 // enumeration reads the circuit and writes only its own mapping entry, so
1252 // one gate the enumeration cannot handle -- an agg-vs-agg shape past the
1253 // contributor cap, an unsupported aggregate -- says nothing about the
1254 // others. Skip it and carry on rather than abandoning the gates that
1255 // follow: those are left for the semiring's own cmp() to deal with, which
1256 // renders them (Formula) or raises, and a gate this loop could have
1257 // resolved should not be demoted to that just because of its position in
1258 // collect_sp_cmp_gates' traversal.
1259 for (gate_t cmp_gate : cmp_gates) {
1260 typename SemiringT::value_type pw;
1261 if (!pw_from_cmp_gate(cmp_gate, pw))
1262 continue;
1263
1264 mapping[cmp_gate] = std::move(pw);
1265 }
1266}
1267
1268#endif
AggregationOperator getAggregationOperator(Oid oid)
Map a PostgreSQL aggregate function OID to an AggregationOperator.
Typed aggregation value, operator, and aggregator abstractions.
AggregationOperator
SQL aggregation functions tracked by ProvSQL.
Definition Aggregation.h:51
@ OR
Boolean OR aggregate.
Definition Aggregation.h:58
@ MAX
MAX → input type.
Definition Aggregation.h:55
@ COUNT
COUNT(*) or COUNT(expr) → integer.
Definition Aggregation.h:52
@ AND
Boolean AND aggregate.
Definition Aggregation.h:57
@ SUM
SUM → integer or float.
Definition Aggregation.h:53
@ ARRAY_AGG
Array aggregation.
Definition Aggregation.h:60
@ MIN
MIN → input type.
Definition Aggregation.h:54
@ CHOOSE
Arbitrary selection (pick one element).
Definition Aggregation.h:59
@ AVG
AVG → float.
Definition Aggregation.h:56
ComparisonOperator
SQL comparison operators used in gate_cmp circuit gates.
Definition Aggregation.h:39
@ LT
Less than (<).
Definition Aggregation.h:43
@ GT
Greater than (>).
Definition Aggregation.h:45
@ LE
Less than or equal (<=).
Definition Aggregation.h:42
@ NE
Not equal (<>).
Definition Aggregation.h:41
@ GE
Greater than or equal (>=).
Definition Aggregation.h:44
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Template implementation of GenericCircuit::evaluate().
Exception type thrown by circuit operations on invalid input.
Definition Circuit.h:206
std::vector< gate_t > & getWires(gate_t g)
Return a mutable reference to the child-wire list of gate g.
Definition Circuit.h:140
gateType getGateType(gate_t g) const
Return the type of gate g.
Definition Circuit.h:130
uuid getUUID(gate_t g) const
Return the UUID string associated with gate g.
Definition Circuit.hpp:46
In-memory provenance circuit with semiring-generic evaluation.
S::value_type evaluate(gate_t g, std::unordered_map< gate_t, typename S::value_type > &provenance_mapping, S semiring) const
Evaluate the sub-circuit rooted at gate g over semiring semiring.
std::string getExtra(gate_t g) const
Return the string extra for gate g.
std::pair< unsigned, unsigned > getInfos(gate_t g) const
Return the integer annotation pair for gate g.
static Datum semimod_gate(const pg_uuid_t *value_token, const pg_uuid_t *token)
The semimod gate value_token ⊗ token.
void provsql_having(GenericCircuit &c, gate_t g, MapT &mapping, SemiringT S=SemiringT{})
Rewrite HAVING comparison gates in the circuit into their possible-world provenance (world enumeratio...
bool rescale_to(long mantissa, int scale, int target_scale, long &out)
bool aggtype_is_text(unsigned oid)
bool aggtype_is_numeric(unsigned oid)
bool aggtype_is_integer(unsigned oid)
bool parse_array_literal(const std::string &s, std::vector< std::string > &out)
ComparisonOperator flip_op(ComparisonOperator op)
bool rank_values_by_type(unsigned typoid, const std::vector< std::string > &vals, const std::string &threshold, std::vector< long > &ranks, long &threshold_rank)
bool aggtype_elem_is_boolean(unsigned oid)
std::vector< gate_t > collect_sp_cmp_gates(GenericCircuit &c, gate_t start)
bool parse_decimal_scaled(const std::string &s, long &mantissa, int &scale)
ComparisonOperator map_cmp_op(GenericCircuit &c, gate_t cmp_gate, bool &ok)
bool aggtype_is_boolean(unsigned oid)
bool semimod_extract_string_and_K(GenericCircuit &c, gate_t semimod_gate, std::string &m_out, gate_t &k_gate_out)
bool extract_constant_string(GenericCircuit &c, gate_t x, std::string &C_out)
Core types, constants, and utilities shared across ProvSQL.
@ PROVSQL_ARITH_PERCENTILE
continuous percentile (order-statistic aggregate): wires are interleaved [ind_1, x_1,...
@ PROVSQL_ARITH_DIV
binary, child0 / child1
@ PROVSQL_ARITH_ASFLOAT8
unary, child0 as double precision
@ PROVSQL_ARITH_LN
unary, natural logarithm of child0 (a negative draw raises at evaluation)
@ PROVSQL_ARITH_ROUND
child0 rounded half away from zero, to child1 decimal digits where a second child is given (SQL round...
@ PROVSQL_ARITH_PLUS
n-ary, sum of children
@ PROVSQL_ARITH_POW
binary, child0 ^ child1 (real branch only: a negative base drawn with a non-integer exponent raises a...
@ PROVSQL_ARITH_ABS
unary, |child0|
@ PROVSQL_ARITH_FLOOR
unary, greatest integer <= child0
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_INTDIV
binary, child0 / child1 truncated toward zero: SQL's division of two integers
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_CEIL
unary, least integer >= child0
@ PROVSQL_ARITH_EXP
unary, e^child0
@ PROVSQL_ARITH_TIMES
n-ary, product of children
@ PROVSQL_ARITH_MIN
n-ary, min of children (order statistic; least / min aggregate)
@ PROVSQL_ARITH_MAX
n-ary, max of children (order statistic; greatest / max aggregate)
@ PROVSQL_ARITH_ASFLOAT4
unary, child0 as real reads it
#define PROVSQL_AGG_TYPE_MASK
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)
#define PROVSQL_GATE_NULL_UUID
UUID of the constant value gate standing for the NULL value, the result of gate_null().
#define PROVSQL_AGG_SCALAR_FLAG
Scalar-aggregation flag, stored in the upper bit of a gate_agg's info2 (whose low 31 bits hold the ag...
std::vector< mask_t > enumerate_array_agg_pair_worlds(const std::vector< std::size_t > &lbits, const std::vector< std::string > &lvals, const std::vector< std::size_t > &rbits, const std::vector< std::string > &rvals, std::size_t nbits, bool want_equal)
Enumerate the worlds where the arrays of two array_agg() aggregates are equal (want_equal) or differ.
Definition subset.cpp:465
std::vector< mask_t > enumerate_valid_worlds(const std::vector< long > &values, long constant, ComparisonOperator op, AggregationOperator agg_kind, bool absorptive, bool &upset, bool is_scalar)
Enumerate all subsets of n tuples satisfying an aggregate predicate.
Definition subset.cpp:425
std::vector< mask_t > enumerate_array_agg_worlds(const std::vector< std::string > &vals, const std::vector< std::string > &target, bool want_equal)
Enumerate the non-empty worlds whose ordered present elements equal (want_equal true) or differ from ...
Definition subset.cpp:495
Enumerate tuple subsets satisfying an aggregate HAVING predicate.
std::vector< bool > mask_t
A bitmask over tuples representing one possible world.
Definition subset.hpp:29