Initial import.

This commit is contained in:
ccgus 2010-07-06 15:53:55 -07:00
parent f8ecf5a031
commit 7ffd84d76b
12 changed files with 2290 additions and 0 deletions

24
LICENSE.txt Normal file
View File

@ -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.

View File

@ -0,0 +1,4 @@
FMDB
====
This is an Objective-C wrapper around SQLite: http://sqlite.org/

79
fmdb.1 Normal file
View File

@ -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

View File

@ -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 = "<absolute>"; };
32A70AAB03705E1F00C91783 /* fmdb_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fmdb_Prefix.pch; sourceTree = "<group>"; };
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 = "<group>"; };
CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FMDatabaseAdditions.m; path = src/FMDatabaseAdditions.m; sourceTree = "<group>"; };
CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMDatabaseAdditions.h; path = src/FMDatabaseAdditions.h; sourceTree = "<group>"; };
CC8C138A0E3135C400FBE1E7 /* CHANGES_AND_TODO_LIST.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGES_AND_TODO_LIST.txt; sourceTree = "<group>"; };
CC8C138B0E3135C400FBE1E7 /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
CC8C138C0E3135C400FBE1E7 /* CONTRIBUTORS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CONTRIBUTORS.txt; sourceTree = "<group>"; };
CCBEBDAB0DF5DE1A003DDD08 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = /usr/lib/libsqlite3.dylib; sourceTree = "<absolute>"; };
CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMDatabase.h; path = src/FMDatabase.h; sourceTree = "<group>"; };
CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FMDatabase.m; path = src/FMDatabase.m; sourceTree = "<group>"; };
CCC24EBE0A13E34D00A6D3E3 /* fmdb.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = fmdb.m; path = src/fmdb.m; sourceTree = "<group>"; };
CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FMResultSet.h; path = src/FMResultSet.h; sourceTree = "<group>"; };
CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FMResultSet.m; path = src/FMResultSet.m; sourceTree = "<group>"; };
/* 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 = "<group>";
};
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 = "<group>";
};
08FB779DFE84155DC02AAC07 /* External Frameworks and Libraries */ = {
isa = PBXGroup;
children = (
CCBEBDAB0DF5DE1A003DDD08 /* libsqlite3.dylib */,
08FB779EFE84155DC02AAC07 /* Foundation.framework */,
);
name = "External Frameworks and Libraries";
sourceTree = "<group>";
};
1AB674ADFE9D54B511CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8DD76FA10486AA7600D96B5E /* fmdb */,
);
name = Products;
sourceTree = "<group>";
};
C6859EA2029092E104C91782 /* Documentation */ = {
isa = PBXGroup;
children = (
C6859EA3029092ED04C91782 /* fmdb.1 */,
);
name = Documentation;
sourceTree = "<group>";
};
/* 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 */;
}

7
fmdb_Prefix.pch Normal file
View File

@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'fmdb' target in the 'fmdb' project.
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif

116
src/FMDatabase.h Normal file
View File

@ -0,0 +1,116 @@
#import <Foundation/Foundation.h>
#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

753
src/FMDatabase.m Normal file
View File

@ -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

31
src/FMDatabaseAdditions.h Normal file
View File

@ -0,0 +1,31 @@
//
// FMDatabaseAdditions.h
// fmkit
//
// Created by August Mueller on 10/30/05.
// Copyright 2005 Flying Meat Inc.. All rights reserved.
//
#import <Foundation/Foundation.h>
@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

114
src/FMDatabaseAdditions.m Normal file
View File

@ -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

75
src/FMResultSet.h Normal file
View File

@ -0,0 +1,75 @@
#import <Foundation/Foundation.h>
#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

332
src/FMResultSet.m Normal file
View File

@ -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

496
src/fmdb.m Normal file
View File

@ -0,0 +1,496 @@
#import <Foundation/Foundation.h>
#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;
}