use crate::db::sql::table_render::render_explain_lines;
use crate::db::{
EntityCatalogDescription, EntityConstraintDescription, EntityFieldDescription,
EntitySchemaDescription, MemoryCatalogDescription, RowProjectionOutput,
StoreCatalogDescription,
sql::table_render::{
SqlDdlRenderInput, render_constraint_validation_finding_line, render_count_lines,
render_describe_lines, render_grouped_lines, render_query_rows_lines,
render_show_columns_lines, render_show_constraints_lines, render_show_entities_lines,
render_show_entities_verbose_lines, render_show_indexes_lines, render_show_memory_lines,
render_show_stores_lines, render_show_stores_verbose_lines, render_sql_ddl_lines,
},
};
use candid::CandidType;
use serde::Deserialize;
use crate::ConstraintValidationFindingOutput;
#[cfg_attr(doc, doc = "SqlGroupedRowsOutput\n\nStructured grouped SQL payload.")]
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct SqlGroupedRowsOutput {
pub entity: String,
pub columns: Vec<String>,
pub rows: Vec<Vec<String>>,
pub row_count: u32,
pub next_cursor: Option<String>,
}
#[cfg_attr(
doc,
doc = "SqlConstraintValidationOutput\n\nTyped progress from one bounded constraint-validation step."
)]
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct SqlConstraintValidationOutput {
pub constraint_id: u32,
pub activation_epoch: Option<u64>,
pub page_sequence: Option<u64>,
pub state: String,
pub revision_status: String,
pub rows_scanned: u64,
pub findings: Vec<ConstraintValidationFindingOutput>,
pub complete: bool,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct SqlQueryPerfResult {
pub result: SqlQueryResult,
pub instructions: u64,
pub planner_instructions: u64,
pub store_instructions: u64,
pub executor_instructions: u64,
pub pure_covering_decode_instructions: u64,
pub pure_covering_row_assembly_instructions: u64,
pub decode_instructions: u64,
pub compiler_instructions: u64,
}
impl SqlQueryPerfResult {
#[doc(hidden)]
#[must_use]
pub fn from_attribution(
result: SqlQueryResult,
attribution: crate::db::SqlQueryPerfAttribution,
) -> Self {
Self {
result,
instructions: attribution.total_local_instructions,
planner_instructions: attribution.execution.planner_local_instructions,
store_instructions: attribution.execution.store_local_instructions,
executor_instructions: attribution.execution.executor_local_instructions,
pure_covering_decode_instructions: attribution
.pure_covering
.map_or(0, |pure_covering| pure_covering.decode_local_instructions),
pure_covering_row_assembly_instructions: attribution
.pure_covering
.map_or(0, |pure_covering| {
pure_covering.row_assembly_local_instructions
}),
decode_instructions: attribution.response_decode_local_instructions,
compiler_instructions: attribution.compile_local_instructions,
}
}
}
#[cfg_attr(doc, doc = "SqlQueryResult\n\nUnified SQL endpoint result.")]
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub enum SqlQueryResult {
Count {
entity: String,
row_count: u32,
},
Projection(RowProjectionOutput),
Grouped(SqlGroupedRowsOutput),
Explain {
entity: String,
explain: String,
},
Describe(EntitySchemaDescription),
ShowConstraints {
entity: String,
constraints: Vec<EntityConstraintDescription>,
},
ShowIndexes {
entity: String,
indexes: Vec<String>,
},
ShowColumns {
entity: String,
columns: Vec<EntityFieldDescription>,
},
ShowEntities {
entities: Vec<EntityCatalogDescription>,
verbose: bool,
},
ShowStores {
stores: Vec<StoreCatalogDescription>,
verbose: bool,
},
ShowMemory {
memory: Vec<MemoryCatalogDescription>,
},
Ddl {
entity: String,
mutation_kind: String,
target_index: String,
target_store: String,
field_path: Vec<String>,
status: String,
rows_scanned: u64,
index_keys_written: u64,
constraint_validation: Option<SqlConstraintValidationOutput>,
},
}
impl SqlQueryResult {
#[must_use]
pub fn render_lines(&self) -> Vec<String> {
match self {
Self::Count { entity, row_count } => render_count_lines(entity.as_str(), *row_count),
Self::Projection(rows) => render_query_rows_lines(rows),
Self::Grouped(rows) => render_grouped_lines(rows),
Self::Explain { explain, .. } => render_explain_lines(explain.as_str()),
Self::Describe(description) => render_describe_lines(description),
Self::ShowConstraints {
entity,
constraints,
} => render_show_constraints_lines(entity.as_str(), constraints.as_slice()),
Self::ShowIndexes { entity, indexes } => {
render_show_indexes_lines(entity.as_str(), indexes.as_slice())
}
Self::ShowColumns { entity, columns } => {
render_show_columns_lines(entity.as_str(), columns.as_slice())
}
Self::ShowEntities { entities, verbose } => {
if *verbose {
render_show_entities_verbose_lines(entities.as_slice())
} else {
render_show_entities_lines(entities.as_slice())
}
}
Self::ShowStores { stores, verbose } => {
if *verbose {
render_show_stores_verbose_lines(stores.as_slice())
} else {
render_show_stores_lines(stores.as_slice())
}
}
Self::ShowMemory { memory } => render_show_memory_lines(memory.as_slice()),
Self::Ddl {
entity,
mutation_kind,
target_index,
target_store,
field_path,
status,
rows_scanned,
index_keys_written,
constraint_validation,
} => {
let mut lines = render_sql_ddl_lines(SqlDdlRenderInput {
entity: entity.as_str(),
mutation_kind: mutation_kind.as_str(),
target_index: target_index.as_str(),
target_store: target_store.as_str(),
field_path: field_path.as_slice(),
status: status.as_str(),
rows_scanned: *rows_scanned,
index_keys_written: *index_keys_written,
});
if let Some(validation) = constraint_validation {
lines.extend(
validation
.findings
.iter()
.map(render_constraint_validation_finding_line),
);
}
lines
}
}
}
#[must_use]
pub fn render_text(&self) -> String {
self.render_lines().join("\n")
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use candid::types::{CandidType, Label, Type, TypeInner};
use super::{SqlQueryPerfResult, SqlQueryResult};
fn named_fields(ty: Type, expected_kind: &str) -> BTreeSet<String> {
let fields = match ty.as_ref() {
TypeInner::Record(fields) if expected_kind == "record" => fields,
TypeInner::Variant(fields) if expected_kind == "variant" => fields,
other => panic!("expected Candid {expected_kind}, got {other:?}"),
};
fields
.iter()
.map(|field| match field.id.as_ref() {
Label::Named(name) => name.clone(),
other => panic!("expected named Candid field, got {other:?}"),
})
.collect()
}
#[test]
fn sql_query_result_candid_shape_always_contains_explain() {
let variants = named_fields(SqlQueryResult::ty(), "variant");
assert!(variants.contains("Explain"));
}
#[test]
fn sql_query_perf_result_owns_the_fixed_public_candid_record() {
let fields = named_fields(SqlQueryPerfResult::ty(), "record");
let expected = BTreeSet::from([
"compiler_instructions".to_string(),
"decode_instructions".to_string(),
"executor_instructions".to_string(),
"instructions".to_string(),
"planner_instructions".to_string(),
"pure_covering_decode_instructions".to_string(),
"pure_covering_row_assembly_instructions".to_string(),
"result".to_string(),
"store_instructions".to_string(),
]);
assert_eq!(fields, expected);
}
}