This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-rust.git


The following commit(s) were added to refs/heads/main by this push:
     new 9cd773a8 feat(vindex): add DiskANN and IVF-SQ/RQ support (#726)
9cd773a8 is described below

commit 9cd773a87426b47c3961e4a079758503f0b218ca
Author: jerry <[email protected]>
AuthorDate: Tue Aug 18 15:16:35 2026 +0800

    feat(vindex): add DiskANN and IVF-SQ/RQ support (#726)
---
 crates/integrations/datafusion/src/procedures.rs   |   2 +-
 crates/integrations/datafusion/tests/procedures.rs |   2 +-
 crates/paimon/src/table/global_index_types.rs      |  22 +-
 crates/paimon/src/table/vector_search_builder.rs   |  42 ++-
 crates/paimon/src/vindex/mod.rs                    | 403 +++++++++++++++++++--
 crates/paimon/src/vindex/reader.rs                 | 204 +++++++++--
 docs/src/sql.md                                    | 119 +++++-
 7 files changed, 714 insertions(+), 80 deletions(-)

diff --git a/crates/integrations/datafusion/src/procedures.rs 
b/crates/integrations/datafusion/src/procedures.rs
index 672300ae..91074351 100644
--- a/crates/integrations/datafusion/src/procedures.rs
+++ b/crates/integrations/datafusion/src/procedures.rs
@@ -585,7 +585,7 @@ async fn proc_create_global_index(
         // Echo the raw argument, not the normalized one, so a typo stays 
visible.
         return Err(DataFusionError::NotImplemented(format!(
             "create_global_index only supports index_type => 'btree', 
'bitmap', or vindex types \
-             ('ivf-flat', 'ivf-pq'), got '{index_type_arg}'"
+             ('ivf-flat', 'ivf-pq', 'ivf-sq', 'ivf-rq', 'diskann'), got 
'{index_type_arg}'"
         )));
     }
     ok_result(ctx)
diff --git a/crates/integrations/datafusion/tests/procedures.rs 
b/crates/integrations/datafusion/tests/procedures.rs
index 2f0c2d1f..458567d8 100644
--- a/crates/integrations/datafusion/tests/procedures.rs
+++ b/crates/integrations/datafusion/tests/procedures.rs
@@ -198,7 +198,7 @@ async fn 
test_create_global_index_rejects_unsupported_index_types() {
                     index_type => '{index_type}'\
                 )"
             ),
-            "only supports index_type => 'btree', 'bitmap', or vindex types 
('ivf-flat', 'ivf-pq')",
+            "only supports index_type => 'btree', 'bitmap', or vindex types 
('ivf-flat', 'ivf-pq', 'ivf-sq', 'ivf-rq', 'diskann')",
         )
         .await;
     }
diff --git a/crates/paimon/src/table/global_index_types.rs 
b/crates/paimon/src/table/global_index_types.rs
index eb83ad72..2fa65e54 100644
--- a/crates/paimon/src/table/global_index_types.rs
+++ b/crates/paimon/src/table/global_index_types.rs
@@ -16,7 +16,10 @@
 // under the License.
 
 use crate::lumina::{is_lumina_index_type, LUMINA_IDENTIFIER};
-use crate::vindex::{is_vindex_index_type, IVF_FLAT_IDENTIFIER, 
IVF_PQ_IDENTIFIER};
+use crate::vindex::{
+    is_vindex_index_type, DISKANN_IDENTIFIER, IVF_FLAT_IDENTIFIER, 
IVF_PQ_IDENTIFIER,
+    IVF_RQ_IDENTIFIER, IVF_SQ_IDENTIFIER,
+};
 
 pub(crate) const BTREE_GLOBAL_INDEX_TYPE: &str = "btree";
 pub(crate) const BITMAP_GLOBAL_INDEX_TYPE: &str = "bitmap";
@@ -35,7 +38,7 @@ pub(crate) fn normalize_sorted_global_index_type(index_type: 
&str) -> Option<&'s
 /// Used verbatim in the unsupported-type error of both the builder and the
 /// DataFusion procedure so the two messages stay in sync.
 pub const SUPPORTED_GLOBAL_INDEX_TYPES_FOR_DROP: &str =
-    "btree, bitmap, lumina, lumina-vector-ann, ivf-flat, ivf-pq";
+    "btree, bitmap, lumina, lumina-vector-ann, ivf-flat, ivf-pq, ivf-sq, 
ivf-rq, diskann";
 
 /// Canonicalize any supported global index type to a stable `&'static str`, or
 /// `None` if unsupported. Case-insensitive. Order: sorted -> lumina -> vindex.
@@ -66,6 +69,9 @@ fn canonical_vindex_identifier(lowered: &str) -> 
Option<&'static str> {
     match lowered {
         IVF_FLAT_IDENTIFIER => Some(IVF_FLAT_IDENTIFIER),
         IVF_PQ_IDENTIFIER => Some(IVF_PQ_IDENTIFIER),
+        IVF_SQ_IDENTIFIER => Some(IVF_SQ_IDENTIFIER),
+        IVF_RQ_IDENTIFIER => Some(IVF_RQ_IDENTIFIER),
+        DISKANN_IDENTIFIER => Some(DISKANN_IDENTIFIER),
         _ => None,
     }
 }
@@ -109,6 +115,18 @@ mod tests {
             normalize_global_index_type_for_drop("IVF-PQ"),
             Some("ivf-pq")
         );
+        assert_eq!(
+            normalize_global_index_type_for_drop("IVF-SQ"),
+            Some("ivf-sq")
+        );
+        assert_eq!(
+            normalize_global_index_type_for_drop("IVF-RQ"),
+            Some("ivf-rq")
+        );
+        assert_eq!(
+            normalize_global_index_type_for_drop("DiskANN"),
+            Some("diskann")
+        );
     }
 
     #[test]
diff --git a/crates/paimon/src/table/vector_search_builder.rs 
b/crates/paimon/src/table/vector_search_builder.rs
index 5919b4c7..12e60d3b 100644
--- a/crates/paimon/src/table/vector_search_builder.rs
+++ b/crates/paimon/src/table/vector_search_builder.rs
@@ -2085,10 +2085,20 @@ fn pk_vector_query_dimension(
                 let dimension = 
LuminaVectorIndexOptions::new(&merged)?.dimension;
                 Ok(Some(dimension as usize))
             } else {
+                let mut dimension_options = HashMap::new();
+                for key in [
+                    "dimension".to_string(),
+                    format!("{index_type}.dimension"),
+                    format!("fields.{}.dimension", vector_field.name()),
+                ] {
+                    if let Some(value) = query_options.get(&key) {
+                        dimension_options.insert(key, value.clone());
+                    }
+                }
                 Ok(Some(
                     VindexVectorIndexOptions::new(
                         table_options,
-                        query_options,
+                        &dimension_options,
                         index_type,
                         vector_field,
                     )?
@@ -3453,6 +3463,28 @@ mod tests {
         assert_eq!(vindex_index_parallelism(4, 8), 4);
     }
 
+    #[test]
+    fn vindex_array_dimension_accepts_diskann_search_options() {
+        let field = DataField::new(
+            1,
+            "embedding".to_string(),
+            DataType::Array(ArrayType::new(DataType::Float(FloatType::new()))),
+        );
+        let query_options = HashMap::from([
+            ("diskann.dimension".to_string(), "8".to_string()),
+            ("diskann.l_search".to_string(), "64".to_string()),
+            (
+                "vindex.reader.memory-budget-bytes".to_string(),
+                "1048576".to_string(),
+            ),
+        ]);
+
+        assert_eq!(
+            pk_vector_query_dimension(&HashMap::new(), &query_options, 
"diskann", &field).unwrap(),
+            Some(8)
+        );
+    }
+
     fn make_field(id: i32, name: &str) -> DataField {
         DataField::new(id, name.to_string(), DataType::Int(IntType::default()))
     }
@@ -4933,8 +4965,12 @@ mod tests {
             VectorIndexBackend::from_index_type("ivf-flat"),
             Some(VectorIndexBackend::Vindex)
         );
-        // `diskann` is Lumina's internal index type, not a top-level index 
type.
-        assert_eq!(VectorIndexBackend::from_index_type("diskann"), None);
+        for index_type in ["ivf-sq", "ivf-rq", "diskann"] {
+            assert_eq!(
+                VectorIndexBackend::from_index_type(index_type),
+                Some(VectorIndexBackend::Vindex)
+            );
+        }
     }
 
     #[test]
diff --git a/crates/paimon/src/vindex/mod.rs b/crates/paimon/src/vindex/mod.rs
index da89d257..8ec93a13 100644
--- a/crates/paimon/src/vindex/mod.rs
+++ b/crates/paimon/src/vindex/mod.rs
@@ -30,6 +30,9 @@ use std::sync::OnceLock;
 
 pub const IVF_FLAT_IDENTIFIER: &str = "ivf-flat";
 pub const IVF_PQ_IDENTIFIER: &str = "ivf-pq";
+pub const IVF_SQ_IDENTIFIER: &str = "ivf-sq";
+pub const IVF_RQ_IDENTIFIER: &str = "ivf-rq";
+pub const DISKANN_IDENTIFIER: &str = "diskann";
 
 const DEFAULT_DIMENSION: &str = "128";
 const DEFAULT_METRIC: &str = "inner_product";
@@ -38,6 +41,23 @@ const DEFAULT_PQ_M: &str = "16";
 const DEFAULT_PQ_USE_OPQ: &str = "false";
 const DEFAULT_TRAIN_SAMPLE_RATIO: f64 = 1.0;
 const VECTOR_SEARCH_TIMING_ENV: &str = "PAIMON_LOG_VECTOR_SEARCH_TIMING";
+const DISKANN_OPTION_KEYS: &[(&str, &str)] = &[
+    ("deployment-profile", "deployment-profile"),
+    ("target-recall", "target-recall"),
+    ("max-bytes-per-vector", "max-bytes-per-vector"),
+    ("pq.code-ratio", "pq.code-ratio"),
+    ("pq.m", "pq.m"),
+    ("pq.bits", "pq.bits"),
+    ("diskann.build-preset", "build-preset"),
+    ("diskann.seed", "seed"),
+    ("diskann.memory-budget-bytes", "memory-budget-bytes"),
+    ("diskann.max-degree", "max-degree"),
+    ("diskann.build-search-list-size", "build-search-list-size"),
+    ("diskann.alpha", "alpha"),
+    ("diskann.storage-layout", "storage-layout"),
+    ("diskann.raw-vector-encoding", "raw-vector-encoding"),
+    ("diskann.build-distance", "build-distance"),
+];
 
 #[cfg(test)]
 static VECTOR_SEARCH_TIMING_TEST_GUARDS: AtomicUsize = AtomicUsize::new(0);
@@ -68,13 +88,23 @@ pub(crate) fn vector_search_timing_enabled() -> bool {
 }
 
 pub fn is_vindex_index_type(index_type: &str) -> bool {
-    matches!(index_type, IVF_FLAT_IDENTIFIER | IVF_PQ_IDENTIFIER)
+    matches!(
+        index_type,
+        IVF_FLAT_IDENTIFIER
+            | IVF_PQ_IDENTIFIER
+            | IVF_SQ_IDENTIFIER
+            | IVF_RQ_IDENTIFIER
+            | DISKANN_IDENTIFIER
+    )
 }
 
 pub(crate) fn native_index_type(index_type: &str) -> Option<&'static str> {
     match index_type {
         IVF_FLAT_IDENTIFIER => Some("ivf_flat"),
         IVF_PQ_IDENTIFIER => Some("ivf_pq"),
+        IVF_SQ_IDENTIFIER => Some("ivf_sq"),
+        IVF_RQ_IDENTIFIER => Some("ivf_rq"),
+        DISKANN_IDENTIFIER => Some("diskann"),
         _ => None,
     }
 }
@@ -108,18 +138,20 @@ impl VindexVectorIndexOptions {
             "dimension".to_string(),
             resolve_dimension(table_options, user_options, index_type, field)?,
         );
-        native_options.insert(
-            "nlist".to_string(),
-            option_value(
-                table_options,
-                user_options,
-                field.name(),
-                index_type,
-                "nlist",
-                "nlist",
-                DEFAULT_NLIST,
-            ),
-        );
+        if index_type != DISKANN_IDENTIFIER {
+            native_options.insert(
+                "nlist".to_string(),
+                option_value(
+                    table_options,
+                    user_options,
+                    field.name(),
+                    index_type,
+                    "nlist",
+                    "nlist",
+                    DEFAULT_NLIST,
+                ),
+            );
+        }
         native_options.insert(
             "metric".to_string(),
             normalize_metric(&option_value(
@@ -159,6 +191,34 @@ impl VindexVectorIndexOptions {
                 ),
             );
         }
+        if index_type == IVF_RQ_IDENTIFIER {
+            for key in ["rq.bits", "max-bytes-per-vector"] {
+                if let Some(value) = optional_value(
+                    table_options,
+                    user_options,
+                    field.name(),
+                    index_type,
+                    key,
+                    key,
+                ) {
+                    native_options.insert(key.to_string(), value);
+                }
+            }
+        }
+        if index_type == DISKANN_IDENTIFIER {
+            for &(native_key, paimon_suffix) in DISKANN_OPTION_KEYS {
+                if let Some(value) = optional_value(
+                    table_options,
+                    user_options,
+                    field.name(),
+                    index_type,
+                    native_key,
+                    paimon_suffix,
+                ) {
+                    native_options.insert(native_key.to_string(), value);
+                }
+            }
+        }
 
         let config = 
VectorIndexConfig::from_options(&native_options).map_err(|e| {
             crate::Error::DataInvalid {
@@ -249,17 +309,40 @@ fn is_supported_user_option_key(key: &str, index_type: 
&str, field_name: &str) -
 
 fn is_allowed_native_key(key: &str, index_type: &str) -> bool {
     match key {
-        "dimension" | "nlist" | "metric" => true,
-        "pq.m" | "use-opq" => index_type == IVF_PQ_IDENTIFIER,
-        _ => false,
+        "dimension" | "metric" => true,
+        "nlist" => index_type != DISKANN_IDENTIFIER,
+        "use-opq" => index_type == IVF_PQ_IDENTIFIER,
+        "rq.bits" => index_type == IVF_RQ_IDENTIFIER,
+        "max-bytes-per-vector" => {
+            matches!(index_type, IVF_RQ_IDENTIFIER | DISKANN_IDENTIFIER)
+        }
+        "pq.m" if index_type == IVF_PQ_IDENTIFIER => true,
+        _ => {
+            index_type == DISKANN_IDENTIFIER
+                && DISKANN_OPTION_KEYS
+                    .iter()
+                    .any(|(native_key, _)| *native_key == key)
+        }
     }
 }
 
 fn is_allowed_paimon_suffix(suffix: &str, index_type: &str) -> bool {
     match suffix {
-        "dimension" | "nlist" | "distance.metric" | "train.sample-ratio" => 
true,
-        "pq.m" | "pq.use-opq" => index_type == IVF_PQ_IDENTIFIER,
-        _ => false,
+        "dimension" | "distance.metric" => true,
+        "nlist" => index_type != DISKANN_IDENTIFIER,
+        "train.sample-ratio" => true,
+        "pq.use-opq" => index_type == IVF_PQ_IDENTIFIER,
+        "rq.bits" => index_type == IVF_RQ_IDENTIFIER,
+        "max-bytes-per-vector" => {
+            matches!(index_type, IVF_RQ_IDENTIFIER | DISKANN_IDENTIFIER)
+        }
+        "pq.m" if index_type == IVF_PQ_IDENTIFIER => true,
+        _ => {
+            index_type == DISKANN_IDENTIFIER
+                && DISKANN_OPTION_KEYS
+                    .iter()
+                    .any(|(_, paimon_suffix)| *paimon_suffix == suffix)
+        }
     }
 }
 
@@ -374,6 +457,11 @@ fn normalize_metric(metric: &str) -> String {
 mod tests {
     use super::*;
     use crate::spec::{ArrayType, FloatType, VectorType};
+    use paimon_vindex_core::index::{
+        IndexType, VectorIndexMetadata, VectorIndexReader, VectorIndexTrainer, 
VectorIndexWriter,
+    };
+    use paimon_vindex_core::io::PosWriter;
+    use std::io::Cursor;
 
     fn array_float_field() -> DataField {
         DataField::new(
@@ -383,10 +471,46 @@ mod tests {
         )
     }
 
+    fn roundtrip_metadata(index_type: &str, user_options: &[(&str, &str)]) -> 
VectorIndexMetadata {
+        let user_options = user_options
+            .iter()
+            .map(|(key, value)| ((*key).to_string(), (*value).to_string()))
+            .collect();
+        let options = VindexVectorIndexOptions::new(
+            &HashMap::new(),
+            &user_options,
+            index_type,
+            &array_float_field(),
+        )
+        .unwrap();
+        let n = 512;
+        let dimension = options.config.dimension();
+        let data = (0..n * dimension)
+            .map(|offset| {
+                let row = offset / dimension;
+                let column = offset % dimension;
+                (row % 4) as f32 * 20.0 + column as f32 * 0.01 + row as f32 * 
0.0001
+            })
+            .collect::<Vec<_>>();
+        let training = VectorIndexTrainer::train(options.config, &data, 
n).unwrap();
+        let mut writer = VectorIndexWriter::new(training);
+        writer
+            .add_vectors(&(0..n as i64).collect::<Vec<_>>(), &data, n)
+            .unwrap();
+        let mut bytes = Vec::new();
+        writer.write(&mut PosWriter::new(&mut bytes)).unwrap();
+        VectorIndexReader::open(Cursor::new(bytes))
+            .unwrap()
+            .metadata()
+    }
+
     #[test]
     fn test_vindex_index_type_identifier_helper() {
         assert!(is_vindex_index_type(IVF_FLAT_IDENTIFIER));
         assert!(is_vindex_index_type(IVF_PQ_IDENTIFIER));
+        assert!(is_vindex_index_type("ivf-sq"));
+        assert!(is_vindex_index_type("ivf-rq"));
+        assert!(is_vindex_index_type("diskann"));
         assert!(!is_vindex_index_type("ivf-hnsw-flat"));
         assert!(!is_vindex_index_type("ivf-hnsw-sq"));
         assert!(!is_vindex_index_type(""));
@@ -433,6 +557,201 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_vindex_options_map_ivf_rq_bits() {
+        let user_options = HashMap::from([
+            ("dimension".to_string(), "8".to_string()),
+            ("nlist".to_string(), "4".to_string()),
+            ("rq.bits".to_string(), "3".to_string()),
+        ]);
+
+        let options = VindexVectorIndexOptions::new(
+            &HashMap::new(),
+            &user_options,
+            IVF_RQ_IDENTIFIER,
+            &array_float_field(),
+        )
+        .unwrap();
+
+        assert_eq!(
+            options.native_options.get("rq.bits").map(String::as_str),
+            Some("3")
+        );
+        assert_eq!(options.config.resolved().rq_bits, Some(3));
+
+        let defaults = VindexVectorIndexOptions::new(
+            &HashMap::new(),
+            &HashMap::new(),
+            IVF_RQ_IDENTIFIER,
+            &array_float_field(),
+        )
+        .unwrap();
+        assert_eq!(defaults.config.resolved().rq_bits, Some(4));
+        assert!(!defaults.native_options.contains_key("rq.bits"));
+
+        let capacity_goal = HashMap::from([
+            ("dimension".to_string(), "100".to_string()),
+            ("nlist".to_string(), "16".to_string()),
+            ("ivf-rq.max-bytes-per-vector".to_string(), "88".to_string()),
+        ]);
+        let inferred = VindexVectorIndexOptions::new(
+            &HashMap::new(),
+            &capacity_goal,
+            IVF_RQ_IDENTIFIER,
+            &array_float_field(),
+        )
+        .unwrap();
+        assert_eq!(inferred.config.resolved().rq_bits, Some(3));
+        assert_eq!(
+            inferred
+                .native_options
+                .get("max-bytes-per-vector")
+                .map(String::as_str),
+            Some("88")
+        );
+        assert!(!inferred.native_options.contains_key("rq.bits"));
+
+        let invalid = HashMap::from([("ivf-rq.rq.bits".to_string(), 
"9".to_string())]);
+        let error = VindexVectorIndexOptions::new(
+            &HashMap::new(),
+            &invalid,
+            IVF_RQ_IDENTIFIER,
+            &array_float_field(),
+        )
+        .expect_err("rq.bits outside 1..=8 must be rejected");
+        assert!(error.to_string().contains("rq.bits"));
+    }
+
+    #[test]
+    fn test_vindex_options_map_diskann_config() {
+        let user_options = HashMap::from([
+            ("diskann.dimension".to_string(), "128".to_string()),
+            ("diskann.distance.metric".to_string(), "l2".to_string()),
+            (
+                "diskann.deployment-profile".to_string(),
+                "local_storage".to_string(),
+            ),
+            ("diskann.target-recall".to_string(), "0.9".to_string()),
+            (
+                "diskann.max-bytes-per-vector".to_string(),
+                "1024".to_string(),
+            ),
+            ("diskann.pq.code-ratio".to_string(), "0.125".to_string()),
+            ("diskann.pq.m".to_string(), "16".to_string()),
+            ("diskann.pq.bits".to_string(), "4".to_string()),
+            ("diskann.build-preset".to_string(), "balanced".to_string()),
+            ("diskann.seed".to_string(), "7".to_string()),
+            ("diskann.max-degree".to_string(), "32".to_string()),
+            (
+                "diskann.build-search-list-size".to_string(),
+                "64".to_string(),
+            ),
+            ("diskann.alpha".to_string(), "1.4".to_string()),
+            (
+                "diskann.memory-budget-bytes".to_string(),
+                "123456".to_string(),
+            ),
+            (
+                "diskann.storage-layout".to_string(),
+                "interleaved".to_string(),
+            ),
+            ("diskann.raw-vector-encoding".to_string(), "f16".to_string()),
+            (
+                "diskann.build-distance".to_string(),
+                "full_precision".to_string(),
+            ),
+        ]);
+
+        let options = VindexVectorIndexOptions::new(
+            &HashMap::new(),
+            &user_options,
+            DISKANN_IDENTIFIER,
+            &array_float_field(),
+        )
+        .unwrap();
+        let resolved = options.config.resolved();
+
+        assert_eq!(resolved.pq_m, Some(16));
+        assert_eq!(resolved.pq_bits, Some(4));
+        let build = resolved.diskann_build.unwrap();
+        assert_eq!(build.max_degree, 32);
+        assert_eq!(build.build_search_list_size, 64);
+        assert_eq!(build.alpha, 1.4);
+        assert!(!options.native_options.contains_key("nlist"));
+        for key in [
+            "deployment-profile",
+            "target-recall",
+            "max-bytes-per-vector",
+            "pq.code-ratio",
+            "diskann.build-preset",
+            "diskann.seed",
+            "diskann.storage-layout",
+            "diskann.raw-vector-encoding",
+            "diskann.build-distance",
+        ] {
+            assert!(options.native_options.contains_key(key), "missing {key}");
+        }
+    }
+
+    #[test]
+    fn test_new_index_types_roundtrip_metadata() {
+        let sq = roundtrip_metadata(
+            IVF_SQ_IDENTIFIER,
+            &[("ivf-sq.dimension", "8"), ("ivf-sq.nlist", "4")],
+        );
+        assert_eq!(sq.index_type, IndexType::IvfSq);
+        assert_eq!(
+            (sq.dimension, sq.nlist, sq.total_vectors, sq.pq_bits),
+            (8, 4, 512, Some(8))
+        );
+
+        let rq = roundtrip_metadata(
+            IVF_RQ_IDENTIFIER,
+            &[
+                ("ivf-rq.dimension", "8"),
+                ("ivf-rq.nlist", "4"),
+                ("ivf-rq.rq.bits", "4"),
+            ],
+        );
+        assert_eq!(rq.index_type, IndexType::IvfRq);
+        assert_eq!(
+            (rq.dimension, rq.nlist, rq.total_vectors, rq.rq_bits),
+            (8, 4, 512, Some(4))
+        );
+
+        let diskann = roundtrip_metadata(
+            DISKANN_IDENTIFIER,
+            &[
+                ("diskann.dimension", "8"),
+                ("diskann.pq.m", "4"),
+                ("diskann.pq.bits", "4"),
+                ("diskann.max-degree", "8"),
+                ("diskann.build-search-list-size", "16"),
+                ("diskann.alpha", "1.2"),
+                ("diskann.raw-vector-encoding", "f32"),
+            ],
+        );
+        assert_eq!(diskann.index_type, IndexType::DiskAnn);
+        assert_eq!(
+            (
+                diskann.dimension,
+                diskann.total_vectors,
+                diskann.pq_m,
+                diskann.pq_bits
+            ),
+            (8, 512, Some(4), Some(4))
+        );
+        let diskann = diskann.diskann.unwrap();
+        assert_eq!(
+            (
+                diskann.max_degree,
+                diskann.build_search_list_size,
+                diskann.alpha
+            ),
+            (8, 16, 1.2)
+        );
+    }
+
     #[test]
     fn test_vindex_options_field_options_override_shared_table_options() {
         let table_options = HashMap::from([
@@ -501,6 +820,15 @@ mod tests {
         .unwrap();
         assert_eq!(options.train_sample_ratio, 1.0);
         assert!(!options.native_options.contains_key("train.sample-ratio"));
+
+        let diskann = VindexVectorIndexOptions::new(
+            &HashMap::new(),
+            &HashMap::from([("diskann.train.sample-ratio".to_string(), 
"0.5".to_string())]),
+            DISKANN_IDENTIFIER,
+            &array_float_field(),
+        )
+        .unwrap();
+        assert_eq!(diskann.train_sample_ratio, 0.5);
     }
 
     #[test]
@@ -617,24 +945,24 @@ mod tests {
 
     #[test]
     fn test_vindex_options_reject_non_applicable_user_options() {
-        let table_options = HashMap::new();
-        let user_options = HashMap::from([
-            ("ivf-flat.dimension".to_string(), "8".to_string()),
-            ("ivf-flat.nlist".to_string(), "4".to_string()),
-            ("ivf-flat.pq.m".to_string(), "2".to_string()),
-        ]);
-
-        let err = VindexVectorIndexOptions::new(
-            &table_options,
-            &user_options,
-            IVF_FLAT_IDENTIFIER,
-            &array_float_field(),
-        )
-        .expect_err("non-applicable user option should be rejected");
+        for (index_type, key) in [
+            (IVF_FLAT_IDENTIFIER, "ivf-flat.pq.m"),
+            (IVF_FLAT_IDENTIFIER, "diskann.max-degree"),
+            (DISKANN_IDENTIFIER, "diskann.nlist"),
+        ] {
+            let user_options = HashMap::from([(key.to_string(), 
"2".to_string())]);
+            let err = VindexVectorIndexOptions::new(
+                &HashMap::new(),
+                &user_options,
+                index_type,
+                &array_float_field(),
+            )
+            .expect_err("non-applicable user option should be rejected");
 
-        assert!(
-            matches!(err, crate::Error::ConfigInvalid { message } if 
message.contains("ivf-flat.pq.m"))
-        );
+            assert!(
+                matches!(err, crate::Error::ConfigInvalid { message } if 
message.contains(key))
+            );
+        }
     }
 
     #[test]
@@ -665,6 +993,9 @@ mod tests {
     fn test_native_index_type_helper() {
         assert_eq!(native_index_type(IVF_FLAT_IDENTIFIER), Some("ivf_flat"));
         assert_eq!(native_index_type(IVF_PQ_IDENTIFIER), Some("ivf_pq"));
+        assert_eq!(native_index_type("ivf-sq"), Some("ivf_sq"));
+        assert_eq!(native_index_type("ivf-rq"), Some("ivf_rq"));
+        assert_eq!(native_index_type("diskann"), Some("diskann"));
         assert_eq!(native_index_type("ivf-hnsw-flat"), None);
         assert_eq!(native_index_type("ivf-hnsw-sq"), None);
         assert_eq!(native_index_type("btree"), None);
diff --git a/crates/paimon/src/vindex/reader.rs 
b/crates/paimon/src/vindex/reader.rs
index f0cb0fe8..8f8eab35 100644
--- a/crates/paimon/src/vindex/reader.rs
+++ b/crates/paimon/src/vindex/reader.rs
@@ -19,7 +19,8 @@ use crate::vector_search::{GlobalIndexIOMeta, VectorSearch};
 use crate::vindex::vector_search_timing_enabled;
 use paimon_vindex_core::distance::MetricType;
 use paimon_vindex_core::index::{
-    VectorIndexMetadata, VectorIndexReader as VIndexReader, VectorSearchParams,
+    IndexType, VectorIndexMetadata, VectorIndexReader as VIndexReader, 
VectorIndexReaderOptions,
+    VectorSearchParams,
 };
 use paimon_vindex_core::io::{ReadRequest, SeekRead, SeekReadCapabilities};
 use std::collections::BinaryHeap;
@@ -30,6 +31,9 @@ use std::time::{Duration, Instant};
 
 const DEFAULT_NPROBE: usize = 16;
 const NPROBE_PARAMETER: &str = "ivf.nprobe";
+// Matches Java's NativeVectorGlobalIndexReader; l_search is intentionally 
snake_case.
+const L_SEARCH_PARAMETER: &str = "diskann.l_search";
+const READER_MEMORY_BUDGET_PARAMETER: &str = 
"vindex.reader.memory-budget-bytes";
 const NATIVE_BATCH_PROCESS_WORKING_SET_BYTES: usize = 64 * 1024 * 1024;
 // Native searches run on dedicated executor threads, so blocking here does 
not block async I/O.
 static NATIVE_BATCH_MEMORY_POOL: NativeBatchMemoryPool =
@@ -367,13 +371,17 @@ impl VindexVectorGlobalIndexReader {
         }
 
         let open_start = self.timing_enabled.then(Instant::now);
+        let reader_options = VectorIndexReaderOptions::new(int_parameter(
+            &self.options,
+            READER_MEMORY_BUDGET_PARAMETER,
+            VectorIndexReaderOptions::default().memory_budget_bytes,
+        )?);
         let source = stream_fn(&self.io_meta.file_path)?;
-        let mut reader = 
VIndexReader::open(VindexInput::new(source)).map_err(|e| {
-            crate::Error::DataInvalid {
+        let mut reader = 
VIndexReader::open_with_options(VindexInput::new(source), reader_options)
+            .map_err(|e| crate::Error::DataInvalid {
                 message: format!("Failed to open paimon-vindex-core reader: 
{}", e),
                 source: Some(Box::new(e)),
-            }
-        })?;
+            })?;
         let vindex_open = open_start.map_or(Duration::ZERO, |start| 
start.elapsed());
         let metadata_start = self.timing_enabled.then(Instant::now);
         let metadata = reader.metadata();
@@ -405,7 +413,7 @@ fn search_vindex(
         return Ok(None);
     };
     let (labels, distances) = execute_scalar_search(reader, vector_search, 
&prepared)?;
-    let id_to_scores = collect_results(&labels, &distances, prepared.top_k, 
metadata.metric);
+    let id_to_scores = collect_results(&labels, &distances, 
prepared.params.top_k, metadata.metric);
     if id_to_scores.is_empty() {
         return Ok(None);
     }
@@ -415,8 +423,7 @@ fn search_vindex(
 
 #[derive(Clone, PartialEq, Eq)]
 struct PreparedSearch {
-    top_k: usize,
-    nprobe: usize,
+    params: VectorSearchParams,
     filter_bytes: Option<Vec<u8>>,
 }
 
@@ -441,7 +448,29 @@ fn prepare_search(
     if top_k == 0 {
         return Ok(None);
     }
-    let nprobe = int_parameter(options, NPROBE_PARAMETER, DEFAULT_NPROBE)?;
+    let mut params = match metadata.index_type {
+        IndexType::DiskAnn => match options.get(L_SEARCH_PARAMETER) {
+            Some(value) => {
+                let l_search = value
+                    .parse::<usize>()
+                    .ok()
+                    .filter(|value| *value > 0)
+                    .ok_or_else(|| crate::Error::DataInvalid {
+                        message: format!(
+                            "Invalid value for '{}': {}. Must be a positive 
integer.",
+                            L_SEARCH_PARAMETER, value
+                        ),
+                        source: None,
+                    })?;
+                VectorSearchParams::with_l_search(top_k, l_search)
+            }
+            None => VectorSearchParams::automatic(top_k),
+        },
+        _ => VectorSearchParams::new(
+            top_k,
+            int_parameter(options, NPROBE_PARAMETER, DEFAULT_NPROBE)?,
+        ),
+    };
 
     let filter_bytes = if let Some(include_ids) = 
&vector_search.include_row_ids {
         if include_ids.is_empty() {
@@ -459,10 +488,10 @@ fn prepare_search(
     } else {
         None
     };
+    params.top_k = top_k;
 
     Ok(Some(PreparedSearch {
-        top_k,
-        nprobe,
+        params,
         filter_bytes,
     }))
 }
@@ -472,7 +501,7 @@ fn execute_scalar_search(
     vector_search: &VectorSearch,
     prepared: &PreparedSearch,
 ) -> crate::Result<(Vec<i64>, Vec<f32>)> {
-    let params = VectorSearchParams::new(prepared.top_k, prepared.nprobe);
+    let params = prepared.params;
     match &prepared.filter_bytes {
         Some(filter) => reader
             .search_with_roaring_filter(&vector_search.vector, params, filter)
@@ -532,7 +561,8 @@ fn search_batch_vindex(
                 let index = indices[0];
                 let (labels, distances) =
                     execute_scalar_search(reader, &vector_searches[index], 
&prepared)?;
-                let map = collect_results(&labels, &distances, prepared.top_k, 
metadata.metric);
+                let map =
+                    collect_results(&labels, &distances, 
prepared.params.top_k, metadata.metric);
                 if !map.is_empty() {
                     results[index] = Some(map);
                 }
@@ -550,7 +580,7 @@ fn search_batch_vindex(
             for &index in indices {
                 queries.extend_from_slice(&vector_searches[index].vector);
             }
-            let params = VectorSearchParams::new(prepared.top_k, 
prepared.nprobe);
+            let params = prepared.params;
             let (labels, distances) = match &prepared.filter_bytes {
                 Some(filter) => reader
                     .search_batch_with_roaring_filter(&queries, indices.len(), 
params, filter)
@@ -565,7 +595,7 @@ fn search_batch_vindex(
                         source: Some(Box::new(e)),
                     })?,
             };
-            let expected = indices.len() * prepared.top_k;
+            let expected = indices.len() * prepared.params.top_k;
             if labels.len() != expected || distances.len() != expected {
                 return Err(crate::Error::DataInvalid {
                     message: format!(
@@ -577,12 +607,12 @@ fn search_batch_vindex(
                 });
             }
             for (query_index, &result_index) in indices.iter().enumerate() {
-                let start = query_index * prepared.top_k;
-                let end = start + prepared.top_k;
+                let start = query_index * prepared.params.top_k;
+                let end = start + prepared.params.top_k;
                 let map = collect_results(
                     &labels[start..end],
                     &distances[start..end],
-                    prepared.top_k,
+                    prepared.params.top_k,
                     metadata.metric,
                 );
                 if !map.is_empty() {
@@ -635,12 +665,25 @@ fn native_batch_query_working_set_bytes(
         .saturating_mul(std::mem::size_of::<f32>() * 2);
     let centroid_products = 
metadata.nlist.saturating_mul(std::mem::size_of::<f32>());
     let probe_results = prepared
-        .nprobe
+        .params
+        .configured_ivf_nprobe()
+        .unwrap_or(0)
         .min(metadata.nlist)
         .saturating_mul(std::mem::size_of::<usize>() + 
std::mem::size_of::<f32>());
-    let top_k_results = prepared.top_k.saturating_mul(
+    let top_k_results = prepared.params.top_k.saturating_mul(
         std::mem::size_of::<i64>() + std::mem::size_of::<f32>() + 
std::mem::size_of::<(f32, i64)>(),
     );
+    // TODO: Mirrors vindex 0.3's live frontier; use upstream scratch-byte 
reporting when exposed.
+    let diskann_candidates = if metadata.index_type == IndexType::DiskAnn {
+        prepared
+            .params
+            .configured_diskann_l_search()
+            .unwrap_or_else(|| 
prepared.params.top_k.saturating_mul(2).max(100))
+            .max(prepared.params.top_k)
+            .saturating_mul(std::mem::size_of::<(usize, f32)>() + 
std::mem::size_of::<(i64, f32)>())
+    } else {
+        0
+    };
     let pq_tables = match (metadata.pq_m, metadata.pq_bits) {
         (Some(m), Some(bits)) => 1usize
             .checked_shl(bits as u32)
@@ -654,6 +697,7 @@ fn native_batch_query_working_set_bytes(
         .saturating_add(centroid_products)
         .saturating_add(probe_results)
         .saturating_add(top_k_results)
+        .saturating_add(diskann_candidates)
         .saturating_add(pq_tables)
         .saturating_add(256)
         .max(1)
@@ -740,6 +784,8 @@ mod tests {
     use crate::vindex::range_reader::VindexFileReader;
     use async_trait::async_trait;
     use bytes::Bytes;
+    use paimon_vindex_core::diskann::DiskAnnBuildParams;
+    use paimon_vindex_core::diskann_io::DiskAnnHeader;
     use paimon_vindex_core::index::{VectorIndexConfig, VectorIndexTrainer, 
VectorIndexWriter};
     use paimon_vindex_core::io::{PosWriter, SeekReadCapabilities};
     use std::cell::Cell;
@@ -935,8 +981,7 @@ mod tests {
             diskann: None,
         };
         let base_prepared = PreparedSearch {
-            top_k: 10,
-            nprobe: 16,
+            params: VectorSearchParams::new(10, 16),
             filter_bytes: None,
         };
         let index_parallelism = 32;
@@ -948,7 +993,7 @@ mod tests {
         assert!(native_batch_chunk_size(&larger_index, &base_prepared, 
index_parallelism) < base);
 
         let mut larger_top_k = base_prepared.clone();
-        larger_top_k.top_k *= 4;
+        larger_top_k.params.top_k *= 4;
         assert!(native_batch_chunk_size(&base_metadata, &larger_top_k, 
index_parallelism) < base);
 
         let mut pq_metadata = base_metadata.clone();
@@ -986,8 +1031,7 @@ mod tests {
             diskann: None,
         };
         let prepared = PreparedSearch {
-            top_k: 10,
-            nprobe: 16,
+            params: VectorSearchParams::new(10, 16),
             filter_bytes: Some(vec![0; 128]),
         };
         let chunk_size = native_batch_chunk_size(&metadata, &prepared, 1);
@@ -1068,6 +1112,116 @@ mod tests {
         assert!(int_parameter(&options, NPROBE_PARAMETER, 
DEFAULT_NPROBE).is_err());
     }
 
+    #[test]
+    fn prepare_diskann_search_uses_automatic_or_explicit_l_search() {
+        let metadata = VectorIndexMetadata {
+            index_type: paimon_vindex_core::index::IndexType::DiskAnn,
+            dimension: TEST_DIMENSION,
+            nlist: 1,
+            metric: MetricType::L2,
+            total_vectors: 100,
+            pq_m: Some(2),
+            pq_bits: Some(8),
+            rq_bits: None,
+            diskann: None,
+        };
+
+        let automatic = prepare_search(&metadata, &HashMap::new(), &query())
+            .unwrap()
+            .unwrap();
+        let automatic_chunk_size = native_batch_chunk_size(&metadata, 
&automatic, 1);
+        assert!(automatic_chunk_size > 1);
+        let wide_search = PreparedSearch {
+            params: VectorSearchParams::with_l_search(10, 4096),
+            filter_bytes: None,
+        };
+        assert!(native_batch_chunk_size(&metadata, &wide_search, 1) < 
automatic_chunk_size);
+        let automatic_params = automatic.params;
+        assert_eq!(
+            automatic_params.search_width,
+            paimon_vindex_core::index::SearchWidth::Auto
+        );
+
+        let explicit = prepare_search(
+            &metadata,
+            &HashMap::from([("diskann.l_search".to_string(), 
"64".to_string())]),
+            &query(),
+        )
+        .unwrap()
+        .unwrap();
+        assert!(automatic != explicit);
+        let explicit_params = explicit.params;
+        assert_eq!(
+            explicit_params.search_width,
+            paimon_vindex_core::index::SearchWidth::DiskAnnLSearch
+        );
+        assert_eq!(explicit_params.width, 64);
+
+        let zero_l_search = prepare_search(
+            &metadata,
+            &HashMap::from([("diskann.l_search".to_string(), 
"0".to_string())]),
+            &query(),
+        )
+        .err()
+        .unwrap();
+        assert!(zero_l_search.to_string().contains("positive integer"));
+
+        let mixed_options = HashMap::from([
+            ("ivf.nprobe".to_string(), "4".to_string()),
+            ("diskann.l_search".to_string(), "64".to_string()),
+        ]);
+        let diskann = prepare_search(&metadata, &mixed_options, &query())
+            .unwrap()
+            .unwrap();
+        assert_eq!(
+            diskann.params.search_width,
+            paimon_vindex_core::index::SearchWidth::DiskAnnLSearch
+        );
+        assert_eq!(diskann.params.width, 64);
+
+        let mut ivf_metadata = metadata;
+        ivf_metadata.index_type = IndexType::IvfFlat;
+        let ivf = prepare_search(&ivf_metadata, &mixed_options, &query())
+            .unwrap()
+            .unwrap();
+        assert_eq!(
+            ivf.params.search_width,
+            paimon_vindex_core::index::SearchWidth::IvfNProbe
+        );
+        assert_eq!(ivf.params.width, 4);
+    }
+
+    #[test]
+    fn diskann_reader_enforces_configured_memory_budget_during_optimization() {
+        let header = DiskAnnHeader::for_layout(
+            8,
+            2,
+            0,
+            2,
+            DiskAnnBuildParams {
+                max_degree: 1,
+                build_search_list_size: 2,
+                ..DiskAnnBuildParams::default()
+            },
+        )
+        .unwrap();
+        let bytes = header.encode().to_vec();
+        let io_meta =
+            GlobalIndexIOMeta::new("budget.index".to_string(), bytes.len() as 
u64, Vec::new());
+        let options = HashMap::from([(
+            "vindex.reader.memory-budget-bytes".to_string(),
+            "1".to_string(),
+        )]);
+        let mut reader = VindexVectorGlobalIndexReader::new(io_meta, options);
+
+        let error = reader
+            .load(|_| Ok(Cursor::new(bytes)))
+            .expect_err("resident state above the configured budget must 
fail");
+        let message = error.to_string();
+        assert!(message.contains("resident"), "{message}");
+        assert!(message.contains("budget"), "{message}");
+    }
+
     #[test]
     fn erased_input_forwards_clone_and_capabilities() {
         let capabilities = SeekReadCapabilities {
diff --git a/docs/src/sql.md b/docs/src/sql.md
index eaf9758a..239d61c0 100644
--- a/docs/src/sql.md
+++ b/docs/src/sql.md
@@ -986,8 +986,8 @@ CREATE TABLE paimon.my_db.items (
 );
 ```
 
-For vector indexes backed by vindex, set `index_type` to `ivf-flat` or
-`ivf-pq`:
+For vector indexes backed by vindex, set `index_type` to `ivf-flat`,
+`ivf-pq`, `ivf-sq`, `ivf-rq`, or `diskann`:
 
 ```sql
 CALL sys.create_global_index(
@@ -998,9 +998,41 @@ CALL sys.create_global_index(
 );
 ```
 
+Examples for the additional vindex index types:
+
+```sql
+-- IVF-SQ uses a fixed 8-bit scalar code.
+CALL sys.create_global_index(
+  table => 'paimon.my_db.items',
+  index_column => 'embedding',
+  index_type => 'ivf-sq',
+  options => 
'ivf-sq.dimension=4,ivf-sq.nlist=256,ivf-sq.distance.metric=cosine'
+);
+
+CALL sys.create_global_index(
+  table => 'paimon.my_db.items',
+  index_column => 'embedding',
+  index_type => 'ivf-rq',
+  options => 
'ivf-rq.dimension=4,ivf-rq.nlist=256,ivf-rq.distance.metric=cosine,ivf-rq.rq.bits=4'
+);
+
+CALL sys.create_global_index(
+  table => 'paimon.my_db.items',
+  index_column => 'embedding',
+  index_type => 'diskann',
+  options => 
'diskann.dimension=4,diskann.distance.metric=cosine,diskann.deployment-profile=local_storage,diskann.build-preset=balanced'
+);
+```
+
+The vindex `diskann` index is separate from the Lumina index whose
+`lumina.index.type` is `diskann`. Use the `diskann.*` options below with
+`index_type => 'diskann'`; use `lumina.*` options only with a Lumina index.
+
 The `options` argument is a comma-separated `key=value` string. User options
 override table options. Use keys prefixed by the selected index type, or set
-field-level table options with `fields.<column>.<option>`:
+field-level table options with `fields.<column>.<option>`. For example,
+`diskann.max-degree` becomes `fields.embedding.max-degree` for an `embedding`
+column:
 
 ```sql
 CREATE TABLE paimon.my_db.image_items (
@@ -1023,13 +1055,40 @@ Supported vindex options:
 |---|---:|---|---|
 | `<index-type>.dimension` | `128` | all vindex types | Vector dimension for 
`ARRAY<FLOAT>` columns. Existing `VECTOR<FLOAT,N>` columns use `N` from the 
type. |
 | `<index-type>.distance.metric` | `inner_product` | all vindex types | 
Distance metric: `inner_product`, `cosine`, or `l2`. |
-| `<index-type>.nlist` | `256` | all vindex types | Number of IVF lists. |
+| `<index-type>.nlist` | `256` | all IVF types | Number of IVF lists. DiskANN 
rejects this option. |
 | `<index-type>.train.sample-ratio` or `fields.<field>.train.sample-ratio` | 
`1.0` | all vindex types | Fraction of shard rows selected evenly for training. 
Must be in `(0, 1]`; all rows are still added to the index. The field-specific 
option takes precedence. |
 | `<index-type>.pq.m` | `16` | `ivf-pq` | Number of product-quantization 
sub-vectors. The dimension must be divisible by this value. |
 | `<index-type>.pq.use-opq` | `false` | `ivf-pq` | Whether to enable OPQ 
before PQ encoding. |
+| `ivf-rq.rq.bits` | `4`, or inferred | `ivf-rq` | Residual-quantization width 
in the range `1` to `8`. When omitted, `ivf-rq.max-bytes-per-vector` can select 
it. |
+| `ivf-rq.max-bytes-per-vector` | unset | `ivf-rq` | Optional positive 
persisted-code budget used to infer `rq.bits`. |
 
-Native vindex aliases are also accepted in the `options` string: `dimension`,
-`metric`, `nlist`, `pq.m`, and `use-opq`.
+IVF-SQ has no `sq.bits` option; it always stores one 8-bit scalar code per
+dimension. DiskANN accepts these build options:
+
+| Option | Default | Description |
+|---|---:|---|
+| `diskann.deployment-profile` | `auto` | Intended serving medium: `auto`, 
`memory`, `local_storage`, `remote_storage`, or `object_store`. |
+| `diskann.target-recall` | unset | Value in `[0, 1]` used to choose a build 
preset when one is not specified. This is a tuning hint, not a recall 
guarantee. |
+| `diskann.max-bytes-per-vector` | unset | Optional positive persisted-size 
budget that guides PQ width and raw-vector encoding. |
+| `diskann.pq.code-ratio` | `0.0625` | Ratio of resident PQ-code bytes to raw 
`f32` vector bytes; must be in `(0, 0.25]` for 8-bit PQ or `(0, 0.125]` for 
4-bit PQ. |
+| `diskann.pq.m` | automatic | Explicit PQ chunk count in `1..=dimension`; 
overrides `pq.code-ratio`. |
+| `diskann.pq.bits` | `8` | PQ width; must be `4` or `8`. |
+| `diskann.build-preset` | inferred | `fast_build`, `balanced`, or 
`high_recall`; without `target-recall` or an explicit value, uses `balanced`. |
+| `diskann.seed` | `42` | Reproducible graph-build seed. |
+| `diskann.memory-budget-bytes` | `8589934592` | Positive internal build-state 
budget. This is not the query Reader budget or a process RSS limit. |
+| `diskann.max-degree` | preset value | Maximum graph out-degree in 
`1..=1023`. |
+| `diskann.build-search-list-size` | preset value | Candidate-list width used 
while building the graph; must be at least `max-degree`. |
+| `diskann.alpha` | preset value | Finite robust-pruning threshold at least 
`1`. |
+| `diskann.storage-layout` | preset value | `auto`, `compact`, or 
`interleaved`. |
+| `diskann.raw-vector-encoding` | preset value | `auto`, `f32`, or `f16` 
rerank-vector encoding. |
+| `diskann.build-distance` | preset value | `auto`, `full_precision`, or 
`product_quantized`. |
+
+For procedure calls, prefer the index-prefixed option names shown above. Native
+vindex aliases are also accepted in the `options` string: `dimension`, 
`metric`,
+`nlist`, `pq.m`, `use-opq`, `rq.bits`, `max-bytes-per-vector`,
+`deployment-profile`, `target-recall`, `pq.code-ratio`, `pq.bits`, and the
+`diskann.*` build keys listed in the table. Build options for another index
+family are rejected rather than ignored.
 
 Inspect committed index files with the `$table_indexes` system table:
 
@@ -1051,9 +1110,9 @@ CALL sys.drop_global_index(
 ```
 
 `index_type` accepts every type the create procedures build: `btree`, `bitmap`,
-`lumina` (or `lumina-vector-ann`), and the vindex types `ivf-flat` and 
`ivf-pq`.
-It defaults to `btree`, is case-insensitive and surrounding whitespace is
-ignored.
+`lumina` (or `lumina-vector-ann`), and the vindex types `ivf-flat`, `ivf-pq`,
+`ivf-sq`, `ivf-rq`, and `diskann`. It defaults to `btree`, is case-insensitive
+and surrounding whitespace is ignored.
 
 ### create_lumina_index
 
@@ -1335,9 +1394,45 @@ The distance metric is configured at index creation time 
via table options:
 ### Vindex Index Options
 
 For vindex-backed search, build the index with
-`CALL sys.create_global_index` and an index type such as `ivf-flat` or
-`ivf-pq`. See [create_global_index](#create_global_index) for the supported
-index types, table requirements, and option keys.
+`CALL sys.create_global_index` and an index type such as `ivf-flat`, `ivf-sq`,
+`ivf-pq`, `ivf-rq`, or `diskann`. See
+[create_global_index](#create_global_index) for the table requirements and
+build option keys.
+
+With Paimon's `SQLContext`, set query-time vindex options for the session 
before
+calling `vector_search`, then reset them when they are no longer needed:
+
+```sql
+-- IVF only; defaults to 16.
+SET 'paimon.ivf.nprobe' = '32';
+SELECT * FROM vector_search('paimon.my_db.items', 'embedding', '[1.0, 0.0, 
0.0, 0.0]', 10);
+RESET 'paimon.ivf.nprobe';
+
+-- DiskANN only; must be at least 1. Omit it to use the automatic search width.
+SET 'paimon.diskann.l_search' = '100';
+SELECT * FROM vector_search('paimon.my_db.items', 'embedding', '[1.0, 0.0, 
0.0, 0.0]', 10);
+RESET 'paimon.diskann.l_search';
+```
+
+Query options are index-family specific and non-applicable options are ignored:
+IVF readers consume only `ivf.nprobe`, while DiskANN readers consume only
+`diskann.l_search`. Setting both options is allowed; each index uses its own.
+
+`vindex.reader.memory-budget-bytes` sets the per-Reader resident-data and cache
+budget (default 4 GiB). It is distinct from the DiskANN build option
+`diskann.memory-budget-bytes` and from process RSS:
+
+```sql
+SET 'paimon.vindex.reader.memory-budget-bytes' = '4294967296';
+SELECT * FROM vector_search('paimon.my_db.items', 'embedding', '[1.0, 0.0, 
0.0, 0.0]', 10);
+RESET 'paimon.vindex.reader.memory-budget-bytes';
+```
+
+These `SET`/`RESET` values are provided by Paimon's `SQLContext`; registering
+`vector_search` directly on a raw DataFusion `SessionContext` does not install
+that dynamic-option path. Rust callers can pass the same unprefixed keys, such
+as `diskann.l_search`, through `VectorSearchBuilder::with_options` or
+`BatchVectorSearchBuilder::with_options`.
 
 ### Lumina Index Options
 

Reply via email to