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,1781 @@
1
+ /*
2
+ NSFNanoEngine.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 "NanoStore_Private.h"
29
+
30
+ #import <stdio.h>
31
+ #import <stdlib.h>
32
+ #import <unistd.h>
33
+
34
+ #pragma mark// ==================================
35
+ #pragma mark// NSFNanoEngine C Declarations
36
+ #pragma mark// ==================================
37
+
38
+ int NSFP_commitCallback(void* nsfdb);
39
+
40
+ static char __NSFP_base64Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
41
+ static NSArray *__NSFP_SQLCommandsReturningData = nil;
42
+ static NSArray *__NSFPSharedROWIDKeywords = nil;
43
+ static NSSet *__NSFPSharedNanoStoreEngineDatatypes = nil;
44
+
45
+ #pragma mark -
46
+
47
+ @implementation NSFNanoEngine
48
+ {
49
+ @protected
50
+ sqlite3 *sqlite;
51
+ NSString *path;
52
+ NSFCacheMethod cacheMethod;
53
+
54
+ /** \cond */
55
+ NSMutableDictionary *schema;
56
+ BOOL willCommitChangeSchema;
57
+ unsigned int busyTimeout;
58
+ /** \endcond */
59
+ }
60
+
61
+ @synthesize sqlite;
62
+ @synthesize path;
63
+ @synthesize cacheMethod;
64
+
65
+ #pragma mark -
66
+
67
+ #pragma mark// ==================================
68
+ #pragma mark// Initialization/Cleanup Methods
69
+ #pragma mark// ==================================
70
+
71
+ + (id)databaseWithPath:(NSString *)thePath
72
+ {
73
+ if (nil == thePath)
74
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
75
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: thePath is nil.", [self class], _cmd]
76
+ userInfo:nil]raise];
77
+
78
+ return [[self alloc]initWithPath:thePath];
79
+ }
80
+
81
+ - (id)initWithPath:(NSString *)thePath
82
+ {
83
+ if (nil == thePath)
84
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
85
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: thePath is nil.", [self class], _cmd]
86
+ userInfo:nil]raise];
87
+
88
+ if ((self = [self init])) {
89
+ path = [thePath copy];
90
+ }
91
+
92
+ return self;
93
+ }
94
+
95
+ /** \cond */
96
+
97
+ + (void)initialize
98
+ {
99
+ __NSFP_SQLCommandsReturningData = [[NSArray alloc]initWithObjects:@"SELECT", @"PRAGMA", @"EXPLAIN", nil];
100
+ }
101
+
102
+ - (id)init
103
+ {
104
+ if ((self = [super init])) {
105
+ path = nil;
106
+ schema = nil;
107
+ }
108
+ return self;
109
+ }
110
+
111
+ - (void)dealloc
112
+ {
113
+ [self close];
114
+
115
+
116
+ }
117
+
118
+ /** \endcond */
119
+
120
+ - (NSString*)description
121
+ {
122
+ return [self NSFP_nestedDescriptionWithPrefixedSpace:@""];
123
+ }
124
+
125
+ #pragma mark// ==================================
126
+ #pragma mark// Opening & Closing Methods
127
+ #pragma mark// ==================================
128
+
129
+ - (BOOL)openWithCacheMethod:(NSFCacheMethod)theCacheMethod useFastMode:(BOOL)useFastMode
130
+ {
131
+ int status = sqlite3_open_v2( [path UTF8String], &sqlite,
132
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_AUTOPROXY | SQLITE_OPEN_FULLMUTEX, NULL);
133
+
134
+ // Set NanoStoreEngine's page size to match the system current page size
135
+ if (0 == [[self tables]count]) {
136
+ NSUInteger systemPageSize = [NSFNanoEngine systemPageSize];
137
+ [self setPageSize:systemPageSize];
138
+ }
139
+
140
+ // Since we're operating with extended result code support, extract the bits
141
+ // and obtain the regular result code
142
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
143
+
144
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
145
+
146
+ if ((SQLITE_OK != status) || (sqlite3_extended_result_codes(self.sqlite, 1) != SQLITE_OK))
147
+ return NO;
148
+
149
+ if ([[path lowercaseString]isEqualToString:NSFMemoryDatabase] == YES) {
150
+
151
+ sqlite3_exec(self.sqlite, "PRAGMA fullfsync = OFF;", NULL, NULL, NULL);
152
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = MEMORY", NULL, NULL, NULL);
153
+ sqlite3_exec(self.sqlite, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);
154
+ sqlite3_exec(self.sqlite, "PRAGMA journal_mode = MEMORY;", NULL, NULL, NULL);
155
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = MEMORY", NULL, NULL, NULL);
156
+
157
+ } else {
158
+
159
+ // Set FastMode accordingly...
160
+ if (YES == useFastMode) {
161
+ sqlite3_exec(self.sqlite, "PRAGMA fullfsync = OFF;", NULL, NULL, NULL);
162
+ sqlite3_exec(self.sqlite, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);
163
+ sqlite3_exec(self.sqlite, "PRAGMA journal_mode = MEMORY;", NULL, NULL, NULL);
164
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = MEMORY", NULL, NULL, NULL);
165
+ } else {
166
+ sqlite3_exec(self.sqlite, "PRAGMA fullfsync = OFF;", NULL, NULL, NULL);
167
+ sqlite3_exec(self.sqlite, "PRAGMA synchronous = FULL;", NULL, NULL, NULL);
168
+ sqlite3_exec(self.sqlite, "PRAGMA journal_mode = DELETE", NULL, NULL, NULL);
169
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = DEFAULT", NULL, NULL, NULL);
170
+ }
171
+
172
+ }
173
+
174
+ // Save whether we want data to be fetched lazily
175
+ cacheMethod = theCacheMethod;
176
+
177
+ [self setBusyTimeout:250];
178
+
179
+ // Refresh the schema cache
180
+ [self NSFP_rebuildDatatypeCache];
181
+
182
+ [self NSFP_installCommitCallback];
183
+
184
+ return YES;
185
+ }
186
+
187
+
188
+ - (BOOL)close
189
+ {
190
+ if (NO == self.sqlite) {
191
+ return NO;
192
+ }
193
+
194
+ if (YES == [self isTransactionActive]) {
195
+ [self rollbackTransaction];
196
+ }
197
+
198
+ // Make sure we clear the temporary data from schema
199
+ NSArray *tempTables = [self temporaryTables];
200
+
201
+ if ([tempTables count] > 0) {
202
+ [self beginTransaction];
203
+
204
+ for (NSString *table in tempTables) {
205
+ [self dropTable:table];
206
+ }
207
+
208
+ [self commitTransaction];
209
+ }
210
+
211
+ int status = sqlite3_close(self.sqlite);
212
+ sqlite = NULL;
213
+
214
+ // Since we're operating with extended result code support, extract the bits
215
+ // and obtain the regular result code
216
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
217
+
218
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
219
+
220
+ return (SQLITE_OK == status);
221
+ }
222
+
223
+ - (BOOL)isDatabaseOpen
224
+ {
225
+ return (NULL != self.sqlite);
226
+ }
227
+
228
+ #pragma mark Transaction Methods
229
+
230
+ - (BOOL)beginTransaction
231
+ {
232
+ if (YES == [self isTransactionActive])
233
+ return NO;
234
+
235
+ willCommitChangeSchema = NO;
236
+
237
+ return [self beginDeferredTransaction];
238
+ }
239
+
240
+ - (BOOL)beginDeferredTransaction
241
+ {
242
+ if (YES == [self isTransactionActive])
243
+ return NO;
244
+
245
+ willCommitChangeSchema = NO;
246
+
247
+ return [self NSFP_beginTransactionMode:@"BEGIN DEFERRED TRANSACTION;"];
248
+ }
249
+
250
+ - (BOOL)commitTransaction
251
+ {
252
+ if (NO == [self isTransactionActive]) {
253
+ willCommitChangeSchema = NO;
254
+ return NO;
255
+ }
256
+
257
+ if (NO == willCommitChangeSchema)
258
+ [self NSFP_uninstallCommitCallback];
259
+
260
+ BOOL success = (nil == [[self executeSQL:@"COMMIT TRANSACTION;"]error]);
261
+
262
+ if (NO == willCommitChangeSchema)
263
+ [self NSFP_installCommitCallback];
264
+
265
+ willCommitChangeSchema = NO;
266
+
267
+ return success;
268
+ }
269
+
270
+ - (BOOL)rollbackTransaction
271
+ {
272
+ if ([self isTransactionActive] == NO) {
273
+ willCommitChangeSchema = NO;
274
+ return NO;
275
+ }
276
+
277
+ BOOL success = (nil == [[self executeSQL:@"ROLLBACK TRANSACTION;"]error]);
278
+
279
+ willCommitChangeSchema = NO;
280
+
281
+ return success;
282
+ }
283
+
284
+ - (BOOL)isTransactionActive
285
+ {
286
+ sqlite3* myDB = self.sqlite;
287
+
288
+ int status = sqlite3_get_autocommit(myDB);
289
+
290
+ // Since we're operating with extended result code support, extract the bits
291
+ // and obtain the regular result code
292
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
293
+
294
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
295
+
296
+ return (0 == status);
297
+ }
298
+
299
+ #pragma mark// ==================================
300
+ #pragma mark// Utility Methods
301
+ #pragma mark// ==================================
302
+
303
+ + (NSString *)stringWithUUID
304
+ {
305
+ CFUUIDRef uuidCF = CFUUIDCreate(NULL);
306
+ NSString *uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuidCF);
307
+ CFRelease(uuidCF);
308
+ return uuid;
309
+ }
310
+
311
+ - (BOOL)compact
312
+ {
313
+ if (NO == [self isTransactionActive])
314
+ return (nil == [[self executeSQL:@"VACUUM;"]error]);
315
+
316
+ return NO;
317
+ }
318
+
319
+ - (BOOL)integrityCheck
320
+ {
321
+ if (NO == [self isTransactionActive]) {
322
+ NSFNanoResult* result = [self executeSQL:@"PRAGMA integrity_check"];
323
+
324
+ // SQLite returns the status as 'ok'. Let's code defensively and lowercase the result.
325
+ return ([[[[result valuesForColumn:@"integrity_check"]lastObject]lowercaseString]isEqualToString:@"ok"]);
326
+ }
327
+
328
+ return NO;
329
+ }
330
+
331
+ + (NSString *)nanoStoreEngineVersion
332
+ {
333
+ return NSFVersionKey;
334
+ }
335
+
336
+ + (NSString *)sqliteVersion
337
+ {
338
+ return [NSString stringWithUTF8String: sqlite3_libversion()];
339
+ }
340
+
341
+ + (NSSet*)sharedNanoStoreEngineDatatypes
342
+ {
343
+ if (nil == __NSFPSharedNanoStoreEngineDatatypes)
344
+ __NSFPSharedNanoStoreEngineDatatypes = [[NSSet alloc]initWithObjects:NSFStringFromNanoDataType(NSFNanoTypeRowUID),
345
+ NSFStringFromNanoDataType(NSFNanoTypeString),
346
+ NSFStringFromNanoDataType(NSFNanoTypeData),
347
+ NSFStringFromNanoDataType(NSFNanoTypeDate),
348
+ NSFStringFromNanoDataType(NSFNanoTypeNumber),
349
+ nil];
350
+
351
+ return __NSFPSharedNanoStoreEngineDatatypes;
352
+ }
353
+
354
+ #pragma mark// ==================================
355
+ #pragma mark// Table and Introspection Methods
356
+ #pragma mark// ==================================
357
+
358
+ - (BOOL)createTable:(NSString *)table withColumns:(NSArray *)columns datatypes:(NSArray *)datatypes
359
+ {
360
+ if (nil == table)
361
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
362
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
363
+ userInfo:nil]raise];
364
+
365
+ if (nil == columns)
366
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
367
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: columns is nil.", [self class], _cmd]
368
+ userInfo:nil]raise];
369
+
370
+ if (nil == datatypes)
371
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
372
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatypes is nil.", [self class], _cmd]
373
+ userInfo:nil]raise];
374
+
375
+ return [self NSFP_createTable:table withColumns:columns datatypes:datatypes isTemporary:NO];
376
+ }
377
+
378
+ - (BOOL)dropTable:(NSString *)table
379
+ {
380
+ if (nil == table)
381
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
382
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
383
+ userInfo:nil]raise];
384
+
385
+ BOOL transactionSetHere = NO;
386
+ if ([self isTransactionActive] == NO)
387
+ transactionSetHere = [self beginTransaction];
388
+
389
+ NSString *theSQLStatement = [[NSString alloc]initWithFormat:@"DROP TABLE %@;", table];
390
+ BOOL everythingIsFine = (nil == [[self executeSQL:theSQLStatement]error]);
391
+
392
+ if (everythingIsFine) {
393
+ theSQLStatement = [[NSString alloc]initWithFormat:@"DELETE FROM %@ WHERE %@ = '%@';", NSFP_SchemaTable, NSFP_TableIdentifier, table];
394
+ everythingIsFine = (nil == [[self executeSQL:theSQLStatement]error]);
395
+ }
396
+
397
+ if (transactionSetHere) {
398
+ if (everythingIsFine)
399
+ [self commitTransaction];
400
+ else
401
+ [self rollbackTransaction];
402
+ }
403
+
404
+ if (everythingIsFine)
405
+ [self NSFP_rebuildDatatypeCache];
406
+
407
+ return everythingIsFine;
408
+ }
409
+
410
+ - (BOOL)createIndexForColumn:(NSString *)column table:(NSString *)table isUnique:(BOOL)flag
411
+ {
412
+ if (nil == column)
413
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
414
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
415
+ userInfo:nil]raise];
416
+ if (nil == table)
417
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
418
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
419
+ userInfo:nil]raise];
420
+
421
+ NSString *theSQLStatement = nil;
422
+
423
+ if (flag)
424
+ theSQLStatement = [[NSString alloc]initWithFormat:@"CREATE UNIQUE INDEX %@_%@_IDX ON %@ (%@);", table, column, table, column];
425
+ else
426
+ theSQLStatement = [[NSString alloc]initWithFormat:@"CREATE INDEX %@_%@_IDX ON %@ (%@);", table, column, table, column];
427
+
428
+ BOOL indexWasCreated = (nil == [[self executeSQL:theSQLStatement]error]);
429
+
430
+ return indexWasCreated;
431
+ }
432
+
433
+ - (void)dropIndex:(NSString *)indexName
434
+ {
435
+ if (nil == indexName)
436
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
437
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: indexName is nil.", [self class], _cmd]
438
+ userInfo:nil]raise];
439
+
440
+ NSString *theSQLStatement = [[NSString alloc]initWithFormat:@"DROP INDEX %@;", indexName];
441
+
442
+ [self executeSQL:theSQLStatement];
443
+ }
444
+
445
+ - (NSArray *)tables
446
+ {
447
+ NSArray *allTables = [self NSFP_flattenAllTables];
448
+ if ([allTables count] == 0)
449
+ return allTables;
450
+
451
+ // Remove NSF's private table
452
+ NSMutableArray *tempTables = [NSMutableArray arrayWithArray:allTables];
453
+ [tempTables removeObject:NSFP_SchemaTable];
454
+
455
+ return tempTables;
456
+ }
457
+
458
+ - (NSDictionary *)allTables
459
+ {
460
+ NSMutableDictionary *allTables = [NSMutableDictionary dictionary];
461
+
462
+ // Make sure we obtain full column names
463
+ [self NSFP_setFullColumnNamesEnabled];
464
+
465
+ NSFNanoResult *databasesResult = [self executeSQL:@"PRAGMA database_list"];
466
+ NSArray *databases = [databasesResult valuesForColumn:@"name"];
467
+
468
+ for (NSString *database in databases) {
469
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT * FROM %@.sqlite_master;", database];
470
+ NSFNanoResult* result = [self executeSQL:theSQLStatement];
471
+ if (nil == [result error]) {
472
+ // Get all tables in the database
473
+ NSArray *databaseTables = [result valuesForColumn:@"sqlite_master.tbl_name"];
474
+ NSSet *tablesPerDatabase = [NSSet setWithArray:databaseTables];
475
+ [allTables setObject: [tablesPerDatabase allObjects] forKey: database];
476
+ }
477
+ }
478
+
479
+ return allTables;
480
+ }
481
+
482
+ - (NSArray *)columnsForTable:(NSString *)table
483
+ {
484
+ if (nil == table)
485
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
486
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
487
+ userInfo:nil]raise];
488
+
489
+ NSString *theSQLStatement = nil;
490
+ NSString *database = [self NSFP_prefixWithDotDelimiter:table];
491
+
492
+ if ([database isEqualToString:table] == NO) {
493
+ database = [NSString stringWithFormat:@"%@.", database];
494
+ table = [self NSFP_suffixWithDotDelimiter:table];
495
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA %@.table_info ('%@');", database, table];
496
+ } else {
497
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA table_info ('%@');", table];
498
+ }
499
+
500
+ NSFNanoResult *result = [self executeSQL:theSQLStatement];
501
+
502
+ return [result valuesForColumn:@"name"];
503
+ }
504
+
505
+ - (NSArray *)datatypesForTable:(NSString *)table
506
+ {
507
+ if (nil == table)
508
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
509
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
510
+ userInfo:nil]raise];
511
+
512
+ NSString *theSQLStatement = nil;
513
+ NSString *database = [self NSFP_prefixWithDotDelimiter:table];
514
+
515
+ if ([database isEqualToString:table] == NO) {
516
+ database = [NSString stringWithFormat:@"%@.", database];
517
+ table = [self NSFP_suffixWithDotDelimiter:table];
518
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA %@.table_info ('%@');", database, table];
519
+ } else {
520
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA table_info ('%@');", table];
521
+ }
522
+
523
+ NSFNanoResult *result = [self executeSQL:theSQLStatement];
524
+
525
+ return [result valuesForColumn:@"type"];
526
+ }
527
+
528
+ - (NSArray *)indexes
529
+ {
530
+ NSFNanoResult* result = [self executeSQL:@"SELECT name FROM sqlite_master WHERE type='index' ORDER BY name"];
531
+
532
+ return [result valuesForColumn:@"sqlite_master.name"];
533
+ }
534
+
535
+ - (NSArray *)indexedColumnsForTable:(NSString *)table
536
+ {
537
+ if (nil == table)
538
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
539
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
540
+ userInfo:nil]raise];
541
+
542
+ NSFNanoResult* result = [self executeSQL:[NSString stringWithFormat:@"SELECT sqlite_master.name FROM sqlite_master WHERE type = 'index' AND sqlite_master.tbl_name = '%@';", table]];
543
+ if ([result numberOfRows] == 0) {
544
+ result = [self executeSQL:[NSString stringWithFormat:@"SELECT sqlite_temp_master.name FROM sqlite_temp_master WHERE type = 'index' AND sqlite_temp_master.tbl_name = '%@';", table]];
545
+ return [result valuesForColumn:@"sqlite_temp_master.name"];
546
+ }
547
+
548
+ return [result valuesForColumn:@"sqlite_master.name"];
549
+ }
550
+
551
+ - (NSArray *)temporaryTables
552
+ {
553
+ NSFNanoResult* result = [self executeSQL:@"SELECT * FROM sqlite_temp_master"];
554
+ return [[NSSet setWithArray:[result valuesForColumn:@"sqlite_temp_master.tbl_name"]]allObjects];
555
+ }
556
+
557
+ - (NSFNanoResult *)executeSQL:(NSString *)theSQLStatement
558
+ {
559
+ if (nil == theSQLStatement)
560
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
561
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
562
+ userInfo:nil]raise];
563
+
564
+ if ([theSQLStatement length] == 0)
565
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
566
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is empty.", [self class], _cmd]
567
+ userInfo:nil]raise];
568
+
569
+ // Check whether we will need to return a dictionary with results
570
+ sqlite3 *sqliteStore = self.sqlite;
571
+ NSMutableDictionary *info = [NSMutableDictionary dictionary];
572
+ BOOL returnInfo = NO;
573
+
574
+ for (NSString *sqlCommand in __NSFP_SQLCommandsReturningData) {
575
+ if ([theSQLStatement compare:sqlCommand options:NSCaseInsensitiveSearch range:NSMakeRange(0, [sqlCommand length])] == NSOrderedSame) {
576
+ returnInfo = YES;
577
+ break;
578
+ }
579
+ }
580
+
581
+ int status = SQLITE_OK;
582
+ char *errorMessage = NULL;
583
+
584
+ if (returnInfo) {
585
+ sqlite3_stmt *theSQLiteStatement = NULL;
586
+
587
+ status = sqlite3_prepare_v2 (sqliteStore, [theSQLStatement UTF8String], -1, &theSQLiteStatement, NULL );
588
+
589
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
590
+
591
+ if (SQLITE_OK == status) {
592
+ info = [NSMutableDictionary dictionary];
593
+ int columnIndex, numColumns = sqlite3_column_count (theSQLiteStatement);
594
+
595
+ while (SQLITE_ROW == sqlite3_step (theSQLiteStatement)) {
596
+ for (columnIndex = 0; columnIndex < numColumns; columnIndex++) {
597
+ // Safety check: obtain the column and value. If the column is NULL, skip the iteration.
598
+ char *columnUTF8 = (char *)sqlite3_column_name (theSQLiteStatement, columnIndex);
599
+ if (NULL == columnUTF8) {
600
+ continue;
601
+ }
602
+ NSString *column = [[NSString alloc]initWithUTF8String:columnUTF8];
603
+
604
+ // Sanity check: some queries return NULL, which would cause a crash below.
605
+ char *valueUTF8 = (char *)sqlite3_column_text (theSQLiteStatement, columnIndex);
606
+ NSString *value = nil;
607
+ if (NULL != valueUTF8) {
608
+ value = [[NSString alloc]initWithUTF8String:valueUTF8];
609
+ } else {
610
+ value = [[NSNull null]description];
611
+ }
612
+
613
+ // Obtain the array to collect the values. If the array doesn't exist, create it.
614
+ NSMutableArray *values = [info objectForKey:column];
615
+ if (nil == values) {
616
+ values = [NSMutableArray new];
617
+ }
618
+ [values addObject:value];
619
+ [info setObject:values forKey:column];
620
+
621
+ // Let's cleanup. This will keep the memory footprint low...
622
+ }
623
+
624
+ }
625
+
626
+ sqlite3_finalize (theSQLiteStatement);
627
+ }
628
+ } else {
629
+ status = sqlite3_exec(sqliteStore, [theSQLStatement UTF8String], NULL, NULL, &errorMessage);
630
+
631
+ // Since we're operating with extended result code support, extract the bits
632
+ // and obtain the regular result code
633
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
634
+
635
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
636
+ }
637
+
638
+ NSFNanoResult *result = nil;
639
+
640
+ if (SQLITE_OK != status) {
641
+ NSString *msg = (NULL != errorMessage) ? [NSString stringWithUTF8String:errorMessage] : [NSString stringWithFormat:@"SQLite error ID: %ld", status];
642
+ result = [NSFNanoResult _resultWithError:[NSError errorWithDomain:NSFDomainKey
643
+ code:NSFNanoStoreErrorKey
644
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, msg]
645
+ forKey:NSLocalizedFailureReasonErrorKey]]];
646
+ } else {
647
+ result = [NSFNanoResult _resultWithDictionary:info];
648
+ }
649
+
650
+ // Cleanup
651
+ if (NULL != errorMessage) {
652
+ sqlite3_free (errorMessage);
653
+ }
654
+
655
+ return result;
656
+ }
657
+
658
+ - (long long)maxRowUIDForTable:(NSString *)table
659
+ {
660
+ if (nil == table) {
661
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
662
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
663
+ userInfo:nil]raise];
664
+ }
665
+
666
+ NSString *sql = [[NSString alloc]initWithFormat:@"SELECT max(ROWID) FROM %@", table];
667
+
668
+ NSFNanoResult *result = [self executeSQL:sql];
669
+
670
+
671
+ return [[result firstValue]longLongValue];
672
+ }
673
+
674
+ #pragma mark// ==================================
675
+ #pragma mark// SQLite Tunning Methods
676
+ #pragma mark// ==================================
677
+
678
+ - (void)setBusyTimeout:(unsigned int)theTimeout
679
+ {
680
+ // If the timeout is out-of-range, default the value
681
+ if ((theTimeout < 100) || (theTimeout > 5 * 1000)) {
682
+ theTimeout = 250;
683
+ }
684
+
685
+ busyTimeout = theTimeout;
686
+
687
+ sqlite3_busy_timeout(self.sqlite, busyTimeout);
688
+ }
689
+
690
+ - (unsigned int)busyTimeout
691
+ {
692
+ return busyTimeout;
693
+ }
694
+
695
+ + (NSInteger)systemPageSize
696
+ {
697
+ static NSUInteger __sSystemPageSize = NSNotFound;
698
+
699
+ if (NSNotFound == __sSystemPageSize) {
700
+ __sSystemPageSize = getpagesize();
701
+ }
702
+
703
+ return __sSystemPageSize;
704
+ }
705
+
706
+ + (NSUInteger)recommendedCacheSize
707
+ {
708
+ static NSUInteger __sRecommendedCacheSize = NSNotFound;
709
+
710
+ if (NSNotFound == __sRecommendedCacheSize) {
711
+ NSUInteger defaultNanoStoreEngineCacheSize = 10000;
712
+ unsigned long long physMem = [[NSProcessInfo processInfo] physicalMemory];
713
+
714
+ physMem = physMem / (512 * 1024 * 1000);
715
+ __sRecommendedCacheSize = (NSInteger)physMem * defaultNanoStoreEngineCacheSize;
716
+ if (0 == __sRecommendedCacheSize) {
717
+ __sRecommendedCacheSize = defaultNanoStoreEngineCacheSize;
718
+ }
719
+ }
720
+
721
+ return __sRecommendedCacheSize;
722
+ }
723
+
724
+ - (BOOL)setCacheSize:(NSUInteger)numberOfPages
725
+ {
726
+ if (numberOfPages < 1000)
727
+ numberOfPages = 1000;
728
+
729
+ [self executeSQL:[NSString stringWithFormat:@"PRAGMA cache_size = %ld", numberOfPages]];
730
+ NSUInteger cacheSize = [self cacheSize];
731
+ return (cacheSize == numberOfPages);
732
+ }
733
+
734
+ - (NSUInteger)cacheSize
735
+ {
736
+ NSFNanoResult *result = [self executeSQL:@"PRAGMA cache_size;"];
737
+ NSString *value = [result firstValue];
738
+ return [value integerValue];
739
+ }
740
+
741
+ - (BOOL)setPageSize:(NSUInteger)numberOfBytes
742
+ {
743
+ [self executeSQL:[NSString stringWithFormat:@"PRAGMA page_size = %ld", numberOfBytes]];
744
+ NSUInteger pageSize = [self pageSize];
745
+ return (pageSize == numberOfBytes);
746
+ }
747
+
748
+ - (NSUInteger)pageSize
749
+ {
750
+ NSFNanoResult *result = [self executeSQL:@"PRAGMA page_size;"];
751
+ NSString *value = [result firstValue];
752
+ return [value integerValue];
753
+ }
754
+
755
+ - (BOOL)setEncodingType:(NSFEncodingType)theEncodingType
756
+ {
757
+ BOOL success = NO;
758
+
759
+ if (NSFEncodingUTF8 == theEncodingType) {
760
+ [self executeSQL:@"PRAGMA encoding = \"UTF-8\";"];
761
+ NSFEncodingType encoding = [self encoding];
762
+ success = (NSFEncodingUTF8 == encoding);
763
+ } else if (NSFEncodingUTF16 == theEncodingType) {
764
+ [self executeSQL:@"PRAGMA encoding = \"UTF-16\";"];
765
+ NSFEncodingType encoding = [self encoding];
766
+ success = (NSFEncodingUTF16 == encoding);
767
+ }
768
+
769
+ return success;
770
+ }
771
+
772
+ - (NSFEncodingType)encoding
773
+ {
774
+ NSFNanoResult *result = [self executeSQL:@"pragma encoding;"];
775
+ NSString *value = [result firstValue];
776
+ return [NSFNanoEngine NSStringToNSFEncodingType:value];
777
+ }
778
+
779
+ + (NSFEncodingType)NSStringToNSFEncodingType:(NSString *)value
780
+ {
781
+ NSFEncodingType convertedValue = NSFEncodingUnknown;
782
+
783
+ if (YES == [value isEqualToString:@"UTF-8"]) {
784
+ convertedValue = NSFEncodingUTF8;
785
+ } else if (YES == [value isEqualToString:@"UTF-16"]) {
786
+ convertedValue = NSFEncodingUTF16;
787
+ }
788
+
789
+ return convertedValue;
790
+ }
791
+
792
+ + (NSString *)NSFEncodingTypeToNSString:(NSFEncodingType)value
793
+ {
794
+ NSString *convertedValue = nil;
795
+
796
+ if (NSFEncodingUTF8 == value) {
797
+ convertedValue = @"UTF-8";
798
+ } else if (NSFEncodingUTF16 == value) {
799
+ convertedValue = @"UTF-16";
800
+ }
801
+
802
+ return convertedValue;
803
+ }
804
+
805
+ - (NSFSynchronousMode)synchronousMode
806
+ {
807
+ NSFNanoResult* result = [self executeSQL:@"PRAGMA synchronous"];
808
+ return [[[result valuesForColumn:@"synchronous"]lastObject]intValue];
809
+ }
810
+
811
+ - (void)setSynchronousMode:(NSFSynchronousMode)mode
812
+ {
813
+ switch (mode) {
814
+ case SynchronousModeOff:
815
+ [self executeSQL:@"PRAGMA synchronous = OFF;"];
816
+ break;
817
+ case SynchronousModeFull:
818
+ [self executeSQL:@"PRAGMA synchronous = FULL;"];
819
+ break;
820
+ default:
821
+ [self executeSQL:@"PRAGMA synchronous = NORMAL;"];
822
+ break;
823
+ }
824
+ }
825
+
826
+ - (NSFTempStoreMode)tempStoreMode
827
+ {
828
+ NSFNanoResult* result = [self executeSQL:@"PRAGMA temp_store"];
829
+
830
+ return [[[result valuesForColumn:@"temp_store"]lastObject]intValue];
831
+ }
832
+
833
+ - (void)setTempStoreMode:(NSFTempStoreMode)mode
834
+ {
835
+ switch (mode) {
836
+ case TempStoreModeFile:
837
+ [self executeSQL:@"PRAGMA temp_store = FILE"];
838
+ break;
839
+ case TempStoreModeMemory:
840
+ [self executeSQL:@"PRAGMA temp_store = MEMORY"];
841
+ break;
842
+ default:
843
+ [self executeSQL:@"PRAGMA temp_store = DEFAULT"];
844
+ break;
845
+ }
846
+ }
847
+
848
+ - (NSFJournalModeMode)journalModeAndReturnError:(out NSError **)outError
849
+ {
850
+ NSFNanoResult *result = [self executeSQL:@"PRAGMA journal_mode; "];
851
+ if (nil != [result error]) {
852
+ if (nil != outError) {
853
+ *outError = [[result error]copy];
854
+ return JournalModeDelete;
855
+ }
856
+ }
857
+
858
+ NSString *journalModeString = [result firstValue];
859
+ if ([journalModeString isEqualToString:@"TRUNCATE"]) return JournalModeDelete;
860
+ else if ([journalModeString isEqualToString:@"TRUNCATE"]) return JournalModeTruncate;
861
+ else if ([journalModeString isEqualToString:@"PERSIST"]) return JournalModePersist;
862
+ else if ([journalModeString isEqualToString:@"MEMORY"]) return JournalModeMemory;
863
+ else if ([journalModeString isEqualToString:@"WAL"]) return JournalModeWAL;
864
+ else return JournalModeOFF;
865
+ }
866
+
867
+ - (BOOL)setJournalMode:(NSFJournalModeMode)theMode
868
+ {
869
+ if (YES == [self isTransactionActive]) {
870
+ return NO;
871
+ }
872
+
873
+ NSString *theModeString = nil;
874
+
875
+ switch (theMode) {
876
+ case JournalModeTruncate:
877
+ theModeString = @"TRUNCATE";
878
+ break;
879
+ case JournalModePersist:
880
+ theModeString = @"PERSIST";
881
+ break;
882
+ case JournalModeMemory:
883
+ theModeString = @"MEMORY";
884
+ break;
885
+ case JournalModeWAL:
886
+ theModeString = @"WAL";
887
+ break;
888
+ case JournalModeOFF:
889
+ theModeString = @"OFF";
890
+ break;
891
+ default:
892
+ theModeString = @"DELETE";
893
+ break;
894
+ }
895
+
896
+ [self executeSQL:[NSString stringWithFormat:@"PRAGMA journal_mode = %@", theModeString]];
897
+
898
+ NSFJournalModeMode verificationMode = [self journalModeAndReturnError:nil];
899
+
900
+ return (verificationMode == theMode);
901
+ }
902
+
903
+ #pragma mark// ==================================
904
+ #pragma mark// Binary Data Methods
905
+ #pragma mark// ==================================
906
+
907
+ + (NSString *)encodeDataToBase64:(NSData*)data
908
+ {
909
+ if (nil == data)
910
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
911
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: data is nil.", [self class], _cmd]
912
+ userInfo:nil]raise];
913
+
914
+ NSInteger decodedDataSize = [data length];
915
+ unsigned char *bytes = (unsigned char *)malloc(decodedDataSize);
916
+
917
+ // Extract the bytes
918
+ [data getBytes:bytes];
919
+
920
+ unsigned char inBuffer[3];
921
+ unsigned char outBuffer[4];
922
+ NSInteger i;
923
+ NSInteger segments;
924
+ char *outputBuffer;
925
+ char *base64Buffer;
926
+
927
+ base64Buffer = outputBuffer = (char *)malloc (decodedDataSize * 4 / 3 + 4);
928
+ if (NULL == outputBuffer) {
929
+ free (bytes);
930
+ return nil;
931
+ }
932
+
933
+ while (decodedDataSize > 0) {
934
+ for (i = segments = 0; i < 3; i++) {
935
+ if (decodedDataSize > 0) {
936
+ segments++;
937
+ inBuffer[i] = *(bytes + i);
938
+ decodedDataSize--;
939
+ } else
940
+ inBuffer[i] = 0;
941
+ }
942
+
943
+ outBuffer [0] = (inBuffer [0] & 0xFC) >> 2;
944
+ outBuffer [1] = ((inBuffer [0] & 0x03) << 4) | ((inBuffer [1] & 0xF0) >> 4);
945
+ outBuffer [2] = ((inBuffer [1] & 0x0F) << 2) | ((inBuffer [2] & 0xC0) >> 6);
946
+ outBuffer [3] = inBuffer [2] & 0x3F;
947
+
948
+ switch (segments) {
949
+ case 1:
950
+ sprintf(outputBuffer, "%c%c==",
951
+ __NSFP_base64Table[outBuffer[0]],
952
+ __NSFP_base64Table[outBuffer[1]]);
953
+ break;
954
+ case 2:
955
+ sprintf(outputBuffer, "%c%c%c=",
956
+ __NSFP_base64Table[outBuffer[0]],
957
+ __NSFP_base64Table[outBuffer[1]],
958
+ __NSFP_base64Table[outBuffer[2]]);
959
+ break;
960
+ default:
961
+ sprintf(outputBuffer, "%c%c%c%c",
962
+ __NSFP_base64Table[outBuffer[0]],
963
+ __NSFP_base64Table[outBuffer[1]],
964
+ __NSFP_base64Table[outBuffer[2]],
965
+ __NSFP_base64Table[outBuffer[3]] );
966
+ break;
967
+ }
968
+
969
+ outputBuffer += 4;
970
+ }
971
+
972
+ *outputBuffer = 0;
973
+
974
+ NSString *myBase64Data = [NSString stringWithUTF8String:base64Buffer];
975
+
976
+ free (base64Buffer);
977
+ free (bytes);
978
+
979
+ return myBase64Data;
980
+ }
981
+
982
+ + (NSData*)decodeDataFromBase64:(NSString *)encodedData
983
+ {
984
+ if (nil == encodedData)
985
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
986
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: encodedData is nil.", [self class], _cmd]
987
+ userInfo:nil]raise];
988
+
989
+ const char* source = [encodedData UTF8String];
990
+ NSUInteger sourceLength = strlen(source);
991
+ char* destination = (char *)malloc(sourceLength * 3/4 + 8);
992
+ char* destinationPtr = destination;
993
+
994
+ NSInteger length = 0;
995
+ NSInteger pivot = 0;
996
+ NSInteger i;
997
+ NSInteger numSegments;
998
+ unsigned char lastSegment[3];
999
+ NSUInteger decodedLength = 0;
1000
+
1001
+ while ((source[length] != '=') && source[length])
1002
+ length++;
1003
+ while (source[length+pivot] == '=')
1004
+ pivot++;
1005
+
1006
+ numSegments = (length + pivot) / 4;
1007
+
1008
+ decodedLength = (numSegments * 3) - pivot;
1009
+
1010
+ for (i = 0; i < numSegments - 1; i++) {
1011
+ [self NSFP_decodeQuantum:(unsigned char *)destination andSource:source];
1012
+ destination += 3;
1013
+ source += 4;
1014
+ }
1015
+
1016
+ [self NSFP_decodeQuantum:lastSegment andSource:source];
1017
+
1018
+ for (i = 0; i < 3 - pivot; i++)
1019
+ destination[i] = lastSegment[i];
1020
+
1021
+ // Construct a NSData with the decoded data
1022
+ NSData* myDummyData = [NSData dataWithBytes:destinationPtr length:decodedLength];
1023
+
1024
+ // Cleanup
1025
+ free (destinationPtr);
1026
+
1027
+ return myDummyData;
1028
+ }
1029
+
1030
+ #pragma mark// ==================================
1031
+ #pragma mark// NSFNanoEngine Private Methods
1032
+ #pragma mark// ==================================
1033
+
1034
+ /** \cond */
1035
+
1036
+ + (NSArray *)NSFP_sharedROWIDKeywords
1037
+ {
1038
+ if (nil == __NSFPSharedROWIDKeywords)
1039
+ __NSFPSharedROWIDKeywords = [[NSArray alloc]initWithObjects:@"ROWID", @"OID", @"_ROWID_", nil];
1040
+
1041
+ return __NSFPSharedROWIDKeywords;
1042
+ }
1043
+
1044
+ - (NSString *)NSFP_cacheMethodToString
1045
+ {
1046
+ switch (cacheMethod) {
1047
+ case CacheAllData:
1048
+ return @"Cache all data";
1049
+ break;
1050
+ case CacheDataOnDemand:
1051
+ return @"Cache data on demand";
1052
+ break;
1053
+ default:
1054
+ return @"Do not cache data";
1055
+ break;
1056
+ }
1057
+
1058
+ return @"<unknown cache method";
1059
+ }
1060
+
1061
+ + (int)NSFP_stripBitsFromExtendedResultCode:(int)extendedResult
1062
+ {
1063
+ return (extendedResult & 0x00FF);
1064
+ }
1065
+
1066
+ - (NSString*)NSFP_nestedDescriptionWithPrefixedSpace:(NSString *)prefixedSpace
1067
+ {
1068
+ if (nil == prefixedSpace) {
1069
+ prefixedSpace = @"";
1070
+ }
1071
+
1072
+ NSMutableString *description = [NSMutableString string];
1073
+ [description appendString:@"\n"];
1074
+ [description appendString:[NSString stringWithFormat:@"%@SQLite address : 0x%x\n", prefixedSpace, self.sqlite]];
1075
+ [description appendString:[NSString stringWithFormat:@"%@Database path : %@\n", prefixedSpace, path]];
1076
+ [description appendString:[NSString stringWithFormat:@"%@Cache method : %@\n", prefixedSpace, [self NSFP_cacheMethodToString]]];
1077
+
1078
+ return description;
1079
+ }
1080
+
1081
+ + (NSDictionary *)_plistToDictionary:(NSString *)aPlist
1082
+ {
1083
+ if (nil == aPlist)
1084
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1085
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aPlist is nil.", [self class], _cmd]
1086
+ userInfo:nil]raise];
1087
+
1088
+ if ([aPlist length] == 0)
1089
+ return nil;
1090
+
1091
+ // Some sanity check...
1092
+ if ([NSPropertyListSerialization propertyList:aPlist isValidForFormat:NSPropertyListXMLFormat_v1_0] == NO)
1093
+ return nil;
1094
+
1095
+ NSString *errorString = nil;
1096
+ NSPropertyListFormat *format = nil;
1097
+ NSData *data = [aPlist dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
1098
+ NSDictionary *dict = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:format errorDescription:&errorString];
1099
+
1100
+ if (nil == dict) {
1101
+ NSLog(@"*** -[%@ %@]: [NSPropertyListSerialization propertyListFromData] failure. %@", [self class], NSStringFromSelector(_cmd), errorString);
1102
+ NSLog(@" Plist data: %@", aPlist);
1103
+ return nil;
1104
+ }
1105
+
1106
+ return dict;
1107
+ }
1108
+
1109
+ + (void)NSFP_decodeQuantum:(unsigned char*)dest andSource:(const char *)src
1110
+ {
1111
+ if (nil == dest)
1112
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1113
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: dest is nil.", [self class], _cmd]
1114
+ userInfo:nil]raise];
1115
+
1116
+ if (nil == src)
1117
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1118
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: src is nil.", [self class], _cmd]
1119
+ userInfo:nil]raise];
1120
+
1121
+ NSUInteger x = 0;
1122
+ NSInteger i;
1123
+ for (i = 0; i < 4; i++) {
1124
+ if (src[i] >= 'A' && src[i] <= 'Z')
1125
+ x = (x << 6) + (NSUInteger)(src[i] - 'A' + 0);
1126
+ else if (src[i] >= 'a' && src[i] <= 'z')
1127
+ x = (x << 6) + (NSUInteger)(src[i] - 'a' + 26);
1128
+ else if (src[i] >= '0' && src[i] <= '9')
1129
+ x = (x << 6) + (NSUInteger)(src[i] - '0' + 52);
1130
+ else if (src[i] == '+')
1131
+ x = (x << 6) + 62;
1132
+ else if (src[i] == '/')
1133
+ x = (x << 6) + 63;
1134
+ else if (src[i] == '=')
1135
+ x = (x << 6);
1136
+ }
1137
+
1138
+ dest[2] = (unsigned char)(x & 255);
1139
+ x >>= 8;
1140
+ dest[1] = (unsigned char)(x & 255);
1141
+ x >>= 8;
1142
+ dest[0] = (unsigned char)(x & 255);
1143
+ }
1144
+
1145
+ - (NSFNanoDatatype)NSFP_datatypeForColumn:(NSString *)tableAndColumn
1146
+ {
1147
+ if (nil == tableAndColumn)
1148
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1149
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableAndColumn is nil.", [self class], _cmd]
1150
+ userInfo:nil]raise];
1151
+
1152
+ NSString *table = [self NSFP_prefixWithDotDelimiter:tableAndColumn];
1153
+ NSString *column = [self NSFP_suffixWithDotDelimiter:tableAndColumn];
1154
+
1155
+ return [self NSFP_datatypeForTable:(NSString *)table column:(NSString *)column];
1156
+ }
1157
+
1158
+ - (NSFNanoDatatype)NSFP_datatypeForTable:(NSString *)table column:(NSString *)column
1159
+ {
1160
+ if (nil == table)
1161
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1162
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
1163
+ userInfo:nil]raise];
1164
+
1165
+ if (nil == column)
1166
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1167
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
1168
+ userInfo:nil]raise];
1169
+
1170
+ NSString *datatype = nil;
1171
+
1172
+ // Check to see if the schema has been cached; take advantage of it if possible...
1173
+ if (nil != schema) {
1174
+ datatype = [[schema objectForKey:table]objectForKey:column];
1175
+ if (nil == datatype) datatype = NSFStringFromNanoDataType(NSFNanoTypeUnknown);
1176
+ } else {
1177
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT %@ from %@ WHERE %@ = '%@' AND %@ = '%@';", NSFP_DatatypeIdentifier, NSFP_SchemaTable, NSFP_TableIdentifier, table, NSFP_ColumnIdentifier, column];
1178
+
1179
+ NSFNanoResult* result = [self executeSQL:theSQLStatement];
1180
+
1181
+ datatype = [[result valuesForColumn:NSFP_FullDatatypeIdentifier]lastObject];
1182
+
1183
+ if (nil == datatype) datatype = NSFStringFromNanoDataType(NSFNanoTypeUnknown);
1184
+
1185
+ NSMutableDictionary *tempSchema = [schema objectForKey:table];
1186
+ if (nil != tempSchema)
1187
+ tempSchema = [[NSMutableDictionary alloc]init];
1188
+ else
1189
+ ;
1190
+
1191
+ [tempSchema setObject:datatype forKey:column];
1192
+ [schema setObject:tempSchema forKey:table];
1193
+
1194
+ tempSchema = nil;
1195
+ }
1196
+
1197
+ return NSFNanoDatatypeFromString(datatype);
1198
+ }
1199
+
1200
+ - (void)NSFP_setFullColumnNamesEnabled
1201
+ {
1202
+ [self executeSQL:@"PRAGMA short_column_names = OFF;"];
1203
+ [self executeSQL:@"PRAGMA full_column_names = ON;"];
1204
+ }
1205
+
1206
+ - (NSArray *)NSFP_flattenAllTables
1207
+ {
1208
+ NSMutableSet *flattenedTables = [[NSMutableSet alloc]init];
1209
+ NSDictionary *allTables = [self allTables];
1210
+ NSEnumerator *enumerator = [allTables keyEnumerator];
1211
+ NSString *database;
1212
+ BOOL addPrefix = ([allTables count] > 1);
1213
+
1214
+ while ((database = [enumerator nextObject])) {
1215
+ NSArray *databaseTables = [allTables objectForKey:database];
1216
+
1217
+ if ((YES == addPrefix) && ([database hasPrefix:@"main"] == NO)) {
1218
+ for (NSString *table in databaseTables) {
1219
+ [flattenedTables addObject:[NSString stringWithFormat:@"%@.%@", database, table]];
1220
+ }
1221
+ } else {
1222
+ [flattenedTables addObjectsFromArray:databaseTables];
1223
+ }
1224
+ }
1225
+
1226
+ NSArray *immutableValues = [flattenedTables allObjects];
1227
+
1228
+ flattenedTables = nil;
1229
+
1230
+ return immutableValues;
1231
+ }
1232
+
1233
+ - (NSInteger)NSFP_prepareSQLite3Statement:(sqlite3_stmt **)aStatement theSQLStatement:(NSString *)aSQLQuery
1234
+ {
1235
+ if (nil == aSQLQuery)
1236
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1237
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aSQLQuery is nil.", [self class], _cmd]
1238
+ userInfo:nil]raise];
1239
+
1240
+ // Prepare SQLite's VM. It's placed here so we can speed up stores...
1241
+ int status = SQLITE_OK;
1242
+ BOOL continueLooping = YES;
1243
+ const char *query = [aSQLQuery UTF8String];
1244
+
1245
+ do {
1246
+ status = sqlite3_prepare_v2(self.sqlite, query, -1, aStatement, NULL);
1247
+
1248
+ // Since we're operating with extended result code support, extract the bits
1249
+ // and obtain the regular result code
1250
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
1251
+
1252
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
1253
+
1254
+ continueLooping = ((SQLITE_LOCKED == status) || (SQLITE_BUSY == status));
1255
+ } while (continueLooping);
1256
+
1257
+ return status;
1258
+ }
1259
+
1260
+ - (BOOL)NSFP_beginTransactionMode:(NSString *)theSQLStatement
1261
+ {
1262
+ if (nil == theSQLStatement)
1263
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1264
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
1265
+ userInfo:nil]raise];
1266
+
1267
+ if ([self isTransactionActive] == NO) {
1268
+ sqlite3_stmt *NSF_sqliteVM;
1269
+ const char *query_tail = [theSQLStatement UTF8String];
1270
+
1271
+ int status = sqlite3_prepare_v2(self.sqlite, query_tail, -1, &NSF_sqliteVM, &query_tail);
1272
+
1273
+ // Since we're operating with extended result code support, extract the bits
1274
+ // and obtain the regular result code
1275
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
1276
+
1277
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
1278
+
1279
+ if (SQLITE_OK == status) {
1280
+ BOOL continueTrying = YES;
1281
+
1282
+ do {
1283
+ status = sqlite3_step(NSF_sqliteVM);
1284
+
1285
+ // Since we're operating with extended result code support, extract the bits
1286
+ // and obtain the regular result code
1287
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
1288
+
1289
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
1290
+
1291
+ switch (status) {
1292
+ case SQLITE_OK:
1293
+ case SQLITE_DONE:
1294
+ continueTrying = NO;
1295
+ break;
1296
+ case SQLITE_BUSY:
1297
+ [self rollbackTransaction];
1298
+ sqlite3_reset(NSF_sqliteVM);
1299
+ continueTrying = YES;
1300
+ break;
1301
+ default:
1302
+ [self rollbackTransaction];
1303
+ continueTrying = NO;
1304
+ break;
1305
+ }
1306
+ } while (continueTrying);
1307
+
1308
+ return (SQLITE_OK == sqlite3_finalize(NSF_sqliteVM));
1309
+ }
1310
+ }
1311
+
1312
+ return NO;
1313
+ }
1314
+
1315
+ - (BOOL)NSFP_createTable:(NSString *)table withColumns:(NSArray *)tableColumns datatypes:(NSArray *)tableDatatypes isTemporary:(BOOL)isTemporaryFlag
1316
+ {
1317
+ if (nil == table)
1318
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1319
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
1320
+ userInfo:nil]raise];
1321
+
1322
+ if (nil == tableColumns)
1323
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1324
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableColumns is nil.", [self class], _cmd]
1325
+ userInfo:nil]raise];
1326
+
1327
+ if (nil == tableDatatypes)
1328
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1329
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableDatatypes is nil.", [self class], _cmd]
1330
+ userInfo:nil]raise];
1331
+
1332
+ if ([tableColumns count] != [tableDatatypes count])
1333
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1334
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: number of columns and datatypes mismatch.", [self class], _cmd]
1335
+ userInfo:nil]raise];
1336
+
1337
+ NSSet *allowedDatatypes = [NSFNanoEngine sharedNanoStoreEngineDatatypes];
1338
+ NSSet *specifiedDatatypes = [NSSet setWithArray:tableDatatypes];
1339
+
1340
+ if (NO == [specifiedDatatypes isSubsetOfSet:allowedDatatypes])
1341
+ return NO;
1342
+
1343
+ // Make sure we have specified ROWID in the group of columns
1344
+ NSMutableArray *revisedColumns = [[NSMutableArray alloc]initWithArray:tableColumns];
1345
+ NSMutableArray *revisedDatatypes = [[NSMutableArray alloc]initWithArray:tableDatatypes];
1346
+ NSInteger ROWIDIndex = [self NSFP_ROWIDPresenceLocation:tableColumns datatypes:tableDatatypes];
1347
+ NSString *ROWIDDatatype = [[NSString alloc]initWithFormat:@"%@ PRIMARY KEY", NSFStringFromNanoDataType(NSFNanoTypeRowUID)];
1348
+
1349
+ if (NSNotFound != ROWIDIndex) {
1350
+ // Even though the ROWID has been specified by the user, we make sure the datatype is correct
1351
+ [revisedDatatypes replaceObjectAtIndex:ROWIDIndex withObject:ROWIDDatatype];
1352
+ } else {
1353
+ // ROWID not found:add it manually
1354
+ [revisedColumns insertObject:NSFRowIDColumnName atIndex:0];
1355
+ [revisedDatatypes insertObject:ROWIDDatatype atIndex:0];
1356
+ }
1357
+
1358
+
1359
+ BOOL transactionSetHere = NO;
1360
+ if (NO == [self isTransactionActive])
1361
+ transactionSetHere = [self beginTransaction];
1362
+
1363
+ BOOL everythingIsFine = YES;
1364
+
1365
+ NSMutableString* theSQLStatement;
1366
+ if (YES == isTemporaryFlag)
1367
+ theSQLStatement = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:@"CREATE TEMPORARY TABLE %@(", table]];
1368
+ else
1369
+ theSQLStatement = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:@"CREATE TABLE %@(", table]];
1370
+
1371
+ NSMutableArray *tableCreationDatatypes = [NSMutableArray arrayWithArray:revisedDatatypes];
1372
+
1373
+ if (YES == [self NSFP_sqlString:theSQLStatement forTable:table withColumns:revisedColumns datatypes:tableCreationDatatypes]) {
1374
+ [theSQLStatement appendString:@");"];
1375
+
1376
+ everythingIsFine = (nil == [[self executeSQL:theSQLStatement]error]);
1377
+
1378
+ if (everythingIsFine) {
1379
+ // Now add the entries to NSFP_SchemaTable
1380
+ NSInteger i, count = [revisedDatatypes count];
1381
+
1382
+ for (i = 0; i < count; i++) {
1383
+ if (NO == [self NSFP_insertStringValues:[NSArray arrayWithObjects:table, [revisedColumns objectAtIndex:i], [revisedDatatypes objectAtIndex:i], nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable]) {
1384
+ everythingIsFine = NO;
1385
+ break;
1386
+ }
1387
+ }
1388
+ }
1389
+ } else {
1390
+ everythingIsFine = NO;
1391
+ }
1392
+
1393
+ if (transactionSetHere) {
1394
+ if (everythingIsFine)
1395
+ [self commitTransaction];
1396
+ else
1397
+ [self rollbackTransaction];
1398
+ }
1399
+
1400
+ if (everythingIsFine)
1401
+ [self NSFP_rebuildDatatypeCache];
1402
+
1403
+ return everythingIsFine;
1404
+ }
1405
+
1406
+ - (BOOL)NSFP_removeColumn:(NSString *)column fromTable:(NSString *)table
1407
+ {
1408
+ // Obtain all current columns and datatypes for table
1409
+ NSArray *tableInfoDatatypes = [self datatypesForTable:table];
1410
+
1411
+ if (nil == column)
1412
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1413
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
1414
+ userInfo:nil]raise];
1415
+
1416
+ if (nil == table)
1417
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1418
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
1419
+ userInfo:nil]raise];
1420
+
1421
+ NSArray *tableInfoColumns = [self columnsForTable:table];
1422
+
1423
+ NSInteger index = [tableInfoColumns indexOfObject:column];
1424
+
1425
+ if (index == NSNotFound)
1426
+ return NO;
1427
+
1428
+ // Add the new column and data type to the list
1429
+ NSMutableArray *tableColumns = [[NSMutableArray alloc]initWithArray:tableInfoColumns];
1430
+ NSMutableArray *tableDatatypes = [[NSMutableArray alloc]initWithArray:tableInfoDatatypes];
1431
+ [tableColumns removeObjectAtIndex:index];
1432
+ [tableDatatypes removeObjectAtIndex:index];
1433
+
1434
+ BOOL transactionSetHere = NO;
1435
+ if (NO == [self isTransactionActive])
1436
+ transactionSetHere = [self beginTransaction];
1437
+
1438
+ // Create a backup table with the columns and datatypes
1439
+ NSUInteger numberOfIssues = 0;
1440
+
1441
+ BOOL isTableTemporary = [[self temporaryTables]containsObject:table];
1442
+ if ([self NSFP_createTable:[NSString stringWithFormat:@"%@_backup", table] withColumns:tableColumns datatypes:tableDatatypes isTemporary:isTableTemporary]) {
1443
+ // Insert all existing data
1444
+ NSMutableString* query = [NSMutableString stringWithString:[NSString stringWithFormat:@"INSERT INTO %@_backup(", table]];
1445
+
1446
+ [self NSFP_sqlString:query appendingTags:tableColumns];
1447
+ [query appendString:@") SELECT "];
1448
+ [self NSFP_sqlString:query appendingTags:tableColumns];
1449
+
1450
+ if (nil == [[self executeSQL:[NSString stringWithFormat:@"%@ FROM %@;", query, table]]error])
1451
+ numberOfIssues++;
1452
+
1453
+ // Delete the old table
1454
+ if ([self dropTable:table] == NO)
1455
+ numberOfIssues++;
1456
+
1457
+ // Create the new table with the columns and datatypes
1458
+ isTableTemporary = [[self temporaryTables]containsObject:table];
1459
+ if ([self NSFP_createTable:table withColumns:tableColumns datatypes:tableDatatypes isTemporary:isTableTemporary] == NO)
1460
+ numberOfIssues++;
1461
+
1462
+ // Copy the data from the backup table
1463
+ query = [NSMutableString stringWithString:[NSString stringWithFormat:@"INSERT INTO %@(", table]];
1464
+
1465
+ [self NSFP_sqlString:query appendingTags:tableColumns];
1466
+ [query appendString:@") SELECT "];
1467
+ [self NSFP_sqlString:query appendingTags:tableColumns];
1468
+
1469
+ if (nil == [[self executeSQL:[NSString stringWithFormat:@"%@ FROM %@_backup;", query, table]]error])
1470
+ numberOfIssues++;
1471
+
1472
+ // Delete the backup table
1473
+ if ([self dropTable:[NSString stringWithFormat:@"%@_backup", table]] == NO)
1474
+ numberOfIssues++;
1475
+ } else {
1476
+ numberOfIssues++;
1477
+ }
1478
+
1479
+ if (transactionSetHere) {
1480
+ if (0 == numberOfIssues) {
1481
+ [self commitTransaction];
1482
+ } else {
1483
+ [self rollbackTransaction];
1484
+ }
1485
+ }
1486
+
1487
+ if (0 == numberOfIssues)
1488
+ [self NSFP_rebuildDatatypeCache];
1489
+
1490
+ return (0 == numberOfIssues);
1491
+ }
1492
+
1493
+ - (void)NSFP_rebuildDatatypeCache
1494
+ {
1495
+ // Cleanup
1496
+ schema = nil;
1497
+ schema = [[NSMutableDictionary alloc]init];
1498
+
1499
+ NSArray *tables = [self NSFP_flattenAllTables];
1500
+ if ([tables count] == 0)
1501
+ return;
1502
+
1503
+ for (NSString *table in tables) {
1504
+ NSArray *columns = [self columnsForTable:table];
1505
+ NSArray *datatypes = [self datatypesForTable:table];
1506
+ if ((nil == table) || (nil != columns) || (nil != datatypes)) {
1507
+ break;
1508
+ }
1509
+
1510
+ // Build the dictionary
1511
+ NSMutableDictionary *tableDictionary = [[NSMutableDictionary alloc]init];
1512
+ NSInteger j, columnCount = [columns count];
1513
+
1514
+ for (j = 0; j < columnCount; j++) {
1515
+ [tableDictionary setObject:[datatypes objectAtIndex:j] forKey:[columns objectAtIndex:j]];
1516
+ }
1517
+
1518
+ [schema setObject:tableDictionary forKey:table];
1519
+ }
1520
+ }
1521
+
1522
+ - (BOOL)NSFP_insertStringValues:(NSArray *)values forColumns:(NSArray *)columns table:(NSString *)table
1523
+ {
1524
+ if (nil == values)
1525
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1526
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: values is nil.", [self class], _cmd]
1527
+ userInfo:nil]raise];
1528
+
1529
+ if (nil == columns)
1530
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1531
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: columns is nil.", [self class], _cmd]
1532
+ userInfo:nil]raise];
1533
+
1534
+ if (nil == table)
1535
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1536
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
1537
+ userInfo:nil]raise];
1538
+
1539
+ // Make sure we have specified ROWID in the group of columns
1540
+ NSMutableArray *revisedColumns = (NSMutableArray*)columns;
1541
+
1542
+ // Escape all values except the one with type NSFNanoTypeRowUID
1543
+ NSMutableArray *escapedValues = [[NSMutableArray alloc]init];
1544
+ NSInteger i, count = [revisedColumns count];
1545
+
1546
+ for (i = 0; i < count; i++) {
1547
+ NSString *column = [revisedColumns objectAtIndex:i];
1548
+ NSString *value = [values objectAtIndex:i];
1549
+ NSString *escapedValue = nil;
1550
+ if (NO == [self NSFP_isColumnROWIDAlias:column forTable:table])
1551
+ escapedValue = [[NSString alloc]initWithFormat:@"'%@'", value];
1552
+ else
1553
+ escapedValue = [[NSString alloc]initWithFormat:@"%@", value];
1554
+ [escapedValues addObject:escapedValue];
1555
+ }
1556
+
1557
+ NSMutableString* theSQLStatement = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:@"INSERT INTO %@(", table]];
1558
+
1559
+ [self NSFP_sqlString:theSQLStatement appendingTags:revisedColumns];
1560
+ [theSQLStatement appendString:@") VALUES("];
1561
+ [self NSFP_sqlString:theSQLStatement appendingTags:escapedValues];
1562
+ [theSQLStatement appendString:@");"];
1563
+ BOOL insertWasOK = (nil == [[self executeSQL:theSQLStatement]error]);
1564
+
1565
+ return insertWasOK;
1566
+ }
1567
+
1568
+ - (void)NSFP_sqlString:(NSMutableString*)theSQLStatement appendingTags:(NSArray *)tags quoteTags:(BOOL)flag
1569
+ {
1570
+ if (nil == theSQLStatement)
1571
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1572
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
1573
+ userInfo:nil]raise];
1574
+
1575
+ if (nil == tags)
1576
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1577
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tags is nil.", [self class], _cmd]
1578
+ userInfo:nil]raise];
1579
+
1580
+ NSInteger i, count = [tags count];
1581
+
1582
+ if (flag) {
1583
+ for (i = 0; i < count; i++) {
1584
+ NSString *tagName = [tags objectAtIndex:i];
1585
+ NSString *escapedValue = [[NSString alloc]initWithFormat:@"'%@'", tagName];
1586
+
1587
+ [theSQLStatement appendString:escapedValue];
1588
+
1589
+ if (i != count - 1)
1590
+ [theSQLStatement appendString:@","];
1591
+ }
1592
+ } else {
1593
+ [theSQLStatement appendString:[tags componentsJoinedByString:@","]];
1594
+ }
1595
+ }
1596
+
1597
+ - (void)NSFP_sqlString:(NSMutableString*)theSQLStatement appendingTags:(NSArray *)tags
1598
+ {
1599
+ if (nil == theSQLStatement)
1600
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1601
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
1602
+ userInfo:nil]raise];
1603
+
1604
+ if (nil == tags)
1605
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1606
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tags is nil.", [self class], _cmd]
1607
+ userInfo:nil]raise];
1608
+
1609
+ [self NSFP_sqlString:theSQLStatement appendingTags:tags quoteTags:NO];
1610
+ }
1611
+
1612
+ - (BOOL)NSFP_sqlString:(NSMutableString*)theSQLStatement forTable:(NSString *)table withColumns:(NSArray *)columns datatypes:(NSArray *)datatypes
1613
+ {
1614
+ if (nil == theSQLStatement)
1615
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1616
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
1617
+ userInfo:nil]raise];
1618
+
1619
+ if (nil == table)
1620
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1621
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
1622
+ userInfo:nil]raise];
1623
+
1624
+ if (nil == columns)
1625
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1626
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: columns is nil.", [self class], _cmd]
1627
+ userInfo:nil]raise];
1628
+
1629
+ if (nil == datatypes)
1630
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1631
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatypes is nil.", [self class], _cmd]
1632
+ userInfo:nil]raise];
1633
+
1634
+ BOOL constructionSucceeded = YES;
1635
+ NSInteger i, count = [columns count];
1636
+
1637
+ for (i = 0; i < count; i++) {
1638
+ NSString *column = [columns objectAtIndex:i];
1639
+ NSString *datatype = [datatypes objectAtIndex:i];
1640
+
1641
+ if (nil != datatype) {
1642
+ // Some datatypes may be empty strings.
1643
+ // See NSFNanoEngine's header file for more info on datatypesForTable:.
1644
+ NSString *columnAndDatatype = nil;
1645
+
1646
+ if ([datatype isEqualToString:@""])
1647
+ columnAndDatatype = [[NSString alloc]initWithFormat:@"%@", column];
1648
+ else
1649
+ columnAndDatatype = [[NSString alloc]initWithFormat:@"%@ %@", column, datatype];
1650
+
1651
+ [theSQLStatement appendString:columnAndDatatype];
1652
+
1653
+ if (i != count - 1)
1654
+ [theSQLStatement appendString:@","];
1655
+ } else {
1656
+ constructionSucceeded = NO;
1657
+ }
1658
+ }
1659
+
1660
+ return constructionSucceeded;
1661
+ }
1662
+
1663
+ - (NSInteger)NSFP_ROWIDPresenceLocation:(NSArray *)tableColumns datatypes:(NSArray *)datatypes
1664
+ {
1665
+ if (nil == tableColumns)
1666
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1667
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableColumns is nil.", [self class], _cmd]
1668
+ userInfo:nil]raise];
1669
+
1670
+ if (nil == datatypes)
1671
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1672
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatypes is nil.", [self class], _cmd]
1673
+ userInfo:nil]raise];
1674
+
1675
+ // First check if we have a datatype of type NSFNanoTypeRowUID
1676
+ NSInteger ROWIDIndex = NSNotFound;
1677
+
1678
+ if (nil != datatypes) {
1679
+ NSInteger i, count = [datatypes count];
1680
+ NSString *rowUIDDatatype = NSFStringFromNanoDataType(NSFNanoTypeRowUID);
1681
+
1682
+ for (i = 0; i < count; i++) {
1683
+ if ([[[datatypes objectAtIndex:i] uppercaseString]isEqualToString:rowUIDDatatype]) {
1684
+ ROWIDIndex = i;
1685
+ break;
1686
+ }
1687
+ }
1688
+ }
1689
+
1690
+ if (NSNotFound == ROWIDIndex) {
1691
+ // Make sure we have specified ROWID in the group of columns
1692
+ NSArray *reservedKeywords = [NSFNanoEngine NSFP_sharedROWIDKeywords];
1693
+
1694
+ for (NSString *tableColumn in tableColumns) {
1695
+ NSInteger index = [reservedKeywords indexOfObject:tableColumn];
1696
+
1697
+ if (NSNotFound != index) {
1698
+ ROWIDIndex = index;
1699
+ break;
1700
+ }
1701
+ }
1702
+ }
1703
+
1704
+ return ROWIDIndex;
1705
+ }
1706
+
1707
+ - (BOOL)NSFP_isColumnROWIDAlias:(NSString *)column forTable:(NSString *)table
1708
+ {
1709
+ if (nil == column)
1710
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1711
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
1712
+ userInfo:nil]raise];
1713
+
1714
+ if (nil == table)
1715
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1716
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
1717
+ userInfo:nil]raise];
1718
+
1719
+ NSString *rowUIDDatatype = NSFStringFromNanoDataType(NSFNanoTypeRowUID);
1720
+
1721
+ if (nil != schema)
1722
+ return [[[schema objectForKey:table]objectForKey:column]isEqualToString:rowUIDDatatype];
1723
+
1724
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@ = '%@' AND %@ = '%@';", NSFP_DatatypeIdentifier, NSFP_SchemaTable, NSFP_TableIdentifier, table, NSFP_ColumnIdentifier, column];
1725
+ NSFNanoResult* result = [self executeSQL:theSQLStatement];
1726
+
1727
+ NSString *columnFound = [[result valuesForColumn:NSFP_FullDatatypeIdentifier]lastObject];
1728
+ BOOL isROWIDAlias = [columnFound isEqualToString:rowUIDDatatype];
1729
+
1730
+ return isROWIDAlias;
1731
+ }
1732
+
1733
+ - (NSString *)NSFP_prefixWithDotDelimiter:(NSString *)tableAndColumn
1734
+ {
1735
+ if (nil == tableAndColumn)
1736
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1737
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableAndColumn is nil.", [self class], _cmd]
1738
+ userInfo:nil]raise];
1739
+
1740
+ NSRange range = [tableAndColumn rangeOfString:@"." options:NSBackwardsSearch];
1741
+ if (NSNotFound == range.location)
1742
+ return tableAndColumn;
1743
+
1744
+ return [tableAndColumn substringToIndex:range.location];
1745
+ }
1746
+
1747
+ - (NSString *)NSFP_suffixWithDotDelimiter:(NSString *)tableAndColumn
1748
+ {
1749
+ if (nil == tableAndColumn)
1750
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
1751
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableAndColumn is nil.", [self class], _cmd]
1752
+ userInfo:nil]raise];
1753
+
1754
+ NSRange range = [tableAndColumn rangeOfString:@"." options:NSBackwardsSearch];
1755
+ if (NSNotFound == range.location)
1756
+ return tableAndColumn;
1757
+
1758
+ range.location++;
1759
+ range.length = [tableAndColumn length] - range.location;
1760
+
1761
+ return [tableAndColumn substringWithRange:range];
1762
+ }
1763
+
1764
+ - (void)NSFP_installCommitCallback
1765
+ {
1766
+ sqlite3_commit_hook( self.sqlite, NSFP_commitCallback, (__bridge void *)(self));
1767
+ }
1768
+
1769
+ - (void)NSFP_uninstallCommitCallback
1770
+ {
1771
+ sqlite3_commit_hook( self.sqlite, NULL, NULL);
1772
+ }
1773
+
1774
+ int NSFP_commitCallback(void* nsfdb)
1775
+ {
1776
+ return SQLITE_OK;
1777
+ }
1778
+
1779
+ /** \endcond */
1780
+
1781
+ @end