@@ -117,6 +117,7 @@ pub(crate) const BLOB_FIELD_OPTION: &str = "blob-field";
117117pub ( crate ) const BLOB_DESCRIPTOR_FIELD_OPTION : & str = "blob-descriptor-field" ;
118118pub ( crate ) const BLOB_VIEW_FIELD_OPTION : & str = "blob-view-field" ;
119119pub const BLOB_VIEW_RESOLVE_ENABLED_OPTION : & str = "blob-view.resolve.enabled" ;
120+ const PK_VECTOR_INDEX_COLUMNS_OPTION : & str = "pk-vector.index.columns" ;
120121
121122/// Merge engine for primary-key tables.
122123///
@@ -1067,6 +1068,72 @@ impl<'a> CoreOptions<'a> {
10671068 } )
10681069 . unwrap_or_default ( )
10691070 }
1071+
1072+ /// True when the PK-vector index column option key is present (regardless of value).
1073+ pub fn primary_key_vector_index_enabled ( & self ) -> bool {
1074+ self . options . contains_key ( PK_VECTOR_INDEX_COLUMNS_OPTION )
1075+ }
1076+
1077+ /// The configured PK-vector index columns, split on ',' and trimmed. Errors when
1078+ /// the key is present but resolves to no non-blank column.
1079+ pub fn primary_key_vector_index_columns ( & self ) -> crate :: Result < Vec < String > > {
1080+ let raw = self
1081+ . options
1082+ . get ( PK_VECTOR_INDEX_COLUMNS_OPTION )
1083+ . ok_or_else ( || crate :: Error :: ConfigInvalid {
1084+ message : "pk-vector.index.columns is not set" . to_string ( ) ,
1085+ } ) ?;
1086+ let columns: Vec < String > = raw
1087+ . split ( ',' )
1088+ . map ( |c| c. trim ( ) . to_string ( ) )
1089+ . filter ( |c| !c. is_empty ( ) )
1090+ . collect ( ) ;
1091+ if columns. is_empty ( ) {
1092+ return Err ( crate :: Error :: ConfigInvalid {
1093+ message : "pk-vector.index.columns is set but names no column" . to_string ( ) ,
1094+ } ) ;
1095+ }
1096+ Ok ( columns)
1097+ }
1098+
1099+ /// The single PK-vector index column. The first release supports exactly one.
1100+ pub fn primary_key_vector_index_column ( & self ) -> crate :: Result < String > {
1101+ let mut columns = self . primary_key_vector_index_columns ( ) ?;
1102+ if columns. len ( ) != 1 {
1103+ return Err ( crate :: Error :: ConfigInvalid {
1104+ message : format ! (
1105+ "pk-vector.index.columns must name exactly one column, got {}" ,
1106+ columns. len( )
1107+ ) ,
1108+ } ) ;
1109+ }
1110+ Ok ( columns. remove ( 0 ) )
1111+ }
1112+
1113+ /// The index type for a PK-vector column. Required — planning and the index reader
1114+ /// both need it, so an absent value is a hard error rather than a guessed default.
1115+ pub fn primary_key_vector_index_type ( & self , col : & str ) -> crate :: Result < String > {
1116+ self . options
1117+ . get ( & format ! ( "fields.{col}.pk-vector.index.type" ) )
1118+ . map ( |v| v. trim ( ) . to_string ( ) )
1119+ . filter ( |v| !v. is_empty ( ) )
1120+ . ok_or_else ( || crate :: Error :: ConfigInvalid {
1121+ message : format ! ( "fields.{col}.pk-vector.index.type is required but not set" ) ,
1122+ } )
1123+ }
1124+
1125+ /// The distance metric name for a PK-vector column, defaulting to inner_product.
1126+ /// Validated against the supported metrics; an unknown value is a hard error.
1127+ pub fn primary_key_vector_distance_metric ( & self , col : & str ) -> crate :: Result < String > {
1128+ let raw = self
1129+ . options
1130+ . get ( & format ! ( "fields.{col}.pk-vector.distance.metric" ) )
1131+ . map ( |v| v. trim ( ) . to_string ( ) )
1132+ . unwrap_or_else ( || "inner_product" . to_string ( ) ) ;
1133+ // Validate now (fail-loud) without exposing the crate-private metric enum.
1134+ crate :: vindex:: pkvector:: metric:: VectorSearchMetric :: parse ( & raw ) ?;
1135+ Ok ( raw)
1136+ }
10701137}
10711138
10721139/// Parse a memory size string to bytes using binary (1024-based) semantics.
@@ -1856,4 +1923,88 @@ mod tests {
18561923 let opts = CoreOptions :: new ( & options) ;
18571924 assert ! ( !opts. ignore_update_before( ) ) ;
18581925 }
1926+
1927+ #[ test]
1928+ fn test_pk_vector_index_disabled_by_default ( ) {
1929+ let opts = HashMap :: new ( ) ;
1930+ assert ! ( !CoreOptions :: new( & opts) . primary_key_vector_index_enabled( ) ) ;
1931+ }
1932+
1933+ #[ test]
1934+ fn test_pk_vector_single_column_and_type_and_metric ( ) {
1935+ let opts = HashMap :: from ( [
1936+ (
1937+ "pk-vector.index.columns" . to_string ( ) ,
1938+ " embedding " . to_string ( ) ,
1939+ ) ,
1940+ (
1941+ "fields.embedding.pk-vector.index.type" . to_string ( ) ,
1942+ "ivf-flat" . to_string ( ) ,
1943+ ) ,
1944+ (
1945+ "fields.embedding.pk-vector.distance.metric" . to_string ( ) ,
1946+ "Inner-Product" . to_string ( ) ,
1947+ ) ,
1948+ ] ) ;
1949+ let co = CoreOptions :: new ( & opts) ;
1950+ assert ! ( co. primary_key_vector_index_enabled( ) ) ;
1951+ assert_eq ! ( co. primary_key_vector_index_column( ) . unwrap( ) , "embedding" ) ;
1952+ assert_eq ! (
1953+ co. primary_key_vector_index_type( "embedding" ) . unwrap( ) ,
1954+ "ivf-flat"
1955+ ) ;
1956+ assert_eq ! (
1957+ co. primary_key_vector_distance_metric( "embedding" ) . unwrap( ) ,
1958+ "Inner-Product"
1959+ ) ;
1960+ }
1961+
1962+ #[ test]
1963+ fn test_pk_vector_metric_defaults_to_inner_product ( ) {
1964+ let opts = HashMap :: from ( [ ( "pk-vector.index.columns" . to_string ( ) , "e" . to_string ( ) ) ] ) ;
1965+ assert_eq ! (
1966+ CoreOptions :: new( & opts)
1967+ . primary_key_vector_distance_metric( "e" )
1968+ . unwrap( ) ,
1969+ "inner_product"
1970+ ) ;
1971+ }
1972+
1973+ #[ test]
1974+ fn test_pk_vector_unknown_metric_errors ( ) {
1975+ let opts = HashMap :: from ( [
1976+ ( "pk-vector.index.columns" . to_string ( ) , "e" . to_string ( ) ) ,
1977+ (
1978+ "fields.e.pk-vector.distance.metric" . to_string ( ) ,
1979+ "manhattan" . to_string ( ) ,
1980+ ) ,
1981+ ] ) ;
1982+ assert ! ( CoreOptions :: new( & opts)
1983+ . primary_key_vector_distance_metric( "e" )
1984+ . is_err( ) ) ;
1985+ }
1986+
1987+ #[ test]
1988+ fn test_pk_vector_empty_columns_errors ( ) {
1989+ let opts = HashMap :: from ( [ ( "pk-vector.index.columns" . to_string ( ) , " , " . to_string ( ) ) ] ) ;
1990+ let co = CoreOptions :: new ( & opts) ;
1991+ assert ! ( co. primary_key_vector_index_enabled( ) ) ; // key present
1992+ assert ! ( co. primary_key_vector_index_columns( ) . is_err( ) ) ;
1993+ }
1994+
1995+ #[ test]
1996+ fn test_pk_vector_multiple_columns_unsupported ( ) {
1997+ let opts = HashMap :: from ( [ ( "pk-vector.index.columns" . to_string ( ) , "a,b" . to_string ( ) ) ] ) ;
1998+ assert ! ( CoreOptions :: new( & opts)
1999+ . primary_key_vector_index_column( )
2000+ . is_err( ) ) ;
2001+ }
2002+
2003+ #[ test]
2004+ fn test_pk_vector_index_type_absent_errors ( ) {
2005+ let opts = HashMap :: from ( [ ( "pk-vector.index.columns" . to_string ( ) , "e" . to_string ( ) ) ] ) ;
2006+ assert ! ( CoreOptions :: new( & opts)
2007+ . primary_key_vector_index_type( "e" )
2008+ . is_err( ) ) ;
2009+ }
18592010}
0 commit comments