qail_core/transpiler/nosql/
qdrant.rs1use crate::ast::*;
2
3pub trait ToQdrant {
4 fn to_qdrant_search(&self) -> String;
5}
6
7impl ToQdrant for Qail {
8 fn to_qdrant_search(&self) -> String {
9 match self.action {
10 Action::Get => build_qdrant_search(self),
11 Action::Put | Action::Add => build_qdrant_upsert(self),
12 Action::Del => build_qdrant_delete(self),
13 _ => format!(
14 "{{ \"error\": \"Action {:?} not supported for Qdrant\" }}",
15 self.action
16 ),
17 }
18 }
19}
20
21fn build_qdrant_upsert(cmd: &Qail) -> String {
22 let mut point_id = "0".to_string(); let mut vector = "[0.0]".to_string();
29 let mut payload_parts = Vec::new();
30
31 for cage in &cmd.cages {
32 match cage.kind {
33 CageKind::Payload | CageKind::Filter => {
34 for cond in &cage.conditions {
35 if let Expr::Named(name) = &cond.left {
36 if name == "id" {
37 point_id = value_to_json(&cond.value);
38 } else if name == "vector" {
39 vector = value_to_json(&cond.value);
40 } else {
41 payload_parts.push(format!(
42 "\"{}\": {}",
43 name,
44 value_to_json(&cond.value)
45 ));
46 }
47 }
48 }
49 }
50 _ => {}
51 }
52 }
53
54 let payload_json = if payload_parts.is_empty() {
55 "{}".to_string()
56 } else {
57 format!("{{ {} }}", payload_parts.join(", "))
58 };
59
60 let point = format!(
62 "{{ \"id\": {}, \"vector\": {}, \"payload\": {} }}",
63 point_id, vector, payload_json
64 );
65
66 format!("{{ \"points\": [{}] }}", point)
67}
68
69fn build_qdrant_delete(cmd: &Qail) -> String {
70 let mut ids = Vec::new();
75
76 for cage in &cmd.cages {
77 if let CageKind::Filter = cage.kind {
78 for cond in &cage.conditions {
79 if let Expr::Named(name) = &cond.left
80 && name == "id"
81 {
82 ids.push(value_to_json(&cond.value));
83 }
84 }
85 }
86 }
87
88 if !ids.is_empty() {
89 format!("{{ \"points\": [{}] }}", ids.join(", "))
90 } else {
91 let filter = build_filter(cmd);
93 format!("{{ \"filter\": {} }}", filter)
94 }
95}
96
97fn build_qdrant_search(cmd: &Qail) -> String {
98 let mut parts = Vec::new();
102
103 let mut vector_found = false;
107
108 for cage in &cmd.cages {
109 if let CageKind::Filter = cage.kind {
110 for cond in &cage.conditions {
111 if cond.op == Operator::Fuzzy {
112 match &cond.value {
116 Value::String(s) => {
117 parts.push(format!("\"vector\": \"{{{{EMBED:{}}}}}\"", s));
120 }
121 _ => {
122 parts.push(format!("\"vector\": {}", value_to_json(&cond.value)));
123 }
124 }
125 vector_found = true;
126 break;
127 }
128 }
129 }
130 if vector_found {
131 break;
132 }
133 }
134
135 if !vector_found {
136 parts.push("\"vector\": [0.0]".to_string()); }
139
140 let filter = build_filter(cmd);
142 if !filter.is_empty() {
143 parts.push(format!("\"filter\": {}", filter));
144 }
145
146 let mut limit = 10;
148 if let Some(l) = get_cage_val(cmd, CageKind::Limit(0)) {
149 limit = l;
150 }
151 parts.push(format!("\"limit\": {}", limit));
152
153 if !cmd.columns.is_empty() {
155 let mut incl = Vec::new();
156 for c in &cmd.columns {
157 if let Expr::Named(n) = c {
158 incl.push(format!("\"{}\"", n));
159 }
160 }
161 parts.push(format!(
162 "\"with_payload\": {{ \"include\": [{}] }}",
163 incl.join(", ")
164 ));
165 } else {
166 parts.push("\"with_payload\": true".to_string());
167 }
168
169 format!("{{ {} }}", parts.join(", "))
170}
171
172fn build_filter(cmd: &Qail) -> String {
173 let mut musts = Vec::new();
175
176 for cage in &cmd.cages {
177 if let CageKind::Filter = cage.kind {
178 for cond in &cage.conditions {
179 if cond.op == Operator::Fuzzy {
181 continue;
182 }
183
184 let val = value_to_json(&cond.value);
185 let col_str = match &cond.left {
186 Expr::Named(name) => name.clone(),
187 expr => expr.to_string(),
188 };
189
190 let clause = match cond.op {
191 Operator::Eq => format!(
192 "{{ \"key\": \"{}\", \"match\": {{ \"value\": {} }} }}",
193 col_str, val
194 ),
195 Operator::Gt => format!(
197 "{{ \"key\": \"{}\", \"range\": {{ \"gt\": {} }} }}",
198 col_str, val
199 ),
200 Operator::Gte => format!(
201 "{{ \"key\": \"{}\", \"range\": {{ \"gte\": {} }} }}",
202 col_str, val
203 ),
204 Operator::Lt => format!(
205 "{{ \"key\": \"{}\", \"range\": {{ \"lt\": {} }} }}",
206 col_str, val
207 ),
208 Operator::Lte => format!(
209 "{{ \"key\": \"{}\", \"range\": {{ \"lte\": {} }} }}",
210 col_str, val
211 ),
212 Operator::Ne => format!(
213 "{{ \"must_not\": [{{ \"key\": \"{}\", \"match\": {{ \"value\": {} }} }}] }}",
214 col_str, val
215 ), _ => format!(
217 "{{ \"key\": \"{}\", \"match\": {{ \"value\": {} }} }}",
218 col_str, val
219 ),
220 };
221 musts.push(clause);
222 }
223 }
224 }
225
226 if musts.is_empty() {
227 return String::new();
228 }
229
230 format!("{{ \"must\": [{}] }}", musts.join(", "))
231}
232
233fn get_cage_val(cmd: &Qail, kind_example: CageKind) -> Option<usize> {
234 for cage in &cmd.cages {
235 if let (CageKind::Limit(n), CageKind::Limit(_)) = (&cage.kind, &kind_example) {
236 return Some(*n);
237 }
238 }
239 None
240}
241
242fn value_to_json(v: &Value) -> String {
243 match v {
244 Value::String(s) => format!("\"{}\"", s),
245 Value::Int(n) => n.to_string(),
246 Value::Float(n) => n.to_string(),
247 Value::Bool(b) => b.to_string(),
248 Value::Array(arr) => {
249 let elems: Vec<String> = arr
250 .iter()
251 .map(|e| match e {
252 Value::Int(i) => i.to_string(),
253 Value::Float(f) => f.to_string(),
254 _ => "0.0".to_string(),
255 })
256 .collect();
257 format!("[{}]", elems.join(", "))
258 }
259 _ => "null".to_string(),
260 }
261}