1use crate::ast::{CollectionMode, QueryCollection, QueryExpr, QueryInput, Stmt};
2use crate::error::QqlError;
3use crate::parser::Parser;
4use alloc::format;
5use alloc::string::String;
6
7pub fn explain(source: &str) -> Result<String, QqlError> {
8 let statement = Parser::parse(source)?;
9 Ok(explain_node(&statement))
10}
11
12pub fn explain_all(source: &str) -> Result<String, QqlError> {
15 let statements = Parser::parse_all(source)?;
16 Ok(explain_nodes(&statements))
17}
18
19pub fn explain_nodes(statements: &[Stmt]) -> String {
21 if statements.is_empty() {
22 return String::new();
23 }
24 let mut output = String::new();
25 for (i, stmt) in statements.iter().enumerate() {
26 if i > 0 {
27 output.push('\n');
28 }
29 output.push_str(&format!("--- Statement {} ---\n", i + 1));
30 output.push_str(&explain_node(stmt));
31 }
32 output
33}
34
35pub fn explain_node(statement: &Stmt) -> String {
36 let mut output = String::new();
37 match statement {
38 Stmt::Query(query) => {
39 output.push_str("Statement: QUERY\n");
40 output.push_str(&format!("Intent: {}\n", query_intent(&query.expression)));
41 match &query.collection {
42 QueryCollection::Explicit(collection) => {
43 output.push_str(&format!("Collection: {}\n", collection));
44 }
45 QueryCollection::Inherited => output.push_str("Collection: inherited\n"),
46 }
47 if !query.ctes.is_empty() {
48 output.push_str(&format!("CTEs: {}\n", query.ctes.len()));
49 }
50 if query.filter.is_some() {
51 output.push_str("Filter: present\n");
52 }
53 if let Some(limit) = query.page.limit {
54 output.push_str(&format!("Limit: {}\n", limit));
55 }
56 }
57 Stmt::Scroll(statement) => output.push_str(&format!(
58 "Statement: SCROLL\nCollection: {}\nLimit: {}\n",
59 statement.collection, statement.limit
60 )),
61 Stmt::Upsert(statement) => output.push_str(&format!(
62 "Statement: UPSERT\nCollection: {}\nPoints: {}\n",
63 statement.collection,
64 statement.points.len()
65 )),
66 Stmt::CreateCollection(statement) => {
67 let mode = match statement.mode {
68 CollectionMode::Dense { .. } => "dense",
69 CollectionMode::Hybrid { .. } => "hybrid",
70 CollectionMode::Rerank => "rerank-oriented",
71 };
72 output.push_str(&format!(
73 "Statement: CREATE COLLECTION\nCollection: {}\nDeclared mode: {}\n",
74 statement.collection, mode
75 ));
76 }
77 Stmt::CreateIndex(statement) => output.push_str(&format!(
78 "Statement: CREATE INDEX\nCollection: {}\nField: {}\n",
79 statement.collection, statement.field
80 )),
81 Stmt::CreateShardKey(statement) => output.push_str(&format!(
82 "Statement: CREATE SHARD KEY\nCollection: {}\nShard: {}\n",
83 statement.collection, statement.shard_key
84 )),
85 Stmt::DropShardKey(statement) => output.push_str(&format!(
86 "Statement: DROP SHARD KEY\nCollection: {}\nShard: {}\n",
87 statement.collection, statement.shard_key
88 )),
89 Stmt::ShowShardKeys(collection) => output.push_str(&format!(
90 "Statement: SHOW SHARD KEYS\nCollection: {}\n",
91 collection
92 )),
93 Stmt::DropIndex(statement) => output.push_str(&format!(
94 "Statement: DROP INDEX\nCollection: {}\nField: {}\n",
95 statement.collection, statement.field
96 )),
97 Stmt::Count(statement) => {
98 output.push_str("Statement: COUNT\n");
99 match &statement.collection {
100 QueryCollection::Explicit(collection) => {
101 output.push_str(&format!("Collection: {}\n", collection));
102 }
103 QueryCollection::Inherited => output.push_str("Collection: inherited\n"),
104 }
105 if statement.filter.is_some() {
106 output.push_str("Filter: present\n");
107 }
108 }
109 Stmt::AlterCollection(statement) => output.push_str(&format!(
110 "Statement: ALTER COLLECTION\nCollection: {}\n",
111 statement.collection
112 )),
113 Stmt::DropCollection(statement) => output.push_str(&format!(
114 "Statement: DROP COLLECTION\nCollection: {}\n",
115 statement.collection
116 )),
117 Stmt::ShowCollections => output.push_str("Statement: SHOW COLLECTIONS\n"),
118 Stmt::ShowCollection(collection) => {
119 output.push_str(&format!(
120 "Statement: SHOW COLLECTION\nCollection: {}\n",
121 collection
122 ));
123 }
124 Stmt::Delete(statement) => output.push_str(&format!(
125 "Statement: DELETE\nCollection: {}\nSelector: typed point selector\n",
126 statement.collection
127 )),
128 Stmt::ClearPayload(statement) => output.push_str(&format!(
129 "Statement: CLEAR PAYLOAD\nCollection: {}\nSelector: typed point selector\n",
130 statement.collection
131 )),
132 Stmt::DeletePayload(statement) => output.push_str(&format!(
133 "Statement: DELETE PAYLOAD\nCollection: {}\nKeys: {:?}\nSelector: typed point selector\n",
134 statement.collection, statement.keys
135 )),
136 Stmt::DeleteVector(statement) => output.push_str(&format!(
137 "Statement: DELETE VECTOR\nCollection: {}\nVectors: {:?}\nSelector: typed point selector\n",
138 statement.collection, statement.vector_names
139 )),
140 Stmt::UpdateVector(statement) => output.push_str(&format!(
141 "Statement: UPDATE VECTOR\nCollection: {}\n",
142 statement.collection
143 )),
144 Stmt::UpdatePayload(statement) => output.push_str(&format!(
145 "Statement: UPDATE PAYLOAD\nCollection: {}\n",
146 statement.collection
147 )),
148 }
149 output
150}
151
152fn query_intent(expression: &QueryExpr) -> &'static str {
153 match expression {
154 QueryExpr::Points { .. } => "retrieve points by ID",
155 QueryExpr::Nearest { mmr: Some(_), .. } => "maximal marginal relevance (MMR) search",
156 QueryExpr::Nearest { input, .. } => match input {
157 QueryInput::Text { .. } => "nearest neighbors from text",
158 QueryInput::Image { .. } => "nearest neighbors from an image",
159 QueryInput::Vector(_) => "nearest neighbors from a vector",
160 QueryInput::Point(_) => "nearest neighbors from a point",
161 },
162 QueryExpr::Recommend { .. } => "recommend from positive and negative examples",
163 QueryExpr::Context { .. } => "context search",
164 QueryExpr::Discover { .. } => "discovery search",
165 QueryExpr::OrderBy { .. } => "payload order query",
166 QueryExpr::SampleRandom => "random sample",
167 QueryExpr::Fusion { .. } => "fuse prefetched result sets",
168 QueryExpr::Formula { .. } => "formula-based scoring",
169 QueryExpr::RelevanceFeedback { .. } => "relevance feedback",
170 QueryExpr::Hybrid { .. } => "hybrid shorthand",
171 QueryExpr::Rerank { .. } => "late-interaction prefetched rerank",
172 QueryExpr::CrossRerank { .. } => "cross-encoder pair rerank of prefetched candidates",
173 }
174}