diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..1cf79eef --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,24 @@ +If you are using fmdb in your project, I'd love to hear about it. Let me +know at gus@flyingmeat.com. + +In short, this is the MIT License. + +Copyright (c) 2008 Flying Meat Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.markdown b/README.markdown index e69de29b..9f458e24 100644 --- a/README.markdown +++ b/README.markdown @@ -0,0 +1,4 @@ +FMDB +==== + +This is an Objective-C wrapper around SQLite: http://sqlite.org/ \ No newline at end of file diff --git a/fmdb.1 b/fmdb.1 new file mode 100644 index 00000000..361887ec --- /dev/null +++ b/fmdb.1 @@ -0,0 +1,79 @@ +.\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. +.\"See Also: +.\"man mdoc.samples for a complete listing of options +.\"man mdoc for the short list of editing options +.\"/usr/share/misc/mdoc.template +.Dd 5/11/06 \" DATE +.Dt fmdb 1 \" Program name and manual section number +.Os Darwin +.Sh NAME \" Section Header - required - don't modify +.Nm fmdb, +.\" The following lines are read in generating the apropos(man -k) database. Use only key +.\" words here as the database is built based on the words here and in the .ND line. +.Nm Other_name_for_same_program(), +.Nm Yet another name for the same program. +.\" Use .Nm macro to designate other names for the documented program. +.Nd This line parsed for whatis database. +.Sh SYNOPSIS \" Section Header - required - don't modify +.Nm +.Op Fl abcd \" [-abcd] +.Op Fl a Ar path \" [-a path] +.Op Ar file \" [file] +.Op Ar \" [file ...] +.Ar arg0 \" Underlined argument - use .Ar anywhere to underline +arg2 ... \" Arguments +.Sh DESCRIPTION \" Section Header - required - don't modify +Use the .Nm macro to refer to your program throughout the man page like such: +.Nm +Underlining is accomplished with the .Ar macro like this: +.Ar underlined text . +.Pp \" Inserts a space +A list of items with descriptions: +.Bl -tag -width -indent \" Begins a tagged list +.It item a \" Each item preceded by .It macro +Description of item a +.It item b +Description of item b +.El \" Ends the list +.Pp +A list of flags and their descriptions: +.Bl -tag -width -indent \" Differs from above in tag removed +.It Fl a \"-a flag as a list item +Description of -a flag +.It Fl b +Description of -b flag +.El \" Ends the list +.Pp +.\" .Sh ENVIRONMENT \" May not be needed +.\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 +.\" .It Ev ENV_VAR_1 +.\" Description of ENV_VAR_1 +.\" .It Ev ENV_VAR_2 +.\" Description of ENV_VAR_2 +.\" .El +.Sh FILES \" File used or created by the topic of the man page +.Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact +.It Pa /usr/share/file_name +FILE_1 description +.It Pa /Users/joeuser/Library/really_long_file_name +FILE_2 description +.El \" Ends the list +.\" .Sh DIAGNOSTICS \" May not be needed +.\" .Bl -diag +.\" .It Diagnostic Tag +.\" Diagnostic informtion here. +.\" .It Diagnostic Tag +.\" Diagnostic informtion here. +.\" .El +.Sh SEE ALSO +.\" List links in ascending order by section, alphabetically within a section. +.\" Please do not reference files that do not exist without filing a bug report +.Xr a 1 , +.Xr b 1 , +.Xr c 1 , +.Xr a 2 , +.Xr b 2 , +.Xr a 3 , +.Xr b 3 +.\" .Sh BUGS \" Document known, unremedied bugs +.\" .Sh HISTORY \" Document history if command behaves in a unique manner \ No newline at end of file diff --git a/fmdb.xcodeproj/project.pbxproj b/fmdb.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ca64d883 --- /dev/null +++ b/fmdb.xcodeproj/project.pbxproj @@ -0,0 +1,259 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; }; + 8DD76F9F0486AA7600D96B5E /* fmdb.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859EA3029092ED04C91782 /* fmdb.1 */; }; + CC50F2CD0DF9183600E4AAAE /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; }; + CCBEBDAC0DF5DE1A003DDD08 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = CCBEBDAB0DF5DE1A003DDD08 /* libsqlite3.dylib */; }; + CCC24EC10A13E34D00A6D3E3 /* FMDatabase.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */; }; + CCC24EC20A13E34D00A6D3E3 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */; }; + CCC24EC50A13E34D00A6D3E3 /* fmdb.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBE0A13E34D00A6D3E3 /* fmdb.m */; }; + CCC24EC60A13E34D00A6D3E3 /* FMResultSet.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */; }; + CCC24EC70A13E34D00A6D3E3 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 8DD76F9E0486AA7600D96B5E /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 8; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + 8DD76F9F0486AA7600D96B5E /* fmdb.1 in CopyFiles */, + CCC24EC10A13E34D00A6D3E3 /* FMDatabase.h in CopyFiles */, + CCC24EC60A13E34D00A6D3E3 /* FMResultSet.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 32A70AAB03705E1F00C91783 /* fmdb_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fmdb_Prefix.pch; sourceTree = ""; }; + 8DD76FA10486AA7600D96B5E /* fmdb */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fmdb; sourceTree = BUILT_PRODUCTS_DIR; }; + C6859EA3029092ED04C91782 /* fmdb.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = fmdb.1; sourceTree = ""; }; + CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FMDatabaseAdditions.m; path = src/FMDatabaseAdditions.m; sourceTree = ""; }; + CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMDatabaseAdditions.h; path = src/FMDatabaseAdditions.h; sourceTree = ""; }; + CC8C138A0E3135C400FBE1E7 /* CHANGES_AND_TODO_LIST.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGES_AND_TODO_LIST.txt; sourceTree = ""; }; + CC8C138B0E3135C400FBE1E7 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; + CC8C138C0E3135C400FBE1E7 /* CONTRIBUTORS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CONTRIBUTORS.txt; sourceTree = ""; }; + CCBEBDAB0DF5DE1A003DDD08 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = /usr/lib/libsqlite3.dylib; sourceTree = ""; }; + CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMDatabase.h; path = src/FMDatabase.h; sourceTree = ""; }; + CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FMDatabase.m; path = src/FMDatabase.m; sourceTree = ""; }; + CCC24EBE0A13E34D00A6D3E3 /* fmdb.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = fmdb.m; path = src/fmdb.m; sourceTree = ""; }; + CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMResultSet.h; path = src/FMResultSet.h; sourceTree = ""; }; + CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FMResultSet.m; path = src/FMResultSet.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8DD76F9B0486AA7600D96B5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */, + CCBEBDAC0DF5DE1A003DDD08 /* libsqlite3.dylib in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 08FB7794FE84155DC02AAC07 /* fmdb */ = { + isa = PBXGroup; + children = ( + CC8C138B0E3135C400FBE1E7 /* LICENSE.txt */, + CC8C138A0E3135C400FBE1E7 /* CHANGES_AND_TODO_LIST.txt */, + CC8C138C0E3135C400FBE1E7 /* CONTRIBUTORS.txt */, + 08FB7795FE84155DC02AAC07 /* Source */, + C6859EA2029092E104C91782 /* Documentation */, + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */, + 1AB674ADFE9D54B511CA2CBB /* Products */, + ); + name = fmdb; + sourceTree = ""; + }; + 08FB7795FE84155DC02AAC07 /* Source */ = { + isa = PBXGroup; + children = ( + CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */, + CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */, + CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */, + CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */, + CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */, + CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */, + 32A70AAB03705E1F00C91783 /* fmdb_Prefix.pch */, + CCC24EBE0A13E34D00A6D3E3 /* fmdb.m */, + ); + name = Source; + sourceTree = ""; + }; + 08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = { + isa = PBXGroup; + children = ( + CCBEBDAB0DF5DE1A003DDD08 /* libsqlite3.dylib */, + 08FB779EFE84155DC02AAC07 /* Foundation.framework */, + ); + name = "External Frameworks and Libraries"; + sourceTree = ""; + }; + 1AB674ADFE9D54B511CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8DD76FA10486AA7600D96B5E /* fmdb */, + ); + name = Products; + sourceTree = ""; + }; + C6859EA2029092E104C91782 /* Documentation */ = { + isa = PBXGroup; + children = ( + C6859EA3029092ED04C91782 /* fmdb.1 */, + ); + name = Documentation; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8DD76F960486AA7600D96B5E /* fmdb */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "fmdb" */; + buildPhases = ( + 8DD76F990486AA7600D96B5E /* Sources */, + 8DD76F9B0486AA7600D96B5E /* Frameworks */, + 8DD76F9E0486AA7600D96B5E /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = fmdb; + productInstallPath = "$(HOME)/bin"; + productName = fmdb; + productReference = 8DD76FA10486AA7600D96B5E /* fmdb */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 08FB7793FE84155DC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "fmdb" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 08FB7794FE84155DC02AAC07 /* fmdb */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 8DD76F960486AA7600D96B5E /* fmdb */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 8DD76F990486AA7600D96B5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CCC24EC20A13E34D00A6D3E3 /* FMDatabase.m in Sources */, + CCC24EC50A13E34D00A6D3E3 /* fmdb.m in Sources */, + CCC24EC70A13E34D00A6D3E3 /* FMResultSet.m in Sources */, + CC50F2CD0DF9183600E4AAAE /* FMDatabaseAdditions.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1DEB927508733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = fmdb_Prefix.pch; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ( + "$(LIBRARY_SEARCH_PATHS)", + "$(SRCROOT)/sqlite", + ); + PRODUCT_NAME = fmdb; + ZERO_LINK = YES; + }; + name = Debug; + }; + 1DEB927608733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = fmdb_Prefix.pch; + INSTALL_PATH = "$(HOME)/bin"; + LIBRARY_SEARCH_PATHS = ( + "$(LIBRARY_SEARCH_PATHS)", + "$(SRCROOT)/sqlite", + ); + PRODUCT_NAME = fmdb; + }; + name = Release; + }; + 1DEB927908733DD40010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = i386; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = macosx10.5; + }; + name = Debug; + }; + 1DEB927A08733DD40010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + PREBINDING = NO; + SDKROOT = macosx10.5; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB927408733DD40010E9CD /* Build configuration list for PBXNativeTarget "fmdb" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927508733DD40010E9CD /* Debug */, + 1DEB927608733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "fmdb" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB927908733DD40010E9CD /* Debug */, + 1DEB927A08733DD40010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 08FB7793FE84155DC02AAC07 /* Project object */; +} diff --git a/fmdb_Prefix.pch b/fmdb_Prefix.pch new file mode 100644 index 00000000..b8c83c33 --- /dev/null +++ b/fmdb_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'fmdb' target in the 'fmdb' project. +// + +#ifdef __OBJC__ + #import +#endif diff --git a/src/FMDatabase.h b/src/FMDatabase.h new file mode 100644 index 00000000..398381b9 --- /dev/null +++ b/src/FMDatabase.h @@ -0,0 +1,116 @@ +#import +#import "sqlite3.h" +#import "FMResultSet.h" + +@interface FMDatabase : NSObject +{ + sqlite3* db; + NSString* databasePath; + BOOL logsErrors; + BOOL crashOnErrors; + BOOL inUse; + BOOL inTransaction; + BOOL traceExecution; + BOOL checkedOut; + int busyRetryTimeout; + BOOL shouldCacheStatements; + 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; + +// 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; +- (sqlite_int64) lastInsertRowId; + +- (sqlite3*) sqliteHandle; + +- (BOOL) executeUpdate:(NSString*)sql, ...; +- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; +- (id) executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args; // you shouldn't ever need to call this. use the previous two instead. + +- (id) executeQuery:(NSString*)sql, ...; +- (id) executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; +- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args; // you shouldn't ever need to call this. use the previous two instead. + +- (BOOL) rollback; +- (BOOL) commit; +- (BOOL) beginTransaction; +- (BOOL) beginDeferredTransaction; + +- (BOOL)logsErrors; +- (void)setLogsErrors:(BOOL)flag; + +- (BOOL)crashOnErrors; +- (void)setCrashOnErrors:(BOOL)flag; + +- (BOOL)inUse; +- (void)setInUse:(BOOL)value; + +- (BOOL)inTransaction; +- (void)setInTransaction:(BOOL)flag; + +- (BOOL)traceExecution; +- (void)setTraceExecution:(BOOL)flag; + +- (BOOL)checkedOut; +- (void)setCheckedOut:(BOOL)flag; + +- (int)busyRetryTimeout; +- (void)setBusyRetryTimeout:(int)newBusyRetryTimeout; + +- (BOOL)shouldCacheStatements; +- (void)setShouldCacheStatements:(BOOL)value; + +- (NSMutableDictionary *)cachedStatements; +- (void)setCachedStatements:(NSMutableDictionary *)value; + + ++ (NSString*) sqliteLibVersion; + + +- (int)changes; + +@end + +@interface FMStatement : NSObject { + sqlite3_stmt *statement; + NSString *query; + long useCount; +} + + +- (void) close; +- (void) reset; + +- (sqlite3_stmt *)statement; +- (void)setStatement:(sqlite3_stmt *)value; + +- (NSString *)query; +- (void)setQuery:(NSString *)value; + +- (long)useCount; +- (void)setUseCount:(long)value; + + +@end + diff --git a/src/FMDatabase.m b/src/FMDatabase.m new file mode 100644 index 00000000..ee4295c5 --- /dev/null +++ b/src/FMDatabase.m @@ -0,0 +1,753 @@ +#import "FMDatabase.h" +#import "unistd.h" + +@implementation FMDatabase + ++ (id)databaseWithPath:(NSString*)aPath { + return [[[self alloc] initWithPath:aPath] autorelease]; +} + +- (id)initWithPath:(NSString*)aPath { + self = [super init]; + + if (self) { + databasePath = [aPath copy]; + db = 0x00; + logsErrors = 0x00; + crashOnErrors = 0x00; + busyRetryTimeout = 0x00; + } + + return self; +} + +- (void)dealloc { + [self close]; + + [cachedStatements release]; + [databasePath release]; + + [super dealloc]; +} + ++ (NSString*) sqliteLibVersion { + return [NSString stringWithFormat:@"%s", sqlite3_libversion()]; +} + +- (NSString *) databasePath { + return databasePath; +} + +- (sqlite3*) sqliteHandle { + return db; +} + +- (BOOL) open { + int err = sqlite3_open([databasePath fileSystemRepresentation], &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 fileSystemRepresentation], &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]; + + if (!db) { + return YES; + } + + int rc; + BOOL retry; + int numberOfRetries = 0; + do { + retry = NO; + rc = sqlite3_close(db); + if (SQLITE_BUSY == rc) { + retry = YES; + usleep(20); + if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) { + NSLog(@"%s:%d", __FUNCTION__, __LINE__); + NSLog(@"Database busy, unable to close"); + return NO; + } + } + else if (SQLITE_OK != rc) { + NSLog(@"error closing!: %d", rc); + } + } + while (retry); + + db = nil; + return YES; +} + +- (void) clearCachedStatements { + + NSEnumerator *e = [cachedStatements objectEnumerator]; + FMStatement *cachedStmt; + + while ((cachedStmt = [e nextObject])) { + [cachedStmt close]; + } + + [cachedStatements removeAllObjects]; +} + +- (FMStatement*) cachedStatementForQuery:(NSString*)query { + return [cachedStatements objectForKey:query]; +} + +- (void) setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query { + //NSLog(@"setting query: %@", query); + query = [query copy]; // in case we got handed in a mutable string... + [statement setQuery:query]; + [cachedStatements setObject:statement forKey:query]; + [query release]; +} + + +- (BOOL) rekey:(NSString*)key { +#ifdef SQLITE_HAS_CODEC + if (!key) { + return NO; + } + + int rc = sqlite3_rekey(db, [key UTF8String], 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], 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) compainAboutInUse { + NSLog(@"The FMDatabase %@ is currently in use.", self); + + if (crashOnErrors) { + NSAssert1(false, @"The FMDatabase %@ is currently in use.", self); + } +} + +- (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); +} + +- (sqlite_int64) lastInsertRowId { + + if (inUse) { + [self compainAboutInUse]; + return NO; + } + [self setInUse:YES]; + + sqlite_int64 ret = sqlite3_last_insert_rowid(db); + + [self setInUse: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]]) { + sqlite3_bind_blob(pStmt, idx, [obj 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(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); + } +} + +- (id) executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args { + + if (inUse) { + [self compainAboutInUse]; + return nil; + } + + [self setInUse:YES]; + + FMResultSet *rs = nil; + + int rc = 0x00;; + sqlite3_stmt *pStmt = 0x00;; + FMStatement *statement = 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) { + retry = YES; + usleep(20); + + if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) { + NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); + NSLog(@"Database busy"); + sqlite3_finalize(pStmt); + [self setInUse:NO]; + return nil; + } + } + else if (SQLITE_OK != rc) { + + + if (logsErrors) { + NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); + NSLog(@"DB Query: %@", sql); + if (crashOnErrors) { +//#if defined(__BIG_ENDIAN__) && !TARGET_IPHONE_SIMULATOR +// asm{ trap }; +//#endif + NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); + } + } + + sqlite3_finalize(pStmt); + + [self setInUse:NO]; + return nil; + } + } + while (retry); + } + + id obj; + int idx = 0; + int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!) + + while (idx < queryCount) { + + if (arrayArgs) { + obj = [arrayArgs objectAtIndex: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); + [self setInUse:NO]; + return nil; + } + + [statement retain]; // to balance the release below + + if (!statement) { + statement = [[FMStatement alloc] init]; + [statement setStatement:pStmt]; + + if (shouldCacheStatements) { + [self setCachedStatement:statement forQuery:sql]; + } + } + + // the statement gets close in rs's dealloc or [rs close]; + rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self]; + [rs setQuery:sql]; + + statement.useCount = statement.useCount + 1; + + [statement release]; + + [self setInUse:NO]; + + return rs; +} + +- (id) executeQuery:(NSString*)sql, ... { + va_list args; + va_start(args, sql); + + id result = [self executeQuery:sql withArgumentsInArray:nil orVAList:args]; + + va_end(args); + return result; +} + +- (id) executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments { + return [self executeQuery:sql withArgumentsInArray:arguments orVAList:nil]; +} + +- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args { + + if (inUse) { + [self compainAboutInUse]; + return NO; + } + + [self setInUse: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) { + retry = YES; + usleep(20); + + if (busyRetryTimeout && (numberOfRetries++ > busyRetryTimeout)) { + NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); + NSLog(@"Database busy"); + sqlite3_finalize(pStmt); + [self setInUse:NO]; + return NO; + } + } + else if (SQLITE_OK != rc) { + + + if (logsErrors) { + NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); + NSLog(@"DB Query: %@", sql); + if (crashOnErrors) { +//#if defined(__BIG_ENDIAN__) && !TARGET_IPHONE_SIMULATOR +// asm{ trap }; +//#endif + NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); + } + } + + sqlite3_finalize(pStmt); + [self setInUse:NO]; + + return NO; + } + } + while (retry); + } + + + id obj; + int idx = 0; + int queryCount = sqlite3_bind_parameter_count(pStmt); + + while (idx < queryCount) { + + if (arrayArgs) { + obj = [arrayArgs objectAtIndex: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); + [self setInUse: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) { + // 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; + 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 || SQLITE_ROW == 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); + + assert( rc!=SQLITE_ROW ); + + + if (shouldCacheStatements && !cachedStmt) { + cachedStmt = [[FMStatement alloc] init]; + + [cachedStmt setStatement:pStmt]; + + [self setCachedStatement:cachedStmt forQuery:sql]; + + [cachedStmt release]; + } + + if (cachedStmt) { + cachedStmt.useCount = cachedStmt.useCount + 1; + rc = sqlite3_reset(pStmt); + } + else { + /* Finalize the virtual machine. This releases all memory and other + ** resources allocated by the sqlite3_prepare() call above. + */ + rc = sqlite3_finalize(pStmt); + } + + [self setInUse:NO]; + + return (rc == SQLITE_OK); +} + + +- (BOOL) executeUpdate:(NSString*)sql, ... { + va_list args; + va_start(args, sql); + + BOOL result = [self executeUpdate:sql withArgumentsInArray:nil orVAList:args]; + + va_end(args); + return result; +} + + + +- (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments { + return [self executeUpdate:sql withArgumentsInArray:arguments orVAList:nil]; +} + +/* +- (id) executeUpdate:(NSString *)sql arguments:(va_list)args { + +} +*/ + +- (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)logsErrors { + return logsErrors; +} +- (void)setLogsErrors:(BOOL)flag { + logsErrors = flag; +} + +- (BOOL)crashOnErrors { + return crashOnErrors; +} +- (void)setCrashOnErrors:(BOOL)flag { + crashOnErrors = flag; +} + +- (BOOL)inUse { + return inUse || inTransaction; +} + +- (void) setInUse:(BOOL)b { + inUse = b; +} + +- (BOOL)inTransaction { + return inTransaction; +} +- (void)setInTransaction:(BOOL)flag { + inTransaction = flag; +} + +- (BOOL)traceExecution { + return traceExecution; +} +- (void)setTraceExecution:(BOOL)flag { + traceExecution = flag; +} + +- (BOOL)checkedOut { + return checkedOut; +} +- (void)setCheckedOut:(BOOL)flag { + checkedOut = flag; +} + + +- (int)busyRetryTimeout { + return busyRetryTimeout; +} +- (void)setBusyRetryTimeout:(int)newBusyRetryTimeout { + busyRetryTimeout = newBusyRetryTimeout; +} + + +- (BOOL)shouldCacheStatements { + return shouldCacheStatements; +} + +- (void)setShouldCacheStatements:(BOOL)value { + + shouldCacheStatements = value; + + if (shouldCacheStatements && !cachedStatements) { + [self setCachedStatements:[NSMutableDictionary dictionary]]; + } + + if (!shouldCacheStatements) { + [self setCachedStatements:nil]; + } +} + +- (NSMutableDictionary *) cachedStatements { + return cachedStatements; +} + +- (void)setCachedStatements:(NSMutableDictionary *)value { + if (cachedStatements != value) { + [cachedStatements release]; + cachedStatements = [value retain]; + } +} + + +- (int)changes { + return(sqlite3_changes(db)); +} + +@end + + + +@implementation FMStatement + +- (void)dealloc { + [self close]; + [query release]; + [super dealloc]; +} + + +- (void) close { + if (statement) { + sqlite3_finalize(statement); + statement = 0x00; + } +} + +- (void) reset { + if (statement) { + sqlite3_reset(statement); + } +} + +- (sqlite3_stmt *) statement { + return statement; +} + +- (void)setStatement:(sqlite3_stmt *)value { + statement = value; +} + +- (NSString *) query { + return query; +} + +- (void)setQuery:(NSString *)value { + if (query != value) { + [query release]; + query = [value retain]; + } +} + +- (long)useCount { + return useCount; +} + +- (void)setUseCount:(long)value { + if (useCount != value) { + useCount = value; + } +} + +- (NSString*) description { + return [NSString stringWithFormat:@"%@ %d hit(s) for query %@", [super description], useCount, query]; +} + + +@end + diff --git a/src/FMDatabaseAdditions.h b/src/FMDatabaseAdditions.h new file mode 100644 index 00000000..38462683 --- /dev/null +++ b/src/FMDatabaseAdditions.h @@ -0,0 +1,31 @@ +// +// FMDatabaseAdditions.h +// fmkit +// +// Created by August Mueller on 10/30/05. +// Copyright 2005 Flying Meat Inc.. All rights reserved. +// + +#import +@interface FMDatabase (FMDatabaseAdditions) + + +- (int)intForQuery:(NSString*)objs, ...; +- (long)longForQuery:(NSString*)objs, ...; +- (BOOL)boolForQuery:(NSString*)objs, ...; +- (double)doubleForQuery:(NSString*)objs, ...; +- (NSString*)stringForQuery:(NSString*)objs, ...; +- (NSData*)dataForQuery:(NSString*)objs, ...; +- (NSDate*)dateForQuery:(NSString*)objs, ...; + +// Notice that there's no dataNoCopyForQuery:. +// That would be a bad idea, because we close out the result set, and then what +// happens to the data that we just didn't copy? Who knows, not I. + + +- (BOOL)tableExists:(NSString*)tableName; +- (FMResultSet*)getSchema; +- (FMResultSet*)getTableSchema:(NSString*)tableName; +- (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName; + +@end diff --git a/src/FMDatabaseAdditions.m b/src/FMDatabaseAdditions.m new file mode 100644 index 00000000..2e1287a9 --- /dev/null +++ b/src/FMDatabaseAdditions.m @@ -0,0 +1,114 @@ +// +// FMDatabaseAdditions.m +// fmkit +// +// Created by August Mueller on 10/30/05. +// Copyright 2005 Flying Meat Inc.. All rights reserved. +// + +#import "FMDatabase.h" +#import "FMDatabaseAdditions.h" + +@implementation FMDatabase (FMDatabaseAdditions) + +#define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \ +va_list args; \ +va_start(args, query); \ +FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orVAList:args]; \ +va_end(args); \ +if (![resultSet next]) { return (type)0; } \ +type ret = [resultSet sel:0]; \ +[resultSet close]; \ +[resultSet setParentDB:nil]; \ +return ret; + + +- (NSString*)stringForQuery:(NSString*)query, ...; { + RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex); +} + +- (int)intForQuery:(NSString*)query, ...; { + RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex); +} + +- (long)longForQuery:(NSString*)query, ...; { + RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex); +} + +- (BOOL)boolForQuery:(NSString*)query, ...; { + RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex); +} + +- (double)doubleForQuery:(NSString*)query, ...; { + RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex); +} + +- (NSData*)dataForQuery:(NSString*)query, ...; { + RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex); +} + +- (NSDate*)dateForQuery:(NSString*)query, ...; { + RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex); +} + + +//check if table exist in database (patch from OZLB) +- (BOOL)tableExists:(NSString*)tableName { + + BOOL returnBool; + //lower case table name + tableName = [tableName lowercaseString]; + //search in sqlite_master table if table exists + FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName]; + //if at least one next exists, table exists + returnBool = [rs next]; + //close and free object + [rs close]; + + return returnBool; +} + +//get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] +//check if table exist in database (patch from OZLB) +- (FMResultSet*)getSchema { + + //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] + FMResultSet *rs = [self executeQuery:@"SELECT type, name, tbl_name, rootpage, sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type != 'meta' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name, type DESC, name"]; + + return rs; +} + +//get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] +- (FMResultSet*)getTableSchema:(NSString*)tableName { + + //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] + FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"PRAGMA table_info(%@)", tableName]]; + + return rs; +} + + +//check if column exist in table +- (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName { + + BOOL returnBool = NO; + //lower case table name + tableName = [tableName lowercaseString]; + //lower case column name + columnName = [columnName lowercaseString]; + //get table schema + FMResultSet *rs = [self getTableSchema: tableName]; + //check if column is present in table schema + while ([rs next]) { + if ([[[rs stringForColumn:@"name"] lowercaseString] isEqualToString: columnName]) { + returnBool = YES; + break; + } + } + //close and free object + [rs close]; + + return returnBool; +} + +@end diff --git a/src/FMResultSet.h b/src/FMResultSet.h new file mode 100644 index 00000000..257ef783 --- /dev/null +++ b/src/FMResultSet.h @@ -0,0 +1,75 @@ +#import +#import "sqlite3.h" + +@class FMDatabase; +@class FMStatement; + +@interface FMResultSet : NSObject { + FMDatabase *parentDB; + FMStatement *statement; + + NSString *query; + NSMutableDictionary *columnNameToIndexMap; + BOOL columnNamesSetup; +} + + ++ (id) resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB; + +- (void) close; + +- (NSString *)query; +- (void)setQuery:(NSString *)value; + +- (FMStatement *)statement; +- (void)setStatement:(FMStatement *)value; + +- (void)setParentDB:(FMDatabase *)newDb; + +- (BOOL) next; +- (BOOL) hasAnotherRow; + +- (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; + +- (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; + +/* +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; +- (NSData*) dataNoCopyForColumnIndex:(int)columnIdx; + +- (BOOL) columnIndexIsNull:(int)columnIdx; +- (BOOL) columnIsNull:(NSString*)columnName; + +- (void) kvcMagic:(id)object; + +@end diff --git a/src/FMResultSet.m b/src/FMResultSet.m new file mode 100644 index 00000000..a210dbc9 --- /dev/null +++ b/src/FMResultSet.m @@ -0,0 +1,332 @@ +#import "FMResultSet.h" +#import "FMDatabase.h" +#import "unistd.h" + +@interface FMResultSet (Private) +- (NSMutableDictionary *)columnNameToIndexMap; +- (void)setColumnNameToIndexMap:(NSMutableDictionary *)value; +@end + +@implementation FMResultSet + ++ (id) resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB { + + FMResultSet *rs = [[FMResultSet alloc] init]; + + [rs setStatement:statement]; + [rs setParentDB:aDB]; + + return [rs autorelease]; +} + +- (void)dealloc { + [self close]; + + [query release]; + query = nil; + + [columnNameToIndexMap release]; + columnNameToIndexMap = nil; + + [super dealloc]; +} + +- (void) close { + + [statement reset]; + [statement release]; + statement = nil; + + // we don't need this anymore... (i think) + //[parentDB setInUse:NO]; + parentDB = nil; +} + +- (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)]]; + } + } +} + +- (BOOL) next { + + int rc; + BOOL retry; + int numberOfRetries = 0; + do { + retry = NO; + + rc = sqlite3_step(statement.statement); + + if (SQLITE_BUSY == 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; + 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); +} + +- (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: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: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]]; +} + + +// 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; +} + + +- (NSString *)query { + return query; +} + +- (void)setQuery:(NSString *)value { + [value retain]; + [query release]; + query = value; +} + +- (NSMutableDictionary *)columnNameToIndexMap { + return columnNameToIndexMap; +} + +- (void)setColumnNameToIndexMap:(NSMutableDictionary *)value { + [value retain]; + [columnNameToIndexMap release]; + columnNameToIndexMap = value; +} + +- (FMStatement *) statement { + return statement; +} + +- (void)setStatement:(FMStatement *)value { + if (statement != value) { + [statement release]; + statement = [value retain]; + } +} + + + +@end diff --git a/src/fmdb.m b/src/fmdb.m new file mode 100644 index 00000000..fb7afec4 --- /dev/null +++ b/src/fmdb.m @@ -0,0 +1,496 @@ +#import +#import "FMDatabase.h" +#import "FMDatabaseAdditions.h" + +#define FMDBQuickCheck(SomeBool) { if (!(SomeBool)) { NSLog(@"Failure on line %d", __LINE__); return 123; } } + +int main (int argc, const char * argv[]) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + + // delete the old db. + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager removeFileAtPath:@"/tmp/tmp.db" handler:nil]; + + FMDatabase* db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"]; + if (![db open]) { + NSLog(@"Could not open db."); + [pool release]; + return 0; + } + + // kind of experimentalish. + [db setShouldCacheStatements:YES]; + + // create a bad statement, just to test the error code. + [db executeUpdate:@"blah blah blah"]; + + FMDBQuickCheck([db hadError]); + + if ([db hadError]) { + NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]); + } + + // but of course, I don't bother checking the error codes below. + // Bad programmer, no cookie. + + [db executeUpdate:@"create table test (a text, b text, c integer, d double, e double)"]; + + + [db beginTransaction]; + int i = 0; + while (i++ < 20) { + [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" , + @"hi'", // look! I put in a ', and I'm not escaping it! + [NSString stringWithFormat:@"number %d", i], + [NSNumber numberWithInt:i], + [NSDate date], + [NSNumber numberWithFloat:2.2f]]; + } + [db commit]; + + + + // do it again, just because + [db beginTransaction]; + i = 0; + while (i++ < 20) { + [db executeUpdate:@"insert into test (a, b, c, d, e) values (?, ?, ?, ?, ?)" , + @"hi again'", // look! I put in a ', and I'm not escaping it! + [NSString stringWithFormat:@"number %d", i], + [NSNumber numberWithInt:i], + [NSDate date], + [NSNumber numberWithFloat:2.2f]]; + } + [db commit]; + + + + + + FMResultSet *rs = [db executeQuery:@"select rowid,* from test where a = ?", @"hi'"]; + while ([rs next]) { + // just print out what we've got in a number of formats. + NSLog(@"%d %@ %@ %@ %@ %f %f", + [rs intForColumn:@"c"], + [rs stringForColumn:@"b"], + [rs stringForColumn:@"a"], + [rs stringForColumn:@"rowid"], + [rs dateForColumn:@"d"], + [rs doubleForColumn:@"d"], + [rs doubleForColumn:@"e"]); + + + if (!([[rs columnNameForIndex:0] isEqualToString:@"rowid"] && + [[rs columnNameForIndex:1] isEqualToString:@"a"]) + ) { + NSLog(@"WHOA THERE BUDDY, columnNameForIndex ISN'T WORKING!"); + return 7; + } + } + // close the result set. + // it'll also close when it's dealloc'd, but we're closing the database before + // the autorelease pool closes, so sqlite will complain about it. + [rs close]; + + // ---------------------------------------------------------------------------------------- + // blob support. + [db executeUpdate:@"create table blobTable (a text, b blob)"]; + + // let's read in an image from safari's app bundle. + NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"]; + if (safariCompass) { + [db executeUpdate:@"insert into blobTable (a, b) values (?,?)", @"safari's compass", safariCompass]; + + rs = [db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"]; + if ([rs next]) { + safariCompass = [rs dataForColumn:@"b"]; + [safariCompass writeToFile:@"/tmp/compass.icns" atomically:NO]; + + // let's look at our fancy image that we just wrote out.. + system("/usr/bin/open /tmp/compass.icns"); + + // ye shall read the header for this function, or suffer the consequences. + safariCompass = [rs dataNoCopyForColumn:@"b"]; + [safariCompass writeToFile:@"/tmp/compass_data_no_copy.icns" atomically:NO]; + system("/usr/bin/open /tmp/compass_data_no_copy.icns"); + } + else { + NSLog(@"Could not select image."); + } + + [rs close]; + + } + else { + NSLog(@"Can't find compass image.."); + } + + + // test out the convenience methods in +Additions + [db executeUpdate:@"create table t1 (a integer)"]; + [db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]]; + int a = [db intForQuery:@"select a from t1 where a = ?", [NSNumber numberWithInt:5]]; + if (a != 5) { + NSLog(@"intForQuery didn't work (a != 5)"); + } + + // test the busy rety timeout schtuff. + + [db setBusyRetryTimeout:50000]; + + FMDatabase *newDb = [FMDatabase databaseWithPath:@"/tmp/tmp.db"]; + [newDb open]; + + rs = [newDb executeQuery:@"select rowid,* from test where a = ?", @"hi'"]; + [rs next]; // just grab one... which will keep the db locked. + + NSLog(@"Testing the busy timeout"); + + BOOL success = [db executeUpdate:@"insert into t1 values (5)"]; + + if (success) { + NSLog(@"Whoa- the database didn't stay locked!"); + return 7; + } + else { + NSLog(@"Hurray, our timeout worked"); + } + + [rs close]; + [newDb close]; + + success = [db executeUpdate:@"insert into t1 values (5)"]; + if (!success) { + NSLog(@"Whoa- the database shouldn't be locked!"); + return 8; + } + else { + NSLog(@"Hurray, we can insert again!"); + } + + + + // test some nullness. + [db executeUpdate:@"create table t2 (a integer, b integer)"]; + + if (![db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]]) { + NSLog(@"UH OH, can't insert a nil value for some reason..."); + } + + + + + rs = [db executeQuery:@"select * from t2"]; + while ([rs next]) { + NSString *a = [rs stringForColumnIndex:0]; + NSString *b = [rs stringForColumnIndex:1]; + + if (a != nil) { + NSLog(@"%s:%d", __FUNCTION__, __LINE__); + NSLog(@"OH OH, PROBLEMO!"); + return 10; + } + else { + NSLog(@"YAY, NULL VALUES"); + } + + if (![b isEqualToString:@"5"]) { + NSLog(@"%s:%d", __FUNCTION__, __LINE__); + NSLog(@"OH OH, PROBLEMO!"); + return 10; + } + } + + + + + + + + + + + // test some inner loop funkness. + [db executeUpdate:@"create table t3 (a somevalue)"]; + + + // do it again, just because + [db beginTransaction]; + i = 0; + while (i++ < 20) { + [db executeUpdate:@"insert into t3 (a) values (?)" , [NSNumber numberWithInt:i]]; + } + [db commit]; + + + + + rs = [db executeQuery:@"select * from t3"]; + while ([rs next]) { + int foo = [rs intForColumnIndex:0]; + + int newVal = foo + 100; + + [db executeUpdate:@"update t3 set a = ? where a = ?" , [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]]; + + + FMResultSet *rs2 = [db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]]; + [rs2 next]; + + if ([rs2 intForColumnIndex:0] != newVal) { + NSLog(@"Oh crap, our update didn't work out!"); + return 9; + } + + [rs2 close]; + } + + + // NSNull tests + [db executeUpdate:@"create table nulltest (a text, b text)"]; + + [db executeUpdate:@"insert into nulltest (a, b) values (?, ?)" , [NSNull null], @"a"]; + [db executeUpdate:@"insert into nulltest (a, b) values (?, ?)" , nil, @"b"]; + + rs = [db executeQuery:@"select * from nulltest"]; + + while ([rs next]) { + + NSString *a = [rs stringForColumnIndex:0]; + NSString *b = [rs stringForColumnIndex:1]; + + if (!b) { + NSLog(@"Oh crap, the nil / null inserts didn't work!"); + return 10; + } + + if (a) { + NSLog(@"Oh crap, the nil / null inserts didn't work (son of error message)!"); + return 11; + } + else { + NSLog(@"HURRAH FOR NSNULL (and nil)!"); + } + } + + + + + + + // null dates + + NSDate *date = [NSDate date]; + [db executeUpdate:@"create table datetest (a double, b double, c double)"]; + [db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date]; + + rs = [db executeQuery:@"select * from datetest"]; + + while ([rs next]) { + + NSDate *a = [rs dateForColumnIndex:0]; + NSDate *b = [rs dateForColumnIndex:1]; + NSDate *c = [rs dateForColumnIndex:2]; + + if (a) { + NSLog(@"Oh crap, the null date insert didn't work!"); + return 12; + } + + if (!c) { + NSLog(@"Oh crap, the 0 date insert didn't work!"); + return 12; + } + + NSTimeInterval dti = fabs([b timeIntervalSinceDate:date]); + + if (floor(dti) > 0.0) { + NSLog(@"Date matches didn't really happen... time difference of %f", dti); + return 13; + } + + + dti = fabs([c timeIntervalSinceDate:[NSDate dateWithTimeIntervalSince1970:0]]); + + if (floor(dti) > 0.0) { + NSLog(@"Date matches didn't really happen... time difference of %f", dti); + return 13; + } + } + + NSDate *foo = [db dateForQuery:@"select b from datetest where c = 0"]; + assert(foo); + NSTimeInterval dti = fabs([foo timeIntervalSinceDate:date]); + if (floor(dti) > 0.0) { + NSLog(@"Date matches didn't really happen... time difference of %f", dti); + return 14; + } + + [db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"]; + + [db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4], [NSNumber numberWithBool:YES]]; + [db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]]; + + rs = [db executeQuery:@"select * from nulltest2"]; + + while ([rs next]) { + + int i = [rs intForColumnIndex:2]; + + if (i == 12) { + // it's the first row we inserted. + FMDBQuickCheck(![rs columnIndexIsNull:0]); + FMDBQuickCheck(![rs columnIndexIsNull:1]); + FMDBQuickCheck(![rs columnIndexIsNull:2]); + FMDBQuickCheck(![rs columnIndexIsNull:3]); + FMDBQuickCheck(![rs columnIndexIsNull:4]); + FMDBQuickCheck( [rs columnIndexIsNull:5]); + + FMDBQuickCheck([[rs dataForColumn:@"d"] length] == [safariCompass length]); + FMDBQuickCheck(![rs dataForColumn:@"notthere"]); + FMDBQuickCheck(![rs stringForColumnIndex:-2]); + FMDBQuickCheck([rs boolForColumnIndex:4]); + FMDBQuickCheck([rs boolForColumn:@"b"]); + + FMDBQuickCheck(fabs(4.4 - [rs doubleForColumn:@"f"]) < 0.0000001); + + FMDBQuickCheck(12 == [rs intForColumn:@"i"]); + FMDBQuickCheck(12 == [rs intForColumnIndex:2]); + + FMDBQuickCheck(0 == [rs intForColumnIndex:12]); // there is no 12 + FMDBQuickCheck(0 == [rs intForColumn:@"notthere"]); + + FMDBQuickCheck(12 == [rs longForColumn:@"i"]); + FMDBQuickCheck(12 == [rs longLongIntForColumn:@"i"]); + } + else { + // let's test various null things. + + FMDBQuickCheck([rs columnIndexIsNull:0]); + FMDBQuickCheck([rs columnIndexIsNull:1]); + FMDBQuickCheck([rs columnIndexIsNull:2]); + FMDBQuickCheck([rs columnIndexIsNull:3]); + FMDBQuickCheck([rs columnIndexIsNull:4]); + FMDBQuickCheck([rs columnIndexIsNull:5]); + + + FMDBQuickCheck(![rs dataForColumn:@"d"]); + + } + } + + + + { + [db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"]; + [db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]]; + [db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]]; + + + rs = [db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]]; + + FMDBQuickCheck([rs next]); + + FMDBQuickCheck([rs hasAnotherRow]); + FMDBQuickCheck(![db hadError]); + + FMDBQuickCheck([[rs stringForColumnIndex:0] isEqualToString:@"one"]); + FMDBQuickCheck([rs intForColumnIndex:1] == 2); + + FMDBQuickCheck([rs next]); + + FMDBQuickCheck([rs intForColumnIndex:1] == 3); + + FMDBQuickCheck(![rs next]); + FMDBQuickCheck(![rs hasAnotherRow]); + + } + + { + + FMDBQuickCheck([db executeUpdate:@"create table t4 (a text, b text)"]); + FMDBQuickCheck(([db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"])); + + rs = [db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"]; + + FMDBQuickCheck((rs != nil)); + + [rs next]; + + FMDBQuickCheck([[rs stringForColumn:@"t4.a"] isEqualToString:@"one"]); + FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"two"]); + + FMDBQuickCheck(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two") == 0); + + [rs close]; + + // let's try these again, with the withArgumentsInArray: variation + FMDBQuickCheck([db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]); + FMDBQuickCheck([db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]); + FMDBQuickCheck(([db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]])); + + rs = [db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]]; + + FMDBQuickCheck((rs != nil)); + + [rs next]; + + FMDBQuickCheck([[rs stringForColumn:@"t4.a"] isEqualToString:@"one"]); + FMDBQuickCheck([[rs stringForColumn:@"b"] isEqualToString:@"two"]); + + FMDBQuickCheck(strcmp((const char*)[rs UTF8StringForColumnName:@"b"], "two") == 0); + + [rs close]; + + + + + + + } + + + + + { + FMDBQuickCheck([db tableExists:@"t4"]); + FMDBQuickCheck(![db tableExists:@"thisdoesntexist"]); + + rs = [db getSchema]; + while ([rs next]) { + FMDBQuickCheck([[rs stringForColumn:@"type"] isEqualToString:@"table"]); + } + + + } + + + + + + // just for fun. + rs = [db executeQuery:@"PRAGMA database_list"]; + while ([rs next]) { + NSString *file = [rs stringForColumn:@"file"]; + NSLog(@"database_list: %@", file); + } + + + // print out some stats if we are using cached statements. + if ([db shouldCacheStatements]) { + + NSEnumerator *e = [[db cachedStatements] objectEnumerator];; + FMStatement *statement; + + while ((statement = [e nextObject])) { + NSLog(@"%@", statement); + } + } + NSLog(@"That was version %@ of sqlite", [FMDatabase sqliteLibVersion]); + + + [db close]; + + [pool release]; + return 0; +}