From 097c396be68333d8af6c92fb7e05cc3f3f23c221 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Fri, 15 Mar 2024 14:59:26 -0700 Subject: [PATCH 01/49] CBL-5524 : Add all keys to the Privacy Manifest file (#3258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NSPrivacyAccessedAPITypes and NSPrivacyCollectedDataTypes are required for generating the App’s privacy report even though in the Apple doc, the only required key for 3rd party SDK is NSPrivacyAccessedAPITypes. This commit adds all keys in case the other keys are required in the future by Apple. --- Resources/PrivacyInfo.xcprivacy | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/Resources/PrivacyInfo.xcprivacy b/Resources/PrivacyInfo.xcprivacy index fe840a043..6cb5e9158 100644 --- a/Resources/PrivacyInfo.xcprivacy +++ b/Resources/PrivacyInfo.xcprivacy @@ -1,17 +1,23 @@ - - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategoryFileTimestamp - NSPrivacyAccessedAPITypeReasons - - C617.1 - - - - + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + NSPrivacyCollectedDataTypes + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + From cb1f0f21fa9b2fcfa6e07509c34d181ae586430c Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Tue, 19 Mar 2024 09:28:20 -0700 Subject: [PATCH 02/49] CBL-5541 : Update vector search test per changes in v1.8 (#3260) * 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: From f12c86df5452a48c39fa91bab534bba1778bb073 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Wed, 27 Mar 2024 14:50:56 +0000 Subject: [PATCH 03/49] CouchbaseLite 3.2.0 Beta 1 package + readme (#3264) --- CouchbaseLite-Swift.podspec | 6 +++--- CouchbaseLite.podspec | 6 +++--- Package.swift | 6 +++--- README.md | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CouchbaseLite-Swift.podspec b/CouchbaseLite-Swift.podspec index bbfea2ecb..e238caee0 100644 --- a/CouchbaseLite-Swift.podspec +++ b/CouchbaseLite-Swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CouchbaseLite-Swift' - s.version = '3.1.0' + s.version = '3.2.0-beta.1' s.license = 'Apache License, Version 2.0' s.homepage = 'https://github.com/couchbase/couchbase-lite-ios' s.summary = 'An embedded syncable NoSQL database for iOS and MacOS apps.' @@ -17,6 +17,6 @@ Pod::Spec.new do |s| s.osx.preserve_paths = 'frameworks/CBL_Swift/macOS/CouchbaseLiteSwift.framework' s.osx.vendored_frameworks = 'frameworks/CBL_Swift/macOS/CouchbaseLiteSwift.framework' - s.ios.deployment_target = '9.0' - s.osx.deployment_target = '10.11' + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '12.0' end diff --git a/CouchbaseLite.podspec b/CouchbaseLite.podspec index 2c5b2cc90..bb4e3b9a3 100644 --- a/CouchbaseLite.podspec +++ b/CouchbaseLite.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CouchbaseLite' - s.version = '3.1.0' + s.version = '3.2.0-beta.1' s.license = 'Apache License, Version 2.0' s.homepage = 'https://github.com/couchbase/couchbase-lite-ios' s.summary = 'An embedded syncable NoSQL database for iOS and MacOS apps.' @@ -17,6 +17,6 @@ Pod::Spec.new do |s| s.osx.preserve_paths = 'frameworks/CBL_ObjC/macOS/CouchbaseLite.framework' s.osx.vendored_frameworks = 'frameworks/CBL_ObjC/macOS/CouchbaseLite.framework' - s.ios.deployment_target = '9.0' - s.osx.deployment_target = '10.11' + s.ios.deployment_target = '12.0' + s.osx.deployment_target = '12.0' end diff --git a/Package.swift b/Package.swift index 3ebcc20a3..89fbc47fb 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "CouchbaseLiteSwift", platforms: [ - .iOS(.v10), .macOS(.v10_12) + .iOS(.v12), .macOS(.v12) ], products: [ .library( @@ -14,8 +14,8 @@ let package = Package( targets: [ .binaryTarget( name: "CouchbaseLiteSwift", - url: "https://packages.couchbase.com/releases/couchbase-lite-ios/3.1.0/couchbase-lite-swift_xc_community_3.1.0.zip", - checksum: "556d6ae41df3b5ebf91dbe45d38214dd7032f97ac1b132f925356d20e3b9ada5" + url: "https://packages.couchbase.com/releases/couchbase-lite-ios/3.2.0-beta.1/couchbase-lite-swift_xc_community_3.2.0-beta.1.zip", + checksum: "39dd4d80a8edb6eb87a2f5ae91753b16064c04c6c74f10f7ef2707902efe14c4" ) ] ) diff --git a/README.md b/README.md index 878173f7a..8414a93b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Couchbase Lite implementation is on top of [Couchbase Lite Core](https://github. ## Requirements -- iOS 11.0+ | macOS 10.14+ +- iOS 12.0+ | macOS 12+ ## Installation @@ -24,7 +24,7 @@ Couchbase Lite implementation is on top of [Couchbase Lite Core](https://github. dependencies: [ .package(name: "CouchbaseLiteSwift", url: "https://github.com/couchbase/couchbase-lite-ios.git", - from: "3.0.0"), + from: "3.2.0-beta.1"), ], ``` @@ -34,7 +34,7 @@ dependencies: [ dependencies: [ .package(name: "CouchbaseLiteSwift", url: "https://github.com/couchbase/couchbase-lite-swift-ee.git", - from: "3.0.0"), + from: "3.2.0-beta.1"), ], ``` From 9b6e07da2872f1049c612b5cb02467f8f1ccc52b Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Thu, 28 Mar 2024 09:10:49 -0700 Subject: [PATCH 04/49] Set API Doc Version in generate release zip script (#3267) * Without explicitly set the version, the tool intelligently picks the version number from the podspec file. This may be OK unless we forgot to update the version in the podspec or we cannot update the version until the release time. * Explicitly set the version number when generating the doc. If the version is not specified, there will be no version shown in the doc. --- Scripts/generate_objc_release_zip.sh | 4 +++- Scripts/generate_swift_release_zip.sh | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Scripts/generate_objc_release_zip.sh b/Scripts/generate_objc_release_zip.sh index 19dbe1234..c3d4fc4df 100755 --- a/Scripts/generate_objc_release_zip.sh +++ b/Scripts/generate_objc_release_zip.sh @@ -160,9 +160,11 @@ then fi VERSION_SUFFIX="" +API_DOC_VERSION="" if [ -n "$VERSION" ] then VERSION_SUFFIX="_$VERSION" + API_DOC_VERSION="$VERSION" fi # Build frameworks: @@ -194,7 +196,7 @@ if [[ -z $NO_API_DOCS ]]; then # Generate API docs: echo "Generate API docs ..." OBJC_UMBRELLA_HEADER=`find $OUTPUT_OBJC_XC_DIR -name "CouchbaseLite.h"` - jazzy --clean --objc --umbrella-header ${OBJC_UMBRELLA_HEADER} --module CouchbaseLite --theme Scripts/Support/Docs/Theme --readme README.md --output ${OUTPUT_DOCS_DIR}/CouchbaseLite + jazzy --clean --objc --umbrella-header ${OBJC_UMBRELLA_HEADER} --module CouchbaseLite --module-version "${API_DOC_VERSION}" --theme Scripts/Support/Docs/Theme --readme README.md --output ${OUTPUT_DOCS_DIR}/CouchbaseLite # >> Objective-C API pushd "$OUTPUT_OBJC_DOCS_DIR" > /dev/null diff --git a/Scripts/generate_swift_release_zip.sh b/Scripts/generate_swift_release_zip.sh index 5671dcbfa..0ec761470 100755 --- a/Scripts/generate_swift_release_zip.sh +++ b/Scripts/generate_swift_release_zip.sh @@ -154,9 +154,11 @@ then fi VERSION_SUFFIX="" +API_DOC_VERSION="" if [ -n "$VERSION" ] then VERSION_SUFFIX="_$VERSION" + API_DOC_VERSION="$VERSION" fi # Build frameworks: @@ -191,7 +193,7 @@ sh Scripts/generate_package_manifest.sh -zip-path "$OUTPUT_DIR/couchbase-lite-sw if [[ -z $NO_API_DOCS ]]; then # Generate API docs: echo "Generate API docs ..." - jazzy --clean --xcodebuild-arguments "clean,build,-scheme,${SCHEME_PREFIX}_Swift,-sdk,iphonesimulator,-destination,generic/platform=iOS Simulator" --module CouchbaseLiteSwift --theme Scripts/Support/Docs/Theme --readme README.md --output ${OUTPUT_DOCS_DIR}/CouchbaseLiteSwift + jazzy --clean --xcodebuild-arguments "clean,build,-scheme,${SCHEME_PREFIX}_Swift,-sdk,iphonesimulator,-destination,generic/platform=iOS Simulator" --module CouchbaseLiteSwift --module-version "${API_DOC_VERSION}" --theme Scripts/Support/Docs/Theme --readme README.md --output ${OUTPUT_DOCS_DIR}/CouchbaseLiteSwift # >> Swift API docs pushd "$OUTPUT_SWIFT_DOCS_DIR" > /dev/null From 80fbdf2c1e08009d37b48d7ae0820e71fe5f08c1 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Thu, 4 Apr 2024 18:11:17 +0100 Subject: [PATCH 05/49] Update LiteCore and VS version (#3269) * update VS version * update LiteCore to 3.2.0-159 * disable check for trained index to pass test --- Objective-C/Tests/VectorSearchTest.m | 3 ++- Swift/Tests/VectorSearchTest.swift | 3 ++- Tests/Extensions/version.txt | 2 +- vendor/couchbase-lite-core | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 93927e70d..d06680600 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -879,7 +879,8 @@ - (void) testCreateVectorIndexWithPQ { CBLQueryResultSet* rs = [q execute: &error]; NSArray* allObjects = rs.allObjects; AssertEqual(allObjects.count, 20); - Assert([self checkIndexWasTrained]); + // will re-enable once we increase the dataset + // Assert([self checkIndexWasTrained]); // Delete index [collection deleteIndexWithName: @"words_index" error: &error]; diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 18b89557e..67b6e27d8 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -775,7 +775,8 @@ class VectorSearchTest: CBLTestCase { let rs = try q.execute() XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) + // will re-enable once we increase the dataset + // XCTAssert(checkIndexWasTrained()) // Delete index try collection.deleteIndex(forName: "words_index") diff --git a/Tests/Extensions/version.txt b/Tests/Extensions/version.txt index 81fbff6c5..51b9b4a13 100644 --- a/Tests/Extensions/version.txt +++ b/Tests/Extensions/version.txt @@ -1 +1 @@ -1.0.0-22 +1.0.0-29 diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index f686852be..98a9e44d7 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit f686852be5fcce2d1f63107a168d1bd0f3039af2 +Subproject commit 98a9e44d78187da8fb8df64d0053998203379dc0 From 61cbaee62966152293957ddcb36a96cbc99d1df6 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Thu, 18 Apr 2024 13:59:33 +0100 Subject: [PATCH 06/49] CBL-5365: Remove CBLErrors.h from Swift Public API (#3271) * import private module defined in modulemap * check for errors using swift definitions instead of obj-c --- Swift/ArrayExpressionSatisfies.swift | 1 + Swift/ArrayFunction.swift | 1 + Swift/ArrayObject.swift | 1 + Swift/Authenticator.swift | 1 + Swift/Blob.swift | 1 + Swift/Collation.swift | 1 + Swift/Collection.swift | 1 + Swift/CollectionConfiguration.swift | 1 + Swift/Conflict.swift | 1 + Swift/ConflictResolver.swift | 1 + Swift/ConsoleLogger.swift | 1 + Swift/CouchbaseLiteSwift.h | 2 - Swift/DataConverter.swift | 1 + Swift/DataSource.swift | 1 + Swift/Database.swift | 1 + Swift/DatabaseConfiguration.swift | 1 + Swift/DictionaryObject.swift | 1 + Swift/Document.swift | 1 + Swift/DocumentFragment.swift | 1 + Swift/Endpoint.swift | 1 + Swift/Errors.swift | 1 + Swift/Expression.swift | 1 + Swift/FileLogger.swift | 1 + Swift/Fragment.swift | 1 + Swift/From.swift | 1 + Swift/FullTextExpression.swift | 1 + Swift/FullTextFunction.swift | 1 + Swift/FullTextIndexExpression.swift | 1 + Swift/Function.swift | 1 + Swift/GroupBy.swift | 1 + Swift/Having.swift | 1 + Swift/Index.swift | 1 + Swift/IndexConfiguration.swift | 1 + Swift/Join.swift | 1 + Swift/Joins.swift | 1 + Swift/Limit.swift | 1 + Swift/ListenerToken.swift | 1 + Swift/Log.swift | 1 + Swift/LogFileConfiguration.swift | 1 + Swift/Meta.swift | 1 + Swift/MutableArrayObject.swift | 1 + Swift/MutableDictionaryObject.swift | 1 + Swift/MutableDocument.swift | 1 + Swift/MutableFragment.swift | 1 + Swift/OrderBy.swift | 1 + Swift/Ordering.swift | 1 + Swift/Parameters.swift | 1 + Swift/PropertyExpression.swift | 1 + Swift/Query.swift | 1 + Swift/Replicator.swift | 1 + Swift/ReplicatorConfiguration.swift | 1 + Swift/Result.swift | 1 + Swift/ResultSet.swift | 1 + Swift/Scope.swift | 1 + Swift/Select.swift | 1 + Swift/SelectResult.swift | 1 + Swift/Tests/CollectionTest.swift | 68 +++++++++---------- Swift/Tests/DatabaseTest.swift | 14 ++-- Swift/Tests/QueryTest+Collection.swift | 4 +- .../Tests/ReplicatorTest+CustomConflict.swift | 6 +- .../Tests/ReplicatorTest+PendingDocIds.swift | 4 +- .../URLEndpointListenerTest+Collection.swift | 4 +- Swift/Tests/URLEndpointListenerTest.swift | 42 ++++++------ Swift/Tests/VectorSearchTest.swift | 10 +-- Swift/ValueExpression.swift | 1 + Swift/VariableExpression.swift | 1 + Swift/Where.swift | 1 + 67 files changed, 134 insertions(+), 78 deletions(-) diff --git a/Swift/ArrayExpressionSatisfies.swift b/Swift/ArrayExpressionSatisfies.swift index f019a8cc0..274a93fc9 100644 --- a/Swift/ArrayExpressionSatisfies.swift +++ b/Swift/ArrayExpressionSatisfies.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /* internal */ enum QuantifiesType { case any, anyAndEvery, every diff --git a/Swift/ArrayFunction.swift b/Swift/ArrayFunction.swift index 9f423495e..836dd4563 100644 --- a/Swift/ArrayFunction.swift +++ b/Swift/ArrayFunction.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Array function factory. public final class ArrayFunction { diff --git a/Swift/ArrayObject.swift b/Swift/ArrayObject.swift index 863d8d3be..19e512c9f 100644 --- a/Swift/ArrayObject.swift +++ b/Swift/ArrayObject.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// ArrayProtocol defines a set of methods for readonly accessing array data. diff --git a/Swift/Authenticator.swift b/Swift/Authenticator.swift index 2cadafdec..8c160f9b4 100644 --- a/Swift/Authenticator.swift +++ b/Swift/Authenticator.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Authenticator objects provide server authentication credentials to the replicator. /// Authenticator is a base opaque protocol; you must instantiate one of diff --git a/Swift/Blob.swift b/Swift/Blob.swift index 38544567f..a43e15257 100644 --- a/Swift/Blob.swift +++ b/Swift/Blob.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Blob contains arbitrary binary data, tagged with a MIME type. /// Blobs can be arbitrarily large, and their data is loaded only on demand (when the `content` diff --git a/Swift/Collation.swift b/Swift/Collation.swift index 27ef603a8..dd9ef0535 100644 --- a/Swift/Collation.swift +++ b/Swift/Collation.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Collation defines how strings are compared and is used when creating a COLLATE expression. /// The COLLATE expression can be used in the WHERE clause when comparing two strings or in the diff --git a/Swift/Collection.swift b/Swift/Collection.swift index 1ff89faec..0aa4df4f4 100644 --- a/Swift/Collection.swift +++ b/Swift/Collection.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A `Collection` represent a collection which is a container for documents. /// diff --git a/Swift/CollectionConfiguration.swift b/Swift/CollectionConfiguration.swift index da601761b..4248a3d63 100644 --- a/Swift/CollectionConfiguration.swift +++ b/Swift/CollectionConfiguration.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// The collection configuration that can be configured specifically for the replication. public struct CollectionConfiguration { diff --git a/Swift/Conflict.swift b/Swift/Conflict.swift index 181067ed6..b21355868 100644 --- a/Swift/Conflict.swift +++ b/Swift/Conflict.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Conflict class public struct Conflict { diff --git a/Swift/ConflictResolver.swift b/Swift/ConflictResolver.swift index bf52283af..cb97d5270 100644 --- a/Swift/ConflictResolver.swift +++ b/Swift/ConflictResolver.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Conflict Resolver protocol public protocol ConflictResolverProtocol { diff --git a/Swift/ConsoleLogger.swift b/Swift/ConsoleLogger.swift index e31d3f605..a95d7909a 100644 --- a/Swift/ConsoleLogger.swift +++ b/Swift/ConsoleLogger.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Console logger for writing log messages to the system console. diff --git a/Swift/CouchbaseLiteSwift.h b/Swift/CouchbaseLiteSwift.h index c91801c28..686447889 100644 --- a/Swift/CouchbaseLiteSwift.h +++ b/Swift/CouchbaseLiteSwift.h @@ -24,5 +24,3 @@ FOUNDATION_EXPORT double CouchbaseLiteSwiftVersionNumber; //! Project version string for CouchbaseLiteSwift. FOUNDATION_EXPORT const unsigned char CouchbaseLiteSwiftVersionString[]; - -#import "CBLErrors.h" diff --git a/Swift/DataConverter.swift b/Swift/DataConverter.swift index bedf8c353..08c020bd1 100644 --- a/Swift/DataConverter.swift +++ b/Swift/DataConverter.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /* internal */ class DataConverter { diff --git a/Swift/DataSource.swift b/Swift/DataSource.swift index 70fd9633d..e3fa8c9f8 100644 --- a/Swift/DataSource.swift +++ b/Swift/DataSource.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A query data source. used for specifiying the data source for your query. diff --git a/Swift/Database.swift b/Swift/Database.swift index ea7e49885..e938ed6ef 100644 --- a/Swift/Database.swift +++ b/Swift/Database.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Concurruncy control type used when saving or deleting a document. /// diff --git a/Swift/DatabaseConfiguration.swift b/Swift/DatabaseConfiguration.swift index 520339cf3..c7a89421f 100644 --- a/Swift/DatabaseConfiguration.swift +++ b/Swift/DatabaseConfiguration.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Configuration for opening a database. diff --git a/Swift/DictionaryObject.swift b/Swift/DictionaryObject.swift index 8c7587aa8..87a67d1ce 100644 --- a/Swift/DictionaryObject.swift +++ b/Swift/DictionaryObject.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// DictionaryProtocol defines a set of methods for readonly accessing dictionary data. diff --git a/Swift/Document.swift b/Swift/Document.swift index b37a9a519..958e79626 100644 --- a/Swift/Document.swift +++ b/Swift/Document.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Couchbase Lite document. The Document is immutable. public class Document : DictionaryProtocol, Equatable, Hashable, Sequence { diff --git a/Swift/DocumentFragment.swift b/Swift/DocumentFragment.swift index 2d8ac12b9..5d7219231 100644 --- a/Swift/DocumentFragment.swift +++ b/Swift/DocumentFragment.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// DocumentFragment provides access to a document object. DocumentFragment also provides /// subscript access by either key or index to the data values of the document which are diff --git a/Swift/Endpoint.swift b/Swift/Endpoint.swift index e5368c517..44c1cae13 100644 --- a/Swift/Endpoint.swift +++ b/Swift/Endpoint.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Replication target endpoint. public protocol Endpoint { diff --git a/Swift/Errors.swift b/Swift/Errors.swift index 680af103d..2273e3985 100644 --- a/Swift/Errors.swift +++ b/Swift/Errors.swift @@ -19,6 +19,7 @@ import Foundation +import CouchbaseLiteSwift_Private public struct CBLError { public static let domain = CBLErrorDomain diff --git a/Swift/Expression.swift b/Swift/Expression.swift index 2b1fa9214..48f76aaad 100644 --- a/Swift/Expression.swift +++ b/Swift/Expression.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Query expression. public protocol ExpressionProtocol { diff --git a/Swift/FileLogger.swift b/Swift/FileLogger.swift index 81685dec2..47eafe463 100644 --- a/Swift/FileLogger.swift +++ b/Swift/FileLogger.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// File logger used for writing log messages to files. To enable the file logger, /// setup the log file configuration and specify the log level as desired. diff --git a/Swift/Fragment.swift b/Swift/Fragment.swift index 5b9a3ae12..aa9dc0e44 100644 --- a/Swift/Fragment.swift +++ b/Swift/Fragment.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// FragmentProtocol provides readonly access to the data value wrapped by a fragment object. public protocol FragmentProtocol { diff --git a/Swift/From.swift b/Swift/From.swift index 1ae03c0e3..9dc8550e3 100644 --- a/Swift/From.swift +++ b/Swift/From.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A From component representing a FROM clause for specifying the data source of the query. public final class From: Query, JoinRouter, WhereRouter, GroupByRouter, OrderByRouter, LimitRouter { diff --git a/Swift/FullTextExpression.swift b/Swift/FullTextExpression.swift index d6ae0ee14..82d9efe57 100644 --- a/Swift/FullTextExpression.swift +++ b/Swift/FullTextExpression.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Full-text expression. @available(*, deprecated, message: "Use FullTextFunction.match(indexName: String, query: String) instead.") diff --git a/Swift/FullTextFunction.swift b/Swift/FullTextFunction.swift index dbd0a9794..a11267144 100644 --- a/Swift/FullTextFunction.swift +++ b/Swift/FullTextFunction.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Full-text function factory. public final class FullTextFunction { diff --git a/Swift/FullTextIndexExpression.swift b/Swift/FullTextIndexExpression.swift index eb09a32a5..9e1118760 100644 --- a/Swift/FullTextIndexExpression.swift +++ b/Swift/FullTextIndexExpression.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Index Expression. public protocol IndexExpressionProtocol { } diff --git a/Swift/Function.swift b/Swift/Function.swift index 4798ef46b..8217348de 100644 --- a/Swift/Function.swift +++ b/Swift/Function.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Function factory. public final class Function { diff --git a/Swift/GroupBy.swift b/Swift/GroupBy.swift index 512bae8fb..bf3d07ef5 100644 --- a/Swift/GroupBy.swift +++ b/Swift/GroupBy.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A GroupBy represents the GROUP BY clause to group the query result. /// The GROUP BY clause is normally used with aggregate functions (AVG, COUNT, MAX, MIN, SUM) diff --git a/Swift/Having.swift b/Swift/Having.swift index d34d22f11..2c8a8e4ab 100644 --- a/Swift/Having.swift +++ b/Swift/Having.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Having represents a HAVING clause of the query statement used for filtering the aggregated /// values from the the GROUP BY clause. diff --git a/Swift/Index.swift b/Swift/Index.swift index 296fd6599..984463080 100644 --- a/Swift/Index.swift +++ b/Swift/Index.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Index protocol. public protocol Index { } diff --git a/Swift/IndexConfiguration.swift b/Swift/IndexConfiguration.swift index 849851512..1b2a3f2e5 100644 --- a/Swift/IndexConfiguration.swift +++ b/Swift/IndexConfiguration.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Configuration for creating indexes. public protocol IndexConfiguration { } diff --git a/Swift/Join.swift b/Swift/Join.swift index e5d1f73b8..76a809782 100644 --- a/Swift/Join.swift +++ b/Swift/Join.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Join component representing a JOIN clause in the query statement. public protocol JoinProtocol { diff --git a/Swift/Joins.swift b/Swift/Joins.swift index 206473a45..40332535f 100644 --- a/Swift/Joins.swift +++ b/Swift/Joins.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A Joins component represents a collection of the joins clauses of the query statement. public final class Joins: Query, WhereRouter, OrderByRouter, LimitRouter { diff --git a/Swift/Limit.swift b/Swift/Limit.swift index 7fe00e7f4..c08829512 100644 --- a/Swift/Limit.swift +++ b/Swift/Limit.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A Limit component represents the LIMIT clause of the query statement. public final class Limit: Query { diff --git a/Swift/ListenerToken.swift b/Swift/ListenerToken.swift index 8ff909dc8..3292f3e1e 100644 --- a/Swift/ListenerToken.swift +++ b/Swift/ListenerToken.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Listener token returned when adding a change listener. The token is used /// for removing the added change listener. diff --git a/Swift/Log.swift b/Swift/Log.swift index 2cb729647..a6ea6ace8 100644 --- a/Swift/Log.swift +++ b/Swift/Log.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Log allows to configure console and file logger or to set a custom logger. public class Log { diff --git a/Swift/LogFileConfiguration.swift b/Swift/LogFileConfiguration.swift index c9c71f126..61ba9ee12 100644 --- a/Swift/LogFileConfiguration.swift +++ b/Swift/LogFileConfiguration.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Log file configuration. public class LogFileConfiguration { diff --git a/Swift/Meta.swift b/Swift/Meta.swift index 2731de5ad..daaa3dc66 100644 --- a/Swift/Meta.swift +++ b/Swift/Meta.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Meta expression. public protocol MetaExpressionProtocol: ExpressionProtocol { diff --git a/Swift/MutableArrayObject.swift b/Swift/MutableArrayObject.swift index 592bbcc89..41f8a41db 100644 --- a/Swift/MutableArrayObject.swift +++ b/Swift/MutableArrayObject.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /** MutableArrayProtocol protocol defines a set of methods for getting and setting array data. */ diff --git a/Swift/MutableDictionaryObject.swift b/Swift/MutableDictionaryObject.swift index c964e9576..7a8840e11 100644 --- a/Swift/MutableDictionaryObject.swift +++ b/Swift/MutableDictionaryObject.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// MutableDictionaryProtocol defines a set of methods for getting and setting /// dictionary data. diff --git a/Swift/MutableDocument.swift b/Swift/MutableDocument.swift index 6debbb270..a3e8346f3 100644 --- a/Swift/MutableDocument.swift +++ b/Swift/MutableDocument.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A mutable version of the Document. public final class MutableDocument : Document, MutableDictionaryProtocol { diff --git a/Swift/MutableFragment.swift b/Swift/MutableFragment.swift index e84660cba..0f287559f 100644 --- a/Swift/MutableFragment.swift +++ b/Swift/MutableFragment.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// MutableFragmentProtocol provides read and write access to the data value /// wrapped by a fragment object. diff --git a/Swift/OrderBy.swift b/Swift/OrderBy.swift index d27a280b2..8a286891f 100644 --- a/Swift/OrderBy.swift +++ b/Swift/OrderBy.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// An OrderBy represents an ORDER BY clause of the query statement. public final class OrderBy: Query, LimitRouter { diff --git a/Swift/Ordering.swift b/Swift/Ordering.swift index a79e89f89..687482cd4 100644 --- a/Swift/Ordering.swift +++ b/Swift/Ordering.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Ordering represents a single ordering component in the query ORDER BY clause. public protocol OrderingProtocol { diff --git a/Swift/Parameters.swift b/Swift/Parameters.swift index 119c93f5d..95cb4692a 100644 --- a/Swift/Parameters.swift +++ b/Swift/Parameters.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Query parameters used for setting values to the query parameters defined in the query. diff --git a/Swift/PropertyExpression.swift b/Swift/PropertyExpression.swift index 46ace6d9a..707ac74ff 100644 --- a/Swift/PropertyExpression.swift +++ b/Swift/PropertyExpression.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Property expression. diff --git a/Swift/Query.swift b/Swift/Query.swift index eaa0cf1a4..d16444e33 100644 --- a/Swift/Query.swift +++ b/Swift/Query.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A database query. /// A Query instance can be constructed by calling one of the select class methods. diff --git a/Swift/Replicator.swift b/Swift/Replicator.swift index 79279ef6a..0eacbfaed 100644 --- a/Swift/Replicator.swift +++ b/Swift/Replicator.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A replicator for replicating document changes between a local database and a target database. diff --git a/Swift/ReplicatorConfiguration.swift b/Swift/ReplicatorConfiguration.swift index c7d69a417..cf64edf8c 100644 --- a/Swift/ReplicatorConfiguration.swift +++ b/Swift/ReplicatorConfiguration.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Replicator type. /// diff --git a/Swift/Result.swift b/Swift/Result.swift index 6d8ca0bfd..c6cdd949c 100644 --- a/Swift/Result.swift +++ b/Swift/Result.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Result represents a single row in the query result. The projecting result value can be /// accessed either by using a zero based index or by a key corresponding to the diff --git a/Swift/ResultSet.swift b/Swift/ResultSet.swift index a48183b0c..c947d796f 100644 --- a/Swift/ResultSet.swift +++ b/Swift/ResultSet.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// ResultSet is a result returned from a query. public final class ResultSet : Sequence, IteratorProtocol { diff --git a/Swift/Scope.swift b/Swift/Scope.swift index 197a87ebe..fece3b7df 100644 --- a/Swift/Scope.swift +++ b/Swift/Scope.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A CBLScope represents a scope or namespace of the collections. /// diff --git a/Swift/Select.swift b/Swift/Select.swift index c248a6728..601461e76 100644 --- a/Swift/Select.swift +++ b/Swift/Select.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// A Select component represents the returning properties in each query result row. public final class Select: Query, FromRouter { diff --git a/Swift/SelectResult.swift b/Swift/SelectResult.swift index 241477e96..cf96e11cb 100644 --- a/Swift/SelectResult.swift +++ b/Swift/SelectResult.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// SelectResult represents a signle return value of the query statement. public protocol SelectResultProtocol { diff --git a/Swift/Tests/CollectionTest.swift b/Swift/Tests/CollectionTest.swift index 0f8fec29b..0b07022ea 100644 --- a/Swift/Tests/CollectionTest.swift +++ b/Swift/Tests/CollectionTest.swift @@ -18,7 +18,7 @@ // import XCTest -import CouchbaseLiteSwift +@testable import CouchbaseLiteSwift class CollectionTest: CBLTestCase { @@ -72,7 +72,7 @@ class CollectionTest: CBLTestCase { } func testDeleteDefaultCollection() throws { - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { try self.db.deleteCollection(name: Database.defaultCollectionName) } @@ -88,7 +88,7 @@ class CollectionTest: CBLTestCase { } func testGetDefaultScopeAfterDeleteDefaultCollection() throws { - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { try self.db.deleteCollection(name: Database.defaultCollectionName) } @@ -324,28 +324,28 @@ class CollectionTest: CBLTestCase { } func testScopeCollectionNameWithIllegalChars() throws { - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: "_") } - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: "a", scope: "_") } - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: "%") } - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: "b", scope: "%") } for char in "!@#$^&*()+={}[]<>,.?/:;\"'\\|`~" { - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: "a\(char)z") } - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: "colA", scope: "a\(char)z") } } @@ -370,11 +370,11 @@ class CollectionTest: CBLTestCase { } name.append("a") - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: name, scope: "scopeA") } - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidParameter) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { let _ = try self.db.createCollection(name: "colA", scope: name) } } @@ -766,57 +766,57 @@ class CollectionTest: CBLTestCase { try onAction() // document(id:) - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.document(id: "doc2") } // save document let mdoc = MutableDocument() - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.save(document: mdoc) } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.save(document: mdoc, conflictHandler: { doc, doc2 in return true }) } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.save(document: mdoc, concurrencyControl: .lastWriteWins) } // delete functions - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.delete(document: doc) } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.delete(document: doc, concurrencyControl: .lastWriteWins) } // purge functions - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.purge(document: doc) } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.purge(id: "doc2") } // doc expiry - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.setDocumentExpiration(id: "doc6", expiration: Date()) } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.getDocumentExpiration(id: "doc6") } // indexes let config = ValueIndexConfiguration.init(["firstName"]) - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.createIndex(withName: "index1", config: config) } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.indexes() } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try col.deleteIndex(forName: "index2") } } @@ -841,10 +841,10 @@ class CollectionTest: CBLTestCase { try onAction() - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try scope.collection(name: "colA") } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try scope.collections() } } @@ -865,35 +865,35 @@ class CollectionTest: CBLTestCase { func getScopesOrCollectionsTest() throws { // default collection/scope - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.defaultCollection() } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.defaultScope() } // collection(s) - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.collection(name: "colA", scope: "scopeA") } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.collections() } // scope(s) - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.scope(name: "scopeA") } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.scopes() } // create/delete collections - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.createCollection(name: "colA", scope: "scopeA") } - expectError(domain: CBLErrorDomain, code: CBLError.notOpen) { + expectError(domain: CBLError.domain, code: CBLError.notOpen) { let _ = try self.db.deleteCollection(name: "colA", scope: "scopeA") } } diff --git a/Swift/Tests/DatabaseTest.swift b/Swift/Tests/DatabaseTest.swift index fcb69e579..bb1426c64 100644 --- a/Swift/Tests/DatabaseTest.swift +++ b/Swift/Tests/DatabaseTest.swift @@ -18,7 +18,7 @@ // import XCTest -import CouchbaseLiteSwift +@testable import CouchbaseLiteSwift class DatabaseTest: CBLTestCase { @@ -367,7 +367,7 @@ class DatabaseTest: CBLTestCase { } } catch { XCTAssertNotNil(error); - XCTAssertEqual((error as NSError).code, CBLErrorUnexpectedError); + XCTAssertEqual((error as NSError).code, CBLError.unexpectedError); } XCTAssertEqual(defaultCollection!.count, 0) } @@ -788,7 +788,7 @@ class DatabaseTest: CBLTestCase { } } catch { XCTAssertNotNil(error); - XCTAssertEqual((error as NSError).code, CBLErrorUnexpectedError); + XCTAssertEqual((error as NSError).code, CBLError.unexpectedError); } XCTAssertEqual(defaultCollection!.count, 10) } @@ -908,7 +908,7 @@ class DatabaseTest: CBLTestCase { let otherDB = try openDB(name: db.name) XCTAssertNotNil(try otherDB.defaultCollection().document(id: doc.id)) XCTAssertEqual(try otherDB.defaultCollection().count, 1) - expectError(domain: CBLError.domain, code: CBLErrorInvalidParameter) { + expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { try otherDB.defaultCollection().purge(document: doc) } try otherDB.close() @@ -917,7 +917,7 @@ class DatabaseTest: CBLTestCase { func testPurgeDocInDifferentDB() throws { let doc = try generateDocument(withID: "doc1") let otherDB = try openDB(name: "otherDB") - expectError(domain: CBLError.domain, code: CBLErrorInvalidParameter) { + expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { try otherDB.defaultCollection().purge(document: doc) } try otherDB.delete() @@ -961,7 +961,7 @@ class DatabaseTest: CBLTestCase { } } catch { XCTAssertNotNil(error); - XCTAssertEqual((error as NSError).code, CBLErrorUnexpectedError); + XCTAssertEqual((error as NSError).code, CBLError.unexpectedError); } XCTAssertEqual(defaultCollection!.count, 10) } @@ -1058,7 +1058,7 @@ class DatabaseTest: CBLTestCase { } } catch { XCTAssertNotNil(error); - XCTAssertEqual((error as NSError).code, CBLErrorUnexpectedError); + XCTAssertEqual((error as NSError).code, CBLError.unexpectedError); } XCTAssertEqual(defaultCollection!.count, 10) } diff --git a/Swift/Tests/QueryTest+Collection.swift b/Swift/Tests/QueryTest+Collection.swift index 1b8fef260..abfcb51b3 100644 --- a/Swift/Tests/QueryTest+Collection.swift +++ b/Swift/Tests/QueryTest+Collection.swift @@ -18,7 +18,7 @@ // import XCTest -import CouchbaseLiteSwift +@testable import CouchbaseLiteSwift class QueryTest_Collection: QueryTest { override func setUpWithError() throws { @@ -61,7 +61,7 @@ class QueryTest_Collection: QueryTest { try loadJSONResource("names_100", collection: col) - expectError(domain: CBLErrorDomain, code: CBLError.invalidQuery) { + expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { let _ = try self.db.createQuery("SELECT name.first FROM person.names ORDER BY name.first LIMIT 1") } } diff --git a/Swift/Tests/ReplicatorTest+CustomConflict.swift b/Swift/Tests/ReplicatorTest+CustomConflict.swift index cdda31bea..9301df57b 100644 --- a/Swift/Tests/ReplicatorTest+CustomConflict.swift +++ b/Swift/Tests/ReplicatorTest+CustomConflict.swift @@ -18,7 +18,7 @@ // import XCTest -import CouchbaseLiteSwift +@testable import CouchbaseLiteSwift class ReplicatorTest_CustomConflict: ReplicatorTest { @@ -655,7 +655,7 @@ class ReplicatorTest_CustomConflict: ReplicatorTest { }) } XCTAssertNotNil(error) - XCTAssertEqual(error?.code, CBLErrorUnexpectedError) + XCTAssertEqual(error?.code, CBLError.unexpectedError) XCTAssert((error?.userInfo[NSLocalizedDescriptionKey] as! String) == "A document contains a blob that was saved to a different " + "database. The save operation cannot complete.") @@ -763,7 +763,7 @@ class ReplicatorTest_CustomConflict: ReplicatorTest { }) }) XCTAssertNotNil(error) - XCTAssertEqual(error?.code, CBLErrorNotFound) + XCTAssertEqual(error?.code, CBLError.notFound) replicator.removeChangeListener(withToken: token) } diff --git a/Swift/Tests/ReplicatorTest+PendingDocIds.swift b/Swift/Tests/ReplicatorTest+PendingDocIds.swift index 49d094a0f..cd6d8014a 100644 --- a/Swift/Tests/ReplicatorTest+PendingDocIds.swift +++ b/Swift/Tests/ReplicatorTest+PendingDocIds.swift @@ -18,7 +18,7 @@ // import XCTest -import CouchbaseLiteSwift +@testable import CouchbaseLiteSwift class ReplicatorTest_PendingDocIds: ReplicatorTest { @@ -104,7 +104,7 @@ class ReplicatorTest_PendingDocIds: ReplicatorTest { pullOnlyError = error as NSError } - XCTAssertEqual(pullOnlyError?.code, CBLErrorUnsupported) + XCTAssertEqual(pullOnlyError?.code, CBLError.unsupported) } func testPendingDocIDsWithCreate() throws { diff --git a/Swift/Tests/URLEndpointListenerTest+Collection.swift b/Swift/Tests/URLEndpointListenerTest+Collection.swift index 08b46e53c..24180ada3 100644 --- a/Swift/Tests/URLEndpointListenerTest+Collection.swift +++ b/Swift/Tests/URLEndpointListenerTest+Collection.swift @@ -18,7 +18,7 @@ // import XCTest -import CouchbaseLiteSwift +@testable import CouchbaseLiteSwift class URLEndpointListenerTest_Collection: URLEndpointListenerTest { override func setUpWithError() throws { @@ -109,7 +109,7 @@ class URLEndpointListenerTest_Collection: URLEndpointListenerTest { rConfig.pinnedServerCertificate = listener.tlsIdentity!.certs[0] rConfig.addCollections([col1a]) - run(config: rConfig, expectedError: CBLErrorHTTPNotFound) + run(config: rConfig, expectedError: CBLError.httpNotFound) try stopListener() } diff --git a/Swift/Tests/URLEndpointListenerTest.swift b/Swift/Tests/URLEndpointListenerTest.swift index d91279d8e..afab7e901 100644 --- a/Swift/Tests/URLEndpointListenerTest.swift +++ b/Swift/Tests/URLEndpointListenerTest.swift @@ -414,7 +414,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: nil, serverCert: tlsID.certs[0], - expectedError: CBLErrorTLSCertUnknownRoot) + expectedError: CBLError.tlsCertUnknownRoot) try TLSIdentity.deleteIdentity(withLabel: "dummy") // No pinned cert @@ -423,7 +423,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: nil, serverCert: nil, - expectedError: CBLErrorTLSCertUnknownRoot) + expectedError: CBLError.tlsCertUnknownRoot) try stopListener(listener: listener) XCTAssertNil(listener.tlsIdentity) @@ -460,7 +460,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: nil, serverCert: tlsID.certs[0], - expectedError: CBLErrorTLSCertUnknownRoot) + expectedError: CBLError.tlsCertUnknownRoot) try TLSIdentity.deleteIdentity(withLabel: "dummy") // No pinned cert @@ -469,7 +469,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: nil, serverCert: nil, - expectedError: CBLErrorTLSCertUnknownRoot) + expectedError: CBLError.tlsCertUnknownRoot) try stopListener(listener: listener) XCTAssertNil(listener.tlsIdentity) @@ -511,22 +511,22 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { // Replicator - No Authenticator: self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, - auth: nil, expectedError: CBLErrorHTTPAuthRequired) + auth: nil, expectedError: CBLError.httpAuthRequired) // Replicator - Wrong Username: var auth = BasicAuthenticator.init(username: "daneil", password: "123") self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, - auth: auth, expectedError: CBLErrorHTTPAuthRequired) + auth: auth, expectedError: CBLError.httpAuthRequired) // Replicator - Wrong Password: auth = BasicAuthenticator.init(username: "daniel", password: "456") self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, - auth: auth, expectedError: CBLErrorHTTPAuthRequired) + auth: auth, expectedError: CBLError.httpAuthRequired) // Replicator - Client Cert Authenticator let certAuth = ClientCertificateAuthenticator(identity: try createTLSIdentity(isServer: false)!) self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, - auth: certAuth, expectedError: CBLErrorHTTPAuthRequired) + auth: certAuth, expectedError: CBLError.httpAuthRequired) try TLSIdentity.deleteIdentity(withLabel: clientCertLabel) // Replicator - Success: @@ -580,7 +580,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { // Replicator: let auth = ClientCertificateAuthenticator(identity: try createTLSIdentity(isServer: false)!) let serverCert = listener.tlsIdentity!.certs[0] - self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, auth: auth, serverCert: serverCert, expectedError: CBLErrorTLSClientCertRejected) + self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, auth: auth, serverCert: serverCert, expectedError: CBLError.tlsClientCertRejected) // Cleanup: try TLSIdentity.deleteIdentity(withLabel: clientCertLabel) @@ -635,7 +635,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { self.ignoreException { self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, - auth: auth, serverCert: serverCert, expectedError: CBLErrorTLSClientCertRejected) + auth: auth, serverCert: serverCert, expectedError: CBLError.tlsClientCertRejected) } // Cleanup: @@ -881,7 +881,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { self.run(target: self.listener!.localURLEndpoint, type: .pushAndPull, continuous: false, auth: nil, serverCert: self.listener!.tlsIdentity!.certs[0], - expectedError: CBLErrorHTTPForbidden) + expectedError: CBLError.httpForbidden) } func testReplicatorServerCertificate() throws { @@ -936,7 +936,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { let activity = change.status.activity if activity == .stopped && change.status.error != nil { // TODO: https://issues.couchbase.com/browse/CBL-1471 - XCTAssertEqual((change.status.error! as NSError).code, CBLErrorTLSCertUnknownRoot) + XCTAssertEqual((change.status.error! as NSError).code, CBLError.tlsCertUnknownRoot) x1.fulfill() } } @@ -1023,7 +1023,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { // Replicator - TLS Error: self.ignoreException { self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, - acceptSelfSignedOnly: false, serverCert: nil, expectedError: CBLErrorTLSCertUnknownRoot) + acceptSelfSignedOnly: false, serverCert: nil, expectedError: CBLError.tlsCertUnknownRoot) } // Replicator - Success: @@ -1047,7 +1047,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { // Replicator - TLS Error: self.ignoreException { self.run(target: listener.localURLEndpoint, type: .pushAndPull, continuous: false, - acceptSelfSignedOnly: false, serverCert: nil, expectedError: CBLErrorTLSCertUnknownRoot) + acceptSelfSignedOnly: false, serverCert: nil, expectedError: CBLError.tlsCertUnknownRoot) } // Replicator - Success: @@ -1126,7 +1126,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { wait(for: [x2], timeout: 5.0) // Check error: - XCTAssertEqual((repl.status.error! as NSError).code, CBLErrorWebSocketGoingAway) + XCTAssertEqual((repl.status.error! as NSError).code, CBLError.webSocketGoingAway) // Check to ensure that the replicator is not accessible: run(target: target, type: .pushAndPull, continuous: false, auth: nil, serverCert: nil, @@ -1151,7 +1151,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: nil, serverCert: self.listener!.tlsIdentity!.certs[0], - expectedError: CBLErrorHTTPAuthRequired) + expectedError: CBLError.httpAuthRequired) // Replicator - Wrong Username: run(target: self.listener!.localURLEndpoint, @@ -1159,7 +1159,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: BasicAuthenticator(username: "daneil", password: "123"), serverCert: self.listener!.tlsIdentity!.certs[0], - expectedError: CBLErrorHTTPAuthRequired) + expectedError: CBLError.httpAuthRequired) // Replicator - Wrong Password: run(target: self.listener!.localURLEndpoint, @@ -1167,7 +1167,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: BasicAuthenticator(username: "daniel", password: "132"), serverCert: self.listener!.tlsIdentity!.certs[0], - expectedError: CBLErrorHTTPAuthRequired) + expectedError: CBLError.httpAuthRequired) // Replicator - Different ClientCertAuthenticator run(target: self.listener!.localURLEndpoint, @@ -1175,7 +1175,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, auth: ClientCertificateAuthenticator(identity: try createTLSIdentity(isServer: false)!), serverCert: self.listener!.tlsIdentity!.certs[0], - expectedError: CBLErrorHTTPAuthRequired) + expectedError: CBLError.httpAuthRequired) // cleanup client cert authenticator identity try TLSIdentity.deleteIdentity(withLabel: clientCertLabel) @@ -1257,7 +1257,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { auth: nil, acceptSelfSignedOnly: true, serverCert: nil, - expectedError: CBLErrorTLSCertUntrusted) + expectedError: CBLError.tlsCertUntrusted) try stopListener() try TLSIdentity.deleteIdentity(withLabel: serverCertLabel) @@ -1280,7 +1280,7 @@ class URLEndpointListenerTest_Main: URLEndpointListenerTest { continuous: false, acceptSelfSignedOnly: true, serverCert: dummyTLSIdentity!.certs[0], - expectedError: CBLErrorTLSCertUnknownRoot) + expectedError: CBLError.tlsCertUnknownRoot) } // listener = cert1; replicator.pin = cert1; acceptSelfSigned = false => pass diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 67b6e27d8..63ec50379 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -7,7 +7,7 @@ // import XCTest -import CouchbaseLiteSwift +@testable 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] @@ -1160,7 +1160,7 @@ class VectorSearchTest: CBLTestCase { names = try collection.indexes() XCTAssertFalse(names.contains("words_index")) - self.expectError(domain: CBLErrorDomain, code: CBLError.missingIndex) { + self.expectError(domain: CBLError.domain, code: CBLError.missingIndex) { _ = try self.db.createQuery(sql) } } @@ -1177,7 +1177,7 @@ class VectorSearchTest: CBLTestCase { /// 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) { + self.expectError(domain: CBLError.domain, code: CBLError.missingIndex) { let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" _ = try self.db.createQuery(sql) } @@ -1266,7 +1266,7 @@ class VectorSearchTest: CBLTestCase { // Check if error thrown for wrong limit values for limit in [-1, 0, 10001] { - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidQuery) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, \(limit)" _ = try self.db.createQuery(sql) } @@ -1399,7 +1399,7 @@ class VectorSearchTest: CBLTestCase { // Query with OR: let sql = "select meta().id, word, catid from _default.words where vector_match(words_index, $vector, 300) OR catid = 'cat1'" - self.expectError(domain: CBLErrorDomain, code: CBLError.invalidQuery) { + self.expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { _ = try self.db.createQuery(sql) } } diff --git a/Swift/ValueExpression.swift b/Swift/ValueExpression.swift index 00335d830..2cc19d580 100644 --- a/Swift/ValueExpression.swift +++ b/Swift/ValueExpression.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Value Expression. /* internal */ class ValueExpression: QueryExpression { diff --git a/Swift/VariableExpression.swift b/Swift/VariableExpression.swift index 7e4f3e572..27d7b41ad 100644 --- a/Swift/VariableExpression.swift +++ b/Swift/VariableExpression.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Variable expression that expresses an array item in the ArrayExpression. public protocol VariableExpressionProtocol: ExpressionProtocol { diff --git a/Swift/Where.swift b/Swift/Where.swift index 1ccfa7814..da38b70d3 100644 --- a/Swift/Where.swift +++ b/Swift/Where.swift @@ -18,6 +18,7 @@ // import Foundation +import CouchbaseLiteSwift_Private /// Where class represents the WHERE clause of the query statement. public final class Where: Query, GroupByRouter, OrderByRouter, LimitRouter { From fe222055ab8bcd300b1104e06051678afe149b70 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Wed, 24 Apr 2024 12:37:11 +0100 Subject: [PATCH 07/49] CBL-5508: Update Min macOS Support Version to 12.0 (#3272) * set project to macos 12 and fix issues * remove MYAnonymousIdentity from project - not used * update LiteCore * change from long to double to handle floating comparison testExpiryNoGreaterThanDate --- CouchbaseLite.xcodeproj/project.pbxproj | 12 ---- .../Internal/Replicator/CBLTrustCheck.mm | 57 +++++++------------ .../Internal/Replicator/CBLWebSocket.mm | 12 ++-- Objective-C/Tests/QueryTest+Meta.m | 2 +- vendor/couchbase-lite-core | 2 +- xcconfigs/Project.xcconfig | 2 +- 6 files changed, 30 insertions(+), 57 deletions(-) diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index c9bff34a0..4a5c6271d 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -325,8 +325,6 @@ 276740B81EE7381E0036DE42 /* CBLTrustCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 276740B51EE7381E0036DE42 /* CBLTrustCheck.h */; }; 276740B91EE7381E0036DE42 /* CBLTrustCheck.mm in Sources */ = {isa = PBXBuildFile; fileRef = 276740B61EE7381E0036DE42 /* CBLTrustCheck.mm */; }; 276740BA1EE7381E0036DE42 /* CBLTrustCheck.mm in Sources */ = {isa = PBXBuildFile; fileRef = 276740B61EE7381E0036DE42 /* CBLTrustCheck.mm */; }; - 27B69A141F2A4C6D00782145 /* MYAnonymousIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B69A131F2A4B7400782145 /* MYAnonymousIdentity.m */; }; - 27B69A151F2A4C6D00782145 /* MYAnonymousIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B69A131F2A4B7400782145 /* MYAnonymousIdentity.m */; }; 27BE3B461E4D662B0012B74A /* Test_Assertions.m in Sources */ = {isa = PBXBuildFile; fileRef = 934F4C6F1E1EFAF600F90659 /* Test_Assertions.m */; }; 27BE3B4B1E4E46120012B74A /* CBLTestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BE3B4A1E4E46120012B74A /* CBLTestCase.swift */; }; 27BE3B4D1E4E51C80012B74A /* DatabaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27BE3B4C1E4E51C80012B74A /* DatabaseTest.swift */; }; @@ -702,7 +700,6 @@ 9343EF38207D611600F19A89 /* CBLData.mm in Sources */ = {isa = PBXBuildFile; fileRef = 930AE46C1EAA6C9100E92E9A /* CBLData.mm */; }; 9343EF39207D611600F19A89 /* CollectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 934F4BD11E1EF19000F90659 /* CollectionUtils.m */; }; 9343EF3A207D611600F19A89 /* Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 934F4C591E1EF25E00F90659 /* Test.m */; }; - 9343EF3C207D611600F19A89 /* MYAnonymousIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B69A131F2A4B7400782145 /* MYAnonymousIdentity.m */; }; 9343EF3D207D611600F19A89 /* CBLBlob.mm in Sources */ = {isa = PBXBuildFile; fileRef = 72A879EF1E2DD51C008466FF /* CBLBlob.mm */; }; 9343EF3E207D611600F19A89 /* CBLAggregateExpression.m in Sources */ = {isa = PBXBuildFile; fileRef = 934A278B1F30E5A5003946A7 /* CBLAggregateExpression.m */; }; 9343EF3F207D611600F19A89 /* CBLJSON.mm in Sources */ = {isa = PBXBuildFile; fileRef = 934F4C9B1E241FB500F90659 /* CBLJSON.mm */; }; @@ -961,7 +958,6 @@ 9343F067207D61AB00F19A89 /* ResultSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93140F001F22AA5E006E18EF /* ResultSet.swift */; }; 9343F068207D61AB00F19A89 /* CBLReplicator+Backgrounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 9374A8A5201FC53600BA0D9E /* CBLReplicator+Backgrounding.m */; }; 9343F069207D61AB00F19A89 /* CBLChangeListenerToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 937F026B1EFC662100060D64 /* CBLChangeListenerToken.m */; }; - 9343F06A207D61AB00F19A89 /* MYAnonymousIdentity.m in Sources */ = {isa = PBXBuildFile; fileRef = 27B69A131F2A4B7400782145 /* MYAnonymousIdentity.m */; }; 9343F06B207D61AB00F19A89 /* CBLIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = 93FD618A2020757500E7F6A1 /* CBLIndex.m */; }; 9343F06C207D61AB00F19A89 /* Limit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9322DCF41F14654E00C4ACF7 /* Limit.swift */; }; 9343F06E207D61AB00F19A89 /* CBLQueryCollation.m in Sources */ = {isa = PBXBuildFile; fileRef = 938E38801F3A5BB4006806C7 /* CBLQueryCollation.m */; }; @@ -2220,8 +2216,6 @@ 275FF6BE1E48081B005F90DD /* CouchbaseLite.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CouchbaseLite.exp; sourceTree = ""; }; 276740B51EE7381E0036DE42 /* CBLTrustCheck.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLTrustCheck.h; sourceTree = ""; }; 276740B61EE7381E0036DE42 /* CBLTrustCheck.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLTrustCheck.mm; sourceTree = ""; }; - 27B69A121F2A4B7400782145 /* MYAnonymousIdentity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MYAnonymousIdentity.h; sourceTree = ""; }; - 27B69A131F2A4B7400782145 /* MYAnonymousIdentity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MYAnonymousIdentity.m; sourceTree = ""; }; 27BE3B451E4D63AF0012B74A /* CBL_Swift.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CBL_Swift.xcconfig; sourceTree = ""; }; 27BE3B4A1E4E46120012B74A /* CBLTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBLTestCase.swift; sourceTree = ""; }; 27BE3B4C1E4E51C80012B74A /* DatabaseTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseTest.swift; sourceTree = ""; }; @@ -3563,8 +3557,6 @@ 934F4BD11E1EF19000F90659 /* CollectionUtils.m */, 275FF6B61E47B2FC005F90DD /* ExceptionUtils.h */, 275FF6B71E47B2FC005F90DD /* ExceptionUtils.m */, - 27B69A121F2A4B7400782145 /* MYAnonymousIdentity.h */, - 27B69A131F2A4B7400782145 /* MYAnonymousIdentity.m */, 9374A89C201FC47D00BA0D9E /* MYBackgroundMonitor.h */, 9374A88A201FC47D00BA0D9E /* MYBackgroundMonitor.m */, 934F4BE91E1EF19000F90659 /* MYErrorUtils.h */, @@ -5999,7 +5991,6 @@ 93140F011F22AA5E006E18EF /* ResultSet.swift in Sources */, 9374A8A9201FC53600BA0D9E /* CBLReplicator+Backgrounding.m in Sources */, 937F02A41EFC7DD000060D64 /* CBLChangeListenerToken.m in Sources */, - 27B69A151F2A4C6D00782145 /* MYAnonymousIdentity.m in Sources */, 93FD618E2020757500E7F6A1 /* CBLIndex.m in Sources */, 9322DCF51F14654E00C4ACF7 /* Limit.swift in Sources */, 938E38841F3A5BB4006806C7 /* CBLQueryCollation.m in Sources */, @@ -6189,7 +6180,6 @@ 9343EF39207D611600F19A89 /* CollectionUtils.m in Sources */, 9343EF3A207D611600F19A89 /* Test.m in Sources */, 1AAB2786227793E50037A880 /* CBLConflict.m in Sources */, - 9343EF3C207D611600F19A89 /* MYAnonymousIdentity.m in Sources */, 9343EF3D207D611600F19A89 /* CBLBlob.mm in Sources */, 9343EF3E207D611600F19A89 /* CBLAggregateExpression.m in Sources */, 9343EF3F207D611600F19A89 /* CBLJSON.mm in Sources */, @@ -6447,7 +6437,6 @@ 9343F067207D61AB00F19A89 /* ResultSet.swift in Sources */, 9343F068207D61AB00F19A89 /* CBLReplicator+Backgrounding.m in Sources */, 9343F069207D61AB00F19A89 /* CBLChangeListenerToken.m in Sources */, - 9343F06A207D61AB00F19A89 /* MYAnonymousIdentity.m in Sources */, 9343F06B207D61AB00F19A89 /* CBLIndex.m in Sources */, 40FC1C5F2B928C1600394276 /* ClientCertificateAuthenticator.swift in Sources */, 9343F06C207D61AB00F19A89 /* Limit.swift in Sources */, @@ -6776,7 +6765,6 @@ 930AE46E1EAA6C9100E92E9A /* CBLData.mm in Sources */, 934F4C111E1EF19000F90659 /* CollectionUtils.m in Sources */, 934F4C5B1E1EF25E00F90659 /* Test.m in Sources */, - 27B69A141F2A4C6D00782145 /* MYAnonymousIdentity.m in Sources */, 72A879F01E2DD51C008466FF /* CBLBlob.mm in Sources */, 934A278E1F30E5A5003946A7 /* CBLAggregateExpression.m in Sources */, 934F4CAE1E241FB500F90659 /* CBLJSON.mm in Sources */, diff --git a/Objective-C/Internal/Replicator/CBLTrustCheck.mm b/Objective-C/Internal/Replicator/CBLTrustCheck.mm index 0d6f28470..9cee6e1ce 100644 --- a/Objective-C/Internal/Replicator/CBLTrustCheck.mm +++ b/Objective-C/Internal/Replicator/CBLTrustCheck.mm @@ -111,23 +111,13 @@ - (BOOL) forceTrusted { SecTrustSetExceptions(_trust, exception); CFRelease(exception); - if (@available(iOS 12.0, macos 10.14, *)) { - CFErrorRef error; - BOOL trusted = SecTrustEvaluateWithError(_trust, &error); - if (!trusted) - CBLWarnError(Sync, @"Failed to force trust"); - - return trusted; - } else { -#if TARGET_OS_MACCATALYST - CBLWarnError(Sync, @"Catalyst:SecTrustEvaluate API not available, macOS < 10.14, iOS < 12"); -#else - SecTrustResultType result; - SecTrustEvaluate(_trust, &result); -#endif - } + + CFErrorRef error; + BOOL trusted = SecTrustEvaluateWithError(_trust, &error); + if (!trusted) + CBLWarnError(Sync, @"Failed to force trust"); - return YES; + return trusted; } @@ -147,22 +137,13 @@ - (NSURLCredential*) checkTrust: (NSError**)outError { SecTrustResultType result; OSStatus err; - if (@available(iOS 12.0, macos 10.14, *)) { - CFErrorRef error; - BOOL trusted = SecTrustEvaluateWithError(_trust, &error); - if (!trusted) { - NSError* cferr = (__bridge NSError*)error; - CBLLogVerbose(Sync, @"SecTrustEvaluateWithError failed(%ld). %@. Evaluating trust result...", (long)cferr.code, (cferr).localizedDescription); - } - err = SecTrustGetTrustResult(_trust, &result); - } else { -#if TARGET_OS_MACCATALYST - CBLWarnError(Sync, @"Catalyst:SecTrustEvaluate API not available, macOS < 10.14, iOS < 12"); - return nil; -#else - err = SecTrustEvaluate(_trust, &result); -#endif + CFErrorRef error; + BOOL trusted = SecTrustEvaluateWithError(_trust, &error); + if (!trusted) { + NSError* cferr = (__bridge NSError*)error; + CBLLogVerbose(Sync, @"SecTrustEvaluateWithError failed(%ld). %@. Evaluating trust result...", (long)cferr.code, (cferr).localizedDescription); } + err = SecTrustGetTrustResult(_trust, &result); if (err) { CBLWarn(Default, @"%@: SecTrustEvaluate failed with err %d", self, (int)err); @@ -179,8 +160,8 @@ - (NSURLCredential*) checkTrust: (NSError**)outError { // If using cert-pinning, accept cert iff it matches the pin: if (_pinnedCertData) { CFIndex count = SecTrustGetCertificateCount(_trust); -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 || __IPHONE_OS_VERSION_MAX_REQUIRED >= 150000 - if (@available(macOS 12.0, iOS 15.0, *)) { +#if __IPHONE_OS_VERSION_MAX_REQUIRED >= 150000 + if (@available(iOS 15.0, *)) { NSData* certData = nil; for (CFIndex i = 0; i < count; i++) { CFArrayRef certs = SecTrustCopyCertificateChain(_trust); @@ -196,7 +177,10 @@ - (NSURLCredential*) checkTrust: (NSError**)outError { #endif { for (CFIndex i = 0; i < count; i++) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" SecCertificateRef cert = SecTrustGetCertificateAtIndex(_trust, i); +#pragma clang diagnostic pop if ([_pinnedCertData isEqual: CFBridgingRelease(SecCertificateCopyData(cert))]) { [self forceTrusted]; return credential; @@ -227,8 +211,8 @@ - (BOOL) isSelfSignedCert: (NSError**)outError { return NO; C4Cert* c4cert = nil; -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 || __IPHONE_OS_VERSION_MAX_REQUIRED >= 150000 - if (@available(macOS 12.0, iOS 15.0, *)) { +#if __IPHONE_OS_VERSION_MAX_REQUIRED >= 150000 + if (@available(iOS 15.0, *)) { CFArrayRef certs = SecTrustCopyCertificateChain(_trust); SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(certs, 0); c4cert = toC4Cert(@[(__bridge id) certRef], outError); @@ -236,7 +220,10 @@ - (BOOL) isSelfSignedCert: (NSError**)outError { } else #endif { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" SecCertificateRef certRef = SecTrustGetCertificateAtIndex(_trust, 0); +#pragma clang diagnostic pop c4cert = toC4Cert(@[(__bridge id) certRef], outError); } diff --git a/Objective-C/Internal/Replicator/CBLWebSocket.mm b/Objective-C/Internal/Replicator/CBLWebSocket.mm index 1c2a19954..f57b735f9 100644 --- a/Objective-C/Internal/Replicator/CBLWebSocket.mm +++ b/Objective-C/Internal/Replicator/CBLWebSocket.mm @@ -45,11 +45,6 @@ #import "CBLCert.h" #endif -extern "C" { -#import "MYAnonymousIdentity.h" -#import "MYErrorUtils.h" -} - using namespace fleece; // Number of bytes to read from the socket at a time @@ -994,8 +989,8 @@ - (BOOL) checkSSLCert { - (void) updateServerCertificateFromTrust: (SecTrustRef)trust { if (trust != NULL) { if (SecTrustGetCertificateCount(trust) > 0) { -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 || __IPHONE_OS_VERSION_MAX_REQUIRED >= 150000 - if (@available(macOS 12.0, iOS 15.0, *)) { +#if __IPHONE_OS_VERSION_MAX_REQUIRED >= 150000 + if (@available(iOS 15.0, *)) { CFArrayRef certs = SecTrustCopyCertificateChain(trust); SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, 0); _replicator.serverCertificate = cert; @@ -1003,7 +998,10 @@ - (void) updateServerCertificateFromTrust: (SecTrustRef)trust { } else #endif { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, 0); +#pragma clang diagnostic pop _replicator.serverCertificate = cert; } } diff --git a/Objective-C/Tests/QueryTest+Meta.m b/Objective-C/Tests/QueryTest+Meta.m index 79fffdd03..94e6a42d5 100644 --- a/Objective-C/Tests/QueryTest+Meta.m +++ b/Objective-C/Tests/QueryTest+Meta.m @@ -290,7 +290,7 @@ - (void) testExpiryNoGreaterThanDate { CBLQuery* q = [CBLQueryBuilder select: @[kDOCID] from: [CBLQueryDataSource database: self.db] where: [[CBLQueryMeta expiration] - greaterThan: [CBLQueryExpression double: future]]]; + greaterThan: [CBLQueryExpression longLong: (long long)future]]]; AssertNotNil(q); NSEnumerator* rs = [q execute:&error]; diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 98a9e44d7..598a379e7 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 98a9e44d78187da8fb8df64d0053998203379dc0 +Subproject commit 598a379e7c9609f97104e7cc2de78ce2db53379c diff --git a/xcconfigs/Project.xcconfig b/xcconfigs/Project.xcconfig index 1f4339a30..9410b1fcb 100644 --- a/xcconfigs/Project.xcconfig +++ b/xcconfigs/Project.xcconfig @@ -22,7 +22,7 @@ CBL_BUILD_NUMBER = 0 CBL_COPYRIGHT_YEAR = 2021 IPHONEOS_DEPLOYMENT_TARGET = 12.0 -MACOSX_DEPLOYMENT_TARGET = 10.14 +MACOSX_DEPLOYMENT_TARGET = 12.0 TVOS_DEPLOYMENT_TARGET = 11.0 CODE_SIGN_IDENTITY = From c574ff7dff9fc4684591d370930f32f95a609c38 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Fri, 3 May 2024 14:30:06 -0700 Subject: [PATCH 08/49] CBL-5693 : Fix missing exported symbols (#3281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Ported the fix from release/3.1 branch. * Used the same approach as CBL-C to generate CE and EE exported symbole file. The workflow will be updating CBL.txt or CBL_EE.txt (Contains EE only symbols) at Objective-C/Exports folder and run generate_exports.sh script. The final export symbol files will be in the Objective-C/Exports/Generated folder. * Ensured to include all symbols by cross checking with the symbols extracted from the header files. * Removed obsoleted predefined classes which don’t exist anymore. --- CouchbaseLite.xcodeproj/project.pbxproj | 30 +++- Objective-C/CBLQueryBuilder.h | 2 +- Objective-C/CBLQueryDataSource.h | 10 +- Objective-C/CBLQueryExpression.h | 1 - .../{CouchbaseLite.exp => Exports/CBL.txt} | 57 ++++---- Objective-C/Exports/CBL_EE.txt | 65 +++++++++ Objective-C/Exports/Generated/CBL.exp | 89 ++++++++++++ Objective-C/Exports/Generated/CBL_EE.exp | 131 ++++++++++++++++++ Objective-C/Exports/generate_exports.sh | 12 ++ xcconfigs/CBL_EE_ObjC.xcconfig | 2 +- xcconfigs/CBL_ObjC.xcconfig | 2 +- 11 files changed, 359 insertions(+), 42 deletions(-) rename Objective-C/{CouchbaseLite.exp => Exports/CBL.txt} (90%) create mode 100644 Objective-C/Exports/CBL_EE.txt create mode 100644 Objective-C/Exports/Generated/CBL.exp create mode 100644 Objective-C/Exports/Generated/CBL_EE.exp create mode 100755 Objective-C/Exports/generate_exports.sh diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 4a5c6271d..1f53c72dd 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -2213,7 +2213,6 @@ 275FF6581E412C66005F90DD /* DocPerfTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DocPerfTest.m; sourceTree = ""; }; 275FF6B61E47B2FC005F90DD /* ExceptionUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExceptionUtils.h; sourceTree = ""; }; 275FF6B71E47B2FC005F90DD /* ExceptionUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExceptionUtils.m; sourceTree = ""; }; - 275FF6BE1E48081B005F90DD /* CouchbaseLite.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CouchbaseLite.exp; sourceTree = ""; }; 276740B51EE7381E0036DE42 /* CBLTrustCheck.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLTrustCheck.h; sourceTree = ""; }; 276740B61EE7381E0036DE42 /* CBLTrustCheck.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLTrustCheck.mm; sourceTree = ""; }; 27BE3B451E4D63AF0012B74A /* CBL_Swift.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CBL_Swift.xcconfig; sourceTree = ""; }; @@ -2243,6 +2242,11 @@ 40086B172B7EDDD400DA6770 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 40086B452B803B2A00DA6770 /* CBLBlockConflictResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLBlockConflictResolver.h; sourceTree = ""; }; 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLBlockConflictResolver.m; sourceTree = ""; }; + 40A789282BE2C7D100CA43A1 /* CBL_EE.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL_EE.exp; sourceTree = ""; }; + 40A789292BE2C7D100CA43A1 /* CBL.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL.exp; sourceTree = ""; }; + 40A7892B2BE2C7D100CA43A1 /* CBL_EE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL_EE.txt; sourceTree = ""; }; + 40A7892C2BE2C7D100CA43A1 /* CBL.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL.txt; sourceTree = ""; }; + 40A7892D2BE2C7D100CA43A1 /* generate_exports.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = generate_exports.sh; sourceTree = ""; }; 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLVectorIndexTypes.h; sourceTree = ""; }; 40E905462B5B6D9D00EDF483 /* CouchbaseLiteSwift.private.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = CouchbaseLiteSwift.private.modulemap; sourceTree = ""; }; 40EF68102B71891A00F0CB50 /* remove_private_module.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = remove_private_module.sh; sourceTree = ""; }; @@ -2263,7 +2267,6 @@ 40FC1AB12B9286E700394276 /* CBLListenerCertificateAuthenticator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLListenerCertificateAuthenticator.m; sourceTree = ""; }; 40FC1AB22B9286E700394276 /* CBLListenerPasswordAuthenticator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLListenerPasswordAuthenticator.m; sourceTree = ""; }; 40FC1AB32B9286E700394276 /* CBLURLEndpointListenerConfiguration.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLURLEndpointListenerConfiguration.mm; sourceTree = ""; }; - 40FC1AB42B9286E700394276 /* CouchbaseLite.exp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.exports; path = CouchbaseLite.exp; sourceTree = ""; }; 40FC1AB72B9286E700394276 /* CBLEncryptionKey+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CBLEncryptionKey+Internal.h"; sourceTree = ""; }; 40FC1AB82B9286E700394276 /* CBLDatabase+EncryptionInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "CBLDatabase+EncryptionInternal.mm"; sourceTree = ""; }; 40FC1ABA2B9286E700394276 /* CBLTLSIdentity+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CBLTLSIdentity+Internal.h"; sourceTree = ""; }; @@ -3069,6 +3072,26 @@ path = Extensions; sourceTree = ""; }; + 40A7892A2BE2C7D100CA43A1 /* Generated */ = { + isa = PBXGroup; + children = ( + 40A789282BE2C7D100CA43A1 /* CBL_EE.exp */, + 40A789292BE2C7D100CA43A1 /* CBL.exp */, + ); + path = Generated; + sourceTree = ""; + }; + 40A7892E2BE2C7D100CA43A1 /* Exports */ = { + isa = PBXGroup; + children = ( + 40A7892A2BE2C7D100CA43A1 /* Generated */, + 40A7892B2BE2C7D100CA43A1 /* CBL_EE.txt */, + 40A7892C2BE2C7D100CA43A1 /* CBL.txt */, + 40A7892D2BE2C7D100CA43A1 /* generate_exports.sh */, + ); + path = Exports; + sourceTree = ""; + }; 40EF68FD2B7755FD00F0CB50 /* Resources */ = { isa = PBXGroup; children = ( @@ -3086,7 +3109,6 @@ 40FC1AD72B9286E700394276 /* Replicator */, 40FC1AB52B9286E700394276 /* Internal */, 40FC1AD62B9286E700394276 /* CBLEdition.h */, - 40FC1AB42B9286E700394276 /* CouchbaseLite.exp */, ); name = "Objective-C"; path = "../couchbase-lite-ios-ee/Sources/Objective-C"; @@ -3983,9 +4005,9 @@ 9388CBE521BF717C005CA66D /* Log */, 934F4C931E241FB500F90659 /* Internal */, 93BFCD931E0380A300E52F8A /* Tests */, + 40A7892E2BE2C7D100CA43A1 /* Exports */, 4006AB4E2B9106000036E66D /* CBLEdition.h */, 9398D9FD1E03531A00464432 /* CouchbaseLite.h */, - 275FF6BE1E48081B005F90DD /* CouchbaseLite.exp */, 9398D9FE1E03531A00464432 /* Info.plist */, ); path = "Objective-C"; diff --git a/Objective-C/CBLQueryBuilder.h b/Objective-C/CBLQueryBuilder.h index 080994158..fdb050954 100644 --- a/Objective-C/CBLQueryBuilder.h +++ b/Objective-C/CBLQueryBuilder.h @@ -20,7 +20,7 @@ #import @class CBLQuery, CBLQuerySelectResult, CBLQueryDataSource, CBLQueryJoin; -@class CBLQueryOrdering, CBLQueryGroupBy, CBLQueryLimit; +@class CBLQueryOrdering, CBLQueryLimit; @class CBLQueryExpression; NS_ASSUME_NONNULL_BEGIN diff --git a/Objective-C/CBLQueryDataSource.h b/Objective-C/CBLQueryDataSource.h index 5ae5407b0..bf64e22ee 100644 --- a/Objective-C/CBLQueryDataSource.h +++ b/Objective-C/CBLQueryDataSource.h @@ -19,7 +19,7 @@ #import -@class CBLQueryDatabase, CBLDatabase; +@class CBLDatabase; @class CBLCollection; NS_ASSUME_NONNULL_BEGIN @@ -34,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN Create a database data source. @param database The database used as the data source as the query. - @return The CBLQueryDatabase instance. + @return The CBLQueryDataSource instance for the database. */ + (instancetype) database: (CBLDatabase*)database __deprecated_msg("Use [DataSource collection: [database defaultCollection]]) instead."); @@ -44,7 +44,7 @@ __deprecated_msg("Use [DataSource collection: [database defaultCollection]]) ins @param database The database used as the data source as the query. @param alias The alias name of the data source. - @return The CBLQueryDatabase instance. + @return The CBLQueryDataSource instance for the database. */ + (instancetype) database: (CBLDatabase*)database as: (nullable NSString*)alias __deprecated_msg("Use [DataSource collection: [database defaultCollection] as:]) instead."); @@ -52,7 +52,7 @@ __deprecated_msg("Use [DataSource collection: [database defaultCollection] as:]) /** Create a collection data source. @param collection The collection used as the data source as the query. - @return The CBLQueryDatabase instance. + @return The CBLQueryDataSource instance for the collection. */ + (instancetype) collection: (CBLCollection*)collection; @@ -61,7 +61,7 @@ __deprecated_msg("Use [DataSource collection: [database defaultCollection] as:]) @param collection The collection used as the data source as the query. @param alias The alias name of the data source. - @return The CBLQueryDatabase instance. + @return The CBLQueryDataSource instance for the collection. */ + (instancetype) collection: (CBLCollection*)collection as: (nullable NSString*)alias; diff --git a/Objective-C/CBLQueryExpression.h b/Objective-C/CBLQueryExpression.h index a75e8c189..3d0d85cc2 100644 --- a/Objective-C/CBLQueryExpression.h +++ b/Objective-C/CBLQueryExpression.h @@ -21,7 +21,6 @@ @class CBLBlob; @class CBLQueryCollation; -@class CBLVariableExpression; @protocol CBLQueryFullTextIndexExpressionProtocol; NS_ASSUME_NONNULL_BEGIN diff --git a/Objective-C/CouchbaseLite.exp b/Objective-C/Exports/CBL.txt similarity index 90% rename from Objective-C/CouchbaseLite.exp rename to Objective-C/Exports/CBL.txt index a62665326..108f7ab4b 100644 --- a/Objective-C/CouchbaseLite.exp +++ b/Objective-C/Exports/CBL.txt @@ -1,7 +1,7 @@ -# CouchbaseLite.exp +# CBL.txt # CouchbaseLite # -# Copyright (c) 2017 Couchbase, Inc All rights reserved. +# Copyright (c) 2024 Couchbase, Inc All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,15 +15,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -# All public global symbols -- classes, functions, constants -- need to be declared here. +# All public symbols -- classes and constants -- need to be declared here. +# Run generate_exports.sh after modifying this file. -# Public classes: +# Classes: .objc_class_name_CBLArray .objc_class_name_CBLAuthenticator .objc_class_name_CBLBasicAuthenticator .objc_class_name_CBLBlob .objc_class_name_CBLCollection +.objc_class_name_CBLCollectionChange .objc_class_name_CBLCollectionConfiguration +.objc_class_name_CBLConflict +.objc_class_name_CBLConflictResolver .objc_class_name_CBLConsoleLogger .objc_class_name_CBLDatabase .objc_class_name_CBLDatabaseChange @@ -34,11 +38,13 @@ .objc_class_name_CBLDocumentFragment .objc_class_name_CBLDocumentReplication .objc_class_name_CBLFileLogger +.objc_class_name_CBLFragment .objc_class_name_CBLFullTextIndex +.objc_class_name_CBLFullTextIndexConfiguration .objc_class_name_CBLFullTextIndexItem -.objc_class_name_CBLFragment .objc_class_name_CBLIndex .objc_class_name_CBLIndexBuilder +.objc_class_name_CBLIndexConfiguration .objc_class_name_CBLLog .objc_class_name_CBLLogFileConfiguration .objc_class_name_CBLMutableArray @@ -70,42 +76,35 @@ .objc_class_name_CBLReplicator .objc_class_name_CBLReplicatorChange .objc_class_name_CBLReplicatorConfiguration +.objc_class_name_CBLReplicatorStatus .objc_class_name_CBLScope .objc_class_name_CBLSessionAuthenticator .objc_class_name_CBLURLEndpoint .objc_class_name_CBLValueIndex -.objc_class_name_CBLValueIndexItem -.objc_class_name_CBLIndexConfiguration -.objc_class_name_CBLFullTextIndexConfiguration .objc_class_name_CBLValueIndexConfiguration +.objc_class_name_CBLValueIndexItem -# Blob Constants: -_kCBLBlobType -_kCBLTypeProperty +# Constants: +_CBLErrorDomain +_kCBLBlobContentTypeProperty _kCBLBlobDigestProperty _kCBLBlobLengthProperty -_kCBLBlobContentTypeProperty - -# Default Constants: -_kCBLDefaultLogFileUsePlaintext -_kCBLDefaultLogFileUsePlainText -_kCBLDefaultLogFileMaxSize -_kCBLDefaultLogFileMaxRotateCount +_kCBLBlobType +_kCBLDefaultCollectionName _kCBLDefaultFullTextIndexIgnoreAccents -_kCBLDefaultReplicatorType -_kCBLDefaultReplicatorContinuous +_kCBLDefaultLogFileMaxRotateCount +_kCBLDefaultLogFileMaxSize +_kCBLDefaultLogFileUsePlainText +_kCBLDefaultReplicatorAcceptParentCookies _kCBLDefaultReplicatorAllowReplicatingInBackground +_kCBLDefaultReplicatorContinuous +_kCBLDefaultReplicatorEnableAutoPurge _kCBLDefaultReplicatorHeartbeat -_kCBLDefaultReplicatorMaxAttemptsSingleShot -_kCBLDefaultReplicatorMaxAttemptsContinuous _kCBLDefaultReplicatorMaxAttemptWaitTime +_kCBLDefaultReplicatorMaxAttemptsContinuous +_kCBLDefaultReplicatorMaxAttemptsSingleShot _kCBLDefaultReplicatorMaxAttemptsWaitTime -_kCBLDefaultReplicatorEnableAutoPurge _kCBLDefaultReplicatorSelfSignedCertificateOnly -_kCBLDefaultReplicatorAcceptParentCookies - -# Others constants: -_CBLErrorDomain -_kCBLAllPropertiesName -_kCBLDefaultCollectionName +_kCBLDefaultReplicatorType _kCBLDefaultScopeName +_kCBLTypeProperty diff --git a/Objective-C/Exports/CBL_EE.txt b/Objective-C/Exports/CBL_EE.txt new file mode 100644 index 000000000..11bba2a83 --- /dev/null +++ b/Objective-C/Exports/CBL_EE.txt @@ -0,0 +1,65 @@ +# CBL_EE.txt +# CouchbaseLite +# +# Copyright (c) 2024 Couchbase, Inc All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# All public symbols -- classes and constants -- need to be declared here. +# Run generate_exports.sh after modifying this file. + +# Classes: +.objc_class_name_CBLCoreMLPredictiveModel +.objc_class_name_CBLClientCertificateAuthenticator +.objc_class_name_CBLDatabaseEndpoint +.objc_class_name_CBLEncryptionKey +.objc_class_name_CBLListenerCertificateAuthenticator +.objc_class_name_CBLListenerPasswordAuthenticator +.objc_class_name_CBLMessage +.objc_class_name_CBLMessageEndpoint +.objc_class_name_CBLMessageEndpointListener +.objc_class_name_CBLMessageEndpointListenerChange +.objc_class_name_CBLMessageEndpointListenerConfiguration +.objc_class_name_CBLMessagingError +.objc_class_name_CBLPrediction +.objc_class_name_CBLPredictiveIndex +.objc_class_name_CBLQueryPredictionFunction +.objc_class_name_CBLTLSIdentity +.objc_class_name_CBLURLEndpointListener +.objc_class_name_CBLURLEndpointListenerConfiguration +.objc_class_name_CBLVectorEncoding +.objc_class_name_CBLVectorIndexConfiguration + +# Constants: +_kCBLDefaultListenerPort +_kCBLDefaultListenerDisableTls +_kCBLDefaultListenerReadOnly +_kCBLDefaultListenerEnableDeltaSync +_kCBLDefaultVectorIndexDistanceMetric +_kCBLDefaultVectorIndexEncoding +_kCBLCertAttrCommonName +_kCBLCertAttrCountry +_kCBLCertAttrEmailAddress +_kCBLCertAttrGivenName +_kCBLCertAttrHostname +_kCBLCertAttrIPAddress +_kCBLCertAttrLocality +_kCBLCertAttrOrganization +_kCBLCertAttrOrganizationUnit +_kCBLCertAttrPostalAddress +_kCBLCertAttrPostalCode +_kCBLCertAttrPseudonym +_kCBLCertAttrRegisteredID +_kCBLCertAttrStateOrProvince +_kCBLCertAttrSurname +_kCBLCertAttrURL diff --git a/Objective-C/Exports/Generated/CBL.exp b/Objective-C/Exports/Generated/CBL.exp new file mode 100644 index 000000000..bf6b5bd12 --- /dev/null +++ b/Objective-C/Exports/Generated/CBL.exp @@ -0,0 +1,89 @@ +# GENERATED BY generate_exports.sh -- DO NOT EDIT + +.objc_class_name_CBLArray +.objc_class_name_CBLAuthenticator +.objc_class_name_CBLBasicAuthenticator +.objc_class_name_CBLBlob +.objc_class_name_CBLCollection +.objc_class_name_CBLCollectionChange +.objc_class_name_CBLCollectionConfiguration +.objc_class_name_CBLConflict +.objc_class_name_CBLConflictResolver +.objc_class_name_CBLConsoleLogger +.objc_class_name_CBLDatabase +.objc_class_name_CBLDatabaseChange +.objc_class_name_CBLDatabaseConfiguration +.objc_class_name_CBLDictionary +.objc_class_name_CBLDocument +.objc_class_name_CBLDocumentChange +.objc_class_name_CBLDocumentFragment +.objc_class_name_CBLDocumentReplication +.objc_class_name_CBLFileLogger +.objc_class_name_CBLFragment +.objc_class_name_CBLFullTextIndex +.objc_class_name_CBLFullTextIndexConfiguration +.objc_class_name_CBLFullTextIndexItem +.objc_class_name_CBLIndex +.objc_class_name_CBLIndexBuilder +.objc_class_name_CBLIndexConfiguration +.objc_class_name_CBLLog +.objc_class_name_CBLLogFileConfiguration +.objc_class_name_CBLMutableArray +.objc_class_name_CBLMutableDictionary +.objc_class_name_CBLMutableDocument +.objc_class_name_CBLMutableFragment +.objc_class_name_CBLQuery +.objc_class_name_CBLQueryArrayExpression +.objc_class_name_CBLQueryArrayFunction +.objc_class_name_CBLQueryBuilder +.objc_class_name_CBLQueryChange +.objc_class_name_CBLQueryCollation +.objc_class_name_CBLQueryDataSource +.objc_class_name_CBLQueryExpression +.objc_class_name_CBLQueryFullTextExpression +.objc_class_name_CBLQueryFullTextFunction +.objc_class_name_CBLQueryFunction +.objc_class_name_CBLQueryJoin +.objc_class_name_CBLQueryLimit +.objc_class_name_CBLQueryMeta +.objc_class_name_CBLQueryOrdering +.objc_class_name_CBLQueryParameters +.objc_class_name_CBLQueryResult +.objc_class_name_CBLQueryResultSet +.objc_class_name_CBLQuerySelectResult +.objc_class_name_CBLQuerySortOrder +.objc_class_name_CBLQueryVariableExpression +.objc_class_name_CBLReplicatedDocument +.objc_class_name_CBLReplicator +.objc_class_name_CBLReplicatorChange +.objc_class_name_CBLReplicatorConfiguration +.objc_class_name_CBLReplicatorStatus +.objc_class_name_CBLScope +.objc_class_name_CBLSessionAuthenticator +.objc_class_name_CBLURLEndpoint +.objc_class_name_CBLValueIndex +.objc_class_name_CBLValueIndexConfiguration +.objc_class_name_CBLValueIndexItem +_CBLErrorDomain +_kCBLBlobContentTypeProperty +_kCBLBlobDigestProperty +_kCBLBlobLengthProperty +_kCBLBlobType +_kCBLDefaultCollectionName +_kCBLDefaultFullTextIndexIgnoreAccents +_kCBLDefaultLogFileMaxRotateCount +_kCBLDefaultLogFileMaxSize +_kCBLDefaultLogFileUsePlainText +_kCBLDefaultReplicatorAcceptParentCookies +_kCBLDefaultReplicatorAllowReplicatingInBackground +_kCBLDefaultReplicatorContinuous +_kCBLDefaultReplicatorEnableAutoPurge +_kCBLDefaultReplicatorHeartbeat +_kCBLDefaultReplicatorMaxAttemptWaitTime +_kCBLDefaultReplicatorMaxAttemptsContinuous +_kCBLDefaultReplicatorMaxAttemptsSingleShot +_kCBLDefaultReplicatorMaxAttemptsWaitTime +_kCBLDefaultReplicatorSelfSignedCertificateOnly +_kCBLDefaultReplicatorType +_kCBLDefaultScopeName +_kCBLTypeProperty diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp new file mode 100644 index 000000000..ebf06e0e4 --- /dev/null +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -0,0 +1,131 @@ +# GENERATED BY generate_exports.sh -- DO NOT EDIT + +.objc_class_name_CBLArray +.objc_class_name_CBLAuthenticator +.objc_class_name_CBLBasicAuthenticator +.objc_class_name_CBLBlob +.objc_class_name_CBLClientCertificateAuthenticator +.objc_class_name_CBLCollection +.objc_class_name_CBLCollectionChange +.objc_class_name_CBLCollectionConfiguration +.objc_class_name_CBLConflict +.objc_class_name_CBLConflictResolver +.objc_class_name_CBLConsoleLogger +.objc_class_name_CBLCoreMLPredictiveModel +.objc_class_name_CBLDatabase +.objc_class_name_CBLDatabaseChange +.objc_class_name_CBLDatabaseConfiguration +.objc_class_name_CBLDatabaseEndpoint +.objc_class_name_CBLDictionary +.objc_class_name_CBLDocument +.objc_class_name_CBLDocumentChange +.objc_class_name_CBLDocumentFragment +.objc_class_name_CBLDocumentReplication +.objc_class_name_CBLEncryptionKey +.objc_class_name_CBLFileLogger +.objc_class_name_CBLFragment +.objc_class_name_CBLFullTextIndex +.objc_class_name_CBLFullTextIndexConfiguration +.objc_class_name_CBLFullTextIndexItem +.objc_class_name_CBLIndex +.objc_class_name_CBLIndexBuilder +.objc_class_name_CBLIndexConfiguration +.objc_class_name_CBLListenerCertificateAuthenticator +.objc_class_name_CBLListenerPasswordAuthenticator +.objc_class_name_CBLLog +.objc_class_name_CBLLogFileConfiguration +.objc_class_name_CBLMessage +.objc_class_name_CBLMessageEndpoint +.objc_class_name_CBLMessageEndpointListener +.objc_class_name_CBLMessageEndpointListenerChange +.objc_class_name_CBLMessageEndpointListenerConfiguration +.objc_class_name_CBLMessagingError +.objc_class_name_CBLMutableArray +.objc_class_name_CBLMutableDictionary +.objc_class_name_CBLMutableDocument +.objc_class_name_CBLMutableFragment +.objc_class_name_CBLPrediction +.objc_class_name_CBLPredictiveIndex +.objc_class_name_CBLQuery +.objc_class_name_CBLQueryArrayExpression +.objc_class_name_CBLQueryArrayFunction +.objc_class_name_CBLQueryBuilder +.objc_class_name_CBLQueryChange +.objc_class_name_CBLQueryCollation +.objc_class_name_CBLQueryDataSource +.objc_class_name_CBLQueryExpression +.objc_class_name_CBLQueryFullTextExpression +.objc_class_name_CBLQueryFullTextFunction +.objc_class_name_CBLQueryFunction +.objc_class_name_CBLQueryJoin +.objc_class_name_CBLQueryLimit +.objc_class_name_CBLQueryMeta +.objc_class_name_CBLQueryOrdering +.objc_class_name_CBLQueryParameters +.objc_class_name_CBLQueryPredictionFunction +.objc_class_name_CBLQueryResult +.objc_class_name_CBLQueryResultSet +.objc_class_name_CBLQuerySelectResult +.objc_class_name_CBLQuerySortOrder +.objc_class_name_CBLQueryVariableExpression +.objc_class_name_CBLReplicatedDocument +.objc_class_name_CBLReplicator +.objc_class_name_CBLReplicatorChange +.objc_class_name_CBLReplicatorConfiguration +.objc_class_name_CBLReplicatorStatus +.objc_class_name_CBLScope +.objc_class_name_CBLSessionAuthenticator +.objc_class_name_CBLTLSIdentity +.objc_class_name_CBLURLEndpoint +.objc_class_name_CBLURLEndpointListener +.objc_class_name_CBLURLEndpointListenerConfiguration +.objc_class_name_CBLValueIndex +.objc_class_name_CBLValueIndexConfiguration +.objc_class_name_CBLValueIndexItem +.objc_class_name_CBLVectorEncoding +.objc_class_name_CBLVectorIndexConfiguration +_CBLErrorDomain +_kCBLBlobContentTypeProperty +_kCBLBlobDigestProperty +_kCBLBlobLengthProperty +_kCBLBlobType +_kCBLCertAttrCommonName +_kCBLCertAttrCountry +_kCBLCertAttrEmailAddress +_kCBLCertAttrGivenName +_kCBLCertAttrHostname +_kCBLCertAttrIPAddress +_kCBLCertAttrLocality +_kCBLCertAttrOrganization +_kCBLCertAttrOrganizationUnit +_kCBLCertAttrPostalAddress +_kCBLCertAttrPostalCode +_kCBLCertAttrPseudonym +_kCBLCertAttrRegisteredID +_kCBLCertAttrStateOrProvince +_kCBLCertAttrSurname +_kCBLCertAttrURL +_kCBLDefaultCollectionName +_kCBLDefaultFullTextIndexIgnoreAccents +_kCBLDefaultListenerDisableTls +_kCBLDefaultListenerEnableDeltaSync +_kCBLDefaultListenerPort +_kCBLDefaultListenerReadOnly +_kCBLDefaultLogFileMaxRotateCount +_kCBLDefaultLogFileMaxSize +_kCBLDefaultLogFileUsePlainText +_kCBLDefaultReplicatorAcceptParentCookies +_kCBLDefaultReplicatorAllowReplicatingInBackground +_kCBLDefaultReplicatorContinuous +_kCBLDefaultReplicatorEnableAutoPurge +_kCBLDefaultReplicatorHeartbeat +_kCBLDefaultReplicatorMaxAttemptWaitTime +_kCBLDefaultReplicatorMaxAttemptsContinuous +_kCBLDefaultReplicatorMaxAttemptsSingleShot +_kCBLDefaultReplicatorMaxAttemptsWaitTime +_kCBLDefaultReplicatorSelfSignedCertificateOnly +_kCBLDefaultReplicatorType +_kCBLDefaultScopeName +_kCBLDefaultVectorIndexDistanceMetric +_kCBLDefaultVectorIndexEncoding +_kCBLTypeProperty diff --git a/Objective-C/Exports/generate_exports.sh b/Objective-C/Exports/generate_exports.sh new file mode 100755 index 000000000..4f3e24317 --- /dev/null +++ b/Objective-C/Exports/generate_exports.sh @@ -0,0 +1,12 @@ +#! /bin/bash -e + +SCRIPT_DIR=`dirname $0` +pushd "$SCRIPT_DIR/generated" > /dev/null + +echo -e "# GENERATED BY generate_exports.sh -- DO NOT EDIT\n" > CBL.exp +echo -e "# GENERATED BY generate_exports.sh -- DO NOT EDIT\n" > CBL_EE.exp + +cat ../CBL.txt | sort | awk '/^[._]/ { print $0 }' >> CBL.exp +cat ../CBL.txt ../CBL_EE.txt | sort | awk '/^[._]/ { print $0 }' >> CBL_EE.exp + +popd > /dev/null diff --git a/xcconfigs/CBL_EE_ObjC.xcconfig b/xcconfigs/CBL_EE_ObjC.xcconfig index a01b27b47..93c36665a 100644 --- a/xcconfigs/CBL_EE_ObjC.xcconfig +++ b/xcconfigs/CBL_EE_ObjC.xcconfig @@ -20,4 +20,4 @@ #include "CBL_EE_Common.xcconfig" #include "CBL_ObjC.xcconfig" -CBL_EXPORTED_SYMBOLS_FILE = ../couchbase-lite-ios-ee/Sources/Objective-C/CouchbaseLite.exp +CBL_EXPORTED_SYMBOLS_FILE = Objective-C/Exports/Generated/CBL_EE.exp diff --git a/xcconfigs/CBL_ObjC.xcconfig b/xcconfigs/CBL_ObjC.xcconfig index 445f4f804..8e7caa5c4 100644 --- a/xcconfigs/CBL_ObjC.xcconfig +++ b/xcconfigs/CBL_ObjC.xcconfig @@ -17,7 +17,7 @@ // limitations under the License. // -CBL_EXPORTED_SYMBOLS_FILE = Objective-C/CouchbaseLite.exp +CBL_EXPORTED_SYMBOLS_FILE = Objective-C/Exports/Generated/CBL.exp DEFINES_MODULE = YES DYLIB_COMPATIBILITY_VERSION = 1 DYLIB_CURRENT_VERSION = 1 From b78033e9611a1db17857d9f6cac35412b6b871e3 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 6 May 2024 15:18:39 +0100 Subject: [PATCH 09/49] CBL-5222: MutableDocument should be usable before creating a database instance (#3278) * init logging for MutableDocument * fix typo --- Objective-C/CBLMutableDocument.mm | 10 +++++++++- Objective-C/Internal/CBLLog+Admin.h | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Objective-C/CBLMutableDocument.mm b/Objective-C/CBLMutableDocument.mm index 492fb6218..ae569e033 100644 --- a/Objective-C/CBLMutableDocument.mm +++ b/Objective-C/CBLMutableDocument.mm @@ -2,7 +2,7 @@ // CBLMutableDocument.m // CouchbaseLite // -// Copyright (c) 2017 Couchbase, Inc All rights reserved. +// Copyright (c) 2024 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ #import "CBLDocument+Internal.h" #import "CBLDatabase+Internal.h" #import "CBLJSON.h" +#import "CBLLog+Internal.h" #import "CBLMisc.h" #import "CBLStringBytes.h" #import "CBLStatus.h" @@ -33,6 +34,13 @@ @implementation CBLMutableDocument ++ (void) initialize { + if (self == [CBLMutableDocument class]) { + // Initialize logging + CBLAssertNotNil(CBLLog.sharedInstance); + } +} + #pragma mark - Initializer + (instancetype) document { diff --git a/Objective-C/Internal/CBLLog+Admin.h b/Objective-C/Internal/CBLLog+Admin.h index df6888b5a..ffc0e0351 100644 --- a/Objective-C/Internal/CBLLog+Admin.h +++ b/Objective-C/Internal/CBLLog+Admin.h @@ -1,8 +1,8 @@ // -// CBLLogging+Admin.h +// CBLLog+Admin.h // CouchbaseLite // -// Copyright (c) 2018 Couchbase, Inc All rights reserved. +// Copyright (c) 2024 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 986803eb36295e7f2400e2d437014b21e591102f Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Wed, 15 May 2024 15:42:36 +0100 Subject: [PATCH 10/49] CBL-5613: Swift API docs for Scope using Obj-c reference (#3284) * fix obj-c ref in swift api * fix some other mistakes or spacing within Swift API Docs --- Swift/Collection.swift | 2 +- Swift/Database+Query.swift | 6 +++--- Swift/From.swift | 4 ++-- Swift/FullTextFunction.swift | 12 +++++++----- Swift/Function.swift | 2 +- Swift/GroupBy.swift | 4 ++-- Swift/Having.swift | 4 ++-- Swift/Index.swift | 6 ++++++ Swift/IndexBuilder.swift | 6 +++--- Swift/Indexable.swift | 2 +- Swift/Joins.swift | 4 ++-- Swift/MutableDocument.swift | 3 +-- Swift/OrderBy.swift | 4 ++-- Swift/Query.swift | 6 +++--- Swift/QueryFactory.swift | 3 +-- Swift/Replicator.swift | 16 +++++++++------- Swift/ReplicatorConfiguration.swift | 10 +++++----- Swift/Scope.swift | 9 ++++++--- 18 files changed, 57 insertions(+), 46 deletions(-) diff --git a/Swift/Collection.swift b/Swift/Collection.swift index 0aa4df4f4..160cecf6d 100644 --- a/Swift/Collection.swift +++ b/Swift/Collection.swift @@ -39,7 +39,7 @@ import CouchbaseLiteSwift_Private /// - Cannot start with _ or %. /// - Both scope and collection names are case sensitive. /// -/// ## CBLCollection Lifespan +/// ## Collection Lifespan /// A `Collection` object and its reference remain valid until either /// the database is closed or the collection itself is deleted, in that case it will /// throw an NSError with the CBLError.notOpen code while accessing the collection APIs. diff --git a/Swift/Database+Query.swift b/Swift/Database+Query.swift index c042163be..a23daf7a4 100644 --- a/Swift/Database+Query.swift +++ b/Swift/Database+Query.swift @@ -24,7 +24,7 @@ extension Database : QueryFactory { /// Creates a Query object from the given query string. /// /// - Parameters: - /// - query Query string + /// - query: Query string. /// - Returns: A query created by the given query string. /// - Throws: An error on when the given query string is invalid. public func createQuery(_ query: String) throws -> Query { @@ -53,8 +53,8 @@ extension Database : QueryFactory { /// will replace the old index. Creating the same index with the same name will be no-ops. /// /// - Parameters: - /// - config: The index configuration - /// - name: The index name. + /// - config: The index configuration + /// - name: The index name. /// - Throws: An error on a failure. @available(*, deprecated, message: "Use database.defaultCollection().createIndex(:name:) instead.") public func createIndex(_ config: IndexConfiguration, name: String) throws { diff --git a/Swift/From.swift b/Swift/From.swift index 9dc8550e3..d99c179b5 100644 --- a/Swift/From.swift +++ b/Swift/From.swift @@ -87,8 +87,8 @@ public final class From: Query, JoinRouter, WhereRouter, GroupByRouter, OrderByR return self.limit(limit, offset: nil) } - /// Creates and chains a Limit object to skip the returned results for the given offset - /// position and to limit the number of results to not more than the given limit value. + /// Creates and chains a Limit object to skip the returned results for the given offset + /// position and to limit the number of results to not more than the given limit value. /// /// - Parameters: /// - limit: The limit expression. diff --git a/Swift/FullTextFunction.swift b/Swift/FullTextFunction.swift index a11267144..8439bf037 100644 --- a/Swift/FullTextFunction.swift +++ b/Swift/FullTextFunction.swift @@ -36,8 +36,9 @@ public final class FullTextFunction { /// Creates a full-text match expression with the given full-text index name and the query text /// - /// - Parameter indexName: The index name. - /// - Parameter query: The query string. + /// - Parameters: + /// - indexName: The index name. + /// - query: The query string. /// - Returns: The full-text match function expression. @available(*, deprecated, message: "Use FullTextFunction.match(withIndex:query) instead.") public static func match(indexName: String, query: String) -> ExpressionProtocol { @@ -59,10 +60,11 @@ public final class FullTextFunction { return QueryExpression(CBLQueryFullTextFunction.rank(withIndex: i.toImpl())) } - /// Creates a full-text match() function with the given full-text index expression and the query text + /// Creates a full-text match() function with the given full-text index expression and the query text /// - /// - Parameter index: The full-text index expression. - /// - Parameter query: The query string. + /// - Parameters: + /// - indexName: TThe full-text index expression. + /// - query: The query string. /// - Returns: The full-text match() function expression. public static func match(_ index: IndexExpressionProtocol, query: String) -> ExpressionProtocol { guard let i = index as? FullTextIndexExpression else { diff --git a/Swift/Function.swift b/Swift/Function.swift index 8217348de..2429f0f28 100644 --- a/Swift/Function.swift +++ b/Swift/Function.swift @@ -259,7 +259,7 @@ public final class Function { /// Creates a TAN(expr) function that returns the tangent of the given numeric expression. /// /// - Parameter expression: The numeric expression. - /// - Returns: The TAN(expr) function. + /// - Returns: The TAN(expr) function. public static func tan(_ expression: ExpressionProtocol) -> ExpressionProtocol { return QueryExpression(CBLQueryFunction.tan(expression.toImpl())) } diff --git a/Swift/GroupBy.swift b/Swift/GroupBy.swift index bf3d07ef5..1e222c9d8 100644 --- a/Swift/GroupBy.swift +++ b/Swift/GroupBy.swift @@ -58,8 +58,8 @@ public final class GroupBy: Query, HavingRouter, OrderByRouter, LimitRouter { return self.limit(limit, offset: nil) } - /// Creates and chains a Limit object to skip the returned results for the given offset - /// position and to limit the number of results to not more than the given limit value. + /// Creates and chains a Limit object to skip the returned results for the given offset + /// position and to limit the number of results to not more than the given limit value. /// /// - Parameters: /// - limit: The limit expression. diff --git a/Swift/Having.swift b/Swift/Having.swift index 2c8a8e4ab..c7050cbbd 100644 --- a/Swift/Having.swift +++ b/Swift/Having.swift @@ -48,8 +48,8 @@ public final class Having: Query, OrderByRouter, LimitRouter { return self.limit(limit, offset: nil) } - /// Creates and chains a Limit object to skip the returned results for the given offset - /// position and to limit the number of results to not more than the given limit value. + /// Creates and chains a Limit object to skip the returned results for the given offset + /// position and to limit the number of results to not more than the given limit value. /// /// - Parameters: /// - limit: The limit expression. diff --git a/Swift/Index.swift b/Swift/Index.swift index 984463080..c8ded0ce6 100644 --- a/Swift/Index.swift +++ b/Swift/Index.swift @@ -28,6 +28,8 @@ public protocol Index { } /// A value index for regular queries. public final class ValueIndex: Index, CBLIndexConvertible { + // MARK: Internal + private let impl: CBLIndex init(items: [ValueIndexItem]) { @@ -135,12 +137,16 @@ public class FullTextIndexItem { protocol CBLIndexConvertible { + // MARK: Internal + func toImpl() -> CBLIndex } extension Index { + // MARK: Internal + func toImpl() -> CBLIndex { if let index = self as? CBLIndexConvertible { return index.toImpl() diff --git a/Swift/IndexBuilder.swift b/Swift/IndexBuilder.swift index 8ad9bdb1f..47c3620e8 100644 --- a/Swift/IndexBuilder.swift +++ b/Swift/IndexBuilder.swift @@ -25,7 +25,7 @@ public class IndexBuilder { /// Create a value index with the given index items. The index items are a list of /// the properties or expressions to be indexed. /// - /// - Parameter: items The index items. + /// - Parameter items: The index items. /// - Returns: The ValueIndex. public static func valueIndex(items: ValueIndexItem...) -> ValueIndex { return valueIndex(items: items); @@ -44,7 +44,7 @@ public class IndexBuilder { /// the index items are the properties that are used to perform the /// match operation against with. /// - /// - Parameter: items The index items. + /// - Parameter items: The index items. /// - Returns: The FullTextIndex. public static func fullTextIndex(items: FullTextIndexItem...) -> FullTextIndex { return fullTextIndex(items: items) @@ -54,7 +54,7 @@ public class IndexBuilder { /// the index items are the properties that are used to perform the /// match operation against with. /// - /// - Parameter: items The index items. + /// - Parameter items: The index items. /// - Returns: The FullTextIndex. public static func fullTextIndex(items: [FullTextIndexItem]) -> FullTextIndex { return FullTextIndex(items: items) diff --git a/Swift/Indexable.swift b/Swift/Indexable.swift index 92227b377..a3ab52785 100644 --- a/Swift/Indexable.swift +++ b/Swift/Indexable.swift @@ -27,7 +27,7 @@ public protocol Indexable { /// Create an index with the index name and config. func createIndex(withName name: String, config: IndexConfiguration) throws - /// Create an index with the index name and index. + /// Create an index with the index and index name. func createIndex(_ index: Index, name: String) throws; /// Delete an index by name. diff --git a/Swift/Joins.swift b/Swift/Joins.swift index 40332535f..8ef8fa4d2 100644 --- a/Swift/Joins.swift +++ b/Swift/Joins.swift @@ -55,8 +55,8 @@ public final class Joins: Query, WhereRouter, OrderByRouter, LimitRouter { return self.limit(limit, offset: nil) } - /// Creates and chains a Limit object to skip the returned results for the given offset - /// position and to limit the number of results to not more than the given limit value. + /// Creates and chains a Limit object to skip the returned results for the given offset + /// position and to limit the number of results to not more than the given limit value. /// /// - Parameters: /// - limit: The limit expression. diff --git a/Swift/MutableDocument.swift b/Swift/MutableDocument.swift index a3e8346f3..883339154 100644 --- a/Swift/MutableDocument.swift +++ b/Swift/MutableDocument.swift @@ -57,8 +57,7 @@ public final class MutableDocument : Document, MutableDictionaryProtocol { /// Initializes a new MutableDocument object with the JSON data. /// - /// - Parameters: - /// - json: The JSON string with data. + /// - Parameter json: The JSON string with data. /// - Throws: An error on a failure. public convenience init(json: String) throws { self.init(CBLMutableDocument()) diff --git a/Swift/OrderBy.swift b/Swift/OrderBy.swift index 8a286891f..078402051 100644 --- a/Swift/OrderBy.swift +++ b/Swift/OrderBy.swift @@ -31,8 +31,8 @@ public final class OrderBy: Query, LimitRouter { return self.limit(limit, offset: nil) } - /// Creates and chains a Limit object to skip the returned results for the given offset - /// position and to limit the number of results to not more than the given limit value. + /// Creates and chains a Limit object to skip the returned results for the given offset + /// position and to limit the number of results to not more than the given limit value. /// /// - Parameters: /// - limit: The limit expression. diff --git a/Swift/Query.swift b/Swift/Query.swift index d16444e33..64e6b28b6 100644 --- a/Swift/Query.swift +++ b/Swift/Query.swift @@ -154,7 +154,7 @@ public class Query { /// - Parameters: /// - database: The database to query. /// - json: JSON data encoding the query. This can be obtained from a Query object's - // JSONRepresentation property. + /// JSONRepresentation property. init(database: Database, JSONRepresentation json: Data) { self.database = database queryImpl = CBLQuery(database: database.impl, jsonRepresentation: json) @@ -162,8 +162,8 @@ public class Query { /// Creates a query, given the query string, as from the expression property. /// - Parameters: - /// - database The database to query. - /// - expressions String representing the query expression. + /// - database: The database to query. + /// - expressions: String representing the query expression. init(database: Database, expressions: String) throws { self.database = database queryImpl = try database.impl.createQuery(expressions) diff --git a/Swift/QueryFactory.swift b/Swift/QueryFactory.swift index bcba46a54..9d1515a30 100644 --- a/Swift/QueryFactory.swift +++ b/Swift/QueryFactory.swift @@ -23,8 +23,7 @@ import Foundation public protocol QueryFactory { /// Creates a Query object from the given query string. /// - /// - Parameters: - /// - query Query string + /// - Parameter query: Query string /// - Returns: A query created by the given query string. /// - Throws: An error on when the given query string is invalid. func createQuery(_ query: String) throws -> Query diff --git a/Swift/Replicator.swift b/Swift/Replicator.swift index 0eacbfaed..acaddeb91 100644 --- a/Swift/Replicator.swift +++ b/Swift/Replicator.swift @@ -29,11 +29,12 @@ public final class Replicator { /// Activity level of a replicator. /// - /// - stopped: The replicator is finished or hit a fatal error. - /// - offline: The replicator is offline as the remote host is unreachable. - /// - connecting: The replicator is connecting to the remote host. - /// - idle: The replicator is inactive waiting for changes or offline. - /// - busy: The replicator is actively transferring data. + /// - Note: + /// - stopped: The replicator is finished or hit a fatal error. + /// - offline: The replicator is offline as the remote host is unreachable. + /// - connecting: The replicator is connecting to the remote host. + /// - idle: The replicator is inactive waiting for changes or offline. + /// - busy: The replicator is actively transferring data. public enum ActivityLevel : UInt8 { case stopped = 0 case offline @@ -231,8 +232,9 @@ public final class Replicator { /// Check whether the document in the given collection is pending to push or not. If the given collection /// is not part of the replicator, an Invalid Parameter Exception will be thrown. /// - /// - Parameter collection The collection where the document belongs - /// - Parameter documentID: The ID of the document to check + /// - Parameters: + /// - collection: The collection where the document belongs. + /// - documentID: The ID of the document to check. /// - Returns: true if the document has one or more revisions pending, false otherwise public func isDocumentPending(_ documentID: String, collection: Collection) throws -> Bool { var error: NSError? diff --git a/Swift/ReplicatorConfiguration.swift b/Swift/ReplicatorConfiguration.swift index cf64edf8c..3434314b0 100644 --- a/Swift/ReplicatorConfiguration.swift +++ b/Swift/ReplicatorConfiguration.swift @@ -21,10 +21,10 @@ import Foundation import CouchbaseLiteSwift_Private /// Replicator type. -/// -/// - pushAndPull: Bidirectional; both push and pull -/// - push: Pushing changes to the target -/// - pull: Pulling changes from the target +/// - Note: +/// - pushAndPull: Bidirectional; both push and pull +/// - push: Pushing changes to the target +/// - pull: Pulling changes from the target public enum ReplicatorType: UInt8 { case pushAndPull = 0 case push @@ -120,7 +120,7 @@ public struct ReplicatorConfiguration { /// Note: channels that are not accessible to the user will be ignored by /// Sync Gateway. /// - /// - Note:Channels are not supported in Peer-to-Peer and Database-to-Database replication. + /// - Note: Channels are not supported in Peer-to-Peer and Database-to-Database replication. @available(*, deprecated, message: """ Use init(target:) and config.addCollection(config:) with a CollectionConfiguration object instead diff --git a/Swift/Scope.swift b/Swift/Scope.swift index fece3b7df..fa22b7e01 100644 --- a/Swift/Scope.swift +++ b/Swift/Scope.swift @@ -20,14 +20,14 @@ import Foundation import CouchbaseLiteSwift_Private -/// A CBLScope represents a scope or namespace of the collections. +/// A Scope represents a scope or namespace of the collections. /// /// The scope implicitly exists when there is at least one collection created under the scope. /// The default scope is exceptional in that it will always exists even there are no collections /// under it. /// -/// `CBLScope` Lifespan -/// A `CBLScope` object remain valid until either the database is closed or +/// `Scope` Lifespan +/// A `Scope` object remain valid until either the database is closed or /// the scope itself is invalidated as all collections in the scope have been deleted. public final class Scope { @@ -50,6 +50,9 @@ public final class Scope { } /// Get a collection in the scope by name. If the collection doesn't exist, a nil value will be returned. + /// - Parameter name: Collection name as String. + /// - Returns: Collection object or nil if it doesn't exist. + /// - Throws: An error on when database object is not available. public func collection(name: String) throws -> Collection? { return try self.database.collection(name: name, scope: impl.name) } From ec83376b97529875a9567da0df5c7b26d5ed0565 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Wed, 15 May 2024 15:45:24 +0100 Subject: [PATCH 11/49] CBL-5710: Add note that the replicator cannot be started in the inBatch() function (#3286) --- Objective-C/CBLReplicator.h | 2 ++ Swift/Replicator.swift | 1 + 2 files changed, 3 insertions(+) diff --git a/Objective-C/CBLReplicator.h b/Objective-C/CBLReplicator.h index ad13115e4..c70bf844e 100644 --- a/Objective-C/CBLReplicator.h +++ b/Objective-C/CBLReplicator.h @@ -75,6 +75,8 @@ NS_ASSUME_NONNULL_BEGIN /** Starts the replicator. This method returns immediately; the replicator runs asynchronously and will report its progress through the replicator change notification. + + @note This method MUST NOT be called within database's inBatch() block, as it will enter deadlock. */ - (void) start; diff --git a/Swift/Replicator.swift b/Swift/Replicator.swift index acaddeb91..e582f02ba 100644 --- a/Swift/Replicator.swift +++ b/Swift/Replicator.swift @@ -98,6 +98,7 @@ public final class Replicator { /// Starts the replicator. This method returns immediately; the replicator runs asynchronously /// and will report its progress through the replicator change notification. + /// - Note: This method MUST NOT be called within database's inBatch() block, as it will enter deadlock. public func start() { registerActiveReplicator() impl.start() From 87d307b0b8c005a2dc29d52691e0a1cf33c20af1 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 20 May 2024 14:11:39 +0100 Subject: [PATCH 12/49] CBL-5514: Swift MutableDocument's collection is not set when a new document is saved (#3287) * change Document.collection API * add tests * remove TODO --- Objective-C/Tests/DocumentTest.m | 25 +++++++++++++++++++++++++ Swift/Collection.swift | 9 +++++++++ Swift/Document.swift | 2 +- Swift/MutableDocument.swift | 4 +--- Swift/Tests/DocumentTest.swift | 21 +++++++++++++++++++++ 5 files changed, 57 insertions(+), 4 deletions(-) diff --git a/Objective-C/Tests/DocumentTest.m b/Objective-C/Tests/DocumentTest.m index 006357ccf..4fea6f7a7 100644 --- a/Objective-C/Tests/DocumentTest.m +++ b/Objective-C/Tests/DocumentTest.m @@ -2237,6 +2237,31 @@ - (void) testSaveBlobAndCompactDB { AssertEqual(blob.length, 0); } +- (void) testGetCollectionAfterDocIsSaved { + NSError* error; + CBLCollection* col1 = [self.db createCollectionWithName:@"collection1" scope: nil error: &error]; + CBLMutableDocument* doc = [[CBLMutableDocument alloc] initWithID: @"doc1"]; + [doc setString: @"hello" forKey: @"greetings"]; + [col1 saveDocument: doc error: &error]; + CBLCollection* docColl = [doc collection]; + AssertEqual(col1, docColl); +} + +- (void) testDocumentResaveInAnotherCollection { + NSError* error; + CBLCollection* colA = [self.db createCollectionWithName:@"collA" scope: nil error: &error]; + CBLCollection* colB = [self.db createCollectionWithName:@"collB" scope: nil error: &error]; + + CBLMutableDocument* doc = [[CBLMutableDocument alloc] initWithID: @"doc1"]; + [doc setString: @"hello" forKey: @"greetings"]; + + [colA saveDocument: doc error: &error]; + doc = [[colA documentWithID: @"doc1" error: &error] toMutable]; + [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in:^BOOL(NSError** e) { + return [colB saveDocument: doc error: e]; + }]; +} + #pragma clang diagnostic pop @end diff --git a/Swift/Collection.swift b/Swift/Collection.swift index 160cecf6d..b35cfa55e 100644 --- a/Swift/Collection.swift +++ b/Swift/Collection.swift @@ -108,6 +108,9 @@ public final class Collection : CollectionChangeObservable, Indexable, Equatable /// the database is closed. public func save(document: MutableDocument) throws { try impl.save(document.impl as! CBLMutableDocument) + if (document.collection == nil) { + document.collection = self + } } /// Save a document into the collection with a specified concurrency control. When specifying @@ -131,6 +134,9 @@ public final class Collection : CollectionChangeObservable, Indexable, Equatable } throw err } + if (document.collection == nil) { + document.collection = self + } return result } @@ -159,6 +165,9 @@ public final class Collection : CollectionChangeObservable, Indexable, Equatable } throw err } + if (document.collection == nil) { + document.collection = self + } return result } diff --git a/Swift/Document.swift b/Swift/Document.swift index 958e79626..649c14dc6 100644 --- a/Swift/Document.swift +++ b/Swift/Document.swift @@ -43,7 +43,7 @@ public class Document : DictionaryProtocol, Equatable, Hashable, Sequence { } /// The collection that the document belongs to. - public let collection: Collection? + internal(set) public var collection: Collection? // MARK: Edit diff --git a/Swift/MutableDocument.swift b/Swift/MutableDocument.swift index 883339154..fc17ba3fc 100644 --- a/Swift/MutableDocument.swift +++ b/Swift/MutableDocument.swift @@ -93,8 +93,7 @@ public final class MutableDocument : Document, MutableDictionaryProtocol { } // MARK: Edit - - // TODO: Implement copy + /// Returns the same MutableDocument object. /// /// - Returns: The MutableDocument object. @@ -306,5 +305,4 @@ public final class MutableDocument : Document, MutableDictionaryProtocol { private var docImpl: CBLMutableDocument { return impl as! CBLMutableDocument } - } diff --git a/Swift/Tests/DocumentTest.swift b/Swift/Tests/DocumentTest.swift index 81331e38d..e70badda6 100644 --- a/Swift/Tests/DocumentTest.swift +++ b/Swift/Tests/DocumentTest.swift @@ -1818,6 +1818,27 @@ class DocumentTest: CBLTestCase { Blob.blobLengthProperty: blob.length]) XCTAssertNil(retrivedBlob) XCTAssertEqual(retrivedBlob?.length ?? 0, 0) + } + + func testGetCollectionAfterDocIsSaved() throws { + let col1 = try db.createCollection(name: "collection1") + let doc = MutableDocument(id: "doc1") + doc.setString("hello", forKey: "greeting") + try col1.save(document: doc) + XCTAssert(doc.collection == col1) + } + + func testDocumentResaveInAnotherCollection() throws { + let colA = try db.createCollection(name: "collA") + let colB = try db.createCollection(name: "collB") + + var doc = MutableDocument(id: "doc1") + doc.setString("hello", forKey: "greeting") + try colA.save(document: doc) + doc = try colA.document(id: "doc1")!.toMutable() + self.expectError(domain: CBLError.domain, code: CBLError.invalidParameter) { + try colB.save(document: doc) + } } } From 39f749d11bdf84fd3ef68a29e350bb69cf5ba738 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Mon, 3 Jun 2024 08:04:23 -0700 Subject: [PATCH 13/49] Update LiteCore to 3.2.0-189 (#3288) * Update LiteCore to 3.2.9-189 * Updated LiteCore to 3.2.9-189. * Updated VS extension to 1.0.0-42. * I have noticed that recently the macOS agent on Github Action is much slower than before. I have added `expTimeout` variable for the default expectation timeout and increased a timeout to 20 seconds. --- Swift/Tests/CBLTestCase.swift | 3 +++ Swift/Tests/CollectionTest.swift | 10 +++++----- Swift/Tests/DatabaseTest.swift | 12 ++++++------ Swift/Tests/DocumentExpirationTest.swift | 24 ++++++++++++------------ Swift/Tests/QueryTest.swift | 4 ++-- Tests/Extensions/version.txt | 2 +- vendor/couchbase-lite-core | 2 +- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/Swift/Tests/CBLTestCase.swift b/Swift/Tests/CBLTestCase.swift index 711e0668b..92a86b25b 100644 --- a/Swift/Tests/CBLTestCase.swift +++ b/Swift/Tests/CBLTestCase.swift @@ -40,6 +40,8 @@ class CBLTestCase: XCTestCase { let otherDatabaseName = "otherdb" + let expTimeout: TimeInterval = 20.0 + var defaultCollection: Collection? var otherDB_defaultCollection: Collection? @@ -95,6 +97,7 @@ class CBLTestCase: XCTestCase { self.otherDB_defaultCollection = nil try! db.close() try! otherDB?.close() + super.tearDown() } diff --git a/Swift/Tests/CollectionTest.swift b/Swift/Tests/CollectionTest.swift index 0b07022ea..6ee39c92f 100644 --- a/Swift/Tests/CollectionTest.swift +++ b/Swift/Tests/CollectionTest.swift @@ -560,7 +560,7 @@ class CollectionTest: CBLTestCase { try createDocNumbered(colA, start: 0, num: 10) try createDocNumbered(colB, start: 0, num: 10) - waitForExpectations(timeout: 10.0) + waitForExpectations(timeout: expTimeout) changeListenerFired = 0 token1.remove() token2.remove() @@ -585,7 +585,7 @@ class CollectionTest: CBLTestCase { try createDocNumbered(colA, start: 0, num: 1) - waitForExpectations(timeout: 10.0) + waitForExpectations(timeout: expTimeout) } } @@ -663,7 +663,7 @@ class CollectionTest: CBLTestCase { try createDocNumbered(colB, start: 0, num: 10) - waitForExpectations(timeout: 10.0) + waitForExpectations(timeout: expTimeout) changeListenerFired = 0; token1.remove() token2.remove() @@ -693,7 +693,7 @@ class CollectionTest: CBLTestCase { doc.setString("str", forKey: "key") try colA.save(document: doc) - waitForExpectations(timeout: 10.0) + waitForExpectations(timeout: expTimeout) } } @@ -940,7 +940,7 @@ class CollectionTest: CBLTestCase { try collection.setDocumentExpiration(id: doc.id, expiration: expiryDate) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) // Remove listener token.remove(); diff --git a/Swift/Tests/DatabaseTest.swift b/Swift/Tests/DatabaseTest.swift index bb1426c64..cdc1c55b9 100644 --- a/Swift/Tests/DatabaseTest.swift +++ b/Swift/Tests/DatabaseTest.swift @@ -1375,7 +1375,7 @@ class DatabaseTest: CBLTestCase { let q2 = QueryBuilder.select().from(ds) q2.addChangeListener { (ch) in change2.fulfill() } - wait(for: [change1, change2], timeout: 5.0) + wait(for: [change1, change2], timeout: expTimeout) try db.close() } @@ -1396,7 +1396,7 @@ class DatabaseTest: CBLTestCase { let q2 = QueryBuilder.select().from(ds) q2.addChangeListener { (ch) in change2.fulfill() } - wait(for: [change1, change2], timeout: 5.0) + wait(for: [change1, change2], timeout: expTimeout) // Replicators: @@ -1418,11 +1418,11 @@ class DatabaseTest: CBLTestCase { let stopped2 = expectation(description: "Stopped 2") startReplicator(r2, idleExpectation: idle2, stoppedExpectation: stopped2) - wait(for: [idle1, idle2], timeout: 5.0) + wait(for: [idle1, idle2], timeout: expTimeout) try! db.close() - wait(for: [stopped1, stopped2], timeout: 5.0) + wait(for: [stopped1, stopped2], timeout: expTimeout) } func startReplicator(_ replicator: Replicator, idleExpectation: XCTestExpectation, stoppedExpectation: XCTestExpectation) { @@ -1452,11 +1452,11 @@ class DatabaseTest: CBLTestCase { let stopped2 = expectation(description: "Stopped 2") startReplicator(r2, idleExpectation: idle2, stoppedExpectation: stopped2) - wait(for: [idle1, idle2], timeout: 5.0) + wait(for: [idle1, idle2], timeout: expTimeout) try! db.close() - wait(for: [stopped1, stopped2], timeout: 5.0) + wait(for: [stopped1, stopped2], timeout: expTimeout) } #endif diff --git a/Swift/Tests/DocumentExpirationTest.swift b/Swift/Tests/DocumentExpirationTest.swift index f6e15201f..4584ceefb 100644 --- a/Swift/Tests/DocumentExpirationTest.swift +++ b/Swift/Tests/DocumentExpirationTest.swift @@ -68,7 +68,7 @@ class DocumentExpirationTest: CBLTestCase { DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { promise.fulfill() } - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) } func testDocumentPurgedAfterExpiration() throws { @@ -89,7 +89,7 @@ class DocumentExpirationTest: CBLTestCase { try defaultCollection!.setDocumentExpiration(id: doc.id, expiration: Date().addingTimeInterval(1)) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) // Remove listener token.remove(); @@ -115,7 +115,7 @@ class DocumentExpirationTest: CBLTestCase { try defaultCollection!.setDocumentExpiration(id: doc.id, expiration: expiryDate) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) // Remove listener token.remove() @@ -157,7 +157,7 @@ class DocumentExpirationTest: CBLTestCase { try defaultCollection!.setDocumentExpiration(id: doc.id, expiration: expiryDate) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) // Validate XCTAssert(purgeTime - begin >= 2.0) @@ -186,7 +186,7 @@ class DocumentExpirationTest: CBLTestCase { } // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) } func testExpiredDocumentPurgedAfterReopenDatabase() throws { @@ -213,7 +213,7 @@ class DocumentExpirationTest: CBLTestCase { XCTAssertNotNil(try self.defaultCollection!.document(id: doc.id)) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) // Remove listener token.remove() @@ -246,7 +246,7 @@ class DocumentExpirationTest: CBLTestCase { XCTAssertNotNil(try otherDB_defaultCollection.document(id: doc.id)) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) XCTAssertNil(try self.defaultCollection!.document(id: doc.id)) XCTAssertNil(try otherDB_defaultCollection.document(id: doc.id)) @@ -283,7 +283,7 @@ class DocumentExpirationTest: CBLTestCase { try defaultCollection!.setDocumentExpiration(id: doc.id, expiration: expiryDate.addingTimeInterval(1.0)) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) // Validate XCTAssert(purgeTime - begin >= 2.0) @@ -317,7 +317,7 @@ class DocumentExpirationTest: CBLTestCase { try defaultCollection!.setDocumentExpiration(id: doc.id, expiration: expiryDate.addingTimeInterval(-9.0)) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) // Validate XCTAssert(purgeTime - begin < 3.0) @@ -346,7 +346,7 @@ class DocumentExpirationTest: CBLTestCase { } // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) } func testSetExpirationThenDeletionAfterwards() throws { @@ -374,7 +374,7 @@ class DocumentExpirationTest: CBLTestCase { try defaultCollection!.delete(document: doc) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) XCTAssertEqual(count, 2) // Remove listener @@ -406,7 +406,7 @@ class DocumentExpirationTest: CBLTestCase { try defaultCollection!.setDocumentExpiration(id: doc.id, expiration: expiryDate) // Wait for result - waitForExpectations(timeout: 5.0) + waitForExpectations(timeout: expTimeout) XCTAssertEqual(count, 2) // Remove listener diff --git a/Swift/Tests/QueryTest.swift b/Swift/Tests/QueryTest.swift index 72b1998eb..addd7345d 100644 --- a/Swift/Tests/QueryTest.swift +++ b/Swift/Tests/QueryTest.swift @@ -1833,10 +1833,10 @@ class QueryTest: CBLTestCase { } } - wait(for: [x1], timeout: 2.0) + wait(for: [x1], timeout: expTimeout) try! self.createDoc(numbered: -1, of: 100) - wait(for: [x2], timeout: 2.0) + wait(for: [x2], timeout: expTimeout) query.removeChangeListener(withToken: token) } diff --git a/Tests/Extensions/version.txt b/Tests/Extensions/version.txt index 51b9b4a13..4d63fcbff 100644 --- a/Tests/Extensions/version.txt +++ b/Tests/Extensions/version.txt @@ -1 +1 @@ -1.0.0-29 +1.0.0-42 diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 598a379e7..fd3224e9f 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 598a379e7c9609f97104e7cc2de78ce2db53379c +Subproject commit fd3224e9fdffbb645f9d7c50a990f3feba8e921c From 12dfb61a18ab33ef9c1c0d203509459ebab1adac Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Mon, 3 Jun 2024 14:32:12 -0700 Subject: [PATCH 14/49] CBL-5660 : Fix a released query context may be used in observer callback (#3285) * Directly ported the fix from release/3.1 branch. * Implemented CBLContextManager class for retaining and mapping the object with its pointer value which can be used as the context for LiteCore's callbacks (e.g. use when creating c4queryobserver objects). The implementation simply stores the object in a map by using its memory address as the key and returns the memory address as the pointer value. * Updated CBLQueryObserver to use CBLContextManager to make sure that the released query context can be detected and not be used. When using the query context, the query context is retained. * Added a test for CBSE-16662 which is related to this issue and two tests to check that there are no notification received without crash (CBL-5660) after removing the token. * Added an internal debug build only C4QueryObserverCallbackDelayInterval config for testing the fix. --- CouchbaseLite.xcodeproj/project.pbxproj | 20 +++ Objective-C/Internal/CBLContextManager.h | 57 +++++++++ Objective-C/Internal/CBLContextManager.m | 69 +++++++++++ Objective-C/Internal/CBLQueryObserver.h | 8 +- Objective-C/Internal/CBLQueryObserver.m | 110 +++++++++++------ Objective-C/Tests/QueryTest+Main.m | 150 +++++++++++++++++++++++ 6 files changed, 377 insertions(+), 37 deletions(-) create mode 100644 Objective-C/Internal/CBLContextManager.h create mode 100644 Objective-C/Internal/CBLContextManager.m diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 1f53c72dd..2f8c15dc9 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -366,6 +366,14 @@ 40086B552B803B2B00DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; 40086B572B803B4300DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; 40086B582B803B4E00DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; + 4017E4652BED6E5400A438EE /* CBLContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4017E4572BED6E5400A438EE /* CBLContextManager.h */; }; + 4017E4662BED6E5400A438EE /* CBLContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4017E4572BED6E5400A438EE /* CBLContextManager.h */; }; + 4017E4672BED6E5400A438EE /* CBLContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4017E4572BED6E5400A438EE /* CBLContextManager.h */; }; + 4017E4682BED6E5400A438EE /* CBLContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4017E4572BED6E5400A438EE /* CBLContextManager.h */; }; + 4017E4692BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; + 4017E46A2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; + 4017E46B2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; + 4017E46C2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; 40C5FD5B2B9947B3004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 40C5FD5C2B9947B9004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; 40EF690C2B7757CF00F0CB50 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 40EF690A2B77564000F0CB50 /* PrivacyInfo.xcprivacy */; }; @@ -2242,6 +2250,8 @@ 40086B172B7EDDD400DA6770 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 40086B452B803B2A00DA6770 /* CBLBlockConflictResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLBlockConflictResolver.h; sourceTree = ""; }; 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLBlockConflictResolver.m; sourceTree = ""; }; + 4017E4572BED6E5400A438EE /* CBLContextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLContextManager.h; sourceTree = ""; }; + 4017E4642BED6E5400A438EE /* CBLContextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLContextManager.m; sourceTree = ""; }; 40A789282BE2C7D100CA43A1 /* CBL_EE.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL_EE.exp; sourceTree = ""; }; 40A789292BE2C7D100CA43A1 /* CBL.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL.exp; sourceTree = ""; }; 40A7892B2BE2C7D100CA43A1 /* CBL_EE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL_EE.txt; sourceTree = ""; }; @@ -3674,6 +3684,8 @@ 937F026B1EFC662100060D64 /* CBLChangeListenerToken.m */, 270AB2BA2073EF57009A4596 /* CBLChangeNotifier.h */, 270AB2BB2073EF57009A4596 /* CBLChangeNotifier.m */, + 4017E4572BED6E5400A438EE /* CBLContextManager.h */, + 4017E4642BED6E5400A438EE /* CBLContextManager.m */, 93B72062205CA6650069F5FC /* CBLException.h */, 934F4C9A1E241FB500F90659 /* CBLJSON.h */, 934F4C9B1E241FB500F90659 /* CBLJSON.mm */, @@ -4407,6 +4419,7 @@ 9383A5901F1EE9550083053D /* CBLQueryResultSet+Internal.h in Headers */, 1AEF0586283380D500D5DDEA /* CBLScope.h in Headers */, 9374A853201165AE00BA0D9E /* CBLURLEndpoint+Internal.h in Headers */, + 4017E4662BED6E5400A438EE /* CBLContextManager.h in Headers */, 93B503651E64B07F002C4680 /* CBLStringBytes.h in Headers */, 69774C4B28361E5B00B1C793 /* CBLIndexable.h in Headers */, 934A279A1F30E5FA003946A7 /* CBLCompoundExpression.h in Headers */, @@ -4597,6 +4610,7 @@ 9343EFE5207D611600F19A89 /* CBLDictionaryFragment.h in Headers */, 40FC1C1E2B928B5000394276 /* CBLVectorEncoding+Internal.h in Headers */, 40FC1C2F2B928BB000394276 /* CBLMessageEndpoint+Internal.h in Headers */, + 4017E4672BED6E5400A438EE /* CBLContextManager.h in Headers */, 933BFE1821A3BE960094530D /* CBLQuery+JSON.h in Headers */, 40FC1BDE2B928A4F00394276 /* CBLPrediction.h in Headers */, 9343EFE6207D611600F19A89 /* CBLReachability.h in Headers */, @@ -4782,6 +4796,7 @@ 40FC1C242B928B5000394276 /* CBLCoreMLPredictiveModel+Internal.h in Headers */, 9343F0F5207D61AB00F19A89 /* CBLDatabase.h in Headers */, 9343F0F6207D61AB00F19A89 /* CBLDocumentChange.h in Headers */, + 4017E4682BED6E5400A438EE /* CBLContextManager.h in Headers */, 1AAFB66E284A260A00878453 /* CBLCollectionChange.h in Headers */, 9343F0F7207D61AB00F19A89 /* CBLReplicatorConfiguration.h in Headers */, 9369A6A8207DC865009B5B83 /* CBLDatabase+EncryptionInternal.h in Headers */, @@ -4993,6 +5008,7 @@ 934F4CB61E241FB500F90659 /* CBLStringBytes.h in Headers */, 27D721BA1F904B2500AA4458 /* CBLNewDictionary.h in Headers */, 93DB7FEC1ED8E1C000C4F845 /* CBLReplicatorConfiguration.h in Headers */, + 4017E4652BED6E5400A438EE /* CBLContextManager.h in Headers */, 9332081A1E77415E000D9993 /* CBLQuery.h in Headers */, 931C14631EAAD3420094F9B2 /* CBLMutableFragment.h in Headers */, 934F4C2B1E1EF19000F90659 /* MYLogging.h in Headers */, @@ -6023,6 +6039,7 @@ 93B503631E64B079002C4680 /* CBLCoreBridge.mm in Sources */, 934A278F1F30E5A5003946A7 /* CBLAggregateExpression.m in Sources */, 938196121EC112280032CC51 /* CBLDocument.mm in Sources */, + 4017E46A2BED6E5400A438EE /* CBLContextManager.m in Sources */, 933F45FA1EC2B47500863ECB /* DataConverter.swift in Sources */, 938196061EC10E890032CC51 /* MutableDictionaryObject.swift in Sources */, 938196021EC10BA40032CC51 /* DictionaryObject.swift in Sources */, @@ -6192,6 +6209,7 @@ 9343EF30207D611600F19A89 /* CBLChangeListenerToken.m in Sources */, 9343EF31207D611600F19A89 /* CBLChangeNotifier.m in Sources */, 9343EF32207D611600F19A89 /* CBLQueryBuilder.m in Sources */, + 4017E46B2BED6E5400A438EE /* CBLContextManager.m in Sources */, 9343EF33207D611600F19A89 /* CBLAuthenticator.m in Sources */, 93EB25C521CDCEC20006FB88 /* CBLQueryParameters.mm in Sources */, 9388CC0121BF74FD005CA66D /* CBLConsoleLogger.m in Sources */, @@ -6354,6 +6372,7 @@ 1AAFB681284A266F00878453 /* CollectionConfiguration.swift in Sources */, 40FC1B7F2B9288A800394276 /* CBLMessageEndpoint.mm in Sources */, 1AEF05A1283380F800D5DDEA /* CBLCollection.mm in Sources */, + 4017E46C2BED6E5400A438EE /* CBLContextManager.m in Sources */, 9343F028207D61AB00F19A89 /* CBLIndexBuilder.m in Sources */, 1A3471502671C8800042C6BA /* CBLFullTextIndexConfiguration.m in Sources */, 9388CC4E21C25141005CA66D /* ConsoleLogger.swift in Sources */, @@ -6850,6 +6869,7 @@ 934F4CAA1E241FB500F90659 /* CBLCoreBridge.mm in Sources */, 9388CBFF21BF74FD005CA66D /* CBLConsoleLogger.m in Sources */, 9322DCE11F14603400C4ACF7 /* CBLQueryLimit.m in Sources */, + 4017E4692BED6E5400A438EE /* CBLContextManager.m in Sources */, 934F4C2A1E1EF19000F90659 /* MYErrorUtils.m in Sources */, 1AEF05A32833900800D5DDEA /* CBLScope.mm in Sources */, 93EC42CD1FB3801E00D54BB4 /* CBLFullTextIndex.m in Sources */, diff --git a/Objective-C/Internal/CBLContextManager.h b/Objective-C/Internal/CBLContextManager.h new file mode 100644 index 000000000..6c8710137 --- /dev/null +++ b/Objective-C/Internal/CBLContextManager.h @@ -0,0 +1,57 @@ +// +// CBLContextManager.h +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Thread-safe context manager for retaining and mapping the object with its pointer value which can be used as + the context for LiteCore's callbacks (e.g. use when creating c4queryobserver objects). The implementation + simply stores the object in a map by using its memory address as the key and returns the memory address + as the pointer value. + + @note + There is a chance that a new registered objects may have the same memory address as the ones previously + unregistered. This implementation can be improved to reduce a chance of reusing the same memory address + by generating integer keys with reuseable integer number + cycle count as inspired by the implementation of + C# GCHandle. For now, the context object MUST BE validated for its originality before use. + */ +@interface CBLContextManager : NSObject + ++ (instancetype) shared; + +/** Register and retain the object. The context pointer of the registered object will be returned. */ +- (void*) registerObject: (id)object; + +/** Unregister the object of the given context pointer. */ +- (void) unregisterObjectForPointer: (void*)ptr; + +/** Get the object of the given context pointer. */ +- (nullable id) objectForPointer: (void*)ptr; + +/** Count number of registered objects. */ +- (NSUInteger) count; + +/** Not available */ +- (instancetype) init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/Internal/CBLContextManager.m b/Objective-C/Internal/CBLContextManager.m new file mode 100644 index 000000000..f9490dcea --- /dev/null +++ b/Objective-C/Internal/CBLContextManager.m @@ -0,0 +1,69 @@ +// +// CBLContextManager.h +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLContextManager.h" + +@implementation CBLContextManager { + NSMutableDictionary* _contextMap; +} + ++ (CBLContextManager*) shared { + static CBLContextManager* shared = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[self alloc] init]; + }); + return shared; +} + +- (instancetype) init { + self = [super init]; + if (self) { + _contextMap = [NSMutableDictionary dictionaryWithCapacity: 20]; + } + return self; +} + +- (void*) registerObject: (id)object { + CBL_LOCK(self) { + void* ptr = (__bridge void *)(object); + [_contextMap setObject: object forKey: [NSValue valueWithPointer: ptr]]; + return ptr; + } +} + +- (void) unregisterObjectForPointer: (void*)ptr { + CBL_LOCK(self) { + [_contextMap removeObjectForKey: [NSValue valueWithPointer: ptr]]; + } +} + +- (id) objectForPointer: (void*)ptr { + CBL_LOCK(self) { + return [_contextMap objectForKey: [NSValue valueWithPointer: ptr]]; + } +} + +- (NSUInteger) count { + CBL_LOCK(self) { + return [_contextMap count]; + } +} + +@end diff --git a/Objective-C/Internal/CBLQueryObserver.h b/Objective-C/Internal/CBLQueryObserver.h index 1b2405de1..2b69405a4 100644 --- a/Objective-C/Internal/CBLQueryObserver.h +++ b/Objective-C/Internal/CBLQueryObserver.h @@ -29,8 +29,6 @@ NS_ASSUME_NONNULL_BEGIN @interface CBLQueryObserver : NSObject -- (instancetype) init NS_UNAVAILABLE; - /** Initialize with a Query. */ - (instancetype) initWithQuery: (CBLQuery*)query columnNames: (NSDictionary*)columnNames @@ -42,6 +40,12 @@ NS_ASSUME_NONNULL_BEGIN /** Stops and frees the observer */ - (void) stop; +- (instancetype) init NS_UNAVAILABLE; + +#ifdef DEBUG ++ (void) setC4QueryObserverCallbackDelayInterval: (NSTimeInterval)delay; +#endif + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/Internal/CBLQueryObserver.m b/Objective-C/Internal/CBLQueryObserver.m index ca939b729..1601e35e2 100644 --- a/Objective-C/Internal/CBLQueryObserver.m +++ b/Objective-C/Internal/CBLQueryObserver.m @@ -16,9 +16,11 @@ // See the License for the specific language governing permissions and // limitations under the License. // + #import "CBLQueryObserver.h" #import "c4.h" #import "CBLChangeNotifier.h" +#import "CBLContextManager.h" #import "CBLQueryChange+Internal.h" #import "CBLQuery+Internal.h" #import "CBLQueryResultSet+Internal.h" @@ -33,8 +35,9 @@ @interface CBLQueryObserver () @implementation CBLQueryObserver { CBLQuery* _query; NSDictionary* _columnNames; - C4QueryObserver* _obs; + C4QueryObserver* _c4obs; CBLChangeListenerToken* _token; + void* _context; } #pragma mark - Constructor @@ -52,9 +55,8 @@ - (instancetype) initWithQuery: (CBLQuery*)query _columnNames = columnNames; _token = token; - // https://github.com/couchbase/couchbase-lite-core/wiki/Thread-Safety - // c4queryobs_create is thread-safe. - _obs = c4queryobs_create(query.c4query, liveQueryCallback, (__bridge void *)self); + _context = [[CBLContextManager shared] registerObject: self]; + _c4obs = c4queryobs_create(query.c4query, liveQueryCallback, _context); // c4queryobs_create is thread-safe. [query.database addActiveStoppable: self]; } @@ -69,15 +71,13 @@ - (void) dealloc { - (void) start { CBL_LOCK(self) { - Assert(_query, @"QueryObserver cannot be restarted."); + Assert(_c4obs, @"QueryObserver cannot be restarted."); [_query.database safeBlock: ^{ - c4queryobs_setEnabled(self->_obs, true); + c4queryobs_setEnabled(self->_c4obs, true); }]; } } -#pragma mark - Internal - - (CBLQuery*) query { CBL_LOCK(self) { return _query; @@ -86,57 +86,97 @@ - (CBLQuery*) query { - (void) stop { CBL_LOCK(self) { - if (!_query) { - return; - } + if ([self isStopped]) { return; } [_query.database safeBlock: ^{ - c4queryobs_setEnabled(self->_obs, false); - c4queryobs_free(self->_obs); - self->_obs = nil; + c4queryobs_setEnabled(self->_c4obs, false); + c4queryobs_free(self->_c4obs); [self->_query.database removeActiveStoppable: self]; }]; + [[CBLContextManager shared] unregisterObjectForPointer: _context]; + _context = nil; + + _c4obs = nil; _query = nil; // Break circular reference cycle _token = nil; // Break circular reference cycle } } +// Must call under self lock +- (BOOL) isStopped { + return _c4obs == nil; +} + +#ifdef DEBUG + +static NSTimeInterval sC4QueryObserverCallbackDelayInterval = 0; + ++ (void) setC4QueryObserverCallbackDelayInterval: (NSTimeInterval)delay { + sC4QueryObserverCallbackDelayInterval = delay; +} + +#endif + #pragma mark - Private -static void liveQueryCallback(C4QueryObserver *obs, C4Query *c4query, void *context) { - CBLQueryObserver* queryObs = (__bridge CBLQueryObserver*)context; - CBLQuery* query = queryObs.query; +static void liveQueryCallback(C4QueryObserver *c4obs, C4Query *c4query, void *context) { +#ifdef DEBUG + if (sC4QueryObserverCallbackDelayInterval > 0) { + [NSThread sleepForTimeInterval: sC4QueryObserverCallbackDelayInterval]; + } +#endif + + // Get and retain object: + id obj = [[CBLContextManager shared] objectForPointer: context]; + CBLQueryObserver* obs = $castIf(CBLQueryObserver, obj); + + // Validate: + if (!obs || obs->_c4obs != c4obs) { + CBLLogVerbose(Query, @"Query observer context was already released, ignore observer callback"); + return; + } + + // Check stopped: + CBLQuery* query = obs.query; if (!query) { + CBLLogVerbose(Query, @"%@: Query observer was already stopped, ignore observer callback", obs); + return; + } + + // MUST get the enumerator inside the callback as the c4obs could be deleted after the callback if called. + __block C4QueryEnumerator* enumerator = NULL; + __block C4Error c4error = {}; + + [query.database safeBlock: ^{ + enumerator = c4queryobs_getEnumerator(c4obs, true, &c4error); + }]; + + if (!enumerator) { + CBLLogVerbose(Query, @"%@: Ignore an empty result (%d/%d)", obs, c4error.domain, c4error.code); return; } dispatch_async(query.database.queryQueue, ^{ - [queryObs postQueryChange: obs]; + [obs postQueryChange: enumerator]; }); }; -- (void) postQueryChange: (C4QueryObserver*)obs { +- (void) postQueryChange: (C4QueryEnumerator*)enumerator { + CBLChangeListenerToken* token; CBL_LOCK(self) { - if (!_query) { + if ([self isStopped]) { + c4queryenum_release( enumerator); + CBLLogVerbose(Query, @"%@: Query observer was already stopped, skip notification", self); return; } - - // Note: enumerator('result') will be released in ~QueryResultContext; no need to release it - __block C4Error c4error = {}; - __block C4QueryEnumerator* result = NULL; - [_query.database safeBlock: ^{ - result = c4queryobs_getEnumerator(obs, true, &c4error); - }]; - - if (!result) { - CBLLogVerbose(Query, @"%@: Ignore an empty result (%d/%d)", self, c4error.domain, c4error.code); - return; - } - - CBLQueryResultSet* rs = [[CBLQueryResultSet alloc] initWithQuery: _query enumerator: result columnNames: _columnNames]; - [_token postChange: [[CBLQueryChange alloc] initWithQuery: _query results: rs error: nil]]; + token = _token; } + + CBLQueryResultSet* rs = [[CBLQueryResultSet alloc] initWithQuery: _query + enumerator: enumerator + columnNames: _columnNames]; + [token postChange: [[CBLQueryChange alloc] initWithQuery: _query results: rs error: nil]]; } @end diff --git a/Objective-C/Tests/QueryTest+Main.m b/Objective-C/Tests/QueryTest+Main.m index cfa831fbc..125f1d670 100644 --- a/Objective-C/Tests/QueryTest+Main.m +++ b/Objective-C/Tests/QueryTest+Main.m @@ -31,6 +31,10 @@ #import "Foundation+CBL.h" #import "CollectionUtils.h" +#ifdef DEBUG +#import "CBLQueryObserver.h" +#endif + @interface QueryTest_Main : QueryTest @end @@ -41,6 +45,13 @@ @implementation QueryTest_Main #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" +- (void) tearDown { +#ifdef DEBUG + [CBLQueryObserver setC4QueryObserverCallbackDelayInterval: 0.0]; +#endif + [super tearDown]; +} + #pragma mark - Where - (void) testNoWhereQuery { @@ -2061,6 +2072,145 @@ - (void) testLiveQueryUpdateQueryParam { [q removeChangeListenerWithToken: token]; } +// CBSE-16662, CBL-5631: thread_mutex crash issue +// This test is a sanity test to check that no crash happens but as the issue was a race condition +// in LiteCore, the test might pass without hitting the issue. +- (void) testCreateAndRemoveLiveQueriesConcurrently { + [self loadNumbers: 1000]; + + NSError* error; + + NSLock *lock = [[NSLock alloc] init]; + dispatch_queue_t queue = dispatch_queue_create("query-queue", DISPATCH_QUEUE_CONCURRENT); + dispatch_queue_t listenerQueue = dispatch_queue_create("query-listener-queue", DISPATCH_QUEUE_CONCURRENT); + + CBLQuery* query = [self.db createQuery: @"select * from _ where number1 < 500" error: &error]; + AssertNotNil(query); + + NSMutableArray>* tokens = [NSMutableArray array]; + NSMutableArray* expectations = [NSMutableArray array]; + + for (int i = 1; i <= 1000; i++) { + XCTestExpectation* exp = [self expectationWithDescription: [NSString stringWithFormat: @"exp %d", i]]; + [expectations addObject: exp]; + + dispatch_async(queue, ^{ + if (i % 2 == 1) { + id token = [query addChangeListenerWithQueue: listenerQueue listener:^(CBLQueryChange *change) { }]; + [lock lock]; + [tokens addObject: token]; + [lock unlock]; + } else { + [lock lock]; + if ([tokens count] > 0) { + id token = [tokens objectAtIndex: 0]; + [tokens removeObjectAtIndex: 0]; + [token remove]; + } + [lock unlock]; + } + [exp fulfill]; + }); + } + + [self waitForExpectations: expectations timeout: 10]; + + // Remove any tokens left: + for (id token in tokens) { + [token remove]; + } + [tokens removeAllObjects]; + + // Wait for listener queue to get drained: + [NSThread sleepForTimeInterval: 5]; +} + +- (void) testLiveQueryNoChangesNotifiedAfterRemoveListenerToken { + // Create 1000 docs: + [self createDocNumbered: 1 of: 100]; + + NSError *error; + NSString* str = [NSString stringWithFormat: @"select * from _ where number1 < 300"]; + CBLQuery* query = [self.db createQuery: str error: &error]; + + __block BOOL tokenRemoved = NO; + + XCTestExpectation* changedExp = [self expectationWithDescription: @"Changed"]; + XCTestExpectation* noChangedExp = [self expectationWithDescription: @"No Changed"]; + noChangedExp.inverted = YES; + + id token = [query addChangeListener:^(CBLQueryChange* change) { + if (tokenRemoved) { + [noChangedExp fulfill]; + } else { + [changedExp fulfill]; + } + }]; + + [self waitForExpectations: @[changedExp] timeout: 5.0]; + + // Remove the token: + tokenRemoved = YES; + [token remove]; + + // Add more docs: + [self createDocNumbered: 101 of: 200]; + + // Wait for no changes notified: + [self waitForExpectations: @[noChangedExp] timeout: 3.0]; +} + +#ifdef DEBUG + +// CBL-5659 : Invalidated context may be used in query observer callback +// This tests that the callback will not be called without a crash. +- (void) testLiveQueryNoDelayedChangesNotifiedAfterRemoveListenerToken { + // Create 1000 docs: + [self createDocNumbered: 1 of: 100]; + + __block BOOL tokenRemoved = NO; + + XCTestExpectation* changedExp = [self expectationWithDescription: @"Changed"]; + XCTestExpectation* noChangedExp = [self expectationWithDescription: @"No Changed"]; + noChangedExp.inverted = YES; + + // Execute the code in an autoreleasepool so that the internal CBLQueryObserver will be released + // immediately right after the token is removed. + @autoreleasepool { + NSError *error; + NSString* str = [NSString stringWithFormat: @"select * from _ where number1 < 300"]; + CBLQuery* query = [self.db createQuery: str error: &error]; + + id token = [query addChangeListener:^(CBLQueryChange* change) { + if (tokenRemoved) { + [noChangedExp fulfill]; + } else { + [changedExp fulfill]; + } + }]; + + [self waitForExpectations: @[changedExp] timeout: 5.0]; + + // Inject some delay in C4QueryObserverCallback: + [CBLQueryObserver setC4QueryObserverCallbackDelayInterval: 3.0]; + + // Add more docs: + [self createDocNumbered: 101 of: 200]; + + // Wait a little for the query to pickup the change: + [NSThread sleepForTimeInterval: 1.0]; + + // Remove the token: + tokenRemoved = YES; + [token remove]; + } + + // Wait for no changes notified: + [self waitForExpectations: @[noChangedExp] timeout: 5.0]; +} + +#endif + #pragma clang diagnostic pop @end From f5063c6de6a31bf648dce2df4ea3642979733b22 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Wed, 5 Jun 2024 14:07:02 +0100 Subject: [PATCH 15/49] CBL-5668: Get Code Coverage at least 80% (#3289) --- Objective-C/Tests/VectorSearchTest.m | 31 ++++++++ Swift/Tests/CBLTestCase.swift | 4 ++ Swift/Tests/DictionaryTest.swift | 54 +++++++++++++- Swift/Tests/DocumentTest.swift | 102 +++++++++++++-------------- Swift/Tests/QueryTest.swift | 102 ++++++++++++++++++++------- 5 files changed, 210 insertions(+), 83 deletions(-) diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index d06680600..3518ad3a7 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -1612,4 +1612,35 @@ - (void) testInvalidVectorMatchWithOrExpression { }]; } + +/* + Private methods tests + */ + +- (void) testIsEqual { + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" + dimensions: 300 + centroids: 20]; + Assert([config.encoding isEqual: [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ8]]); +} + +- (void) testHash { + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" + dimensions: 300 + centroids: 20]; + /* + type = 3 (SQ) + bits = 8 + */ + AssertEqual(config.encoding.hash, 32682); + + config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: 5 bits: 4]; + /* + type = 2 (PQ) + pq_subquantizers = 5 + bits = 4 + */ + AssertEqual(config.encoding.hash, 31872); +} + @end diff --git a/Swift/Tests/CBLTestCase.swift b/Swift/Tests/CBLTestCase.swift index 92a86b25b..f1bfb5696 100644 --- a/Swift/Tests/CBLTestCase.swift +++ b/Swift/Tests/CBLTestCase.swift @@ -152,6 +152,10 @@ class CBLTestCase: XCTestCase { return MutableDocument(id: id) } + func createDocument(data: [String:Any]) -> MutableDocument { + return MutableDocument(data: data) + } + func createDocument(_ id: String?, data: [String:Any]) -> MutableDocument { return MutableDocument(id: id, data: data) } diff --git a/Swift/Tests/DictionaryTest.swift b/Swift/Tests/DictionaryTest.swift index 25f79f5b8..9a2c2ab23 100644 --- a/Swift/Tests/DictionaryTest.swift +++ b/Swift/Tests/DictionaryTest.swift @@ -23,6 +23,8 @@ import CouchbaseLiteSwift class DictionaryTest: CBLTestCase { + let kTestDate = "2017-01-01T00:00:00.000Z" + func testCreateDictionary() throws { let address = MutableDictionaryObject() XCTAssertEqual(address.count, 0) @@ -75,20 +77,66 @@ class DictionaryTest: CBLTestCase { }) } + func testSettersDictionary() throws { + let dict = MutableDictionaryObject() + dict.setValue("value", forKey: "key") + XCTAssertEqual("value", dict.value(forKey: "key") as! String); + + dict.setValue(2, forKey: "key") + XCTAssertEqual(2, dict.value(forKey: "key") as! Int64); + + dict.setString("string", forKey: "key") + XCTAssertEqual("string", dict.string(forKey: "key")); + + dict.setNumber(2, forKey: "key") + XCTAssertEqual(2, dict.number(forKey: "key")); + + dict.setInt(2, forKey: "key") + XCTAssertEqual(2, dict.int(forKey: "key")); + + dict.setInt64(Int64("2")!, forKey: "key") + XCTAssertEqual(2, dict.int64(forKey: "key")); + + let double = Double.random(in: -1.0...1.0) + dict.setDouble(double, forKey: "key") + XCTAssertEqual(double, dict.double(forKey: "key")); + + let float = Float.random(in: -1.0...1.0) + dict.setFloat(float , forKey: "key") + XCTAssertEqual(float, dict.float(forKey: "key")); + + dict.setBoolean(true, forKey: "key") + XCTAssertEqual(true, dict.boolean(forKey: "key")); + + dict.setDate(dateFromJson(kTestDate), forKey: "key") + XCTAssertEqual(dateFromJson(kTestDate), dict.date(forKey: "key")); + + let data1 = "data1".data(using: String.Encoding.utf8)! + let blob = Blob.init(contentType: "text/plain", data: data1) + dict.setBlob(blob, forKey: "key") + XCTAssertEqual(blob, dict.blob(forKey: "key")); + + let array = MutableArrayObject() + array.addString("a1") + array.addDictionary(MutableDictionaryObject(data: ["name": "n1"])) + dict.setArray(array, forKey: "key") + XCTAssertEqual(array, dict.array(forKey: "key")); + } + func testSetNestedDictionaries() throws { let doc = createDocument("doc1") let level1 = MutableDictionaryObject() level1.setValue("n1", forKey: "name") - doc.setValue(level1, forKey: "level1") + doc.setDictionary(level1, forKey: "level1") let level2 = MutableDictionaryObject() level2.setValue("n2", forKey: "name") - level1.setValue(level2, forKey: "level2") + level1.setDictionary(level2, forKey: "level2") let level3 = MutableDictionaryObject() level3.setValue("n3", forKey: "name") - level2.setValue(level3, forKey: "level3") + level2.setDictionary(level3, forKey: "level3") XCTAssert(doc.dictionary(forKey: "level1")! === level1) XCTAssert(level1.dictionary(forKey: "level2")! === level2) diff --git a/Swift/Tests/DocumentTest.swift b/Swift/Tests/DocumentTest.swift index e70badda6..26bc32fe2 100644 --- a/Swift/Tests/DocumentTest.swift +++ b/Swift/Tests/DocumentTest.swift @@ -124,8 +124,7 @@ class DocumentTest: CBLTestCase { "state": "CA"], "phones": ["650-123-0001", "650-123-0002"]] - let doc = createDocument("doc1") - doc.setData(dict) + let doc = createDocument(data: dict) XCTAssertTrue(doc.toDictionary() == dict) try saveDocument(doc) @@ -211,16 +210,16 @@ class DocumentTest: CBLTestCase { func testSetString() throws { var doc = createDocument("doc1") - doc.setValue("string1", forKey: "string1") - doc.setValue("string2", forKey: "string2") + doc.setString("string1", forKey: "string1") + doc.setString("string2", forKey: "string2") try saveDocument(doc) { (d) in XCTAssertEqual(d.string(forKey: "string1"), "string1") XCTAssertEqual(d.string(forKey: "string2"), "string2") } // Update: - doc.setValue("string1a", forKey: "string1") - doc.setValue("string2a", forKey: "string2") + doc.setString("string1a", forKey: "string1") + doc.setString("string2a", forKey: "string2") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.string(forKey: "string1"), "string1a") XCTAssertEqual(d.string(forKey: "string2"), "string2a") @@ -228,8 +227,8 @@ class DocumentTest: CBLTestCase { // Get and update: doc = try defaultCollection!.document(id: doc.id)!.toMutable() - doc.setValue("string1b", forKey: "string1") - doc.setValue("string2b", forKey: "string2") + doc.setString("string1b", forKey: "string1") + doc.setString("string2b", forKey: "string2") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.string(forKey: "string1"), "string1b") XCTAssertEqual(d.string(forKey: "string2"), "string2b") @@ -258,11 +257,11 @@ class DocumentTest: CBLTestCase { func testSetNumber() throws { var doc = createDocument("doc1") - doc.setValue(1, forKey: "number1") - doc.setValue(0, forKey: "number2") - doc.setValue(-1, forKey: "number3") - doc.setValue(1.1, forKey: "number4") - doc.setValue(12345678, forKey: "number5") + doc.setNumber(1, forKey: "number1") + doc.setNumber(0, forKey: "number2") + doc.setNumber(-1, forKey: "number3") + doc.setNumber(1.1, forKey: "number4") + doc.setNumber(12345678, forKey: "number5") try saveDocument(doc, eval: { (d) in XCTAssertEqual(doc.int(forKey: "number1"), 1) XCTAssertEqual(doc.int(forKey: "number2"), 0) @@ -273,11 +272,11 @@ class DocumentTest: CBLTestCase { }) // Update: - doc.setValue(0, forKey: "number1") - doc.setValue(1, forKey: "number2") - doc.setValue(1.1, forKey: "number3") - doc.setValue(-1, forKey: "number4") - doc.setValue(-12345678, forKey: "number5") + doc.setNumber(0, forKey: "number1") + doc.setNumber(1, forKey: "number2") + doc.setNumber(1.1, forKey: "number3") + doc.setNumber(-1, forKey: "number4") + doc.setNumber(-12345678, forKey: "number5") try saveDocument(doc, eval: { (d) in XCTAssertEqual(doc.int(forKey: "number1"), 0) XCTAssertEqual(doc.int(forKey: "number2"), 1) @@ -289,11 +288,11 @@ class DocumentTest: CBLTestCase { // Get and update: doc = try defaultCollection!.document(id: doc.id)!.toMutable() - doc.setValue(1, forKey: "number1") - doc.setValue(0, forKey: "number2") - doc.setValue(2.1, forKey: "number3") - doc.setValue(-2, forKey: "number4") - doc.setValue(-123456789, forKey: "number5") + doc.setNumber(1, forKey: "number1") + doc.setNumber(0, forKey: "number2") + doc.setNumber(2.1, forKey: "number3") + doc.setNumber(-2, forKey: "number4") + doc.setNumber(-123456789, forKey: "number5") try saveDocument(doc, eval: { (d) in XCTAssertEqual(doc.int(forKey: "number1"), 1) XCTAssertEqual(doc.int(forKey: "number2"), 0) @@ -366,14 +365,14 @@ class DocumentTest: CBLTestCase { func testSetGetMinMaxNumbers() throws { let doc = createDocument("doc1") - doc.setValue(Int.min, forKey: "min_int") - doc.setValue(Int.max, forKey: "max_int") - doc.setValue(Int64.min, forKey: "min_int64") - doc.setValue(Int64.max, forKey: "max_int64") - doc.setValue(Float.leastNormalMagnitude, forKey: "min_float") - doc.setValue(Float.greatestFiniteMagnitude, forKey: "max_float") - doc.setValue(Double.leastNormalMagnitude, forKey: "min_double") - doc.setValue(Double.greatestFiniteMagnitude, forKey: "max_double") + doc.setInt(Int.min, forKey: "min_int") + doc.setInt(Int.max, forKey: "max_int") + doc.setInt64(Int64.min, forKey: "min_int64") + doc.setInt64(Int64.max, forKey: "max_int64") + doc.setFloat(Float.leastNormalMagnitude, forKey: "min_float") + doc.setFloat(Float.greatestFiniteMagnitude, forKey: "max_float") + doc.setDouble(Double.leastNormalMagnitude, forKey: "min_double") + doc.setDouble(Double.greatestFiniteMagnitude, forKey: "max_double") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.int(forKey: "min_int"), Int.min); @@ -400,47 +399,42 @@ class DocumentTest: CBLTestCase { func testSetGetFloatNumbers() throws { let doc = createDocument("doc1") - doc.setValue(1.00, forKey: "number1") - doc.setValue(1.49, forKey: "number2") - doc.setValue(1.50, forKey: "number3") - doc.setValue(1.51, forKey: "number4") - doc.setValue(1.99, forKey: "number5") + doc.setFloat(1.00, forKey: "number1") + doc.setFloat(1.49, forKey: "number2") + doc.setFloat(1.50, forKey: "number3") + doc.setFloat(1.51, forKey: "number4") + doc.setFloat(1.99, forKey: "number5") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.int(forKey: "number1"), 1); XCTAssertEqual(d.float(forKey: "number1"), 1.00); - XCTAssertEqual(d.double(forKey: "number1"), 1.00); XCTAssertEqual(d.int(forKey: "number2"), 1); XCTAssertEqual(d.float(forKey: "number2"), 1.49); - XCTAssertEqual(d.double(forKey: "number2"), 1.49); XCTAssertEqual(d.int(forKey: "number3"), 1); XCTAssertEqual(d.float(forKey: "number3"), 1.50); - XCTAssertEqual(d.double(forKey: "number3"), 1.50); XCTAssertEqual(d.int(forKey: "number4"), 1); XCTAssertEqual(d.float(forKey: "number4"), 1.51); - XCTAssertEqual(d.double(forKey: "number4"), 1.51); XCTAssertEqual(d.int(forKey: "number5"), 1); XCTAssertEqual(d.float(forKey: "number5"), 1.99); - XCTAssertEqual(d.double(forKey: "number5"), 1.99); }) } func testSetBoolean() throws { var doc = createDocument("doc1") - doc.setValue(true, forKey: "boolean1") - doc.setValue(false, forKey: "boolean2") + doc.setBoolean(true, forKey: "boolean1") + doc.setBoolean(false, forKey: "boolean2") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.boolean(forKey: "boolean1"), true); XCTAssertEqual(d.boolean(forKey: "boolean2"), false); }) // Update: - doc.setValue(false, forKey: "boolean1") - doc.setValue(true, forKey: "boolean2") + doc.setBoolean(false, forKey: "boolean1") + doc.setBoolean(true, forKey: "boolean2") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.boolean(forKey: "boolean1"), false); XCTAssertEqual(d.boolean(forKey: "boolean2"), true); @@ -448,8 +442,8 @@ class DocumentTest: CBLTestCase { // Get and update: doc = try defaultCollection!.document(id: doc.id)!.toMutable() - doc.setValue(true, forKey: "boolean1") - doc.setValue(false, forKey: "boolean2") + doc.setBoolean(true, forKey: "boolean1") + doc.setBoolean(false, forKey: "boolean2") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.boolean(forKey: "boolean1"), true); XCTAssertEqual(d.boolean(forKey: "boolean2"), false); @@ -481,7 +475,7 @@ class DocumentTest: CBLTestCase { let date = Date() let dateStr = jsonFromDate(date) XCTAssertTrue(dateStr.count > 0) - doc.setValue(date, forKey: "date") + doc.setDate(date, forKey: "date") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.value(forKey: "date") as! String, dateStr); XCTAssertEqual(d.string(forKey: "date"), dateStr); @@ -491,7 +485,7 @@ class DocumentTest: CBLTestCase { // Update: var nuDate = Date(timeInterval: 60.0, since: date) var nuDateStr = jsonFromDate(nuDate) - doc.setValue(nuDate, forKey: "date") + doc.setDate(nuDate, forKey: "date") try saveDocument(doc, eval: { (d) in XCTAssertEqual(d.value(forKey: "date") as! String, nuDateStr); XCTAssertEqual(d.string(forKey: "date"), nuDateStr); @@ -534,7 +528,7 @@ class DocumentTest: CBLTestCase { var doc = createDocument("doc1") let content = kTestBlob.data(using: .utf8)! let blob = Blob(contentType: "text/plain", data: content) - doc.setValue(blob, forKey: "blob") + doc.setBlob(blob, forKey: "blob") try saveDocument(doc, eval: { (d) in XCTAssertTrue(d.blob(forKey: "blob")!.properties == blob.properties) XCTAssertEqual(d.blob(forKey: "blob")!.content, content) @@ -543,7 +537,7 @@ class DocumentTest: CBLTestCase { // Update: var nuContent = "1234567890".data(using: .utf8)! var nuBlob = Blob(contentType: "text/plain", data: nuContent) - doc.setValue(nuBlob, forKey: "blob") + doc.setBlob(nuBlob, forKey: "blob") try saveDocument(doc, eval: { (d) in XCTAssertTrue(d.blob(forKey: "blob")!.properties == nuBlob.properties) XCTAssertEqual(d.blob(forKey: "blob")!.content, nuContent) @@ -553,7 +547,7 @@ class DocumentTest: CBLTestCase { doc = try defaultCollection!.document(id: doc.id)!.toMutable() nuContent = "abcdefg".data(using: .utf8)! nuBlob = Blob(contentType: "text/plain", data: nuContent) - doc.setValue(nuBlob, forKey: "blob") + doc.setBlob(nuBlob, forKey: "blob") try saveDocument(doc, eval: { (d) in XCTAssertTrue(d.blob(forKey: "blob")!.properties == nuBlob.properties) XCTAssertEqual(d.blob(forKey: "blob")!.content, nuContent) @@ -584,7 +578,7 @@ class DocumentTest: CBLTestCase { var doc = createDocument("doc1") var dict = MutableDictionaryObject() dict.setValue("1 Main street", forKey: "street") - doc.setValue(dict, forKey: "dict") + doc.setDictionary(dict, forKey: "dict") XCTAssertTrue(doc.value(forKey: "dict") as! DictionaryObject === dict) try saveDocument(doc, eval: { (d) in let savedDict = doc.value(forKey: "dict") as! DictionaryObject @@ -660,7 +654,7 @@ class DocumentTest: CBLTestCase { array.addValue("item1") array.addValue("item2") array.addValue("item3") - doc.setValue(array, forKey: "array") + doc.setArray(array, forKey: "array") XCTAssertTrue(doc.value(forKey: "array") as! ArrayObject === array) XCTAssertTrue(doc.array(forKey: "array")! === array) try saveDocument(doc, eval: { (d) in diff --git a/Swift/Tests/QueryTest.swift b/Swift/Tests/QueryTest.swift index addd7345d..c8a96eccb 100644 --- a/Swift/Tests/QueryTest.swift +++ b/Swift/Tests/QueryTest.swift @@ -1280,9 +1280,9 @@ class QueryTest: CBLTestCase { func testCompareWithUnicodeCollation() throws { let bothSensitive = Collation.unicode() - let accentSensitive = Collation.unicode().ignoreCase(true) - let caseSensitive = Collation.unicode().ignoreAccents(true) - let noSensitive = Collation.unicode().ignoreCase(true).ignoreCase(true) + let caseInsensitive = Collation.unicode().ignoreCase(true) + let accentInsensitive = Collation.unicode().ignoreAccents(true) + let noSensitive = Collation.unicode().ignoreCase(true).ignoreAccents(true) let testCases = [ // Edge cases: empty and 1-char strings: @@ -1297,30 +1297,30 @@ class QueryTest: CBLTestCase { ("abc", "abC", false, bothSensitive), ("AB", "abc", false, bothSensitive), - // Case insenstive: - ("ABCDEF", "ZYXWVU", false, accentSensitive), - ("ABCDEF", "Z", false, accentSensitive), + // Case insensitive: + ("ABCDEF", "ZYXWVU", false, caseInsensitive), + ("ABCDEF", "Z", false, caseInsensitive), - ("a", "A", true, accentSensitive), - ("abc", "ABC", true, accentSensitive), - ("ABA", "abc", false, accentSensitive), + ("a", "A", true, caseInsensitive), + ("abc", "ABC", true, caseInsensitive), + ("ABA", "abc", false, caseInsensitive), - ("commonprefix1", "commonprefix2", false, accentSensitive), - ("commonPrefix1", "commonprefix2", false, accentSensitive), + ("commonprefix1", "commonprefix2", false, caseInsensitive), + ("commonPrefix1", "commonprefix2", false, caseInsensitive), - ("abcdef", "abcdefghijklm", false, accentSensitive), - ("abcdeF", "abcdefghijklm", false, accentSensitive), + ("abcdef", "abcdefghijklm", false, caseInsensitive), + ("abcdeF", "abcdefghijklm", false, caseInsensitive), // Now bring in non-ASCII characters: - ("a", "á", false, accentSensitive), - ("", "á", false, accentSensitive), - ("á", "á", true, accentSensitive), - ("•a", "•A", true, accentSensitive), + ("a", "á", false, caseInsensitive), + ("", "á", false, caseInsensitive), + ("á", "á", true, caseInsensitive), + ("•a", "•A", true, caseInsensitive), - ("test a", "test á", false, accentSensitive), - ("test á", "test b", false, accentSensitive), - ("test á", "test Á", true, accentSensitive), - ("test á1", "test Á2", false, accentSensitive), + ("test a", "test á", false, caseInsensitive), + ("test á", "test b", false, caseInsensitive), + ("test á", "test Á", true, caseInsensitive), + ("test á1", "test Á2", false, caseInsensitive), // Case sensitive, diacritic sensitive: ("ABCDEF", "ZYXWVU", false, bothSensitive), @@ -1338,11 +1338,11 @@ class QueryTest: CBLTestCase { ("test u", "test Ü", false, bothSensitive), // Case sensitive, diacritic insensitive - ("abc", "ABC", false, caseSensitive), - ("test á", "test a", true, caseSensitive), - ("test á", "test A", false, caseSensitive), - ("test á", "test b", false, caseSensitive), - ("test á", "test Á", false, caseSensitive), + ("abc", "ABC", false, accentInsensitive), + ("test á", "test a", true, accentInsensitive), + ("test á", "test A", false, accentInsensitive), + ("test á", "test b", false, accentInsensitive), + ("test á", "test Á", false, accentInsensitive), // Case and diacritic insensitive ("test á", "test Á", true, noSensitive) @@ -1366,6 +1366,56 @@ class QueryTest: CBLTestCase { } } + func testCompareWithASCIICollation() throws { + let ascii = Collation.ascii() + let caseInsensitive = Collation.ascii().ignoreCase(true) + + let testCases = [ + // Edge cases: empty and 1-char strings: + ("", "", true, ascii), + ("", "a", false, ascii), + ("a", "a", true, ascii), + + // Case sensitive: + ("A", "a", false, ascii), + ("abc", "abc", true, ascii), + ("Aaa", "abc", false, ascii), + ("abC", "abc", false, ascii), + ("AB", "abc", false, ascii), + + + // Case insenstive: + ("ABCDEF", "ZYXWVU", false, caseInsensitive), + ("ABCDEF", "Z", false, caseInsensitive), + ("a", "A", true, caseInsensitive), + ("ABC", "abc", true, caseInsensitive), + ("ABA", "abc", false, caseInsensitive), + + ("commonprefix1", "commonprefix2", false, caseInsensitive), + ("commonPrefix1", "commonprefix2", false, caseInsensitive), + + ("abcdef", "abcdefghijklm", false, caseInsensitive), + ("abcdeF", "abcdefghijklm", false, caseInsensitive), + ] + + for data in testCases { + let doc = createDocument() + doc.setValue(data.0, forKey: "value") + try saveDocument(doc) + + let VALUE = Expression.property("value") + let comparison = data.2 ? + VALUE.collate(data.3).equalTo(Expression.string(data.1)) : + VALUE.collate(data.3).lessThan(Expression.string(data.1)) + + let q = QueryBuilder.select().from(DataSource.collection(defaultCollection!)).where(comparison) + let numRow = try verifyQuery(q, block: { (n, r) in }) + XCTAssertEqual(numRow, 1) + + try defaultCollection!.delete(document: doc) + } + } + func testResultSetEnumeration() throws { try loadNumbers(5) let q = QueryBuilder From e652471572ef2fa5e65b2d58dfc1ac3a08e2e92d Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Thu, 6 Jun 2024 14:32:18 +0100 Subject: [PATCH 16/49] CBL-3385: Allow null expression parameter for Function.count(expression) (#3290) --- Objective-C/CBLQueryFunction.h | 2 +- Objective-C/CBLQueryFunction.m | 6 ++++-- Swift/Function.swift | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Objective-C/CBLQueryFunction.h b/Objective-C/CBLQueryFunction.h index 38a107a54..72f56bafd 100644 --- a/Objective-C/CBLQueryFunction.h +++ b/Objective-C/CBLQueryFunction.h @@ -46,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN @param expression The expression. @return The COUNT(expr) function. */ -+ (CBLQueryExpression*) count: (CBLQueryExpression*)expression; ++ (CBLQueryExpression*) count: (nullable CBLQueryExpression*)expression; /** Creates a MIN(expr) function expression that returns the minimum value diff --git a/Objective-C/CBLQueryFunction.m b/Objective-C/CBLQueryFunction.m index a9d19e094..bd93c4e6d 100644 --- a/Objective-C/CBLQueryFunction.m +++ b/Objective-C/CBLQueryFunction.m @@ -32,8 +32,10 @@ + (CBLQueryExpression*) avg: (CBLQueryExpression*)expression { params: @[expression]]; } -+ (CBLQueryExpression*) count: (CBLQueryExpression*)expression { - CBLAssertNotNil(expression); ++ (CBLQueryExpression*) count: (nullable CBLQueryExpression*)expression { + if (expression == nil) { + expression = [CBLQueryExpression all]; + } return [[CBLFunctionExpression alloc] initWithFunction: @"COUNT()" params: @[expression]]; diff --git a/Swift/Function.swift b/Swift/Function.swift index 2429f0f28..bb5a505da 100644 --- a/Swift/Function.swift +++ b/Swift/Function.swift @@ -39,8 +39,8 @@ public final class Function { /// /// - Parameter expression: The expression. /// - Returns: The COUNT(expr) function. - public static func count(_ expression: ExpressionProtocol) -> ExpressionProtocol { - return QueryExpression(CBLQueryFunction.count(expression.toImpl())) + public static func count(_ expression: ExpressionProtocol? = nil) -> ExpressionProtocol { + return QueryExpression(CBLQueryFunction.count(expression != nil ? expression!.toImpl() : nil)) } /// Create a MIN(expr) function expression that returns the minimum value From b1c5ce6272e6d66ba92926e621469c0d179c5eb4 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 7 Jun 2024 19:04:56 +0100 Subject: [PATCH 17/49] sometimes query becomes nil before is stopped (#3292) --- Objective-C/Internal/CBLQueryObserver.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Objective-C/Internal/CBLQueryObserver.m b/Objective-C/Internal/CBLQueryObserver.m index 1601e35e2..82e66dc9b 100644 --- a/Objective-C/Internal/CBLQueryObserver.m +++ b/Objective-C/Internal/CBLQueryObserver.m @@ -164,6 +164,7 @@ static void liveQueryCallback(C4QueryObserver *c4obs, C4Query *c4query, void *co - (void) postQueryChange: (C4QueryEnumerator*)enumerator { CBLChangeListenerToken* token; + CBLQuery* query; CBL_LOCK(self) { if ([self isStopped]) { c4queryenum_release( enumerator); @@ -171,12 +172,13 @@ - (void) postQueryChange: (C4QueryEnumerator*)enumerator { return; } token = _token; + query = _query; } - CBLQueryResultSet* rs = [[CBLQueryResultSet alloc] initWithQuery: _query + CBLQueryResultSet* rs = [[CBLQueryResultSet alloc] initWithQuery: query enumerator: enumerator columnNames: _columnNames]; - [token postChange: [[CBLQueryChange alloc] initWithQuery: _query results: rs error: nil]]; + [token postChange: [[CBLQueryChange alloc] initWithQuery: query results: rs error: nil]]; } @end From 1e9a1ce1b3c25150e511329ac7621c2799fff4b1 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 14 Jun 2024 22:33:41 +0100 Subject: [PATCH 18/49] CBL-5750 + CBL-5757 Lazy Index - ObjC (#3291) - Update to LiteCore 3.2.0-195 and VS-43 --- CouchbaseLite.xcodeproj/project.pbxproj | 49 + Objective-C/CBLCollection.h | 14 +- Objective-C/CBLCollection.mm | 24 + Objective-C/CBLQueryIndex.h | 69 + Objective-C/CBLQueryIndex.mm | 80 + Objective-C/CouchbaseLite.h | 2 + Objective-C/Internal/CBLQueryIndex+Internal.h | 40 + Objective-C/Tests/QueryTest+Collection.m | 2 +- Objective-C/Tests/VectorSearchTest.m | 1463 ++++++++++++++++- Tests/Extensions/version.txt | 2 +- vendor/couchbase-lite-core | 2 +- 11 files changed, 1741 insertions(+), 6 deletions(-) create mode 100644 Objective-C/CBLQueryIndex.h create mode 100644 Objective-C/CBLQueryIndex.mm create mode 100644 Objective-C/Internal/CBLQueryIndex+Internal.h diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 2f8c15dc9..790f21ef0 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -1746,6 +1746,24 @@ AE5803F92B9B969D001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AE5803FA2B9B96BF001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; }; AE5803FB2B9B96BF001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + AE83D07C2C06242F0055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AE83D07D2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; + AE83D07E2C06242F0055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AE83D07F2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; + AE83D0802C0627630055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AE83D0812C0627630055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AE83D0842C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AE83D0852C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; + AE83D0862C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AE83D0872C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; + AECD59FB2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; + AECD59FE2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; + AECD5A002C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; + AECD5A012C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; + AECD5A162C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; + AECD5A172C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; + AECD5A512C125D4800B1247E /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; + AECD5A522C125D4800B1247E /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -2753,6 +2771,12 @@ AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VectorSearchTest.m; sourceTree = ""; }; AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorSearchTest.swift; sourceTree = ""; }; AE5803D72B9B5B2A001A1BE3 /* WordEmbeddingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordEmbeddingModel.swift; sourceTree = ""; }; + AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLQueryIndex.h; sourceTree = ""; }; + AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLQueryIndex.mm; sourceTree = ""; }; + AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLIndexUpdater.h; sourceTree = ""; }; + AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLIndexUpdater.mm; sourceTree = ""; }; + AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLQueryIndex+Internal.h"; sourceTree = ""; }; + AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLIndexUpdater+Internal.h"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -3295,6 +3319,8 @@ 40FC1BB72B928A4A00394276 /* CBLVectorIndexConfiguration.h */, 40FC1BC32B928A4A00394276 /* CBLVectorIndexConfiguration.mm */, 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */, + AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */, + AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */, ); name = Vector; sourceTree = ""; @@ -3320,6 +3346,7 @@ 40FC1ACE2B9286E700394276 /* CBLScalarQuantizer.h */, 40FC1ACD2B9286E700394276 /* CBLScalarQuantizer.mm */, 40FC1ACB2B9286E700394276 /* CBLVectorEncoding+Internal.h */, + AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */, ); name = Vector; sourceTree = ""; @@ -4041,6 +4068,7 @@ 9383A58E1F1EE9550083053D /* CBLQueryResultSet+Internal.h */, 1A3BA96B272C5899002EAB2E /* CBLQueryObserver.h */, 1A3BA96C272C5899002EAB2E /* CBLQueryObserver.m */, + AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */, ); name = Query; sourceTree = ""; @@ -4277,6 +4305,8 @@ 1A3471482671C87F0042C6BA /* CBLFullTextIndexConfiguration.m */, 1A34715C2671C9230042C6BA /* CBLValueIndexConfiguration.h */, 1A34715D2671C9230042C6BA /* CBLValueIndexConfiguration.m */, + AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */, + AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */, ); name = Index; sourceTree = ""; @@ -4391,6 +4421,7 @@ 9388CC5921C25FDE005CA66D /* CBLLog+Swift.h in Headers */, 93DB7FED1ED8E1C000C4F845 /* CBLReplicatorConfiguration.h in Headers */, 938196191EC113770032CC51 /* CBLArrayFragment.h in Headers */, + AE83D0812C0627630055D2CF /* CBLQueryIndex.h in Headers */, 9384D8281FC405BF00FE89D8 /* CBLQueryFullTextExpression.h in Headers */, 275F92841E4D30A4007FD5A2 /* CouchbaseLiteSwift.h in Headers */, 9383A5851F1EE7C00083053D /* CBLQueryResultSet.h in Headers */, @@ -4441,6 +4472,7 @@ 93EC42E31FB387AB00D54BB4 /* CBLQueryJSONEncoding.h in Headers */, 9308F4041E64B22200F53EE4 /* ExceptionUtils.h in Headers */, 1AAFB670284A260A00878453 /* CBLCollectionChangeObservable.h in Headers */, + AECD59FE2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 1A8E2FBC28FF75D500E141A8 /* CBLDefaults.h in Headers */, 939B1B2D200990FB00FAA3CB /* CBLValueExpression.h in Headers */, 937F01E71EFB280000060D64 /* CBLAuthenticator+Internal.h in Headers */, @@ -4511,6 +4543,7 @@ 9343EFB1207D611600F19A89 /* CBLReplicator+Internal.h in Headers */, 9343EFB2207D611600F19A89 /* CBLHTTPLogic.h in Headers */, 9343EFB4207D611600F19A89 /* CBLQueryVariableExpression+Internal.h in Headers */, + AE83D07C2C06242F0055D2CF /* CBLQueryIndex.h in Headers */, 9343EFB5207D611600F19A89 /* CBLException.h in Headers */, 40FC1C0C2B928ADC00394276 /* CBLTLSIdentity+Internal.h in Headers */, 40FC1C0F2B928ADC00394276 /* CBLListenerCertificateAuthenticator+Internal.h in Headers */, @@ -4524,6 +4557,7 @@ 40FC1C092B928ADC00394276 /* CBLURLEndpointListener+Internal.h in Headers */, 9343EFBB207D611600F19A89 /* CBLMutableArray.h in Headers */, 9343EFBC207D611600F19A89 /* CollectionUtils.h in Headers */, + AECD5A002C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 9343EFBD207D611600F19A89 /* CBLMutableArrayFragment.h in Headers */, 6932D4A729547B7B00D28C18 /* CBLQueryFullTextIndexExpressionProtocol.h in Headers */, 1A3470C3266F3E7C0042C6BA /* CBLIndexConfiguration.h in Headers */, @@ -4576,12 +4610,14 @@ 9343EFD2207D611600F19A89 /* CBLCompoundExpression.h in Headers */, 4006AB4D2B9104DE0036E66D /* CouchbaseLite.h in Headers */, 40FC1B712B92889700394276 /* CBLReplicatorConfiguration+ServerCert.h in Headers */, + AECD5A162C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */, 9343EFD3207D611600F19A89 /* CBLQueryVariableExpression.h in Headers */, 9369A6A6207DC7CB009B5B83 /* CBLDatabase+EncryptionInternal.h in Headers */, 40FC1B772B9288A800394276 /* CBLMessageEndpointListener.h in Headers */, 40FC1C212B928B5000394276 /* CBLPrediction+Internal.h in Headers */, 9343EFD4207D611600F19A89 /* CBLAggregateExpression.h in Headers */, 9343EFD5207D611600F19A89 /* CBLDocument+Internal.h in Headers */, + AE83D0842C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */, 93EB263421DF19D00006FB88 /* CBLDocumentFlags.h in Headers */, 1A1612BA283E60A200AA4987 /* CBLReplicatorTypes.h in Headers */, 40FC1B4F2B92873000394276 /* CBLDatabaseConfiguration+Encryption.h in Headers */, @@ -4677,6 +4713,7 @@ 40FC1B4C2B92872000394276 /* CBLDatabase+Encryption.h in Headers */, 40FC1BF92B928A4F00394276 /* CBLVectorIndexConfiguration.h in Headers */, 93292D9F22BD448400A0862A /* CBLReplicatorConfiguration+Swift.h in Headers */, + AECD5A012C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 1AAFB672284A260A00878453 /* CBLCollectionChangeObservable.h in Headers */, 9343F0B6207D61AB00F19A89 /* CBLReachability.h in Headers */, 40FC1C2D2B928B5000394276 /* CBLPrediction+Swift.h in Headers */, @@ -4707,6 +4744,7 @@ 9343F0C8207D61AB00F19A89 /* CBLQueryVariableExpression+Internal.h in Headers */, 9343F0C9207D61AB00F19A89 /* CBLStatus.h in Headers */, 9343F0CA207D61AB00F19A89 /* CBLData.h in Headers */, + AECD5A172C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */, 1ABA63B62881A9DE005835E7 /* CBLCollectionConfiguration+Internal.h in Headers */, 9343F0CC207D61AB00F19A89 /* CBLChangeNotifier.h in Headers */, 1A3471612671C9230042C6BA /* CBLValueIndexConfiguration.h in Headers */, @@ -4714,6 +4752,7 @@ 9343F0CE207D61AB00F19A89 /* CBLFunctionExpression.h in Headers */, 9343F0CF207D61AB00F19A89 /* CBLException.h in Headers */, 9343F0D1207D61AB00F19A89 /* Test.h in Headers */, + AE83D07E2C06242F0055D2CF /* CBLQueryIndex.h in Headers */, 40FC1BF22B928A4F00394276 /* CBLPredictiveIndex.h in Headers */, 93E1873A211122EB001D52B9 /* MYURLUtils.h in Headers */, 9343F0D2207D61AB00F19A89 /* CBLQueryArrayExpression.h in Headers */, @@ -4776,6 +4815,7 @@ 9343F0EC207D61AB00F19A89 /* CBLFullTextIndex.h in Headers */, 1AAFB677284A263C00878453 /* CBLQueryFactory.h in Headers */, 9343F0ED207D61AB00F19A89 /* CBLQuerySelectResult.h in Headers */, + AE83D0862C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */, 1AA2EDF928A676E100DEB47E /* CBLConflictResolverBridge.h in Headers */, 9343F0EE207D61AB00F19A89 /* CBLMutableDictionaryFragment.h in Headers */, 9343F0EF207D61AB00F19A89 /* CBLVersion.h in Headers */, @@ -4980,6 +5020,7 @@ 934A278C1F30E5A5003946A7 /* CBLAggregateExpression.h in Headers */, 93900CFE1EA197F000745D4F /* CBLDocument+Internal.h in Headers */, 9322DCDF1F14603400C4ACF7 /* CBLQueryLimit.h in Headers */, + AE83D0802C0627630055D2CF /* CBLQueryIndex.h in Headers */, 1AEF059A283380F800D5DDEA /* CBLCollection.h in Headers */, 69002EB9234E693F00776107 /* CBLErrorMessage.h in Headers */, 9388CC5821C25FDE005CA66D /* CBLLog+Swift.h in Headers */, @@ -5030,6 +5071,7 @@ 934F4C291E1EF19000F90659 /* MYErrorUtils.h in Headers */, 93E18734211122D9001D52B9 /* MYURLUtils.h in Headers */, 6932D4A629547B7800D28C18 /* CBLQueryFullTextIndexExpressionProtocol.h in Headers */, + AECD59FB2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 93EB263321DF19D00006FB88 /* CBLDocumentFlags.h in Headers */, 937A69031F0731230058277F /* CBLQueryFunction.h in Headers */, 9381961E1EC11A8C0032CC51 /* CBLDictionary+Swift.h in Headers */, @@ -5415,6 +5457,7 @@ 9398D9091E03434200464432 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = NO; CLASSPREFIX = CBL; LastSwiftUpdateCheck = 1010; LastUpgradeCheck = 1020; @@ -6032,6 +6075,7 @@ 93FD618E2020757500E7F6A1 /* CBLIndex.m in Sources */, 9322DCF51F14654E00C4ACF7 /* Limit.swift in Sources */, 938E38841F3A5BB4006806C7 /* CBLQueryCollation.m in Sources */, + AECD5A522C125D4800B1247E /* CBLQueryIndex.mm in Sources */, 9384D80C1FC3F75700FE89D8 /* CBLQueryArrayFunction.m in Sources */, 93AF51C41E80A6FC00E200F0 /* CBLQueryOrdering.m in Sources */, 93DB7FEF1ED8E1C000C4F845 /* CBLReplicatorConfiguration.m in Sources */, @@ -6221,6 +6265,7 @@ 9343EF3A207D611600F19A89 /* Test.m in Sources */, 1AAB2786227793E50037A880 /* CBLConflict.m in Sources */, 9343EF3D207D611600F19A89 /* CBLBlob.mm in Sources */, + AE83D07D2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */, 9343EF3E207D611600F19A89 /* CBLAggregateExpression.m in Sources */, 9343EF3F207D611600F19A89 /* CBLJSON.mm in Sources */, 9343EF40207D611600F19A89 /* CBLParseDate.c in Sources */, @@ -6241,6 +6286,7 @@ 9343EF4C207D611600F19A89 /* CBLValueIndex.m in Sources */, 40FC1BFE2B928AB100394276 /* CBLKeyChain.mm in Sources */, 93E18739211122EB001D52B9 /* MYURLUtils.m in Sources */, + AE83D0852C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */, 9343EF4D207D611600F19A89 /* CBLIndex.m in Sources */, 1AEF05A52833900800D5DDEA /* CBLScope.mm in Sources */, 9343EF4F207D611600F19A89 /* CBLQueryMeta.m in Sources */, @@ -6341,6 +6387,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AE83D0872C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */, 40FC1C5C2B928C1600394276 /* ListenerPasswordAuthenticator.swift in Sources */, 40FC1BFA2B928A4F00394276 /* CBLVectorIndexConfiguration.mm in Sources */, 9343F016207D61AB00F19A89 /* CBLBlob.mm in Sources */, @@ -6377,6 +6424,7 @@ 1A3471502671C8800042C6BA /* CBLFullTextIndexConfiguration.m in Sources */, 9388CC4E21C25141005CA66D /* ConsoleLogger.swift in Sources */, 40FC1C2B2B928B5000394276 /* CBLNoneVectorEncoding.mm in Sources */, + AE83D07F2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */, 40FC1B822B9288A800394276 /* CBLMessagingError.m in Sources */, 9343F029207D61AB00F19A89 /* CBLWebSocket.mm in Sources */, 9343F02A207D61AB00F19A89 /* Parameters.swift in Sources */, @@ -6873,6 +6921,7 @@ 934F4C2A1E1EF19000F90659 /* MYErrorUtils.m in Sources */, 1AEF05A32833900800D5DDEA /* CBLScope.mm in Sources */, 93EC42CD1FB3801E00D54BB4 /* CBLFullTextIndex.m in Sources */, + AECD5A512C125D4800B1247E /* CBLQueryIndex.mm in Sources */, 93DB7FEE1ED8E1C000C4F845 /* CBLReplicatorConfiguration.m in Sources */, 934F4C2C1E1EF19000F90659 /* MYLogging.m in Sources */, 93EC42E81FB3930E00D54BB4 /* CBLQueryArrayExpression.m in Sources */, diff --git a/Objective-C/CBLCollection.h b/Objective-C/CBLCollection.h index 076eaee1c..9ee40310d 100644 --- a/Objective-C/CBLCollection.h +++ b/Objective-C/CBLCollection.h @@ -27,6 +27,7 @@ @class CBLDocumentFragment; @class CBLMutableDocument; @class CBLScope; +@class CBLQueryIndex; @protocol CBLListenerToken; NS_ASSUME_NONNULL_BEGIN @@ -173,7 +174,7 @@ extern NSString* const kCBLDefaultCollectionName; @param document The document. @param error On return, the error if any. - @return /True on success, false on failure. + @return True on success, false on failure. */ - (BOOL) deleteDocument: (CBLDocument*)document error: (NSError**)error; @@ -268,6 +269,17 @@ extern NSString* const kCBLDefaultCollectionName; queue: (nullable dispatch_queue_t)queue listener: (void (^)(CBLDocumentChange*))listener; + +/** + Get a query index object by name. + + @param name The index name. + @param error On return, the error if any. + @return CBLQueryIndex object if index exists. If not, it will return nil. + */ +- (nullable CBLQueryIndex*) indexWithName: (NSString*)name + error: (NSError**)error; + #pragma mark - /** Not available */ diff --git a/Objective-C/CBLCollection.mm b/Objective-C/CBLCollection.mm index e0903a19b..c73240594 100644 --- a/Objective-C/CBLCollection.mm +++ b/Objective-C/CBLCollection.mm @@ -31,6 +31,7 @@ #import "CBLIndexable.h" #import "CBLIndexConfiguration+Internal.h" #import "CBLIndex+Internal.h" +#import "CBLQueryIndex+Internal.h" #import "CBLScope.h" #import "CBLScope+Internal.h" #import "CBLStatus.h" @@ -951,4 +952,27 @@ - (FLSliceResult) emptyFLSliceResult: (CBLDatabase*)db { return result; } +- (nullable CBLQueryIndex*) indexWithName: (nonnull NSString*)name + error: (NSError**)error { + CBLAssertNotNil(name); + + CBL_LOCK(_mutex) { + if (![self checkIsValid: error]) + return nil; + + C4Error c4err = {}; + CBLStringBytes iName(name); + + C4Index* c4index = c4coll_getIndex(_c4col, iName, &c4err); + if (!c4index) { + if (c4err.code != 0){ + convertError(c4err, error); + } + return nil; + } + + return [[CBLQueryIndex alloc] initWithC4Index: c4index name: name collection: self]; + } +} + @end diff --git a/Objective-C/CBLQueryIndex.h b/Objective-C/CBLQueryIndex.h new file mode 100644 index 000000000..32d0f2799 --- /dev/null +++ b/Objective-C/CBLQueryIndex.h @@ -0,0 +1,69 @@ +// +// CBLQueryIndex.h +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +#ifdef COUCHBASE_ENTERPRISE +#import +#endif + +NS_ASSUME_NONNULL_BEGIN +/** + QueryIndex object representing an existing index in the collection. + */ + +@interface CBLQueryIndex : NSObject + +/** + The collection object. + */ +@property (readonly, nonatomic) CBLCollection* collection; + +/** + The index name. + */ +@property (readonly, nonatomic) NSString* name; + +#ifdef COUCHBASE_ENTERPRISE +/** + ENTERPRISE EDITION ONLY + + For updating lazy vector indexes only. + Finds new or updated documents for which vectors need to be (re)computed and + return an IndexUpdater object used for setting the computed vectors for updating the index. + The limit parameter is for setting the max number of vectors to be computed. + + If index is up-to-date, nil will be returned. + If the index is not lazy, an error will be returned. + + @param limit The limit per update.. + @param error On return, the error if any. + @return CBLIndexUpdater object if there are updates to be done, or nil if the index is up-to-date or if an error occurred. + */ +- (nullable CBLIndexUpdater*) beginUpdateWithLimit: (uint64_t)limit + error: (NSError**)error; +#endif + +/** Not available */ +- (instancetype) init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/CBLQueryIndex.mm b/Objective-C/CBLQueryIndex.mm new file mode 100644 index 000000000..bb915a629 --- /dev/null +++ b/Objective-C/CBLQueryIndex.mm @@ -0,0 +1,80 @@ +// +// CBLQueryIndex.mm +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLDatabase+Internal.h" +#import "CBLQueryIndex+Internal.h" +#import "CBLCoreBridge.h" +#import "CBLStatus.h" + +#ifdef COUCHBASE_ENTERPRISE +#import "CBLIndexUpdater+Internal.h" +#endif + +@implementation CBLQueryIndex { + // retained database mutex + id _mutex; +} + +@synthesize collection = _collection, name = _name, c4index = _c4index; + +- (instancetype) initWithC4Index: (C4Index*)c4index + name: (NSString*)name + collection: (CBLCollection*)collection { + self = [super init]; + if (self) { + _c4index = c4index; + _collection = collection; + // grab name from c4index + _name = name; + _mutex = _collection.database.mutex; + } + return self; +} + +- (void) dealloc { + c4index_release(_c4index); +} + +#ifdef COUCHBASE_ENTERPRISE + +- (nullable CBLIndexUpdater*) beginUpdateWithLimit: (uint64_t)limit + error: (NSError**)error { + CBL_LOCK(_mutex){ + C4Error c4err = {}; + C4IndexUpdater* _c4updater = c4index_beginUpdate(_c4index, (size_t)limit, &c4err); + + if(!_c4updater) { + if(c4err.code != 0) { + convertError(c4err, error); + } + return nil; + } + + return [[CBLIndexUpdater alloc] initWithC4Updater:_c4updater + queryIndex: self]; + } +} + +#endif + +- (id) mutex { + return _mutex; +} + +@end diff --git a/Objective-C/CouchbaseLite.h b/Objective-C/CouchbaseLite.h index 26beca7d7..ef7d007bd 100644 --- a/Objective-C/CouchbaseLite.h +++ b/Objective-C/CouchbaseLite.h @@ -78,6 +78,7 @@ FOUNDATION_EXPORT const unsigned char CouchbaseLiteVersionString[]; #import #import #import +#import #import #import #import @@ -111,6 +112,7 @@ FOUNDATION_EXPORT const unsigned char CouchbaseLiteVersionString[]; #import #import #import +#import #import #import #import diff --git a/Objective-C/Internal/CBLQueryIndex+Internal.h b/Objective-C/Internal/CBLQueryIndex+Internal.h new file mode 100644 index 000000000..542f1b649 --- /dev/null +++ b/Objective-C/Internal/CBLQueryIndex+Internal.h @@ -0,0 +1,40 @@ +// +// CBLQueryIndex+Internal.h +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import +#import +#import "c4Index.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface CBLQueryIndex () + +@property (nonatomic, readonly) C4Index* c4index; + +- (id) mutex; + +- (instancetype) initWithC4Index: (C4Index*) c4index + name: (NSString*) name + collection: (CBLCollection*) collection; +@end + + + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/QueryTest+Collection.m b/Objective-C/Tests/QueryTest+Collection.m index 57abda00e..febd517d1 100644 --- a/Objective-C/Tests/QueryTest+Collection.m +++ b/Objective-C/Tests/QueryTest+Collection.m @@ -389,7 +389,7 @@ - (void) testFtsJoinWithCollection { CBLFullTextIndex* descIndex = [CBLIndexBuilder fullTextIndexWithItems: @[desc]]; descIndex.ignoreAccents = NO; Assert([flowersCol createIndex: descIndex name: @"descIndex" error: &error], - @"Error when creating value index: %@", error); + @"Error when creating fts index: %@", error); NSString* qStr = @"SELECT f.name, f.description, c.color FROM test.flowers as f JOIN test.colors as c ON f.cid = c.cid WHERE match(f.descIndex, 'red') ORDER BY f.name"; CBLQuery* q = [self.db createQuery: qStr error: &error]; diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 3518ad3a7..9ff3fa59c 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -20,6 +20,8 @@ #import "CBLTestCase.h" #import "CBLWordEmbeddingModel.h" #import "CustomLogger.h" +#import "CBLQueryIndex.h" +#import "CBLJSON.h" #define kDinnerVector @[@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] @@ -27,8 +29,9 @@ @interface VectorSearchTest : CBLTestCase @end +#pragma mark - Vector Search test /** - Test Spec : https://docs.google.com/document/d/1p8RPmlXjA5KKvHLoFR6dcubFlObAqomlacxVbEvXYoU + Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0001-Vector-Search.md */ @implementation VectorSearchTest { CustomLogger* _logger; @@ -77,6 +80,47 @@ - (BOOL) checkIndexWasTrained { return ![_logger containsString: @"Untrained index; queries may be slow"]; } +- (nullable NSArray*) vectorArrayForWord: (NSString*) word + collection: (CBLCollection*) collection { + AssertNotNil(collection); + NSError* error; + NSString* queryString = [NSString stringWithFormat:@"SELECT vector FROM %@ WHERE word = '%@'", + collection.name, word]; + + CBLQuery* q = [_db createQuery: queryString error: &error]; + if (!q) { + NSLog(@"Can't create query: %@/%ld", error.domain, (long)error.code); + return nil; + } + + CBLQueryResultSet* rs = [q execute: &error]; + if (!rs) { + NSLog(@"Can't execute query: %@/%ld", error.domain, (long)error.code); + return nil; + } + + NSArray* vector; + CBLQueryResult *result = [rs nextObject]; + if (result) { + id value = [result valueAtIndex:0]; + if ([value isKindOfClass:[CBLArray class]]) { + vector = (NSArray*)value; + // Additional check to ensure all elements are NSNumber + for (id element in vector) { + if (![element isKindOfClass:[NSNumber class]]) { + NSLog(@"Unexpected type in vector array"); + return nil; + } + } + } else { + NSLog(@"Query result is not an NSArray"); + } + } + return vector; +} + + + /** * 1. TestVectorIndexConfigurationDefaultValue * Description @@ -1612,7 +1656,6 @@ - (void) testInvalidVectorMatchWithOrExpression { }]; } - /* Private methods tests */ @@ -1643,4 +1686,1420 @@ - (void) testHash { AssertEqual(config.encoding.hash, 31872); } +#pragma mark - Lazy Vector Index test +/** + * Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0002-Lazy-Vector-Index.md + */ + +/** + * 1. TestIsLazyDefaultValue + * Description + * Test that isLazy property is false by default. + * Steps + * 1. Create a VectorIndexConfiguration object. + * - expression: "vector" + * - dimensions: 300 + * - centroids: 20 + * 2. Check that isLazy returns false. + */ +- (void) testIsLazyDefaultValue { + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" + dimensions: 300 + centroids: 20]; + AssertEqual(config.isLazy, false); +} + +/** + * 2. TestIsLazyAccessor + * + * Description + * Test that isLazy getter/setter of the VectorIndexConfiguration work as expected. + * + * Steps + * 1. Create a VectorIndexConfiguration object. + * - expression: word + * - dimensions: 300 + * - centroids : 20 + * 2. Set isLazy to true + * 3. Check that isLazy returns true. + */ +- (void) testIsLazyAccessor { + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" + dimensions: 300 + centroids: 20]; + config.isLazy = true; + AssertEqual(config.isLazy, true); +} + +/** + * 3. TestGetNonExistingIndex + * + * Description + * Test that getting non-existing index object by name returning null. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Get a QueryIndex object from the default collection with the name as + * "nonexistingindex". + * 3. Check that the result is null. + */ +// CBL-5864 +- (void) _testGetNonExistingIndex { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + + CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"nonexistingindex" error: &error]; + AssertNil(qIndex); +} + +/** + * 4. TestGetExistingNonVectorIndex + * + * Description + * Test that getting non-existing index object by name returning an index object correctly. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create a value index named "value_index" in the default collection + * with the expression as "value". + * 3. Get a QueryIndex object from the default collection with the name as + * "value_index". + * 4. Check that the result is not null. + * 5. Check that the QueryIndex's name is "value_index". + * 6. Check that the QueryIndex's collection is the same instance that + * is used for getting the QueryIndex object. + */ +- (void) testGetExistingNonVectorIndex { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + + CBLValueIndexItem* item = [CBLValueIndexItem expression: + [CBLQueryExpression property: @"value"]]; + CBLValueIndex* vIndex = [CBLIndexBuilder valueIndexWithItems: @[item]]; + [defaultCollection createIndex: vIndex name: @"value_index" error: &error]; + AssertNil(error); + + CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"value_index" error: &error]; + AssertEqual(qIndex.name, @"value_index"); + AssertEqual(qIndex.collection, defaultCollection); +} + +/** + * 5. TestGetExistingVectorIndex + * + * Description + * Test that getting an existing index object by name returning an index object correctly. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "vector" + * - dimensions: 300 + * - centroids : 8 + * 3. Get a QueryIndex object from the words collection with the name as "words_index". + * 4. Check that the result is not null. + * 5. Check that the QueryIndex's name is "words_index". + * 6. Check that the QueryIndex's collection is the same instance that is used for + * getting the index. + */ +- (void) testGetExistingVectorIndex { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + AssertNotNil(qIndex); + AssertEqual(qIndex.name, @"words_index"); + AssertEqual(qIndex.collection, collection); +} + +/** + * 6. TestGetIndexOnClosedDatabase + * + * Description + * Test that a CouchbaseLiteException is thrown when getting an index object on a closed database. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Close the test database. + * 3. Get a QueryIndex object from the default collection with the name as "nonexistingindex". + * 4. Check that a CouchbaseLiteException with the code NotOpen is thrown. + */ +- (void) testGetIndexOnClosedDatabase { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + + [self.db close: &error]; + + [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { + return [defaultCollection indexWithName: @"nonexistingindex" error: err] != nil; + }]; +} + +/** + * 7. TestGetIndexOnDeletedCollection + * + * Description + * Test that a CouchbaseLiteException is thrown when getting an index object on a deleted collection. + * + * Steps + * 1. Create a collection named "collA" in the default scope from a test database. + * 2. Delete the collection named "collA" in the default scope from the test database. + * 3. Using the collection object from the step 1, get a QueryIndex object from the default collection + * with the name as "nonexistingindex". + * 4. Check that a CouchbaseLiteException with the code NotOpen is thrown. + */ +- (void) testGetIndexOnDeletedCollection { + NSError* error; + CBLCollection* colA = [self.db createCollectionWithName: @"collA" scope: nil error: &error]; + AssertNil(error); + + [self.db deleteCollectionWithName: @"collA" scope: nil error: &error]; + + [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { + return [colA indexWithName: @"nonexistingindex" error: err] != nil; + }]; +} + +/** + * 8. TestLazyVectorIndexNotAutoUpdatedChangedDocs + * + * Description + * Test that the lazy index is lazy. The index will not be automatically + * updated when the documents are created or updated. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Create an SQL++ query: + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >) + * 4. Execute the query and check that 0 results are returned. + * 5. Update the documents: + * - Create _default.words.word301 with the content from _default.extwords.word1 + * - Update _default.words.word1 with the content from _default.extwords.word3 + * 6. Execute the same query and check that 0 results are returned. + */ +- (void) testLazyVectorIndexNotAutoUpdatedChangedDocs { + NSError* error; + CBLCollection* wordsCollection = [_db collectionWithName: @"words" scope: nil error: &error]; + CBLCollection* extWordsCollection = [_db collectionWithName: @"extwords" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([wordsCollection createIndexWithName: @"words_index" config: config error: &error]); + + // Query: + NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector)"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNil(error); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [q setParameters: parameters]; + + CBLQueryResultSet* rs = [q execute: &error]; + NSArray* results = rs.allResults; + AssertEqual(results.count, 0); + + // Update docs: + CBLDocument* extWord1 = [extWordsCollection documentWithID: @"word1" error : &error]; + CBLMutableDocument* word301 = [self createDocument: @"word301" data: [extWord1 toDictionary]]; + Assert([wordsCollection saveDocument: word301 error: &error]); + + CBLDocument* extWord3 = [extWordsCollection documentWithID: @"word3" error : &error]; + CBLMutableDocument* word1 = [[wordsCollection documentWithID: @"word1" error: &error] toMutable]; + [word1 setData: [extWord3 toDictionary]]; + Assert([wordsCollection saveDocument: word1 error: &error]); + + rs = [q execute: &error]; + results = rs.allResults; + AssertEqual(results.count, 0); +} + +/** + * 9. TestLazyVectorIndexAutoUpdateDeletedDocs + * + * Description + * Test that when the lazy vector index automatically update when documents are + * deleted. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * 4. Check that the IndexUpdater is not null and IndexUpdater.count = 1. + * 5. With the IndexUpdater object: + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * - Call finish() + * 6. Create an SQL++ query: + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 7. Execute the query and check that 1 results are returned. + * 8. Check that the word gotten from the query result is the same as the word in Step 5. + * 9. Delete _default.words.word1 doc. + * 10. Execute the same query as Step again and check that 0 results are returned. + */ +- (void) testLazyVectorIndexAutoUpdateDeletedDocs { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; + AssertNotNil(indexUpdater); + AssertEqual(indexUpdater.count, 1); + + // Update Index: + NSString* word = [indexUpdater stringAtIndex: 0]; + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: 0 error: &error]; + [indexUpdater finishWithError: &error]; + AssertNil(error); + + // Query: + NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNil(error); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [q setParameters: parameters]; + + CBLQueryResultSet* rs = [q execute: &error]; + + NSDictionary* wordMap = [self toDocIDWordMap: rs]; + AssertEqual(wordMap.count, 1); + AssertNotNil(wordMap[word]); + + [collection deleteDocument:[collection documentWithID: @"word1" error:&error] error:&error]; + rs = [q execute: &error]; + AssertEqual(rs.allResults.count, 0); +} + +/** + * 10. TestLazyVectorIndexAutoUpdatePurgedDocs + * + * Description + * Test that when the lazy vector index automatically update when documents are + * purged. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * 4. Check that the IndexUpdater is not null and IndexUpdater.count = 1. + * 5. With the IndexUpdater object: + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. With the IndexUpdater object, call finish() + * 7. Create an SQL++ query: + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 8. Execute the query and check that 1 results are returned. + * 9. Check that the word gotten from the query result is the same as the word in Step 5. + * 10. Purge _default.words.word1 doc. + * 11. Execute the same query as Step again and check that 0 results are returned. + */ +- (void) testLazyVectorIndexAutoUpdatePurgedDocs { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; + AssertNotNil(indexUpdater); + AssertEqual(indexUpdater.count, 1); + + // Update Index: + NSString* word = [indexUpdater stringAtIndex: 0]; + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: 0 error: &error]; + [indexUpdater finishWithError: &error]; + AssertNil(error); + + // Query: + NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNil(error); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [q setParameters: parameters]; + + CBLQueryResultSet* rs = [q execute: &error]; + + NSDictionary* wordMap = [self toDocIDWordMap: rs]; + AssertEqual(wordMap.count, 1); + AssertNotNil(wordMap[word]); + + [collection purgeDocumentWithID: @"word1" error: &error]; + rs = [q execute: &error]; + AssertEqual(rs.allResults.count, 0); +} + +/** + * 11. TestIndexUpdaterBeginUpdateOnNonVectorIndex + * + * Description + * Test that a CouchbaseLiteException is thrown when calling beginUpdate on + * a non vector index. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create a value index named "value_index" in the default collection with the + * expression as "value". + * 3. Get a QueryIndex object from the default collection with the name as + * "value_index". + * 4. Call beginUpdate() with limit 10 on the QueryIndex object. + * 5. Check that a CouchbaseLiteException with the code Unsupported is thrown. + */ +- (void) testIndexUpdaterBeginUpdateOnNonVectorIndex { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + AssertNil(error); + + CBLValueIndexItem* item = [CBLValueIndexItem expression: + [CBLQueryExpression property: @"value"]]; + CBLValueIndex* vIndex = [CBLIndexBuilder valueIndexWithItems: @[item]]; + [defaultCollection createIndex: vIndex name: @"value_index" error: &error]; + + AssertNil(error); + + CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"value_index" error: &error]; + + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [qIndex beginUpdateWithLimit: 10 error: err] != nil; + }]; +} + +/** + * 12. TestIndexUpdaterBeginUpdateOnNonLazyVectorIndex + * + * Description + * Test that a CouchbaseLiteException is thrown when calling beginUpdate + * on a non lazy vector index. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * 3. Get a QueryIndex object from the words collection with the name as + * "words_index". + * 4. Call beginUpdate() with limit 10 on the QueryIndex object. + * 5. Check that a CouchbaseLiteException with the code Unsupported is thrown. + */ +- (void) testIndexUpdaterBeginUpdateOnNonLazyVectorIndex { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [qIndex beginUpdateWithLimit: 10 error: err] != nil; + }]; +} + +/** + * 13. TestIndexUpdaterBeginUpdateWithZeroLimit + * + * Description + * Test that an InvalidArgument exception is returned when calling beginUpdate + * with zero limit. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words collec + * "words_index". + * 4. Call beginUpdate() with limit 0 on the QueryIndex object. + * 5. Check that an InvalidArgumentException is thrown. + */ +- (void) testIndexUpdaterBeginUpdateWithZeroLimit { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in: ^BOOL(NSError** err) { + return [qIndex beginUpdateWithLimit: 0 error: err] != nil; + }]; +} + +/** + * 14. TestIndexUpdaterBeginUpdateOnLazyVectorIndex + * + * Description + * Test that calling beginUpdate on a lazy vector index returns an IndexUpdater. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 on the QueryIndex object. + * 5. Check that the returned IndexUpdater is not null. + * 6. Check that the IndexUpdater.count is 10. + */ +- (void) testIndexUpdaterBeginUpdateOnLazyVectorIndex { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; + AssertNotNil(indexUpdater); + AssertEqual(indexUpdater.count, 10); +} + +/** + * 15. TestIndexUpdaterGettingValues + * + * Description + * Test all type getters and toArary() from the Array interface. The test + * may be divided this test into multiple tests per type getter as appropriate. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create the followings documents: + * - doc-0 : { "value": "a string" } + * - doc-1 : { "value": 100 } + * - doc-2 : { "value": 20.8 } + * - doc-3 : { "value": true } + * - doc-4 : { "value": false } + * - doc-5 : { "value": Date("2024-05-10T00:00:00.000Z") } + * - doc-6 : { "value": Blob(Data("I'm Bob")) } + * - doc-7 : { "value": {"name": "Bob"} } + * - doc-8 : { "value": ["one", "two", "three"] } + * - doc-9 : { "value": null } + * 3. Create a vector index named "vector_index" in the default collection. + * - expression: "value" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 4. Get a QueryIndex object from the default collection with the name as + * "vector_index". + * 5. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 6. Check that the IndexUpdater.count is 10. + * 7. Get string value from each index and check the followings: + * - getString(0) : value == "a string" + * - getString(1) : value == null + * - getString(2) : value == null + * - getString(3) : value == null + * - getString(4) : value == null + * - getString(5) : value == "2024-05-10T00:00:00.000Z" + * - getString(6) : value == null + * - getString(7) : value == null + * - getString(8) : value == null + * - getString(9) : value == null + * 8. Get integer value from each index and check the followings: + * - getInt(0) : value == 0 + * - getInt(1) : value == 100 + * - getInt(2) : value == 20 + * - getInt(3) : value == 1 + * - getInt(4) : value == 0 + * - getInt(5) : value == 0 + * - getInt(6) : value == 0 + * - getInt(7) : value == 0 + * - getInt(8) : value == 0 + * - getInt(9) : value == 0 + * 9. Get float value from each index and check the followings: + * - getFloat(0) : value == 0.0 + * - getFloat(1) : value == 100.0 + * - getFloat(2) : value == 20.8 + * - getFloat(3) : value == 1.0 + * - getFloat(4) : value == 0.0 + * - getFloat(5) : value == 0.0 + * - getFloat(6) : value == 0.0 + * - getFloat(7) : value == 0.0 + * - getFloat(8) : value == 0.0 + * - getFloat(9) : value == 0.0 + * 10. Get double value from each index and check the followings: + * - getDouble(0) : value == 0.0 + * - getDouble(1) : value == 100.0 + * - getDouble(2) : value == 20.8 + * - getDouble(3) : value == 1.0 + * - getDouble(4) : value == 0.0 + * - getDouble(5) : value == 0.0 + * - getDouble(6) : value == 0.0 + * - getDouble(7) : value == 0.0 + * - getDouble(8) : value == 0.0 + * - getDouble(9) : value == 0.0 + * 11. Get boolean value from each index and check the followings: + * - getBoolean(0) : value == true + * - getBoolean(1) : value == true + * - getBoolean(2) : value == true + * - getBoolean(3) : value == true + * - getBoolean(4) : value == false + * - getBoolean(5) : value == true + * - getBoolean(6) : value == true + * - getBoolean(7) : value == true + * - getBoolean(8) : value == true + * - getBoolean(9) : value == false + * 12. Get date value from each index and check the followings: + * - getDate(0) : value == "2024-05-10T00:00:00.000Z" + * - getDate(1) : value == null + * - getDate(2) : value == null + * - getDate(3) : value == null + * - getDate(4) : value == null + * - getDate(5) : value == Date("2024-05-10T00:00:00.000Z") + * - getDate(6) : value == null + * - getDate(7) : value == null + * - getDate(8) : value == null + * - getDate(9) : value == null + * 13. Get blob value from each index and check the followings: + * - getBlob(0) : value == null + * - getBlob(1) : value == null + * - getBlob(2) : value == null + * - getBlob(3) : value == null + * - getBlob(4) : value == null + * - getBlob(5) : value == null + * - getBlob(6) : value == Blob(Data("I'm Bob")) + * - getBlob(7) : value == null + * - getBlob(8) : value == null + * - getBlob(9) : value == null + * 14. Get dictionary object from each index and check the followings: + * - getDictionary(0) : value == null + * - getDictionary(1) : value == null + * - getDictionary(2) : value == null + * - getDictionary(3) : value == null + * - getDictionary(4) : value == null + * - getDictionary(5) : value == null + * - getDictionary(6) : value == null + * - getDictionary(7) : value == Dictionary({"name": "Bob"}) + * - getDictionary(8) : value == null + * - getDictionary(9) : value == null + * 15. Get array object from each index and check the followings: + * - getArray(0) : value == null + * - getArray(1) : value == null + * - getArray(2) : value == null + * - getArray(3) : value == null + * - getArray(4) : value == null + * - getArray(5) : value == null + * - getArray(6) : value == null + * - getArray(7) : value == null + * - getArray(8) : value == Array(["one", "two", "three"]) + * - getArray(9) : value == null + * 16. Get value from each index and check the followings: + * - getValue(0) : value == "a string" + * - getValue(1) : value == PlatformNumber(100) + * - getValue(2) : value == PlatformNumber(20.8) + * - getValue(3) : value == PlatformBoolean(true) + * - getValue(4) : value == PlatformBoolean(false) + * - getValue(5) : value == Date("2024-05-10T00:00:00.000Z") + * - getValue(6) : value == Blob(Data("I'm Bob")) + * - getValue(7) : value == Dictionary({"name": "Bob"}) + * - getValue(8) : value == Array(["one", "two", "three"]) + * - getValue(9) : value == null + * 17. Get IndexUodater values as a platform array by calling toArray() and check + * that the array contains all values as expected. + */ +- (void) testIndexUpdaterGettingValues { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + AssertNil(error); + + CBLMutableDocument* mdoc = [self createDocument: @"doc-0"]; + [mdoc setValue: @"a string" forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-1"]; + [mdoc setValue: @(100) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-2"]; + [mdoc setValue: @(20.8) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-3"]; + [mdoc setValue: @(true) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-4"]; + [mdoc setValue: @(false) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-5"]; + [mdoc setValue: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"] forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-6"]; + NSData* content = [@"I'm Blob" dataUsingEncoding: NSUTF8StringEncoding]; + CBLBlob* blob = [[CBLBlob alloc] initWithContentType:@"text/plain" data: content]; + [mdoc setValue: blob forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-7"]; + CBLMutableDictionary* dict = [[CBLMutableDictionary alloc] init]; + [dict setValue: @"Bob" forKey: @"name"]; + [mdoc setValue: dict forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-8"]; + CBLMutableArray* array = [[CBLMutableArray alloc] init]; + [array addValue: @"one"]; + [array addValue: @"two"]; + [array addValue: @"three"]; + [mdoc setValue: array forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-9"]; + [mdoc setValue: [NSNull null] forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"value" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([defaultCollection createIndexWithName: @"vector_index" config: config error: &error]); + + CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"vector_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; + AssertEqual(indexUpdater.count, 10); + + // String getter + Assert([[indexUpdater stringAtIndex: 0] isEqual: @"a string"]); + AssertEqual([indexUpdater stringAtIndex: 1], nil); + AssertEqual([indexUpdater stringAtIndex: 2], nil); + AssertEqual([indexUpdater stringAtIndex: 3], nil); + AssertEqual([indexUpdater stringAtIndex: 4], nil); + Assert([[indexUpdater stringAtIndex: 5] isEqual: @"2024-05-10T00:00:00.000Z"]); + AssertEqual([indexUpdater stringAtIndex: 6], nil); + AssertEqual([indexUpdater stringAtIndex: 7], nil); + AssertEqual([indexUpdater stringAtIndex: 8], nil); + AssertEqual([indexUpdater stringAtIndex: 9], nil); + + // Int getter + AssertEqual([indexUpdater integerAtIndex: 0], 0); + AssertEqual([indexUpdater integerAtIndex: 1], 100); + AssertEqual([indexUpdater integerAtIndex: 2], 20); + AssertEqual([indexUpdater integerAtIndex: 3], 1); + AssertEqual([indexUpdater integerAtIndex: 4], 0); + AssertEqual([indexUpdater integerAtIndex: 5], 0); + AssertEqual([indexUpdater integerAtIndex: 6], 0); + AssertEqual([indexUpdater integerAtIndex: 7], 0); + AssertEqual([indexUpdater integerAtIndex: 8], 0); + AssertEqual([indexUpdater integerAtIndex: 9], 0); + + // Float getter + AssertEqual([indexUpdater floatAtIndex: 0], 0.0); + AssertEqual([indexUpdater floatAtIndex: 1], 100.0); + AssertEqual([indexUpdater floatAtIndex: 2], (float)20.8); + AssertEqual([indexUpdater floatAtIndex: 3], 1.0); + AssertEqual([indexUpdater floatAtIndex: 4], 0.0); + AssertEqual([indexUpdater floatAtIndex: 5], 0.0); + AssertEqual([indexUpdater floatAtIndex: 6], 0.0); + AssertEqual([indexUpdater floatAtIndex: 7], 0.0); + AssertEqual([indexUpdater floatAtIndex: 8], 0.0); + AssertEqual([indexUpdater floatAtIndex: 9], 0.0); + + // Double getter + AssertEqual([indexUpdater doubleAtIndex: 0], 0.0); + AssertEqual([indexUpdater doubleAtIndex: 1], 100.0); + AssertEqual([indexUpdater doubleAtIndex: 2], 20.8); + AssertEqual([indexUpdater doubleAtIndex: 3], 1.0); + AssertEqual([indexUpdater doubleAtIndex: 4], 0.0); + AssertEqual([indexUpdater doubleAtIndex: 5], 0.0); + AssertEqual([indexUpdater doubleAtIndex: 6], 0.0); + AssertEqual([indexUpdater doubleAtIndex: 7], 0.0); + AssertEqual([indexUpdater doubleAtIndex: 8], 0.0); + AssertEqual([indexUpdater doubleAtIndex: 9], 0.0); + + // Boolean getter + AssertEqual([indexUpdater booleanAtIndex: 0], true); + AssertEqual([indexUpdater booleanAtIndex: 1], true); + AssertEqual([indexUpdater booleanAtIndex: 2], true); + AssertEqual([indexUpdater booleanAtIndex: 3], true); + AssertEqual([indexUpdater booleanAtIndex: 4], false); + AssertEqual([indexUpdater booleanAtIndex: 5], true); + AssertEqual([indexUpdater booleanAtIndex: 6], true); + AssertEqual([indexUpdater booleanAtIndex: 7], true); + AssertEqual([indexUpdater booleanAtIndex: 8], true); + AssertEqual([indexUpdater booleanAtIndex: 9], false); + + // Date getter + AssertEqual([indexUpdater dateAtIndex: 0], nil); + AssertEqual([indexUpdater dateAtIndex: 1], nil); + AssertEqual([indexUpdater dateAtIndex: 2], nil); + AssertEqual([indexUpdater dateAtIndex: 3], nil); + AssertEqual([indexUpdater dateAtIndex: 4], nil); + Assert([[indexUpdater dateAtIndex: 5] isEqual: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"]]); + AssertEqual([indexUpdater dateAtIndex: 6], nil); + AssertEqual([indexUpdater dateAtIndex: 7], nil); + AssertEqual([indexUpdater dateAtIndex: 8], nil); + AssertEqual([indexUpdater dateAtIndex: 9], nil); + + // Blob getter + AssertEqual([indexUpdater blobAtIndex: 0], nil); + AssertEqual([indexUpdater blobAtIndex: 1], nil); + AssertEqual([indexUpdater blobAtIndex: 2], nil); + AssertEqual([indexUpdater blobAtIndex: 3], nil); + AssertEqual([indexUpdater blobAtIndex: 4], nil); + AssertEqual([indexUpdater blobAtIndex: 5], nil); + Assert([[indexUpdater blobAtIndex: 6] isEqual: blob]); + AssertEqual([indexUpdater blobAtIndex: 7], nil); + AssertEqual([indexUpdater blobAtIndex: 8], nil); + AssertEqual([indexUpdater blobAtIndex: 9], nil); + + // Dict getter + AssertEqual([indexUpdater dictionaryAtIndex: 0], nil); + AssertEqual([indexUpdater dictionaryAtIndex: 1], nil); + AssertEqual([indexUpdater dictionaryAtIndex: 2], nil); + AssertEqual([indexUpdater dictionaryAtIndex: 3], nil); + AssertEqual([indexUpdater dictionaryAtIndex: 4], nil); + AssertEqual([indexUpdater dictionaryAtIndex: 5], nil); + AssertEqual([indexUpdater dictionaryAtIndex: 6], nil); + Assert([[indexUpdater dictionaryAtIndex: 7] isEqual: dict]); + AssertEqual([indexUpdater dictionaryAtIndex: 8], nil); + AssertEqual([indexUpdater dictionaryAtIndex: 9], nil); + + // Array getter + AssertEqual([indexUpdater arrayAtIndex: 0], nil); + AssertEqual([indexUpdater arrayAtIndex: 1], nil); + AssertEqual([indexUpdater arrayAtIndex: 2], nil); + AssertEqual([indexUpdater arrayAtIndex: 3], nil); + AssertEqual([indexUpdater arrayAtIndex: 4], nil); + AssertEqual([indexUpdater arrayAtIndex: 5], nil); + AssertEqual([indexUpdater arrayAtIndex: 6], nil); + AssertEqual([indexUpdater arrayAtIndex: 7], nil); + Assert([[indexUpdater arrayAtIndex: 8] isEqual: array]); + AssertEqual([indexUpdater arrayAtIndex: 9], nil); + + // Value getter + Assert([[indexUpdater valueAtIndex: 0] isEqual: @"a string"]); + Assert([[indexUpdater valueAtIndex: 1] isEqual: @(100)]); + Assert([[indexUpdater valueAtIndex: 2] isEqual: @(20.8)]); + Assert([[indexUpdater valueAtIndex: 3] isEqual: @(true)]); + Assert([[indexUpdater valueAtIndex: 4] isEqual: @(false)]); + Assert([[indexUpdater valueAtIndex: 5] isEqual: @"2024-05-10T00:00:00.000Z"]); + Assert([[indexUpdater valueAtIndex: 6] isEqual: blob]); + Assert([[indexUpdater valueAtIndex: 7] isEqual: dict]); + Assert([[indexUpdater valueAtIndex: 8] isEqual: array]); + Assert([[indexUpdater valueAtIndex: 9] isEqual: [NSNull null]]); + + NSArray* trueArray = @[@"a string", @(100), @(20.8), @(true), @(false), @"2024-05-10T00:00:00.000Z", blob, [dict toDictionary], [array toArray], [NSNull null]]; + NSArray* updaterArray = [indexUpdater toArray]; + for (NSUInteger i = 0; i < trueArray.count; i++) { + NSLog(@"Item: %@", updaterArray[i]); + AssertEqualObjects(updaterArray[i], trueArray[i]); + } +} + +/** + * 17. TestIndexUpdaterSetFloatArrayVectors + * + * Description + * Test that setting float array vectors works as expected. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 5. With the IndexUpdater object, for each index from 0 to 9. + * - Get the word string from the IndexUpdater and store the word string in a set for verifying + * the vector search result. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. With the IndexUpdater object, call finish() + * 7. Execute a vector search query. + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 8. Check that there are 10 words returned. + * 9. Check that the word is in the word set from the step 5. + */ + +- (void) testIndexUpdaterSetFloatArrayVectors { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; + AssertNotNil(indexUpdater); + AssertEqual(indexUpdater.count, 10); + + // Update Index: + NSMutableArray* indexedWords = [NSMutableArray array]; + for(NSUInteger i = 0; i < indexUpdater.count; i++) { + NSString* word = [indexUpdater stringAtIndex: i]; + [indexedWords addObject: word]; + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: i error: &error]; + } + [indexUpdater finishWithError: &error]; + AssertNil(error); + + // Query: + NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNil(error); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [q setParameters: parameters]; + + CBLQueryResultSet* rs = [q execute: &error]; + + NSDictionary* wordMap = [self toDocIDWordMap: rs]; + AssertEqual(wordMap.count, 10); + for(NSString* word in indexedWords) { + Assert(wordMap[word]); + } +} + +/** + * 20. TestIndexUpdaterSetInvalidVectorDimensions + * + * Description + * Test thta the vector with the invalid dimenions different from the dimensions + * set to the configuration will not be included in the index. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * 5. With the IndexUpdater object, call setVector() with a float array as [1.0] + * 6. Check that the setVector throws CouchbaseLiteException with the InvalidParameter error. + */ +// CBL-5814 +- (void) _testIndexUpdaterSetInvalidVectorDimensions { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; + AssertNotNil(indexUpdater); + AssertEqual(indexUpdater.count, 1); + + [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in: ^BOOL(NSError** err) { + return [indexUpdater setVector: @[@1.0] atIndex: 0 error: err]; + }]; +} + +/** + * 21. TestIndexUpdaterSkipVectors + * + * Description + * Test that skipping vectors works as expected. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 5. With the IndexUpdater object, for each index from 0 - 9. + * - Get the word string from the IndexUpdater. + * - If index % 2 == 0, + * - Store the word string in a skipped word set for verifying the + * skipped words later. + * - Call skipVector at the index. + * - If index % 2 != 0, + * - Store the word string in a indexed word set for verifying the + * vector search result. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. With the IndexUpdater object, call finish() + * 7. Execute a vector search query. + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 8. Check that there are 5 words returned. + * 9. Check that the word is in the indexed word set from the step 5. + * 10. Call beginUpdate() with limit 5 to get an IndexUpdater object. + * 11. With the IndexUpdater object, for each index from 0 - 4. + * - Get the word string from the dictionary for the key named "word". + * - Check that the word is in the skipped word set from the step 5. + */ +// CBL-5842 +- (void) _testIndexUpdaterSkipVectors{ + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; + + AssertNotNil(indexUpdater); + AssertEqual(indexUpdater.count, 10); + + // Update Index: + NSMutableArray* skippedWords = [NSMutableArray array]; + NSMutableArray* indexedWords = [NSMutableArray array]; + for(NSUInteger i = 0; i < indexUpdater.count; i++) { + NSString* word = [indexUpdater stringAtIndex: i]; + if (i % 2 == 0) { + [skippedWords addObject: word]; + [indexUpdater skipVectorAtIndex: i]; + } else { + [indexedWords addObject: word]; + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: i error: &error]; + } + } + [indexUpdater finishWithError: &error]; + AssertNil(error); + AssertEqual(skippedWords.count, 5); + AssertEqual(indexedWords.count, 5); + + // Query: + NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNil(error); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [q setParameters: parameters]; + + CBLQueryResultSet* rs = [q execute: &error]; + + NSDictionary* wordMap = [self toDocIDWordMap: rs]; + AssertEqual(wordMap.count, 5); + for(NSString* word in indexedWords) { + Assert(wordMap[word]); + } + + // Update index for the skipped words: + indexUpdater = [qIndex beginUpdateWithLimit: 5 error: &error]; + AssertEqual(indexUpdater.count, 5); + for(NSUInteger i = 0; i < indexUpdater.count; i++) { + NSString* word = [indexUpdater stringAtIndex: i]; + Assert([skippedWords containsObject: word]); + } +} + +/** + * 22. TestIndexUpdaterFinishWithIncompletedUpdate + * + * Description + * Test that a CouchbaseLiteException is thrown when calling finish() on + * an IndexUpdater that has incomplete updated. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 2 to get an IndexUpdater object. + * 5. With the IndexUpdater object, call finish(). + * 6. Check that a CouchbaseLiteException with code UnsupportedOperation is thrown. + * 7. For the index 0, + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 8. With the IndexUpdater object, call finish(). + * 9. Check that a CouchbaseLiteException with code UnsupportedOperation is thrown. + */ +- (void) testIndexUpdaterFinishWithIncompletedUpdate { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 2 error: &error]; + AssertNotNil(indexUpdater); + + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [indexUpdater finishWithError: err]; + }]; + + NSString* word = [indexUpdater stringAtIndex: 0]; + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: 0 error: &error]; + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [indexUpdater finishWithError: err]; + }]; +} + +/** + * 23. TestIndexUpdaterCaughtUp + * + * Description + * Test that when the lazy vector index is caught up, calling beginUpdate() to + * get an IndexUpdater will return null. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 100 to get an IndexUpdater object. + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 4. Repeat Step 3 two more times. + * 5. Call beginUpdate() with limit 100 to get an IndexUpdater object. + * 6. Check that the returned IndexUpdater is null. + */ +- (void) testIndexUpdaterCaughtUp { + NSError* error; + CBLIndexUpdater* indexUpdater; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + + // Update index 3 times: + for(NSUInteger i = 0; i < 3; i++) { + indexUpdater = [qIndex beginUpdateWithLimit: 100 error: &error]; + AssertNotNil(indexUpdater); + + for(NSUInteger j = 0; j < indexUpdater.count; j++) { + NSString* word = [indexUpdater stringAtIndex: j]; + NSLog(@"%@", word); + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: j error: &error]; + } + [indexUpdater finishWithError: &error]; + } + + indexUpdater = [qIndex beginUpdateWithLimit: 100 error: &error]; + AssertNil(indexUpdater); +} + +/** + * 24. TestNonFinishedIndexUpdaterNotUpdateIndex + * + * Description + * Test that the index updater can be released without calling finish(), + * and the released non-finished index updater doesn't update the index. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 5. With the IndexUpdater object, for each index from 0 - 9. + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. Release or close the index updater object. + * 7. Execute a vector search query. + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 8. Check that there are 0 words returned. + */ + +- (void) testNonFinishedIndexUpdaterNotUpdateIndex { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; + + // Update index: + for(NSUInteger i = 0; i < indexUpdater.count; i++) { + NSString* word = [indexUpdater stringAtIndex: i]; + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: i error: &error]; + } + + // "Release" CBLIndexUpdater + indexUpdater = nil; + + // Query: + NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNil(error); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [q setParameters: parameters]; + + CBLQueryResultSet* rs = [q execute: &error]; + AssertEqual(rs.allObjects.count, 0); +} + +/** + * 25. TestIndexUpdaterIndexOutOfBounds + * + * Description + * Test that when using getter, setter, and skip function with the index that + * is out of bounds, an IndexOutOfBounds or InvalidArgument exception + * is throws. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create the followings documents: + * - doc-0 : { "value": "a string" } + * 3. Create a vector index named "vector_index" in the default collection. + * - expression: "value" + * - dimensions: 3 + * - centroids : 8 + * - isLazy : true + * 4. Get a QueryIndex object from the default collection with the name as + * "vector_index". + * 5. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 6. Check that the IndexUpdater.count is 1. + * 7. Call each getter function with index = -1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 8. Call each getter function with index = 1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 9. Call setVector() function with a vector = [1.0, 2.0, 3.0] and index = -1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 10. Call setVector() function with a vector = [1.0, 2.0, 3.0] and index = 1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 9. Call skipVector() function with index = -1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 10. Call skipVector() function with index = 1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + */ +- (void) testIndexUpdaterIndexOutOfBounds { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + AssertNil(error); + + CBLMutableDocument* mdoc = [self createDocument: @"doc-0"]; + [mdoc setValue: @"a string" forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"value" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([defaultCollection createIndexWithName: @"vector_index" config: config error: &error]); + CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"vector_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; + AssertEqual(indexUpdater.count, 1); + + for(NSNumber* index in @[@-1, @1]) { + + // This is in line with ArrayProtocol, throws RangeException + [self expectException: @"NSRangeException" in:^{ + [indexUpdater valueAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater stringAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater numberAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater integerAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater doubleAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater floatAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater longLongAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater dateAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater arrayAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater dictionaryAtIndex: [index unsignedIntegerValue]]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [indexUpdater booleanAtIndex: [index unsignedIntegerValue]]; + }]; + + NSArray* array = @[@1.0, @2.0, @3.0]; + [self expectException: @"NSInvalidArgumentException" in:^{ + NSError* outError; + [indexUpdater setVector: array atIndex: [index unsignedIntegerValue] error: &outError]; + + [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in: ^BOOL(NSError** err) { + return [indexUpdater setVector: array atIndex: [index unsignedIntegerValue] error: err]; + }]; + }]; + + [self expectException: @"NSInvalidArgumentException" in:^{ + [indexUpdater skipVectorAtIndex: [index unsignedIntegerValue]]; + }]; + } +} + +/** + * 26. TestIndexUpdaterCallFinishTwice + * + * Description + * Test that when calling IndexUpdater's finish() after it was finished, + * a CuchbaseLiteException is thrown. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index.. + * 8. Call finish() and check that the finish() is successfully called. + * 9. Call finish() again and check that a CouchbaseLiteException with the code Unsupported is thrown. + */ +// CBL-5843 +- (void) _testIndexUpdaterCallFinishTwice { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" + dimensions: 300 + centroids: 8]; + config.isLazy = true; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; + CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; + + // Update index: + NSString* word = [indexUpdater stringAtIndex: 0]; + NSArray* vector = [self vectorArrayForWord: word collection: collection]; + [indexUpdater setVector: vector atIndex: 0 error: &error]; + [indexUpdater finishWithError: &error]; + AssertNotNil(error); + + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [indexUpdater finishWithError: err]; + }]; +} + @end diff --git a/Tests/Extensions/version.txt b/Tests/Extensions/version.txt index 4d63fcbff..5d46c9b2a 100644 --- a/Tests/Extensions/version.txt +++ b/Tests/Extensions/version.txt @@ -1 +1 @@ -1.0.0-42 +1.0.0-43 diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index fd3224e9f..8f520c50e 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit fd3224e9fdffbb645f9d7c50a990f3feba8e921c +Subproject commit 8f520c50e81efe4123971b484a27df7c35c1b134 From a38b8794c1487889d7edc7a9ec2b333eaee7faf9 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 14 Jun 2024 22:34:58 +0100 Subject: [PATCH 19/49] Change PR Validation (#3293) * build for test only in Actions * build and test for both macOS and iOS in Jenkins --- .github/workflows/xcodebuild-ios.yml | 10 ++-------- .github/workflows/xcodebuild-mac.yml | 11 ++--------- Jenkinsfile | 2 +- ...ll_request_build.sh => pull_request_build_test.sh} | 4 ++-- 4 files changed, 7 insertions(+), 20 deletions(-) rename Scripts/{pull_request_build.sh => pull_request_build_test.sh} (78%) diff --git a/.github/workflows/xcodebuild-ios.yml b/.github/workflows/xcodebuild-ios.yml index d3cff20c8..b70afb7b8 100644 --- a/.github/workflows/xcodebuild-ios.yml +++ b/.github/workflows/xcodebuild-ios.yml @@ -12,7 +12,7 @@ on: jobs: build: - name: build-and-test + name: build runs-on: macOS-latest strategy: matrix: @@ -32,10 +32,4 @@ jobs: SCHEME: ${{ matrix.scheme }} run: | DEVICE=$(cat device) - xcodebuild build-for-testing -scheme "${SCHEME}" -project CouchbaseLite.xcodeproj -destination "platform=iOS Simulator,name=${DEVICE}" - - name: "Test" - env: - SCHEME: ${{ matrix.scheme }} - run: | - DEVICE=$(cat device) - xcodebuild test-without-building -scheme "${SCHEME}" -project CouchbaseLite.xcodeproj -destination "platform=iOS Simulator,name=${DEVICE}" + xcodebuild build-for-testing -scheme "${SCHEME}" -project CouchbaseLite.xcodeproj -destination "platform=iOS Simulator,name=${DEVICE}" \ No newline at end of file diff --git a/.github/workflows/xcodebuild-mac.yml b/.github/workflows/xcodebuild-mac.yml index 4fe7f0446..62cec9426 100644 --- a/.github/workflows/xcodebuild-mac.yml +++ b/.github/workflows/xcodebuild-mac.yml @@ -12,7 +12,7 @@ on: jobs: build: - name: build-and-test + name: build runs-on: macOS-latest strategy: matrix: @@ -30,11 +30,4 @@ jobs: PLATFORM: ${{ matrix.platform }} ARCH: ${{ matrix.arch }} run: | - xcodebuild build-for-testing -scheme "${SCHEME}" -project CouchbaseLite.xcodeproj -destination "${PLATFORM},arch=${ARCH}" - - name: "Test" - env: - SCHEME: ${{ matrix.scheme }} - PLATFORM: ${{ matrix.platform }} - ARCH: ${{ matrix.arch }} - run: | - xcodebuild test-without-building -scheme "${SCHEME}" -project CouchbaseLite.xcodeproj -destination "${PLATFORM},arch=${ARCH}" + xcodebuild build-for-testing -scheme "${SCHEME}" -project CouchbaseLite.xcodeproj -destination "${PLATFORM},arch=${ARCH}" \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 4d823e8c1..5a456b194 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -51,7 +51,7 @@ pipeline { stage('Build'){ steps { sh """ - ./couchbase-lite-ios/Scripts/pull_request_build.sh + ./couchbase-lite-ios/Scripts/pull_request_build_test.sh """ } } diff --git a/Scripts/pull_request_build.sh b/Scripts/pull_request_build_test.sh similarity index 78% rename from Scripts/pull_request_build.sh rename to Scripts/pull_request_build_test.sh index b58114453..8c7859e20 100755 --- a/Scripts/pull_request_build.sh +++ b/Scripts/pull_request_build_test.sh @@ -6,11 +6,11 @@ if [ "$1" = "-enterprise" ] cd couchbase-lite-ios fi -SCHEMES_MACOS=("CBL_EE_ObjC" "CBL_EE_Swift") +SCHEMES_MACOS=("CBL_EE_ObjC_Tests" "CBL_EE_Swift_Tests") for SCHEMES_MACOS in "${SCHEMES_MACOS[@]}" do - xcodebuild build -project CouchbaseLite.xcodeproj -scheme "$SCHEMES_MACOS" -destination "platform=macOS" + xcodebuild test -project CouchbaseLite.xcodeproj -scheme "$SCHEMES_MACOS" -destination "platform=macOS" done TEST_SIMULATOR=$(xcrun xctrace list devices 2>&1 | grep -oE 'iPhone.*?[^\(]+' | head -1 | sed 's/Simulator//g' | awk '{$1=$1;print}') From affb1b35a78f979d15b575ff52a5d07d01e4fb1e Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Fri, 21 Jun 2024 21:44:32 -0700 Subject: [PATCH 20/49] CBL-5811 : Support Vector Dimension to 4096 and Vector in Base64 String (#3294) * Updated test 3 and added test 27. * The implementation update is in the EE repo. --- Objective-C/Tests/VectorSearchTest.m | 66 ++++++++++++++++++++++++++-- Swift/Tests/VectorSearchTest.swift | 53 ++++++++++++++++++++-- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 9ff3fa59c..c9fdf7bb9 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -25,6 +25,8 @@ #define kDinnerVector @[@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] +#define kLunchVectorBase64 @"4OYevd8eyDxJGj69HCKOvoCJYTzQCJs9xhDbPp1Y6r2OTEm/ZKz1vtRbwL1Ik8I9+RQFPpyGBD69OEI9ul+evZD71L2nI4y8uTINPnVN+702+c4+8zToPEoGKj6xEqi93vPFvQDdK71Z6yC+yPT1PqXtQD99ENY+xnh+PpBEOD6aIUi+eVezvg24fj0YAJ++46c4vfVFOr57sWU+A+lqPdFq3T1ZJg6+Ok6yvs1/Cr5blju+ITa9vAFxlj1+8h4+c7UePe6fUL6OaDu+wR5IvnGmxj7eR2O+fYrsPf8kw73IOfq8YOJtvAxBMj0g99O8+toTPr0v8r2I4mK+Yxd1PTGxhbzu3aS9zeJEPqKy0Ty2cOy9YqgQPL7af703wFK9965hvOM0pz2VuAc+RIyTu4nxi73pigA9RCjpvVTOFj6zPIC+HTsrvrcpTz4vXzS6ArPxvM+VNL3hJgk+9pM7vtP1jL51sao8q4oJPonfBDxkAiC9XvJUPWiWTD1Kwbe+4KHOvUQmjjypsrS6i4MJPjRnWz0g8E4+Ad3IvVsKMT5O7Qw9X4tFPbpriT1TYme8uw5uvqBar72DLEa+vgAvvkHVs74kKk2+gNkOvZkV57zBfcC+/WM7PrKQQb4+adC9ftEXPmKYRz47RKM9+4mbPZZ76zs4LZq+0gIXPgNoxL26tT09rGFdvPdQqDwi/Y8939OLvYVTQr7J8hK+ljyeveMZsL5xeGi8sppcPfezjT11QuU9cvRpPSoby7yIZ3U9FUPXPd/y1z2xBhu9CfRyvbjXR72xLjk+9rkLvrdWJD2u+Iy9TtM/vlc0Ez4E1ju9XtcrPP+4Cr5ymDu+DfEAPswpP770tKm+3u07vsXxXb19zcC8MQ/APX507T2e7Ei+XYKGPiQ6SD0MORK+Lk4NP1zuHTzrAKW+Eu2WvSGPRj6fL7g9IdSgPkNyojxUSPi95uGqvJugrj0Bqbc9x1eVPk8qh74NlYk+07gZPVqt271XR2E+bMxmOyw0JD1Lg2Y+h+GDvRpuj70YCss890HtPdFwMz7oo7I+RpgXv4/lkz54b+Y8l6yOPdbWYj3H+4G+Q4wXvsXhyD0ayts9XIXBPndXLj34Q1I+0zfQu5pblj66UKa9dSWqvRl1xb04RQK9HsA6PrH2rD2r8wC+XQQPPlSirDwC3zU+K7Z4vUfVML4xHyY92TguPigvMj2emD8+q3AXPsSHWz4Cq5+9P/o7PveDcD095w++4fc9vvE81j17lt09AY7CvHD/Nz7FdCe+t7z4PDJPZD4Qsce9mdwZPtvzDj60sz6+ETvUPTLZ970Gauu83dW7PZZPCj51tCc+yMYtPYrmSjyUcpE+GCDgPf1tGr7aODg+ESYGPmu52T070vi9kW0vvaiwWj6JgQ6+hoehPVygk77JeOg8yCI+PtSnpD2I6w0+z3IFPRUoLD7boxM+XJYbviPzNrxBSBs+XO+WPpkuH74N9+m9tds9PiCinT6BaZ2+tGIfvhZSTj2ZP2k+cld+PHx1Kj4uOfK9bsXHPRx8Bz5OlMg96nYOPuLAub0CeRY+KQEZPogLdT5gk7g+Z0nEPJHztT1Dc3o9" + @interface VectorSearchTest : CBLTestCase @end @@ -200,12 +202,12 @@ - (void) testVectorIndexConfigurationSettersAndGetters { * Steps * 1. Create a VectorIndexConfiguration object. * - expression: "vector" - * - dimensions: 2 and 2048 + * - dimensions: 2 and 4096 * - 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. + * 4. Change the dimensions to 1 and 4097. * 5. Check that an invalid argument exception is thrown. */ - (void) testDimensionsValidation { @@ -221,7 +223,7 @@ - (void) testDimensionsValidation { Assert([names containsObject: @"words_index_1"]); CBLVectorIndexConfiguration* config2 = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 2048 + dimensions: 4096 centroids: 20]; AssertNotNil(config2); Assert([collection createIndexWithName: @"words_index_2" config: config2 error: &error]); @@ -236,7 +238,7 @@ - (void) testDimensionsValidation { [self expectException: NSInvalidArgumentException in:^{ (void) [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 2049 + dimensions: 4097 centroids: 20]; }]; } @@ -1656,6 +1658,62 @@ - (void) testInvalidVectorMatchWithOrExpression { }]; } +/** + * 27. TestIndexVectorInBase64 + * + * Description + * Test that the vector in Base64 string can be indexed. + * + * Steps + * 1. Copy database words_db. + * 2. Get the vector value from _default.words.word49's vector property as an array of floats. + * 3. Convert the array of floats from Step 2 into binary data and then into Base64 string. + * - See "Vector in Base64 for Lunch" section for the pre-calculated base64 string + * 4. Update _default.words.word49 with "vector" = Base64 string from Step 3. + * 5. Create a vector index named "words_index" in _default.words collection. + * - expression: "vector" + * - dimensions: 300 + * - centroids : 8 + * 6. Check that the index is created without an error returned. + * 7. Create an SQL++ query: + * - SELECT meta().id, word, vector_distance(words_index) + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 20) + * 8. Execute the query and check that 20 results are returned. + * 9. Check that the result also contains doc id = word49. + */ +- (void) testIndexVectorInBase64 { + NSError* error; + CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + + CBLMutableDocument* doc = [[collection documentWithID: @"word49" error: &error] toMutable]; + [doc setString: kLunchVectorBase64 forKey: @"vector"]; + Assert([collection saveDocument: doc error: &error]); + + // Create index + CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" + dimensions: 300 + centroids: 8]; + Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + + // Query: + NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; + CBLQuery* q = [_db createQuery: sql error: &error]; + AssertNil(error); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [q setParameters: parameters]; + + NSString* explain = [q explain: &error]; + Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); + + CBLQueryResultSet* rs = [q execute: &error]; + NSDictionary* wordMap = [self toDocIDWordMap: rs]; + AssertEqual(wordMap.count, 20); + AssertNotNil(wordMap[@"word49"]); +} + /* Private methods tests */ diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 63ec50379..b12afad54 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -12,6 +12,8 @@ import XCTest 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] + let lunchVectorBase64 = "4OYevd8eyDxJGj69HCKOvoCJYTzQCJs9xhDbPp1Y6r2OTEm/ZKz1vtRbwL1Ik8I9+RQFPpyGBD69OEI9ul+evZD71L2nI4y8uTINPnVN+702+c4+8zToPEoGKj6xEqi93vPFvQDdK71Z6yC+yPT1PqXtQD99ENY+xnh+PpBEOD6aIUi+eVezvg24fj0YAJ++46c4vfVFOr57sWU+A+lqPdFq3T1ZJg6+Ok6yvs1/Cr5blju+ITa9vAFxlj1+8h4+c7UePe6fUL6OaDu+wR5IvnGmxj7eR2O+fYrsPf8kw73IOfq8YOJtvAxBMj0g99O8+toTPr0v8r2I4mK+Yxd1PTGxhbzu3aS9zeJEPqKy0Ty2cOy9YqgQPL7af703wFK9965hvOM0pz2VuAc+RIyTu4nxi73pigA9RCjpvVTOFj6zPIC+HTsrvrcpTz4vXzS6ArPxvM+VNL3hJgk+9pM7vtP1jL51sao8q4oJPonfBDxkAiC9XvJUPWiWTD1Kwbe+4KHOvUQmjjypsrS6i4MJPjRnWz0g8E4+Ad3IvVsKMT5O7Qw9X4tFPbpriT1TYme8uw5uvqBar72DLEa+vgAvvkHVs74kKk2+gNkOvZkV57zBfcC+/WM7PrKQQb4+adC9ftEXPmKYRz47RKM9+4mbPZZ76zs4LZq+0gIXPgNoxL26tT09rGFdvPdQqDwi/Y8939OLvYVTQr7J8hK+ljyeveMZsL5xeGi8sppcPfezjT11QuU9cvRpPSoby7yIZ3U9FUPXPd/y1z2xBhu9CfRyvbjXR72xLjk+9rkLvrdWJD2u+Iy9TtM/vlc0Ez4E1ju9XtcrPP+4Cr5ymDu+DfEAPswpP770tKm+3u07vsXxXb19zcC8MQ/APX507T2e7Ei+XYKGPiQ6SD0MORK+Lk4NP1zuHTzrAKW+Eu2WvSGPRj6fL7g9IdSgPkNyojxUSPi95uGqvJugrj0Bqbc9x1eVPk8qh74NlYk+07gZPVqt271XR2E+bMxmOyw0JD1Lg2Y+h+GDvRpuj70YCss890HtPdFwMz7oo7I+RpgXv4/lkz54b+Y8l6yOPdbWYj3H+4G+Q4wXvsXhyD0ayts9XIXBPndXLj34Q1I+0zfQu5pblj66UKa9dSWqvRl1xb04RQK9HsA6PrH2rD2r8wC+XQQPPlSirDwC3zU+K7Z4vUfVML4xHyY92TguPigvMj2emD8+q3AXPsSHWz4Cq5+9P/o7PveDcD095w++4fc9vvE81j17lt09AY7CvHD/Nz7FdCe+t7z4PDJPZD4Qsce9mdwZPtvzDj60sz6+ETvUPTLZ970Gauu83dW7PZZPCj51tCc+yMYtPYrmSjyUcpE+GCDgPf1tGr7aODg+ESYGPmu52T070vi9kW0vvaiwWj6JgQ6+hoehPVygk77JeOg8yCI+PtSnpD2I6w0+z3IFPRUoLD7boxM+XJYbviPzNrxBSBs+XO+WPpkuH74N9+m9tds9PiCinT6BaZ2+tGIfvhZSTj2ZP2k+cld+PHx1Kj4uOfK9bsXHPRx8Bz5OlMg96nYOPuLAub0CeRY+KQEZPogLdT5gk7g+Z0nEPJHztT1Dc3o9" + var logger: CustomLogger! override func setUpWithError() throws { @@ -130,12 +132,12 @@ class VectorSearchTest: CBLTestCase { /// Steps /// 1. Create a VectorIndexConfiguration object. /// - expression: "vector" - /// - dimensions: 2 and 2048 + /// - dimensions: 2 and 4096 /// - 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. + /// 4. Change the dimensions to 1 and 4097. /// 5. Check that an invalid argument exception is thrown. func testDimensionsValidation() throws { let collection = try db.collection(name: "words")! @@ -146,7 +148,7 @@ class VectorSearchTest: CBLTestCase { var names = try collection.indexes() XCTAssert(names.contains("words_index_1")) - let config2 = VectorIndexConfiguration(expression: "vector", dimensions: 2048, centroids: 20) + let config2 = VectorIndexConfiguration(expression: "vector", dimensions: 4096, centroids: 20) try collection.createIndex(withName: "words_index_2", config: config2) names = try collection.indexes() @@ -157,7 +159,7 @@ class VectorSearchTest: CBLTestCase { } expectExcepion(exception: .invalidArgumentException) { - _ = VectorIndexConfiguration(expression: "vector", dimensions: 2049, centroids: 20) + _ = VectorIndexConfiguration(expression: "vector", dimensions: 4097, centroids: 20) } } @@ -1403,4 +1405,47 @@ class VectorSearchTest: CBLTestCase { _ = try self.db.createQuery(sql) } } + + /// 27. TestIndexVectorInBase64 + /// Description + /// Test that the vector in Base64 string can be indexed. + /// Steps + /// 1. Copy database words_db. + /// 2. Get the vector value from _default.words.word49's vector property as an array of floats. + /// 3. Convert the array of floats from Step 2 into binary data and then into Base64 string. + /// - See "Vector in Base64 for Lunch" section for the pre-calculated base64 string + /// 4. Update _default.words.word49 with "vector" = Base64 string from Step 3. + /// 5. Create a vector index named "words_index" in _default.words collection. + /// - expression: "vector" + /// - dimensions: 300 + /// - centroids : 8 + /// 6. Check that the index is created without an error returned. + /// 7. Create an SQL++ query: + /// - SELECT meta().id, word, vector_distance(words_index) + /// FROM _default.words + /// WHERE vector_match(words_index, < dinner vector >, 20) + /// 8. Execute the query and check that 20 results are returned. + /// 9. Check that the result also contains doc id = word49. + func testIndexVectorInBase64() throws { + let collection = try db.collection(name: "words")! + + var doc = try collection.document(id: "word49")!.toMutable() + doc.setString(lunchVectorBase64, forKey: "vector") + try collection.save(document: doc) + + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try collection.createIndex(withName: "words_index", config: config) + + let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" + let parameters = Parameters() + parameters.setValue(dinnerVector, forName: "vector") + + let q = try self.db.createQuery(sql) + q.parameters = parameters + let rs = try q.execute() + + let wordMap: [String: String] = toDocIDWordMap(rs: rs) + XCTAssertEqual(wordMap.count, 20) + XCTAssertNotNil(wordMap["word49"]) + } } From fbdac561b703734512e6ee1c94b411960990a958 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Mon, 24 Jun 2024 10:52:38 -0700 Subject: [PATCH 21/49] CBL-5803 : Implement Lazy Vector Index and Test for Swift (#3295) * Implemented Lazy Vector Index and Test for Swift * Refactored VectorSearchTest in both Swift and Objective-C to reduce duplicate codes. * Updated some API docs. * The companion change commit is in the EE repo. --- CouchbaseLite.xcodeproj/project.pbxproj | 32 +- Objective-C/CBLArray.h | 6 +- Objective-C/CBLCollection.h | 11 - Objective-C/CBLCollection.mm | 4 +- Objective-C/CBLDictionary.h | 8 +- Objective-C/CBLIndexable.h | 5 + Objective-C/CBLQueryIndex.h | 18 +- Objective-C/CBLQueryIndex.mm | 4 + Objective-C/Exports/CBL.txt | 1 + Objective-C/Exports/CBL_EE.txt | 1 + Objective-C/Exports/Generated/CBL.exp | 1 + Objective-C/Exports/Generated/CBL_EE.exp | 2 + Objective-C/Tests/CollectionTest.m | 5 +- Objective-C/Tests/LazyVectorIndexTest.m | 1272 ++++++++++ Objective-C/Tests/VectorSearchTest.h | 74 + Objective-C/Tests/VectorSearchTest.m | 2479 +++----------------- Swift/ArrayObject.swift | 13 +- Swift/Collection.swift | 14 + Swift/CouchbaseLiteSwift.private.modulemap | 1 + Swift/DataConverter.swift | 10 + Swift/DictionaryObject.swift | 4 +- Swift/Indexable.swift | 4 +- Swift/QueryIndex.swift | 66 + Swift/Result.swift | 7 +- Swift/Tests/CollectionTest.swift | 3 + Swift/Tests/DocumentTest.swift | 20 + Swift/Tests/LazyVectorIndexTest.swift | 821 +++++++ Swift/Tests/VectorSearchTest.swift | 700 ++---- 28 files changed, 2945 insertions(+), 2641 deletions(-) create mode 100644 Objective-C/Tests/LazyVectorIndexTest.m create mode 100644 Objective-C/Tests/VectorSearchTest.h create mode 100644 Swift/QueryIndex.swift create mode 100644 Swift/Tests/LazyVectorIndexTest.swift diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 790f21ef0..73fe69dd7 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -374,6 +374,15 @@ 4017E46A2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; 4017E46B2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; 4017E46C2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; + 406F8DEB2C26901A000223FC /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; + 406F8DEC2C26901A000223FC /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; + 406F8DFB2C27C303000223FC /* IndexUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DF92C27C302000223FC /* IndexUpdater.swift */; }; + 406F8DFF2C27F0A9000223FC /* LazyVectorIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */; }; + 406F8E002C27F0AB000223FC /* LazyVectorIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */; }; + 40AA72952C28938D007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; + 40AA72962C28938E007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; + 40AA729D2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */; }; + 40AA729E2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */; }; 40C5FD5B2B9947B3004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 40C5FD5C2B9947B9004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; 40EF690C2B7757CF00F0CB50 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 40EF690A2B77564000F0CB50 /* PrivacyInfo.xcprivacy */; }; @@ -1735,8 +1744,6 @@ 93FD618E2020757500E7F6A1 /* CBLIndex.m in Sources */ = {isa = PBXBuildFile; fileRef = 93FD618A2020757500E7F6A1 /* CBLIndex.m */; }; AE006DE42B7BB22000884E2B /* CBLWordEmbeddingModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DD32B7B9FEF00884E2B /* CBLWordEmbeddingModel.m */; }; AE006DE52B7BB22000884E2B /* CBLWordEmbeddingModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DD32B7B9FEF00884E2B /* CBLWordEmbeddingModel.m */; }; - AE006DE92B7BB98B00884E2B /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; - AE006DEA2B7BB98B00884E2B /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; AE5803A42B99C67D001A1BE3 /* VectorSearchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */; }; AE5803A52B99C67D001A1BE3 /* VectorSearchTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */; }; AE5803D82B9B5B2A001A1BE3 /* WordEmbeddingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE5803D72B9B5B2A001A1BE3 /* WordEmbeddingModel.swift */; }; @@ -2270,11 +2277,16 @@ 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLBlockConflictResolver.m; sourceTree = ""; }; 4017E4572BED6E5400A438EE /* CBLContextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLContextManager.h; sourceTree = ""; }; 4017E4642BED6E5400A438EE /* CBLContextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLContextManager.m; sourceTree = ""; }; + 406F8DEA2C26901A000223FC /* QueryIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryIndex.swift; sourceTree = ""; }; + 406F8DF92C27C302000223FC /* IndexUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexUpdater.swift; sourceTree = ""; }; + 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyVectorIndexTest.swift; sourceTree = ""; }; 40A789282BE2C7D100CA43A1 /* CBL_EE.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL_EE.exp; sourceTree = ""; }; 40A789292BE2C7D100CA43A1 /* CBL.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL.exp; sourceTree = ""; }; 40A7892B2BE2C7D100CA43A1 /* CBL_EE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL_EE.txt; sourceTree = ""; }; 40A7892C2BE2C7D100CA43A1 /* CBL.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL.txt; sourceTree = ""; }; 40A7892D2BE2C7D100CA43A1 /* generate_exports.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = generate_exports.sh; sourceTree = ""; }; + 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LazyVectorIndexTest.m; sourceTree = ""; }; + 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VectorSearchTest.h; sourceTree = ""; }; 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLVectorIndexTypes.h; sourceTree = ""; }; 40E905462B5B6D9D00EDF483 /* CouchbaseLiteSwift.private.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = CouchbaseLiteSwift.private.modulemap; sourceTree = ""; }; 40EF68102B71891A00F0CB50 /* remove_private_module.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = remove_private_module.sh; sourceTree = ""; }; @@ -3059,6 +3071,7 @@ 934608EE247F35CC00CF2F27 /* URLEndpointListenerTest.swift */, 1A13DD3328B8809300BC1084 /* URLEndpointListenerTest+Collection.swift */, AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */, + 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */, 1AA91DC522B0356000BF0BDE /* CustomLogger.swift */, 93249D81246B99FD000A8A6E /* iOS */, 939B79241E679017009A70EF /* Info.plist */, @@ -3424,6 +3437,7 @@ children = ( 40FC1C4F2B928C1600394276 /* VectorEncoding.swift */, 40FC1C4E2B928C1600394276 /* VectorIndexConfiguration.swift */, + 406F8DF92C27C302000223FC /* IndexUpdater.swift */, ); name = Vector; sourceTree = ""; @@ -4120,7 +4134,9 @@ 1A9617F2289BF3C10037E78E /* URLEndpointListenerTest+Collection.m */, 1AA3D77122AB06E10098E16B /* CustomLogger.h */, 1AA3D77222AB06E10098E16B /* CustomLogger.m */, + 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */, AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */, + 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */, 93DECF3E200DBE5800F44953 /* Support */, 936483AA1E4431C6008D08B3 /* iOS */, 275FF5FA1E3FBD3B005F90DD /* Performance */, @@ -4284,6 +4300,7 @@ 1A84E8F4268560E600C43AF9 /* IndexConfiguration.swift */, 931DE0661F4E3EC2003EF76F /* Index.swift */, 93EB261A21DDC34C0006FB88 /* IndexBuilder.swift */, + 406F8DEA2C26901A000223FC /* QueryIndex.swift */, ); name = Index; sourceTree = ""; @@ -6096,6 +6113,7 @@ 931DE0671F4E3EC2003EF76F /* Index.swift in Sources */, 93CED8CD20488C1300E6F0A4 /* Blob.swift in Sources */, 938B36A7200745FF004485D8 /* CBLQueryResultArray.m in Sources */, + 406F8DEB2C26901A000223FC /* QueryIndex.swift in Sources */, 9384D8631FC4163D00FE89D8 /* FullTextFunction.swift in Sources */, 1A8E2FB328FF756600E141A8 /* Defaults.swift in Sources */, 9322DCF71F14671F00C4ACF7 /* LimitRouter.swift in Sources */, @@ -6236,6 +6254,7 @@ 93BB1C9E246BB2BF004FFA00 /* DatabaseTest.swift in Sources */, 93BB1C9C246BB2BB004FFA00 /* CBLTestCase.swift in Sources */, 93BB1CB8246BB2F4004FFA00 /* ReplicatorTest+CustomConflict.swift in Sources */, + 406F8E002C27F0AB000223FC /* LazyVectorIndexTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6555,6 +6574,7 @@ 9343F07C207D61AB00F19A89 /* ReplicatorConfiguration.swift in Sources */, 40FC1C012B928AB200394276 /* CBLCert.mm in Sources */, 9343F07E207D61AB00F19A89 /* CBLCollationExpression.m in Sources */, + 406F8DFB2C27C303000223FC /* IndexUpdater.swift in Sources */, 9343F07F207D61AB00F19A89 /* Index.swift in Sources */, 9343F080207D61AB00F19A89 /* Blob.swift in Sources */, 1A84E901268560E600C43AF9 /* IndexConfiguration.swift in Sources */, @@ -6609,6 +6629,7 @@ 9388CC5121C2514C005CA66D /* FileLogger.swift in Sources */, 40FC1C662B928C1600394276 /* VectorIndexConfiguration.swift in Sources */, 9343F0A3207D61AB00F19A89 /* CBLReplicator.mm in Sources */, + 406F8DEC2C26901A000223FC /* QueryIndex.swift in Sources */, 40FC1C632B928C1600394276 /* DatabaseEndpoint.swift in Sources */, 9343F0A4207D61AB00F19A89 /* CBLMutableDictionary.mm in Sources */, 9343F0A5207D61AB00F19A89 /* MutableArrayObject.swift in Sources */, @@ -6645,18 +6666,19 @@ 933F841D220BA4100093EC88 /* PredictiveQueryTest+CoreML.m in Sources */, 1AA3D78722AB07D70098E16B /* CustomLogger.m in Sources */, 9343F13B207D61EC00F19A89 /* CBLTestCase.m in Sources */, - AE006DE92B7BB98B00884E2B /* VectorSearchTest.m in Sources */, 1A4160DB22836E8C0061A567 /* ReplicatorTest+Main.m in Sources */, 930C7F9220FE4F7500C74A12 /* CBLMockConnectionErrorLogic.m in Sources */, 1AA6744B227924120018CC6D /* QueryTest+Join.m in Sources */, 934EF8312460D0770053A47C /* TLSIdentityTest.m in Sources */, 9343F13C207D61EC00F19A89 /* DatabaseTest.m in Sources */, 1ABA63B028813A8C005835E7 /* ReplicatorTest+Collection.m in Sources */, + 40AA72962C28938E007FB1E0 /* VectorSearchTest.m in Sources */, 1AA6744A227924120018CC6D /* QueryTest+Meta.m in Sources */, AE006DE42B7BB22000884E2B /* CBLWordEmbeddingModel.m in Sources */, 1A621D7A2887DCFD0017F905 /* QueryTest+Collection.m in Sources */, 93EB25CC21CDD12A0006FB88 /* PredictiveQueryTest.m in Sources */, 1AF555D422948BD90077DF6D /* QueryTest+Main.m in Sources */, + 40AA729D2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */, 9343F13D207D61EC00F19A89 /* MigrationTest.m in Sources */, 9343F13E207D61EC00F19A89 /* DocumentTest.m in Sources */, 9343F140207D61EC00F19A89 /* DictionaryTest.m in Sources */, @@ -6699,6 +6721,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 40AA72952C28938D007FB1E0 /* VectorSearchTest.m in Sources */, 9343F173207D633300F19A89 /* ReplicatorTest.m in Sources */, 1AF555D622948BE00077DF6D /* QueryTest+Main.m in Sources */, 1ABA63B128813A8D005835E7 /* ReplicatorTest+Collection.m in Sources */, @@ -6708,8 +6731,8 @@ 9343F175207D633300F19A89 /* DatabaseTest.m in Sources */, 1AA6744C227924130018CC6D /* QueryTest+Meta.m in Sources */, 934EF8322460D07B0053A47C /* TLSIdentityTest.m in Sources */, - AE006DEA2B7BB98B00884E2B /* VectorSearchTest.m in Sources */, 9369A6AF207DD105009B5B83 /* DatabaseEncryptionTest.m in Sources */, + 40AA729E2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */, 9343F176207D633300F19A89 /* DocumentTest.m in Sources */, 1A13DD4228B882A800BC1084 /* URLEndpointListenerTest+Main.m in Sources */, 9388CBAE21BD9187005CA66D /* DocumentExpirationTest.m in Sources */, @@ -6771,6 +6794,7 @@ 9343F198207D636300F19A89 /* CBLTestCase.swift in Sources */, 93C50EB021BDFC7B00C7E980 /* DocumentExpirationTest.swift in Sources */, 9369A6B7207DEB60009B5B83 /* DatabaseEncryptionTest.swift in Sources */, + 406F8DFF2C27F0A9000223FC /* LazyVectorIndexTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Objective-C/CBLArray.h b/Objective-C/CBLArray.h index a96c0c45e..3d5328cf2 100644 --- a/Objective-C/CBLArray.h +++ b/Objective-C/CBLArray.h @@ -35,11 +35,11 @@ NS_ASSUME_NONNULL_BEGIN /* Gets value at the given index. The object types are CBLBlob, - CBLArray, CBLDictionary, NSNumber, or NSString based on the underlying - data type; or nil if the value is nil. + CBLArray, CBLDictionary, NSNumber, NSString, or NSNull based on + the underlying data type. @param index The index. - @return The object or nil. + @return The value or nil. */ - (nullable id) valueAtIndex: (NSUInteger)index; diff --git a/Objective-C/CBLCollection.h b/Objective-C/CBLCollection.h index 9ee40310d..9f633d035 100644 --- a/Objective-C/CBLCollection.h +++ b/Objective-C/CBLCollection.h @@ -269,17 +269,6 @@ extern NSString* const kCBLDefaultCollectionName; queue: (nullable dispatch_queue_t)queue listener: (void (^)(CBLDocumentChange*))listener; - -/** - Get a query index object by name. - - @param name The index name. - @param error On return, the error if any. - @return CBLQueryIndex object if index exists. If not, it will return nil. - */ -- (nullable CBLQueryIndex*) indexWithName: (NSString*)name - error: (NSError**)error; - #pragma mark - /** Not available */ diff --git a/Objective-C/CBLCollection.mm b/Objective-C/CBLCollection.mm index c73240594..e18dbcced 100644 --- a/Objective-C/CBLCollection.mm +++ b/Objective-C/CBLCollection.mm @@ -966,7 +966,9 @@ - (nullable CBLQueryIndex*) indexWithName: (nonnull NSString*)name C4Index* c4index = c4coll_getIndex(_c4col, iName, &c4err); if (!c4index) { if (c4err.code != 0){ - convertError(c4err, error); + if (c4err.domain != LiteCoreDomain || c4err.code != kC4ErrorMissingIndex) { + convertError(c4err, error); + } } return nil; } diff --git a/Objective-C/CBLDictionary.h b/Objective-C/CBLDictionary.h index 212e24f75..30e9dc726 100644 --- a/Objective-C/CBLDictionary.h +++ b/Objective-C/CBLDictionary.h @@ -43,12 +43,12 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Type Setters /** - Gets a property's value. The object types are CBLBlob, CBLArray, - CBLDictionary, NSNumber, or NSString based on the underlying data type; or nil if the - property value is NSNull or the property doesn't exist. + Gets a property's value. The object types are CBLBlob, CBLArray, CBLDictionary, + NSNumber, NSString, or NSNull based on the underlying data type; or nil if the + the property doesn't exist. @param key The key. - @return The object value or nil. + @return The value or nil. */ - (nullable id) valueForKey: (NSString*)key; diff --git a/Objective-C/CBLIndexable.h b/Objective-C/CBLIndexable.h index 2f475bcef..279fc43d8 100644 --- a/Objective-C/CBLIndexable.h +++ b/Objective-C/CBLIndexable.h @@ -19,6 +19,7 @@ #import #import +#import NS_ASSUME_NONNULL_BEGIN /** The Indexable interface defines a set of functions for managing the query indexes. */ @@ -38,6 +39,10 @@ NS_ASSUME_NONNULL_BEGIN /** Delete an index by name. */ - (BOOL) deleteIndexWithName: (NSString*)name error: (NSError**)error; +/** Get an index object by name. */ +- (nullable CBLQueryIndex*) indexWithName: (NSString*)name + error: (NSError**)error NS_SWIFT_NOTHROW; + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/CBLQueryIndex.h b/Objective-C/CBLQueryIndex.h index 32d0f2799..d539f7114 100644 --- a/Objective-C/CBLQueryIndex.h +++ b/Objective-C/CBLQueryIndex.h @@ -25,20 +25,16 @@ #endif NS_ASSUME_NONNULL_BEGIN + /** - QueryIndex object representing an existing index in the collection. + CBLQueryIndex object representing an existing index in the collection. */ - @interface CBLQueryIndex : NSObject -/** - The collection object. - */ +/** The collection. */ @property (readonly, nonatomic) CBLCollection* collection; -/** - The index name. - */ +/** The index name. */ @property (readonly, nonatomic) NSString* name; #ifdef COUCHBASE_ENTERPRISE @@ -47,18 +43,18 @@ NS_ASSUME_NONNULL_BEGIN For updating lazy vector indexes only. Finds new or updated documents for which vectors need to be (re)computed and - return an IndexUpdater object used for setting the computed vectors for updating the index. + return a CBLIndexUpdater object used for setting the computed vectors for updating the index. The limit parameter is for setting the max number of vectors to be computed. If index is up-to-date, nil will be returned. If the index is not lazy, an error will be returned. - @param limit The limit per update.. + @param limit The limit per update. @param error On return, the error if any. @return CBLIndexUpdater object if there are updates to be done, or nil if the index is up-to-date or if an error occurred. */ - (nullable CBLIndexUpdater*) beginUpdateWithLimit: (uint64_t)limit - error: (NSError**)error; + error: (NSError**)error NS_SWIFT_NOTHROW; #endif /** Not available */ diff --git a/Objective-C/CBLQueryIndex.mm b/Objective-C/CBLQueryIndex.mm index bb915a629..54460f06e 100644 --- a/Objective-C/CBLQueryIndex.mm +++ b/Objective-C/CBLQueryIndex.mm @@ -55,6 +55,10 @@ - (void) dealloc { - (nullable CBLIndexUpdater*) beginUpdateWithLimit: (uint64_t)limit error: (NSError**)error { + if (limit == 0) { + [NSException raise: NSInvalidArgumentException format: @"limit must be > 0"]; + } + CBL_LOCK(_mutex){ C4Error c4err = {}; C4IndexUpdater* _c4updater = c4index_beginUpdate(_c4index, (size_t)limit, &c4err); diff --git a/Objective-C/Exports/CBL.txt b/Objective-C/Exports/CBL.txt index 108f7ab4b..2fe82be9a 100644 --- a/Objective-C/Exports/CBL.txt +++ b/Objective-C/Exports/CBL.txt @@ -62,6 +62,7 @@ .objc_class_name_CBLQueryFullTextExpression .objc_class_name_CBLQueryFullTextFunction .objc_class_name_CBLQueryFunction +.objc_class_name_CBLQueryIndex .objc_class_name_CBLQueryJoin .objc_class_name_CBLQueryLimit .objc_class_name_CBLQueryMeta diff --git a/Objective-C/Exports/CBL_EE.txt b/Objective-C/Exports/CBL_EE.txt index 11bba2a83..9a9001559 100644 --- a/Objective-C/Exports/CBL_EE.txt +++ b/Objective-C/Exports/CBL_EE.txt @@ -23,6 +23,7 @@ .objc_class_name_CBLClientCertificateAuthenticator .objc_class_name_CBLDatabaseEndpoint .objc_class_name_CBLEncryptionKey +.objc_class_name_CBLIndexUpdater .objc_class_name_CBLListenerCertificateAuthenticator .objc_class_name_CBLListenerPasswordAuthenticator .objc_class_name_CBLMessage diff --git a/Objective-C/Exports/Generated/CBL.exp b/Objective-C/Exports/Generated/CBL.exp index bf6b5bd12..2bd035767 100644 --- a/Objective-C/Exports/Generated/CBL.exp +++ b/Objective-C/Exports/Generated/CBL.exp @@ -43,6 +43,7 @@ .objc_class_name_CBLQueryFullTextExpression .objc_class_name_CBLQueryFullTextFunction .objc_class_name_CBLQueryFunction +.objc_class_name_CBLQueryIndex .objc_class_name_CBLQueryJoin .objc_class_name_CBLQueryLimit .objc_class_name_CBLQueryMeta diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp index ebf06e0e4..babe2e82d 100644 --- a/Objective-C/Exports/Generated/CBL_EE.exp +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -30,6 +30,7 @@ .objc_class_name_CBLIndex .objc_class_name_CBLIndexBuilder .objc_class_name_CBLIndexConfiguration +.objc_class_name_CBLIndexUpdater .objc_class_name_CBLListenerCertificateAuthenticator .objc_class_name_CBLListenerPasswordAuthenticator .objc_class_name_CBLLog @@ -57,6 +58,7 @@ .objc_class_name_CBLQueryFullTextExpression .objc_class_name_CBLQueryFullTextFunction .objc_class_name_CBLQueryFunction +.objc_class_name_CBLQueryIndex .objc_class_name_CBLQueryJoin .objc_class_name_CBLQueryLimit .objc_class_name_CBLQueryMeta diff --git a/Objective-C/Tests/CollectionTest.m b/Objective-C/Tests/CollectionTest.m index 286402e9f..01723379e 100644 --- a/Objective-C/Tests/CollectionTest.m +++ b/Objective-C/Tests/CollectionTest.m @@ -950,7 +950,10 @@ - (void) testUseInvalidCollection: (NSString*)collectionName onAction: (void (^) return [col createIndexWithName: @"index2" config: config2 error: err]; }]; - // get indexes, delete index + // get index, get indexes, delete index + [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { + return [col indexWithName: @"index1" error: err]; + }]; [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { return [col indexes: err] != nil; }]; diff --git a/Objective-C/Tests/LazyVectorIndexTest.m b/Objective-C/Tests/LazyVectorIndexTest.m new file mode 100644 index 000000000..2b0576c02 --- /dev/null +++ b/Objective-C/Tests/LazyVectorIndexTest.m @@ -0,0 +1,1272 @@ +// +// LazyVectorIndexTest.m +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#import "VectorSearchTest.h" +#import "CBLJSON.h" + +/** + * Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0002-Lazy-Vector-Index.md + * + * Test 6. TestGetIndexOnClosedDatabase is tested in CollectionTest + * Test 7. TestGetIndexOnDeletedCollection is tested in CollectionTest + */ + +#define LAZY_VECTOR_INDEX_CONFIG(E, D, C) [self lazyVectorIndexConfigWithExpression: (E) dimensions: (D) centroids: (C)] + +@interface LazyVectorIndexTest : VectorSearchTest + +@end + +@implementation LazyVectorIndexTest + +- (CBLQueryIndex*) wordsIndex { + CBLQueryIndex* index = [self.wordsCollection indexWithName: kWordsIndexName error: nil]; + AssertNotNil(index); + return index; +} + +- (CBLVectorIndexConfiguration*) lazyVectorIndexConfigWithExpression: (NSString*)expression + dimensions: (unsigned int)dimensions + centroids: (unsigned int)centroids { + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(expression, dimensions, centroids); + config.isLazy = true; + return config; +} + +- (nullable NSArray*) vectorForWord: (NSString*)word collection: (CBLCollection*)collection { + NSError* error; + NSString* sql = [NSString stringWithFormat:@"SELECT vector FROM %@ WHERE word = '%@'", collection.name, word]; + + CBLQuery* query = [self.wordDB createQuery: sql error: &error]; + AssertNotNil(query); + + CBLQueryResultSet* rs = [query execute: &error]; + AssertNotNil(rs); + + NSArray* vector; + CBLQueryResult *result = [rs nextObject]; + if (result) { + id value = [result arrayAtIndex: 0]; + if (value) { + vector = (NSArray*)value; + } + } + return vector; +} + +- (nullable NSArray*) vectorForWord: (NSString*)word { + NSArray* results = [self vectorForWord: word collection: self.wordsCollection]; + if (!results) { + results = [self vectorForWord: word collection: self.extWordsCollection]; + } + AssertNotNil(results); + return results; +} + +/** + * 1. TestIsLazyDefaultValue + * Description + * Test that isLazy property is false by default. + * Steps + * 1. Create a VectorIndexConfiguration object. + * - expression: "vector" + * - dimensions: 300 + * - centroids: 20 + * 2. Check that isLazy returns false. + */ +- (void) testIsLazyDefaultValue { + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + AssertEqual(config.isLazy, false); +} + +/** + * 2. TestIsLazyAccessor + * + * Description + * Test that isLazy getter/setter of the VectorIndexConfiguration work as expected. + * + * Steps + * 1. Create a VectorIndexConfiguration object. + * - expression: word + * - dimensions: 300 + * - centroids : 20 + * 2. Set isLazy to true + * 3. Check that isLazy returns true. + */ +- (void) testIsLazyAccessor { + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + config.isLazy = true; + AssertEqual(config.isLazy, true); +} + +/** + * 3. TestGetNonExistingIndex + * + * Description + * Test that getting non-existing index object by name returning null. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Get a QueryIndex object from the default collection with the name as + * "nonexistingindex". + * 3. Check that the result is null. + */ +- (void) testGetNonExistingIndex { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + AssertNil([defaultCollection indexWithName: @"nonexistingindex" error: &error]); + AssertEqual(error.code, 0); +} + +/** + * 4. TestGetExistingNonVectorIndex + * + * Description + * Test that getting non-existing index object by name returning an index object correctly. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create a value index named "value_index" in the default collection + * with the expression as "value". + * 3. Get a QueryIndex object from the default collection with the name as + * "value_index". + * 4. Check that the result is not null. + * 5. Check that the QueryIndex's name is "value_index". + * 6. Check that the QueryIndex's collection is the same instance that + * is used for getting the QueryIndex object. + */ +- (void) testGetExistingNonVectorIndex { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + + CBLValueIndexItem* item = [CBLValueIndexItem expression: + [CBLQueryExpression property: @"value"]]; + CBLValueIndex* vIndex = [CBLIndexBuilder valueIndexWithItems: @[item]]; + [defaultCollection createIndex: vIndex name: @"value_index" error: &error]; + AssertNil(error); + + CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"value_index" error: &error]; + AssertEqual(qIndex.name, @"value_index"); + AssertEqual(qIndex.collection, defaultCollection); +} + +/** + * 5. TestGetExistingVectorIndex + * + * Description + * Test that getting an existing index object by name returning an index object correctly. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "vector" + * - dimensions: 300 + * - centroids : 8 + * 3. Get a QueryIndex object from the words collection with the name as "words_index". + * 4. Check that the result is not null. + * 5. Check that the QueryIndex's name is "words_index". + * 6. Check that the QueryIndex's collection is the same instance that is used for + * getting the index. + */ +- (void) testGetExistingVectorIndex { + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; + + CBLQueryIndex* index = [self wordsIndex]; + AssertEqual(index.name, @"words_index"); + AssertEqual(index.collection, self.wordsCollection); +} + +/** + * 8. TestLazyVectorIndexNotAutoUpdatedChangedDocs + * + * Description + * Test that the lazy index is lazy. The index will not be automatically + * updated when the documents are created or updated. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Create an SQL++ query: + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >) + * 4. Execute the query and check that 0 results are returned. + * 5. Update the documents: + * - Create _default.words.word301 with the content from _default.extwords.word1 + * - Update _default.words.word1 with the content from _default.extwords.word3 + * 6. Execute the same query and check that 0 results are returned. + */ +- (void) testLazyVectorIndexNotAutoUpdatedChangedDocs { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: nil]; + AssertEqual(rs.allObjects.count, 0); + + // Update docs: + NSError* error; + CBLDocument* extWord1 = [self.extWordsCollection documentWithID: @"word1" error : &error]; + CBLMutableDocument* word301 = [self createDocument: @"word301" data: [extWord1 toDictionary]]; + Assert([self.wordsCollection saveDocument: word301 error: &error]); + + CBLDocument* extWord3 = [self.extWordsCollection documentWithID: @"word3" error : &error]; + CBLMutableDocument* word1 = [[self.wordsCollection documentWithID: @"word1" error: &error] toMutable]; + [word1 setData: [extWord3 toDictionary]]; + Assert([self.wordsCollection saveDocument: word1 error: &error]); + + rs = [self executeWordsQueryNoTrainingCheckWithLimit: nil]; + AssertEqual(rs.allObjects.count, 0); +} + +/** + * 9. TestLazyVectorIndexAutoUpdateDeletedDocs + * + * Description + * Test that when the lazy vector index automatically update when documents are + * deleted. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * 4. Check that the IndexUpdater is not null and IndexUpdater.count = 1. + * 5. With the IndexUpdater object: + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * - Call finish() + * 6. Create an SQL++ query: + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 7. Execute the query and check that 1 results are returned. + * 8. Check that the word gotten from the query result is the same as the word in Step 5. + * 9. Delete _default.words.word1 doc. + * 10. Execute the same query as Step again and check that 0 results are returned. + */ +- (void) testLazyVectorIndexAutoUpdateDeletedDocs { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 1 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 1); + + // Update Index: + NSString* word = [updater stringAtIndex: 0]; + NSArray* vector = [self vectorForWord: word]; + [updater setVector: vector atIndex: 0 error: &error]; + [updater finishWithError: &error]; + AssertNil(error); + + // Query: + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + AssertEqual(rs.allObjects.count, 1); + + // Delete doc and requery: + [self.wordsCollection deleteDocument: [self.wordsCollection documentWithID: @"word1" error: &error] error: &error]; + rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + AssertEqual(rs.allObjects.count, 0); +} + +/** + * 10. TestLazyVectorIndexAutoUpdatePurgedDocs + * + * Description + * Test that when the lazy vector index automatically update when documents are + * purged. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * 4. Check that the IndexUpdater is not null and IndexUpdater.count = 1. + * 5. With the IndexUpdater object: + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. With the IndexUpdater object, call finish() + * 7. Create an SQL++ query: + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 8. Execute the query and check that 1 results are returned. + * 9. Check that the word gotten from the query result is the same as the word in Step 5. + * 10. Purge _default.words.word1 doc. + * 11. Execute the same query as Step again and check that 0 results are returned. + */ +- (void) testLazyVectorIndexAutoUpdatePurgedDocs { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 1 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 1); + + // Update Index: + NSString* word = [updater stringAtIndex: 0]; + NSArray* vector = [self vectorForWord: word]; + [updater setVector: vector atIndex: 0 error: &error]; + [updater finishWithError: &error]; + AssertNil(error); + + // Query: + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + AssertEqual(rs.allObjects.count, 1); + + // Delete doc and requery: + [self.wordsCollection purgeDocumentWithID: @"word1" error: &error]; + rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + AssertEqual(rs.allObjects.count, 0); +} + +/** + * 11. TestIndexUpdaterBeginUpdateOnNonVectorIndex + * + * Description + * Test that a CouchbaseLiteException is thrown when calling beginUpdate on + * a non vector index. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create a value index named "value_index" in the default collection with the + * expression as "value". + * 3. Get a QueryIndex object from the default collection with the name as + * "value_index". + * 4. Call beginUpdate() with limit 10 on the QueryIndex object. + * 5. Check that a CouchbaseLiteException with the code Unsupported is thrown. + */ +- (void) testIndexUpdaterBeginUpdateOnNonVectorIndex { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + AssertNil(error); + + CBLValueIndexItem* item = [CBLValueIndexItem expression: + [CBLQueryExpression property: @"value"]]; + CBLValueIndex* vIndex = [CBLIndexBuilder valueIndexWithItems: @[item]]; + [defaultCollection createIndex: vIndex name: @"value_index" error: &error]; + + AssertNil(error); + + CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"value_index" error: &error]; + + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [qIndex beginUpdateWithLimit: 10 error: err] != nil; + }]; +} + +/** + * 12. TestIndexUpdaterBeginUpdateOnNonLazyVectorIndex + * + * Description + * Test that a CouchbaseLiteException is thrown when calling beginUpdate + * on a non lazy vector index. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * 3. Get a QueryIndex object from the words collection with the name as + * "words_index". + * 4. Call beginUpdate() with limit 10 on the QueryIndex object. + * 5. Check that a CouchbaseLiteException with the code Unsupported is thrown. + */ +- (void) testIndexUpdaterBeginUpdateOnNonLazyVectorIndex { + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; + + CBLQueryIndex* index = [self wordsIndex]; + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [index beginUpdateWithLimit: 10 error: err] != nil; + }]; +} + +/** + * 13. TestIndexUpdaterBeginUpdateWithZeroLimit + * + * Description + * Test that an InvalidArgument exception is returned when calling beginUpdate + * with zero limit. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words collec + * "words_index". + * 4. Call beginUpdate() with limit 0 on the QueryIndex object. + * 5. Check that an InvalidArgumentException is thrown. + */ +- (void) testIndexUpdaterBeginUpdateWithZeroLimit { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + CBLQueryIndex* index = [self wordsIndex]; + + [self expectException: @"NSInvalidArgumentException" in: ^{ + [index beginUpdateWithLimit: 0 error: nil]; + }]; +} + +/** + * 14. TestIndexUpdaterBeginUpdateOnLazyVectorIndex + * + * Description + * Test that calling beginUpdate on a lazy vector index returns an IndexUpdater. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 on the QueryIndex object. + * 5. Check that the returned IndexUpdater is not null. + * 6. Check that the IndexUpdater.count is 10. + */ +- (void) testIndexUpdaterBeginUpdateOnLazyVectorIndex { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 10 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 10); +} + +/** + * 15. TestIndexUpdaterGettingValues + * + * Description + * Test all type getters and toArary() from the Array interface. The test + * may be divided this test into multiple tests per type getter as appropriate. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create the followings documents: + * - doc-0 : { "value": "a string" } + * - doc-1 : { "value": 100 } + * - doc-2 : { "value": 20.8 } + * - doc-3 : { "value": true } + * - doc-4 : { "value": false } + * - doc-5 : { "value": Date("2024-05-10T00:00:00.000Z") } + * - doc-6 : { "value": Blob(Data("I'm Bob")) } + * - doc-7 : { "value": {"name": "Bob"} } + * - doc-8 : { "value": ["one", "two", "three"] } + * - doc-9 : { "value": null } + * 3. Create a vector index named "vector_index" in the default collection. + * - expression: "value" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 4. Get a QueryIndex object from the default collection with the name as + * "vector_index". + * 5. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 6. Check that the IndexUpdater.count is 10. + * 7. Get string value from each index and check the followings: + * - getString(0) : value == "a string" + * - getString(1) : value == null + * - getString(2) : value == null + * - getString(3) : value == null + * - getString(4) : value == null + * - getString(5) : value == "2024-05-10T00:00:00.000Z" + * - getString(6) : value == null + * - getString(7) : value == null + * - getString(8) : value == null + * - getString(9) : value == null + * 8. Get integer value from each index and check the followings: + * - getInt(0) : value == 0 + * - getInt(1) : value == 100 + * - getInt(2) : value == 20 + * - getInt(3) : value == 1 + * - getInt(4) : value == 0 + * - getInt(5) : value == 0 + * - getInt(6) : value == 0 + * - getInt(7) : value == 0 + * - getInt(8) : value == 0 + * - getInt(9) : value == 0 + * 9. Get float value from each index and check the followings: + * - getFloat(0) : value == 0.0 + * - getFloat(1) : value == 100.0 + * - getFloat(2) : value == 20.8 + * - getFloat(3) : value == 1.0 + * - getFloat(4) : value == 0.0 + * - getFloat(5) : value == 0.0 + * - getFloat(6) : value == 0.0 + * - getFloat(7) : value == 0.0 + * - getFloat(8) : value == 0.0 + * - getFloat(9) : value == 0.0 + * 10. Get double value from each index and check the followings: + * - getDouble(0) : value == 0.0 + * - getDouble(1) : value == 100.0 + * - getDouble(2) : value == 20.8 + * - getDouble(3) : value == 1.0 + * - getDouble(4) : value == 0.0 + * - getDouble(5) : value == 0.0 + * - getDouble(6) : value == 0.0 + * - getDouble(7) : value == 0.0 + * - getDouble(8) : value == 0.0 + * - getDouble(9) : value == 0.0 + * 11. Get boolean value from each index and check the followings: + * - getBoolean(0) : value == true + * - getBoolean(1) : value == true + * - getBoolean(2) : value == true + * - getBoolean(3) : value == true + * - getBoolean(4) : value == false + * - getBoolean(5) : value == true + * - getBoolean(6) : value == true + * - getBoolean(7) : value == true + * - getBoolean(8) : value == true + * - getBoolean(9) : value == false + * 12. Get date value from each index and check the followings: + * - getDate(0) : value == "2024-05-10T00:00:00.000Z" + * - getDate(1) : value == null + * - getDate(2) : value == null + * - getDate(3) : value == null + * - getDate(4) : value == null + * - getDate(5) : value == Date("2024-05-10T00:00:00.000Z") + * - getDate(6) : value == null + * - getDate(7) : value == null + * - getDate(8) : value == null + * - getDate(9) : value == null + * 13. Get blob value from each index and check the followings: + * - getBlob(0) : value == null + * - getBlob(1) : value == null + * - getBlob(2) : value == null + * - getBlob(3) : value == null + * - getBlob(4) : value == null + * - getBlob(5) : value == null + * - getBlob(6) : value == Blob(Data("I'm Bob")) + * - getBlob(7) : value == null + * - getBlob(8) : value == null + * - getBlob(9) : value == null + * 14. Get dictionary object from each index and check the followings: + * - getDictionary(0) : value == null + * - getDictionary(1) : value == null + * - getDictionary(2) : value == null + * - getDictionary(3) : value == null + * - getDictionary(4) : value == null + * - getDictionary(5) : value == null + * - getDictionary(6) : value == null + * - getDictionary(7) : value == Dictionary({"name": "Bob"}) + * - getDictionary(8) : value == null + * - getDictionary(9) : value == null + * 15. Get array object from each index and check the followings: + * - getArray(0) : value == null + * - getArray(1) : value == null + * - getArray(2) : value == null + * - getArray(3) : value == null + * - getArray(4) : value == null + * - getArray(5) : value == null + * - getArray(6) : value == null + * - getArray(7) : value == null + * - getArray(8) : value == Array(["one", "two", "three"]) + * - getArray(9) : value == null + * 16. Get value from each index and check the followings: + * - getValue(0) : value == "a string" + * - getValue(1) : value == PlatformNumber(100) + * - getValue(2) : value == PlatformNumber(20.8) + * - getValue(3) : value == PlatformBoolean(true) + * - getValue(4) : value == PlatformBoolean(false) + * - getValue(5) : value == Date("2024-05-10T00:00:00.000Z") + * - getValue(6) : value == Blob(Data("I'm Bob")) + * - getValue(7) : value == Dictionary({"name": "Bob"}) + * - getValue(8) : value == Array(["one", "two", "three"]) + * - getValue(9) : value == null + * 17. Get IndexUodater values as a platform array by calling toArray() and check + * that the array contains all values as expected. + */ +- (void) testIndexUpdaterGettingValues { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + + CBLMutableDocument* mdoc = [self createDocument: @"doc-0"]; + [mdoc setValue: @"a string" forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-1"]; + [mdoc setValue: @(100) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-2"]; + [mdoc setValue: @(20.8) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-3"]; + [mdoc setValue: @(true) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-4"]; + [mdoc setValue: @(false) forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-5"]; + [mdoc setValue: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"] forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-6"]; + NSData* content = [@"I'm Blob" dataUsingEncoding: NSUTF8StringEncoding]; + CBLBlob* blob = [[CBLBlob alloc] initWithContentType:@"text/plain" data: content]; + [mdoc setValue: blob forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-7"]; + CBLMutableDictionary* dict = [[CBLMutableDictionary alloc] init]; + [dict setValue: @"Bob" forKey: @"name"]; + [mdoc setValue: dict forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-8"]; + CBLMutableArray* array = [[CBLMutableArray alloc] init]; + [array addValue: @"one"]; + [array addValue: @"two"]; + [array addValue: @"three"]; + [mdoc setValue: array forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + mdoc = [self createDocument: @"doc-9"]; + [mdoc setValue: [NSNull null] forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + // Create index + [self createVectorIndexInCollection: defaultCollection + name: @"vector_index" + config: LAZY_VECTOR_INDEX_CONFIG(@"value", 300, 8)]; + + CBLQueryIndex* index = [defaultCollection indexWithName: @"vector_index" error: &error]; + AssertNotNil(index); + + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 10 error: &error]; + AssertEqual(updater.count, 10); + + // String getter + Assert([[updater stringAtIndex: 0] isEqual: @"a string"]); + AssertEqual([updater stringAtIndex: 1], nil); + AssertEqual([updater stringAtIndex: 2], nil); + AssertEqual([updater stringAtIndex: 3], nil); + AssertEqual([updater stringAtIndex: 4], nil); + Assert([[updater stringAtIndex: 5] isEqual: @"2024-05-10T00:00:00.000Z"]); + AssertEqual([updater stringAtIndex: 6], nil); + AssertEqual([updater stringAtIndex: 7], nil); + AssertEqual([updater stringAtIndex: 8], nil); + AssertEqual([updater stringAtIndex: 9], nil); + + // Int getter + AssertEqual([updater integerAtIndex: 0], 0); + AssertEqual([updater integerAtIndex: 1], 100); + AssertEqual([updater integerAtIndex: 2], 20); + AssertEqual([updater integerAtIndex: 3], 1); + AssertEqual([updater integerAtIndex: 4], 0); + AssertEqual([updater integerAtIndex: 5], 0); + AssertEqual([updater integerAtIndex: 6], 0); + AssertEqual([updater integerAtIndex: 7], 0); + AssertEqual([updater integerAtIndex: 8], 0); + AssertEqual([updater integerAtIndex: 9], 0); + + // Float getter + AssertEqual([updater floatAtIndex: 0], 0.0); + AssertEqual([updater floatAtIndex: 1], 100.0); + AssertEqual([updater floatAtIndex: 2], (float)20.8); + AssertEqual([updater floatAtIndex: 3], 1.0); + AssertEqual([updater floatAtIndex: 4], 0.0); + AssertEqual([updater floatAtIndex: 5], 0.0); + AssertEqual([updater floatAtIndex: 6], 0.0); + AssertEqual([updater floatAtIndex: 7], 0.0); + AssertEqual([updater floatAtIndex: 8], 0.0); + AssertEqual([updater floatAtIndex: 9], 0.0); + + // Double getter + AssertEqual([updater doubleAtIndex: 0], 0.0); + AssertEqual([updater doubleAtIndex: 1], 100.0); + AssertEqual([updater doubleAtIndex: 2], 20.8); + AssertEqual([updater doubleAtIndex: 3], 1.0); + AssertEqual([updater doubleAtIndex: 4], 0.0); + AssertEqual([updater doubleAtIndex: 5], 0.0); + AssertEqual([updater doubleAtIndex: 6], 0.0); + AssertEqual([updater doubleAtIndex: 7], 0.0); + AssertEqual([updater doubleAtIndex: 8], 0.0); + AssertEqual([updater doubleAtIndex: 9], 0.0); + + // Boolean getter + AssertEqual([updater booleanAtIndex: 0], true); + AssertEqual([updater booleanAtIndex: 1], true); + AssertEqual([updater booleanAtIndex: 2], true); + AssertEqual([updater booleanAtIndex: 3], true); + AssertEqual([updater booleanAtIndex: 4], false); + AssertEqual([updater booleanAtIndex: 5], true); + AssertEqual([updater booleanAtIndex: 6], true); + AssertEqual([updater booleanAtIndex: 7], true); + AssertEqual([updater booleanAtIndex: 8], true); + AssertEqual([updater booleanAtIndex: 9], false); + + // Date getter + AssertEqual([updater dateAtIndex: 0], nil); + AssertEqual([updater dateAtIndex: 1], nil); + AssertEqual([updater dateAtIndex: 2], nil); + AssertEqual([updater dateAtIndex: 3], nil); + AssertEqual([updater dateAtIndex: 4], nil); + Assert([[updater dateAtIndex: 5] isEqual: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"]]); + AssertEqual([updater dateAtIndex: 6], nil); + AssertEqual([updater dateAtIndex: 7], nil); + AssertEqual([updater dateAtIndex: 8], nil); + AssertEqual([updater dateAtIndex: 9], nil); + + // Blob getter + AssertEqual([updater blobAtIndex: 0], nil); + AssertEqual([updater blobAtIndex: 1], nil); + AssertEqual([updater blobAtIndex: 2], nil); + AssertEqual([updater blobAtIndex: 3], nil); + AssertEqual([updater blobAtIndex: 4], nil); + AssertEqual([updater blobAtIndex: 5], nil); + Assert([[updater blobAtIndex: 6] isEqual: blob]); + AssertEqual([updater blobAtIndex: 7], nil); + AssertEqual([updater blobAtIndex: 8], nil); + AssertEqual([updater blobAtIndex: 9], nil); + + // Dict getter + AssertEqual([updater dictionaryAtIndex: 0], nil); + AssertEqual([updater dictionaryAtIndex: 1], nil); + AssertEqual([updater dictionaryAtIndex: 2], nil); + AssertEqual([updater dictionaryAtIndex: 3], nil); + AssertEqual([updater dictionaryAtIndex: 4], nil); + AssertEqual([updater dictionaryAtIndex: 5], nil); + AssertEqual([updater dictionaryAtIndex: 6], nil); + Assert([[updater dictionaryAtIndex: 7] isEqual: dict]); + AssertEqual([updater dictionaryAtIndex: 8], nil); + AssertEqual([updater dictionaryAtIndex: 9], nil); + + // Array getter + AssertEqual([updater arrayAtIndex: 0], nil); + AssertEqual([updater arrayAtIndex: 1], nil); + AssertEqual([updater arrayAtIndex: 2], nil); + AssertEqual([updater arrayAtIndex: 3], nil); + AssertEqual([updater arrayAtIndex: 4], nil); + AssertEqual([updater arrayAtIndex: 5], nil); + AssertEqual([updater arrayAtIndex: 6], nil); + AssertEqual([updater arrayAtIndex: 7], nil); + Assert([[updater arrayAtIndex: 8] isEqual: array]); + AssertEqual([updater arrayAtIndex: 9], nil); + + // Value getter + Assert([[updater valueAtIndex: 0] isEqual: @"a string"]); + Assert([[updater valueAtIndex: 1] isEqual: @(100)]); + Assert([[updater valueAtIndex: 2] isEqual: @(20.8)]); + Assert([[updater valueAtIndex: 3] isEqual: @(true)]); + Assert([[updater valueAtIndex: 4] isEqual: @(false)]); + Assert([[updater valueAtIndex: 5] isEqual: @"2024-05-10T00:00:00.000Z"]); + Assert([[updater valueAtIndex: 6] isEqual: blob]); + Assert([[updater valueAtIndex: 7] isEqual: dict]); + Assert([[updater valueAtIndex: 8] isEqual: array]); + Assert([[updater valueAtIndex: 9] isEqual: [NSNull null]]); + + NSArray* expected = @[@"a string", @(100), @(20.8), @(true), @(false), @"2024-05-10T00:00:00.000Z", blob, + [dict toDictionary], [array toArray], [NSNull null]]; + NSArray* updaterArray = [updater toArray]; + for (NSUInteger i = 0; i < expected.count; i++) { + AssertEqualObjects(updaterArray[i], expected[i]); + } +} + +/** + * 17. TestIndexUpdaterSetFloatArrayVectors + * + * Description + * Test that setting float array vectors works as expected. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 5. With the IndexUpdater object, for each index from 0 to 9. + * - Get the word string from the IndexUpdater and store the word string in a set for verifying + * the vector search result. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. With the IndexUpdater object, call finish() + * 7. Execute a vector search query. + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 8. Check that there are 10 words returned. + * 9. Check that the word is in the word set from the step 5. + */ + +- (void) testIndexUpdaterSetFloatArrayVectors { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 10 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 10); + + // Update Index: + NSMutableArray* indexedWords = [NSMutableArray array]; + for(NSUInteger i = 0; i < updater.count; i++) { + NSString* word = [updater stringAtIndex: i]; + NSArray* vector = [self vectorForWord: word]; + Assert([updater setVector: vector atIndex: i error: &error]); + [indexedWords addObject: word]; + } + + Assert([updater finishWithError: &error]); + + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 + queryDistance: false + andClause: nil + checkTraining: false]; + NSDictionary* wordMap = [self toDocIDWordMap: rs]; + AssertEqual(wordMap.count, 10); + + NSArray* words = [wordMap allValues]; + for(NSString* word in words) { + Assert([indexedWords containsObject: word]); + } +} + +/** + * 20. TestIndexUpdaterSetInvalidVectorDimensions + * + * Description + * Test thta the vector with the invalid dimenions different from the dimensions + * set to the configuration will not be included in the index. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * 5. With the IndexUpdater object, call setVector() with a float array as [1.0] + * 6. Check that the setVector throws CouchbaseLiteException with the InvalidParameter error. + */ +// CBL-5814 +- (void) _testIndexUpdaterSetInvalidVectorDimensions { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 1 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 1); + + [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in: ^BOOL(NSError** err) { + return [updater setVector: @[@1.0] atIndex: 0 error: err]; + }]; +} + +/** + * 21. TestIndexUpdaterSkipVectors + * + * Description + * Test that skipping vectors works as expected. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 5. With the IndexUpdater object, for each index from 0 - 9. + * - Get the word string from the IndexUpdater. + * - If index % 2 == 0, + * - Store the word string in a skipped word set for verifying the skipped words later. + * - Call skipVector at the index. + * - If index % 2 != 0, + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. With the IndexUpdater object, call finish() + * 7. Call beginUpdate with limit 10 to get an IndexUpdater object. + * 8. With the IndexUpdater object, for each index + * - Get the word string from the dictionary for the key named "word". + * - Check if the word is in the skipped word set from the Step 5. If the word + * is in the skipped word set, remove the word from the skipped word set. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index + * 9. With the IndexUpdater object, call finish() + * 10. Repeat Step 7, until the returned IndexUpdater is null or the skipped word set + * has zero words in it. + * 11. Verify that the skipped word set has zero words in it. + */ +- (void) _testIndexUpdaterSkipVectors { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 10 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 10); + + // Update Index: + NSMutableArray* skippedWords = [NSMutableArray array]; + NSMutableArray* indexedWords = [NSMutableArray array]; + for(NSUInteger i = 0; i < updater.count; i++) { + NSString* word = [updater stringAtIndex: i]; + if (i % 2 == 0) { + [skippedWords addObject: word]; + [updater skipVectorAtIndex: i]; + } else { + [indexedWords addObject: word]; + NSArray* vector = [self vectorForWord: word]; + [updater setVector: vector atIndex: i error: &error]; + } + } + [updater finishWithError: &error]; + AssertNil(error); + AssertEqual(skippedWords.count, 5); + AssertEqual(indexedWords.count, 5); + + // Update index for the skipped words: + updater = [index beginUpdateWithLimit: 10 error: &error]; + for (NSUInteger i = 0; i < updater.count; i++) { + NSString* word = [updater stringAtIndex: i]; + [skippedWords removeObject: word]; + } + AssertEqual(skippedWords.count, 0); +} + +/** + * 22. TestIndexUpdaterFinishWithIncompletedUpdate + * + * Description + * Test that a CouchbaseLiteException is thrown when calling finish() on + * an IndexUpdater that has incomplete updated. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 2 to get an IndexUpdater object. + * 5. With the IndexUpdater object, call finish(). + * 6. Check that a CouchbaseLiteException with code UnsupportedOperation is thrown. + * 7. For the index 0, + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 8. With the IndexUpdater object, call finish(). + * 9. Check that a CouchbaseLiteException with code UnsupportedOperation is thrown. + */ +- (void) testIndexUpdaterFinishWithIncompletedUpdate { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 10)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 2 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 2); + + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [updater finishWithError: err]; + }]; + + NSString* word = [updater stringAtIndex: 0]; + NSArray* vector = [self vectorForWord: word]; + [updater setVector: vector atIndex: 0 error: &error]; + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [updater finishWithError: err]; + }]; +} + +/** + * 23. TestIndexUpdaterCaughtUp + * + * Description + * Test that when the lazy vector index is caught up, calling beginUpdate() to + * get an IndexUpdater will return null. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 100 to get an IndexUpdater object. + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 4. Repeat Step 3 two more times. + * 5. Call beginUpdate() with limit 100 to get an IndexUpdater object. + * 6. Check that the returned IndexUpdater is null. + */ +- (void) testIndexUpdaterCaughtUp { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 10)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + + for (NSUInteger i = 0; i < 3; i++) { + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 100 error: &error]; + AssertNotNil(updater); + + for(NSUInteger j = 0; j < updater.count; j++) { + NSString* word = [updater stringAtIndex: j]; + NSArray* vector = [self vectorForWord: word]; + Assert([updater setVector: vector atIndex: j error: &error]); + } + Assert([updater finishWithError: &error]); + } + + AssertNil([index beginUpdateWithLimit: 100 error: &error]); + AssertEqual(error.code, 0); +} + +/** + * 24. TestNonFinishedIndexUpdaterNotUpdateIndex + * + * Description + * Test that the index updater can be released without calling finish(), + * and the released non-finished index updater doesn't update the index. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Get a QueryIndex object from the words with the name as "words_index". + * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 5. With the IndexUpdater object, for each index from 0 - 9. + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index. + * 6. Release or close the index updater object. + * 7. Execute a vector search query. + * - SELECT word + * FROM _default.words + * WHERE vector_match(words_index, < dinner vector >, 300) + * 8. Check that there are 0 words returned. + */ + +- (void) testNonFinishedIndexUpdaterNotUpdateIndex { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 10 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 10); + + // Update index: + for(NSUInteger i = 0; i < updater.count; i++) { + NSString* word = [updater stringAtIndex: i]; + NSArray* vector = [self vectorForWord: word]; + Assert([updater setVector: vector atIndex: i error: &error]); + } + + // "Release" CBLIndexUpdater + updater = nil; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 + queryDistance: false + andClause: nil + checkTraining: false]; + + AssertEqual(rs.allObjects.count, 0); +} + +/** + * 25. TestIndexUpdaterIndexOutOfBounds + * + * Description + * Test that when using getter, setter, and skip function with the index that + * is out of bounds, an IndexOutOfBounds or InvalidArgument exception + * is throws. + * + * Steps + * 1. Get the default collection from a test database. + * 2. Create the followings documents: + * - doc-0 : { "value": "a string" } + * 3. Create a vector index named "vector_index" in the default collection. + * - expression: "value" + * - dimensions: 3 + * - centroids : 8 + * - isLazy : true + * 4. Get a QueryIndex object from the default collection with the name as + * "vector_index". + * 5. Call beginUpdate() with limit 10 to get an IndexUpdater object. + * 6. Check that the IndexUpdater.count is 1. + * 7. Call each getter function with index = -1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 8. Call each getter function with index = 1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 9. Call setVector() function with a vector = [1.0, 2.0, 3.0] and index = -1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 10. Call setVector() function with a vector = [1.0, 2.0, 3.0] and index = 1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 9. Call skipVector() function with index = -1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + * 10. Call skipVector() function with index = 1 and check that + * an IndexOutOfBounds or InvalidArgument exception is thrown. + */ +- (void) testIndexUpdaterIndexOutOfBounds { + NSError* error; + CBLCollection* defaultCollection = [self.db defaultCollection: &error]; + + CBLMutableDocument* mdoc = [self createDocument: @"doc-0"]; + [mdoc setValue: @"a string" forKey: @"value"]; + [defaultCollection saveDocument: mdoc error: &error]; + + [self createVectorIndexInCollection: defaultCollection + name: @"vector_index" + config: LAZY_VECTOR_INDEX_CONFIG(@"value", 300, 8)]; + + CBLQueryIndex* index = [defaultCollection indexWithName: @"vector_index" error: &error]; + AssertNotNil(index); + + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 10 error: &error]; + AssertEqual(updater.count, 1); + + // This is in line with ArrayProtocol, throws RangeException + [self expectException: @"NSRangeException" in:^{ + [updater valueAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater stringAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater numberAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater integerAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater doubleAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater floatAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater longLongAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater dateAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater arrayAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater dictionaryAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater booleanAtIndex: 1]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater setVector: @[@1.0, @2.0, @3.0] atIndex: 1 error: nil]; + }]; + + [self expectException: @"NSRangeException" in:^{ + [updater skipVectorAtIndex: 1]; + }]; +} + +/** + * 26. TestIndexUpdaterCallFinishTwice + * + * Description + * Test that when calling IndexUpdater's finish() after it was finished, + * a CuchbaseLiteException is thrown. + * + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in the _default.words collection. + * - expression: "word" + * - dimensions: 300 + * - centroids : 8 + * - isLazy : true + * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. + * - Get the word string from the IndexUpdater. + * - Query the vector by word from the _default.words collection. + * - Convert the vector result which is an array object to a platform's float array. + * - Call setVector() with the platform's float array at the index.. + * 8. Call finish() and check that the finish() is successfully called. + * 9. Call finish() again and check that a CouchbaseLiteException with the code Unsupported is thrown. + */ +// CBL-5843 +- (void) _testIndexUpdaterCallFinishTwice { + [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; + + NSError* error; + CBLQueryIndex* index = [self wordsIndex]; + CBLIndexUpdater* updater = [index beginUpdateWithLimit: 1 error: &error]; + AssertNotNil(updater); + AssertEqual(updater.count, 1); + + NSString* word = [updater stringAtIndex: 0]; + NSArray* vector = [self vectorForWord: word]; + Assert([updater setVector: vector atIndex: 0 error: &error]); + Assert([updater finishWithError: &error]); + + [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + return [updater finishWithError: err]; + }]; +} + +@end diff --git a/Objective-C/Tests/VectorSearchTest.h b/Objective-C/Tests/VectorSearchTest.h new file mode 100644 index 000000000..27f166269 --- /dev/null +++ b/Objective-C/Tests/VectorSearchTest.h @@ -0,0 +1,74 @@ +// +// VectorIndexTest.h +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLTestCase.h" + +#ifdef COUCHBASE_ENTERPRISE + +NS_ASSUME_NONNULL_BEGIN + +#define kWordsDatabaseName @"words_db" + +#define kWordsCollectionName @"words" + +#define kExtWordsCollectionName @"extwords" + +#define kWordsIndexName @"words_index" + +#define kWordPredictiveModelName @"WordEmbedding" + +#define VECTOR_INDEX_CONFIG(E, D, C) [[CBLVectorIndexConfiguration alloc] initWithExpression: (E) dimensions: (D) centroids: (C)] + + +@interface VectorSearchTest : CBLTestCase + +@property (nonatomic, readonly) CBLDatabase* wordDB; + +@property (nonatomic, readonly) CBLCollection* wordsCollection; + +@property (nonatomic, readonly) CBLCollection* extWordsCollection; + +- (void) createVectorIndexInCollection: (CBLCollection*)collection + name: (NSString*)name + config: (CBLVectorIndexConfiguration*)config; + +- (void) createWordsIndexWithConfig: (CBLVectorIndexConfiguration*)config; + +- (void) deleteWordsIndex; + +- (NSString*) wordsQueryStringWithLimit: (nullable NSNumber*)limit + queryDistance: (BOOL) queryDistance + andClause: (nullable NSString*)andClause; + +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (nullable NSNumber*)limit + queryDistance: (BOOL) queryDistance + andClause: (nullable NSString*)andClause + checkTraining: (BOOL) checkTraining; + +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (nullable NSNumber*)limit; + +- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (nullable NSNumber*)limit; + +- (NSDictionary*) toDocIDWordMap: (CBLQueryResultSet*)resultSet; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index c9fdf7bb9..324d1b64f 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -17,30 +17,61 @@ // limitations under the License. // -#import "CBLTestCase.h" +#import "VectorSearchTest.h" #import "CBLWordEmbeddingModel.h" #import "CustomLogger.h" -#import "CBLQueryIndex.h" -#import "CBLJSON.h" #define kDinnerVector @[@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] #define kLunchVectorBase64 @"4OYevd8eyDxJGj69HCKOvoCJYTzQCJs9xhDbPp1Y6r2OTEm/ZKz1vtRbwL1Ik8I9+RQFPpyGBD69OEI9ul+evZD71L2nI4y8uTINPnVN+702+c4+8zToPEoGKj6xEqi93vPFvQDdK71Z6yC+yPT1PqXtQD99ENY+xnh+PpBEOD6aIUi+eVezvg24fj0YAJ++46c4vfVFOr57sWU+A+lqPdFq3T1ZJg6+Ok6yvs1/Cr5blju+ITa9vAFxlj1+8h4+c7UePe6fUL6OaDu+wR5IvnGmxj7eR2O+fYrsPf8kw73IOfq8YOJtvAxBMj0g99O8+toTPr0v8r2I4mK+Yxd1PTGxhbzu3aS9zeJEPqKy0Ty2cOy9YqgQPL7af703wFK9965hvOM0pz2VuAc+RIyTu4nxi73pigA9RCjpvVTOFj6zPIC+HTsrvrcpTz4vXzS6ArPxvM+VNL3hJgk+9pM7vtP1jL51sao8q4oJPonfBDxkAiC9XvJUPWiWTD1Kwbe+4KHOvUQmjjypsrS6i4MJPjRnWz0g8E4+Ad3IvVsKMT5O7Qw9X4tFPbpriT1TYme8uw5uvqBar72DLEa+vgAvvkHVs74kKk2+gNkOvZkV57zBfcC+/WM7PrKQQb4+adC9ftEXPmKYRz47RKM9+4mbPZZ76zs4LZq+0gIXPgNoxL26tT09rGFdvPdQqDwi/Y8939OLvYVTQr7J8hK+ljyeveMZsL5xeGi8sppcPfezjT11QuU9cvRpPSoby7yIZ3U9FUPXPd/y1z2xBhu9CfRyvbjXR72xLjk+9rkLvrdWJD2u+Iy9TtM/vlc0Ez4E1ju9XtcrPP+4Cr5ymDu+DfEAPswpP770tKm+3u07vsXxXb19zcC8MQ/APX507T2e7Ei+XYKGPiQ6SD0MORK+Lk4NP1zuHTzrAKW+Eu2WvSGPRj6fL7g9IdSgPkNyojxUSPi95uGqvJugrj0Bqbc9x1eVPk8qh74NlYk+07gZPVqt271XR2E+bMxmOyw0JD1Lg2Y+h+GDvRpuj70YCss890HtPdFwMz7oo7I+RpgXv4/lkz54b+Y8l6yOPdbWYj3H+4G+Q4wXvsXhyD0ayts9XIXBPndXLj34Q1I+0zfQu5pblj66UKa9dSWqvRl1xb04RQK9HsA6PrH2rD2r8wC+XQQPPlSirDwC3zU+K7Z4vUfVML4xHyY92TguPigvMj2emD8+q3AXPsSHWz4Cq5+9P/o7PveDcD095w++4fc9vvE81j17lt09AY7CvHD/Nz7FdCe+t7z4PDJPZD4Qsce9mdwZPtvzDj60sz6+ETvUPTLZ970Gauu83dW7PZZPCj51tCc+yMYtPYrmSjyUcpE+GCDgPf1tGr7aODg+ESYGPmu52T070vi9kW0vvaiwWj6JgQ6+hoehPVygk77JeOg8yCI+PtSnpD2I6w0+z3IFPRUoLD7boxM+XJYbviPzNrxBSBs+XO+WPpkuH74N9+m9tds9PiCinT6BaZ2+tGIfvhZSTj2ZP2k+cld+PHx1Kj4uOfK9bsXHPRx8Bz5OlMg96nYOPuLAub0CeRY+KQEZPogLdT5gk7g+Z0nEPJHztT1Dc3o9" -@interface VectorSearchTest : CBLTestCase - -@end - #pragma mark - Vector Search test /** Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0001-Vector-Search.md */ @implementation VectorSearchTest { CustomLogger* _logger; + + CBLDatabase* _wordDB; + CBLDatabase* _modelDB; + + // For some reason the autorelease poll could run after the teardown is called. + // To avoid leaking check issue, lazy creating these from the tests. + CBLCollection* _wordsCollection; + CBLCollection* _extWordsCollection; +} + +@synthesize wordDB=_wordDB; + +- (CBLCollection*) wordsCollection { + if (!_wordsCollection) { + _wordsCollection = [_wordDB collectionWithName: kWordsCollectionName scope: nil error: nil]; + } + return _wordsCollection; +} + +- (CBLCollection*) extWordsCollection { + if (!_extWordsCollection) { + _extWordsCollection = [_wordDB collectionWithName: kExtWordsCollectionName scope: nil error: nil]; + } + return _extWordsCollection; } - (void) setUp { + [self deleteDBNamed: kWordsDatabaseName error: nil]; + [super setUp]; + + NSError* error; + + CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; + config.directory = self.directory; + + NSString* path = [self databasePath: @"words_db.cblite2" inDirectory: @"vectorsearch"]; + Assert([CBLDatabase copyFromPath:path toDatabase: kWordsDatabaseName withConfig: config error: &error]); + + _wordDB = [self openDBNamed: kWordsDatabaseName error: &error]; + AssertNotNil(_wordDB); _logger = [[CustomLogger alloc] init]; _logger.level = kCBLLogLevelInfo; @@ -50,28 +81,37 @@ - (void) setUp { - (void) tearDown { CBLDatabase.log.custom = nil; - [super tearDown]; -} - -- (void) initDB { + _wordsCollection = nil; + _extWordsCollection = nil; + NSError* error; - CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; - config.directory = self.directory; + if (_wordDB) { + Assert([_wordDB close: &error], @"Failed to close db: %@", error); + _wordDB = nil; + } - NSString* path = [self databasePath: @"words_db.cblite2" inDirectory: @"vectorsearch"]; - [CBLDatabase copyFromPath:path toDatabase: kDatabaseName withConfig: config error: &error]; + if (_modelDB) { + Assert([_modelDB close: &error], @"Failed to close db: %@", error); + _modelDB = nil; + } - [self openDB]; + [self unregisterPredictiveModel]; + + [super tearDown]; } -- (NSDictionary*) toDocIDWordMap: (CBLQueryResultSet*)resultSet { - NSMutableDictionary* wordMap = [NSMutableDictionary dictionary]; - for (CBLQueryResult* result in resultSet) { - NSString* docID = [result stringAtIndex: 0]; - NSString* word = [result stringAtIndex: 1]; - wordMap[docID] = word; +- (void) registerPredictiveModel { + if (!_modelDB) { + _modelDB = [self openDBNamed: kWordsDatabaseName error: nil]; + AssertNotNil(_modelDB); } - return wordMap; + + CBLWordEmbeddingModel* model = [[CBLWordEmbeddingModel alloc] initWithDatabase: _modelDB]; + [CBLDatabase.prediction registerModel: model withName: kWordPredictiveModelName]; +} + +- (void) unregisterPredictiveModel { + [CBLDatabase.prediction unregisterModelWithName: kWordPredictiveModelName]; } - (void) resetIndexWasTrainedLog { @@ -82,46 +122,87 @@ - (BOOL) checkIndexWasTrained { return ![_logger containsString: @"Untrained index; queries may be slow"]; } -- (nullable NSArray*) vectorArrayForWord: (NSString*) word - collection: (CBLCollection*) collection { - AssertNotNil(collection); - NSError* error; - NSString* queryString = [NSString stringWithFormat:@"SELECT vector FROM %@ WHERE word = '%@'", - collection.name, word]; +- (void) createVectorIndexInCollection: (CBLCollection*)collection + name: (NSString*)name + config: (CBLVectorIndexConfiguration*)config { + Assert([collection createIndexWithName: name config: config error: nil]); + Assert([[collection indexes: nil] containsObject: name]); +} + +- (void) createWordsIndexWithConfig: (CBLVectorIndexConfiguration*)config { + [self createVectorIndexInCollection: self.wordsCollection name: kWordsIndexName config: config]; +} + +- (void) deleteWordsIndex { + Assert([_wordsCollection deleteIndexWithName: kWordsIndexName error: nil]); + AssertFalse([[self.wordsCollection indexes: nil] containsObject: @"words_index"]); +} + +- (NSString*) wordsQueryStringWithLimit: (NSNumber*)limit + queryDistance: (BOOL) queryDistance + andClause: (NSString*)andClause { + NSString* sql = @"SELECT meta().id, word, catid"; + if (queryDistance) { + sql = [sql stringByAppendingFormat: @", VECTOR_DISTANCE(%@)", kWordsIndexName]; + } - CBLQuery* q = [_db createQuery: queryString error: &error]; - if (!q) { - NSLog(@"Can't create query: %@/%ld", error.domain, (long)error.code); - return nil; + sql = [sql stringByAppendingFormat: @" FROM %@ WHERE VECTOR_MATCH(%@, $vector)", + kWordsCollectionName, kWordsIndexName]; + + if (andClause) { + sql = [sql stringByAppendingFormat: @" %@", andClause]; } - - CBLQueryResultSet* rs = [q execute: &error]; - if (!rs) { - NSLog(@"Can't execute query: %@/%ld", error.domain, (long)error.code); - return nil; + + if (limit) { + sql = [sql stringByAppendingFormat: @" LIMIT %d", [limit intValue]]; } + + return sql; +} - NSArray* vector; - CBLQueryResult *result = [rs nextObject]; - if (result) { - id value = [result valueAtIndex:0]; - if ([value isKindOfClass:[CBLArray class]]) { - vector = (NSArray*)value; - // Additional check to ensure all elements are NSNumber - for (id element in vector) { - if (![element isKindOfClass:[NSNumber class]]) { - NSLog(@"Unexpected type in vector array"); - return nil; - } - } - } else { - NSLog(@"Query result is not an NSArray"); - } +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSNumber*)limit + queryDistance: (BOOL) queryDistance + andClause: (NSString*)andClause + checkTraining: (BOOL) checkTraining { + NSError* error; + NSString* sql = [self wordsQueryStringWithLimit: limit queryDistance: queryDistance andClause: andClause]; + CBLQuery* query = [_wordDB createQuery: sql error: &error]; + AssertNotNil(query); + + CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; + [parameters setValue: kDinnerVector forName: @"vector"]; + [query setParameters: parameters]; + + NSString* explain = [query explain: &error]; + Assert([explain rangeOfString: @"kv_.words:vector:words_index"].location != NSNotFound); + + CBLQueryResultSet* rs = [query execute: &error]; + AssertNotNil(rs); + + if (checkTraining) { + XCTAssert([self checkIndexWasTrained]); } - return vector; + + return rs; +} + +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSNumber*)limit { + return [self executeWordsQueryWithLimit: limit queryDistance: false andClause: nil checkTraining: true]; } +- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSNumber*)limit { + return [self executeWordsQueryWithLimit: limit queryDistance: false andClause: nil checkTraining: false]; +} +- (NSDictionary*) toDocIDWordMap: (CBLQueryResultSet*)resultSet { + NSMutableDictionary* wordMap = [NSMutableDictionary dictionary]; + for (CBLQueryResult* result in resultSet) { + NSString* docID = [result stringAtIndex: 0]; + NSString* word = [result stringAtIndex: 1]; + wordMap[docID] = word; + } + return wordMap; +} /** * 1. TestVectorIndexConfigurationDefaultValue @@ -131,7 +212,7 @@ - (BOOL) checkIndexWasTrained { * 1. Create a VectorIndexConfiguration object. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * 2. Get and check the following property values: * - encoding: 8-Bit Scalar Quantizer Encoding * - metric: Euclidean Distance @@ -141,9 +222,7 @@ - (BOOL) checkIndexWasTrained { * property to the tests for verification. */ - (void) testVectorIndexConfigurationDefaultValue { - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); AssertEqualObjects(config.encoding, [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ8]); AssertEqual(config.metric, kCBLDistanceMetricEuclidean); AssertEqual(config.minTrainingSize, 25 * config.centroids); @@ -158,7 +237,7 @@ - (void) testVectorIndexConfigurationDefaultValue { * 1. Create a VectorIndexConfiguration object with the following properties. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - encoding: None * - metric: Cosine Distance * - minTrainingSize: 100 @@ -174,9 +253,7 @@ - (void) testVectorIndexConfigurationDefaultValue { * - maxTrainingSize: 200 */ - (void) testVectorIndexConfigurationSettersAndGetters { - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); CBLVectorEncoding* noneEncoding = [CBLVectorEncoding none]; config.encoding = noneEncoding; config.metric = kCBLDistanceMetricCosine; @@ -186,7 +263,7 @@ - (void) testVectorIndexConfigurationSettersAndGetters { AssertEqual(config.expression, @"vector"); AssertEqualObjects(config.expressions, @[@"vector"]); AssertEqual(config.dimensions, 300); - AssertEqual(config.centroids, 20); + AssertEqual(config.centroids, 8); AssertEqual(config.encoding, noneEncoding); AssertEqual(config.metric, kCBLDistanceMetricCosine); AssertEqual(config.minTrainingSize, 100); @@ -203,7 +280,7 @@ - (void) testVectorIndexConfigurationSettersAndGetters { * 1. Create a VectorIndexConfiguration object. * - expression: "vector" * - dimensions: 2 and 4096 - * - centroids: 20 + * - centroids: 8 * 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. @@ -211,35 +288,16 @@ - (void) testVectorIndexConfigurationSettersAndGetters { * 5. Check that an invalid argument exception is thrown. */ - (void) testDimensionsValidation { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - CBLVectorIndexConfiguration* config1 = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 2 - centroids: 20]; - AssertNotNil(config1); - Assert([collection createIndexWithName: @"words_index_1" config: config1 error: &error]); - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index_1"]); - - CBLVectorIndexConfiguration* config2 = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 4096 - centroids: 20]; - AssertNotNil(config2); - Assert([collection createIndexWithName: @"words_index_2" config: config2 error: &error]); - names = [collection indexes: &error]; - Assert([names containsObject: @"words_index_2"]); + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 2, 8)]; + + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 4096, 8)]; [self expectException: NSInvalidArgumentException in:^{ - (void) [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 1 - centroids: 20]; + VECTOR_INDEX_CONFIG(@"vector", 1, 8); }]; [self expectException: NSInvalidArgumentException in:^{ - (void) [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 4097 - centroids: 20]; + VECTOR_INDEX_CONFIG(@"vector", 4097, 8); }]; } @@ -261,31 +319,18 @@ - (void) testDimensionsValidation { * 5. Check that an invalid argument exception is thrown. */ - (void) testCentroidsValidation { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - CBLVectorIndexConfiguration* config1 = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 1]; - AssertNotNil(config1); - Assert([collection createIndexWithName: @"words_index_1" config: config1 error: &error]); - - CBLVectorIndexConfiguration* config2 = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 64000]; - AssertNotNil(config2); - Assert([collection createIndexWithName: @"words_index_2" config: config2 error: &error]); + CBLVectorIndexConfiguration* config1 = VECTOR_INDEX_CONFIG(@"vector", 300, 1); + Assert([self.wordsCollection createIndexWithName: @"words_index_1" config: config1 error: nil]); + + CBLVectorIndexConfiguration* config2 = VECTOR_INDEX_CONFIG(@"vector", 300, 64000); + Assert([self.wordsCollection createIndexWithName: @"words_index_2" config: config2 error: nil]); [self expectException: NSInvalidArgumentException in:^{ - (void) [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 0]; + (void) VECTOR_INDEX_CONFIG(@"vector", 300, 0); }]; [self expectException: NSInvalidArgumentException in:^{ - (void) [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 64001]; + (void) VECTOR_INDEX_CONFIG(@"vector", 300, 64001); }]; } @@ -316,32 +361,10 @@ - (void) testCentroidsValidation { * 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: 8]; - config.minTrainingSize = 200; + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - 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]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); } /** @@ -378,53 +401,29 @@ - (void) testCreateVectorIndex { * 11. Reset the custom logger. */ - (void) testUpdateVectorIndex { - NSError* error; - CBLCollection* wordsCollection = [_db collectionWithName: @"words" scope: nil error: &error]; - CBLCollection* extWordsCollection = [_db collectionWithName: @"extwords" scope: nil error: &error]; - - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - - Assert([wordsCollection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [wordsCollection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 350)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; - CBLQueryResultSet* rs = [q execute: &error]; - NSArray* results = rs.allObjects; - AssertEqual(results.count, 300); - Assert([self checkIndexWasTrained]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; + AssertEqual(rs.allObjects.count, 300); // Update docs: - CBLDocument* extWord1 = [extWordsCollection documentWithID: @"word1" error : &error]; + NSError* error; + CBLDocument* extWord1 = [self.extWordsCollection documentWithID: @"word1" error : &error]; CBLMutableDocument* word301 = [self createDocument: @"word301" data: [extWord1 toDictionary]]; - Assert([wordsCollection saveDocument: word301 error: &error]); + Assert([self.wordsCollection saveDocument: word301 error: &error]); - CBLDocument* extWord2 = [extWordsCollection documentWithID: @"word2" error : &error]; + CBLDocument* extWord2 = [self.extWordsCollection documentWithID: @"word2" error : &error]; CBLMutableDocument* word302 = [self createDocument: @"word302" data: [extWord2 toDictionary]]; - Assert([wordsCollection saveDocument: word302 error: &error]); + Assert([self.wordsCollection saveDocument: word302 error: &error]); - CBLDocument* extWord3 = [extWordsCollection documentWithID: @"word3" error : &error]; - CBLMutableDocument* word1 = [[wordsCollection documentWithID: @"word1" error: &error] toMutable]; + CBLDocument* extWord3 = [self.extWordsCollection documentWithID: @"word3" error : &error]; + CBLMutableDocument* word1 = [[self.wordsCollection documentWithID: @"word1" error: &error] toMutable]; [word1 setData: [extWord3 toDictionary]]; - Assert([wordsCollection saveDocument: word1 error: &error]); + Assert([self.wordsCollection saveDocument: word1 error: &error]); - [wordsCollection deleteDocument: [wordsCollection documentWithID: @"word2" error :&error] error: &error]; - - rs = [q execute: &error]; + [self.wordsCollection deleteDocument: [self.wordsCollection documentWithID: @"word2" error :&error] error: &error]; + rs = [self executeWordsQueryWithLimit: @350]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 301); AssertEqualObjects(wordMap[@"word301"], [word301 stringForKey: @"word"]); @@ -467,48 +466,30 @@ - (void) testUpdateVectorIndex { */ - (void) testCreateVectorIndexWithInvalidVectors { NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; // Update docs: - CBLMutableDocument* auxDoc = [[collection documentWithID: @"word1" error: &error] toMutable]; + CBLMutableDocument* auxDoc = [[self.wordsCollection documentWithID: @"word1" error: &error] toMutable]; [auxDoc setArray: nil forKey: @"vector"]; - [collection saveDocument: auxDoc error: &error]; + [self.wordsCollection saveDocument: auxDoc error: &error]; - auxDoc = [[collection documentWithID: @"word2" error: &error] toMutable]; + auxDoc = [[self.wordsCollection documentWithID: @"word2" error: &error] toMutable]; [auxDoc setString: @"string" forKey: @"vector"]; - [collection saveDocument: auxDoc error: &error]; + [self.wordsCollection saveDocument: auxDoc error: &error]; - auxDoc = [[collection documentWithID: @"word3" error: &error] toMutable]; + auxDoc = [[self.wordsCollection documentWithID: @"word3" error: &error] toMutable]; [auxDoc removeValueForKey: @"vector"]; - [collection saveDocument: auxDoc error: &error]; + [self.wordsCollection saveDocument: auxDoc error: &error]; - auxDoc = [[collection documentWithID: @"word4" error: &error] toMutable]; + auxDoc = [[self.wordsCollection documentWithID: @"word4" error: &error] toMutable]; CBLMutableArray* vector = [auxDoc arrayForKey: @"vector"]; [vector removeValueAtIndex: 0]; - Assert([collection saveDocument: auxDoc error: &error]); + Assert([self.wordsCollection saveDocument: auxDoc error: &error]); // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 350)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; - + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 296); AssertNil(wordMap[@"word1"]); @@ -517,12 +498,13 @@ - (void) testCreateVectorIndexWithInvalidVectors { AssertNil(wordMap[@"word4"]); Assert([self checkIndexWasTrained]); - auxDoc = [[collection documentWithID: @"word5" error: &error] toMutable]; + // Update doc: + auxDoc = [[self.wordsCollection documentWithID: @"word5" error: &error] toMutable]; [auxDoc setString: nil forKey: @"vector"]; - [collection saveDocument: auxDoc error: &error]; - - rs = [q execute: &error]; + [self.wordsCollection saveDocument: auxDoc error: &error]; + // Query: + rs = [self executeWordsQueryWithLimit: @350]; wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 295); AssertNil(wordMap[@"word5"]); @@ -562,70 +544,41 @@ - (void) testCreateVectorIndexWithInvalidVectors { * 12. Reset the custom logger. */ - (void) testCreateVectorIndexUsingPredictionModel { - NSError* error; - - CBLCollection* wordsCollection = [_db collectionWithName: @"words" scope: nil error: &error]; - CBLCollection* extWordsCollection = [_db collectionWithName: @"extwords" scope: nil error: &error]; - - CBLDatabase *modelDb = [self openDBNamed: kDatabaseName error: &error]; - CBLWordEmbeddingModel* model = [[CBLWordEmbeddingModel alloc] initWithDatabase: modelDb]; - [CBLDatabase.prediction registerModel: model withName: @"WordEmbedding"]; + [self registerPredictiveModel]; NSString* expr = @"prediction(WordEmbedding,{\"word\": word}).vector"; - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: expr - dimensions: 300 - centroids: 8]; - Assert([wordsCollection createIndexWithName: @"words_pred_index" config: config error: &error]); - - NSArray* names = [wordsCollection indexes: &error]; - Assert([names containsObject: @"words_pred_index"]); - - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_pred_index, $vector, 350)"; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - - CBLQuery* q = [_db createQuery: sql error: &error]; - [q setParameters: parameters]; + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(expr, 300, 8)]; - 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]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; + AssertEqual(rs.allObjects.count, 300); // Create words.word301 with extwords.word1 content - CBLDocument* extWord1 = [extWordsCollection documentWithID: @"word1" error : &error]; + NSError* error; + CBLDocument* extWord1 = [self.extWordsCollection documentWithID: @"word1" error : &error]; CBLMutableDocument* word301 = [self createDocument: @"word301" data: [extWord1 toDictionary]]; - Assert([wordsCollection saveDocument: word301 error: &error]); + Assert([self.wordsCollection saveDocument: word301 error: &error]); // Create words.word302 with extwords.word2 content - CBLDocument* extWord2 = [extWordsCollection documentWithID: @"word2" error : &error]; + CBLDocument* extWord2 = [self.extWordsCollection documentWithID: @"word2" error : &error]; CBLMutableDocument* word302 = [self createDocument: @"word302" data: [extWord2 toDictionary]]; - Assert([wordsCollection saveDocument: word302 error: &error]); + Assert([self.wordsCollection saveDocument: word302 error: &error]); // Update words.word1 with extwords.word3 content - CBLDocument* extWord3 = [extWordsCollection documentWithID: @"word3" error : &error]; - CBLMutableDocument* word1 = [[wordsCollection documentWithID: @"word1" error: &error] toMutable]; + CBLDocument* extWord3 = [self.extWordsCollection documentWithID: @"word3" error : &error]; + CBLMutableDocument* word1 = [[self.wordsCollection documentWithID: @"word1" error: &error] toMutable]; [word1 setData: [extWord3 toDictionary]]; - Assert([wordsCollection saveDocument: word1 error: &error]); + Assert([self.wordsCollection saveDocument: word1 error: &error]); // Delete words.word2 - [wordsCollection deleteDocument: [wordsCollection documentWithID: @"word2" error : &error] error: &error]; - - rs = [q execute: &error]; + [_wordsCollection deleteDocument: [self.wordsCollection documentWithID: @"word2" error : &error] error: &error]; + rs = [self executeWordsQueryWithLimit: @350]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 301); AssertEqualObjects(wordMap[@"word301"], [word301 stringForKey: @"word"]); AssertEqualObjects(wordMap[@"word302"], [word302 stringForKey: @"word"]); AssertEqualObjects(wordMap[@"word1"], [word1 stringForKey: @"word"]); AssertNil(wordMap[@"word2"]); - - [CBLDatabase.prediction unregisterModelWithName: @"WordEmbedding"]; } /** @@ -663,54 +616,33 @@ - (void) testCreateVectorIndexUsingPredictionModel { * 13. Reset the custom logger. */ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - CBLDatabase *modelDb = [self openDBNamed: kDatabaseName error: &error]; - CBLWordEmbeddingModel* model = [[CBLWordEmbeddingModel alloc] initWithDatabase: modelDb]; - [CBLDatabase.prediction registerModel: model withName: @"WordEmbedding"]; + [self registerPredictiveModel]; // Update docs: - CBLMutableDocument* auxDoc = [[collection documentWithID: @"word1" error: &error] toMutable]; + NSError* error; + CBLMutableDocument* auxDoc = [[self.wordsCollection documentWithID: @"word1" error: &error] toMutable]; [auxDoc setArray: nil forKey: @"vector"]; - Assert([collection saveDocument: auxDoc error: &error]); + Assert([self.wordsCollection saveDocument: auxDoc error: &error]); - auxDoc = [[collection documentWithID: @"word2" error: &error] toMutable]; + auxDoc = [[_wordsCollection documentWithID: @"word2" error: &error] toMutable]; [auxDoc setString: @"string" forKey: @"vector"]; - Assert([collection saveDocument:auxDoc error:&error]); + Assert([self.wordsCollection saveDocument:auxDoc error:&error]); - auxDoc = [[collection documentWithID: @"word3" error: &error] toMutable]; + auxDoc = [[_wordsCollection documentWithID: @"word3" error: &error] toMutable]; [auxDoc removeValueForKey: @"vector"]; - Assert([collection saveDocument:auxDoc error:&error]); + Assert([self.wordsCollection saveDocument:auxDoc error:&error]); - auxDoc = [[collection documentWithID: @"word4" error: &error] toMutable]; + auxDoc = [[_wordsCollection documentWithID: @"word4" error: &error] toMutable]; CBLMutableArray* vector = [auxDoc arrayForKey: @"vector"]; [vector removeValueAtIndex: 0]; - Assert([collection saveDocument: auxDoc error: &error]); + Assert([self.wordsCollection saveDocument: auxDoc error: &error]); // Create vector index NSString* expr = @"prediction(WordEmbedding,{\"word\": word}).vector"; - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: expr - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_pred_index" config: config error: &error]); + [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(expr, 300, 8)]; - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_pred_index"]); - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_pred_index, $vector, 350)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_pred_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; - + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 296); AssertNil(wordMap[@"word1"]); @@ -719,17 +651,14 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { AssertNil(wordMap[@"word4"]); Assert([self checkIndexWasTrained]); - auxDoc = [[collection documentWithID: @"word5" error: &error] toMutable]; + auxDoc = [[self.wordsCollection documentWithID: @"word5" error: &error] toMutable]; [auxDoc setString: @"Fried Chicken" forKey: @"word"]; - Assert([collection saveDocument: auxDoc error: &error]); + Assert([self.wordsCollection saveDocument: auxDoc error: &error]); - rs = [q execute: &error]; - + rs = [self executeWordsQueryWithLimit: @350]; wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 295); AssertNil(wordMap[@"word5"]); - - [CBLDatabase.prediction unregisterModelWithName: @"WordEmbedding"]; } /** @@ -759,58 +688,33 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { * 11. Repeat Step 2 – 10 by using SQ6 and SQ8 respectively. */ - (void) testCreateVectorIndexWithSQ { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); config.encoding = [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ4]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + [self createWordsIndexWithConfig: config]; - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - 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]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); // Repeat using SQ6 [self resetIndexWasTrainedLog]; - [collection deleteIndexWithName: @"words_index" error: &error]; + [self deleteWordsIndex]; config.encoding = [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ6]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + [self createWordsIndexWithConfig: config]; // Rerun query: - rs = [q execute: &error]; - allObjects = rs.allObjects; - AssertEqual(allObjects.count, 20); - Assert([self checkIndexWasTrained]); + rs = [self executeWordsQueryWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); // Repeat using SQ8 [self resetIndexWasTrainedLog]; - [collection deleteIndexWithName: @"words_index" error: &error]; + [self deleteWordsIndex]; config.encoding = [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + [self createWordsIndexWithConfig: config]; // Rerun query: - rs = [q execute: &error]; - allObjects = rs.allObjects; - AssertEqual(allObjects.count, 20); - Assert([self checkIndexWasTrained]); + rs = [self executeWordsQueryWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); } /** @@ -837,39 +741,16 @@ - (void) testCreateVectorIndexWithSQ { * 9. Reset the custom logger. */ - (void) testCreateVectorIndexWithNoneEncoding { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); config.encoding = [CBLVectorEncoding none]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; + [self createWordsIndexWithConfig: config]; - 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]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); } /** * FAILED : https://issues.couchbase.com/browse/CBL-5538 - * Disable bits = 12 for now. * * 12. TestCreateVectorIndexWithPQ * Description @@ -897,42 +778,18 @@ - (void) testCreateVectorIndexWithNoneEncoding { * 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 @[@4, @8, /* @12 */]) { + for (NSNumber* bits in @[@4, @8, @12]) { // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: 5 bits: bit.unsignedIntValue]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: 5 bits: bits.unsignedIntValue]; + [self createWordsIndexWithConfig: config]; // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - 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); - // will re-enable once we increase the dataset - // Assert([self checkIndexWasTrained]); + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); // Delete index - [collection deleteIndexWithName: @"words_index" error: &error]; - - // Reset log - [self resetIndexWasTrainedLog]; + Assert([self.wordsCollection deleteIndexWithName: @"words_index" error: nil]); } } @@ -957,41 +814,27 @@ - (void) testCreateVectorIndexWithPQ { * 7. Check that an invalid argument exception is thrown. */ - (void) testSubquantizersValidation { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: 2 bits: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); // Step 5: Use valid subquantizer values - for (NSNumber* subq in @[@3, @4, @5, @6, @10, @12, @15, @20, @25, @30, @50, @60, @75, @100, @150, @300]) { - [collection deleteIndexWithName: @"words_index" error: &error]; + for (NSNumber* subq in @[@3, @4, @5, @150, @300]) { config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: subq.unsignedIntValue bits: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + [self createWordsIndexWithConfig: config]; + [self deleteWordsIndex]; } // Step 7: Check if exception thrown for wrong subquantizers: - [self expectException: NSInvalidArgumentException in:^{ - for (NSNumber* subq in @[@0, @7]) { - [collection deleteIndexWithName: @"words_index" error: nil]; - config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: subq.unsignedIntValue bits: 8]; - [collection createIndexWithName: @"words_index" config: config error: nil]; - } - }]; + CBLCollection* collection = self.wordsCollection; + for (NSNumber* subq in @[@0, @7]) { + config.encoding = [CBLVectorEncoding productQuantizerWithSubquantizers: subq.unsignedIntValue bits: 8]; + [self expectException: NSInvalidArgumentException in: ^{ + [collection createIndexWithName: kWordsIndexName config: config error: nil]; + }]; + [self deleteWordsIndex]; + } } /** - * 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 @@ -1016,37 +859,13 @@ - (void) testSubquantizersValidation { * 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: 8]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); config.minTrainingSize = 100; config.maxTrainingSize = 100; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + [self createWordsIndexWithConfig: config]; - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - 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]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); } /** @@ -1060,7 +879,7 @@ - (void) testeCreateVectorIndexWithFixedTrainingSize { * 2. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * - minTrainingSize: 1 and maxTrainingSize: 100 * 3. Check that the index is created without an error returned. * 4. Delete the "words_index" @@ -1071,35 +890,29 @@ - (void) testeCreateVectorIndexWithFixedTrainingSize { * 6. Check that an invalid argument exception was thrown for all cases in step 4. */ - (void) testValidateMinMaxTrainingSize { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); config.minTrainingSize = 1; config.maxTrainingSize = 100; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Check if exception thrown for wrong values - [self expectException: NSInvalidArgumentException in:^{ - NSArray *> *trainingSizes = @[ - @[@0, @0], - @[@0, @100], - @[@10, @9] - ]; + [self createWordsIndexWithConfig: config]; + + NSArray *> *trainingSizes = @[ + @[@0, @0], + @[@0, @100], + @[@10, @9] + ]; + + CBLCollection* collection = self.wordsCollection; + for (size_t i = 0; i < trainingSizes.count; i++) { + config.minTrainingSize = trainingSizes[i][0].unsignedIntValue; + config.maxTrainingSize = trainingSizes[i][1].unsignedIntValue; - for (size_t i = 0; i < trainingSizes.count; i++) { - [collection deleteIndexWithName: @"words_index" error: nil]; - config.minTrainingSize = trainingSizes[i][0].unsignedIntValue; - config.maxTrainingSize = trainingSizes[i][1].unsignedIntValue; - [collection createIndexWithName: @"words_index" config: config error: nil]; - } - }]; + // Check if exception thrown for wrong values + [self expectException: NSInvalidArgumentException in:^{ + [collection createIndexWithName: kWordsIndexName config: config error: nil]; + }]; + + [self deleteWordsIndex]; + } } /** @@ -1127,35 +940,13 @@ - (void) testValidateMinMaxTrainingSize { * 9. Reset the custom logger. */ - (void) testQueryUntrainedVectorIndex { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - // out of bounds (300 words in db) + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); config.minTrainingSize = 400; config.maxTrainingSize = 500; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); + [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [q execute: &error]; - NSArray* allObjects = rs.allObjects; - AssertEqual(allObjects.count, 20); + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); AssertFalse([self checkIndexWasTrained]); } @@ -1184,38 +975,17 @@ - (void) testQueryUntrainedVectorIndex { * 9. Reset the custom logger. */ - (void) testCreateVectorIndexWithCosineDistance { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); config.metric = kCBLDistanceMetricCosine; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + [self createWordsIndexWithConfig: config]; - // Query: - NSString* sql = @"select meta().id, word, vector_distance(words_index) from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; - NSArray* allObjects = rs.allResults; - AssertEqual(allObjects.count, 20); - for(CBLQueryResult* result in rs){ + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20 queryDistance: true andClause: false checkTraining: false]; + NSArray* results = rs.allResults; + AssertEqual(results.count, 20); + for(CBLQueryResult* result in results){ Assert([result doubleAtIndex: 3] > 0); Assert([result doubleAtIndex: 3] < 1); } - Assert([self checkIndexWasTrained]); } /** @@ -1243,37 +1013,16 @@ - (void) testCreateVectorIndexWithCosineDistance { * 9. Reset the custom logger. */ - (void) testCreateVectorIndexWithEuclideanDistance { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create vector index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); config.metric = kCBLDistanceMetricEuclidean; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + [self createWordsIndexWithConfig: config]; - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query: - NSString* sql = @"select meta().id, word, vector_distance(words_index) from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; - NSArray* allObjects = rs.allResults; - AssertEqual(allObjects.count, 20); - for(CBLQueryResult* result in rs){ + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20 queryDistance: true andClause: false checkTraining: false]; + NSArray* results = rs.allResults; + AssertEqual(results.count, 20); + for(CBLQueryResult* result in results){ Assert([result doubleAtIndex: 3] > 0); } - Assert([self checkIndexWasTrained]); } /** @@ -1296,21 +1045,12 @@ - (void) testCreateVectorIndexWithEuclideanDistance { * 6. Check that the index is created without an error returned. */ - (void) testCreateVectorIndexWithExistingName { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; + [self createWordsIndexWithConfig: config]; - // Create and recreate vector index using the same config - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - // Recreate index with same name using different config - CBLVectorIndexConfiguration* config2 = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vectors" - dimensions: 300 - centroids: 20]; - Assert([collection createIndexWithName: @"words_index" config: config2 error: &error]); + config = VECTOR_INDEX_CONFIG(@"vectors", 300, 8); + [self createWordsIndexWithConfig: config]; } /** @@ -1341,41 +1081,17 @@ - (void) testCreateVectorIndexWithExistingName { * 12. Reset the custom logger. */ - (void) testDeleteVectorIndex { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + AssertEqual(rs.allObjects.count, 20); - 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 deleteWordsIndex]; [self expectError: CBLErrorDomain code: CBLErrorMissingIndex in: ^BOOL(NSError **err) { - return [self->_db createQuery: sql error: err]; + NSString* sql = [self wordsQueryStringWithLimit: @20 queryDistance: false andClause: nil]; + return [self->_wordDB createQuery: sql error: err] != nil; }]; } @@ -1394,8 +1110,8 @@ - (void) testDeleteVectorIndex { */ - (void) testVectorMatchOnNonExistingIndex { [self expectError: CBLErrorDomain code: CBLErrorMissingIndex in: ^BOOL(NSError **err) { - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - return [self->_db createQuery: sql error: err]; + NSString* sql = [self wordsQueryStringWithLimit: @20 queryDistance: false andClause: nil]; + return [self->_wordDB createQuery: sql error: err] != nil; }]; } @@ -1423,34 +1139,11 @@ - (void) testVectorMatchOnNonExistingIndex { * 9. Reset the custom logger. */ - (void) testVectorMatchDefaultLimit { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: nil]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; - NSArray* allObjects = rs.allResults; - AssertEqual(allObjects.count, 3); - Assert([self checkIndexWasTrained]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: nil]; + AssertEqual(rs.allObjects.count, 3); } /** @@ -1464,7 +1157,7 @@ - (void) testVectorMatchDefaultLimit { * 2. Create a vector index named "words_index" in _default.words collection. * - expression: "vector" * - dimensions: 300 - * - centroids: 20 + * - centroids: 8 * 3. Check that the index is created without an error returned. * 4. Create an SQL++ query. * - SELECT meta().id, word @@ -1476,30 +1169,22 @@ - (void) testVectorMatchDefaultLimit { * 7. Check that a CouchbaseLiteException is returned when creating the query. */ - (void) testVectorMatchLimitBoundary { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; // Check valid query with 1 and 10000 set limit for (NSNumber* limit in @[@1, @10000]) { - NSString* sql = [NSString stringWithFormat: @"select meta().id, word from _default.words where vector_match(words_index, $vector, %d)", limit.unsignedIntValue]; - Assert([_db createQuery: sql error: &error]); + NSError* error; + NSString* sql = [self wordsQueryStringWithLimit: limit queryDistance: false andClause: nil]; + Assert([_wordDB createQuery: sql error: &error]); AssertNil(error); } // Check if error thrown for wrong limit values for (NSNumber* limit in @[@-1, @0, @10001]) { [self expectError: CBLErrorDomain code: CBLErrorInvalidQuery in: ^BOOL(NSError** err) { - return [self.db createQuery: [NSString stringWithFormat:@"select meta().id, word from _default.words where vector_match(words_index, $vector, %d)", limit.unsignedIntValue] - error: err] != nil; + NSString* sql = [self wordsQueryStringWithLimit: limit queryDistance: false andClause: nil]; + return [self->_wordDB createQuery: sql error: err] != nil; }]; } } @@ -1529,37 +1214,19 @@ - (void) testVectorMatchLimitBoundary { * 10. Reset the custom logger. */ - (void) testVectorMatchWithAndExpression { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query with a single AND: - NSString* sql = @"select word, catid from _default.words where vector_match(words_index, $vector, 300) AND catid = 'cat1'"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 + queryDistance: false + andClause: @"AND catid = 'cat1'" + checkTraining: true]; - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; NSArray* results = rs.allResults; AssertEqual(results.count, 50); for(CBLQueryResult* result in results){ - AssertEqualObjects([result valueAtIndex: 1], @"cat1"); + AssertEqualObjects([result valueAtIndex: 2], @"cat1"); } - Assert([self checkIndexWasTrained]); } /** @@ -1587,37 +1254,19 @@ - (void) testVectorMatchWithAndExpression { * 10. Reset the custom logger. */ - (void) testVectorMatchWithMultipleAndExpression { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 + queryDistance: false + andClause: @"AND word is valued AND catid = 'cat1'" + checkTraining: true]; - // Query with mutiple ANDs: - NSString* sql = @"select word, catid from _default.words where (vector_match(words_index, $vector, 300) AND word is valued) AND catid = 'cat1'"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; NSArray* results = rs.allResults; AssertEqual(results.count, 50); for(CBLQueryResult* result in results){ - AssertEqualObjects([result stringAtIndex: 1], @"cat1"); + AssertEqualObjects([result valueAtIndex: 2], @"cat1"); } - Assert([self checkIndexWasTrained]); } /** @@ -1638,23 +1287,14 @@ - (void) testVectorMatchWithMultipleAndExpression { * 5. Check that a CouchbaseLiteException is returned when creating the query. */ - (void) testInvalidVectorMatchWithOrExpression { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - NSArray* names = [collection indexes: &error]; - Assert([names containsObject: @"words_index"]); - - // Query with OR: - NSString* sql = @"select meta().id, word, catid from _default.words where vector_match(words_index, $vector, 300) OR catid = 'cat1'"; - [self expectError: CBLErrorDomain code: CBLErrorInvalidQuery in: ^BOOL(NSError** err) { - return [self.db createQuery: sql - error: err] != nil; + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; + + [self expectError: CBLErrorDomain code: CBLErrorInvalidQuery in: ^BOOL(NSError **err) { + NSString* sql = [self wordsQueryStringWithLimit: @20 + queryDistance: false + andClause: @"OR catid = 'cat1'"]; + return [self->_wordDB createQuery: sql error: err] != nil; }]; } @@ -1684,31 +1324,14 @@ - (void) testInvalidVectorMatchWithOrExpression { */ - (void) testIndexVectorInBase64 { NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - CBLMutableDocument* doc = [[collection documentWithID: @"word49" error: &error] toMutable]; + CBLMutableDocument* doc = [[self.wordsCollection documentWithID: @"word49" error: &error] toMutable]; [doc setString: kLunchVectorBase64 forKey: @"vector"]; - Assert([collection saveDocument: doc error: &error]); + Assert([self.wordsCollection saveDocument: doc error: &error]); - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + [self createWordsIndexWithConfig: config]; - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector, 20)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - NSString* explain = [q explain: &error]; - Assert([explain rangeOfString: @"SCAN kv_.words:vector:words_index"].location != NSNotFound); - - CBLQueryResultSet* rs = [q execute: &error]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 20); AssertNotNil(wordMap[@"word49"]); @@ -1744,1420 +1367,4 @@ - (void) testHash { AssertEqual(config.encoding.hash, 31872); } -#pragma mark - Lazy Vector Index test -/** - * Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0002-Lazy-Vector-Index.md - */ - -/** - * 1. TestIsLazyDefaultValue - * Description - * Test that isLazy property is false by default. - * Steps - * 1. Create a VectorIndexConfiguration object. - * - expression: "vector" - * - dimensions: 300 - * - centroids: 20 - * 2. Check that isLazy returns false. - */ -- (void) testIsLazyDefaultValue { - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; - AssertEqual(config.isLazy, false); -} - -/** - * 2. TestIsLazyAccessor - * - * Description - * Test that isLazy getter/setter of the VectorIndexConfiguration work as expected. - * - * Steps - * 1. Create a VectorIndexConfiguration object. - * - expression: word - * - dimensions: 300 - * - centroids : 20 - * 2. Set isLazy to true - * 3. Check that isLazy returns true. - */ -- (void) testIsLazyAccessor { - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" - dimensions: 300 - centroids: 20]; - config.isLazy = true; - AssertEqual(config.isLazy, true); -} - -/** - * 3. TestGetNonExistingIndex - * - * Description - * Test that getting non-existing index object by name returning null. - * - * Steps - * 1. Get the default collection from a test database. - * 2. Get a QueryIndex object from the default collection with the name as - * "nonexistingindex". - * 3. Check that the result is null. - */ -// CBL-5864 -- (void) _testGetNonExistingIndex { - NSError* error; - CBLCollection* defaultCollection = [self.db defaultCollection: &error]; - - CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"nonexistingindex" error: &error]; - AssertNil(qIndex); -} - -/** - * 4. TestGetExistingNonVectorIndex - * - * Description - * Test that getting non-existing index object by name returning an index object correctly. - * - * Steps - * 1. Get the default collection from a test database. - * 2. Create a value index named "value_index" in the default collection - * with the expression as "value". - * 3. Get a QueryIndex object from the default collection with the name as - * "value_index". - * 4. Check that the result is not null. - * 5. Check that the QueryIndex's name is "value_index". - * 6. Check that the QueryIndex's collection is the same instance that - * is used for getting the QueryIndex object. - */ -- (void) testGetExistingNonVectorIndex { - NSError* error; - CBLCollection* defaultCollection = [self.db defaultCollection: &error]; - - CBLValueIndexItem* item = [CBLValueIndexItem expression: - [CBLQueryExpression property: @"value"]]; - CBLValueIndex* vIndex = [CBLIndexBuilder valueIndexWithItems: @[item]]; - [defaultCollection createIndex: vIndex name: @"value_index" error: &error]; - AssertNil(error); - - CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"value_index" error: &error]; - AssertEqual(qIndex.name, @"value_index"); - AssertEqual(qIndex.collection, defaultCollection); -} - -/** - * 5. TestGetExistingVectorIndex - * - * Description - * Test that getting an existing index object by name returning an index object correctly. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "vector" - * - dimensions: 300 - * - centroids : 8 - * 3. Get a QueryIndex object from the words collection with the name as "words_index". - * 4. Check that the result is not null. - * 5. Check that the QueryIndex's name is "words_index". - * 6. Check that the QueryIndex's collection is the same instance that is used for - * getting the index. - */ -- (void) testGetExistingVectorIndex { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - AssertNotNil(qIndex); - AssertEqual(qIndex.name, @"words_index"); - AssertEqual(qIndex.collection, collection); -} - -/** - * 6. TestGetIndexOnClosedDatabase - * - * Description - * Test that a CouchbaseLiteException is thrown when getting an index object on a closed database. - * - * Steps - * 1. Get the default collection from a test database. - * 2. Close the test database. - * 3. Get a QueryIndex object from the default collection with the name as "nonexistingindex". - * 4. Check that a CouchbaseLiteException with the code NotOpen is thrown. - */ -- (void) testGetIndexOnClosedDatabase { - NSError* error; - CBLCollection* defaultCollection = [self.db defaultCollection: &error]; - - [self.db close: &error]; - - [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { - return [defaultCollection indexWithName: @"nonexistingindex" error: err] != nil; - }]; -} - -/** - * 7. TestGetIndexOnDeletedCollection - * - * Description - * Test that a CouchbaseLiteException is thrown when getting an index object on a deleted collection. - * - * Steps - * 1. Create a collection named "collA" in the default scope from a test database. - * 2. Delete the collection named "collA" in the default scope from the test database. - * 3. Using the collection object from the step 1, get a QueryIndex object from the default collection - * with the name as "nonexistingindex". - * 4. Check that a CouchbaseLiteException with the code NotOpen is thrown. - */ -- (void) testGetIndexOnDeletedCollection { - NSError* error; - CBLCollection* colA = [self.db createCollectionWithName: @"collA" scope: nil error: &error]; - AssertNil(error); - - [self.db deleteCollectionWithName: @"collA" scope: nil error: &error]; - - [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { - return [colA indexWithName: @"nonexistingindex" error: err] != nil; - }]; -} - -/** - * 8. TestLazyVectorIndexNotAutoUpdatedChangedDocs - * - * Description - * Test that the lazy index is lazy. The index will not be automatically - * updated when the documents are created or updated. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Create an SQL++ query: - * - SELECT word - * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >) - * 4. Execute the query and check that 0 results are returned. - * 5. Update the documents: - * - Create _default.words.word301 with the content from _default.extwords.word1 - * - Update _default.words.word1 with the content from _default.extwords.word3 - * 6. Execute the same query and check that 0 results are returned. - */ -- (void) testLazyVectorIndexNotAutoUpdatedChangedDocs { - NSError* error; - CBLCollection* wordsCollection = [_db collectionWithName: @"words" scope: nil error: &error]; - CBLCollection* extWordsCollection = [_db collectionWithName: @"extwords" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([wordsCollection createIndexWithName: @"words_index" config: config error: &error]); - - // Query: - NSString* sql = @"select meta().id, word from _default.words where vector_match(words_index, $vector)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - CBLQueryResultSet* rs = [q execute: &error]; - NSArray* results = rs.allResults; - AssertEqual(results.count, 0); - - // Update docs: - CBLDocument* extWord1 = [extWordsCollection documentWithID: @"word1" error : &error]; - CBLMutableDocument* word301 = [self createDocument: @"word301" data: [extWord1 toDictionary]]; - Assert([wordsCollection saveDocument: word301 error: &error]); - - CBLDocument* extWord3 = [extWordsCollection documentWithID: @"word3" error : &error]; - CBLMutableDocument* word1 = [[wordsCollection documentWithID: @"word1" error: &error] toMutable]; - [word1 setData: [extWord3 toDictionary]]; - Assert([wordsCollection saveDocument: word1 error: &error]); - - rs = [q execute: &error]; - results = rs.allResults; - AssertEqual(results.count, 0); -} - -/** - * 9. TestLazyVectorIndexAutoUpdateDeletedDocs - * - * Description - * Test that when the lazy vector index automatically update when documents are - * deleted. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. - * 4. Check that the IndexUpdater is not null and IndexUpdater.count = 1. - * 5. With the IndexUpdater object: - * - Get the word string from the IndexUpdater. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index. - * - Call finish() - * 6. Create an SQL++ query: - * - SELECT word - * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) - * 7. Execute the query and check that 1 results are returned. - * 8. Check that the word gotten from the query result is the same as the word in Step 5. - * 9. Delete _default.words.word1 doc. - * 10. Execute the same query as Step again and check that 0 results are returned. - */ -- (void) testLazyVectorIndexAutoUpdateDeletedDocs { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; - AssertNotNil(indexUpdater); - AssertEqual(indexUpdater.count, 1); - - // Update Index: - NSString* word = [indexUpdater stringAtIndex: 0]; - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: 0 error: &error]; - [indexUpdater finishWithError: &error]; - AssertNil(error); - - // Query: - NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - CBLQueryResultSet* rs = [q execute: &error]; - - NSDictionary* wordMap = [self toDocIDWordMap: rs]; - AssertEqual(wordMap.count, 1); - AssertNotNil(wordMap[word]); - - [collection deleteDocument:[collection documentWithID: @"word1" error:&error] error:&error]; - rs = [q execute: &error]; - AssertEqual(rs.allResults.count, 0); -} - -/** - * 10. TestLazyVectorIndexAutoUpdatePurgedDocs - * - * Description - * Test that when the lazy vector index automatically update when documents are - * purged. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. - * 4. Check that the IndexUpdater is not null and IndexUpdater.count = 1. - * 5. With the IndexUpdater object: - * - Get the word string from the IndexUpdater. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index. - * 6. With the IndexUpdater object, call finish() - * 7. Create an SQL++ query: - * - SELECT word - * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) - * 8. Execute the query and check that 1 results are returned. - * 9. Check that the word gotten from the query result is the same as the word in Step 5. - * 10. Purge _default.words.word1 doc. - * 11. Execute the same query as Step again and check that 0 results are returned. - */ -- (void) testLazyVectorIndexAutoUpdatePurgedDocs { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; - AssertNotNil(indexUpdater); - AssertEqual(indexUpdater.count, 1); - - // Update Index: - NSString* word = [indexUpdater stringAtIndex: 0]; - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: 0 error: &error]; - [indexUpdater finishWithError: &error]; - AssertNil(error); - - // Query: - NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - CBLQueryResultSet* rs = [q execute: &error]; - - NSDictionary* wordMap = [self toDocIDWordMap: rs]; - AssertEqual(wordMap.count, 1); - AssertNotNil(wordMap[word]); - - [collection purgeDocumentWithID: @"word1" error: &error]; - rs = [q execute: &error]; - AssertEqual(rs.allResults.count, 0); -} - -/** - * 11. TestIndexUpdaterBeginUpdateOnNonVectorIndex - * - * Description - * Test that a CouchbaseLiteException is thrown when calling beginUpdate on - * a non vector index. - * - * Steps - * 1. Get the default collection from a test database. - * 2. Create a value index named "value_index" in the default collection with the - * expression as "value". - * 3. Get a QueryIndex object from the default collection with the name as - * "value_index". - * 4. Call beginUpdate() with limit 10 on the QueryIndex object. - * 5. Check that a CouchbaseLiteException with the code Unsupported is thrown. - */ -- (void) testIndexUpdaterBeginUpdateOnNonVectorIndex { - NSError* error; - CBLCollection* defaultCollection = [self.db defaultCollection: &error]; - AssertNil(error); - - CBLValueIndexItem* item = [CBLValueIndexItem expression: - [CBLQueryExpression property: @"value"]]; - CBLValueIndex* vIndex = [CBLIndexBuilder valueIndexWithItems: @[item]]; - [defaultCollection createIndex: vIndex name: @"value_index" error: &error]; - - AssertNil(error); - - CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"value_index" error: &error]; - - [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { - return [qIndex beginUpdateWithLimit: 10 error: err] != nil; - }]; -} - -/** - * 12. TestIndexUpdaterBeginUpdateOnNonLazyVectorIndex - * - * Description - * Test that a CouchbaseLiteException is thrown when calling beginUpdate - * on a non lazy vector index. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * 3. Get a QueryIndex object from the words collection with the name as - * "words_index". - * 4. Call beginUpdate() with limit 10 on the QueryIndex object. - * 5. Check that a CouchbaseLiteException with the code Unsupported is thrown. - */ -- (void) testIndexUpdaterBeginUpdateOnNonLazyVectorIndex { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - - [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { - return [qIndex beginUpdateWithLimit: 10 error: err] != nil; - }]; -} - -/** - * 13. TestIndexUpdaterBeginUpdateWithZeroLimit - * - * Description - * Test that an InvalidArgument exception is returned when calling beginUpdate - * with zero limit. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Get a QueryIndex object from the words collec - * "words_index". - * 4. Call beginUpdate() with limit 0 on the QueryIndex object. - * 5. Check that an InvalidArgumentException is thrown. - */ -- (void) testIndexUpdaterBeginUpdateWithZeroLimit { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in: ^BOOL(NSError** err) { - return [qIndex beginUpdateWithLimit: 0 error: err] != nil; - }]; -} - -/** - * 14. TestIndexUpdaterBeginUpdateOnLazyVectorIndex - * - * Description - * Test that calling beginUpdate on a lazy vector index returns an IndexUpdater. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Get a QueryIndex object from the words with the name as "words_index". - * 4. Call beginUpdate() with limit 10 on the QueryIndex object. - * 5. Check that the returned IndexUpdater is not null. - * 6. Check that the IndexUpdater.count is 10. - */ -- (void) testIndexUpdaterBeginUpdateOnLazyVectorIndex { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; - AssertNotNil(indexUpdater); - AssertEqual(indexUpdater.count, 10); -} - -/** - * 15. TestIndexUpdaterGettingValues - * - * Description - * Test all type getters and toArary() from the Array interface. The test - * may be divided this test into multiple tests per type getter as appropriate. - * - * Steps - * 1. Get the default collection from a test database. - * 2. Create the followings documents: - * - doc-0 : { "value": "a string" } - * - doc-1 : { "value": 100 } - * - doc-2 : { "value": 20.8 } - * - doc-3 : { "value": true } - * - doc-4 : { "value": false } - * - doc-5 : { "value": Date("2024-05-10T00:00:00.000Z") } - * - doc-6 : { "value": Blob(Data("I'm Bob")) } - * - doc-7 : { "value": {"name": "Bob"} } - * - doc-8 : { "value": ["one", "two", "three"] } - * - doc-9 : { "value": null } - * 3. Create a vector index named "vector_index" in the default collection. - * - expression: "value" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 4. Get a QueryIndex object from the default collection with the name as - * "vector_index". - * 5. Call beginUpdate() with limit 10 to get an IndexUpdater object. - * 6. Check that the IndexUpdater.count is 10. - * 7. Get string value from each index and check the followings: - * - getString(0) : value == "a string" - * - getString(1) : value == null - * - getString(2) : value == null - * - getString(3) : value == null - * - getString(4) : value == null - * - getString(5) : value == "2024-05-10T00:00:00.000Z" - * - getString(6) : value == null - * - getString(7) : value == null - * - getString(8) : value == null - * - getString(9) : value == null - * 8. Get integer value from each index and check the followings: - * - getInt(0) : value == 0 - * - getInt(1) : value == 100 - * - getInt(2) : value == 20 - * - getInt(3) : value == 1 - * - getInt(4) : value == 0 - * - getInt(5) : value == 0 - * - getInt(6) : value == 0 - * - getInt(7) : value == 0 - * - getInt(8) : value == 0 - * - getInt(9) : value == 0 - * 9. Get float value from each index and check the followings: - * - getFloat(0) : value == 0.0 - * - getFloat(1) : value == 100.0 - * - getFloat(2) : value == 20.8 - * - getFloat(3) : value == 1.0 - * - getFloat(4) : value == 0.0 - * - getFloat(5) : value == 0.0 - * - getFloat(6) : value == 0.0 - * - getFloat(7) : value == 0.0 - * - getFloat(8) : value == 0.0 - * - getFloat(9) : value == 0.0 - * 10. Get double value from each index and check the followings: - * - getDouble(0) : value == 0.0 - * - getDouble(1) : value == 100.0 - * - getDouble(2) : value == 20.8 - * - getDouble(3) : value == 1.0 - * - getDouble(4) : value == 0.0 - * - getDouble(5) : value == 0.0 - * - getDouble(6) : value == 0.0 - * - getDouble(7) : value == 0.0 - * - getDouble(8) : value == 0.0 - * - getDouble(9) : value == 0.0 - * 11. Get boolean value from each index and check the followings: - * - getBoolean(0) : value == true - * - getBoolean(1) : value == true - * - getBoolean(2) : value == true - * - getBoolean(3) : value == true - * - getBoolean(4) : value == false - * - getBoolean(5) : value == true - * - getBoolean(6) : value == true - * - getBoolean(7) : value == true - * - getBoolean(8) : value == true - * - getBoolean(9) : value == false - * 12. Get date value from each index and check the followings: - * - getDate(0) : value == "2024-05-10T00:00:00.000Z" - * - getDate(1) : value == null - * - getDate(2) : value == null - * - getDate(3) : value == null - * - getDate(4) : value == null - * - getDate(5) : value == Date("2024-05-10T00:00:00.000Z") - * - getDate(6) : value == null - * - getDate(7) : value == null - * - getDate(8) : value == null - * - getDate(9) : value == null - * 13. Get blob value from each index and check the followings: - * - getBlob(0) : value == null - * - getBlob(1) : value == null - * - getBlob(2) : value == null - * - getBlob(3) : value == null - * - getBlob(4) : value == null - * - getBlob(5) : value == null - * - getBlob(6) : value == Blob(Data("I'm Bob")) - * - getBlob(7) : value == null - * - getBlob(8) : value == null - * - getBlob(9) : value == null - * 14. Get dictionary object from each index and check the followings: - * - getDictionary(0) : value == null - * - getDictionary(1) : value == null - * - getDictionary(2) : value == null - * - getDictionary(3) : value == null - * - getDictionary(4) : value == null - * - getDictionary(5) : value == null - * - getDictionary(6) : value == null - * - getDictionary(7) : value == Dictionary({"name": "Bob"}) - * - getDictionary(8) : value == null - * - getDictionary(9) : value == null - * 15. Get array object from each index and check the followings: - * - getArray(0) : value == null - * - getArray(1) : value == null - * - getArray(2) : value == null - * - getArray(3) : value == null - * - getArray(4) : value == null - * - getArray(5) : value == null - * - getArray(6) : value == null - * - getArray(7) : value == null - * - getArray(8) : value == Array(["one", "two", "three"]) - * - getArray(9) : value == null - * 16. Get value from each index and check the followings: - * - getValue(0) : value == "a string" - * - getValue(1) : value == PlatformNumber(100) - * - getValue(2) : value == PlatformNumber(20.8) - * - getValue(3) : value == PlatformBoolean(true) - * - getValue(4) : value == PlatformBoolean(false) - * - getValue(5) : value == Date("2024-05-10T00:00:00.000Z") - * - getValue(6) : value == Blob(Data("I'm Bob")) - * - getValue(7) : value == Dictionary({"name": "Bob"}) - * - getValue(8) : value == Array(["one", "two", "three"]) - * - getValue(9) : value == null - * 17. Get IndexUodater values as a platform array by calling toArray() and check - * that the array contains all values as expected. - */ -- (void) testIndexUpdaterGettingValues { - NSError* error; - CBLCollection* defaultCollection = [self.db defaultCollection: &error]; - AssertNil(error); - - CBLMutableDocument* mdoc = [self createDocument: @"doc-0"]; - [mdoc setValue: @"a string" forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-1"]; - [mdoc setValue: @(100) forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-2"]; - [mdoc setValue: @(20.8) forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-3"]; - [mdoc setValue: @(true) forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-4"]; - [mdoc setValue: @(false) forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-5"]; - [mdoc setValue: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"] forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-6"]; - NSData* content = [@"I'm Blob" dataUsingEncoding: NSUTF8StringEncoding]; - CBLBlob* blob = [[CBLBlob alloc] initWithContentType:@"text/plain" data: content]; - [mdoc setValue: blob forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-7"]; - CBLMutableDictionary* dict = [[CBLMutableDictionary alloc] init]; - [dict setValue: @"Bob" forKey: @"name"]; - [mdoc setValue: dict forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-8"]; - CBLMutableArray* array = [[CBLMutableArray alloc] init]; - [array addValue: @"one"]; - [array addValue: @"two"]; - [array addValue: @"three"]; - [mdoc setValue: array forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - mdoc = [self createDocument: @"doc-9"]; - [mdoc setValue: [NSNull null] forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"value" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([defaultCollection createIndexWithName: @"vector_index" config: config error: &error]); - - CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"vector_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; - AssertEqual(indexUpdater.count, 10); - - // String getter - Assert([[indexUpdater stringAtIndex: 0] isEqual: @"a string"]); - AssertEqual([indexUpdater stringAtIndex: 1], nil); - AssertEqual([indexUpdater stringAtIndex: 2], nil); - AssertEqual([indexUpdater stringAtIndex: 3], nil); - AssertEqual([indexUpdater stringAtIndex: 4], nil); - Assert([[indexUpdater stringAtIndex: 5] isEqual: @"2024-05-10T00:00:00.000Z"]); - AssertEqual([indexUpdater stringAtIndex: 6], nil); - AssertEqual([indexUpdater stringAtIndex: 7], nil); - AssertEqual([indexUpdater stringAtIndex: 8], nil); - AssertEqual([indexUpdater stringAtIndex: 9], nil); - - // Int getter - AssertEqual([indexUpdater integerAtIndex: 0], 0); - AssertEqual([indexUpdater integerAtIndex: 1], 100); - AssertEqual([indexUpdater integerAtIndex: 2], 20); - AssertEqual([indexUpdater integerAtIndex: 3], 1); - AssertEqual([indexUpdater integerAtIndex: 4], 0); - AssertEqual([indexUpdater integerAtIndex: 5], 0); - AssertEqual([indexUpdater integerAtIndex: 6], 0); - AssertEqual([indexUpdater integerAtIndex: 7], 0); - AssertEqual([indexUpdater integerAtIndex: 8], 0); - AssertEqual([indexUpdater integerAtIndex: 9], 0); - - // Float getter - AssertEqual([indexUpdater floatAtIndex: 0], 0.0); - AssertEqual([indexUpdater floatAtIndex: 1], 100.0); - AssertEqual([indexUpdater floatAtIndex: 2], (float)20.8); - AssertEqual([indexUpdater floatAtIndex: 3], 1.0); - AssertEqual([indexUpdater floatAtIndex: 4], 0.0); - AssertEqual([indexUpdater floatAtIndex: 5], 0.0); - AssertEqual([indexUpdater floatAtIndex: 6], 0.0); - AssertEqual([indexUpdater floatAtIndex: 7], 0.0); - AssertEqual([indexUpdater floatAtIndex: 8], 0.0); - AssertEqual([indexUpdater floatAtIndex: 9], 0.0); - - // Double getter - AssertEqual([indexUpdater doubleAtIndex: 0], 0.0); - AssertEqual([indexUpdater doubleAtIndex: 1], 100.0); - AssertEqual([indexUpdater doubleAtIndex: 2], 20.8); - AssertEqual([indexUpdater doubleAtIndex: 3], 1.0); - AssertEqual([indexUpdater doubleAtIndex: 4], 0.0); - AssertEqual([indexUpdater doubleAtIndex: 5], 0.0); - AssertEqual([indexUpdater doubleAtIndex: 6], 0.0); - AssertEqual([indexUpdater doubleAtIndex: 7], 0.0); - AssertEqual([indexUpdater doubleAtIndex: 8], 0.0); - AssertEqual([indexUpdater doubleAtIndex: 9], 0.0); - - // Boolean getter - AssertEqual([indexUpdater booleanAtIndex: 0], true); - AssertEqual([indexUpdater booleanAtIndex: 1], true); - AssertEqual([indexUpdater booleanAtIndex: 2], true); - AssertEqual([indexUpdater booleanAtIndex: 3], true); - AssertEqual([indexUpdater booleanAtIndex: 4], false); - AssertEqual([indexUpdater booleanAtIndex: 5], true); - AssertEqual([indexUpdater booleanAtIndex: 6], true); - AssertEqual([indexUpdater booleanAtIndex: 7], true); - AssertEqual([indexUpdater booleanAtIndex: 8], true); - AssertEqual([indexUpdater booleanAtIndex: 9], false); - - // Date getter - AssertEqual([indexUpdater dateAtIndex: 0], nil); - AssertEqual([indexUpdater dateAtIndex: 1], nil); - AssertEqual([indexUpdater dateAtIndex: 2], nil); - AssertEqual([indexUpdater dateAtIndex: 3], nil); - AssertEqual([indexUpdater dateAtIndex: 4], nil); - Assert([[indexUpdater dateAtIndex: 5] isEqual: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"]]); - AssertEqual([indexUpdater dateAtIndex: 6], nil); - AssertEqual([indexUpdater dateAtIndex: 7], nil); - AssertEqual([indexUpdater dateAtIndex: 8], nil); - AssertEqual([indexUpdater dateAtIndex: 9], nil); - - // Blob getter - AssertEqual([indexUpdater blobAtIndex: 0], nil); - AssertEqual([indexUpdater blobAtIndex: 1], nil); - AssertEqual([indexUpdater blobAtIndex: 2], nil); - AssertEqual([indexUpdater blobAtIndex: 3], nil); - AssertEqual([indexUpdater blobAtIndex: 4], nil); - AssertEqual([indexUpdater blobAtIndex: 5], nil); - Assert([[indexUpdater blobAtIndex: 6] isEqual: blob]); - AssertEqual([indexUpdater blobAtIndex: 7], nil); - AssertEqual([indexUpdater blobAtIndex: 8], nil); - AssertEqual([indexUpdater blobAtIndex: 9], nil); - - // Dict getter - AssertEqual([indexUpdater dictionaryAtIndex: 0], nil); - AssertEqual([indexUpdater dictionaryAtIndex: 1], nil); - AssertEqual([indexUpdater dictionaryAtIndex: 2], nil); - AssertEqual([indexUpdater dictionaryAtIndex: 3], nil); - AssertEqual([indexUpdater dictionaryAtIndex: 4], nil); - AssertEqual([indexUpdater dictionaryAtIndex: 5], nil); - AssertEqual([indexUpdater dictionaryAtIndex: 6], nil); - Assert([[indexUpdater dictionaryAtIndex: 7] isEqual: dict]); - AssertEqual([indexUpdater dictionaryAtIndex: 8], nil); - AssertEqual([indexUpdater dictionaryAtIndex: 9], nil); - - // Array getter - AssertEqual([indexUpdater arrayAtIndex: 0], nil); - AssertEqual([indexUpdater arrayAtIndex: 1], nil); - AssertEqual([indexUpdater arrayAtIndex: 2], nil); - AssertEqual([indexUpdater arrayAtIndex: 3], nil); - AssertEqual([indexUpdater arrayAtIndex: 4], nil); - AssertEqual([indexUpdater arrayAtIndex: 5], nil); - AssertEqual([indexUpdater arrayAtIndex: 6], nil); - AssertEqual([indexUpdater arrayAtIndex: 7], nil); - Assert([[indexUpdater arrayAtIndex: 8] isEqual: array]); - AssertEqual([indexUpdater arrayAtIndex: 9], nil); - - // Value getter - Assert([[indexUpdater valueAtIndex: 0] isEqual: @"a string"]); - Assert([[indexUpdater valueAtIndex: 1] isEqual: @(100)]); - Assert([[indexUpdater valueAtIndex: 2] isEqual: @(20.8)]); - Assert([[indexUpdater valueAtIndex: 3] isEqual: @(true)]); - Assert([[indexUpdater valueAtIndex: 4] isEqual: @(false)]); - Assert([[indexUpdater valueAtIndex: 5] isEqual: @"2024-05-10T00:00:00.000Z"]); - Assert([[indexUpdater valueAtIndex: 6] isEqual: blob]); - Assert([[indexUpdater valueAtIndex: 7] isEqual: dict]); - Assert([[indexUpdater valueAtIndex: 8] isEqual: array]); - Assert([[indexUpdater valueAtIndex: 9] isEqual: [NSNull null]]); - - NSArray* trueArray = @[@"a string", @(100), @(20.8), @(true), @(false), @"2024-05-10T00:00:00.000Z", blob, [dict toDictionary], [array toArray], [NSNull null]]; - NSArray* updaterArray = [indexUpdater toArray]; - for (NSUInteger i = 0; i < trueArray.count; i++) { - NSLog(@"Item: %@", updaterArray[i]); - AssertEqualObjects(updaterArray[i], trueArray[i]); - } -} - -/** - * 17. TestIndexUpdaterSetFloatArrayVectors - * - * Description - * Test that setting float array vectors works as expected. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Get a QueryIndex object from the words with the name as "words_index". - * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. - * 5. With the IndexUpdater object, for each index from 0 to 9. - * - Get the word string from the IndexUpdater and store the word string in a set for verifying - * the vector search result. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index. - * 6. With the IndexUpdater object, call finish() - * 7. Execute a vector search query. - * - SELECT word - * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) - * 8. Check that there are 10 words returned. - * 9. Check that the word is in the word set from the step 5. - */ - -- (void) testIndexUpdaterSetFloatArrayVectors { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; - AssertNotNil(indexUpdater); - AssertEqual(indexUpdater.count, 10); - - // Update Index: - NSMutableArray* indexedWords = [NSMutableArray array]; - for(NSUInteger i = 0; i < indexUpdater.count; i++) { - NSString* word = [indexUpdater stringAtIndex: i]; - [indexedWords addObject: word]; - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: i error: &error]; - } - [indexUpdater finishWithError: &error]; - AssertNil(error); - - // Query: - NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - CBLQueryResultSet* rs = [q execute: &error]; - - NSDictionary* wordMap = [self toDocIDWordMap: rs]; - AssertEqual(wordMap.count, 10); - for(NSString* word in indexedWords) { - Assert(wordMap[word]); - } -} - -/** - * 20. TestIndexUpdaterSetInvalidVectorDimensions - * - * Description - * Test thta the vector with the invalid dimenions different from the dimensions - * set to the configuration will not be included in the index. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Get a QueryIndex object from the words with the name as "words_index". - * 4. Call beginUpdate() with limit 1 to get an IndexUpdater object. - * 5. With the IndexUpdater object, call setVector() with a float array as [1.0] - * 6. Check that the setVector throws CouchbaseLiteException with the InvalidParameter error. - */ -// CBL-5814 -- (void) _testIndexUpdaterSetInvalidVectorDimensions { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; - AssertNotNil(indexUpdater); - AssertEqual(indexUpdater.count, 1); - - [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in: ^BOOL(NSError** err) { - return [indexUpdater setVector: @[@1.0] atIndex: 0 error: err]; - }]; -} - -/** - * 21. TestIndexUpdaterSkipVectors - * - * Description - * Test that skipping vectors works as expected. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Get a QueryIndex object from the words with the name as "words_index". - * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. - * 5. With the IndexUpdater object, for each index from 0 - 9. - * - Get the word string from the IndexUpdater. - * - If index % 2 == 0, - * - Store the word string in a skipped word set for verifying the - * skipped words later. - * - Call skipVector at the index. - * - If index % 2 != 0, - * - Store the word string in a indexed word set for verifying the - * vector search result. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index. - * 6. With the IndexUpdater object, call finish() - * 7. Execute a vector search query. - * - SELECT word - * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) - * 8. Check that there are 5 words returned. - * 9. Check that the word is in the indexed word set from the step 5. - * 10. Call beginUpdate() with limit 5 to get an IndexUpdater object. - * 11. With the IndexUpdater object, for each index from 0 - 4. - * - Get the word string from the dictionary for the key named "word". - * - Check that the word is in the skipped word set from the step 5. - */ -// CBL-5842 -- (void) _testIndexUpdaterSkipVectors{ - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; - - AssertNotNil(indexUpdater); - AssertEqual(indexUpdater.count, 10); - - // Update Index: - NSMutableArray* skippedWords = [NSMutableArray array]; - NSMutableArray* indexedWords = [NSMutableArray array]; - for(NSUInteger i = 0; i < indexUpdater.count; i++) { - NSString* word = [indexUpdater stringAtIndex: i]; - if (i % 2 == 0) { - [skippedWords addObject: word]; - [indexUpdater skipVectorAtIndex: i]; - } else { - [indexedWords addObject: word]; - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: i error: &error]; - } - } - [indexUpdater finishWithError: &error]; - AssertNil(error); - AssertEqual(skippedWords.count, 5); - AssertEqual(indexedWords.count, 5); - - // Query: - NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - CBLQueryResultSet* rs = [q execute: &error]; - - NSDictionary* wordMap = [self toDocIDWordMap: rs]; - AssertEqual(wordMap.count, 5); - for(NSString* word in indexedWords) { - Assert(wordMap[word]); - } - - // Update index for the skipped words: - indexUpdater = [qIndex beginUpdateWithLimit: 5 error: &error]; - AssertEqual(indexUpdater.count, 5); - for(NSUInteger i = 0; i < indexUpdater.count; i++) { - NSString* word = [indexUpdater stringAtIndex: i]; - Assert([skippedWords containsObject: word]); - } -} - -/** - * 22. TestIndexUpdaterFinishWithIncompletedUpdate - * - * Description - * Test that a CouchbaseLiteException is thrown when calling finish() on - * an IndexUpdater that has incomplete updated. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Get a QueryIndex object from the words with the name as "words_index". - * 4. Call beginUpdate() with limit 2 to get an IndexUpdater object. - * 5. With the IndexUpdater object, call finish(). - * 6. Check that a CouchbaseLiteException with code UnsupportedOperation is thrown. - * 7. For the index 0, - * - Get the word string from the IndexUpdater. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index. - * 8. With the IndexUpdater object, call finish(). - * 9. Check that a CouchbaseLiteException with code UnsupportedOperation is thrown. - */ -- (void) testIndexUpdaterFinishWithIncompletedUpdate { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 2 error: &error]; - AssertNotNil(indexUpdater); - - [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { - return [indexUpdater finishWithError: err]; - }]; - - NSString* word = [indexUpdater stringAtIndex: 0]; - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: 0 error: &error]; - [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { - return [indexUpdater finishWithError: err]; - }]; -} - -/** - * 23. TestIndexUpdaterCaughtUp - * - * Description - * Test that when the lazy vector index is caught up, calling beginUpdate() to - * get an IndexUpdater will return null. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Call beginUpdate() with limit 100 to get an IndexUpdater object. - * - Get the word string from the IndexUpdater. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index. - * 4. Repeat Step 3 two more times. - * 5. Call beginUpdate() with limit 100 to get an IndexUpdater object. - * 6. Check that the returned IndexUpdater is null. - */ -- (void) testIndexUpdaterCaughtUp { - NSError* error; - CBLIndexUpdater* indexUpdater; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - - // Update index 3 times: - for(NSUInteger i = 0; i < 3; i++) { - indexUpdater = [qIndex beginUpdateWithLimit: 100 error: &error]; - AssertNotNil(indexUpdater); - - for(NSUInteger j = 0; j < indexUpdater.count; j++) { - NSString* word = [indexUpdater stringAtIndex: j]; - NSLog(@"%@", word); - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: j error: &error]; - } - [indexUpdater finishWithError: &error]; - } - - indexUpdater = [qIndex beginUpdateWithLimit: 100 error: &error]; - AssertNil(indexUpdater); -} - -/** - * 24. TestNonFinishedIndexUpdaterNotUpdateIndex - * - * Description - * Test that the index updater can be released without calling finish(), - * and the released non-finished index updater doesn't update the index. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Get a QueryIndex object from the words with the name as "words_index". - * 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. - * 5. With the IndexUpdater object, for each index from 0 - 9. - * - Get the word string from the IndexUpdater. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index. - * 6. Release or close the index updater object. - * 7. Execute a vector search query. - * - SELECT word - * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) - * 8. Check that there are 0 words returned. - */ - -- (void) testNonFinishedIndexUpdaterNotUpdateIndex { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; - - // Update index: - for(NSUInteger i = 0; i < indexUpdater.count; i++) { - NSString* word = [indexUpdater stringAtIndex: i]; - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: i error: &error]; - } - - // "Release" CBLIndexUpdater - indexUpdater = nil; - - // Query: - NSString* sql = @"select word, meta().id from _default.words where vector_match(words_index, $vector, 300)"; - CBLQuery* q = [_db createQuery: sql error: &error]; - AssertNil(error); - - CBLQueryParameters* parameters = [[CBLQueryParameters alloc] init]; - [parameters setValue: kDinnerVector forName: @"vector"]; - [q setParameters: parameters]; - - CBLQueryResultSet* rs = [q execute: &error]; - AssertEqual(rs.allObjects.count, 0); -} - -/** - * 25. TestIndexUpdaterIndexOutOfBounds - * - * Description - * Test that when using getter, setter, and skip function with the index that - * is out of bounds, an IndexOutOfBounds or InvalidArgument exception - * is throws. - * - * Steps - * 1. Get the default collection from a test database. - * 2. Create the followings documents: - * - doc-0 : { "value": "a string" } - * 3. Create a vector index named "vector_index" in the default collection. - * - expression: "value" - * - dimensions: 3 - * - centroids : 8 - * - isLazy : true - * 4. Get a QueryIndex object from the default collection with the name as - * "vector_index". - * 5. Call beginUpdate() with limit 10 to get an IndexUpdater object. - * 6. Check that the IndexUpdater.count is 1. - * 7. Call each getter function with index = -1 and check that - * an IndexOutOfBounds or InvalidArgument exception is thrown. - * 8. Call each getter function with index = 1 and check that - * an IndexOutOfBounds or InvalidArgument exception is thrown. - * 9. Call setVector() function with a vector = [1.0, 2.0, 3.0] and index = -1 and check that - * an IndexOutOfBounds or InvalidArgument exception is thrown. - * 10. Call setVector() function with a vector = [1.0, 2.0, 3.0] and index = 1 and check that - * an IndexOutOfBounds or InvalidArgument exception is thrown. - * 9. Call skipVector() function with index = -1 and check that - * an IndexOutOfBounds or InvalidArgument exception is thrown. - * 10. Call skipVector() function with index = 1 and check that - * an IndexOutOfBounds or InvalidArgument exception is thrown. - */ -- (void) testIndexUpdaterIndexOutOfBounds { - NSError* error; - CBLCollection* defaultCollection = [self.db defaultCollection: &error]; - AssertNil(error); - - CBLMutableDocument* mdoc = [self createDocument: @"doc-0"]; - [mdoc setValue: @"a string" forKey: @"value"]; - [defaultCollection saveDocument: mdoc error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"value" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([defaultCollection createIndexWithName: @"vector_index" config: config error: &error]); - CBLQueryIndex* qIndex = [defaultCollection indexWithName: @"vector_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 10 error: &error]; - AssertEqual(indexUpdater.count, 1); - - for(NSNumber* index in @[@-1, @1]) { - - // This is in line with ArrayProtocol, throws RangeException - [self expectException: @"NSRangeException" in:^{ - [indexUpdater valueAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater stringAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater numberAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater integerAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater doubleAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater floatAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater longLongAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater dateAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater arrayAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater dictionaryAtIndex: [index unsignedIntegerValue]]; - }]; - - [self expectException: @"NSRangeException" in:^{ - [indexUpdater booleanAtIndex: [index unsignedIntegerValue]]; - }]; - - NSArray* array = @[@1.0, @2.0, @3.0]; - [self expectException: @"NSInvalidArgumentException" in:^{ - NSError* outError; - [indexUpdater setVector: array atIndex: [index unsignedIntegerValue] error: &outError]; - - [self expectError: CBLErrorDomain code: CBLErrorInvalidParameter in: ^BOOL(NSError** err) { - return [indexUpdater setVector: array atIndex: [index unsignedIntegerValue] error: err]; - }]; - }]; - - [self expectException: @"NSInvalidArgumentException" in:^{ - [indexUpdater skipVectorAtIndex: [index unsignedIntegerValue]]; - }]; - } -} - -/** - * 26. TestIndexUpdaterCallFinishTwice - * - * Description - * Test that when calling IndexUpdater's finish() after it was finished, - * a CuchbaseLiteException is thrown. - * - * Steps - * 1. Copy database words_db. - * 2. Create a vector index named "words_index" in the _default.words collection. - * - expression: "word" - * - dimensions: 300 - * - centroids : 8 - * - isLazy : true - * 3. Call beginUpdate() with limit 1 to get an IndexUpdater object. - * - Get the word string from the IndexUpdater. - * - Query the vector by word from the _default.words collection. - * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index.. - * 8. Call finish() and check that the finish() is successfully called. - * 9. Call finish() again and check that a CouchbaseLiteException with the code Unsupported is thrown. - */ -// CBL-5843 -- (void) _testIndexUpdaterCallFinishTwice { - NSError* error; - CBLCollection* collection = [_db collectionWithName: @"words" scope: nil error: &error]; - - // Create index - CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"word" - dimensions: 300 - centroids: 8]; - config.isLazy = true; - Assert([collection createIndexWithName: @"words_index" config: config error: &error]); - CBLQueryIndex* qIndex = [collection indexWithName: @"words_index" error: &error]; - CBLIndexUpdater* indexUpdater = [qIndex beginUpdateWithLimit: 1 error: &error]; - - // Update index: - NSString* word = [indexUpdater stringAtIndex: 0]; - NSArray* vector = [self vectorArrayForWord: word collection: collection]; - [indexUpdater setVector: vector atIndex: 0 error: &error]; - [indexUpdater finishWithError: &error]; - AssertNotNil(error); - - [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { - return [indexUpdater finishWithError: err]; - }]; -} - @end diff --git a/Swift/ArrayObject.swift b/Swift/ArrayObject.swift index 19e512c9f..cc4516ca8 100644 --- a/Swift/ArrayObject.swift +++ b/Swift/ArrayObject.swift @@ -52,6 +52,7 @@ public protocol ArrayProtocol: ArrayFragment { func toArray() -> Array + func toJSON() -> String } @@ -64,7 +65,7 @@ public class ArrayObject: ArrayProtocol, Equatable, Hashable, Sequence { } /// Gets the value at the given index. The value types are Blob, ArrayObject, - /// DictionaryObject, Number, or String based on the underlying data type. + /// DictionaryObject, Number, String, or NSNull based on the underlying data type. /// /// - Parameter index: The index. /// - Returns: The value located at the index. @@ -188,14 +189,8 @@ public class ArrayObject: ArrayProtocol, Equatable, Hashable, Sequence { public func toArray() -> Array { var array: [Any] = [] for value in self { - switch value { - case let v as DictionaryObject: - array.append(v.toDictionary()) - case let v as ArrayObject: - array.append(v.toArray()) - default: - array.append(value) - } + let val = DataConverter.toPlainObject(value) + array.append(val != nil ? val! : NSNull()) } return array } diff --git a/Swift/Collection.swift b/Swift/Collection.swift index b35cfa55e..f64c73a35 100644 --- a/Swift/Collection.swift +++ b/Swift/Collection.swift @@ -325,6 +325,20 @@ public final class Collection : CollectionChangeObservable, Indexable, Equatable try impl.deleteIndex(withName: name) } + /// Get an index by name. Return nil if the index doesn't exists. + public func index(withName name: String) throws -> QueryIndex? { + var error: NSError? + let index = impl.index(withName: name, error: &error) + if let err = error { + throw err + } + + guard let indexImpl = index else { + return nil + } + return QueryIndex(indexImpl, collection: self) + } + // MARK: Equatable public static func == (lhs: Collection, rhs: Collection) -> Bool { diff --git a/Swift/CouchbaseLiteSwift.private.modulemap b/Swift/CouchbaseLiteSwift.private.modulemap index 3df29c427..f20bc1c52 100644 --- a/Swift/CouchbaseLiteSwift.private.modulemap +++ b/Swift/CouchbaseLiteSwift.private.modulemap @@ -75,6 +75,7 @@ framework module CouchbaseLiteSwift_Private { header "CBLQueryFunction.h" header "CBLQueryFullTextExpression.h" header "CBLQueryFullTextFunction.h" + header "CBLQueryIndex.h" header "CBLQueryJoin.h" header "CBLQueryLimit.h" header "CBLQueryMeta.h" diff --git a/Swift/DataConverter.swift b/Swift/DataConverter.swift index 08c020bd1..0d9f593f1 100644 --- a/Swift/DataConverter.swift +++ b/Swift/DataConverter.swift @@ -101,4 +101,14 @@ import CouchbaseLiteSwift_Private return result } + static func toPlainObject(_ value: Any?) -> Any? { + switch value { + case let v as DictionaryObject: + return v.toDictionary() + case let v as ArrayObject: + return v.toArray() + default: + return value + } + } } diff --git a/Swift/DictionaryObject.swift b/Swift/DictionaryObject.swift index 87a67d1ce..c112168fe 100644 --- a/Swift/DictionaryObject.swift +++ b/Swift/DictionaryObject.swift @@ -73,8 +73,8 @@ public class DictionaryObject: DictionaryProtocol, Equatable, Hashable, Sequence } /// Gets a property's value. The value types are Blob, ArrayObject, - /// DictionaryObject, Number, or String based on the underlying data type; or nil - /// if the value is nil or the property doesn't exist. + /// DictionaryObject, Number, String or NSNull based on the underlying data type; + /// or nil if the property doesn't exist. /// /// - Parameter key: The key. /// - Returns: The value or nil. diff --git a/Swift/Indexable.swift b/Swift/Indexable.swift index a3ab52785..fa304851f 100644 --- a/Swift/Indexable.swift +++ b/Swift/Indexable.swift @@ -32,5 +32,7 @@ public protocol Indexable { /// Delete an index by name. func deleteIndex(forName name: String) throws - + + /// Return an index object, or nil if the index doesn't exist. + func index(withName name: String) throws -> QueryIndex? } diff --git a/Swift/QueryIndex.swift b/Swift/QueryIndex.swift new file mode 100644 index 000000000..b59325ca4 --- /dev/null +++ b/Swift/QueryIndex.swift @@ -0,0 +1,66 @@ +// +// QueryIndex.swift +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import CouchbaseLiteSwift_Private + +/// QueryIndex object representing an existing index in the collection. +public class QueryIndex { + + /// The collection. + public let collection: Collection + + /// The index name. + public var name: String { impl.name } + +#if COUCHBASE_ENTERPRISE + + /// ENTERPRISE EDITION ONLY + /// + /// For updating lazy vector indexes only. + /// Finds new or updated documents for which vectors need to be (re)computed and + /// return an IndexUpdater object used for setting the computed vectors for updating the index. + /// The limit parameter is for setting the max number of vectors to be computed. + /// - Parameter limit: The limit per update. + /// - Returns: IndexUpdater object if there are updates to be done, or nil if the index is up-to-date. + public func beginUpdate(limit: UInt64) throws -> IndexUpdater? { + var error: NSError? + let updater = impl.beginUpdate(withLimit: limit, error: &error) + if let err = error { + throw err + } + + guard let updaterImpl = updater else { + return nil + } + + return IndexUpdater(updaterImpl) + } +#endif + + // MARK: Internal + + private let impl: CBLQueryIndex + + init(_ impl: CBLQueryIndex, collection: Collection) { + self.impl = impl + self.collection = collection + } + +} diff --git a/Swift/Result.swift b/Swift/Result.swift index c6cdd949c..e23eb05c3 100644 --- a/Swift/Result.swift +++ b/Swift/Result.swift @@ -154,7 +154,12 @@ public final class Result : ArrayProtocol, DictionaryProtocol, Sequence { /// /// - Returns: The Array representing all values. public func toArray() -> Array { - return impl.toArray() + var array: [Any] = [] + for i in 0.. QueryIndex { + let index = try wordsCollection.index(withName: wordsIndexName) + XCTAssertNotNil(index) + return index! + } + + func lazyConfig(_ config: VectorIndexConfiguration) -> VectorIndexConfiguration { + var nuConfig = config + nuConfig.isLazy = true + return nuConfig + } + + func vector(forWord word: String) -> [Float]? { + let model = WordEmbeddingModel(db: wordDB) + if let vector = model.getWordVector(word: word, collection: wordsCollectionName) { + return vector.toArray() as? [Float] + } + if let vector = model.getWordVector(word: word, collection: extWordsCollectionName) { + return vector.toArray() as? [Float] + } + return nil + } + + /// 1. TestIsLazyDefaultValue + /// + /// Description + /// Test that isLazy property is false by default. + /// + /// Steps + /// 1. Create a VectorIndexConfiguration object. + /// - expression: “vector” + /// - dimensions: 300 + /// - centroids : 20 + /// 2. Check that isLazy returns false. + func testIsLazyDefaultValue() throws { + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + XCTAssertFalse(config.isLazy) + } + + /// 2. TestIsLazyAccessor + /// + /// Description + /// Test that isLazy getter/setter of the VectorIndexConfiguration work as expected. + /// + /// Steps + /// 1. Create a VectorIndexConfiguration object. + /// - expression: word + /// - dimensions: 300 + /// - centroids : 20 + /// 2. Set isLazy to true + /// 3. Check that isLazy returns true. + func testIsLazyAccessor() throws { + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) + config.isLazy = true + XCTAssertTrue(config.isLazy) + } + + /// 3. TestGetNonExistingIndex + /// + /// Description + /// Test that getting non-existing index object by name returning null. + /// + /// Steps + /// 1. Get the default collection from a test database. + /// 2. Get a QueryIndex object from the default collection with the name as + /// "nonexistingindex". + /// 3. Check that the result is null. + func testGetNonExistingIndex() throws { + let collection = try db.defaultCollection() + let index = try collection.index(withName: "nonexistingindex") + XCTAssertNil(index) + } + + /// 5. TestGetExistingVectorIndex + /// + /// Description + /// Test that getting an existing index object by name returning an index object correctly. + /// + /// Steps + /// 1. Copy database words_db. + /// 2. Create a vector index named "words_index" in the _default.words collection. + /// - expression: "word" + /// - dimensions: 300 + /// - centroids : 8 + /// 3. Get a QueryIndex object from the words collection with the name as + /// "words_index". + /// 4. Check that the result is not null. + /// 5. Check that the QueryIndex's name is "words_index". + /// 6. Check that the QueryIndex's collection is the same instance that is used for + /// getting the index. + func testGetExistingVectorIndex() throws { + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: config) + let index = try wordsIndex() + XCTAssertEqual(index.name, wordsIndexName) + XCTAssert(index.collection === wordsCollection) + } + + /// 12. TestIndexUpdaterBeginUpdateOnNonLazyVectorIndex + /// + /// Description + /// Test that a CouchbaseLiteException is thrown when calling beginUpdate + /// on a non lazy vector index. + /// + /// Steps + /// 1. Copy database words_db. + /// 2. Create a vector index named "words_index" in the _default.words collecti + /// - expression: "word" + /// - dimensions: 300 + /// - centroids : 8 + /// 3. Get a QueryIndex object from the words collection with the name as + /// "words_index". + /// 4. Call beginUpdate() with limit 10 on the QueryIndex object. + /// 5. Check that a CouchbaseLiteException with the code Unsupported is thrown. + func testIndexUpdaterBeginUpdateOnNonLazyVectorIndex() throws { + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: config) + + let index = try wordsIndex() + + expectError(domain: CBLError.domain, code: CBLError.unsupported) { + _ = try index.beginUpdate(limit: 10) + } + } + + /// 13. TestIndexUpdaterBeginUpdateWithZeroLimit + /// + /// Description + /// Test that an InvalidArgument exception is returned when calling beginUpdate + /// with zero limit. + /// + /// Steps + /// 1. Copy database words_db. + /// 2. Create a vector index named "words_index" in the _default.words collection. + /// - expression: "word" + /// - dimensions: 300 + /// - centroids : 8 + /// - isLazy : true + /// 3. Get a QueryIndex object from the words collection with the name as + /// "words_index". + /// 4. Call beginUpdate() with limit 0 on the QueryIndex object. + /// 5. Check that an InvalidArgumentException is thrown. + func testIndexUpdaterBeginUpdateWithZeroLimit() throws { + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: config) + + let index = try wordsIndex() + + expectExcepion(exception: .invalidArgumentException) { + _ = try? index.beginUpdate(limit: 0) + } + } + + /// 14. TestIndexUpdaterBeginUpdateOnLazyVectorIndex + /// + /// Description + /// Test that calling beginUpdate on a lazy vector index returns an IndexUpdater. + /// + /// Steps + /// 1. Copy database words_db. + /// 2. Create a vector index named "words_index" in the _default.words collection. + /// - expression: "word" + /// - dimensions: 300 + /// - centroids : 8 + /// - isLazy : true + /// 3. Get a QueryIndex object from the words with the name as "words_index". + /// 4. Call beginUpdate() with limit 10 on the QueryIndex object. + /// 5. Check that the returned IndexUpdater is not null. + /// 6. Check that the IndexUpdater.count is 10. + func testIndexUpdaterBeginUpdateOnLazyVectorIndex() throws { + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: lazyConfig(config)) + + let index = try wordsIndex() + let updater = try index.beginUpdate(limit: 10) + XCTAssertNotNil(updater) + XCTAssertEqual(updater?.count, 10) + } + + /// 15. TestIndexUpdaterGettingValues + /// + /// Description + /// Test all type getters and toArary() from the Array interface. The test + /// may be divided this test into multiple tests per type getter as appropriate. + /// + /// Steps + /// 1. Get the default collection from a test database. + /// 2. Create the followings documents: + /// - doc-0 : { "value": "a string" } + /// - doc-1 : { "value": 100 } + /// - doc-2 : { "value": 20.8 } + /// - doc-3 : { "value": true } + /// - doc-4 : { "value": false } + /// - doc-5 : { "value": Date("2024-05-10T00:00:00.000Z") } + /// - doc-6 : { "value": Blob(Data("I'm Bob")) } + /// - doc-7 : { "value": {"name": "Bob"} } + /// - doc-8 : { "value": ["one", "two", "three"] } + /// - doc-9 : { "value": null } + /// 3. Create a vector index named "vector_index" in the default collection. + /// - expression: "value" + /// - dimensions: 300 + /// - centroids : 8 + /// - isLazy : true + /// 4. Get a QueryIndex object from the default collection with the name as + /// "vector_index". + /// 5. Call beginUpdate() with limit 10 to get an IndexUpdater object. + /// 6. Check that the IndexUpdater.count is 10. + /// 7. Get string value from each index and check the followings: + /// - getString(0) : value == "a string" + /// - getString(1) : value == null + /// - getString(2) : value == null + /// - getString(3) : value == null + /// - getString(4) : value == null + /// - getString(5) : value == "2024-05-10T00:00:00.000Z" + /// - getString(6) : value == null + /// - getString(7) : value == null + /// - getString(8) : value == null + /// - getString(9) : value == null + /// 8. Get integer value from each index and check the followings: + /// - getInt(0) : value == 0 + /// - getInt(1) : value == 100 + /// - getInt(2) : value == 20 + /// - getInt(3) : value == 1 + /// - getInt(4) : value == 0 + /// - getInt(5) : value == 0 + /// - getInt(6) : value == 0 + /// - getInt(7) : value == 0 + /// - getInt(8) : value == 0 + /// - getInt(9) : value == 0 + /// 9. Get float value from each index and check the followings: + /// - getFloat(0) : value == 0.0 + /// - getFloat(1) : value == 100.0 + /// - getFloat(2) : value == 20.8 + /// - getFloat(3) : value == 1.0 + /// - getFloat(4) : value == 0.0 + /// - getFloat(5) : value == 0.0 + /// - getFloat(6) : value == 0.0 + /// - getFloat(7) : value == 0.0 + /// - getFloat(8) : value == 0.0 + /// - getFloat(9) : value == 0.0 + /// 10. Get double value from each index and check the followings: + /// - getDouble(0) : value == 0.0 + /// - getDouble(1) : value == 100.0 + /// - getDouble(2) : value == 20.8 + /// - getDouble(3) : value == 1.0 + /// - getDouble(4) : value == 0.0 + /// - getDouble(5) : value == 0.0 + /// - getDouble(6) : value == 0.0 + /// - getDouble(7) : value == 0.0 + /// - getDouble(8) : value == 0.0 + /// - getDouble(9) : value == 0.0 + /// 11. Get boolean value from each index and check the followings: + /// - getBoolean(0) : value == true + /// - getBoolean(1) : value == true + /// - getBoolean(2) : value == true + /// - getBoolean(3) : value == true + /// - getBoolean(4) : value == false + /// - getBoolean(5) : value == true + /// - getBoolean(6) : value == true + /// - getBoolean(7) : value == true + /// - getBoolean(8) : value == true + /// - getBoolean(9) : value == false + /// 12. Get date value from each index and check the followings: + /// - getDate(0) : value == null + /// - getDate(1) : value == null + /// - getDate(2) : value == null + /// - getDate(3) : value == null + /// - getDate(4) : value == null + /// - getDate(5) : value == Date("2024-05-10T00:00:00.000Z") + /// - getDate(6) : value == null + /// - getDate(7) : value == null + /// - getDate(8) : value == null + /// - getDate(9) : value == null + /// 13. Get blob value from each index and check the followings: + /// - getBlob(0) : value == null + /// - getBlob(1) : value == null + /// - getBlob(2) : value == null + /// - getBlob(3) : value == null + /// - getBlob(4) : value == null + /// - getBlob(5) : value == null + /// - getBlob(6) : value == Blob(Data("I'm Bob")) + /// - getBlob(7) : value == null + /// - getBlob(8) : value == null + /// - getBlob(9) : value == null + /// 14. Get dictionary object from each index and check the followings: + /// - getDictionary(0) : value == null + /// - getDictionary(1) : value == null + /// - getDictionary(2) : value == null + /// - getDictionary(3) : value == null + /// - getDictionary(4) : value == null + /// - getDictionary(5) : value == null + /// - getDictionary(6) : value == null + /// - getDictionary(7) : value == Dictionary({"name": "Bob"}) + /// - getDictionary(8) : value == null + /// - getDictionary(9) : value == null + /// 15. Get array object from each index and check the followings: + /// - getArray(0) : value == null + /// - getArray(1) : value == null + /// - getArray(2) : value == null + /// - getArray(3) : value == null + /// - getArray(4) : value == null + /// - getArray(5) : value == null + /// - getArray(6) : value == null + /// - getArray(7) : value == null + /// - getArray(8) : value == Array(["one", "two", "three"]) + /// - getArray(9) : value == null + /// 16. Get value from each index and check the followings: + /// - getValue(0) : value == "a string" + /// - getValue(1) : value == PlatformNumber(100) + /// - getValue(2) : value == PlatformNumber(20.8) + /// - getValue(3) : value == PlatformBoolean(true) + /// - getValue(4) : value == PlatformBoolean(false) + /// - getValue(5) : value == "2024-05-10T00:00:00.000Z" + /// - getValue(6) : value == Blob(Data("I'm Bob")) + /// - getValue(7) : value == PlatformDict({"name": "Bob"}) + /// - getValue(8) : value == PlatformArray(["one", "two", "three"]) + /// - getValue(9) : value == null + /// 17. Get IndexUodater values as a platform array by calling toArray() and check + /// that the array contains all values as expected. + func testIndexUpdaterGettingValues() throws { + let collection = try db.defaultCollection() + + let doc0 = createDocument(data: ["value": "a string"]) + try collection.save(document: doc0) + + let doc1 = createDocument(data: ["value": 100]) + try collection.save(document: doc1) + + let doc2 = createDocument(data: ["value": 20.8]) + try collection.save(document: doc2) + + let doc3 = createDocument(data: ["value": true]) + try collection.save(document: doc3) + + let doc4 = createDocument(data: ["value": false]) + try collection.save(document: doc4) + + let date = dateFromJson("2024-05-10T00:00:00.000Z") + let doc5 = createDocument(data: ["value": date]) + try collection.save(document: doc5) + + let content = "I'm Bob".data(using: .utf8)! + let blob = Blob(contentType: "text/plain", data: content) + let doc6 = createDocument(data: ["value": blob]) + try collection.save(document: doc6) + + let doc7 = createDocument(data: ["value": ["name": "Bob"]]) + try collection.save(document: doc7) + + let doc8 = createDocument(data: ["value": ["one", "two", "three"]]) + try collection.save(document: doc8) + + let doc9 = createDocument(data: ["value": NSNull()]) + try collection.save(document: doc9) + + let config = VectorIndexConfiguration(expression: "value", dimensions: 300, centroids: 8) + try createVectorIndex(collection: collection, name: "vector_index", config: lazyConfig(config)) + + let index = try collection.index(withName: "vector_index") + XCTAssertNotNil(index) + + let indexUpdater = try index!.beginUpdate(limit: 10) + XCTAssertNotNil(indexUpdater) + XCTAssertEqual(indexUpdater!.count, 10) + + let updater = indexUpdater! + + // String: + XCTAssertEqual(updater.string(at: 0), "a string") + XCTAssertNil(updater.string(at: 1)) + XCTAssertNil(updater.string(at: 2)) + XCTAssertNil(updater.string(at: 3)) + XCTAssertNil(updater.string(at: 4)) + XCTAssertEqual(updater.string(at: 5), "2024-05-10T00:00:00.000Z") + XCTAssertNil(updater.string(at: 6)) + XCTAssertNil(updater.string(at: 7)) + XCTAssertNil(updater.string(at: 8)) + XCTAssertNil(updater.string(at: 9)) + + // Int: + XCTAssertEqual(updater.int(at: 0), 0) + XCTAssertEqual(updater.int(at: 1), 100) + XCTAssertEqual(updater.int(at: 2), 20) + XCTAssertEqual(updater.int(at: 3), 1) + XCTAssertEqual(updater.int(at: 4), 0) + XCTAssertEqual(updater.int(at: 5), 0) + XCTAssertEqual(updater.int(at: 6), 0) + XCTAssertEqual(updater.int(at: 7), 0) + XCTAssertEqual(updater.int(at: 8), 0) + XCTAssertEqual(updater.int(at: 9), 0) + + // Int64: + XCTAssertEqual(updater.int64(at: 0), 0) + XCTAssertEqual(updater.int64(at: 1), 100) + XCTAssertEqual(updater.int64(at: 2), 20) + XCTAssertEqual(updater.int64(at: 3), 1) + XCTAssertEqual(updater.int64(at: 4), 0) + XCTAssertEqual(updater.int64(at: 5), 0) + XCTAssertEqual(updater.int64(at: 6), 0) + XCTAssertEqual(updater.int64(at: 7), 0) + XCTAssertEqual(updater.int64(at: 8), 0) + XCTAssertEqual(updater.int64(at: 9), 0) + + // Float: + XCTAssertEqual(updater.float(at: 0), 0.0) + XCTAssertEqual(updater.float(at: 1), 100.0) + XCTAssertEqual(updater.float(at: 2), 20.8) + XCTAssertEqual(updater.float(at: 3), 1.0) + XCTAssertEqual(updater.float(at: 4), 0) + XCTAssertEqual(updater.float(at: 5), 0) + XCTAssertEqual(updater.float(at: 6), 0) + XCTAssertEqual(updater.float(at: 7), 0) + XCTAssertEqual(updater.float(at: 8), 0) + XCTAssertEqual(updater.float(at: 9), 0) + + // Double: + XCTAssertEqual(updater.double(at: 0), 0.0) + XCTAssertEqual(updater.double(at: 1), 100.0) + XCTAssertEqual(updater.double(at: 2), 20.8) + XCTAssertEqual(updater.double(at: 3), 1.0) + XCTAssertEqual(updater.double(at: 4), 0) + XCTAssertEqual(updater.double(at: 5), 0) + XCTAssertEqual(updater.double(at: 6), 0) + XCTAssertEqual(updater.double(at: 7), 0) + XCTAssertEqual(updater.double(at: 8), 0) + XCTAssertEqual(updater.double(at: 9), 0) + + // Boolean: + XCTAssertEqual(updater.boolean(at: 0), true) + XCTAssertEqual(updater.boolean(at: 1), true) + XCTAssertEqual(updater.boolean(at: 2), true) + XCTAssertEqual(updater.boolean(at: 3), true) + XCTAssertEqual(updater.boolean(at: 4), false) + XCTAssertEqual(updater.boolean(at: 5), true) + XCTAssertEqual(updater.boolean(at: 6), true) + XCTAssertEqual(updater.boolean(at: 7), true) + XCTAssertEqual(updater.boolean(at: 8), true) + XCTAssertEqual(updater.boolean(at: 9), false) + + // Date: + XCTAssertNil(updater.date(at: 0)) + XCTAssertNil(updater.date(at: 1)) + XCTAssertNil(updater.date(at: 2)) + XCTAssertNil(updater.date(at: 3)) + XCTAssertNil(updater.date(at: 4)) + let getDate = updater.date(at: 5) + XCTAssertNotNil(getDate) + XCTAssertEqual(jsonFromDate(getDate!), "2024-05-10T00:00:00.000Z") + XCTAssertNil(updater.date(at: 6)) + XCTAssertNil(updater.date(at: 7)) + XCTAssertNil(updater.date(at: 8)) + XCTAssertNil(updater.date(at: 9)) + + // Blob: + XCTAssertNil(updater.blob(at: 0)) + XCTAssertNil(updater.blob(at: 1)) + XCTAssertNil(updater.blob(at: 2)) + XCTAssertNil(updater.blob(at: 3)) + XCTAssertNil(updater.blob(at: 4)) + XCTAssertNil(updater.blob(at: 5)) + let getBlob = updater.blob(at: 6) + XCTAssertNotNil(getBlob) + XCTAssertEqual(getBlob!.content, content) + XCTAssertNil(updater.blob(at: 7)) + XCTAssertNil(updater.blob(at: 8)) + XCTAssertNil(updater.blob(at: 9)) + + // Dict: + XCTAssertNil(updater.dictionary(at: 0)) + XCTAssertNil(updater.dictionary(at: 1)) + XCTAssertNil(updater.dictionary(at: 2)) + XCTAssertNil(updater.dictionary(at: 3)) + XCTAssertNil(updater.dictionary(at: 4)) + XCTAssertNil(updater.dictionary(at: 5)) + XCTAssertNil(updater.dictionary(at: 6)) + let dict = updater.dictionary(at: 7) + XCTAssertNotNil(dict) + XCTAssertTrue(dict! == doc7.dictionary(forKey: "value")) + XCTAssertNil(updater.dictionary(at: 8)) + XCTAssertNil(updater.dictionary(at: 9)) + + // Array: + XCTAssertNil(updater.array(at: 0)) + XCTAssertNil(updater.array(at: 1)) + XCTAssertNil(updater.array(at: 2)) + XCTAssertNil(updater.array(at: 3)) + XCTAssertNil(updater.array(at: 4)) + XCTAssertNil(updater.array(at: 5)) + XCTAssertNil(updater.array(at: 6)) + XCTAssertNil(updater.array(at: 7)) + let array = updater.array(at: 8) + XCTAssertNotNil(array) + XCTAssertTrue(array! == doc8.array(forKey: "value")) + XCTAssertNil(updater.array(at: 9)) + + // value: + XCTAssertTrue(updater.value(at: 0) as? String == "a string") + XCTAssertTrue(updater.value(at: 1) as? Int == 100) + XCTAssertTrue(updater.value(at: 2) as? Double == 20.8) + XCTAssertTrue(updater.value(at: 3) as? Bool == true) + XCTAssertTrue(updater.value(at: 4) as? Bool == false) + XCTAssertTrue(updater.value(at: 5) as? String == "2024-05-10T00:00:00.000Z") + XCTAssertTrue(updater.value(at: 6) as? Blob == blob) + XCTAssertTrue(updater.value(at: 7) as? DictionaryObject == doc7.dictionary(forKey: "value")) + XCTAssertTrue(updater.value(at: 8) as? ArrayObject == doc8.array(forKey: "value")) + XCTAssertTrue(updater.value(at: 9) as? NSNull == NSNull()) + + // toArray + let values = updater.toArray() + XCTAssertTrue(values[0] as? String == "a string") + XCTAssertTrue(values[1] as? Int == 100) + XCTAssertTrue(values[2] as? Double == 20.8) + XCTAssertTrue(values[3] as? Bool == true) + XCTAssertTrue(values[4] as? Bool == false) + XCTAssertTrue(values[5] as? String == "2024-05-10T00:00:00.000Z") + XCTAssertTrue(values[6] as? Blob == blob) + XCTAssertTrue(values[7] as! Dictionary == doc7.dictionary(forKey: "value")!.toDictionary()) + XCTAssertTrue(values[8] as! Array == doc8.array(forKey: "value")!.toArray()) + XCTAssertTrue(values[9] as? NSNull == NSNull()) + + // toJSON + XCTAssertTrue(updater.toJSON().count > 0) + } + + /// 16. TestIndexUpdaterArrayIterator + /// + /// Description + /// Test that iterating the index updater using the platform array iterator + /// interface works as expected. + /// + /// Steps + /// 1. Copy database words_db. + /// 2. Create a vector index named "words_index" in the _default.words collection. + /// - expression: "word" + /// - dimensions: 300 + /// - centroids : 8 + /// - isLazy : true + /// 3. Get a QueryIndex object from the words with the name as "words_index". + /// 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + /// 5. Check that the IndexUpdater.count is 10. + /// 6. Iterate using the platfrom array iterator. + /// 7. For each iteration, check that the value is the same as the value getting + /// from getValue(index). + /// 8. Check that there were 10 iteration calls. + func testIndexUpdaterArrayIterator() throws { + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: lazyConfig(config)) + + let index = try wordsIndex() + let updater = try index.beginUpdate(limit: 10)! + var i = 0 + for value in updater { + XCTAssertEqual(value as? String, updater[i].string) + i = i+1 + } + XCTAssertEqual(i, 10) + } + + /// 17. TestIndexUpdaterSetFloatArrayVectors + /// + /// Description + /// Test that setting float array vectors works as expected. + /// + /// Steps + /// 1. Copy database words_db. + /// 2. Create a vector index named "words_index" in the _default.words collection. + /// - expression: "word" + /// - dimensions: 300 + /// - centroids : 8 + /// - isLazy : true + /// 3. Get a QueryIndex object from the words with the name as "words_index". + /// 4. Call beginUpdate() with limit 10 to get an IndexUpdater object. + /// 5. With the IndexUpdater object, for each index from 0 to 9. + /// - Get the word string from the IndexUpdater and store the word string in a set for verifying + /// the vector search result. + /// - Query the vector by word from the _default.words collection. + /// - Convert the vector result which is an array object to a platform's float array. + /// - Call setVector() with the platform's float array at the index. + /// 6. With the IndexUpdater object, call finish() + /// 7. Execute a vector search query. + /// - SELECT word + /// FROM _default.words + /// WHERE vector_match(words_index, < dinner vector >, 300) + /// 8. Check that there are 10 words returned. + /// 9. Check that the word is in the word set from the step 5. + func testIndexUpdaterSetFloatArrayVectors() throws { + let config = VectorIndexConfiguration(expression: "word", dimensions: 300, centroids: 8) + try createWordsIndex(config: lazyConfig(config)) + + let index = try wordsIndex() + let updater = try index.beginUpdate(limit: 10)! + XCTAssertEqual(updater.count, 10) + + var words: [String] = []; + for i in 0.. [String: String] { var wordMap: [String: String] = [:] for result in rs.allResults() { @@ -64,6 +94,79 @@ class VectorSearchTest: CBLTestCase { return wordMap } + func registerPredictiveModel() throws { + if modelDB == nil { + modelDB = try openDB(name: wordsDatabaseName) + } + + guard let modelDB = self.modelDB else { + XCTFail("Cannot open model DB") + return + } + + let model = WordEmbeddingModel(db: modelDB) + Database.prediction.registerModel(model, withName: wordPredictiveModelName) + } + + func unregisterPredictiveModel() { + Database.prediction.unregisterModel(withName: wordPredictiveModelName) + } + + func createVectorIndex(collection: Collection, name: String, config: VectorIndexConfiguration) throws { + try collection.createIndex(withName: name, config: config) + } + + func createWordsIndex(config: VectorIndexConfiguration) throws { + try wordsCollection.createIndex(withName: wordsIndexName, config: config) + + let names = try wordsCollection.indexes() + XCTAssert(names.contains(wordsIndexName)) + } + + func deleteWordsIndex() throws { + try wordsCollection.deleteIndex(forName: wordsIndexName) + } + + func wordsQueryString(limit: Int?, queryDistance: Bool = false, andExpr: String? = nil) -> String { + var sql = "SELECT meta().id, word, catid" + if (queryDistance) { + sql = sql + ", VECTOR_DISTANCE(\(wordsIndexName)) " + } else { + sql = sql + " " + } + sql = sql + "FROM \(wordsCollectionName) WHERE VECTOR_MATCH(\(wordsIndexName), $vector)" + + if let andExpr = andExpr { + sql = sql + " \(andExpr)" + } + + if let limit = limit { + sql = sql + " LIMIT \(limit)" + } + + return sql; + } + + func executeWordsQuery(limit: Int? = nil, checkTraining: Bool = true, queryDistance: Bool = false, + andExpr: String? = nil) throws -> ResultSet + { + let sql = wordsQueryString(limit: limit, queryDistance: queryDistance, andExpr: andExpr) + let query = try wordDB.createQuery(sql) + + let parameters = Parameters() + parameters.setValue(dinnerVector, forName: "vector") + query.parameters = parameters + + let explain = try query.explain() as NSString + XCTAssertNotEqual(explain.range(of: "kv_.words:vector:words_index").location, NSNotFound) + + let rs = try query.execute() + if (checkTraining) { + XCTAssert(checkIndexWasTrained()) + } + return rs + } + /// 1. TestVectorIndexConfigurationDefaultValue /// Description /// Test that the VectorIndexConfiguration has all default values returned as expected. @@ -133,33 +236,25 @@ class VectorSearchTest: CBLTestCase { /// 1. Create a VectorIndexConfiguration object. /// - expression: "vector" /// - dimensions: 2 and 4096 - /// - centroids: 20 + /// - centroids: 8 /// 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 4097. /// 5. Check that an invalid argument exception is thrown. func testDimensionsValidation() throws { - let collection = try db.collection(name: "words")! - - let config1 = VectorIndexConfiguration(expression: "vector", dimensions: 2, centroids: 20) - 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: 4096, centroids: 20) - try collection.createIndex(withName: "words_index_2", config: config2) + let config1 = VectorIndexConfiguration(expression: "vector", dimensions: 2, centroids: 8) + try wordsCollection.createIndex(withName: "words_index_1", config: config1) - names = try collection.indexes() - XCTAssert(names.contains("words_index_2")) + let config2 = VectorIndexConfiguration(expression: "vector", dimensions: 4096, centroids: 8) + try wordsCollection.createIndex(withName: "words_index_2", config: config2) expectExcepion(exception: .invalidArgumentException) { - _ = VectorIndexConfiguration(expression: "vector", dimensions: 1, centroids: 20) + _ = VectorIndexConfiguration(expression: "vector", dimensions: 1, centroids: 8) } expectExcepion(exception: .invalidArgumentException) { - _ = VectorIndexConfiguration(expression: "vector", dimensions: 4097, centroids: 20) + _ = VectorIndexConfiguration(expression: "vector", dimensions: 4097, centroids: 8) } } @@ -179,19 +274,11 @@ class VectorSearchTest: CBLTestCase { /// 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")) + try wordsCollection.createIndex(withName: "words_index_1", config: config1) 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")) + try wordsCollection.createIndex(withName: "words_index_2", config: config2) expectExcepion(exception: .invalidArgumentException) { _ = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 0) @@ -227,26 +314,11 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) - try collection.createIndex(withName: "words_index", config: config1) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) + try createWordsIndex(config: config1) - let rs = try q.execute() + let rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) } /// 6. TestUpdateVectorIndex @@ -281,29 +353,12 @@ class VectorSearchTest: CBLTestCase { /// - 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")) + try createWordsIndex(config: config) // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 350)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - var rs = try q.execute() + var rs = try executeWordsQuery(limit: 350) XCTAssertEqual(rs.allResults().count, 300) - XCTAssert(checkIndexWasTrained()) // Update docs: let extWord1 = try extWordsCollection.document(id: "word1")! @@ -323,7 +378,8 @@ class VectorSearchTest: CBLTestCase { try wordsCollection.delete(document: wordsCollection.document(id: "word2")!) - rs = try q.execute() + // Query: + rs = try executeWordsQuery(limit: 350) let wordMap: [String: String] = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 301) XCTAssertEqual(wordMap["word301"], word301.string(forKey: "word")) @@ -363,57 +419,43 @@ class VectorSearchTest: CBLTestCase { /// do not include document word5. /// 11. Reset the custom logger. func testCreateVectorIndexWithInvalidVectors() throws { - let collection = try db.collection(name: "words")! - // Update docs: - var auxDoc = try collection.document(id: "word1")!.toMutable() + var auxDoc = try wordsCollection.document(id: "word1")!.toMutable() auxDoc.setArray(nil, forKey: "vector") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - auxDoc = try collection.document(id: "word2")!.toMutable() + auxDoc = try wordsCollection.document(id: "word2")!.toMutable() auxDoc.setString("string", forKey: "vector") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - auxDoc = try collection.document(id: "word3")!.toMutable() + auxDoc = try wordsCollection.document(id: "word3")!.toMutable() auxDoc.removeValue(forKey: "vector") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - auxDoc = try collection.document(id: "word4")!.toMutable() + auxDoc = try wordsCollection.document(id: "word4")!.toMutable() let vector = auxDoc.array(forKey: "vector") vector!.removeValue(at: 0) - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) 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")) + try createWordsIndex(config: config) // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 350)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - var rs = try q.execute() + var rs = try executeWordsQuery(limit: 350) 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() + // Update docs: + auxDoc = try wordsCollection.document(id: "word5")!.toMutable() auxDoc.setString(nil, forKey: "vector") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - rs = try q.execute() + // Query: + rs = try executeWordsQuery(limit: 350) wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 295) XCTAssertNil(wordMap["word5"]) @@ -451,33 +493,14 @@ class VectorSearchTest: CBLTestCase { /// - 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")! - - let modelDb = try openDB(name: databaseName) - let model = WordEmbeddingModel(db: modelDb) - Database.prediction.registerModel(model, withName: "WordEmbedding") + try registerPredictiveModel() let exp = "prediction(WordEmbedding,{\"word\": word}).vector" - let config = VectorIndexConfiguration(expression: exp, dimensions: 300, centroids: 8) - try wordsCollection.createIndex(withName: "words_pred_index", config: config) - - let names = try wordsCollection.indexes() - XCTAssert(names.contains("words_pred_index")) + try createWordsIndex(config: config) // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_pred_index, $vector, 350)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_pred_index").location, NSNotFound) - - var rs = try q.execute() + var rs = try executeWordsQuery(limit: 350) XCTAssertEqual(rs.allResults().count, 300) XCTAssert(checkIndexWasTrained()) @@ -502,7 +525,7 @@ class VectorSearchTest: CBLTestCase { // Delete words.word2 try wordsCollection.delete(document: wordsCollection.document(id: "word2")!) - rs = try q.execute() + rs = try executeWordsQuery(limit: 350) let wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 301) XCTAssertEqual(wordMap["word301"], word301.string(forKey: "word")) @@ -546,49 +569,31 @@ class VectorSearchTest: CBLTestCase { /// 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) - let model = WordEmbeddingModel(db: modelDb) - Database.prediction.registerModel(model, withName: "WordEmbedding") + try registerPredictiveModel() // Update docs: - var auxDoc = try collection.document(id: "word1")!.toMutable() + var auxDoc = try wordsCollection.document(id: "word1")!.toMutable() auxDoc.setArray(nil, forKey: "vector") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - auxDoc = try collection.document(id: "word2")!.toMutable() + auxDoc = try wordsCollection.document(id: "word2")!.toMutable() auxDoc.setString("string", forKey: "vector") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - auxDoc = try collection.document(id: "word3")!.toMutable() + auxDoc = try wordsCollection.document(id: "word3")!.toMutable() auxDoc.removeValue(forKey: "vector") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - auxDoc = try collection.document(id: "word4")!.toMutable() + auxDoc = try wordsCollection.document(id: "word4")!.toMutable() let vector = auxDoc.array(forKey: "vector") vector!.removeValue(at: 0) - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) let exp = "prediction(WordEmbedding,{\"word\": word}).vector" - let config = VectorIndexConfiguration(expression: exp, dimensions: 300, centroids: 8) - try collection.createIndex(withName: "words_pred_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_pred_index")) - - // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_pred_index, $vector, 350)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters + try createWordsIndex(config: config) - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_pred_index").location, NSNotFound) - - var rs = try q.execute() + var rs = try executeWordsQuery(limit: 350) var wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 296) XCTAssertNil(wordMap["word1"]) @@ -597,11 +602,11 @@ class VectorSearchTest: CBLTestCase { XCTAssertNil(wordMap["word4"]) XCTAssert(checkIndexWasTrained()) - auxDoc = try collection.document(id: "word5")!.toMutable() + auxDoc = try wordsCollection.document(id: "word5")!.toMutable() auxDoc.setString("Fried Chicken", forKey: "word") - try collection.save(document: auxDoc) + try wordsCollection.save(document: auxDoc) - rs = try q.execute() + rs = try executeWordsQuery(limit: 350) wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 295) XCTAssertNil(wordMap["word5"]) @@ -632,51 +637,33 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) config.encoding = .scalarQuantizer(type: .SQ4) - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) + try createWordsIndex(config: config) // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - var rs = try q.execute() + var rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) // Repeat using SQ6 resetIndexWasTrainedLog() - try collection.deleteIndex(forName: "words_index") + try deleteWordsIndex() config.encoding = .scalarQuantizer(type: .SQ6) - try collection.createIndex(withName: "words_index", config: config) + try createWordsIndex(config: config) // Rerun query: - rs = try q.execute() + rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) // Repeat using SQ8 resetIndexWasTrainedLog() - try collection.deleteIndex(forName: "words_index") + try deleteWordsIndex() config.encoding = .scalarQuantizer(type: .SQ8) - try collection.createIndex(withName: "words_index", config: config) + try createWordsIndex(config: config) // Rerun query: - rs = try q.execute() + rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) } /// 11. TestCreateVectorIndexWithNoneEncoding @@ -701,28 +688,12 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) config.encoding = .none - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters + try createWordsIndex(config: config) - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() + let rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) } /// FAILED : https://issues.couchbase.com/browse/CBL-5538 @@ -753,35 +724,18 @@ class VectorSearchTest: CBLTestCase { /// 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*/] { // Create vector index 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) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) + try createWordsIndex(config: config) // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() + let rs = try executeWordsQuery(limit: 20, checkTraining: false) XCTAssertEqual(rs.allResults().count, 20) - // will re-enable once we increase the dataset - // XCTAssert(checkIndexWasTrained()) // Delete index - try collection.deleteIndex(forName: "words_index") + try deleteWordsIndex() // Reset log resetIndexWasTrainedLog() @@ -807,32 +761,27 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) config.encoding = .productQuantizer(subquantizers: 2, bits: 8) - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) + try createWordsIndex(config: config) // Step 5: Use valid subquantizer values for numberOfSubq in [3, 4, 5, 6, 10, 12, 15, 20, 25, 30, 50, 60, 75, 100, 150, 300] { - try collection.deleteIndex(forName: "words_index") + try deleteWordsIndex() config.encoding = .productQuantizer(subquantizers: UInt32(numberOfSubq), bits: 8) - try collection.createIndex(withName: "words_index", config: config) + try createWordsIndex(config: config) } // Step 7: Check if exception thrown for wrong subquantizers: for numberOfSubq in [0, 7] { - try collection.deleteIndex(forName: "words_index") + try deleteWordsIndex() config.encoding = .productQuantizer(subquantizers: UInt32(numberOfSubq), bits: 8) expectExcepion(exception: .invalidArgumentException) { - try! collection.createIndex(withName: "words_index", config: config) + try? self.createWordsIndex(config: config) } } } - /// 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. /// @@ -859,30 +808,13 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) config.minTrainingSize = 100 config.maxTrainingSize = 100 - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") + try createWordsIndex(config: config) - let q = try self.db.createQuery(sql) - q.parameters = parameters - - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() + let rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) } /// 15. TestValidateMinMaxTrainingSize @@ -905,23 +837,18 @@ class VectorSearchTest: CBLTestCase { /// - 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")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) config.minTrainingSize = 1 config.maxTrainingSize = 100 - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) + try createWordsIndex(config: config) let trainingSizes: [[UInt32]] = [[0, 0], [0, 100], [10, 9]] for size in trainingSizes { - try collection.deleteIndex(forName: "words_index") + try deleteWordsIndex() config.minTrainingSize = size[0] config.maxTrainingSize = size[1] expectExcepion(exception: .invalidArgumentException) { - try! collection.createIndex(withName: "words_index", config: config) + try? self.createWordsIndex(config: config) } } } @@ -949,29 +876,13 @@ class VectorSearchTest: CBLTestCase { /// queries may be slow” message exists in the log. /// 9. Reset the custom logger. func testQueryUntrainedVectorIndex() throws { - let collection = try db.collection(name: "words")! - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) // out of bounds (300 words in db) config.minTrainingSize = 400 config.maxTrainingSize = 500 - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) + try createWordsIndex(config: config) - // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() + let rs = try executeWordsQuery(limit: 20, checkTraining: false) XCTAssertEqual(rs.allResults().count, 20) XCTAssertFalse(checkIndexWasTrained()) } @@ -999,33 +910,17 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) config.metric = .cosine - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query: - let sql = "select meta().id, word, vector_distance(words_index) from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) + try createWordsIndex(config: config) - let rs = try q.execute() - XCTAssertEqual(rs.allResults().count, 20) - for result in rs.allResults() { + let rs = try executeWordsQuery(limit: 20, queryDistance: true) + let results = rs.allResults() + XCTAssertEqual(results.count, 20) + for result in results { XCTAssert(result.double(at: 3) > 0) - XCTAssert(result.double(at: 3) > 1) + XCTAssert(result.double(at: 3) < 1) } - XCTAssert(checkIndexWasTrained()) } /// 18. TestCreateVectorIndexWithEuclideanDistance @@ -1051,32 +946,15 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) config.metric = .euclidean - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query: - let sql = "select meta().id, word, vector_distance(words_index) from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) + try createWordsIndex(config: config) - let rs = try q.execute() + let rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) for result in rs.allResults() { XCTAssert(result.double(at: 3) > 0) } - XCTAssert(checkIndexWasTrained()) } /// 19. TestCreateVectorIndexWithExistingName @@ -1088,25 +966,23 @@ class VectorSearchTest: CBLTestCase { /// 2. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// 3. Check that the index is created without an error returned. /// 4. Repeat step 2 and check that the index is created without an error returned. /// 5. Create a vector index named "words_index" in _default.words collection. /// - expression: "vectors" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// 6. Check that the index is created without an error returned. func testCreateVectorIndexWithExistingName() throws { - let collection = try db.collection(name: "words")! - // Create and recreate vector index using the same config - let config1 = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) - try collection.createIndex(withName: "words_index", config: config1) - try collection.createIndex(withName: "words_index", config: config1) + let config1 = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: config1) + try createWordsIndex(config: config1) // Recreate index with same name using different config - let config2 = VectorIndexConfiguration(expression: "vectors", dimensions: 300, centroids: 20) - try collection.createIndex(withName: "words_index", config: config2) + let config2 = VectorIndexConfiguration(expression: "vectors", dimensions: 300, centroids: 8) + try createWordsIndex(config: config2) } /// 20. TestDeleteVectorIndex @@ -1135,35 +1011,18 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) - try collection.createIndex(withName: "words_index", config: config) - - var names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters + try createWordsIndex(config: config) - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() + let rs = try executeWordsQuery(limit: 20) XCTAssertEqual(rs.allResults().count, 20) - XCTAssert(checkIndexWasTrained()) - try collection.deleteIndex(forName: "words_index") - names = try collection.indexes() + try deleteWordsIndex() + let names = try wordsCollection.indexes() XCTAssertFalse(names.contains("words_index")) self.expectError(domain: CBLError.domain, code: CBLError.missingIndex) { - _ = try self.db.createQuery(sql) + _ = try self.executeWordsQuery(limit: 20) } } @@ -1180,8 +1039,7 @@ class VectorSearchTest: CBLTestCase { /// 3. Check that a CouchbaseLiteException is returned as the index doesn’t exist. func testVectorMatchOnNonExistingIndex() throws { self.expectError(domain: CBLError.domain, code: CBLError.missingIndex) { - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - _ = try self.db.createQuery(sql) + _ = try self.executeWordsQuery(limit: 20) } } @@ -1207,28 +1065,11 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query: - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters + try createWordsIndex(config: config) - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() + let rs = try executeWordsQuery() XCTAssertEqual(rs.allResults().count, 3) - XCTAssert(checkIndexWasTrained()) } /// 23. TestVectorMatchLimitBoundary @@ -1241,7 +1082,7 @@ class VectorSearchTest: CBLTestCase { /// 2. Create a vector index named "words_index" in _default.words collection. /// - expression: "vector" /// - dimensions: 300 - /// - centroids: 20 + /// - centroids: 8 /// 3. Check that the index is created without an error returned. /// 4. Create an SQL++ query. /// - SELECT meta().id, word @@ -1252,25 +1093,18 @@ class VectorSearchTest: CBLTestCase { /// 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) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: config) // Check valid query with 1 and 10000 set limit for limit in [1, 10000] { - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, \(limit))" - _ = try self.db.createQuery(sql) + _ = try executeWordsQuery(limit: limit) } // Check if error thrown for wrong limit values for limit in [-1, 0, 10001] { self.expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, \(limit)" - _ = try self.db.createQuery(sql) + _ = try self.executeWordsQuery(limit: limit) } } } @@ -1298,31 +1132,15 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query with a single AND: - let sql = "select word, catid from _default.words where vector_match(words_index, $vector, 300) AND catid = 'cat1'" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters + try createWordsIndex(config: config) - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() - XCTAssertEqual(rs.allResults().count, 50) - for result in rs.allResults() { - XCTAssertEqual(result.value(at: 1) as! String, "cat1") + let rs = try executeWordsQuery(limit: 300, andExpr: "AND catid = 'cat1'") + let results = rs.allResults() + XCTAssertEqual(results.count, 50) + for result in results { + XCTAssertEqual(result.value(at: 2) as! String, "cat1") } - XCTAssert(checkIndexWasTrained()) } /// 25. TestVectorMatchWithMultipleAndExpression @@ -1348,31 +1166,15 @@ class VectorSearchTest: CBLTestCase { /// 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: 8) - try collection.createIndex(withName: "words_index", config: config) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) - - // Query with mutiple ANDs: - let sql = "select word, catid from _default.words where (vector_match(words_index, $vector, 300) AND word is valued) AND catid = 'cat1'" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters + try createWordsIndex(config: config) - let explain = try q.explain() as NSString - XCTAssertNotEqual(explain.range(of: "SCAN kv_.words:vector:words_index").location, NSNotFound) - - let rs = try q.execute() - XCTAssertEqual(rs.allResults().count, 50) - for result in rs.allResults() { - XCTAssertEqual(result.value(at: 1) as! String, "cat1") + let rs = try executeWordsQuery(limit: 300, andExpr: "AND word is valued AND catid = 'cat1'") + let results = rs.allResults() + XCTAssertEqual(results.count, 50) + for result in results { + XCTAssertEqual(result.value(at: 2) as! String, "cat1") } - XCTAssert(checkIndexWasTrained()) } /// 26. TestInvalidVectorMatchWithOrExpression @@ -1391,18 +1193,11 @@ class VectorSearchTest: CBLTestCase { /// 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) - - let names = try collection.indexes() - XCTAssert(names.contains("words_index")) + let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + try createWordsIndex(config: config) - // Query with OR: - let sql = "select meta().id, word, catid from _default.words where vector_match(words_index, $vector, 300) OR catid = 'cat1'" self.expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { - _ = try self.db.createQuery(sql) + _ = try self.executeWordsQuery(limit: 300, andExpr: "OR catid = 'cat1'") } } @@ -1427,23 +1222,14 @@ class VectorSearchTest: CBLTestCase { /// 8. Execute the query and check that 20 results are returned. /// 9. Check that the result also contains doc id = word49. func testIndexVectorInBase64() throws { - let collection = try db.collection(name: "words")! - - var doc = try collection.document(id: "word49")!.toMutable() + let doc = try wordsCollection.document(id: "word49")!.toMutable() doc.setString(lunchVectorBase64, forKey: "vector") - try collection.save(document: doc) + try wordsCollection.save(document: doc) let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) - try collection.createIndex(withName: "words_index", config: config) - - let sql = "select meta().id, word from _default.words where vector_match(words_index, $vector, 20)" - let parameters = Parameters() - parameters.setValue(dinnerVector, forName: "vector") - - let q = try self.db.createQuery(sql) - q.parameters = parameters - let rs = try q.execute() + try createWordsIndex(config: config) + let rs = try executeWordsQuery(limit: 20) let wordMap: [String: String] = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 20) XCTAssertNotNil(wordMap["word49"]) From 19d3dbf3ad923b3ce770d9f9e277c5776360d185 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Mon, 24 Jun 2024 10:52:52 -0700 Subject: [PATCH 22/49] CBL-5567 : Implement log replicator heiroglyphics (#3296) * Implemented Replicator log heiroglyphics by using symbols to describe replicator. * Removed ":" that separates log meesage and the replicator heiroglyphics as it seems to be redundant. --- Objective-C/CBLReplicator.mm | 55 ++++++++++++------- .../Replicator/CBLReplicator+Backgrounding.m | 4 +- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Objective-C/CBLReplicator.mm b/Objective-C/CBLReplicator.mm index 7b564e2c5..7bc8f1431 100644 --- a/Objective-C/CBLReplicator.mm +++ b/Objective-C/CBLReplicator.mm @@ -147,7 +147,21 @@ - (void) dealloc { } - (NSString*) description { - return _replicatorID; + if (!_desc) { + BOOL isPull = _config.replicatorType == kCBLReplicatorTypePull || + _config.replicatorType == kCBLReplicatorTypePushAndPull; + BOOL isPush = _config.replicatorType == kCBLReplicatorTypePush || + _config.replicatorType == kCBLReplicatorTypePushAndPull; + + _desc = [NSString stringWithFormat: @"CBLReplicator[%@ (%@%@%@) %@]", + _replicatorID, + isPull ? @"<" : @"", + _config.continuous ? @"*" : @"o", + isPush ? @">" : @"", + _config.target.description + ]; + } + return _desc; } - (void) start { @@ -156,9 +170,9 @@ - (void) start { - (void) startWithReset: (BOOL)reset { CBL_LOCK(self) { - CBLLogInfo(Sync, @"%@: cols=%@ Starting...", self, _config.collections); + CBLLogInfo(Sync, @"%@ Collections[%@] Starting...", self, _config.collections); if (_state != kCBLStateStopped && _state != kCBLStateSuspended) { - CBLWarn(Sync, @"%@: Replicator has already been started (state = %d, status = %d); ignored.", + CBLWarn(Sync, @"%@ Replicator has already been started (state = %d, status = %d); ignored.", self, _state, _rawStatus.level); return; } @@ -182,8 +196,7 @@ - (void) startWithReset: (BOOL)reset { // Failed to create C4Replicator: NSError *error = nil; convertError(err, &error); - CBLWarnError(Sync, @"%@: Replicator cannot be created: %@", - self, error.localizedDescription); + CBLWarnError(Sync, @"%@ Replicator cannot be created: %@", self, error.localizedDescription); status = {kC4Stopped, {}, err}; } [self setProgressLevel: _progressLevel]; @@ -346,12 +359,12 @@ static C4ReplicatorValidationFunction filter(CBLReplicationFilter filter, bool i - (void) stop { CBL_LOCK(self) { if (_state <= kCBLStateStopping) { - CBLWarn(Sync, @"%@: Replicator has been stopped or is stopping (state = %d, status = %d); ignore stop.", + CBLWarn(Sync, @"%@ Replicator has been stopped or is stopping (state = %d, status = %d); ignore stop.", self, _state, _rawStatus.level); return; } - CBLLogInfo(Sync, @"%@: Stopping...", self); + CBLLogInfo(Sync, @"%@ Stopping...", self); _state = kCBLStateStopping; Assert(_repl); @@ -373,7 +386,7 @@ - (void) stopped { [self endBackgroundingMonitor]; #endif - CBLLogInfo(Sync, @"%@: Replicator is now stopped.", self); + CBLLogInfo(Sync, @"%@ Replicator is now stopped.", self); } - (void) safeBlock:(void (^)())block { @@ -388,7 +401,7 @@ - (void) idled { #if TARGET_OS_IPHONE [self endCurrentBackgroundTask]; #endif - CBLLogInfo(Sync, @"%@: Replicator is now idled.", self); + CBLLogInfo(Sync, @"%@ Replicator is now idled.", self); } #pragma mark - Server Certificate @@ -461,7 +474,7 @@ - (void) setProgressLevel: (CBLReplicatorProgressLevel)level { if (_repl) { BOOL success = c4repl_setProgressLevel(_repl, (C4ReplicatorProgressLevel)level, nullptr); assert(success); - CBLLogVerbose(Sync, @"%@: setProgressLevel to LiteCore; level = %d, status = %d", self, level, success); + CBLLogVerbose(Sync, @"%@ Set ProgressLevel to LiteCore; level = %d, status = %d", self, level, success); } } @@ -483,7 +496,7 @@ - (void) setProgressLevel: (CBLReplicatorProgressLevel)level { C4Error err = {}; if (![self _setupC4Replicator: &err]) { convertError(err, error); - CBLWarnError(Sync, @"%@: Replicator cannot be created: %d/%d", self, err.domain, err.code); + CBLWarnError(Sync, @"%@ Replicator cannot be created: %d/%d", self, err.domain, err.code); return nil; } } @@ -492,7 +505,7 @@ - (void) setProgressLevel: (CBLReplicatorProgressLevel)level { C4SliceResult result = c4repl_getPendingDocIDs(_repl, collection.c4spec, &err); if (err.code > 0) { convertError(err, error); - CBLWarnError(Sync, @"Error while fetching pending documentIds: %d/%d", err.domain, err.code); + CBLWarnError(Sync, @"%@ Error while fetching pending documentIds: %d/%d", self, err.domain, err.code); return nil; } @@ -533,7 +546,7 @@ - (BOOL) isDocumentPending: (NSString *)documentID C4Error err = {}; if (![self _setupC4Replicator: &err]) { convertError(err, error); - CBLWarnError(Sync, @"%@: Replicator cannot be created: %d/%d", self, err.domain, err.code); + CBLWarnError(Sync, @"%@ Replicator cannot be created: %d/%d", self, err.domain, err.code); return NO; } } @@ -543,7 +556,7 @@ - (BOOL) isDocumentPending: (NSString *)documentID BOOL isPending = c4repl_isDocumentPending(_repl, docID, collection.c4spec, &err); if (err.code > 0) { convertError(err, error); - CBLWarnError(Sync, @"Error getting document pending status: %d/%d", err.domain, err.code); + CBLWarnError(Sync, @"%@ Error getting document pending status: %d/%d", self, err.domain, err.code); return false; } @@ -564,7 +577,7 @@ - (void) initReachability: (NSURL*)remoteURL { if (!remoteURL || _reachability) return; - CBLLogInfo(Sync, @"%@: Initialize reachability", self); + CBLLogInfo(Sync, @"%@ Initialize reachability", self); NSString* hostname = remoteURL.host; if ([hostname isEqualToString: @"localhost"] || [hostname isEqualToString: @"127.0.0.1"]) return; @@ -583,7 +596,7 @@ - (void) startReachability { // Should be called from _dispatchQueue - (void) stopReachability { if (_reachability.isMonitoring) { - CBLLogInfo(Sync, @"%@: Stopping Reachability ...", self); + CBLLogInfo(Sync, @"%@ Stopping Reachability ...", self); [_reachability stop]; } } @@ -592,7 +605,7 @@ - (void) stopReachability { - (void) reachabilityChanged { CBL_LOCK(self) { if (_reachability.isMonitoring && _reachability.reachable) { - CBLLogInfo(Sync, @"%@: Reachability reported server may now be reachable ...", self); + CBLLogInfo(Sync, @"%@ Reachability reported server may now be reachable ...", self); c4repl_setHostReachable(_repl, _reachability.reachable); } } @@ -611,7 +624,7 @@ static void statusChanged(C4Replicator *repl, C4ReplicatorStatus status, void *c // Called from statusChanged(), on the dispatch queue - (void) c4StatusChanged: (C4ReplicatorStatus)c4Status { CBL_LOCK(self) { - CBLDebug(Sync, @"%@: Received C4ReplicatorStatus Changed, status = %d (state = %d)", + CBLDebug(Sync, @"%@ Received C4ReplicatorStatus Changed, status = %d (state = %d)", self, c4Status.level, _state); // Record raw status: @@ -740,7 +753,7 @@ - (void) postDocumentReplications: (NSArray*)docs pushin - (void) logErrorOnDocument: (CBLReplicatedDocument*)doc pushing: (BOOL)pushing { C4Error c4err = doc.c4Error; if (doc.c4Error.code) - CBLLogInfo(Sync, @"%@: %serror %s '%@': %d/%d", self, (doc.isTransientError ? "transient " : ""), + CBLLogInfo(Sync, @"%@ %serror %s '%@': %d/%d", self, (doc.isTransientError ? "transient " : ""), (pushing ? "pushing" : "pulling"), doc.id, c4err.domain, c4err.code); } @@ -774,7 +787,7 @@ - (void) _resolveConflict: (CBLReplicatedDocument*)doc { } } - CBLLogInfo(Sync, @"%@: Resolve conflicting version of '%@'", self, doc.id); + CBLLogInfo(Sync, @"%@ Resolve conflicting version of '%@'", self, doc.id); CBLCollection* c = [_collectionMap objectForKey: $sprintf(@"%@.%@", doc.scope, doc.collection)]; Assert(c, kCBLErrorMessageCollectionNotFoundDuringConflict); @@ -786,7 +799,7 @@ - (void) _resolveConflict: (CBLReplicatedDocument*)doc { if (![c resolveConflictInDocument: doc.id withConflictResolver: colConfig.conflictResolver error: &error]) { - CBLWarn(Sync, @"%@: Conflict resolution of '%@' failed: %@", self, doc.id, error); + CBLWarn(Sync, @"%@ Conflict resolution of '%@' failed: %@", self, doc.id, error); } [doc updateError: error]; diff --git a/Objective-C/Internal/Replicator/CBLReplicator+Backgrounding.m b/Objective-C/Internal/Replicator/CBLReplicator+Backgrounding.m index e0649bb29..5f3484e65 100644 --- a/Objective-C/Internal/Replicator/CBLReplicator+Backgrounding.m +++ b/Objective-C/Internal/Replicator/CBLReplicator+Backgrounding.m @@ -33,7 +33,7 @@ - (void) startBackgroundingMonitor { return; } - CBLLogInfo(Sync, @"%@: Starting backgrounding monitor...", self); + CBLLogInfo(Sync, @"%@ Starting backgrounding monitor...", self); NSFileProtectionType prot = self.fileProtection; if ([prot isEqual: NSFileProtectionComplete] || [prot isEqual: NSFileProtectionCompleteUnlessOpen]) { @@ -70,7 +70,7 @@ - (void) endBackgroundingMonitor { return; } - CBLLogInfo(Sync, @"%@: Ending backgrounding monitor...", self); + CBLLogInfo(Sync, @"%@ Ending backgrounding monitor...", self); [NSNotificationCenter.defaultCenter removeObserver: self name: UIApplicationProtectedDataWillBecomeUnavailable object: nil]; From 58ad341b2efd3d461c40f924aeef0852908a4f30 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Tue, 25 Jun 2024 10:22:22 -0700 Subject: [PATCH 23/49] CBL-5859 : Allow explicitly enable vector search (#3297) * Implemented Extension.enableVectorSearch in both Objective-C and Swift. * Moved the logic from CBLDatabase.mm to CBLExtension in ee repo. * Used LiteCore 61cfa302d61b8baef2c2cfeeb79c24653e1603a0 temporarily to allow the function to be implemented. --- CouchbaseLite.xcodeproj/project.pbxproj | 16 ++++++++++++++++ Objective-C/CBLDatabase.mm | 14 -------------- Objective-C/CouchbaseLite.h | 1 + Objective-C/Exports/CBL_EE.txt | 1 + Objective-C/Tests/VectorSearchTest.h | 1 - Objective-C/Tests/VectorSearchTest.m | 1 + Swift/Tests/VectorSearchTest.swift | 2 ++ vendor/couchbase-lite-core | 2 +- 8 files changed, 22 insertions(+), 16 deletions(-) diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 73fe69dd7..a3ad64f60 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -366,6 +366,11 @@ 40086B552B803B2B00DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; 40086B572B803B4300DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; 40086B582B803B4E00DA6770 /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; + 400AAFDB2C2A843B00DB6223 /* CBLExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 400AAFCD2C2A843B00DB6223 /* CBLExtension.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 400AAFDC2C2A843B00DB6223 /* CBLExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 400AAFCD2C2A843B00DB6223 /* CBLExtension.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 400AAFDD2C2A843B00DB6223 /* CBLExtension.mm in Sources */ = {isa = PBXBuildFile; fileRef = 400AAFDA2C2A843B00DB6223 /* CBLExtension.mm */; }; + 400AAFDE2C2A843B00DB6223 /* CBLExtension.mm in Sources */ = {isa = PBXBuildFile; fileRef = 400AAFDA2C2A843B00DB6223 /* CBLExtension.mm */; }; + 400AAFE02C2A845E00DB6223 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 400AAFDF2C2A845E00DB6223 /* Extension.swift */; }; 4017E4652BED6E5400A438EE /* CBLContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4017E4572BED6E5400A438EE /* CBLContextManager.h */; }; 4017E4662BED6E5400A438EE /* CBLContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4017E4572BED6E5400A438EE /* CBLContextManager.h */; }; 4017E4672BED6E5400A438EE /* CBLContextManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 4017E4572BED6E5400A438EE /* CBLContextManager.h */; }; @@ -2275,6 +2280,9 @@ 40086B172B7EDDD400DA6770 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 40086B452B803B2A00DA6770 /* CBLBlockConflictResolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLBlockConflictResolver.h; sourceTree = ""; }; 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLBlockConflictResolver.m; sourceTree = ""; }; + 400AAFCD2C2A843B00DB6223 /* CBLExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLExtension.h; sourceTree = ""; }; + 400AAFDA2C2A843B00DB6223 /* CBLExtension.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLExtension.mm; sourceTree = ""; }; + 400AAFDF2C2A845E00DB6223 /* Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; 4017E4572BED6E5400A438EE /* CBLContextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLContextManager.h; sourceTree = ""; }; 4017E4642BED6E5400A438EE /* CBLContextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLContextManager.m; sourceTree = ""; }; 406F8DEA2C26901A000223FC /* QueryIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryIndex.swift; sourceTree = ""; }; @@ -3169,6 +3177,8 @@ 40FC1AA52B9286E700394276 /* CBLDatabaseConfiguration+Encryption.h */, 40FC1AA72B9286E700394276 /* CBLEncryptionKey.h */, 40FC1AA32B9286E700394276 /* CBLEncryptionKey.m */, + 400AAFCD2C2A843B00DB6223 /* CBLExtension.h */, + 400AAFDA2C2A843B00DB6223 /* CBLExtension.mm */, ); path = Database; sourceTree = ""; @@ -3382,6 +3392,7 @@ children = ( 40FC1C3A2B928C1600394276 /* Database+Encryption.swift */, 40FC1C3B2B928C1600394276 /* EncryptionKey.swift */, + 400AAFDF2C2A845E00DB6223 /* Extension.swift */, ); path = Database; sourceTree = ""; @@ -4549,6 +4560,7 @@ 40FC1BD82B928A4F00394276 /* CBLIndexBuilder+Prediction.h in Headers */, 9343EFA9207D611600F19A89 /* CBLDatabase.h in Headers */, 9343EFAA207D611600F19A89 /* CBLQueryExpression+Internal.h in Headers */, + 400AAFDB2C2A843B00DB6223 /* CBLExtension.h in Headers */, 9343EFAB207D611600F19A89 /* CBLAuthenticator+Internal.h in Headers */, 1AAFB671284A260A00878453 /* CBLCollectionChangeObservable.h in Headers */, 9343EFAC207D611600F19A89 /* CBLListenerToken.h in Headers */, @@ -4845,6 +4857,7 @@ 40FC1B842B9288A800394276 /* CBLMessageEndpoint.h in Headers */, 40FC1B742B92889800394276 /* CBLReplicatorConfiguration+ServerCert.h in Headers */, 9343F0F1207D61AB00F19A89 /* CBLSessionAuthenticator.h in Headers */, + 400AAFDC2C2A843B00DB6223 /* CBLExtension.h in Headers */, 1A53B2932966EA2A0010A73E /* CBLQueryFullTextIndexExpressionProtocol.h in Headers */, 9343F0F2207D61AB00F19A89 /* CBLReplicator.h in Headers */, 9343F0F3207D61AB00F19A89 /* CBLQueryVariableExpression.h in Headers */, @@ -6321,6 +6334,7 @@ 9343EF59207D611600F19A89 /* CBLReplicator.mm in Sources */, 40FC1B5F2B9287BD00394276 /* CBLURLEndpointListener.mm in Sources */, 9343EF5A207D611600F19A89 /* CBLBlobStream.mm in Sources */, + 400AAFDD2C2A843B00DB6223 /* CBLExtension.mm in Sources */, 40FC1B542B92873C00394276 /* CBLEncryptionKey.m in Sources */, 69ABB5ED2976A5DF00DA0229 /* CBLDNSService.mm in Sources */, 9343EF5B207D611600F19A89 /* CBLQueryFunction.m in Sources */, @@ -6413,6 +6427,7 @@ 9343F017207D61AB00F19A89 /* Authenticator.swift in Sources */, 93EB261C21DDC34C0006FB88 /* IndexBuilder.swift in Sources */, 9343F018207D61AB00F19A89 /* CBLAuthenticator.m in Sources */, + 400AAFE02C2A845E00DB6223 /* Extension.swift in Sources */, 6932D4A4295478B000D28C18 /* CBLQueryFullTextIndexExpression.m in Sources */, 9343F019207D61AB00F19A89 /* CBLBlobStream.mm in Sources */, 9343F01A207D61AB00F19A89 /* CBLQuerySelectResult.m in Sources */, @@ -6536,6 +6551,7 @@ 1AA2EDFB28A676E100DEB47E /* CBLConflictResolverBridge.m in Sources */, 1AAFB66A284A260A00878453 /* CBLCollectionChange.m in Sources */, 40FC1BF52B928A4F00394276 /* CBLDatabase+Prediction.m in Sources */, + 400AAFDE2C2A843B00DB6223 /* CBLExtension.mm in Sources */, 9343F063207D61AB00F19A89 /* Fragment.swift in Sources */, 9343F064207D61AB00F19A89 /* VariableExpression.swift in Sources */, 9343F065207D61AB00F19A89 /* Query.swift in Sources */, diff --git a/Objective-C/CBLDatabase.mm b/Objective-C/CBLDatabase.mm index 97629a4e0..8cfc9b42f 100644 --- a/Objective-C/CBLDatabase.mm +++ b/Objective-C/CBLDatabase.mm @@ -126,23 +126,9 @@ + (void) CBLInit { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self checkFileLogging]; - [self setExtensionPath]; }); } -/** Detect and set extension path */ -+ (void) setExtensionPath { -#ifdef COUCHBASE_ENTERPRISE - // Note: For non app, the bundle will not be found. - NSBundle* vsBundle = [NSBundle bundleWithIdentifier: kVectorSearchExtIdentifier]; - if (vsBundle.bundlePath) { - CBLLogInfo(Database, @"Set extension path : %@", vsBundle.bundlePath); - CBLStringBytes path(vsBundle.bundlePath); - c4_setExtensionPath(path); - } -#endif -} - /** Check and show warning if file logging is not configured. */ + (void) checkFileLogging { if (!CBLDatabase.log.file.config) { diff --git a/Objective-C/CouchbaseLite.h b/Objective-C/CouchbaseLite.h index ef7d007bd..ffbb0e0fe 100644 --- a/Objective-C/CouchbaseLite.h +++ b/Objective-C/CouchbaseLite.h @@ -111,6 +111,7 @@ FOUNDATION_EXPORT const unsigned char CouchbaseLiteVersionString[]; #import #import #import +#import #import #import #import diff --git a/Objective-C/Exports/CBL_EE.txt b/Objective-C/Exports/CBL_EE.txt index 9a9001559..79ebbef56 100644 --- a/Objective-C/Exports/CBL_EE.txt +++ b/Objective-C/Exports/CBL_EE.txt @@ -23,6 +23,7 @@ .objc_class_name_CBLClientCertificateAuthenticator .objc_class_name_CBLDatabaseEndpoint .objc_class_name_CBLEncryptionKey +.objc_class_name_CBLExtension .objc_class_name_CBLIndexUpdater .objc_class_name_CBLListenerCertificateAuthenticator .objc_class_name_CBLListenerPasswordAuthenticator diff --git a/Objective-C/Tests/VectorSearchTest.h b/Objective-C/Tests/VectorSearchTest.h index 27f166269..3d6d613e5 100644 --- a/Objective-C/Tests/VectorSearchTest.h +++ b/Objective-C/Tests/VectorSearchTest.h @@ -35,7 +35,6 @@ NS_ASSUME_NONNULL_BEGIN #define VECTOR_INDEX_CONFIG(E, D, C) [[CBLVectorIndexConfiguration alloc] initWithExpression: (E) dimensions: (D) centroids: (C)] - @interface VectorSearchTest : CBLTestCase @property (nonatomic, readonly) CBLDatabase* wordDB; diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 324d1b64f..653f0d292 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -63,6 +63,7 @@ - (void) setUp { [super setUp]; NSError* error; + Assert([CBLExtension enableVectorSearch: &error]); CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; config.directory = self.directory; diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index c510d7f53..057684446 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -49,6 +49,8 @@ class VectorSearchTest: CBLTestCase { super.setUp() + try! Extension.enableVectorSearch() + var config = DatabaseConfiguration() config.directory = self.directory let res = ("Support/databases/vectorsearch" as NSString).appendingPathComponent("words_db") diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 8f520c50e..61cfa302d 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 8f520c50e81efe4123971b484a27df7c35c1b134 +Subproject commit 61cfa302d61b8baef2c2cfeeb79c24653e1603a0 From c22c0956159ed58893fbeecbb22866d98aa34c00 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 1 Jul 2024 20:02:24 +0100 Subject: [PATCH 24/49] CBL-5886: Add missing numProbes and correct min/maxTrainingSize default value (#3298) * add numProbes property and fix tests * all VS tests enabled now * LiteCore 3.2.0-210 * update VS to 1.0.0-52 * rename test class names --- CouchbaseLite.xcodeproj/project.pbxproj | 24 ++-- ...ectorIndexTest.m => VectorLazyIndexTest.m} | 24 ++-- Objective-C/Tests/VectorSearchTest.m | 119 +++++++++++------- ...exTest.swift => VectorLazyIndexTest.swift} | 4 +- Swift/Tests/VectorSearchTest.swift | 103 +++++++++------ Tests/Extensions/version.txt | 2 +- vendor/couchbase-lite-core | 2 +- 7 files changed, 166 insertions(+), 112 deletions(-) rename Objective-C/Tests/{LazyVectorIndexTest.m => VectorLazyIndexTest.m} (98%) rename Swift/Tests/{LazyVectorIndexTest.swift => VectorLazyIndexTest.swift} (99%) diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index a3ad64f60..ef62e5511 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -382,12 +382,12 @@ 406F8DEB2C26901A000223FC /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; 406F8DEC2C26901A000223FC /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; 406F8DFB2C27C303000223FC /* IndexUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DF92C27C302000223FC /* IndexUpdater.swift */; }; - 406F8DFF2C27F0A9000223FC /* LazyVectorIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */; }; - 406F8E002C27F0AB000223FC /* LazyVectorIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */; }; + 406F8DFF2C27F0A9000223FC /* VectorLazyIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */; }; + 406F8E002C27F0AB000223FC /* VectorLazyIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */; }; 40AA72952C28938D007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; 40AA72962C28938E007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; - 40AA729D2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */; }; - 40AA729E2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */; }; + 40AA729D2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */; }; + 40AA729E2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */; }; 40C5FD5B2B9947B3004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 40C5FD5C2B9947B9004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; 40EF690C2B7757CF00F0CB50 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 40EF690A2B77564000F0CB50 /* PrivacyInfo.xcprivacy */; }; @@ -2287,13 +2287,13 @@ 4017E4642BED6E5400A438EE /* CBLContextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLContextManager.m; sourceTree = ""; }; 406F8DEA2C26901A000223FC /* QueryIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryIndex.swift; sourceTree = ""; }; 406F8DF92C27C302000223FC /* IndexUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexUpdater.swift; sourceTree = ""; }; - 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyVectorIndexTest.swift; sourceTree = ""; }; + 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorLazyIndexTest.swift; sourceTree = ""; }; 40A789282BE2C7D100CA43A1 /* CBL_EE.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL_EE.exp; sourceTree = ""; }; 40A789292BE2C7D100CA43A1 /* CBL.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL.exp; sourceTree = ""; }; 40A7892B2BE2C7D100CA43A1 /* CBL_EE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL_EE.txt; sourceTree = ""; }; 40A7892C2BE2C7D100CA43A1 /* CBL.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL.txt; sourceTree = ""; }; 40A7892D2BE2C7D100CA43A1 /* generate_exports.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = generate_exports.sh; sourceTree = ""; }; - 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LazyVectorIndexTest.m; sourceTree = ""; }; + 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VectorLazyIndexTest.m; sourceTree = ""; }; 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VectorSearchTest.h; sourceTree = ""; }; 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLVectorIndexTypes.h; sourceTree = ""; }; 40E905462B5B6D9D00EDF483 /* CouchbaseLiteSwift.private.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = CouchbaseLiteSwift.private.modulemap; sourceTree = ""; }; @@ -3079,7 +3079,7 @@ 934608EE247F35CC00CF2F27 /* URLEndpointListenerTest.swift */, 1A13DD3328B8809300BC1084 /* URLEndpointListenerTest+Collection.swift */, AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */, - 406F8DFC2C27F097000223FC /* LazyVectorIndexTest.swift */, + 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */, 1AA91DC522B0356000BF0BDE /* CustomLogger.swift */, 93249D81246B99FD000A8A6E /* iOS */, 939B79241E679017009A70EF /* Info.plist */, @@ -4147,7 +4147,7 @@ 1AA3D77222AB06E10098E16B /* CustomLogger.m */, 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */, AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */, - 40AA72972C28B1A3007FB1E0 /* LazyVectorIndexTest.m */, + 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */, 93DECF3E200DBE5800F44953 /* Support */, 936483AA1E4431C6008D08B3 /* iOS */, 275FF5FA1E3FBD3B005F90DD /* Performance */, @@ -6267,7 +6267,7 @@ 93BB1C9E246BB2BF004FFA00 /* DatabaseTest.swift in Sources */, 93BB1C9C246BB2BB004FFA00 /* CBLTestCase.swift in Sources */, 93BB1CB8246BB2F4004FFA00 /* ReplicatorTest+CustomConflict.swift in Sources */, - 406F8E002C27F0AB000223FC /* LazyVectorIndexTest.swift in Sources */, + 406F8E002C27F0AB000223FC /* VectorLazyIndexTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6694,7 +6694,7 @@ 1A621D7A2887DCFD0017F905 /* QueryTest+Collection.m in Sources */, 93EB25CC21CDD12A0006FB88 /* PredictiveQueryTest.m in Sources */, 1AF555D422948BD90077DF6D /* QueryTest+Main.m in Sources */, - 40AA729D2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */, + 40AA729D2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */, 9343F13D207D61EC00F19A89 /* MigrationTest.m in Sources */, 9343F13E207D61EC00F19A89 /* DocumentTest.m in Sources */, 9343F140207D61EC00F19A89 /* DictionaryTest.m in Sources */, @@ -6748,7 +6748,7 @@ 1AA6744C227924130018CC6D /* QueryTest+Meta.m in Sources */, 934EF8322460D07B0053A47C /* TLSIdentityTest.m in Sources */, 9369A6AF207DD105009B5B83 /* DatabaseEncryptionTest.m in Sources */, - 40AA729E2C28B1A3007FB1E0 /* LazyVectorIndexTest.m in Sources */, + 40AA729E2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */, 9343F176207D633300F19A89 /* DocumentTest.m in Sources */, 1A13DD4228B882A800BC1084 /* URLEndpointListenerTest+Main.m in Sources */, 9388CBAE21BD9187005CA66D /* DocumentExpirationTest.m in Sources */, @@ -6810,7 +6810,7 @@ 9343F198207D636300F19A89 /* CBLTestCase.swift in Sources */, 93C50EB021BDFC7B00C7E980 /* DocumentExpirationTest.swift in Sources */, 9369A6B7207DEB60009B5B83 /* DatabaseEncryptionTest.swift in Sources */, - 406F8DFF2C27F0A9000223FC /* LazyVectorIndexTest.swift in Sources */, + 406F8DFF2C27F0A9000223FC /* VectorLazyIndexTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Objective-C/Tests/LazyVectorIndexTest.m b/Objective-C/Tests/VectorLazyIndexTest.m similarity index 98% rename from Objective-C/Tests/LazyVectorIndexTest.m rename to Objective-C/Tests/VectorLazyIndexTest.m index 2b0576c02..115b43787 100644 --- a/Objective-C/Tests/LazyVectorIndexTest.m +++ b/Objective-C/Tests/VectorLazyIndexTest.m @@ -28,11 +28,11 @@ #define LAZY_VECTOR_INDEX_CONFIG(E, D, C) [self lazyVectorIndexConfigWithExpression: (E) dimensions: (D) centroids: (C)] -@interface LazyVectorIndexTest : VectorSearchTest +@interface VectorLazyIndexTest : VectorSearchTest @end -@implementation LazyVectorIndexTest +@implementation VectorLazyIndexTest - (CBLQueryIndex*) wordsIndex { CBLQueryIndex* index = [self.wordsCollection indexWithName: kWordsIndexName error: nil]; @@ -208,7 +208,7 @@ - (void) testGetExistingVectorIndex { * 3. Create an SQL++ query: * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >) + * WHERE vector_match(words_index, ) * 4. Execute the query and check that 0 results are returned. * 5. Update the documents: * - Create _default.words.word301 with the content from _default.extwords.word1 @@ -261,7 +261,7 @@ - (void) testLazyVectorIndexNotAutoUpdatedChangedDocs { * 6. Create an SQL++ query: * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) + * WHERE vector_match(words_index, ) LIMIT 300 * 7. Execute the query and check that 1 results are returned. * 8. Check that the word gotten from the query result is the same as the word in Step 5. * 9. Delete _default.words.word1 doc. @@ -318,7 +318,7 @@ - (void) testLazyVectorIndexAutoUpdateDeletedDocs { * 7. Create an SQL++ query: * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) + * WHERE vector_match(words_index, ) LIMIT 300 * 8. Execute the query and check that 1 results are returned. * 9. Check that the word gotten from the query result is the same as the word in Step 5. * 10. Purge _default.words.word1 doc. @@ -827,7 +827,7 @@ - (void) testIndexUpdaterGettingValues { * 7. Execute a vector search query. * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) + * WHERE vector_match(words_index, ) LIMIT 300 * 8. Check that there are 10 words returned. * 9. Check that the word is in the word set from the step 5. */ @@ -884,8 +884,7 @@ - (void) testIndexUpdaterSetFloatArrayVectors { * 5. With the IndexUpdater object, call setVector() with a float array as [1.0] * 6. Check that the setVector throws CouchbaseLiteException with the InvalidParameter error. */ -// CBL-5814 -- (void) _testIndexUpdaterSetInvalidVectorDimensions { +- (void) testIndexUpdaterSetInvalidVectorDimensions { [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; NSError* error; @@ -1091,7 +1090,7 @@ - (void) testIndexUpdaterCaughtUp { * 7. Execute a vector search query. * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 300) + * WHERE vector_match(words_index, ) LIMIT 300 * 8. Check that there are 0 words returned. */ @@ -1247,10 +1246,9 @@ - (void) testIndexUpdaterIndexOutOfBounds { * - Convert the vector result which is an array object to a platform's float array. * - Call setVector() with the platform's float array at the index.. * 8. Call finish() and check that the finish() is successfully called. - * 9. Call finish() again and check that a CouchbaseLiteException with the code Unsupported is thrown. + * 9. Call finish() again and check that a CouchbaseLiteException with the code NotOpen is thrown. */ -// CBL-5843 -- (void) _testIndexUpdaterCallFinishTwice { +- (void) testIndexUpdaterCallFinishTwice { [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; NSError* error; @@ -1264,7 +1262,7 @@ - (void) _testIndexUpdaterCallFinishTwice { Assert([updater setVector: vector atIndex: 0 error: &error]); Assert([updater finishWithError: &error]); - [self expectError: CBLErrorDomain code: CBLErrorUnsupported in: ^BOOL(NSError** err) { + [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { return [updater finishWithError: err]; }]; } diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 653f0d292..bd577efcb 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -217,8 +217,8 @@ - (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSNumber*)lim * 2. Get and check the following property values: * - encoding: 8-Bit Scalar Quantizer Encoding * - metric: Euclidean Distance - * - minTrainingSize: 25 * centroids - * - maxTrainingSize: 256 * centroids + * - minTrainingSize: 0 + * - maxTrainingSize: 0 * 3. To check the encoding type, platform code will have to expose some internal * property to the tests for verification. */ @@ -226,8 +226,9 @@ - (void) testVectorIndexConfigurationDefaultValue { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); AssertEqualObjects(config.encoding, [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ8]); AssertEqual(config.metric, kCBLDistanceMetricEuclidean); - AssertEqual(config.minTrainingSize, 25 * config.centroids); - AssertEqual(config.maxTrainingSize, 256 * config.centroids); + AssertEqual(config.minTrainingSize, 0); + AssertEqual(config.maxTrainingSize, 0); + AssertEqual(config.numProbes, 0); } /** @@ -354,7 +355,7 @@ - (void) testCentroidsValidation { * 6. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 20 * 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” @@ -385,7 +386,7 @@ - (void) testCreateVectorIndex { * 5. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 350) + * WHERE vector_match(words_index, ) LIMIT 350 * 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” @@ -454,7 +455,7 @@ - (void) testUpdateVectorIndex { * 6. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 350) + * WHERE vector_match(words_index, ) LIMIT 350 * 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” @@ -528,7 +529,7 @@ - (void) testCreateVectorIndexWithInvalidVectors { * 6. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_pred_index, , 350) + * WHERE vector_match(words_pred_index, ) LIMIT 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 300 results are returned. * 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” @@ -605,7 +606,7 @@ - (void) testCreateVectorIndexUsingPredictionModel { * 7. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_pred_index, , 350) + * WHERE vector_match(words_pred_index, ) LIMIT 350 * 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. @@ -679,7 +680,7 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { * 5. Create an SQL++ query * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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” @@ -734,7 +735,7 @@ - (void) testCreateVectorIndexWithSQ { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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” @@ -751,8 +752,6 @@ - (void) testCreateVectorIndexWithNoneEncoding { } /** - * FAILED : https://issues.couchbase.com/browse/CBL-5538 - * * 12. TestCreateVectorIndexWithPQ * Description * Using the PQ Encoding, test that the vector index can be created and used. The @@ -769,7 +768,7 @@ - (void) testCreateVectorIndexWithNoneEncoding { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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” @@ -852,7 +851,7 @@ - (void) testSubquantizersValidation { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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” @@ -884,11 +883,9 @@ - (void) testeCreateVectorIndexWithFixedTrainingSize { * - minTrainingSize: 1 and maxTrainingSize: 100 * 3. Check that the index is created without an error returned. * 4. Delete the "words_index" - * 5. Repeat Step 2 with the following cases: - * - minTrainingSize = 0 and maxTrainingSize 0 - * - 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. + * 5. Repeat Step 2 with the following case: + * - minTrainingSize = 10 and maxTrainingSize = 9 + * 6. Check that an invalid argument exception was thrown. */ - (void) testValidateMinMaxTrainingSize { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); @@ -896,24 +893,13 @@ - (void) testValidateMinMaxTrainingSize { config.maxTrainingSize = 100; [self createWordsIndexWithConfig: config]; - NSArray *> *trainingSizes = @[ - @[@0, @0], - @[@0, @100], - @[@10, @9] - ]; - + [self deleteWordsIndex]; + config.minTrainingSize = 10; + config.maxTrainingSize = 9; CBLCollection* collection = self.wordsCollection; - for (size_t i = 0; i < trainingSizes.count; i++) { - config.minTrainingSize = trainingSizes[i][0].unsignedIntValue; - config.maxTrainingSize = trainingSizes[i][1].unsignedIntValue; - - // Check if exception thrown for wrong values - [self expectException: NSInvalidArgumentException in:^{ - [collection createIndexWithName: kWordsIndexName config: config error: nil]; - }]; - - [self deleteWordsIndex]; - } + [self expectException: NSInvalidArgumentException in:^{ + [collection createIndexWithName: kWordsIndexName config: config error: nil]; + }]; } /** @@ -933,7 +919,7 @@ - (void) testValidateMinMaxTrainingSize { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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 not trained by checking that the “Untrained index; @@ -967,7 +953,7 @@ - (void) testQueryUntrainedVectorIndex { * 5. Create an SQL++ query. * - SELECT meta().id, word,vector_distance(words_index) * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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 and the vector * distance value is in between 0 – 1.0 inclusively. @@ -1005,7 +991,7 @@ - (void) testCreateVectorIndexWithCosineDistance { * 5. Create an SQL++ query. * - SELECT meta().id, word, vector_distance(words_index) * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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 and the * distance value is more than zero. @@ -1070,7 +1056,7 @@ - (void) testCreateVectorIndexWithExistingName { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 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” @@ -1106,7 +1092,7 @@ - (void) testDeleteVectorIndex { * 2. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , 20) + * WHERE vector_match(words_index, ) LIMIT 20 * 3. Check that a CouchbaseLiteException is returned as the index doesn’t exist. */ - (void) testVectorMatchOnNonExistingIndex { @@ -1163,7 +1149,7 @@ - (void) testVectorMatchDefaultLimit { * 4. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, , ) + * WHERE vector_match(words_index, ) LIMIT * - 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 @@ -1205,7 +1191,7 @@ - (void) testVectorMatchLimitBoundary { * 5. Create an SQL++ query. * - SELECT word, catid * FROM _default.words - * WHERE vector_match(words_index, , 300) AND catid = 'cat1' + * WHERE vector_match(words_index, ) AND catid = 'cat1' LIMIT 300 * 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 @@ -1245,7 +1231,7 @@ - (void) testVectorMatchWithAndExpression { * 5. Create an SQL++ query. * - SELECT word, catid * FROM _default.words - * WHERE (vector_match(words_index, , 300) AND word is valued) AND catid = 'cat1' + * WHERE (vector_match(words_index, ) AND word is valued) AND catid = 'cat1' LIMIT 300 * 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 @@ -1284,7 +1270,7 @@ - (void) testVectorMatchWithMultipleAndExpression { * 4. Create an SQL++ query. * - SELECT word, catid * FROM _default.words - * WHERE vector_match(words_index, , 20) OR catid = 1 + * WHERE vector_match(words_index, ) OR catid = 1 LIMIT 20 * 5. Check that a CouchbaseLiteException is returned when creating the query. */ - (void) testInvalidVectorMatchWithOrExpression { @@ -1319,7 +1305,7 @@ - (void) testInvalidVectorMatchWithOrExpression { * 7. Create an SQL++ query: * - SELECT meta().id, word, vector_distance(words_index) * FROM _default.words - * WHERE vector_match(words_index, < dinner vector >, 20) + * WHERE vector_match(words_index, ) LIMIT 20 * 8. Execute the query and check that 20 results are returned. * 9. Check that the result also contains doc id = word49. */ @@ -1338,6 +1324,45 @@ - (void) testIndexVectorInBase64 { AssertNotNil(wordMap[@"word49"]); } +/** + * 28. TestNumProbes + * + * Description + * Test that the numProces specified is effective. + * Steps + * 1. Copy database words_db. + * 2. Create a vector index named "words_index" in _default.words collection. + * - expression: "vector" + * - dimensions: 300 + * - centroids : 8 + * - numProbes: 5 + * 3. Check that the index is created without an error returned. + * 4. Create an SQL++ query: + * - SELECT meta().id, word + * FROM _default.words + * WHERE vector_match(words_index, ) LIMIT 300 + * 5. Execute the query and check that 20 results are returned. + * 6. Repeat step 2 - 6 but change the numProbes to 1. + * 7. Verify the number of results returned in Step 5 is larger than Step 6. +*/ +- (void) testNumProbes { + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + config.numProbes = 5; + [self createWordsIndexWithConfig: config]; + + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300]; + NSUInteger numResultsFor5Probes = [[rs allObjects] count]; + Assert(numResultsFor5Probes > 0); + + config.numProbes = 1; + [self createWordsIndexWithConfig: config]; + rs = [self executeWordsQueryWithLimit: @300]; + NSUInteger numResultsFor1Probes = [[rs allObjects] count]; + Assert(numResultsFor1Probes > 0); + + Assert(numResultsFor5Probes > numResultsFor1Probes); +} + /* Private methods tests */ diff --git a/Swift/Tests/LazyVectorIndexTest.swift b/Swift/Tests/VectorLazyIndexTest.swift similarity index 99% rename from Swift/Tests/LazyVectorIndexTest.swift rename to Swift/Tests/VectorLazyIndexTest.swift index 812b5abcb..7559ae209 100644 --- a/Swift/Tests/LazyVectorIndexTest.swift +++ b/Swift/Tests/VectorLazyIndexTest.swift @@ -39,7 +39,7 @@ import XCTest * - Test 6 TestGetIndexOnClosedDatabase is done in CollectionTest.testUseCollectionAPIWhenDatabaseIsClosed() * - Test 7 testInvalidCollection) is done in CollectionTest.testUseCollectionAPIOnDeletedCollection() */ -class LazyVectorIndexTest: VectorSearchTest { +class VectorLazyIndexTest: VectorSearchTest { func wordsIndex() throws -> QueryIndex { let index = try wordsCollection.index(withName: wordsIndexName) XCTAssertNotNil(index) @@ -622,7 +622,7 @@ class LazyVectorIndexTest: VectorSearchTest { /// 7. Execute a vector search query. /// - SELECT word /// FROM _default.words - /// WHERE vector_match(words_index, < dinner vector >, 300) + /// WHERE vector_match(words_index, ) LIMIT 300 /// 8. Check that there are 10 words returned. /// 9. Check that the word is in the word set from the step 5. func testIndexUpdaterSetFloatArrayVectors() throws { diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 057684446..167031438 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -180,16 +180,17 @@ class VectorSearchTest: CBLTestCase { /// 2. Get and check the following property values: /// - encoding: 8-Bit Scalar Quantizer Encoding /// - metric: Euclidean Distance - /// - minTrainingSize: 25 * centroids - /// - maxTrainingSize: 256 * centroids + /// - minTrainingSize: 0 + /// - maxTrainingSize: 0 /// 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)); XCTAssertEqual(config.metric, VectorIndexConfiguration.defaultDistanceMetric) - XCTAssertEqual(config.minTrainingSize, 25 * config.centroids) - XCTAssertEqual(config.maxTrainingSize, 256 * config.centroids) + XCTAssertEqual(config.minTrainingSize, 0) + XCTAssertEqual(config.maxTrainingSize, 0) + XCTAssertEqual(config.numProbes, 0) } /// 2. TestVectorIndexConfigurationSettersAndGetters @@ -309,7 +310,7 @@ class VectorSearchTest: CBLTestCase { /// 6. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 20 /// 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” @@ -339,7 +340,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 350) + /// WHERE vector_match(words_index, ) LIMIT 350 /// 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” @@ -410,7 +411,7 @@ class VectorSearchTest: CBLTestCase { /// 6. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 350) + /// WHERE vector_match(words_index, ) LIMIT 350 /// 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” @@ -479,7 +480,7 @@ class VectorSearchTest: CBLTestCase { /// 6. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_pred_index, , 350) + /// WHERE vector_match(words_pred_index, ) LIMIT 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 300 results are returned. /// 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” @@ -560,7 +561,7 @@ class VectorSearchTest: CBLTestCase { /// 7. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_pred_index, , 350) + /// WHERE vector_match(words_pred_index, ) LIMIT 350 /// 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. @@ -630,7 +631,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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” @@ -683,7 +684,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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” @@ -717,7 +718,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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” @@ -803,7 +804,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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” @@ -833,25 +834,20 @@ class VectorSearchTest: CBLTestCase { /// - minTrainingSize: 1 and maxTrainingSize: 100 /// 3. Check that the index is created without an error returned. /// 4. Delete the "words_index" - /// 5. Repeat Step 2 with the following cases: - /// - minTrainingSize = 0 and maxTrainingSize 0 - /// - 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. + /// 5. Repeat Step 2 with the following case: + /// - minTrainingSize = 10 and maxTrainingSize = 9 + /// 6. Check that an invalid argument exception was thrown. func testValidateMinMaxTrainingSize() throws { var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 20) config.minTrainingSize = 1 config.maxTrainingSize = 100 try createWordsIndex(config: config) - let trainingSizes: [[UInt32]] = [[0, 0], [0, 100], [10, 9]] - for size in trainingSizes { - try deleteWordsIndex() - config.minTrainingSize = size[0] - config.maxTrainingSize = size[1] - expectExcepion(exception: .invalidArgumentException) { - try? self.createWordsIndex(config: config) - } + try deleteWordsIndex() + config.minTrainingSize = 10 + config.maxTrainingSize = 9 + expectExcepion(exception: .invalidArgumentException) { + try? self.createWordsIndex(config: config) } } @@ -871,7 +867,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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 not trained by checking that the “Untrained index; @@ -904,7 +900,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word,vector_distance(words_index) /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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 and the vector /// distance value is in between 0 – 1.0 inclusively. @@ -940,7 +936,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word, vector_distance(words_index) /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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 and the /// distance value is more than zero. @@ -1002,7 +998,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 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” @@ -1037,7 +1033,7 @@ class VectorSearchTest: CBLTestCase { /// 2. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, , 20) + /// WHERE vector_match(words_index, ) LIMIT 20 /// 3. Check that a CouchbaseLiteException is returned as the index doesn’t exist. func testVectorMatchOnNonExistingIndex() throws { self.expectError(domain: CBLError.domain, code: CBLError.missingIndex) { @@ -1089,7 +1085,7 @@ 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 /// - 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 @@ -1125,7 +1121,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words - /// WHERE vector_match(words_index, , 300) AND catid = 'cat1' + /// WHERE vector_match(words_index, ) AND catid = 'cat1' LIMIT 300 /// 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 @@ -1159,7 +1155,7 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words - /// WHERE (vector_match(words_index, , 300) AND word is valued) AND catid = 'cat1' + /// WHERE (vector_match(words_index, ) AND word is valued) AND catid = 'cat1' LIMIT 300 /// 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 @@ -1192,7 +1188,7 @@ class VectorSearchTest: CBLTestCase { /// 4. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words - /// WHERE vector_match(words_index, , 20) OR catid = 1 + /// WHERE vector_match(words_index, ) OR catid = 1 LIMIT 20 /// 5. Check that a CouchbaseLiteException is returned when creating the query. func testInvalidVectorMatchWithOrExpression() throws { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) @@ -1220,7 +1216,7 @@ class VectorSearchTest: CBLTestCase { /// 7. Create an SQL++ query: /// - SELECT meta().id, word, vector_distance(words_index) /// FROM _default.words - /// WHERE vector_match(words_index, < dinner vector >, 20) + /// WHERE vector_match(words_index, ) LIMIT 20 /// 8. Execute the query and check that 20 results are returned. /// 9. Check that the result also contains doc id = word49. func testIndexVectorInBase64() throws { @@ -1236,4 +1232,39 @@ class VectorSearchTest: CBLTestCase { XCTAssertEqual(wordMap.count, 20) XCTAssertNotNil(wordMap["word49"]) } + + /// 28. TestNumProbes + /// Description + /// Test that the numProces specified is effective. + /// Steps + /// 1. Copy database words_db. + /// 2. Create a vector index named "words_index" in _default.words collection. + /// - expression: "vector" + /// - dimensions: 300 + /// - centroids : 8 + /// - numProbes: 5 + /// 3. Check that the index is created without an error returned. + /// 4. Create an SQL++ query: + /// - SELECT meta().id, word + /// FROM _default.words + /// WHERE vector_match(words_index, ) LIMIT 300 + /// 5. Execute the query and check that 20 results are returned. + /// 6. Repeat step 2 - 6 but change the numProbes to 1. + /// 7. Verify the number of results returned in Step 5 is larger than Step 6. + func testNumProbes() throws { + var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) + config.numProbes = 5 + try createWordsIndex(config: config) + var rs = try executeWordsQuery(limit: 300) + let numResultsFor5Probes = rs.allResults().count + XCTAssert(numResultsFor5Probes > 0) + + config.numProbes = 1; + try createWordsIndex(config: config) + rs = try executeWordsQuery(limit: 300) + let numResultsFor1Probes = rs.allResults().count + XCTAssert(numResultsFor1Probes > 0) + + XCTAssert(numResultsFor5Probes > numResultsFor1Probes) + } } diff --git a/Tests/Extensions/version.txt b/Tests/Extensions/version.txt index 5d46c9b2a..645ad1271 100644 --- a/Tests/Extensions/version.txt +++ b/Tests/Extensions/version.txt @@ -1 +1 @@ -1.0.0-43 +1.0.0-52 diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 61cfa302d..1b03b5456 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 61cfa302d61b8baef2c2cfeeb79c24653e1603a0 +Subproject commit 1b03b5456c508d431aa4ddfff82bda18f2fd392a From 6cff97b51a95f2378062cadc8c27a4b82c6eebb8 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Tue, 2 Jul 2024 14:56:13 +0100 Subject: [PATCH 25/49] CBL-5932: Update default constants and public symbols (#3299) * update Defaults and exported symbols * fix isLazy --- Objective-C/CBLDefaults.h | 15 ++++++++++++--- Objective-C/CBLDefaults.m | 10 ++++++++-- Objective-C/Exports/CBL_EE.txt | 4 ++++ Objective-C/Exports/Generated/CBL_EE.exp | 5 +++++ Swift/Defaults.swift | 18 +++++++++++++----- 5 files changed, 42 insertions(+), 10 deletions(-) diff --git a/Objective-C/CBLDefaults.h b/Objective-C/CBLDefaults.h index ccf4739de..fbb9f7a44 100644 --- a/Objective-C/CBLDefaults.h +++ b/Objective-C/CBLDefaults.h @@ -33,9 +33,6 @@ /** [NO] Plaintext is not used, and instead binary encoding is used in log files */ extern const BOOL kCBLDefaultLogFileUsePlaintext; -/** [NO] Plaintext is not used, and instead binary encoding is used in log files */ -extern const BOOL kCBLDefaultLogFileUsePlainText __deprecated_msg("Use kCBLDefaultLogFileUsePlaintext instead."); - /** [524288] 512 KiB for the size of a log file */ extern const uint64_t kCBLDefaultLogFileMaxSize; @@ -86,12 +83,24 @@ extern const BOOL kCBLDefaultReplicatorAcceptParentCookies; #pragma mark - CBLVectorIndexConfiguration +/** [YES] Vectors are not lazily indexed, by default */ +extern const BOOL kCBLDefaultVectorIndexIsLazy; + /** [kCBLSQ8] Vectors are encoded by using 8-bit Scalar Quantizer encoding, by default */ extern const CBLScalarQuantizerType kCBLDefaultVectorIndexEncoding; /** [kCBLDistanceMetricEuclidean] By default, vectors are compared using Euclidean metrics */ extern const CBLDistanceMetric kCBLDefaultVectorIndexDistanceMetric; +/** [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters. */ +extern const unsigned int kCBLDefaultVectorIndexMinTrainingSize; + +/** [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters */ +extern const unsigned int kCBLDefaultVectorIndexMaxTrainingSize; + +/** [0] By default, the value will be determined based on the number of centroids. */ +extern const unsigned int kCBLDefaultVectorIndexNumProbes; + #pragma mark - CBLURLEndpointListenerConfiguration /** [0] No port specified, the OS will assign one */ diff --git a/Objective-C/CBLDefaults.m b/Objective-C/CBLDefaults.m index 8373eb6ba..585d188c3 100644 --- a/Objective-C/CBLDefaults.m +++ b/Objective-C/CBLDefaults.m @@ -26,8 +26,6 @@ const BOOL kCBLDefaultLogFileUsePlaintext = NO; -const BOOL kCBLDefaultLogFileUsePlainText = NO; - const uint64_t kCBLDefaultLogFileMaxSize = 524288; const NSInteger kCBLDefaultLogFileMaxRotateCount = 1; @@ -64,10 +62,18 @@ #pragma mark - CBLVectorIndexConfiguration +const BOOL kCBLDefaultVectorIndexIsLazy = NO; + const CBLScalarQuantizerType kCBLDefaultVectorIndexEncoding = kCBLSQ8; const CBLDistanceMetric kCBLDefaultVectorIndexDistanceMetric = kCBLDistanceMetricEuclidean; +const unsigned int kCBLDefaultVectorIndexMinTrainingSize = 0; + +const unsigned int kCBLDefaultVectorIndexMaxTrainingSize = 0; + +const unsigned int kCBLDefaultVectorIndexNumProbes = 0; + #pragma mark - CBLURLEndpointListenerConfiguration const unsigned short kCBLDefaultListenerPort = 0; diff --git a/Objective-C/Exports/CBL_EE.txt b/Objective-C/Exports/CBL_EE.txt index 79ebbef56..15d7b41c6 100644 --- a/Objective-C/Exports/CBL_EE.txt +++ b/Objective-C/Exports/CBL_EE.txt @@ -47,8 +47,12 @@ _kCBLDefaultListenerPort _kCBLDefaultListenerDisableTls _kCBLDefaultListenerReadOnly _kCBLDefaultListenerEnableDeltaSync +_kCBLDefaultVectorIndexIsLazy _kCBLDefaultVectorIndexDistanceMetric _kCBLDefaultVectorIndexEncoding +_kCBLDefaultVectorIndexMinTrainingSize +_kCBLDefaultVectorIndexMaxTrainingSize +_kCBLDefaultVectorIndexNumProbes _kCBLCertAttrCommonName _kCBLCertAttrCountry _kCBLCertAttrEmailAddress diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp index babe2e82d..795be47c5 100644 --- a/Objective-C/Exports/Generated/CBL_EE.exp +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -22,6 +22,7 @@ .objc_class_name_CBLDocumentFragment .objc_class_name_CBLDocumentReplication .objc_class_name_CBLEncryptionKey +.objc_class_name_CBLExtension .objc_class_name_CBLFileLogger .objc_class_name_CBLFragment .objc_class_name_CBLFullTextIndex @@ -130,4 +131,8 @@ _kCBLDefaultReplicatorType _kCBLDefaultScopeName _kCBLDefaultVectorIndexDistanceMetric _kCBLDefaultVectorIndexEncoding +_kCBLDefaultVectorIndexIsLazy +_kCBLDefaultVectorIndexMaxTrainingSize +_kCBLDefaultVectorIndexMinTrainingSize +_kCBLDefaultVectorIndexNumProbes _kCBLTypeProperty diff --git a/Swift/Defaults.swift b/Swift/Defaults.swift index 1adefabca..07ca1f387 100644 --- a/Swift/Defaults.swift +++ b/Swift/Defaults.swift @@ -26,10 +26,6 @@ public extension LogFileConfiguration { /// [false] Plaintext is not used, and instead binary encoding is used in log files static let defaultUsePlaintext: Bool = false - - /// [false] Plaintext is not used, and instead binary encoding is used in log files - /// @available(*, deprecated, message: "Use LogFileConfiguration.defaultUsePlaintext instead.") - static let defaultUsePlainText: Bool = false /// [524288] 512 KiB for the size of a log file static let defaultMaxSize: UInt64 = 524288 @@ -72,7 +68,7 @@ public extension ReplicatorConfiguration { /// [300] Max wait time between retry attempts in seconds /// @available(*, deprecated, message: "Use ReplicatorConfiguration.defaultMaxAttemptsWaitTime instead.") static let defaultMaxAttemptWaitTime: TimeInterval = 300 - + /// [true] Purge documents when a user loses access static let defaultEnableAutoPurge: Bool = true @@ -88,12 +84,24 @@ public extension ReplicatorConfiguration { public extension VectorIndexConfiguration { + /// [false] Vectors are not lazily indexed, by default + static let defaultIsLazy: Bool = false + /// [ScalarQuantizerType.SQ8] Vectors are encoded by using 8-bit Scalar Quantizer encoding, by default static let defaultEncoding: ScalarQuantizerType = ScalarQuantizerType.SQ8 /// [DistanceMetric.euclidean] By default, vectors are compared using Euclidean metrics static let defaultDistanceMetric: DistanceMetric = DistanceMetric.euclidean + /// [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters. + static let defaultMinTrainingSize: UInt32 = 0 + + /// [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters + static let defaultMaxTrainingSize: UInt32 = 0 + + /// [0] By default, the value will be determined based on the number of centroids. + static let defaultNumProbes: UInt32 = 0 + } public extension URLEndpointListenerConfiguration { From 6b109deb3fcf8ae134a1f760d4b8c8fdbaf29bb1 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Tue, 2 Jul 2024 07:37:05 -0700 Subject: [PATCH 26/49] CBL-5928 : CBLErrors.h is not included in the umbrella header (#3301) Made the CBLErrors.h a private header for CBL_Swift and CBL_EE_Swift target. --- CouchbaseLite.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index ef62e5511..4bc558912 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -294,7 +294,7 @@ 273E555D1F79AF69000182F1 /* ArrayTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 93DD9BA71EB419BB00E502A2 /* ArrayTest.m */; }; 273E555E1F79AF79000182F1 /* MiscTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 93384F8B1EB151C100976B41 /* MiscTest.m */; }; 2747666420191BFA007B39D1 /* CBLErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 27476651201912B5007B39D1 /* CBLErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 27476665201946A3007B39D1 /* CBLErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 27476651201912B5007B39D1 /* CBLErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 27476665201946A3007B39D1 /* CBLErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 27476651201912B5007B39D1 /* CBLErrors.h */; settings = {ATTRIBUTES = (Private, ); }; }; 274B4F8121A4D51100B2B4E6 /* CBLQuery+JSON.h in Headers */ = {isa = PBXBuildFile; fileRef = 933BFE1521A3BE960094530D /* CBLQuery+JSON.h */; }; 275229C71E776BC100E630FA /* CBLReplicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 275229C51E776BC100E630FA /* CBLReplicator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 275229C81E776BC100E630FA /* CBLReplicator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 275229C61E776BC100E630FA /* CBLReplicator.mm */; }; @@ -1081,7 +1081,7 @@ 9343F0D7207D61AB00F19A89 /* CBLAuthenticator.h in Headers */ = {isa = PBXBuildFile; fileRef = 937F01DF1EFB269300060D64 /* CBLAuthenticator.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9343F0D8207D61AB00F19A89 /* CBLEndpoint.h in Headers */ = {isa = PBXBuildFile; fileRef = 93DBCFF42004B5FD0017CA83 /* CBLEndpoint.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9343F0D9207D61AB00F19A89 /* CBLQueryResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 9383A5881F1EE8EF0083053D /* CBLQueryResult.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 9343F0DA207D61AB00F19A89 /* CBLErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 27476651201912B5007B39D1 /* CBLErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9343F0DA207D61AB00F19A89 /* CBLErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 27476651201912B5007B39D1 /* CBLErrors.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9343F0DB207D61AB00F19A89 /* CBLQueryExpression.h in Headers */ = {isa = PBXBuildFile; fileRef = 9332080A1E77415E000D9993 /* CBLQueryExpression.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9343F0DC207D61AB00F19A89 /* CBLReplicatorChange.h in Headers */ = {isa = PBXBuildFile; fileRef = 93B41CAE1F04706100A7F114 /* CBLReplicatorChange.h */; settings = {ATTRIBUTES = (Private, ); }; }; 9343F0DD207D61AB00F19A89 /* CBLArray+Swift.h in Headers */ = {isa = PBXBuildFile; fileRef = 938196201EC11CDF0032CC51 /* CBLArray+Swift.h */; settings = {ATTRIBUTES = (Private, ); }; }; From 9cf63e0a5c43e7de11c98c76b5f2af1c540af24c Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Tue, 2 Jul 2024 07:37:22 -0700 Subject: [PATCH 27/49] CBL-5927 : Fix Duplicate CBLQueryIndex Interface Definition (#3300) * Fixed the circular reference b/w CBLCollection and CBLQueryIndex. * Fixed warning in tests. --- CouchbaseLite.xcodeproj/project.pbxproj | 4 ++-- Objective-C/CBLCollection.h | 2 +- Objective-C/CBLCollection.mm | 1 - Objective-C/CBLIndexable.h | 9 ++++++--- Objective-C/CBLQueryIndex.h | 3 ++- Objective-C/Tests/DatabaseTest.m | 2 +- Objective-C/Tests/DocumentTest.m | 4 ++-- Swift/Collection.swift | 1 - 8 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 4bc558912..908d5bc21 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -4325,6 +4325,8 @@ 93FD618A2020757500E7F6A1 /* CBLIndex.m */, 93FD615F20204E3600E7F6A1 /* CBLIndexBuilder.h */, 93FD616020204E3600E7F6A1 /* CBLIndexBuilder.m */, + AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */, + AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */, 93EC42CB1FB3801E00D54BB4 /* CBLValueIndex.h */, 93EC42CC1FB3801E00D54BB4 /* CBLValueIndex.m */, 1A3470BF266F3E7C0042C6BA /* CBLIndexConfiguration.h */, @@ -4333,8 +4335,6 @@ 1A3471482671C87F0042C6BA /* CBLFullTextIndexConfiguration.m */, 1A34715C2671C9230042C6BA /* CBLValueIndexConfiguration.h */, 1A34715D2671C9230042C6BA /* CBLValueIndexConfiguration.m */, - AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */, - AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */, ); name = Index; sourceTree = ""; diff --git a/Objective-C/CBLCollection.h b/Objective-C/CBLCollection.h index 9f633d035..0fa0c8559 100644 --- a/Objective-C/CBLCollection.h +++ b/Objective-C/CBLCollection.h @@ -17,6 +17,7 @@ // limitations under the License. // +#import #import #import #import @@ -27,7 +28,6 @@ @class CBLDocumentFragment; @class CBLMutableDocument; @class CBLScope; -@class CBLQueryIndex; @protocol CBLListenerToken; NS_ASSUME_NONNULL_BEGIN diff --git a/Objective-C/CBLCollection.mm b/Objective-C/CBLCollection.mm index e18dbcced..2e19fa14f 100644 --- a/Objective-C/CBLCollection.mm +++ b/Objective-C/CBLCollection.mm @@ -28,7 +28,6 @@ #import "CBLDocumentChangeNotifier.h" #import "CBLDocument+Internal.h" #import "CBLErrorMessage.h" -#import "CBLIndexable.h" #import "CBLIndexConfiguration+Internal.h" #import "CBLIndex+Internal.h" #import "CBLQueryIndex+Internal.h" diff --git a/Objective-C/CBLIndexable.h b/Objective-C/CBLIndexable.h index 279fc43d8..2fd70f57f 100644 --- a/Objective-C/CBLIndexable.h +++ b/Objective-C/CBLIndexable.h @@ -17,11 +17,14 @@ // limitations under the License. // -#import -#import -#import +#import + +@class CBLIndex; +@class CBLIndexConfiguration; +@class CBLQueryIndex; NS_ASSUME_NONNULL_BEGIN + /** The Indexable interface defines a set of functions for managing the query indexes. */ @protocol CBLIndexable diff --git a/Objective-C/CBLQueryIndex.h b/Objective-C/CBLQueryIndex.h index d539f7114..ffe2c7c5f 100644 --- a/Objective-C/CBLQueryIndex.h +++ b/Objective-C/CBLQueryIndex.h @@ -18,12 +18,13 @@ // #import -#import #ifdef COUCHBASE_ENTERPRISE #import #endif +@class CBLCollection; + NS_ASSUME_NONNULL_BEGIN /** diff --git a/Objective-C/Tests/DatabaseTest.m b/Objective-C/Tests/DatabaseTest.m index 643db0784..70d715421 100644 --- a/Objective-C/Tests/DatabaseTest.m +++ b/Objective-C/Tests/DatabaseTest.m @@ -2242,7 +2242,7 @@ - (void) testCopyToExistingDatabase { Assert(nudb, @"Cannot open the new database: %@", error); [self expectError: NSPOSIXErrorDomain code: EEXIST in: ^BOOL(NSError** error2) { - return [CBLDatabase copyFromPath: _db.path toDatabase: dbName withConfig: config error: error2]; + return [CBLDatabase copyFromPath: self->_db.path toDatabase: dbName withConfig: config error: error2]; }]; // Clean up: diff --git a/Objective-C/Tests/DocumentTest.m b/Objective-C/Tests/DocumentTest.m index 4fea6f7a7..2d92a4bf2 100644 --- a/Objective-C/Tests/DocumentTest.m +++ b/Objective-C/Tests/DocumentTest.m @@ -111,7 +111,7 @@ - (void) testCreateDocWithEmptyStringID { __block NSError *error = nil; // to skip test exception breakpoint [self ignoreException:^{ - AssertFalse([_db saveDocument: doc error: &error]); + AssertFalse([self->_db saveDocument: doc error: &error]); }]; AssertEqual(error.code, CBLErrorBadDocID); AssertEqualObjects(error.domain, CBLErrorDomain); @@ -1558,7 +1558,7 @@ - (void) testPurgeDocument { // Purge before save: [self expectError: CBLErrorDomain code: CBLErrorNotFound in: ^BOOL(NSError** err) { - return [_db purgeDocument: doc1 error: err]; + return [self->_db purgeDocument: doc1 error: err]; }]; // Save: diff --git a/Swift/Collection.swift b/Swift/Collection.swift index f64c73a35..18791674c 100644 --- a/Swift/Collection.swift +++ b/Swift/Collection.swift @@ -152,7 +152,6 @@ public final class Collection : CollectionChangeObservable, Indexable, Equatable /// the database is closed. public func save(document: MutableDocument, conflictHandler: @escaping (MutableDocument, Document?) -> Bool) throws -> Bool { - var error: NSError? let result = impl.save( document.impl as! CBLMutableDocument, From 3de150c7ab1558eeb176595543265c253c33df15 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 8 Jul 2024 19:03:25 +0300 Subject: [PATCH 28/49] CBL-5893: Throw exception for everything if finish() was successfully called beforehand (#3302) * Update test 26/27 per new behaviour * LiteCore 3.2.0-211 --- Objective-C/Tests/VectorLazyIndexTest.m | 29 ++++++++++++++++++++----- vendor/couchbase-lite-core | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Objective-C/Tests/VectorLazyIndexTest.m b/Objective-C/Tests/VectorLazyIndexTest.m index 115b43787..acf22761a 100644 --- a/Objective-C/Tests/VectorLazyIndexTest.m +++ b/Objective-C/Tests/VectorLazyIndexTest.m @@ -1244,9 +1244,10 @@ - (void) testIndexUpdaterIndexOutOfBounds { * - Get the word string from the IndexUpdater. * - Query the vector by word from the _default.words collection. * - Convert the vector result which is an array object to a platform's float array. - * - Call setVector() with the platform's float array at the index.. - * 8. Call finish() and check that the finish() is successfully called. - * 9. Call finish() again and check that a CouchbaseLiteException with the code NotOpen is thrown. + * - Call setVector() with the platform's float array at the index. + * 4. Call finish() and check that the finish() is successfully called. + * 5. Call finish() again and check that a CouchbaseLiteException with the code NotOpen is thrown. + * 6. Count, getValue, setVector, skipVector throw exception. */ - (void) testIndexUpdaterCallFinishTwice { [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; @@ -1262,8 +1263,26 @@ - (void) testIndexUpdaterCallFinishTwice { Assert([updater setVector: vector atIndex: 0 error: &error]); Assert([updater finishWithError: &error]); - [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { - return [updater finishWithError: err]; + [self expectException: @"NSInternalInconsistencyException" in:^{ + [updater count]; + }]; + + [self expectException: @"NSInternalInconsistencyException" in:^{ + [updater valueAtIndex: 0]; + }]; + + [self expectException: @"NSInternalInconsistencyException" in:^{ + NSError* outError; + [updater setVector: vector atIndex: 0 error: &outError]; + }]; + + [self expectException: @"NSInternalInconsistencyException" in:^{ + [updater skipVectorAtIndex: 0]; + }]; + + [self expectException: @"NSInternalInconsistencyException" in:^{ + NSError* outError; + [updater finishWithError: &outError]; }]; } diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 1b03b5456..36aec1c42 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 1b03b5456c508d431aa4ddfff82bda18f2fd392a +Subproject commit 36aec1c423d0a450ebb293807c5aaf270eaff976 From 9b939f361ae4af6a58d5007f1c04d36349317b23 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Wed, 10 Jul 2024 02:54:20 +0300 Subject: [PATCH 29/49] CBL-5956: Update DistanceMetric (#3303) * update defaults for euclideanSquare * fix tests --- Objective-C/CBLDefaults.h | 4 ++-- Objective-C/CBLDefaults.m | 2 +- Objective-C/Tests/VectorSearchTest.m | 4 ++-- Swift/Defaults.swift | 4 ++-- Swift/Tests/VectorSearchTest.swift | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Objective-C/CBLDefaults.h b/Objective-C/CBLDefaults.h index fbb9f7a44..5b7d43e43 100644 --- a/Objective-C/CBLDefaults.h +++ b/Objective-C/CBLDefaults.h @@ -83,13 +83,13 @@ extern const BOOL kCBLDefaultReplicatorAcceptParentCookies; #pragma mark - CBLVectorIndexConfiguration -/** [YES] Vectors are not lazily indexed, by default */ +/** [NO] Vectors are not lazily indexed, by default */ extern const BOOL kCBLDefaultVectorIndexIsLazy; /** [kCBLSQ8] Vectors are encoded by using 8-bit Scalar Quantizer encoding, by default */ extern const CBLScalarQuantizerType kCBLDefaultVectorIndexEncoding; -/** [kCBLDistanceMetricEuclidean] By default, vectors are compared using Euclidean metrics */ +/** [kCBLDistanceMetricEuclideanSquared] By default, vectors are compared using Euclidean metrics */ extern const CBLDistanceMetric kCBLDefaultVectorIndexDistanceMetric; /** [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters. */ diff --git a/Objective-C/CBLDefaults.m b/Objective-C/CBLDefaults.m index 585d188c3..2fd31fbe2 100644 --- a/Objective-C/CBLDefaults.m +++ b/Objective-C/CBLDefaults.m @@ -66,7 +66,7 @@ const CBLScalarQuantizerType kCBLDefaultVectorIndexEncoding = kCBLSQ8; -const CBLDistanceMetric kCBLDefaultVectorIndexDistanceMetric = kCBLDistanceMetricEuclidean; +const CBLDistanceMetric kCBLDefaultVectorIndexDistanceMetric = kCBLDistanceMetricEuclideanSquared; const unsigned int kCBLDefaultVectorIndexMinTrainingSize = 0; diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index bd577efcb..ef5477da8 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -225,7 +225,7 @@ - (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSNumber*)lim - (void) testVectorIndexConfigurationDefaultValue { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); AssertEqualObjects(config.encoding, [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ8]); - AssertEqual(config.metric, kCBLDistanceMetricEuclidean); + AssertEqual(config.metric, kCBLDistanceMetricEuclideanSquared); AssertEqual(config.minTrainingSize, 0); AssertEqual(config.maxTrainingSize, 0); AssertEqual(config.numProbes, 0); @@ -1001,7 +1001,7 @@ - (void) testCreateVectorIndexWithCosineDistance { */ - (void) testCreateVectorIndexWithEuclideanDistance { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); - config.metric = kCBLDistanceMetricEuclidean; + config.metric = kCBLDistanceMetricEuclideanSquared; [self createWordsIndexWithConfig: config]; CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20 queryDistance: true andClause: false checkTraining: false]; diff --git a/Swift/Defaults.swift b/Swift/Defaults.swift index 07ca1f387..d6fbee503 100644 --- a/Swift/Defaults.swift +++ b/Swift/Defaults.swift @@ -90,8 +90,8 @@ public extension VectorIndexConfiguration { /// [ScalarQuantizerType.SQ8] Vectors are encoded by using 8-bit Scalar Quantizer encoding, by default static let defaultEncoding: ScalarQuantizerType = ScalarQuantizerType.SQ8 - /// [DistanceMetric.euclidean] By default, vectors are compared using Euclidean metrics - static let defaultDistanceMetric: DistanceMetric = DistanceMetric.euclidean + /// [DistanceMetric.euclideanSquared] By default, vectors are compared using Euclidean metrics + static let defaultDistanceMetric: DistanceMetric = DistanceMetric.euclideanSquared /// [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters. static let defaultMinTrainingSize: UInt32 = 0 diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 167031438..34ff95a67 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -945,7 +945,7 @@ class VectorSearchTest: CBLTestCase { /// 9. Reset the custom logger. func testCreateVectorIndexWithEuclideanDistance() throws { var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) - config.metric = .euclidean + config.metric = .euclideanSquared try createWordsIndex(config: config) let rs = try executeWordsQuery(limit: 20) From 670ba907aaa493e0a5c2bfd3e6aa4dffcbb7dca2 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Thu, 11 Jul 2024 09:59:48 -0700 Subject: [PATCH 30/49] CBL-5690 : Update Distance Metric Enum and VS SQL in Tests (#3304) * Used new Vector Search SQL in Tests * Separated the base xctest class to have no tests, otherwise the tests in the base class will be included in the sub classes as well. * Used forward declaration where possibles in CBLQueryIndex / CBLIndexUpdater implementation. * Fixed warning in URLEndpointListenerTests and Replicator Tests. * Used LiteCore 3.2.0-215 and VS 1.0.0-56 --- .gitignore | 2 + CouchbaseLite.xcodeproj/project.pbxproj | 93 ++-- Objective-C/CBLCollectionTypes.h | 2 + Objective-C/CBLQueryIndex.h | 2 +- Objective-C/CBLQueryIndex.mm | 3 +- Objective-C/Internal/CBLQueryIndex+Internal.h | 11 +- Objective-C/Tests/ReplicatorTest+Collection.m | 7 +- Objective-C/Tests/ReplicatorTest.h | 8 +- Objective-C/Tests/URLEndpointListenerTest.h | 2 +- ...azyIndexTest.m => VectorSearchTest+Lazy.m} | 58 +-- Objective-C/Tests/VectorSearchTest.h | 23 +- Objective-C/Tests/VectorSearchTest.m | 408 ++++++++++-------- ...Test.swift => VectorSearchTest+Lazy.swift} | 54 ++- Swift/Tests/VectorSearchTest.swift | 286 ++++++------ Tests/Extensions/version.txt | 2 +- vendor/couchbase-lite-core | 2 +- 16 files changed, 496 insertions(+), 467 deletions(-) rename Objective-C/Tests/{VectorLazyIndexTest.m => VectorSearchTest+Lazy.m} (97%) rename Swift/Tests/{VectorLazyIndexTest.swift => VectorSearchTest+Lazy.swift} (96%) diff --git a/.gitignore b/.gitignore index 9bd1197ac..7ff519ad3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ frameworks/ # CBL files: Tests/Extensions/CouchbaseLiteVectorSearch.xcframework +Tests/Extensions/LICENSE.txt +Tests/Extensions/Build vendor/couchbase-lite-core-EE # spm diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 908d5bc21..9e781a476 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -97,8 +97,8 @@ 1A3BA96E272C589A002EAB2E /* CBLQueryObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A3BA96B272C5899002EAB2E /* CBLQueryObserver.h */; }; 1A3BA96F272C589A002EAB2E /* CBLQueryObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A3BA96C272C5899002EAB2E /* CBLQueryObserver.m */; }; 1A3BA970272C589A002EAB2E /* CBLQueryObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A3BA96C272C5899002EAB2E /* CBLQueryObserver.m */; }; - 1A3BA97C272C58B5002EAB2E /* CBLQueryObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A3BA96B272C5899002EAB2E /* CBLQueryObserver.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 1A3BA97D272C58B7002EAB2E /* CBLQueryObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A3BA96B272C5899002EAB2E /* CBLQueryObserver.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 1A3BA97C272C58B5002EAB2E /* CBLQueryObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A3BA96B272C5899002EAB2E /* CBLQueryObserver.h */; }; + 1A3BA97D272C58B7002EAB2E /* CBLQueryObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 1A3BA96B272C5899002EAB2E /* CBLQueryObserver.h */; }; 1A3BA97E272C58B9002EAB2E /* CBLQueryObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A3BA96C272C5899002EAB2E /* CBLQueryObserver.m */; }; 1A3BA97F272C58BA002EAB2E /* CBLQueryObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A3BA96C272C5899002EAB2E /* CBLQueryObserver.m */; }; 1A3F5556274345AA0088ECF1 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3F5555274345AA0088ECF1 /* Errors.swift */; }; @@ -214,7 +214,6 @@ 1AAFB685284A266F00878453 /* CollectionChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB679284A266F00878453 /* CollectionChange.swift */; }; 1AAFB687284A266F00878453 /* CollectionChangeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB67A284A266F00878453 /* CollectionChangeObservable.swift */; }; 1AAFB689284A266F00878453 /* CollectionChangeObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB67A284A266F00878453 /* CollectionChangeObservable.swift */; }; - 1AAFB68B284A266F00878453 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB67B284A266F00878453 /* Collection.swift */; }; 1AAFB68D284A266F00878453 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB67B284A266F00878453 /* Collection.swift */; }; 1AAFB68F284A266F00878453 /* Scope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB67C284A266F00878453 /* Scope.swift */; }; 1AAFB691284A266F00878453 /* Scope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB67C284A266F00878453 /* Scope.swift */; }; @@ -379,15 +378,26 @@ 4017E46A2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; 4017E46B2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; 4017E46C2BED6E5400A438EE /* CBLContextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4017E4642BED6E5400A438EE /* CBLContextManager.m */; }; - 406F8DEB2C26901A000223FC /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; - 406F8DEC2C26901A000223FC /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; + 401D7FC12C3F82BC00DAAB62 /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 401D7FB22C3F764100DAAB62 /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 401D7FC22C3F82BD00DAAB62 /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 401D7FB22C3F764100DAAB62 /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 401D7FC32C3F82C200DAAB62 /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = 401D7FB32C3F764100DAAB62 /* CBLQueryIndex.mm */; }; + 401D7FC42C3F82C300DAAB62 /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = 401D7FB32C3F764100DAAB62 /* CBLQueryIndex.mm */; }; + 401D7FC52C3F82F500DAAB62 /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; + 401D7FC62C3F82F600DAAB62 /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; + 401D7FE92C3F8AE000DAAB62 /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; + 401D7FEA2C3F8AE100DAAB62 /* QueryIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DEA2C26901A000223FC /* QueryIndex.swift */; }; + 401D7FEB2C3F8C3F00DAAB62 /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 401D7FB22C3F764100DAAB62 /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 401D7FEC2C3F8C4300DAAB62 /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = 401D7FB22C3F764100DAAB62 /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 401D7FED2C3F8F5300DAAB62 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AAFB67B284A266F00878453 /* Collection.swift */; }; + 401D7FEE2C3F906800DAAB62 /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = 401D7FB32C3F764100DAAB62 /* CBLQueryIndex.mm */; }; + 401D7FEF2C3F906800DAAB62 /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = 401D7FB32C3F764100DAAB62 /* CBLQueryIndex.mm */; }; 406F8DFB2C27C303000223FC /* IndexUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DF92C27C302000223FC /* IndexUpdater.swift */; }; - 406F8DFF2C27F0A9000223FC /* VectorLazyIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */; }; - 406F8E002C27F0AB000223FC /* VectorLazyIndexTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */; }; + 406F8DFF2C27F0A9000223FC /* VectorSearchTest+Lazy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */; }; + 406F8E002C27F0AB000223FC /* VectorSearchTest+Lazy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */; }; 40AA72952C28938D007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; 40AA72962C28938E007FB1E0 /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; - 40AA729D2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */; }; - 40AA729E2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */; }; + 40AA729D2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */; }; + 40AA729E2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */; }; 40C5FD5B2B9947B3004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 40C5FD5C2B9947B9004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; 40EF690C2B7757CF00F0CB50 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 40EF690A2B77564000F0CB50 /* PrivacyInfo.xcprivacy */; }; @@ -1758,24 +1768,12 @@ AE5803F92B9B969D001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AE5803FA2B9B96BF001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; }; AE5803FB2B9B96BF001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - AE83D07C2C06242F0055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AE83D07D2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; - AE83D07E2C06242F0055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Private, ); }; }; - AE83D07F2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; - AE83D0802C0627630055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AE83D0812C0627630055D2CF /* CBLQueryIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */; settings = {ATTRIBUTES = (Private, ); }; }; AE83D0842C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; AE83D0852C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; AE83D0862C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Private, ); }; }; AE83D0872C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; - AECD59FB2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; - AECD59FE2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; - AECD5A002C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; - AECD5A012C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */; }; AECD5A162C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; AECD5A172C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; - AECD5A512C125D4800B1247E /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; - AECD5A522C125D4800B1247E /* CBLQueryIndex.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -2285,15 +2283,17 @@ 400AAFDF2C2A845E00DB6223 /* Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; }; 4017E4572BED6E5400A438EE /* CBLContextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLContextManager.h; sourceTree = ""; }; 4017E4642BED6E5400A438EE /* CBLContextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBLContextManager.m; sourceTree = ""; }; + 401D7FB22C3F764100DAAB62 /* CBLQueryIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBLQueryIndex.h; sourceTree = ""; }; + 401D7FB32C3F764100DAAB62 /* CBLQueryIndex.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLQueryIndex.mm; sourceTree = ""; }; 406F8DEA2C26901A000223FC /* QueryIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryIndex.swift; sourceTree = ""; }; 406F8DF92C27C302000223FC /* IndexUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexUpdater.swift; sourceTree = ""; }; - 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorLazyIndexTest.swift; sourceTree = ""; }; + 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VectorSearchTest+Lazy.swift"; sourceTree = ""; }; 40A789282BE2C7D100CA43A1 /* CBL_EE.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL_EE.exp; sourceTree = ""; }; 40A789292BE2C7D100CA43A1 /* CBL.exp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.exports; path = CBL.exp; sourceTree = ""; }; 40A7892B2BE2C7D100CA43A1 /* CBL_EE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL_EE.txt; sourceTree = ""; }; 40A7892C2BE2C7D100CA43A1 /* CBL.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CBL.txt; sourceTree = ""; }; 40A7892D2BE2C7D100CA43A1 /* generate_exports.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = generate_exports.sh; sourceTree = ""; }; - 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VectorLazyIndexTest.m; sourceTree = ""; }; + 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "VectorSearchTest+Lazy.m"; sourceTree = ""; }; 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VectorSearchTest.h; sourceTree = ""; }; 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLVectorIndexTypes.h; sourceTree = ""; }; 40E905462B5B6D9D00EDF483 /* CouchbaseLiteSwift.private.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = CouchbaseLiteSwift.private.modulemap; sourceTree = ""; }; @@ -2791,8 +2791,6 @@ AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VectorSearchTest.m; sourceTree = ""; }; AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorSearchTest.swift; sourceTree = ""; }; AE5803D72B9B5B2A001A1BE3 /* WordEmbeddingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordEmbeddingModel.swift; sourceTree = ""; }; - AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLQueryIndex.h; sourceTree = ""; }; - AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLQueryIndex.mm; sourceTree = ""; }; AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLIndexUpdater.h; sourceTree = ""; }; AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLIndexUpdater.mm; sourceTree = ""; }; AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLQueryIndex+Internal.h"; sourceTree = ""; }; @@ -3079,7 +3077,7 @@ 934608EE247F35CC00CF2F27 /* URLEndpointListenerTest.swift */, 1A13DD3328B8809300BC1084 /* URLEndpointListenerTest+Collection.swift */, AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */, - 406F8DFC2C27F097000223FC /* VectorLazyIndexTest.swift */, + 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */, 1AA91DC522B0356000BF0BDE /* CustomLogger.swift */, 93249D81246B99FD000A8A6E /* iOS */, 939B79241E679017009A70EF /* Info.plist */, @@ -4147,7 +4145,7 @@ 1AA3D77222AB06E10098E16B /* CustomLogger.m */, 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */, AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */, - 40AA72972C28B1A3007FB1E0 /* VectorLazyIndexTest.m */, + 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */, 93DECF3E200DBE5800F44953 /* Support */, 936483AA1E4431C6008D08B3 /* iOS */, 275FF5FA1E3FBD3B005F90DD /* Performance */, @@ -4325,8 +4323,8 @@ 93FD618A2020757500E7F6A1 /* CBLIndex.m */, 93FD615F20204E3600E7F6A1 /* CBLIndexBuilder.h */, 93FD616020204E3600E7F6A1 /* CBLIndexBuilder.m */, - AE83D07A2C06242F0055D2CF /* CBLQueryIndex.h */, - AE83D07B2C06242F0055D2CF /* CBLQueryIndex.mm */, + 401D7FB22C3F764100DAAB62 /* CBLQueryIndex.h */, + 401D7FB32C3F764100DAAB62 /* CBLQueryIndex.mm */, 93EC42CB1FB3801E00D54BB4 /* CBLValueIndex.h */, 93EC42CC1FB3801E00D54BB4 /* CBLValueIndex.m */, 1A3470BF266F3E7C0042C6BA /* CBLIndexConfiguration.h */, @@ -4449,7 +4447,6 @@ 9388CC5921C25FDE005CA66D /* CBLLog+Swift.h in Headers */, 93DB7FED1ED8E1C000C4F845 /* CBLReplicatorConfiguration.h in Headers */, 938196191EC113770032CC51 /* CBLArrayFragment.h in Headers */, - AE83D0812C0627630055D2CF /* CBLQueryIndex.h in Headers */, 9384D8281FC405BF00FE89D8 /* CBLQueryFullTextExpression.h in Headers */, 275F92841E4D30A4007FD5A2 /* CouchbaseLiteSwift.h in Headers */, 9383A5851F1EE7C00083053D /* CBLQueryResultSet.h in Headers */, @@ -4497,10 +4494,10 @@ 2753AFFA1EC39CA200C12E98 /* CBLWebSocket.h in Headers */, 9308F4061E64B22800F53EE4 /* MYErrorUtils.h in Headers */, 9308F4081E64B22D00F53EE4 /* MYLogging.h in Headers */, + 401D7FC22C3F82BD00DAAB62 /* CBLQueryIndex.h in Headers */, 93EC42E31FB387AB00D54BB4 /* CBLQueryJSONEncoding.h in Headers */, 9308F4041E64B22200F53EE4 /* ExceptionUtils.h in Headers */, 1AAFB670284A260A00878453 /* CBLCollectionChangeObservable.h in Headers */, - AECD59FE2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 1A8E2FBC28FF75D500E141A8 /* CBLDefaults.h in Headers */, 939B1B2D200990FB00FAA3CB /* CBLValueExpression.h in Headers */, 937F01E71EFB280000060D64 /* CBLAuthenticator+Internal.h in Headers */, @@ -4510,6 +4507,7 @@ 938E389E1F3A7A47006806C7 /* CBLCollationExpression.h in Headers */, 4006AB7C2B9169C50036E66D /* CBLEdition.h in Headers */, 1AAB27682277836F0037A880 /* CBLConflict.h in Headers */, + 401D7FC62C3F82F600DAAB62 /* CBLQueryIndex+Internal.h in Headers */, 93B503771E64B0BB002C4680 /* CBLDatabase+Internal.h in Headers */, 93CD016E1E94923900AFB3FA /* CBLQuery+Internal.h in Headers */, 93FD6187202053BE00E7F6A1 /* CBLIndex+Internal.h in Headers */, @@ -4544,6 +4542,7 @@ 9343EF9C207D611600F19A89 /* CBLReplicator+Backgrounding.h in Headers */, 9388CC3421C18673005CA66D /* CBLLog+Internal.h in Headers */, 9343EF9D207D611600F19A89 /* CBLFleece.hh in Headers */, + 401D7FEB2C3F8C3F00DAAB62 /* CBLQueryIndex.h in Headers */, 930B369024AAFACB000DF2B3 /* CBLDocBranchIterator.h in Headers */, 9343EF9E207D611600F19A89 /* CBLQueryOrdering.h in Headers */, 40FC1C1D2B928B5000394276 /* CBLProductQuantizer.h in Headers */, @@ -4572,7 +4571,6 @@ 9343EFB1207D611600F19A89 /* CBLReplicator+Internal.h in Headers */, 9343EFB2207D611600F19A89 /* CBLHTTPLogic.h in Headers */, 9343EFB4207D611600F19A89 /* CBLQueryVariableExpression+Internal.h in Headers */, - AE83D07C2C06242F0055D2CF /* CBLQueryIndex.h in Headers */, 9343EFB5207D611600F19A89 /* CBLException.h in Headers */, 40FC1C0C2B928ADC00394276 /* CBLTLSIdentity+Internal.h in Headers */, 40FC1C0F2B928ADC00394276 /* CBLListenerCertificateAuthenticator+Internal.h in Headers */, @@ -4586,7 +4584,6 @@ 40FC1C092B928ADC00394276 /* CBLURLEndpointListener+Internal.h in Headers */, 9343EFBB207D611600F19A89 /* CBLMutableArray.h in Headers */, 9343EFBC207D611600F19A89 /* CollectionUtils.h in Headers */, - AECD5A002C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 9343EFBD207D611600F19A89 /* CBLMutableArrayFragment.h in Headers */, 6932D4A729547B7B00D28C18 /* CBLQueryFullTextIndexExpressionProtocol.h in Headers */, 1A3470C3266F3E7C0042C6BA /* CBLIndexConfiguration.h in Headers */, @@ -4742,7 +4739,6 @@ 40FC1B4C2B92872000394276 /* CBLDatabase+Encryption.h in Headers */, 40FC1BF92B928A4F00394276 /* CBLVectorIndexConfiguration.h in Headers */, 93292D9F22BD448400A0862A /* CBLReplicatorConfiguration+Swift.h in Headers */, - AECD5A012C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 1AAFB672284A260A00878453 /* CBLCollectionChangeObservable.h in Headers */, 9343F0B6207D61AB00F19A89 /* CBLReachability.h in Headers */, 40FC1C2D2B928B5000394276 /* CBLPrediction+Swift.h in Headers */, @@ -4781,7 +4777,6 @@ 9343F0CE207D61AB00F19A89 /* CBLFunctionExpression.h in Headers */, 9343F0CF207D61AB00F19A89 /* CBLException.h in Headers */, 9343F0D1207D61AB00F19A89 /* Test.h in Headers */, - AE83D07E2C06242F0055D2CF /* CBLQueryIndex.h in Headers */, 40FC1BF22B928A4F00394276 /* CBLPredictiveIndex.h in Headers */, 93E1873A211122EB001D52B9 /* MYURLUtils.h in Headers */, 9343F0D2207D61AB00F19A89 /* CBLQueryArrayExpression.h in Headers */, @@ -4925,6 +4920,7 @@ 40FC1B802B9288A800394276 /* CBLMessage.h in Headers */, 40FC1C372B928BD900394276 /* CBLEdition.h in Headers */, 40FC1C272B928B5000394276 /* CBLPredictiveIndex+Internal.h in Headers */, + 401D7FEC2C3F8C4300DAAB62 /* CBLQueryIndex.h in Headers */, 9343F11D207D61AB00F19A89 /* CBLNewDictionary.h in Headers */, 9343F11E207D61AB00F19A89 /* CBLWebSocket.h in Headers */, 9343F11F207D61AB00F19A89 /* MYErrorUtils.h in Headers */, @@ -5050,7 +5046,6 @@ 934A278C1F30E5A5003946A7 /* CBLAggregateExpression.h in Headers */, 93900CFE1EA197F000745D4F /* CBLDocument+Internal.h in Headers */, 9322DCDF1F14603400C4ACF7 /* CBLQueryLimit.h in Headers */, - AE83D0802C0627630055D2CF /* CBLQueryIndex.h in Headers */, 1AEF059A283380F800D5DDEA /* CBLCollection.h in Headers */, 69002EB9234E693F00776107 /* CBLErrorMessage.h in Headers */, 9388CC5821C25FDE005CA66D /* CBLLog+Swift.h in Headers */, @@ -5098,10 +5093,10 @@ 1AAFB6A4284A294200878453 /* CBLCollection+Internal.h in Headers */, 934A27AB1F30E641003946A7 /* CBLParameterExpression.h in Headers */, 938E389D1F3A7A47006806C7 /* CBLCollationExpression.h in Headers */, + 401D7FC12C3F82BC00DAAB62 /* CBLQueryIndex.h in Headers */, 934F4C291E1EF19000F90659 /* MYErrorUtils.h in Headers */, 93E18734211122D9001D52B9 /* MYURLUtils.h in Headers */, 6932D4A629547B7800D28C18 /* CBLQueryFullTextIndexExpressionProtocol.h in Headers */, - AECD59FB2C0E137200B1247E /* CBLQueryIndex+Internal.h in Headers */, 93EB263321DF19D00006FB88 /* CBLDocumentFlags.h in Headers */, 937A69031F0731230058277F /* CBLQueryFunction.h in Headers */, 9381961E1EC11A8C0032CC51 /* CBLDictionary+Swift.h in Headers */, @@ -5111,6 +5106,7 @@ 938196211EC11CDF0032CC51 /* CBLArray+Swift.h in Headers */, 4006AB4F2B9106000036E66D /* CBLEdition.h in Headers */, 93FD6186202053BE00E7F6A1 /* CBLIndex+Internal.h in Headers */, + 401D7FC52C3F82F500DAAB62 /* CBLQueryIndex+Internal.h in Headers */, 1A3BA95D272A4738002EAB2E /* CBLLockable.h in Headers */, 72A87A051E2E0E70008466FF /* CBLBlobStream.h in Headers */, 1AAB27412273AB420037A880 /* CBLConflict+Internal.h in Headers */, @@ -5874,6 +5870,7 @@ /* Begin PBXShellScriptBuildPhase section */ 40EF68002B71889F00F0CB50 /* Remove Private Module */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -6063,6 +6060,7 @@ 9381961C1EC113860032CC51 /* CBLFragment.m in Sources */, 93EC42E91FB3930E00D54BB4 /* CBLQueryArrayExpression.m in Sources */, 93B41D7E1F05B3A800A7F114 /* Join.swift in Sources */, + 401D7FED2C3F8F5300DAAB62 /* Collection.swift in Sources */, 1AEF059F283380F800D5DDEA /* CBLCollection.mm in Sources */, 938CDF291E807F9F002EE790 /* OrderByRouter.swift in Sources */, 1A8E2FB828FF75D500E141A8 /* CBLDefaults.m in Sources */, @@ -6105,7 +6103,7 @@ 93FD618E2020757500E7F6A1 /* CBLIndex.m in Sources */, 9322DCF51F14654E00C4ACF7 /* Limit.swift in Sources */, 938E38841F3A5BB4006806C7 /* CBLQueryCollation.m in Sources */, - AECD5A522C125D4800B1247E /* CBLQueryIndex.mm in Sources */, + 401D7FC42C3F82C300DAAB62 /* CBLQueryIndex.mm in Sources */, 9384D80C1FC3F75700FE89D8 /* CBLQueryArrayFunction.m in Sources */, 93AF51C41E80A6FC00E200F0 /* CBLQueryOrdering.m in Sources */, 93DB7FEF1ED8E1C000C4F845 /* CBLReplicatorConfiguration.m in Sources */, @@ -6126,7 +6124,7 @@ 931DE0671F4E3EC2003EF76F /* Index.swift in Sources */, 93CED8CD20488C1300E6F0A4 /* Blob.swift in Sources */, 938B36A7200745FF004485D8 /* CBLQueryResultArray.m in Sources */, - 406F8DEB2C26901A000223FC /* QueryIndex.swift in Sources */, + 401D7FEA2C3F8AE100DAAB62 /* QueryIndex.swift in Sources */, 9384D8631FC4163D00FE89D8 /* FullTextFunction.swift in Sources */, 1A8E2FB328FF756600E141A8 /* Defaults.swift in Sources */, 9322DCF71F14671F00C4ACF7 /* LimitRouter.swift in Sources */, @@ -6175,7 +6173,6 @@ 93EC42CE1FB3801E00D54BB4 /* CBLFullTextIndex.m in Sources */, 9381962A1EC152410032CC51 /* Document.swift in Sources */, 1A3470C6266F3E7C0042C6BA /* CBLIndexConfiguration.m in Sources */, - 1AAFB68B284A266F00878453 /* Collection.swift in Sources */, 9380D2731F0DBD79007DD84A /* SelectResult.swift in Sources */, 93B503661E64B083002C4680 /* CBLStringBytes.mm in Sources */, 1AAB2785227793E40037A880 /* CBLConflict.m in Sources */, @@ -6267,7 +6264,7 @@ 93BB1C9E246BB2BF004FFA00 /* DatabaseTest.swift in Sources */, 93BB1C9C246BB2BB004FFA00 /* CBLTestCase.swift in Sources */, 93BB1CB8246BB2F4004FFA00 /* ReplicatorTest+CustomConflict.swift in Sources */, - 406F8E002C27F0AB000223FC /* VectorLazyIndexTest.swift in Sources */, + 406F8E002C27F0AB000223FC /* VectorSearchTest+Lazy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6297,7 +6294,6 @@ 9343EF3A207D611600F19A89 /* Test.m in Sources */, 1AAB2786227793E50037A880 /* CBLConflict.m in Sources */, 9343EF3D207D611600F19A89 /* CBLBlob.mm in Sources */, - AE83D07D2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */, 9343EF3E207D611600F19A89 /* CBLAggregateExpression.m in Sources */, 9343EF3F207D611600F19A89 /* CBLJSON.mm in Sources */, 9343EF40207D611600F19A89 /* CBLParseDate.c in Sources */, @@ -6309,6 +6305,7 @@ 9343EF45207D611600F19A89 /* CBLMutableDocument.mm in Sources */, 40FC1BD92B928A4F00394276 /* CBLPrediction.mm in Sources */, 9343EF46207D611600F19A89 /* CBLReachability.m in Sources */, + 401D7FEE2C3F906800DAAB62 /* CBLQueryIndex.mm in Sources */, 40FC1C062B928AC300394276 /* CBLDatabase+EncryptionInternal.mm in Sources */, 9343EF47207D611600F19A89 /* CBLNewDictionary.mm in Sources */, 1A8E2FB928FF75D500E141A8 /* CBLDefaults.m in Sources */, @@ -6458,7 +6455,6 @@ 1A3471502671C8800042C6BA /* CBLFullTextIndexConfiguration.m in Sources */, 9388CC4E21C25141005CA66D /* ConsoleLogger.swift in Sources */, 40FC1C2B2B928B5000394276 /* CBLNoneVectorEncoding.mm in Sources */, - AE83D07F2C06242F0055D2CF /* CBLQueryIndex.mm in Sources */, 40FC1B822B9288A800394276 /* CBLMessagingError.m in Sources */, 9343F029207D61AB00F19A89 /* CBLWebSocket.mm in Sources */, 9343F02A207D61AB00F19A89 /* Parameters.swift in Sources */, @@ -6484,6 +6480,7 @@ 9343F036207D61AB00F19A89 /* Ordering.swift in Sources */, 9343F037207D61AB00F19A89 /* Replicator.swift in Sources */, 93EB25C621CDCEC20006FB88 /* CBLQueryParameters.mm in Sources */, + 401D7FEF2C3F906800DAAB62 /* CBLQueryIndex.mm in Sources */, 9343F038207D61AB00F19A89 /* FromRouter.swift in Sources */, 9343F039207D61AB00F19A89 /* CBLQueryFullTextFunction.m in Sources */, 9386852A21B09C5400BB1242 /* DocumentReplication.swift in Sources */, @@ -6569,6 +6566,7 @@ 9343F06F207D61AB00F19A89 /* CBLQueryArrayFunction.m in Sources */, 9343F070207D61AB00F19A89 /* CBLQueryOrdering.m in Sources */, 40FC1C672B928C1600394276 /* VectorEncoding.swift in Sources */, + 401D7FE92C3F8AE000DAAB62 /* QueryIndex.swift in Sources */, 9343F071207D61AB00F19A89 /* CBLReplicatorConfiguration.m in Sources */, 1A416035227D0AD50061A567 /* ConflictResolver.swift in Sources */, 9343F072207D61AB00F19A89 /* CBLCoreBridge.mm in Sources */, @@ -6645,7 +6643,6 @@ 9388CC5121C2514C005CA66D /* FileLogger.swift in Sources */, 40FC1C662B928C1600394276 /* VectorIndexConfiguration.swift in Sources */, 9343F0A3207D61AB00F19A89 /* CBLReplicator.mm in Sources */, - 406F8DEC2C26901A000223FC /* QueryIndex.swift in Sources */, 40FC1C632B928C1600394276 /* DatabaseEndpoint.swift in Sources */, 9343F0A4207D61AB00F19A89 /* CBLMutableDictionary.mm in Sources */, 9343F0A5207D61AB00F19A89 /* MutableArrayObject.swift in Sources */, @@ -6694,7 +6691,7 @@ 1A621D7A2887DCFD0017F905 /* QueryTest+Collection.m in Sources */, 93EB25CC21CDD12A0006FB88 /* PredictiveQueryTest.m in Sources */, 1AF555D422948BD90077DF6D /* QueryTest+Main.m in Sources */, - 40AA729D2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */, + 40AA729D2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */, 9343F13D207D61EC00F19A89 /* MigrationTest.m in Sources */, 9343F13E207D61EC00F19A89 /* DocumentTest.m in Sources */, 9343F140207D61EC00F19A89 /* DictionaryTest.m in Sources */, @@ -6748,7 +6745,7 @@ 1AA6744C227924130018CC6D /* QueryTest+Meta.m in Sources */, 934EF8322460D07B0053A47C /* TLSIdentityTest.m in Sources */, 9369A6AF207DD105009B5B83 /* DatabaseEncryptionTest.m in Sources */, - 40AA729E2C28B1A3007FB1E0 /* VectorLazyIndexTest.m in Sources */, + 40AA729E2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */, 9343F176207D633300F19A89 /* DocumentTest.m in Sources */, 1A13DD4228B882A800BC1084 /* URLEndpointListenerTest+Main.m in Sources */, 9388CBAE21BD9187005CA66D /* DocumentExpirationTest.m in Sources */, @@ -6810,7 +6807,7 @@ 9343F198207D636300F19A89 /* CBLTestCase.swift in Sources */, 93C50EB021BDFC7B00C7E980 /* DocumentExpirationTest.swift in Sources */, 9369A6B7207DEB60009B5B83 /* DatabaseEncryptionTest.swift in Sources */, - 406F8DFF2C27F0A9000223FC /* VectorLazyIndexTest.swift in Sources */, + 406F8DFF2C27F0A9000223FC /* VectorSearchTest+Lazy.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6961,7 +6958,7 @@ 934F4C2A1E1EF19000F90659 /* MYErrorUtils.m in Sources */, 1AEF05A32833900800D5DDEA /* CBLScope.mm in Sources */, 93EC42CD1FB3801E00D54BB4 /* CBLFullTextIndex.m in Sources */, - AECD5A512C125D4800B1247E /* CBLQueryIndex.mm in Sources */, + 401D7FC32C3F82C200DAAB62 /* CBLQueryIndex.mm in Sources */, 93DB7FEE1ED8E1C000C4F845 /* CBLReplicatorConfiguration.m in Sources */, 934F4C2C1E1EF19000F90659 /* MYLogging.m in Sources */, 93EC42E81FB3930E00D54BB4 /* CBLQueryArrayExpression.m in Sources */, diff --git a/Objective-C/CBLCollectionTypes.h b/Objective-C/CBLCollectionTypes.h index 93bf49515..c7a5d1ece 100644 --- a/Objective-C/CBLCollectionTypes.h +++ b/Objective-C/CBLCollectionTypes.h @@ -17,6 +17,8 @@ // limitations under the License. // +#import + NS_ASSUME_NONNULL_BEGIN /** diff --git a/Objective-C/CBLQueryIndex.h b/Objective-C/CBLQueryIndex.h index ffe2c7c5f..4a86711ab 100644 --- a/Objective-C/CBLQueryIndex.h +++ b/Objective-C/CBLQueryIndex.h @@ -20,7 +20,7 @@ #import #ifdef COUCHBASE_ENTERPRISE -#import +@class CBLIndexUpdater; #endif @class CBLCollection; diff --git a/Objective-C/CBLQueryIndex.mm b/Objective-C/CBLQueryIndex.mm index 54460f06e..3d14bd265 100644 --- a/Objective-C/CBLQueryIndex.mm +++ b/Objective-C/CBLQueryIndex.mm @@ -17,9 +17,10 @@ // limitations under the License. // +#import "CBLCollection.h" +#import "CBLCoreBridge.h" #import "CBLDatabase+Internal.h" #import "CBLQueryIndex+Internal.h" -#import "CBLCoreBridge.h" #import "CBLStatus.h" #ifdef COUCHBASE_ENTERPRISE diff --git a/Objective-C/Internal/CBLQueryIndex+Internal.h b/Objective-C/Internal/CBLQueryIndex+Internal.h index 542f1b649..416df3f76 100644 --- a/Objective-C/Internal/CBLQueryIndex+Internal.h +++ b/Objective-C/Internal/CBLQueryIndex+Internal.h @@ -18,10 +18,11 @@ // #import -#import -#import +#import "CBLQueryIndex.h" #import "c4Index.h" +@class CBLCollection; + NS_ASSUME_NONNULL_BEGIN @interface CBLQueryIndex () @@ -31,10 +32,8 @@ NS_ASSUME_NONNULL_BEGIN - (id) mutex; - (instancetype) initWithC4Index: (C4Index*) c4index - name: (NSString*) name - collection: (CBLCollection*) collection; + name: (NSString*) name + collection: (CBLCollection*) collection; @end - - NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/ReplicatorTest+Collection.m b/Objective-C/Tests/ReplicatorTest+Collection.m index ed7aff8cd..8f94f1664 100644 --- a/Objective-C/Tests/ReplicatorTest+Collection.m +++ b/Objective-C/Tests/ReplicatorTest+Collection.m @@ -47,12 +47,12 @@ - (void)tearDown { - (void) testCreateReplicatorWithNoCollections { [self expectException: NSInvalidArgumentException in:^{ - CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: _config]; + CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: self->_config]; NSLog(@"%@", r); }]; [self expectException: NSInvalidArgumentException in:^{ - [_config addCollections: @[] config: nil]; + [self->_config addCollections: @[] config: nil]; }]; } @@ -1310,7 +1310,6 @@ - (void) testCollectionIsDocumentPending { } - (void) testCollectionDocumentReplicationEvents { - CBLDocumentFlags flags = 0; NSError* error = nil; CBLCollection* col1a = [self.db createCollectionWithName: @"colA" scope: @"scopeA" error: &error]; @@ -1342,6 +1341,8 @@ - (void) testCollectionDocumentReplicationEvents { CBLCollectionConfiguration* colConfig = [[CBLCollectionConfiguration alloc] init]; [config addCollections: @[col1a, col1b] config: colConfig]; CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: config]; + + __block CBLDocumentFlags flags = 0; __block int docsCount = 0; __block NSMutableArray* docs = [[NSMutableArray alloc] init]; id token = [r addDocumentReplicationListener: ^(CBLDocumentReplication* docReplication) { diff --git a/Objective-C/Tests/ReplicatorTest.h b/Objective-C/Tests/ReplicatorTest.h index 8157ff186..3ededf266 100644 --- a/Objective-C/Tests/ReplicatorTest.h +++ b/Objective-C/Tests/ReplicatorTest.h @@ -57,10 +57,10 @@ NS_ASSUME_NONNULL_BEGIN - (void) eraseRemoteEndpoint: (CBLURLEndpoint*)endpoint; -- (id) sendRequestToEndpoint: (CBLURLEndpoint*)endpoint - method: (NSString*)method - path: (nullable NSString*)path - body: (nullable id)body; +- (nullable id) sendRequestToEndpoint: (CBLURLEndpoint*)endpoint + method: (NSString*)method + path: (nullable NSString*)path + body: (nullable id)body; #pragma mark - Certifciate diff --git a/Objective-C/Tests/URLEndpointListenerTest.h b/Objective-C/Tests/URLEndpointListenerTest.h index 537ee107b..447916832 100644 --- a/Objective-C/Tests/URLEndpointListenerTest.h +++ b/Objective-C/Tests/URLEndpointListenerTest.h @@ -48,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN - (void) stopListener: (CBLURLEndpointListener*)listener; // TLS Identity management -- (CBLTLSIdentity*) tlsIdentity: (BOOL)isServer; +- (nullable CBLTLSIdentity*) tlsIdentity: (BOOL)isServer; - (void) cleanupTLSIdentity: (BOOL)isServer; - (void) deleteFromKeyChain: (CBLTLSIdentity*)identity; diff --git a/Objective-C/Tests/VectorLazyIndexTest.m b/Objective-C/Tests/VectorSearchTest+Lazy.m similarity index 97% rename from Objective-C/Tests/VectorLazyIndexTest.m rename to Objective-C/Tests/VectorSearchTest+Lazy.m index acf22761a..edf5b6b22 100644 --- a/Objective-C/Tests/VectorLazyIndexTest.m +++ b/Objective-C/Tests/VectorSearchTest+Lazy.m @@ -1,5 +1,5 @@ // -// LazyVectorIndexTest.m +// VectorSearchTest+Lazy.m // CouchbaseLite // // Copyright (c) 2024 Couchbase, Inc All rights reserved. @@ -22,17 +22,24 @@ /** * Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0002-Lazy-Vector-Index.md * - * Test 6. TestGetIndexOnClosedDatabase is tested in CollectionTest - * Test 7. TestGetIndexOnDeletedCollection is tested in CollectionTest + * Vesion: 2.0.1 + * + * Test 6. TestGetIndexOnClosedDatabase is tested in CollectionTest + * Test 7. TestGetIndexOnDeletedCollection is tested in CollectionTest */ #define LAZY_VECTOR_INDEX_CONFIG(E, D, C) [self lazyVectorIndexConfigWithExpression: (E) dimensions: (D) centroids: (C)] -@interface VectorLazyIndexTest : VectorSearchTest +@interface VectorSearchTest_Lazy : VectorSearchTest @end -@implementation VectorLazyIndexTest +@implementation VectorSearchTest_Lazy + +/** Override the default VectorSearch Expression */ +- (NSString*) wordsQueryDefaultExpression { + return @"word"; +} - (CBLQueryIndex*) wordsIndex { CBLQueryIndex* index = [self.wordsCollection indexWithName: kWordsIndexName error: nil]; @@ -40,7 +47,7 @@ - (CBLQueryIndex*) wordsIndex { return index; } -- (CBLVectorIndexConfiguration*) lazyVectorIndexConfigWithExpression: (NSString*)expression +- (CBLVectorIndexConfiguration*) lazyVectorIndexConfigWithExpression: (NSString*)expression dimensions: (unsigned int)dimensions centroids: (unsigned int)centroids { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(expression, dimensions, centroids); @@ -208,7 +215,8 @@ - (void) testGetExistingVectorIndex { * 3. Create an SQL++ query: * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, ) + * ORDER BY APPROX_VECTOR_DISTANCE(word, $dinnerVector) + * LIMIT 10 * 4. Execute the query and check that 0 results are returned. * 5. Update the documents: * - Create _default.words.word301 with the content from _default.extwords.word1 @@ -218,7 +226,7 @@ - (void) testGetExistingVectorIndex { - (void) testLazyVectorIndexNotAutoUpdatedChangedDocs { [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; - CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: nil]; + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: 10]; AssertEqual(rs.allObjects.count, 0); // Update docs: @@ -232,7 +240,7 @@ - (void) testLazyVectorIndexNotAutoUpdatedChangedDocs { [word1 setData: [extWord3 toDictionary]]; Assert([self.wordsCollection saveDocument: word1 error: &error]); - rs = [self executeWordsQueryNoTrainingCheckWithLimit: nil]; + rs = [self executeWordsQueryNoTrainingCheckWithLimit: 10]; AssertEqual(rs.allObjects.count, 0); } @@ -261,7 +269,8 @@ - (void) testLazyVectorIndexNotAutoUpdatedChangedDocs { * 6. Create an SQL++ query: * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 300 + * ORDER BY APPROX_VECTOR_DISTANCE(word, $dinnerVector) + * LIMIT 300 * 7. Execute the query and check that 1 results are returned. * 8. Check that the word gotten from the query result is the same as the word in Step 5. * 9. Delete _default.words.word1 doc. @@ -284,12 +293,12 @@ - (void) testLazyVectorIndexAutoUpdateDeletedDocs { AssertNil(error); // Query: - CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: 300]; AssertEqual(rs.allObjects.count, 1); // Delete doc and requery: [self.wordsCollection deleteDocument: [self.wordsCollection documentWithID: @"word1" error: &error] error: &error]; - rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + rs = [self executeWordsQueryNoTrainingCheckWithLimit: 300]; AssertEqual(rs.allObjects.count, 0); } @@ -318,7 +327,8 @@ - (void) testLazyVectorIndexAutoUpdateDeletedDocs { * 7. Create an SQL++ query: * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 300 + * ORDER BY APPROX_VECTOR_DISTANCE(word, $dinnerVector) + * LIMIT 300 * 8. Execute the query and check that 1 results are returned. * 9. Check that the word gotten from the query result is the same as the word in Step 5. * 10. Purge _default.words.word1 doc. @@ -341,12 +351,12 @@ - (void) testLazyVectorIndexAutoUpdatePurgedDocs { AssertNil(error); // Query: - CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: 300]; AssertEqual(rs.allObjects.count, 1); // Delete doc and requery: [self.wordsCollection purgeDocumentWithID: @"word1" error: &error]; - rs = [self executeWordsQueryNoTrainingCheckWithLimit: @300]; + rs = [self executeWordsQueryNoTrainingCheckWithLimit: 300]; AssertEqual(rs.allObjects.count, 0); } @@ -827,7 +837,8 @@ - (void) testIndexUpdaterGettingValues { * 7. Execute a vector search query. * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 300 + * ORDER BY APPROX_VECTOR_DISTANCE(word, $dinnerVector) + * LIMIT 300 * 8. Check that there are 10 words returned. * 9. Check that the word is in the word set from the step 5. */ @@ -852,10 +863,7 @@ - (void) testIndexUpdaterSetFloatArrayVectors { Assert([updater finishWithError: &error]); - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 - queryDistance: false - andClause: nil - checkTraining: false]; + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: 300]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 10); @@ -1090,10 +1098,10 @@ - (void) testIndexUpdaterCaughtUp { * 7. Execute a vector search query. * - SELECT word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 300 + * ORDER BY APPROX_VECTOR_DISTANCE(word, $dinnerVector) + * LIMIT 300 * 8. Check that there are 0 words returned. */ - - (void) testNonFinishedIndexUpdaterNotUpdateIndex { [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; @@ -1112,11 +1120,7 @@ - (void) testNonFinishedIndexUpdaterNotUpdateIndex { // "Release" CBLIndexUpdater updater = nil; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 - queryDistance: false - andClause: nil - checkTraining: false]; - + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: 300]; AssertEqual(rs.allObjects.count, 0); } diff --git a/Objective-C/Tests/VectorSearchTest.h b/Objective-C/Tests/VectorSearchTest.h index 3d6d613e5..4d55eeb4b 100644 --- a/Objective-C/Tests/VectorSearchTest.h +++ b/Objective-C/Tests/VectorSearchTest.h @@ -51,18 +51,25 @@ NS_ASSUME_NONNULL_BEGIN - (void) deleteWordsIndex; -- (NSString*) wordsQueryStringWithLimit: (nullable NSNumber*)limit - queryDistance: (BOOL) queryDistance - andClause: (nullable NSString*)andClause; +/** For the test subclasses to override the default vector expression. */ +- (NSString*) wordsQueryDefaultExpression; -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (nullable NSNumber*)limit - queryDistance: (BOOL) queryDistance - andClause: (nullable NSString*)andClause +- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit + metric: (nullable NSString*)metric + vectorExpression: (nullable NSString*)vectorExpression + whereClause: (nullable NSString*)whereClause; + +- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit; + +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit + metric: (nullable NSString*)metric + vectorExpression: (nullable NSString*)vectorExpression + whereClause: (nullable NSString*)whereClause checkTraining: (BOOL) checkTraining; -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (nullable NSNumber*)limit; +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit; -- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (nullable NSNumber*)limit; +- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSUInteger)limit; - (NSDictionary*) toDocIDWordMap: (CBLQueryResultSet*)resultSet; diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index ef5477da8..ffba8e784 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -29,6 +29,7 @@ /** Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0001-Vector-Search.md */ + @implementation VectorSearchTest { CustomLogger* _logger; @@ -139,34 +140,50 @@ - (void) deleteWordsIndex { AssertFalse([[self.wordsCollection indexes: nil] containsObject: @"words_index"]); } -- (NSString*) wordsQueryStringWithLimit: (NSNumber*)limit - queryDistance: (BOOL) queryDistance - andClause: (NSString*)andClause { - NSString* sql = @"SELECT meta().id, word, catid"; - if (queryDistance) { - sql = [sql stringByAppendingFormat: @", VECTOR_DISTANCE(%@)", kWordsIndexName]; +- (NSString*) wordsQueryDefaultExpression { + return @"vector"; +} + +- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit + metric: (nullable NSString*)metric + vectorExpression: (nullable NSString*)vectorExpression + whereClause: (nullable NSString*)whereClause { + NSString* sql = [NSString stringWithFormat: @"SELECT meta().id, word, catid FROM %@ ", kWordsCollectionName]; + + if (whereClause) { + sql = [sql stringByAppendingFormat: @"WHERE %@ ", whereClause]; } - sql = [sql stringByAppendingFormat: @" FROM %@ WHERE VECTOR_MATCH(%@, $vector)", - kWordsCollectionName, kWordsIndexName]; - - if (andClause) { - sql = [sql stringByAppendingFormat: @" %@", andClause]; + if (!vectorExpression) { + vectorExpression = [self wordsQueryDefaultExpression]; } - if (limit) { - sql = [sql stringByAppendingFormat: @" LIMIT %d", [limit intValue]]; + if (metric) { + sql = [sql stringByAppendingFormat: @"ORDER BY APPROX_VECTOR_DISTANCE(%@, $vector, %@) ", vectorExpression, metric]; + } else { + sql = [sql stringByAppendingFormat: @"ORDER BY APPROX_VECTOR_DISTANCE(%@, $vector) ", vectorExpression]; } + + sql = [sql stringByAppendingFormat: @"LIMIT %lu", (unsigned long)limit]; + return sql; } -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSNumber*)limit - queryDistance: (BOOL) queryDistance - andClause: (NSString*)andClause +- (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit { + return [self wordsQueryStringWithLimit: limit metric: nil vectorExpression: nil whereClause: nil]; +} + +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit + metric: (NSString*)metric + vectorExpression: (NSString*)vectorExpression + whereClause: (NSString*)whereClause checkTraining: (BOOL) checkTraining { NSError* error; - NSString* sql = [self wordsQueryStringWithLimit: limit queryDistance: queryDistance andClause: andClause]; + NSString* sql = [self wordsQueryStringWithLimit: limit + metric: metric + vectorExpression: vectorExpression + whereClause: whereClause]; CBLQuery* query = [_wordDB createQuery: sql error: &error]; AssertNotNil(query); @@ -187,12 +204,20 @@ - (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSNumber*)limit return rs; } -- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSNumber*)limit { - return [self executeWordsQueryWithLimit: limit queryDistance: false andClause: nil checkTraining: true]; +- (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit { + return [self executeWordsQueryWithLimit: limit + metric: nil + vectorExpression: nil + whereClause: nil + checkTraining: true]; } -- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSNumber*)limit { - return [self executeWordsQueryWithLimit: limit queryDistance: false andClause: nil checkTraining: false]; +- (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSUInteger)limit { + return [self executeWordsQueryWithLimit: limit + metric: nil + vectorExpression: nil + whereClause: nil + checkTraining: false]; } - (NSDictionary*) toDocIDWordMap: (CBLQueryResultSet*)resultSet { @@ -205,6 +230,20 @@ - (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSNumber*)lim return wordMap; } +@end + +/** + * Test Spec: + * https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0001-Vector-Search.md + * + * Version: 2.0.9 + */ +@interface VectorSearchTest_Main : VectorSearchTest + +@end + +@implementation VectorSearchTest_Main + /** * 1. TestVectorIndexConfigurationDefaultValue * Description @@ -355,7 +394,8 @@ - (void) testCentroidsValidation { * 6. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 20 * 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” @@ -365,7 +405,7 @@ - (void) testCentroidsValidation { - (void) testCreateVectorIndex { [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); } @@ -386,7 +426,8 @@ - (void) testCreateVectorIndex { * 5. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 350 + * WHERE vector_match(words_index, ) + * LIMIT 350 * 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” @@ -405,7 +446,7 @@ - (void) testCreateVectorIndex { - (void) testUpdateVectorIndex { [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 350]; AssertEqual(rs.allObjects.count, 300); // Update docs: @@ -425,7 +466,7 @@ - (void) testUpdateVectorIndex { [self.wordsCollection deleteDocument: [self.wordsCollection documentWithID: @"word2" error :&error] error: &error]; - rs = [self executeWordsQueryWithLimit: @350]; + rs = [self executeWordsQueryWithLimit: 350]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 301); AssertEqualObjects(wordMap[@"word301"], [word301 stringForKey: @"word"]); @@ -455,7 +496,8 @@ - (void) testUpdateVectorIndex { * 6. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 350 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 350 * 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” @@ -491,7 +533,7 @@ - (void) testCreateVectorIndexWithInvalidVectors { [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(@"vector", 300, 8)]; // Query: - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 350]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 296); AssertNil(wordMap[@"word1"]); @@ -506,7 +548,7 @@ - (void) testCreateVectorIndexWithInvalidVectors { [self.wordsCollection saveDocument: auxDoc error: &error]; // Query: - rs = [self executeWordsQueryWithLimit: @350]; + rs = [self executeWordsQueryWithLimit: 350]; wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 295); AssertNil(wordMap[@"word5"]); @@ -529,7 +571,8 @@ - (void) testCreateVectorIndexWithInvalidVectors { * 6. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_pred_index, ) LIMIT 350 + * ORDER BY APPROX_VECTOR_DISTANCE(prediction(WordEmbedding, {'word': word}).vector, $dinerVector) + * LIMIT 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 300 results are returned. * 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” @@ -551,7 +594,11 @@ - (void) testCreateVectorIndexUsingPredictionModel { NSString* expr = @"prediction(WordEmbedding,{\"word\": word}).vector"; [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(expr, 300, 8)]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 350 + metric: nil + vectorExpression: expr + whereClause: nil + checkTraining: true]; AssertEqual(rs.allObjects.count, 300); // Create words.word301 with extwords.word1 content @@ -572,9 +619,14 @@ - (void) testCreateVectorIndexUsingPredictionModel { Assert([self.wordsCollection saveDocument: word1 error: &error]); // Delete words.word2 - [_wordsCollection deleteDocument: [self.wordsCollection documentWithID: @"word2" error : &error] error: &error]; - - rs = [self executeWordsQueryWithLimit: @350]; + [self.wordsCollection deleteDocument: [self.wordsCollection documentWithID: @"word2" error : &error] + error: &error]; + + rs = [self executeWordsQueryWithLimit: 350 + metric: nil + vectorExpression: expr + whereClause: nil + checkTraining: true]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 301); AssertEqualObjects(wordMap[@"word301"], [word301 stringForKey: @"word"]); @@ -606,7 +658,8 @@ - (void) testCreateVectorIndexUsingPredictionModel { * 7. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_pred_index, ) LIMIT 350 + * ORDER BY APPROX_VECTOR_DISTANCE(prediction(WordEmbedding, {'word': word}).vector, $dinerVector) + * LIMIT 350 * 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. @@ -626,15 +679,15 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { [auxDoc setArray: nil forKey: @"vector"]; Assert([self.wordsCollection saveDocument: auxDoc error: &error]); - auxDoc = [[_wordsCollection documentWithID: @"word2" error: &error] toMutable]; + auxDoc = [[self.wordsCollection documentWithID: @"word2" error: &error] toMutable]; [auxDoc setString: @"string" forKey: @"vector"]; Assert([self.wordsCollection saveDocument:auxDoc error:&error]); - auxDoc = [[_wordsCollection documentWithID: @"word3" error: &error] toMutable]; + auxDoc = [[self.wordsCollection documentWithID: @"word3" error: &error] toMutable]; [auxDoc removeValueForKey: @"vector"]; Assert([self.wordsCollection saveDocument:auxDoc error:&error]); - auxDoc = [[_wordsCollection documentWithID: @"word4" error: &error] toMutable]; + auxDoc = [[self.wordsCollection documentWithID: @"word4" error: &error] toMutable]; CBLMutableArray* vector = [auxDoc arrayForKey: @"vector"]; [vector removeValueAtIndex: 0]; Assert([self.wordsCollection saveDocument: auxDoc error: &error]); @@ -644,7 +697,11 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { [self createWordsIndexWithConfig: VECTOR_INDEX_CONFIG(expr, 300, 8)]; // Query: - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @350]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 350 + metric: nil + vectorExpression: expr + whereClause: nil + checkTraining: true]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 296); AssertNil(wordMap[@"word1"]); @@ -657,7 +714,11 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { [auxDoc setString: @"Fried Chicken" forKey: @"word"]; Assert([self.wordsCollection saveDocument: auxDoc error: &error]); - rs = [self executeWordsQueryWithLimit: @350]; + rs = [self executeWordsQueryWithLimit: 350 + metric: nil + vectorExpression: expr + whereClause: nil + checkTraining: true]; wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 295); AssertNil(wordMap[@"word5"]); @@ -680,7 +741,8 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { * 5. Create an SQL++ query * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 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” @@ -695,7 +757,7 @@ - (void) testCreateVectorIndexWithSQ { config.encoding = [CBLVectorEncoding scalarQuantizerWithType: kCBLSQ4]; [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); // Repeat using SQ6 @@ -705,7 +767,7 @@ - (void) testCreateVectorIndexWithSQ { [self createWordsIndexWithConfig: config]; // Rerun query: - rs = [self executeWordsQueryWithLimit: @20]; + rs = [self executeWordsQueryWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); // Repeat using SQ8 @@ -715,7 +777,7 @@ - (void) testCreateVectorIndexWithSQ { [self createWordsIndexWithConfig: config]; // Rerun query: - rs = [self executeWordsQueryWithLimit: @20]; + rs = [self executeWordsQueryWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); } @@ -735,7 +797,8 @@ - (void) testCreateVectorIndexWithSQ { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 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” @@ -747,7 +810,7 @@ - (void) testCreateVectorIndexWithNoneEncoding { config.encoding = [CBLVectorEncoding none]; [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); } @@ -768,7 +831,8 @@ - (void) testCreateVectorIndexWithNoneEncoding { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 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” @@ -785,7 +849,7 @@ - (void) testCreateVectorIndexWithPQ { [self createWordsIndexWithConfig: config]; // Query: - CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); // Delete index @@ -851,7 +915,8 @@ - (void) testSubquantizersValidation { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 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” @@ -864,7 +929,7 @@ - (void) testeCreateVectorIndexWithFixedTrainingSize { config.maxTrainingSize = 100; [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); } @@ -919,7 +984,8 @@ - (void) testValidateMinMaxTrainingSize { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 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 not trained by checking that the “Untrained index; @@ -932,83 +998,64 @@ - (void) testQueryUntrainedVectorIndex { config.maxTrainingSize = 500; [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryNoTrainingCheckWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); AssertFalse([self checkIndexWasTrained]); } /** - * 17. TestCreateVectorIndexWithCosineDistance + * 17. TestCreateVectorIndexWithDistanceMetric * Description - * Test that the vector index can be created and used with the cosine distance metric. + * Test that the vector index can be created with all supported distance metrics. * Steps * 1. Copy database words_db. - * 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 - * - metric: Cosine - * 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, ) LIMIT 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 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 { - CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); - config.metric = kCBLDistanceMetricCosine; - [self createWordsIndexWithConfig: config]; - - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20 queryDistance: true andClause: false checkTraining: false]; - NSArray* results = rs.allResults; - AssertEqual(results.count, 20); - for(CBLQueryResult* result in results){ - Assert([result doubleAtIndex: 3] > 0); - Assert([result doubleAtIndex: 3] < 1); - } -} - -/** - * 18. TestCreateVectorIndexWithEuclideanDistance - * Description - * Test that the vector index can be created and used with the euclidean distance metric. - * Steps - * 1. Copy database words_db. - * 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 - * - metric: Euclidean - * 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, ) LIMIT 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 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. + * 2. For each distance metric types : euclideanSquared, euclidean, cosine, and dot, + * create a vector index named "words_index" in _default.words collection: + * - expression: "vector" + * - dimensions: 300 + * - centroids : 8 + * - metric: + * 3. Check that the index is created without an error returned. + * 4. Create an SQL++ query with the correspoding SQL++ metric name string: + * "EUCLIDEAN_SQUARED", "EUCLIDEAN", "COSINE", and "DOT" + * - SELECT meta().id, word + * FROM _default.words + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector, "") + * LIMIT 20 + * 5. Check the explain() result of the query to ensure that the "words_index" is used. + * 6. Verify that the index was trained. + * 7. Execute the query and check that 20 results are returned. */ -- (void) testCreateVectorIndexWithEuclideanDistance { - CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); - config.metric = kCBLDistanceMetricEuclideanSquared; - [self createWordsIndexWithConfig: config]; +- (void) TestCreateVectorIndexWithDistanceMetric { + NSArray*metrics = @[ + @(kCBLDistanceMetricEuclideanSquared), + @(kCBLDistanceMetricEuclidean), + @(kCBLDistanceMetricCosine), + @(kCBLDistanceMetricDot) + ]; + + NSArray*metricNames = @[ + @"EUCLIDEAN_SQUARED", + @"EUCLIDEAN", + @"COSINE", + @"DOT" + ]; + + for (NSUInteger i = 0; i < metrics.count; i++) { + CBLDistanceMetric metic = (CBLDistanceMetric)[metrics[i] intValue]; + NSString* metricName = metricNames[i]; + + CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); + config.metric = metic; + [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20 queryDistance: true andClause: false checkTraining: false]; - NSArray* results = rs.allResults; - AssertEqual(results.count, 20); - for(CBLQueryResult* result in results){ - Assert([result doubleAtIndex: 3] > 0); + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 20 + metric: metricName + vectorExpression: @"vector" + whereClause: nil + checkTraining: true]; + NSArray* results = rs.allResults; + AssertEqual(results.count, 20); } } @@ -1056,7 +1103,8 @@ - (void) testCreateVectorIndexWithExistingName { * 5. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 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” @@ -1071,14 +1119,14 @@ - (void) testDeleteVectorIndex { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 20]; AssertEqual(rs.allObjects.count, 20); [self deleteWordsIndex]; [self expectError: CBLErrorDomain code: CBLErrorMissingIndex in: ^BOOL(NSError **err) { - NSString* sql = [self wordsQueryStringWithLimit: @20 queryDistance: false andClause: nil]; - return [self->_wordDB createQuery: sql error: err] != nil; + NSString* sql = [self wordsQueryStringWithLimit: 20 metric: nil vectorExpression: nil whereClause: nil]; + return [self.wordDB createQuery: sql error: err] != nil; }]; } @@ -1092,47 +1140,17 @@ - (void) testDeleteVectorIndex { * 2. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 20 * 3. Check that a CouchbaseLiteException is returned as the index doesn’t exist. */ - (void) testVectorMatchOnNonExistingIndex { [self expectError: CBLErrorDomain code: CBLErrorMissingIndex in: ^BOOL(NSError **err) { - NSString* sql = [self wordsQueryStringWithLimit: @20 queryDistance: false andClause: nil]; - return [self->_wordDB createQuery: sql error: err] != nil; + NSString* sql = [self wordsQueryStringWithLimit: 20 metric: nil vectorExpression: nil whereClause: nil]; + return [self.wordDB createQuery: sql error: err] != nil; }]; } -/** - * 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. - * Steps - * 1. Copy database words_db. - * 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 - * 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, ) - * 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 { - CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); - [self createWordsIndexWithConfig: config]; - - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: nil]; - AssertEqual(rs.allObjects.count, 3); -} - /** * 23. TestVectorMatchLimitBoundary * Description @@ -1149,7 +1167,8 @@ - (void) testVectorMatchDefaultLimit { * 4. Create an SQL++ query. * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT * - 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 @@ -1162,24 +1181,24 @@ - (void) testVectorMatchLimitBoundary { // Check valid query with 1 and 10000 set limit for (NSNumber* limit in @[@1, @10000]) { NSError* error; - NSString* sql = [self wordsQueryStringWithLimit: limit queryDistance: false andClause: nil]; - Assert([_wordDB createQuery: sql error: &error]); + NSString* sql = [self wordsQueryStringWithLimit: [limit unsignedIntegerValue]]; + Assert([self.wordDB createQuery: sql error: &error]); AssertNil(error); } // Check if error thrown for wrong limit values for (NSNumber* limit in @[@-1, @0, @10001]) { [self expectError: CBLErrorDomain code: CBLErrorInvalidQuery in: ^BOOL(NSError** err) { - NSString* sql = [self wordsQueryStringWithLimit: limit queryDistance: false andClause: nil]; - return [self->_wordDB createQuery: sql error: err] != nil; + NSString* sql = [self wordsQueryStringWithLimit: [limit unsignedIntegerValue]]; + return [self.wordDB createQuery: sql error: err] != nil; }]; } } /** - * 24. TestVectorMatchWithAndExpression + * 24. TestHybridVectorSearch * Description - * Test that vector_match can be used in AND expression. + * Test a simple hybrid search with WHERE clause. * Steps * 1. Copy database words_db. * 2. Register a custom logger to capture the INFO log. @@ -1191,7 +1210,9 @@ - (void) testVectorMatchLimitBoundary { * 5. Create an SQL++ query. * - SELECT word, catid * FROM _default.words - * WHERE vector_match(words_index, ) AND catid = 'cat1' LIMIT 300 + * WHERE catid = "cat1" + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 300 * 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 @@ -1200,13 +1221,14 @@ - (void) testVectorMatchLimitBoundary { * doesn’t exist in the log. * 10. Reset the custom logger. */ -- (void) testVectorMatchWithAndExpression { +- (void) testHybridVectorSearch { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 - queryDistance: false - andClause: @"AND catid = 'cat1'" + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 300 + metric: nil + vectorExpression: @"vector" + whereClause: @"catid = 'cat1'" checkTraining: true]; NSArray* results = rs.allResults; @@ -1217,9 +1239,9 @@ - (void) testVectorMatchWithAndExpression { } /** - * 25. TestVectorMatchWithMultipleAndExpression + * 25. TestHybridVectorSearchWithAND * Description - * Test that vector_match can be used in multiple AND expressions. + * Test hybrid search with multiple AND * Steps * 1. Copy database words_db. * 2. Register a custom logger to capture the INFO log. @@ -1231,7 +1253,9 @@ - (void) testVectorMatchWithAndExpression { * 5. Create an SQL++ query. * - SELECT word, catid * FROM _default.words - * WHERE (vector_match(words_index, ) AND word is valued) AND catid = 'cat1' LIMIT 300 + * WHERE catid = "cat1" AND word is valued + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 300 * 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 @@ -1240,13 +1264,14 @@ - (void) testVectorMatchWithAndExpression { * doesn’t exist in the log. * 10. Reset the custom logger. */ -- (void) testVectorMatchWithMultipleAndExpression { +- (void) testHybridVectorSearchWithAND { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300 - queryDistance: false - andClause: @"AND word is valued AND catid = 'cat1'" + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 300 + metric: nil + vectorExpression: @"vector" + whereClause: @"word is valued AND catid = 'cat1'" checkTraining: true]; NSArray* results = rs.allResults; @@ -1257,9 +1282,9 @@ - (void) testVectorMatchWithMultipleAndExpression { } /** - * 26. TestInvalidVectorMatchWithOrExpression + * 26. TestInvalidHybridVectorSearchWithOR * Description - * Test that vector_match cannot be used with OR expression. + * Test that APPROX_VECTOR_DISTANCE cannot be used with OR expression. * Steps * 1. Copy database words_db. * 2. Create a vector index named "words_index" in _default.words collection. @@ -1270,18 +1295,21 @@ - (void) testVectorMatchWithMultipleAndExpression { * 4. Create an SQL++ query. * - SELECT word, catid * FROM _default.words - * WHERE vector_match(words_index, ) OR catid = 1 LIMIT 20 + * WHERE APPROX_VECTOR_DISTANCE(vector, $dinerVector) < 10 OR catid = 'cat1' + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 20 * 5. Check that a CouchbaseLiteException is returned when creating the query. */ -- (void) testInvalidVectorMatchWithOrExpression { +- (void) testInvalidHybridVectorSearchWithOR { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); [self createWordsIndexWithConfig: config]; [self expectError: CBLErrorDomain code: CBLErrorInvalidQuery in: ^BOOL(NSError **err) { - NSString* sql = [self wordsQueryStringWithLimit: @20 - queryDistance: false - andClause: @"OR catid = 'cat1'"]; - return [self->_wordDB createQuery: sql error: err] != nil; + NSString* sql = [self wordsQueryStringWithLimit: 20 + metric: nil + vectorExpression: @"vector" + whereClause: @"APPROX_VECTOR_DISTANCE(vector, $vector) OR catid = 'cat1'"]; + return [self.wordDB createQuery: sql error: err] != nil; }]; } @@ -1303,9 +1331,10 @@ - (void) testInvalidVectorMatchWithOrExpression { * - centroids : 8 * 6. Check that the index is created without an error returned. * 7. Create an SQL++ query: - * - SELECT meta().id, word, vector_distance(words_index) + * - SELECT meta().id, word, * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 20 + * WHERE vector_match(words_index, ) + * LIMIT 20 * 8. Execute the query and check that 20 results are returned. * 9. Check that the result also contains doc id = word49. */ @@ -1318,7 +1347,7 @@ - (void) testIndexVectorInBase64 { CBLVectorIndexConfiguration* config = VECTOR_INDEX_CONFIG(@"vector", 300, 8); [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @20]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 20]; NSDictionary* wordMap = [self toDocIDWordMap: rs]; AssertEqual(wordMap.count, 20); AssertNotNil(wordMap[@"word49"]); @@ -1340,7 +1369,8 @@ - (void) testIndexVectorInBase64 { * 4. Create an SQL++ query: * - SELECT meta().id, word * FROM _default.words - * WHERE vector_match(words_index, ) LIMIT 300 + * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + * LIMIT 300 * 5. Execute the query and check that 20 results are returned. * 6. Repeat step 2 - 6 but change the numProbes to 1. * 7. Verify the number of results returned in Step 5 is larger than Step 6. @@ -1350,13 +1380,13 @@ - (void) testNumProbes { config.numProbes = 5; [self createWordsIndexWithConfig: config]; - CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: @300]; + CBLQueryResultSet* rs = [self executeWordsQueryWithLimit: 300]; NSUInteger numResultsFor5Probes = [[rs allObjects] count]; Assert(numResultsFor5Probes > 0); config.numProbes = 1; [self createWordsIndexWithConfig: config]; - rs = [self executeWordsQueryWithLimit: @300]; + rs = [self executeWordsQueryWithLimit: 300]; NSUInteger numResultsFor1Probes = [[rs allObjects] count]; Assert(numResultsFor1Probes > 0); diff --git a/Swift/Tests/VectorLazyIndexTest.swift b/Swift/Tests/VectorSearchTest+Lazy.swift similarity index 96% rename from Swift/Tests/VectorLazyIndexTest.swift rename to Swift/Tests/VectorSearchTest+Lazy.swift index 7559ae209..260e9230d 100644 --- a/Swift/Tests/VectorLazyIndexTest.swift +++ b/Swift/Tests/VectorSearchTest+Lazy.swift @@ -1,5 +1,5 @@ // -// LazyVectorIndex.swift +// VectorSearchTest+Lazy.swift // CouchbaseLite // // Copyright (c) 2024 Couchbase, Inc. All rights reserved. @@ -19,27 +19,34 @@ import XCTest @testable import CouchbaseLiteSwift -/** - * Test Spec : - * https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0002-Lazy-Vector-Index.md - * - * Note: - * - Tested in Objective-C - * - Test 4 TestGetExistingNonVectorIndex - * - Test 8 TestLazyVectorIndexNotAutoUpdatedChangedDocs - * - Test 9 TestLazyVectorIndexAutoUpdateDeletedDocs - * - Test 10 TestLazyVectorIndexAutoUpdatePurgedDocs - * - Test 11 TestIndexUpdaterBeginUpdateOnNonVectorIndex - * - Test 20 TestIndexUpdaterSetInvalidVectorDimensions - * - Test 22 TestIndexUpdaterFinishWithIncompletedUpdate - * - Test 23 TestIndexUpdaterCaughtUp - * - Test 24 TestNonFinishedIndexUpdaterNotUpdateIndex - * - Test 26 TestIndexUpdaterCallFinishTwice - * - Test 27 TestIndexUpdaterUseAfterFinished - * - Test 6 TestGetIndexOnClosedDatabase is done in CollectionTest.testUseCollectionAPIWhenDatabaseIsClosed() - * - Test 7 testInvalidCollection) is done in CollectionTest.testUseCollectionAPIOnDeletedCollection() - */ -class VectorLazyIndexTest: VectorSearchTest { +/// +/// Test Spec : +/// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0002-Lazy-Vector-Index.md +/// +/// Vesion: 2.0.1 +/// +/// Note: +/// - Tested in Objective-C +/// - Test 4 TestGetExistingNonVectorIndex +/// - Test 8 TestLazyVectorIndexNotAutoUpdatedChangedDocs +/// - Test 9 TestLazyVectorIndexAutoUpdateDeletedDocs +/// - Test 10 TestLazyVectorIndexAutoUpdatePurgedDocs +/// - Test 11 TestIndexUpdaterBeginUpdateOnNonVectorIndex +/// - Test 20 TestIndexUpdaterSetInvalidVectorDimensions +/// - Test 22 TestIndexUpdaterFinishWithIncompletedUpdate +/// - Test 23 TestIndexUpdaterCaughtUp +/// - Test 24 TestNonFinishedIndexUpdaterNotUpdateIndex +/// - Test 26 TestIndexUpdaterCallFinishTwice +/// - Test 27 TestIndexUpdaterUseAfterFinished +/// - Test 6 TestGetIndexOnClosedDatabase is done in CollectionTest.testUseCollectionAPIWhenDatabaseIsClosed() +/// - Test 7 testInvalidCollection) is done in CollectionTest.testUseCollectionAPIOnDeletedCollection() +/// +class VectorSearchTest_Lazy : VectorSearchTest { + /// Override the default VectorSearch Expression + override func wordsQueryDefaultExpression() -> String{ + return "word" + } + func wordsIndex() throws -> QueryIndex { let index = try wordsCollection.index(withName: wordsIndexName) XCTAssertNotNil(index) @@ -622,7 +629,8 @@ class VectorLazyIndexTest: VectorSearchTest { /// 7. Execute a vector search query. /// - SELECT word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 300 + /// ORDER BY APPROX_VECTOR_DISTANCE(word, $dinnerVector) + /// LIMIT 300 /// 8. Check that there are 10 words returned. /// 9. Check that the word is in the word set from the step 5. func testIndexUpdaterSetFloatArrayVectors() throws { diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 34ff95a67..36dbc8878 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -129,30 +129,43 @@ class VectorSearchTest: CBLTestCase { try wordsCollection.deleteIndex(forName: wordsIndexName) } - func wordsQueryString(limit: Int?, queryDistance: Bool = false, andExpr: String? = nil) -> String { - var sql = "SELECT meta().id, word, catid" - if (queryDistance) { - sql = sql + ", VECTOR_DISTANCE(\(wordsIndexName)) " - } else { - sql = sql + " " - } - sql = sql + "FROM \(wordsCollectionName) WHERE VECTOR_MATCH(\(wordsIndexName), $vector)" + func wordsQueryDefaultExpression() -> String { + return "vector" + } + + func wordsQueryString(limit: Int, + metric: String? = nil, + vectorExpression: String? = nil, + whereExpression: String? = nil) -> String { + var sql = "SELECT meta().id, word, catid " + + sql = sql + "FROM \(wordsCollectionName) " - if let andExpr = andExpr { - sql = sql + " \(andExpr)" + if let whereExpr = whereExpression { + sql = sql + "WHERE \(whereExpr) " } - if let limit = limit { - sql = sql + " LIMIT \(limit)" + let expr = vectorExpression != nil ? vectorExpression! : self.wordsQueryDefaultExpression() + + if let metric = metric { + sql = sql + "ORDER BY APPROX_VECTOR_DISTANCE(\(expr), $vector, \"\(metric)\") " + } else { + sql = sql + "ORDER BY APPROX_VECTOR_DISTANCE(\(expr), $vector) " } + sql = sql + "LIMIT \(limit)" + return sql; } - func executeWordsQuery(limit: Int? = nil, checkTraining: Bool = true, queryDistance: Bool = false, - andExpr: String? = nil) throws -> ResultSet - { - let sql = wordsQueryString(limit: limit, queryDistance: queryDistance, andExpr: andExpr) + func executeWordsQuery(limit: Int, + metric: String? = nil, + vectorExpression: String? = nil, + whereExpression: String? = nil, checkTraining: Bool = true) throws -> ResultSet { + let sql = wordsQueryString(limit: limit, + metric: metric, + vectorExpression: vectorExpression, + whereExpression: whereExpression) let query = try wordDB.createQuery(sql) let parameters = Parameters() @@ -168,6 +181,15 @@ class VectorSearchTest: CBLTestCase { } return rs } +} + +/// +/// Test Spec: +/// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0001-Vector-Search.md +/// +/// Version: 2.0.9 +/// +class VectorSearchTest_Main: VectorSearchTest { /// 1. TestVectorIndexConfigurationDefaultValue /// Description @@ -310,7 +332,8 @@ class VectorSearchTest: CBLTestCase { /// 6. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 20 /// 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” @@ -340,7 +363,8 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 350 + /// WHERE vector_match(words_index, ) + /// LIMIT 350 /// 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” @@ -411,7 +435,8 @@ class VectorSearchTest: CBLTestCase { /// 6. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 350 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 350 /// 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” @@ -480,7 +505,8 @@ class VectorSearchTest: CBLTestCase { /// 6. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_pred_index, ) LIMIT 350 + /// ORDER BY APPROX_VECTOR_DISTANCE(prediction(WordEmbedding, {'word': word}).vector, $dinerVector) + /// LIMIT 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 300 results are returned. /// 9. Verify that the index was trained by checking that the “Untrained index; queries may be slow” @@ -498,12 +524,12 @@ class VectorSearchTest: CBLTestCase { func testCreateVectorIndexUsingPredictionModel() throws { try registerPredictiveModel() - let exp = "prediction(WordEmbedding,{\"word\": word}).vector" - let config = VectorIndexConfiguration(expression: exp, dimensions: 300, centroids: 8) + let expr = "prediction(WordEmbedding,{\"word\": word}).vector" + let config = VectorIndexConfiguration(expression: expr, dimensions: 300, centroids: 8) try createWordsIndex(config: config) // Query: - var rs = try executeWordsQuery(limit: 350) + var rs = try executeWordsQuery(limit: 350, vectorExpression: expr) XCTAssertEqual(rs.allResults().count, 300) XCTAssert(checkIndexWasTrained()) @@ -528,7 +554,7 @@ class VectorSearchTest: CBLTestCase { // Delete words.word2 try wordsCollection.delete(document: wordsCollection.document(id: "word2")!) - rs = try executeWordsQuery(limit: 350) + rs = try executeWordsQuery(limit: 350, vectorExpression: expr) let wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 301) XCTAssertEqual(wordMap["word301"], word301.string(forKey: "word")) @@ -561,7 +587,8 @@ class VectorSearchTest: CBLTestCase { /// 7. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_pred_index, ) LIMIT 350 + /// ORDER BY APPROX_VECTOR_DISTANCE(prediction(WordEmbedding, {'word': word}).vector, $dinerVector) + /// LIMIT 350 /// 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. @@ -592,11 +619,11 @@ class VectorSearchTest: CBLTestCase { vector!.removeValue(at: 0) try wordsCollection.save(document: auxDoc) - let exp = "prediction(WordEmbedding,{\"word\": word}).vector" - let config = VectorIndexConfiguration(expression: exp, dimensions: 300, centroids: 8) + let expr = "prediction(WordEmbedding,{\"word\": word}).vector" + let config = VectorIndexConfiguration(expression: expr, dimensions: 300, centroids: 8) try createWordsIndex(config: config) - var rs = try executeWordsQuery(limit: 350) + var rs = try executeWordsQuery(limit: 350, vectorExpression: expr) var wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 296) XCTAssertNil(wordMap["word1"]) @@ -609,7 +636,7 @@ class VectorSearchTest: CBLTestCase { auxDoc.setString("Fried Chicken", forKey: "word") try wordsCollection.save(document: auxDoc) - rs = try executeWordsQuery(limit: 350) + rs = try executeWordsQuery(limit: 350, vectorExpression: expr) wordMap = toDocIDWordMap(rs: rs) XCTAssertEqual(wordMap.count, 295) XCTAssertNil(wordMap["word5"]) @@ -631,7 +658,8 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 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” @@ -684,7 +712,8 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 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” @@ -699,9 +728,6 @@ class VectorSearchTest: CBLTestCase { XCTAssertEqual(rs.allResults().count, 20) } - /// 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 @@ -718,7 +744,8 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 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” @@ -727,7 +754,7 @@ class VectorSearchTest: CBLTestCase { /// 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 { - for numberOfBits in [8, 4, /*12*/] { + for numberOfBits in [8, 4, 12] { // Create vector index var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) config.encoding = .productQuantizer(subquantizers: 5, bits: UInt32(numberOfBits)) @@ -804,7 +831,8 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 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” @@ -867,7 +895,8 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 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 not trained by checking that the “Untrained index; @@ -885,73 +914,39 @@ class VectorSearchTest: CBLTestCase { XCTAssertFalse(checkIndexWasTrained()) } - /// 17. TestCreateVectorIndexWithCosineDistance - /// Description - /// Test that the vector index can be created and used with the cosine distance metric. - /// Steps - /// 1. Copy database words_db. - /// 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 - /// - metric: Cosine - /// 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, ) LIMIT 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 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 { - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) - config.metric = .cosine - try createWordsIndex(config: config) - - let rs = try executeWordsQuery(limit: 20, queryDistance: true) - let results = rs.allResults() - XCTAssertEqual(results.count, 20) - for result in results { - XCTAssert(result.double(at: 3) > 0) - XCTAssert(result.double(at: 3) < 1) - } - } - - /// 18. TestCreateVectorIndexWithEuclideanDistance + /// + /// 17. TestCreateVectorIndexWithDistanceMetric /// Description - /// Test that the vector index can be created and used with the euclidean distance metric. + /// Test that the vector index can be created with all supported distance metrics. /// Steps - /// 1. Copy database words_db. - /// 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 - /// - metric: Euclidean - /// 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, ) LIMIT 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 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 { - var config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) - config.metric = .euclideanSquared - try createWordsIndex(config: config) - - let rs = try executeWordsQuery(limit: 20) - XCTAssertEqual(rs.allResults().count, 20) - for result in rs.allResults() { - XCTAssert(result.double(at: 3) > 0) + /// 1. Copy database words_db. + /// 2. For each distance metric types : euclideanSquared, euclidean, cosine, and dot, + /// create a vector index named "words_index" in _default.words collection: + /// - expression: "vector" + /// - dimensions: 300 + /// - centroids : 8 + /// - metric: + /// 3. Check that the index is created without an error returned. + /// 4. Create an SQL++ query with the correspoding SQL++ metric name string: + /// "EUCLIDEAN_SQUARED", "EUCLIDEAN", "COSINE", and "DOT" + /// - SELECT meta().id, word + /// FROM _default.words + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector, "") + /// LIMIT 20 + /// 5. Check the explain() result of the query to ensure that the "words_index" is used. + /// 6. Verify that the index was trained. + /// 7. Execute the query and check that 20 results are returned. + func testCreateVectorIndexWithDistanceMetric() throws { + let metrics: [DistanceMetric] = [.euclideanSquared, .euclidean, .cosine, .dot] + let metricNames = ["EUCLIDEAN_SQUARED", "EUCLIDEAN", "COSINE", "DOT"] + for i in 0..) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 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” @@ -1033,7 +1029,8 @@ class VectorSearchTest: CBLTestCase { /// 2. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 20 /// 3. Check that a CouchbaseLiteException is returned as the index doesn’t exist. func testVectorMatchOnNonExistingIndex() throws { self.expectError(domain: CBLError.domain, code: CBLError.missingIndex) { @@ -1041,35 +1038,6 @@ 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. - /// Steps - /// 1. Copy database words_db. - /// 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 - /// 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, ) - /// 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 config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) - try createWordsIndex(config: config) - - let rs = try executeWordsQuery() - XCTAssertEqual(rs.allResults().count, 3) - } - /// 23. TestVectorMatchLimitBoundary /// Description /// Test vector_match’s limit boundary which is between 1 - 10000 inclusively. When @@ -1085,7 +1053,8 @@ class VectorSearchTest: CBLTestCase { /// 4. Create an SQL++ query. /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT /// - 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 @@ -1107,9 +1076,9 @@ class VectorSearchTest: CBLTestCase { } } - /// 24. TestVectorMatchWithAndExpression + /// 24. TestHybridVectorSearch /// Description - /// Test that vector_match can be used in AND expression. + /// Test a simple hybrid search with WHERE clause. /// Steps /// 1. Copy database words_db. /// 2. Register a custom logger to capture the INFO log. @@ -1121,7 +1090,9 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words - /// WHERE vector_match(words_index, ) AND catid = 'cat1' LIMIT 300 + /// WHERE catid = "cat1" + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 300 /// 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 @@ -1129,11 +1100,11 @@ class VectorSearchTest: CBLTestCase { /// 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 { + func TestHybridVectorSearch() throws { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try createWordsIndex(config: config) - let rs = try executeWordsQuery(limit: 300, andExpr: "AND catid = 'cat1'") + let rs = try executeWordsQuery(limit: 300, whereExpression: "catid = 'cat1'") let results = rs.allResults() XCTAssertEqual(results.count, 50) for result in results { @@ -1141,9 +1112,9 @@ class VectorSearchTest: CBLTestCase { } } - /// 25. TestVectorMatchWithMultipleAndExpression + /// 25. TestHybridVectorSearchWithAND /// Description - /// Test that vector_match can be used in multiple AND expressions. + /// Test hybrid search with multiple AND /// Steps /// 1. Copy database words_db. /// 2. Register a custom logger to capture the INFO log. @@ -1155,7 +1126,9 @@ class VectorSearchTest: CBLTestCase { /// 5. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words - /// WHERE (vector_match(words_index, ) AND word is valued) AND catid = 'cat1' LIMIT 300 + /// WHERE catid = "cat1" AND word is valued + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 300 /// 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 @@ -1163,11 +1136,11 @@ class VectorSearchTest: CBLTestCase { /// 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 { + func TestHybridVectorSearchWithAND() throws { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try createWordsIndex(config: config) - let rs = try executeWordsQuery(limit: 300, andExpr: "AND word is valued AND catid = 'cat1'") + let rs = try executeWordsQuery(limit: 300, whereExpression: "word is valued AND catid = 'cat1'") let results = rs.allResults() XCTAssertEqual(results.count, 50) for result in results { @@ -1175,9 +1148,9 @@ class VectorSearchTest: CBLTestCase { } } - /// 26. TestInvalidVectorMatchWithOrExpression + /// 26. TestInvalidHybridVectorSearchWithOR /// Description - /// Test that vector_match cannot be used with OR expression. + /// Test that APPROX_VECTOR_DISTANCE cannot be used with OR expression. /// Steps /// 1. Copy database words_db. /// 2. Create a vector index named "words_index" in _default.words collection. @@ -1188,14 +1161,16 @@ class VectorSearchTest: CBLTestCase { /// 4. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words - /// WHERE vector_match(words_index, ) OR catid = 1 LIMIT 20 + /// WHERE APPROX_VECTOR_DISTANCE(vector, $dinerVector) < 10 OR catid = 'cat1' + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 20 /// 5. Check that a CouchbaseLiteException is returned when creating the query. - func testInvalidVectorMatchWithOrExpression() throws { + func TestInvalidHybridVectorSearchWithOR() throws { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try createWordsIndex(config: config) self.expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { - _ = try self.executeWordsQuery(limit: 300, andExpr: "OR catid = 'cat1'") + _ = try self.executeWordsQuery(limit: 300, whereExpression: "APPROX_VECTOR_DISTANCE(vector, $vector) OR catid = 'cat1'") } } @@ -1214,9 +1189,10 @@ class VectorSearchTest: CBLTestCase { /// - centroids : 8 /// 6. Check that the index is created without an error returned. /// 7. Create an SQL++ query: - /// - SELECT meta().id, word, vector_distance(words_index) - /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 20 + /// - SELECT meta().id, word, + /// FROM _default.words + /// WHERE vector_match(words_index, ) + /// LIMIT 20 /// 8. Execute the query and check that 20 results are returned. /// 9. Check that the result also contains doc id = word49. func testIndexVectorInBase64() throws { @@ -1247,7 +1223,8 @@ class VectorSearchTest: CBLTestCase { /// 4. Create an SQL++ query: /// - SELECT meta().id, word /// FROM _default.words - /// WHERE vector_match(words_index, ) LIMIT 300 + /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) + /// LIMIT 300 /// 5. Execute the query and check that 20 results are returned. /// 6. Repeat step 2 - 6 but change the numProbes to 1. /// 7. Verify the number of results returned in Step 5 is larger than Step 6. @@ -1267,4 +1244,5 @@ class VectorSearchTest: CBLTestCase { XCTAssert(numResultsFor5Probes > numResultsFor1Probes) } + } diff --git a/Tests/Extensions/version.txt b/Tests/Extensions/version.txt index 645ad1271..556fc95d9 100644 --- a/Tests/Extensions/version.txt +++ b/Tests/Extensions/version.txt @@ -1 +1 @@ -1.0.0-52 +1.0.0-56 diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 36aec1c42..d708da28f 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 36aec1c423d0a450ebb293807c5aaf270eaff976 +Subproject commit d708da28f6af945153d3b6fc715c0752c5de4409 From 000d542e7cb72386f0c98c3d5dc8f605905d558a Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Thu, 11 Jul 2024 14:41:09 -0700 Subject: [PATCH 31/49] CBL-5990 : Fix _kCBLDefaultLogFileUsePlaintext symbol (#3305) * Fixed _kCBLDefaultLogFileUsePlaintext symbol. * Added missing deprecated kCBLDefaultLogFileUsePlainText. --- Objective-C/CBLDefaults.h | 3 +++ Objective-C/CBLDefaults.m | 2 ++ Objective-C/Exports/CBL.txt | 1 + Objective-C/Exports/Generated/CBL.exp | 1 + Objective-C/Exports/Generated/CBL_EE.exp | 1 + Swift/Defaults.swift | 4 ++++ 6 files changed, 12 insertions(+) diff --git a/Objective-C/CBLDefaults.h b/Objective-C/CBLDefaults.h index 5b7d43e43..0536aa1d7 100644 --- a/Objective-C/CBLDefaults.h +++ b/Objective-C/CBLDefaults.h @@ -33,6 +33,9 @@ /** [NO] Plaintext is not used, and instead binary encoding is used in log files */ extern const BOOL kCBLDefaultLogFileUsePlaintext; +/** [NO] Plaintext is not used, and instead binary encoding is used in log files */ +extern const BOOL kCBLDefaultLogFileUsePlainText __deprecated_msg("Use kCBLDefaultLogFileUsePlaintext instead."); + /** [524288] 512 KiB for the size of a log file */ extern const uint64_t kCBLDefaultLogFileMaxSize; diff --git a/Objective-C/CBLDefaults.m b/Objective-C/CBLDefaults.m index 2fd31fbe2..36140a8cf 100644 --- a/Objective-C/CBLDefaults.m +++ b/Objective-C/CBLDefaults.m @@ -26,6 +26,8 @@ const BOOL kCBLDefaultLogFileUsePlaintext = NO; +const BOOL kCBLDefaultLogFileUsePlainText = NO; + const uint64_t kCBLDefaultLogFileMaxSize = 524288; const NSInteger kCBLDefaultLogFileMaxRotateCount = 1; diff --git a/Objective-C/Exports/CBL.txt b/Objective-C/Exports/CBL.txt index 2fe82be9a..ad423663c 100644 --- a/Objective-C/Exports/CBL.txt +++ b/Objective-C/Exports/CBL.txt @@ -95,6 +95,7 @@ _kCBLDefaultCollectionName _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize +_kCBLDefaultLogFileUsePlaintext _kCBLDefaultLogFileUsePlainText _kCBLDefaultReplicatorAcceptParentCookies _kCBLDefaultReplicatorAllowReplicatingInBackground diff --git a/Objective-C/Exports/Generated/CBL.exp b/Objective-C/Exports/Generated/CBL.exp index 2bd035767..9062cefab 100644 --- a/Objective-C/Exports/Generated/CBL.exp +++ b/Objective-C/Exports/Generated/CBL.exp @@ -75,6 +75,7 @@ _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize _kCBLDefaultLogFileUsePlainText +_kCBLDefaultLogFileUsePlaintext _kCBLDefaultReplicatorAcceptParentCookies _kCBLDefaultReplicatorAllowReplicatingInBackground _kCBLDefaultReplicatorContinuous diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp index 795be47c5..5084aa65e 100644 --- a/Objective-C/Exports/Generated/CBL_EE.exp +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -117,6 +117,7 @@ _kCBLDefaultListenerReadOnly _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize _kCBLDefaultLogFileUsePlainText +_kCBLDefaultLogFileUsePlaintext _kCBLDefaultReplicatorAcceptParentCookies _kCBLDefaultReplicatorAllowReplicatingInBackground _kCBLDefaultReplicatorContinuous diff --git a/Swift/Defaults.swift b/Swift/Defaults.swift index d6fbee503..75584f8ba 100644 --- a/Swift/Defaults.swift +++ b/Swift/Defaults.swift @@ -26,6 +26,10 @@ public extension LogFileConfiguration { /// [false] Plaintext is not used, and instead binary encoding is used in log files static let defaultUsePlaintext: Bool = false + + /// [false] Plaintext is not used, and instead binary encoding is used in log files + /// @available(*, deprecated, message: "Use LogFileConfiguration.defaultUsePlaintext instead.") + static let defaultUsePlainText: Bool = false /// [524288] 512 KiB for the size of a log file static let defaultMaxSize: UInt64 = 524288 From 42c172dd7820527c7f26a779581f1de1c3c63276 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Fri, 12 Jul 2024 11:30:55 -0700 Subject: [PATCH 32/49] Update LiteCore 3.2.0-219 (#3306) * Used LiteCore RC v3.2.0-219 (554bf8f7) * Used VS 1.0.0-57 * Fixed objective-c API doc generating script. * Fixed VectorSearchTest 17 and 26. * Removed non-existing file from xcode project. --- CouchbaseLite.xcodeproj/project.pbxproj | 2 -- Objective-C/Tests/VectorSearchTest.m | 14 +++++++------- Scripts/generate_objc_release_zip.sh | 5 +++-- Swift/Tests/VectorSearchTest.swift | 8 ++++---- Tests/Extensions/version.txt | 2 +- vendor/couchbase-lite-core | 2 +- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index 9e781a476..e5cfc2615 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -2160,7 +2160,6 @@ 1A4160D922836C7F0061A567 /* ReplicatorTest+Main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "ReplicatorTest+Main.m"; sourceTree = ""; }; 1A4FE769225ED344009D5F43 /* MiscCppTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MiscCppTest.mm; sourceTree = ""; }; 1A53B2852966AB910010A73E /* FullTextIndexExpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullTextIndexExpression.swift; sourceTree = ""; }; - 1A5E51D824AAC70E002E0341 /* pull_request_build.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = pull_request_build.sh; sourceTree = ""; }; 1A6084EB2875842A0037C66F /* CollectionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTest.swift; sourceTree = ""; }; 1A621D6C2887DCE70017F905 /* QueryTest+Collection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "QueryTest+Collection.m"; sourceTree = ""; }; 1A690CC824214EE70084D017 /* ReplicatorTest+PendingDocIds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReplicatorTest+PendingDocIds.swift"; sourceTree = ""; }; @@ -3830,7 +3829,6 @@ 1AF98EB827AD22C500B3EA5F /* generate_swift_release_zip.sh */, 9388CB9321BCDF8B005CA66D /* get_repo_version.sh */, 9388CB7621BCDF8B005CA66D /* prepare_cocoapods.sh */, - 1A5E51D824AAC70E002E0341 /* pull_request_build.sh */, 40EF68102B71891A00F0CB50 /* remove_private_module.sh */, 9388CB9521BCDF8B005CA66D /* strip_frameworks.sh */, 9367E86E24948C3F00775F97 /* xctest_crash_log.sh */, diff --git a/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index ffba8e784..804b63b63 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -159,7 +159,7 @@ - (NSString*) wordsQueryStringWithLimit: (NSUInteger)limit } if (metric) { - sql = [sql stringByAppendingFormat: @"ORDER BY APPROX_VECTOR_DISTANCE(%@, $vector, %@) ", vectorExpression, metric]; + sql = [sql stringByAppendingFormat: @"ORDER BY APPROX_VECTOR_DISTANCE(%@, $vector, \"%@\") ", vectorExpression, metric]; } else { sql = [sql stringByAppendingFormat: @"ORDER BY APPROX_VECTOR_DISTANCE(%@, $vector) ", vectorExpression]; } @@ -236,7 +236,7 @@ - (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSUInteger)li * Test Spec: * https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0001-Vector-Search.md * - * Version: 2.0.9 + * Version: 2.1.0 */ @interface VectorSearchTest_Main : VectorSearchTest @@ -1023,10 +1023,10 @@ - (void) testQueryUntrainedVectorIndex { * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector, "") * LIMIT 20 * 5. Check the explain() result of the query to ensure that the "words_index" is used. - * 6. Verify that the index was trained. - * 7. Execute the query and check that 20 results are returned. + * 6. Execute the query and check that 20 results are returned. + * 7. Verify that the index was trained. */ -- (void) TestCreateVectorIndexWithDistanceMetric { +- (void) testCreateVectorIndexWithDistanceMetric { NSArray*metrics = @[ @(kCBLDistanceMetricEuclideanSquared), @(kCBLDistanceMetricEuclidean), @@ -1295,7 +1295,7 @@ - (void) testHybridVectorSearchWithAND { * 4. Create an SQL++ query. * - SELECT word, catid * FROM _default.words - * WHERE APPROX_VECTOR_DISTANCE(vector, $dinerVector) < 10 OR catid = 'cat1' + * WHERE APPROX_VECTOR_DISTANCE(vector, $dinerVector) < 0.5 OR catid = 'cat1' * ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) * LIMIT 20 * 5. Check that a CouchbaseLiteException is returned when creating the query. @@ -1308,7 +1308,7 @@ - (void) testInvalidHybridVectorSearchWithOR { NSString* sql = [self wordsQueryStringWithLimit: 20 metric: nil vectorExpression: @"vector" - whereClause: @"APPROX_VECTOR_DISTANCE(vector, $vector) OR catid = 'cat1'"]; + whereClause: @"APPROX_VECTOR_DISTANCE(vector, $vector) < 0.5 OR catid = 'cat1'"]; return [self.wordDB createQuery: sql error: err] != nil; }]; } diff --git a/Scripts/generate_objc_release_zip.sh b/Scripts/generate_objc_release_zip.sh index c3d4fc4df..c7ad2db0e 100755 --- a/Scripts/generate_objc_release_zip.sh +++ b/Scripts/generate_objc_release_zip.sh @@ -195,7 +195,9 @@ popd > /dev/null if [[ -z $NO_API_DOCS ]]; then # Generate API docs: echo "Generate API docs ..." - OBJC_UMBRELLA_HEADER=`find $OUTPUT_OBJC_XC_DIR -name "CouchbaseLite.h"` + OBJC_UMBRELLA_HEADER=$(find $OUTPUT_OBJC_XC_DIR -name "CouchbaseLite.h" -print -quit) + echo "Umbrella Doc: ${OBJC_UMBRELLA_HEADER}" + sed -i.bak 's|#import |#import "\1"|g' "${OBJC_UMBRELLA_HEADER}" jazzy --clean --objc --umbrella-header ${OBJC_UMBRELLA_HEADER} --module CouchbaseLite --module-version "${API_DOC_VERSION}" --theme Scripts/Support/Docs/Theme --readme README.md --output ${OUTPUT_DOCS_DIR}/CouchbaseLite # >> Objective-C API @@ -208,4 +210,3 @@ fi rm -rf "$BUILD_DIR" rm -rf "$OUTPUT_OBJC_XC_DIR" rm -rf "$OUTPUT_DOCS_DIR" - diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 36dbc8878..95fa1d878 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -187,8 +187,8 @@ class VectorSearchTest: CBLTestCase { /// Test Spec: /// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0001-Vector-Search.md /// -/// Version: 2.0.9 -/// +/// Version: 2.1.0 +/// class VectorSearchTest_Main: VectorSearchTest { /// 1. TestVectorIndexConfigurationDefaultValue @@ -1161,7 +1161,7 @@ class VectorSearchTest_Main: VectorSearchTest { /// 4. Create an SQL++ query. /// - SELECT word, catid /// FROM _default.words - /// WHERE APPROX_VECTOR_DISTANCE(vector, $dinerVector) < 10 OR catid = 'cat1' + /// WHERE APPROX_VECTOR_DISTANCE(vector, $dinerVector) < 0.5 OR catid = 'cat1' /// ORDER BY APPROX_VECTOR_DISTANCE(vector, $dinerVector) /// LIMIT 20 /// 5. Check that a CouchbaseLiteException is returned when creating the query. @@ -1170,7 +1170,7 @@ class VectorSearchTest_Main: VectorSearchTest { try createWordsIndex(config: config) self.expectError(domain: CBLError.domain, code: CBLError.invalidQuery) { - _ = try self.executeWordsQuery(limit: 300, whereExpression: "APPROX_VECTOR_DISTANCE(vector, $vector) OR catid = 'cat1'") + _ = try self.executeWordsQuery(limit: 300, whereExpression: "APPROX_VECTOR_DISTANCE(vector, $vector) < 0.5 OR catid = 'cat1'") } } diff --git a/Tests/Extensions/version.txt b/Tests/Extensions/version.txt index 556fc95d9..be723c282 100644 --- a/Tests/Extensions/version.txt +++ b/Tests/Extensions/version.txt @@ -1 +1 @@ -1.0.0-56 +1.0.0-57 diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index d708da28f..554bf8f72 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit d708da28f6af945153d3b6fc715c0752c5de4409 +Subproject commit 554bf8f7261657ee5468f9e11b98656d6ee3c983 From a9390ca308f5f721eb5c6099438e14e1b3b0037a Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Mon, 22 Jul 2024 09:10:35 -0700 Subject: [PATCH 33/49] Update LiteCore to 3.2.0-220 (#3307) * Updated LiteCore to 3.2.0-220. * Used VS 1.0.0-58. * Fixed an incorrect test name. --- Swift/Tests/VectorSearchTest.swift | 2 +- Tests/Extensions/version.txt | 2 +- vendor/couchbase-lite-core | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 95fa1d878..57d84bdf2 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -1136,7 +1136,7 @@ class VectorSearchTest_Main: VectorSearchTest { /// 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 TestHybridVectorSearchWithAND() throws { + func testHybridVectorSearchWithAND() throws { let config = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 8) try createWordsIndex(config: config) diff --git a/Tests/Extensions/version.txt b/Tests/Extensions/version.txt index be723c282..a3b2d30cd 100644 --- a/Tests/Extensions/version.txt +++ b/Tests/Extensions/version.txt @@ -1 +1 @@ -1.0.0-57 +1.0.0-58 diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 554bf8f72..7f0707145 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 554bf8f7261657ee5468f9e11b98656d6ee3c983 +Subproject commit 7f0707145d9db2af04a9494ee7e271a8302a6a7c From 554a3010ce332eb987943a614f6ab024bf8b5f08 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 5 Aug 2024 17:46:51 +0300 Subject: [PATCH 34/49] CBL-6070: Implement Database Full-Sync Option (#3318) * LiteCore 3.2.0-223 * cherry picked 5c10248 from release/3.1 branch. A couple of small, easy-to-resolve conflicts - nothing important. CBL-6066: Implement Database Full-Sync Option (#3317) - Implemented FullSync DatabaseConfiguration property. - Only Obj-C tests do check for c4db_config2 kC4DB_DiskSyncFull flag --- Objective-C/CBLDatabase.mm | 8 +++ Objective-C/CBLDatabaseConfiguration.h | 13 ++++ Objective-C/CBLDatabaseConfiguration.m | 8 ++- Objective-C/CBLDefaults.h | 5 ++ Objective-C/CBLDefaults.m | 4 ++ Objective-C/Exports/CBL.txt | 1 + Objective-C/Exports/Generated/CBL.exp | 1 + Objective-C/Exports/Generated/CBL_EE.exp | 1 + Objective-C/Internal/CBLDatabase+Internal.h | 5 +- Objective-C/Tests/CollectionTest.m | 2 +- Objective-C/Tests/DatabaseTest.m | 73 +++++++++++++++++++++ Swift/DatabaseConfiguration.swift | 15 ++++- Swift/Defaults.swift | 7 ++ Swift/Tests/DatabaseTest.swift | 55 +++++++++++++++- vendor/couchbase-lite-core | 2 +- 15 files changed, 193 insertions(+), 7 deletions(-) diff --git a/Objective-C/CBLDatabase.mm b/Objective-C/CBLDatabase.mm index 8cfc9b42f..a0a97040f 100644 --- a/Objective-C/CBLDatabase.mm +++ b/Objective-C/CBLDatabase.mm @@ -1024,6 +1024,8 @@ static BOOL setupDatabaseDirectory(NSString *dir, NSError **outError) static C4DatabaseConfig2 c4DatabaseConfig2 (CBLDatabaseConfiguration *config) { C4DatabaseConfig2 c4config = kDBConfig; + if (config.fullSync) + c4config.flags |= kC4DB_DiskSyncFull; #ifdef COUCHBASE_ENTERPRISE if (config.encryptionKey) c4config.encryptionKey = [CBLDatabase c4EncryptionKey: config.encryptionKey]; @@ -1139,4 +1141,10 @@ - (uint64_t) activeStoppableCount { } } +#pragma mark - Private for test + +- (const C4DatabaseConfig2*) getC4DBConfig { + return c4db_getConfig2(_c4db); +} + @end diff --git a/Objective-C/CBLDatabaseConfiguration.h b/Objective-C/CBLDatabaseConfiguration.h index 90cc82db1..b1b25804e 100644 --- a/Objective-C/CBLDatabaseConfiguration.h +++ b/Objective-C/CBLDatabaseConfiguration.h @@ -30,6 +30,19 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, copy) NSString* directory; +/** + As Couchbase Lite normally configures its databases, there is a very + small (though non-zero) chance that a power failure at just the wrong + time could cause the most recently committed transaction's changes to + be lost. This would cause the database to appear as it did immediately + before that transaction. + + Setting this mode true ensures that an operating system crash or + power failure will not cause the loss of any data. FULL synchronous + is very safe but it is also dramatically slower. + */ +@property (nonatomic) BOOL fullSync; + /** Initializes the CBLDatabaseConfiguration object. */ diff --git a/Objective-C/CBLDatabaseConfiguration.m b/Objective-C/CBLDatabaseConfiguration.m index aace931fa..9f0f44d5c 100644 --- a/Objective-C/CBLDatabaseConfiguration.m +++ b/Objective-C/CBLDatabaseConfiguration.m @@ -19,12 +19,13 @@ #import "CBLDatabaseConfiguration.h" #import "CBLDatabase+Internal.h" +#import "CBLDefaults.h" @implementation CBLDatabaseConfiguration { BOOL _readonly; } -@synthesize directory=_directory; +@synthesize directory=_directory, fullSync=_fullSync; #ifdef COUCHBASE_ENTERPRISE @synthesize encryptionKey=_encryptionKey; @@ -47,11 +48,14 @@ - (instancetype) initWithConfig: (nullable CBLDatabaseConfiguration*)config if (config) { _directory = config.directory; + _fullSync = config.fullSync; #ifdef COUCHBASE_ENTERPRISE _encryptionKey = config.encryptionKey; #endif - } else + } else { _directory = [CBLDatabaseConfiguration defaultDirectory]; + _fullSync = kCBLDefaultDatabaseFullSync; + } } return self; } diff --git a/Objective-C/CBLDefaults.h b/Objective-C/CBLDefaults.h index 0536aa1d7..eac560e00 100644 --- a/Objective-C/CBLDefaults.h +++ b/Objective-C/CBLDefaults.h @@ -28,6 +28,11 @@ #endif +#pragma mark - CBLDatabaseConfiguration + +/** [NO] Full sync is off by default because the performance hit is seldom worth the benefit */ +extern const BOOL kCBLDefaultDatabaseFullSync; + #pragma mark - CBLLogFileConfiguration /** [NO] Plaintext is not used, and instead binary encoding is used in log files */ diff --git a/Objective-C/CBLDefaults.m b/Objective-C/CBLDefaults.m index 36140a8cf..5fc203610 100644 --- a/Objective-C/CBLDefaults.m +++ b/Objective-C/CBLDefaults.m @@ -22,6 +22,10 @@ #import "CBLDefaults.h" +#pragma mark - CBLDatabaseConfiguration + +const BOOL kCBLDefaultDatabaseFullSync = NO; + #pragma mark - CBLLogFileConfiguration const BOOL kCBLDefaultLogFileUsePlaintext = NO; diff --git a/Objective-C/Exports/CBL.txt b/Objective-C/Exports/CBL.txt index ad423663c..ef152e06f 100644 --- a/Objective-C/Exports/CBL.txt +++ b/Objective-C/Exports/CBL.txt @@ -91,6 +91,7 @@ _kCBLBlobContentTypeProperty _kCBLBlobDigestProperty _kCBLBlobLengthProperty _kCBLBlobType +_kCBLDefaultDatabaseFullSync _kCBLDefaultCollectionName _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount diff --git a/Objective-C/Exports/Generated/CBL.exp b/Objective-C/Exports/Generated/CBL.exp index 9062cefab..36c0f6db2 100644 --- a/Objective-C/Exports/Generated/CBL.exp +++ b/Objective-C/Exports/Generated/CBL.exp @@ -71,6 +71,7 @@ _kCBLBlobDigestProperty _kCBLBlobLengthProperty _kCBLBlobType _kCBLDefaultCollectionName +_kCBLDefaultDatabaseFullSync _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp index 5084aa65e..dc7d1a526 100644 --- a/Objective-C/Exports/Generated/CBL_EE.exp +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -109,6 +109,7 @@ _kCBLCertAttrStateOrProvince _kCBLCertAttrSurname _kCBLCertAttrURL _kCBLDefaultCollectionName +_kCBLDefaultDatabaseFullSync _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultListenerDisableTls _kCBLDefaultListenerEnableDeltaSync diff --git a/Objective-C/Internal/CBLDatabase+Internal.h b/Objective-C/Internal/CBLDatabase+Internal.h index 394d8facb..2ed985d2b 100644 --- a/Objective-C/Internal/CBLDatabase+Internal.h +++ b/Objective-C/Internal/CBLDatabase+Internal.h @@ -46,7 +46,6 @@ NS_ASSUME_NONNULL_BEGIN /// CBLDatabase: - @interface CBLDatabase () @property (readonly, nonatomic, nullable) C4Database* c4db; @@ -80,6 +79,10 @@ NS_ASSUME_NONNULL_BEGIN - (id) mutex; +#pragma mark - Private for test + +- (const C4DatabaseConfig2*) getC4DBConfig; + @end /// CBLDatabaseConfiguration: diff --git a/Objective-C/Tests/CollectionTest.m b/Objective-C/Tests/CollectionTest.m index 01723379e..7ec875d14 100644 --- a/Objective-C/Tests/CollectionTest.m +++ b/Objective-C/Tests/CollectionTest.m @@ -952,7 +952,7 @@ - (void) testUseInvalidCollection: (NSString*)collectionName onAction: (void (^) // get index, get indexes, delete index [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { - return [col indexWithName: @"index1" error: err]; + return [col indexWithName: @"index1" error: err] != nil; }]; [self expectError: CBLErrorDomain code: CBLErrorNotOpen in: ^BOOL(NSError** err) { return [col indexes: err] != nil; diff --git a/Objective-C/Tests/DatabaseTest.m b/Objective-C/Tests/DatabaseTest.m index 70d715421..3de019525 100644 --- a/Objective-C/Tests/DatabaseTest.m +++ b/Objective-C/Tests/DatabaseTest.m @@ -2818,6 +2818,79 @@ - (void) testDBEventTrigged { [token remove]; } +#pragma mark - Full Sync Option + +/** + Test Spec for Database Full Sync Option https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0003-SQLite-Options.md + */ + +/** + 1. TestSQLiteFullSyncConfig + Description + Test that the FullSync default is as expected and that it's setter and getter work. + Steps + 1. Create a DatabaseConfiguration object. + 2. Get and check the value of the FullSync property: it should be false. + 3. Set the FullSync property true. + 4. Get the config FullSync property and verify that it is true. + 5. Set the FullSync property false. + 6. Get the config FullSync property and verify that it is false. + */ +- (void) testSQLiteFullSyncConfig { + CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; + AssertFalse(config.fullSync); + + config.fullSync = true; + Assert(config.fullSync); + + config.fullSync = false; + AssertFalse(config.fullSync); +} + +/** + 2. TestDBWithFullSync + Description + Test that a Database respects the FullSync property. + Steps + 1. Create a DatabaseConfiguration object and set Full Sync false. + 2. Create a database with the config. + 3. Get the configuration object from the Database and verify that FullSync is false. + 4. Use c4db_config2 (perhaps necessary only for this test) to confirm that its config does not contain the kC4DB_DiskSyncFull flag. + 5. Set the config's FullSync property true. + 6. Create a database with the config. + 7. Get the configuration object from the Database and verify that FullSync is true. + 8. Use c4db_config2 to confirm that its config contains the kC4DB_DiskSyncFull flag. + */ +- (void) testDBWithFullSync { + NSString* dbName = @"fullsyncdb"; + [CBLDatabase deleteDatabase: dbName inDirectory: self.directory error: nil]; + AssertFalse([CBLDatabase databaseExists: dbName inDirectory: self.directory]); + + CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; + config.directory = self.directory; + NSError* error; + CBLDatabase* db = [[CBLDatabase alloc] initWithName: dbName + config: config + error: &error]; + AssertNil(error); + AssertNotNil(db, @"Couldn't open db: %@", error); + AssertFalse([db config].fullSync); + AssertFalse(([db getC4DBConfig]->flags & kC4DB_DiskSyncFull) == kC4DB_DiskSyncFull); + + [self closeDatabase: db]; + + config.fullSync = true; + db = [[CBLDatabase alloc] initWithName: dbName + config: config + error: &error]; + AssertNil(error); + AssertNotNil(db, @"Couldn't open db: %@", error); + Assert([db config].fullSync); + Assert(([db getC4DBConfig]->flags & kC4DB_DiskSyncFull) == kC4DB_DiskSyncFull); + + [self closeDatabase: db]; +} + #pragma clang diagnostic pop @end diff --git a/Swift/DatabaseConfiguration.swift b/Swift/DatabaseConfiguration.swift index c7a89421f..fb25a5e64 100644 --- a/Swift/DatabaseConfiguration.swift +++ b/Swift/DatabaseConfiguration.swift @@ -2,7 +2,7 @@ // DatabaseConfiguration.swift // CouchbaseLite // -// Copyright (c) 2017 Couchbase, Inc All rights reserved. +// Copyright (c) 2024 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,6 +27,17 @@ public struct DatabaseConfiguration { /// Path to the directory to store the database in. public var directory: String = CBLDatabaseConfiguration().directory + /// As Couchbase Lite normally configures its databases, there is a very + /// small (though non-zero) chance that a power failure at just the wrong + /// time could cause the most recently committed transaction's changes to + /// be lost. This would cause the database to appear as it did immediately + /// before that transaction. + /// + /// Setting this mode true ensures that an operating system crash or + /// power failure will not cause the loss of any data. FULL synchronous + /// is very safe but it is also dramatically slower. + public var fullSync: Bool = defaultFullSync + #if COUCHBASE_ENTERPRISE /// The key to encrypt the database with. public var encryptionKey: EncryptionKey? @@ -41,6 +52,7 @@ public struct DatabaseConfiguration { public init(config: DatabaseConfiguration?) { if let c = config { self.directory = c.directory + self.fullSync = c.fullSync #if COUCHBASE_ENTERPRISE self.encryptionKey = c.encryptionKey #endif @@ -52,6 +64,7 @@ public struct DatabaseConfiguration { func toImpl() -> CBLDatabaseConfiguration { let config = CBLDatabaseConfiguration() config.directory = self.directory + config.fullSync = self.fullSync #if COUCHBASE_ENTERPRISE config.encryptionKey = self.encryptionKey?.impl #endif diff --git a/Swift/Defaults.swift b/Swift/Defaults.swift index 75584f8ba..28e245fbd 100644 --- a/Swift/Defaults.swift +++ b/Swift/Defaults.swift @@ -22,6 +22,13 @@ import Foundation +public extension DatabaseConfiguration { + + /// [false] Full sync is off by default because the performance hit is seldom worth the benefit + static let defaultFullSync: Bool = false + +} + public extension LogFileConfiguration { /// [false] Plaintext is not used, and instead binary encoding is used in log files diff --git a/Swift/Tests/DatabaseTest.swift b/Swift/Tests/DatabaseTest.swift index cdc1c55b9..f8e5fa1a9 100644 --- a/Swift/Tests/DatabaseTest.swift +++ b/Swift/Tests/DatabaseTest.swift @@ -2,7 +2,7 @@ // DatabaseTest.swift // CouchbaseLite // -// Copyright (c) 2017 Couchbase, Inc All rights reserved. +// Copyright (c) 2024 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -1529,4 +1529,57 @@ class DatabaseTest: CBLTestCase { XCTAssertNotNil(db.config.encryptionKey) #endif } + + // MARK: Full Sync Option + /// Test Spec for Database Full Sync Option + /// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0003-SQLite-Options.md + + /// 1. TestSQLiteFullSyncConfig + /// Description: + /// Test that the FullSync default is as expected and that it's setter and getter work. + /// Steps + /// 1. Create a DatabaseConfiguration object. + /// 2. Get and check the value of the FullSync property: it should be false. + /// 3. Set the FullSync property true. + /// 4. Get the config FullSync property and verify that it is true. + /// 5. Set the FullSync property false. + /// 6. Get the config FullSync property and verify that it is false. + func testSQLiteFullSyncConfig() { + var config = DatabaseConfiguration() + XCTAssertFalse(config.fullSync) + + config.fullSync = true + XCTAssert(config.fullSync) + + config.fullSync = false + XCTAssertFalse(config.fullSync) + } + + /// 2. TestDBWithFullSync + /// Description: + /// Test that the FullSync default is as expected and that it's setter and getter work. + /// Steps + /// 1. Create a DatabaseConfiguration object and set Full Sync false. + /// 2. Create a database with the config. + /// 3. Get the configuration object from the Database and verify that FullSync is false. + /// 4. Use c4db_config2 (perhaps necessary only for this test) to confirm that its config does not contain the kC4DB_DiskSyncFull flag. - done in Obj-C + /// 5. Set the config's FullSync property true. + /// 6. Create a database with the config. + /// 7. Get the configuration object from the Database and verify that FullSync is true. + /// 8. Use c4db_config2 to confirm that its config contains the kC4DB_DiskSyncFull flag. - done in Obj-C + func testDBWithFullSync() throws { + let dbName = "fullsyncdb" + try deleteDB(name: dbName) + XCTAssertFalse(Database.exists(withName: dbName, inDirectory: self.directory)) + + var config = DatabaseConfiguration() + config.directory = self.directory + db = try Database(name: dbName, config: config) + XCTAssertFalse(DatabaseConfiguration(config: config).fullSync) + + db = nil + config.fullSync = true + db = try Database(name: dbName, config: config) + XCTAssert(DatabaseConfiguration(config: config).fullSync) + } } diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 7f0707145..0b30e0c60 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 7f0707145d9db2af04a9494ee7e271a8302a6a7c +Subproject commit 0b30e0c60b89efb879109194e4281012bffbfa98 From 49b5114cced9b2f14e1712d1034bb6e0f178fd94 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 9 Aug 2024 18:50:14 +0300 Subject: [PATCH 35/49] CBL-6144: Add testConcurrentCreateAndQuery to verify query's lock (#3320) * port CBL-6140 from release/3.1 * enable testDatabaseChange and testDocumentChange --- Objective-C/Tests/ConcurrentTest.m | 39 +++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/Objective-C/Tests/ConcurrentTest.m b/Objective-C/Tests/ConcurrentTest.m index 36af0dddc..ac7b871e2 100644 --- a/Objective-C/Tests/ConcurrentTest.m +++ b/Objective-C/Tests/ConcurrentTest.m @@ -292,7 +292,6 @@ - (void) testConcurrentCompact { }]; } -#if 0 - (void) testDatabaseChange { XCTestExpectation* exp1 = [self expectationWithDescription: @"Create"]; XCTestExpectation* exp2 = [self expectationWithDescription: @"Change"]; @@ -302,15 +301,13 @@ - (void) testDatabaseChange { }]; [self concurrentRuns: 1 waitUntilDone: NO withBlock: ^(NSUInteger rIndex) { - [_db saveDocument: [[CBLMutableDocument alloc] initWithID: @"doc1"] error: nil]; + [self->_db saveDocument: [[CBLMutableDocument alloc] initWithID: @"doc1"] error: nil]; [exp1 fulfill]; }]; [self waitForExpectations: @[exp2] timeout: 10.0]; // Test deadlock } -#endif //TEMP -#if 0 //TEMP - (void) testDocumentChange { XCTestExpectation* exp1 = [self expectationWithDescription: @"Create"]; XCTestExpectation* exp2 = [self expectationWithDescription: @"Change"]; @@ -320,13 +317,43 @@ - (void) testDocumentChange { }]; [self concurrentRuns: 1 waitUntilDone: NO withBlock: ^(NSUInteger rIndex) { - [_db saveDocument: [[CBLMutableDocument alloc] initWithID: @"doc1"] error: nil]; + [self->_db saveDocument: [[CBLMutableDocument alloc] initWithID: @"doc1"] error: nil]; [exp1 fulfill]; }]; [self waitForExpectations: @[exp2] timeout: 10.0]; // Test deadlock } -#endif + +- (void) testConcurrentCreateAndQuery { + NSError* outError; + const NSUInteger kNDocs = 10; + const NSUInteger kNConcurrents = 3; + __block NSArray* allObjects= @[]; + + NSString* queryString = @"SELECT * FROM _"; + CBLQuery* query = [self.db createQuery: queryString error: &outError]; + + + [self concurrentRuns: kNConcurrents waitUntilDone: YES withBlock: ^(NSUInteger rIndex) { + NSError* error; + if (rIndex % 2 == 0){ + [self.db inBatch: &error usingBlock: ^{ + NSError* err; + Assert([self createAndSaveDocs: kNDocs error: &err], + @"Error creating docs: %@", err); + CBLQueryResultSet* rs = [query execute: &err]; + allObjects = rs.allObjects; + }]; + } else { + Assert([self createAndSaveDocs: kNDocs error: &error], + @"Error creating docs: %@", error); + CBLQueryResultSet* rs = [query execute: &error]; + allObjects = rs.allObjects; + } + + }]; + AssertEqual(self.db.count, allObjects.count); +} #pragma clang diagnostic pop From f0872ec3dacf3547c71d427be794051875ff46db Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Wed, 28 Aug 2024 10:52:01 -0700 Subject: [PATCH 36/49] CBL-6191 : Fix null URL for proxy CONNECT request (#3324) NSURL cannot be created with only host and port anymore starting from iOS 17. Changed to use CFURLCreateWithString directly. --- Objective-C/Internal/Replicator/CBLHTTPLogic.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Objective-C/Internal/Replicator/CBLHTTPLogic.m b/Objective-C/Internal/Replicator/CBLHTTPLogic.m index 6cc7393c3..0f7fe67e9 100644 --- a/Objective-C/Internal/Replicator/CBLHTTPLogic.m +++ b/Objective-C/Internal/Replicator/CBLHTTPLogic.m @@ -143,11 +143,13 @@ - (CFHTTPMessageRef) newHTTPRequest { // Create the CFHTTPMessage: CFHTTPMessageRef httpMsg; if (_proxyType == kCBLHTTPProxy && _useProxyCONNECT) { - NSURL *requestURL = [NSURL URLWithString: $sprintf(@"%@:%d", url.host, url.my_effectivePort)]; + NSString *destination = $sprintf(@"%@:%d", url.host, url.my_effectivePort); + CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, (__bridge CFStringRef)destination, false); httpMsg = CFHTTPMessageCreateRequest(NULL, CFSTR("CONNECT"), - (__bridge CFURLRef)requestURL, + requestURL, kCFHTTPVersion1_1); + CFRelease(requestURL); } else { httpMsg = CFHTTPMessageCreateRequest(NULL, (__bridge CFStringRef)_urlRequest.HTTPMethod, From 5c5a6083c4e7ae73ac7606b0d7d59c94d1eef7e7 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Thu, 29 Aug 2024 00:00:36 +0300 Subject: [PATCH 37/49] update to LiteCore 3.2.0-224 (#3325) --- vendor/couchbase-lite-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 0b30e0c60..163b832d9 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 0b30e0c60b89efb879109194e4281012bffbfa98 +Subproject commit 163b832d936f795c2c2a3cf15990c03787616294 From a49f7dbcbd9a411dd0b473d92514bb90bd368f34 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 30 Aug 2024 16:24:03 +0300 Subject: [PATCH 38/49] CouchbaseLite CE SPM 3.2.0 (#3326) --- CouchbaseLite-Swift.podspec | 2 +- CouchbaseLite.podspec | 2 +- Package.swift | 4 ++-- README.md | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CouchbaseLite-Swift.podspec b/CouchbaseLite-Swift.podspec index e238caee0..ee8ffb293 100644 --- a/CouchbaseLite-Swift.podspec +++ b/CouchbaseLite-Swift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CouchbaseLite-Swift' - s.version = '3.2.0-beta.1' + s.version = '3.2.0' s.license = 'Apache License, Version 2.0' s.homepage = 'https://github.com/couchbase/couchbase-lite-ios' s.summary = 'An embedded syncable NoSQL database for iOS and MacOS apps.' diff --git a/CouchbaseLite.podspec b/CouchbaseLite.podspec index bb4e3b9a3..f8b9ad883 100644 --- a/CouchbaseLite.podspec +++ b/CouchbaseLite.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'CouchbaseLite' - s.version = '3.2.0-beta.1' + s.version = '3.2.0' s.license = 'Apache License, Version 2.0' s.homepage = 'https://github.com/couchbase/couchbase-lite-ios' s.summary = 'An embedded syncable NoSQL database for iOS and MacOS apps.' diff --git a/Package.swift b/Package.swift index 89fbc47fb..88652d88d 100644 --- a/Package.swift +++ b/Package.swift @@ -14,8 +14,8 @@ let package = Package( targets: [ .binaryTarget( name: "CouchbaseLiteSwift", - url: "https://packages.couchbase.com/releases/couchbase-lite-ios/3.2.0-beta.1/couchbase-lite-swift_xc_community_3.2.0-beta.1.zip", - checksum: "39dd4d80a8edb6eb87a2f5ae91753b16064c04c6c74f10f7ef2707902efe14c4" + url: "https://packages.couchbase.com/releases/couchbase-lite-ios/3.2.0/couchbase-lite-swift_xc_community_3.2.0.zip", + checksum: "62c9f70038628822fa5d275b4c64c64f58abba6a5c178859f60160ee0e7baefd" ) ] ) diff --git a/README.md b/README.md index 8414a93b2..e06f47607 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Couchbase Lite implementation is on top of [Couchbase Lite Core](https://github. dependencies: [ .package(name: "CouchbaseLiteSwift", url: "https://github.com/couchbase/couchbase-lite-ios.git", - from: "3.2.0-beta.1"), + from: "3.2.0"), ], ``` @@ -34,7 +34,7 @@ dependencies: [ dependencies: [ .package(name: "CouchbaseLiteSwift", url: "https://github.com/couchbase/couchbase-lite-swift-ee.git", - from: "3.2.0-beta.1"), + from: "3.2.0"), ], ``` From 804a9277ead5f48bfaa6fc233e99413f5096a6a1 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 23 Sep 2024 20:33:12 +0300 Subject: [PATCH 39/49] CBL-5180: Implement Array Index API (#3327) * ArrayIndex template * add CBLArrayIndexConfiguration to CouchbaseLite.h and exports * add header to CE modulemap --- CouchbaseLite.xcodeproj/project.pbxproj | 24 +++++++- Objective-C/CBLArrayIndexConfiguration.h | 66 +++++++++++++++++++++ Objective-C/CBLArrayIndexConfiguration.m | 62 +++++++++++++++++++ Objective-C/CBLFullTextIndexConfiguration.m | 3 +- Objective-C/CouchbaseLite.h | 1 + Objective-C/Exports/CBL.txt | 1 + Objective-C/Exports/Generated/CBL.exp | 1 + Objective-C/Exports/Generated/CBL_EE.exp | 1 + Swift/CouchbaseLiteSwift.private.modulemap | 1 + Swift/IndexConfiguration.swift | 32 ++++++++++ 10 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 Objective-C/CBLArrayIndexConfiguration.h create mode 100644 Objective-C/CBLArrayIndexConfiguration.m diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index e5cfc2615..a1774e633 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -1772,6 +1772,14 @@ AE83D0852C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; AE83D0862C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Private, ); }; }; AE83D0872C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; + AEC806B72C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEC806B82C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; + AEC806B92C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; + AEC806BA2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AEC806BB2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + AEC806BC2C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; + AEC806BD2C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; + AEC806BE2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */; settings = {ATTRIBUTES = (Private, ); }; }; AECD5A162C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; AECD5A172C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; /* End PBXBuildFile section */ @@ -2792,6 +2800,8 @@ AE5803D72B9B5B2A001A1BE3 /* WordEmbeddingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordEmbeddingModel.swift; sourceTree = ""; }; AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLIndexUpdater.h; sourceTree = ""; }; AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLIndexUpdater.mm; sourceTree = ""; }; + AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLArrayIndexConfiguration.h; sourceTree = ""; }; + AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLArrayIndexConfiguration.m; sourceTree = ""; }; AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLQueryIndex+Internal.h"; sourceTree = ""; }; AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLIndexUpdater+Internal.h"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -4331,6 +4341,8 @@ 1A3471482671C87F0042C6BA /* CBLFullTextIndexConfiguration.m */, 1A34715C2671C9230042C6BA /* CBLValueIndexConfiguration.h */, 1A34715D2671C9230042C6BA /* CBLValueIndexConfiguration.m */, + AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */, + AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */, ); name = Index; sourceTree = ""; @@ -4432,6 +4444,7 @@ 932EA56D2061FF7E00EDB667 /* CBLVersion.h in Headers */, 9384D80A1FC3F75700FE89D8 /* CBLQueryArrayFunction.h in Headers */, 937F01DD1EFB1A2500060D64 /* CBLSessionAuthenticator.h in Headers */, + AEC806BA2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */, 27E35A9C1E8C522200E103F9 /* CBLReplicator.h in Headers */, 9388CBEE21BF727A005CA66D /* CBLLog.h in Headers */, 939B1B5B2009C04100FAA3CB /* CBLQueryVariableExpression.h in Headers */, @@ -4658,6 +4671,7 @@ 9343EFDD207D611600F19A89 /* CBLTrustCheck.h in Headers */, 9343EFDE207D611600F19A89 /* CBLBasicAuthenticator.h in Headers */, 1A34714B2671C8800042C6BA /* CBLFullTextIndexConfiguration.h in Headers */, + AEC806BB2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */, 9343EFE0207D611600F19A89 /* CBLJSON.h in Headers */, 9388CBFD21BF74FD005CA66D /* CBLConsoleLogger.h in Headers */, 9343EFE1207D611600F19A89 /* CBLDocumentChange.h in Headers */, @@ -4923,6 +4937,7 @@ 9343F11E207D61AB00F19A89 /* CBLWebSocket.h in Headers */, 9343F11F207D61AB00F19A89 /* MYErrorUtils.h in Headers */, 40FC1C162B928ADD00394276 /* CBLListenerCertificateAuthenticator+Internal.h in Headers */, + AEC806BE2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */, 69002EBF234E695600776107 /* CBLErrorMessage.h in Headers */, 9343F121207D61AB00F19A89 /* MYLogging.h in Headers */, 40FC1B632B9287BD00394276 /* CBLTLSIdentity.h in Headers */, @@ -5031,6 +5046,7 @@ 934F4CA91E241FB500F90659 /* CBLCoreBridge.h in Headers */, 9384D8091FC3F75700FE89D8 /* CBLQueryArrayFunction.h in Headers */, 274B4F8121A4D51100B2B4E6 /* CBLQuery+JSON.h in Headers */, + AEC806B72C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */, 931C14661EAAD6610094F9B2 /* CBLMutableDictionaryFragment.h in Headers */, 934A27B71F30E810003946A7 /* CBLQuantifiedExpression.h in Headers */, 9383A58F1F1EE9550083053D /* CBLQueryResultSet+Internal.h in Headers */, @@ -5523,7 +5539,7 @@ }; 9343EF2A207D611600F19A89 = { DevelopmentTeam = N2Q372V7W2; - LastSwiftMigration = 1340; + LastSwiftMigration = 1540; }; 9343F135207D61EC00F19A89 = { DevelopmentTeam = N2Q372V7W2; @@ -5554,7 +5570,7 @@ 9398D9111E03434200464432 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = N2Q372V7W2; - LastSwiftMigration = 1340; + LastSwiftMigration = 1540; ProvisioningStyle = Automatic; }; 9398D91A1E03434200464432 = { @@ -6012,6 +6028,7 @@ 938CDF201E807F45002EE790 /* DataSource.swift in Sources */, 93CED8CB20488BD400E6F0A4 /* DocumentChange.swift in Sources */, 9386852921B09C5400BB1242 /* DocumentReplication.swift in Sources */, + AEC806B92C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */, 93EC42D41FB3801E00D54BB4 /* CBLValueIndex.m in Sources */, 937A69061F0731230058277F /* CBLQueryFunction.m in Sources */, 93FD616420204E3600E7F6A1 /* CBLIndexBuilder.m in Sources */, @@ -6374,6 +6391,7 @@ 9343EF72207D611600F19A89 /* CBLQueryVariableExpression.m in Sources */, 9343EF73207D611600F19A89 /* CBLQuery.mm in Sources */, 9343EF74207D611600F19A89 /* CBLArray.mm in Sources */, + AEC806BC2C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */, 9343EF75207D611600F19A89 /* CBLQuantifiedExpression.m in Sources */, 9343EF76207D611600F19A89 /* CBLCoreBridge.mm in Sources */, 9343EF77207D611600F19A89 /* CBLQueryLimit.m in Sources */, @@ -6525,6 +6543,7 @@ 9343F053207D61AB00F19A89 /* PropertyExpression.swift in Sources */, 9343F054207D61AB00F19A89 /* CBLChangeNotifier.m in Sources */, 9388CC0221BF74FD005CA66D /* CBLConsoleLogger.m in Sources */, + AEC806BD2C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */, 1AAFB691284A266F00878453 /* Scope.swift in Sources */, 40FC1C642B928C1600394276 /* MessageEndpointBridge.swift in Sources */, 9343F055207D61AB00F19A89 /* CBLDocumentChangeNotifier.mm in Sources */, @@ -6896,6 +6915,7 @@ 9383A5861F1EE7C00083053D /* CBLQueryResultSet.mm in Sources */, 93E17F0C1ED3AC8100671CA1 /* CBLDatabaseChange.m in Sources */, 9388CC0921BF750E005CA66D /* CBLFileLogger.mm in Sources */, + AEC806B82C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */, 933208171E77415E000D9993 /* CBLQueryOrdering.m in Sources */, 931C14541EAABCE70094F9B2 /* CBLFragment.m in Sources */, 9380C6F01E15B8C20011E8CB /* CBLMutableDocument.mm in Sources */, diff --git a/Objective-C/CBLArrayIndexConfiguration.h b/Objective-C/CBLArrayIndexConfiguration.h new file mode 100644 index 000000000..ca12d41eb --- /dev/null +++ b/Objective-C/CBLArrayIndexConfiguration.h @@ -0,0 +1,66 @@ +// +// CBLArrayIndexConfiguration.h +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Configuration for indexing property values within nested arrays in documents, + intended for use with the UNNEST query. + */ + +@interface CBLArrayIndexConfiguration : CBLIndexConfiguration + +/** + Path to the array, which can be nested. + */ + +@property (nonatomic, readonly) NSString* path; + +/** + The expressions representing the values within the array to be indexed. + */ + +@property (nonatomic, readonly, nullable) NSArray* expressions; + +/** + Initializes the configuration with paths to the nested array + and the optional expressions for the values within the arrays to be indexed. + @param path Path to the array, which can be nested to be indexed. + @note Use "[]" to represent a property that is an array of each nested + array level. For a single array or the last level array, the "[]" is optional. + + For instance, use "contacts[].phones" to specify an array of phones within + each contact. + @param expressions An optional array of strings, where each string + represents an expression defining the values within the array + to be indexed. If the array specified by the path contains + scalar values, this parameter can be null. + + @return The CBLArrayIndexConfiguration object. + */ + +- (instancetype) initWithPath: (NSString*)path + expressions: (nullable NSArray*)expressions; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/CBLArrayIndexConfiguration.m b/Objective-C/CBLArrayIndexConfiguration.m new file mode 100644 index 000000000..d735f607f --- /dev/null +++ b/Objective-C/CBLArrayIndexConfiguration.m @@ -0,0 +1,62 @@ +// +// CBLArrayIndexConfiguration.m +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLArrayIndexConfiguration.h" +#import "CBLIndexConfiguration+Internal.h" + +@implementation CBLArrayIndexConfiguration + +@synthesize path=_path, expressions=_expressions; + +- (instancetype) initWithPath: (NSString*) path + expressions: (nullable NSArray*)expressions { + + if(!expressions) { + self = [super initWithIndexType: kC4ArrayIndex + expressions: @[@""]]; + } + + if ([expressions count] == 0) { + [NSException raise: NSInvalidArgumentException format: + @"Expressions cannot be empty "]; + } + + self = [super initWithIndexType: kC4ArrayIndex + expressions: expressions]; + + if (self) { + _path = path; + _expressions = expressions; + } + + return self; +} + +- (instancetype) initWithPath: (NSString*)path { + self = [self initWithPath: path expressions: nil]; + return self; +} + +- (C4IndexOptions) indexOptions { + C4IndexOptions c4options = { }; + //c4options.unnestPath = _path; + return c4options; +} + +@end diff --git a/Objective-C/CBLFullTextIndexConfiguration.m b/Objective-C/CBLFullTextIndexConfiguration.m index 0715c0618..c91e6b98b 100644 --- a/Objective-C/CBLFullTextIndexConfiguration.m +++ b/Objective-C/CBLFullTextIndexConfiguration.m @@ -27,7 +27,8 @@ @implementation CBLFullTextIndexConfiguration - (instancetype) initWithExpression: (NSArray*)expressions ignoreAccents: (BOOL)ignoreAccents language: (NSString* __nullable)language { - self = [super initWithIndexType: kC4FullTextIndex expressions: expressions]; + self = [super initWithIndexType: kC4FullTextIndex + expressions: expressions]; if (self) { // there is no default 'ignoreAccents', since its NOT an optional argument. _ignoreAccents = ignoreAccents; diff --git a/Objective-C/CouchbaseLite.h b/Objective-C/CouchbaseLite.h index ffbb0e0fe..6f24fd78c 100644 --- a/Objective-C/CouchbaseLite.h +++ b/Objective-C/CouchbaseLite.h @@ -27,6 +27,7 @@ FOUNDATION_EXPORT const unsigned char CouchbaseLiteVersionString[]; #import #import +#import #import #import #import diff --git a/Objective-C/Exports/CBL.txt b/Objective-C/Exports/CBL.txt index ef152e06f..09677e7c1 100644 --- a/Objective-C/Exports/CBL.txt +++ b/Objective-C/Exports/CBL.txt @@ -21,6 +21,7 @@ # Classes: .objc_class_name_CBLArray .objc_class_name_CBLAuthenticator +.objc_class_name_CBLArrayIndexConfiguration .objc_class_name_CBLBasicAuthenticator .objc_class_name_CBLBlob .objc_class_name_CBLCollection diff --git a/Objective-C/Exports/Generated/CBL.exp b/Objective-C/Exports/Generated/CBL.exp index 36c0f6db2..7fd127c07 100644 --- a/Objective-C/Exports/Generated/CBL.exp +++ b/Objective-C/Exports/Generated/CBL.exp @@ -1,6 +1,7 @@ # GENERATED BY generate_exports.sh -- DO NOT EDIT .objc_class_name_CBLArray +.objc_class_name_CBLArrayIndexConfiguration .objc_class_name_CBLAuthenticator .objc_class_name_CBLBasicAuthenticator .objc_class_name_CBLBlob diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp index dc7d1a526..c10cd3242 100644 --- a/Objective-C/Exports/Generated/CBL_EE.exp +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -1,6 +1,7 @@ # GENERATED BY generate_exports.sh -- DO NOT EDIT .objc_class_name_CBLArray +.objc_class_name_CBLArrayIndexConfiguration .objc_class_name_CBLAuthenticator .objc_class_name_CBLBasicAuthenticator .objc_class_name_CBLBlob diff --git a/Swift/CouchbaseLiteSwift.private.modulemap b/Swift/CouchbaseLiteSwift.private.modulemap index f20bc1c52..ccd5fc695 100644 --- a/Swift/CouchbaseLiteSwift.private.modulemap +++ b/Swift/CouchbaseLiteSwift.private.modulemap @@ -20,6 +20,7 @@ framework module CouchbaseLiteSwift_Private { header "CBLArray.h" header "CBLArrayFragment.h" + header "CBLArrayIndexConfiguration.h" header "CBLAuthenticator.h" header "CBLBasicAuthenticator.h" header "CBLBlob.h" diff --git a/Swift/IndexConfiguration.swift b/Swift/IndexConfiguration.swift index 1b2a3f2e5..d77f5b847 100644 --- a/Swift/IndexConfiguration.swift +++ b/Swift/IndexConfiguration.swift @@ -69,6 +69,38 @@ public struct ValueIndexConfiguration: IndexConfiguration, IndexConfigConvertabl } } +/// Configuration for indexing property values within nested arrays in documents, +/// intended for use with the UNNEST query. +public struct ArrayIndexConfiguration: IndexConfiguration, IndexConfigConvertable { + /// Path to the array, which can be nested. + public let path: String + + /// The expressions representing the values within the array to be indexed. + public let expressions: [String]? + + /// Initializes the configuration with paths to the nested array and the optional + /// expressions for the values within the arrays to be indexed. + /// - Parameter path Path to the array, which can be nested to be indexed. + /// - Note Use "[]" to represent a property that is an array of each nested array level. + /// For a single array or the last level array, the "[]" is optional. + /// For instance, use "contacts[].phones" to specify an array of phones within each contact. + /// - Parameter expressions An optional array of strings, where each string + /// represents an expression defining the values within the array to be indexed. + /// If the array specified by the path contains scalar values, this parameter can be null. + /// - Returns The ArrayIndexConfiguration object. + public init(path: String, _ expressions: [String]?) { + self.path = path + self.expressions = expressions + } + + // MARK: Internal + + func toImpl() -> CBLIndexConfiguration { + return CBLArrayIndexConfiguration(path: path, expressions: expressions) + } +} + + // MARK: Internal protocol IndexConfigConvertable { From e2c459c7e3336b15fab01dfd2c43cace7cfd1c33 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 7 Oct 2024 15:25:35 +0100 Subject: [PATCH 40/49] CBL-5181: Array Index API tests and adjustments (#3330) * LiteCore 3.2.1-5 * Obj-C tests and api update * Swift tests and api update * CBL-5899 --- CouchbaseLite.xcodeproj/project.pbxproj | 20 ++ Objective-C/CBLArrayIndexConfiguration.m | 8 +- Objective-C/CBLCollection.mm | 312 ++++++++++-------- Objective-C/Internal/CBLCollection+Internal.h | 3 + Objective-C/Internal/CBLCollection+Swift.h | 2 + Objective-C/Internal/CBLQueryIndex+Internal.h | 1 + Objective-C/Tests/CollectionTest.m | 8 - Objective-C/Tests/Support/profiles_100.json | 100 ++++++ Objective-C/Tests/UnnestArrayIndexTest.m | 107 ++++++ Objective-C/Tests/VectorSearchTest+Lazy.m | 16 +- Swift/Collection.swift | 4 + Swift/IndexConfiguration.swift | 9 +- Swift/Tests/ArrayTest.swift | 6 +- Swift/Tests/CBLTestCase.swift | 2 +- Swift/Tests/DictionaryTest.swift | 4 +- Swift/Tests/DocumentTest.swift | 14 +- Swift/Tests/ReplicatorTest+Collection.swift | 8 +- Swift/Tests/UnnestArrayTest.swift | 85 +++++ Swift/Tests/VectorSearchTest+Lazy.swift | 28 +- Swift/Tests/VectorSearchTest.swift | 12 +- vendor/couchbase-lite-core | 2 +- 21 files changed, 546 insertions(+), 205 deletions(-) create mode 100644 Objective-C/Tests/Support/profiles_100.json create mode 100644 Objective-C/Tests/UnnestArrayIndexTest.m create mode 100644 Swift/Tests/UnnestArrayTest.swift diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index a1774e633..d85c63d38 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -1768,6 +1768,10 @@ AE5803F92B9B969D001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; AE5803FA2B9B96BF001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; }; AE5803FB2B9B96BF001A1BE3 /* CouchbaseLiteVectorSearch.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 40086B152B7EDDD400DA6770 /* CouchbaseLiteVectorSearch.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + AE5F25582CAC310700AAB7F4 /* UnnestArrayIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */; }; + AE5F25592CAC310800AAB7F4 /* UnnestArrayIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */; }; + AE5F255A2CAC310900AAB7F4 /* UnnestArrayIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */; }; + AE5F255B2CAC311100AAB7F4 /* UnnestArrayIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */; }; AE83D0842C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Public, ); }; }; AE83D0852C0637ED0055D2CF /* CBLIndexUpdater.mm in Sources */ = {isa = PBXBuildFile; fileRef = AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */; }; AE83D0862C0637ED0055D2CF /* CBLIndexUpdater.h in Headers */ = {isa = PBXBuildFile; fileRef = AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -1780,6 +1784,10 @@ AEC806BC2C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; AEC806BD2C89EA68001C9723 /* CBLArrayIndexConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */; }; AEC806BE2C89EA68001C9723 /* CBLArrayIndexConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */; settings = {ATTRIBUTES = (Private, ); }; }; + AECA897D2CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */; }; + AECA897E2CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */; }; + AECA897F2CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */; }; + AECA89802CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */; }; AECD5A162C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; AECD5A172C0E21D900B1247E /* CBLIndexUpdater+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */; }; /* End PBXBuildFile section */ @@ -2798,10 +2806,12 @@ AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VectorSearchTest.m; sourceTree = ""; }; AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VectorSearchTest.swift; sourceTree = ""; }; AE5803D72B9B5B2A001A1BE3 /* WordEmbeddingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordEmbeddingModel.swift; sourceTree = ""; }; + AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UnnestArrayIndexTest.m; sourceTree = ""; }; AE83D0822C0637ED0055D2CF /* CBLIndexUpdater.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLIndexUpdater.h; sourceTree = ""; }; AE83D0832C0637ED0055D2CF /* CBLIndexUpdater.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CBLIndexUpdater.mm; sourceTree = ""; }; AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLArrayIndexConfiguration.h; sourceTree = ""; }; AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLArrayIndexConfiguration.m; sourceTree = ""; }; + AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnnestArrayTest.swift; sourceTree = ""; }; AECD59F92C0E137200B1247E /* CBLQueryIndex+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLQueryIndex+Internal.h"; sourceTree = ""; }; AECD5A0F2C0E21D900B1247E /* CBLIndexUpdater+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLIndexUpdater+Internal.h"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -3087,6 +3097,7 @@ 1A13DD3328B8809300BC1084 /* URLEndpointListenerTest+Collection.swift */, AE5803A32B99C67D001A1BE3 /* VectorSearchTest.swift */, 406F8DFC2C27F097000223FC /* VectorSearchTest+Lazy.swift */, + AECA897C2CADADAA00C7B6BE /* UnnestArrayTest.swift */, 1AA91DC522B0356000BF0BDE /* CustomLogger.swift */, 93249D81246B99FD000A8A6E /* iOS */, 939B79241E679017009A70EF /* Info.plist */, @@ -4154,6 +4165,7 @@ 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */, AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */, 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */, + AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */, 93DECF3E200DBE5800F44953 /* Support */, 936483AA1E4431C6008D08B3 /* iOS */, 275FF5FA1E3FBD3B005F90DD /* Performance */, @@ -6212,6 +6224,7 @@ 1A8F85092893F318008C4333 /* QueryTest+Collection.swift in Sources */, 93E17F151ED4ED4000671CA1 /* NotificationTest.swift in Sources */, 27BE3B4D1E4E51C80012B74A /* DatabaseTest.swift in Sources */, + AECA897D2CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */, 1A6084F828758AE00037C66F /* CollectionTest.swift in Sources */, 934C2CDE1F4FC0D20010316F /* CBLTestHelper.m in Sources */, 9388CC5521C25CC7005CA66D /* LogTest.swift in Sources */, @@ -6255,6 +6268,7 @@ 93BB1CB4246BB2ED004FFA00 /* ReplicatorTest.swift in Sources */, 1A6084FA28758AE20037C66F /* CollectionTest.swift in Sources */, 93BB1CB1246BB2E4004FFA00 /* PredictiveQueryTest.swift in Sources */, + AECA89802CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */, 93BB1CBA246BB2F4004FFA00 /* CustomLogger.swift in Sources */, 1A13DD4428B882BA00BC1084 /* URLEndpointListenerTest+Collection.swift in Sources */, 93BB1CA1246BB2CA004FFA00 /* DateTimeQueryFunctionTest.swift in Sources */, @@ -6698,6 +6712,7 @@ 9343F13B207D61EC00F19A89 /* CBLTestCase.m in Sources */, 1A4160DB22836E8C0061A567 /* ReplicatorTest+Main.m in Sources */, 930C7F9220FE4F7500C74A12 /* CBLMockConnectionErrorLogic.m in Sources */, + AE5F255A2CAC310900AAB7F4 /* UnnestArrayIndexTest.m in Sources */, 1AA6744B227924120018CC6D /* QueryTest+Join.m in Sources */, 934EF8312460D0770053A47C /* TLSIdentityTest.m in Sources */, 9343F13C207D61EC00F19A89 /* DatabaseTest.m in Sources */, @@ -6772,6 +6787,7 @@ 930C7F9520FE4F7500C74A12 /* CBLMockConnection.m in Sources */, 9343F179207D633300F19A89 /* QueryTest.m in Sources */, 1A621D7B2887DCFE0017F905 /* QueryTest+Collection.m in Sources */, + AE5F255B2CAC311100AAB7F4 /* UnnestArrayIndexTest.m in Sources */, 933F841E220BA4100093EC88 /* PredictiveQueryTest+CoreML.m in Sources */, 1AA3D78822AB07D80098E16B /* CustomLogger.m in Sources */, 1A2F2C3627FD5B2200084B3C /* TrustCheckTest.m in Sources */, @@ -6800,6 +6816,7 @@ 93EB25A421CDCC160006FB88 /* PredictiveQueryTest.swift in Sources */, 1A6084FB28758AE90037C66F /* CollectionTest.swift in Sources */, 9343F18E207D636300F19A89 /* DocumentTest.swift in Sources */, + AECA897F2CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */, 1A13DD4328B882BA00BC1084 /* URLEndpointListenerTest+Collection.swift in Sources */, 9343F18F207D636300F19A89 /* ArrayTest.swift in Sources */, 1A690CD7242150DF0084D017 /* ReplicatorTest+PendingDocIds.swift in Sources */, @@ -6877,6 +6894,7 @@ 1ABA63AF28813A8A005835E7 /* ReplicatorTest+Collection.m in Sources */, 1A621D792887DCFC0017F905 /* QueryTest+Collection.m in Sources */, 9388CBAC21BD917D005CA66D /* DocumentExpirationTest.m in Sources */, + AE5F25592CAC310800AAB7F4 /* UnnestArrayIndexTest.m in Sources */, 1AA67439227924110018CC6D /* QueryTest+Meta.m in Sources */, 9385F3041FC645D300032037 /* ConcurrentTest.m in Sources */, 93CD018E1E95546200AFB3FA /* QueryTest.m in Sources */, @@ -7018,6 +7036,7 @@ 9378C5911E25B3F0001BB196 /* DatabaseTest.m in Sources */, 1A4FE76A225ED344009D5F43 /* MiscCppTest.mm in Sources */, 1A908401288027EE006B1885 /* ReplicatorTest+Collection.m in Sources */, + AE5F25582CAC310700AAB7F4 /* UnnestArrayIndexTest.m in Sources */, 1AC83BC821C026D100792098 /* DateTimeQueryFunctionTest.m in Sources */, 938B3702200D7D1D004485D8 /* MigrationTest.m in Sources */, 1AA3D78422AB07C50098E16B /* CustomLogger.m in Sources */, @@ -7051,6 +7070,7 @@ 1A8F85082893F317008C4333 /* QueryTest+Collection.swift in Sources */, 93BB1CAE246BB2DC004FFA00 /* QueryTest.swift in Sources */, 93BB1CAD246BB2DC004FFA00 /* NotificationTest.swift in Sources */, + AECA897E2CADADAA00C7B6BE /* UnnestArrayTest.swift in Sources */, 1A6084F728758ADF0037C66F /* CollectionTest.swift in Sources */, 93BB1CA6246BB2D3004FFA00 /* LogTest.swift in Sources */, 93BB1CB7246BB2F3004FFA00 /* CustomLogger.swift in Sources */, diff --git a/Objective-C/CBLArrayIndexConfiguration.m b/Objective-C/CBLArrayIndexConfiguration.m index d735f607f..64538a205 100644 --- a/Objective-C/CBLArrayIndexConfiguration.m +++ b/Objective-C/CBLArrayIndexConfiguration.m @@ -30,11 +30,9 @@ - (instancetype) initWithPath: (NSString*) path if(!expressions) { self = [super initWithIndexType: kC4ArrayIndex expressions: @[@""]]; - } - - if ([expressions count] == 0) { + } else if ([expressions count] == 0 || [expressions[0] length] == 0) { [NSException raise: NSInvalidArgumentException format: - @"Expressions cannot be empty "]; + @"Empty expressions is not allowed, use nil instead"]; } self = [super initWithIndexType: kC4ArrayIndex @@ -55,7 +53,7 @@ - (instancetype) initWithPath: (NSString*)path { - (C4IndexOptions) indexOptions { C4IndexOptions c4options = { }; - //c4options.unnestPath = _path; + c4options.unnestPath = [_path UTF8String]; return c4options; } diff --git a/Objective-C/CBLCollection.mm b/Objective-C/CBLCollection.mm index 2e19fa14f..8313fd7e7 100644 --- a/Objective-C/CBLCollection.mm +++ b/Objective-C/CBLCollection.mm @@ -473,151 +473,6 @@ - (BOOL) setDocumentExpirationWithID: (NSString*)documentID } } -#pragma mark - Internal - -- (BOOL) checkIsValid: (NSError**)error { - BOOL valid = c4coll_isValid(_c4col); - if (!valid) { - if (error) - *error = CBLCollectionErrorNotOpen; - } - - return valid; -} - -- (BOOL) isValid { - return [self checkIsValid: nil]; -} - -- (BOOL) database: (CBLDatabase*)db isValid: (NSError**)error { - BOOL valid = db != nil; - if (!valid) { - if (error) - *error = CBLDatabaseErrorNotOpen; - } - - return valid; -} - -- (C4CollectionSpec) c4spec { - // Convert to cString directly instead of using CBLStringBytes to avoid - // stack-use-after-scope problem when a small string is kept in the - // _local stack based buffer of the CBLStringBytes - C4Slice name = c4str([_name cStringUsingEncoding: NSUTF8StringEncoding]); - C4Slice scopeName = c4str([_scope.name cStringUsingEncoding: NSUTF8StringEncoding]); - return { .name = name, .scope = scopeName }; -} - -- (BOOL) isEqual: (id)object { - if (self == object) - return YES; - - CBLCollection* other = $castIf(CBLCollection, object); - if (!other) - return NO; - - if (!(other && [self.name isEqual: other.name] && - [self.scope.name isEqual: other.scope.name] && - [self.database.path isEqual: other.database.path])) { - return NO; - } - - if (!self.isValid || !other.isValid) - return NO; - - return YES; -} - -- (id) addCollectionChangeListener: (void (^)(CBLCollectionChange*))listener - queue: (dispatch_queue_t)queue { - if (!_colChangeNotifier) { - _colChangeNotifier = [CBLChangeNotifier new]; - C4Error c4err = {}; - _colObs = c4dbobs_createOnCollection(_c4col, colObserverCallback, (__bridge void *)self, &c4err); - if (!_colObs) { - CBLWarn(Database, @"%@ Failed to create collection obs c4col=%p err=%d/%d", - self, _c4col, c4err.domain, c4err.code); - } - } - - return [_colChangeNotifier addChangeListenerWithQueue: queue listener: listener delegate: self]; -} - -static void colObserverCallback(C4CollectionObserver* obs, void* context) { - CBLCollection *c = (__bridge CBLCollection *)context; - dispatch_async(c.dispatchQueue, ^{ - [c postCollectionChanged]; - }); -} - -- (void) postCollectionChanged { - CBL_LOCK(_mutex) { - if (!_colObs || !_c4col) - return; - - const uint32_t kMaxChanges = 100u; - C4DatabaseChange changes[kMaxChanges]; - bool external = false; - C4CollectionObservation obs = {}; - NSMutableArray* docIDs = [NSMutableArray new]; - do { - // Read changes in batches of kMaxChanges: - obs = c4dbobs_getChanges(_colObs, changes, kMaxChanges); - if (obs.numChanges == 0 || external != obs.external || docIDs.count > 1000) { - if(docIDs.count > 0) { - CBLCollectionChange* change = [[CBLCollectionChange alloc] initWithCollection: self - documentIDs: docIDs - isExternal: external]; - [_colChangeNotifier postChange: change]; - docIDs = [NSMutableArray new]; - } - } - - external = obs.external; - for(uint32_t i = 0; i < obs.numChanges; i++) { - NSString *docID =slice2string(changes[i].docID); - [docIDs addObject: docID]; - } - c4dbobs_releaseChanges(changes, obs.numChanges); - } while(obs.numChanges > 0); - } -} - -- (void) removeToken: (id)token { - CBL_LOCK(_mutex) { - CBLChangeListenerToken* t = (CBLChangeListenerToken*)token; - if (t.context) - [self removeDocumentChangeListenerWithToken: token]; - else { - if ([_colChangeNotifier removeChangeListenerWithToken: token] == 0) { - c4dbobs_free(_colObs); - _colObs = nil; - _colChangeNotifier = nil; - } - } - } -} - -- (void) removeDocumentChangeListenerWithToken: (CBLChangeListenerToken*)token { - CBL_LOCK(_mutex) { - NSString* documentID = (NSString*)token.context; - CBLDocumentChangeNotifier* notifier = _docChangeNotifiers[documentID]; - if (notifier && [notifier removeChangeListenerWithToken: token] == 0) { - [notifier stop]; - [_docChangeNotifiers removeObjectForKey:documentID]; - } - } -} - -- (void) freeC4Observer { - c4dbobs_free(_colObs); - _colObs = nullptr; - _colChangeNotifier = nil; - - [_docChangeNotifiers.allValues makeObjectsPerformSelector: @selector(stop)]; - _docChangeNotifiers = nil; -} - #pragma mark - Document listener - (id) addDocumentChangeListenerWithDocumentID: documentID @@ -976,4 +831,171 @@ - (nullable CBLQueryIndex*) indexWithName: (nonnull NSString*)name } } +#pragma mark - Internal + +- (BOOL) checkIsValid: (NSError**)error { + BOOL valid = c4coll_isValid(_c4col); + if (!valid) { + if (error) + *error = CBLCollectionErrorNotOpen; + } + + return valid; +} + +- (BOOL) isValid { + return [self checkIsValid: nil]; +} + +- (BOOL) database: (CBLDatabase*)db isValid: (NSError**)error { + BOOL valid = db != nil; + if (!valid) { + if (error) + *error = CBLDatabaseErrorNotOpen; + } + + return valid; +} + +- (C4CollectionSpec) c4spec { + // Convert to cString directly instead of using CBLStringBytes to avoid + // stack-use-after-scope problem when a small string is kept in the + // _local stack based buffer of the CBLStringBytes + C4Slice name = c4str([_name cStringUsingEncoding: NSUTF8StringEncoding]); + C4Slice scopeName = c4str([_scope.name cStringUsingEncoding: NSUTF8StringEncoding]); + return { .name = name, .scope = scopeName }; +} + +- (BOOL) isEqual: (id)object { + if (self == object) + return YES; + + CBLCollection* other = $castIf(CBLCollection, object); + if (!other) + return NO; + + if (!(other && [self.name isEqual: other.name] && + [self.scope.name isEqual: other.scope.name] && + [self.database.path isEqual: other.database.path])) { + return NO; + } + + if (!self.isValid || !other.isValid) + return NO; + + return YES; +} + +- (id) addCollectionChangeListener: (void (^)(CBLCollectionChange*))listener + queue: (dispatch_queue_t)queue { + if (!_colChangeNotifier) { + _colChangeNotifier = [CBLChangeNotifier new]; + C4Error c4err = {}; + _colObs = c4dbobs_createOnCollection(_c4col, colObserverCallback, (__bridge void *)self, &c4err); + if (!_colObs) { + CBLWarn(Database, @"%@ Failed to create collection obs c4col=%p err=%d/%d", + self, _c4col, c4err.domain, c4err.code); + } + } + + return [_colChangeNotifier addChangeListenerWithQueue: queue listener: listener delegate: self]; +} + +static void colObserverCallback(C4CollectionObserver* obs, void* context) { + CBLCollection *c = (__bridge CBLCollection *)context; + dispatch_async(c.dispatchQueue, ^{ + [c postCollectionChanged]; + }); +} + +- (void) postCollectionChanged { + CBL_LOCK(_mutex) { + if (!_colObs || !_c4col) + return; + + const uint32_t kMaxChanges = 100u; + C4DatabaseChange changes[kMaxChanges]; + bool external = false; + C4CollectionObservation obs = {}; + NSMutableArray* docIDs = [NSMutableArray new]; + do { + // Read changes in batches of kMaxChanges: + obs = c4dbobs_getChanges(_colObs, changes, kMaxChanges); + if (obs.numChanges == 0 || external != obs.external || docIDs.count > 1000) { + if(docIDs.count > 0) { + CBLCollectionChange* change = [[CBLCollectionChange alloc] initWithCollection: self + documentIDs: docIDs + isExternal: external]; + [_colChangeNotifier postChange: change]; + docIDs = [NSMutableArray new]; + } + } + + external = obs.external; + for(uint32_t i = 0; i < obs.numChanges; i++) { + NSString *docID =slice2string(changes[i].docID); + [docIDs addObject: docID]; + } + c4dbobs_releaseChanges(changes, obs.numChanges); + } while(obs.numChanges > 0); + } +} + +- (void) removeToken: (id)token { + CBL_LOCK(_mutex) { + CBLChangeListenerToken* t = (CBLChangeListenerToken*)token; + if (t.context) + [self removeDocumentChangeListenerWithToken: token]; + else { + if ([_colChangeNotifier removeChangeListenerWithToken: token] == 0) { + c4dbobs_free(_colObs); + _colObs = nil; + _colChangeNotifier = nil; + } + } + } +} + +- (void) removeDocumentChangeListenerWithToken: (CBLChangeListenerToken*)token { + CBL_LOCK(_mutex) { + NSString* documentID = (NSString*)token.context; + CBLDocumentChangeNotifier* notifier = _docChangeNotifiers[documentID]; + if (notifier && [notifier removeChangeListenerWithToken: token] == 0) { + [notifier stop]; + [_docChangeNotifiers removeObjectForKey:documentID]; + } + } +} + +- (void) freeC4Observer { + c4dbobs_free(_colObs); + _colObs = nullptr; + _colChangeNotifier = nil; + + [_docChangeNotifiers.allValues makeObjectsPerformSelector: @selector(stop)]; + _docChangeNotifiers = nil; +} + +- (nullable NSArray*) indexesInfo: (NSError**)error { + CBL_LOCK(_mutex) { + if (![self checkIsValid: error]) + return nil; + + C4Error err = {}; + C4SliceResult res = c4coll_getIndexesInfo(_c4col, &err); + if (err.code != 0){ + convertError(err, error); + return nil; + } + + FLDoc doc = FLDoc_FromResultData(res, kFLTrusted, nullptr, nullslice); + FLSliceResult_Release(res); + + NSArray* indexes = FLValue_GetNSObject(FLDoc_GetRoot(doc), nullptr); + FLDoc_Release(doc); + + return indexes; + } +} + @end diff --git a/Objective-C/Internal/CBLCollection+Internal.h b/Objective-C/Internal/CBLCollection+Internal.h index 2d9d340f7..c2038829e 100644 --- a/Objective-C/Internal/CBLCollection+Internal.h +++ b/Objective-C/Internal/CBLCollection+Internal.h @@ -21,6 +21,7 @@ #import "CBLCollection.h" #import "CBLChangeListenerToken.h" #import "CBLDatabase.h" +#import "c4.h" #define CBLCollectionErrorNotOpen [NSError errorWithDomain: CBLErrorDomain \ code: CBLErrorNotOpen \ @@ -62,6 +63,8 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL) checkIsValid: (NSError**)error; +- (nullable NSArray*) indexesInfo: (NSError**)error; + @end @interface CBLCollectionChange () diff --git a/Objective-C/Internal/CBLCollection+Swift.h b/Objective-C/Internal/CBLCollection+Swift.h index a92e4c1a4..4e9edc917 100644 --- a/Objective-C/Internal/CBLCollection+Swift.h +++ b/Objective-C/Internal/CBLCollection+Swift.h @@ -28,6 +28,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) BOOL isValid; +- (nullable NSArray*) indexesInfo: (NSError**)error; + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/Internal/CBLQueryIndex+Internal.h b/Objective-C/Internal/CBLQueryIndex+Internal.h index 416df3f76..bd062f706 100644 --- a/Objective-C/Internal/CBLQueryIndex+Internal.h +++ b/Objective-C/Internal/CBLQueryIndex+Internal.h @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype) initWithC4Index: (C4Index*) c4index name: (NSString*) name collection: (CBLCollection*) collection; + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/CollectionTest.m b/Objective-C/Tests/CollectionTest.m index 7ec875d14..bf971cc20 100644 --- a/Objective-C/Tests/CollectionTest.m +++ b/Objective-C/Tests/CollectionTest.m @@ -25,14 +25,6 @@ @interface CollectionTest : CBLTestCase @implementation CollectionTest -- (void) setUp { - [super setUp]; -} - -- (void) tearDown { - [super tearDown]; -} - - (void) testGetNonExistingDoc { NSError* error = nil; CBLCollection* col = [self.db defaultCollection: &error]; diff --git a/Objective-C/Tests/Support/profiles_100.json b/Objective-C/Tests/Support/profiles_100.json new file mode 100644 index 000000000..9cfc6319a --- /dev/null +++ b/Objective-C/Tests/Support/profiles_100.json @@ -0,0 +1,100 @@ +{"pid": "p-0001", "name": {"first": "Lue", "last": "Laserna"}, "contacts": [{"address": {"city": "San Pedro", "state": "CA", "street": "19 Deer Loop", "zip": "90732"}, "emails": ["lue.laserna@nosql-matters.org", "laserna@nosql-matters.org"], "phones": [{"numbers": ["310-8268551", "310-7618427"], "preferred": false, "type": "home"}, {"numbers": ["310-9601308"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "San Pedro", "state": "CA", "street": "1820 Maple Ln", "zip": "90732"}, "emails": ["Lue@email.com", "Laserna@email.com"], "phones": [{"numbers": ["310-6653153"], "preferred": false, "type": "home"}, {"numbers": ["310-4833623"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting"]} +{"pid": "p-0002", "name": {"first": "Jasper", "last": "Grebel"}, "contacts": [{"address": {"city": "Burns", "state": "KS", "street": "19 Florida Loop", "zip": "66840"}, "emails": ["jasper.grebel@nosql-matters.org"], "phones": [{"numbers": ["316-2417120", "316-2767391"], "preferred": false, "type": "home"}, {"numbers": ["316-8833161"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Burns", "state": "KS", "street": "4795 Willow Loop", "zip": "66840"}, "emails": ["Jasper@email.com", "Grebel@email.com"], "phones": [{"numbers": ["316-9487549"], "preferred": true, "type": "home"}, {"numbers": ["316-4737548"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["shopping"]} +{"pid": "p-0003", "name": {"first": "Kandra", "last": "Beichner"}, "contacts": [{"address": {"city": "Tacoma", "state": "WA", "street": "6 John Run", "zip": "98434"}, "emails": ["kandra.beichner@nosql-matters.org", "kandra@nosql-matters.org"], "phones": [{"numbers": ["253-0405964"], "preferred": false, "type": "home"}, {"numbers": ["253-7421842"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Tacoma", "state": "WA", "street": "9509 Cedar Ave", "zip": "98434"}, "emails": ["Kandra@email.com", "Beichner@email.com"], "phones": [{"numbers": ["253-5727806"], "preferred": false, "type": "home"}, {"numbers": ["253-8671217"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["swimming"]} +{"pid": "p-0004", "name": {"first": "Jeff", "last": "Schmith"}, "contacts": [{"address": {"city": "Poughkeepsie", "state": "AR", "street": "14 198th St", "zip": "72569"}, "emails": ["jeff.schmith@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["870-5974023"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Poughkeepsie", "state": "AR", "street": "9356 Willow Cir", "zip": "72569"}, "emails": ["Jeff@email.com", "Schmith@email.com"], "phones": [{"numbers": ["870-4182309"], "preferred": true, "type": "home"}, {"numbers": ["870-1205865"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting", "boxing", "reading"]} +{"pid": "p-0005", "name": {"first": "Tuan", "last": "Climer"}, "contacts": [{"address": {"city": "Bedminster", "state": "NJ", "street": "6 Kansas St", "zip": "07921"}, "emails": ["tuan.climer@nosql-matters.org"], "phones": [{"numbers": ["908-8376478"], "preferred": false, "type": "home"}, {"numbers": ["908-9178111"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Bedminster", "state": "NJ", "street": "4812 Birch Ct", "zip": "07921"}, "emails": ["Tuan@email.com", "Climer@email.com"], "phones": [{"numbers": ["908-8128877"], "preferred": false, "type": "home"}, {"numbers": ["908-7362917"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["ironing"]} +{"pid": "p-0006", "name": {"first": "Warner", "last": "Lemaire"}, "contacts": [{"address": {"city": "Spotsylvania", "state": "VA", "street": "14 234th St", "zip": "22553"}, "emails": ["warner.lemaire@nosql-matters.org", "warner@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["540-4382519"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Spotsylvania", "state": "VA", "street": "4699 Bear Way", "zip": "22553"}, "emails": ["Warner@email.com", "Lemaire@email.com"], "phones": [{"numbers": ["540-9773061"], "preferred": true, "type": "home"}, {"numbers": ["540-5985360"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["driving"]} +{"pid": "p-0007", "name": {"first": "Hugh", "last": "Potash"}, "contacts": [{"address": {"city": "Elmira", "state": "NY", "street": "16 Beechwood Way", "zip": "14902"}, "emails": ["hugh.potash@nosql-matters.org", "potash@nosql-matters.org", "hugh@nosql-matters.org"], "phones": [{"numbers": ["607-5183546"], "preferred": false, "type": "home"}, {"numbers": ["607-9211193"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Elmira", "state": "NY", "street": "6492 Pine St", "zip": "14902"}, "emails": ["Hugh@email.com", "Potash@email.com"], "phones": [{"numbers": ["607-9278334"], "preferred": false, "type": "home"}, {"numbers": ["607-8563742"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0008", "name": {"first": "Jennefer", "last": "Menning"}, "contacts": [{"address": {"city": "Albany", "state": "MN", "street": "1 Euclid Dr", "zip": "56307"}, "emails": ["jennefer.menning@nosql-matters.org", "jennefer@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["320-4119897"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Albany", "state": "MN", "street": "734 Deer Ln", "zip": "56307"}, "emails": ["Jennefer@email.com", "Menning@email.com"], "phones": [{"numbers": ["320-9330377"], "preferred": true, "type": "home"}, {"numbers": ["320-9733917"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0009", "name": {"first": "Claude", "last": "Willcott"}, "contacts": [{"address": {"city": "Storm Lake", "state": "IA", "street": "2 Country club Ave", "zip": "50588"}, "emails": ["claude.willcott@nosql-matters.org", "willcott@nosql-matters.org", "claude@nosql-matters.org"], "phones": [{"numbers": ["712-3896363"], "preferred": false, "type": "home"}, {"numbers": ["712-2126890"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Storm Lake", "state": "IA", "street": "7109 Hickory Loop", "zip": "50588"}, "emails": ["Claude@email.com", "Willcott@email.com"], "phones": [{"numbers": ["712-5214269"], "preferred": false, "type": "home"}, {"numbers": ["712-4499777"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chess", "driving"]} +{"pid": "p-0010", "name": {"first": "Maximina", "last": "Kilzer"}, "contacts": [{"address": {"city": "Carmen", "state": "OK", "street": "4 Phillips Ln", "zip": "73726"}, "emails": ["maximina.kilzer@nosql-matters.org", "kilzer@nosql-matters.org"], "phones": [{"numbers": ["580-7678062"], "preferred": false, "type": "home"}, {"numbers": ["580-9283690"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Carmen", "state": "OK", "street": "1956 Bear Ln", "zip": "73726"}, "emails": ["Maximina@email.com", "Kilzer@email.com"], "phones": [{"numbers": ["580-8455552"], "preferred": true, "type": "home"}, {"numbers": ["580-5366847"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["shopping", "travelling"]} +{"pid": "p-0011", "name": {"first": "Calvin", "last": "Porro"}, "contacts": [{"address": {"city": "Alberton", "state": "MT", "street": "20 12th Ave", "zip": "59820"}, "emails": ["calvin.porro@nosql-matters.org"], "phones": [{"numbers": ["406-7464035"], "preferred": false, "type": "home"}, {"numbers": ["406-6637752"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Alberton", "state": "MT", "street": "1569 Birch Cir", "zip": "59820"}, "emails": ["Calvin@email.com", "Porro@email.com"], "phones": [{"numbers": ["406-8751787"], "preferred": false, "type": "home"}, {"numbers": ["406-9518454"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0012", "name": {"first": "Diedre", "last": "Clinton"}, "contacts": [{"address": {"city": "Cotopaxi", "state": "CO", "street": "2 Fraser Ave", "zip": "81223"}, "emails": ["diedre.clinton@nosql-matters.org", "clinton@nosql-matters.org", "diedre@nosql-matters.org"], "phones": [{"numbers": ["719-7055896"], "preferred": false, "type": "home"}, {"numbers": ["719-3495060"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Cotopaxi", "state": "CO", "street": "5663 Elm St", "zip": "81223"}, "emails": ["Diedre@email.com", "Clinton@email.com"], "phones": [{"numbers": ["719-2593824"], "preferred": true, "type": "home"}, {"numbers": ["719-3103360"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["swimming"]} +{"pid": "p-0013", "name": {"first": "Lavone", "last": "Peery"}, "contacts": [{"address": {"city": "Council", "state": "ID", "street": "7 Wyoming Hwy", "zip": "83612"}, "emails": ["lavone.peery@nosql-matters.org", "lavone@nosql-matters.org"], "phones": [{"numbers": ["208-6845728", "208-9763317"], "preferred": false, "type": "home"}, {"numbers": ["208-7873856"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Council", "state": "ID", "street": "2066 Birch St", "zip": "83612"}, "emails": ["Lavone@email.com", "Peery@email.com"], "phones": [{"numbers": ["208-5765929"], "preferred": false, "type": "home"}, {"numbers": ["208-5891683"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0014", "name": {"first": "Stephen", "last": "Jakovac"}, "contacts": [{"address": {"city": "Alexandria", "state": "MN", "street": "10 4th St", "zip": "56308"}, "emails": ["stephen.jakovac@nosql-matters.org"], "phones": [{"numbers": ["320-2503176", "320-9515697"], "preferred": false, "type": "home"}, {"numbers": ["320-6021769"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Alexandria", "state": "MN", "street": "9428 Oak Loop", "zip": "56308"}, "emails": ["Stephen@email.com", "Jakovac@email.com"], "phones": [{"numbers": ["320-3858702"], "preferred": true, "type": "home"}, {"numbers": ["320-4400953"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0015", "name": {"first": "Cleveland", "last": "Bejcek"}, "contacts": [{"address": {"city": "Santa Cruz", "state": "CA", "street": "14 Patriot Hwy", "zip": "95062"}, "emails": ["cleveland.bejcek@nosql-matters.org", "cleveland@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["831-3372818"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Santa Cruz", "state": "CA", "street": "7939 Willow Blvd", "zip": "95062"}, "emails": ["Cleveland@email.com", "Bejcek@email.com"], "phones": [{"numbers": ["831-4379408"], "preferred": false, "type": "home"}, {"numbers": ["831-6416909"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chess", "checkers"]} +{"pid": "p-0016", "name": {"first": "Mickie", "last": "Menchaca"}, "contacts": [{"address": {"city": "Beachwood", "state": "NJ", "street": "1 Kansas Aly", "zip": "08722"}, "emails": ["mickie.menchaca@nosql-matters.org", "mickie@nosql-matters.org"], "phones": [{"numbers": ["732-1143581"], "preferred": false, "type": "home"}, {"numbers": ["732-9452370"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Beachwood", "state": "NJ", "street": "8460 Birch St", "zip": "08722"}, "emails": ["Mickie@email.com", "Menchaca@email.com"], "phones": [{"numbers": ["732-2496198"], "preferred": true, "type": "home"}, {"numbers": ["732-2309811"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0017", "name": {"first": "Jason", "last": "Meneley"}, "contacts": [{"address": {"city": "Cape Vincent", "state": "NY", "street": "9 Spring garden Dr", "zip": "13618"}, "emails": ["jason.meneley@nosql-matters.org", "jason@nosql-matters.org"], "phones": [{"numbers": ["315-7142142", "315-0405535"], "preferred": false, "type": "home"}, {"numbers": ["315-7155449"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Cape Vincent", "state": "NY", "street": "3798 Bear Cir", "zip": "13618"}, "emails": ["Jason@email.com", "Meneley@email.com"], "phones": [{"numbers": ["315-7267568"], "preferred": false, "type": "home"}, {"numbers": ["315-4949930"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["climbing"]} +{"pid": "p-0018", "name": {"first": "Fredda", "last": "Persten"}, "contacts": [{"address": {"city": "El Paso", "state": "TX", "street": "6 University Cir", "zip": "88546"}, "emails": ["fredda.persten@nosql-matters.org", "persten@nosql-matters.org"], "phones": [{"numbers": ["915-7112133", "915-5032376"], "preferred": false, "type": "home"}, {"numbers": ["915-8572661"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "El Paso", "state": "TX", "street": "5212 Birch Rd", "zip": "88546"}, "emails": ["Fredda@email.com", "Persten@email.com"], "phones": [{"numbers": ["915-4151393"], "preferred": true, "type": "home"}, {"numbers": ["915-2704319"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0019", "name": {"first": "Ann", "last": "Skaar"}, "contacts": [{"address": {"city": "Bainville", "state": "MT", "street": "6 17th Ave", "zip": "59212"}, "emails": ["ann.skaar@nosql-matters.org", "skaar@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["406-7696353"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Bainville", "state": "MT", "street": "3862 Oak Way", "zip": "59212"}, "emails": ["Ann@email.com", "Skaar@email.com"], "phones": [{"numbers": ["406-4877500"], "preferred": false, "type": "home"}, {"numbers": ["406-8219852"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["boxing"]} +{"pid": "p-0020", "name": {"first": "Corey", "last": "Shiroma"}, "contacts": [{"address": {"city": "Saint Louis", "state": "MO", "street": "19 Crescent Loop", "zip": "63108"}, "emails": ["corey.shiroma@nosql-matters.org", "shiroma@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["314-6388133"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Saint Louis", "state": "MO", "street": "5949 Bear Loop", "zip": "63108"}, "emails": ["Corey@email.com", "Shiroma@email.com"], "phones": [{"numbers": ["314-2267929"], "preferred": true, "type": "home"}, {"numbers": ["314-7981160"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["swimming", "driving"]} +{"pid": "p-0021", "name": {"first": "Graig", "last": "Flax"}, "contacts": [{"address": {"city": "Belvidere Center", "state": "VT", "street": "7 Wallick Ln", "zip": "05442"}, "emails": ["graig.flax@nosql-matters.org", "graig@nosql-matters.org"], "phones": [{"numbers": ["802-4827967"], "preferred": false, "type": "home"}, {"numbers": ["802-3309983"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Belvidere Center", "state": "VT", "street": "8133 Hickory St", "zip": "05442"}, "emails": ["Graig@email.com", "Flax@email.com"], "phones": [{"numbers": ["802-5250430"], "preferred": false, "type": "home"}, {"numbers": ["802-9833507"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["skiing", "climbing"]} +{"pid": "p-0022", "name": {"first": "Nydia", "last": "Weeden"}, "contacts": [{"address": {"city": "Tampa", "state": "FL", "street": "6 Mozart St", "zip": "33612"}, "emails": ["nydia.weeden@nosql-matters.org"], "phones": [{"numbers": ["813-5324600", "813-4712316"], "preferred": false, "type": "home"}, {"numbers": ["813-1441450"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Tampa", "state": "FL", "street": "774 Eagle Cir", "zip": "33612"}, "emails": ["Nydia@email.com", "Weeden@email.com"], "phones": [{"numbers": ["813-5545153"], "preferred": true, "type": "home"}, {"numbers": ["813-8596825"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0023", "name": {"first": "Minerva", "last": "Reinbold"}, "contacts": [{"address": {"city": "Anthony", "state": "NM", "street": "7 Branchton Ln", "zip": "88021"}, "emails": ["minerva.reinbold@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["505-5286000"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Anthony", "state": "NM", "street": "9477 Birch Cir", "zip": "88021"}, "emails": ["Minerva@email.com", "Reinbold@email.com"], "phones": [{"numbers": ["505-1227441"], "preferred": false, "type": "home"}, {"numbers": ["505-6624231"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chess", "climbing"]} +{"pid": "p-0024", "name": {"first": "Lou", "last": "Cheroki"}, "contacts": [{"address": {"city": "Olla", "state": "LA", "street": "10 Connecticut Dr", "zip": "71465"}, "emails": ["lou.cheroki@nosql-matters.org", "cheroki@nosql-matters.org"], "phones": [{"numbers": ["318-5241811"], "preferred": false, "type": "home"}, {"numbers": ["318-8487451"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Olla", "state": "LA", "street": "6357 Pine St", "zip": "71465"}, "emails": ["Lou@email.com", "Cheroki@email.com"], "phones": [{"numbers": ["318-2855580"], "preferred": true, "type": "home"}, {"numbers": ["318-6205083"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0025", "name": {"first": "Frank", "last": "Sedano"}, "contacts": [{"address": {"city": "Scammon", "state": "KS", "street": "17 Woodson Ave", "zip": "66773"}, "emails": ["frank.sedano@nosql-matters.org", "sedano@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["316-9962555"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Scammon", "state": "KS", "street": "6535 Eagle St", "zip": "66773"}, "emails": ["Frank@email.com", "Sedano@email.com"], "phones": [{"numbers": ["316-1648710"], "preferred": false, "type": "home"}, {"numbers": ["316-4673223"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["ironing"]} +{"pid": "p-0026", "name": {"first": "Tracey", "last": "Braylock"}, "contacts": [{"address": {"city": "Keithsburg", "state": "IL", "street": "10 Wallick Run", "zip": "61442"}, "emails": ["tracey.braylock@nosql-matters.org", "braylock@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["309-4732729"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Keithsburg", "state": "IL", "street": "4038 Deer Dr", "zip": "61442"}, "emails": ["Tracey@email.com", "Braylock@email.com"], "phones": [{"numbers": ["309-1271178"], "preferred": true, "type": "home"}, {"numbers": ["309-3335990"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0027", "name": {"first": "Hayden", "last": "Daniel"}, "contacts": [{"address": {"city": "Blue Rapids", "state": "KS", "street": "4 Main Loop", "zip": "66411"}, "emails": ["hayden.daniel@nosql-matters.org", "daniel@nosql-matters.org", "hayden@nosql-matters.org"], "phones": [{"numbers": ["785-4595093", "785-9794490"], "preferred": false, "type": "home"}, {"numbers": ["785-4736562"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Blue Rapids", "state": "KS", "street": "8929 Fox Cir", "zip": "66411"}, "emails": ["Hayden@email.com", "Daniel@email.com"], "phones": [{"numbers": ["785-8082578"], "preferred": false, "type": "home"}, {"numbers": ["785-7408127"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["travelling"]} +{"pid": "p-0028", "name": {"first": "Jene", "last": "Sance"}, "contacts": [{"address": {"city": "Fall Branch", "state": "TN", "street": "12 Mound Ln", "zip": "37656"}, "emails": ["jene.sance@nosql-matters.org", "sance@nosql-matters.org", "jene@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["423-3516663"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Fall Branch", "state": "TN", "street": "7412 Willow Loop", "zip": "37656"}, "emails": ["Jene@email.com", "Sance@email.com"], "phones": [{"numbers": ["423-7873914"], "preferred": true, "type": "home"}, {"numbers": ["423-8545326"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0029", "name": {"first": "Toccara", "last": "Damato"}, "contacts": [{"address": {"city": "Garneill", "state": "MT", "street": "6 Deer Way", "zip": "59445"}, "emails": ["toccara.damato@nosql-matters.org", "toccara@nosql-matters.org"], "phones": [{"numbers": ["406-4971630"], "preferred": false, "type": "home"}, {"numbers": ["406-2973636"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Garneill", "state": "MT", "street": "801 Bear Dr", "zip": "59445"}, "emails": ["Toccara@email.com", "Damato@email.com"], "phones": [{"numbers": ["406-4298498"], "preferred": false, "type": "home"}, {"numbers": ["406-2089522"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["running", "snowboarding"]} +{"pid": "p-0030", "name": {"first": "Josefina", "last": "Hams"}, "contacts": [{"address": {"city": "Croydon", "state": "PA", "street": "7 Center St", "zip": "19021"}, "emails": ["josefina.hams@nosql-matters.org"], "phones": [{"numbers": ["215-9059514", "215-9507320"], "preferred": false, "type": "home"}, {"numbers": ["215-1497624"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Croydon", "state": "PA", "street": "6573 Eagle Ln", "zip": "19021"}, "emails": ["Josefina@email.com", "Hams@email.com"], "phones": [{"numbers": ["215-2107599"], "preferred": true, "type": "home"}, {"numbers": ["215-8140096"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chess", "boxing"]} +{"pid": "p-0031", "name": {"first": "Randy", "last": "Klenovich"}, "contacts": [{"address": {"city": "Davis", "state": "NC", "street": "22 3rd Ave", "zip": "28524"}, "emails": ["randy.klenovich@nosql-matters.org"], "phones": [{"numbers": ["252-4611502", "252-6980326"], "preferred": false, "type": "home"}, {"numbers": ["252-8481195"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Davis", "state": "NC", "street": "7865 Willow Dr", "zip": "28524"}, "emails": ["Randy@email.com", "Klenovich@email.com"], "phones": [{"numbers": ["252-6021954"], "preferred": false, "type": "home"}, {"numbers": ["252-2604763"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chess", "boxing"]} +{"pid": "p-0032", "name": {"first": "Gonzalo", "last": "Boeshore"}, "contacts": [{"address": {"city": "Sterling Heights", "state": "MI", "street": "18 Willard Dr", "zip": "48310"}, "emails": ["gonzalo.boeshore@nosql-matters.org"], "phones": [{"numbers": ["810-6542772"], "preferred": false, "type": "home"}, {"numbers": ["810-9833933"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Sterling Heights", "state": "MI", "street": "3403 Fox Way", "zip": "48310"}, "emails": ["Gonzalo@email.com", "Boeshore@email.com"], "phones": [{"numbers": ["810-8966286"], "preferred": true, "type": "home"}, {"numbers": ["810-2098693"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0033", "name": {"first": "Hai", "last": "Treamer"}, "contacts": [{"address": {"city": "Sioux City", "state": "IA", "street": "6 Deer St", "zip": "51108"}, "emails": ["hai.treamer@nosql-matters.org"], "phones": [{"numbers": ["712-5347242", "712-7460448"], "preferred": false, "type": "home"}, {"numbers": ["712-2797887"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Sioux City", "state": "IA", "street": "648 Willow Pl", "zip": "51108"}, "emails": ["Hai@email.com", "Treamer@email.com"], "phones": [{"numbers": ["712-6251202"], "preferred": false, "type": "home"}, {"numbers": ["712-3580759"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0034", "name": {"first": "Sammy", "last": "Coldivar"}, "contacts": [{"address": {"city": "Buckhorn", "state": "NM", "street": "2 Commercial Way", "zip": "88025"}, "emails": ["sammy.coldivar@nosql-matters.org"], "phones": [{"numbers": ["505-1394678"], "preferred": false, "type": "home"}, {"numbers": ["505-3439790"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Buckhorn", "state": "NM", "street": "2721 Eagle Ln", "zip": "88025"}, "emails": ["Sammy@email.com", "Coldivar@email.com"], "phones": [{"numbers": ["505-7212465"], "preferred": true, "type": "home"}, {"numbers": ["505-3034229"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0035", "name": {"first": "Son", "last": "Dabe"}, "contacts": [{"address": {"city": "Oneida", "state": "KS", "street": "20 Beechwood Ln", "zip": "66522"}, "emails": ["son.dabe@nosql-matters.org"], "phones": [{"numbers": ["785-4240372"], "preferred": false, "type": "home"}, {"numbers": ["785-6830163"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Oneida", "state": "KS", "street": "617 Maple Ln", "zip": "66522"}, "emails": ["Son@email.com", "Dabe@email.com"], "phones": [{"numbers": ["785-2667233"], "preferred": false, "type": "home"}, {"numbers": ["785-8278974"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["checkers"]} +{"pid": "p-0036", "name": {"first": "Carlee", "last": "Degenfelder"}, "contacts": [{"address": {"city": "Pasadena", "state": "CA", "street": "22 Laramie Cir", "zip": "91189"}, "emails": ["carlee.degenfelder@nosql-matters.org", "degenfelder@nosql-matters.org", "carlee@nosql-matters.org"], "phones": [{"numbers": ["626-4051264", "626-2789580"], "preferred": false, "type": "home"}, {"numbers": ["626-1024917"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Pasadena", "state": "CA", "street": "3032 Birch Rd", "zip": "91189"}, "emails": ["Carlee@email.com", "Degenfelder@email.com"], "phones": [{"numbers": ["626-7089153"], "preferred": true, "type": "home"}, {"numbers": ["626-1456983"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0037", "name": {"first": "Mose", "last": "Backlund"}, "contacts": [{"address": {"city": "Tennga", "state": "GA", "street": "1 1st Ave", "zip": "30751"}, "emails": ["mose.backlund@nosql-matters.org", "backlund@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["706-8671495"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Tennga", "state": "GA", "street": "2147 Birch Blvd", "zip": "30751"}, "emails": ["Mose@email.com", "Backlund@email.com"], "phones": [{"numbers": ["706-1894392"], "preferred": false, "type": "home"}, {"numbers": ["706-6929420"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["biking", "travelling"]} +{"pid": "p-0038", "name": {"first": "Clayton", "last": "Hilda"}, "contacts": [{"address": {"city": "Bolivar", "state": "NY", "street": "22 18th St", "zip": "14715"}, "emails": ["clayton.hilda@nosql-matters.org", "clayton@nosql-matters.org"], "phones": [{"numbers": ["716-0043859", "716-7107175"], "preferred": false, "type": "home"}, {"numbers": ["716-7393704"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Bolivar", "state": "NY", "street": "3414 Birch Dr", "zip": "14715"}, "emails": ["Clayton@email.com", "Hilda@email.com"], "phones": [{"numbers": ["716-3463493"], "preferred": true, "type": "home"}, {"numbers": ["716-5087998"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0039", "name": {"first": "Charley", "last": "Demora"}, "contacts": [{"address": {"city": "North Grafton", "state": "MA", "street": "5 6th St", "zip": "01536"}, "emails": ["charley.demora@nosql-matters.org"], "phones": [{"numbers": ["508-9084206", "508-1037607"], "preferred": false, "type": "home"}, {"numbers": ["508-6649354"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "North Grafton", "state": "MA", "street": "9750 Bear Blvd", "zip": "01536"}, "emails": ["Charley@email.com", "Demora@email.com"], "phones": [{"numbers": ["508-5905226"], "preferred": false, "type": "home"}, {"numbers": ["508-7979365"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0040", "name": {"first": "Dorene", "last": "Gunther"}, "contacts": [{"address": {"city": "Lynn", "state": "MA", "street": "4 Main Run", "zip": "01910"}, "emails": ["dorene.gunther@nosql-matters.org", "gunther@nosql-matters.org"], "phones": [{"numbers": ["781-5720472"], "preferred": false, "type": "home"}, {"numbers": ["781-7245064"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Lynn", "state": "MA", "street": "4998 Bear Way", "zip": "01910"}, "emails": ["Dorene@email.com", "Gunther@email.com"], "phones": [{"numbers": ["781-7210246"], "preferred": true, "type": "home"}, {"numbers": ["781-9752653"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting", "boxing", "driving", "ironing"]} +{"pid": "p-0041", "name": {"first": "Alane", "last": "Goldade"}, "contacts": [{"address": {"city": "Portland", "state": "OR", "street": "22 Wyoming Ct", "zip": "97206"}, "emails": ["alane.goldade@nosql-matters.org", "alane@nosql-matters.org"], "phones": [{"numbers": ["503-3482925", "503-9205542"], "preferred": false, "type": "home"}, {"numbers": ["503-3702996"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Portland", "state": "OR", "street": "2048 Fox Blvd", "zip": "97206"}, "emails": ["Alane@email.com", "Goldade@email.com"], "phones": [{"numbers": ["503-7592566"], "preferred": false, "type": "home"}, {"numbers": ["503-3775835"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0042", "name": {"first": "Salina", "last": "Sue"}, "contacts": [{"address": {"city": "Minier", "state": "IL", "street": "4 New hampshire Way", "zip": "61759"}, "emails": ["salina.sue@nosql-matters.org"], "phones": [{"numbers": ["309-7368050"], "preferred": false, "type": "home"}, {"numbers": ["309-7718021"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Minier", "state": "IL", "street": "3702 Eagle Ct", "zip": "61759"}, "emails": ["Salina@email.com", "Sue@email.com"], "phones": [{"numbers": ["309-5243282"], "preferred": true, "type": "home"}, {"numbers": ["309-7797062"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["running", "shopping"]} +{"pid": "p-0043", "name": {"first": "Rico", "last": "Hoopengardner"}, "contacts": [{"address": {"city": "Downey", "state": "CA", "street": "14 Phillips Hwy", "zip": "90242"}, "emails": ["rico.hoopengardner@nosql-matters.org"], "phones": [{"numbers": ["562-1439117"], "preferred": false, "type": "home"}, {"numbers": ["562-6290832"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Downey", "state": "CA", "street": "3262 Cedar Pl", "zip": "90242"}, "emails": ["Rico@email.com", "Hoopengardner@email.com"], "phones": [{"numbers": ["562-3983851"], "preferred": false, "type": "home"}, {"numbers": ["562-9813323"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["biking"]} +{"pid": "p-0044", "name": {"first": "Laurence", "last": "Vahena"}, "contacts": [{"address": {"city": "Medina", "state": "TX", "street": "18 Santa fe Hwy", "zip": "78055"}, "emails": ["laurence.vahena@nosql-matters.org", "laurence@nosql-matters.org"], "phones": [{"numbers": ["830-1060061"], "preferred": false, "type": "home"}, {"numbers": ["830-5366966"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Medina", "state": "TX", "street": "941 Hickory Dr", "zip": "78055"}, "emails": ["Laurence@email.com", "Vahena@email.com"], "phones": [{"numbers": ["830-4168362"], "preferred": true, "type": "home"}, {"numbers": ["830-5227032"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["running"]} +{"pid": "p-0045", "name": {"first": "Jasper", "last": "Okorududu"}, "contacts": [{"address": {"city": "Witter", "state": "AR", "street": "7 21st St", "zip": "72776"}, "emails": ["jasper.okorududu@nosql-matters.org", "okorududu@nosql-matters.org"], "phones": [{"numbers": ["501-7977106", "501-7138486"], "preferred": false, "type": "home"}, {"numbers": ["501-8463297"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Witter", "state": "AR", "street": "3569 Willow Dr", "zip": "72776"}, "emails": ["Jasper@email.com", "Okorududu@email.com"], "phones": [{"numbers": ["501-6648246"], "preferred": false, "type": "home"}, {"numbers": ["501-3963422"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["boxing", "climbing"]} +{"pid": "p-0046", "name": {"first": "Tyesha", "last": "Loehrer"}, "contacts": [{"address": {"city": "Dittmer", "state": "MO", "street": "9 266th Ave", "zip": "63023"}, "emails": ["tyesha.loehrer@nosql-matters.org", "tyesha@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["636-7867764"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Dittmer", "state": "MO", "street": "2719 Fox Ln", "zip": "63023"}, "emails": ["Tyesha@email.com", "Loehrer@email.com"], "phones": [{"numbers": ["636-6152496"], "preferred": true, "type": "home"}, {"numbers": ["636-7218303"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["boxing"]} +{"pid": "p-0047", "name": {"first": "Murray", "last": "Zirk"}, "contacts": [{"address": {"city": "San Juan", "state": "PR", "street": "10 Main Ct", "zip": "00936"}, "emails": ["murray.zirk@nosql-matters.org", "zirk@nosql-matters.org"], "phones": [{"numbers": ["787-5001534", "787-8378996"], "preferred": false, "type": "home"}, {"numbers": ["787-5390615"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "San Juan", "state": "PR", "street": "2271 Deer Ct", "zip": "00936"}, "emails": ["Murray@email.com", "Zirk@email.com"], "phones": [{"numbers": ["787-3305480"], "preferred": false, "type": "home"}, {"numbers": ["787-5256476"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0048", "name": {"first": "Virgil", "last": "Mulneix"}, "contacts": [{"address": {"city": "Birmingham", "state": "AL", "street": "12 Woodlawn St", "zip": "35243"}, "emails": ["virgil.mulneix@nosql-matters.org"], "phones": [{"numbers": ["205-9970849"], "preferred": false, "type": "home"}, {"numbers": ["205-2943448"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Birmingham", "state": "AL", "street": "6634 Deer Blvd", "zip": "35243"}, "emails": ["Virgil@email.com", "Mulneix@email.com"], "phones": [{"numbers": ["205-1061163"], "preferred": true, "type": "home"}, {"numbers": ["205-1872718"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting", "boxing"]} +{"pid": "p-0049", "name": {"first": "Miles", "last": "Norden"}, "contacts": [{"address": {"city": "West Camp", "state": "NY", "street": "19 Patriot Blvd", "zip": "12490"}, "emails": ["miles.norden@nosql-matters.org"], "phones": [{"numbers": ["914-3581265", "914-5948065"], "preferred": false, "type": "home"}, {"numbers": ["914-8287917"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "West Camp", "state": "NY", "street": "432 Hickory Pl", "zip": "12490"}, "emails": ["Miles@email.com", "Norden@email.com"], "phones": [{"numbers": ["914-1870215"], "preferred": false, "type": "home"}, {"numbers": ["914-1193936"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0050", "name": {"first": "Quinn", "last": "Cote"}, "contacts": [{"address": {"city": "Bassett", "state": "VA", "street": "10 Ottawa Run", "zip": "24055"}, "emails": ["quinn.cote@nosql-matters.org", "cote@nosql-matters.org"], "phones": [{"numbers": ["540-4505047", "540-3769551"], "preferred": false, "type": "home"}, {"numbers": ["540-8296934"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Bassett", "state": "VA", "street": "5135 Willow Ln", "zip": "24055"}, "emails": ["Quinn@email.com", "Cote@email.com"], "phones": [{"numbers": ["540-7833574"], "preferred": true, "type": "home"}, {"numbers": ["540-9963699"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting"]} +{"pid": "p-0051", "name": {"first": "Shavonne", "last": "Finchum"}, "contacts": [{"address": {"city": "Salisbury Mills", "state": "NY", "street": "12 Vermont St", "zip": "12577"}, "emails": ["shavonne.finchum@nosql-matters.org", "finchum@nosql-matters.org"], "phones": [{"numbers": ["914-8440818"], "preferred": false, "type": "home"}, {"numbers": ["914-6337776"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Salisbury Mills", "state": "NY", "street": "5849 Maple Pl", "zip": "12577"}, "emails": ["Shavonne@email.com", "Finchum@email.com"], "phones": [{"numbers": ["914-9660079"], "preferred": false, "type": "home"}, {"numbers": ["914-3237506"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0052", "name": {"first": "Justine", "last": "Girone"}, "contacts": [{"address": {"city": "Denver", "state": "MO", "street": "13 Florida Hwy", "zip": "64441"}, "emails": ["justine.girone@nosql-matters.org", "girone@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["660-2399853"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Denver", "state": "MO", "street": "8431 Pine Pl", "zip": "64441"}, "emails": ["Justine@email.com", "Girone@email.com"], "phones": [{"numbers": ["660-2378409"], "preferred": true, "type": "home"}, {"numbers": ["660-8469184"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0053", "name": {"first": "Dorthea", "last": "Visnic"}, "contacts": [{"address": {"city": "San Bernardino", "state": "CA", "street": "14 New hampshire Way", "zip": "92402"}, "emails": ["dorthea.visnic@nosql-matters.org", "dorthea@nosql-matters.org"], "phones": [{"numbers": ["909-1143770"], "preferred": false, "type": "home"}, {"numbers": ["909-1659360"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "San Bernardino", "state": "CA", "street": "7809 Fox Ct", "zip": "92402"}, "emails": ["Dorthea@email.com", "Visnic@email.com"], "phones": [{"numbers": ["909-8607765"], "preferred": false, "type": "home"}, {"numbers": ["909-9113178"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0054", "name": {"first": "Rolland", "last": "Rodrigres"}, "contacts": [{"address": {"city": "Cornwall Bridge", "state": "CT", "street": "17 16th Ave", "zip": "06754"}, "emails": ["rolland.rodrigres@nosql-matters.org"], "phones": [{"numbers": ["860-8120488"], "preferred": false, "type": "home"}, {"numbers": ["860-2902140"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Cornwall Bridge", "state": "CT", "street": "563 Willow St", "zip": "06754"}, "emails": ["Rolland@email.com", "Rodrigres@email.com"], "phones": [{"numbers": ["860-3341996"], "preferred": true, "type": "home"}, {"numbers": ["860-4979629"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0055", "name": {"first": "Elias", "last": "Morch"}, "contacts": [{"address": {"city": "Houston", "state": "TX", "street": "13 Deer Ct", "zip": "77248"}, "emails": ["elias.morch@nosql-matters.org", "morch@nosql-matters.org"], "phones": [{"numbers": ["713-2682695"], "preferred": false, "type": "home"}, {"numbers": ["713-9473116"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Houston", "state": "TX", "street": "7198 Pine Ave", "zip": "77248"}, "emails": ["Elias@email.com", "Morch@email.com"], "phones": [{"numbers": ["713-2628966"], "preferred": false, "type": "home"}, {"numbers": ["713-3965086"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0056", "name": {"first": "Ashley", "last": "Byczek"}, "contacts": [{"address": {"city": "Avon by the Sea", "state": "NJ", "street": "18 262nd Ave", "zip": "07717"}, "emails": ["ashley.byczek@nosql-matters.org", "ashley@nosql-matters.org"], "phones": [{"numbers": ["732-7878019", "732-1294501"], "preferred": false, "type": "home"}, {"numbers": ["732-5553585"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Avon by the Sea", "state": "NJ", "street": "4026 Bear Way", "zip": "07717"}, "emails": ["Ashley@email.com", "Byczek@email.com"], "phones": [{"numbers": ["732-9099243"], "preferred": true, "type": "home"}, {"numbers": ["732-6779254"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chess", "boxing", "reading", "snowboarding", "ironing"]} +{"pid": "p-0057", "name": {"first": "Rosella", "last": "Anastos"}, "contacts": [{"address": {"city": "Yantis", "state": "TX", "street": "8 Main St", "zip": "75497"}, "emails": ["rosella.anastos@nosql-matters.org", "anastos@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["903-2651753"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Yantis", "state": "TX", "street": "6801 Elm Ct", "zip": "75497"}, "emails": ["Rosella@email.com", "Anastos@email.com"], "phones": [{"numbers": ["903-6061000"], "preferred": false, "type": "home"}, {"numbers": ["903-5885463"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting"]} +{"pid": "p-0058", "name": {"first": "Nicky", "last": "Hopkins"}, "contacts": [{"address": {"city": "Hankins", "state": "NY", "street": "20 Liberty Loop", "zip": "12741"}, "emails": ["nicky.hopkins@nosql-matters.org", "hopkins@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["914-4065900"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Hankins", "state": "NY", "street": "8249 Eagle Way", "zip": "12741"}, "emails": ["Nicky@email.com", "Hopkins@email.com"], "phones": [{"numbers": ["914-3624991"], "preferred": true, "type": "home"}, {"numbers": ["914-7070720"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["shopping"]} +{"pid": "p-0059", "name": {"first": "Marcy", "last": "Bloem"}, "contacts": [{"address": {"city": "Hanover", "state": "WV", "street": "10 Last chance Pl", "zip": "24839"}, "emails": ["marcy.bloem@nosql-matters.org", "marcy@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["304-4496505"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Hanover", "state": "WV", "street": "6160 Birch Way", "zip": "24839"}, "emails": ["Marcy@email.com", "Bloem@email.com"], "phones": [{"numbers": ["304-2957533"], "preferred": false, "type": "home"}, {"numbers": ["304-2981108"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0060", "name": {"first": "Long", "last": "Mulinix"}, "contacts": [{"address": {"city": "Ashland", "state": "OH", "street": "17 Swann Way", "zip": "44805"}, "emails": ["long.mulinix@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["419-5864638"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Ashland", "state": "OH", "street": "4718 Deer Ave", "zip": "44805"}, "emails": ["Long@email.com", "Mulinix@email.com"], "phones": [{"numbers": ["419-2942582"], "preferred": true, "type": "home"}, {"numbers": ["419-1758355"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["travelling", "climbing"]} +{"pid": "p-0061", "name": {"first": "Marlen", "last": "Heusner"}, "contacts": [{"address": {"city": "Hartford", "state": "VT", "street": "16 Industrial Dr", "zip": "05047"}, "emails": ["marlen.heusner@nosql-matters.org", "heusner@nosql-matters.org"], "phones": [{"numbers": ["802-6968411", "802-9326817"], "preferred": false, "type": "home"}, {"numbers": ["802-1405129"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Hartford", "state": "VT", "street": "3158 Fox Pl", "zip": "05047"}, "emails": ["Marlen@email.com", "Heusner@email.com"], "phones": [{"numbers": ["802-8838666"], "preferred": false, "type": "home"}, {"numbers": ["802-5537072"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting"]} +{"pid": "p-0062", "name": {"first": "Tonie", "last": "Mauro"}, "contacts": [{"address": {"city": "Neelyville", "state": "MO", "street": "4 Biltmore Ct", "zip": "63954"}, "emails": ["tonie.mauro@nosql-matters.org"], "phones": [{"numbers": ["573-7582822"], "preferred": false, "type": "home"}, {"numbers": ["573-6593161"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Neelyville", "state": "MO", "street": "1095 Deer Rd", "zip": "63954"}, "emails": ["Tonie@email.com", "Mauro@email.com"], "phones": [{"numbers": ["573-8376173"], "preferred": true, "type": "home"}, {"numbers": ["573-7681891"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["snowboarding"]} +{"pid": "p-0063", "name": {"first": "Gerard", "last": "Shroyer"}, "contacts": [{"address": {"city": "Clarita", "state": "OK", "street": "14 18th St", "zip": "74535"}, "emails": ["gerard.shroyer@nosql-matters.org", "shroyer@nosql-matters.org"], "phones": [{"numbers": ["580-9002737", "580-2706148"], "preferred": false, "type": "home"}, {"numbers": ["580-1905949"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Clarita", "state": "OK", "street": "1026 Pine Dr", "zip": "74535"}, "emails": ["Gerard@email.com", "Shroyer@email.com"], "phones": [{"numbers": ["580-7365585"], "preferred": false, "type": "home"}, {"numbers": ["580-8734574"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting"]} +{"pid": "p-0064", "name": {"first": "Rosina", "last": "Leinen"}, "contacts": [{"address": {"city": "La Quinta", "state": "CA", "street": "3 Palmer Ln", "zip": "92253"}, "emails": ["rosina.leinen@nosql-matters.org", "rosina@nosql-matters.org"], "phones": [{"numbers": ["760-2596242"], "preferred": false, "type": "home"}, {"numbers": ["760-3518333"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "La Quinta", "state": "CA", "street": "1896 Maple Loop", "zip": "92253"}, "emails": ["Rosina@email.com", "Leinen@email.com"], "phones": [{"numbers": ["760-9660550"], "preferred": true, "type": "home"}, {"numbers": ["760-7004175"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting", "reading"]} +{"pid": "p-0065", "name": {"first": "Abe", "last": "Troiani"}, "contacts": [{"address": {"city": "Hudson", "state": "IL", "street": "5 Beekman St", "zip": "61748"}, "emails": ["abe.troiani@nosql-matters.org", "troiani@nosql-matters.org", "abe@nosql-matters.org"], "phones": [{"numbers": ["309-4888909", "309-3260513"], "preferred": false, "type": "home"}, {"numbers": ["309-7643768"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Hudson", "state": "IL", "street": "826 Eagle Cir", "zip": "61748"}, "emails": ["Abe@email.com", "Troiani@email.com"], "phones": [{"numbers": ["309-8123217"], "preferred": false, "type": "home"}, {"numbers": ["309-2831187"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["shopping"]} +{"pid": "p-0066", "name": {"first": "Carri", "last": "Mickler"}, "contacts": [{"address": {"city": "Reno", "state": "NV", "street": "13 290th St", "zip": "89570"}, "emails": ["carri.mickler@nosql-matters.org"], "phones": [{"numbers": ["775-3352309"], "preferred": false, "type": "home"}, {"numbers": ["775-9517270"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Reno", "state": "NV", "street": "569 Oak Ave", "zip": "89570"}, "emails": ["Carri@email.com", "Mickler@email.com"], "phones": [{"numbers": ["775-7913134"], "preferred": true, "type": "home"}, {"numbers": ["775-9131482"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0067", "name": {"first": "Julissa", "last": "Filosa"}, "contacts": [{"address": {"city": "Creston", "state": "IA", "street": "8 Beechwood Aly", "zip": "50801"}, "emails": ["julissa.filosa@nosql-matters.org"], "phones": [{"numbers": ["515-0629512", "515-7012469"], "preferred": false, "type": "home"}, {"numbers": ["515-7517235"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Creston", "state": "IA", "street": "3672 Pine Loop", "zip": "50801"}, "emails": ["Julissa@email.com", "Filosa@email.com"], "phones": [{"numbers": ["515-5589751"], "preferred": false, "type": "home"}, {"numbers": ["515-4531302"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["ironing"]} +{"pid": "p-0068", "name": {"first": "Dusty", "last": "Sistrunk"}, "contacts": [{"address": {"city": "Saint Anthony", "state": "IA", "street": "18 Santa fe Cir", "zip": "50239"}, "emails": ["dusty.sistrunk@nosql-matters.org"], "phones": [{"numbers": ["515-0638915", "515-6682501"], "preferred": false, "type": "home"}, {"numbers": ["515-3141779"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Saint Anthony", "state": "IA", "street": "6391 Eagle Ct", "zip": "50239"}, "emails": ["Dusty@email.com", "Sistrunk@email.com"], "phones": [{"numbers": ["515-1692053"], "preferred": true, "type": "home"}, {"numbers": ["515-5394014"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["travelling"]} +{"pid": "p-0069", "name": {"first": "Brandon", "last": "Ulibarri"}, "contacts": [{"address": {"city": "Odenton", "state": "MD", "street": "9 Beechwood Aly", "zip": "21113"}, "emails": ["brandon.ulibarri@nosql-matters.org", "brandon@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["410-8239484"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Odenton", "state": "MD", "street": "131 Maple Ave", "zip": "21113"}, "emails": ["Brandon@email.com", "Ulibarri@email.com"], "phones": [{"numbers": ["410-7365666"], "preferred": false, "type": "home"}, {"numbers": ["410-2688666"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0070", "name": {"first": "Angelina", "last": "Mcadoo"}, "contacts": [{"address": {"city": "Kingman", "state": "IN", "street": "20 Ottawa Pl", "zip": "47952"}, "emails": ["angelina.mcadoo@nosql-matters.org"], "phones": [{"numbers": ["765-1949397", "765-4847931"], "preferred": false, "type": "home"}, {"numbers": ["765-2231053"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Kingman", "state": "IN", "street": "5803 Bear Ct", "zip": "47952"}, "emails": ["Angelina@email.com", "Mcadoo@email.com"], "phones": [{"numbers": ["765-3256232"], "preferred": true, "type": "home"}, {"numbers": ["765-8129695"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0071", "name": {"first": "Julio", "last": "Schwanebeck"}, "contacts": [{"address": {"city": "Henderson", "state": "NV", "street": "9 Country club Aly", "zip": "89012"}, "emails": ["julio.schwanebeck@nosql-matters.org", "schwanebeck@nosql-matters.org"], "phones": [{"numbers": ["702-0808220"], "preferred": false, "type": "home"}, {"numbers": ["702-5113648"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Henderson", "state": "NV", "street": "8987 Elm Ln", "zip": "89012"}, "emails": ["Julio@email.com", "Schwanebeck@email.com"], "phones": [{"numbers": ["702-7963128"], "preferred": false, "type": "home"}, {"numbers": ["702-8293004"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["checkers"]} +{"pid": "p-0072", "name": {"first": "Mirian", "last": "Guzzardo"}, "contacts": [{"address": {"city": "Santa Clarita", "state": "CA", "street": "10 290th Ave", "zip": "91383"}, "emails": ["mirian.guzzardo@nosql-matters.org", "mirian@nosql-matters.org"], "phones": [{"numbers": ["661-4619498"], "preferred": false, "type": "home"}, {"numbers": ["661-3851332"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Santa Clarita", "state": "CA", "street": "7570 Eagle Rd", "zip": "91383"}, "emails": ["Mirian@email.com", "Guzzardo@email.com"], "phones": [{"numbers": ["661-6114117"], "preferred": true, "type": "home"}, {"numbers": ["661-5194552"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["swimming"]} +{"pid": "p-0073", "name": {"first": "Sharri", "last": "Pletsch"}, "contacts": [{"address": {"city": "San Francisco", "state": "CA", "street": "13 Fraser Ln", "zip": "94153"}, "emails": ["sharri.pletsch@nosql-matters.org", "sharri@nosql-matters.org"], "phones": [{"numbers": ["415-0144394"], "preferred": false, "type": "home"}, {"numbers": ["415-1356909"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "San Francisco", "state": "CA", "street": "1120 Pine Ave", "zip": "94153"}, "emails": ["Sharri@email.com", "Pletsch@email.com"], "phones": [{"numbers": ["415-2580482"], "preferred": false, "type": "home"}, {"numbers": ["415-6817947"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0074", "name": {"first": "Brenton", "last": "Lynskey"}, "contacts": [{"address": {"city": "Augusta", "state": "GA", "street": "20 Kearney Way", "zip": "30906"}, "emails": ["brenton.lynskey@nosql-matters.org", "lynskey@nosql-matters.org", "brenton@nosql-matters.org"], "phones": [{"numbers": ["706-9360969"], "preferred": false, "type": "home"}, {"numbers": ["706-7214674"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Augusta", "state": "GA", "street": "3182 Fox Cir", "zip": "30906"}, "emails": ["Brenton@email.com", "Lynskey@email.com"], "phones": [{"numbers": ["706-1098786"], "preferred": true, "type": "home"}, {"numbers": ["706-5765256"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0075", "name": {"first": "Lavern", "last": "Hamlet"}, "contacts": [{"address": {"city": "Silver City", "state": "NV", "street": "12 Calvert Blvd", "zip": "89428"}, "emails": ["lavern.hamlet@nosql-matters.org"], "phones": [{"numbers": ["775-6709905", "775-9088264"], "preferred": false, "type": "home"}, {"numbers": ["775-1665836"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Silver City", "state": "NV", "street": "7718 Birch Cir", "zip": "89428"}, "emails": ["Lavern@email.com", "Hamlet@email.com"], "phones": [{"numbers": ["775-5419798"], "preferred": false, "type": "home"}, {"numbers": ["775-1482134"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["running"]} +{"pid": "p-0076", "name": {"first": "Monte", "last": "Mihlfeld"}, "contacts": [{"address": {"city": "Monticello", "state": "MN", "street": "21 5th St", "zip": "55587"}, "emails": ["monte.mihlfeld@nosql-matters.org"], "phones": [{"numbers": ["612-6706220"], "preferred": false, "type": "home"}, {"numbers": ["612-7372636"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Monticello", "state": "MN", "street": "2249 Eagle Way", "zip": "55587"}, "emails": ["Monte@email.com", "Mihlfeld@email.com"], "phones": [{"numbers": ["612-1968599"], "preferred": true, "type": "home"}, {"numbers": ["612-5353412"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["swimming"]} +{"pid": "p-0077", "name": {"first": "Bradford", "last": "Imperial"}, "contacts": [{"address": {"city": "Saint Louis", "state": "MO", "street": "3 Belmont Ct", "zip": "63198"}, "emails": ["bradford.imperial@nosql-matters.org", "bradford@nosql-matters.org"], "phones": [{"numbers": ["314-9336935"], "preferred": false, "type": "home"}, {"numbers": ["314-6073952"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Saint Louis", "state": "MO", "street": "8631 Pine Ct", "zip": "63198"}, "emails": ["Bradford@email.com", "Imperial@email.com"], "phones": [{"numbers": ["314-9252274"], "preferred": false, "type": "home"}, {"numbers": ["314-5211657"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["ironing"]} +{"pid": "p-0078", "name": {"first": "Eryn", "last": "Daquilante"}, "contacts": [{"address": {"city": "Wellesley Hills", "state": "MA", "street": "11 20th St", "zip": "02481"}, "emails": ["eryn.daquilante@nosql-matters.org", "daquilante@nosql-matters.org"], "phones": [{"numbers": ["781-6373282"], "preferred": false, "type": "home"}, {"numbers": ["781-3944303"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Wellesley Hills", "state": "MA", "street": "6974 Oak Dr", "zip": "02481"}, "emails": ["Eryn@email.com", "Daquilante@email.com"], "phones": [{"numbers": ["781-8321396"], "preferred": true, "type": "home"}, {"numbers": ["781-2809399"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0079", "name": {"first": "Roslyn", "last": "Aerni"}, "contacts": [{"address": {"city": "Peaks Island", "state": "ME", "street": "16 Champlain St", "zip": "04108"}, "emails": ["roslyn.aerni@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["207-4178418"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Peaks Island", "state": "ME", "street": "5830 Oak Dr", "zip": "04108"}, "emails": ["Roslyn@email.com", "Aerni@email.com"], "phones": [{"numbers": ["207-1905917"], "preferred": false, "type": "home"}, {"numbers": ["207-9445068"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["shopping", "reading"]} +{"pid": "p-0080", "name": {"first": "Chas", "last": "Cefalu"}, "contacts": [{"address": {"city": "Brodhead", "state": "KY", "street": "4 Ontario Rd", "zip": "40409"}, "emails": ["chas.cefalu@nosql-matters.org"], "phones": [{"numbers": ["606-5073368"], "preferred": false, "type": "home"}, {"numbers": ["606-7752094"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Brodhead", "state": "KY", "street": "5822 Maple Cir", "zip": "40409"}, "emails": ["Chas@email.com", "Cefalu@email.com"], "phones": [{"numbers": ["606-6191816"], "preferred": true, "type": "home"}, {"numbers": ["606-6866552"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0081", "name": {"first": "Berneice", "last": "Migliori"}, "contacts": [{"address": {"city": "Orfordville", "state": "WI", "street": "9 4th St", "zip": "53576"}, "emails": ["berneice.migliori@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["608-3390083"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Orfordville", "state": "WI", "street": "1872 Eagle St", "zip": "53576"}, "emails": ["Berneice@email.com", "Migliori@email.com"], "phones": [{"numbers": ["608-9758010"], "preferred": false, "type": "home"}, {"numbers": ["608-2279722"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["swimming"]} +{"pid": "p-0082", "name": {"first": "Patricia", "last": "Shoji"}, "contacts": [{"address": {"city": "Tippo", "state": "MS", "street": "15 Kansas Dr", "zip": "38962"}, "emails": ["patricia.shoji@nosql-matters.org"], "phones": [{"numbers": ["662-2743361", "662-7499550"], "preferred": false, "type": "home"}, {"numbers": ["662-5478932"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Tippo", "state": "MS", "street": "4524 Fox Rd", "zip": "38962"}, "emails": ["Patricia@email.com", "Shoji@email.com"], "phones": [{"numbers": ["662-8873305"], "preferred": true, "type": "home"}, {"numbers": ["662-2802043"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0083", "name": {"first": "Kenton", "last": "Bagent"}, "contacts": [{"address": {"city": "Omaha", "state": "NE", "street": "22 Wallace Ln", "zip": "68120"}, "emails": ["kenton.bagent@nosql-matters.org", "bagent@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["402-9778294"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Omaha", "state": "NE", "street": "3513 Oak Ave", "zip": "68120"}, "emails": ["Kenton@email.com", "Bagent@email.com"], "phones": [{"numbers": ["402-4716492"], "preferred": false, "type": "home"}, {"numbers": ["402-3196381"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["running", "snowboarding", "skiing"]} +{"pid": "p-0084", "name": {"first": "Elyse", "last": "Leis"}, "contacts": [{"address": {"city": "Solsberry", "state": "IN", "street": "18 Fuller Ln", "zip": "47459"}, "emails": ["elyse.leis@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["812-2168367"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Solsberry", "state": "IN", "street": "373 Hickory Rd", "zip": "47459"}, "emails": ["Elyse@email.com", "Leis@email.com"], "phones": [{"numbers": ["812-1419542"], "preferred": true, "type": "home"}, {"numbers": ["812-3741153"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["running"]} +{"pid": "p-0085", "name": {"first": "Maryjo", "last": "Tariq"}, "contacts": [{"address": {"city": "Broad Run", "state": "VA", "street": "6 Morningside Pl", "zip": "20137"}, "emails": ["maryjo.tariq@nosql-matters.org"], "phones": [{"numbers": ["540-7768397"], "preferred": false, "type": "home"}, {"numbers": ["540-1695368"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Broad Run", "state": "VA", "street": "6670 Cedar Loop", "zip": "20137"}, "emails": ["Maryjo@email.com", "Tariq@email.com"], "phones": [{"numbers": ["540-9133623"], "preferred": false, "type": "home"}, {"numbers": ["540-2636703"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["swimming", "snowboarding", "skiing", "ironing"]} +{"pid": "p-0086", "name": {"first": "Margaretta", "last": "Ogwynn"}, "contacts": [{"address": {"city": "Faulkton", "state": "SD", "street": "13 Palmer Pl", "zip": "57438"}, "emails": ["margaretta.ogwynn@nosql-matters.org", "ogwynn@nosql-matters.org"], "phones": [{"numbers": ["605-3511227"], "preferred": false, "type": "home"}, {"numbers": ["605-9222296"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Faulkton", "state": "SD", "street": "8802 Pine Pl", "zip": "57438"}, "emails": ["Margaretta@email.com", "Ogwynn@email.com"], "phones": [{"numbers": ["605-6145635"], "preferred": true, "type": "home"}, {"numbers": ["605-8063640"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0087", "name": {"first": "Glory", "last": "Mollenkopf"}, "contacts": [{"address": {"city": "Ridgewood", "state": "NY", "street": "6 1st Ave", "zip": "11386"}, "emails": ["glory.mollenkopf@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["718-9211053"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Ridgewood", "state": "NY", "street": "2025 Eagle Ct", "zip": "11386"}, "emails": ["Glory@email.com", "Mollenkopf@email.com"], "phones": [{"numbers": ["718-6115058"], "preferred": false, "type": "home"}, {"numbers": ["718-8964275"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0088", "name": {"first": "Janette", "last": "Nicholes"}, "contacts": [{"address": {"city": "New York", "state": "NY", "street": "7 Rawlins Pl", "zip": "10011"}, "emails": ["janette.nicholes@nosql-matters.org"], "phones": [{"numbers": ["212-6481067"], "preferred": false, "type": "home"}, {"numbers": ["212-1053805"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "New York", "state": "NY", "street": "5084 Birch Loop", "zip": "10011"}, "emails": ["Janette@email.com", "Nicholes@email.com"], "phones": [{"numbers": ["212-4485205"], "preferred": true, "type": "home"}, {"numbers": ["212-4276605"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["checkers"]} +{"pid": "p-0089", "name": {"first": "Brandon", "last": "Laidlaw"}, "contacts": [{"address": {"city": "Birmingham", "state": "AL", "street": "21 Cliffbourne Aly", "zip": "35240"}, "emails": ["brandon.laidlaw@nosql-matters.org", "laidlaw@nosql-matters.org"], "phones": [{"numbers": ["205-1673400", "205-0536483"], "preferred": false, "type": "home"}, {"numbers": ["205-7100843"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Birmingham", "state": "AL", "street": "8368 Maple Cir", "zip": "35240"}, "emails": ["Brandon@email.com", "Laidlaw@email.com"], "phones": [{"numbers": ["205-8544438"], "preferred": false, "type": "home"}, {"numbers": ["205-4811936"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0090", "name": {"first": "Arturo", "last": "Kinatyan"}, "contacts": [{"address": {"city": "Rock Point", "state": "AZ", "street": "16 Last chance Run", "zip": "86545"}, "emails": ["arturo.kinatyan@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["520-2800014"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Rock Point", "state": "AZ", "street": "4302 Pine Ct", "zip": "86545"}, "emails": ["Arturo@email.com", "Kinatyan@email.com"], "phones": [{"numbers": ["520-9311152"], "preferred": true, "type": "home"}, {"numbers": ["520-8881070"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["chatting"]} +{"pid": "p-0091", "name": {"first": "Eddie", "last": "Colangelo"}, "contacts": [{"address": {"city": "Schenectady", "state": "NY", "street": "16 Center Ln", "zip": "12308"}, "emails": ["eddie.colangelo@nosql-matters.org", "colangelo@nosql-matters.org", "eddie@nosql-matters.org"], "phones": [{"numbers": ["518-1453490"], "preferred": false, "type": "home"}, {"numbers": ["518-4608724"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Schenectady", "state": "NY", "street": "5038 Eagle St", "zip": "12308"}, "emails": ["Eddie@email.com", "Colangelo@email.com"], "phones": [{"numbers": ["518-1950063"], "preferred": false, "type": "home"}, {"numbers": ["518-5870179"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0092", "name": {"first": "Margrett", "last": "Heartz"}, "contacts": [{"address": {"city": "Baraboo", "state": "WI", "street": "18 Mintwood Way", "zip": "53913"}, "emails": ["margrett.heartz@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["608-9946461"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Baraboo", "state": "WI", "street": "8652 Fox Pl", "zip": "53913"}, "emails": ["Margrett@email.com", "Heartz@email.com"], "phones": [{"numbers": ["608-8619124"], "preferred": true, "type": "home"}, {"numbers": ["608-9788830"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0093", "name": {"first": "Verna", "last": "Wigfield"}, "contacts": [{"address": {"city": "Belmont", "state": "LA", "street": "2 Main Blvd", "zip": "71406"}, "emails": ["verna.wigfield@nosql-matters.org", "wigfield@nosql-matters.org"], "phones": [{"numbers": ["318-5784855", "318-8625864"], "preferred": false, "type": "home"}, {"numbers": ["318-4351938"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Belmont", "state": "LA", "street": "4397 Birch Ct", "zip": "71406"}, "emails": ["Verna@email.com", "Wigfield@email.com"], "phones": [{"numbers": ["318-5731503"], "preferred": false, "type": "home"}, {"numbers": ["318-9241107"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0094", "name": {"first": "Jarrod", "last": "Litwiler"}, "contacts": [{"address": {"city": "Brockway", "state": "PA", "street": "17 Walnut Dr", "zip": "15824"}, "emails": ["jarrod.litwiler@nosql-matters.org", "jarrod@nosql-matters.org"], "phones": [{"numbers": ["814-7395320", "814-1794652"], "preferred": false, "type": "home"}, {"numbers": ["814-6097548"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Brockway", "state": "PA", "street": "5954 Cedar Cir", "zip": "15824"}, "emails": ["Jarrod@email.com", "Litwiler@email.com"], "phones": [{"numbers": ["814-4223653"], "preferred": true, "type": "home"}, {"numbers": ["814-9491935"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["running"]} +{"pid": "p-0095", "name": {"first": "Kelley", "last": "Wala"}, "contacts": [{"address": {"city": "Tulsa", "state": "OK", "street": "21 Neosho Ln", "zip": "74129"}, "emails": ["kelley.wala@nosql-matters.org", "kelley@nosql-matters.org"], "phones": [{"numbers": ["918-1081617", "918-4968318"], "preferred": false, "type": "home"}, {"numbers": ["918-1620751"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Tulsa", "state": "OK", "street": "8268 Elm Cir", "zip": "74129"}, "emails": ["Kelley@email.com", "Wala@email.com"], "phones": [{"numbers": ["918-3268600"], "preferred": false, "type": "home"}, {"numbers": ["918-7031002"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0096", "name": {"first": "Shaunta", "last": "Geringer"}, "contacts": [{"address": {"city": "Lehman", "state": "PA", "street": "15 Spring Run", "zip": "18627"}, "emails": ["shaunta.geringer@nosql-matters.org", "shaunta@nosql-matters.org"], "phones": [{"numbers": ["570-4850697"], "preferred": false, "type": "home"}, {"numbers": ["570-4314468"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Lehman", "state": "PA", "street": "2507 Elm Blvd", "zip": "18627"}, "emails": ["Shaunta@email.com", "Geringer@email.com"], "phones": [{"numbers": ["570-6503681"], "preferred": true, "type": "home"}, {"numbers": ["570-8475727"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["snowboarding", "travelling"]} +{"pid": "p-0097", "name": {"first": "Ira", "last": "Rechel"}, "contacts": [{"address": {"city": "Lawrenceville", "state": "VA", "street": "9 Riggs Pl", "zip": "23868"}, "emails": ["ira.rechel@nosql-matters.org"], "phones": [{"numbers": ["804-3225311", "804-7803572"], "preferred": false, "type": "home"}, {"numbers": ["804-3483070"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Lawrenceville", "state": "VA", "street": "6335 Eagle Ct", "zip": "23868"}, "emails": ["Ira@email.com", "Rechel@email.com"], "phones": [{"numbers": ["804-1116981"], "preferred": false, "type": "home"}, {"numbers": ["804-6594632"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["running", "shopping", "skiing"]} +{"pid": "p-0098", "name": {"first": "Concetta", "last": "Rotondo"}, "contacts": [{"address": {"city": "Vichy", "state": "MO", "street": "9 19th Ave", "zip": "65580"}, "emails": ["concetta.rotondo@nosql-matters.org"], "phones": [{"numbers": [], "preferred": false, "type": "home"}, {"numbers": ["573-7427716"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Vichy", "state": "MO", "street": "7423 Oak Ave", "zip": "65580"}, "emails": ["Concetta@email.com", "Rotondo@email.com"], "phones": [{"numbers": ["573-1182504"], "preferred": true, "type": "home"}, {"numbers": ["573-1194496"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": []} +{"pid": "p-0099", "name": {"first": "Georgetta", "last": "Kolding"}, "contacts": [{"address": {"city": "Marshallville", "state": "OH", "street": "22 290th St", "zip": "44645"}, "emails": ["georgetta.kolding@nosql-matters.org", "kolding@nosql-matters.org"], "phones": [{"numbers": ["330-6429454"], "preferred": false, "type": "home"}, {"numbers": ["330-1989984"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Marshallville", "state": "OH", "street": "2812 Willow Way", "zip": "44645"}, "emails": ["Georgetta@email.com", "Kolding@email.com"], "phones": [{"numbers": ["330-4925907"], "preferred": false, "type": "home"}, {"numbers": ["330-7514225"], "preferred": true, "type": "mobile"}], "type": "secondary"}], "likes": ["running", "boxing", "reading"]} +{"pid": "p-0100", "name": {"first": "Rickie", "last": "Bakaler"}, "contacts": [{"address": {"city": "Wallace", "state": "KS", "street": "20 14th St", "zip": "67761"}, "emails": ["rickie.bakaler@nosql-matters.org"], "phones": [{"numbers": ["785-8179421", "785-2447694"], "preferred": false, "type": "home"}, {"numbers": ["785-4731991"], "preferred": true, "type": "mobile"}], "type": "primary"}, {"address": {"city": "Wallace", "state": "KS", "street": "7867 Hickory Blvd", "zip": "67761"}, "emails": ["Rickie@email.com", "Bakaler@email.com"], "phones": [{"numbers": ["785-8958174"], "preferred": true, "type": "home"}, {"numbers": ["785-6669457"], "preferred": false, "type": "mobile"}], "type": "secondary"}], "likes": ["biking"]} diff --git a/Objective-C/Tests/UnnestArrayIndexTest.m b/Objective-C/Tests/UnnestArrayIndexTest.m new file mode 100644 index 000000000..b7f6048cf --- /dev/null +++ b/Objective-C/Tests/UnnestArrayIndexTest.m @@ -0,0 +1,107 @@ +// +// UnnestArrayIndexTest.m +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc. All rights reserved. +// +// Licensed under the Couchbase License Agreement (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://info.couchbase.com/rs/302-GJY-034/images/2017-10-30_License_Agreement.pdf +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLTestCase.h" +#import "CBLArrayIndexConfiguration.h" +#import "CBLCollection+Internal.h" + +@interface UnnestArrayIndexTest : CBLTestCase + +@end + +@implementation UnnestArrayIndexTest + +/** + Test Spec v1.0.1: + https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0004-Unnest-Array-Index.md + */ + +/** + 1. TestArrayIndexConfigInvalidExpressions + Description + Test that creating an ArrayIndexConfiguration with invalid expressions which are an empty expressions or contain null. + Steps + 1. Create a ArrayIndexConfiguration object. + - path: "contacts" + - expressions: [] + 2. Check that an invalid arument exception is thrown. + */ + +- (void) testArrayIndexConfigInvalidExpressions { + [self expectException: NSInvalidArgumentException in:^{ + (void) [[CBLArrayIndexConfiguration alloc] initWithPath:@"contacts" expressions: @[]]; + }]; + + [self expectException: NSInvalidArgumentException in:^{ + (void) [[CBLArrayIndexConfiguration alloc] initWithPath:@"contacts" expressions: @[@""]]; + }]; +} + +/** + 2. TestCreateArrayIndexWithPath + Description + Test that creating an array index with only path works as expected. + Steps + 1. Load profiles.json into the collection named "_default.profiles". + 2. Create a ArrayIndexConfiguration object. + - path: "contacts" + - expressions: null + 3. Create an array index named "contacts" in the profiles collection. + 4. Get index names from the profiles collection and check that the index named "contacts" exists. + 5. Get info of the index named "contacts" using an internal API and check that the index has path and expressions as configured. + */ + +- (void) testCreateArrayIndexWithPath { + NSError* err; + CBLCollection* profiles = [self.db createCollectionWithName: @"profiles" scope: nil error: &err]; + [self loadJSONResource: @"profiles_100" toCollection: profiles]; + + CBLArrayIndexConfiguration* config = [[CBLArrayIndexConfiguration alloc] initWithPath: @"contacts" expressions: nil]; + [profiles createIndexWithName: @"contacts" config: config error: &err]; + NSArray* indexes = [profiles indexesInfo: nil]; + AssertEqual(indexes.count, 1u); + AssertEqualObjects(indexes[0][@"expr"], @""); +} + +/** + 3. TestCreateArrayIndexWithPathAndExpressions + Description + Test that creating an array index with path and expressions works as expected. + Steps + 1. Load profiles.json into the collection named "_default.profiles". + 2. Create a ArrayIndexConfiguration object. + - path: "contacts" + - expressions: ["address.city", "address.state"] + 3. Create an array index named "contacts" in the profiles collection. + 4. Get index names from the profiles collection and check that the index named "contacts" exists. + 5. Get info of the index named "contacts" using an internal API and check that the index has path and expressions as configured. + */ +- (void) testCreateArrayIndexWithPathAndExpressions { + NSError* err; + CBLCollection* profiles = [self.db createCollectionWithName: @"profiles" scope: nil error: &err]; + [self loadJSONResource: @"profiles_100" toCollection: profiles]; + + CBLArrayIndexConfiguration* config = [[CBLArrayIndexConfiguration alloc] initWithPath: @"contacts" expressions: @[@"address.city", @"address.state"]]; + [profiles createIndexWithName: @"contacts" config: config error: &err]; + + NSArray* indexes = [profiles indexesInfo: nil]; + AssertEqual(indexes.count, 1u); + AssertEqualObjects(indexes[0][@"expr"], @"address.city,address.state"); +} + +@end diff --git a/Objective-C/Tests/VectorSearchTest+Lazy.m b/Objective-C/Tests/VectorSearchTest+Lazy.m index edf5b6b22..1daec825c 100644 --- a/Objective-C/Tests/VectorSearchTest+Lazy.m +++ b/Objective-C/Tests/VectorSearchTest+Lazy.m @@ -1231,7 +1231,7 @@ - (void) testIndexUpdaterIndexOutOfBounds { } /** - * 26. TestIndexUpdaterCallFinishTwice + * 26. TestIndexUpdaterCallFinishTwice + 27. TestIndexUpdaterUseAfterFinished * * Description * Test that when calling IndexUpdater's finish() after it was finished, @@ -1250,10 +1250,10 @@ - (void) testIndexUpdaterIndexOutOfBounds { * - Convert the vector result which is an array object to a platform's float array. * - Call setVector() with the platform's float array at the index. * 4. Call finish() and check that the finish() is successfully called. - * 5. Call finish() again and check that a CouchbaseLiteException with the code NotOpen is thrown. + * 5. Call finish() again and check that it throws exception. * 6. Count, getValue, setVector, skipVector throw exception. */ -- (void) testIndexUpdaterCallFinishTwice { +- (void) testIndexUpdaterUseAfterFinished { [self createWordsIndexWithConfig: LAZY_VECTOR_INDEX_CONFIG(@"word", 300, 8)]; NSError* error; @@ -1267,6 +1267,11 @@ - (void) testIndexUpdaterCallFinishTwice { Assert([updater setVector: vector atIndex: 0 error: &error]); Assert([updater finishWithError: &error]); + [self expectException: @"NSInternalInconsistencyException" in:^{ + NSError* outError; + [updater finishWithError: &outError]; + }]; + [self expectException: @"NSInternalInconsistencyException" in:^{ [updater count]; }]; @@ -1283,11 +1288,6 @@ - (void) testIndexUpdaterCallFinishTwice { [self expectException: @"NSInternalInconsistencyException" in:^{ [updater skipVectorAtIndex: 0]; }]; - - [self expectException: @"NSInternalInconsistencyException" in:^{ - NSError* outError; - [updater finishWithError: &outError]; - }]; } @end diff --git a/Swift/Collection.swift b/Swift/Collection.swift index 18791674c..50d61b2f0 100644 --- a/Swift/Collection.swift +++ b/Swift/Collection.swift @@ -352,6 +352,10 @@ public final class Collection : CollectionChangeObservable, Indexable, Equatable // MARK: Internal + func indexesInfo() throws -> [[String: Any]]? { + return try impl.indexesInfo() as? [[String: Any]] + } + init(_ impl: CBLCollection, db: Database) { self.impl = impl self.database = db diff --git a/Swift/IndexConfiguration.swift b/Swift/IndexConfiguration.swift index d77f5b847..dfd928060 100644 --- a/Swift/IndexConfiguration.swift +++ b/Swift/IndexConfiguration.swift @@ -88,7 +88,14 @@ public struct ArrayIndexConfiguration: IndexConfiguration, IndexConfigConvertabl /// represents an expression defining the values within the array to be indexed. /// If the array specified by the path contains scalar values, this parameter can be null. /// - Returns The ArrayIndexConfiguration object. - public init(path: String, _ expressions: [String]?) { + public init(path: String, _ expressions: [String]? = nil) { + if let expressions = expressions { + if expressions.isEmpty || (expressions.count == 1 && expressions[0].isEmpty) { + NSException(name: .invalidArgumentException, + reason: "Empty expressions is not allowed, use nil instead", + userInfo: nil).raise() + } + } self.path = path self.expressions = expressions } diff --git a/Swift/Tests/ArrayTest.swift b/Swift/Tests/ArrayTest.swift index d408e01ba..f29427c61 100644 --- a/Swift/Tests/ArrayTest.swift +++ b/Swift/Tests/ArrayTest.swift @@ -235,7 +235,7 @@ class ArrayTest: CBLTestCase { func testUnsavedMutableArrayToJSON() throws { let json = "[{\"unsaved\":\"mutableDoc\"}]" var mArray = try MutableArrayObject(json: json) - expectExcepion(exception: .internalInconsistencyException) { + expectException(exception: .internalInconsistencyException) { let _ = mArray.toJSON() } @@ -244,7 +244,7 @@ class ArrayTest: CBLTestCase { try saveDocument(mDoc) mArray = mDoc.array(forKey: "array")! - expectExcepion(exception: .internalInconsistencyException) { + expectException(exception: .internalInconsistencyException) { let _ = mArray.toJSON() } } @@ -278,7 +278,7 @@ class ArrayTest: CBLTestCase { var blob = mDoc.blob(forKey: "origin") // before save it should throw the exception - expectExcepion(exception: .internalInconsistencyException) { + expectException(exception: .internalInconsistencyException) { print("\(blob!.content?.count ?? 0)") } try self.db.saveDocument(mDoc) diff --git a/Swift/Tests/CBLTestCase.swift b/Swift/Tests/CBLTestCase.swift index f1bfb5696..5aa06eace 100644 --- a/Swift/Tests/CBLTestCase.swift +++ b/Swift/Tests/CBLTestCase.swift @@ -274,7 +274,7 @@ class CBLTestCase: XCTestCase { } } - func expectExcepion(exception: NSExceptionName, block: @escaping () -> Void) { + func expectException(exception: NSExceptionName, block: @escaping () -> Void) { var exceptionThrown = false do { try CBLTestHelper.catchException { diff --git a/Swift/Tests/DictionaryTest.swift b/Swift/Tests/DictionaryTest.swift index 9a2c2ab23..f16d9d82d 100644 --- a/Swift/Tests/DictionaryTest.swift +++ b/Swift/Tests/DictionaryTest.swift @@ -268,7 +268,7 @@ class DictionaryTest: CBLTestCase { mDoc = doc!.toMutable() mDict = dict!.toMutable() mDict.setValue("newValueAppended", forKey: "newKeyAppended") - expectExcepion(exception: .internalInconsistencyException) { + expectException(exception: .internalInconsistencyException) { let _ = mDict.toJSON() } mDoc.setValue(mDict, forKey: "dict") @@ -282,7 +282,7 @@ class DictionaryTest: CBLTestCase { func testUnsavedMutableDictionaryToJSON() throws { let mDict = try MutableDictionaryObject(json: "{\"unsaved\":\"dict\"}") - expectExcepion(exception: .internalInconsistencyException) { + expectException(exception: .internalInconsistencyException) { let _ = mDict.toJSON() } } diff --git a/Swift/Tests/DocumentTest.swift b/Swift/Tests/DocumentTest.swift index cc361e99c..d0a757a88 100644 --- a/Swift/Tests/DocumentTest.swift +++ b/Swift/Tests/DocumentTest.swift @@ -1653,7 +1653,7 @@ class DocumentTest: CBLTestCase { func testUnsavedMutableDocumentToJSON() throws { let mDoc = try MutableDocument(id: "doc", json: "{\"unsaved\":\"doc\"}") - expectExcepion(exception: .internalInconsistencyException) { + expectException(exception: .internalInconsistencyException) { let _ = mDoc.toJSON() } } @@ -1732,7 +1732,7 @@ class DocumentTest: CBLTestCase { func testUnsavedBlob() throws { let content = kTestBlob.data(using: .utf8)! let blob = Blob(contentType: "text/plain", data: content) - expectExcepion(exception: .internalInconsistencyException) { + expectException(exception: .internalInconsistencyException) { let _ = blob.toJSON() } } @@ -1751,7 +1751,7 @@ class DocumentTest: CBLTestCase { try self.db.saveBlob(blob: blob) var b: Blob? - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { b = try! self.db.getBlob(properties: [Blob.typeProperty:"bl0b", Blob.blobDigestProperty: blob.digest! as String, Blob.blobContentType: "text/plain", @@ -1759,7 +1759,7 @@ class DocumentTest: CBLTestCase { } XCTAssertNil(b) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { b = try! self.db.getBlob(properties: ["type":Blob.blobType, Blob.blobDigestProperty: blob.digest! as String, Blob.blobContentType: "text/plain", @@ -1767,7 +1767,7 @@ class DocumentTest: CBLTestCase { } XCTAssertNil(b) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { b = try! self.db.getBlob(properties: [Blob.typeProperty:Blob.blobType, Blob.blobDigestProperty: blob.digest! as String, Blob.blobContentType: 1234, @@ -1775,7 +1775,7 @@ class DocumentTest: CBLTestCase { } XCTAssertNil(b) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { b = try! self.db.getBlob(properties: [Blob.typeProperty:Blob.blobType, Blob.blobDigestProperty: blob.digest! as String, Blob.blobContentType: "text/plain", @@ -1783,7 +1783,7 @@ class DocumentTest: CBLTestCase { } XCTAssertNil(b) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { b = try! self.db.getBlob(properties: [Blob.typeProperty:Blob.blobType, Blob.blobDigestProperty: 12, Blob.blobContentType: "text/plain", diff --git a/Swift/Tests/ReplicatorTest+Collection.swift b/Swift/Tests/ReplicatorTest+Collection.swift index 93ee82470..ad8bc9d8a 100644 --- a/Swift/Tests/ReplicatorTest+Collection.swift +++ b/Swift/Tests/ReplicatorTest+Collection.swift @@ -360,7 +360,7 @@ class ReplicatorTest_Collection: ReplicatorTest { let target = URLEndpoint(url: url) var config = ReplicatorConfiguration(target: target) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { config.addCollections([col1a, col1b]) } @@ -371,7 +371,7 @@ class ReplicatorTest_Collection: ReplicatorTest { XCTAssertEqual(config.collections.count, 1) XCTAssert(config.collections.contains(where: { $0.name == "colA" && $0.scope.name == "scopeA" })) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { config.addCollection(col1b) } } @@ -389,7 +389,7 @@ class ReplicatorTest_Collection: ReplicatorTest { let target = URLEndpoint(url: url) var config = ReplicatorConfiguration(target: target) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { config.addCollections([col1a, col1b]) } @@ -400,7 +400,7 @@ class ReplicatorTest_Collection: ReplicatorTest { XCTAssertEqual(config.collections.count, 1) XCTAssert(config.collections.contains(where: { $0.name == "colA" && $0.scope.name == "scopeA" })) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { config.addCollection(col1b) } } diff --git a/Swift/Tests/UnnestArrayTest.swift b/Swift/Tests/UnnestArrayTest.swift new file mode 100644 index 000000000..096d15766 --- /dev/null +++ b/Swift/Tests/UnnestArrayTest.swift @@ -0,0 +1,85 @@ +// +// UnnestArrayTest.swift +// CouchbaseLite +// +// Copyright (c) 2024 Couchbase, Inc. All rights reserved. +// +// Licensed under the Couchbase License Agreement (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://info.couchbase.com/rs/302-GJY-034/images/2017-10-30_License_Agreement.pdf +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +@testable import CouchbaseLiteSwift + +/// Test Spec v1.0.1: +/// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0004-Unnest-Array-Index.md + +class UnnestArrayTest: CBLTestCase { + /// 1. TestArrayIndexConfigInvalidExpressions + /// Description + /// Test that creating an ArrayIndexConfiguration with invalid expressions which are an empty expressions or contain null. + /// Steps + /// 1. Create a ArrayIndexConfiguration object. + /// - path: "contacts" + /// - expressions: [] + /// 2. Check that an invalid arument exception is thrown. + func testArrayIndexConfigInvalidExpressions() throws { + expectException(exception: .invalidArgumentException) { + _ = ArrayIndexConfiguration(path: "contacts", []) + } + + expectException(exception: .invalidArgumentException) { + _ = ArrayIndexConfiguration(path: "contacts", [""]) + } + } + + /// 2. TestCreateArrayIndexWithPath + /// Description + /// Test that creating an ArrayIndexConfiguration with invalid expressions which are an empty expressions or contain null. + /// Steps + /// 1. Load profiles.json into the collection named "_default.profiles". + /// 2. Create a ArrayIndexConfiguration object. + /// - path: "contacts" + /// - expressions: null + /// 3. Create an array index named "contacts" in the profiles collection. + /// 4. Get index names from the profiles collection and check that the index named "contacts" exists. + /// 5. Get info of the index named "contacts" using an internal API and check that the index has path and expressions as configured. + func testCreateArrayIndexWithPath() throws { + let profiles = try db.createCollection(name: "profiles") + try loadJSONResource("profiles_100", collection: profiles) + let config = ArrayIndexConfiguration(path: "contacts") + try profiles.createIndex(withName: "contacts", config: config) + let indexes = try profiles.indexesInfo() + XCTAssertEqual(indexes!.count, 1) + XCTAssertEqual(indexes![0]["expr"] as! String, "") + } + + /// 3. TestCreateArrayIndexWithPathAndExpressions + /// Description + /// Test that creating an array index with path and expressions works as expected. + /// Steps + /// 1. Load profiles.json into the collection named "_default.profiles". + /// 2. Create a ArrayIndexConfiguration object. + /// - path: "contacts" + /// - expressions: ["address.city", "address.state"] + /// 3. Create an array index named "contacts" in the profiles collection. + /// 4. Get index names from the profiles collection and check that the index named "contacts" exists. + /// 5. Get info of the index named "contacts" using an internal API and check that the index has path and expressions as configured. + func testCreateArrayIndexWithPathAndExpressions() throws { + let profiles = try db.createCollection(name: "profiles") + try loadJSONResource("profiles_100", collection: profiles) + let config = ArrayIndexConfiguration(path: "contacts", ["address.city", "address.state"]) + try profiles.createIndex(withName: "contacts", config: config) + let indexes = try profiles.indexesInfo() + XCTAssertEqual(indexes!.count, 1) + XCTAssertEqual(indexes![0]["expr"] as! String, "address.city,address.state") + } +} diff --git a/Swift/Tests/VectorSearchTest+Lazy.swift b/Swift/Tests/VectorSearchTest+Lazy.swift index 260e9230d..5fe62799c 100644 --- a/Swift/Tests/VectorSearchTest+Lazy.swift +++ b/Swift/Tests/VectorSearchTest+Lazy.swift @@ -195,7 +195,7 @@ class VectorSearchTest_Lazy : VectorSearchTest { let index = try wordsIndex() - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { _ = try? index.beginUpdate(limit: 0) } } @@ -774,55 +774,55 @@ class VectorSearchTest_Lazy : VectorSearchTest { let updater = try index.beginUpdate(limit: 10)! XCTAssertEqual(updater.count, 1) - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.string(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.int(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.int64(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.float(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.double(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.boolean(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.date(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.blob(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.dictionary(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.array(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { _ = updater.value(at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { try! updater.setVector([1.0, 2.0, 3.0], at: 1) } - expectExcepion(exception: .rangeException) { + expectException(exception: .rangeException) { updater.skipVector(at: 1) } } diff --git a/Swift/Tests/VectorSearchTest.swift b/Swift/Tests/VectorSearchTest.swift index 57d84bdf2..9bb1e87db 100644 --- a/Swift/Tests/VectorSearchTest.swift +++ b/Swift/Tests/VectorSearchTest.swift @@ -274,11 +274,11 @@ class VectorSearchTest_Main: VectorSearchTest { let config2 = VectorIndexConfiguration(expression: "vector", dimensions: 4096, centroids: 8) try wordsCollection.createIndex(withName: "words_index_2", config: config2) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { _ = VectorIndexConfiguration(expression: "vector", dimensions: 1, centroids: 8) } - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { _ = VectorIndexConfiguration(expression: "vector", dimensions: 4097, centroids: 8) } } @@ -305,11 +305,11 @@ class VectorSearchTest_Main: VectorSearchTest { let config2 = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 64000) try wordsCollection.createIndex(withName: "words_index_2", config: config2) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { _ = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 0) } - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { _ = VectorIndexConfiguration(expression: "vector", dimensions: 300, centroids: 64001) } } @@ -806,7 +806,7 @@ class VectorSearchTest_Main: VectorSearchTest { for numberOfSubq in [0, 7] { try deleteWordsIndex() config.encoding = .productQuantizer(subquantizers: UInt32(numberOfSubq), bits: 8) - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { try? self.createWordsIndex(config: config) } } @@ -874,7 +874,7 @@ class VectorSearchTest_Main: VectorSearchTest { try deleteWordsIndex() config.minTrainingSize = 10 config.maxTrainingSize = 9 - expectExcepion(exception: .invalidArgumentException) { + expectException(exception: .invalidArgumentException) { try? self.createWordsIndex(config: config) } } diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 163b832d9..55ddf2ca9 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 163b832d936f795c2c2a3cf15990c03787616294 +Subproject commit 55ddf2ca9410efadb88ed87b6370c07aeb908ef9 From d05c46b9f8fddd8a456fc93d0ecef113c4f20379 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Mon, 14 Oct 2024 15:18:38 +0100 Subject: [PATCH 41/49] Proxy CONNECT request replace false with NULL (#3332) --- Objective-C/Internal/Replicator/CBLHTTPLogic.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objective-C/Internal/Replicator/CBLHTTPLogic.m b/Objective-C/Internal/Replicator/CBLHTTPLogic.m index 0f7fe67e9..3c4db9634 100644 --- a/Objective-C/Internal/Replicator/CBLHTTPLogic.m +++ b/Objective-C/Internal/Replicator/CBLHTTPLogic.m @@ -144,7 +144,7 @@ - (CFHTTPMessageRef) newHTTPRequest { CFHTTPMessageRef httpMsg; if (_proxyType == kCBLHTTPProxy && _useProxyCONNECT) { NSString *destination = $sprintf(@"%@:%d", url.host, url.my_effectivePort); - CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, (__bridge CFStringRef)destination, false); + CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, (__bridge CFStringRef)destination, NULL); httpMsg = CFHTTPMessageCreateRequest(NULL, CFSTR("CONNECT"), requestURL, From e2ea8b265a3ef9e0fa54a3243d335f73727e62c2 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 18 Oct 2024 12:14:06 +0100 Subject: [PATCH 42/49] fix ArrayIndexConfiguration constructor (#3334) --- Objective-C/CBLArrayIndexConfiguration.m | 6 +++--- Objective-C/CBLIndexConfiguration.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Objective-C/CBLArrayIndexConfiguration.m b/Objective-C/CBLArrayIndexConfiguration.m index 64538a205..2cac13eb1 100644 --- a/Objective-C/CBLArrayIndexConfiguration.m +++ b/Objective-C/CBLArrayIndexConfiguration.m @@ -33,11 +33,11 @@ - (instancetype) initWithPath: (NSString*) path } else if ([expressions count] == 0 || [expressions[0] length] == 0) { [NSException raise: NSInvalidArgumentException format: @"Empty expressions is not allowed, use nil instead"]; + } else { + self = [super initWithIndexType: kC4ArrayIndex + expressions: expressions]; } - self = [super initWithIndexType: kC4ArrayIndex - expressions: expressions]; - if (self) { _path = path; _expressions = expressions; diff --git a/Objective-C/CBLIndexConfiguration.m b/Objective-C/CBLIndexConfiguration.m index a6c547221..a456757aa 100644 --- a/Objective-C/CBLIndexConfiguration.m +++ b/Objective-C/CBLIndexConfiguration.m @@ -42,7 +42,7 @@ - (instancetype) initWithIndexType: (C4IndexType)type expressions: (NSArray Date: Fri, 18 Oct 2024 12:14:23 +0100 Subject: [PATCH 43/49] CBL-6307: Database MMap Configuration API and tests (#3333) * mmap and tests * LiteCore 3.2.1-9 --- Objective-C/CBLDatabase.mm | 3 ++ Objective-C/CBLDatabaseConfiguration.h | 9 ++++ Objective-C/CBLDatabaseConfiguration.m | 4 +- Objective-C/CBLDefaults.h | 5 +- Objective-C/CBLDefaults.m | 2 + Objective-C/Tests/DatabaseTest.m | 64 +++++++++++++++++++++++++- Swift/DatabaseConfiguration.swift | 11 +++++ Swift/Defaults.swift | 5 +- Swift/Tests/DatabaseTest.swift | 55 ++++++++++++++++++++-- vendor/couchbase-lite-core | 2 +- 10 files changed, 152 insertions(+), 8 deletions(-) diff --git a/Objective-C/CBLDatabase.mm b/Objective-C/CBLDatabase.mm index a0a97040f..1e4e4c833 100644 --- a/Objective-C/CBLDatabase.mm +++ b/Objective-C/CBLDatabase.mm @@ -1026,6 +1026,9 @@ static C4DatabaseConfig2 c4DatabaseConfig2 (CBLDatabaseConfiguration *config) { if (config.fullSync) c4config.flags |= kC4DB_DiskSyncFull; + if (!config.mmapEnabled) + c4config.flags |= kC4DB_MmapDisabled; + #ifdef COUCHBASE_ENTERPRISE if (config.encryptionKey) c4config.encryptionKey = [CBLDatabase c4EncryptionKey: config.encryptionKey]; diff --git a/Objective-C/CBLDatabaseConfiguration.h b/Objective-C/CBLDatabaseConfiguration.h index b1b25804e..c86ac5ea8 100644 --- a/Objective-C/CBLDatabaseConfiguration.h +++ b/Objective-C/CBLDatabaseConfiguration.h @@ -43,6 +43,15 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic) BOOL fullSync; +/** + Enables or disables memory-mapped I/O. By default, memory-mapped I/O is enabled. + Disabling it may affect database performance. Typically, there is no need to modify this setting. + + @note: Memory-mapped I/O is always disabled to prevent database corruption on macOS. + As a result, setting this configuration has no effect on the macOS platform. + */ +@property (nonatomic) BOOL mmapEnabled; + /** Initializes the CBLDatabaseConfiguration object. */ diff --git a/Objective-C/CBLDatabaseConfiguration.m b/Objective-C/CBLDatabaseConfiguration.m index 9f0f44d5c..9c31f7df4 100644 --- a/Objective-C/CBLDatabaseConfiguration.m +++ b/Objective-C/CBLDatabaseConfiguration.m @@ -25,7 +25,7 @@ @implementation CBLDatabaseConfiguration { BOOL _readonly; } -@synthesize directory=_directory, fullSync=_fullSync; +@synthesize directory=_directory, fullSync=_fullSync, mmapEnabled=_mmapEnabled; #ifdef COUCHBASE_ENTERPRISE @synthesize encryptionKey=_encryptionKey; @@ -49,12 +49,14 @@ - (instancetype) initWithConfig: (nullable CBLDatabaseConfiguration*)config if (config) { _directory = config.directory; _fullSync = config.fullSync; + _mmapEnabled = config.mmapEnabled; #ifdef COUCHBASE_ENTERPRISE _encryptionKey = config.encryptionKey; #endif } else { _directory = [CBLDatabaseConfiguration defaultDirectory]; _fullSync = kCBLDefaultDatabaseFullSync; + _mmapEnabled = kCBLDefaultDatabaseMmapEnabled; } } return self; diff --git a/Objective-C/CBLDefaults.h b/Objective-C/CBLDefaults.h index eac560e00..d0c8f287e 100644 --- a/Objective-C/CBLDefaults.h +++ b/Objective-C/CBLDefaults.h @@ -33,6 +33,9 @@ /** [NO] Full sync is off by default because the performance hit is seldom worth the benefit */ extern const BOOL kCBLDefaultDatabaseFullSync; +/** [NO] Memory mapped database files are disabled by default. Always disabled for macOS. */ +extern const BOOL kCBLDefaultDatabaseMmapEnabled; + #pragma mark - CBLLogFileConfiguration /** [NO] Plaintext is not used, and instead binary encoding is used in log files */ @@ -97,7 +100,7 @@ extern const BOOL kCBLDefaultVectorIndexIsLazy; /** [kCBLSQ8] Vectors are encoded by using 8-bit Scalar Quantizer encoding, by default */ extern const CBLScalarQuantizerType kCBLDefaultVectorIndexEncoding; -/** [kCBLDistanceMetricEuclideanSquared] By default, vectors are compared using Euclidean metrics */ +/** [kCBLDistanceMetricEuclideanSquared] By default, vectors are compared using Squared Euclidean metrics */ extern const CBLDistanceMetric kCBLDefaultVectorIndexDistanceMetric; /** [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters. */ diff --git a/Objective-C/CBLDefaults.m b/Objective-C/CBLDefaults.m index 5fc203610..0509747e2 100644 --- a/Objective-C/CBLDefaults.m +++ b/Objective-C/CBLDefaults.m @@ -26,6 +26,8 @@ const BOOL kCBLDefaultDatabaseFullSync = NO; +const BOOL kCBLDefaultDatabaseMmapEnabled = YES; + #pragma mark - CBLLogFileConfiguration const BOOL kCBLDefaultLogFileUsePlaintext = NO; diff --git a/Objective-C/Tests/DatabaseTest.m b/Objective-C/Tests/DatabaseTest.m index 3de019525..bf70e5bd7 100644 --- a/Objective-C/Tests/DatabaseTest.m +++ b/Objective-C/Tests/DatabaseTest.m @@ -2821,7 +2821,7 @@ - (void) testDBEventTrigged { #pragma mark - Full Sync Option /** - Test Spec for Database Full Sync Option https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0003-SQLite-Options.md + Test Spec v1.0.0: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0003-SQLite-Options.md */ /** @@ -2891,6 +2891,68 @@ - (void) testDBWithFullSync { [self closeDatabase: db]; } +#pragma mark - MMap +/** Test Spec v1.0.1: + https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0006-MMap-Config.md + */ + +/** + 1. TestDefaultMMapConfig + Description + Test that the mmapEnabled default value is as expected and that it's setter and getter work. + Steps + 1. Create a DatabaseConfiguration object. + 2. Get and check that the value of the mmapEnabled property is true. + 3. Set the mmapEnabled property to false and verify that the value is false. + 4. Set the mmapEnabled property to true, and verify that the mmap value is true. + */ + +- (void) testDefaultMMapConfig { + CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; + Assert(config.mmapEnabled); + + config.mmapEnabled = false; + AssertFalse(config.mmapEnabled); + + config.mmapEnabled = true; + Assert(config.mmapEnabled); +} + +/** +2. TestDatabaseWithConfiguredMMap +Description + Test that a Database respects the mmapEnabled property. +Steps + 1. Create a DatabaseConfiguration object and set mmapEnabled to false. + 2. Create a database with the config. + 3. Get the configuration object from the database and check that the mmapEnabled is false. + 4. Use c4db_config2 to confirm that its config contains the kC4DB_MmapDisabled flag + 5. Set the config's mmapEnabled property true + 6. Create a database with the config. + 7. Get the configuration object from the database and verify that mmapEnabled is true + 8. Use c4db_config2 to confirm that its config doesn't contains the kC4DB_MmapDisabled flag + */ + +- (void) testDatabaseWithConfiguredMMap { + NSError* err; + CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; + + config.mmapEnabled = false; + CBLDatabase* db1 = [[CBLDatabase alloc] initWithName: @"mmap1" config: config error:&err]; + CBLDatabaseConfiguration* tempConfig = [db1 config]; + AssertFalse(tempConfig.mmapEnabled); + Assert(([db1 getC4DBConfig]->flags & kC4DB_MmapDisabled) == kC4DB_MmapDisabled); + + config.mmapEnabled = true; + CBLDatabase* db2 = [[CBLDatabase alloc] initWithName: @"mmap2" config: config error:&err]; + tempConfig = [db2 config]; + Assert(tempConfig.mmapEnabled); + AssertFalse(([db2 getC4DBConfig]->flags & kC4DB_MmapDisabled) == kC4DB_MmapDisabled); + + db1 = nil; + db2 = nil; +} + #pragma clang diagnostic pop @end diff --git a/Swift/DatabaseConfiguration.swift b/Swift/DatabaseConfiguration.swift index fb25a5e64..9af90dcfa 100644 --- a/Swift/DatabaseConfiguration.swift +++ b/Swift/DatabaseConfiguration.swift @@ -38,6 +38,12 @@ public struct DatabaseConfiguration { /// is very safe but it is also dramatically slower. public var fullSync: Bool = defaultFullSync + /// Enables or disables memory-mapped I/O. By default, memory-mapped I/O is enabled. + /// Disabling it may affect database performance. Typically, there is no need to modify this setting. + /// - Note: Memory-mapped I/O is always disabled to prevent database corruption on macOS. + /// As a result, setting this configuration has no effect on the macOS platform. + public var mmapEnabled: Bool = defaultMmapEnabled; + #if COUCHBASE_ENTERPRISE /// The key to encrypt the database with. public var encryptionKey: EncryptionKey? @@ -53,6 +59,8 @@ public struct DatabaseConfiguration { if let c = config { self.directory = c.directory self.fullSync = c.fullSync + self.mmapEnabled = c.mmapEnabled + #if COUCHBASE_ENTERPRISE self.encryptionKey = c.encryptionKey #endif @@ -65,9 +73,12 @@ public struct DatabaseConfiguration { let config = CBLDatabaseConfiguration() config.directory = self.directory config.fullSync = self.fullSync + config.mmapEnabled = self.mmapEnabled + #if COUCHBASE_ENTERPRISE config.encryptionKey = self.encryptionKey?.impl #endif + return config } } diff --git a/Swift/Defaults.swift b/Swift/Defaults.swift index 28e245fbd..572143e4e 100644 --- a/Swift/Defaults.swift +++ b/Swift/Defaults.swift @@ -27,6 +27,9 @@ public extension DatabaseConfiguration { /// [false] Full sync is off by default because the performance hit is seldom worth the benefit static let defaultFullSync: Bool = false + /// [false] Memory mapped database files are disabled by default. Always disabled for macOS. + static let defaultMmapEnabled: Bool = true + } public extension LogFileConfiguration { @@ -101,7 +104,7 @@ public extension VectorIndexConfiguration { /// [ScalarQuantizerType.SQ8] Vectors are encoded by using 8-bit Scalar Quantizer encoding, by default static let defaultEncoding: ScalarQuantizerType = ScalarQuantizerType.SQ8 - /// [DistanceMetric.euclideanSquared] By default, vectors are compared using Euclidean metrics + /// [DistanceMetric.euclideanSquared] By default, vectors are compared using Squared Euclidean metrics static let defaultDistanceMetric: DistanceMetric = DistanceMetric.euclideanSquared /// [0] By default, the value will be determined based on the number of centroids, encoding types, and the encoding parameters. diff --git a/Swift/Tests/DatabaseTest.swift b/Swift/Tests/DatabaseTest.swift index f8e5fa1a9..ff158eb57 100644 --- a/Swift/Tests/DatabaseTest.swift +++ b/Swift/Tests/DatabaseTest.swift @@ -1531,7 +1531,7 @@ class DatabaseTest: CBLTestCase { } // MARK: Full Sync Option - /// Test Spec for Database Full Sync Option + /// Test Spec v1.0.0: /// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0003-SQLite-Options.md /// 1. TestSQLiteFullSyncConfig @@ -1575,11 +1575,60 @@ class DatabaseTest: CBLTestCase { var config = DatabaseConfiguration() config.directory = self.directory db = try Database(name: dbName, config: config) - XCTAssertFalse(DatabaseConfiguration(config: config).fullSync) + XCTAssertFalse(db.config.fullSync) db = nil config.fullSync = true db = try Database(name: dbName, config: config) - XCTAssert(DatabaseConfiguration(config: config).fullSync) + XCTAssert(db.config.fullSync) } + + // MARK: MMap + /// Test Spec v1.0.1: + /// https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0006-MMap-Config.md + + /// 1. TestDefaultMMapConfig + /// Description + /// Test that the mmapEnabled default value is as expected and that it's setter and getter work. + /// Steps + /// 1. Create a DatabaseConfiguration object. + /// 2. Get and check that the value of the mmapEnabled property is true. + /// 3. Set the mmapEnabled property to false and verify that the value is false. + /// 4. Set the mmapEnabled property to true, and verify that the mmap value is true. + + func testDefaultMMapConfig() throws { + var config = DatabaseConfiguration() + XCTAssertTrue(config.mmapEnabled) + + config.mmapEnabled = false; + XCTAssertFalse(config.mmapEnabled) + + config.mmapEnabled = true; + XCTAssertTrue(config.mmapEnabled) + } + + /// 2. TestDatabaseWithConfiguredMMap + /// Description + /// Test that a Database respects the mmapEnabled property. + /// Steps + /// 1. Create a DatabaseConfiguration object and set mmapEnabled to false. + /// 2. Create a database with the config. + /// 3. Get the configuration object from the database and check that the mmapEnabled is false. + /// 4. Use c4db_config2 to confirm that its config contains the kC4DB_MmapDisabled flag - done in Obj-C + /// 5. Set the config's mmapEnabled property true + /// 6. Create a database with the config. + /// 7. Get the configuration object from the database and verify that mmapEnabled is true + /// 8. Use c4db_config2 to confirm that its config doesn't contains the kC4DB_MmapDisabled flag - done in Obj-C + + func testDatabaseWithConfiguredMMap() throws { + var config = DatabaseConfiguration() + config.mmapEnabled = false; + let db1 = try Database(name: "mmap1", config: config) + XCTAssertFalse(db1.config.mmapEnabled) + + config.mmapEnabled = true; + let db2 = try Database(name: "mmap2", config: config) + XCTAssert(db2.config.mmapEnabled) + } + } diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 55ddf2ca9..da8b267c0 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 55ddf2ca9410efadb88ed87b6370c07aeb908ef9 +Subproject commit da8b267c0ef7ae36a814dc3f5af984f112dc2151 From 9334e457d0ef203e7253c8d0ca3403de0bfaebba Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Thu, 24 Oct 2024 12:25:40 +0100 Subject: [PATCH 44/49] CBL-6349: Implement Document's getRevisionHistory() for E2E Test Server (#3335) * get c4doc using kDocGetAll content lvl for this * added docID to CBL c4doc * LiteCore 3.2.1-12 --- Objective-C/CBLDocument.h | 3 +++ Objective-C/CBLDocument.mm | 15 ++++++++++++ Objective-C/CBLMutableDocument.mm | 7 +++--- Objective-C/Internal/CBLC4Document.h | 5 +++- Objective-C/Internal/CBLC4Document.mm | 6 ++++- Objective-C/Tests/DocumentTest.m | 34 +++++++++++++++++++++++++++ Swift/Document.swift | 7 ++++++ Swift/Tests/DocumentTest.swift | 27 +++++++++++++++++++++ vendor/couchbase-lite-core | 2 +- 9 files changed, 99 insertions(+), 7 deletions(-) diff --git a/Objective-C/CBLDocument.h b/Objective-C/CBLDocument.h index d1fb3e5a7..48208a184 100644 --- a/Objective-C/CBLDocument.h +++ b/Objective-C/CBLDocument.h @@ -58,6 +58,9 @@ NS_ASSUME_NONNULL_BEGIN /** Return document data as JSON String. */ - (NSString*) toJSON; +/** Internally used for testing purpose. */ +- (nullable NSString*) _getRevisionHistory; + @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/CBLDocument.mm b/Objective-C/CBLDocument.mm index 105b70073..fa6e7c01c 100644 --- a/Objective-C/CBLDocument.mm +++ b/Objective-C/CBLDocument.mm @@ -29,6 +29,7 @@ #import "CBLFleece.hh" #import "MRoot.hh" #import "CBLErrorMessage.h" +#import using namespace fleece; @@ -199,6 +200,20 @@ - (void) replaceC4Doc: (CBLC4Document*)c4doc { } } +- (nullable NSString*) _getRevisionHistory { + if (!_collection) { + return nil; + } + + CBL_LOCK(self) { + C4Error err; + C4Document* doc = c4coll_getDoc(_collection.c4col, _c4Doc.docID, true, kDocGetAll, &err); + NSString* revHistory = doc ? sliceResult2string(c4doc_getRevisionHistory(doc, UINT_MAX, nil, 0)) : nil; + c4doc_release(doc); + return revHistory; + } +} + #pragma mark - Fleece Encoding - (FLSliceResult) encodeWithRevFlags: (C4RevisionFlags*)outRevFlags error:(NSError**)outError { diff --git a/Objective-C/CBLMutableDocument.mm b/Objective-C/CBLMutableDocument.mm index ae569e033..402d643ca 100644 --- a/Objective-C/CBLMutableDocument.mm +++ b/Objective-C/CBLMutableDocument.mm @@ -100,9 +100,9 @@ - (instancetype) initWithID: (nullable NSString*)documentID return self; } -/* internal */ - (instancetype) initAsCopyWithDocument: (CBLDocument*)doc - dict: (nullable CBLDictionary*)dict -{ +#pragma mark - Internal +- (instancetype) initAsCopyWithDocument: (CBLDocument*)doc + dict: (nullable CBLDictionary*)dict { self = [self initWithCollection: doc.collection documentID: doc.id c4Doc: doc.c4Doc]; @@ -111,7 +111,6 @@ - (instancetype) initWithID: (nullable NSString*)documentID _dict = [dict mutableCopy]; } return self; - } #pragma mark - Edit diff --git a/Objective-C/Internal/CBLC4Document.h b/Objective-C/Internal/CBLC4Document.h index 574f9c827..7608a869e 100644 --- a/Objective-C/Internal/CBLC4Document.h +++ b/Objective-C/Internal/CBLC4Document.h @@ -2,7 +2,7 @@ // CBLC4Document.h // CouchbaseLite // -// Copyright (c) 2017 Couchbase, Inc All rights reserved. +// Copyright (c) 2024 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -32,6 +32,9 @@ NS_ASSUME_NONNULL_BEGIN /** Sequence of the selected revision. */ @property (readonly, nonatomic) C4SequenceNumber sequence; +/** Document ID of the selected revision. */ +@property (readonly, nonatomic) C4String docID; + /** Revision ID of the selected revision. */ @property (readonly, nonatomic) C4String revID; diff --git a/Objective-C/Internal/CBLC4Document.mm b/Objective-C/Internal/CBLC4Document.mm index 477e9af3d..3d505c498 100644 --- a/Objective-C/Internal/CBLC4Document.mm +++ b/Objective-C/Internal/CBLC4Document.mm @@ -2,7 +2,7 @@ // CBLC4Document.mm // CouchbaseLite // -// Copyright (c) 2017 Couchbase, Inc All rights reserved. +// Copyright (c) 2024 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -49,6 +49,10 @@ - (C4SequenceNumber) sequence { return _rawDoc->selectedRev.sequence; } +- (C4String) docID { + return _rawDoc->docID; +} + - (C4String) revID { return _rawDoc->selectedRev.revID; } diff --git a/Objective-C/Tests/DocumentTest.m b/Objective-C/Tests/DocumentTest.m index 2d92a4bf2..956c9e1ac 100644 --- a/Objective-C/Tests/DocumentTest.m +++ b/Objective-C/Tests/DocumentTest.m @@ -2262,6 +2262,40 @@ - (void) testDocumentResaveInAnotherCollection { }]; } +#pragma mark - Revision history + +/** https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0005-Version-Vector.md */ + +/** + 2. TestDocumentRevisionHistory + Description + Test that the document's timestamp returns value as expected. + Steps + 1. Create a new document with id = "doc1" + 2. Get document's _revisionIDs and check that the value returned is an empty array. + 3. Save the document into the default collection. + 4. Get document's _revisionIDs and check that the value returned is an array containing a + single revision id which is the revision id of the documnt. + 5. Get the document id = "doc1" from the database. + 6. Get document's _revisionIDs and check that the value returned is an array containing a + single revision id which is the revision id of the documnt. + */ +- (void) testDocumentRevisionHistory { + NSError* err; + CBLCollection* defaultCollection = [self.db defaultCollection: &err]; + AssertNil(err); + + CBLMutableDocument* doc = [[CBLMutableDocument alloc] initWithID: @"doc1"]; + Assert(doc); + AssertNil(doc._getRevisionHistory); + + Assert([defaultCollection saveDocument:doc error: &err]); + Assert(doc._getRevisionHistory); + + doc = [[defaultCollection documentWithID: @"doc1" error: &err] toMutable]; + Assert(doc._getRevisionHistory); +} + #pragma clang diagnostic pop @end diff --git a/Swift/Document.swift b/Swift/Document.swift index 649c14dc6..48fa38295 100644 --- a/Swift/Document.swift +++ b/Swift/Document.swift @@ -45,6 +45,13 @@ public class Document : DictionaryProtocol, Equatable, Hashable, Sequence { /// The collection that the document belongs to. internal(set) public var collection: Collection? + // MARK: Unsupported - Internal use for testing + + /// Internally used for testing purpose. + public func _getRevisionHistory() -> String? { + return impl._getRevisionHistory() + } + // MARK: Edit /// Returns a mutable copy of the document. diff --git a/Swift/Tests/DocumentTest.swift b/Swift/Tests/DocumentTest.swift index d0a757a88..a461b8100 100644 --- a/Swift/Tests/DocumentTest.swift +++ b/Swift/Tests/DocumentTest.swift @@ -1855,4 +1855,31 @@ class DocumentTest: CBLTestCase { } } } + + // MARK: toJSONTimestamp & Revision history + + // https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0005-Version-Vector.md + + // 2. TestDocumentRevisionHistory + // Description + // Test that the document's timestamp returns value as expected. + // Steps + // 1. Create a new document with id = "doc1" + // 2. Get document's _revisionIDs and check that the value returned is an empty array. + // 3. Save the document into the default collection. + // 4. Get document's _revisionIDs and check that the value returned is an array containing a + // single revision id which is the revision id of the documnt. + // 5. Get the document id = "doc1" from the database. + // 6. Get document's _revisionIDs and check that the value returned is an array containing a + // single revision id which is the revision id of the documnt. + func testDocumentRevisionHistory() throws { + let doc = MutableDocument(id: "doc1") + assert(doc._getRevisionHistory() == nil) + + try defaultCollection!.save(document: doc) + assert(doc._getRevisionHistory() != nil) + + let remoteDoc = try defaultCollection!.document(id: "doc1")!.toMutable(); + assert(doc._getRevisionHistory() != nil) + } } diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index da8b267c0..fb6e666c1 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit da8b267c0ef7ae36a814dc3f5af984f112dc2151 +Subproject commit fb6e666c1adebd873872540c7e0ffe0cc01320a3 From 39b36fb35530a6c78da3115744c9e9fd30ce28f2 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 25 Oct 2024 13:50:33 +0100 Subject: [PATCH 45/49] LiteCore 3.2.1-18 (#3336) --- vendor/couchbase-lite-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index fb6e666c1..a34d197ed 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit fb6e666c1adebd873872540c7e0ffe0cc01320a3 +Subproject commit a34d197ed29705bab61f4291702120a6c27945ce From f8609d65315c4f955a0bc306a3d056401cfa5e43 Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Wed, 30 Oct 2024 14:13:49 +0000 Subject: [PATCH 46/49] Update LiteCore to 3.2.1-19 (#3337) --- vendor/couchbase-lite-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index a34d197ed..c67cbd3dd 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit a34d197ed29705bab61f4291702120a6c27945ce +Subproject commit c67cbd3dd40538203e9d0a9f64f9fe70613f01d1 From 80342dce0bb62e93effaf78f566eaf5f9897847f Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Fri, 1 Nov 2024 19:10:39 +0000 Subject: [PATCH 47/49] LiteCore 3.2.1-19 --- vendor/couchbase-lite-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/couchbase-lite-core b/vendor/couchbase-lite-core index 7f0707145..c67cbd3dd 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 7f0707145d9db2af04a9494ee7e271a8302a6a7c +Subproject commit c67cbd3dd40538203e9d0a9f64f9fe70613f01d1 From 4e8765a98a880a6bd402ce53137155b7b299ebee Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Tue, 5 Nov 2024 15:12:10 +0000 Subject: [PATCH 48/49] Export symbol for mmap constant (#3341) --- Objective-C/Exports/CBL.txt | 1 + Objective-C/Exports/Generated/CBL.exp | 1 + Objective-C/Exports/Generated/CBL_EE.exp | 1 + 3 files changed, 3 insertions(+) diff --git a/Objective-C/Exports/CBL.txt b/Objective-C/Exports/CBL.txt index 09677e7c1..f4ab20ba0 100644 --- a/Objective-C/Exports/CBL.txt +++ b/Objective-C/Exports/CBL.txt @@ -93,6 +93,7 @@ _kCBLBlobDigestProperty _kCBLBlobLengthProperty _kCBLBlobType _kCBLDefaultDatabaseFullSync +_kCBLDefaultDatabaseMmapEnabled _kCBLDefaultCollectionName _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount diff --git a/Objective-C/Exports/Generated/CBL.exp b/Objective-C/Exports/Generated/CBL.exp index 7fd127c07..ac0e1da7d 100644 --- a/Objective-C/Exports/Generated/CBL.exp +++ b/Objective-C/Exports/Generated/CBL.exp @@ -73,6 +73,7 @@ _kCBLBlobLengthProperty _kCBLBlobType _kCBLDefaultCollectionName _kCBLDefaultDatabaseFullSync +_kCBLDefaultDatabaseMmapEnabled _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultLogFileMaxRotateCount _kCBLDefaultLogFileMaxSize diff --git a/Objective-C/Exports/Generated/CBL_EE.exp b/Objective-C/Exports/Generated/CBL_EE.exp index c10cd3242..3c2e2ee30 100644 --- a/Objective-C/Exports/Generated/CBL_EE.exp +++ b/Objective-C/Exports/Generated/CBL_EE.exp @@ -111,6 +111,7 @@ _kCBLCertAttrSurname _kCBLCertAttrURL _kCBLDefaultCollectionName _kCBLDefaultDatabaseFullSync +_kCBLDefaultDatabaseMmapEnabled _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultListenerDisableTls _kCBLDefaultListenerEnableDeltaSync From e9dfc05b558f0c42d5eb3e0c3cf7f8b8c8d7b25e Mon Sep 17 00:00:00 2001 From: Vlad Velicu Date: Tue, 5 Nov 2024 18:35:11 +0000 Subject: [PATCH 49/49] ArrayIndexConfiguration explicit expressions arg on set (#3342) --- Swift/IndexConfiguration.swift | 2 +- Swift/Tests/UnnestArrayTest.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Swift/IndexConfiguration.swift b/Swift/IndexConfiguration.swift index dfd928060..48c4258a5 100644 --- a/Swift/IndexConfiguration.swift +++ b/Swift/IndexConfiguration.swift @@ -88,7 +88,7 @@ public struct ArrayIndexConfiguration: IndexConfiguration, IndexConfigConvertabl /// represents an expression defining the values within the array to be indexed. /// If the array specified by the path contains scalar values, this parameter can be null. /// - Returns The ArrayIndexConfiguration object. - public init(path: String, _ expressions: [String]? = nil) { + public init(path: String, expressions: [String]? = nil) { if let expressions = expressions { if expressions.isEmpty || (expressions.count == 1 && expressions[0].isEmpty) { NSException(name: .invalidArgumentException, diff --git a/Swift/Tests/UnnestArrayTest.swift b/Swift/Tests/UnnestArrayTest.swift index 096d15766..8e4db4350 100644 --- a/Swift/Tests/UnnestArrayTest.swift +++ b/Swift/Tests/UnnestArrayTest.swift @@ -33,11 +33,11 @@ class UnnestArrayTest: CBLTestCase { /// 2. Check that an invalid arument exception is thrown. func testArrayIndexConfigInvalidExpressions() throws { expectException(exception: .invalidArgumentException) { - _ = ArrayIndexConfiguration(path: "contacts", []) + _ = ArrayIndexConfiguration(path: "contacts", expressions: []) } expectException(exception: .invalidArgumentException) { - _ = ArrayIndexConfiguration(path: "contacts", [""]) + _ = ArrayIndexConfiguration(path: "contacts", expressions: [""]) } } @@ -76,7 +76,7 @@ class UnnestArrayTest: CBLTestCase { func testCreateArrayIndexWithPathAndExpressions() throws { let profiles = try db.createCollection(name: "profiles") try loadJSONResource("profiles_100", collection: profiles) - let config = ArrayIndexConfiguration(path: "contacts", ["address.city", "address.state"]) + let config = ArrayIndexConfiguration(path: "contacts",expressions: ["address.city", "address.state"]) try profiles.createIndex(withName: "contacts", config: config) let indexes = try profiles.indexesInfo() XCTAssertEqual(indexes!.count, 1)