diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index e5cfc2615..d85c63d38 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -1768,10 +1768,26 @@ 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, ); }; }; 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, ); }; }; + 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 */ @@ -2790,8 +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 */ @@ -3077,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 */, @@ -4144,6 +4165,7 @@ 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */, AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */, 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */, + AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */, 93DECF3E200DBE5800F44953 /* Support */, 936483AA1E4431C6008D08B3 /* iOS */, 275FF5FA1E3FBD3B005F90DD /* Performance */, @@ -4331,6 +4353,8 @@ 1A3471482671C87F0042C6BA /* CBLFullTextIndexConfiguration.m */, 1A34715C2671C9230042C6BA /* CBLValueIndexConfiguration.h */, 1A34715D2671C9230042C6BA /* CBLValueIndexConfiguration.m */, + AEC806B52C89EA68001C9723 /* CBLArrayIndexConfiguration.h */, + AEC806B62C89EA68001C9723 /* CBLArrayIndexConfiguration.m */, ); name = Index; sourceTree = ""; @@ -4432,6 +4456,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 +4683,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 +4949,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 +5058,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 +5551,7 @@ }; 9343EF2A207D611600F19A89 = { DevelopmentTeam = N2Q372V7W2; - LastSwiftMigration = 1340; + LastSwiftMigration = 1540; }; 9343F135207D61EC00F19A89 = { DevelopmentTeam = N2Q372V7W2; @@ -5554,7 +5582,7 @@ 9398D9111E03434200464432 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = N2Q372V7W2; - LastSwiftMigration = 1340; + LastSwiftMigration = 1540; ProvisioningStyle = Automatic; }; 9398D91A1E03434200464432 = { @@ -6012,6 +6040,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 */, @@ -6195,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 */, @@ -6238,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 */, @@ -6374,6 +6405,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 +6557,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 */, @@ -6679,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 */, @@ -6753,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 */, @@ -6781,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 */, @@ -6858,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 */, @@ -6896,6 +6933,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 */, @@ -6998,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 */, @@ -7031,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.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..2cac13eb1 --- /dev/null +++ b/Objective-C/CBLArrayIndexConfiguration.m @@ -0,0 +1,60 @@ +// +// 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: @[@""]]; + } 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]; + } + + 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 UTF8String]; + return c4options; +} + +@end 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/CBLDatabase.mm b/Objective-C/CBLDatabase.mm index 8cfc9b42f..1e4e4c833 100644 --- a/Objective-C/CBLDatabase.mm +++ b/Objective-C/CBLDatabase.mm @@ -1024,6 +1024,11 @@ static BOOL setupDatabaseDirectory(NSString *dir, NSError **outError) static C4DatabaseConfig2 c4DatabaseConfig2 (CBLDatabaseConfiguration *config) { C4DatabaseConfig2 c4config = kDBConfig; + 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]; @@ -1139,4 +1144,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..c86ac5ea8 100644 --- a/Objective-C/CBLDatabaseConfiguration.h +++ b/Objective-C/CBLDatabaseConfiguration.h @@ -30,6 +30,28 @@ 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; + +/** + 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 aace931fa..9c31f7df4 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, mmapEnabled=_mmapEnabled; #ifdef COUCHBASE_ENTERPRISE @synthesize encryptionKey=_encryptionKey; @@ -47,11 +48,16 @@ - (instancetype) initWithConfig: (nullable CBLDatabaseConfiguration*)config if (config) { _directory = config.directory; + _fullSync = config.fullSync; + _mmapEnabled = config.mmapEnabled; #ifdef COUCHBASE_ENTERPRISE _encryptionKey = config.encryptionKey; #endif - } else + } else { _directory = [CBLDatabaseConfiguration defaultDirectory]; + _fullSync = kCBLDefaultDatabaseFullSync; + _mmapEnabled = kCBLDefaultDatabaseMmapEnabled; + } } return self; } diff --git a/Objective-C/CBLDefaults.h b/Objective-C/CBLDefaults.h index 0536aa1d7..d0c8f287e 100644 --- a/Objective-C/CBLDefaults.h +++ b/Objective-C/CBLDefaults.h @@ -28,6 +28,14 @@ #endif +#pragma mark - CBLDatabaseConfiguration + +/** [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 */ @@ -92,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 36140a8cf..0509747e2 100644 --- a/Objective-C/CBLDefaults.m +++ b/Objective-C/CBLDefaults.m @@ -22,6 +22,12 @@ #import "CBLDefaults.h" +#pragma mark - CBLDatabaseConfiguration + +const BOOL kCBLDefaultDatabaseFullSync = NO; + +const BOOL kCBLDefaultDatabaseMmapEnabled = YES; + #pragma mark - CBLLogFileConfiguration const BOOL kCBLDefaultLogFileUsePlaintext = NO; 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/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/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 #import +#import #import #import #import diff --git a/Objective-C/Exports/CBL.txt b/Objective-C/Exports/CBL.txt index ad423663c..f4ab20ba0 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 @@ -91,6 +92,8 @@ _kCBLBlobContentTypeProperty _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 9062cefab..ac0e1da7d 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 @@ -71,6 +72,8 @@ _kCBLBlobDigestProperty _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 5084aa65e..3c2e2ee30 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 @@ -109,6 +110,8 @@ _kCBLCertAttrStateOrProvince _kCBLCertAttrSurname _kCBLCertAttrURL _kCBLDefaultCollectionName +_kCBLDefaultDatabaseFullSync +_kCBLDefaultDatabaseMmapEnabled _kCBLDefaultFullTextIndexIgnoreAccents _kCBLDefaultListenerDisableTls _kCBLDefaultListenerEnableDeltaSync 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/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/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/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/Internal/Replicator/CBLHTTPLogic.m b/Objective-C/Internal/Replicator/CBLHTTPLogic.m index 6cc7393c3..3c4db9634 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, NULL); httpMsg = CFHTTPMessageCreateRequest(NULL, CFSTR("CONNECT"), - (__bridge CFURLRef)requestURL, + requestURL, kCFHTTPVersion1_1); + CFRelease(requestURL); } else { httpMsg = CFHTTPMessageCreateRequest(NULL, (__bridge CFStringRef)_urlRequest.HTTPMethod, 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/DatabaseTest.m b/Objective-C/Tests/DatabaseTest.m index 70d715421..bf70e5bd7 100644 --- a/Objective-C/Tests/DatabaseTest.m +++ b/Objective-C/Tests/DatabaseTest.m @@ -2818,6 +2818,141 @@ - (void) testDBEventTrigged { [token remove]; } +#pragma mark - 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 + 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 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/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/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/Objective-C/Tests/VectorSearchTest.m b/Objective-C/Tests/VectorSearchTest.m index 457b1e1b3..d6900b93e 100644 --- a/Objective-C/Tests/VectorSearchTest.m +++ b/Objective-C/Tests/VectorSearchTest.m @@ -124,7 +124,7 @@ - (BOOL) checkIndexWasTrained { return ![_logger containsString: @"Untrained index; queries may be slow"]; } -- (void) createVectorIndexInCollection: (CBLCollection*)collection +- (void) createVectorIndexInCollection: (CBLCollection*)collection name: (NSString*)name config: (CBLVectorIndexConfiguration*)config { Assert([collection createIndexWithName: name config: config error: nil]); @@ -180,9 +180,9 @@ - (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit whereClause: (NSString*)whereClause checkTraining: (BOOL) checkTraining { NSError* error; - NSString* sql = [self wordsQueryStringWithLimit: limit + NSString* sql = [self wordsQueryStringWithLimit: limit metric: metric - vectorExpression: vectorExpression + vectorExpression: vectorExpression whereClause: whereClause]; CBLQuery* query = [_wordDB createQuery: sql error: &error]; AssertNotNil(query); @@ -205,7 +205,7 @@ - (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit } - (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit { - return [self executeWordsQueryWithLimit: limit + return [self executeWordsQueryWithLimit: limit metric: nil vectorExpression: nil whereClause: nil @@ -213,7 +213,7 @@ - (CBLQueryResultSet*) executeWordsQueryWithLimit: (NSUInteger)limit { } - (CBLQueryResultSet*) executeWordsQueryNoTrainingCheckWithLimit: (NSUInteger)limit { - return [self executeWordsQueryWithLimit: limit + return [self executeWordsQueryWithLimit: limit metric: nil vectorExpression: nil whereClause: nil @@ -398,7 +398,7 @@ - (void) testCentroidsValidation { * 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” + * 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. */ @@ -430,7 +430,7 @@ - (void) testCreateVectorIndex { * 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” + * 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 @@ -500,7 +500,7 @@ - (void) testUpdateVectorIndex { * 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” + * 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. @@ -745,7 +745,7 @@ - (void) testCreateVectorIndexUsingPredictionModelWithInvalidVectors { * 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” + * 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. @@ -1353,7 +1353,7 @@ - (void) testIndexVectorInBase64 { AssertNotNil(wordMap[@"word49"]); } -/** +/** * 28. TestNumProbes * * Description @@ -1408,7 +1408,7 @@ - (void) testHash { CBLVectorIndexConfiguration* config = [[CBLVectorIndexConfiguration alloc] initWithExpression: @"vector" dimensions: 300 centroids: 20]; - /* + /* type = 3 (SQ) bits = 8 */ 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 6bd0706a4..8cffe0b5b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Couchbase Lite implementation is on top of [Couchbase Lite Core](https://github. ## Requirements +- iOS 12.0+ | macOS 12+ - iOS 12.0+ | macOS 12.0+ @@ -24,7 +25,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 +35,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"), ], ``` 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/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/DatabaseConfiguration.swift b/Swift/DatabaseConfiguration.swift index c7a89421f..9af90dcfa 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,23 @@ 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 + + /// 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? @@ -41,6 +58,9 @@ public struct DatabaseConfiguration { public init(config: 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 @@ -52,9 +72,13 @@ public struct DatabaseConfiguration { func toImpl() -> CBLDatabaseConfiguration { 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 75584f8ba..572143e4e 100644 --- a/Swift/Defaults.swift +++ b/Swift/Defaults.swift @@ -22,6 +22,16 @@ 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 + + /// [false] Memory mapped database files are disabled by default. Always disabled for macOS. + static let defaultMmapEnabled: Bool = true + +} + public extension LogFileConfiguration { /// [false] Plaintext is not used, and instead binary encoding is used in log files @@ -94,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/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/IndexConfiguration.swift b/Swift/IndexConfiguration.swift index 1b2a3f2e5..48c4258a5 100644 --- a/Swift/IndexConfiguration.swift +++ b/Swift/IndexConfiguration.swift @@ -69,6 +69,45 @@ 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]? = 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 + } + + // MARK: Internal + + func toImpl() -> CBLIndexConfiguration { + return CBLArrayIndexConfiguration(path: path, expressions: expressions) + } +} + + // MARK: Internal protocol IndexConfigConvertable { 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/DatabaseTest.swift b/Swift/Tests/DatabaseTest.swift index cdc1c55b9..ff158eb57 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,106 @@ class DatabaseTest: CBLTestCase { XCTAssertNotNil(db.config.encryptionKey) #endif } + + // MARK: 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 + /// 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(db.config.fullSync) + + db = nil + config.fullSync = true + db = try Database(name: dbName, config: config) + 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/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..a461b8100 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", @@ -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/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..8e4db4350 --- /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", expressions: []) + } + + expectException(exception: .invalidArgumentException) { + _ = ArrayIndexConfiguration(path: "contacts", expressions: [""]) + } + } + + /// 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",expressions: ["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 005231e9d..e4f004fdc 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 7f0707145..c67cbd3dd 160000 --- a/vendor/couchbase-lite-core +++ b/vendor/couchbase-lite-core @@ -1 +1 @@ -Subproject commit 7f0707145d9db2af04a9494ee7e271a8302a6a7c +Subproject commit c67cbd3dd40538203e9d0a9f64f9fe70613f01d1