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 by enumerating possible worlds, for
13 * any compiled semiring. If the HAVING gate type is incompatible with
14 * the requested semiring, the function is a no-op.
15 */
16#ifndef PROVSQL_HAVING_SEMANTICS_HPP
17#define PROVSQL_HAVING_SEMANTICS_HPP
18
19#include <algorithm>
20#include <cstdint>
21#include <functional>
22#include <map>
23#include <stdexcept>
24#include <string>
25#include <vector>
26
27#include "GenericCircuit.hpp"
28#include "provsql_utils.h"
29#include "subset.hpp"
30#include "Aggregation.h"
31
32/** @cond INTERNAL */
33namespace provsql_having_detail {
34std::vector<gate_t> collect_sp_cmp_gates(GenericCircuit &c, gate_t start);
35bool extract_constant_string(GenericCircuit &c, gate_t x, std::string &C_out);
36bool semimod_extract_string_and_K(GenericCircuit &c, gate_t semimod_gate, std::string &m_out, gate_t &k_gate_out);
37bool aggtype_is_text(unsigned oid);
38bool aggtype_is_integer(unsigned oid);
39bool aggtype_is_boolean(unsigned oid);
40// True if @p oid is boolean, or an array whose element type is boolean. Used
41// by the array_agg comparison to reconcile PostgreSQL's two boolean text forms
42// (scalar 'true'/'false' vs array-element 't'/'f').
43bool aggtype_elem_is_boolean(unsigned oid);
44bool aggtype_is_numeric(unsigned oid);
45bool parse_array_literal(const std::string &s, std::vector<std::string> &out);
46bool parse_decimal_scaled(const std::string &s, long &mantissa, int &scale);
47bool rescale_to(long mantissa, int scale, int target_scale, long &out);
48ComparisonOperator map_cmp_op(GenericCircuit &c, gate_t cmp_gate, bool &ok);
50}
51/** @endcond */
52
53/**
54 * @brief Rewrite HAVING comparison gates in the circuit by enumerating possible worlds.
55 *
56 * @tparam SemiringT The semiring type used for evaluation.
57 * @tparam MapT The provenance mapping type (gate_t → semiring value).
58 * @param c Circuit to rewrite.
59 * @param g Root gate of the sub-circuit to inspect.
60 * @param mapping Provenance mapping updated in place.
61 * @param S Semiring instance (default-constructed for stateless semirings).
62 */
63template <typename SemiringT, typename MapT>
66 gate_t g,
67 MapT &mapping,
68 SemiringT S = SemiringT{})
69{
70 using namespace provsql_having_detail;
71
72 std::vector<gate_t> cmp_gates = collect_sp_cmp_gates(c, g);
73
74 if (cmp_gates.empty())
75 return;
76
77 // Whether the world enumeration over these contributor annotations can
78 // be built *certified*: the semiring persists d-DNNF certificates
79 // (circuit-building BoolExpr), every contributor is an independent
80 // literal (base Bernoulli or constant) so world terms are decomposable,
81 // no literal repeats (a repeat would couple two contributors), and the
82 // contributor count is small enough that the complete-worlds
83 // enumeration the certificate requires stays affordable.
84 auto certifiable_contributors =
85 [&](const std::vector<typename SemiringT::value_type> &kv) -> bool {
86 if (!S.certifying())
87 return false;
88 if (kv.size() > 16)
89 return false;
90 for (size_t i = 0; i < kv.size(); ++i) {
91 if (!S.independent_literal(kv[i]))
92 return false;
93 // Distinctness among non-constant literals (constants are
94 // variable-free, so repeats of one() / zero() are harmless).
95 if (kv[i] == S.one() || kv[i] == S.zero())
96 continue;
97 for (size_t j = 0; j < i; ++j)
98 if (kv[j] == kv[i])
99 return false;
100 }
101 return true;
102 };
103
104 // Combine a set of valid-world bitmasks (from an exhaustive enumeration) into
105 // the m-semiring predicate-provenance. Shared by every HAVING domain that
106 // enumerates exact worlds (the numeric comparison and array_agg): a certifying
107 // semiring gets one certified world term per world under a deterministic OR;
108 // otherwise each world contributes ∏present ⊗ (1 ⊖ ⊕missing), with the upset
109 // and monotone MIN/MAX shortcuts dropping the absent contributors that the
110 // enumeration left unconstrained. @p op / @p agg_kind drive only those
111 // shortcuts (no-ops for array_agg).
112 auto combine_exhaustive_worlds =
113 [&](const std::vector<mask_t> &worlds,
114 const std::vector<typename SemiringT::value_type> &kvals,
115 bool upset, ComparisonOperator op,
116 AggregationOperator agg_kind) -> typename SemiringT::value_type {
117 if (worlds.empty())
118 return S.zero();
119
120 if (certifiable_contributors(kvals)) {
121 std::vector<typename SemiringT::value_type> disjuncts;
122 disjuncts.reserve(worlds.size());
123 for (const auto &mask : worlds) {
124 std::vector<typename SemiringT::value_type> present, missing;
125 for (size_t i = 0; i < kvals.size(); ++i)
126 (mask[i] ? present : missing).push_back(kvals[i]);
127 disjuncts.push_back(S.certified_world_term(present, missing));
128 }
129 return S.certified_exclusive_plus(disjuncts);
130 }
131
132 std::vector<typename SemiringT::value_type> disjuncts;
133 disjuncts.reserve(worlds.size());
134 const size_t n = kvals.size();
135
136 for (const auto &mask : worlds) {
137 std::vector<typename SemiringT::value_type> present, missing;
138 present.reserve(n);
139 missing.reserve(n);
140
141 for (size_t i = 0; i < n; ++i) {
142 if (mask[i]) {
143 if (kvals[i] != S.one())
144 present.push_back(kvals[i]);
145 } else if (upset) {
146 // The world enumeration produced an upset generated by a subset
147 } else if ((op == ComparisonOperator::GE || op == ComparisonOperator::GT) &&
148 S.absorptive() && agg_kind == AggregationOperator::MAX) {
149 // Monotonously increasing behavior: do not add anything to missing
150 } else if ((op == ComparisonOperator::LE || op == ComparisonOperator::LT) &&
151 S.absorptive() && agg_kind == AggregationOperator::MIN) {
152 // Monotonously decreasing behavior: do not add anything to missing
153 } else {
154 if (kvals[i] != S.zero())
155 missing.push_back(kvals[i]);
156 }
157 }
158
159 auto present_prod = S.times(present);
160
161 if (missing.empty()) {
162 disjuncts.push_back(std::move(present_prod));
163 } else {
164 auto monus_factor = S.monus(S.one(), S.plus(missing));
165 auto term = monus_factor;
166 if (present_prod != S.one())
167 term = S.times(std::vector<typename SemiringT::value_type>{
168 present_prod, monus_factor});
169 disjuncts.push_back(std::move(term));
170 }
171 }
172
173 return S.plus(disjuncts);
174 };
175
176 auto pw_from_cmp_gate = [&](gate_t cmp_gate, typename SemiringT::value_type &pw_out) -> bool {
177 const auto &cw = c.getWires(cmp_gate);
178 if (cw.size() != 2) return false;
179
180 gate_t L = cw[0];
181 gate_t R = cw[1];
182
183 bool okop = false;
184 ComparisonOperator op = map_cmp_op(c, cmp_gate, okop);
185 if (!okop) return false;
186
187 auto build_from = [&](gate_t agg_side, gate_t const_side, ComparisonOperator effective_op) -> bool {
188 if (c.getGateType(agg_side) != gate_agg) return false;
189
190 // info2 of the gate_agg is the aggregate's result type -- the
191 // comparison domain (int / numeric / float / text) -- in its low 31 bits;
192 // the high bit is the scalar-aggregation flag, masked off here.
193 const unsigned aggtype = c.getInfos(agg_side).second & PROVSQL_AGG_TYPE_MASK;
194 const bool is_scalar =
195 (c.getInfos(agg_side).second & PROVSQL_AGG_SCALAR_FLAG) != 0;
196 AggregationOperator agg_kind = getAggregationOperator(c.getInfos(agg_side).first);
197 const auto &children = c.getWires(agg_side);
198
199 // ---- Value-as-text domain: choose() over a non-numeric scalar constant.
200 // Covers text and any other type whose values round-trip through
201 // their text representation (bool, date, uuid, enum, ...); numeric
202 // choose() goes to the numeric domain below (which also supports the
203 // ordering comparisons). Only = / <> are exposed here. ----
204 if (aggtype_is_text(aggtype) ||
205 (agg_kind == AggregationOperator::CHOOSE &&
206 !aggtype_is_numeric(aggtype))) {
207 std::string C_str;
208 if (!extract_constant_string(c, const_side, C_str)) return false;
209
210 std::vector<std::string> mvals_str;
211 std::vector<typename SemiringT::value_type> kvals;
212 mvals_str.reserve(children.size());
213 kvals.reserve(children.size());
214 for (gate_t ch : children) {
215 if (c.getGateType(ch) != gate_semimod) return false;
216 std::string m_str;
217 gate_t k_gate{};
218 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
219 mvals_str.push_back(m_str);
220 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
221 }
222
223 // Only choose() is supported (the only text-valued aggregate whose
224 // possible-world value is decided occurrence-by-occurrence), and
225 // only = / <> are exposed for text.
226 if (agg_kind != AggregationOperator::CHOOSE)
227 throw std::runtime_error(
228 "comparing an aggregate with a text constant in HAVING "
229 "is only implemented for choose()");
230 if (effective_op != ComparisonOperator::EQ &&
231 effective_op != ComparisonOperator::NE)
232 throw std::runtime_error(
233 "only = and <> are supported when comparing choose() "
234 "with a text constant");
235
236 // choose() is PICKFIRST: in a world W its value is that of the
237 // lowest-index present element. So choose(W) op C holds iff the
238 // first present element matches; summing the annotations of all such
239 // worlds telescopes (free suffix sums to one):
240 // pw = ⊕_{i : vᵢ matches} kᵢ ⊗ (⊗_{j<i} (1 ⊖ kⱼ))
241 //
242 // The disjuncts are mutually exclusive by construction (they
243 // differ on the first present index): when the semiring
244 // certifies enumerations, build them as certified world terms
245 // under a certified deterministic OR instead.
246 if (certifiable_contributors(kvals)) {
247 std::vector<typename SemiringT::value_type> disjuncts;
248 std::vector<typename SemiringT::value_type> before;
249 for (size_t i = 0; i < kvals.size(); ++i) {
250 bool match = (effective_op == ComparisonOperator::EQ)
251 ? (mvals_str[i] == C_str)
252 : (mvals_str[i] != C_str);
253 if (match)
254 disjuncts.push_back(S.certified_world_term(
255 std::vector<typename SemiringT::value_type>{kvals[i]},
256 before));
257 before.push_back(kvals[i]);
258 }
259 pw_out = disjuncts.empty() ? S.zero()
260 : S.certified_exclusive_plus(disjuncts);
261 return true;
262 }
263 const auto one = S.one();
264 std::vector<typename SemiringT::value_type> disjuncts;
265 auto prefix = one;
266 for (size_t i = 0; i < kvals.size(); ++i) {
267 bool match = (effective_op == ComparisonOperator::EQ)
268 ? (mvals_str[i] == C_str)
269 : (mvals_str[i] != C_str);
270 if (match) {
271 if (prefix == one)
272 disjuncts.push_back(kvals[i]);
273 else if (kvals[i] == one)
274 disjuncts.push_back(prefix);
275 else
276 disjuncts.push_back(S.times(
277 std::vector<typename SemiringT::value_type>{kvals[i], prefix}));
278 }
279 auto absent = S.monus(one, kvals[i]);
280 prefix = (prefix == one)
281 ? absent
282 : S.times(std::vector<typename SemiringT::value_type>{
283 prefix, absent});
284 }
285 pw_out = disjuncts.empty() ? S.zero() : S.plus(disjuncts);
286 return true;
287 }
288
289 // ---- Boolean comparison domain: bool_or / bool_and (and the every
290 // alias for bool_and) compared with a boolean constant. A boolean
291 // aggregate has only two possible values, so the worlds yielding the
292 // wanted value are characterised directly in the m-semiring rather
293 // than by a 2^n enumeration. The rows partition by their value into
294 // a class that must have at least one present member ("someE") and a
295 // class that must be wholly absent ("noneF"):
296 // bool_or = true : someE = true-rows (false free)
297 // bool_or = false : someE = false-rows, noneF = true-rows
298 // bool_and = true : someE = true-rows, noneF = false-rows
299 // bool_and = false : someE = false-rows (true free)
300 // "at least one of someE present" telescopes by the first present
301 // index (the choose pattern); "none of noneF present" is a product
302 // of complements. Non-empty groups are enforced by someE being
303 // non-empty. ----
304 if (aggtype_is_boolean(aggtype)) {
305 if (agg_kind != AggregationOperator::OR &&
306 agg_kind != AggregationOperator::AND)
307 return false;
308 if (effective_op != ComparisonOperator::EQ &&
309 effective_op != ComparisonOperator::NE)
310 throw std::runtime_error(
311 "only = and <> are supported when comparing a boolean aggregate "
312 "(bool_or / bool_and / every) with a constant in HAVING");
313
314 auto parse_bool = [](const std::string &s, bool &ok) -> bool {
315 ok = true;
316 if (s == "t" || s == "true" || s == "1") return true;
317 if (s == "f" || s == "false" || s == "0") return false;
318 ok = false; return false;
319 };
320
321 std::string C_str;
322 if (!extract_constant_string(c, const_side, C_str)) return false;
323 bool okc = false;
324 const bool C = parse_bool(C_str, okc);
325 if (!okc) return false;
326 // The aggregate value the satisfying worlds must produce.
327 const bool target = (effective_op == ComparisonOperator::EQ) ? C : !C;
328
329 std::vector<bool> vals;
330 std::vector<typename SemiringT::value_type> kvals;
331 vals.reserve(children.size());
332 kvals.reserve(children.size());
333 for (gate_t ch : children) {
334 if (c.getGateType(ch) != gate_semimod) return false;
335 std::string m_str;
336 gate_t k_gate{};
337 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
338 bool okv = false;
339 const bool b = parse_bool(m_str, okv);
340 if (!okv) return false;
341 vals.push_back(b);
342 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
343 }
344
345 const bool want_or = (agg_kind == AggregationOperator::OR);
346 std::vector<size_t> someE; // at least one of these must be present
347 std::vector<size_t> noneF; // all of these must be absent
348 if (want_or == target) {
349 // bool_or=true / bool_and=false: one trigger row suffices, the other
350 // class is free.
351 for (size_t i = 0; i < vals.size(); ++i)
352 if (vals[i] == want_or) someE.push_back(i);
353 } else {
354 // bool_or=false / bool_and=true: the trigger class must be wholly
355 // absent and the opposite class must have at least one present row.
356 for (size_t i = 0; i < vals.size(); ++i)
357 (vals[i] == want_or ? noneF : someE).push_back(i);
358 }
359
360 if (someE.empty()) { pw_out = S.zero(); return true; }
361
362 if (certifiable_contributors(kvals)) {
363 std::vector<typename SemiringT::value_type> disjuncts;
364 std::vector<typename SemiringT::value_type> before; // someE rows before i
365 for (size_t e : someE) {
366 std::vector<typename SemiringT::value_type> missing = before;
367 for (size_t f : noneF) missing.push_back(kvals[f]);
368 disjuncts.push_back(S.certified_world_term(
369 std::vector<typename SemiringT::value_type>{kvals[e]}, missing));
370 before.push_back(kvals[e]);
371 }
372 pw_out = S.certified_exclusive_plus(disjuncts);
373 return true;
374 }
375
376 const auto one = S.one();
377 // "none of noneF present": product of complements.
378 auto none_factor = one;
379 for (size_t f : noneF) {
380 auto absent = S.monus(one, kvals[f]);
381 none_factor = (none_factor == one)
382 ? absent
383 : S.times(std::vector<typename SemiringT::value_type>{none_factor,
384 absent});
385 }
386 // "at least one of someE present": telescope by first present index.
387 std::vector<typename SemiringT::value_type> disjuncts;
388 auto prefix = one;
389 for (size_t i : someE) {
390 if (prefix == one)
391 disjuncts.push_back(kvals[i]);
392 else if (kvals[i] == one)
393 disjuncts.push_back(prefix);
394 else
395 disjuncts.push_back(S.times(
396 std::vector<typename SemiringT::value_type>{kvals[i], prefix}));
397 auto absent = S.monus(one, kvals[i]);
398 prefix = (prefix == one)
399 ? absent
400 : S.times(std::vector<typename SemiringT::value_type>{prefix, absent});
401 }
402 auto some_value = disjuncts.empty() ? S.zero() : S.plus(disjuncts);
403 if (none_factor == one)
404 pw_out = some_value;
405 else if (some_value == one)
406 pw_out = none_factor;
407 else
408 pw_out = S.times(
409 std::vector<typename SemiringT::value_type>{none_factor, some_value});
410 return true;
411 }
412
413 // ---- Array comparison domain: array_agg(x) against a constant array.
414 // No aggregate-specific optimization (the general pipeline): scan the
415 // non-empty worlds whose ordered present elements equal (=) or differ
416 // (<>) from the constant array, then combine those worlds in the
417 // m-semiring. Elements are compared as their text representations,
418 // so any element type works. ----
419 if (agg_kind == AggregationOperator::ARRAY_AGG) {
420 if (effective_op != ComparisonOperator::EQ &&
421 effective_op != ComparisonOperator::NE)
422 throw std::runtime_error(
423 "only = and <> are supported when comparing array_agg() with a "
424 "constant array in HAVING");
425
426 std::string C_str;
427 if (!extract_constant_string(c, const_side, C_str)) return false;
428 std::vector<std::string> target;
429 if (!parse_array_literal(C_str, target)) return false;
430
431 std::vector<std::string> vals;
432 std::vector<typename SemiringT::value_type> kvals;
433 vals.reserve(children.size());
434 kvals.reserve(children.size());
435 for (gate_t ch : children) {
436 if (c.getGateType(ch) != gate_semimod) return false;
437 std::string m_str;
438 gate_t k_gate{};
439 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
440 vals.push_back(m_str);
441 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
442 }
443
444 // Boolean elements: the row values carry the scalar bool text
445 // ('true'/'false') while the constant array's elements come back in
446 // PostgreSQL's array form ('t'/'f'). Canonicalise both sides so the
447 // text comparison below sees the same representation.
448 if (aggtype_elem_is_boolean(aggtype)) {
449 auto canon_bool = [](std::string &s) {
450 if (s == "t" || s == "true" || s == "1") s = "true";
451 else if (s == "f" || s == "false" || s == "0") s = "false";
452 };
453 for (auto &e : target) canon_bool(e);
454 for (auto &v : vals) canon_bool(v);
455 }
456
457 auto worlds = enumerate_array_agg_worlds(
458 vals, target, effective_op == ComparisonOperator::EQ);
459 pw_out = combine_exhaustive_worlds(worlds, kvals, /*upset=*/false,
460 effective_op, agg_kind);
461 return true;
462 }
463
464 // ---- Numeric comparison domain (int / numeric / float): unify by
465 // scaling every value and the threshold to a common integer
466 // grid by their decimal text, so a numeric(p,d) / finite-decimal
467 // float column is evaluated exactly and fractional thresholds
468 // work. Integer is the scale-0 case. ----
469 std::string C_str;
470 if (!extract_constant_string(c, const_side, C_str)) return false;
471 long C_mant = 0; int C_scale = 0;
472 if (!parse_decimal_scaled(C_str, C_mant, C_scale)) return false;
473
474 std::vector<long> m_mant;
475 std::vector<int> m_scale;
476 std::vector<typename SemiringT::value_type> kvals;
477 m_mant.reserve(children.size());
478 m_scale.reserve(children.size());
479 kvals.reserve(children.size());
480 for (gate_t ch : children) {
481 if (c.getGateType(ch) != gate_semimod) return false;
482 std::string m_str;
483 gate_t k_gate{};
484 if (!semimod_extract_string_and_K(c, ch, m_str, k_gate)) return false;
485 long mm = 0; int ms = 0;
486 if (!parse_decimal_scaled(m_str, mm, ms)) return false;
487 m_mant.push_back(mm);
488 m_scale.push_back(ms);
489 kvals.push_back(c.evaluate<SemiringT>(k_gate, mapping, S));
490 }
491
492 // COUNT(*) is SUM of unit 1s; detect on the unscaled values.
493 if (agg_kind == AggregationOperator::SUM) {
494 bool all_one = true;
495 for (size_t i = 0; i < m_mant.size(); ++i)
496 if (!(m_mant[i] == 1 && m_scale[i] == 0)) { all_one = false; break; }
497 if (all_one) agg_kind = AggregationOperator::COUNT;
498 }
499
500 // Common scale: rescale every value and the threshold to integers.
501 int target_scale = C_scale;
502 for (int ms : m_scale) target_scale = std::max(target_scale, ms);
503 long C = 0;
504 if (!rescale_to(C_mant, C_scale, target_scale, C)) return false;
505 std::vector<long> mvals(m_mant.size());
506 for (size_t i = 0; i < m_mant.size(); ++i)
507 if (!rescale_to(m_mant[i], m_scale[i], target_scale, mvals[i])) return false;
508
509 // A certified enumeration needs the *complete* valid worlds (every
510 // contributor present or explicitly negated): the upset shortcut
511 // and the monotone MIN/MAX skips below produce overlapping,
512 // non-exclusive disjuncts, sound for absorptive evaluation but
513 // unmarkable. When certifying, request the full enumeration --
514 // the same one non-absorptive semirings already use.
515 const bool certify = certifiable_contributors(kvals);
516
517 bool upset = false;
518 auto worlds = enumerate_valid_worlds(mvals, C, effective_op, agg_kind,
519 certify ? false : S.absorptive(),
520 upset, is_scalar);
521
522 pw_out = combine_exhaustive_worlds(worlds, kvals, upset, op, agg_kind);
523 return true;
524 };
525
526 // General possible-worlds evaluation for a comparison over an *arithmetic
527 // expression of one or more aggregates* (sum(x)*sum(y) > k, sum(x) > sum(y),
528 // 100/sum(x) > 5, ...), which the single-aggregate path above cannot fold.
529 // We enumerate the joint possible worlds over the union of the aggregates'
530 // contributors, evaluate the arithmetic numerically in each world, and
531 // combine the worlds where the comparison holds in the semiring exactly as
532 // the single-aggregate path does (present_prod ⊗ (1 ⊖ missing_sum)). This
533 // is exponential in the number of distinct contributors, so it bails out
534 // (returning false, leaving the gate unresolved) beyond a small bound.
535 auto build_general = [&](gate_t Lx, gate_t Rx,
536 ComparisonOperator opx) -> bool {
537 struct AggInfo {
539 bool is_int; // integer-typed result?
540 std::vector<std::pair<int, double> > contribs; // (distinct-K index, value)
541 };
542 // A decimal text denotes an integer iff it has no fractional/exponent part.
543 auto text_is_int = [](const std::string &s) -> bool {
544 if (s.empty()) return false;
545 for (char ch : s)
546 if (!((ch >= '0' && ch <= '9') || ch == '+' || ch == '-'))
547 return false;
548 return true;
549 };
550 std::map<gate_t, AggInfo> aggs;
551 std::map<gate_t, int> kindex;
552 std::vector<gate_t> kgates;
553
554 std::function<bool(gate_t)> collect = [&](gate_t gx) -> bool {
555 gate_type gt = c.getGateType(gx);
556 if (gt == gate_agg) {
557 if (aggs.count(gx))
558 return true;
559 AggInfo ai;
560 ai.kind = getAggregationOperator(c.getInfos(gx).first);
561 ai.is_int = aggtype_is_integer(c.getInfos(gx).second & PROVSQL_AGG_TYPE_MASK);
562 for (gate_t ch : c.getWires(gx)) {
563 if (c.getGateType(ch) != gate_semimod)
564 return false;
565 std::string ms;
566 gate_t kg{};
567 if (!semimod_extract_string_and_K(c, ch, ms, kg))
568 return false;
569 int idx;
570 auto it = kindex.find(kg);
571 if (it == kindex.end()) {
572 idx = static_cast<int>(kgates.size());
573 kindex[kg] = idx;
574 kgates.push_back(kg);
575 } else
576 idx = it->second;
577 double m;
578 try { m = std::stod(ms); } catch (...) { return false; }
579 ai.contribs.emplace_back(idx, m);
580 }
581 aggs.emplace(gx, std::move(ai));
582 return true;
583 }
584 if (gt == gate_arith) {
585 for (gate_t ch : c.getWires(gx))
586 if (!collect(ch))
587 return false;
588 return true;
589 }
590 if (gt == gate_value)
591 return true;
592 if (gt == gate_semimod) { // a constant threshold: semimod(1, value)
593 std::string ms;
594 gate_t kg{};
595 if (!semimod_extract_string_and_K(c, gx, ms, kg))
596 return false;
597 return c.getGateType(kg) == gate_one;
598 }
599 return false;
600 };
601
602 if (!collect(Lx) || !collect(Rx))
603 return false;
604 if (aggs.empty())
605 return false;
606 const size_t n = kgates.size();
607 if (n == 0 || n > 24) // 2^n enumeration: keep it bounded
608 return false;
609
610 // Numeric value of a subexpression in a given world, tracking whether it
611 // is integer-valued so that division floors as SQL does (NULL -> false).
612 std::function<bool(gate_t, uint64_t, double &, bool &)> eval =
613 [&](gate_t gx, uint64_t world, double &out, bool &is_int) -> bool {
614 gate_type gt = c.getGateType(gx);
615 if (gt == gate_value) {
616 std::string s = c.getExtra(gx);
617 try { out = std::stod(s); } catch (...) { return false; }
618 is_int = text_is_int(s);
619 return true;
620 }
621 if (gt == gate_semimod) { // constant threshold
622 std::string ms; gate_t kg{};
623 if (!semimod_extract_string_and_K(c, gx, ms, kg)) return false;
624 try { out = std::stod(ms); } catch (...) { return false; }
625 is_int = text_is_int(ms);
626 return true;
627 }
628 if (gt == gate_agg) {
629 const AggInfo &ai = aggs.at(gx);
630 double acc = 0, mn = 0, mx = 0;
631 long cnt = 0;
632 bool first = true;
633 for (const auto &pr : ai.contribs)
634 if (world & (uint64_t(1) << pr.first)) {
635 double m = pr.second;
636 acc += m; ++cnt;
637 if (first) { mn = mx = m; first = false; }
638 else { mn = std::min(mn, m); mx = std::max(mx, m); }
639 }
640 is_int = ai.is_int;
641 switch (ai.kind) {
642 case AggregationOperator::SUM: out = acc; return true;
643 case AggregationOperator::COUNT: out = acc; return true; // values 1 or 0/1
644 case AggregationOperator::AVG: if (cnt == 0) return false; out = acc / cnt; return true;
645 case AggregationOperator::MIN: if (cnt == 0) return false; out = mn; return true;
646 case AggregationOperator::MAX: if (cnt == 0) return false; out = mx; return true;
647 default: return false;
648 }
649 }
650 if (gt == gate_arith) {
651 const auto &w = c.getWires(gx);
652 unsigned aop = static_cast<unsigned>(c.getInfos(gx).first);
653 if (aop == PROVSQL_ARITH_PLUS || aop == PROVSQL_ARITH_TIMES) {
654 double r = (aop == PROVSQL_ARITH_PLUS) ? 0 : 1;
655 bool all_int = true;
656 for (gate_t ch : w) {
657 double v; bool vi;
658 if (!eval(ch, world, v, vi)) return false;
659 if (aop == PROVSQL_ARITH_PLUS) r += v; else r *= v;
660 all_int = all_int && vi;
661 }
662 out = r; is_int = all_int; return true;
663 }
664 if (aop == PROVSQL_ARITH_MINUS) {
665 if (w.size() != 2) return false;
666 double a, b; bool ai, bi;
667 if (!eval(w[0], world, a, ai) || !eval(w[1], world, b, bi)) return false;
668 out = a - b; is_int = ai && bi; return true;
669 }
670 if (aop == PROVSQL_ARITH_DIV) {
671 if (w.size() != 2) return false;
672 double a, b; bool ai, bi;
673 if (!eval(w[0], world, a, ai) || !eval(w[1], world, b, bi)) return false;
674 if (b == 0) return false;
675 if (ai && bi) { // SQL integer division truncates toward zero
676 out = static_cast<double>(static_cast<long long>(a) /
677 static_cast<long long>(b));
678 is_int = true;
679 } else {
680 out = a / b; is_int = false;
681 }
682 return true;
683 }
684 if (aop == PROVSQL_ARITH_NEG) {
685 if (w.size() != 1) return false;
686 double a; bool ai;
687 if (!eval(w[0], world, a, ai)) return false;
688 out = -a; is_int = ai; return true;
689 }
690 return false;
691 }
692 return false;
693 };
694
695 std::vector<typename SemiringT::value_type> kval(n);
696 for (size_t i = 0; i < n; ++i)
697 kval[i] = c.evaluate<SemiringT>(kgates[i], mapping, S);
698
699 // The joint enumeration below is over complete worlds already:
700 // certify the disjuncts when the semiring and contributors allow.
701 const bool certify = certifiable_contributors(kval);
702
703 std::vector<typename SemiringT::value_type> disjuncts;
704 const uint64_t total = uint64_t(1) << n;
705 for (uint64_t world = 1; world < total; ++world) { // skip the empty world
706 double lv, rv;
707 bool lint, rint;
708 if (!eval(Lx, world, lv, lint) || !eval(Rx, world, rv, rint))
709 continue; // NULL comparison: false
710 bool holds = false;
711 switch (opx) {
712 case ComparisonOperator::EQ: holds = (lv == rv); break;
713 case ComparisonOperator::NE: holds = (lv != rv); break;
714 case ComparisonOperator::LT: holds = (lv < rv); break;
715 case ComparisonOperator::LE: holds = (lv <= rv); break;
716 case ComparisonOperator::GT: holds = (lv > rv); break;
717 case ComparisonOperator::GE: holds = (lv >= rv); break;
718 }
719 if (!holds)
720 continue;
721
722 std::vector<typename SemiringT::value_type> present, missing;
723 for (size_t i = 0; i < n; ++i) {
724 if (world & (uint64_t(1) << i)) {
725 if (certify || kval[i] != S.one()) present.push_back(kval[i]);
726 } else {
727 if (certify || kval[i] != S.zero()) missing.push_back(kval[i]);
728 }
729 }
730 if (certify) {
731 disjuncts.push_back(S.certified_world_term(present, missing));
732 continue;
733 }
734 auto present_prod = S.times(present);
735 if (missing.empty())
736 disjuncts.push_back(std::move(present_prod));
737 else {
738 auto monus_factor = S.monus(S.one(), S.plus(missing));
739 disjuncts.push_back(
740 present_prod == S.one()
741 ? monus_factor
742 : S.times(std::vector<typename SemiringT::value_type>{
743 present_prod, monus_factor}));
744 }
745 }
746
747 pw_out = disjuncts.empty() ? S.zero()
748 : certify ? S.certified_exclusive_plus(disjuncts)
749 : S.plus(disjuncts);
750 return true;
751 };
752
753 if (c.getGateType(L) == gate_agg && build_from(L, R, op))
754 return true;
755 if (c.getGateType(R) == gate_agg && build_from(R, L, flip_op(op)))
756 return true;
757
758 return build_general(L, R, op);
759 };
760
761 for (gate_t cmp_gate : cmp_gates) {
762 typename SemiringT::value_type pw;
763 if (!pw_from_cmp_gate(cmp_gate, pw))
764 return;
765
766 mapping[cmp_gate] = std::move(pw);
767 }
768}
769
770#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().
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
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.
void provsql_having(GenericCircuit &c, gate_t g, MapT &mapping, SemiringT S=SemiringT{})
Rewrite HAVING comparison gates in the circuit by enumerating possible worlds.
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 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_DIV
binary, child0 / child1
@ PROVSQL_ARITH_PLUS
n-ary, sum of children
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_TIMES
n-ary, product of children
#define PROVSQL_AGG_TYPE_MASK
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)
#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_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:465
Enumerate tuple subsets satisfying an aggregate HAVING predicate.