From 53429d3edab9f62d512dd7c431ee53b87a02974e Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Tue, 19 Mar 2024 20:16:24 -0700 Subject: [PATCH] CBL-5541 : Update vector search test per changes in v1.8 * Port the change from release/3.2 branch. * Test 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 20, 22, 23, 24, 25 : Use centroids = 8. * Test 5, 6, 7, 8, 9, 10, 11, 12, 14, 17, 18, 20, 22, 24, 25 : Verify that the index is trained. * Test16 : Verify that the index is untrained. * Test 20 : Add the step to create the query after the index was deleted. --- Objective-C/Tests/CustomLogger.h | 2 + Objective-C/Tests/CustomLogger.m | 9 + Objective-C/Tests/VectorSearchTest.m | 406 ++++++++++++------- Swift/Tests/CustomLogger.swift | 9 + Swift/Tests/VectorSearchTest.swift | 579 ++++++++++++++++----------- 5 files changed, 618 insertions(+), 387 deletions(-) diff --git a/Objective-C/Tests/CustomLogger.h b/Objective-C/Tests/CustomLogger.h index 0866efb1e..d39ce82e5 100644 --- a/Objective-C/Tests/CustomLogger.h +++ b/Objective-C/Tests/CustomLogger.h @@ -30,6 +30,8 @@ NS_ASSUME_NONNULL_BEGIN - (void) reset; +- (BOOL) containsString: (NSString *)string; + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/CustomLogger.m b/Objective-C/Tests/CustomLogger.m index 009dde26c..20f73bfbf 100644 --- a/Objective-C/Tests/CustomLogger.m +++ b/Objective-C/Tests/CustomLogger.m @@ -42,6 +42,15 @@ - (void) reset { [_lines removeAllObjects]; } +- (BOOL) containsString: (NSString *)string { + for (NSString* line in _lines) { + if ([line containsString: string]) { + return YES; + } + } + return NO; +} + - (void)logWithLevel: (CBLLogLevel)level domain: (CBLLogDomain)domain message: (NSString*)message { [_lines addObject: message]; } diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 7690381f1..93927e70d 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -30,13 +30,21 @@ @interface VectorSearchTest : CBLTestCase /** Test Spec : https://docs.google.com/document/d/1p8RPmlXjA5KKvHLoFR6dcubFlObAqomlacxVbEvXYoU */ -@implementation VectorSearchTest +@implementation VectorSearchTest { + CustomLogger* _logger; +} - (void) setUp { [super setUp]; + + _logger = [[CustomLogger alloc] init]; + _logger.level = kCBLLogLevelInfo; + CBLDatabase.log.custom = _logger; } - (void) tearDown { + CBLDatabase.log.custom = nil; + [super tearDown]; } @@ -61,6 +69,14 @@ - (void) initDB { return wordMap; } +- (void) resetIndexWasTrainedLog { + [_logger reset]; +} + +- (BOOL) checkIndexWasTrained { + return ![_logger containsString: @"Untrained index; queries may be slow"]; +} + /** * 1. TestVectorIndexConfigurationDefaultValue * Description @@ -143,8 +159,8 @@ - (void) testVectorIndexConfigurationSettersAndGetters { * - dimensions: 2 and 2048 * - centroids: 20 * 2. Check that the config can be created without an error thrown. - 3. Use the config to create the index and check that the index - can be created successfully. + * 3. Use the config to create the index and check that the index + * can be created successfully. * 4. Change the dimensions to 1 and 2049. * 5. Check that an invalid argument exception is thrown. */ @@ -235,26 +251,32 @@ - (void) testCentroidsValidation { * index can be used in the query. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 * - centroids: 20 - * 3. Check that the index is created without an error returned. - * 4. Get index names from the _default.words collection and check that the index + * 4. Check that the index is created without an error returned. + * 5. Get index names from the _default.words collection and check that the index * names contains “words_index”. - * 5. Create an SQL++ query: + * 6. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 20) - * 6. Check the explain() result of the query to ensure that the "words_index" is used. - * 7. Execute the query and check that 20 results are returned. + * 7. Check the explain() result of the query to ensure that the "words_index" is used. + * 8. Execute the query and check that 20 results are returned. + * 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 10. Reset the custom logger. */ - (void) testCreateVectorIndex { NSError* error; CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; + config.minTrainingSize = 200; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); NSArray* names = [collection indexes: &error]; @@ -273,6 +295,7 @@ - (void) testCreateVectorIndex { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); } /** @@ -283,27 +306,30 @@ - (void) testCreateVectorIndex { * used in the query. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 8 (The default number of probes which is the max number of - * centroids the query will look for the vector). - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query: + * - centroids: 8 + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 350) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 300 results are returned. - * 7. Update the documents: + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 300 results are returned. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Update the documents: * - Create _default.words.word301 with the content from _default.extwords.word1 * - Create _default.words.word302 with the content from _default.extwords.word2 * - Update _default.words.word1 with the content from _default.extwords.word3 * - Delete _default.words.word2 - * 8. Execute the query again and check that 301 results are returned, and + * 10. Execute the query again and check that 301 results are returned, and * - word301 and word302 are included. * - word1’s word is updated with the word from _default.extwords.word3 * - word2 is not included. + * 11. Reset the custom logger. */ - (void) testUpdateVectorIndex { NSError* error; @@ -333,6 +359,7 @@ - (void) testUpdateVectorIndex { CBLQueryResultSet* rs = [q execute: &error]; NSArray* results = rs.allObjects; AssertEqual(results.count, 300); + Assert([self checkIndexWasTrained]); // Update docs: CBLDocument* extWord1 = [extWordsCollection documentWithID: @"word1" error : &error]; @@ -367,27 +394,30 @@ - (void) testUpdateVectorIndex { * invalid vectors, the invalid vectors will be skipped from indexing. * Steps * 1. Copy database words_db. - * 2. Update documents: + * 2. Register a custom logger to capture the INFO log. + * 3. Update documents: * - Update _default.words word1 with "vector" = null * - Update _default.words word2 with "vector" = "string" * - Update _default.words word3 by removing the "vector" key. * - Update _default.words word4 by removing one number from the "vector" key. - * 3. Create a vector index named "words_index" in _default.words collection. + * 4. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 8 (The default number of probes which is the max number of - * centroids the query will look for the vector). - * 4. Check that the index is created without an error returned. - * 5. Create an SQL++ query. + * - centroids: 8 + * 5. Check that the index is created without an error returned. + * 6. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 350) - * 6. Execute the query and check that 296 results are returned, and the results + * 7. Execute the query and check that 296 results are returned, and the results * do not include document word1, word2, word3, and word4. - * 7. Update an already index vector with an invalid vector. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Update an already index vector with an invalid vector. * - Update _default.words word5 with "vector" = null. - * 8. Execute the query and check that 295 results are returned, and the results + * 10. Execute the query and check that 295 results are returned, and the results * do not include document word5. + * 11. Reset the custom logger. */ - (void) testCreateVectorIndexWithInvalidVectors { NSError* error; @@ -439,6 +469,7 @@ - (void) testCreateVectorIndexWithInvalidVectors { AssertNil(wordMap[@"word2"]); AssertNil(wordMap[@"word3"]); AssertNil(wordMap[@"word4"]); + Assert([self checkIndexWasTrained]); auxDoc = [[collection documentWithID: @"word5" error: &error] toMutable]; [auxDoc setString: nil forKey: @"vector"]; @@ -458,28 +489,31 @@ - (void) testCreateVectorIndexWithInvalidVectors { * the vectors returned by a predictive model. * Steps * 1. Copy database words_db. - * 2. Register "WordEmbedding" predictive model defined in section 2. - * 3. Create a vector index named "words_pred_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Register "WordEmbedding" predictive model defined in section 2. + * 4. Create a vector index named "words_pred_index" in _default.words collection. * - expression: "prediction(WordEmbedding, {"word": word}).vector" * - dimensions: 300 - * - centroids: 8 (The default number of probes which is the max number of - * centroids the query will look for the vector). - * 4. Check that the index is created without an error returned. - * 5. Create an SQL++ query: + * - centroids: 8 + * 5. Check that the index is created without an error returned. + * 6. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_pred_index, , 350) - * 6. Check the explain() result of the query to ensure that the "words_pred_index" is used. - * 7. Execute the query and check that 300 results are returned. - * 8. Update the vector index: + * 7. Check the explain() result of the query to ensure that the "words_pred_index" is used. + * 8. Execute the query and check that 300 results are returned. + * 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 10. Update the vector index: * - Create _default.words.word301 with the content from _default.extwords.word1 * - Create _default.words.word302 with the content from _default.extwords.word2 * - Update _default.words.word1 with the content from _default.extwords.word3 * - Delete _default.words.word2 - * 9. Execute the query and check that 301 results are returned. + * 11. Execute the query and check that 301 results are returned. * - word301 and word302 are included. * - word1 is updated with the word from _default.extwords.word2. * - word2 is not included. + * 12. Reset the custom logger. */ - (void) testCreateVectorIndexUsingPredictionModel { NSError* error; @@ -511,9 +545,11 @@ - (void) testCreateVectorIndexUsingPredictionModel { NSString* explain = [q explain: &error]; Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_pred_index"].location != NSNotFound); + CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 300); + Assert([self checkIndexWasTrained]); // Create words.word301 with extwords.word1 content CBLDocument* extWord1 = [extWordsCollection documentWithID: @"word1" error : &error]; @@ -554,29 +590,31 @@ - (void) testCreateVectorIndexUsingPredictionModel { * from indexing. * Steps * 1. Copy database words_db. - * 2. Register "WordEmbedding" predictive model defined in section 2. - * 3. Update documents. + * 2. Register a custom logger to capture the INFO log. + * 3. Register "WordEmbedding" predictive model defined in section 2. + * 4. Update documents. * - Update _default.words word1 with "vector" = null * - Update _default.words word2 with "vector" = "string" * - Update _default.words word3 by removing the "vector" key. * - Update _default.words word4 by removing one number from the "vector" key. - * 4. Create a vector index named "words_prediction_index" in _default.words collection. + * 5. Create a vector index named "words_prediction_index" in _default.words collection. * - expression: "prediction(WordEmbedding, {"word": word}).embedding" * - dimensions: 300 - * - centroids: 8 (The default number of probes which is the max number - * of centroids the query will look for the vector). - * 5. Check that the index is created without an error returned. - * 6. Create an SQL++ query. + * - centroids: 8 + * 6. Check that the index is created without an error returned. + * 7. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_pred_index, , 350) - * 7. Check the explain() result of the query to ensure that the "words_predi_index" is used. - * 8. Execute the query and check that 296 results are returned and the results + * 8. Check the explain() result of the query to ensure that the "words_predi_index" is used. + * 9. Execute the query and check that 296 results are returned and the results * do not include word1, word2, word3, and word4. - * 9. Update an already index vector with a non existing word in the database. + * 10. Verify that the index was trained by checking that the “Untrained index; queries may be slow” doesn’t exist in the log. + * 11. Update an already index vector with a non existing word in the database. * - Update _default.words.word5 with “word” = “Fried Chicken”. - * 10. Execute the query and check that 295 results are returned, and the results + * 12. Execute the query and check that 295 results are returned, and the results * do not include document word5. + * 13. Reset the custom logger. */ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { NSError* error; @@ -633,6 +671,7 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { AssertNil(wordMap[@"word2"]); AssertNil(wordMap[@"word3"]); AssertNil(wordMap[@"word4"]); + Assert([self checkIndexWasTrained]); auxDoc = [[collection documentWithID: @"word5" error: &error] toMutable]; [auxDoc setString: @"Fried Chicken" forKey: @"word"]; @@ -654,20 +693,24 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { * index can be created and used. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - encoding: ScalarQuantizer(type: SQ4) - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 20) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 20 results are returned. - * 7. Delete the "words_index". - * 8. Repeat Step 2 – 7 by using SQ6 and SQ8 respectively. + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 20 results are returned. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Delete the "words_index". + * 10. Reset the custom logger. + * 11. Repeat Step 2 – 10 by using SQ6 and SQ8 respectively. */ - (void) testCreateVectorIndexWithSQ { NSError* error; @@ -676,7 +719,7 @@ - (void) testCreateVectorIndexWithSQ { // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; config.encoding = [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ4]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -697,8 +740,10 @@ - (void) testCreateVectorIndexWithSQ { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); // Repeat using SQ6 + [self resetIndexWasTrainedLog]; [collection deleteIndexWithName: @"words_index" error: &error]; config.encoding = [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ6]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -707,8 +752,10 @@ - (void) testCreateVectorIndexWithSQ { rs = [q execute: &error]; allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); // Repeat using SQ8 + [self resetIndexWasTrainedLog]; [collection deleteIndexWithName: @"words_index" error: &error]; config.encoding = [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ8]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -717,6 +764,7 @@ - (void) testCreateVectorIndexWithSQ { rs = [q execute: &error]; allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); } /** @@ -725,18 +773,22 @@ - (void) testCreateVectorIndexWithSQ { * Using the None Encoding, test that the vector index can be created and used. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - encoding: None - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 20) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 20 results are returned. + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 20 results are returned. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Reset the custom logger. */ - (void) testCreateVectorIndexWithNoneEncoding { NSError* error; @@ -745,7 +797,7 @@ - (void) testCreateVectorIndexWithNoneEncoding { // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; config.encoding = [CBLVectorEncoding none]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -762,42 +814,51 @@ - (void) testCreateVectorIndexWithNoneEncoding { NSString* explain = [q explain: &error]; Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); + CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); } /** + * FAILED : https://issues.couchbase.com/browse/CBL-5538 + * Disable bits = 12 for now. + * * 12. TestCreateVectorIndexWithPQ * Description * Using the PQ Encoding, test that the vector index can be created and used. The * test also tests the lower and upper bounds of the PQ’s bits. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - encoding : PQ(subquantizers: 5 bits: 8) - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 20) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 20 results are returned. - * 7. Delete the “words_index”. - * 8. Repeat steps 2 to 7 by changing the PQ’s bits to 4 and 12 respectively. + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 20 results are returned. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Delete the “words_index”. + * 10. Reset the custom logger. + * 11. Repeat steps 2 to 10 by changing the PQ’s bits to 4 and 12 respectively. */ - (void) testCreateVectorIndexWithPQ { NSError* error; CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - for (NSNumber* bit in @[@8, @4, @12]) { + for (NSNumber* bit in @[@4, @8, /* @12 */]) { // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: 5 bits: bit.unsignedIntValue]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -818,9 +879,13 @@ - (void) testCreateVectorIndexWithPQ { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); // Delete index [collection deleteIndexWithName: @"words_index" error: &error]; + + // Reset log + [self resetIndexWasTrainedLog]; } } @@ -835,12 +900,12 @@ - (void) testCreateVectorIndexWithPQ { * 2. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - PQ(subquantizers: 2, bits: 8) * 3. Check that the index is created without an error returned. * 4. Delete the "words_index". * 5. Repeat steps 2 to 4 by changing the subquantizers to - * 3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 75, 100, 150, and 300. + * 3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 75, 100, 150, and 300. * 6. Repeat step 2 to 4 by changing the subquantizers to 0 and 7. * 7. Check that an invalid argument exception is thrown. */ @@ -851,7 +916,7 @@ - (void) testSubquantizersValidation { // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: 2 bits: 8]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -876,33 +941,43 @@ - (void) testSubquantizersValidation { } /** + * https://issues.couchbase.com/browse/CBL-5537 + * The test will fail when using centroid = 20 as the number of vectors for training + * the index is not low. + * * 14. TestCreateVectorIndexWithFixedTrainingSize * Description * Test that the vector index can be created and trained when minTrainingSize * equals to maxTrainingSize. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - minTrainingSize: 100 and maxTrainingSize: 100 - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 20) * 5. Check the explain() result of the query to ensure that the "words_index" is used. * 6. Execute the query and check that 20 results are returned. + * 7. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 8. Reset the custom logger. */ - (void) testeCreateVectorIndexWithFixedTrainingSize { + CBLDatabase.log.console.level = kCBLLogLevelVerbose; + NSError* error; CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; config.minTrainingSize = 100; config.maxTrainingSize = 100; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -920,9 +995,11 @@ - (void) testeCreateVectorIndexWithFixedTrainingSize { NSString* explain = [q explain: &error]; Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); + CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); } /** @@ -984,32 +1061,32 @@ - (void) testValidateMinMaxTrainingSize { * Test that the untrained vector index can be used in queries. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - minTrainingSize: 400 * - maxTrainingSize: 500 - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 20) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 20 results are returned. + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 20 results are returned. + * 8. Verify that the index was not trained by checking that the “Untrained index; + * queries may be slow” message exists in the log. + * 9. Reset the custom logger. */ - (void) testQueryUntrainedVectorIndex { - CustomLogger* custom = [[CustomLogger alloc] init]; - custom.level = kCBLLogLevelInfo; - CBLDatabase.log.custom = custom; - NSError* error; CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; // out of bounds (300 words in db) config.minTrainingSize = 400; config.maxTrainingSize = 500; @@ -1028,12 +1105,11 @@ - (void) testQueryUntrainedVectorIndex { NSString* explain = [q explain: &error]; Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); + CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); - - Assert([custom.lines containsObject: @"SQLite message: vectorsearch: Untrained index; queries may be slow."]); - CBLDatabase.log.custom = nil; + AssertFalse([self checkIndexWasTrained]); } /** @@ -1042,19 +1118,23 @@ - (void) testQueryUntrainedVectorIndex { * Test that the vector index can be created and used with the cosine distance metric. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - metric: Cosine - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word,vector_distance(words_index) * FROM _default.words * WHERE vector_match(words_index, , 20) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 20 results are returned and the vector + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 20 results are returned and the vector * distance value is in between 0 – 1.0 inclusively. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Reset the custom logger. */ - (void) testCreateVectorIndexWithCosineDistance { NSError* error; @@ -1063,7 +1143,7 @@ - (void) testCreateVectorIndexWithCosineDistance { // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; config.metric = kCBLDistanceMetricCosine; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -1084,11 +1164,11 @@ - (void) testCreateVectorIndexWithCosineDistance { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allResults; AssertEqual(allObjects.count, 20); - for(CBLQueryResult* result in rs){ Assert([result doubleAtIndex: 3] > 0); Assert([result doubleAtIndex: 3] < 1); } + Assert([self checkIndexWasTrained]); } /** @@ -1097,19 +1177,23 @@ - (void) testCreateVectorIndexWithCosineDistance { * Test that the vector index can be created and used with the euclidean distance metric. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - metric: Euclidean - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word, vector_distance(words_index) * FROM _default.words * WHERE vector_match(words_index, , 20) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 20 results are returned and the + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 20 results are returned and the * distance value is more than zero. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Reset the custom logger. */ - (void) testCreateVectorIndexWithEuclideanDistance { NSError* error; @@ -1118,7 +1202,7 @@ - (void) testCreateVectorIndexWithEuclideanDistance { // Create vector index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; config.metric = kCBLDistanceMetricEuclidean; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); @@ -1139,10 +1223,10 @@ - (void) testCreateVectorIndexWithEuclideanDistance { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allResults; AssertEqual(allObjects.count, 20); - for(CBLQueryResult* result in rs){ Assert([result doubleAtIndex: 3] > 0); } + Assert([self checkIndexWasTrained]); } /** @@ -1189,19 +1273,25 @@ - (void) testCreateVectorIndexWithExistingName { * configuration is the same. Otherwise, an error will be returned. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vectors" * - dimensions: 300 - * - centroids: 20 - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * - centroids: 8 + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, , 20) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 20 results are returned. - * 7. Delete index named "words_index". - * 8. Check that getIndexes() does not contain "words_index". + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 20 results are returned. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Delete index named "words_index". + * 10. Check that getIndexes() does not contain "words_index". + * 11. Create the same query again and check that a CouchbaseLiteException is returned + * as the index doesn’t exist. + * 12. Reset the custom logger. */ - (void) testDeleteVectorIndex { NSError* error; @@ -1209,7 +1299,7 @@ - (void) testDeleteVectorIndex { CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); NSArray* names = [collection indexes: &error]; @@ -1229,12 +1319,17 @@ - (void) testDeleteVectorIndex { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allResults; AssertEqual(allObjects.count, 20); + Assert([self checkIndexWasTrained]); // Delete index [collection deleteIndexWithName: @"words_index" error: &error]; names = [collection indexes: &error]; AssertFalse([names containsObject: @"words_index"]); + + [self expectError: CBLErrorDomain code: CBLErrorMissingIndex in: ^BOOL(NSError **err) { + return [self->_db createQuery: sql error: err]; + }]; } /** @@ -1264,17 +1359,21 @@ - (void) testVectorMatchOnNonExistingIndex { * when using the vector_match query without the limit number specified. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * - centroids: 8 + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words * WHERE vector_match(words_index, ) - * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Execute the query and check that 3 results are returned. + * 6. Check the explain() result of the query to ensure that the "words_index" is used. + * 7. Execute the query and check that 3 results are returned. + * 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 9. Reset the custom logger. */ - (void) testVectorMatchDefaultLimit { NSError* error; @@ -1283,7 +1382,7 @@ - (void) testVectorMatchDefaultLimit { // Create index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); NSArray* names = [collection indexes: &error]; @@ -1304,6 +1403,7 @@ - (void) testVectorMatchDefaultLimit { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allResults; AssertEqual(allObjects.count, 3); + Assert([self checkIndexWasTrained]); } /** @@ -1363,19 +1463,23 @@ - (void) testVectorMatchLimitBoundary { * Test that vector_match can be used in AND expression. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * - centroids: 8 + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT word, catid * FROM _default.words * WHERE vector_match(words_index, , 300) AND catid = 'cat1' - * 5. Check that the query can be created without an error. - * 6. Check the explain() result of the query to ensure that the "words_index" is used. - * 7. Execute the query and check that the number of results returned is 50 - * (there are 50 words in catid=1), and the results contain only catid == 'cat1'. + * 6. Check that the query can be created without an error. + * 7. Check the explain() result of the query to ensure that the "words_index" is used. + * 8. Execute the query and check that the number of results returned is 50 + * (there are 50 words in catid=1), and the results contain only catid == 'cat1'. + * 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 10. Reset the custom logger. */ - (void) testVectorMatchWithAndExpression { NSError* error; @@ -1384,7 +1488,7 @@ - (void) testVectorMatchWithAndExpression { // Create index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); NSArray* names = [collection indexes: &error]; @@ -1405,10 +1509,10 @@ - (void) testVectorMatchWithAndExpression { CBLQueryResultSet* rs = [q execute: &error]; NSArray* results = rs.allResults; AssertEqual(results.count, 50); - for(CBLQueryResult* result in results){ AssertEqualObjects([result valueAtIndex: 1], @"cat1"); } + Assert([self checkIndexWasTrained]); } /** @@ -1417,19 +1521,23 @@ - (void) testVectorMatchWithAndExpression { * Test that vector_match can be used in multiple AND expressions. * Steps * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in _default.words collection. + * 2. Register a custom logger to capture the INFO log. + * 3. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 - * 3. Check that the index is created without an error returned. - * 4. Create an SQL++ query. + * - centroids: 8 + * 4. Check that the index is created without an error returned. + * 5. Create an SQL++ query. * - SELECT word, catid * FROM _default.words * WHERE (vector_match(words_index, , 300) AND word is valued) AND catid = 'cat1' - * 5. Check that the query can be created without an error. - * 6. Check the explain() result of the query to ensure that the "words_index" is used. - * 7. Execute the query and check that the number of results returned is 50 - * (there are 50 words in catid=1), and the results contain only catid == 'cat1'. + * 6. Check that the query can be created without an error. + * 7. Check the explain() result of the query to ensure that the "words_index" is used. + * 8. Execute the query and check that the number of results returned is 50 + * (there are 50 words in catid=1), and the results contain only catid == 'cat1'. + * 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + * doesn’t exist in the log. + * 10. Reset the custom logger. */ - (void) testVectorMatchWithMultipleAndExpression { NSError* error; @@ -1438,7 +1546,7 @@ - (void) testVectorMatchWithMultipleAndExpression { // Create index CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 - centroids: 20]; + centroids: 8]; Assert([collection createIndexWithName: @"words_index" config: config error: &error]); NSArray* names = [collection indexes: &error]; @@ -1459,10 +1567,10 @@ - (void) testVectorMatchWithMultipleAndExpression { CBLQueryResultSet* rs = [q execute: &error]; NSArray* results = rs.allResults; AssertEqual(results.count, 50); - for(CBLQueryResult* result in results){ AssertEqualObjects([result stringAtIndex: 1], @"cat1"); } + Assert([self checkIndexWasTrained]); } /** diff --git a/Swift/Tests/CustomLogger.swift b/Swift/Tests/CustomLogger.swift index 774febe2a..433cd5316 100644 --- a/Swift/Tests/CustomLogger.swift +++ b/Swift/Tests/CustomLogger.swift @@ -34,4 +34,13 @@ class CustomLogger: Logger { lines.append(message) } + func containsString(_ string: String) -> Bool { + for line in lines { + if (line as NSString).contains(string) { + return true + } + } + return false + } + } diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 83ecf7c49..18b89557e 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -12,14 +12,30 @@ import CouchbaseLiteSwift class VectorSearchTest: CBLTestCase { let dinnerVector = [0.03193166106939316, 0.032055653631687164, 0.07188114523887634, -0.09893740713596344, -0.07693558186292648, 0.07570040225982666, 0.42786234617233276, -0.11442682892084122, -0.7863243818283081, -0.47983086109161377, -0.10168658196926117, 0.10985997319221497, -0.15261511504650116, -0.08458329737186432, -0.16363860666751862, -0.20225222408771515, -0.2593214809894562, -0.032738097012043, -0.16649988293647766, -0.059701453894376755, 0.17472036182880402, -0.007310086861252785, -0.13918264210224152, -0.07260780036449432, -0.02461239881813526, -0.04195880889892578, -0.15714778006076813, 0.48038315773010254, 0.7536261677742004, 0.41809454560279846, -0.17144775390625, 0.18296195566654205, -0.10611499845981598, 0.11669538915157318, 0.07423929125070572, -0.3105475902557373, -0.045081984251737595, -0.18190748989582062, 0.22430984675884247, 0.05735112354159355, -0.017394868656992912, -0.148889422416687, -0.20618586242198944, -0.1446581482887268, 0.061972495168447495, 0.07787969708442688, 0.14225411415100098, 0.20560632646083832, 0.1786964386701584, -0.380594402551651, -0.18301603198051453, -0.19542981684207916, 0.3879885971546173, -0.2219538390636444, 0.11549852043390274, -0.0021717497147619724, -0.10556972026824951, 0.030264658853411674, 0.16252967715263367, 0.06010117009282112, -0.045007310807704926, 0.02435707487165928, 0.12623260915279388, -0.12688252329826355, -0.3306281864643097, 0.06452160328626633, 0.0707000121474266, -0.04959108680486679, -0.2567063570022583, -0.01878536120057106, -0.10857286304235458, -0.01754194125533104, -0.0713721290230751, 0.05946013703942299, -0.1821729987859726, -0.07293688505887985, -0.2778160572052002, 0.17880073189735413, -0.04669278487563133, 0.05351974070072174, -0.23292849957942963, 0.05746332183480263, 0.15462779998779297, -0.04772235080599785, -0.003306782804429531, 0.058290787041187286, 0.05908169597387314, 0.00504430802538991, -0.1262340396642685, 0.11612161248922348, 0.25303348898887634, 0.18580256402492523, 0.09704313427209854, -0.06087183952331543, 0.19697663187980652, -0.27528849244117737, -0.0837797075510025, -0.09988483041524887, -0.20565757155418396, 0.020984146744012833, 0.031014855951070786, 0.03521743416786194, -0.05171370506286621, 0.009112107567489147, -0.19296088814735413, -0.19363830983638763, 0.1591167151927948, -0.02629968523979187, -0.1695055067539215, -0.35807400941848755, -0.1935291737318039, -0.17090126872062683, -0.35123637318611145, -0.20035606622695923, -0.03487539291381836, 0.2650701701641083, -0.1588021069765091, 0.32268261909484863, -0.024521857500076294, -0.11985184997320175, 0.14826008677482605, 0.194917231798172, 0.07971998304128647, 0.07594677060842514, 0.007186363451182842, -0.14641280472278595, 0.053229596465826035, 0.0619836151599884, 0.003207010915502906, -0.12729716300964355, 0.13496214151382446, 0.107656329870224, -0.16516226530075073, -0.033881571143865585, -0.11175122112035751, -0.005806141998618841, -0.4765360355377197, 0.11495379358530045, 0.1472187340259552, 0.3781401813030243, 0.10045770555734634, -0.1352398842573166, -0.17544329166412354, -0.13191302120685577, -0.10440415143966675, 0.34598618745803833, 0.09728766977787018, -0.25583627820014954, 0.035236816853284836, 0.16205145418643951, -0.06128586828708649, 0.13735555112361908, 0.11582338809967041, -0.10182418674230576, 0.1370954066514969, 0.15048766136169434, 0.06671152263879776, -0.1884871870279312, -0.11004580557346344, 0.24694739282131195, -0.008159132674336433, -0.11668405681848526, -0.01214478351175785, 0.10379738360643387, -0.1626262664794922, 0.09377897530794144, 0.11594484746456146, -0.19621512293815613, 0.26271334290504456, 0.04888357222080231, -0.10103251039981842, 0.33250945806503296, 0.13565145432949066, -0.23888370394706726, -0.13335271179676056, -0.0076894499361515045, 0.18256276845932007, 0.3276212215423584, -0.06567271053791046, -0.1853761374950409, 0.08945729583501816, 0.13876311480998993, 0.09976287186145782, 0.07869105041027069, -0.1346970647573471, 0.29857659339904785, 0.1329529583454132, 0.11350086331367493, 0.09112624824047089, -0.12515446543693542, -0.07917925715446472, 0.2881546914577484, -1.4532661225530319e-05, -0.07712751626968384, 0.21063975989818573, 0.10858846455812454, -0.009552721865475178, 0.1629313975572586, -0.39703384041786194, 0.1904662847518921, 0.18924959003925323, -0.09611514210700989, 0.001136621693149209, -0.1293390840291977, -0.019481558352708817, 0.09661063551902771, -0.17659670114517212, 0.11671938002109528, 0.15038564801216125, -0.020016824826598167, -0.20642194151878357, 0.09050136059522629, -0.1768183410167694, -0.2891409397125244, 0.04596589505672455, -0.004407480824738741, 0.15323616564273834, 0.16503025591373444, 0.17370983958244324, 0.02883041836321354, 0.1463884711265564, 0.14786243438720703, -0.026439940556883812, -0.03113352134823799, 0.10978181660175323, 0.008928884752094746, 0.24813824892044067, -0.06918247044086456, 0.06958142668008804, 0.17475970089435577, 0.04911438003182411, 0.17614248394966125, 0.19236832857131958, -0.1425514668226242, -0.056531358510255814, -0.03680772706866264, -0.028677923604846, -0.11353116482496262, 0.012293893843889236, -0.05192646384239197, 0.20331953465938568, 0.09290937334299088, 0.15373043715953827, 0.21684466302394867, 0.40546831488609314, -0.23753701150417328, 0.27929359674453735, -0.07277711480855942, 0.046813879162073135, 0.06883064657449722, -0.1033223420381546, 0.15769273042678833, 0.21685580909252167, -0.00971329677850008, 0.17375953495502472, 0.027193285524845123, -0.09943609684705734, 0.05770351365208626, 0.0868956446647644, -0.02671697922050953, -0.02979189157485962, 0.024517420679330826, -0.03931192681193352, -0.35641804337501526, -0.10590721666812897, -0.2118944674730301, -0.22070199251174927, 0.0941486731171608, 0.19881175458431244, 0.1815279871225357, -0.1256905049085617, -0.0683583989739418, 0.19080783426761627, -0.009482398629188538, -0.04374842345714569, 0.08184348791837692, 0.20070189237594604, 0.039221834391355515, -0.12251003831624985, -0.04325549304485321, 0.03840530663728714, -0.19840988516807556, -0.13591833412647247, 0.03073180839419365, 0.1059495136141777, -0.10656466335058212, 0.048937033861875534, -0.1362423598766327, -0.04138947278261185, 0.10234509408473969, 0.09793911874294281, 0.1391254961490631, -0.0906999260187149, 0.146945983171463, 0.14941848814487457, 0.23930180072784424, 0.36049938201904297, 0.0239607822149992, 0.08884347230195999, 0.061145078390836716] + var logger: CustomLogger! + override func setUpWithError() throws { try super.setUpWithError() + + logger = CustomLogger() + logger.level = .info + Database.log.custom = logger } - + override func tearDownWithError() throws { + Database.log.custom = nil + try super.tearDownWithError() } + func resetIndexWasTrainedLog() { + logger.reset() + } + + func checkIndexWasTrained() -> Bool { + return !logger.containsString("Untrained index; queries may be slow") + } + override func initDB() throws { if !Database.exists(withName: "words_db") { do { @@ -59,8 +75,8 @@ class VectorSearchTest: CBLTestCase { /// - metric: Euclidean Distance /// - minTrainingSize: 25 * centroids /// - maxTrainingSize: 256 * centroids - /// 3. To check the encoding type, platform code will have to expose some internal property to the tests for verification. - + /// 3. To check the encoding type, platform code will have to expose some internal + /// property to the tests for verification. func testVectorIndexConfigurationDefaultValue() throws { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) XCTAssertEqual(config.encoding, .scalarQuantizer(type: VectorIndexConfiguration.defaultEncoding)); @@ -69,12 +85,11 @@ class VectorSearchTest: CBLTestCase { XCTAssertEqual(config.maxTrainingSize, 256 * config.centroids) } - /// 2. TestVectorIndexConfigurationSettersAndGetters /// Description /// Test that all getters and setters of the VectorIndexConfiguration work as expected. /// Steps - /// 1. Create a VectorIndexConfiguration object. + /// 1. Create a VectorIndexConfiguration object with the following properties. /// - expression: "vector" /// - dimensions: 300 /// - centroids: 20 @@ -82,16 +97,15 @@ class VectorSearchTest: CBLTestCase { /// - metric: Cosine Distance /// - minTrainingSize: 100 /// - maxTrainingSize: 200 - /// 2. Get and check the following property values: + /// 2. Get and check the following properties. /// - expression: "vector" /// - expressions: ["vector"] - /// - distance: 300 + /// - dimensions: 300 /// - centroids: 20 /// - encoding: None /// - metric: Cosine /// - minTrainingSize: 100 /// - maxTrainingSize: 200 - func testVectorIndexConfigurationSettersAndGetters() throws { var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) config.encoding = .none @@ -110,17 +124,19 @@ class VectorSearchTest: CBLTestCase { /// 3. TestDimensionsValidation /// Description - /// Test that the dimensions are validated correctly. The invalid argument exception should be thrown when creating vector index configuration objects with invalid dimensions. + /// Test that the dimensions are validated correctly. The invalid argument exception + /// should be thrown when creating vector index configuration objects with invalid + /// dimensions. /// Steps /// 1. Create a VectorIndexConfiguration object. /// - expression: "vector" /// - dimensions: 2 and 2048 /// - centroids: 20 - /// 2. Check that the config can be created without an error thrown. - /// 3. Use the config to create the index and check that the index can be created successfully. - /// 4. Change the dimensions to 1 and 2049. - /// 5. Check that an invalid argument exception is thrown. - + /// 2. Check that the config can be created without an error thrown. + /// 3. Use the config to create the index and check that the index + /// can be created successfully. + /// 4. Change the dimensions to 1 and 2049. + /// 5. Check that an invalid argument exception is thrown. func testDimensionsValidation() throws { let collection = try db.collection(name: "words")! @@ -144,69 +160,76 @@ class VectorSearchTest: CBLTestCase { _ = VectorIndexConfiguration(expression: "vector", dimensions: 2049, centroids: 20) } } - - + /// 4. TestCentroidsValidation /// Description - /// Test that the centroids value is validated correctly. The invalid argument exception should be thrown when creating vector index configuration objects with invalid centroids. + /// Test that the centroids value is validated correctly. The invalid argument + /// exception should be thrown when creating vector index configuration objects with + /// invalid centroids.. /// Steps /// 1. Create a VectorIndexConfiguration object. /// - expression: "vector" /// - dimensions: 300 /// - centroids: 1 and 64000 /// 2. Check that the config can be created without an error thrown. - /// 3. Use the config to create the index and check that the index can be created successfully. + /// 3. Use the config to create the index and check that the index + /// can be created successfully. /// 4. Change the centroids to 0 and 64001. /// 5. Check that an invalid argument exception is thrown. - func testCentroidsValidation() throws { let collection = try db.collection(name: "words")! let config1 = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 1) try collection.createIndex(withName: "words_index_1", config: config1) - + var names = try collection.indexes() XCTAssert(names.contains("words_index_1")) let config2 = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 64000) try collection.createIndex(withName: "words_index_2", config: config2) - + names = try collection.indexes() XCTAssert(names.contains("words_index_2")) expectExcepion(exception: .invalidArgumentException) { - _ = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 0) + _ = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 0) } expectExcepion(exception: .invalidArgumentException) { - _ = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 64001) + _ = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 64001) } } - + /// 5. TestCreateVectorIndex /// Description - /// Using the default configuration, test that the vector index can be created from the embedded vectors in the documents. The test also verifies that the created index can be used in the query. + /// Using the default configuration, test that the vector index can be created from + /// the embedded vectors in the documents. The test also verifies that the created + /// index can be used in the query. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 - /// 3. Check that the index is created without an error returned. - /// 4. Get index names from the _default.words collection and check that the index names contains “words_index” - /// 5. Create an SQL++ query: + /// - centroids: 8 + /// 4. Check that the index is created without an error returned. + /// 5. Get index names from the _default.words collection and check that the index + /// names contains “words_index”. + /// 6. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 20) - /// 6. Check the explain() result of the query to ensure that the "words_index" is used. - /// 7. Execute the query and check that 20 results are returned. - + /// 7. Check the explain() result of the query to ensure that the "words_index" is used. + /// 8. Execute the query and check that 20 results are returned. + /// 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 10. Reset the custom logger. func testCreateVectorIndex() throws{ let collection = try db.collection(name: "words")! - let config1 = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + let config1 = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try collection.createIndex(withName: "words_index", config: config1) - + let names = try collection.indexes() XCTAssert(names.contains("words_index")) @@ -219,44 +242,50 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - let rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) } /// 6. TestUpdateVectorIndex /// Description - /// Test that the vector index created from the embedded vectors will be updated when documents are changed. The test also verifies that the created index can be used in the query. + /// Test that the vector index created from the embedded vectors will be updated + /// when documents are changed. The test also verifies that the created index can be + /// used in the query. /// Steps /// 1. Copy database words_db. - /// 2. Create a VectorIndexConfiguration object. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 8 (The default number of probes which is the max number of centroids the query will look for the vector). - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query: + /// - centroids: 8 + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 350) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 300 results are returned. - /// 7. Update the documents: + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 300 results are returned. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Update the documents: /// - Create _default.words.word301 with the content from _default.extwords.word1 /// - Create _default.words.word302 with the content from _default.extwords.word2 /// - Update _default.words.word1 with the content from _default.extwords.word3 /// - Delete _default.words.word2 - /// 8. Execute the query again, check that 301 results are returned, and: - /// - word301 and word302 are included + /// 10. Execute the query again and check that 301 results are returned, and + /// - word301 and word302 are included. /// - word1’s word is updated with the word from _default.extwords.word3 - /// - word2 is not included - + /// - word2 is not included. + /// 11. Reset the custom logger. func testUpdateVectorIndex() throws { let wordsCollection = try db.collection(name: "words")! let extWordsCollection = try db.collection(name: "extwords")! let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) - + try wordsCollection.createIndex(withName: "words_index", config: config) - + let names = try wordsCollection.indexes() XCTAssert(names.contains("words_index")) @@ -270,8 +299,9 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + var rs = try q.execute() XCTAssertEqual(rs.allResults().count, 300) + XCTAssert(checkIndexWasTrained()) // Update docs: let extWord1 = try extWordsCollection.document(id: "word1")! @@ -300,31 +330,36 @@ class VectorSearchTest: CBLTestCase { XCTAssertNil(wordMap["word2"]) } - /// 7. TestCreateVectorIndexWithInvalidVectors /// Description - /// Using the default configuration, test that when creating the vector index with invalid vectors, the invalid vectors will be skipped from indexing. + /// Using the default configuration, test that when creating the vector index with + /// invalid vectors, the invalid vectors will be skipped from indexing. /// Steps /// 1. Copy database words_db. - /// 2. Update the documents: + /// 2. Register a custom logger to capture the INFO log. + /// 3. Update documents: /// - Update _default.words word1 with "vector" = null /// - Update _default.words word2 with "vector" = "string" /// - Update _default.words word3 by removing the "vector" key. /// - Update _default.words word4 by removing one number from the "vector" key. - /// 3. Create a vector index named "words_index" in _default.words collection. + /// 4. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 8 (The default number of probes which is the max number of centroids the query will look for the vector). - /// 4. Check that the index is created without an error returned. - /// 5. Create an SQL++ query: + /// - centroids: 8 + /// 5. Check that the index is created without an error returned. + /// 6. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 350) - /// 6. Execute the query and check that 296 results are returned, and the results do not include document word1, word2, word3, and word4. - /// 7. Update an already index vector with an invalid vector. + /// 7. Execute the query and check that 296 results are returned, and the results + /// do not include document word1, word2, word3, and word4. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Update an already index vector with an invalid vector. /// - Update _default.words word5 with "vector" = null. - /// 8. Execute the query and check that 295 results are returned, and the results do not include document word5. - + /// 10. Execute the query and check that 295 results are returned, and the results + /// do not include document word5. + /// 11. Reset the custom logger. func testCreateVectorIndexWithInvalidVectors() throws { let collection = try db.collection(name: "words")! @@ -349,7 +384,7 @@ class VectorSearchTest: CBLTestCase { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try collection.createIndex(withName: "words_index", config: config) - + let names = try collection.indexes() XCTAssert(names.contains("words_index")) @@ -363,13 +398,14 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + var rs = try q.execute() var wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 296) XCTAssertNil(wordMap["word1"]) XCTAssertNil(wordMap["word2"]) XCTAssertNil(wordMap["word3"]) XCTAssertNil(wordMap["word4"]) + XCTAssert(checkIndexWasTrained()) auxDoc = try collection.document(id: "word5")!.toMutable() auxDoc.setString(nil, forKey: "vector") @@ -381,34 +417,37 @@ class VectorSearchTest: CBLTestCase { XCTAssertNil(wordMap["word5"]) } - /// 8. TestCreateVectorIndexUsingPredictionModel /// Description - /// Using the default configuration, test that the vector index can be created from the vectors returned by a predictive model. + /// Using the default configuration, test that the vector index can be created from + /// the vectors returned by a predictive model. /// Steps /// 1. Copy database words_db. - /// 2. Register "WordEmbedding" predictive model defined in section 2. - /// 3. Create a vector index named "words_pred_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Register "WordEmbedding" predictive model defined in section 2. + /// 4. Create a vector index named "words_pred_index" in _default.words collection. /// - expression: "prediction(WordEmbedding, {"word": word}).vector" /// - dimensions: 300 - /// - centroids: 8 (The default number of probes which is the max number of centroids the query will look for the vector). - /// 4. Check that the index is created without an error returned. - /// 5. Create an SQL++ query: + /// - centroids: 8 + /// 5. Check that the index is created without an error returned. + /// 6. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_pred_index, , 350) - /// 6. Check the explain() result of the query to ensure that the "words_pred_index" is used. - /// 7. Execute the query and check that 300 results are returned. - /// 8. Update the vector index: + /// 7. Check the explain() result of the query to ensure that the "words_pred_index" is used. + /// 8. Execute the query and check that 300 results are returned. + /// 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 10. Update the vector index: /// - Create _default.words.word301 with the content from _default.extwords.word1 /// - Create _default.words.word302 with the content from _default.extwords.word2 /// - Update _default.words.word1 with the content from _default.extwords.word3 /// - Delete _default.words.word2 - /// 9. Execute the query and check that 301 results are returned. + /// 11. Execute the query and check that 301 results are returned. /// - word301 and word302 are included. /// - word1 is updated with the word from _default.extwords.word2. /// - word2 is not included. - + /// 12. Reset the custom logger. func testCreateVectorIndexUsingPredictionModel() throws { let wordsCollection = try db.collection(name: "words")! let extWordsCollection = try db.collection(name: "extwords")! @@ -436,8 +475,9 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_pred_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + var rs = try q.execute() XCTAssertEqual(rs.allResults().count, 300) + XCTAssert(checkIndexWasTrained()) // Create words.word301 with extwords.word1 content let extWord1 = try extWordsCollection.document(id: "word1")! @@ -473,30 +513,36 @@ class VectorSearchTest: CBLTestCase { /// 9. TestCreateVectorIndexUsingPredictiveModelWithInvalidVectors /// Description - /// Using the default configuration, test that when creating the vector index using a predictive model with invalid vectors, the invalid vectors will be skipped from indexing. + /// Using the default configuration, test that when creating the vector index using + /// a predictive model with invalid vectors, the invalid vectors will be skipped + /// from indexing. /// Steps /// 1. Copy database words_db. - /// 2. Register "WordEmbedding" predictive model defined in section 2. - /// 3. Update documents. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Register "WordEmbedding" predictive model defined in section 2. + /// 4. Update documents. /// - Update _default.words word1 with "vector" = null /// - Update _default.words word2 with "vector" = "string" /// - Update _default.words word3 by removing the "vector" key. - /// - Update _default.words word4 by removing one number from the centroids the query will look for the vector). - /// 4. Create a vector index named "words_prediction_index" in _default.words collection. + /// - Update _default.words word4 by removing one number from the "vector" key. + /// 5. Create a vector index named "words_prediction_index" in _default.words collection. /// - expression: "prediction(WordEmbedding, {"word": word}).embedding" /// - dimensions: 300 - /// - centroids: 8 (The default number of probes which is the max number of centroids the query will look for the vector). - /// 5. Check that the index is created without an error returned. - /// 6. Create an SQL++ query: + /// - centroids: 8 + /// 6. Check that the index is created without an error returned. + /// 7. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_pred_index, , 350) - /// 7. Check the explain() result of the query to ensure that the "words_pred_index" is used. - /// 8. Execute the query and check that 296 results are returned and the results do not include word1, word2, word3, and word4. - /// 9. Update an already index vector with a non existing word in the database. + /// 8. Check the explain() result of the query to ensure that the "words_predi_index" is used. + /// 9. Execute the query and check that 296 results are returned and the results + /// do not include word1, word2, word3, and word4. + /// 10. Verify that the index was trained by checking that the “Untrained index; queries may be slow” doesn’t exist in the log. + /// 11. Update an already index vector with a non existing word in the database. /// - Update _default.words.word5 with “word” = “Fried Chicken”. - /// 9. Execute the query and check that 295 results are returned, and the results do not include document word5. - + /// 12. Execute the query and check that 295 results are returned, and the results + /// do not include document word5. + /// 13. Reset the custom logger. func testCreateVectorIndexUsingPredictiveModelWithInvalidVectors() throws { let collection = try db.collection(name: "words")! let modelDb = try openDB(name: databaseName) @@ -540,13 +586,14 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_pred_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + var rs = try q.execute() var wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 296) XCTAssertNil(wordMap["word1"]) XCTAssertNil(wordMap["word2"]) XCTAssertNil(wordMap["word3"]) XCTAssertNil(wordMap["word4"]) + XCTAssert(checkIndexWasTrained()) auxDoc = try collection.document(id: "word5")!.toMutable() auxDoc.setString("Fried Chicken", forKey: "word") @@ -560,28 +607,32 @@ class VectorSearchTest: CBLTestCase { /// 10. TestCreateVectorIndexWithSQ /// Description - /// Using different types of the Scalar Quantizer Encoding, test that the vector index can be created and used. + /// Using different types of the Scalar Quantizer Encoding, test that the vector + /// index can be created and used. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - encoding: ScalarQuantizer(type: SQ4) - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 20) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 20 results are returned. - /// 7. Delete the "words_index". - /// 8. Repeat Step 2 – 7 by using SQ6 and SQ8 respectively. - + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 20 results are returned. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Delete the "words_index". + /// 10. Reset the custom logger. + /// 11. Repeat Step 2 – 10 by using SQ6 and SQ8 respectively. func testCreateVectorIndexWithSQ() throws { let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.encoding = .scalarQuantizer(type: .SQ4) try collection.createIndex(withName: "words_index", config: config) @@ -599,10 +650,12 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + var rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) // Repeat using SQ6 + resetIndexWasTrainedLog() try collection.deleteIndex(forName: "words_index") config.encoding = .scalarQuantizer(type: .SQ6) try collection.createIndex(withName: "words_index", config: config) @@ -610,8 +663,10 @@ class VectorSearchTest: CBLTestCase { // Rerun query: rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) // Repeat using SQ8 + resetIndexWasTrainedLog() try collection.deleteIndex(forName: "words_index") config.encoding = .scalarQuantizer(type: .SQ8) try collection.createIndex(withName: "words_index", config: config) @@ -619,30 +674,33 @@ class VectorSearchTest: CBLTestCase { // Rerun query: rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) } - /// 11. TestCreateVectorIndexWithNoneEncoding /// Description /// Using the None Encoding, test that the vector index can be created and used. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - encoding: None - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 20) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 20 results are returned. - + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 20 results are returned. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Reset the custom logger. func testCreateVectorIndexWithNoneEncoding() throws { let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.encoding = .none try collection.createIndex(withName: "words_index", config: config) @@ -660,37 +718,44 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) } + /// FAILED : https://issues.couchbase.com/browse/CBL-5538 + /// Disable bits = 12 for now. + /// /// 12. TestCreateVectorIndexWithPQ /// Description /// Using the PQ Encoding, test that the vector index can be created and used. The /// test also tests the lower and upper bounds of the PQ’s bits. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - encoding : PQ(subquantizers: 5 bits: 8) - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 20) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 20 results are returned. - /// 7. Delete the “words_index”. - /// 8. Repeat steps 2 to 7 by changing the PQ’s bits to 4 and 12 respectively. - + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 20 results are returned. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Delete the “words_index”. + /// 10. Reset the custom logger. + /// 11. Repeat steps 2 to 10 by changing the PQ’s bits to 4 and 12 respectively. func testCreateVectorIndexWithPQ() throws { let collection = try! db.collection(name: "words")! - for numberOfBits in [8, 4, 12] { + for numberOfBits in [8, 4, /*12*/] { // Create vector index - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.encoding = .productQuantizer(subquantizers: 5, bits: UInt32(numberOfBits)) try collection.createIndex(withName: "words_index", config: config) @@ -708,15 +773,18 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) // Delete index try collection.deleteIndex(forName: "words_index") + + // Reset log + resetIndexWasTrainedLog() } } - /// 13. TestSubquantizersValidation /// Description /// Test that the PQ’s subquantizers value is validated with dimensions correctly. @@ -727,18 +795,17 @@ class VectorSearchTest: CBLTestCase { /// 2. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - PQ(subquantizers: 2, bits: 8) /// 3. Check that the index is created without an error returned. /// 4. Delete the "words_index". - /// 5. Repeat steps 2 to 4 by changing the subquantizers to - /// 3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 75, 100, 150, and 300. + /// 5. Repeat steps 2 to 4 by changing the subquantizers to + /// 3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 75, 100, 150, and 300. /// 6. Repeat step 2 to 4 by changing the subquantizers to 0 and 7. /// 7. Check that an invalid argument exception is thrown. - func testSubquantizersValidation() throws { let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.encoding = .productQuantizer(subquantizers: 2, bits: 8) try collection.createIndex(withName: "words_index", config: config) @@ -762,28 +829,36 @@ class VectorSearchTest: CBLTestCase { } } + /// https://issues.couchbase.com/browse/CBL-5537 + /// The test will fail when using centroid = 20 as the number of vectors for training + /// the index is not low. + /// /// 14. TestCreateVectorIndexWithFixedTrainingSize /// Description - /// Test that the vector index can be created and trained when minTrainingSize equals to maxTrainingSize. + /// Test that the vector index can be created and trained when minTrainingSize + /// equals to maxTrainingSize. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - minTrainingSize: 100 and maxTrainingSize: 100 - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 20) /// 5. Check the explain() result of the query to ensure that the "words_index" is used. /// 6. Execute the query and check that 20 results are returned. - + /// 7. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 8. Reset the custom logger. func testeCreateVectorIndexWithFixedTrainingSize() throws { let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.minTrainingSize = 100 config.maxTrainingSize = 100 try collection.createIndex(withName: "words_index", config: config) @@ -802,13 +877,16 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) } /// 15. TestValidateMinMaxTrainingSize /// Description - /// Test that the minTrainingSize and maxTrainingSize values are validated correctly. The invalid argument exception should be thrown when the vector index is created with invalid minTrainingSize or maxTrainingSize. + /// Test that the minTrainingSize and maxTrainingSize values are validated + /// correctly. The invalid argument exception should be thrown when the vector index + /// is created with invalid minTrainingSize or maxTrainingSize. /// Steps /// 1. Copy database words_db. /// 2. Create a vector index named "words_index" in _default.words collection. @@ -823,7 +901,6 @@ class VectorSearchTest: CBLTestCase { /// - minTrainingSize = 0 and maxTrainingSize 100 /// - minTrainingSize = 10 and maxTrainingSize 9 /// 6. Check that an invalid argument exception was thrown for all cases in step 4. - func testValidateMinMaxTrainingSize() throws { let collection = try db.collection(name: "words")! @@ -851,28 +928,27 @@ class VectorSearchTest: CBLTestCase { /// Test that the untrained vector index can be used in queries. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - minTrainingSize: 400 /// - maxTrainingSize: 500 - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, , 20) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 20 results are returned. - + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 20 results are returned. + /// 8. Verify that the index was not trained by checking that the “Untrained index; + /// queries may be slow” message exists in the log. + /// 9. Reset the custom logger. func testQueryUntrainedVectorIndex() throws { - let customLogger = CustomLogger() - customLogger.level = .info - Database.log.custom = customLogger - let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) // out of bounds (300 words in db) config.minTrainingSize = 400 config.maxTrainingSize = 500 @@ -892,12 +968,9 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) - - XCTAssert(customLogger.lines - .contains("SQLite message: vectorsearch: Untrained index; queries may be slow.")) - Database.log.custom = nil + XCTAssertFalse(checkIndexWasTrained()) } /// 17. TestCreateVectorIndexWithCosineDistance @@ -905,23 +978,27 @@ class VectorSearchTest: CBLTestCase { /// Test that the vector index can be created and used with the cosine distance metric. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - metric: Cosine - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word,vector_distance(words_index) /// FROM _default.words /// WHERE vector_match(words_index, , 20) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 20 results are returned and the vector distance value is in between 0 – 1.0 inclusively. - + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 20 results are returned and the vector + /// distance value is in between 0 – 1.0 inclusively. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Reset the custom logger. func testCreateVectorIndexWithCosineDistance() throws { let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.metric = .cosine try collection.createIndex(withName: "words_index", config: config) @@ -939,13 +1016,13 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) - for result in rs.allResults() { XCTAssert(result.double(at: 3) > 0) XCTAssert(result.double(at: 3) > 1) } + XCTAssert(checkIndexWasTrained()) } /// 18. TestCreateVectorIndexWithEuclideanDistance @@ -953,23 +1030,27 @@ class VectorSearchTest: CBLTestCase { /// Test that the vector index can be created and used with the euclidean distance metric. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// - metric: Euclidean - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word, vector_distance(words_index) /// FROM _default.words /// WHERE vector_match(words_index, , 20) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 20 results are returned and the distance value is more than zero. - + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 20 results are returned and the + /// distance value is more than zero. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Reset the custom logger. func testCreateVectorIndexWithEuclideanDistance() throws { let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.metric = .euclidean try collection.createIndex(withName: "words_index", config: config) @@ -987,17 +1068,18 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) - for result in rs.allResults() { XCTAssert(result.double(at: 3) > 0) } + XCTAssert(checkIndexWasTrained()) } /// 19. TestCreateVectorIndexWithExistingName /// Description - /// Test that creating a new vector index with an existing name is fine if the index configuration is the same or not. + /// Test that creating a new vector index with an existing name is fine if the index + /// configuration is the same or not. /// Steps /// 1. Copy database words_db. /// 2. Create a vector index named "words_index" in _default.words collection. @@ -1011,7 +1093,6 @@ class VectorSearchTest: CBLTestCase { /// - dimensions: 300 /// - centroids: 20 /// 6. Check that the index is created without an error returned. - func testCreateVectorIndexWithExistingName() throws { let collection = try db.collection(name: "words")! @@ -1027,27 +1108,33 @@ class VectorSearchTest: CBLTestCase { /// 20. TestDeleteVectorIndex /// Description - /// Test that creating a new vector index with an existing name is fine if the index configuration is the same. Otherwise, an error will be returned. + /// Test that creating a new vector index with an existing name is fine if the index + /// configuration is the same. Otherwise, an error will be returned. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vectors" /// - dimensions: 300 - /// - centroids: 20 - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// - centroids: 8 + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 20 results are returned. - /// 7. Delete index named "words_index". - /// 8. Check that getIndexes() does not contain "words_index". - + /// WHERE vector_match(words_index, , 20) + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 20 results are returned. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Delete index named "words_index". + /// 10. Check that getIndexes() does not contain "words_index". + /// 11. Create the same query again and check that a CouchbaseLiteException is returned + /// as the index doesn’t exist. + /// 12. Reset the custom logger. func testDeleteVectorIndex() throws { let collection = try db.collection(name: "words")! - let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try collection.createIndex(withName: "words_index", config: config) var names = try collection.indexes() @@ -1064,18 +1151,23 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) + XCTAssert(checkIndexWasTrained()) try collection.deleteIndex(forName: "words_index") names = try collection.indexes() XCTAssertFalse(names.contains("words_index")) + + self.expectError(domain: CBLErrorDomain, code: CBLError.missingIndex) { + _ = try self.db.createQuery(sql) + } } - /// 21. TestVectorMatchOnNonExistingIndex /// Description - /// Test that an error will be returned when creating a vector match query that uses a non existing index. + /// Test that an error will be returned when creating a vector match query that uses + /// a non existing index. /// Steps /// 1. Copy database words_db. /// 2. Create an SQL++ query. @@ -1083,7 +1175,6 @@ class VectorSearchTest: CBLTestCase { /// FROM _default.words /// WHERE vector_match(words_index, , 20) /// 3. Check that a CouchbaseLiteException is returned as the index doesn’t exist. - func testVectorMatchOnNonExistingIndex() throws { self.expectError(domain: CBLErrorDomain, code: CBLError.missingIndex) { let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" @@ -1093,28 +1184,32 @@ class VectorSearchTest: CBLTestCase { /// 22. TestVectorMatchDefaultLimit /// Description - /// Test that the number of rows returned is limited to the default value which is 3 when using the vector_match query without the limit number specified. + /// Test that the number of rows returned is limited to the default value which is 3 + /// when using the vector_match query without the limit number specified. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// - centroids: 8 + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words /// WHERE vector_match(words_index, ) - /// 5. Check the explain() result of the query to ensure that the "words_index" is used. - /// 6. Execute the query and check that 3 results are returned. - + /// 6. Check the explain() result of the query to ensure that the "words_index" is used. + /// 7. Execute the query and check that 3 results are returned. + /// 8. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 9. Reset the custom logger. func testVectorMatchDefaultLimit() throws { let collection = try db.collection(name: "words")! - let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try collection.createIndex(withName: "words_index", config: config) - var names = try collection.indexes() + let names = try collection.indexes() XCTAssert(names.contains("words_index")) // Query: @@ -1128,13 +1223,16 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 3) + XCTAssert(checkIndexWasTrained()) } /// 23. TestVectorMatchLimitBoundary /// Description - /// Test vector_match’s limit boundary which is between 1 - 10000 inclusively. When creating vector_match queries with an out-out-bound limit, an error should be returned. + /// Test vector_match’s limit boundary which is between 1 - 10000 inclusively. When + /// creating vector_match queries with an out-out-bound limit, an error should be + /// returned. /// Steps /// 1. Copy database words_db. /// 2. Create a vector index named "words_index" in _default.words collection. @@ -1145,19 +1243,18 @@ class VectorSearchTest: CBLTestCase { /// 4. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , ) + /// WHERE vector_match(words_index, , ) /// - limit: 1 and 10000 /// 5. Check that the query can be created without an error. /// 6. Repeat step 4 with the limit: -1, 0, and 10001 /// 7. Check that a CouchbaseLiteException is returned when creating the query. - func testVectorMatchLimitBoundary() throws { let collection = try db.collection(name: "words")! let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) try collection.createIndex(withName: "words_index", config: config) - var names = try collection.indexes() + let names = try collection.indexes() XCTAssert(names.contains("words_index")) // Check valid query with 1 and 10000 set limit @@ -1180,26 +1277,30 @@ class VectorSearchTest: CBLTestCase { /// Test that vector_match can be used in AND expression. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// - centroids: 8 + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words /// WHERE vector_match(words_index, , 300) AND catid = 'cat1' - /// 5. Check that the query can be created without an error. - /// 6. Check the explain() result of the query to ensure that the "words_index" is used. - /// 7. Execute the query and check that the number of results returned is 50 (there are 50 words in catid=1), and the results contain only catid == 'cat1'. - + /// 6. Check that the query can be created without an error. + /// 7. Check the explain() result of the query to ensure that the "words_index" is used. + /// 8. Execute the query and check that the number of results returned is 50 + /// (there are 50 words in catid=1), and the results contain only catid == 'cat1'. + /// 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 10. Reset the custom logger. func testVectorMatchWithAndExpression() throws { let collection = try db.collection(name: "words")! - let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try collection.createIndex(withName: "words_index", config: config) - var names = try collection.indexes() + let names = try collection.indexes() XCTAssert(names.contains("words_index")) // Query with a single AND: @@ -1213,12 +1314,12 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 50) - for result in rs.allResults() { XCTAssertEqual(result.value(at: 1) as! String, "cat1") } + XCTAssert(checkIndexWasTrained()) } /// 25. TestVectorMatchWithMultipleAndExpression @@ -1226,26 +1327,30 @@ class VectorSearchTest: CBLTestCase { /// Test that vector_match can be used in multiple AND expressions. /// Steps /// 1. Copy database words_db. - /// 2. Create a vector index named "words_index" in _default.words collection. + /// 2. Register a custom logger to capture the INFO log. + /// 3. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 - /// 3. Check that the index is created without an error returned. - /// 4. Create an SQL++ query. + /// - centroids: 8 + /// 4. Check that the index is created without an error returned. + /// 5. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words /// WHERE (vector_match(words_index, , 300) AND word is valued) AND catid = 'cat1' - /// 5. Check that the query can be created without an error. - /// 6. Check the explain() result of the query to ensure that the "words_index" is used. - /// 7. Execute the query and check that the number of results returned is 50 (there are 50 words in catid=1), and the results contain only catid == 'cat1'. - + /// 6. Check that the query can be created without an error. + /// 7. Check the explain() result of the query to ensure that the "words_index" is used. + /// 8. Execute the query and check that the number of results returned is 50 + /// (there are 50 words in catid=1), and the results contain only catid == 'cat1'. + /// 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” + /// doesn’t exist in the log. + /// 10. Reset the custom logger. func testVectorMatchWithMultipleAndExpression() throws { let collection = try db.collection(name: "words")! - let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try collection.createIndex(withName: "words_index", config: config) - var names = try collection.indexes() + let names = try collection.indexes() XCTAssert(names.contains("words_index")) // Query with mutiple ANDs: @@ -1259,15 +1364,14 @@ class VectorSearchTest: CBLTestCase { let explain = try q.explain() as NSString XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - var rs: ResultSet = try q.execute() + let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 50) - for result in rs.allResults() { XCTAssertEqual(result.value(at: 1) as! String, "cat1") } + XCTAssert(checkIndexWasTrained()) } - /// 26. TestInvalidVectorMatchWithOrExpression /// Description /// Test that vector_match cannot be used with OR expression. @@ -1283,14 +1387,13 @@ class VectorSearchTest: CBLTestCase { /// FROM _default.words /// WHERE vector_match(words_index, , 20) OR catid = 1 /// 5. Check that a CouchbaseLiteException is returned when creating the query. - func testInvalidVectorMatchWithOrExpression() throws { let collection = try db.collection(name: "words")! let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) try collection.createIndex(withName: "words_index", config: config) - var names = try collection.indexes() + let names = try collection.indexes() XCTAssert(names.contains("words_index")) // Query with OR: