qql_core/ast/statement/mutation.rs
1//! Typed AST for mutation statements (UPSERT, DELETE, UPDATE, etc.).
2
3use super::types::*;
4use crate::ast::{FilterExpr, Value};
5use alloc::string::String;
6use alloc::vec::Vec;
7
8/// Role of an `EMBED` directive.
9#[derive(Debug, Clone, PartialEq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub enum EmbedKind {
12 /// Dense embedding (the default role).
13 Dense {
14 /// Optional embedding model override.
15 model: Option<String>,
16 },
17 /// Sparse (e.g. BM25) embedding.
18 Sparse {
19 /// Optional embedding model override.
20 model: Option<String>,
21 },
22 /// Multivector / ColBERT bag (`embed_multi` → MultiDense).
23 Multi {
24 /// Optional embedding model override.
25 model: Option<String>,
26 },
27 /// Image / CLIP vision path or URL → dense vector (`embed_image`).
28 Image {
29 /// Optional embedding model override.
30 model: Option<String>,
31 },
32}
33
34/// `EMBED <field> INTO <vector> [USING …]` directive.
35#[derive(Debug, Clone, PartialEq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct EmbedDirective {
38 /// Payload field providing the embedding input.
39 pub source_field: String,
40 /// Named vector to write into.
41 pub target_vector: String,
42 /// Embedding role and model for this directive.
43 pub kind: EmbedKind,
44}
45
46/// Upsert-level `USING` embedding clause.
47#[derive(Debug, Clone, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
49pub enum EmbeddingSpec {
50 /// `USING DENSE` / `USING MODEL` / `USING VECTOR` — dense embedding.
51 Dense {
52 /// Optional embedding model.
53 model: Option<String>,
54 /// Optional target vector name.
55 vector: Option<String>,
56 /// Optional payload field to embed.
57 field: Option<String>,
58 },
59 /// `USING SPARSE` — sparse embedding.
60 Sparse {
61 /// Optional sparse model (e.g. BM25-compatible).
62 model: Option<String>,
63 /// Optional target vector name.
64 vector: Option<String>,
65 /// Optional payload field to embed.
66 field: Option<String>,
67 },
68 /// `USING HYBRID` — parallel dense + sparse embedding.
69 Hybrid {
70 /// Optional dense embedding model.
71 dense_model: Option<String>,
72 /// Optional dense target vector name.
73 dense_vector: Option<String>,
74 /// Optional dense input payload field.
75 dense_field: Option<String>,
76 /// Optional sparse embedding model.
77 sparse_model: Option<String>,
78 /// Optional sparse target vector name.
79 sparse_vector: Option<String>,
80 /// Optional sparse input payload field.
81 sparse_field: Option<String>,
82 },
83 /// Multivector / ColBERT: text → bag of token vectors for a named multi slot.
84 MultiVector {
85 /// Optional multivector embedding model.
86 model: Option<String>,
87 /// Optional target multivector name.
88 vector: Option<String>,
89 /// Optional payload field to embed.
90 field: Option<String>,
91 },
92 /// Image / CLIP vision: payload field holds a path or URL → dense vector.
93 Image {
94 /// Optional CLIP vision model.
95 model: Option<String>,
96 /// Optional target dense vector name.
97 vector: Option<String>,
98 /// Optional payload field holding the image path or URL.
99 field: Option<String>,
100 },
101 /// Combined specs (e.g. DENSE + SPARSE + MULTI VECTOR colbert).
102 Multi(Vec<EmbeddingSpec>),
103}
104
105/// One `VALUES {…}` object of an `UPSERT INTO`.
106#[derive(Debug, Clone, PartialEq)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
108pub struct UpsertPoint {
109 /// Point identifier (unsigned integer or string).
110 pub id: PointId,
111 /// Optional pre-computed vectors, unnamed or by name.
112 pub vectors: Option<PointVectors>,
113 /// Remaining object entries as payload key-value pairs.
114 pub payload: Vec<(String, Value)>,
115}
116
117/// One entry of an `UPSERT INTO … VALUES` list: either an inline point
118/// object or a whole-point placeholder (`:name` / `?`) bound later to a
119/// point dict (or a list of point dicts, splicing several points).
120///
121/// `untagged` keeps the JSON shape of inline points identical to before,
122/// so existing AST snapshots are unaffected.
123#[derive(Debug, Clone, PartialEq)]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125#[cfg_attr(feature = "serde", serde(untagged))]
126pub enum PointEntry {
127 /// Inline `{id: …, …}` point object.
128 Inline(UpsertPoint),
129 /// Named whole-point placeholder (`:name`).
130 Param(
131 String,
132 #[cfg_attr(
133 feature = "serde",
134 serde(default, skip_serializing_if = "Option::is_none")
135 )]
136 Option<alloc::boxed::Box<crate::error::Span>>,
137 ),
138 /// Positional whole-point placeholder (`?`).
139 PositionalParam(
140 usize,
141 #[cfg_attr(
142 feature = "serde",
143 serde(default, skip_serializing_if = "Option::is_none")
144 )]
145 Option<alloc::boxed::Box<crate::error::Span>>,
146 ),
147}
148
149impl PointEntry {
150 /// Borrow the inline point, if this entry is one (`VALUES {…}` rows
151 /// always are; placeholders become inline once bound).
152 pub fn as_inline(&self) -> Option<&UpsertPoint> {
153 match self {
154 PointEntry::Inline(point) => Some(point),
155 PointEntry::Param(..) | PointEntry::PositionalParam(..) => None,
156 }
157 }
158}
159
160/// `UPSERT INTO <collection> VALUES …` statement.
161#[derive(Debug, Clone, PartialEq)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
163pub struct UpsertStmt {
164 /// Target collection.
165 pub collection: String,
166 /// Points to upsert.
167 pub points: Vec<PointEntry>,
168 /// Optional `USING` embedding clause.
169 pub embedding: Option<EmbeddingSpec>,
170 /// `EMBED <field> INTO <vector>` directives.
171 pub embed: Vec<EmbedDirective>,
172 /// `UPDATE FILTER <filter>` guard: only matching points update.
173 #[cfg_attr(
174 feature = "serde",
175 serde(default, skip_serializing_if = "Option::is_none")
176 )]
177 pub update_filter: Option<FilterExpr>,
178 /// `UPDATE MODE <insert_only | update_only | upsert>` guard.
179 #[cfg_attr(
180 feature = "serde",
181 serde(default, skip_serializing_if = "Option::is_none")
182 )]
183 pub update_mode: Option<UpsertUpdateMode>,
184 /// `SHARD '<key>'` or `SHARD <n>` routing key.
185 pub shard_key: Option<super::ShardKey>,
186 /// Optional write durability confirmation (`WAIT true` / `WAIT false`).
187 pub wait: Option<bool>,
188}
189
190/// Write mode for `UPDATE MODE` on `UPSERT` (OpenAPI `UpdateMode`).
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
193#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
194pub enum UpsertUpdateMode {
195 /// Only insert new points, do not update existing points.
196 InsertOnly,
197 /// Only update existing points, do not insert new points.
198 UpdateOnly,
199 /// Insert new points, update existing points (the default).
200 Upsert,
201}
202
203#[cfg(feature = "serde")]
204fn is_false(value: &bool) -> bool {
205 !*value
206}
207
208/// `CLEAR PAYLOAD FROM <collection> WHERE …` statement.
209#[derive(Debug, Clone, PartialEq)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
211pub struct ClearPayloadStmt {
212 /// Target collection.
213 pub collection: String,
214 /// Points whose payload is cleared.
215 pub selector: PointSelector,
216 /// `SHARD '<key>'` routing key.
217 pub shard_key: Option<super::ShardKey>,
218 /// Optional write durability confirmation (`WAIT true` / `WAIT false`).
219 pub wait: Option<bool>,
220}
221
222/// `DELETE VECTOR <names> FROM <collection> WHERE …` statement.
223#[derive(Debug, Clone, PartialEq)]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225pub struct DeleteVectorStmt {
226 /// Target collection.
227 pub collection: String,
228 /// Points whose named vectors are removed.
229 pub selector: PointSelector,
230 /// Named vectors to remove.
231 pub vector_names: Vec<String>,
232 /// `SHARD '<key>'` routing key.
233 pub shard_key: Option<super::ShardKey>,
234 /// Optional write durability confirmation (`WAIT true` / `WAIT false`).
235 pub wait: Option<bool>,
236}
237
238/// Point selection used by mutation statements.
239#[derive(Debug, Clone, PartialEq)]
240#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
241pub enum PointSelector {
242 /// A single point ID.
243 Id(PointId),
244 /// An explicit list of point IDs.
245 Ids(Vec<PointId>),
246 /// All points matching a filter.
247 Filter(Box<FilterExpr>),
248}
249
250/// `DELETE FROM <collection> WHERE …` statement.
251#[derive(Debug, Clone, PartialEq)]
252#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
253pub struct DeleteStmt {
254 /// Target collection.
255 pub collection: String,
256 /// Points to delete.
257 pub selector: PointSelector,
258 /// `SHARD '<key>'` routing key.
259 pub shard_key: Option<super::ShardKey>,
260 /// Optional write durability confirmation (`WAIT true` / `WAIT false`).
261 pub wait: Option<bool>,
262}
263
264/// One point in `UPDATE … SET VECTOR` (unnamed, one named vector, or a name map).
265#[derive(Debug, Clone, PartialEq)]
266#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
267pub struct UpdateVectorPoint {
268 /// Point whose vectors are replaced.
269 pub id: PointId,
270 /// Replacement vectors (unnamed, named map, or a parameter).
271 pub vectors: PointVectors,
272}
273
274/// `UPDATE <collection> SET VECTOR …` statement.
275///
276/// Compact form (`SET VECTOR [name] = … WHERE id = …`) is one point. Batch
277/// form (`SET VECTOR VALUES {id, vector}, …`) is the inverse of REST
278/// `PUT /points/vectors` and gRPC `UpdatePointVectors.points`.
279#[derive(Debug, Clone, PartialEq)]
280#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
281pub struct UpdateVectorStmt {
282 /// Target collection.
283 pub collection: String,
284 /// Points whose vectors are replaced. Never empty after a successful parse.
285 pub points: Vec<UpdateVectorPoint>,
286 /// `SHARD '<key>'` routing key.
287 pub shard_key: Option<super::ShardKey>,
288 /// Optional write durability confirmation (`WAIT true` / `WAIT false`).
289 pub wait: Option<bool>,
290}
291
292/// `DELETE PAYLOAD <keys> FROM <collection> WHERE …` statement.
293#[derive(Debug, Clone, PartialEq)]
294#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
295pub struct DeletePayloadStmt {
296 /// Target collection.
297 pub collection: String,
298 /// Payload keys to remove.
299 pub keys: Vec<String>,
300 /// Points whose payload keys are removed.
301 pub selector: PointSelector,
302 /// `SHARD '<key>'` routing key.
303 pub shard_key: Option<super::ShardKey>,
304 /// Optional write durability confirmation (`WAIT true` / `WAIT false`).
305 pub wait: Option<bool>,
306}
307
308/// `UPDATE <collection> SET PAYLOAD = {…} WHERE …` statement.
309#[derive(Debug, Clone, PartialEq)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
311pub struct UpdatePayloadStmt {
312 /// Target collection.
313 pub collection: String,
314 /// Points to update.
315 pub selector: PointSelector,
316 /// Payload keys to merge into the points.
317 pub payload: Vec<(String, Value)>,
318 /// `KEY '<path>'` nested assignment path (OpenAPI `SetPayload.key`).
319 #[cfg_attr(
320 feature = "serde",
321 serde(default, skip_serializing_if = "Option::is_none")
322 )]
323 pub key: Option<String>,
324 /// `OVERWRITE` flag: replace the full payload instead of merging.
325 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "is_false"))]
326 pub overwrite: bool,
327 /// `SHARD '<key>'` routing key.
328 pub shard_key: Option<super::ShardKey>,
329 /// Optional write durability confirmation (`WAIT true` / `WAIT false`).
330 pub wait: Option<bool>,
331}