1use crate::encoding::rdf_type_id;
10use crate::error::Result;
11use crate::sql::{col, expect_len, Capabilities, Request, Response, Statement};
12use std::collections::{BTreeSet, HashMap};
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct PredicateStats {
18 pub triples: f64,
19 pub distinct_subjects: f64,
20 pub distinct_objects: f64,
21}
22
23#[derive(Debug, Clone, Default)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct Stats {
27 pub available: bool,
29 pub total: f64,
30 pub predicates: HashMap<i64, PredicateStats>,
31 pub classes: HashMap<i64, f64>,
33 pub pairs: HashMap<(i64, i64), f64>,
36 pub graph_index: bool,
37 pub text_index: bool,
39 pub transitive: BTreeSet<i64>,
41 pub version: crate::version::VersionState,
43}
44
45fn id_col(caps: &Capabilities, c: &str) -> String {
46 if caps.int64_as_text {
47 format!("CAST({c} AS TEXT)")
48 } else {
49 c.into()
50 }
51}
52
53impl Stats {
54 pub fn load_request(caps: &Capabilities) -> Request {
56 Request::read(vec![
57 Statement::new("SELECT key, value FROM oxilite_meta"),
58 Statement::new(format!(
59 "SELECT {}, triples, distinct_s, distinct_o FROM stats_pred",
60 id_col(caps, "p")
61 )),
62 Statement::new(format!(
63 "SELECT {}, instances FROM stats_class",
64 id_col(caps, "o")
65 )),
66 crate::reason::transitive_statement(|c| id_col(caps, c)),
67 Statement::new(format!(
68 "SELECT {}, {}, n FROM stats_po",
69 id_col(caps, "p"),
70 id_col(caps, "o")
71 )),
72 ])
73 }
74
75 pub fn from_response(response: &Response) -> Result<Self> {
76 expect_len(response, 5)?;
77 let mut stats = Self::default();
78 for row in &response[0].rows {
79 let key = col(row, 0)?.as_str().unwrap_or_default();
80 let value = col(row, 1)?.clone().into_string().unwrap_or_default();
81 match key {
82 "graph_index" => stats.graph_index = value == "1",
83 "text_index" => stats.text_index = value == "1",
84 k @ ("versioning" | "history" | "stamp_column" | "stamp_index" | "as_of_index") => {
85 stats.version.absorb(k, &value)
86 }
87 "total" => {
88 stats.total = value.parse().unwrap_or(0.0);
89 stats.available = true;
90 }
91 _ => {}
92 }
93 }
94 for row in &response[1].rows {
95 let (Some(p), Some(t), Some(ds), Some(d_o)) = (
96 col(row, 0)?.as_i64(),
97 col(row, 1)?.as_f64(),
98 col(row, 2)?.as_f64(),
99 col(row, 3)?.as_f64(),
100 ) else {
101 continue;
102 };
103 stats.predicates.insert(
104 p,
105 PredicateStats {
106 triples: t,
107 distinct_subjects: ds.max(1.0),
108 distinct_objects: d_o.max(1.0),
109 },
110 );
111 }
112 for row in &response[2].rows {
113 if let (Some(o), Some(n)) = (col(row, 0)?.as_i64(), col(row, 1)?.as_f64()) {
114 stats.classes.insert(o, n);
115 }
116 }
117 for row in &response[3].rows {
118 if let Some(p) = col(row, 0)?.as_i64() {
119 stats.transitive.insert(p);
120 }
121 }
122 for row in &response[4].rows {
123 if let (Some(p), Some(o), Some(n)) = (
124 col(row, 0)?.as_i64(),
125 col(row, 1)?.as_i64(),
126 col(row, 2)?.as_f64(),
127 ) {
128 stats.pairs.insert((p, o), n);
129 }
130 }
131 Ok(stats)
132 }
133
134 pub fn refresh_request() -> Request {
137 let mut r = Self::refresh_statements();
138 r.extend(crate::reason::closure_statements());
139 r.extend(crate::shapes::refresh_statements());
140 Request::atomic(r)
141 }
142
143 fn refresh_statements() -> Vec<Statement> {
144 vec![
145 "DELETE FROM stats_pred".into(),
146 "INSERT INTO stats_pred(p, triples, distinct_s, distinct_o) \
147 SELECT p, COUNT(*), COUNT(DISTINCT s), COUNT(DISTINCT o) FROM quads GROUP BY p"
148 .into(),
149 "DELETE FROM stats_class".into(),
150 Statement::new(format!(
151 "INSERT INTO stats_class(o, instances) SELECT o, COUNT(*) FROM quads WHERE p = {} GROUP BY o",
152 rdf_type_id()
153 )),
154 "INSERT OR REPLACE INTO oxilite_meta(key, value) SELECT 'total', CAST(COUNT(*) AS TEXT) FROM quads"
155 .into(),
156 "DELETE FROM stats_po".into(),
157 Statement::new(format!(
158 "INSERT INTO stats_po(p, o, n) SELECT q.p, q.o, COUNT(*) FROM quads q JOIN stats_pred sp ON sp.p = q.p \
159 WHERE sp.distinct_o <= 1024 AND q.p <> {} GROUP BY q.p, q.o, sp.triples, sp.distinct_o \
160 HAVING COUNT(*) >= 4.0 * sp.triples / sp.distinct_o ORDER BY COUNT(*) DESC LIMIT 2000",
161 rdf_type_id()
162 )),
163 ]
164 }
165}