ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
having_semantics.cpp
Go to the documentation of this file.
1/**
2 * @file having_semantics.cpp
3 * @brief Helper definitions for HAVING-clause provenance evaluation.
4 *
5 * Defines the small non-template helpers declared in
6 * @c provsql_having_detail in @c having_semantics.hpp. The actual
7 * possible-worlds enumeration logic is the @c provsql_having() template
8 * in the header.
9 */
10extern "C" {
11#include "postgres.h"
12#include "catalog/pg_type.h"
13#include "utils/lsyscache.h"
14}
15#include "c_cpp_compatibility.h"
16
17#include <climits>
18#include <string>
19#include <unordered_set>
20#include <vector>
21
22#include "having_semantics.hpp"
23
25
26// The comparison-domain type of a HAVING aggregate is the aggregate's
27// result type, stored in info2 of the gate_agg (set by
28// provenance_aggregate as set_infos(agg, aggfnoid, aggtype)). This
29// classifies it so the cmp can be evaluated in the right domain.
30bool aggtype_is_text(unsigned oid) {
31 switch (oid) {
32 case TEXTOID: case VARCHAROID: case BPCHAROID: case CHAROID: case NAMEOID:
33 return true;
34 default:
35 return false;
36 }
37}
38
39bool aggtype_is_integer(unsigned oid) {
40 switch (oid) {
41 case INT2OID: case INT4OID: case INT8OID:
42 return true;
43 default:
44 return false;
45 }
46}
47
48bool aggtype_is_boolean(unsigned oid) {
49 return oid == BOOLOID;
50}
51
52// array_agg's result type is the array type (e.g. boolean[]); its element type
53// is the comparison domain. PostgreSQL serialises a scalar bool as
54// 'true'/'false' but a bool array element as 't'/'f', so the array_agg
55// comparison must know when the elements are boolean to reconcile the two.
56bool aggtype_elem_is_boolean(unsigned oid) {
57 if (oid == BOOLOID)
58 return true;
59 Oid elem = get_element_type(oid);
60 return elem == BOOLOID;
61}
62
63// Types handled by the numeric comparison domain (scaled to a common integer
64// grid). choose() over these is evaluated there -- including ordering
65// comparisons; choose() over any other type falls to the value-as-text domain.
66bool aggtype_is_numeric(unsigned oid) {
67 switch (oid) {
68 case INT2OID: case INT4OID: case INT8OID:
69 case FLOAT4OID: case FLOAT8OID: case NUMERICOID:
70 return true;
71 default:
72 return false;
73 }
74}
75
76// Parse a PostgreSQL array output literal -- "{1,2}", "{a,\"b,c\"}" -- into its
77// top-level element texts (surrounding double quotes removed, backslash escapes
78// resolved). Returns false on a malformed or nested-array literal. Sufficient
79// for one-dimensional arrays of scalar elements, which is what array_agg over a
80// provenance-tracked column produces.
81bool parse_array_literal(const std::string &s, std::vector<std::string> &out) {
82 out.clear();
83 size_t i = 0, n = s.size();
84 while (i < n && isspace((unsigned char) s[i])) i++;
85 if (i >= n || s[i] != '{') return false;
86 i++;
87 while (i < n && isspace((unsigned char) s[i])) i++;
88 if (i < n && s[i] == '}') return true; // empty array
89 while (i < n) {
90 std::string elem;
91 while (i < n && isspace((unsigned char) s[i])) i++;
92 if (i < n && s[i] == '"') {
93 i++;
94 while (i < n && s[i] != '"') {
95 if (s[i] == '\\' && i + 1 < n) { elem.push_back(s[i + 1]); i += 2; }
96 else { elem.push_back(s[i]); i++; }
97 }
98 if (i >= n) return false;
99 i++; // closing quote
100 } else {
101 while (i < n && s[i] != ',' && s[i] != '}') { elem.push_back(s[i]); i++; }
102 while (!elem.empty() && isspace((unsigned char) elem.back())) elem.pop_back();
103 }
104 out.push_back(elem);
105 while (i < n && isspace((unsigned char) s[i])) i++;
106 if (i < n && s[i] == ',') { i++; continue; }
107 if (i < n && s[i] == '}') return true;
108 return false;
109 }
110 return false;
111}
112
113// Parse a plain decimal literal ("-12.340", "6", "6.5") into a scaled
114// integer: the value is @c mantissa * 10^(-scale). Returns false on
115// exponential notation, inf / nan, or anything that is not a plain
116// decimal -- those fall back to the existing (string / error) handling.
117bool parse_decimal_scaled(const std::string &s, long &mantissa, int &scale) {
118 if (s.empty()) return false;
119 std::size_t i = 0;
120 bool neg = false;
121 if (s[i] == '+' || s[i] == '-') { neg = (s[i] == '-'); ++i; }
122 std::string digits;
123 int sc = 0;
124 bool seen_dot = false, seen_digit = false;
125 for (; i < s.size(); ++i) {
126 char ch = s[i];
127 if (ch == '.') {
128 if (seen_dot) return false;
129 seen_dot = true;
130 } else if (ch >= '0' && ch <= '9') {
131 digits.push_back(ch);
132 if (seen_dot) ++sc;
133 seen_digit = true;
134 } else {
135 return false; // 'e'/'E', inf, nan, separators, ...
136 }
137 }
138 if (!seen_digit) return false;
139 // Drop trailing zeros in the fractional part: they do not change the value
140 // (15.0000000000000000 == 15, 15.50 == 15.5) but inflate the scale, and a
141 // large scale forces every value to be rescaled to a huge integer grid that
142 // the value-aware sum DP cannot represent. Numeric division in particular
143 // yields such trailing-zero-padded thresholds. Only fractional zeros are
144 // dropped (scale > 0); trailing zeros of an integer (100) are significant.
145 while (sc > 0 && !digits.empty() && digits.back() == '0') {
146 digits.pop_back();
147 --sc;
148 }
149 try {
150 std::size_t pos = 0;
151 long long val = std::stoll(digits, &pos);
152 if (pos != digits.size()) return false;
153 mantissa = neg ? -static_cast<long>(val) : static_cast<long>(val);
154 scale = sc;
155 return true;
156 } catch (...) { // out_of_range / invalid
157 return false;
158 }
159}
160
161// Rescale a (mantissa, scale) decimal to a common target scale, i.e.
162// mantissa * 10^(target_scale - scale). Returns false on overflow.
163bool rescale_to(long mantissa, int scale, int target_scale, long &out) {
164 long factor = 1;
165 for (int k = 0; k < target_scale - scale; ++k) {
166 if (factor > (LONG_MAX / 10)) return false;
167 factor *= 10;
168 }
169 if (mantissa != 0 &&
170 (mantissa > LONG_MAX / factor || mantissa < LONG_MIN / factor))
171 return false;
172 out = mantissa * factor;
173 return true;
174}
175
176// Map a cmp gate's Postgres operator to subset.cpp's ComparisonOperator
178 return cmpOpFromOid(c.getInfos(cmp_gate).first, ok);
179}
180
181// Flip operator for "C op agg" <=> "agg flip(op) C"
193
194// Extract a gate_semimod's gate_value extra as a raw string, along with its
195// K-gate operand. Used by the value-as-text HAVING comparison path.
198 gate_t semimod_gate,
199 std::string &m_out,
200 gate_t &k_gate_out)
201{
202 if (c.getGateType(semimod_gate) != gate_semimod) return false;
203
204 const auto &w = c.getWires(semimod_gate);
205 if (w.size() != 2) return false;
206
207 if (c.getGateType(w[1]) != gate_value) return false;
208 m_out = c.getExtra(w[1]);
209
210 k_gate_out = w[0];
211 return true;
212}
213
214// Extract a constant C encoded as gate_semimod(gate_one, gate_value("C")),
215// returning the gate_value's extra as a raw string. Used by the
216// value-as-text HAVING comparison path.
217bool extract_constant_string(GenericCircuit &c, gate_t x, std::string &C_out) {
218 if (c.getGateType(x) != gate_semimod)
219 return false;
220
221 const auto &w = c.getWires(x);
222 if (w.size() != 2)
223 return false;
224
225 if (c.getGateType(w[0]) != gate_one)
226 return false;
227
228 if (c.getGateType(w[1]) != gate_value)
229 return false;
230
231 C_out = c.getExtra(w[1]);
232 return true;
233}
234
235// Whether a comparison side is (or is arithmetic over) an aggregate: descend
236// through gate_arith until a gate_agg is found. Used to recognise the cmp
237// gates that the HAVING / WHERE-on-aggregate evaluator must resolve (as
238// opposed to RV comparisons, which carry gate_rv leaves instead).
240 gate_type t = c.getGateType(g);
241 if (t == gate_agg)
242 return true;
243 if (t == gate_arith) {
244 for (gate_t ch : c.getWires(g))
245 if (side_has_agg(c, ch))
246 return true;
247 }
248 return false;
249}
250
251// Collect cmp gates in the prov circuit
252std::vector<gate_t> collect_sp_cmp_gates(GenericCircuit &c, gate_t start) {
253 std::vector<gate_t> out;
254 std::vector<gate_t> stack;
255 stack.push_back(start);
256
257 std::unordered_set<gate_t> seen;
258
259 while (!stack.empty()) {
260 gate_t cur = stack.back();
261 stack.pop_back();
262
263 if (!seen.insert(cur).second) continue;
264
265 if (c.getGateType(cur) == gate_cmp) {
266 const auto &cw = c.getWires(cur);
267 if (cw.size() == 2) {
268 gate_t L = cw[0];
269 gate_t R = cw[1];
270
271 // Any comparison with an aggregate on either side (directly, or under
272 // arithmetic): the single-aggregate-vs-constant ones are handled by the
273 // fast path, the rest (agg-vs-agg, products of aggregates, c/agg, ...)
274 // by the general possible-worlds enumeration. RV comparisons carry
275 // gate_rv leaves instead and are left to the RV evaluator.
276 if (side_has_agg(c, L) || side_has_agg(c, R))
277 out.push_back(cur);
278 }
279 }
280
281 const auto &w = c.getWires(cur);
282 for (gate_t ch : w) stack.push_back(ch);
283 }
284 return out;
285}
286
287} // namespace provsql_having_detail
ComparisonOperator cmpOpFromOid(Oid op_oid, bool &ok)
Map a PostgreSQL comparison-operator OID to a ComparisonOperator.
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
Fix macro conflicts between PostgreSQL headers and the C++ STL/Boost.
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.
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.
Provenance evaluation helper for HAVING-clause circuits.
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)
static bool side_has_agg(GenericCircuit &c, gate_t g)
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)
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)