nano-store 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +3 -0
  2. data/.gitmodules +3 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +25 -0
  6. data/README.md +193 -0
  7. data/Rakefile +1 -0
  8. data/app/app_delegate.rb +5 -0
  9. data/lib/nano-store.rb +1 -0
  10. data/lib/nano_store/bag.rb +98 -0
  11. data/lib/nano_store/model.rb +142 -0
  12. data/lib/nano_store/nano_store.rb +36 -0
  13. data/lib/nano_store/store_extension.rb +150 -0
  14. data/lib/nano_store/version.rb +3 -0
  15. data/lib/nano_store.rb +14 -0
  16. data/nano-store.gemspec +17 -0
  17. data/resources/.gitignore +0 -0
  18. data/spec/bag_spec.rb +66 -0
  19. data/spec/model_spec.rb +130 -0
  20. data/spec/nano_store_spec.rb +48 -0
  21. data/spec/store_extension_spec.rb +110 -0
  22. data/vendor/NanoStore/Classes/Advanced/NSFNanoEngine.h +542 -0
  23. data/vendor/NanoStore/Classes/Advanced/NSFNanoEngine.m +1781 -0
  24. data/vendor/NanoStore/Classes/Advanced/NSFNanoResult.h +137 -0
  25. data/vendor/NanoStore/Classes/Advanced/NSFNanoResult.m +265 -0
  26. data/vendor/NanoStore/Classes/Private/NSFNanoBag_Private.h +37 -0
  27. data/vendor/NanoStore/Classes/Private/NSFNanoEngine_Private.h +69 -0
  28. data/vendor/NanoStore/Classes/Private/NSFNanoExpression_Private.h +35 -0
  29. data/vendor/NanoStore/Classes/Private/NSFNanoGlobals_Private.h +99 -0
  30. data/vendor/NanoStore/Classes/Private/NSFNanoObject_Private.h +35 -0
  31. data/vendor/NanoStore/Classes/Private/NSFNanoPredicate_Private.h +35 -0
  32. data/vendor/NanoStore/Classes/Private/NSFNanoResult_Private.h +43 -0
  33. data/vendor/NanoStore/Classes/Private/NSFNanoSearch_Private.h +48 -0
  34. data/vendor/NanoStore/Classes/Private/NSFNanoStore_Private.h +57 -0
  35. data/vendor/NanoStore/Classes/Private/NanoStore_Private.h +37 -0
  36. data/vendor/NanoStore/Classes/Public/NSFNanoBag.h +306 -0
  37. data/vendor/NanoStore/Classes/Public/NSFNanoBag.m +485 -0
  38. data/vendor/NanoStore/Classes/Public/NSFNanoExpression.h +125 -0
  39. data/vendor/NanoStore/Classes/Public/NSFNanoExpression.m +103 -0
  40. data/vendor/NanoStore/Classes/Public/NSFNanoGlobals.h +323 -0
  41. data/vendor/NanoStore/Classes/Public/NSFNanoGlobals.m +145 -0
  42. data/vendor/NanoStore/Classes/Public/NSFNanoObject.h +298 -0
  43. data/vendor/NanoStore/Classes/Public/NSFNanoObject.m +187 -0
  44. data/vendor/NanoStore/Classes/Public/NSFNanoObjectProtocol.h +119 -0
  45. data/vendor/NanoStore/Classes/Public/NSFNanoPredicate.h +123 -0
  46. data/vendor/NanoStore/Classes/Public/NSFNanoPredicate.m +130 -0
  47. data/vendor/NanoStore/Classes/Public/NSFNanoSearch.h +381 -0
  48. data/vendor/NanoStore/Classes/Public/NSFNanoSearch.m +835 -0
  49. data/vendor/NanoStore/Classes/Public/NSFNanoSortDescriptor.h +124 -0
  50. data/vendor/NanoStore/Classes/Public/NSFNanoSortDescriptor.m +79 -0
  51. data/vendor/NanoStore/Classes/Public/NSFNanoStore.h +475 -0
  52. data/vendor/NanoStore/Classes/Public/NSFNanoStore.m +1375 -0
  53. data/vendor/NanoStore/Classes/Public/NanoStore.h +463 -0
  54. data/vendor/NanoStore/LICENSE +25 -0
  55. data/vendor/NanoStore/NanoStore.bridgesupport +1215 -0
  56. data/vendor/NanoStore/README.md +411 -0
  57. metadata +118 -0
@@ -0,0 +1,1375 @@
1
+ /*
2
+ NSFNanoStore.m
3
+ NanoStore
4
+
5
+ Copyright (c) 2010 Webbo, L.L.C. All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without modification, are permitted
8
+ provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice, this list of conditions
11
+ and the following disclaimer.
12
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
13
+ and the following disclaimer in the documentation and/or other materials provided with the distribution.
14
+ * Neither the name of Webbo nor the names of its contributors may be used to endorse or promote
15
+ products derived from this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
18
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
19
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
20
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
+ SUCH DAMAGE.
25
+ */
26
+
27
+ #import "NanoStore.h"
28
+ #import "NSFNanoObjectProtocol.h"
29
+ #import "NanoStore_Private.h"
30
+ #import "NSFNanoStore_Private.h"
31
+
32
+ #include <stdlib.h>
33
+
34
+ @implementation NSFNanoStore
35
+ {
36
+ @protected
37
+ NSFNanoEngine *nanoStoreEngine;
38
+ NSFEngineProcessingMode nanoEngineProcessingMode;
39
+ NSUInteger saveInterval;
40
+
41
+ /** \cond */
42
+ NSMutableArray *addedObjects;
43
+ BOOL _isOurTransaction;
44
+ sqlite3_stmt *_storeValuesStatement;
45
+ sqlite3_stmt *_storeKeysStatement;
46
+ /** \endcond */
47
+ }
48
+
49
+ @synthesize nanoStoreEngine;
50
+ @synthesize nanoEngineProcessingMode;
51
+ @synthesize saveInterval;
52
+
53
+ // ----------------------------------------------
54
+ // Initialization / Cleanup
55
+ // ----------------------------------------------
56
+
57
+ + (NSFNanoStore *)createStoreWithType:(NSFNanoStoreType)theType path:(NSString *)thePath
58
+ {
59
+ return [[self alloc]initStoreWithType:theType path:thePath];
60
+ }
61
+
62
+ + (NSFNanoStore *)createAndOpenStoreWithType:(NSFNanoStoreType)theType path:(NSString *)thePath error:(out NSError **)outError
63
+ {
64
+ NSFNanoStore *nanoStore = [[self alloc]initStoreWithType:theType path:thePath];
65
+ [nanoStore openWithError:outError];
66
+ return nanoStore;
67
+ }
68
+
69
+ - (id)initStoreWithType:(NSFNanoStoreType)theType path:(NSString *)thePath
70
+ {
71
+ switch (theType) {
72
+ case NSFMemoryStoreType:
73
+ thePath = NSFMemoryDatabase;
74
+ break;
75
+ case NSFTemporaryStoreType:
76
+ thePath = NSFTemporaryDatabase;
77
+ break;
78
+ default:
79
+ // Do nothing
80
+ break;
81
+ }
82
+
83
+ if (nil == thePath) {
84
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
85
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: the path cannot be nil.", [self class], _cmd]
86
+ userInfo:nil]raise];
87
+ }
88
+
89
+ if ((self = [super init])) {
90
+ nanoStoreEngine = [[NSFNanoEngine alloc]initWithPath:[thePath stringByExpandingTildeInPath]];
91
+ if (nil == nanoStoreEngine) {
92
+ _NSFLog([NSString stringWithFormat:@"*** -[%@ %s]: [NSFNanoEngine initWithPath:] failed: %@", [self class], _cmd, thePath]);
93
+ [self closeWithError:nil];
94
+ return nil;
95
+ }
96
+
97
+ nanoEngineProcessingMode = NSFEngineProcessingDefaultMode;
98
+
99
+ _isOurTransaction = NO;
100
+ saveInterval = 1;
101
+
102
+ _storeValuesStatement = NULL;
103
+ _storeKeysStatement = NULL;
104
+
105
+ addedObjects = [[NSMutableArray alloc]initWithCapacity:saveInterval];
106
+ }
107
+
108
+ return self;
109
+ }
110
+
111
+ - (void)dealloc
112
+ {
113
+ [self closeWithError:nil];
114
+
115
+
116
+ }
117
+
118
+ - (NSString *)filePath
119
+ {
120
+ return [nanoStoreEngine path];
121
+ }
122
+
123
+ - (BOOL)openWithError:(out NSError **)outError
124
+ {
125
+ if ([nanoStoreEngine isDatabaseOpen] == YES)
126
+ return YES;
127
+
128
+ if ([nanoStoreEngine openWithCacheMethod:CacheAllData useFastMode:(NSFEngineProcessingFastMode == nanoEngineProcessingMode)] == NO) {
129
+ NSString *message = [NSString stringWithFormat:@"*** -[%@ %s]: open database failed: %@", [self class], _cmd, [self filePath]];
130
+ _NSFLog(message);
131
+ if (nil != outError)
132
+ *outError = [NSError errorWithDomain:NSFDomainKey
133
+ code:NSFNanoStoreErrorKey
134
+ userInfo:[NSDictionary dictionaryWithObject:message
135
+ forKey:NSLocalizedFailureReasonErrorKey]];
136
+ [self closeWithError:nil];
137
+ return NO;
138
+ }
139
+
140
+ if ([self _setupCachingSchema] == NO) {
141
+ NSString *message = [NSString stringWithFormat:@"*** -[%@ %s]: the schema could not be created when opening database: %@", [self class], _cmd, [self filePath]];
142
+ _NSFLog(message);
143
+ if (nil != outError)
144
+ *outError = [NSError errorWithDomain:NSFDomainKey
145
+ code:NSFNanoStoreErrorKey
146
+ userInfo:[NSDictionary dictionaryWithObject:message
147
+ forKey:NSLocalizedFailureReasonErrorKey]];
148
+ [self closeWithError:nil];
149
+ return NO;
150
+ }
151
+
152
+ if ([self _initializePreparedStatementsWithError:outError] == NO) {
153
+ NSString *message = [NSString stringWithFormat:@"*** -[%@ %s]: the SQL statements could not be prepared when opening database: %@", [self class], _cmd, [self filePath]];
154
+ _NSFLog(message);
155
+ [self closeWithError:nil];
156
+ return NO;
157
+ }
158
+
159
+ return YES;
160
+ }
161
+
162
+ - (BOOL)closeWithError:(out NSError **)outError
163
+ {
164
+ BOOL success = [self saveStoreAndReturnError:outError];
165
+ [self _releasePreparedStatements];
166
+ [nanoStoreEngine close];
167
+
168
+ return success;
169
+ }
170
+
171
+ - (BOOL)isClosed
172
+ {
173
+ return ([nanoStoreEngine isDatabaseOpen] == NO);
174
+ }
175
+
176
+ - (NSString*)description
177
+ {
178
+ return [self _nestedDescriptionWithPrefixedSpace:@""];
179
+ }
180
+
181
+ - (BOOL)hasUnsavedChanges
182
+ {
183
+ return ([addedObjects count] > 0);
184
+ }
185
+
186
+ #pragma mark -
187
+
188
+ - (BOOL)addObject:(id <NSFNanoObjectProtocol>)object error:(out NSError **)outError
189
+ {
190
+ NSArray *wrapper = [[NSArray alloc]initWithObjects:object, nil];
191
+ BOOL success = [self addObjectsFromArray:wrapper error:outError];
192
+ return success;
193
+ }
194
+
195
+ - (BOOL)addObjectsFromArray:(NSArray *)someObjects error:(out NSError **)outError
196
+ {
197
+ if (nil == someObjects) {
198
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
199
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: someObjects is nil.", [self class], _cmd]
200
+ userInfo:nil]raise];
201
+ }
202
+
203
+ if ([someObjects count] == 0) {
204
+ if (nil != outError)
205
+ *outError = [NSError errorWithDomain:NSFDomainKey
206
+ code:NSFNanoStoreErrorKey
207
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: ([someObjects count] == 0)", [self class], _cmd]
208
+ forKey:NSLocalizedFailureReasonErrorKey]];
209
+ return NO;
210
+ }
211
+
212
+ // Add the regular objects. For bags, redirect it the saving method.
213
+ NSMutableArray *nonBagObjects = [[NSMutableArray alloc]initWithCapacity:[someObjects count]];
214
+
215
+ for (id object in someObjects) {
216
+ // If it's a bag, make sure the name is unique
217
+ if (YES == [object isKindOfClass:[NSFNanoBag class]]) {
218
+ NSFNanoBag *bag = (NSFNanoBag *)object;
219
+ NSString *bagName = bag.name;
220
+ if (bagName.length > 0) {
221
+ NSFNanoBag *bagWithSameName = [self bagWithName:bagName];
222
+ if (nil != bagWithSameName) {
223
+ if (nil != outError) {
224
+ *outError = [NSError errorWithDomain:NSFDomainKey
225
+ code:NSFNanoStoreErrorKey
226
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: a bag named '%@' already exists.", [self class], _cmd, bagName]
227
+ forKey:NSLocalizedFailureReasonErrorKey]];
228
+
229
+ return NO;
230
+ }
231
+ }
232
+ }
233
+
234
+
235
+ // If it's a bag, process it first by gathering. If it's not dirty, there's no need to save...
236
+ if (YES == [object hasUnsavedChanges]) {
237
+ NSError *error = nil;
238
+
239
+ // Associate the bag to this store
240
+ if (nil == [object store]) {
241
+ [object _setStore:self];
242
+ }
243
+
244
+ if (NO == [object _saveInStore:self error:&error]) {
245
+ [[NSException exceptionWithName:NSFNanoStoreUnableToManipulateStoreException
246
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, [error localizedDescription]]
247
+ userInfo:nil]raise];
248
+ }
249
+ }
250
+ } else {
251
+ if (NO == [(id)object conformsToProtocol:@protocol(NSFNanoObjectProtocol)]) {
252
+ [[NSException exceptionWithName:NSFNonConformingNanoObjectProtocolException
253
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: the object does not conform to NSFNanoObjectProtocol.", [self class], _cmd]
254
+ userInfo:nil]raise];
255
+ }
256
+
257
+ if (nil == [object nanoObjectKey]) {
258
+ [[NSException exceptionWithName:NSFNanoObjectBehaviorException
259
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: unexpected NSFNanoObject behavior. Reason: the object's key is nil.", [self class], _cmd]
260
+ userInfo:nil]raise];
261
+ }
262
+
263
+ [nonBagObjects addObject:object];
264
+ }
265
+ }
266
+
267
+ BOOL success = [self _addObjectsFromArray:nonBagObjects forceSave:NO error:outError];
268
+
269
+
270
+ return success;
271
+ }
272
+
273
+ - (BOOL)removeObject:(id <NSFNanoObjectProtocol>)theObject error:(out NSError **)outError
274
+ {
275
+ NSArray *wrapper = [[NSArray alloc]initWithObjects:theObject, nil];
276
+ BOOL success = [self removeObjectsInArray:wrapper error:outError];
277
+ return success;
278
+ }
279
+
280
+ - (BOOL)removeObjectsWithKeysInArray:(NSArray *)someKeys error:(out NSError **)outError
281
+ {
282
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
283
+ return NO;
284
+
285
+ if (nil == someKeys)
286
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
287
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: someKeys is nil.", [self class], _cmd]
288
+ userInfo:nil]raise];
289
+
290
+ NSUInteger count = [someKeys count];
291
+
292
+ if (0 == count)
293
+ return NO;
294
+
295
+ BOOL transactionStartedHere = [self beginTransactionAndReturnError:nil];
296
+
297
+ NSString *theSQLStatement = [[NSString alloc]initWithFormat:@"CREATE TEMP TABLE %@(x);", NSF_Private_ToDeleteTableKey];
298
+ [nanoStoreEngine executeSQL:theSQLStatement];
299
+
300
+ sqlite3_stmt *statement;
301
+ theSQLStatement = [[NSString alloc]initWithFormat:@"INSERT INTO %@ VALUES (?);", NSF_Private_ToDeleteTableKey];
302
+ BOOL success = [self _prepareSQLite3Statement:&statement theSQLStatement:theSQLStatement];
303
+
304
+ if (success) {
305
+ for (NSString *key in someKeys) {
306
+ int status = sqlite3_reset (statement);
307
+ if (SQLITE_OK != status) {
308
+ break;
309
+ }
310
+
311
+ // Bind and execute the statement...
312
+ status = sqlite3_bind_text ( statement, 1, [key UTF8String], -1, SQLITE_STATIC);
313
+
314
+ // Since we're operating with extended result code support, extract the bits
315
+ // and obtain the regular result code
316
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
317
+
318
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
319
+
320
+ if (SQLITE_OK == status) {
321
+ [self _executeSQLite3StepUsingSQLite3Statement:statement];
322
+ }
323
+ }
324
+ sqlite3_finalize(statement);
325
+ }
326
+
327
+ _NSFLog(@" Before removing the keys to be stored from NSFKeys...");
328
+ theSQLStatement = [[NSString alloc]initWithFormat:@"DELETE FROM %@ WHERE %@ IN (SELECT * FROM %@);", NSFKeys, NSFKey, NSF_Private_ToDeleteTableKey];
329
+ [nanoStoreEngine executeSQL:theSQLStatement];
330
+
331
+ _NSFLog(@" Before removing the keys to be stored from NSFValues...");
332
+ theSQLStatement = [[NSString alloc]initWithFormat:@"DELETE FROM %@ WHERE %@ IN (SELECT * FROM %@);", NSFValues, NSFKey, NSF_Private_ToDeleteTableKey];
333
+ [nanoStoreEngine executeSQL:theSQLStatement];
334
+
335
+ _NSFLog(@" Before DROP TABLE NSF_Private_ToDeleteTableKey...");
336
+ theSQLStatement = [[NSString alloc]initWithFormat:@"DROP TABLE %@;", NSF_Private_ToDeleteTableKey];
337
+ [nanoStoreEngine executeSQL:theSQLStatement];
338
+
339
+ if (transactionStartedHere)
340
+ if ([self commitTransactionAndReturnError:nil] == NO)
341
+ _NSFLog(@" Could not commit the transaction.");
342
+
343
+ return YES;
344
+ }
345
+
346
+ - (BOOL)removeObjectsInArray:(NSArray *)someObjects error:(out NSError **)outError
347
+ {
348
+ NSMutableArray *someKeys = [NSMutableArray array];
349
+
350
+ // Extract the keys from the objects
351
+ for (id object in someObjects) {
352
+ if (NO == [(id)object conformsToProtocol:@protocol(NSFNanoObjectProtocol)]) {
353
+ [[NSException exceptionWithName:NSFNonConformingNanoObjectProtocolException
354
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: the object does not conform to NSFNanoObjectProtocol.", [self class], _cmd]
355
+ userInfo:nil]raise];
356
+ } else {
357
+ NSString *objectKey = [(id)object nanoObjectKey];
358
+ if (nil == objectKey) {
359
+ [[NSException exceptionWithName:NSFNanoObjectBehaviorException
360
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: unexpected NSFNanoObject behavior. Reason: the object's key is nil.", [self class], _cmd]
361
+ userInfo:nil]raise];
362
+ }
363
+ [someKeys addObject:objectKey];
364
+ }
365
+ }
366
+
367
+ return [self removeObjectsWithKeysInArray:someKeys error:outError];
368
+ }
369
+
370
+ #pragma mark -
371
+ #pragma mark Searching
372
+ #pragma mark -
373
+
374
+ - (NSArray *)bags
375
+ {
376
+ NSFNanoSearch *search = [NSFNanoSearch searchWithStore:self];
377
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT NSFKey, NSFPlist, NSFObjectClass FROM NSFKeys WHERE NSFObjectClass = \"%@\"", NSStringFromClass([NSFNanoBag class])];
378
+
379
+ return [[search executeSQL:theSQLStatement returnType:NSFReturnObjects error:nil]allValues];
380
+
381
+ }
382
+
383
+ - (NSFNanoBag *)bagWithName:(NSString *)theName
384
+ {
385
+ NSFNanoSearch *search = [NSFNanoSearch searchWithStore:self];
386
+
387
+ search.attribute = NSF_Private_NSFNanoBag_Name;
388
+ search.match = NSFEqualTo;
389
+ search.value = theName;
390
+
391
+ // Returns a dictionary with the UUID of the object (key) and the NanoObject (value).
392
+ return [[[search searchObjectsWithReturnType:NSFReturnObjects error:nil]allObjects]lastObject];
393
+ }
394
+
395
+ - (NSArray *)bagsWithKeysInArray:(NSArray *)someKeys
396
+ {
397
+ if ([someKeys count] == 0) {
398
+ return [NSArray array];
399
+ }
400
+
401
+ NSFNanoSearch *search = [NSFNanoSearch searchWithStore:self];
402
+ NSString *quotedString = [NSFNanoSearch _quoteStrings:someKeys joiningWithDelimiter:@","];
403
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT NSFKey, NSFPlist, NSFObjectClass FROM NSFKeys WHERE NSFKey IN (%@) AND NSFObjectClass = \"%@\"", quotedString, NSStringFromClass([NSFNanoBag class])];
404
+
405
+ return [[search executeSQL:theSQLStatement returnType:NSFReturnObjects error:nil]allValues];
406
+ }
407
+
408
+ - (NSArray *)bagsContainingObjectWithKey:(NSString *)aKey
409
+ {
410
+ if (nil == aKey) {
411
+ return [NSArray array];
412
+ }
413
+
414
+ NSFNanoSearch *search = [NSFNanoSearch searchWithStore:self];
415
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT NSFKey, NSFPlist, NSFObjectClass FROM NSFKeys WHERE NSFKey IN (SELECT DISTINCT (NSFKEY) FROM NSFValues WHERE NSFValue = \"%@\") AND NSFObjectClass = \"%@\"", aKey, NSStringFromClass([NSFNanoBag class])];
416
+
417
+ return [[search executeSQL:theSQLStatement returnType:NSFReturnObjects error:nil]allValues];
418
+ }
419
+
420
+ - (NSArray *)objectsWithKeysInArray:(NSArray *)someKeys
421
+ {
422
+ if ([someKeys count] == 0) {
423
+ return [NSArray array];
424
+ }
425
+
426
+ NSFNanoSearch *search = [NSFNanoSearch searchWithStore:self];
427
+ NSString *quotedString = [NSFNanoSearch _quoteStrings:someKeys joiningWithDelimiter:@","];
428
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT NSFKey, NSFPlist, NSFObjectClass FROM NSFKeys WHERE NSFKey IN (%@)", quotedString];
429
+
430
+ return [[search executeSQL:theSQLStatement returnType:NSFReturnObjects error:nil]allValues];
431
+ }
432
+
433
+ - (NSArray *)allObjectClasses
434
+ {
435
+ NSFNanoResult *results = [self _executeSQL:@"SELECT DISTINCT(NSFObjectClass) FROM NSFKeys"];
436
+
437
+ return [results valuesForColumn:@"NSFKeys.NSFObjectClass"];
438
+ }
439
+
440
+ - (NSArray *)objectsOfClassNamed:(NSString *)theClassName
441
+ {
442
+ return [self objectsOfClassNamed:theClassName usingSortDescriptors:nil];
443
+ }
444
+
445
+ - (NSArray *)objectsOfClassNamed:(NSString *)theClassName usingSortDescriptors:(NSArray *)theSortDescriptors
446
+ {
447
+ if (nil == theClassName) {
448
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
449
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: the class name cannot be nil.", [self class], _cmd]
450
+ userInfo:nil]raise];
451
+ }
452
+
453
+ NSFNanoSearch *search = [NSFNanoSearch searchWithStore:self];
454
+ search.sort = theSortDescriptors;
455
+
456
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT NSFKey, NSFPlist, NSFObjectClass FROM NSFKeys WHERE NSFObjectClass = \"%@\"", theClassName];
457
+
458
+ if (nil == theSortDescriptors)
459
+ return [[search executeSQL:theSQLStatement returnType:NSFReturnObjects error:nil] allValues];
460
+ else
461
+ return [search executeSQL:theSQLStatement returnType:NSFReturnObjects error:nil];
462
+ }
463
+
464
+ - (long long)countOfObjectsOfClassNamed:(NSString *)theClassName
465
+ {
466
+ if (nil == theClassName) {
467
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
468
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: the class name cannot be nil.", [self class], _cmd]
469
+ userInfo:nil]raise];
470
+ }
471
+
472
+ NSFNanoSearch *search = [NSFNanoSearch searchWithStore:self];
473
+
474
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT count(*) FROM NSFKeys WHERE NSFObjectClass = \"%@\"", theClassName];
475
+ NSFNanoResult *results = [search executeSQL:theSQLStatement];
476
+
477
+ return [[results firstValue]longLongValue];
478
+ }
479
+
480
+ #pragma mark -
481
+ #pragma mark Database Optimizations and Maintenance
482
+ #pragma mark -
483
+
484
+ - (BOOL)beginTransactionAndReturnError:(out NSError **)outError
485
+ {
486
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
487
+ return NO;
488
+
489
+ if ([self _isOurTransaction] == YES)
490
+ return NO;
491
+
492
+ [self _setIsOurTransaction:[[self nanoStoreEngine]beginTransaction]];
493
+
494
+ return [self _isOurTransaction];
495
+ }
496
+
497
+ - (BOOL)commitTransactionAndReturnError:(out NSError **)outError
498
+ {
499
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
500
+ return NO;
501
+
502
+ if ([self _isOurTransaction] == YES) {
503
+ if ([[self nanoStoreEngine]commitTransaction] == YES) {
504
+ [self _setIsOurTransaction:NO];
505
+ return YES;
506
+ }
507
+ }
508
+
509
+ return NO;
510
+ }
511
+
512
+ - (BOOL)rollbackTransactionAndReturnError:(out NSError **)outError
513
+ {
514
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
515
+ return NO;
516
+
517
+ if ([self _isOurTransaction] == YES) {
518
+ [[self nanoStoreEngine]rollbackTransaction];
519
+ [self _setIsOurTransaction:NO];
520
+ return YES;
521
+ }
522
+
523
+ return NO;
524
+ }
525
+
526
+ #pragma mark -
527
+
528
+ // ----------------------------------------------
529
+ // Store/save unsaved objects
530
+ // ----------------------------------------------
531
+
532
+ - (BOOL)saveStoreAndReturnError:(out NSError **)outError
533
+ {
534
+ // We are really not saving anything new, just indicating that we should commit the unsaved changes.
535
+ if (NO == self.hasUnsavedChanges) {
536
+ return YES;
537
+ }
538
+
539
+ return [self _addObjectsFromArray:[NSArray array] forceSave:YES error:outError];
540
+ }
541
+
542
+ - (void)discardUnsavedChanges
543
+ {
544
+ [addedObjects removeAllObjects];
545
+ }
546
+
547
+ // ----------------------------------------------
548
+ // Clearing the store
549
+ // ----------------------------------------------
550
+
551
+ - (BOOL)removeAllObjectsFromStoreAndReturnError:(out NSError **)outError
552
+ {
553
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
554
+ return NO;
555
+
556
+ NSError *resultKeys = [[self _executeSQL:[NSString stringWithFormat:@"DROP TABLE %@", NSFKeys]]error];
557
+ NSError *resultValues = [[self _executeSQL:[NSString stringWithFormat:@"DROP TABLE %@", NSFValues]]error];
558
+
559
+ [self _setupCachingSchema];
560
+
561
+ [self rebuildIndexesAndReturnError:nil];
562
+
563
+ if ((nil != resultKeys) || (nil != resultValues)) {
564
+ if (nil != outError) {
565
+ *outError = [NSError errorWithDomain:NSFDomainKey
566
+ code:NSFNanoStoreErrorKey
567
+ userInfo:[NSDictionary dictionaryWithObject:@"Could not remove all objects from the database."
568
+ forKey:NSLocalizedDescriptionKey]];
569
+ }
570
+ return NO;
571
+ }
572
+
573
+ return YES;
574
+ }
575
+
576
+ // ----------------------------------------------
577
+ // Compacting the database
578
+ // ----------------------------------------------
579
+
580
+ - (BOOL)compactStoreAndReturnError:(out NSError **)outError
581
+ {
582
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
583
+ return NO;
584
+
585
+ return [[self nanoStoreEngine]compact];
586
+ }
587
+
588
+ - (BOOL)clearIndexesAndReturnError:(out NSError **)outError
589
+ {
590
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
591
+ return NO;
592
+
593
+ NSArray *indexes = [[self nanoStoreEngine]indexes];
594
+
595
+ _NSFLog(@"Before clearIndexes...");
596
+ NSDate *startDate = [NSDate date];
597
+
598
+ for (NSString *index in indexes)
599
+ [[self nanoStoreEngine]dropIndex:index];
600
+
601
+ NSTimeInterval seconds = [[NSDate date]timeIntervalSinceDate:startDate];
602
+ _NSFLog(@"Done. Clearing the indexes took %.3f seconds", seconds);
603
+
604
+ return YES;
605
+ }
606
+
607
+ - (BOOL)rebuildIndexesAndReturnError:(out NSError **)outError
608
+ {
609
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
610
+ return NO;
611
+
612
+ // Force the indexes to be dropped
613
+ [self clearIndexesAndReturnError:nil];
614
+
615
+ _NSFLog(@"Before rebuildIndexes...");
616
+ NSDate *startDate = [NSDate date];
617
+
618
+ _NSFLog(@" [[self nanoStoreEngine]createIndexForColumn: NSFKey table: NSFValues isUnique:NO]: %@", [[self nanoStoreEngine]createIndexForColumn:NSFKey table:NSFValues isUnique:NO] ? @"YES" : @"NO");
619
+ _NSFLog(@" [[self nanoStoreEngine]createIndexForColumn: NSFAttribute table: NSFValues isUnique:NO]: %@", [[self nanoStoreEngine]createIndexForColumn:NSFAttribute table:NSFValues isUnique:NO] ? @"YES" : @"NO");
620
+ _NSFLog(@" [[self nanoStoreEngine]createIndexForColumn: NSFValue table: NSFValues isUnique:NO]: %@", [[self nanoStoreEngine]createIndexForColumn:NSFValue table:NSFValues isUnique:NO] ? @"YES" : @"NO");
621
+
622
+ _NSFLog(@" [[self nanoStoreEngine]createIndexForColumn: NSFKey table: NSFKeys isUnique:YES]: %@", [[self nanoStoreEngine]createIndexForColumn:NSFKey table:NSFKeys isUnique:YES] ? @"YES" : @"NO");
623
+ _NSFLog(@" [[self nanoStoreEngine]createIndexForColumn: NSFCalendarDate table: NSFKeys isUnique:NO]: %@", [[self nanoStoreEngine]createIndexForColumn:NSFCalendarDate table:NSFKeys isUnique:NO] ? @"YES" : @"NO");
624
+ _NSFLog(@" [[self nanoStoreEngine]createIndexForColumn: NSFObjectClass table: NSFKeys isUnique:NO]: %@", [[self nanoStoreEngine]createIndexForColumn:NSFObjectClass table:NSFKeys isUnique:NO] ? @"YES" : @"NO");
625
+
626
+ NSTimeInterval seconds = [[NSDate date]timeIntervalSinceDate:startDate];
627
+ _NSFLog(@"Done. Rebuilding the indexes took %.3f seconds", seconds);
628
+
629
+ return YES;
630
+ }
631
+
632
+ - (BOOL)saveStoreToDirectoryAtPath:(NSString *)path compactDatabase:(BOOL)compact error:(out NSError **)outError
633
+ {
634
+ if (nil == path)
635
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
636
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: path is nil.", [self class], _cmd]
637
+ userInfo:nil]raise];
638
+
639
+ // Make sure we've expanded the tilde
640
+ path = [path stringByExpandingTildeInPath];
641
+
642
+ if ([self _checkNanoStoreIsReadyAndReturnError:outError] == NO)
643
+ return NO;
644
+
645
+ if ([[self nanoStoreEngine]isTransactionActive]) {
646
+ if (nil != outError)
647
+ *outError = [NSError errorWithDomain:NSFDomainKey
648
+ code:NSFNanoStoreErrorKey
649
+ userInfo:[NSDictionary dictionaryWithObject:@"Cannot backup store. A transaction is still open."
650
+ forKey:NSLocalizedDescriptionKey]];
651
+ return NO;
652
+ }
653
+
654
+ if ([[self filePath]isEqualToString:NSFMemoryDatabase] == YES) {
655
+ return [self _backupMemoryStoreToDirectoryAtPath:path extension:nil compact:compact error:outError];
656
+ } else {
657
+ return [self _backupFileStoreToDirectoryAtPath:path extension:nil compact:compact error:outError];
658
+ }
659
+
660
+ return NO;
661
+ }
662
+
663
+ #pragma mark - Private Methods
664
+ #pragma mark -
665
+
666
+ /** \cond */
667
+
668
+ + (NSFNanoStore *)_debug
669
+ {
670
+ return [NSFNanoStore createStoreWithType:NSFPersistentStoreType path:[@"~/Desktop/NanoStoreDebug.db" stringByExpandingTildeInPath]];
671
+ }
672
+
673
+ - (NSFNanoResult *)_executeSQL:(NSString *)theSQLStatement
674
+ {
675
+ if (nil == theSQLStatement)
676
+ return nil;
677
+
678
+ return [[self nanoStoreEngine]executeSQL:theSQLStatement];
679
+ }
680
+
681
+ - (BOOL)_initializePreparedStatementsWithError:(out NSError **)outError
682
+ {
683
+ BOOL hasInitializationSucceeded = YES;
684
+
685
+ if (NULL == _storeValuesStatement) {
686
+ NSString *theSQLStatement = [[NSString alloc]initWithFormat:@"INSERT INTO %@(%@, %@, %@, %@) VALUES (?,?,?,?);", NSFValues, NSFKey, NSFAttribute, NSFValue, NSFDatatype];
687
+ hasInitializationSucceeded = [self _prepareSQLite3Statement:&_storeValuesStatement theSQLStatement:theSQLStatement];
688
+
689
+ if ((nil != outError) && (NO == hasInitializationSucceeded)) {
690
+ *outError = [NSError errorWithDomain:NSFDomainKey
691
+ code:NSFNanoStoreErrorKey
692
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: failed to prepare _storeValuesStatement.", [self class], _cmd]
693
+ forKey:NSLocalizedFailureReasonErrorKey]];
694
+ }
695
+ }
696
+
697
+ if ((NULL == _storeKeysStatement) && (YES == hasInitializationSucceeded)) {
698
+ NSString *theSQLStatement = [[NSString alloc]initWithFormat:@"INSERT INTO %@(%@, %@, %@, %@) VALUES (?,?,?,?);", NSFKeys, NSFKey, NSFPlist, NSFCalendarDate, NSFObjectClass];
699
+ hasInitializationSucceeded = [self _prepareSQLite3Statement:&_storeKeysStatement theSQLStatement:theSQLStatement];
700
+
701
+ if ((nil != outError) && (NO == hasInitializationSucceeded)) {
702
+ *outError = [NSError errorWithDomain:NSFDomainKey
703
+ code:NSFNanoStoreErrorKey
704
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: failed to prepare _storeKeysStatement.", [self class], _cmd]
705
+ forKey:NSLocalizedFailureReasonErrorKey]];
706
+ }
707
+ }
708
+
709
+ return hasInitializationSucceeded;
710
+ }
711
+
712
+ - (void)_releasePreparedStatements
713
+ {
714
+ if (_storeValuesStatement != NULL) { sqlite3_finalize(_storeValuesStatement);_storeValuesStatement = NULL; }
715
+ if (_storeKeysStatement != NULL) { sqlite3_finalize(_storeKeysStatement);_storeKeysStatement = NULL; }
716
+ }
717
+
718
+ - (void)_setIsOurTransaction:(BOOL)value
719
+ {
720
+ if (_isOurTransaction != value) {
721
+ _isOurTransaction = value;
722
+ }
723
+ }
724
+
725
+ - (BOOL)_isOurTransaction
726
+ {
727
+ return _isOurTransaction;
728
+ }
729
+
730
+ - (NSString*)_nestedDescriptionWithPrefixedSpace:(NSString *)prefixedSpace
731
+ {
732
+ if (nil == prefixedSpace) {
733
+ prefixedSpace = @"";
734
+ }
735
+
736
+ NSMutableString *description = [NSMutableString string];
737
+ [description appendString:@"\n"];
738
+ [description appendString:[NSString stringWithFormat:@"%@NanoStore address : 0x%x\n", prefixedSpace, self]];
739
+ [description appendString:[NSString stringWithFormat:@"%@Is our transaction? : %@\n", prefixedSpace, (_isOurTransaction ? @"Yes" : @"No")]];
740
+ [description appendString:[NSString stringWithFormat:@"%@Save interval : %ld\n", prefixedSpace, (saveInterval == 0 ? 1 : saveInterval)]];
741
+ [description appendString:[NSString stringWithFormat:@"%@Engine : %@\n", prefixedSpace, [nanoStoreEngine NSFP_nestedDescriptionWithPrefixedSpace:@" "]]];
742
+
743
+ return description;
744
+ }
745
+
746
+ - (BOOL)_checkNanoStoreIsReadyAndReturnError:(out NSError **)outError
747
+ {
748
+ if (nil == [self nanoStoreEngine]) {
749
+ if (nil != outError)
750
+ *outError = [NSError errorWithDomain:NSFDomainKey
751
+ code:NSFNanoStoreErrorKey
752
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: the NSF store has not been set.", [self class], _cmd]
753
+ forKey:NSLocalizedFailureReasonErrorKey]];
754
+ return NO;
755
+ }
756
+
757
+ if ([[self nanoStoreEngine]isDatabaseOpen] == NO) {
758
+ if (nil != outError)
759
+ *outError = [NSError errorWithDomain:NSFDomainKey
760
+ code:NSFNanoStoreErrorKey
761
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: the store is not open.", [self class], _cmd]
762
+ forKey:NSLocalizedFailureReasonErrorKey]];
763
+ return NO;
764
+ }
765
+
766
+ return YES;
767
+ }
768
+
769
+ - (BOOL)_setupCachingSchema
770
+ {
771
+ NSString *theSQLStatement;
772
+ BOOL success;
773
+ NSArray *tables = [[self nanoStoreEngine]tables];
774
+ NSString *rowUIDDatatype = NSFStringFromNanoDataType(NSFNanoTypeRowUID);
775
+ NSString *stringDatatype = NSFStringFromNanoDataType(NSFNanoTypeString);
776
+ NSString *dateDatatype = NSFStringFromNanoDataType(NSFNanoTypeDate);
777
+
778
+ // Setup the Values table
779
+ if ([tables containsObject:NSFValues] == NO) {
780
+ theSQLStatement = [NSString stringWithFormat:@"CREATE TABLE %@(ROWID INTEGER PRIMARY KEY, %@ TEXT, %@ TEXT, %@ NONE, %@ TEXT);", NSFValues, NSFKey, NSFAttribute, NSFValue, NSFDatatype];
781
+ success = (nil == [[[self nanoStoreEngine]executeSQL:theSQLStatement]error]);
782
+ if (NO == success)
783
+ return NO;
784
+
785
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFValues, NSFRowIDColumnName, rowUIDDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
786
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFValues, NSFKey, stringDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
787
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFValues, NSFAttribute, stringDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
788
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFValues, NSFValue, stringDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
789
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFValues, NSFDatatype, stringDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
790
+ }
791
+
792
+ // Setup the Plist table
793
+ if ([tables containsObject:NSFKeys] == NO) {
794
+ theSQLStatement = [NSString stringWithFormat:@"CREATE TABLE %@(ROWID INTEGER PRIMARY KEY, %@ TEXT, %@ TEXT, %@ TEXT, %@ TEXT);", NSFKeys, NSFKey, NSFPlist, NSFCalendarDate, NSFObjectClass];
795
+ success = (nil == [[[self nanoStoreEngine]executeSQL:theSQLStatement]error]);
796
+ if (NO == success)
797
+ return NO;
798
+
799
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFKeys, NSFRowIDColumnName, rowUIDDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
800
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFKeys, NSFKey, stringDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
801
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFKeys, NSFPlist, stringDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
802
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFKeys, dateDatatype, dateDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
803
+ [[self nanoStoreEngine]NSFP_insertStringValues:[NSArray arrayWithObjects:NSFKeys, NSFObjectClass, stringDatatype, nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable];
804
+ }
805
+
806
+ return YES;
807
+ }
808
+
809
+ - (BOOL)_storeDictionary:(NSDictionary *)someInfo forKey:(NSString *)aKey forClassNamed:(NSString *)className usingSQLite3Statement:(sqlite3_stmt *)storeValuesStatement error:(out NSError **)outError
810
+ {
811
+ if (nil == someInfo)
812
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
813
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: someInfo is nil.", [self class], _cmd]
814
+ userInfo:nil]raise];
815
+
816
+ if (nil == aKey)
817
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
818
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aKey is nil.", [self class], _cmd]
819
+ userInfo:nil]raise];
820
+
821
+ if (nil == storeValuesStatement)
822
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
823
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aStatement is NULL.", [self class], _cmd]
824
+ userInfo:nil]raise];
825
+
826
+ NSRange range = [aKey rangeOfString:@"."];
827
+ if (NSNotFound != range.location)
828
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
829
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aKey cannot contain a period ('.')", [self class], _cmd]
830
+ userInfo:nil]raise];
831
+
832
+ NSArray *keys = [someInfo allKeys];
833
+ for (NSString *key in keys) {
834
+ range = [key rangeOfString:@"."];
835
+ if (NSNotFound != range.location)
836
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
837
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: the keys of the dictionary cannot contain a period ('.')", [self class], _cmd]
838
+ userInfo:nil]raise];
839
+ }
840
+
841
+ const char *aKeyUTF8 = [aKey UTF8String];
842
+ BOOL success = YES;
843
+
844
+ // Flatten the dictionary
845
+ {
846
+ NSMutableArray *flattenedKeys = [NSMutableArray new];
847
+ NSMutableArray *flattenedValues = [NSMutableArray new];
848
+
849
+ @autoreleasepool {
850
+ [self _flattenCollection:someInfo keys:&flattenedKeys values:&flattenedValues];
851
+
852
+ NSUInteger i, count = [flattenedKeys count];
853
+
854
+ success = NO;
855
+
856
+ for (i = 0; i < count; i++) {
857
+ NSString *attribute = [flattenedKeys objectAtIndex:i];
858
+ id value = [flattenedValues objectAtIndex:i];
859
+
860
+ // Reset, as required by SQLite...
861
+ int status = sqlite3_reset (storeValuesStatement);
862
+
863
+ // Since we're operating with extended result code support, extract the bits
864
+ // and obtain the regular result code
865
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
866
+
867
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
868
+
869
+ if (SQLITE_OK == status) {
870
+
871
+ // Bind and execute the statement...
872
+ BOOL resultBindKey = (sqlite3_bind_text (storeValuesStatement, 1, aKeyUTF8, -1, SQLITE_STATIC) == SQLITE_OK);
873
+ BOOL resultBindAttribute = (sqlite3_bind_text (storeValuesStatement, 2, [attribute UTF8String], -1, SQLITE_STATIC) == SQLITE_OK);
874
+
875
+ // Take advantage of manifest typing
876
+ // Branch the type of bind based on the type to be stored: NSString, NSData, NSDate or NSNumber
877
+ NSFNanoDatatype valueDataType = [self _NSFDatatypeOfObject:value];
878
+ BOOL resultBindValue = NO;
879
+
880
+ switch (valueDataType) {
881
+ case NSFNanoTypeData:
882
+ resultBindValue = (sqlite3_bind_blob(storeValuesStatement, 3, [value bytes], [value length], NULL) == SQLITE_OK);
883
+ break;
884
+ case NSFNanoTypeString:
885
+ case NSFNanoTypeDate:
886
+ resultBindValue = (sqlite3_bind_text (storeValuesStatement, 3, [[self _stringFromValue:value]UTF8String], -1, SQLITE_STATIC) == SQLITE_OK);
887
+ break;
888
+ break;
889
+ case NSFNanoTypeNumber:
890
+ resultBindValue = (sqlite3_bind_double (storeValuesStatement, 3, [value doubleValue]) == SQLITE_OK);
891
+ break;
892
+ default:
893
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
894
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatype %@ cannot be stored because its class type is unknown.", [self class], _cmd, [value class]]
895
+ userInfo:nil]raise];
896
+ break;
897
+ }
898
+
899
+ // Store the element's datatype so we can recreate it later on when we read it back from the store...
900
+ NSString *valueDatatypeString = NSFStringFromNanoDataType(valueDataType);
901
+ BOOL resultBindDatatype = (sqlite3_bind_text (storeValuesStatement, 4, [valueDatatypeString UTF8String], -1, SQLITE_STATIC) == SQLITE_OK);
902
+
903
+ success = (resultBindKey && resultBindAttribute && resultBindValue && resultBindDatatype);
904
+ if (success) {
905
+ [self _executeSQLite3StepUsingSQLite3Statement:storeValuesStatement];
906
+ }
907
+ }
908
+ }
909
+
910
+ }
911
+ }
912
+
913
+ if (YES == success) {
914
+ // Save the Key and its Plist (if it applies)
915
+ NSString *dictXML = nil;
916
+ NSString *errorString = nil;
917
+
918
+ NSData *dictData = [NSPropertyListSerialization dataFromPropertyList:someInfo format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorString];
919
+ if (nil != errorString) {
920
+ NSLog(@" Dictionary: %@", someInfo);
921
+ NSLog(@"*** -[%@ %@]: [NSPropertyListSerialization dataFromPropertyList] failure. %@", [self class], NSStringFromSelector(_cmd), errorString);
922
+ NSLog(@" Dictionary info: %@", someInfo);
923
+ success = NO;
924
+ } else {
925
+ if ([dictData length] > 0)
926
+ dictXML = [[NSString alloc]initWithBytes:[dictData bytes]length:[dictData length]encoding:NSUTF8StringEncoding];
927
+ else
928
+ dictXML = @"";
929
+
930
+ if (nil == dictXML) {
931
+ if (nil != outError)
932
+ *outError = [NSError errorWithDomain:NSFDomainKey
933
+ code:NSF_Private_InvalidParameterDataCodeKey
934
+ userInfo:[NSDictionary dictionaryWithObject:@"Couldn't serialize the object: %@"
935
+ forKey:NSLocalizedDescriptionKey]];
936
+ success = NO;
937
+ } else {
938
+ // Reset, as required by SQLite...
939
+ int status = sqlite3_reset (_storeKeysStatement);
940
+
941
+ // Since we're operating with extended result code support, extract the bits
942
+ // and obtain the regular result code
943
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
944
+
945
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
946
+
947
+ // Bind and execute the statement...
948
+ if (SQLITE_OK == status) {
949
+
950
+ BOOL resultBindKey = (sqlite3_bind_text (_storeKeysStatement, 1, aKeyUTF8, -1, SQLITE_STATIC) == SQLITE_OK);
951
+ BOOL resultBindPlist = (sqlite3_bind_text (_storeKeysStatement, 2, [dictXML UTF8String], -1, SQLITE_STATIC) == SQLITE_OK);
952
+ BOOL resultBindCalendarDate = (sqlite3_bind_text (_storeKeysStatement, 3, [[NSFNanoStore _calendarDateToString:[NSDate date]]UTF8String], -1, SQLITE_STATIC) == SQLITE_OK);
953
+ BOOL resultBindClass = (sqlite3_bind_text (_storeKeysStatement, 4, [className UTF8String], -1, SQLITE_STATIC) == SQLITE_OK);
954
+
955
+ success = (resultBindKey && resultBindPlist && resultBindCalendarDate && resultBindClass);
956
+ if (success) {
957
+ [self _executeSQLite3StepUsingSQLite3Statement:_storeKeysStatement];
958
+ }
959
+ }
960
+ }
961
+ }
962
+ }
963
+
964
+ return success;
965
+ }
966
+
967
+ - (NSFNanoDatatype)_NSFDatatypeOfObject:(id)value
968
+ {
969
+ NSFNanoDatatype type = NSFNanoTypeUnknown;
970
+
971
+ if ([value isKindOfClass:[NSString class]])
972
+ return NSFNanoTypeString;
973
+ else if ([value isKindOfClass:[NSNumber class]])
974
+ return NSFNanoTypeNumber;
975
+ else if ([value isKindOfClass:[NSDate class]])
976
+ return NSFNanoTypeDate;
977
+ else if ([value isKindOfClass:[NSData class]])
978
+ return NSFNanoTypeData;
979
+
980
+ return type;
981
+ }
982
+
983
+ - (NSString *)_stringFromValue:(id)aValue
984
+ {
985
+ if (nil != aValue) {
986
+ if ([aValue isKindOfClass:[NSString class]]) {
987
+ return aValue;
988
+ } else if ([aValue isKindOfClass:[NSDate class]]) {
989
+ return [NSFNanoStore _calendarDateToString:aValue];
990
+ } else if ([aValue respondsToSelector:@selector(stringValue)]) {
991
+ return [aValue stringValue];
992
+ } else if ([aValue respondsToSelector:@selector(description)]) {
993
+ return [aValue description];
994
+ } else {
995
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
996
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatype %@ doesn't respond to selector 'stringValue' or 'description'.", [self class], _cmd, [aValue class]]
997
+ userInfo:nil]raise];
998
+ }
999
+ }
1000
+
1001
+ return [[NSNull null]description];
1002
+ }
1003
+
1004
+ + (NSString *)_calendarDateToString:(NSDate *)aDate
1005
+ {
1006
+ static NSDateFormatter *__sNSFNanoStoreDateFormatter = nil;
1007
+ if (nil == __sNSFNanoStoreDateFormatter) {
1008
+ __sNSFNanoStoreDateFormatter = [NSDateFormatter new];
1009
+ [__sNSFNanoStoreDateFormatter setDateStyle:NSDateFormatterShortStyle];
1010
+ [__sNSFNanoStoreDateFormatter setTimeStyle:NSDateFormatterFullStyle];
1011
+ [__sNSFNanoStoreDateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"];
1012
+ }
1013
+
1014
+ if (nil == aDate)
1015
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1016
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aDate is nil.", [self class], _cmd]
1017
+ userInfo:nil]raise];
1018
+
1019
+ return [__sNSFNanoStoreDateFormatter stringFromDate:aDate];
1020
+ }
1021
+
1022
+ - (void)_flattenCollection:(NSDictionary *)info keys:(NSMutableArray **)flattenedKeys values:(NSMutableArray **)flattenedValues
1023
+ {
1024
+ NSMutableArray *keyPath = [NSMutableArray new];
1025
+ [self _flattenCollection:info keyPath:&keyPath keys:flattenedKeys values:flattenedValues];
1026
+ }
1027
+
1028
+ - (void)_flattenCollection:(id)someObject keyPath:(NSMutableArray **)aKeyPath keys:(NSMutableArray **)flattenedKeys values:(NSMutableArray **)flattenedValues
1029
+ {
1030
+ BOOL isOfTypeCollection = ([someObject isKindOfClass:[NSDictionary class]] || [someObject isKindOfClass:[NSArray class]]);
1031
+
1032
+ if (NO == isOfTypeCollection) {
1033
+ if (nil != flattenedKeys) {
1034
+ NSString *keyPath = [*aKeyPath componentsJoinedByString:@"."];
1035
+ [*flattenedKeys addObject:keyPath];
1036
+ [*flattenedValues addObject:someObject];
1037
+ }
1038
+ } else {
1039
+ if ([someObject isKindOfClass:[NSDictionary class]]) {
1040
+ for (NSString *key in someObject) {
1041
+ [*aKeyPath addObject:key];
1042
+ [self _flattenCollection:[someObject objectForKey:key] keyPath:aKeyPath keys:flattenedKeys values:flattenedValues];
1043
+ [*aKeyPath removeLastObject];
1044
+ }
1045
+ } else if ([someObject isKindOfClass:[NSArray class]]) {
1046
+ for (id anObject in someObject) {
1047
+ [self _flattenCollection:anObject keyPath:aKeyPath keys:flattenedKeys values:flattenedValues];
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ - (BOOL)_prepareSQLite3Statement:(sqlite3_stmt **)aStatement theSQLStatement:(NSString *)aSQLQuery
1054
+ {
1055
+ // Prepare SQLite's VM. It's placed here so we can speed up stores...
1056
+ sqlite3* sqliteDatabase = [[self nanoStoreEngine]sqlite];
1057
+ int status = SQLITE_OK;
1058
+ BOOL continueLooping = YES;
1059
+ const char *query = [aSQLQuery UTF8String];
1060
+
1061
+ do {
1062
+ status = sqlite3_prepare_v2(sqliteDatabase, query, (int)strlen(query), aStatement, &query);
1063
+
1064
+ // Since we're operating with extended result code support, extract the bits
1065
+ // and obtain the regular result code
1066
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
1067
+
1068
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
1069
+
1070
+ continueLooping = ((SQLITE_LOCKED == status) || (SQLITE_BUSY == status));
1071
+ } while (continueLooping);
1072
+
1073
+ return (SQLITE_OK == status);
1074
+ }
1075
+
1076
+ - (void)_executeSQLite3StepUsingSQLite3Statement:(sqlite3_stmt *)aStatement
1077
+ {
1078
+ BOOL waitingForRow = YES;
1079
+
1080
+ do {
1081
+ int status = sqlite3_step(aStatement);
1082
+
1083
+ // Since we're operating with extended result code support, extract the bits
1084
+ // and obtain the regular result code
1085
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
1086
+
1087
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
1088
+
1089
+ switch (status) {
1090
+ case SQLITE_BUSY:
1091
+ break;
1092
+ case SQLITE_OK:
1093
+ case SQLITE_DONE:
1094
+ waitingForRow = NO;
1095
+ break;
1096
+ case SQLITE_ROW:
1097
+ waitingForRow = NO;
1098
+ break;
1099
+ default:
1100
+ waitingForRow = NO;
1101
+ break;
1102
+ }
1103
+ } while (waitingForRow);
1104
+ }
1105
+
1106
+ - (BOOL)_addObjectsFromArray:(NSArray *)someObjects forceSave:(BOOL)forceSave error:(out NSError **)outError
1107
+ {
1108
+ // Collect the objects
1109
+ [addedObjects addObjectsFromArray:someObjects];
1110
+
1111
+ // No need to continue if there's nothing to be saved
1112
+ NSUInteger unsavedObjectsCount = [addedObjects count];
1113
+ if (0 == unsavedObjectsCount) {
1114
+ return YES;
1115
+ }
1116
+
1117
+ if ((YES == forceSave) || (0 == unsavedObjectsCount % saveInterval)) {
1118
+ NSDate *startStoringDate = [NSDate date];
1119
+
1120
+ NSDate *startRemovingDate = [NSDate date];
1121
+ _NSFLog(@" Removing the objects to be stored...");
1122
+ NSMutableSet *keys = [NSMutableSet new];
1123
+ NSInteger i = unsavedObjectsCount;
1124
+
1125
+ // Remove all objects non conforming with the NSFNanoObjectProtocol
1126
+ while ( i-- ) {
1127
+ id object = [addedObjects objectAtIndex:i];
1128
+ if (NO == [object conformsToProtocol:@protocol(NSFNanoObjectProtocol)]) {
1129
+ [addedObjects removeObjectAtIndex:i];
1130
+ i--;
1131
+ continue;
1132
+ }
1133
+
1134
+ NSString *objectKey = [(id)object nanoObjectKey];
1135
+ if (nil == objectKey) {
1136
+ [[NSException exceptionWithName:NSFNanoObjectBehaviorException
1137
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: unexpected NSFNanoObject behavior. Reason: the object's key is nil.", [self class], _cmd]
1138
+ userInfo:nil]raise];
1139
+ }
1140
+ [keys addObject:objectKey];
1141
+ }
1142
+
1143
+ // Recalculate how many elements we have left
1144
+ unsavedObjectsCount = [addedObjects count];
1145
+
1146
+ if (unsavedObjectsCount > 0) {
1147
+ if (NO == [self removeObjectsWithKeysInArray:[keys allObjects] error:outError]) {
1148
+ [[NSException exceptionWithName:NSFNanoStoreUnableToManipulateStoreException
1149
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, [*outError localizedDescription]]
1150
+ userInfo:nil]raise];
1151
+ }
1152
+ }
1153
+
1154
+ NSTimeInterval secondsRemoving = [[NSDate date]timeIntervalSinceDate:startRemovingDate];
1155
+ _NSFLog(@" Done. Removing the objects took %.3f seconds", secondsRemoving);
1156
+
1157
+ // Store the objects...
1158
+ BOOL transactionStartedHere = [self beginTransactionAndReturnError:nil];
1159
+
1160
+ _NSFLog(@" Storing %ld objects...", unsavedObjectsCount);
1161
+
1162
+ // Reset the default save interval if needed...
1163
+ if (0 == saveInterval) {
1164
+ self.saveInterval = 1;
1165
+ }
1166
+
1167
+ for (id object in addedObjects) {
1168
+ @autoreleasepool {
1169
+ // If the object was originally created by storing a class not recognized by this process, honor it and store it with the right class string.
1170
+ NSString *className = nil;
1171
+ if (YES == [object respondsToSelector:@selector(originalClassString)]) {
1172
+ className = [object originalClassString];
1173
+ }
1174
+
1175
+ // Otherwise, just save the class name of the object being stored
1176
+ if (nil == className) {
1177
+ className = NSStringFromClass([object class]);
1178
+ }
1179
+
1180
+ if (NO == [self _storeDictionary:[object nanoObjectDictionaryRepresentation] forKey:[(id)object nanoObjectKey] forClassNamed:className usingSQLite3Statement:_storeValuesStatement error:outError]) {
1181
+ [[NSException exceptionWithName:NSFNanoStoreUnableToManipulateStoreException
1182
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, [*outError localizedDescription]]
1183
+ userInfo:nil]raise];
1184
+ }
1185
+
1186
+ i++;
1187
+
1188
+ // Commit every 'saveInterval' interations...
1189
+ if ((0 == i % self.saveInterval) && transactionStartedHere) {
1190
+ if (NO == [self commitTransactionAndReturnError:outError]) {
1191
+ [[NSException exceptionWithName:NSFNanoStoreUnableToManipulateStoreException
1192
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, [*outError localizedDescription]]
1193
+ userInfo:nil]raise];
1194
+ }
1195
+
1196
+ if (YES == transactionStartedHere) {
1197
+ transactionStartedHere = [self beginTransactionAndReturnError:outError];
1198
+ if (NO == transactionStartedHere) {
1199
+ [[NSException exceptionWithName:NSFNanoStoreUnableToManipulateStoreException
1200
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, [*outError localizedDescription]]
1201
+ userInfo:nil]raise];
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+ }
1207
+
1208
+ // Commit the changes
1209
+ if (transactionStartedHere) {
1210
+ if (NO == [self commitTransactionAndReturnError:outError]) {
1211
+ [[NSException exceptionWithName:NSFNanoStoreUnableToManipulateStoreException
1212
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, [*outError localizedDescription]]
1213
+ userInfo:nil]raise];
1214
+ }
1215
+ }
1216
+
1217
+ NSTimeInterval secondsStoring = [[NSDate date]timeIntervalSinceDate:startStoringDate];
1218
+ double ratio = unsavedObjectsCount/secondsStoring;
1219
+ _NSFLog(@" Done. Storing the objects took %.3f seconds (%.0f keys/sec.)", secondsStoring, ratio);
1220
+
1221
+ [addedObjects removeAllObjects];
1222
+ }
1223
+
1224
+ return YES;
1225
+ }
1226
+
1227
+ + (NSDictionary *)_defaultTestData
1228
+ {
1229
+ NSArray *dishesInfo = [NSArray arrayWithObject:@"Cassoulet"];
1230
+ NSDictionary *citiesInfo = [NSDictionary dictionaryWithObjectsAndKeys:
1231
+ @"Bouillabaisse", @"Marseille",
1232
+ dishesInfo, @"Nice",
1233
+ @"Good", @"Rating",
1234
+ nil, nil];
1235
+ NSDictionary *countriesInfo = [NSDictionary dictionaryWithObjectsAndKeys:
1236
+ @"Barcelona", @"Spain",
1237
+ @"San Francisco", @"USA",
1238
+ citiesInfo, @"France",
1239
+ @"Very Good", @"Rating",
1240
+ nil, nil];
1241
+ NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
1242
+ @"Tito", @"FirstName",
1243
+ @"Ciuro", @"LastName",
1244
+ countriesInfo, @"Countries",
1245
+ [NSNumber numberWithUnsignedInt:(arc4random() % 32767) + 1], @"SomeNumber",
1246
+ @"To be decided", @"Rating",
1247
+ nil, nil];
1248
+
1249
+ return info;
1250
+ }
1251
+
1252
+ // ----------------------------------------------
1253
+ // Backup the store to a specific location
1254
+ // ----------------------------------------------
1255
+
1256
+ - (BOOL)_backupFileStoreToDirectoryAtPath:(NSString *)backupPath extension:(NSString *)anExtension compact:(BOOL)flag error:(out NSError **)outError
1257
+ {
1258
+ NSString *filePath = [self filePath];
1259
+ if ((anExtension != nil) && (NO == [backupPath hasSuffix:anExtension]))
1260
+ backupPath = [NSString stringWithFormat:@"%@.%@", backupPath, anExtension];
1261
+
1262
+ // Make sure we the destination path is not the same as the source!
1263
+ if (YES == [filePath isEqualToString:backupPath]) {
1264
+ if (nil != outError)
1265
+ *outError = [NSError errorWithDomain:NSFDomainKey
1266
+ code:NSFNanoStoreErrorKey
1267
+ userInfo:[NSDictionary dictionaryWithObject:@"Cannot backup store. The source and destination directories are the same."
1268
+ forKey:NSLocalizedDescriptionKey]];
1269
+ return NO;
1270
+ }
1271
+
1272
+ NSFileManager *fm = [NSFileManager defaultManager];
1273
+ BOOL destinationLocationIsClear = YES;
1274
+
1275
+ if (YES == [fm fileExistsAtPath:backupPath]) {
1276
+ destinationLocationIsClear = [fm removeItemAtPath:backupPath error:nil];
1277
+ if (NO == destinationLocationIsClear) {
1278
+ if (nil != outError)
1279
+ *outError = [NSError errorWithDomain:NSFDomainKey
1280
+ code:NSF_Private_MacOSXErrorCodeKey
1281
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Existing file couldn't be removed in path: %@. Backup cannot proceed.", backupPath]
1282
+ forKey:NSLocalizedDescriptionKey]];
1283
+ return NO;
1284
+ }
1285
+ }
1286
+
1287
+ if (flag)
1288
+ // First compact the store
1289
+ [self compactStoreAndReturnError:outError];
1290
+
1291
+ // Try to copy the file to the destination
1292
+ if ([fm fileExistsAtPath:filePath]) {
1293
+ [fm copyItemAtPath:filePath toPath:backupPath error:outError];
1294
+ } else {
1295
+ if (nil != outError)
1296
+ *outError = [NSError errorWithDomain:NSFDomainKey
1297
+ code:NSF_Private_MacOSXErrorCodeKey
1298
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"File doesn't exist at path: %@", filePath]
1299
+ forKey:NSLocalizedDescriptionKey]];
1300
+ return NO;
1301
+ }
1302
+
1303
+ return YES;
1304
+ }
1305
+
1306
+ - (BOOL)_backupMemoryStoreToDirectoryAtPath:(NSString *)backupPath extension:(NSString *)anExtension compact:(BOOL)flag error:(out NSError **)outError
1307
+ {
1308
+ NSString *filePath = [self filePath];
1309
+ if ((anExtension != nil) && (NO == [backupPath hasSuffix:anExtension])) {
1310
+ backupPath = [NSString stringWithFormat:@"%@.%@", backupPath, anExtension];
1311
+ }
1312
+
1313
+ // Make sure we the destination path is not the same as the source!
1314
+ if (YES == [filePath isEqualToString:backupPath]) {
1315
+ if (nil != outError)
1316
+ *outError = [NSError errorWithDomain:NSFDomainKey
1317
+ code:NSFNanoStoreErrorKey
1318
+ userInfo:[NSDictionary dictionaryWithObject:@"Cannot backup store. The source and destination directories are the same."
1319
+ forKey:NSLocalizedDescriptionKey]];
1320
+ return NO;
1321
+ }
1322
+
1323
+ if (flag) {
1324
+ // First compact the store
1325
+ [self compactStoreAndReturnError:outError];
1326
+ }
1327
+
1328
+ NSFileManager *fm = [NSFileManager defaultManager];
1329
+ BOOL destinationLocationIsClear = YES;
1330
+
1331
+ if (YES == [fm fileExistsAtPath:backupPath]) {
1332
+ destinationLocationIsClear = [fm removeItemAtPath:backupPath error:nil];
1333
+ if (NO == destinationLocationIsClear) {
1334
+ if (nil != outError)
1335
+ *outError = [NSError errorWithDomain:NSFDomainKey
1336
+ code:NSF_Private_MacOSXErrorCodeKey
1337
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Existing file couldn't be removed in path: %@. Backup cannot proceed.", backupPath]
1338
+ forKey:NSLocalizedDescriptionKey]];
1339
+ return NO;
1340
+ }
1341
+ }
1342
+
1343
+ NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSFNanoEngine stringWithUUID]];
1344
+
1345
+ NSFNanoStore *fileDB = [NSFNanoStore createStoreWithType:NSFPersistentStoreType path:tempPath];
1346
+ if (NO == [fileDB openWithError:outError])
1347
+ return NO;
1348
+
1349
+ // Attach the file-based database to the memory-based one
1350
+ NSString *theSQLStatement = [NSString stringWithFormat:@"ATTACH DATABASE '%@' AS fileDB", [fileDB filePath]];
1351
+ [self _executeSQL:theSQLStatement];
1352
+
1353
+ // Transfer the NSFKeys table
1354
+ NSString *columns = [[[self nanoStoreEngine]columnsForTable:NSFKeys]componentsJoinedByString:@", "];
1355
+ theSQLStatement = [NSString stringWithFormat:@"INSERT INTO fileDB.%@ (%@) SELECT * FROM main.%@", NSFKeys, columns, NSFKeys];
1356
+ [self _executeSQL:theSQLStatement];
1357
+
1358
+ // Transfer the NSFValues table
1359
+ columns = [[[self nanoStoreEngine]columnsForTable:NSFValues]componentsJoinedByString:@", "];
1360
+ theSQLStatement = [NSString stringWithFormat:@"INSERT INTO fileDB.%@ (%@) SELECT * FROM main.%@", NSFValues, columns, NSFValues];
1361
+ [self _executeSQL:theSQLStatement];
1362
+
1363
+ // Safely detach the file-based database
1364
+ [self _executeSQL:@"DETACH DATABASE fileDB"];
1365
+
1366
+ // We can now close the database
1367
+ [fileDB closeWithError:outError];
1368
+
1369
+ // Move the file to the specified destination
1370
+ return [fm moveItemAtPath:tempPath toPath:backupPath error:outError];
1371
+ }
1372
+
1373
+ /** \endcond */
1374
+
1375
+ @end