Skip to main content

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}