@@ -2381,6 +2381,264 @@ ApiResponse UpdateIndexConfig(const json& request) {
23812381 });
23822382}
23832383
2384+ // ============================================================================
2385+ // Scalar Index Management
2386+ // ============================================================================
2387+
2388+ static LanceDBIndexType parse_scalar_index_type (const std::string& type_str) {
2389+ if (type_str == " BTREE" ) return LANCEDB_INDEX_BTREE ;
2390+ if (type_str == " BITMAP" ) return LANCEDB_INDEX_BITMAP ;
2391+ if (type_str == " LABELLIST" ) return LANCEDB_INDEX_LABELLIST ;
2392+ return LANCEDB_INDEX_AUTO ; // invalid sentinel
2393+ }
2394+
2395+ static std::string index_type_to_string (LanceDBIndexType type) {
2396+ switch (type) {
2397+ case LANCEDB_INDEX_BTREE : return " BTREE" ;
2398+ case LANCEDB_INDEX_BITMAP : return " BITMAP" ;
2399+ case LANCEDB_INDEX_LABELLIST : return " LABELLIST" ;
2400+ case LANCEDB_INDEX_FTS : return " FTS" ;
2401+ case LANCEDB_INDEX_IVF_FLAT : return " IVF_FLAT" ;
2402+ case LANCEDB_INDEX_IVF_PQ : return " IVF_PQ" ;
2403+ case LANCEDB_INDEX_IVF_HNSW_PQ : return " IVF_HNSW_PQ" ;
2404+ case LANCEDB_INDEX_IVF_HNSW_SQ : return " IVF_HNSW_SQ" ;
2405+ default : return " UNKNOWN" ;
2406+ }
2407+ }
2408+
2409+ // CreateScalarIndex - Create scalar indexes on specific columns
2410+ ApiResponse CreateScalarIndex (const json& request) {
2411+ if (!request.contains (" indexName" )) {
2412+ return make_error (400 , " ValidationException" , " indexName is required" );
2413+ }
2414+ if (!request.contains (" vectorBucketName" )) {
2415+ return make_error (400 , " ValidationException" , " vectorBucketName is required" );
2416+ }
2417+ if (!request.contains (" columns" ) || !request[" columns" ].is_array () || request[" columns" ].empty ()) {
2418+ return make_error (400 , " ValidationException" ,
2419+ " columns array is required (each element: {name, indexType})" );
2420+ }
2421+
2422+ std::string bucket_name = request[" vectorBucketName" ].get <std::string>();
2423+ std::string index_name = request[" indexName" ].get <std::string>();
2424+
2425+ if (!LanceDBHelper::index_exists (bucket_name, index_name)) {
2426+ return make_error (404 , " NotFoundException" ,
2427+ " Index '" + index_name + " ' not found" );
2428+ }
2429+
2430+ // Validate columns before connecting
2431+ struct ColSpec {
2432+ std::string name;
2433+ LanceDBIndexType type;
2434+ };
2435+ std::vector<ColSpec> col_specs;
2436+
2437+ for (const auto & col : request[" columns" ]) {
2438+ if (!col.contains (" name" ) || !col[" name" ].is_string () || col[" name" ].get <std::string>().empty ()) {
2439+ return make_error (400 , " ValidationException" ,
2440+ " Each column must have a 'name' field" );
2441+ }
2442+ std::string col_name = col[" name" ].get <std::string>();
2443+ std::string type_str = col.value (" indexType" , " BTREE" );
2444+
2445+ LanceDBIndexType idx_type = parse_scalar_index_type (type_str);
2446+ if (idx_type == LANCEDB_INDEX_AUTO ) {
2447+ return make_error (400 , " ValidationException" ,
2448+ " Invalid indexType '" + type_str + " ' for column '" + col_name +
2449+ " '. Must be BTREE, BITMAP, or LABELLIST" );
2450+ }
2451+ col_specs.push_back ({col_name, idx_type});
2452+ }
2453+
2454+ // Connect and open table
2455+ std::string db_path = utils::get_index_db_path (bucket_name, index_name);
2456+ LanceDBConnection* conn = LanceDBHelper::connect (db_path);
2457+ if (!conn) {
2458+ return make_error (500 , " InternalServerException" ,
2459+ " Failed to connect to index database" );
2460+ }
2461+
2462+ LanceDBTable* table = LanceDBHelper::open_table (conn, " vectors" );
2463+ if (!table) {
2464+ lancedb_connection_free (conn);
2465+ return make_error (500 , " InternalServerException" ,
2466+ " Failed to open vectors table" );
2467+ }
2468+
2469+ // Create a scalar index for each column
2470+ json created = json::array ();
2471+ for (const auto & spec : col_specs) {
2472+ const char * col_name = spec.name .c_str ();
2473+ const char * columns[] = {col_name};
2474+ LanceDBScalarIndexConfig cfg;
2475+ cfg.replace = 1 ;
2476+ cfg.force_update_statistics = 0 ;
2477+
2478+ char * err_msg = nullptr ;
2479+ LanceDBError result = lancedb_table_create_scalar_index (
2480+ table, columns, 1 , spec.type , &cfg, &err_msg);
2481+
2482+ if (result != LANCEDB_SUCCESS ) {
2483+ std::string error = err_msg ? err_msg : lancedb_error_to_message (result);
2484+ if (err_msg) lancedb_free_string (err_msg);
2485+ lancedb_table_free (table);
2486+ lancedb_connection_free (conn);
2487+ return make_error (500 , " InternalServerException" ,
2488+ " Failed to create scalar index on column '" + spec.name + " ': " + error);
2489+ }
2490+
2491+ created.push_back ({
2492+ {" column" , spec.name },
2493+ {" indexType" , index_type_to_string (spec.type )}
2494+ });
2495+
2496+ LOG_INFO (" CREATE_SCALAR_INDEX" , index_name,
2497+ " Scalar index created on column '" + spec.name +
2498+ " ' (type=" + index_type_to_string (spec.type ) + " )" );
2499+ }
2500+
2501+ lancedb_table_free (table);
2502+ lancedb_connection_free (conn);
2503+
2504+ return make_success ({
2505+ {" indexName" , index_name},
2506+ {" vectorBucketName" , bucket_name},
2507+ {" createdIndexes" , created}
2508+ });
2509+ }
2510+
2511+ // ListScalarIndexes - List all indexes on the table
2512+ ApiResponse ListScalarIndexes (const json& request) {
2513+ if (!request.contains (" indexName" )) {
2514+ return make_error (400 , " ValidationException" , " indexName is required" );
2515+ }
2516+ if (!request.contains (" vectorBucketName" )) {
2517+ return make_error (400 , " ValidationException" , " vectorBucketName is required" );
2518+ }
2519+
2520+ std::string bucket_name = request[" vectorBucketName" ].get <std::string>();
2521+ std::string index_name = request[" indexName" ].get <std::string>();
2522+
2523+ if (!LanceDBHelper::index_exists (bucket_name, index_name)) {
2524+ return make_error (404 , " NotFoundException" ,
2525+ " Index '" + index_name + " ' not found" );
2526+ }
2527+
2528+ std::string db_path = utils::get_index_db_path (bucket_name, index_name);
2529+ LanceDBConnection* conn = LanceDBHelper::connect (db_path);
2530+ if (!conn) {
2531+ return make_error (500 , " InternalServerException" ,
2532+ " Failed to connect to index database" );
2533+ }
2534+
2535+ LanceDBTable* table = LanceDBHelper::open_table (conn, " vectors" );
2536+ if (!table) {
2537+ lancedb_connection_free (conn);
2538+ return make_error (500 , " InternalServerException" ,
2539+ " Failed to open vectors table" );
2540+ }
2541+
2542+ LanceDBIndexInfo* indices = nullptr ;
2543+ size_t count = 0 ;
2544+ char * err_msg = nullptr ;
2545+ LanceDBError result = lancedb_table_list_indices_detailed (
2546+ table, &indices, &count, &err_msg);
2547+
2548+ if (result != LANCEDB_SUCCESS ) {
2549+ std::string error = err_msg ? err_msg : lancedb_error_to_message (result);
2550+ if (err_msg) lancedb_free_string (err_msg);
2551+ lancedb_table_free (table);
2552+ lancedb_connection_free (conn);
2553+ return make_error (500 , " InternalServerException" ,
2554+ " Failed to list indices: " + error);
2555+ }
2556+
2557+ json indexes_json = json::array ();
2558+ for (size_t i = 0 ; i < count; i++) {
2559+ json idx;
2560+ idx[" name" ] = indices[i].name ? std::string (indices[i].name ) : " " ;
2561+ idx[" indexType" ] = index_type_to_string (indices[i].index_type );
2562+
2563+ json cols = json::array ();
2564+ for (size_t j = 0 ; j < indices[i].num_columns ; j++) {
2565+ if (indices[i].columns [j]) {
2566+ cols.push_back (std::string (indices[i].columns [j]));
2567+ }
2568+ }
2569+ idx[" columns" ] = cols;
2570+ indexes_json.push_back (idx);
2571+ }
2572+
2573+ if (indices) lancedb_free_index_list_detailed (indices, count);
2574+ lancedb_table_free (table);
2575+ lancedb_connection_free (conn);
2576+
2577+ return make_success ({
2578+ {" indexName" , index_name},
2579+ {" vectorBucketName" , bucket_name},
2580+ {" indexes" , indexes_json}
2581+ });
2582+ }
2583+
2584+ // DropScalarIndex - Drop a scalar index by name
2585+ ApiResponse DropScalarIndex (const json& request) {
2586+ if (!request.contains (" indexName" )) {
2587+ return make_error (400 , " ValidationException" , " indexName is required" );
2588+ }
2589+ if (!request.contains (" vectorBucketName" )) {
2590+ return make_error (400 , " ValidationException" , " vectorBucketName is required" );
2591+ }
2592+ if (!request.contains (" scalarIndexName" )) {
2593+ return make_error (400 , " ValidationException" , " scalarIndexName is required" );
2594+ }
2595+
2596+ std::string bucket_name = request[" vectorBucketName" ].get <std::string>();
2597+ std::string index_name = request[" indexName" ].get <std::string>();
2598+ std::string scalar_index_name = request[" scalarIndexName" ].get <std::string>();
2599+
2600+ if (!LanceDBHelper::index_exists (bucket_name, index_name)) {
2601+ return make_error (404 , " NotFoundException" ,
2602+ " Index '" + index_name + " ' not found" );
2603+ }
2604+
2605+ std::string db_path = utils::get_index_db_path (bucket_name, index_name);
2606+ LanceDBConnection* conn = LanceDBHelper::connect (db_path);
2607+ if (!conn) {
2608+ return make_error (500 , " InternalServerException" ,
2609+ " Failed to connect to index database" );
2610+ }
2611+
2612+ LanceDBTable* table = LanceDBHelper::open_table (conn, " vectors" );
2613+ if (!table) {
2614+ lancedb_connection_free (conn);
2615+ return make_error (500 , " InternalServerException" ,
2616+ " Failed to open vectors table" );
2617+ }
2618+
2619+ char * err_msg = nullptr ;
2620+ LanceDBError result = lancedb_table_drop_index (table, scalar_index_name.c_str (), &err_msg);
2621+
2622+ lancedb_table_free (table);
2623+ lancedb_connection_free (conn);
2624+
2625+ if (result != LANCEDB_SUCCESS ) {
2626+ std::string error = err_msg ? err_msg : lancedb_error_to_message (result);
2627+ if (err_msg) lancedb_free_string (err_msg);
2628+ return make_error (500 , " InternalServerException" ,
2629+ " Failed to drop index '" + scalar_index_name + " ': " + error);
2630+ }
2631+
2632+ LOG_INFO (" DROP_SCALAR_INDEX" , index_name,
2633+ " Dropped scalar index '" + scalar_index_name + " '" );
2634+
2635+ return make_success ({
2636+ {" indexName" , index_name},
2637+ {" vectorBucketName" , bucket_name},
2638+ {" droppedIndex" , scalar_index_name}
2639+ });
2640+ }
2641+
23842642// ============================================================================
23852643// Command Line Interface
23862644// ============================================================================
@@ -2401,6 +2659,9 @@ S3 Vector Concurrent Service with Background Indexing
24012659 TriggerRebuild Manually trigger index rebuild
24022660 GetIndexState Get current index state and stats
24032661 UpdateIndexConfig Update index configuration and thresholds
2662+ CreateScalarIndex Create scalar indexes on table columns
2663+ ListScalarIndexes List all indexes on the table
2664+ DropScalarIndex Drop an index by name
24042665 --help, -h Show this help message
24052666
24062667DESIGN:
@@ -2480,12 +2741,40 @@ S3 Vector Concurrent Service with Background Indexing
24802741 "deletionRatioThreshold": 0.25
24812742 }'
24822743
2744+ # Create scalar indexes on columns
2745+ ./s3vector_concurrent_service CreateScalarIndex '{
2746+ "vectorBucketName": "my-bucket",
2747+ "indexName": "my-index",
2748+ "columns": [
2749+ {"name": "category", "indexType": "BITMAP"},
2750+ {"name": "price", "indexType": "BTREE"}
2751+ ]
2752+ }'
2753+
2754+ # List all indexes on the table
2755+ ./s3vector_concurrent_service ListScalarIndexes '{
2756+ "vectorBucketName": "my-bucket",
2757+ "indexName": "my-index"
2758+ }'
2759+
2760+ # Drop a scalar index by name
2761+ ./s3vector_concurrent_service DropScalarIndex '{
2762+ "vectorBucketName": "my-bucket",
2763+ "indexName": "my-index",
2764+ "scalarIndexName": "category_idx"
2765+ }'
2766+
24832767INDEX TYPES:
24842768 IVF_FLAT - Inverted file index (no compression)
24852769 IVF_PQ - IVF with product quantization (default)
24862770 IVF_HNSW_PQ - IVF with HNSW graph and PQ
24872771 IVF_HNSW_SQ - IVF with HNSW graph and scalar quantization
24882772
2773+ SCALAR INDEX TYPES:
2774+ BTREE - B-tree index (ordered data, range queries)
2775+ BITMAP - Bitmap index (low-cardinality categorical data)
2776+ LABELLIST - Label list index (list/tag columns)
2777+
24892778QUERY PARAMETERS:
24902779 nprobes - Number of IVF partitions to search (higher = more accurate)
24912780 refineFactor - Number of candidates to refine (higher = more accurate)
@@ -2601,6 +2890,12 @@ int main(int argc, char* argv[]) {
26012890 response = GetIndexState (request);
26022891 } else if (command == " UpdateIndexConfig" ) {
26032892 response = UpdateIndexConfig (request);
2893+ } else if (command == " CreateScalarIndex" ) {
2894+ response = CreateScalarIndex (request);
2895+ } else if (command == " ListScalarIndexes" ) {
2896+ response = ListScalarIndexes (request);
2897+ } else if (command == " DropScalarIndex" ) {
2898+ response = DropScalarIndex (request);
26042899 } else {
26052900 std::cerr << " Unknown command: " << command << std::endl;
26062901 print_help ();
0 commit comments