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 schema_scopes: BTreeSet<i64>,
43 pub version: crate::version::VersionState,
45 #[cfg_attr(feature = "serde", serde(skip))]
47 pub vector_indexes: Vec<crate::vector::VectorIndex>,
48 #[cfg_attr(feature = "serde", serde(skip))]
50 pub vector_problems: Vec<String>,
51 #[cfg_attr(feature = "serde", serde(skip))]
53 pub vector_built: std::collections::BTreeMap<String, String>,
54}
55
56fn id_col(caps: &Capabilities, c: &str) -> String {
57 if caps.int64_as_text {
58 format!("CAST({c} AS TEXT)")
59 } else {
60 c.into()
61 }
62}
63
64impl Stats {
65 pub fn load_request(caps: &Capabilities) -> Request {
67 let mut r = Request::read(vec![
68 Statement::new("SELECT key, value FROM oxilite_meta"),
69 Statement::new(format!(
70 "SELECT {}, triples, distinct_s, distinct_o FROM stats_pred",
71 id_col(caps, "p")
72 )),
73 Statement::new(format!(
74 "SELECT {}, instances FROM stats_class",
75 id_col(caps, "o")
76 )),
77 crate::reason::transitive_statement(|c| id_col(caps, c)),
78 Statement::new(format!(
79 "SELECT {}, {}, n FROM stats_po",
80 id_col(caps, "p"),
81 id_col(caps, "o")
82 )),
83 crate::registry::scopes_statement(|c| id_col(caps, c)),
84 ]);
85 if caps.vectors {
88 r.statements
89 .push(crate::vector::definitions_statement(|c| id_col(caps, c)));
90 }
91 r
92 }
93
94 pub fn from_response(response: &Response) -> Result<Self> {
95 if response.len() != 7 {
96 expect_len(response, 6)?;
97 }
98 let mut stats = Self::default();
99 for row in &response[0].rows {
100 let key = col(row, 0)?.as_str().unwrap_or_default();
101 let value = col(row, 1)?.clone().into_string().unwrap_or_default();
102 if let Some(name) = key.strip_prefix("vector:") {
103 stats.vector_built.insert(name.to_owned(), value);
104 continue;
105 }
106 match key {
107 "graph_index" => stats.graph_index = value == "1",
108 "text_index" => stats.text_index = value == "1",
109 k @ ("versioning" | "history" | "stamp_column" | "stamp_index" | "as_of_index") => {
110 stats.version.absorb(k, &value)
111 }
112 "total" => {
113 stats.total = value.parse().unwrap_or(0.0);
114 stats.available = true;
115 }
116 _ => {}
117 }
118 }
119 for row in &response[1].rows {
120 let (Some(p), Some(t), Some(ds), Some(d_o)) = (
121 col(row, 0)?.as_i64(),
122 col(row, 1)?.as_f64(),
123 col(row, 2)?.as_f64(),
124 col(row, 3)?.as_f64(),
125 ) else {
126 continue;
127 };
128 stats.predicates.insert(
129 p,
130 PredicateStats {
131 triples: t,
132 distinct_subjects: ds.max(1.0),
133 distinct_objects: d_o.max(1.0),
134 },
135 );
136 }
137 for row in &response[2].rows {
138 if let (Some(o), Some(n)) = (col(row, 0)?.as_i64(), col(row, 1)?.as_f64()) {
139 stats.classes.insert(o, n);
140 }
141 }
142 for row in &response[3].rows {
143 if let Some(p) = col(row, 0)?.as_i64() {
144 stats.transitive.insert(p);
145 }
146 }
147 for row in &response[5].rows {
148 if let Some(g) = col(row, 0)?.as_i64() {
149 stats.schema_scopes.insert(g);
150 }
151 }
152 for row in &response[4].rows {
153 if let (Some(p), Some(o), Some(n)) = (
154 col(row, 0)?.as_i64(),
155 col(row, 1)?.as_i64(),
156 col(row, 2)?.as_f64(),
157 ) {
158 stats.pairs.insert((p, o), n);
159 }
160 }
161 if let Some(defs) = response.get(6) {
162 let (indexes, problems) = crate::vector::definitions_from_rows(&defs.rows);
163 stats.vector_indexes = indexes;
164 stats.vector_problems = problems;
165 }
166 Ok(stats)
167 }
168
169 pub fn refresh_request() -> Request {
172 let mut r = Self::refresh_statements();
173 r.extend(crate::reason::closure_statements());
174 r.extend(crate::shapes::refresh_statements());
175 Request::atomic(r)
176 }
177
178 fn refresh_statements() -> Vec<Statement> {
179 vec![
180 "DELETE FROM stats_pred".into(),
181 "INSERT INTO stats_pred(p, triples, distinct_s, distinct_o) \
182 SELECT p, COUNT(*), COUNT(DISTINCT s), COUNT(DISTINCT o) FROM quads GROUP BY p"
183 .into(),
184 "DELETE FROM stats_class".into(),
185 Statement::new(format!(
186 "INSERT INTO stats_class(o, instances) SELECT o, COUNT(*) FROM quads WHERE p = {} GROUP BY o",
187 rdf_type_id()
188 )),
189 "INSERT OR REPLACE INTO oxilite_meta(key, value) SELECT 'total', CAST(COUNT(*) AS TEXT) FROM quads"
190 .into(),
191 "DELETE FROM stats_po".into(),
192 Statement::new(format!(
193 "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 \
194 WHERE sp.distinct_o <= 1024 AND q.p <> {} GROUP BY q.p, q.o, sp.triples, sp.distinct_o \
195 HAVING COUNT(*) >= 4.0 * sp.triples / sp.distinct_o ORDER BY COUNT(*) DESC LIMIT 2000",
196 rdf_type_id()
197 )),
198 ]
199 }
200}