Browse files

added new items

Signed-off-by: Rifat Nabi <to.rifat@gmail.com>
  • Loading branch information...
1 parent edd3684 commit 9c1acf0beb30faaad6aaef023c07353ae5301dee @torifat torifat committed Jun 28, 2012
Showing with 5,402 additions and 3 deletions.
  1. +56 −2 AvroKeyboard.xcodeproj/project.pbxproj
  2. +133 −0 FMDatabase.h
  3. +1,147 −0 FMDatabase.m
  4. +75 −0 FMDatabasePool.h
  5. +244 −0 FMDatabasePool.m
  6. +102 −0 FMResultSet.h
  7. +423 −0 FMResultSet.m
  8. +295 −0 RegexKitLite.h
  9. +2,636 −0 RegexKitLite.m
  10. +29 −0 RegexParser.h
  11. +261 −0 RegexParser.m
  12. +1 −1 regex.json
View
58 AvroKeyboard.xcodeproj/project.pbxproj
@@ -7,6 +7,13 @@
objects = {
/* Begin PBXBuildFile section */
+ 350CECE5159C45790009F0FE /* database.db3 in Resources */ = {isa = PBXBuildFile; fileRef = 350CECD9159C45790009F0FE /* database.db3 */; };
+ 350CECE6159C45790009F0FE /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 350CECDB159C45790009F0FE /* FMDatabase.m */; };
+ 350CECE7159C45790009F0FE /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = 350CECDD159C45790009F0FE /* FMDatabasePool.m */; };
+ 350CECE8159C45790009F0FE /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 350CECDF159C45790009F0FE /* FMResultSet.m */; };
+ 350CECE9159C45790009F0FE /* regex.json in Resources */ = {isa = PBXBuildFile; fileRef = 350CECE0159C45790009F0FE /* regex.json */; };
+ 350CECEA159C45790009F0FE /* RegexKitLite.m in Sources */ = {isa = PBXBuildFile; fileRef = 350CECE2159C45790009F0FE /* RegexKitLite.m */; };
+ 350CECEB159C45790009F0FE /* RegexParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 350CECE4159C45790009F0FE /* RegexParser.m */; };
3512ED5A159785400085E81E /* preferences.nib in Resources */ = {isa = PBXBuildFile; fileRef = 3512ED581597853F0085E81E /* preferences.nib */; };
35895BF01597639A00F2A3ED /* AutoCorrect.m in Sources */ = {isa = PBXBuildFile; fileRef = 35895BEF1597639A00F2A3ED /* AutoCorrect.m */; };
35895BF3159763A300F2A3ED /* autodict.dct in Resources */ = {isa = PBXBuildFile; fileRef = 35895BF2159763A300F2A3ED /* autodict.dct */; };
@@ -32,6 +39,18 @@
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32CA4F630368D1EE00C91783 /* AvroKeyboard_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvroKeyboard_Prefix.pch; sourceTree = "<group>"; };
+ 350CECD9159C45790009F0FE /* database.db3 */ = {isa = PBXFileReference; lastKnownFileType = file; path = database.db3; sourceTree = "<group>"; };
+ 350CECDA159C45790009F0FE /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = "<group>"; };
+ 350CECDB159C45790009F0FE /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = "<group>"; };
+ 350CECDC159C45790009F0FE /* FMDatabasePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabasePool.h; sourceTree = "<group>"; };
+ 350CECDD159C45790009F0FE /* FMDatabasePool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabasePool.m; sourceTree = "<group>"; };
+ 350CECDE159C45790009F0FE /* FMResultSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMResultSet.h; sourceTree = "<group>"; };
+ 350CECDF159C45790009F0FE /* FMResultSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMResultSet.m; sourceTree = "<group>"; };
+ 350CECE0159C45790009F0FE /* regex.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = regex.json; sourceTree = "<group>"; };
+ 350CECE1159C45790009F0FE /* RegexKitLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegexKitLite.h; sourceTree = "<group>"; };
+ 350CECE2159C45790009F0FE /* RegexKitLite.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexKitLite.m; sourceTree = "<group>"; };
+ 350CECE3159C45790009F0FE /* RegexParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegexParser.h; sourceTree = "<group>"; };
+ 350CECE4159C45790009F0FE /* RegexParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RegexParser.m; sourceTree = "<group>"; };
3512ED591597853F0085E81E /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/preferences.nib; sourceTree = "<group>"; };
35895BEE1597639A00F2A3ED /* AutoCorrect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoCorrect.h; sourceTree = "<group>"; };
35895BEF1597639A00F2A3ED /* AutoCorrect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AutoCorrect.m; sourceTree = "<group>"; };
@@ -74,7 +93,11 @@
children = (
35B007C51597BF56000CE409 /* Controllers */,
35B007C31597BF4C000CE409 /* Models */,
+ 350CECED159C45870009F0FE /* FMDatabase */,
+ 350CECEE159C45C50009F0FE /* RegexKit */,
35B007CC1597BFC3000CE409 /* Others */,
+ 35DBC8691597A15B00B9772E /* MainMenuAppDelegate.h */,
+ 35DBC86A1597A15B00B9772E /* MainMenuAppDelegate.m */,
);
name = Classes;
sourceTree = "<group>";
@@ -132,6 +155,8 @@
35B47477159A668D00C69C7D /* avro.icns */,
35895BF2159763A300F2A3ED /* autodict.dct */,
35F4DE72159404A3001EAC81 /* data.json */,
+ 350CECE0159C45790009F0FE /* regex.json */,
+ 350CECD9159C45790009F0FE /* database.db3 */,
35895BFA1597706800F2A3ED /* MainMenu.nib */,
3512ED581597853F0085E81E /* preferences.nib */,
8D1107310486CEB800E47090 /* Info.plist */,
@@ -149,6 +174,28 @@
name = Frameworks;
sourceTree = "<group>";
};
+ 350CECED159C45870009F0FE /* FMDatabase */ = {
+ isa = PBXGroup;
+ children = (
+ 350CECDA159C45790009F0FE /* FMDatabase.h */,
+ 350CECDB159C45790009F0FE /* FMDatabase.m */,
+ 350CECDC159C45790009F0FE /* FMDatabasePool.h */,
+ 350CECDD159C45790009F0FE /* FMDatabasePool.m */,
+ 350CECDE159C45790009F0FE /* FMResultSet.h */,
+ 350CECDF159C45790009F0FE /* FMResultSet.m */,
+ );
+ name = FMDatabase;
+ sourceTree = "<group>";
+ };
+ 350CECEE159C45C50009F0FE /* RegexKit */ = {
+ isa = PBXGroup;
+ children = (
+ 350CECE1159C45790009F0FE /* RegexKitLite.h */,
+ 350CECE2159C45790009F0FE /* RegexKitLite.m */,
+ );
+ name = RegexKit;
+ sourceTree = "<group>";
+ };
35B007C31597BF4C000CE409 /* Models */ = {
isa = PBXGroup;
children = (
@@ -172,12 +219,12 @@
35B007CC1597BFC3000CE409 /* Others */ = {
isa = PBXGroup;
children = (
- 35DBC8691597A15B00B9772E /* MainMenuAppDelegate.h */,
- 35DBC86A1597A15B00B9772E /* MainMenuAppDelegate.m */,
35895BEE1597639A00F2A3ED /* AutoCorrect.h */,
35895BEF1597639A00F2A3ED /* AutoCorrect.m */,
35F4DE7D1594096B001EAC81 /* AvroParser.h */,
35F4DE7E1594096B001EAC81 /* AvroParser.m */,
+ 350CECE3159C45790009F0FE /* RegexParser.h */,
+ 350CECE4159C45790009F0FE /* RegexParser.m */,
359C1C6E159286750080E2FD /* Candidates.h */,
359C1C6F159286750080E2FD /* Candidates.m */,
);
@@ -241,6 +288,8 @@
35895BFE1597706800F2A3ED /* MainMenu.nib in Resources */,
3512ED5A159785400085E81E /* preferences.nib in Resources */,
35B47478159A668D00C69C7D /* avro.icns in Resources */,
+ 350CECE5159C45790009F0FE /* database.db3 in Resources */,
+ 350CECE9159C45790009F0FE /* regex.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -259,6 +308,11 @@
35DBC86B1597A15B00B9772E /* MainMenuAppDelegate.m in Sources */,
35B007C81597BF89000CE409 /* AutoCorrectModel.m in Sources */,
35B007CB1597BF9A000CE409 /* PreferencesController.m in Sources */,
+ 350CECE6159C45790009F0FE /* FMDatabase.m in Sources */,
+ 350CECE7159C45790009F0FE /* FMDatabasePool.m in Sources */,
+ 350CECE8159C45790009F0FE /* FMResultSet.m in Sources */,
+ 350CECEA159C45790009F0FE /* RegexKitLite.m in Sources */,
+ 350CECEB159C45790009F0FE /* RegexParser.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
133 FMDatabase.h
@@ -0,0 +1,133 @@
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+#import "FMResultSet.h"
+#import "FMDatabasePool.h"
+
+
+#if ! __has_feature(objc_arc)
+ #define FMDBAutorelease(__v) ([__v autorelease]);
+ #define FMDBReturnAutoreleased FMDBAutorelease
+
+ #define FMDBRetain(__v) ([__v retain]);
+ #define FMDBReturnRetained FMDBRetain
+
+ #define FMDBRelease(__v) ([__v release]);
+#else
+ // -fobjc-arc
+ #define FMDBAutorelease(__v)
+ #define FMDBReturnAutoreleased(__v) (__v)
+
+ #define FMDBRetain(__v)
+ #define FMDBReturnRetained(__v) (__v)
+
+ #define FMDBRelease(__v)
+#endif
+
+
+@interface FMDatabase : NSObject {
+
+ sqlite3* _db;
+ NSString* _databasePath;
+ BOOL _logsErrors;
+ BOOL _crashOnErrors;
+ BOOL _traceExecution;
+ BOOL _checkedOut;
+ BOOL _shouldCacheStatements;
+ BOOL _isExecutingStatement;
+ BOOL _inTransaction;
+ int _busyRetryTimeout;
+
+ NSMutableDictionary *_cachedStatements;
+ NSMutableSet *_openResultSets;
+ NSMutableSet *_openFunctions;
+
+}
+
+
+@property (assign) BOOL traceExecution;
+@property (assign) BOOL checkedOut;
+@property (assign) int busyRetryTimeout;
+@property (assign) BOOL crashOnErrors;
+@property (assign) BOOL logsErrors;
+@property (retain) NSMutableDictionary *cachedStatements;
+
+
++ (id)databaseWithPath:(NSString*)inPath;
+- (id)initWithPath:(NSString*)inPath;
+
+- (BOOL)open;
+#if SQLITE_VERSION_NUMBER >= 3005000
+- (BOOL)openWithFlags:(int)flags;
+#endif
+- (BOOL)close;
+- (BOOL)goodConnection;
+- (void)clearCachedStatements;
+- (void)closeOpenResultSets;
+- (BOOL)hasOpenResultSets;
+
+// encryption methods. You need to have purchased the sqlite encryption extensions for these to work.
+- (BOOL)setKey:(NSString*)key;
+- (BOOL)rekey:(NSString*)key;
+
+- (NSString *)databasePath;
+
+- (NSString*)lastErrorMessage;
+
+- (int)lastErrorCode;
+- (BOOL)hadError;
+- (NSError*)lastError;
+
+- (sqlite_int64)lastInsertRowId;
+
+- (sqlite3*)sqliteHandle;
+
+- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ...;
+- (BOOL)executeUpdate:(NSString*)sql, ...;
+- (BOOL)executeUpdateWithFormat:(NSString *)format, ...;
+- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
+- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments;
+
+- (FMResultSet *)executeQuery:(NSString*)sql, ...;
+- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...;
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
+- (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments;
+
+- (BOOL)rollback;
+- (BOOL)commit;
+- (BOOL)beginTransaction;
+- (BOOL)beginDeferredTransaction;
+- (BOOL)inTransaction;
+- (BOOL)shouldCacheStatements;
+- (void)setShouldCacheStatements:(BOOL)value;
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+- (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr;
+- (NSError*)inSavePoint:(void (^)(BOOL *rollback))block;
+#endif
+
++ (BOOL)isSQLiteThreadSafe;
++ (NSString*)sqliteLibVersion;
+
+- (int)changes;
+
+- (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block;
+
+@end
+
+@interface FMStatement : NSObject {
+ sqlite3_stmt *_statement;
+ NSString *_query;
+ long _useCount;
+}
+
+@property (assign) long useCount;
+@property (retain) NSString *query;
+@property (assign) sqlite3_stmt *statement;
+
+- (void)close;
+- (void)reset;
+
+@end
+
View
1,147 FMDatabase.m
@@ -0,0 +1,1147 @@
+#import "FMDatabase.h"
+#import "unistd.h"
+#import <objc/runtime.h>
+
+@interface FMDatabase ()
+
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
+- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args;
+@end
+
+@implementation FMDatabase
+@synthesize cachedStatements=_cachedStatements;
+@synthesize logsErrors=_logsErrors;
+@synthesize crashOnErrors=_crashOnErrors;
+@synthesize busyRetryTimeout=_busyRetryTimeout;
+@synthesize checkedOut=_checkedOut;
+@synthesize traceExecution=_traceExecution;
+
++ (id)databaseWithPath:(NSString*)aPath {
+ return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
+}
+
++ (NSString*)sqliteLibVersion {
+ return [NSString stringWithFormat:@"%s", sqlite3_libversion()];
+}
+
++ (BOOL)isSQLiteThreadSafe {
+ // make sure to read the sqlite headers on this guy!
+ return sqlite3_threadsafe() != 0;
+}
+
+- (id)initWithPath:(NSString*)aPath {
+
+ assert(sqlite3_threadsafe()); // whoa there big boy- gotta make sure sqlite it happy with what we're going to do.
+
+ self = [super init];
+
+ if (self) {
+ _databasePath = [aPath copy];
+ _openResultSets = [[NSMutableSet alloc] init];
+ _db = 0x00;
+ _logsErrors = 0x00;
+ _crashOnErrors = 0x00;
+ _busyRetryTimeout = 0x00;
+ }
+
+ return self;
+}
+
+- (void)finalize {
+ [self close];
+ [super finalize];
+}
+
+- (void)dealloc {
+ [self close];
+ FMDBRelease(_openResultSets);
+ FMDBRelease(_cachedStatements);
+ FMDBRelease(_databasePath);
+ FMDBRelease(_openFunctions);
+
+#if ! __has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
+- (NSString *)databasePath {
+ return _databasePath;
+}
+
+- (sqlite3*)sqliteHandle {
+ return _db;
+}
+
+- (BOOL)open {
+ if (_db) {
+ return YES;
+ }
+
+ int err = sqlite3_open((_databasePath ? [_databasePath fileSystemRepresentation] : ":memory:"), &_db );
+ if(err != SQLITE_OK) {
+ NSLog(@"error opening!: %d", err);
+ return NO;
+ }
+
+ return YES;
+}
+
+#if SQLITE_VERSION_NUMBER >= 3005000
+- (BOOL)openWithFlags:(int)flags {
+ int err = sqlite3_open_v2((_databasePath ? [_databasePath fileSystemRepresentation] : ":memory:"), &_db, flags, NULL /* Name of VFS module to use */);
+ if(err != SQLITE_OK) {
+ NSLog(@"error opening!: %d", err);
+ return NO;
+ }
+ return YES;
+}
+#endif
+
+
+- (BOOL)close {
+
+ [self clearCachedStatements];
+ [self closeOpenResultSets];
+
+ if (!_db) {
+ return YES;
+ }
+
+ int rc;
+ BOOL retry;
+ int numberOfRetries = 0;
+ BOOL triedFinalizingOpenStatements = NO;
+
+ do {
+ retry = NO;
+ rc = sqlite3_close(_db);
+
+ if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+
+ retry = YES;
+ usleep(20);
+
+ if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+ NSLog(@"%s:%d", __FUNCTION__, __LINE__);
+ NSLog(@"Database busy, unable to close");
+ return NO;
+ }
+
+ if (!triedFinalizingOpenStatements) {
+ triedFinalizingOpenStatements = YES;
+ sqlite3_stmt *pStmt;
+ while ((pStmt = sqlite3_next_stmt(_db, 0x00)) !=0) {
+ NSLog(@"Closing leaked statement");
+ sqlite3_finalize(pStmt);
+ }
+ }
+ }
+ else if (SQLITE_OK != rc) {
+ NSLog(@"error closing!: %d", rc);
+ }
+ }
+ while (retry);
+
+ _db = nil;
+ return YES;
+}
+
+- (void)clearCachedStatements {
+
+ for (FMStatement *cachedStmt in [_cachedStatements objectEnumerator]) {
+ [cachedStmt close];
+ }
+
+ [_cachedStatements removeAllObjects];
+}
+
+- (BOOL)hasOpenResultSets {
+ return [_openResultSets count] > 0;
+}
+
+- (void)closeOpenResultSets {
+
+ //Copy the set so we don't get mutation errors
+ NSMutableSet *openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]);
+ for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) {
+ FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue];
+
+ [rs setParentDB:nil];
+ [rs close];
+
+ [_openResultSets removeObject:rsInWrappedInATastyValueMeal];
+ }
+}
+
+- (void)resultSetDidClose:(FMResultSet *)resultSet {
+ NSValue *setValue = [NSValue valueWithNonretainedObject:resultSet];
+
+ [_openResultSets removeObject:setValue];
+}
+
+- (FMStatement*)cachedStatementForQuery:(NSString*)query {
+ return [_cachedStatements objectForKey:query];
+}
+
+- (void)setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query {
+
+ query = [query copy]; // in case we got handed in a mutable string...
+
+ [statement setQuery:query];
+
+ [_cachedStatements setObject:statement forKey:query];
+
+ FMDBRelease(query);
+}
+
+
+- (BOOL)rekey:(NSString*)key {
+#ifdef SQLITE_HAS_CODEC
+ if (!key) {
+ return NO;
+ }
+
+ int rc = sqlite3_rekey(_db, [key UTF8String], (int)strlen([key UTF8String]));
+
+ if (rc != SQLITE_OK) {
+ NSLog(@"error on rekey: %d", rc);
+ NSLog(@"%@", [self lastErrorMessage]);
+ }
+
+ return (rc == SQLITE_OK);
+#else
+ return NO;
+#endif
+}
+
+- (BOOL)setKey:(NSString*)key {
+#ifdef SQLITE_HAS_CODEC
+ if (!key) {
+ return NO;
+ }
+
+ int rc = sqlite3_key(_db, [key UTF8String], (int)strlen([key UTF8String]));
+
+ return (rc == SQLITE_OK);
+#else
+ return NO;
+#endif
+}
+
+- (BOOL)goodConnection {
+
+ if (!_db) {
+ return NO;
+ }
+
+ FMResultSet *rs = [self executeQuery:@"select name from sqlite_master where type='table'"];
+
+ if (rs) {
+ [rs close];
+ return YES;
+ }
+
+ return NO;
+}
+
+- (void)warnInUse {
+ NSLog(@"The FMDatabase %@ is currently in use.", self);
+
+#ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert1(false, @"The FMDatabase %@ is currently in use.", self);
+ }
+#endif
+}
+
+- (BOOL)databaseExists {
+
+ if (!_db) {
+
+ NSLog(@"The FMDatabase %@ is not open.", self);
+
+ #ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert1(false, @"The FMDatabase %@ is not open.", self);
+ }
+ #endif
+
+ return NO;
+ }
+
+ return YES;
+}
+
+- (NSString*)lastErrorMessage {
+ return [NSString stringWithUTF8String:sqlite3_errmsg(_db)];
+}
+
+- (BOOL)hadError {
+ int lastErrCode = [self lastErrorCode];
+
+ return (lastErrCode > SQLITE_OK && lastErrCode < SQLITE_ROW);
+}
+
+- (int)lastErrorCode {
+ return sqlite3_errcode(_db);
+}
+
+
+- (NSError*)errorWithMessage:(NSString*)message {
+ NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey];
+
+ return [NSError errorWithDomain:@"FMDatabase" code:sqlite3_errcode(_db) userInfo:errorMessage];
+}
+
+- (NSError*)lastError {
+ return [self errorWithMessage:[self lastErrorMessage]];
+}
+
+- (sqlite_int64)lastInsertRowId {
+
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return NO;
+ }
+
+ _isExecutingStatement = YES;
+
+ sqlite_int64 ret = sqlite3_last_insert_rowid(_db);
+
+ _isExecutingStatement = NO;
+
+ return ret;
+}
+
+- (int)changes {
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return 0;
+ }
+
+ _isExecutingStatement = YES;
+
+ int ret = sqlite3_changes(_db);
+
+ _isExecutingStatement = NO;
+
+ return ret;
+}
+
+- (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
+
+ if ((!obj) || ((NSNull *)obj == [NSNull null])) {
+ sqlite3_bind_null(pStmt, idx);
+ }
+
+ // FIXME - someday check the return codes on these binds.
+ else if ([obj isKindOfClass:[NSData class]]) {
+ const void *bytes = [obj bytes];
+ if (!bytes) {
+ // it's an empty NSData object, aka [NSData data].
+ // Don't pass a NULL pointer, or sqlite will bind a SQL null instead of a blob.
+ bytes = "";
+ }
+ sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);
+ }
+ else if ([obj isKindOfClass:[NSDate class]]) {
+ sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]);
+ }
+ else if ([obj isKindOfClass:[NSNumber class]]) {
+
+ if (strcmp([obj objCType], @encode(BOOL)) == 0) {
+ sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
+ }
+ else if (strcmp([obj objCType], @encode(int)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, [obj longValue]);
+ }
+ else if (strcmp([obj objCType], @encode(long)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, [obj longValue]);
+ }
+ else if (strcmp([obj objCType], @encode(long long)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
+ }
+ else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) {
+ sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
+ }
+ else if (strcmp([obj objCType], @encode(float)) == 0) {
+ sqlite3_bind_double(pStmt, idx, [obj floatValue]);
+ }
+ else if (strcmp([obj objCType], @encode(double)) == 0) {
+ sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
+ }
+ else {
+ sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
+ }
+ }
+ else {
+ sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
+ }
+}
+
+- (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments {
+
+ NSUInteger length = [sql length];
+ unichar last = '\0';
+ for (NSUInteger i = 0; i < length; ++i) {
+ id arg = nil;
+ unichar current = [sql characterAtIndex:i];
+ unichar add = current;
+ if (last == '%') {
+ switch (current) {
+ case '@':
+ arg = va_arg(args, id);
+ break;
+ case 'c':
+ // warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg has undefined behavior because arguments will be promoted to 'int'
+ arg = [NSString stringWithFormat:@"%c", va_arg(args, int)];
+ break;
+ case 's':
+ arg = [NSString stringWithUTF8String:va_arg(args, char*)];
+ break;
+ case 'd':
+ case 'D':
+ case 'i':
+ arg = [NSNumber numberWithInt:va_arg(args, int)];
+ break;
+ case 'u':
+ case 'U':
+ arg = [NSNumber numberWithUnsignedInt:va_arg(args, unsigned int)];
+ break;
+ case 'h':
+ i++;
+ if (i < length && [sql characterAtIndex:i] == 'i') {
+ // warning: second argument to 'va_arg' is of promotable type 'short'; this va_arg has undefined behavior because arguments will be promoted to 'int'
+ arg = [NSNumber numberWithShort:(short)(va_arg(args, int))];
+ }
+ else if (i < length && [sql characterAtIndex:i] == 'u') {
+ // warning: second argument to 'va_arg' is of promotable type 'unsigned short'; this va_arg has undefined behavior because arguments will be promoted to 'int'
+ arg = [NSNumber numberWithUnsignedShort:(unsigned short)(va_arg(args, uint))];
+ }
+ else {
+ i--;
+ }
+ break;
+ case 'q':
+ i++;
+ if (i < length && [sql characterAtIndex:i] == 'i') {
+ arg = [NSNumber numberWithLongLong:va_arg(args, long long)];
+ }
+ else if (i < length && [sql characterAtIndex:i] == 'u') {
+ arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)];
+ }
+ else {
+ i--;
+ }
+ break;
+ case 'f':
+ arg = [NSNumber numberWithDouble:va_arg(args, double)];
+ break;
+ case 'g':
+ // warning: second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double'
+ arg = [NSNumber numberWithFloat:(float)(va_arg(args, double))];
+ break;
+ case 'l':
+ i++;
+ if (i < length) {
+ unichar next = [sql characterAtIndex:i];
+ if (next == 'l') {
+ i++;
+ if (i < length && [sql characterAtIndex:i] == 'd') {
+ //%lld
+ arg = [NSNumber numberWithLongLong:va_arg(args, long long)];
+ }
+ else if (i < length && [sql characterAtIndex:i] == 'u') {
+ //%llu
+ arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)];
+ }
+ else {
+ i--;
+ }
+ }
+ else if (next == 'd') {
+ //%ld
+ arg = [NSNumber numberWithLong:va_arg(args, long)];
+ }
+ else if (next == 'u') {
+ //%lu
+ arg = [NSNumber numberWithUnsignedLong:va_arg(args, unsigned long)];
+ }
+ else {
+ i--;
+ }
+ }
+ else {
+ i--;
+ }
+ break;
+ default:
+ // something else that we can't interpret. just pass it on through like normal
+ break;
+ }
+ }
+ else if (current == '%') {
+ // percent sign; skip this character
+ add = '\0';
+ }
+
+ if (arg != nil) {
+ [cleanedSQL appendString:@"?"];
+ [arguments addObject:arg];
+ }
+ else if (add != '\0') {
+ [cleanedSQL appendFormat:@"%C", add];
+ }
+ last = current;
+ }
+}
+
+- (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments {
+ return [self executeQuery:sql withArgumentsInArray:nil orDictionary:arguments orVAList:nil];
+}
+
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
+
+ if (![self databaseExists]) {
+ return 0x00;
+ }
+
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return 0x00;
+ }
+
+ _isExecutingStatement = YES;
+
+ int rc = 0x00;
+ sqlite3_stmt *pStmt = 0x00;
+ FMStatement *statement = 0x00;
+ FMResultSet *rs = 0x00;
+
+ if (_traceExecution && sql) {
+ NSLog(@"%@ executeQuery: %@", self, sql);
+ }
+
+ if (_shouldCacheStatements) {
+ statement = [self cachedStatementForQuery:sql];
+ pStmt = statement ? [statement statement] : 0x00;
+ }
+
+ int numberOfRetries = 0;
+ BOOL retry = NO;
+
+ if (!pStmt) {
+ do {
+ retry = NO;
+ rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
+
+ if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+ retry = YES;
+ usleep(20);
+
+ if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return nil;
+ }
+ }
+ else if (SQLITE_OK != rc) {
+
+ if (_logsErrors) {
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ NSLog(@"DB Query: %@", sql);
+ NSLog(@"DB Path: %@", _databasePath);
+#ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ }
+#endif
+ }
+
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return nil;
+ }
+ }
+ while (retry);
+ }
+
+ id obj;
+ int idx = 0;
+ int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!)
+
+ // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
+ if (dictionaryArgs) {
+
+ for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
+
+ // Prefix the key with a colon.
+ NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
+
+ // Get the index for the parameter name.
+ int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
+
+ FMDBRelease(parameterName);
+
+ if (namedIdx > 0) {
+ // Standard binding from here.
+ [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
+ }
+ else {
+ NSLog(@"Could not find index for %@", dictionaryKey);
+ }
+ }
+
+ // we need the count of params to avoid an error below.
+ idx = (int) [[dictionaryArgs allKeys] count];
+ }
+ else {
+
+ while (idx < queryCount) {
+
+ if (arrayArgs) {
+ obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
+ }
+ else {
+ obj = va_arg(args, id);
+ }
+
+ if (_traceExecution) {
+ NSLog(@"obj: %@", obj);
+ }
+
+ idx++;
+
+ [self bindObject:obj toColumn:idx inStatement:pStmt];
+ }
+ }
+
+ if (idx != queryCount) {
+ NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)");
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return nil;
+ }
+
+ FMDBRetain(statement); // to balance the release below
+
+ if (!statement) {
+ statement = [[FMStatement alloc] init];
+ [statement setStatement:pStmt];
+
+ if (_shouldCacheStatements) {
+ [self setCachedStatement:statement forQuery:sql];
+ }
+ }
+
+ // the statement gets closed in rs's dealloc or [rs close];
+ rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self];
+ [rs setQuery:sql];
+
+ NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs];
+ [_openResultSets addObject:openResultSet];
+
+ [statement setUseCount:[statement useCount] + 1];
+
+ FMDBRelease(statement);
+
+ _isExecutingStatement = NO;
+
+ return rs;
+}
+
+- (FMResultSet *)executeQuery:(NSString*)sql, ... {
+ va_list args;
+ va_start(args, sql);
+
+ id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args];
+
+ va_end(args);
+ return result;
+}
+
+- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... {
+ va_list args;
+ va_start(args, format);
+
+ NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]];
+ NSMutableArray *arguments = [NSMutableArray array];
+ [self extractSQL:format argumentsList:args intoString:sql arguments:arguments];
+
+ va_end(args);
+
+ return [self executeQuery:sql withArgumentsInArray:arguments];
+}
+
+- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments {
+ return [self executeQuery:sql withArgumentsInArray:arguments orDictionary:nil orVAList:nil];
+}
+
+- (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args {
+
+ if (![self databaseExists]) {
+ return NO;
+ }
+
+ if (_isExecutingStatement) {
+ [self warnInUse];
+ return NO;
+ }
+
+ _isExecutingStatement = YES;
+
+ int rc = 0x00;
+ sqlite3_stmt *pStmt = 0x00;
+ FMStatement *cachedStmt = 0x00;
+
+ if (_traceExecution && sql) {
+ NSLog(@"%@ executeUpdate: %@", self, sql);
+ }
+
+ if (_shouldCacheStatements) {
+ cachedStmt = [self cachedStatementForQuery:sql];
+ pStmt = cachedStmt ? [cachedStmt statement] : 0x00;
+ }
+
+ int numberOfRetries = 0;
+ BOOL retry = NO;
+
+ if (!pStmt) {
+
+ do {
+ retry = NO;
+ rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
+ if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+ retry = YES;
+ usleep(20);
+
+ if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return NO;
+ }
+ }
+ else if (SQLITE_OK != rc) {
+
+ if (_logsErrors) {
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ NSLog(@"DB Query: %@", sql);
+ NSLog(@"DB Path: %@", _databasePath);
+#ifndef NS_BLOCK_ASSERTIONS
+ if (_crashOnErrors) {
+ abort();
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
+ }
+#endif
+ }
+
+ sqlite3_finalize(pStmt);
+
+ if (outErr) {
+ *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
+ }
+
+ _isExecutingStatement = NO;
+ return NO;
+ }
+ }
+ while (retry);
+ }
+
+ id obj;
+ int idx = 0;
+ int queryCount = sqlite3_bind_parameter_count(pStmt);
+
+ // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support
+ if (dictionaryArgs) {
+
+ for (NSString *dictionaryKey in [dictionaryArgs allKeys]) {
+
+ // Prefix the key with a colon.
+ NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey];
+
+ // Get the index for the parameter name.
+ int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]);
+
+ FMDBRelease(parameterName);
+
+ if (namedIdx > 0) {
+ // Standard binding from here.
+ [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt];
+ }
+ else {
+ NSLog(@"Could not find index for %@", dictionaryKey);
+ }
+ }
+
+ // we need the count of params to avoid an error below.
+ idx = (int) [[dictionaryArgs allKeys] count];
+ }
+ else {
+
+ while (idx < queryCount) {
+
+ if (arrayArgs) {
+ obj = [arrayArgs objectAtIndex:(NSUInteger)idx];
+ }
+ else {
+ obj = va_arg(args, id);
+ }
+
+ if (_traceExecution) {
+ NSLog(@"obj: %@", obj);
+ }
+
+ idx++;
+
+ [self bindObject:obj toColumn:idx inStatement:pStmt];
+ }
+ }
+
+
+ if (idx != queryCount) {
+ NSLog(@"Error: the bind count is not correct for the # of variables (%@) (executeUpdate)", sql);
+ sqlite3_finalize(pStmt);
+ _isExecutingStatement = NO;
+ return NO;
+ }
+
+ /* Call sqlite3_step() to run the virtual machine. Since the SQL being
+ ** executed is not a SELECT statement, we assume no data will be returned.
+ */
+ numberOfRetries = 0;
+
+ do {
+ rc = sqlite3_step(pStmt);
+ retry = NO;
+
+ if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+ // this will happen if the db is locked, like if we are doing an update or insert.
+ // in that case, retry the step... and maybe wait just 10 milliseconds.
+ retry = YES;
+ if (SQLITE_LOCKED == rc) {
+ rc = sqlite3_reset(pStmt);
+ if (rc != SQLITE_LOCKED) {
+ NSLog(@"Unexpected result from sqlite3_reset (%d) eu", rc);
+ }
+ }
+ usleep(20);
+
+ if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
+ NSLog(@"Database busy");
+ retry = NO;
+ }
+ }
+ else if (SQLITE_DONE == rc) {
+ // all is well, let's return.
+ }
+ else if (SQLITE_ERROR == rc) {
+ NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
+ NSLog(@"DB Query: %@", sql);
+ }
+ else if (SQLITE_MISUSE == rc) {
+ // uh oh.
+ NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
+ NSLog(@"DB Query: %@", sql);
+ }
+ else {
+ // wtf?
+ NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
+ NSLog(@"DB Query: %@", sql);
+ }
+
+ } while (retry);
+
+ if (rc == SQLITE_ROW) {
+ NSAssert1(NO, @"A executeUpdate is being called with a query string '%@'", sql);
+ }
+
+ if (_shouldCacheStatements && !cachedStmt) {
+ cachedStmt = [[FMStatement alloc] init];
+
+ [cachedStmt setStatement:pStmt];
+
+ [self setCachedStatement:cachedStmt forQuery:sql];
+
+ FMDBRelease(cachedStmt);
+ }
+
+ int closeErrorCode;
+
+ if (cachedStmt) {
+ [cachedStmt setUseCount:[cachedStmt useCount] + 1];
+ closeErrorCode = sqlite3_reset(pStmt);
+ }
+ else {
+ /* Finalize the virtual machine. This releases all memory and other
+ ** resources allocated by the sqlite3_prepare() call above.
+ */
+ closeErrorCode = sqlite3_finalize(pStmt);
+ }
+
+ if (closeErrorCode != SQLITE_OK) {
+ NSLog(@"Unknown error finalizing or resetting statement (%d: %s)", closeErrorCode, sqlite3_errmsg(_db));
+ NSLog(@"DB Query: %@", sql);
+ }
+
+ _isExecutingStatement = NO;
+ return (rc == SQLITE_DONE || rc == SQLITE_OK);
+}
+
+
+- (BOOL)executeUpdate:(NSString*)sql, ... {
+ va_list args;
+ va_start(args, sql);
+
+ BOOL result = [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:nil orVAList:args];
+
+ va_end(args);
+ return result;
+}
+
+- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments {
+ return [self executeUpdate:sql error:nil withArgumentsInArray:arguments orDictionary:nil orVAList:nil];
+}
+
+- (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments {
+ return [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:arguments orVAList:nil];
+}
+
+- (BOOL)executeUpdateWithFormat:(NSString*)format, ... {
+ va_list args;
+ va_start(args, format);
+
+ NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]];
+ NSMutableArray *arguments = [NSMutableArray array];
+
+ [self extractSQL:format argumentsList:args intoString:sql arguments:arguments];
+
+ va_end(args);
+
+ return [self executeUpdate:sql withArgumentsInArray:arguments];
+}
+
+- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ... {
+ va_list args;
+ va_start(args, outErr);
+
+ BOOL result = [self executeUpdate:sql error:outErr withArgumentsInArray:nil orDictionary:nil orVAList:args];
+
+ va_end(args);
+ return result;
+}
+
+- (BOOL)rollback {
+ BOOL b = [self executeUpdate:@"rollback transaction"];
+
+ if (b) {
+ _inTransaction = NO;
+ }
+
+ return b;
+}
+
+- (BOOL)commit {
+ BOOL b = [self executeUpdate:@"commit transaction"];
+
+ if (b) {
+ _inTransaction = NO;
+ }
+
+ return b;
+}
+
+- (BOOL)beginDeferredTransaction {
+
+ BOOL b = [self executeUpdate:@"begin deferred transaction"];
+ if (b) {
+ _inTransaction = YES;
+ }
+
+ return b;
+}
+
+- (BOOL)beginTransaction {
+
+ BOOL b = [self executeUpdate:@"begin exclusive transaction"];
+ if (b) {
+ _inTransaction = YES;
+ }
+
+ return b;
+}
+
+- (BOOL)inTransaction {
+ return _inTransaction;
+}
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+
+- (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr {
+
+ // FIXME: make sure the savepoint name doesn't have a ' in it.
+
+ NSParameterAssert(name);
+
+ if (![self executeUpdate:[NSString stringWithFormat:@"savepoint '%@';", name]]) {
+
+ if (*outErr) {
+ *outErr = [self lastError];
+ }
+
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr {
+
+ NSParameterAssert(name);
+
+ BOOL worked = [self executeUpdate:[NSString stringWithFormat:@"release savepoint '%@';", name]];
+
+ if (!worked && *outErr) {
+ *outErr = [self lastError];
+ }
+
+ return worked;
+}
+
+- (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr {
+
+ NSParameterAssert(name);
+
+ BOOL worked = [self executeUpdate:[NSString stringWithFormat:@"rollback transaction to savepoint '%@';", name]];
+
+ if (!worked && *outErr) {
+ *outErr = [self lastError];
+ }
+
+ return worked;
+}
+
+- (NSError*)inSavePoint:(void (^)(BOOL *rollback))block {
+ static unsigned long savePointIdx = 0;
+
+ NSString *name = [NSString stringWithFormat:@"dbSavePoint%ld", savePointIdx++];
+
+ BOOL shouldRollback = NO;
+
+ NSError *err = 0x00;
+
+ if (![self startSavePointWithName:name error:&err]) {
+ return err;
+ }
+
+ block(&shouldRollback);
+
+ if (shouldRollback) {
+ [self rollbackToSavePointWithName:name error:&err];
+ }
+ else {
+ [self releaseSavePointWithName:name error:&err];
+ }
+
+ return err;
+}
+
+#endif
+
+
+- (BOOL)shouldCacheStatements {
+ return _shouldCacheStatements;
+}
+
+- (void)setShouldCacheStatements:(BOOL)value {
+
+ _shouldCacheStatements = value;
+
+ if (_shouldCacheStatements && !_cachedStatements) {
+ [self setCachedStatements:[NSMutableDictionary dictionary]];
+ }
+
+ if (!_shouldCacheStatements) {
+ [self setCachedStatements:nil];
+ }
+}
+
+void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv);
+void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv) {
+#if ! __has_feature(objc_arc)
+ void (^block)(sqlite3_context *context, int argc, sqlite3_value **argv) = (id)sqlite3_user_data(context);
+#else
+ void (^block)(sqlite3_context *context, int argc, sqlite3_value **argv) = (__bridge id)sqlite3_user_data(context);
+#endif
+ block(context, argc, argv);
+}
+
+
+- (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block {
+
+ if (!_openFunctions) {
+ _openFunctions = [NSMutableSet new];
+ }
+
+ id b = FMDBReturnAutoreleased([block copy]);
+
+ [_openFunctions addObject:b];
+
+ /* I tried adding custom functions to release the block when the connection is destroyed- but they seemed to never be called, so we use _openFunctions to store the values instead. */
+#if ! __has_feature(objc_arc)
+ sqlite3_create_function([self sqliteHandle], [name UTF8String], count, SQLITE_UTF8, (void*)b, &FMDBBlockSQLiteCallBackFunction, 0x00, 0x00);
+#else
+ sqlite3_create_function([self sqliteHandle], [name UTF8String], count, SQLITE_UTF8, (__bridge void*)b, &FMDBBlockSQLiteCallBackFunction, 0x00, 0x00);
+#endif
+}
+
+@end
+
+
+
+@implementation FMStatement
+@synthesize statement=_statement;
+@synthesize query=_query;
+@synthesize useCount=_useCount;
+
+- (void)finalize {
+ [self close];
+ [super finalize];
+}
+
+- (void)dealloc {
+ [self close];
+ FMDBRelease(_query);
+#if ! __has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
+- (void)close {
+ if (_statement) {
+ sqlite3_finalize(_statement);
+ _statement = 0x00;
+ }
+}
+
+- (void)reset {
+ if (_statement) {
+ sqlite3_reset(_statement);
+ }
+}
+
+- (NSString*)description {
+ return [NSString stringWithFormat:@"%@ %ld hit(s) for query %@", [super description], _useCount, _query];
+}
+
+
+@end
+
View
75 FMDatabasePool.h
@@ -0,0 +1,75 @@
+//
+// FMDatabasePool.h
+// fmdb
+//
+// Created by August Mueller on 6/22/11.
+// Copyright 2011 Flying Meat Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+
+/*
+
+ ***README OR SUFFER***
+Before using FMDatabasePool, please consider using FMDatabaseQueue instead.
+
+If you really really really know what you're doing and FMDatabasePool is what
+you really really need (ie, you're using a read only database), OK you can use
+it. But just be careful not to deadlock!
+
+For an example on deadlocking, search for:
+ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD
+in the main.m file.
+
+*/
+
+
+
+@class FMDatabase;
+
+@interface FMDatabasePool : NSObject {
+ NSString *_path;
+
+ dispatch_queue_t _lockQueue;
+
+ NSMutableArray *_databaseInPool;
+ NSMutableArray *_databaseOutPool;
+
+ __unsafe_unretained id _delegate;
+
+ NSUInteger _maximumNumberOfDatabasesToCreate;
+}
+
+@property (retain) NSString *path;
+@property (assign) id delegate;
+@property (assign) NSUInteger maximumNumberOfDatabasesToCreate;
+
++ (id)databasePoolWithPath:(NSString*)aPath;
+- (id)initWithPath:(NSString*)aPath;
+
+- (NSUInteger)countOfCheckedInDatabases;
+- (NSUInteger)countOfCheckedOutDatabases;
+- (NSUInteger)countOfOpenDatabases;
+- (void)releaseAllDatabases;
+
+- (void)inDatabase:(void (^)(FMDatabase *db))block;
+
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
+- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block;
+
+#if SQLITE_VERSION_NUMBER >= 3007000
+// NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock.
+// If you need to nest, use FMDatabase's startSavePointWithName:error: instead.
+- (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block;
+#endif
+
+@end
+
+
+@interface NSObject (FMDatabasePoolDelegate)
+
+- (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database;
+
+@end
+
View
244 FMDatabasePool.m
@@ -0,0 +1,244 @@
+//
+// FMDatabasePool.m
+// fmdb
+//
+// Created by August Mueller on 6/22/11.
+// Copyright 2011 Flying Meat Inc. All rights reserved.
+//
+
+#import "FMDatabasePool.h"
+#import "FMDatabase.h"
+
+@interface FMDatabasePool()
+
+- (void)pushDatabaseBackInPool:(FMDatabase*)db;
+- (FMDatabase*)db;
+
+@end
+
+
+@implementation FMDatabasePool
+@synthesize path=_path;
+@synthesize delegate=_delegate;
+@synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate;
+
+
++ (id)databasePoolWithPath:(NSString*)aPath {
+ return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]);
+}
+
+- (id)initWithPath:(NSString*)aPath {
+
+ self = [super init];
+
+ if (self != nil) {
+ _path = [aPath copy];
+ _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
+ _databaseInPool = FMDBReturnRetained([NSMutableArray array]);
+ _databaseOutPool = FMDBReturnRetained([NSMutableArray array]);
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+
+ _delegate = 0x00;
+ FMDBRelease(_path);
+ FMDBRelease(_databaseInPool);
+ FMDBRelease(_databaseOutPool);
+
+ if (_lockQueue) {
+ dispatch_release(_lockQueue);
+ _lockQueue = 0x00;
+ }
+#if ! __has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
+
+- (void)executeLocked:(void (^)(void))aBlock {
+ dispatch_sync(_lockQueue, aBlock);
+}
+
+- (void)pushDatabaseBackInPool:(FMDatabase*)db {
+
+ if (!db) { // db can be null if we set an upper bound on the # of databases to create.
+ return;
+ }
+
+ [self executeLocked:^() {
+
+ if ([_databaseInPool containsObject:db]) {
+ [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise];
+ }
+
+ [_databaseInPool addObject:db];
+ [_databaseOutPool removeObject:db];
+
+ }];
+}
+
+- (FMDatabase*)db {
+
+ __block FMDatabase *db;
+
+ [self executeLocked:^() {
+ db = [_databaseInPool lastObject];
+
+ if (db) {
+ [_databaseOutPool addObject:db];
+ [_databaseInPool removeLastObject];
+ }
+ else {
+
+ if (_maximumNumberOfDatabasesToCreate) {
+ NSUInteger currentCount = [_databaseOutPool count] + [_databaseInPool count];
+
+ if (currentCount >= _maximumNumberOfDatabasesToCreate) {
+ NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount);
+ return;
+ }
+ }
+
+ db = [FMDatabase databaseWithPath:_path];
+ }
+
+ //This ensures that the db is opened before returning
+ if ([db open]) {
+ if ([_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![_delegate databasePool:self shouldAddDatabaseToPool:db]) {
+ [db close];
+ db = 0x00;
+ }
+ else {
+ //It should not get added in the pool twice if lastObject was found
+ if (![_databaseOutPool containsObject:db]) {
+ [_databaseOutPool addObject:db];
+ }
+ }
+ }
+ else {
+ NSLog(@"Could not open up the database at path %@", _path);
+ db = 0x00;
+ }
+ }];
+
+ return db;
+}
+
+- (NSUInteger)countOfCheckedInDatabases {
+
+ __block NSUInteger count;
+
+ [self executeLocked:^() {
+ count = [_databaseInPool count];
+ }];
+
+ return count;
+}
+
+- (NSUInteger)countOfCheckedOutDatabases {
+
+ __block NSUInteger count;
+
+ [self executeLocked:^() {
+ count = [_databaseOutPool count];
+ }];
+
+ return count;
+}
+
+- (NSUInteger)countOfOpenDatabases {
+ __block NSUInteger count;
+
+ [self executeLocked:^() {
+ count = [_databaseOutPool count] + [_databaseInPool count];
+ }];
+
+ return count;
+}
+
+- (void)releaseAllDatabases {
+ [self executeLocked:^() {
+ [_databaseOutPool removeAllObjects];
+ [_databaseInPool removeAllObjects];
+ }];
+}
+
+- (void)inDatabase:(void (^)(FMDatabase *db))block {
+
+ FMDatabase *db = [self db];
+
+ block(db);
+
+ [self pushDatabaseBackInPool:db];
+}
+
+- (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block {
+
+ BOOL shouldRollback = NO;
+
+ FMDatabase *db = [self db];
+
+ if (useDeferred) {
+ [db beginDeferredTransaction];
+ }
+ else {
+ [db beginTransaction];
+ }
+
+
+ block(db, &shouldRollback);
+
+ if (shouldRollback) {
+ [db rollback];
+ }
+ else {
+ [db commit];
+ }
+
+ [self pushDatabaseBackInPool:db];
+}
+
+- (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+ [self beginTransaction:YES withBlock:block];
+}
+
+- (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block {
+ [self beginTransaction:NO withBlock:block];
+}
+#if SQLITE_VERSION_NUMBER >= 3007000
+- (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block {
+
+ static unsigned long savePointIdx = 0;
+
+ NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++];
+
+ BOOL shouldRollback = NO;
+
+ FMDatabase *db = [self db];
+
+ NSError *err = 0x00;
+
+ if (![db startSavePointWithName:name error:&err]) {
+ [self pushDatabaseBackInPool:db];
+ return err;
+ }
+
+ block(db, &shouldRollback);
+
+ if (shouldRollback) {
+ [db rollbackToSavePointWithName:name error:&err];
+ }
+ else {
+ [db releaseSavePointWithName:name error:&err];
+ }
+
+ [self pushDatabaseBackInPool:db];
+
+ return err;
+}
+#endif
+
+@end
View
102 FMResultSet.h
@@ -0,0 +1,102 @@
+#import <Foundation/Foundation.h>
+#import "sqlite3.h"
+
+#ifndef __has_feature // Optional.
+#define __has_feature(x) 0 // Compatibility with non-clang compilers.
+#endif
+
+#ifndef NS_RETURNS_NOT_RETAINED
+#if __has_feature(attribute_ns_returns_not_retained)
+#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
+#else
+#define NS_RETURNS_NOT_RETAINED
+#endif
+#endif
+
+@class FMDatabase;
+@class FMStatement;
+
+@interface FMResultSet : NSObject {
+ FMDatabase *_parentDB;
+ FMStatement *_statement;
+
+ NSString *_query;
+ NSMutableDictionary *_columnNameToIndexMap;
+ BOOL _columnNamesSetup;
+}
+
+@property (retain) NSString *query;
+@property (retain) NSMutableDictionary *columnNameToIndexMap;
+@property (retain) FMStatement *statement;
+
++ (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB;
+
+- (void)close;
+
+- (void)setParentDB:(FMDatabase *)newDb;
+
+- (BOOL)next;
+- (BOOL)hasAnotherRow;
+
+- (int)columnCount;
+
+- (int)columnIndexForName:(NSString*)columnName;
+- (NSString*)columnNameForIndex:(int)columnIdx;
+
+- (int)intForColumn:(NSString*)columnName;
+- (int)intForColumnIndex:(int)columnIdx;
+
+- (long)longForColumn:(NSString*)columnName;
+- (long)longForColumnIndex:(int)columnIdx;
+
+- (long long int)longLongIntForColumn:(NSString*)columnName;
+- (long long int)longLongIntForColumnIndex:(int)columnIdx;
+
+- (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName;
+- (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx;
+
+- (BOOL)boolForColumn:(NSString*)columnName;
+- (BOOL)boolForColumnIndex:(int)columnIdx;
+
+- (double)doubleForColumn:(NSString*)columnName;
+- (double)doubleForColumnIndex:(int)columnIdx;
+
+- (NSString*)stringForColumn:(NSString*)columnName;
+- (NSString*)stringForColumnIndex:(int)columnIdx;
+
+- (NSDate*)dateForColumn:(NSString*)columnName;
+- (NSDate*)dateForColumnIndex:(int)columnIdx;
+
+- (NSData*)dataForColumn:(NSString*)columnName;
+- (NSData*)dataForColumnIndex:(int)columnIdx;
+
+- (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx;
+- (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName;
+
+// returns one of NSNumber, NSString, NSData, or NSNull
+- (id)objectForColumnName:(NSString*)columnName;
+- (id)objectForColumnIndex:(int)columnIdx;
+
+/*
+If you are going to use this data after you iterate over the next row, or after you close the
+result set, make sure to make a copy of the data first (or just use dataForColumn:/dataForColumnIndex:)
+If you don't, you're going to be in a world of hurt when you try and use the data.
+*/
+- (NSData*)dataNoCopyForColumn:(NSString*)columnName NS_RETURNS_NOT_RETAINED;
+- (NSData*)dataNoCopyForColumnIndex:(int)columnIdx NS_RETURNS_NOT_RETAINED;
+
+- (BOOL)columnIndexIsNull:(int)columnIdx;
+- (BOOL)columnIsNull:(NSString*)columnName;
+
+
+/* Returns a dictionary of the row results mapped to case sensitive keys of the column names. */
+- (NSDictionary*)resultDictionary;
+
+/* Please use resultDictionary instead. Also, beware that resultDictionary is case sensitive! */
+- (NSDictionary*)resultDict __attribute__ ((deprecated));
+
+- (void)kvcMagic:(id)object;
+
+
+@end
+
View
423 FMResultSet.m
@@ -0,0 +1,423 @@
+#import "FMResultSet.h"
+#import "FMDatabase.h"
+#import "unistd.h"
+
+@interface FMDatabase ()
+- (void)resultSetDidClose:(FMResultSet *)resultSet;
+@end
+
+
+@interface FMResultSet (Private)
+- (NSMutableDictionary *)columnNameToIndexMap;
+- (void)setColumnNameToIndexMap:(NSMutableDictionary *)value;
+@end
+
+@implementation FMResultSet
+@synthesize query=_query;
+@synthesize columnNameToIndexMap=_columnNameToIndexMap;
+@synthesize statement=_statement;
+
++ (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB {
+
+ FMResultSet *rs = [[FMResultSet alloc] init];
+
+ [rs setStatement:statement];
+ [rs setParentDB:aDB];
+
+ return FMDBReturnAutoreleased(rs);
+}
+
+- (void)finalize {
+ [self close];
+ [super finalize];
+}
+
+- (void)dealloc {
+ [self close];
+
+ FMDBRelease(_query);
+ _query = nil;
+
+ FMDBRelease(_columnNameToIndexMap);
+ _columnNameToIndexMap = nil;
+
+#if ! __has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
+- (void)close {
+ [_statement reset];
+ FMDBRelease(_statement);
+ _statement = nil;
+
+ // we don't need this anymore... (i think)
+ //[_parentDB setInUse:NO];
+ [_parentDB resultSetDidClose:self];
+ _parentDB = nil;
+}
+
+- (int)columnCount {
+ return sqlite3_column_count([_statement statement]);
+}
+
+- (void)setupColumnNames {
+
+ if (!_columnNameToIndexMap) {
+ [self setColumnNameToIndexMap:[NSMutableDictionary dictionary]];
+ }
+
+ int columnCount = sqlite3_column_count([_statement statement]);
+
+ int columnIdx = 0;
+ for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+ [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx]
+ forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]];
+ }
+ _columnNamesSetup = YES;
+}
+
+- (void)kvcMagic:(id)object {
+
+ int columnCount = sqlite3_column_count([_statement statement]);
+
+ int columnIdx = 0;
+ for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+
+ const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx);
+
+ // check for a null row
+ if (c) {
+ NSString *s = [NSString stringWithUTF8String:c];
+
+ [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]];
+ }
+ }
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+
+- (NSDictionary*)resultDict {
+
+ NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]);
+
+ if (num_cols > 0) {
+ NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
+
+ if (!_columnNamesSetup) {
+ [self setupColumnNames];
+ }
+
+ NSEnumerator *columnNames = [_columnNameToIndexMap keyEnumerator];
+ NSString *columnName = nil;
+ while ((columnName = [columnNames nextObject])) {
+ id objectValue = [self objectForColumnName:columnName];
+ [dict setObject:objectValue forKey:columnName];
+ }
+
+ return FMDBReturnAutoreleased([dict copy]);
+ }
+ else {
+ NSLog(@"Warning: There seem to be no columns in this set.");
+ }
+
+ return nil;
+}
+
+#pragma clang diagnostic pop
+
+- (NSDictionary*)resultDictionary {
+
+ NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]);
+
+ if (num_cols > 0) {
+ NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
+
+ int columnCount = sqlite3_column_count([_statement statement]);
+
+ int columnIdx = 0;
+ for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+
+ NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)];
+ id objectValue = [self objectForColumnIndex:columnIdx];
+ [dict setObject:objectValue forKey:columnName];
+ }
+
+ return dict;
+ }
+ else {
+ NSLog(@"Warning: There seem to be no columns in this set.");
+ }
+
+ return nil;
+}
+
+
+
+
+
+- (BOOL)next {
+
+ int rc;
+ BOOL retry;
+ int numberOfRetries = 0;
+ do {
+ retry = NO;
+
+ rc = sqlite3_step([_statement statement]);
+
+ if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+ // this will happen if the db is locked, like if we are doing an update or insert.
+ // in that case, retry the step... and maybe wait just 10 milliseconds.
+ retry = YES;
+ if (SQLITE_LOCKED == rc) {
+ rc = sqlite3_reset([_statement statement]);
+ if (rc != SQLITE_LOCKED) {
+ NSLog(@"Unexpected result from sqlite3_reset (%d) rs", rc);
+ }
+ }
+ usleep(20);
+
+ if ([_parentDB busyRetryTimeout] && (numberOfRetries++ > [_parentDB busyRetryTimeout])) {
+
+ NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]);
+ NSLog(@"Database busy");
+ break;
+ }
+ }
+ else if (SQLITE_DONE == rc || SQLITE_ROW == rc) {
+ // all is well, let's return.
+ }
+ else if (SQLITE_ERROR == rc) {
+ NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+ break;
+ }
+ else if (SQLITE_MISUSE == rc) {
+ // uh oh.
+ NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+ break;
+ }
+ else {
+ // wtf?
+ NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle]));
+ break;
+ }
+
+ } while (retry);
+
+
+ if (rc != SQLITE_ROW) {
+ [self close];
+ }
+
+ return (rc == SQLITE_ROW);
+}
+
+- (BOOL)hasAnotherRow {
+ return sqlite3_errcode([_parentDB sqliteHandle]) == SQLITE_ROW;
+}
+
+- (int)columnIndexForName:(NSString*)columnName {
+
+ if (!_columnNamesSetup) {
+ [self setupColumnNames];
+ }
+
+ columnName = [columnName lowercaseString];
+
+ NSNumber *n = [_columnNameToIndexMap objectForKey:columnName];
+
+ if (n) {
+ return [n intValue];
+ }
+
+ NSLog(@"Warning: I could not find the column named '%@'.", columnName);
+
+ return -1;
+}
+
+
+
+- (int)intForColumn:(NSString*)columnName {
+ return [self intForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (int)intForColumnIndex:(int)columnIdx {
+ return sqlite3_column_int([_statement statement], columnIdx);
+}
+
+- (long)longForColumn:(NSString*)columnName {
+ return [self longForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (long)longForColumnIndex:(int)columnIdx {
+ return (long)sqlite3_column_int64([_statement statement], columnIdx);
+}
+
+- (long long int)longLongIntForColumn:(NSString*)columnName {
+ return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (long long int)longLongIntForColumnIndex:(int)columnIdx {
+ return sqlite3_column_int64([_statement statement], columnIdx);
+}
+
+- (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName {
+ return [self unsignedLongLongIntForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx {
+ return (unsigned long long int)[self longLongIntForColumnIndex:columnIdx];
+}
+
+- (BOOL)boolForColumn:(NSString*)columnName {
+ return [self boolForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (BOOL)boolForColumnIndex:(int)columnIdx {
+ return ([self intForColumnIndex:columnIdx] != 0);
+}
+
+- (double)doubleForColumn:(NSString*)columnName {
+ return [self doubleForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (double)doubleForColumnIndex:(int)columnIdx {
+ return sqlite3_column_double([_statement statement], columnIdx);
+}
+
+- (NSString*)stringForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx);
+
+ if (!c) {
+ // null row.
+ return nil;
+ }
+
+ return [NSString stringWithUTF8String:c];
+}
+
+- (NSString*)stringForColumn:(NSString*)columnName {
+ return [self stringForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSDate*)dateForColumn:(NSString*)columnName {
+ return [self dateForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSDate*)dateForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ return [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]];
+}
+
+
+- (NSData*)dataForColumn:(NSString*)columnName {
+ return [self dataForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSData*)dataForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ int dataSize = sqlite3_column_bytes([_statement statement], columnIdx);
+
+ NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)dataSize];
+
+ memcpy([data mutableBytes], sqlite3_column_blob([_statement statement], columnIdx), dataSize);
+
+ return data;
+}
+
+
+- (NSData*)dataNoCopyForColumn:(NSString*)columnName {
+ return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (NSData*)dataNoCopyForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ int dataSize = sqlite3_column_bytes([_statement statement], columnIdx);
+
+ NSData *data = [NSData dataWithBytesNoCopy:(void *)sqlite3_column_blob([_statement statement], columnIdx) length:(NSUInteger)dataSize freeWhenDone:NO];
+
+ return data;
+}
+
+
+- (BOOL)columnIndexIsNull:(int)columnIdx {
+ return sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL;
+}
+
+- (BOOL)columnIsNull:(NSString*)columnName {
+ return [self columnIndexIsNull:[self columnIndexForName:columnName]];
+}
+
+- (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx {
+
+ if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+ return nil;
+ }
+
+ return sqlite3_column_text([_statement statement], columnIdx);
+}
+
+- (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName {
+ return [self UTF8StringForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+- (id)objectForColumnIndex:(int)columnIdx {
+ int columnType = sqlite3_column_type([_statement statement], columnIdx);
+
+ id returnValue = nil;
+
+ if (columnType == SQLITE_INTEGER) {
+ returnValue = [NSNumber numberWithLongLong:[self longLongIntForColumnIndex:columnIdx]];
+ }
+ else if (columnType == SQLITE_FLOAT) {
+ returnValue = [NSNumber numberWithDouble:[self doubleForColumnIndex:columnIdx]];
+ }
+ else if (columnType == SQLITE_BLOB) {
+ returnValue = [self dataForColumnIndex:columnIdx];
+ }
+ else {
+ //default to a string for everything else
+ returnValue = [self stringForColumnIndex:columnIdx];
+ }
+
+ if (returnValue == nil) {
+ returnValue = [NSNull null];
+ }
+
+ return returnValue;
+}
+
+- (id)objectForColumnName:(NSString*)columnName {
+ return [self objectForColumnIndex:[self columnIndexForName:columnName]];
+}
+
+// returns autoreleased NSString containing the name of the column in the result set
+- (NSString*)columnNameForIndex:(int)columnIdx {
+ return [NSString stringWithUTF8String: sqlite3_column_name([_statement statement], columnIdx)];
+}
+
+- (void)setParentDB:(FMDatabase *)newDb {
+ _parentDB = newDb;
+}
+
+
+@end
View
295 RegexKitLite.h
@@ -0,0 +1,295 @@
+//
+// RegexKitLite.h
+// http://regexkit.sourceforge.net/
+// Licensed under the terms of the BSD License, as specified below.
+//
+
+/*
+ Copyright (c) 2008-2010, John Engelhart
+
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the Zang Industries nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef __OBJC__
+#import <Foundation/NSArray.h>
+#import <Foundation/NSError.h>
+#import <Foundation/NSObjCRuntime.h>
+#import <Foundation/NSRange.h>
+#import <Foundation/NSString.h>
+#endif // __OBJC__
+
+#include <limits.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <TargetConditionals.h>
+#include <AvailabilityMacros.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef REGEXKITLITE_VERSION_DEFINED
+#define REGEXKITLITE_VERSION_DEFINED
+
+#define _RKL__STRINGIFY(b) #b
+#define _RKL_STRINGIFY(a) _RKL__STRINGIFY(a)
+#define _RKL_JOIN_VERSION(a,b) _RKL_STRINGIFY(a##.##b)
+#define _RKL_VERSION_STRING(a,b) _RKL_JOIN_VERSION(a,b)
+
+#define REGEXKITLITE_VERSION_MAJOR 4
+#define REGEXKITLITE_VERSION_MINOR 0
+
+#define REGEXKITLITE_VERSION_CSTRING _RKL_VERSION_STRING(REGEXKITLITE_VERSION_MAJOR, REGEXKITLITE_VERSION_MINOR)
+#define REGEXKITLITE_VERSION_NSSTRING @REGEXKITLITE_VERSION_CSTRING
+
+#endif // REGEXKITLITE_VERSION_DEFINED
+
+#if !defined(RKL_BLOCKS) && defined(NS_BLOCKS_AVAILABLE) && (NS_BLOCKS_AVAILABLE == 1)
+#define RKL_BLOCKS 1
+#endif
+
+#if defined(RKL_BLOCKS) && (RKL_BLOCKS == 1)
+#define _RKL_BLOCKS_ENABLED 1
+#endif // defined(RKL_BLOCKS) && (RKL_BLOCKS == 1)
+
+#if defined(_RKL_BLOCKS_ENABLED) && !defined(__BLOCKS__)
+#warning RegexKitLite support for Blocks is enabled, but __BLOCKS__ is not defined. This compiler may not support Blocks, in which case the behavior is undefined. This will probably cause numerous compiler errors.
+#endif // defined(_RKL_BLOCKS_ENABLED) && !defined(__BLOCKS__)
+
+// For Mac OS X < 10.5.
+#ifndef NSINTEGER_DEFINED
+#define NSINTEGER_DEFINED
+#if defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
+typedef long NSInteger;
+typedef unsigned long NSUInteger;
+#define NSIntegerMin LONG_MIN
+#define NSIntegerMax LONG_MAX
+#define NSUIntegerMax ULONG_MAX
+#else // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
+typedef int NSInteger;
+typedef unsigned int NSUInteger;
+#define NSIntegerMin INT_MIN
+#define NSIntegerMax INT_MAX
+#define NSUIntegerMax UINT_MAX
+#endif // defined(__LP64__) || defined(NS_BUILD_32_LIKE_64)
+#endif // NSINTEGER_DEFINED
+
+#ifndef RKLREGEXOPTIONS_DEFINED
+#define RKLREGEXOPTIONS_DEFINED
+
+// These must be identical to their ICU regex counterparts. See http://www.icu-project.org/userguide/regexp.html
+enum {
+ RKLNoOptions = 0,
+ RKLCaseless = 2,
+ RKLComments = 4,
+ RKLDotAll = 32,
+ RKLMultiline = 8,
+ RKLUnicodeWordBoundaries = 256
+};
+typedef uint32_t RKLRegexOptions; // This must be identical to the ICU 'flags' argument type.
+
+#endif // RKLREGEXOPTIONS_DEFINED
+
+#ifndef RKLREGEXENUMERATIONOPTIONS_DEFINED
+#define RKLREGEXENUMERATIONOPTIONS_DEFINED
+
+enum {
+ RKLRegexEnumerationNoOptions = 0UL,
+ RKLRegexEnumerationCapturedStringsNotRequired = 1UL << 9,
+ RKLRegexEnumerationReleaseStringReturnedByReplacementBlock = 1UL << 10,
+ RKLRegexEnumerationFastCapturedStringsXXX = 1UL << 11,
+};
+typedef NSUInteger RKLRegexEnumerationOptions;
+
+#endif // RKLREGEXENUMERATIONOPTIONS_DEFINED
+
+#ifndef _REGEXKITLITE_H_
+#define _REGEXKITLITE_H_
+
+#if defined(__GNUC__) && (__GNUC__ >= 4) && defined(__APPLE_CC__) && (__APPLE_CC__ >= 5465)
+#define RKL_DEPRECATED_ATTRIBUTE __attribute__((deprecated))
+#else
+#define RKL_DEPRECATED_ATTRIBUTE
+#endif
+
+#if defined(NS_REQUIRES_NIL_TERMINATION)
+#define RKL_REQUIRES_NIL_TERMINATION NS_REQUIRES_NIL_TERMINATION
+#else // defined(NS_REQUIRES_NIL_TERMINATION)
+#define RKL_REQUIRES_NIL_TERMINATION
+#endif // defined(NS_REQUIRES_NIL_TERMINATION)
+
+// This requires a few levels of rewriting to get the desired results.
+#define _RKL_CONCAT_2(c,d) c ## d
+#define _RKL_CONCAT(a,b) _RKL_CONCAT_2(a,b)
+
+#ifdef RKL_PREPEND_TO_METHODS
+#define RKL_METHOD_PREPEND(x) _RKL_CONCAT(RKL_PREPEND_TO_METHODS, x)
+#else // RKL_PREPEND_TO_METHODS
+#define RKL_METHOD_PREPEND(x) x
+#endif // RKL_PREPEND_TO_METHODS
+
+// If it looks like low memory notifications might be available, add code to register and respond to them.
+// This is (should be) harmless if it turns out that this isn't the case, since the notification that we register for,
+// UIApplicationDidReceiveMemoryWarningNotification, is dynamically looked up via dlsym().
+#if ((defined(TARGET_OS_EMBEDDED) && (TARGET_OS_EMBEDDED != 0)) || (defined(TARGET_OS_IPHONE) && (TARGET_OS_IPHONE != 0))) && (!defined(RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS) || (RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS != 0))
+#define RKL_REGISTER_FOR_IPHONE_LOWMEM_NOTIFICATIONS 1
+#endif
+
+#ifdef __OBJC__
+
+// NSException exception name.
+extern NSString * const RKLICURegexException;
+
+// NSError error domains and user info keys.
+extern NSString * const RKLICURegexErrorDomain;
+
+extern NSString * const RKLICURegexEnumerationOptionsErrorKey;
+extern NSString * const RKLICURegexErrorCodeErrorKey;
+extern NSString * const RKLICURegexErrorNameErrorKey;
+extern NSString * const RKLICURegexLineErrorKey;
+extern NSString * const RKLICURegexOffsetErrorKey;
+extern NSString * const RKLICURegexPreContextErrorKey;
+extern NSString * const RKLICURegexPostContextErrorKey;
+extern NSString * const RKLICURegexRegexErrorKey;
+extern NSString * const RKLICURegexRegexOptionsErrorKey;
+extern NSString * const RKLICURegexReplacedCountErrorKey;
+extern NSString * const RKLICURegexReplacedStringErrorKey;
+extern NSString * const RKLICURegexReplacementStringErrorKey;
+extern NSString * const RKLICURegexSubjectRangeErrorKey;
+extern NSString * const RKLICURegexSubjectStringErrorKey;
+
+@interface NSString (RegexKitLiteAdditions)
+
++ (void)RKL_METHOD_PREPEND(clearStringCache);
+
+// Although these are marked as deprecated, a bug in GCC prevents a warning from being issues for + class methods. Filed bug with Apple, #6736857.
++ (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex RKL_DEPRECATED_ATTRIBUTE;
++ (NSInteger)RKL_METHOD_PREPEND(captureCountForRegex):(NSString *)regex options:(RKLRegexOptions)options error:(NSError **)error RKL_DEPRECATED_ATTRIBUTE;
+
+- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex;
+- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex range:(NSRange)range;
+- (NSArray *)RKL_METHOD_PREPEND(componentsSeparatedByRegex):(NSString *)regex options:(RKLRegexOptions)options range:(NSRange)range error:(NSError **)error;
+
+- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex;
+- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex inRange:(NSRange)range;
+- (BOOL)RKL_METHOD_PREPEND(isMatchedByRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range error:(NSError **)error;
+
+- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex;
+- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex capture:(NSInteger)capture;
+- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex inRange:(NSRange)range;
+- (NSRange)RKL_METHOD_PREPEND(rangeOfRegex):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error;
+
+- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex;
+- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex capture:(NSInteger)capture;
+- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex inRange:(NSRange)range;
+- (NSString *)RKL_METHOD_PREPEND(stringByMatching):(NSString *)regex options:(RKLRegexOptions)options inRange:(NSRange)range capture:(NSInteger)capture error:(NSError **)error;
+
+- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement;
+- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement range:(NSRange)searchRange;
+- (NSString *)RKL_METHOD_PREPEND(stringByReplacingOccurrencesOfRegex):(NSString *)regex withString:(NSString *)replacement options:(RKLRegexOptions)options range:(NSRange)searchRange error:(NSError **)error;
+
+ //// >= 3.0
+
+- (NSInteger)RKL_METHOD_PREPEND(captureCount);
+- (NSInteger)RKL_METHOD_PREPEND(captureCountWithOptions):(RKLRegexOptions)options error:(NSError **)error;
+
+- (BOOL)RKL_METHOD_PREPEND(isRegexValid);
+- (BOOL)RKL_METHOD_PREPEND(isRegexValidWithOptions):(RKLRegexOptions)options error:(NSError **)