use candid::Decode;
use icydb::db::EntitySchemaDescription;
use crate::{
cli::CanisterTarget,
config::{SCHEMA_ENDPOINT, require_configured_endpoint},
icp::require_created_canister,
table::{ColumnAlign, append_indented_table},
};
use super::{
call_query,
render::{render_field_list, yes_no},
};
pub(crate) fn run_schema_show_command(target: CanisterTarget) -> Result<(), String> {
require_configured_endpoint(target.canister_name(), SCHEMA_ENDPOINT)?;
require_created_canister(target.environment(), target.canister_name())?;
let candid_bytes = call_query(
target.environment(),
target.canister_name(),
SCHEMA_ENDPOINT.method(),
"()",
)?;
let response = Decode!(
candid_bytes.as_slice(),
Result<Vec<icydb::db::EntitySchemaDescription>, icydb::Error>
)
.map_err(|err| err.to_string())?;
match response {
Ok(report) => {
print!("{}", render_schema_report(report.as_slice()));
Ok(())
}
Err(err) => Err(format!(
"IcyDB schema method '{}' failed on canister '{}' in environment '{}': {err}",
SCHEMA_ENDPOINT.method(),
target.canister_name(),
target.environment(),
)),
}
}
pub(crate) fn render_schema_report(report: &[EntitySchemaDescription]) -> String {
let mut output = String::new();
let entity_rows = report
.iter()
.map(|entity| {
[
entity.entity_name().to_string(),
entity.fields().len().to_string(),
entity.indexes().len().to_string(),
entity.relations().len().to_string(),
entity.primary_key().to_string(),
entity.entity_path().to_string(),
]
})
.collect::<Vec<_>>();
let field_rows = report
.iter()
.flat_map(|entity| {
entity.fields().iter().map(|field| {
[
entity.entity_name().to_string(),
field.name().to_string(),
field
.slot()
.map_or_else(|| "-".to_string(), |slot| slot.to_string()),
field.kind().to_string(),
yes_no(field.nullable()).to_string(),
yes_no(field.primary_key()).to_string(),
yes_no(field.queryable()).to_string(),
field.origin().to_string(),
]
})
})
.collect::<Vec<_>>();
let index_rows = report
.iter()
.flat_map(|entity| {
entity.indexes().iter().map(|index| {
[
entity.entity_name().to_string(),
index.name().to_string(),
render_field_list(index.fields()),
yes_no(index.unique()).to_string(),
index.origin().to_string(),
]
})
})
.collect::<Vec<_>>();
let relation_rows = report
.iter()
.flat_map(|entity| {
entity.relations().iter().map(|relation| {
[
entity.entity_name().to_string(),
relation.field().to_string(),
relation.target_entity_name().to_string(),
format!("{:?}", relation.strength()),
format!("{:?}", relation.cardinality()),
]
})
})
.collect::<Vec<_>>();
output.push_str("IcyDB schema\n");
output.push_str(
format!(
" entities: {}\n fields: {}\n indexes: {}\n relations: {}\n\n",
report.len(),
field_rows.len(),
index_rows.len(),
relation_rows.len(),
)
.as_str(),
);
append_schema_entity_table(&mut output, entity_rows.as_slice());
output.push('\n');
append_schema_field_table(&mut output, field_rows.as_slice());
output.push('\n');
append_schema_index_table(&mut output, index_rows.as_slice());
output.push('\n');
append_schema_relation_table(&mut output, relation_rows.as_slice());
output
}
fn append_schema_entity_table(output: &mut String, rows: &[[String; 6]]) {
output.push_str("entities\n");
if rows.is_empty() {
output.push_str(" None\n");
return;
}
append_indented_table(
output,
" ",
&[
"entity",
"fields",
"indexes",
"relations",
"primary key",
"path",
],
rows,
&[
ColumnAlign::Left,
ColumnAlign::Right,
ColumnAlign::Right,
ColumnAlign::Right,
ColumnAlign::Left,
ColumnAlign::Left,
],
);
}
fn append_schema_field_table(output: &mut String, rows: &[[String; 8]]) {
output.push_str("fields\n");
if rows.is_empty() {
output.push_str(" None\n");
return;
}
append_indented_table(
output,
" ",
&[
"entity",
"field",
"slot",
"type",
"nullable",
"pk",
"queryable",
"origin",
],
rows,
&[
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Right,
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
],
);
}
fn append_schema_index_table(output: &mut String, rows: &[[String; 5]]) {
output.push_str("indexes\n");
if rows.is_empty() {
output.push_str(" None\n");
return;
}
append_indented_table(
output,
" ",
&["entity", "index", "fields", "unique", "origin"],
rows,
&[
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
],
);
}
fn append_schema_relation_table(output: &mut String, rows: &[[String; 5]]) {
output.push_str("relations\n");
if rows.is_empty() {
output.push_str(" None\n");
return;
}
append_indented_table(
output,
" ",
&["entity", "field", "target", "strength", "cardinality"],
rows,
&[
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
ColumnAlign::Left,
],
);
}