Skip to content

Commit 0b04d2e

Browse files
authored
feat: for Postgres and Qdrant, show notes when vector is stored as json (#794)
1 parent 0165dde commit 0b04d2e

File tree

4 files changed

+69
-14
lines changed

4 files changed

+69
-14
lines changed

examples/face_recognition/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def extract_faces(content: bytes) -> list[FaceBase]:
7575
@cocoindex.op.function(cache=True, behavior_version=1, gpu=True)
7676
def extract_face_embedding(
7777
face: bytes,
78-
) -> cocoindex.Vector[cocoindex.Float32, typing.Literal[128]]:
78+
) -> cocoindex.Vector[cocoindex.Float32]:
7979
"""Extract the embedding of a face."""
8080
img = Image.open(io.BytesIO(face)).convert("RGB")
8181
embedding = face_recognition.face_encodings(

src/ops/targets/postgres.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,12 +426,41 @@ pub struct TableSetupAction {
426426
pub struct SetupStatus {
427427
create_pgvector_extension: bool,
428428
actions: TableSetupAction,
429+
vector_as_jsonb_columns: Vec<(String, ValueType)>,
429430
}
430431

431432
impl SetupStatus {
432433
fn new(desired_state: Option<SetupState>, existing: setup::CombinedState<SetupState>) -> Self {
433434
let table_action =
434435
TableMainSetupAction::from_states(desired_state.as_ref(), &existing, false);
436+
let vector_as_jsonb_columns = desired_state
437+
.as_ref()
438+
.iter()
439+
.flat_map(|s| {
440+
s.columns.value_columns.iter().filter_map(|(name, schema)| {
441+
if let ValueType::Basic(BasicValueType::Vector(vec_schema)) = schema
442+
&& !convertible_to_pgvector(vec_schema)
443+
{
444+
let is_touched = match &table_action.table_upsertion {
445+
Some(TableUpsertionAction::Create { values, .. }) => {
446+
values.contains_key(name)
447+
}
448+
Some(TableUpsertionAction::Update {
449+
columns_to_upsert, ..
450+
}) => columns_to_upsert.contains_key(name),
451+
None => false,
452+
};
453+
if is_touched {
454+
Some((name.clone(), schema.clone()))
455+
} else {
456+
None
457+
}
458+
} else {
459+
None
460+
}
461+
})
462+
})
463+
.collect::<Vec<_>>();
435464
let (indexes_to_delete, indexes_to_create) = desired_state
436465
.as_ref()
437466
.map(|desired| {
@@ -469,6 +498,7 @@ impl SetupStatus {
469498
indexes_to_delete,
470499
indexes_to_create,
471500
},
501+
vector_as_jsonb_columns,
472502
}
473503
}
474504
}
@@ -506,6 +536,13 @@ fn describe_index_spec(index_name: &str, index_spec: &VectorIndexDef) -> String
506536
impl setup::ResourceSetupStatus for SetupStatus {
507537
fn describe_changes(&self) -> Vec<setup::ChangeDescription> {
508538
let mut descriptions = self.actions.table_action.describe_changes();
539+
for (column_name, schema) in self.vector_as_jsonb_columns.iter() {
540+
descriptions.push(setup::ChangeDescription::Note(format!(
541+
"Field `{}` has type `{}`. Only number vector with fixed size is supported by pgvector. It will be stored as `jsonb`.",
542+
column_name,
543+
schema
544+
)));
545+
}
509546
if self.create_pgvector_extension {
510547
descriptions.push(setup::ChangeDescription::Action(
511548
"Create pg_vector extension (if not exists)".to_string(),

src/ops/targets/qdrant.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ struct VectorDef {
9595
struct SetupState {
9696
#[serde(default)]
9797
vectors: BTreeMap<String, VectorDef>,
98+
99+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
100+
unsupported_vector_fields: Vec<(String, ValueType)>,
98101
}
99102

100103
#[derive(Debug)]
@@ -131,6 +134,12 @@ impl setup::ResourceSetupStatus for SetupStatus {
131134
format!(" with vectors: {vector_descriptions}")
132135
}
133136
)));
137+
for (name, schema) in add_collection.unsupported_vector_fields.iter() {
138+
result.push(setup::ChangeDescription::Note(format!(
139+
"Field `{}` has type `{}`. Only number vector with fixed size is supported by Qdrant. It will be stored in payload.",
140+
name, schema
141+
)));
142+
}
134143
}
135144
result
136145
}
@@ -311,6 +320,7 @@ impl StorageFactoryBase for Factory {
311320

312321
let mut fields_info = Vec::<FieldInfo>::new();
313322
let mut vector_def = BTreeMap::<String, VectorDef>::new();
323+
let mut unsupported_vector_fields = Vec::<(String, ValueType)>::new();
314324

315325
for field in d.value_fields_schema.iter() {
316326
let vector_size = parse_supported_vector_size(&field.value_type.typ);
@@ -326,6 +336,12 @@ impl StorageFactoryBase for Factory {
326336
metric: DEFAULT_VECTOR_SIMILARITY_METRIC,
327337
},
328338
);
339+
} else if matches!(
340+
&field.value_type.typ,
341+
schema::ValueType::Basic(schema::BasicValueType::Vector(_))
342+
) {
343+
// This is a vector field but not supported by Qdrant
344+
unsupported_vector_fields.push((field.name.clone(), field.value_type.typ.clone()));
329345
}
330346
}
331347

@@ -370,6 +386,7 @@ impl StorageFactoryBase for Factory {
370386
},
371387
desired_setup_state: SetupState {
372388
vectors: vector_def,
389+
unsupported_vector_fields,
373390
},
374391
})
375392
})
@@ -398,16 +415,12 @@ impl StorageFactoryBase for Factory {
398415
_auth_registry: &Arc<AuthRegistry>,
399416
) -> Result<Self::SetupStatus> {
400417
let desired_exists = desired.is_some();
401-
let add_collection = desired
402-
.filter(|state| {
403-
!existing.always_exists()
404-
|| existing
405-
.possible_versions()
406-
.any(|v| v.vectors != state.vectors)
407-
})
408-
.map(|state| SetupState {
409-
vectors: state.vectors,
410-
});
418+
let add_collection = desired.filter(|state| {
419+
!existing.always_exists()
420+
|| existing
421+
.possible_versions()
422+
.any(|v| v.vectors != state.vectors)
423+
});
411424
let delete_collection = existing.possible_versions().next().is_some()
412425
&& (!desired_exists || add_collection.is_some());
413426
Ok(SetupStatus {

src/setup/states.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,16 +305,21 @@ impl<K, S, C: ResourceSetupStatus> std::fmt::Display for ResourceSetupInfo<K, S,
305305
let changes = setup_status.describe_changes();
306306
if !changes.is_empty() {
307307
let mut f = indented(f).with_str(INDENT);
308-
writeln!(f, "{}", "TODO:".color(AnsiColors::BrightBlack))?;
308+
writeln!(f, "")?;
309309
for change in changes {
310310
match change {
311311
ChangeDescription::Action(action) => {
312-
writeln!(f, " - {}", action.color(AnsiColors::BrightBlack))?;
312+
writeln!(
313+
f,
314+
"{} {}",
315+
"TODO:".color(AnsiColors::BrightBlack).bold(),
316+
action.color(AnsiColors::BrightBlack)
317+
)?;
313318
}
314319
ChangeDescription::Note(note) => {
315320
writeln!(
316321
f,
317-
" {} {}",
322+
"{} {}",
318323
"NOTE:".color(AnsiColors::Yellow).bold(),
319324
note.color(AnsiColors::Yellow)
320325
)?;

0 commit comments

Comments
 (0)