use super::*;
pub(super) const CHECKPOINT_COLUMNS: [&str; 2] = ["success", "message"];
#[derive(Debug, PartialEq, Eq)]
pub(super) struct CheckpointCall {
pub(super) columns: Vec<&'static str>,
}
pub(super) fn strip_keyword_ci<'a>(input: &'a str, keyword: &str) -> Option<&'a str> {
if !input.get(..keyword.len())?.eq_ignore_ascii_case(keyword) {
return None;
}
let rest = &input[keyword.len()..];
match rest.chars().next() {
Some(c) if c.is_alphanumeric() || c == '_' => None,
_ => Some(rest.trim_start()),
}
}
pub(super) fn parse_checkpoint_call(query: &str) -> Option<CheckpointCall> {
parse_procedure_call(query, "db.checkpoint", &CHECKPOINT_COLUMNS)
.map(|columns| CheckpointCall { columns })
}
pub(super) fn checkpoint_stream(
call: &CheckpointCall,
message: String,
type_str: &str,
started: Instant,
) -> ResultStream {
let values: Vec<BoltValue> = call
.columns
.iter()
.map(|column| {
debug_assert!(
CHECKPOINT_COLUMNS.contains(column),
"parse_checkpoint_call yielded an unknown column: {column}"
);
if *column == CHECKPOINT_COLUMNS[0] {
BoltValue::Boolean(true)
} else {
BoltValue::String(message.clone())
}
})
.collect();
ResultStream {
metadata: ResultMetadata {
columns: call.columns.iter().map(|c| (*c).to_string()).collect(),
extra: BoltDict::new(),
},
records: vec![BoltRecord { values }],
summary: BoltDict::from([
("type".to_string(), BoltValue::String(type_str.to_string())),
(
"t_last".to_string(),
BoltValue::Integer(started.elapsed().as_millis() as i64),
),
]),
}
}
pub(super) const COMPONENTS_COLUMNS: [&str; 3] = ["name", "versions", "edition"];
pub(super) const SHOW_CURRENT_USER_COLUMNS: [&str; 3] = ["username", "roles", "flags"];
pub(super) const SHOW_DATABASES_COLUMNS: [&str; 13] = [
"name",
"type",
"aliases",
"access",
"address",
"role",
"writer",
"requestedStatus",
"currentStatus",
"statusMessage",
"default",
"home",
"constituents",
];
#[derive(Debug, PartialEq, Eq)]
pub(super) enum ServerFactsVerb {
DbmsComponents,
DbmsShowCurrentUser,
ShowDatabases,
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct ServerFactsCall {
pub(super) verb: ServerFactsVerb,
pub(super) columns: Vec<&'static str>,
}
pub(super) fn parse_procedure_call(
query: &str,
proc: &str,
declared: &'static [&'static str],
) -> Option<Vec<&'static str>> {
let mut body = query.trim();
if let Some(stripped) = body.strip_suffix(';') {
body = stripped.trim_end();
}
let rest = strip_keyword_ci(body, "call")?;
let rest = strip_keyword_ci(rest, proc)?;
let rest = rest.strip_prefix('(')?.trim_start();
let rest = rest.strip_prefix(')')?.trim_start();
if rest.is_empty() {
return Some(declared.to_vec());
}
let yielded = strip_keyword_ci(rest, "yield")?;
let mut columns: Vec<&'static str> = Vec::with_capacity(declared.len());
for raw in yielded.split(',') {
let name = raw.trim();
let canonical = declared.iter().find(|column| **column == name)?;
if columns.contains(canonical) {
return None;
}
columns.push(*canonical);
}
Some(columns)
}
pub(super) fn parse_server_facts_call(query: &str) -> Option<ServerFactsCall> {
if let Some(columns) = parse_procedure_call(query, "dbms.components", &COMPONENTS_COLUMNS) {
return Some(ServerFactsCall {
verb: ServerFactsVerb::DbmsComponents,
columns,
});
}
if let Some(columns) =
parse_procedure_call(query, "dbms.showcurrentuser", &SHOW_CURRENT_USER_COLUMNS)
{
return Some(ServerFactsCall {
verb: ServerFactsVerb::DbmsShowCurrentUser,
columns,
});
}
let mut body = query.trim();
if let Some(stripped) = body.strip_suffix(';') {
body = stripped.trim_end();
}
let rest = strip_keyword_ci(body, "show")?;
let rest = strip_keyword_ci(rest, "databases")?;
if rest.is_empty() {
return Some(ServerFactsCall {
verb: ServerFactsVerb::ShowDatabases,
columns: SHOW_DATABASES_COLUMNS.to_vec(),
});
}
None
}
pub(super) fn plan_from_explain_rows(
columns: &[String],
rows: &[Vec<kglite::datatypes::values::Value>],
) -> Option<BoltValue> {
use kglite::datatypes::values::Value;
let step_idx = columns.iter().position(|c| c == "step")?;
let op_idx = columns.iter().position(|c| c == "operation")?;
let est_idx = columns.iter().position(|c| c == "estimated_rows")?;
let mut steps: Vec<(i64, String, Option<i64>)> = Vec::new();
let mut passes: Vec<BoltValue> = Vec::new();
for row in rows {
let step = match row.get(step_idx)? {
Value::Int64(i) => *i,
_ => return None,
};
let op = match row.get(op_idx)? {
Value::String(s) => s.clone(),
_ => return None,
};
let est = match row.get(est_idx) {
Some(Value::Int64(i)) => Some(*i),
_ => None,
};
if let Some(name) = op.strip_prefix("OptimizerPass ") {
passes.push(BoltValue::String(name.to_string()));
} else {
steps.push((step, op, est));
}
}
if steps.is_empty() {
return None;
}
steps.sort_by_key(|(step, _, _)| *step);
let text: String = {
let mut lines = Vec::with_capacity(steps.len() + passes.len());
for (_, op, est) in steps.iter().rev() {
match est {
Some(est) => lines.push(format!("+ {op} (estimated rows: {est})")),
None => lines.push(format!("+ {op}")),
}
}
if !passes.is_empty() {
let names: Vec<&str> = passes
.iter()
.filter_map(|p| match p {
BoltValue::String(s) => Some(s.as_str()),
_ => None,
})
.collect();
lines.push(format!("optimizer passes: {}", names.join(", ")));
}
lines.join("\n")
};
let mut node: Option<BoltValue> = None;
let last = steps.len() - 1;
for (i, (_, op, est)) in steps.into_iter().enumerate() {
let mut args = BoltDict::new();
if let Some(est) = est {
args.insert("EstimatedRows".to_string(), BoltValue::Float(est as f64));
}
if i == last {
args.insert(
"string-representation".to_string(),
BoltValue::String(text.clone()),
);
args.insert(
"runtime".to_string(),
BoltValue::String("kglite".to_string()),
);
if !passes.is_empty() {
args.insert(
"optimizer-passes".to_string(),
BoltValue::List(std::mem::take(&mut passes)),
);
}
}
let children = match node.take() {
Some(child) => vec![child],
None => Vec::new(),
};
node = Some(BoltValue::Dict(BoltDict::from([
("operatorType".to_string(), BoltValue::String(op)),
("args".to_string(), BoltValue::Dict(args)),
("identifiers".to_string(), BoltValue::List(Vec::new())),
("children".to_string(), BoltValue::List(children)),
])));
}
node
}
pub(super) fn server_facts_stream(
columns: &[&'static str],
values: &[(&'static str, BoltValue)],
started: Instant,
) -> ResultStream {
let record: Vec<BoltValue> = columns
.iter()
.map(|column| {
values
.iter()
.find(|(name, _)| name == column)
.map(|(_, value)| value.clone())
.unwrap_or(BoltValue::Null)
})
.collect();
ResultStream {
metadata: ResultMetadata {
columns: columns.iter().map(|c| (*c).to_string()).collect(),
extra: BoltDict::new(),
},
records: vec![BoltRecord { values: record }],
summary: BoltDict::from([
("type".to_string(), BoltValue::String("r".to_string())),
(
"t_last".to_string(),
BoltValue::Integer(started.elapsed().as_millis() as i64),
),
]),
}
}