@@ -648,9 +648,14 @@ impl FromCursor for ColTypeOption {
648648 fn from_cursor ( cursor : & mut Cursor < & [ u8 ] > , version : Version ) -> error:: Result < ColTypeOption > {
649649 let id = ColType :: from_cursor ( cursor, version) ?;
650650 let value = match id {
651- ColType :: Custom => Some ( ColTypeOptionValue :: CString (
652- from_cursor_str ( cursor) ?. to_string ( ) ,
653- ) ) ,
651+ ColType :: Custom => {
652+ let class_name = from_cursor_str ( cursor) ?. to_string ( ) ;
653+ if let Some ( vec_info) = parse_vector_class_name ( & class_name) {
654+ Some ( ColTypeOptionValue :: CVector ( vec_info. 0 , vec_info. 1 ) )
655+ } else {
656+ Some ( ColTypeOptionValue :: CString ( class_name) )
657+ }
658+ }
654659 ColType :: Set => {
655660 let col_type = ColTypeOption :: from_cursor ( cursor, version) ?;
656661 Some ( ColTypeOptionValue :: CSet ( Box :: new ( col_type) ) )
@@ -691,6 +696,9 @@ pub enum ColTypeOptionValue {
691696 UdtType ( CUdt ) ,
692697 TupleType ( CTuple ) ,
693698 CMap ( Box < ColTypeOption > , Box < ColTypeOption > ) ,
699+ /// Vector type parsed from Custom class name: element type name + dimensions.
700+ /// e.g. VectorType(FloatType, 768) → CVector("FloatType", 768)
701+ CVector ( String , u16 ) ,
694702}
695703
696704impl Serialize for ColTypeOptionValue {
@@ -707,10 +715,47 @@ impl Serialize for ColTypeOptionValue {
707715 v1. serialize ( cursor, version) ;
708716 v2. serialize ( cursor, version) ;
709717 }
718+ Self :: CVector ( elem_type, dimensions) => {
719+ // Serialize as Custom type class name string
720+ let class_name = format ! (
721+ "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.{} , {})" ,
722+ elem_type, dimensions
723+ ) ;
724+ serialize_str ( cursor, & class_name, version) ;
725+ }
710726 }
711727 }
712728}
713729
730+ const VECTOR_TYPE_PREFIX : & str = "org.apache.cassandra.db.marshal.VectorType(" ;
731+
732+ /// Parse a Custom type class name to detect VectorType.
733+ ///
734+ /// Cassandra 5.0 sends vector columns as Custom (0x0000) with class name:
735+ /// `org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType , 4)`
736+ ///
737+ /// Returns Some((element_type_short_name, dimensions)) if it's a VectorType.
738+ fn parse_vector_class_name ( class_name : & str ) -> Option < ( String , u16 ) > {
739+ let inner = class_name
740+ . strip_prefix ( VECTOR_TYPE_PREFIX ) ?
741+ . strip_suffix ( ')' ) ?;
742+
743+ // Split on last comma — format is "element_type , dimensions"
744+ let ( elem_str, dim_str) = if let Some ( pos) = inner. rfind ( ',' ) {
745+ ( & inner[ ..pos] , inner[ pos + 1 ..] . trim ( ) )
746+ } else {
747+ return None ;
748+ } ;
749+
750+ let elem_str = elem_str. trim ( ) ;
751+
752+ // Extract short type name from fully qualified class name
753+ let elem_short = elem_str. rsplit ( '.' ) . next ( ) . unwrap_or ( elem_str) . to_string ( ) ;
754+
755+ let dimensions: u16 = dim_str. parse ( ) . ok ( ) ?;
756+ Some ( ( elem_short, dimensions) )
757+ }
758+
714759/// User defined type.
715760#[ derive( Debug , Clone , PartialEq , Ord , PartialOrd , Eq , Hash ) ]
716761pub struct CUdt {
@@ -1617,3 +1662,96 @@ mod schema_change {
16171662 test_encode_decode ( bytes, expected) ;
16181663 }
16191664}
1665+
1666+ #[ cfg( test) ]
1667+ mod vector_type {
1668+ use super :: * ;
1669+
1670+ #[ test]
1671+ fn parse_vector_float_4 ( ) {
1672+ let class_name =
1673+ "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType , 4)" ;
1674+ let ( elem, dims) = parse_vector_class_name ( class_name) . unwrap ( ) ;
1675+ assert_eq ! ( elem, "FloatType" ) ;
1676+ assert_eq ! ( dims, 4 ) ;
1677+ }
1678+
1679+ #[ test]
1680+ fn parse_vector_float_768 ( ) {
1681+ let class_name =
1682+ "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType , 768)" ;
1683+ let ( elem, dims) = parse_vector_class_name ( class_name) . unwrap ( ) ;
1684+ assert_eq ! ( elem, "FloatType" ) ;
1685+ assert_eq ! ( dims, 768 ) ;
1686+ }
1687+
1688+ #[ test]
1689+ fn parse_vector_double ( ) {
1690+ let class_name =
1691+ "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.DoubleType , 3)" ;
1692+ let ( elem, dims) = parse_vector_class_name ( class_name) . unwrap ( ) ;
1693+ assert_eq ! ( elem, "DoubleType" ) ;
1694+ assert_eq ! ( dims, 3 ) ;
1695+ }
1696+
1697+ #[ test]
1698+ fn parse_vector_compact_format ( ) {
1699+ let class_name =
1700+ "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType,4)" ;
1701+ let ( elem, dims) = parse_vector_class_name ( class_name) . unwrap ( ) ;
1702+ assert_eq ! ( elem, "FloatType" ) ;
1703+ assert_eq ! ( dims, 4 ) ;
1704+ }
1705+
1706+ #[ test]
1707+ fn parse_non_vector_returns_none ( ) {
1708+ assert ! ( parse_vector_class_name( "org.apache.cassandra.db.marshal.UTF8Type" ) . is_none( ) ) ;
1709+ assert ! ( parse_vector_class_name( "" ) . is_none( ) ) ;
1710+ assert ! ( parse_vector_class_name( "not a type" ) . is_none( ) ) ;
1711+ }
1712+
1713+ #[ test]
1714+ fn custom_type_with_vector_class_name_becomes_cvector ( ) {
1715+ // Simulate what from_cursor would produce for Custom(VectorType(...))
1716+ let class_name = "org.apache.cassandra.db.marshal.VectorType(org.apache.cassandra.db.marshal.FloatType , 4)" ;
1717+
1718+ // Build wire bytes: [0x0000 (Custom)][string class_name]
1719+ let mut buf = Vec :: new ( ) ;
1720+ buf. extend_from_slice ( & 0x0000u16 . to_be_bytes ( ) ) ; // Custom type ID
1721+ buf. extend_from_slice ( & ( class_name. len ( ) as u16 ) . to_be_bytes ( ) ) ;
1722+ buf. extend_from_slice ( class_name. as_bytes ( ) ) ;
1723+
1724+ let mut cursor = Cursor :: new ( buf. as_slice ( ) ) ;
1725+ let option = ColTypeOption :: from_cursor ( & mut cursor, Version :: V4 ) . unwrap ( ) ;
1726+
1727+ assert_eq ! ( option. id, ColType :: Custom ) ;
1728+ match option. value {
1729+ Some ( ColTypeOptionValue :: CVector ( elem, dims) ) => {
1730+ assert_eq ! ( elem, "FloatType" ) ;
1731+ assert_eq ! ( dims, 4 ) ;
1732+ }
1733+ other => panic ! ( "expected CVector, got {:?}" , other) ,
1734+ }
1735+ }
1736+
1737+ #[ test]
1738+ fn custom_type_non_vector_stays_cstring ( ) {
1739+ let class_name = "org.apache.cassandra.db.marshal.UTF8Type" ;
1740+
1741+ let mut buf = Vec :: new ( ) ;
1742+ buf. extend_from_slice ( & 0x0000u16 . to_be_bytes ( ) ) ;
1743+ buf. extend_from_slice ( & ( class_name. len ( ) as u16 ) . to_be_bytes ( ) ) ;
1744+ buf. extend_from_slice ( class_name. as_bytes ( ) ) ;
1745+
1746+ let mut cursor = Cursor :: new ( buf. as_slice ( ) ) ;
1747+ let option = ColTypeOption :: from_cursor ( & mut cursor, Version :: V4 ) . unwrap ( ) ;
1748+
1749+ assert_eq ! ( option. id, ColType :: Custom ) ;
1750+ match option. value {
1751+ Some ( ColTypeOptionValue :: CString ( s) ) => {
1752+ assert_eq ! ( s, class_name) ;
1753+ }
1754+ other => panic ! ( "expected CString, got {:?}" , other) ,
1755+ }
1756+ }
1757+ }
0 commit comments