mod channel_pool;
pub mod client;
pub mod prelude;
#[allow(clippy::all)]
#[rustfmt::skip]
pub mod arc_vector;
pub mod filters;
#[cfg(feature = "serde")]
pub mod serde;
use arc_vector::{value::Kind::*, ListValue, RetrievedPoint, ScoredPoint, Struct, Value};
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
static NULL_VALUE: Value = Value {
kind: Some(NullValue(0)),
};
macro_rules! get_payload {
($ty:ty) => {
impl $ty {
#[doc = concat!("use arc_vector_rust::arc_vector::", stringify!($ty), ";")]
#[doc = concat!("let point = ", stringify!($ty), "::default();")]
pub fn get(&self, key: &str) -> &Value {
self.payload.get(key).unwrap_or(&NULL_VALUE)
}
}
};
}
get_payload!(RetrievedPoint);
get_payload!(ScoredPoint);
macro_rules! extract {
($kind:ident, $check:ident) => {
#[doc = stringify!($kind)]
pub fn $check(&self) -> bool {
matches!(self.kind, Some($kind(_)))
}
};
($kind:ident, $check:ident, $extract:ident, $ty:ty) => {
extract!($kind, $check);
#[doc = stringify!($kind)]
pub fn $extract(&self) -> Option<$ty> {
if let Some($kind(v)) = self.kind {
Some(v)
} else {
None
}
}
};
($kind:ident, $check:ident, $extract:ident, ref $ty:ty) => {
extract!($kind, $check);
#[doc = stringify!($kind)]
pub fn $extract(&self) -> Option<&$ty> {
if let Some($kind(v)) = &self.kind {
Some(v)
} else {
None
}
}
};
}
impl Value {
extract!(NullValue, is_null);
extract!(BoolValue, is_bool, as_bool, bool);
extract!(IntegerValue, is_integer, as_integer, i64);
extract!(DoubleValue, is_double, as_double, f64);
extract!(StringValue, is_str, as_str, ref String);
extract!(ListValue, is_list, as_list, ref [Value]);
extract!(StructValue, is_struct, as_struct, ref Struct);
#[cfg(feature = "serde")]
pub fn into_json(self) -> serde_json::Value {
use serde_json::Value as JsonValue;
match self.kind {
Some(BoolValue(b)) => JsonValue::Bool(b),
Some(IntegerValue(i)) => JsonValue::from(i),
Some(DoubleValue(d)) => JsonValue::from(d),
Some(StringValue(s)) => JsonValue::String(s),
Some(ListValue(vs)) => vs.into_iter().map(Value::into_json).collect(),
Some(StructValue(s)) => s
.fields
.into_iter()
.map(|(k, v)| (k, v.into_json()))
.collect(),
Some(NullValue(_)) | None => JsonValue::Null,
}
}
}
#[cfg(feature = "serde")]
impl From<Value> for serde_json::Value {
fn from(value: Value) -> Self {
value.into_json()
}
}
impl Display for Value {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.kind {
Some(BoolValue(b)) => write!(f, "{}", b),
Some(IntegerValue(i)) => write!(f, "{}", i),
Some(DoubleValue(v)) => write!(f, "{}", v),
Some(StringValue(s)) => write!(f, "{:?}", s),
Some(ListValue(vs)) => {
let mut i = vs.values.iter();
write!(f, "[")?;
if let Some(first) = i.next() {
write!(f, "{}", first)?;
for v in i {
write!(f, ",{}", v)?;
}
}
write!(f, "]")
}
Some(StructValue(s)) => {
let mut i = s.fields.iter();
write!(f, "{{")?;
if let Some((key, value)) = i.next() {
write!(f, "{:?}:{}", key, value)?;
for (key, value) in i {
write!(f, ",{:?}:{}", key, value)?;
}
}
write!(f, "}}")
}
_ => write!(f, "null"),
}
}
}
pub mod error {
use std::marker::PhantomData;
pub struct NotA<T> {
marker: PhantomData<T>,
}
impl<T> Default for NotA<T> {
fn default() -> Self {
NotA {
marker: PhantomData,
}
}
}
}
use error::NotA;
macro_rules! not_a {
($ty:ty) => {
impl Error for NotA<$ty> {}
impl Debug for NotA<$ty> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl Display for NotA<$ty> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(concat!("not a ", stringify!($ty)))
}
}
};
}
macro_rules! impl_try_from {
($ty:ty, $key:ident) => {
not_a!($ty);
impl std::convert::TryFrom<Value> for $ty {
type Error = NotA<$ty>;
fn try_from(v: Value) -> Result<Self, NotA<$ty>> {
if let Some($key(t)) = v.kind {
Ok(t)
} else {
Err(NotA::default())
}
}
}
};
}
impl_try_from!(bool, BoolValue);
impl_try_from!(i64, IntegerValue);
impl_try_from!(f64, DoubleValue);
impl_try_from!(String, StringValue);
not_a!(ListValue);
not_a!(Struct);
impl Value {
pub fn iter_list(&self) -> Result<impl Iterator<Item = &Value>, NotA<ListValue>> {
if let Some(ListValue(values)) = &self.kind {
Ok(values.iter())
} else {
Err(NotA::default())
}
}
pub fn get_struct(&self, key: &str) -> Result<&Value, NotA<Struct>> {
if let Some(StructValue(Struct { fields })) = &self.kind {
Ok(fields.get(key).unwrap_or(&NULL_VALUE))
} else {
Err(NotA::default())
}
}
}
impl std::ops::Deref for ListValue {
type Target = [Value];
fn deref(&self) -> &[Value] {
&self.values
}
}
impl IntoIterator for ListValue {
type Item = Value;
type IntoIter = std::vec::IntoIter<Value>;
fn into_iter(self) -> Self::IntoIter {
self.values.into_iter()
}
}
impl ListValue {
pub fn iter(&self) -> std::slice::Iter<'_, Value> {
self.values.iter()
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use crate::arc_vector::value::Kind::*;
use crate::arc_vector::vectors_config::Config;
use crate::arc_vector::{
CreateFieldIndexCollection, FieldType, ListValue, Struct, Value, VectorParams,
VectorsConfig,
};
use std::collections::HashMap;
#[test]
fn display() {
let value = Value {
kind: Some(StructValue(Struct {
fields: [
("text", StringValue("Hi ArcVector!".into())),
("int", IntegerValue(42)),
("float", DoubleValue(1.23)),
(
"list",
ListValue(ListValue {
values: vec![Value {
kind: Some(NullValue(0)),
}],
}),
),
(
"struct",
StructValue(Struct {
fields: [(
"bool".into(),
Value {
kind: Some(BoolValue(true)),
},
)]
.into(),
}),
),
]
.into_iter()
.map(|(k, v)| (k.into(), Value { kind: Some(v) }))
.collect(),
})),
};
let text = format!("{}", value);
assert!([
"\"float\":1.23",
"\"list\":[null]",
"\"struct\":{\"bool\":true}",
"\"int\":42",
"\"text\":\"Hi ArcVector!\""
]
.into_iter()
.all(|item| text.contains(item)));
}
#[tokio::test]
async fn test_arc_vector_queries() -> anyhow::Result<()> {
let config = ArcVectorClientConfig::from_url("http://localhost:6334");
let client = ArcVectorClient::new(Some(config))?;
let health = client.health_check().await?;
println!("{:?}", health);
let collections_list = client.list_collections().await?;
println!("{:?}", collections_list);
let collection_name = "test";
client.delete_collection(collection_name).await?;
client
.create_collection(&CreateCollection {
collection_name: collection_name.into(),
vectors_config: Some(VectorsConfig {
config: Some(Config::Params(VectorParams {
size: 10,
distance: Distance::Cosine.into(),
hnsw_config: None,
quantization_config: None,
on_disk: None,
})),
}),
..Default::default()
})
.await?;
let collection_info = client.collection_info(collection_name).await?;
println!("{:#?}", collection_info);
let mut sub_payload = Payload::new();
sub_payload.insert("foo", "Not bar");
let payload: Payload = vec![
("foo", "Bar".into()),
("bar", 12.into()),
("sub_payload", sub_payload.into()),
]
.into_iter()
.collect::<HashMap<_, Value>>()
.into();
let points = vec![PointStruct::new(0, vec![12.; 10], payload)];
client
.upsert_points_blocking(collection_name, points, None)
.await?;
let search_result = client
.search_points(&SearchPoints {
collection_name: collection_name.into(),
vector: vec![11.; 10],
filter: None,
limit: 10,
with_payload: Some(true.into()),
params: None,
score_threshold: None,
offset: None,
vector_name: None,
with_vectors: None,
read_consistency: None,
})
.await?;
eprintln!("search_result = {:#?}", search_result);
let new_payload: Payload = vec![("foo", "BAZ".into())]
.into_iter()
.collect::<HashMap<_, Value>>()
.into();
client
.set_payload(collection_name, &vec![0.into()].into(), new_payload, None)
.await?;
client
.delete_payload_blocking(
collection_name,
&vec![0.into()].into(),
vec!["sub_payload".to_string()],
None,
)
.await?;
let points = client
.get_points(collection_name, &[0.into()], Some(true), Some(true), None)
.await?;
assert_eq!(points.result.len(), 1);
let point = points.result[0].clone();
assert!(point.payload.contains_key("foo"));
assert!(!point.payload.contains_key("sub_payload"));
client
.delete_points(collection_name, &vec![0.into()].into(), None)
.await?;
client
.with_points_client(|mut client| async move {
client
.create_field_index(CreateFieldIndexCollection {
collection_name: collection_name.to_string(),
wait: None,
field_name: "foo".to_string(),
field_type: Some(FieldType::Keyword as i32),
field_index_params: None,
ordering: None,
})
.await
})
.await?;
client.create_snapshot(collection_name).await?;
#[cfg(feature = "download_snapshots")]
client
.download_snapshot("test.tar", collection_name, None, None)
.await?;
Ok(())
}
}