1use serde::{Deserialize, Serialize};
32
33use crate::schema::{DistanceMetric, VectorConfig};
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37#[non_exhaustive]
38pub enum VectorParam {
39 Vector(Vec<f32>),
41 Int(i64),
43 String(String),
45 Json(serde_json::Value),
47}
48
49impl VectorParam {
50 #[must_use]
52 pub fn to_sql_literal(&self) -> String {
53 match self {
54 VectorParam::Vector(v) => {
55 let values: Vec<String> = v.iter().map(std::string::ToString::to_string).collect();
56 format!("'[{}]'::vector", values.join(","))
57 },
58 VectorParam::Int(i) => i.to_string(),
59 VectorParam::String(s) => format!("'{}'", s.replace('\'', "''")),
60 VectorParam::Json(j) => format!("'{j}'::jsonb"),
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct VectorSearchQuery {
68 pub table: String,
70 pub embedding_column: String,
72 pub select_columns: Vec<String>,
74 pub distance_metric: DistanceMetric,
76 pub limit: u32,
78 pub where_clause: Option<String>,
80 pub order_by: Option<String>,
82 pub include_distance: bool,
84 pub offset: Option<u32>,
86}
87
88impl Default for VectorSearchQuery {
89 fn default() -> Self {
90 Self {
91 table: String::new(),
92 embedding_column: "embedding".to_string(),
93 select_columns: Vec::new(),
94 distance_metric: DistanceMetric::Cosine,
95 limit: 10,
96 where_clause: None,
97 order_by: None,
98 include_distance: false,
99 offset: None,
100 }
101 }
102}
103
104impl VectorSearchQuery {
105 pub fn new(table: impl Into<String>) -> Self {
107 Self {
108 table: table.into(),
109 ..Default::default()
110 }
111 }
112
113 pub fn with_embedding_column(mut self, column: impl Into<String>) -> Self {
115 self.embedding_column = column.into();
116 self
117 }
118
119 #[must_use = "builder method returns modified builder"]
121 pub fn with_select_columns(mut self, columns: Vec<String>) -> Self {
122 self.select_columns = columns;
123 self
124 }
125
126 #[must_use = "builder method returns modified builder"]
128 pub const fn with_distance_metric(mut self, metric: DistanceMetric) -> Self {
129 self.distance_metric = metric;
130 self
131 }
132
133 #[must_use = "builder method returns modified builder"]
135 pub const fn with_limit(mut self, limit: u32) -> Self {
136 self.limit = limit;
137 self
138 }
139
140 pub fn with_where(mut self, clause: impl Into<String>) -> Self {
142 self.where_clause = Some(clause.into());
143 self
144 }
145
146 #[must_use = "builder method returns modified builder"]
148 pub const fn with_distance_score(mut self) -> Self {
149 self.include_distance = true;
150 self
151 }
152
153 #[must_use = "builder method returns modified builder"]
155 pub const fn with_offset(mut self, offset: u32) -> Self {
156 self.offset = Some(offset);
157 self
158 }
159}
160
161#[derive(Debug, Clone)]
163pub struct VectorInsertQuery {
164 pub table: String,
166 pub columns: Vec<String>,
168 pub vector_column: String,
170 pub upsert: bool,
172 pub conflict_columns: Vec<String>,
174 pub update_columns: Vec<String>,
176 pub returning: Option<String>,
178}
179
180impl Default for VectorInsertQuery {
181 fn default() -> Self {
182 Self {
183 table: String::new(),
184 columns: Vec::new(),
185 vector_column: "embedding".to_string(),
186 upsert: false,
187 conflict_columns: vec!["id".to_string()],
188 update_columns: Vec::new(),
189 returning: Some("id".to_string()),
190 }
191 }
192}
193
194impl VectorInsertQuery {
195 pub fn new(table: impl Into<String>) -> Self {
197 Self {
198 table: table.into(),
199 ..Default::default()
200 }
201 }
202
203 #[must_use = "builder method returns modified builder"]
205 pub fn with_columns(mut self, columns: Vec<String>) -> Self {
206 self.columns = columns;
207 self
208 }
209
210 pub fn with_vector_column(mut self, column: impl Into<String>) -> Self {
212 self.vector_column = column.into();
213 self
214 }
215
216 #[must_use = "builder method returns modified builder"]
218 pub fn with_upsert(mut self, conflict_columns: Vec<String>) -> Self {
219 self.upsert = true;
220 self.conflict_columns = conflict_columns;
221 self
222 }
223
224 #[must_use = "builder method returns modified builder"]
226 pub fn with_update_columns(mut self, columns: Vec<String>) -> Self {
227 self.update_columns = columns;
228 self
229 }
230
231 pub fn with_returning(mut self, column: impl Into<String>) -> Self {
233 self.returning = Some(column.into());
234 self
235 }
236}
237
238#[must_use = "call .build() to construct the final value"]
243#[derive(Debug, Clone, Default)]
244pub struct VectorQueryBuilder {
245 placeholder_style: PlaceholderStyle,
247}
248
249#[derive(Debug, Clone, Copy, Default)]
251#[non_exhaustive]
252pub enum PlaceholderStyle {
253 #[default]
255 Dollar,
256 QuestionMark,
258}
259
260impl VectorQueryBuilder {
261 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub const fn with_question_marks() -> Self {
268 Self {
269 placeholder_style: PlaceholderStyle::QuestionMark,
270 }
271 }
272
273 fn placeholder(&self, index: usize) -> String {
275 match self.placeholder_style {
276 PlaceholderStyle::Dollar => format!("${index}"),
277 PlaceholderStyle::QuestionMark => "?".to_string(),
278 }
279 }
280
281 #[must_use]
299 pub fn similarity_search(
300 &self,
301 query: &VectorSearchQuery,
302 query_embedding: &[f32],
303 ) -> (String, Vec<VectorParam>) {
304 let mut params = Vec::new();
305 let mut param_idx = 1;
306
307 params.push(VectorParam::Vector(query_embedding.to_vec()));
309 let embedding_placeholder = format!("{}::vector", self.placeholder(param_idx));
310 param_idx += 1;
311
312 let distance_op = query.distance_metric.operator();
313
314 let select_clause = if query.select_columns.is_empty() {
316 if query.include_distance {
317 format!(
318 "*, ({} {} {}) AS distance",
319 query.embedding_column, distance_op, embedding_placeholder
320 )
321 } else {
322 "*".to_string()
323 }
324 } else {
325 let cols = query.select_columns.join(", ");
326 if query.include_distance {
327 format!(
328 "{}, ({} {} {}) AS distance",
329 cols, query.embedding_column, distance_op, embedding_placeholder
330 )
331 } else {
332 cols
333 }
334 };
335
336 let where_clause = if let Some(ref clause) = query.where_clause {
338 format!("\nWHERE {clause}")
339 } else {
340 String::new()
341 };
342
343 let order_clause = format!(
345 "\nORDER BY {} {} {}",
346 query.embedding_column, distance_op, embedding_placeholder
347 );
348
349 let limit_clause = format!("\nLIMIT {}", self.placeholder(param_idx));
351 params.push(VectorParam::Int(i64::from(query.limit)));
352 param_idx += 1;
353
354 let offset_clause = if let Some(offset) = query.offset {
356 let clause = format!("\nOFFSET {}", self.placeholder(param_idx));
357 params.push(VectorParam::Int(i64::from(offset)));
358 clause
359 } else {
360 String::new()
361 };
362
363 let sql = format!(
364 "SELECT {}\nFROM {}{}{}{}{}",
365 select_clause, query.table, where_clause, order_clause, limit_clause, offset_clause
366 );
367
368 (sql, params)
369 }
370
371 #[must_use]
380 pub fn insert_one(
381 &self,
382 query: &VectorInsertQuery,
383 values: &[VectorParam],
384 ) -> (String, Vec<VectorParam>) {
385 let columns = query.columns.join(", ");
386
387 let placeholders: Vec<String> = values
388 .iter()
389 .enumerate()
390 .map(|(i, v)| {
391 let ph = self.placeholder(i + 1);
392 if matches!(v, VectorParam::Vector(_)) {
393 format!("{ph}::vector")
394 } else {
395 ph
396 }
397 })
398 .collect();
399
400 let values_clause = placeholders.join(", ");
401
402 let returning_clause = if let Some(ref col) = query.returning {
403 format!("\nRETURNING {col}")
404 } else {
405 String::new()
406 };
407
408 let sql = if query.upsert {
409 let conflict_cols = query.conflict_columns.join(", ");
410
411 let update_cols: Vec<&String> = if query.update_columns.is_empty() {
413 query.columns.iter().filter(|c| !query.conflict_columns.contains(c)).collect()
415 } else {
416 query.update_columns.iter().collect()
417 };
418
419 let update_clause: String = update_cols
420 .iter()
421 .map(|c| format!("{c} = EXCLUDED.{c}"))
422 .collect::<Vec<_>>()
423 .join(", ");
424
425 format!(
426 "INSERT INTO {} ({})\nVALUES ({})\nON CONFLICT ({}) DO UPDATE SET {}{}",
427 query.table, columns, values_clause, conflict_cols, update_clause, returning_clause
428 )
429 } else {
430 format!(
431 "INSERT INTO {} ({})\nVALUES ({}){}",
432 query.table, columns, values_clause, returning_clause
433 )
434 };
435
436 (sql, values.to_vec())
437 }
438
439 #[must_use]
451 pub fn insert_batch(
452 &self,
453 query: &VectorInsertQuery,
454 rows: &[Vec<VectorParam>],
455 ) -> (String, Vec<VectorParam>) {
456 if rows.is_empty() {
457 return (String::new(), Vec::new());
458 }
459
460 let columns = query.columns.join(", ");
461 let cols_per_row = query.columns.len();
462
463 let mut all_params = Vec::new();
464 let mut values_clauses = Vec::new();
465
466 for (row_idx, row) in rows.iter().enumerate() {
467 let base_idx = row_idx * cols_per_row + 1;
468 let placeholders: Vec<String> = row
469 .iter()
470 .enumerate()
471 .map(|(i, v)| {
472 let ph = self.placeholder(base_idx + i);
473 if matches!(v, VectorParam::Vector(_)) {
474 format!("{ph}::vector")
475 } else {
476 ph
477 }
478 })
479 .collect();
480
481 values_clauses.push(format!("({})", placeholders.join(", ")));
482 all_params.extend(row.clone());
483 }
484
485 let returning_clause = if let Some(ref col) = query.returning {
486 format!("\nRETURNING {col}")
487 } else {
488 String::new()
489 };
490
491 let sql = format!(
492 "INSERT INTO {} ({})\nVALUES\n {}{}",
493 query.table,
494 columns,
495 values_clauses.join(",\n "),
496 returning_clause
497 );
498
499 (sql, all_params)
500 }
501
502 #[must_use]
509 pub fn create_index(&self, config: &VectorConfig, table: &str, column: &str) -> Option<String> {
510 config.index_type.index_sql(table, column, config.distance_metric)
511 }
512}