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.
- data/.gitignore +3 -0
- data/.gitmodules +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +193 -0
- data/Rakefile +1 -0
- data/app/app_delegate.rb +5 -0
- data/lib/nano-store.rb +1 -0
- data/lib/nano_store/bag.rb +98 -0
- data/lib/nano_store/model.rb +142 -0
- data/lib/nano_store/nano_store.rb +36 -0
- data/lib/nano_store/store_extension.rb +150 -0
- data/lib/nano_store/version.rb +3 -0
- data/lib/nano_store.rb +14 -0
- data/nano-store.gemspec +17 -0
- data/resources/.gitignore +0 -0
- data/spec/bag_spec.rb +66 -0
- data/spec/model_spec.rb +130 -0
- data/spec/nano_store_spec.rb +48 -0
- data/spec/store_extension_spec.rb +110 -0
- data/vendor/NanoStore/Classes/Advanced/NSFNanoEngine.h +542 -0
- data/vendor/NanoStore/Classes/Advanced/NSFNanoEngine.m +1781 -0
- data/vendor/NanoStore/Classes/Advanced/NSFNanoResult.h +137 -0
- data/vendor/NanoStore/Classes/Advanced/NSFNanoResult.m +265 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoBag_Private.h +37 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoEngine_Private.h +69 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoExpression_Private.h +35 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoGlobals_Private.h +99 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoObject_Private.h +35 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoPredicate_Private.h +35 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoResult_Private.h +43 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoSearch_Private.h +48 -0
- data/vendor/NanoStore/Classes/Private/NSFNanoStore_Private.h +57 -0
- data/vendor/NanoStore/Classes/Private/NanoStore_Private.h +37 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoBag.h +306 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoBag.m +485 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoExpression.h +125 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoExpression.m +103 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoGlobals.h +323 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoGlobals.m +145 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoObject.h +298 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoObject.m +187 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoObjectProtocol.h +119 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoPredicate.h +123 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoPredicate.m +130 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoSearch.h +381 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoSearch.m +835 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoSortDescriptor.h +124 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoSortDescriptor.m +79 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoStore.h +475 -0
- data/vendor/NanoStore/Classes/Public/NSFNanoStore.m +1375 -0
- data/vendor/NanoStore/Classes/Public/NanoStore.h +463 -0
- data/vendor/NanoStore/LICENSE +25 -0
- data/vendor/NanoStore/NanoStore.bridgesupport +1215 -0
- data/vendor/NanoStore/README.md +411 -0
- 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
|