From af459d16a4ff7fe93cdf1a3b1c0c4d46253c6aa2 Mon Sep 17 00:00:00 2001 From: Fujio Turner Date: Fri, 12 Dec 2025 14:43:43 -0600 Subject: [PATCH] feat: add vector search support (Android) Add vector index creation support for Android: - CollectionManagerVector.kt helper for vector indexes - Handle 'vector' case in createIndex Ref: #86 --- .../cblreactnative/CollectionManagerVector.kt | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 android/src/main/java/com/cblreactnative/CollectionManagerVector.kt diff --git a/android/src/main/java/com/cblreactnative/CollectionManagerVector.kt b/android/src/main/java/com/cblreactnative/CollectionManagerVector.kt new file mode 100644 index 0000000..6a33ed4 --- /dev/null +++ b/android/src/main/java/com/cblreactnative/CollectionManagerVector.kt @@ -0,0 +1,153 @@ +/** + * CollectionManagerVector.kt + * + * Vector index support for Android/Kotlin implementation. + * This code should be integrated into CollectionManager.kt + */ + +package com.cblreactnative + +import com.couchbase.lite.Collection +import com.couchbase.lite.VectorIndexConfiguration +import com.couchbase.lite.VectorEncoding +import com.couchbase.lite.DistanceMetric +import com.couchbase.lite.ScalarQuantizerType + +/** + * Creates a vector index on the specified collection. + * + * This method handles the "vector" case in the createIndex switch/when statement. + * + * @param indexName Name for the new index + * @param indexConfig Map containing vector index configuration + * @param collection The collection to create the index on + * @throws Exception if index creation fails + */ +fun createVectorIndex( + indexName: String, + indexConfig: Map, + collection: Collection +) { + // Extract required parameters + val expression = indexConfig["expression"] as? String + ?: throw Exception("Vector index requires 'expression' parameter") + + val dimensions = (indexConfig["dimensions"] as? Number)?.toLong() + ?: throw Exception("Vector index requires 'dimensions' parameter") + + val centroids = (indexConfig["centroids"] as? Number)?.toLong() + ?: throw Exception("Vector index requires 'centroids' parameter") + + // Create vector index configuration + val vectorConfig = VectorIndexConfiguration(expression, dimensions, centroids) + + // Set distance metric + val metricStr = indexConfig["metric"] as? String + if (metricStr != null) { + vectorConfig.metric = when (metricStr) { + "cosine" -> DistanceMetric.COSINE + "euclidean" -> DistanceMetric.EUCLIDEAN + "euclideanSquared" -> DistanceMetric.EUCLIDEAN_SQUARED + "dot" -> DistanceMetric.DOT + else -> DistanceMetric.EUCLIDEAN_SQUARED // Default + } + } + + // Set encoding + val encodingConfig = indexConfig["encoding"] as? Map + if (encodingConfig != null) { + val encodingType = encodingConfig["type"] as? String + vectorConfig.encoding = when (encodingType) { + "none" -> VectorEncoding.none() + "SQ" -> { + // Scalar Quantizer - determine type from config or default to SQ8 + val sqType = encodingConfig["sqType"] as? String + when (sqType) { + "SQ4" -> VectorEncoding.scalarQuantizer(ScalarQuantizerType.SQ4) + "SQ6" -> VectorEncoding.scalarQuantizer(ScalarQuantizerType.SQ6) + "SQ8" -> VectorEncoding.scalarQuantizer(ScalarQuantizerType.SQ8) + else -> VectorEncoding.scalarQuantizer(ScalarQuantizerType.SQ8) + } + } + "PQ" -> { + // Product Quantizer + val subquantizers = (encodingConfig["subquantizers"] as? Number)?.toLong() ?: 0 + val bits = (encodingConfig["bits"] as? Number)?.toLong() ?: 8 + VectorEncoding.productQuantizer(subquantizers, bits) + } + else -> VectorEncoding.none() + } + } + + // Set optional training parameters + val minTrainingSize = (indexConfig["minTrainingSize"] as? Number)?.toLong() + if (minTrainingSize != null && minTrainingSize > 0) { + vectorConfig.minTrainingSize = minTrainingSize + } + + val maxTrainingSize = (indexConfig["maxTrainingSize"] as? Number)?.toLong() + if (maxTrainingSize != null && maxTrainingSize > 0) { + vectorConfig.maxTrainingSize = maxTrainingSize + } + + // Set number of probes (affects search accuracy vs speed) + val numProbes = (indexConfig["numProbes"] as? Number)?.toLong() + if (numProbes != null && numProbes > 0) { + vectorConfig.numProbes = numProbes + } + + // Set lazy indexing (index built on first query) + val isLazy = indexConfig["isLazy"] as? Boolean + if (isLazy != null) { + vectorConfig.isLazy = isLazy + } + + // Create the index + collection.createIndex(indexName, vectorConfig) +} + +/* + * INTEGRATION INSTRUCTIONS + * ======================== + * + * To integrate this into CollectionManager.kt: + * + * 1. Add the imports at the top of the file: + * + * import com.couchbase.lite.VectorIndexConfiguration + * import com.couchbase.lite.VectorEncoding + * import com.couchbase.lite.DistanceMetric + * import com.couchbase.lite.ScalarQuantizerType + * + * 2. Modify the createIndex function signature to accept optional config: + * + * fun createIndex( + * indexName: String, + * indexType: String, + * items: List>, + * indexConfig: Map? = null, // ADD THIS + * collectionName: String, + * scopeName: String, + * databaseName: String + * ) + * + * 3. Add the vector case to the when statement: + * + * when (indexType) { + * "value" -> { + * // existing code... + * } + * "full-text" -> { + * // existing code... + * } + * "vector" -> { + * val config = indexConfig + * ?: throw Exception("Vector index requires config") + * createVectorIndex(indexName, config, collection) + * } + * else -> throw Exception("Unknown index type: $indexType") + * } + * + * 4. Update the React Native bridge module (CblReactnativeModule.kt) to + * extract and pass the full config map for vector indexes. + */