45#include "access/htup_details.h"
46#include "access/xact.h"
47#include "catalog/pg_type.h"
48#include "executor/spi.h"
49#include "utils/array.h"
50#include "utils/builtins.h"
51#include "utils/resowner.h"
52#include "utils/uuid.h"
85#include <unordered_map>
86#include <unordered_set>
92class MobiusDecline :
public std::runtime_error {
94 explicit MobiusDecline(
const std::string &w) : std::runtime_error(w) {}
101 bool operator==(
const Term &o)
const {
return isVar==o.isVar && v==o.v; }
107 std::vector<Term> args;
110using Disjunct = std::vector<MAtom>;
111using Sentence = std::vector<Disjunct>;
117 int n_components = 0;
118 int n_cnf_conjuncts = 0;
119 int lattice_size = 0;
120 int lattice_collapsed = 0;
123 bool cancelled_hard =
false;
126 double probability = 0.0;
139bool homExists(
const Disjunct &p,
const Disjunct &q,
140 std::size_t ai, std::map<long,Term> &asg)
144 const MAtom &pa = p[ai];
145 for(
const MAtom &qa : q) {
146 if(qa.rel != pa.rel || qa.args.size() != pa.args.size())
148 std::vector<long> undo;
150 for(std::size_t k=0; k<pa.args.size(); ++k) {
151 const Term &pt = pa.args[k];
152 const Term &qt = qa.args[k];
154 if(qt.isVar || qt.v != pt.v) { ok=
false;
break; }
156 auto it = asg.find(pt.v);
157 if(it == asg.end()) { asg[pt.v]=qt; undo.push_back(pt.v); }
158 else if(!(it->second == qt)) { ok=
false;
break; }
161 if(ok && homExists(p, q, ai+1, asg))
163 for(
long u : undo) asg.erase(u);
169bool hom(
const Disjunct &p,
const Disjunct &q)
171 std::map<long,Term> asg;
172 return homExists(p, q, 0, asg);
177bool equiv(
const Disjunct &p,
const Disjunct &q)
179 return hom(p,q) && hom(q,p);
188 explicit UF(
int n) : p(n) {
for(
int i=0;i<n;++i) p[i]=i; }
189 int find(
int x){
while(p[x]!=x){ p[x]=p[p[x]]; x=p[x]; }
return x; }
190 void uni(
int a,
int b){ p[find(a)]=find(b); }
196std::vector<Disjunct> componentsOf(
const Disjunct &d)
198 const int n =
static_cast<int>(d.size());
200 std::map<long, int> firstAtomOfVar;
202 for(
const auto &t : d[i].args)
204 auto it = firstAtomOfVar.find(t.v);
205 if(it==firstAtomOfVar.end()) firstAtomOfVar[t.v]=i;
206 else uf.uni(it->second, i);
208 std::map<int, Disjunct> byroot;
210 byroot[uf.find(i)].push_back(d[i]);
211 std::vector<Disjunct> out;
212 for(
auto &kv : byroot) out.push_back(std::move(kv.second));
217bool ground(
const MAtom &a)
219 for(
const auto &t : a.args)
if(t.isVar)
return false;
230 std::map<std::pair<unsigned, std::vector<long>>, std::string> tok;
231 std::set<std::pair<unsigned, std::vector<long>>> present;
233 std::map<std::pair<unsigned,int>, std::set<long>> domain;
235 bool isPresent(
unsigned rel,
const std::vector<long> &el)
const {
236 return present.count({rel, el}) > 0;
238 const std::string *token(
unsigned rel,
const std::vector<long> &el)
const {
239 auto it = tok.find({rel, el});
240 return it==tok.end() ? nullptr : &it->second;
248class MobiusCompiler {
250 MobiusCompiler(
const FactIndex &fi, MobiusStats &st) : fi(fi), st(st) {
254 if(!OidIsValid(times_oid) || !OidIsValid(plus_oid))
255 provsql_error(
"ucq_mobius: provenance_times / provenance_plus unavailable");
265 pg_uuid_t compileTop(
const Sentence &s,
const std::string &lineage =
"") {
266 pending_lineage = lineage;
267 lineage_consumed =
false;
268 pg_uuid_t root = compile(s,
true);
269 if(!lineage.empty() && !lineage_consumed)
273 root = mkMobius({root}, {1}, lineage);
280 Oid times_oid = InvalidOid;
281 Oid plus_oid = InvalidOid;
282 std::unordered_map<std::string, std::string> memo;
283 std::unordered_set<std::string> created;
284 std::string pending_lineage;
285 bool lineage_consumed =
false;
295 pg_uuid_t callProvenance(
bool isAnd,
const std::vector<pg_uuid_t> &ch) {
296 std::vector<Datum> datums;
297 datums.reserve(ch.size());
298 for(
const auto &u : ch)
299 datums.push_back(UUIDPGetDatum(
const_cast<pg_uuid_t *
>(&u)));
300 ArrayType *arr = construct_array(datums.empty()?
nullptr:datums.data(),
301 static_cast<int>(datums.size()),
303 Datum res = OidFunctionCall1(isAnd ? times_oid : plus_oid,
304 PointerGetDatum(arr));
312 throw MobiusDecline(
"Möbius: data-cost cap (provsql.mobius_max_gates) "
313 "exceeded -- the |D|^k recursion is too large");
314 return *DatumGetUUIDP(res);
318 pg_uuid_t mkConst(
bool one) {
return callProvenance(one, {}); }
324 pg_uuid_t mkBool(
bool isAnd, std::vector<pg_uuid_t> ch) {
325 std::vector<std::string> texts;
326 texts.reserve(ch.size());
327 for(
const auto &c : ch) texts.push_back(
uuid2string(c));
328 std::sort(texts.begin(), texts.end());
329 texts.erase(std::unique(texts.begin(), texts.end()), texts.end());
330 std::vector<pg_uuid_t> uniq;
331 uniq.reserve(texts.size());
332 for(
const auto &t : texts) uniq.push_back(
string2uuid(t));
333 return callProvenance(isAnd, uniq);
343 pg_uuid_t mkMobius(
const std::vector<pg_uuid_t> &children,
344 const std::vector<long> &coeffs,
345 const std::string &lineage =
"") {
346 std::map<std::string,long> bycoeff;
347 std::vector<std::string> order;
348 for(std::size_t i=0;i<children.size();++i) {
350 if(!bycoeff.count(u)) order.push_back(u);
351 bycoeff[u] += coeffs[i];
353 std::vector<pg_uuid_t> ch;
354 std::string extra, name =
"mobius[";
355 if(!lineage.empty()) {
357 extra +=
"L:" + lineage +
" ";
358 name +=
"L:" + lineage +
",";
360 for(
const std::string &u : order) {
361 if(bycoeff[u]==0)
continue;
363 extra += u +
":" + std::to_string(bycoeff[u]) +
" ";
364 name += u +
":" + std::to_string(bycoeff[u]) +
",";
367 if(ch.empty() || (!lineage.empty() && ch.size()==1))
368 return mkConst(
false);
372 static_cast<unsigned>(ch.size()),
382 std::string canonDisjunct(
const Disjunct &d)
const {
387 std::vector<std::string> atomsigRaw;
388 for(
const auto &a : d) {
389 std::string sg =
"r" + std::to_string(a.rel) +
"(";
390 for(
const auto &t : a.args) {
391 if(t.isVar) sg +=
"v";
else sg +=
"c"+std::to_string(t.v);
395 atomsigRaw.push_back(sg);
399 std::vector<int> order(d.size());
400 for(std::size_t i=0;i<d.size();++i) order[i]=
static_cast<int>(i);
401 std::sort(order.begin(), order.end(),
402 [&](
int a,
int b){ return atomsigRaw[a]<atomsigRaw[b]; });
403 std::map<long,int> ren;
405 for(
int idx : order) {
406 const MAtom &a = d[idx];
407 out +=
"r"+std::to_string(a.rel)+
"(";
408 for(
const auto &t : a.args) {
410 auto it = ren.find(t.v);
411 int id = (it==ren.end()) ? (ren[t.v]=
static_cast<int>(ren.size())) : it->second;
412 out +=
"v"+std::to_string(
id);
413 }
else out +=
"c"+std::to_string(t.v);
421 std::string canonSentence(
const Sentence &s)
const {
422 std::vector<std::string> parts;
423 for(
const auto &d : s) parts.push_back(canonDisjunct(d));
424 std::sort(parts.begin(), parts.end());
425 parts.erase(std::unique(parts.begin(), parts.end()), parts.end());
427 for(
const auto &p : parts){ out += p; out +=
"|"; }
433 pg_uuid_t compile(
const Sentence &sentence,
bool top=
false);
439 bool findSeparator(
const Sentence &s,
440 std::set<std::pair<unsigned,int>> &occ,
441 std::vector<std::set<long>> &classVarsPerDisj);
444 pg_uuid_t mobiusStep(
const Sentence &s,
bool top);
449bool MobiusCompiler::findSeparator(
const Sentence &s,
450 std::set<std::pair<unsigned,int>> &occOut,
451 std::vector<std::set<long>> &classVarsPerDisj)
454 std::map<long,int> id;
455 auto vid = [&](
long v)->
int{
456 auto it=
id.find(v);
if(it!=
id.end())
return it->second;
457 int n=
static_cast<int>(
id.size());
id[v]=n;
return n;
459 for(
const auto &d : s)
for(
const auto &a : d)
for(
const auto &t : a.args)
460 if(t.isVar) vid(t.v);
461 if(
id.empty())
return false;
462 UF uf(
static_cast<int>(
id.size()));
463 std::map<std::pair<unsigned,int>,
long> firstAtPos;
464 for(
const auto &d : s)
for(
const auto &a : d)
465 for(std::size_t k=0;k<a.args.size();++k) {
466 if(!a.args[k].isVar)
continue;
467 auto key = std::make_pair(a.rel,
static_cast<int>(k));
468 auto it = firstAtPos.find(key);
469 if(it==firstAtPos.end()) firstAtPos[key]=a.args[k].v;
470 else uf.uni(vid(it->second), vid(a.args[k].v));
474 std::vector<long> idToVar(
id.size());
475 for(
auto &kv :
id) idToVar[kv.second]=kv.first;
478 for(
int i=0;i<static_cast<int>(
id.size());++i) roots.insert(uf.find(i));
480 for(
int root : roots) {
482 std::vector<std::set<long>> cvars(s.size());
483 for(std::size_t di=0; di<s.size() && covers; ++di) {
484 const Disjunct &d = s[di];
485 for(
const auto &a : d) {
487 for(
const auto &t : a.args)
488 if(t.isVar && uf.find(vid(t.v))==root) {
489 atomHas=
true; cvars[di].insert(t.v);
491 if(!atomHas){ covers=
false;
break; }
494 if(!covers)
continue;
497 for(
const auto &d : s)
for(
const auto &a : d)
498 for(std::size_t k=0;k<a.args.size();++k)
499 if(a.args[k].isVar && uf.find(vid(a.args[k].v))==root)
500 occOut.insert({a.rel,
static_cast<int>(k)});
501 classVarsPerDisj = std::move(cvars);
509pg_uuid_t MobiusCompiler::mobiusStep(
const Sentence &s,
bool top)
513 std::vector<Disjunct> literals;
514 auto litId = [&](
const Disjunct &c)->
int{
515 for(std::size_t i=0;i<literals.size();++i)
516 if(equiv(literals[i], c))
return static_cast<int>(i);
517 literals.push_back(c);
518 return static_cast<int>(literals.size()-1);
520 std::vector<std::set<int>> terms;
521 for(
const auto &d : s) {
523 for(
const auto &c : componentsOf(d)) t.insert(litId(c));
524 terms.push_back(std::move(t));
526 if(top) st.n_components =
static_cast<int>(literals.size());
530 const int L =
static_cast<int>(literals.size());
531 std::vector<std::vector<char>> imp(L, std::vector<char>(L, 0));
532 for(
int i=0;i<L;++i)
for(
int j=0;j<L;++j)
533 imp[i][j] = hom(literals[j], literals[i]) ? 1 : 0;
537 auto minimiseDisj = [&](std::set<int> in)->std::vector<int>{
538 std::vector<int> v(in.begin(), in.end());
539 std::vector<char> keep(v.size(),1);
540 for(std::size_t a=0;a<v.size();++a)
for(std::size_t b=0;b<v.size();++b)
541 if(a!=b && keep[b] && imp[v[a]][v[b]]) { keep[a]=0;
break; }
542 std::vector<int> out;
543 for(std::size_t a=0;a<v.size();++a)
if(keep[a]) out.push_back(v[a]);
544 std::sort(out.begin(), out.end());
550 std::set<std::vector<int>> clauseSet;
551 std::vector<std::set<int>> clauses;
553 std::vector<int> pick(terms.size(), 0);
555 unsigned long prod = 1;
556 for(
const auto &t : terms) prod *= std::max<std::size_t>(1, t.size());
558 throw MobiusDecline(
"Möbius: DNF->CNF transversal count too large");
559 std::function<void(std::size_t,std::set<int>&)> gen =
560 [&](std::size_t ti, std::set<int> &acc){
561 if(ti==terms.size()){
562 std::vector<int> mn = minimiseDisj(acc);
563 if(!mn.empty()) clauseSet.insert(mn);
566 for(
int lit : terms[ti]) {
567 bool fresh = acc.insert(lit).second;
569 if(fresh) acc.erase(lit);
572 { std::set<int> acc; gen(0, acc); }
577 std::vector<std::vector<int>> cl(clauseSet.begin(), clauseSet.end());
578 auto disjImplies = [&](
const std::vector<int>&A,
const std::vector<int>&B){
579 for(
int a : A){
bool ok=
false;
for(
int b : B)
if(imp[a][b]){ ok=
true;
break; }
580 if(!ok)
return false; }
583 std::vector<char> keepCl(cl.size(),1);
584 for(std::size_t a=0;a<cl.size();++a)
for(std::size_t b=0;b<cl.size();++b)
585 if(a!=b && keepCl[a] && cl[a]!=cl[b] && disjImplies(cl[a],cl[b]))
587 std::vector<std::vector<int>> M;
588 for(std::size_t a=0;a<cl.size();++a)
if(keepCl[a]) M.push_back(cl[a]);
590 const int m =
static_cast<int>(M.size());
591 if(top) { st.n_cnf_conjuncts = m; st.lattice_size = (m<=30)?(1<<m):0; }
593 throw MobiusDecline(
"Möbius: no inclusion-exclusion structure (M<2); "
594 "sentence has no separator and does not decompose");
596 throw MobiusDecline(
"Möbius: CNF conjunct count exceeds the cap (M>8)");
601 std::map<std::vector<int>,
long> coeff;
602 for(
unsigned mask=1; mask < (1u<<m); ++mask) {
604 for(
int i=0;i<m;++i)
if(mask & (1u<<i))
605 lits.insert(M[i].begin(), M[i].end());
606 std::vector<int> key = minimiseDisj(lits);
607 const int pc = __builtin_popcount(mask);
608 coeff[key] += (pc & 1) ? 1 : -1;
610 if(top) st.lattice_collapsed =
static_cast<int>(coeff.size());
614 std::vector<pg_uuid_t> children;
615 std::vector<long> coeffs;
617 bool cancelledHard =
false;
618 for(
const auto &kv : coeff) {
627 for(
int lid : kv.first) el.push_back(literals[lid]);
628 std::set<std::pair<unsigned,int>> occ;
629 std::vector<std::set<long>> cv;
631 if(el.size() >= 2 && !findSeparator(el, occ, cv)) {
633 UF uf(
static_cast<int>(el.size()));
634 std::map<unsigned,int> firstRelDisj;
635 for(std::size_t i=0;i<el.size();++i)
636 for(
const auto &a : el[i])
637 {
auto it=firstRelDisj.find(a.rel);
638 if(it==firstRelDisj.end()) firstRelDisj[a.rel]=
static_cast<int>(i);
639 else uf.uni(it->second,
static_cast<int>(i)); }
640 std::set<int> r;
for(std::size_t i=0;i<el.size();++i) r.insert(uf.find(
static_cast<int>(i)));
641 if(r.size()==1) cancelledHard =
true;
647 for(
int lid : kv.first) el.push_back(literals[lid]);
648 children.push_back(compile(el,
false));
649 coeffs.push_back(kv.second);
651 if(top) { st.n_cancelled = cancelled; st.cancelled_hard = cancelledHard;
652 st.n_nonzero =
static_cast<int>(children.size()); }
655 return mkConst(
false);
659 if(top && !pending_lineage.empty()) {
660 lineage_consumed =
true;
661 return mkMobius(children, coeffs, pending_lineage);
663 return mkMobius(children, coeffs);
668pg_uuid_t MobiusCompiler::compile(
const Sentence &sentence0,
bool top)
670 CHECK_FOR_INTERRUPTS();
677 for(
const Disjunct &d0 : sentence0) {
679 for(
const MAtom &a : d0) {
681 std::vector<long> el;
682 for(
const auto &t : a.args) el.push_back(t.v);
683 if(!fi.isPresent(a.rel, el)) { dead=
true;
break; }
686 if(!dead && !d0.empty()) s.push_back(d0);
689 return mkConst(
false);
694 auto isGround = [&](
const Disjunct &d){
695 for(
const auto &a : d)
if(!ground(a))
return false;
698 auto tokenAnd = [&](
const Disjunct &d)->pg_uuid_t {
699 std::vector<pg_uuid_t> toks;
700 for(
const auto &a : d) {
701 std::vector<long> el;
for(
const auto &t : a.args) el.push_back(t.v);
702 const std::string *tk = fi.token(a.rel, el);
703 if(tk==
nullptr)
return mkConst(
false);
704 if(tk->empty())
continue;
707 return mkBool(
true, toks);
711 const std::string key = canonSentence(s);
713 auto it = memo.find(key);
714 if(it!=memo.end()) { ++st.memo_hits;
return string2uuid(it->second); }
722 const int n =
static_cast<int>(s.size());
724 std::map<unsigned,int> firstDisjWithRel;
726 for(
const auto &a : s[i]) {
727 auto it=firstDisjWithRel.find(a.rel);
728 if(it==firstDisjWithRel.end()) firstDisjWithRel[a.rel]=i;
729 else uf.uni(it->second, i);
731 std::map<int, Sentence> groups;
732 for(
int i=0;i<n;++i) groups[uf.find(i)].push_back(s[i]);
733 if(groups.size() > 1) {
734 std::vector<pg_uuid_t> ch;
735 for(
auto &kv : groups) ch.push_back(compile(kv.second,
false));
736 result = mkBool(
false, ch);
746 result = tokenAnd(s[0]);
755 std::vector<Disjunct> comps = componentsOf(s[0]);
756 if(comps.size() > 1) {
757 for(std::size_t i=0;i<comps.size();++i)
758 for(std::size_t j=i+1;j<comps.size();++j) {
759 std::set<unsigned> ri;
760 for(
const auto &a : comps[i]) ri.insert(a.rel);
761 for(
const auto &a : comps[j])
763 throw MobiusDecline(
"Möbius: within-disjunct self-join (a "
764 "relation shared by two components) -- needs "
765 "ranking/shattering");
767 std::vector<pg_uuid_t> ch;
768 for(
auto &c : comps) { Sentence one{c}; ch.push_back(compile(one,
false)); }
769 result = mkBool(
true, ch);
779 std::set<std::pair<unsigned,int>> occ;
780 std::vector<std::set<long>> classVars;
781 if(findSeparator(s, occ, classVars)) {
784 for(
const auto &rp : occ) {
785 auto it = fi.domain.find(rp);
786 if(it!=fi.domain.end()) dom.insert(it->second.begin(), it->second.end());
788 std::vector<pg_uuid_t> ch;
792 sub.reserve(s.size());
793 for(std::size_t di=0; di<s.size(); ++di) {
795 for(
const MAtom &at : s[di]) {
797 for(
auto &t : na.args)
798 if(t.isVar && classVars[di].count(t.v)) { t.isVar=
false; t.v=a; }
799 nd.push_back(std::move(na));
801 sub.push_back(std::move(nd));
803 ch.push_back(compile(sub,
false));
805 result = mkBool(
false, ch);
814 throw MobiusDecline(
"Möbius: non-hierarchical single CQ (no separator); "
815 "the query is #P-hard");
816 result = mobiusStep(s, top);
825int checkedLen(ArrayType *a,
const char *what) {
826 if(a==NULL)
return 0;
827 if(ARR_NDIM(a)>1)
provsql_error(
"ucq_mobius: %s must be 1-D", what);
828 if(ARR_HASNULL(a))
provsql_error(
"ucq_mobius: %s must not contain NULLs", what);
829 return ARR_NDIM(a)==0 ? 0 : ARR_DIMS(a)[0];
831const int32 *intArr(FunctionCallInfo fcinfo,
int n,
const char *what,
int &len){
832 ArrayType *a = PG_ARGISNULL(n)?NULL:PG_GETARG_ARRAYTYPE_P(n);
833 len = checkedLen(a, what);
834 return (a==NULL||len==0)?NULL:(
const int32*)ARR_DATA_PTR(a);
841Sentence buildSentenceArrays(
const int32 *d_nvars,
int n_disj,
842 const int32 *a_disj,
const int32 *a_rel,
843 const int32 *a_vars,
int n_av,
844 const int32 *a_arity,
int n_ad,
845 std::vector<long> &base)
847 if(n_disj==0)
provsql_error(
"ucq_mobius: the UCQ has no disjuncts");
848 base.assign(n_disj, 0);
850 for(
int d=0; d<n_disj; ++d){ base[d]=acc; acc += d_nvars[d] + 1; }
854 for(
int i=0;i<n_ad;++i){
855 const int d=a_disj[i];
856 if(d<0||d>=n_disj)
provsql_error(
"ucq_mobius: atom disjunct out of range");
857 const int ar=a_arity[i];
858 if(ar<0||voff+ar>n_av)
860 MAtom at; at.rel=
static_cast<unsigned>(a_rel[i]);
861 for(
int k=0;k<ar;++k){
862 Term t; t.isVar=
true; t.v = base[d] + a_vars[voff+k];
863 at.args.push_back(t);
866 s[d].push_back(std::move(at));
873FactIndex buildFactIndexArrays(
const int32 *f_rel,
int n_fr,
874 const int32 *f_elems,
int n_fe,
875 const int32 *f_arity,
876 const pg_uuid_t *tok)
891 std::map<std::string, std::pair<unsigned, std::vector<long>>> token_owner;
893 for(
int i=0;i<n_fr;++i){
894 const int ar=f_arity[i];
895 if(ar<0||eoff+ar>n_fe)
896 provsql_error(
"ucq_mobius: fact_elems shorter than arities");
897 unsigned rel=
static_cast<unsigned>(f_rel[i]);
898 std::vector<long> el;
899 for(
int k=0;k<ar;++k){
900 long e=
static_cast<long>(f_elems[eoff+k]);
902 fi.domain[{rel,k}].insert(e);
905 fi.present.insert({rel, el});
907 for(
int b=0;b<16;++b)
if(tok[i].data[b]!=0) nil=
false;
909 pg_uuid_t tk = tok[i];
910 const Datum gt = DirectFunctionCall1(
get_gate_type, UUIDPGetDatum(&tk));
911 if(
static_cast<Oid
>(DatumGetInt32(gt)) != input_oid)
912 throw MobiusDecline(
"non-TID input: a fact token is not a bare "
913 "gate_input (correlated / derived lineage)");
924 const std::string newtok = nil ? std::string() :
uuid2string(tok[i]);
925 auto existing = fi.tok.find({rel, el});
926 if(existing != fi.tok.end() && existing->second != newtok)
927 throw MobiusDecline(
"bag multiplicity: two distinct tuples share an "
928 "element tuple (non-reduced data) -- defer to "
929 "joint-width, which keeps the multiplicity");
935 if(!newtok.empty()) {
936 auto own = token_owner.find(newtok);
937 if(own != token_owner.end() && own->second != std::make_pair(rel, el))
938 throw MobiusDecline(
"self-join overlap: one tuple feeds two fact slots "
939 "(non-disjoint self-join) -- defer to joint-width");
940 token_owner[newtok] = {rel, el};
942 fi.tok[{rel, el}] = newtok;
948Sentence decodeQuery(FunctionCallInfo fcinfo)
950 int n_disj,n_ad,n_ar,n_av,n_aa;
951 const int32 *d_nvars = intArr(fcinfo,0,
"disjunct_nvars",n_disj);
952 const int32 *a_disj = intArr(fcinfo,1,
"atom_disjunct",n_ad);
953 const int32 *a_rel = intArr(fcinfo,2,
"atom_rel",n_ar);
954 const int32 *a_vars = intArr(fcinfo,3,
"atom_vars",n_av);
955 const int32 *a_arity = intArr(fcinfo,4,
"atom_arity",n_aa);
956 if(n_ad!=n_ar || n_ad!=n_aa)
957 provsql_error(
"ucq_mobius: atom arrays must have the same length");
958 std::vector<long> base;
959 return buildSentenceArrays(d_nvars,n_disj,a_disj,a_rel,a_vars,n_av,
964FactIndex decodeFacts(FunctionCallInfo fcinfo)
966 int n_fr,n_fe,n_fa,n_ft;
967 const int32 *f_rel = intArr(fcinfo,5,
"fact_rel",n_fr);
968 const int32 *f_elems = intArr(fcinfo,6,
"fact_elems",n_fe);
969 const int32 *f_arity = intArr(fcinfo,7,
"fact_arity",n_fa);
970 ArrayType *toks = PG_ARGISNULL(8)?NULL:PG_GETARG_ARRAYTYPE_P(8);
971 n_ft = checkedLen(toks,
"fact_tokens");
972 if(n_fr!=n_fa || n_fr!=n_ft)
973 provsql_error(
"ucq_mobius: fact arrays must have the same length");
974 const pg_uuid_t *tok = (toks&&n_ft)?(
const pg_uuid_t*)ARR_DATA_PTR(toks):NULL;
975 return buildFactIndexArrays(f_rel,n_fr,f_elems,n_fe,f_arity,tok);
988struct MobAnswerCache {
991 std::vector<long> base;
992 std::vector<int> d_nvars;
994 std::map<std::string,long> val_to_id;
995 std::map<std::string,std::string> tokcache;
998void mobAnswerCacheDelete(
void *arg) {
delete reinterpret_cast<MobAnswerCache*
>(arg); }
1000std::string mobHeadKey(
const std::vector<std::string> &vals)
1003 for(
const auto &v : vals){ k += v; k.push_back(
'\x1f'); }
1009bool mobGather(Datum descriptor, MobAnswerCache *c)
1012 Oid argt[1] = { JSONBOID };
1013 Datum argv[1] = { descriptor };
1014 char argn[1] = {
' ' };
1015 const int rc = SPI_execute_with_args(
1016 "SELECT * FROM provsql.ucq_joint_gather($1)", 1, argt, argv, argn,
true, 1);
1017 if(rc != SPI_OK_SELECT || SPI_processed != 1) { SPI_finish();
return false; }
1021 TupleDesc td = SPI_tuptable->tupdesc;
1022 HeapTuple row = SPI_tuptable->vals[0];
1024 auto ia = [&](
int col,
int &n)->
const int32*{
1025 Datum d = SPI_getbinval(row, td, col, &isnull);
1026 if(isnull){ n=0;
return nullptr; }
1027 ArrayType *a = DatumGetArrayTypeP(d);
1028 n = ArrayGetNItems(ARR_NDIM(a), ARR_DIMS(a));
1029 return (
const int32*) ARR_DATA_PTR(a);
1031 int n_dnv,n_adisj,n_arel,n_avars,n_aarity,n_frel,n_felems,n_farity;
1032 const int32 *dnv = ia(1,n_dnv);
1033 const int32 *adisj = ia(2,n_adisj);
1034 const int32 *arel = ia(3,n_arel);
1035 const int32 *avars = ia(4,n_avars);
1036 const int32 *aarity = ia(5,n_aarity);
1037 const int32 *frel = ia(6,n_frel);
1038 const int32 *felems = ia(7,n_felems);
1039 const int32 *farity = ia(8,n_farity);
1040 Datum dtok = SPI_getbinval(row, td, 9, &isnull);
1041 ArrayType *atok = isnull ? nullptr : DatumGetArrayTypeP(dtok);
1042 const int n_ftok = atok ? ArrayGetNItems(ARR_NDIM(atok), ARR_DIMS(atok)) : 0;
1043 const pg_uuid_t *ftok = atok ? (
const pg_uuid_t*) ARR_DATA_PTR(atok) : nullptr;
1044 if(n_frel != n_farity || n_frel != n_ftok)
1045 throw MobiusDecline(
"ucq_mobius: fact arrays length mismatch");
1047 c->sentence = buildSentenceArrays(dnv,n_dnv,adisj,arel,avars,n_avars,
1048 aarity,n_adisj,c->base);
1049 c->d_nvars.assign(dnv, dnv+n_dnv);
1050 c->fi = buildFactIndexArrays(frel,n_frel,felems,n_felems,farity,ftok);
1053 Datum dval = SPI_getbinval(row, td, 10, &isnull);
1055 ArrayType *aval = DatumGetArrayTypeP(dval);
1056 Datum *elems;
bool *nulls;
int nval;
1057 deconstruct_array(aval, TEXTOID, -1,
false,
TYPALIGN_INT,
1058 &elems, &nulls, &nval);
1059 for(
int i=0;i<nval;++i)
1061 c->val_to_id[TextDatumGetCString(elems[i])] = i;
1092 Sentence s = decodeQuery(fcinfo);
1093 FactIndex fi = decodeFacts(fcinfo);
1094 std::string lineage;
1095 if(!PG_ARGISNULL(9))
1098 MobiusCompiler mc(fi, st);
1099 pg_uuid_t root = mc.compileTop(s, lineage);
1102 PG_RETURN_UUID_P(u);
1103 }
catch(
const MobiusDecline &e) {
1105 }
catch(
const std::exception &e) {
1121 Sentence s = decodeQuery(fcinfo);
1122 FactIndex fi = decodeFacts(fcinfo);
1124 MobiusCompiler mc(fi, st);
1129 if(get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
1130 provsql_error(
"ucq_mobius_compile_stats: expected composite return type");
1131 tupdesc = BlessTupleDesc(tupdesc);
1133 bool nulls[9] = {
false,
false,
false,
false,
false,
false,
false,
false,
false};
1134 values[0] = Float8GetDatum(st.probability);
1135 values[1] = Int32GetDatum(st.n_components);
1136 values[2] = Int32GetDatum(st.n_cnf_conjuncts);
1137 values[3] = Int32GetDatum(st.lattice_collapsed);
1138 values[4] = Int32GetDatum(st.n_nonzero);
1139 values[5] = Int32GetDatum(st.n_cancelled);
1140 values[6] = BoolGetDatum(st.cancelled_hard);
1141 values[7] = Int64GetDatum(
static_cast<int64
>(st.dd_size));
1142 values[8] = Int64GetDatum(
static_cast<int64
>(st.memo_hits));
1143 PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
1144 }
catch(
const std::exception &e) {
1147 provsql_error(
"ucq_mobius_compile_stats: unknown exception");
1166 MobAnswerCache *
cache =
1167 reinterpret_cast<MobAnswerCache*
>(fcinfo->flinfo->fn_extra);
1169 if(
cache ==
nullptr) {
1170 MemoryContext fnctx = fcinfo->flinfo->fn_mcxt;
1171 cache =
new MobAnswerCache();
1172 MemoryContextCallback *cb = (MemoryContextCallback*)
1173 MemoryContextAllocZero(fnctx,
sizeof(MemoryContextCallback));
1174 cb->func = mobAnswerCacheDelete;
1176 MemoryContextRegisterResetCallback(fnctx, cb);
1177 fcinfo->flinfo->fn_extra =
cache;
1179 if(!PG_ARGISNULL(0)) {
1181 MemoryContext oldcxt = CurrentMemoryContext;
1182 ResourceOwner oldowner = CurrentResourceOwner;
1183 BeginInternalSubTransaction(NULL);
1186 cache->ready = mobGather(PG_GETARG_DATUM(0),
cache);
1187 ReleaseCurrentSubTransaction();
1188 MemoryContextSwitchTo(oldcxt);
1189 CurrentResourceOwner = oldowner;
1193 MemoryContextSwitchTo(oldcxt);
1194 RollbackAndReleaseCurrentSubTransaction();
1195 MemoryContextSwitchTo(oldcxt);
1196 CurrentResourceOwner = oldowner;
1198 cache->ready =
false;
1205 if(
cache->ready && !PG_ARGISNULL(1) && !PG_ARGISNULL(2)) {
1206 std::vector<int> head_vars;
1208 ArrayType *a = PG_GETARG_ARRAYTYPE_P(1);
1209 const int32 *d = (
const int32*) ARR_DATA_PTR(a);
1210 const int n = ArrayGetNItems(ARR_NDIM(a), ARR_DIMS(a));
1211 for(
int i=0;i<n;++i) head_vars.push_back(d[i]);
1213 std::vector<std::string> head_vals;
1215 ArrayType *a = PG_GETARG_ARRAYTYPE_P(2);
1216 Datum *elems;
bool *nulls;
int n;
1217 deconstruct_array(a, TEXTOID, -1,
false,
TYPALIGN_INT, &elems, &nulls, &n);
1218 for(
int i=0;i<n;++i)
1219 head_vals.push_back(nulls[i] ? std::string() : TextDatumGetCString(elems[i]));
1222 if(head_vars.size() == head_vals.size()) {
1223 const std::string key = mobHeadKey(head_vals);
1224 auto it =
cache->tokcache.find(key);
1225 if(it !=
cache->tokcache.end()) {
1228 PG_RETURN_UUID_P(u);
1233 Sentence s =
cache->sentence;
1234 bool resolved =
true;
1235 for(std::size_t h=0; h<head_vars.size() && resolved; ++h) {
1236 auto vit =
cache->val_to_id.find(head_vals[h]);
1237 if(vit ==
cache->val_to_id.end()) { resolved =
false;
break; }
1238 const long val = vit->second;
1239 const int hv = head_vars[h];
1240 for(std::size_t d=0; d<s.size(); ++d) {
1241 const long gid =
cache->base[d] + hv;
1242 for(MAtom &at : s[d])
1243 for(Term &t : at.args)
1244 if(t.isVar && t.v == gid) { t.isVar=
false; t.v=val; }
1251 std::string lineage;
1252 if(!PG_ARGISNULL(3))
1255 MobiusCompiler mc(
cache->fi, st);
1256 pg_uuid_t root = mc.compileTop(s, lineage);
1260 PG_RETURN_UUID_P(u);
1270 PG_RETURN_DATUM(PG_GETARG_DATUM(3));
pg_uuid_t provsqlUuidV5(const std::string &name)
RFC 4122 version-5 UUID in the ProvSQL namespace.
Content-addressed materialisation of a certified d-D into the mmap provenance store.
static CircuitCache cache
Process-local singleton circuit gate cache.
Fix macro conflicts between PostgreSQL headers and the C++ STL/Boost.
PostgreSQL cross-version compatibility shims for ProvSQL.
#define TYPALIGN_INT
Alignment codes for the array routines (construct_array / deconstruct_array).
Datum ucq_mobius_materialize_tracked(PG_FUNCTION_ARGS)
Materialise the safe-UCQ Möbius circuit and return its root token.
Datum ucq_mobius_provenance_answer(PG_FUNCTION_ARGS)
Per-answer Möbius provenance (the planner-substituted entry point for a non-Boolean UCQ with free hea...
Datum get_gate_type(PG_FUNCTION_ARGS)
PostgreSQL-callable wrapper for get_gate_type().
int provsql_mobius_max_gates
Data-cost cap of the Möbius route: it declines (falling through to joint-width / the ladder) once its...
Datum ucq_mobius_compile_stats(PG_FUNCTION_ARGS)
Compile the Möbius circuit and return the lattice statistics plus the probability (the demonstrabilit...
Shared declaration for the Möbius-route probability sweep.
double mobius_probability_of(pg_uuid_t token)
Probability of a Möbius-route token (a gate_mobius-rooted circuit, or any Boolean island beneath one)...
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
void provsql_internal_set_extra(const pg_uuid_t *token, const char *str)
Internal entry point behind set_extra(): worker IPC only.
void provsql_internal_create_gate(const pg_uuid_t *token, gate_type type, unsigned nb_children, const pg_uuid_t *children_data)
Internal entry point behind create_gate(): cache + worker IPC.
Background worker and IPC primitives for mmap-backed circuit storage.
constants_t get_constants(bool failure_if_not_possible)
Retrieve the cached OID constants for the current database.
Core types, constants, and utilities shared across ProvSQL.
@ gate_mobius
Signed Möbius combination: a MEASURE-only gate carrying one integer coefficient per child (in extra,...
pg_uuid_t string2uuid(const string &source)
Parse a UUID string into a pg_uuid_t.
string uuid2string(pg_uuid_t uuid)
Format a pg_uuid_t as a std::string.
bool operator==(const pg_uuid_t &u, const pg_uuid_t &v)
Test two pg_uuid_t values for equality.
C++ utility functions for UUID manipulation.
Oid GATE_TYPE_TO_OID[nb_gate_types]
Array of the OID of each provenance_gate ENUM value.
Oid OID_FUNCTION_PROVENANCE_PLUS
OID of the provenance_plus FUNCTION.
Oid OID_FUNCTION_PROVENANCE_TIMES
OID of the provenance_times FUNCTION.