use super::*;
#[derive(Debug, Clone, Serialize)]
pub struct QueryStats {
pub execution_time: Duration,
pub result_count: usize,
pub query_type: String,
pub success: bool,
pub error_message: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RdfSerializationFormat {
Turtle,
NTriples,
RdfXml,
JsonLd,
NQuads,
}
#[derive(Debug, Clone, Serialize)]
pub struct StoreChange {
pub id: u64,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub operation_type: String,
pub affected_graphs: Vec<String>,
pub triple_count: usize,
pub dataset_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ChangeDetectionParams {
pub since: chrono::DateTime<chrono::Utc>,
pub graphs: Option<Vec<String>>,
pub operation_types: Option<Vec<String>>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Default)]
pub(super) struct StoreMetadata {
pub(super) created_at: Option<Instant>,
pub(super) last_modified: Option<Instant>,
pub(super) total_queries: u64,
pub(super) total_updates: u64,
pub(super) query_cache_hits: u64,
pub(super) query_cache_misses: u64,
pub(super) change_log: Vec<StoreChange>,
pub(super) last_change_id: u64,
}
#[derive(Debug)]
pub struct QueryResult {
pub inner: CoreQueryResult,
pub stats: QueryStats,
}
impl QueryResult {
pub fn to_json(&self) -> FusekiResult<String> {
match &self.inner {
CoreQueryResult::Select {
variables,
bindings,
} => {
let json_result = serde_json::json!(
{ "head" : { "vars" : variables }, "results" : { "bindings" :
bindings.iter().map(| binding | { binding.iter().map(| (k, v) | { (k
.clone(), serde_json::json!({ "type" : match v { Term::NamedNode(_)
=> "uri", Term::BlankNode(_) => "bnode", Term::Literal(_) =>
"literal", Term::Variable(_) => "variable", Term::QuotedTriple(_) =>
"quotedTriple", }, "value" : v.to_string() })) }).collect::<
serde_json::Map < String, serde_json::Value >> () }).collect::< Vec <
_ >> () } }
);
Ok(json_result.to_string())
}
CoreQueryResult::Ask(result) => {
let json_result = serde_json::json!({ "head" : {}, "boolean" : result });
Ok(json_result.to_string())
}
CoreQueryResult::Construct(_) => Err(FusekiError::unsupported_media_type(
"CONSTRUCT queries should use RDF format, not JSON",
)),
}
}
pub fn to_xml(&self) -> FusekiResult<String> {
match &self.inner {
CoreQueryResult::Select {
variables,
bindings,
} => {
let mut xml = String::from(
"<?xml version=\"1.0\"?>\n<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\">\n",
);
xml.push_str(" <head>\n");
for var in variables {
xml.push_str(&format!(" <variable name=\"{var}\"/>\n"));
}
xml.push_str(" </head>\n <results>\n");
for binding in bindings {
xml.push_str(" <result>\n");
for (var, term) in binding {
xml.push_str(&format!(" <binding name=\"{var}\">\n"));
match term {
Term::NamedNode(node) => {
xml.push_str(&format!(" <uri>{}</uri>\n", node.as_str()));
}
Term::BlankNode(node) => {
xml.push_str(&format!(
" <bnode>{}</bnode>\n",
node.as_str()
));
}
Term::Literal(literal) => {
xml.push_str(&format!(
" <literal>{}</literal>\n",
literal.value()
));
}
Term::Variable(variable) => {
xml.push_str(&format!(
" <variable>{}</variable>\n",
variable.as_str()
));
}
Term::QuotedTriple(triple) => {
xml.push_str(
&format!(
" <quotedTriple><<{} {} {}>></quotedTriple>\n",
triple.subject(), triple.predicate(), triple.object()
),
);
}
}
xml.push_str(" </binding>\n");
}
xml.push_str(" </result>\n");
}
xml.push_str(" </results>\n</sparql>");
Ok(xml)
}
CoreQueryResult::Ask(result) => {
let xml = format!(
"<?xml version=\"1.0\"?>\n<sparql xmlns=\"http://www.w3.org/2005/sparql-results#\">\n <head/>\n <boolean>{result}</boolean>\n</sparql>"
);
Ok(xml)
}
_ => Err(FusekiError::unsupported_media_type(
"XML format only supported for SELECT and ASK queries",
)),
}
}
pub fn to_csv(&self) -> FusekiResult<String> {
match &self.inner {
CoreQueryResult::Select {
variables,
bindings,
} => {
let mut csv = variables.join(",");
csv.push('\n');
for binding in bindings {
let values: Vec<String> = variables
.iter()
.map(|var| {
binding
.get(var)
.map(|term| term.to_string())
.unwrap_or_default()
})
.collect();
csv.push_str(&values.join(","));
csv.push('\n');
}
Ok(csv)
}
_ => Err(FusekiError::unsupported_media_type(
"CSV format only supported for SELECT queries",
)),
}
}
pub fn to_tsv(&self) -> FusekiResult<String> {
match &self.inner {
CoreQueryResult::Select {
variables,
bindings,
} => {
let mut tsv = variables.join("\t");
tsv.push('\n');
for binding in bindings {
let values: Vec<String> = variables
.iter()
.map(|var| {
binding
.get(var)
.map(|term| term.to_string())
.unwrap_or_default()
})
.collect();
tsv.push_str(&values.join("\t"));
tsv.push('\n');
}
Ok(tsv)
}
_ => Err(FusekiError::unsupported_media_type(
"TSV format only supported for SELECT queries",
)),
}
}
pub fn to_rdf(&self, format: RdfSerializationFormat) -> FusekiResult<String> {
match &self.inner {
CoreQueryResult::Construct(triples) => {
let core_format = match format {
RdfSerializationFormat::Turtle => CoreRdfFormat::Turtle,
RdfSerializationFormat::NTriples => CoreRdfFormat::NTriples,
RdfSerializationFormat::RdfXml => CoreRdfFormat::RdfXml,
RdfSerializationFormat::JsonLd => {
return Err(FusekiError::unsupported_media_type(
"JSON-LD not supported yet",
));
}
RdfSerializationFormat::NQuads => CoreRdfFormat::NQuads,
};
let serializer = Serializer::new(core_format);
let graph = oxirs_core::model::graph::Graph::from_iter(triples.clone());
serializer.serialize_graph(&graph).map_err(|e| {
FusekiError::parse(format!("Failed to serialize CONSTRUCT result: {e}"))
})
}
_ => Err(FusekiError::unsupported_media_type(
"RDF format only supported for CONSTRUCT and DESCRIBE queries",
)),
}
}
pub fn format_as(&self, format: ResultFormat) -> FusekiResult<String> {
match format {
ResultFormat::Json => self.to_json(),
ResultFormat::Xml => self.to_xml(),
ResultFormat::Csv => self.to_csv(),
ResultFormat::Tsv => self.to_tsv(),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct UpdateStats {
pub execution_time: Duration,
pub quads_inserted: usize,
pub quads_deleted: usize,
pub operation_type: String,
pub success: bool,
pub error_message: Option<String>,
}
#[derive(Debug)]
pub struct UpdateResult {
pub stats: UpdateStats,
}
#[derive(Debug, Serialize)]
pub struct StoreStats {
pub triple_count: usize,
pub dataset_count: usize,
pub total_queries: u64,
pub total_updates: u64,
pub cache_hit_ratio: f64,
pub uptime_seconds: u64,
pub change_log_size: usize,
pub latest_change_id: u64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ResultFormat {
Json,
Xml,
Csv,
Tsv,
}