///|
/// Static query structure for editors, local source catalogues and capability
/// checks. Counts describe syntax, independently of data and result cardinality.
pub struct QueryProfile {
priv form : String
priv variables : Array[String]
priv sources : Array[(Term, Bool)]
priv mut subqueries : Int32
priv mut existence_checks : Int32
priv mut property_paths : Int32
priv mut aggregates : Int32
}
///|
/// Pairs contain an IRI and whether its clause is FROM NAMED.
pub fn PreparedQuery::dataset_sources(
self : PreparedQuery,
) -> Array[(Term, Bool)] {
self.query.datasets.copy()
}
///|
fn QueryProfile::expression(self : QueryProfile, e : Expr) -> Unit {
match e {
Unary(_, item) => self.expression(item)
Binary(_, left, right) => {
self.expression(left)
self.expression(right)
}
Call(_, args) =>
for item in args {
self.expression(item)
}
Aggregate(_, value, _, _) => {
self.aggregates += 1
if value is Some(item) {
self.expression(item)
}
}
Exists(group, _) => {
self.existence_checks += 1
self.group(group)
}
_ => ()
}
}
///|
fn QueryProfile::group(self : QueryProfile, group : Group) -> Unit {
for pattern in group.patterns {
match pattern {
Triple(_, Predicate(_) | PathVar(_), _) => ()
Triple(_, _, _) => self.property_paths += 1
Filter(e) | Bind(e, _) => self.expression(e)
Nested(g) | Optional(g) | Graph(_, g) | Minus(g) => self.group(g)
Union(groups) =>
for g in groups {
self.group(g)
}
Subselect(q) => {
self.subqueries += 1
self.query(q)
}
Values(_, _) => ()
}
}
}
///|
fn QueryProfile::query(self : QueryProfile, q : Query) -> Unit {
self.group(q.group)
for p in q.projections {
self.expression(p.expression)
}
for (e, _) in q.group_by {
self.expression(e)
}
for e in q.having {
self.expression(e)
}
for (e, _) in q.order {
self.expression(e)
}
}
///|
pub fn PreparedQuery::profile(self : PreparedQuery) -> QueryProfile {
let result = QueryProfile::{
form: self.form(),
variables: solution_variables(self.query),
sources: self.dataset_sources(),
subqueries: 0,
existence_checks: 0,
property_paths: 0,
aggregates: 0,
}
result.query(self.query)
result
}
///|
pub fn QueryProfile::to_json(self : QueryProfile) -> String {
Json::object({
"version": Json::number(1.0),
"form": Json::string(self.form),
"variables": Json::array(self.variables.map(Json::string)),
"datasetSources": Json::array(
self.sources.map(entry => {
Json::object({
"iri": Json::string(entry.0.lexical),
"named": Json::boolean(entry.1),
})
}),
),
"subqueries": Json::number(self.subqueries.to_double()),
"existenceChecks": Json::number(self.existence_checks.to_double()),
"propertyPaths": Json::number(self.property_paths.to_double()),
"aggregates": Json::number(self.aggregates.to_double()),
}).stringify()
}