nano-store 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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