use super::fields::{BaseSchemaField, FieldType};
use crate::{RedisWrite, ToRedisArgs};
mod flat;
mod hnsw;
mod vamana;
pub use flat::*;
pub use hnsw::*;
pub use vamana::*;
#[derive(Debug, Clone)]
pub(crate) enum VectorAlgorithm {
Flat(FlatVectorOptions),
Hnsw(HnswVectorOptions),
Vamana(VamanaVectorOptions),
}
impl VectorAlgorithm {
fn name(&self) -> &'static [u8] {
match self {
Self::Flat(_) => b"FLAT",
Self::Hnsw(_) => b"HNSW",
Self::Vamana(_) => b"SVS-VAMANA",
}
}
fn num_of_args(&self) -> usize {
match self {
Self::Flat(options) => options.num_of_args(),
Self::Hnsw(options) => options.num_of_args(),
Self::Vamana(options) => options.num_of_args(),
}
}
fn write_options<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
match self {
Self::Flat(options) => options.write_redis_args(out),
Self::Hnsw(options) => options.write_redis_args(out),
Self::Vamana(options) => options.write_redis_args(out),
}
}
}
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum VectorType {
Float32,
Float64,
BFloat16,
Float16,
Int8,
UInt8,
}
impl ToRedisArgs for VectorType {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
out.write_arg(match self {
Self::Float32 => b"FLOAT32",
Self::Float64 => b"FLOAT64",
Self::BFloat16 => b"BFLOAT16",
Self::Float16 => b"FLOAT16",
Self::Int8 => b"INT8",
Self::UInt8 => b"UINT8",
});
}
}
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum DistanceMetric {
L2,
IP,
Cosine,
}
impl ToRedisArgs for DistanceMetric {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
out.write_arg(match self {
Self::L2 => b"L2",
Self::IP => b"IP",
Self::Cosine => b"COSINE",
});
}
}
#[derive(Debug, Clone)]
pub(crate) struct VectorFieldCommon {
base: BaseSchemaField,
vector_type: VectorType,
dim: u32,
distance_metric: DistanceMetric,
}
impl VectorFieldCommon {
const NUM_OF_ARGS: usize = 6;
pub(crate) fn alias(mut self, alias: impl Into<String>) -> Self {
self.base = self.base.alias(alias);
self
}
pub(crate) fn index_missing(mut self, index_missing: bool) -> Self {
self.base = self.base.index_missing(index_missing);
self
}
}
#[must_use = "Vector field has no effect unless inserted into a schema"]
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct VectorField {
common: VectorFieldCommon,
algorithm: VectorAlgorithm,
}
impl VectorField {
pub fn alias(mut self, alias: impl Into<String>) -> Self {
self.common = self.common.alias(alias);
self
}
pub fn index_missing(mut self, index_missing: bool) -> Self {
self.common = self.common.index_missing(index_missing);
self
}
}
impl ToRedisArgs for VectorField {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
let common = &self.common;
if let Some(alias) = &common.base.alias {
out.write_arg(b"AS");
alias.write_redis_args(out);
}
common.base.field_type.write_redis_args(out);
out.write_arg(self.algorithm.name());
let attributes_count = VectorFieldCommon::NUM_OF_ARGS + self.algorithm.num_of_args();
attributes_count.write_redis_args(out);
out.write_arg(b"TYPE");
common.vector_type.write_redis_args(out);
out.write_arg(b"DIM");
common.dim.write_redis_args(out);
out.write_arg(b"DISTANCE_METRIC");
common.distance_metric.write_redis_args(out);
self.algorithm.write_options(out);
if common.base.index_missing {
out.write_arg(b"INDEXMISSING");
}
}
}
impl VectorField {
pub fn flat(
vector_type: VectorType,
dim: u32,
distance_metric: DistanceMetric,
) -> FlatVectorFieldBuilder {
FlatVectorFieldBuilder::new(VectorFieldCommon {
base: BaseSchemaField::new(FieldType::Vector),
vector_type,
dim,
distance_metric,
})
}
pub fn hnsw(
vector_type: VectorType,
dim: u32,
distance_metric: DistanceMetric,
) -> HnswVectorFieldBuilder {
HnswVectorFieldBuilder::new(VectorFieldCommon {
base: BaseSchemaField::new(FieldType::Vector),
vector_type,
dim,
distance_metric,
})
}
pub fn vamana(
vector_type: VamanaVectorType,
dim: u32,
distance_metric: DistanceMetric,
) -> VamanaVectorFieldBuilder {
VamanaVectorFieldBuilder::new(VectorFieldCommon {
base: BaseSchemaField::new(FieldType::Vector),
vector_type: vector_type.into(),
dim,
distance_metric,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema;
use crate::search::FtCreateCommand;
#[test]
fn test_zero_dimension_is_sent_to_server() {
let schema = schema! {
"embedding" => VectorField::flat(VectorType::Float32, 0, DistanceMetric::Cosine).build(),
};
let ft_create = FtCreateCommand::new("index", schema);
assert_eq!(
ft_create.into_args(),
"FT.CREATE index SCHEMA embedding VECTOR FLAT 6 TYPE FLOAT32 DIM 0 DISTANCE_METRIC COSINE"
);
}
}