contextdb_engine/
cli_render.rs1use crate::Database;
4use contextdb_core::Value;
5use contextdb_core::table_meta::{ColumnType, TableMeta};
6use std::fmt::Write;
7
8pub fn render_column_type(col_type: &ColumnType) -> String {
10 match col_type {
11 ColumnType::Integer => "INTEGER".to_string(),
12 ColumnType::Real => "REAL".to_string(),
13 ColumnType::Text => "TEXT".to_string(),
14 ColumnType::Boolean => "BOOLEAN".to_string(),
15 ColumnType::Json => "JSON".to_string(),
16 ColumnType::Uuid => "UUID".to_string(),
17 ColumnType::Vector(dim) => format!("VECTOR({dim})"),
18 ColumnType::Timestamp => "TIMESTAMP".to_string(),
19 ColumnType::TxId => "TXID".to_string(),
20 }
21}
22
23pub fn render_table_meta(table: &str, meta: &TableMeta) -> String {
27 render_table_meta_inner(table, meta, false)
28}
29
30pub fn render_table_meta_verbose(table: &str, meta: &TableMeta) -> String {
34 render_table_meta_inner(table, meta, true)
35}
36
37fn render_table_meta_inner(table: &str, meta: &TableMeta, verbose: bool) -> String {
38 let mut buf = String::new();
39 writeln!(&mut buf, "CREATE TABLE {table} (").unwrap();
40 let mut first = true;
41 for col in &meta.columns {
42 if !first {
43 buf.push_str(",\n");
44 }
45 first = false;
46 let mut ty = render_column_type(&col.column_type);
47 if !col.nullable && !col.primary_key {
48 ty.push_str(" NOT NULL");
49 }
50 if col.primary_key {
51 ty.push_str(" PRIMARY KEY");
52 }
53 if col.immutable {
54 ty.push_str(" IMMUTABLE");
55 }
56 write!(&mut buf, " {} {}", col.name, ty).unwrap();
57 }
58 buf.push_str("\n)");
59 if meta.immutable {
60 buf.push_str(" IMMUTABLE");
61 }
62 if let Some(sm) = &meta.state_machine {
63 let mut entries: Vec<_> = sm.transitions.iter().collect();
64 entries.sort_by(|a, b| a.0.cmp(b.0));
65 let transitions: Vec<String> = entries
66 .into_iter()
67 .map(|(from, tos)| format!("{from} -> [{}]", tos.join(", ")))
68 .collect();
69 write!(
70 &mut buf,
71 " STATE MACHINE ({}: {})",
72 sm.column,
73 transitions.join(", ")
74 )
75 .unwrap();
76 }
77 if !meta.dag_edge_types.is_empty() {
78 let edge_types = meta
79 .dag_edge_types
80 .iter()
81 .map(|edge_type| format!("'{edge_type}'"))
82 .collect::<Vec<_>>()
83 .join(", ");
84 write!(&mut buf, " DAG({edge_types})").unwrap();
85 }
86 buf.push_str(";\n");
87 for decl in &meta.indexes {
88 if !verbose && decl.kind == contextdb_core::IndexKind::Auto {
89 continue;
90 }
91 let cols: Vec<String> = decl
92 .columns
93 .iter()
94 .map(|(c, dir)| {
95 let dir_str = match dir {
96 contextdb_core::SortDirection::Asc => "ASC",
97 contextdb_core::SortDirection::Desc => "DESC",
98 };
99 format!("{c} {dir_str}")
100 })
101 .collect();
102 writeln!(
103 &mut buf,
104 "CREATE INDEX {} ON {} ({});",
105 decl.name,
106 table,
107 cols.join(", ")
108 )
109 .unwrap();
110 }
111 buf
112}
113
114pub fn render_explain(
117 db: &Database,
118 sql: &str,
119 params: &std::collections::HashMap<String, Value>,
120) -> contextdb_core::Result<String> {
121 let result = db.execute(sql, params)?;
122 let mut out = String::new();
123 out.push_str(result.trace.physical_plan);
124 if let Some(idx) = &result.trace.index_used {
125 out.push_str(&format!(" {{ index: {idx} }}"));
126 }
127 out.push('\n');
128 if !result.trace.predicates_pushed.is_empty() {
129 out.push_str(" predicates_pushed: [");
130 for (i, p) in result.trace.predicates_pushed.iter().enumerate() {
131 if i > 0 {
132 out.push_str(", ");
133 }
134 out.push_str(p.as_ref());
135 }
136 out.push_str("]\n");
137 }
138 if !result.trace.indexes_considered.is_empty() {
139 out.push_str(" indexes_considered: [");
140 for (i, c) in result.trace.indexes_considered.iter().enumerate() {
141 if i > 0 {
142 out.push_str(", ");
143 }
144 out.push_str(&format!("{}: {}", c.name, c.rejected_reason));
145 }
146 out.push_str("]\n");
147 }
148 if result.trace.sort_elided {
149 out.push_str(" sort_elided: true\n");
150 }
151 Ok(out)
152}
153
154pub fn value_to_string(v: &Value) -> String {
156 match v {
157 Value::Null => "NULL".to_string(),
158 Value::Bool(b) => b.to_string(),
159 Value::Int64(n) => n.to_string(),
160 Value::Float64(f) => f.to_string(),
161 Value::Text(s) => s.clone(),
162 Value::Uuid(u) => u.to_string(),
163 Value::Timestamp(ts) => ts.to_string(),
164 Value::Json(j) => j.to_string(),
165 Value::Vector(vs) => format!("{vs:?}"),
166 Value::TxId(tx) => tx.0.to_string(),
167 }
168}
169
170pub fn render_sync_status(db: &Database) -> String {
172 format!("Committed TxId: {}\n", db.committed_watermark().0)
173}