Full Code of ccgus/fmdb for AI

master d3abf748a278 cached
55 files
545.7 KB
145.0k tokens
8 symbols
1 requests
Download .txt
Showing preview only (568K chars total). Download the full file or copy to clipboard to get everything.
Repository: ccgus/fmdb
Branch: master
Commit: d3abf748a278
Files: 55
Total size: 545.7 KB

Directory structure:
gitextract_7bzgmki1/

├── .gitignore
├── .travis.yml
├── CHANGES_AND_TODO_LIST.txt
├── COCOAPODS.md
├── CONTRIBUTORS.txt
├── FMDB.podspec
├── LICENSE.txt
├── Package.swift
├── README.markdown
├── Tests/
│   ├── Base.lproj/
│   │   └── InfoPlist.strings
│   ├── FMDBTempDBTests.h
│   ├── FMDBTempDBTests.m
│   ├── FMDatabaseAdditionsTests.m
│   ├── FMDatabaseFTS3Tests.m
│   ├── FMDatabaseFTS3WithModuleNameTests.m
│   ├── FMDatabasePoolTests.m
│   ├── FMDatabaseQueueTests.m
│   ├── FMDatabaseTests.m
│   ├── FMResultSetTests.m
│   ├── Schemes/
│   │   └── Tests.xcscheme
│   ├── Tests-Info.plist
│   ├── Tests-Prefix.pch
│   └── en.lproj/
│       └── InfoPlist.strings
├── fmdb.1
├── fmdb.xcodeproj/
│   ├── project.pbxproj
│   └── xcshareddata/
│       └── xcschemes/
│           ├── FMDB MacOS.xcscheme
│           ├── FMDB iOS.xcscheme
│           ├── FMDB watchOS.xcscheme
│           └── FMDB xrOS.xcscheme
├── privacy/
│   ├── PrivacyInfo.xcprivacy
│   └── README.md
└── src/
    ├── extra/
    │   ├── InMemoryOnDiskIO/
    │   │   ├── FMDatabase+InMemoryOnDiskIO.h
    │   │   └── FMDatabase+InMemoryOnDiskIO.m
    │   └── fts3/
    │       ├── FMDatabase+FTS3.h
    │       ├── FMDatabase+FTS3.m
    │       ├── FMTokenizers.h
    │       ├── FMTokenizers.m
    │       └── fts3_tokenizer.h
    ├── fmdb/
    │   ├── FMDB.h
    │   ├── FMDatabase+SQLCipher.h
    │   ├── FMDatabase+SQLCipher.m
    │   ├── FMDatabase.h
    │   ├── FMDatabase.m
    │   ├── FMDatabaseAdditions.h
    │   ├── FMDatabaseAdditions.m
    │   ├── FMDatabasePool.h
    │   ├── FMDatabasePool.m
    │   ├── FMDatabaseQueue.h
    │   ├── FMDatabaseQueue.m
    │   ├── FMResultSet.h
    │   ├── FMResultSet.m
    │   ├── Info.plist
    │   └── info-xrOS.plist
    └── sample/
        ├── fmdb_Prefix.pch
        └── main.m

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
build
*.xcodeproj/*.pbxuser
*.xcodeproj/*.perspectivev3
*.xcodeproj/xcuserdata
fmdb.xcodeproj/*.mode1v3
*.xcodeproj/project.xcworkspace/xcuserdata/
fmdb.xcodeproj/project.xcworkspace
.swiftpm


================================================
FILE: .travis.yml
================================================
language: objective-c
xcode_project: fmdb.xcodeproj
xcode_scheme: Tests
before_install:
    - mkdir -p "fmdb.xcodeproj/xcshareddata/xcschemes" && cp Tests/Schemes/*.xcscheme "fmdb.xcodeproj/xcshareddata/xcschemes/"


================================================
FILE: CHANGES_AND_TODO_LIST.txt
================================================
TODO:
Zip, nada, zilch.  Got any ideas?

If you would like to contribute some code ... awesome!  I just ask that you make it conform to the coding conventions already set in here, and to add the necessary of tests for your new code to tests target.  And of course, the code should be of general use to more than just a couple of folks.  Send your patches to gus@flyingmeat.com.

2024.05.29 Version 2.7.12
    Fix Privacy Manifest resource bundling for CocoaPods and Swift Package Manager installation methods.

2023.02.08 - 2023.05.23 Versions 2.7.9 - 2.7.11
    CocoaPods-related fixes and tweaks.

2020.03.25 Version 2.7.8
    Add `valueForColumn` functions to expose `sqlite3_column_type`. Add comments to `dataForColumn` re SQL behavior of zero-length BLOBs.

2020.05.06 Version 2.7.7
    Add `prepare` and `bind` methods so you can prepare a statement once and bind values repeatedly.

2020.04.23 Version 2.7.6
    A new tag for the Swift Package Manager.

2018.10.23 Version 2.7.5
    Xcode 10 support. Probably some other stuff over the past year as well.
    
    Added confidence inspiring release notes. Also the tests pass.

2017.10.23 Version 2.7.4
    Added support for explicit transactions.

    Add warning that `beginTransaction` and `inTransaction` behavior is likely to change.

2017.10.20 Version 2.7.3
    Added support for immediate transactions and checkpoint. (thanks to @benasher44)

    Updated nullability of `FMDatabaseQueue` initialization methods. (thanks to @robotive)
    In a related correction, we updated README to avoid confusing reference to nullability
    changes in 2.7. (In 2.7, as part of the nullability audit, I (@robertryan) accidentally
    declared a few `FMDatabaseQueue` initializers to be nonnull, but @robotive pointed out
    that they actually _were_ nullable. Most of that nullability audit stands as it was
    released in 2.7.0, but this fixes a few little errors.

    Minor correction to README, whereby Swift example code implicitly suggested using
    Documents folder for database, whereas with the advent of the new Files app, Apple suggests
    using the "Application Support" directory for files that are not documents for end-user
    interaction. Clearly, use whatever is appropriate in your case, but no longer assume that
    you should just place files in Documents folder, as that is often not the appropriate location.

2017.06.01 Version 2.7.2
    Make blocks `nonescaping` (thanks to @benasher44)

    Update method documentation.

2017.06.01 Version 2.7.1
    Adjust `valueLong` return type and `resultLong` parameter to suppress warning.

    Fix pointer comparison to avoid static analysis warning in `columnIndexForName`.

2017.05.26 Version 2.7
    Audited library for nullability, offering informational warnings for Objective-C users during static analysis, but significantly changes interface for Swift users, more accurately representing parameters and return values as optional or non-optional, as appropriate.

    Renamed a number of methods, deprecating old names.

    Converted some methods to properties. Again, it should be largely transparent from Objective-C users, but Swift users will find they'll just have to remove some `()` in their code, lending itself to more logical looking code.

    The `objectForColumn` used to return `NSNull` if you supplied it an invalid subscript. It now returns `nil`, more customary for subscript operators.

    For more information, see https://github.com/ccgus/fmdb/pull/584.

2015.12.28
    Removed `sqlite3.h` from the headers to simplify incorporation of FMDB into a framework. This eliminates the dreaded "non-modular headers" error in frameworks.

    To accomplish this, the few references to SQLite pointers have been changed to return `void` pointers. This means that if you have application code that used SQLite pointers exposed by FMDB, you may have to cast the pointer to the appropriate type when you use it (and `#import <sqlite3.h>` yourself). In general, we would advise against using SQLite pointers unless you absolutely need to.

    Also changed the `FMDatabaseVariadic.swift` to throw errors for Swift 2.

2015.10.29
    Added renditions of `executeUpdate:values:error:` and `executeQuery:values:error:`, which in Swift 2 throw errors.

2015.01.23
    Added Swift renditions of the variadic methods of `FMDatabaseAdditions`.

2014.10.19
    Added a `nextWithError:` to `FMResultSet`.  Thanks to Roshan Muralidharan for the patch.

2014.09.10
    New classes for exposing SQLite's FTS features.  Thanks to Andrew Goodale for the code.

2014.04.23
    New executeStatements: method, which will take a single UTF-8 string with multiple statements in it.  This is great for batch updates.  There is also a executeStatements:withResultBlock: version which takes a callback block which will be used for any statements which return rows in the bulk statement.  Thanks to Rob Ryan for contributing code for this.

    Deprecated `update:withErrorAndBindings:` in favor of `executeUpdate:withErrorAndBindings:` or `executeUpdate:values:error:`.

2014.04.09
    Added back in busy handler code after a brief hiatus (check out the 2013.12.10 notes).  But now doing so with sqlite3_busy_handler instead of while loops in the various execution places.
    Added some new optional classes that will help with doing batch updates  - check out FMSQLStatementSplitter.h for more info.  Thanks to Julius Scott for the patches.

2014.03.08
    A few administrative changes:

        - Move FMDB source files into three subdirectories, either src/fmdb, src/sample, or src/extras.
        - Renamed fmdb.m to main.m and moved it into src/sample so that it's clear its a sample and it won't be included in project for those users who manually drag fmdb source into their projects.
        - Created FMDB.h for those users who would prefer to do a single #import and get all of the key headers. 

2014.01.17
    It's never been safe to reentrantly call -[FMDatabaseQueue inDatabase:], as it would block.  Which can be kind of annoying - so now FMDB will crash instead (thanks to Mike Ash for the patch).

2013.12.10
    Lots of little updates - new test targets, ARC simplification, and open flags to FMDatabaseQueue (thanks Graham Dennis), FMDatabaseQueue now has + (Class)databaseClass; which can return a new subclass of FMDatabase for custom stuff (thanks Timur Islamgulov),
    
    ## IMPORTANT##
    int busyRetryTimeout has been change to NSTimeInterval busyTimeout in FMDatabase.  Instead of using it's homegrown "try again every so often if it's locked", we use sqlite's built in support for this.  Why wasn't FMDatabase using it before?  Because Gus didn't know about it.  Thanks to Jens Alfke for pointing this out and providing some code to work with.

2013.10.21
    Fixed a problem where having statement caching turned on would cause issues when trying to use two result sets with the same query but different binding parameters.  Thanks to Nick Hodapp for the original patch.
    Fixed a problem where save points weren't being created with the correct names, and were not cleaned up properly.  Thanks to Graham Dennis for the patches.

2013.10.16
    Added methods that expose va_list arguments.  Thanks to Ibrahim Ennafaa for the patch.
    
2013.09.26
    Logs errors is now turned on by default.  It's easy to turn off, and if you're seeing errors then you should probably fix those.

2013.06.26
    Added `NS_FORMAT_FUNCTION` qualifier to `executeQueryWithFormat` and `executeUpdateWithFormat`. These methods take format strings and variable number of arguments and you will now receive compiler warnings if the types of your parameters dont match the format string.

2013.06.04
    Merged in Robert Ryan's comments in .h header files. These comments hopefully make the .h more readable, but just as importantly, can be parsed by [`appledoc`](http://gentlebytes.com/appledoc/) to create HTML documentation or Xcode docsets. <https://github.com/ccgus/fmdb/pull/150>

        - To build that HTML documentation, once you've installed `appledoc`, you issue the command [note Jan 2015: the `no-create-docset` needed due to recent changes to `appledoc`]:

             appledoc --project-name FMDB --project-company ccgus --explicit-crossref --no-merge-categories --no-create-docset --output ../Documentation --ignore *.m .

        - If you want online help integrated right into Xcode (which is no longer needed because Xcode now automatically integrates documentation found in the .h files), you can issue the command:

             appledoc --project-name FMDB --project-company ccgus --explicit-crossref --merge-categories --install-docset --output --ignore *.m ../Documentation .

2013.05.24
    Merged in Chris Wright's date format additions to FMDatabase.
    Fixed a problem where executeUpdateWithFormat: + %@ as a placeholder and the value was nil would cause a bad value to be inserted.  Thanks to rustybox on github for the fix.
    Fixed a variable argument crash if an incorrect number of arguments are passed in - patch by Joshua Tessier.
    Baked in support for the new application_id pragma in SQLite version 3.7.17:
        - (uint32_t)applicationID;
        - (void)setApplicationID:(uint32_t)appID;
        - (NSString*)applicationIDString;
        - (void)setApplicationIDString:(NSString*)s;


2013.04.17
    Added two new methods to FMDatabase for setting crypto keys, which take NSData objects.  Thanks to Phillip Kast for the patch! <https://github.com/ccgus/fmdb/pull/135>

2013.02.19
    Fixed potential dereference of NULL outErr argument in -startSavePointWithName:error: and -releaseSavePointWithName:error:.  Thanks to Jim Correia for the patch!

2013.02.05
    Added an "extra" folder which contains additional things which might be useful for programmers, but which I don't think should be part of the standard classes.  At this time- there's only a single category in there which will load and save an on disk database to an in memory database.  Thanks to Peter Carr who provided these additions!  

2013.01.31
    Lazy init of columnNameToIndexMap in FMResultSet, and you are now able to use it before asking for any rows first.  Thanks to Daniel Dickison for the patch!

2012.12.17
    Now resetting cached statements before executing a query on them (as well as resetting them at the close of a result set).  There was an issue where if you used the same query twice without closing the result set of the first one, you would get results back from the previous query, or maybe an exhausted result set.  Thanks to note173 on github for pointing out the problem.

2012.12.13
    Changed up how the binding count is calculated when passing a dictionary for named parameter support.  Thanks to Samuel Chen for pointing out the problem.

2012.11.23
    Added keyed and indexed subscript support to FMResultSet- so you can do use the fancy boxed syntax against it (rs[@"foo"] & rs[0]).  Thanks to Robert Ryan for the patches!

2012.08.08
    Fixed some problems when compiling with ARC against the 10.8 SDK (patch from Geoffrey Foster!).

2012.05.29:
    Changed up the behavior of binding empty NSData objects ([NSData data]).  It will now insert an empty value, rather than a null value- which is consistent with [NSMutableData data] and empty strings (see https://github.com/ccgus/fmdb/issues/73 for a discussion on this).  Thanks to Jens Alfke for pointing this out!
    
2012.05.25:
    Deprecated columnExists:columnName: in favor of columnExists:inTableWithName:
    Remembered to update the changes notes.  I've been forgetting to do this recently.
    
2012.03.22:
    Deprecated resultDict and replaced it with resultDictionary on FMResultSet.  Slight change in behavior as well- resultDictionary will return case sensitive keys.
    Fixed a problem with getTableSchema: not working with table names that start with a number.


2012.02.10:
    Changed up FMDatabasePool so that you can't "pop" it from a pool anymore.  I just consider this too risky- use the block based functions instead.
    Also provided a good reason in main.m for why you should use FMDatabaseQueue instead.  Search for "ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS".
    I consider this branch 2.0 at this point- I'll let it bake for a couple of days, then push it to the main repo.
    

2012.01.06:
    Added a new method to FMDatabase to make custom functions out of a block:
    - (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block
    
    Check out the function "testSQLiteFunction" in main.m for an example.

2011.07.14:
    Added methods for named parameters, using keys from an NSDictionary (Thanks to Drarok Ithaqua for the patches!)
    Changed FMDatabase's "- (BOOL)update:(NSString*)sql error:(NSError**)outErr bind:(id)bindArgs, ... " to "- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ..." as the previous method didn't actually work as advertised in the way it was written.  Thanks to @jaekwon for pointing this out.

2011.06.22
    Changed some methods to properties.  Hello 2011.
    Added a warning when you try and use a database that wasn't opened.  Hacked together based on patches from Drarok Ithaqua.
    Fixed a problem under GC where leaked statments were keeping a database from closing.  Patch from Chris Dolan.
    Added + (BOOL)isThreadSafe to FMDatabase.  It'll let you know if the version of SQLite you are running is compiled with it's thread safe options.  THIS DOES NOT MEAN FMDATABASE IS THREAD SAFE.  I haven't done a review of it for this case, so I'm just saying.

2011.04.09
    Added a method to validate a SQL statement.
    Added a method to retrieve the number of columns in a result set.
    Added two methods to execute queries and updates with NSString-style format specifiers.
    Thanks to Dave DeLong for the patches!

2011.03.12
    Added compatibility with garbage collection.
    When an FMDatabase is closed, all open FMResultSets pertaining to that database are also closed.
    Added:
    - (id) objectForColumnIndex:(int)columnIdx;
    - (id) objectForColumnName:(NSString*)columnName;
    Changes by Dave DeLong.

2011.02.05
    The -(int)changes; method on FMDatabase is a bit more robust now, and there's a new static library target.  And if a database path is nil, we now open up a :memory: database. Patch from Pascal Pfiffner!

2011.01.13
    Happy New Year!
    Now checking for SQLITE_LOCKED along with SQLITE_BUSY when trying to perform a query.  Patch from Jeff Meininger!

2010.12.28
    Fixed some compiler warnings (thanks to Casey Fleser!)

2010.11.30
    Added and updated some new methods to take NSError** params.
    Only execute the assertion macros if NS_BLOCK_ASSERTIONS is not set.  Patch from David E. Wheeler!

2010.09.19
    The signature for FMDatabase's executeQuery* methods now return FMResultSet instead if id.  Patch from Augie Fackler!

2010.08.24:
    Added resultDict to FMResultSet, which returns a dictionary of column values.  Thanks to Pascal Pfiffner for the patch!
    Cleaned up some formatting.

2010.06.21:
    
    Changed up FMDatabase's close method to return a boolean, and fixed a compiler warning when compiling in 64bit land.  Thanks to Jens Alfke for the patches!


2010.04.04:
    Added:
        dateForQuery which works like the other fooForQuery methods.  Thanks to Matt Stevens for the patch!

2009.10.18:
    
    Added:
        FMDB now checks for longLongValue in NSNumbers passed for selects or updates, and binds that value to an sqlite int64
        
    Thanks for the patch from Brian Stern!

    Changed:
        renamed getDataBaseSchema: to getSchema.  It didn't actually use the param.  whooops.




2009.10.14:
    
    Reworked:
        - (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
        - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
        
        These two methods now point to:
        - (id) executeQuery:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args;
        - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args;
        
        because the vargs were causing headaches in 64bit land, and on the iphone, and it's fragile n' stuff.

    
    Added:
        - (FMResultSet*) getTableSchema:(NSString*)tableName;
        - (BOOL) columnExists:(NSString*)tableName columnName:(NSString*)columnName;
    
    to FMDatabaseAdditions.  Patch from OZLB
    
2009.09.22
    Disabled the following FMDatabaseAdditions when compiled as 64 bit:
        - (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
        and
        - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;
    
    Since they crash, I just print out a warning now.  Got a patch to fix it?  Send it to gus@flyingmeat.com
    
    
    Added:
        - (BOOL) tableExists:(NSString*)tableName;
        - (FMResultSet*) getDataBaseSchema:(NSString*)tableName;
    
    to FMDatabaseAdditions.  Patch from OZLB
    
    
2009.09.1
    Added:
    - (BOOL) openWithFlags:(int)flags;
    To FMDatabase, which allows you to open up the database with certain flags for sqlite 3.5+
    
    Thanks to Dan Wright for the addition.
    
2009.07.17
    Added:
    - (const unsigned char *) UTF8StringForColumnIndex:(int)columnIdx;
    - (const unsigned char *) UTF8StringForColumnName:(NSString*)columnName;
    to FMResultSet, patch from Nathan Stitt.

2009.05.23
    Added:
    - (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments;
    - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;    
    thanks to code from Phong Long.
    
    
    Fix to FMResultSet's - (BOOL) hadError, as it was returning true for SQLITE_ROW & SQLITE_DONE, which aren't actually errors.
    Added:
    - (BOOL) hasAnotherRow; which lets you know if there is another row waiting in the result set.
    
    Thanks to code from Dave DeLong.
    

2009.05.10
    replaced sqlite3_prepare calls with sqlite3_prepare_v2, since it's shiny and new.
    Now making sure not to call any assembly if on the iphone (asm{ trap }).
    Tested on 10.6.  It works.  Not that I didn't expect it not to- but you never know…

2009.05.05
    stringForColumnIndex, stringForColumn, dataForColumnIndex, dataForColumn, dataNoCopyForColumnIndex, and dataNoCopyForColumn now return nil if a null value was inserted into its table row.  dateForColumnIndex already did this.
    
    Also added the following methods to test if a column is null or not:
        - (BOOL) columnIndexIsNull:(int)columnIdx
        - (BOOL) columnIsNull:(NSString*)columnName
    
    And finally, just general code cleanup + refactoring.  Happy Cinco de Mayo!
    
2009.04.27
    added columnNameForIndex: to FMResultSet which returns the column name for the given index.

2009.04.12
    dateForColumnIndex: now returns null, if a null value was inserted into its table row.  Patch by Robbie Hanson.

2009.03.11
Now importing unistd.h, which the absence of was causing some problems on the iPhone.  Reported by multiple people, but Hal Mueller actually got me to make the change.

2009.03.03
Added (int)changes; to FMDatabase.  From the sqlite docs: "This function returns the number of database rows that were changed (or inserted or deleted) by the most recent SQL statement."
Patch contributed by Tracy Harton

2009.01.02
HAPPY NEW YEAR WOOOO!
Added dataNoCopyForColumn: and dataNoCopyForColumnIndex: to FMResultSet.
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.

2008.12.29
Some changes to make Clang's static analysis happy (http://clang.llvm.org/StaticAnalysis.html). (patch provided by Matt Comi)

2008.12.14
Added longLongIntForColumn: and longLongIntForColumnIndex: to FMResultSet. (patch provided by Will Cosgrove)

2008.11.20
Added a check for NSNull in bindObject, which works just like passing nil does (patch provided by Robert Tolar Haining)

2008.11.02
Removed the block keeping you from performing updates or selects while going through a result set.

2008.10.30
Some bug fixes + warning fixes from Brian Stern (thanks again Brian!)

2008.10.03
Fixed a crasher in FMResultSet's close where if the parent DB was already released, the result set would be talking to a bad address and fmdb went boom. (thanks to Brian Stern for the patch)

2008.06.06
Thanks to Zach Wily for the return of FMDatabaseAdditions!

2008.06.03:
Removed all exceptions, now you should use [db hadError] to check for an error.  I hate (ns)exceptions, I'm not sure why I put them in.
Moved to google code.
Various little cleanup thingies.

2008.07.03
Thanks to Kris Markel for some extra trace outputs, and a fix where the database would be locked if it was too busy.

2008.07.10
Thanks to Daniel Pasco and Bil Moorehead for catching a problem where the column names lookup table was created on every row iteration.  Doh! 

2008.07.18
FMDatabase will now cache statements if you turn it on using shouldCacheStatements:YES.  In theory, this should speed things up.  Test it out, let me know if it makes things good for ya.
Note: This is pretty new code, so it hasn't gone through a lot of testing... you've been warned. (seems to work though!)

2008.00.01
Fixed a problemo that kept it from compiling for the iPhone (Thanks to Sam Steele for the patch)



questions? comments? patches?  Send them to gus@flyingmeat.com


================================================
FILE: COCOAPODS.md
================================================
# CocoaPods release process

1. Update `s.version` in `FMDB.Podspec`.
2. Tag the release (`git tag x.y.z && git push --tags`).
3. Lint the podspec as a pre-check.
 - Run `pod spec lint` from within a clean working copy.
 - If you have any failures, address the errors mentioned.
 - Sometimes, errors are cryptic. A common problem is not having **all** of the supported simulators (macOS, iOS, watchOS, and tvOS) installed and updated.
 - You can narrow down the problem platform(s) with e.g. `pod spec lint --platforms=watchos` to see which pass and which fail.
 - You can also get a _lot_ more info with `pod spec lint --verbose`.
4. Push the podspec up to CocoaPods with `pod trunk push`. You will need access as well as an active session (`pod trunk me` / `pod trunk register`).
5. 🍻

================================================
FILE: CONTRIBUTORS.txt
================================================
Thanks to the following folks for patches and other contributions:

D. Richard Hipp (The author of SQLite)
Dominic Yu
Zach Wily
Kris Markel
Blackhole Media
Daniel Pasco
Bil Moorehead
Sam Steele
Brian Stern
Robert Tolar Haining
Will Cosgrove
Matt Comi
Tracy Harton
Robbie Hanson
Wouter F. Wessels
Phong Long
Nathan Stitt
Dan Wright
OZLB
Matt Stevens
Jens Alfke
Pascal Pfiffner
Augie Fackler
David E. Wheeler
Casey Fleser
Jeff Meininger
Pascal Pfiffner
Dave DeLong
Drarok Ithaqua
Chris Dolan
Sriram Patil
Geoffrey Foster
Robert Ryan
Samuel Chen
Daniel Dickison
Peter Carr
Jim Correia
Phillip Kast
Chris Wright
Joshua Tessier
Graham Dennis
Nick Hodapp
Xianliang Li (Oldman)
David Hart
Mike Ash
Julius Scott
Justin Miller
Troy Stump

Aaaaannnd, Gus Mueller (that's me!)


================================================
FILE: FMDB.podspec
================================================
Pod::Spec.new do |s|
  s.name = 'FMDB'
  s.version = '2.7.12'
  s.summary = 'A Cocoa / Objective-C wrapper around SQLite.'
  s.homepage = 'https://github.com/ccgus/fmdb'
  s.license = 'MIT'
  s.author = { 'August Mueller' => 'gus@flyingmeat.com' }
  s.source = { :git => 'https://github.com/ccgus/fmdb.git', :tag => "#{s.version}" }
  s.requires_arc = true
  s.ios.deployment_target = '12.0'
  s.osx.deployment_target = '10.13'
  s.watchos.deployment_target = '7.0'
  s.tvos.deployment_target = '12.0'
  s.cocoapods_version = '>= 1.12.0'
  s.default_subspec = 'standard'

  s.subspec 'Core' do |ss|
    ss.source_files = 'src/fmdb/FM*.{h,m}'
    ss.exclude_files = 'src/fmdb.m'
    ss.header_dir = 'fmdb'
    ss.resource_bundles = { 'FMDB_Privacy' => 'privacy/PrivacyInfo.xcprivacy' }
  end

  # use the built-in library version of sqlite3
  s.subspec 'standard' do |ss|
    ss.dependency 'FMDB/Core'
    ss.library = 'sqlite3'
  end

  # use the built-in library version of sqlite3 with custom FTS tokenizer source files
  s.subspec 'FTS' do |ss|
    ss.dependency 'FMDB/standard'
    ss.source_files = 'src/extra/fts3/*.{h,m}'
  end

  # build the latest stable version of sqlite3
  s.subspec 'standalone' do |ss|
    ss.dependency 'FMDB/Core'
    ss.dependency 'sqlite3', '~> 3.46'
    ss.xcconfig = { 'OTHER_CFLAGS' => '$(inherited) -DFMDB_SQLITE_STANDALONE' }
  end

  # build with FTS support and custom FTS tokenizer source files
  s.subspec 'standalone-fts' do |ss|
    ss.dependency 'FMDB/Core'
    ss.dependency 'sqlite3/fts', '~> 3.46'
    ss.xcconfig = { 'OTHER_CFLAGS' => '$(inherited) -DFMDB_SQLITE_STANDALONE' }
    ss.source_files = 'src/extra/fts3/*.{h,m}'
  end

  # use SQLCipher and enable -DSQLITE_HAS_CODEC flag
  s.subspec 'SQLCipher' do |ss|
    ss.dependency 'FMDB/Core'
    ss.dependency 'SQLCipher', '~> 4.6'
    ss.xcconfig = { 'OTHER_CFLAGS' => '$(inherited) -DSQLITE_HAS_CODEC -DHAVE_USLEEP=1 -DSQLCIPHER_CRYPTO', 'HEADER_SEARCH_PATHS' => 'SQLCipher' }
  end
end


================================================
FILE: LICENSE.txt
================================================
If you are using FMDB in your project, I'd love to hear about it.  Let Gus know
by sending an email to gus@flyingmeat.com.

And if you happen to come across either Gus Mueller or Rob Ryan in a bar, you
might consider purchasing a drink of their choosing if FMDB has been useful to
you.

Finally, and shortly, this is the MIT License.

Copyright (c) 2008-2014 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.

================================================
FILE: Package.swift
================================================
// swift-tools-version:6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let applePlatforms: [PackageDescription.Platform] = [.iOS, .macOS, .watchOS, .tvOS, .visionOS]

let sqlcipherTraitTargetCondition: TargetDependencyCondition? = .when(platforms: applePlatforms, traits: ["SQLCipher"])

let sqlcipherTraitBuildSettingCondition: BuildSettingCondition? = .when(platforms: applePlatforms, traits: ["SQLCipher"])

let package = Package(
    name: "FMDB",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(name: "FMDB", targets: ["FMDB"]),
    ],
    traits: [
        .trait(name: "SQLCipher", description: "Enables SQLCipher encryption when a passphrase is supplied to FMDatabase")
    ],
    dependencies: [
        .package(url:"https://github.com/sqlcipher/SQLCipher.swift", from: "4.11.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "FMDB",
            dependencies: [
                .product(name: "SQLCipher", package: "SQLCipher.swift", condition: sqlcipherTraitTargetCondition),
            ],
            path: "src/fmdb",
            resources: [.process("../../privacy/PrivacyInfo.xcprivacy")],
            publicHeadersPath: ".",
            cSettings: [
                .define("SQLITE_HAS_CODEC", to: nil, sqlcipherTraitBuildSettingCondition),
                .define("SQLCIPHER_CRYPTO", to: nil, sqlcipherTraitBuildSettingCondition)
            ]
        )
    ]
)


================================================
FILE: README.markdown
================================================
# FMDB v2.7
<!--[![Platform](https://img.shields.io/cocoapods/p/FMDB.svg?style=flat)](http://cocoadocs.org/docsets/Alamofire)-->
[![CocoaPods Compatible](https://img.shields.io/cocoapods/v/FMDB.svg)](https://img.shields.io/cocoapods/v/FMDB.svg)
[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)

This is an Objective-C wrapper around [SQLite](https://sqlite.org/).

## The FMDB Mailing List:
https://groups.google.com/group/fmdb

## Read the SQLite FAQ:
https://www.sqlite.org/faq.html

Since FMDB is built on top of SQLite, you're going to want to read this page top to bottom at least once.  And while you're there, make sure to bookmark the SQLite Documentation page: https://www.sqlite.org/docs.html

## Contributing
Do you have an awesome idea that deserves to be in FMDB?  You might consider pinging ccgus first to make sure he hasn't already ruled it out for some reason.  Otherwise pull requests are great, and make sure you stick to the local coding conventions.  However, please be patient and if you haven't heard anything from ccgus for a week or more, you might want to send a note asking what's up.

## Installing

### CocoaPods

FMDB can be installed using [CocoaPods](https://cocoapods.org/).

If you haven't done so already, you might want to initialize the project, to have it produce a `Podfile` template for you:

```
$ pod init
```

Then, edit the `Podfile`, adding `FMDB`:

```ruby
# Uncomment the next line to define a global platform for your project
# platform :ios, '12.0'

target 'MyApp' do
    # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
    use_frameworks!

    # Pods for MyApp2

    pod 'FMDB'
    # pod 'FMDB/FTS'   # FMDB with FTS
    # pod 'FMDB/standalone'   # FMDB with latest SQLite amalgamation source
    # pod 'FMDB/standalone/FTS'   # FMDB with latest SQLite amalgamation source and FTS
    # pod 'FMDB/SQLCipher'   # FMDB with SQLCipher
end
```

Then install the pods:

```
$ pod install
```

Then open the `.xcworkspace` rather than the `.xcodeproj`.

For more information on Cocoapods visit https://cocoapods.org.

**If using FMDB with [SQLCipher](https://www.zetetic.net/sqlcipher/) you must use the FMDB/SQLCipher subspec. The FMDB/SQLCipher subspec declares SQLCipher as a dependency, allowing FMDB to be compiled with the `-DSQLITE_HAS_CODEC` flag.**

### Carthage

Once you make sure you have [the latest version of Carthage](https://github.com/Carthage/Carthage/releases), you can open up a command line terminal, navigate to your project's main directory, and then do the following commands:

```
$ echo ' github "ccgus/fmdb" ' > ./Cartfile
$ carthage update
```

You can then configure your project as outlined in Carthage's [Getting Started](https://github.com/Carthage/Carthage#getting-started) (i.e. for iOS, adding the framework to the "Link Binary with Libraries" in your target and adding the `copy-frameworks` script; in macOS, adding the framework to the list of "Embedded Binaries").

### Swift Package Manager

Declare FMDB as a package dependency.
```swift
.package(
    name: "FMDB", 
    url: "https://github.com/ccgus/fmdb", 
    .upToNextMinor(from: "2.7.12")),
```

Use FMDB in target dependencies
```swift
.product(name: "FMDB", package: "FMDB")
```

#### Swift Package Manager with SQLCipher Encryption

If you want to use [SQLCipher](https://www.zetetic.net/sqlcipher/) with FMDB Swift Package you can specify the `SQLCipher` trait when consuming FMDB Swift Package.

```swift
depedencies: [
  .package(url: "https://github.com/ccgus/fmdb", from: "2.7.12", traits: ["SQLCipher"])
]
```

As of Xcode 16.4 (16F6), there's no direct way in the Xcode UI to select trait variations so you'll need to use a local wrapper package to pull in the FMDB dependency with the `SQLCipher` trait enabled:

```swift
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "AppDependencies",
    platforms: [
        .macOS(.v10_14),
        .iOS(.v13),
        .macCatalyst(.v13),
        .watchOS(.v8),
        .tvOS(.v15),
        .visionOS(.v1)
    ],
    products: [
        .library(
            name: "AppDependencies",
            targets: ["AppDependencies"]),
    ],
    dependencies: [
        .package(
            url: "https://github.com/ccgus/fmdb",
            from: "2.7.12",
            traits: ["SQLCipher"])
    ],
    targets: [
        .target(
            name: "AppDependencies",
            dependencies: [
                .product(
                    name: "FMDB",
                    package: "FMDB")
            ]
        )
    ]
)
```

Within Xcode add your local `AppDependencies` wrapper package as a package dependency and FMDB with SQLCipher functionality will be accessible.

Using the `SQLCipher` trait will cause FMDB to include a dependency on SQLCipher.swift and enable `FMDatabase+SQLCipher` methods to set the database key and encrypt a new database:

```swift
import FMDB

let db = FMDatabase(path: NSTemporaryDirectory().appending("tmp.db"))
guard db.open() else {
    print("Unable to open datbase")
    return
}
db.setKey("sup3rs3cr3t")
// perform database operations to read from/write to the encrypted database
db.close()
```

To encrypt an existing database:

```swift
// path to unencrypted db
let plaintextDb = FMDatabase(path: NSTemporaryDirectory().appending("unencrypted.db"))
guard plaintextDb.open() else {
    print("Unable to open database")
    return
}
// path to export encrypted db to (non-existent prior to export)
let encryptedDbPath = NSTemporaryDirectory().appending("encrypted.db")
// attach encrypted db using desired key
try plaintextDb.executeUpdate("ATTACH DATABASE ? AS encrypted KEY ?", values: [encryptedDbPath, "encryptedPass"])
// sqlcipher_export providing the alias of the attached encrypted db
plaintextDb.executeStatements("SELECT sqlcipher_export('encrypted');")
// detach the encrypted db
plaintextDb.executeStatements("DETACH DATABASE encrypted;")
```

## FMDB Class Reference:
https://ccgus.github.io/fmdb/html/index.html

## Automatic Reference Counting (ARC) or Manual Memory Management?
You can use either style in your Cocoa project.  FMDB will figure out which you are using at compile time and do the right thing.

## What's New in FMDB 2.7

FMDB 2.7 attempts to support a more natural interface. This represents a fairly significant change for Swift developers (audited for nullability; shifted to properties in external interfaces where possible rather than methods; etc.). For Objective-C developers, this should be a fairly seamless transition (unless you were using the ivars that were previously exposed in the public interface, which you shouldn't have been doing, anyway!). 

### Nullability and Swift Optionals

FMDB 2.7 is largely the same as prior versions, but has been audited for nullability. For Objective-C users, this simply means that if you perform a static analysis of your FMDB-based project, you may receive more meaningful warnings as you review your project, but there are likely to be few, if any, changes necessary in your code.

For Swift users, this nullability audit results in changes that are not entirely backward compatible with FMDB 2.6, but is a little more Swifty. Before FMDB was audited for nullability, Swift was forced to defensively assume that variables were optional, but the library now more accurately knows which properties and method parameters are optional, and which are not.

This means, though, that Swift code written for FMDB 2.7 may require changes. For example, consider the following Swift 3/Swift 4 code for FMDB 2.6:

```swift
queue.inTransaction { db, rollback in
    do {
        guard let db == db else {
            // handle error here
            return
        }

        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [1])
        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [2])
    } catch {
        rollback?.pointee = true
    }
}
```

Because FMDB 2.6 was not audited for nullability, Swift inferred that `db` and `rollback` were optionals. But, now, in FMDB 2.7, Swift now knows that, for example, neither `db` nor `rollback` above can be `nil`, so they are no longer optionals. Thus it becomes:

```swift
queue.inTransaction { db, rollback in
    do {
        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [1])
        try db.executeUpdate("INSERT INTO foo (bar) VALUES (?)", values: [2])
    } catch {
        rollback.pointee = true
    }
}
```

### Custom Functions

In the past, when writing custom functions, you would have to generally include your own `@autoreleasepool` block to avoid problems when writing functions that scanned through a large table. Now, FMDB will automatically wrap it in an autorelease pool, so you don't have to.

Also, in the past, when retrieving the values passed to the function, you had to drop down to the SQLite C API and include your own `sqlite3_value_XXX` calls. There are now `FMDatabase` methods, `valueInt`, `valueString`, etc., so you can stay within Swift and/or Objective-C, without needing to call the C functions yourself. Likewise, when specifying the return values, you no longer need to call `sqlite3_result_XXX` C API, but rather you can use `FMDatabase` methods, `resultInt`, `resultString`, etc. There is a new `enum` for `valueType` called `SqliteValueType`, which can be used for checking the type of parameter passed to the custom function.

Thus, you can do something like (as of Swift 3):

```swift
db.makeFunctionNamed("RemoveDiacritics", arguments: 1) { context, argc, argv in
    guard db.valueType(argv[0]) == .text || db.valueType(argv[0]) == .null else {
        db.resultError("Expected string parameter", context: context)
        return
    }

    if let string = db.valueString(argv[0])?.folding(options: .diacriticInsensitive, locale: nil) {
        db.resultString(string, context: context)
    } else {
        db.resultNull(context: context)
    }
}
```

And you can then use that function in your SQL (in this case, matching both "Jose" and "José"):

```sql
SELECT * FROM employees WHERE RemoveDiacritics(first_name) LIKE 'jose'
```

Note, the method `makeFunctionNamed:maximumArguments:withBlock:` has been renamed to `makeFunctionNamed:arguments:block:`, to more accurately reflect the functional intent of the second parameter.

### API Changes

In addition to the `makeFunctionNamed` noted above, there are a few other API changes. Specifically, 

 - To become consistent with the rest of the API, the methods `objectForColumnName` and `UTF8StringForColumnName` have been renamed to `objectForColumn` and `UTF8StringForColumn`.

 - Note, the `objectForColumn` (and the associted subscript operator) now returns `nil` if an invalid column name/index is passed to it. It used to return `NSNull`.

 - To avoid confusion with `FMDatabaseQueue` method `inTransaction`, which performs transactions, the `FMDatabase` method to determine whether you are in a transaction or not, `inTransaction`, has been replaced with a read-only property, `isInTransaction`. 

 - Several functions have been converted to properties, namely, `databasePath`, `maxBusyRetryTimeInterval`, `shouldCacheStatements`, `sqliteHandle`, `hasOpenResultSets`, `lastInsertRowId`, `changes`, `goodConnection`, `columnCount`, `resultDictionary`, `applicationID`, `applicationIDString`, `userVersion`, `countOfCheckedInDatabases`, `countOfCheckedOutDatabases`, and `countOfOpenDatabases`. For Objective-C users, this has little material impact, but for Swift users, it results in a slightly more natural interface. Note: For Objective-C developers, previously versions of FMDB exposed many ivars (but we hope you weren't using them directly, anyway!), but the implmentation details for these are no longer exposed.

### URL Methods

In keeping with Apple's shift from paths to URLs, there are now `NSURL` renditions of the various `init` methods, previously only accepting paths. 

## Usage
There are three main classes in FMDB:

1. `FMDatabase` - Represents a single SQLite database.  Used for executing SQL statements.
2. `FMResultSet` - Represents the results of executing a query on an `FMDatabase`.
3. `FMDatabaseQueue` - If you're wanting to perform queries and updates on multiple threads, you'll want to use this class.  It's described in the "Thread Safety" section below.

### Database Creation
An `FMDatabase` is created with a path to a SQLite database file.  This path can be one of these three:

1. A file system path.  The file does not have to exist on disk.  If it does not exist, it is created for you.
2. An empty string (`@""`).  An empty database is created at a temporary location.  This database is deleted when the `FMDatabase` connection is closed.
3. `NULL`.  An in-memory database is created.  This database will be destroyed when the `FMDatabase` connection is closed.

(For more information on temporary and in-memory databases, read the sqlite documentation on the subject: https://www.sqlite.org/inmemorydb.html)

```objc
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
FMDatabase *db = [FMDatabase databaseWithPath:path];
```

### Opening

Before you can interact with the database, it must be opened.  Opening fails if there are insufficient resources or permissions to open and/or create the database.

```objc
if (![db open]) {
    // [db release];   // uncomment this line in manual referencing code; in ARC, this is not necessary/permitted
    db = nil;
    return;
}
```

### Executing Updates

Any sort of SQL statement which is not a `SELECT` statement qualifies as an update.  This includes `CREATE`, `UPDATE`, `INSERT`, `ALTER`, `COMMIT`, `BEGIN`, `DETACH`, `DELETE`, `DROP`, `END`, `EXPLAIN`, `VACUUM`, and `REPLACE` statements (plus many more).  Basically, if your SQL statement does not begin with `SELECT`, it is an update statement.

Executing updates returns a single value, a `BOOL`.  A return value of `YES` means the update was successfully executed, and a return value of `NO` means that some error was encountered.  You may invoke the `-lastErrorMessage` and `-lastErrorCode` methods to retrieve more information.

### Executing Queries

A `SELECT` statement is a query and is executed via one of the `-executeQuery...` methods.

Executing queries returns an `FMResultSet` object if successful, and `nil` upon failure.  You should use the `-lastErrorMessage` and `-lastErrorCode` methods to determine why a query failed.

In order to iterate through the results of your query, you use a `while()` loop.  You also need to "step" from one record to the other.  With FMDB, the easiest way to do that is like this:

```objc
FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"];
while ([s next]) {
    //retrieve values for each record
}
```

You must always invoke `-[FMResultSet next]` before attempting to access the values returned in a query, even if you're only expecting one:

```objc
FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"];
if ([s next]) {
    int totalCount = [s intForColumnIndex:0];
}
[s close];  // Call the -close method on the FMResultSet if you cannot confirm whether the result set is exhausted.
```

`FMResultSet` has many methods to retrieve data in an appropriate format:

- `intForColumn:`
- `longForColumn:`
- `longLongIntForColumn:`
- `boolForColumn:`
- `doubleForColumn:`
- `stringForColumn:`
- `dateForColumn:`
- `dataForColumn:`
- `dataNoCopyForColumn:`
- `UTF8StringForColumn:`
- `objectForColumn:`

Each of these methods also has a `{type}ForColumnIndex:` variant that is used to retrieve the data based on the position of the column in the results, as opposed to the column's name.

Typically, there's no need to `-close` an `FMResultSet` yourself, since that happens when either the result set is exhausted. However, if you only pull out a single request or any other number of requests which don't exhaust the result set, you will need to call the `-close` method on the `FMResultSet`.

### Closing

When you have finished executing queries and updates on the database, you should `-close` the `FMDatabase` connection so that SQLite will relinquish any resources it has acquired during the course of its operation.

```objc
[db close];
```

### Transactions

`FMDatabase` can begin and commit a transaction by invoking one of the appropriate methods or executing a begin/end transaction statement.

### Multiple Statements and Batch Stuff

You can use `FMDatabase`'s executeStatements:withResultBlock: to do multiple statements in a string:

```objc
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
                 "create table bulktest2 (id integer primary key autoincrement, y text);"
                 "create table bulktest3 (id integer primary key autoincrement, z text);"
                 "insert into bulktest1 (x) values ('XXX');"
                 "insert into bulktest2 (y) values ('YYY');"
                 "insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

sql = @"select count(*) as count from bulktest1;"
       "select count(*) as count from bulktest2;"
       "select count(*) as count from bulktest3;";

success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
    NSInteger count = [dictionary[@"count"] integerValue];
    XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
    return 0;
}];
```

### Data Sanitization

When providing a SQL statement to FMDB, you should not attempt to "sanitize" any values before insertion.  Instead, you should use the standard SQLite binding syntax:

```sql
INSERT INTO myTable VALUES (?, ?, ?, ?)
```

The `?` character is recognized by SQLite as a placeholder for a value to be inserted.  The execution methods all accept a variable number of arguments (or a representation of those arguments, such as an `NSArray`, `NSDictionary`, or a `va_list`), which are properly escaped for you.

And, to use that SQL with the `?` placeholders from Objective-C:

```objc
NSInteger identifier = 42;
NSString *name = @"Liam O'Flaherty (\"the famous Irish author\")";
NSDate *date = [NSDate date];
NSString *comment = nil;

BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", @(identifier), name, date, comment ?: [NSNull null]];
if (!success) {
    NSLog(@"error = %@", [db lastErrorMessage]);
}
```

> **Note:** Fundamental data types, like the `NSInteger` variable `identifier`, should be as a `NSNumber` objects, achieved by using the `@` syntax, shown above. Or you can use the `[NSNumber numberWithInt:identifier]` syntax, too.
>
> Likewise, SQL `NULL` values should be inserted as `[NSNull null]`. For example, in the case of `comment` which might be `nil` (and is in this example), you can use the `comment ?: [NSNull null]` syntax, which will insert the string if `comment` is not `nil`, but will insert `[NSNull null]` if it is `nil`.

In Swift, you would use `executeUpdate(values:)`, which not only is a concise Swift syntax, but also `throws` errors for proper error handling:

```swift
do {
    let identifier = 42
    let name = "Liam O'Flaherty (\"the famous Irish author\")"
    let date = Date()
    let comment: String? = nil

    try db.executeUpdate("INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", values: [identifier, name, date, comment ?? NSNull()])
} catch {
    print("error = \(error)")
}
```

> **Note:** In Swift, you don't have to wrap fundamental numeric types like you do in Objective-C. But if you are going to insert an optional string, you would probably use the `comment ?? NSNull()` syntax (i.e., if it is `nil`, use `NSNull`, otherwise use the string).

Alternatively, you may use named parameters syntax:

```sql
INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)
```

The parameters *must* start with a colon. SQLite itself supports other characters, but internally the dictionary keys are prefixed with a colon, do **not** include the colon in your dictionary keys.

```objc
NSDictionary *arguments = @{@"identifier": @(identifier), @"name": name, @"date": date, @"comment": comment ?: [NSNull null]};
BOOL success = [db executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
if (!success) {
    NSLog(@"error = %@", [db lastErrorMessage]);
}
```

The key point is that one should not use `NSString` method `stringWithFormat` to manually insert values into the SQL statement, itself. Nor should one Swift string interpolation to insert values into the SQL. Use `?` placeholders for values to be inserted into the database (or used in `WHERE` clauses in `SELECT` statements).

<h2 id="threads">Using FMDatabaseQueue and Thread Safety.</h2>

Using a single instance of `FMDatabase` from multiple threads at once is a bad idea.  It has always been OK to make a `FMDatabase` object *per thread*.  Just don't share a single instance across threads, and definitely not across multiple threads at the same time.  Bad things will eventually happen and you'll eventually get something to crash, or maybe get an exception, or maybe meteorites will fall out of the sky and hit your Mac Pro.  *This would suck*.

**So don't instantiate a single `FMDatabase` object and use it across multiple threads.**

Instead, use `FMDatabaseQueue`. Instantiate a single `FMDatabaseQueue` and use it across multiple threads. The `FMDatabaseQueue` object will synchronize and coordinate access across the multiple threads. Here's how to use it:

First, make your queue.

```objc
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
```

Then use it like so:


```objc
[queue inDatabase:^(FMDatabase *db) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    FMResultSet *rs = [db executeQuery:@"select * from foo"];
    while ([rs next]) {
        …
    }
}];
```

An easy way to wrap things up in a transaction can be done like this:

```objc
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];
    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

    if (whoopsSomethingWrongHappened) {
        *rollback = YES;
        return;
    }

    // etc ...
}];
```

The Swift equivalent would be:

```swift
queue.inTransaction { db, rollback in
    do {
        try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [1])
        try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [2])
        try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [3])

        if whoopsSomethingWrongHappened {
            rollback.pointee = true
            return
        }

        // etc ...
    } catch {
        rollback.pointee = true
        print(error)
    }
}
```

(Note, as of Swift 3, use `pointee`. But in Swift 2.3, use `memory` rather than `pointee`.)

`FMDatabaseQueue` will run the blocks on a serialized queue (hence the name of the class).  So if you call `FMDatabaseQueue`'s methods from multiple threads at the same time, they will be executed in the order they are received.  This way queries and updates won't step on each other's toes, and every one is happy.

**Note:** The calls to `FMDatabaseQueue`'s methods are blocking.  So even though you are passing along blocks, they will **not** be run on another thread.

## Making custom sqlite functions, based on blocks.

You can do this!  For an example, look for `-makeFunctionNamed:` in main.m

## Swift

You can use FMDB in Swift projects too.

To do this, you must:

1. Copy the relevant `.m` and `.h` files from the FMDB `src` folder into your project.

 You can copy all of them (which is easiest), or only the ones you need. Likely you will need [`FMDatabase`](https://ccgus.github.io/fmdb/html/Classes/FMDatabase.html) and [`FMResultSet`](https://ccgus.github.io/fmdb/html/Classes/FMResultSet.html) at a minimum. [`FMDatabaseAdditions`](https://ccgus.github.io/fmdb/html/Categories/FMDatabase+FMDatabaseAdditions.html) provides some very useful convenience methods, so you will likely want that, too. If you are doing multithreaded access to a database, [`FMDatabaseQueue`](https://ccgus.github.io/fmdb/html/Classes/FMDatabaseQueue.html) is quite useful, too. If you choose to not copy all of the files from the `src` directory, though, you may want to update `FMDB.h` to only reference the files that you included in your project.

 Note, if you're copying all of the files from the `src` folder into to your project (which is recommended), you may want to drag the individual files into your project, not the folder, itself, because if you drag the folder, you won't be prompted to add the bridging header (see next point).

2. If prompted to create a "bridging header", you should do so. If not prompted and if you don't already have a bridging header, add one.

 For more information on bridging headers, see [Swift and Objective-C in the Same Project](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_76).

3. In your bridging header, add a line that says:
    ```objc
    #import "FMDB.h"
    ```

4. Use the variations of `executeQuery` and `executeUpdate` with the `sql` and `values` parameters with `try` pattern, as shown below. These renditions of `executeQuery` and `executeUpdate` both `throw` errors in true Swift fashion.

If you do the above, you can then write Swift code that uses `FMDatabase`. For example, as of Swift 3:

```swift
let fileURL = try! FileManager.default
    .url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    .appendingPathComponent("test.sqlite")

let database = FMDatabase(url: fileURL)

guard database.open() else {
    print("Unable to open database")
    return
}

do {
    try database.executeUpdate("create table test(x text, y text, z text)", values: nil)
    try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["a", "b", "c"])
    try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["e", "f", "g"])

    let rs = try database.executeQuery("select x, y, z from test", values: nil)
    while rs.next() {
        if let x = rs.string(forColumn: "x"), let y = rs.string(forColumn: "y"), let z = rs.string(forColumn: "z") {
            print("x = \(x); y = \(y); z = \(z)")
        }
    }
} catch {
    print("failed: \(error.localizedDescription)")
}

database.close()
```

## History

The history and changes are availbe on its [GitHub page](https://github.com/ccgus/fmdb) and are summarized in the "CHANGES_AND_TODO_LIST.txt" file.

## Contributors

The contributors to FMDB are contained in the "Contributors.txt" file.

## Additional projects using FMDB, which might be interesting to the discerning developer.

 * FMDBMigrationManager, A SQLite schema migration management system for FMDB: https://github.com/layerhq/FMDBMigrationManager
 * FCModel, An alternative to Core Data for people who like having direct SQL access: https://github.com/marcoarment/FCModel

## Quick notes on FMDB's coding style

Spaces, not tabs.  Square brackets, not dot notation.  Look at what FMDB already does with curly brackets and such, and stick to that style.

## Reporting bugs

Reduce your bug down to the smallest amount of code possible.  You want to make it super easy for the developers to see and reproduce your bug.  If it helps, pretend that the person who can fix your bug is active on shipping 3 major products, works on a handful of open source projects, has a newborn baby, and is generally very very busy.

And we've even added a template function to main.m (FMDBReportABugFunction) in the FMDB distribution to help you out:

* Open up fmdb project in Xcode.
* Open up main.m and modify the FMDBReportABugFunction to reproduce your bug.
    * Setup your table(s) in the code.
    * Make your query or update(s).
    * Add some assertions which demonstrate the bug.

Then you can bring it up on the FMDB mailing list by showing your nice and compact FMDBReportABugFunction, or you can report the bug via the github FMDB bug reporter.

**Optional:**

Figure out where the bug is, fix it, and send a patch in or bring that up on the mailing list.  Make sure all the other tests run after your modifications.

## Support

The support channels for FMDB are the mailing list (see above), filing a bug here, or maybe on Stack Overflow.  So that is to say, support is provided by the community and on a voluntary basis.

FMDB development is overseen by Gus Mueller of Flying Meat.  If FMDB been helpful to you, consider purchasing an app from FM or telling all your friends about it.

## License

The license for FMDB is contained in the "License.txt" file.

If you happen to come across either Gus Mueller or Rob Ryan in a bar, you might consider purchasing a drink of their choosing if FMDB has been useful to you.

(The drink is for them of course, shame on you for trying to keep it.)


================================================
FILE: Tests/Base.lproj/InfoPlist.strings
================================================
/* Localized versions of Info.plist keys */



================================================
FILE: Tests/FMDBTempDBTests.h
================================================
//
//  FMDBTempDBTests.h
//  fmdb
//
//  Created by Graham Dennis on 24/11/2013.
//
//

#import <XCTest/XCTest.h>
#import "FMDatabase.h"

@protocol FMDBTempDBTests <NSObject>

@optional
+ (void)populateDatabase:(FMDatabase *)database;

@end

@interface FMDBTempDBTests : XCTestCase <FMDBTempDBTests>

@property FMDatabase *db;
@property (readonly) NSString *databasePath;

@end


================================================
FILE: Tests/FMDBTempDBTests.m
================================================
//
//  FMDBTempDBTests.m
//  fmdb
//
//  Created by Graham Dennis on 24/11/2013.
//
//

#import "FMDBTempDBTests.h"

static NSString *const testDatabasePath = @"/private/tmp/tmp.db";
static NSString *const populatedDatabasePath = @"/private/tmp/tmp-populated.db";

@implementation FMDBTempDBTests

+ (void)setUp
{
    [super setUp];
    
    // Delete old populated database
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager removeItemAtPath:populatedDatabasePath error:NULL];
    
    if ([self respondsToSelector:@selector(populateDatabase:)]) {
        FMDatabase *db = [FMDatabase databaseWithPath:populatedDatabasePath];
        
        [db open];
        [self populateDatabase:db];
        [db close];
    }
}

- (void)setUp
{
    [super setUp];
    
    // Delete the old database
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager removeItemAtPath:testDatabasePath error:NULL];
    
    if ([[self class] respondsToSelector:@selector(populateDatabase:)]) {
        [fileManager copyItemAtPath:populatedDatabasePath toPath:testDatabasePath error:NULL];
    }
    
    self.db = [FMDatabase databaseWithPath:testDatabasePath];
    
    XCTAssertTrue([self.db open], @"Wasn't able to open database");
    [self.db setShouldCacheStatements:YES];
}

- (void)tearDown
{
    [super tearDown];
    
    [self.db close];
}

- (NSString *)databasePath
{
    return testDatabasePath;
}

@end


================================================
FILE: Tests/FMDatabaseAdditionsTests.m
================================================
//
//  FMDatabaseAdditionsTests.m
//  fmdb
//
//  Created by Graham Dennis on 24/11/2013.
//
//

#import <XCTest/XCTest.h>
#import "FMDatabaseAdditions.h"

#if FMDB_SQLITE_STANDALONE
#import <sqlite3/sqlite3.h>
#elif SQLCIPHER_CRYPTO
#import <SQLCipher/sqlite3.h>
#else
#import <sqlite3.h>
#endif

@interface FMDatabaseAdditionsTests : FMDBTempDBTests

@end

@implementation FMDatabaseAdditionsTests

- (void)setUp
{
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown
{
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testFunkyTableNames
{
    [self.db executeUpdate:@"create table '234 fds' (foo text)"];
    XCTAssertFalse([self.db hadError], @"table creation should have succeeded");
    FMResultSet *rs = [self.db getTableSchema:@"234 fds"];
    XCTAssertTrue([rs next], @"Schema should have succeded");
    [rs close];
    XCTAssertFalse([self.db hadError], @"There shouldn't be any errors");
}

- (void)testBoolForQuery
{
    BOOL result = [self.db boolForQuery:@"SELECT ? not null", @""];
    XCTAssertTrue(result, @"Empty strings should be considered true");
    
    result = [self.db boolForQuery:@"SELECT ? not null", [NSMutableData data]];
    XCTAssertTrue(result, @"Empty mutable data should be considered true");
    
    result = [self.db boolForQuery:@"SELECT ? not null", [NSData data]];
    XCTAssertTrue(result, @"Empty data should be considered true");
}


- (void)testIntForQuery
{
    [self.db executeUpdate:@"create table t1 (a integer)"];
    [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
    
    XCTAssertEqual([self.db changes], 1, @"There should only be one change");
    
    int ia = [self.db intForQuery:@"select a from t1 where a = ?", [NSNumber numberWithInt:5]];
    XCTAssertEqual(ia, 5, @"foo");
}

- (void)testDateForQuery
{
    NSDate *date = [NSDate date];
    [self.db executeUpdate:@"create table datetest (a double, b double, c double)"];
    [self.db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date];

    NSDate *foo = [self.db dateForQuery:@"select b from datetest where c = 0"];
    XCTAssertEqualWithAccuracy([foo timeIntervalSinceDate:date], 0.0, 1.0, @"Dates should be the same to within a second");
}

- (void)testValidate {
    NSError *error;
    XCTAssert([self.db validateSQL:@"create table datetest (a double, b double, c double)" error:&error]);
    XCTAssertNil(error, @"There should be no error object");
}

- (void)testFailValidate {
    NSError *error;
    XCTAssertFalse([self.db validateSQL:@"blah blah blah" error:&error]);
    XCTAssert(error, @"There should be no error object");
}

- (void)testTableExists {
    XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]);

    XCTAssertTrue([self.db tableExists:@"t4"]);
    XCTAssertFalse([self.db tableExists:@"thisdoesntexist"]);
    
    FMResultSet *rs = [self.db getSchema];
    while ([rs next]) {
        XCTAssertEqualObjects([rs stringForColumn:@"type"], @"table");
    }

}

- (void)testColumnExists {
    [self.db executeUpdate:@"create table nulltest (a text, b text)"];
    
    XCTAssertTrue([self.db columnExists:@"a" inTableWithName:@"nulltest"]);
    XCTAssertTrue([self.db columnExists:@"b" inTableWithName:@"nulltest"]);
    XCTAssertFalse([self.db columnExists:@"c" inTableWithName:@"nulltest"]);
}

- (void)testUserVersion {
    [[self db] setUserVersion:12];
    
    XCTAssertTrue([[self db] userVersion] == 12);
}

- (void)testApplicationID {
#if SQLITE_VERSION_NUMBER >= 3007017
    uint32_t appID = NSHFSTypeCodeFromFileType(NSFileTypeForHFSTypeCode('fmdb'));
    
    [self.db setApplicationID:appID];
    
    uint32_t rAppID = [self.db applicationID];
    
    XCTAssertEqual(rAppID, appID);
    
    [self.db setApplicationIDString:@"acrn"];
    
    NSString *s = [self.db applicationIDString];
    
    XCTAssertEqualObjects(s, @"acrn");
#else
    NSString *errorMessage = NSLocalizedStringFromTable(@"Application ID functions require SQLite 3.7.17", @"FMDB", nil);
    XCTFail("%@", errorMessage);
    if (self.db.logsErrors) NSLog(@"%@", errorMessage);
#endif
}

@end


================================================
FILE: Tests/FMDatabaseFTS3Tests.m
================================================
//
//  FMDatabaseFTS3Tests.m
//  fmdb
//
//  Created by Seaview Software on 8/26/14.
//
//

#import "FMDBTempDBTests.h"
#import "FMDatabase+FTS3.h"
#import "FMTokenizers.h"

@interface FMDatabaseFTS3Tests : FMDBTempDBTests

@end

/**
 This tokenizer extends the simple tokenizer to remove 's' from the end of each token if present.
 Used for testing of the tokenization system.
 */
@interface FMDepluralizerTokenizer : NSObject <FMTokenizerDelegate>
+ (instancetype)tokenizerWithBaseTokenizer:(id<FMTokenizerDelegate>)tokenizer;
@property (nonatomic, strong) id<FMTokenizerDelegate> m_baseTokenizer;
@end

static id<FMTokenizerDelegate> g_simpleTok = nil;
static id<FMTokenizerDelegate> g_depluralizeTok = nil;

@implementation FMDatabaseFTS3Tests

+ (void)populateDatabase:(FMDatabase *)db
{
    [db executeUpdate:@"CREATE VIRTUAL TABLE mail USING fts3(subject, body)"];
    
    [db executeUpdate:@"INSERT INTO mail VALUES('hello world', 'This message is a hello world message.')"];
    [db executeUpdate:@"INSERT INTO mail VALUES('urgent: serious', 'This mail is seen as a more serious mail')"];

    // Create a tokenizer instance that will not be de-allocated when the method finishes.
    g_simpleTok = [[FMSimpleTokenizer alloc] initWithLocale:NULL];
    [FMDatabase registerTokenizer:g_simpleTok withKey:@"testTok"];
    
    g_depluralizeTok = [FMDepluralizerTokenizer tokenizerWithBaseTokenizer:g_simpleTok];
    [FMDatabase registerTokenizer:g_depluralizeTok withKey:@"depluralize"];
}

- (void)setUp
{
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown
{
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testOffsets
{
    FMResultSet *results = [self.db executeQuery:@"SELECT offsets(mail) FROM mail WHERE mail MATCH 'world'"];
    
    if ([results next]) {
        FMTextOffsets *offsets = [results offsetsForColumnIndex:0];
        
        [offsets enumerateWithBlock:^(NSInteger columnNumber, NSInteger termNumber, NSRange matchRange) {
            if (columnNumber == 0) {
                XCTAssertEqual(termNumber, 0L);
                XCTAssertEqual(matchRange.location, 6UL);
                XCTAssertEqual(matchRange.length, 5UL);
            } else if (columnNumber == 1) {
                XCTAssertEqual(termNumber, 0L);
                XCTAssertEqual(matchRange.location, 24UL);
                XCTAssertEqual(matchRange.length, 5UL);
            }
        }];
    }
}

- (void)testTokenizer
{
    [self.db installTokenizerModule];
    
    BOOL ok = [self.db executeUpdate:@"CREATE VIRTUAL TABLE simple USING fts3(tokenize=fmdb testTok)"];
    XCTAssertTrue(ok, @"Failed to create virtual table: %@", [self.db lastErrorMessage]);

    // The FMSimpleTokenizer handles non-ASCII characters well, since it's based on CFStringTokenizer.
    NSString *text = @"I like the band Queensrÿche. They are really great musicians.";
    
    ok = [self.db executeUpdate:@"INSERT INTO simple VALUES(?)", text];
    XCTAssertTrue(ok, @"Failed to insert data: %@", [self.db lastErrorMessage]);
    
    FMResultSet *results = [self.db executeQuery:@"SELECT * FROM simple WHERE simple MATCH ?", @"Queensrÿche"];
    XCTAssertTrue([results next], @"Failed to find result");
    
    ok = [self.db executeUpdate:@"CREATE VIRTUAL TABLE depluralize_t USING fts3(tokenize=fmdb depluralize)"];
    XCTAssertTrue(ok, @"Failed to create virtual table with depluralize tokenizer: %@", [self.db lastErrorMessage]);

    ok = [self.db executeUpdate:@"INSERT INTO depluralize_t VALUES(?)", text];
    XCTAssertTrue(ok, @"Failed to insert data: %@", [self.db lastErrorMessage]);

    //If depluralization is working, searching for 'bands' should still provide a match as 'band' is in the text
    results = [self.db executeQuery:@"SELECT * FROM depluralize_t WHERE depluralize_t MATCH ?", @"bands"];
    XCTAssertTrue([results next], @"Failed to find result");
    
    //Demonstrate that depluralization mattered; we should NOT find any results when searching the simple table as it does not use that tokenizer
    results = [self.db executeQuery:@"SELECT * FROM simple WHERE simple MATCH ?", @"bands"];
    XCTAssertFalse([results next], @"Found a result where none should be found");
}

@end



#pragma mark -

@implementation FMDepluralizerTokenizer

+ (instancetype)tokenizerWithBaseTokenizer:(id<FMTokenizerDelegate>)tokenizer
{
    return [[self alloc] initWithBaseTokenizer:tokenizer];
}

- (instancetype)initWithBaseTokenizer:(id<FMTokenizerDelegate>)tokenizer
{
    NSParameterAssert(tokenizer);
    
    if ((self = [super init])) {
        self.m_baseTokenizer = tokenizer;
    }
    return self;
}

- (void)openTokenizerCursor:(FMTokenizerCursor *)cursor
{
    [self.m_baseTokenizer openTokenizerCursor:cursor];
}

- (BOOL)nextTokenForCursor:(FMTokenizerCursor *)cursor
{
    BOOL done = [self.m_baseTokenizer nextTokenForCursor:cursor];

    if (!done) {
        NSMutableString *tokenString = (__bridge NSMutableString *)(cursor->tokenString);
        if ([tokenString hasSuffix:@"s"])
            [tokenString deleteCharactersInRange:NSMakeRange(tokenString.length-1, 1)];
    }

    return done;
}

- (void)closeTokenizerCursor:(FMTokenizerCursor *)cursor
{
    [self.m_baseTokenizer closeTokenizerCursor:cursor];
}


@end


================================================
FILE: Tests/FMDatabaseFTS3WithModuleNameTests.m
================================================
//
//  FMDatabaseFTS3WithKeyTests.m
//  fmdb
//
//  Created by Stephan Heilner on 1/21/15.
//
//

#import "FMDBTempDBTests.h"
#import "FMDatabase+FTS3.h"
#import "FMTokenizers.h"

@interface FMDatabaseFTS3WithModuleNameTests : FMDBTempDBTests

@end

static id<FMTokenizerDelegate> g_testTok = nil;

@implementation FMDatabaseFTS3WithModuleNameTests

+ (void)populateDatabase:(FMDatabase *)db
{
    [db executeUpdate:@"CREATE VIRTUAL TABLE mail USING fts3(subject, body)"];
    
    [db executeUpdate:@"INSERT INTO mail VALUES('hello world', 'This message is a hello world message.')"];
    [db executeUpdate:@"INSERT INTO mail VALUES('urgent: serious', 'This mail is seen as a more serious mail')"];
    
    // Create a tokenizer instance that will not be de-allocated when the method finishes.
    g_testTok = [[FMSimpleTokenizer alloc] initWithLocale:NULL];
    [FMDatabase registerTokenizer:g_testTok];
}

- (void)setUp
{
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown
{
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testOffsets
{
    FMResultSet *results = [self.db executeQuery:@"SELECT offsets(mail) FROM mail WHERE mail MATCH 'world'"];
    
    if ([results next]) {
        FMTextOffsets *offsets = [results offsetsForColumnIndex:0];
        
        [offsets enumerateWithBlock:^(NSInteger columnNumber, NSInteger termNumber, NSRange matchRange) {
            if (columnNumber == 0) {
                XCTAssertEqual(termNumber, 0L);
                XCTAssertEqual(matchRange.location, 6UL);
                XCTAssertEqual(matchRange.length, 5UL);
            } else if (columnNumber == 1) {
                XCTAssertEqual(termNumber, 0L);
                XCTAssertEqual(matchRange.location, 24UL);
                XCTAssertEqual(matchRange.length, 5UL);
            }
        }];
    }
}

- (void)testTokenizer
{
    [self.db installTokenizerModuleWithName:@"TestModuleName"];
    
    BOOL ok = [self.db executeUpdate:@"CREATE VIRTUAL TABLE simple_fts USING fts3(tokenize=TestModuleName)"];
    XCTAssertTrue(ok, @"Failed to create virtual table: %@", [self.db lastErrorMessage]);
    
    // The FMSimpleTokenizer handles non-ASCII characters well, since it's based on CFStringTokenizer.
    NSString *text = @"I like the band Queensrÿche. They are really great.";
    
    ok = [self.db executeUpdate:@"INSERT INTO simple_fts VALUES(?)", text];
    XCTAssertTrue(ok, @"Failed to insert data: %@", [self.db lastErrorMessage]);
    
    FMResultSet *results = [self.db executeQuery:@"SELECT * FROM simple_fts WHERE simple_fts MATCH ?", @"Queensrÿche"];
    XCTAssertTrue([results next], @"Failed to find result");
}

@end


================================================
FILE: Tests/FMDatabasePoolTests.m
================================================
//
//  FMDatabasePoolTests.m
//  fmdb
//
//  Created by Graham Dennis on 24/11/2013.
//
//

#import <XCTest/XCTest.h>

#if FMDB_SQLITE_STANDALONE
#import <sqlite3/sqlite3.h>
#elif SQLCIPHER_CRYPTO
#import <SQLCipher/sqlite3.h>
#else
#import <sqlite3.h>
#endif

@interface FMDatabasePoolTests : FMDBTempDBTests

@property FMDatabasePool *pool;

@end

@implementation FMDatabasePoolTests

+ (void)populateDatabase:(FMDatabase *)db
{
    [db executeUpdate:@"create table easy (a text)"];
    [db executeUpdate:@"create table easy2 (a text)"];

    [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]];
    [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]];
    [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]];

    [db executeUpdate:@"create table likefoo (foo text)"];
    [db executeUpdate:@"insert into likefoo values ('hi')"];
    [db executeUpdate:@"insert into likefoo values ('hello')"];
    [db executeUpdate:@"insert into likefoo values ('not')"];
}

- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
    
    [self setPool:[FMDatabasePool databasePoolWithPath:self.databasePath]];
    
    [[self pool] setDelegate:self];
    
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testURLOpenNoURL {
    FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:nil];
    XCTAssert(pool, @"Database pool should be returned");
    pool = nil;
}

- (void)testURLOpen {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabasePool *pool = [FMDatabasePool databasePoolWithURL:fileURL];
    XCTAssert(pool, @"Database pool should be returned");
    pool = nil;
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}

- (void)testURLOpenInit {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:fileURL];
    XCTAssert(pool, @"Database pool should be returned");
    pool = nil;
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}

- (void)testURLOpenWithOptions {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabasePool *pool = [FMDatabasePool databasePoolWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
    [pool inDatabase:^(FMDatabase * _Nonnull db) {
        XCTAssertNil(db, @"The database should not have been created");
    }];
}

- (void)testURLOpenInitWithOptions {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
    [pool inDatabase:^(FMDatabase * _Nonnull db) {
        XCTAssertNil(db, @"The database should not have been created");
    }];
    
    pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE];
    [pool inDatabase:^(FMDatabase * _Nonnull db) {
        XCTAssert(db, @"The database should have been created");

        BOOL success = [db executeUpdate:@"CREATE TABLE foo (bar INT)"];
        XCTAssert(success, @"Create failed");
        success = [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", @42];
        XCTAssert(success, @"Insert failed");
    }];
    
    pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READONLY];
    [pool inDatabase:^(FMDatabase * _Nonnull db) {
        XCTAssert(db, @"Now database pool should open have been created");
        BOOL success = [db executeUpdate:@"CREATE TABLE baz (qux INT)"];
        XCTAssertFalse(success, @"But updates should fail on read only database");
    }];
    pool = nil;
    
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}

- (void)testURLOpenWithOptionsVfs {
    sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
    vfs.zName = "MyCustomVFS";
    XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));
    
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabasePool *pool = [[FMDatabasePool alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
    XCTAssert(pool, @"Database pool should not have been created");
    pool = nil;
    
    XCTAssertEqual(SQLITE_OK, sqlite3_vfs_unregister(&vfs));
}

- (void)testPoolIsInitiallyEmpty {
    XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"Pool should be empty on creation");
}

- (void)testDatabaseCreation {
    __block FMDatabase *db1;
    
    [self.pool inDatabase:^(FMDatabase *db) {
        
        XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1, @"Should only have one database at this point");
        
        db1 = db;
        
    }];
    
    [self.pool inDatabase:^(FMDatabase *db) {
        XCTAssertEqualObjects(db, db1, @"We should get the same database back because there was no need to create a new one");
        
        [self.pool inDatabase:^(FMDatabase *db2) {
            XCTAssertNotEqualObjects(db2, db, @"We should get a different database because the first was in use.");
        }];
        
    }];
    
    XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2);
    
    [self.pool releaseAllDatabases];

    XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"We should be back to zero databases again");
}

- (void)testCheckedInCheckoutOutCount
{
    [self.pool inDatabase:^(FMDatabase *aDb) {
        
        XCTAssertEqual([self.pool countOfCheckedInDatabases],   (NSUInteger)0);
        XCTAssertEqual([self.pool countOfCheckedOutDatabases],  (NSUInteger)1);
        
        XCTAssertTrue(([aDb executeUpdate:@"insert into easy (a) values (?)", @"hi"]));
        
        // just for fun.
        FMResultSet *rs = [aDb executeQuery:@"select * from easy"];
        XCTAssertNotNil(rs);
        XCTAssertTrue([rs next]);
        while ([rs next]) { ; } // whatevers.
        
        XCTAssertEqual([self.pool countOfOpenDatabases],        (NSUInteger)1);
        XCTAssertEqual([self.pool countOfCheckedInDatabases],   (NSUInteger)0);
        XCTAssertEqual([self.pool countOfCheckedOutDatabases],  (NSUInteger)1);
    }];
    
    XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1);
}

- (void)testMaximumDatabaseLimit
{
    [self.pool setMaximumNumberOfDatabasesToCreate:2];
    
    [self.pool inDatabase:^(FMDatabase *db) {
        [self.pool inDatabase:^(FMDatabase *db2) {
            [self.pool inDatabase:^(FMDatabase *db3) {
                XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2);
                XCTAssertNil(db3, @"The third database must be nil because we have a maximum of 2 databases in the pool");
            }];
            
        }];
    }];
}

- (void)testTransaction
{
    [self.pool inTransaction:^(FMDatabase *adb, BOOL *rollback) {
        [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]];
        [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]];
        [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]];
        
        XCTAssertEqual([self.pool countOfOpenDatabases],        (NSUInteger)1);
        XCTAssertEqual([self.pool countOfCheckedInDatabases],   (NSUInteger)0);
        XCTAssertEqual([self.pool countOfCheckedOutDatabases],  (NSUInteger)1);
    }];

    XCTAssertEqual([self.pool countOfOpenDatabases],        (NSUInteger)1);
    XCTAssertEqual([self.pool countOfCheckedInDatabases],   (NSUInteger)1);
    XCTAssertEqual([self.pool countOfCheckedOutDatabases],  (NSUInteger)0);
}

- (void)testSelect
{
    [self.pool inDatabase:^(FMDatabase *db) {
        FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1001]];
        XCTAssertNotNil(rs);
        XCTAssertTrue ([rs next]);
        XCTAssertFalse([rs next]);
    }];
}

- (void)testTransactionRollback
{
    [self.pool inDeferredTransaction:^(FMDatabase *adb, BOOL *rollback) {
        XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1004]]));
        XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1005]]));
        XCTAssertTrue([[adb executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should be in database");
        
        *rollback = YES;
    }];
    
    [self.pool inDatabase:^(FMDatabase *db) {
        XCTAssertFalse([[db executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should not be in database");
    }];

    XCTAssertEqual([self.pool countOfOpenDatabases],        (NSUInteger)1);
    XCTAssertEqual([self.pool countOfCheckedInDatabases],   (NSUInteger)1);
    XCTAssertEqual([self.pool countOfCheckedOutDatabases],  (NSUInteger)0);
}

- (void)testSavepoint
{
    NSError *err = [self.pool inSavePoint:^(FMDatabase *db, BOOL *rollback) {
        [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1006]];
    }];
    
    XCTAssertNil(err);
}

- (void)testNestedSavepointRollback
{
    NSError *err = [self.pool inSavePoint:^(FMDatabase *adb, BOOL *rollback) {
        XCTAssertFalse([adb hadError]);
        XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1009]]));
        
        [adb inSavePoint:^(BOOL *arollback) {
            XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1010]]));
            *arollback = YES;
        }];
    }];
    
    
    XCTAssertNil(err);
    
    [self.pool inDatabase:^(FMDatabase *db) {
        FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1009]];
        XCTAssertTrue ([rs next]);
        XCTAssertFalse([rs next]); // close it out.
        
        rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1010]];
        XCTAssertFalse([rs next]);
    }];
}

- (void)testLikeStringQuery
{
    [self.pool inDatabase:^(FMDatabase *db) {
        int count = 0;
        FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
        while ([rsl next]) {
            count++;
        }
        
        XCTAssertEqual(count, 2);
        
        count = 0;
        rsl = [db executeQuery:@"select * from likefoo where foo like ?", @"h%"];
        while ([rsl next]) {
            count++;
        }
        
        XCTAssertEqual(count, 2);
        
    }];
}

- (void)testStressTest
{
    size_t ops = 128;
    
    dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(ops, dqueue, ^(size_t nby) {
        
        // just mix things up a bit for demonstration purposes.
        if (nby % 2 == 1) {
            
            [NSThread sleepForTimeInterval:.001];
        }
        
        [self.pool inDatabase:^(FMDatabase *db) {
            FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
            XCTAssertNotNil(rsl);
            int i = 0;
            while ([rsl next]) {
                i++;
                if (nby % 3 == 1) {
                    [NSThread sleepForTimeInterval:.0005];
                }
            }
            XCTAssertEqual(i, 2);
        }];
    });
    
    XCTAssert([self.pool countOfOpenDatabases] < 64, @"There should be significantly less than 64 databases after that stress test");
}


- (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database {
    [database setMaxBusyRetryTimeInterval:10];
    // [database setCrashOnErrors:YES];
    return YES;
}

- (void)testReadWriteStressTest
{
    int ops = 16;
    
    dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(ops, dqueue, ^(size_t nby) {
        
        // just mix things up a bit for demonstration purposes.
        if (nby % 2 == 1) {
            [NSThread sleepForTimeInterval:.01];
            
            [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) {
                FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"];
                XCTAssertNotNil(rsl);
                while ([rsl next]) {
                    ;// whatever.
                }
                
            }];
            
        }
        
        if (nby % 3 == 1) {
            [NSThread sleepForTimeInterval:.01];
        }
        
        [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) {
            XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]);
            XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('2')"]);
            XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('3')"]);
        }];
    });
    
    [self.pool releaseAllDatabases];
    
    [self.pool inDatabase:^(FMDatabase *db) {
        XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]);
    }];
}

@end


================================================
FILE: Tests/FMDatabaseQueueTests.m
================================================
//
//  FMDatabaseQueueTests.m
//  fmdb
//
//  Created by Graham Dennis on 24/11/2013.
//
//

#import <XCTest/XCTest.h>
#import "FMDatabaseQueue.h"

#if FMDB_SQLITE_STANDALONE
#import <sqlite3/sqlite3.h>
#elif SQLCIPHER_CRYPTO
#import <SQLCipher/sqlite3.h>
#else
#import <sqlite3.h>
#endif

@interface FMDatabaseQueueTests : FMDBTempDBTests

@property FMDatabaseQueue *queue;

@end

@implementation FMDatabaseQueueTests

+ (void)populateDatabase:(FMDatabase *)db
{
    [db executeUpdate:@"create table easy (a text)"];
    
    [db executeUpdate:@"create table qfoo (foo text)"];
    [db executeUpdate:@"insert into qfoo values ('hi')"];
    [db executeUpdate:@"insert into qfoo values ('hello')"];
    [db executeUpdate:@"insert into qfoo values ('not')"];
}

- (void)setUp
{
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
    
    self.queue = [FMDatabaseQueue databaseQueueWithPath:self.databasePath];
}

- (void)tearDown
{
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testURLOpenNoPath {
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] init];
    XCTAssert(queue, @"Database queue should be returned");
    queue = nil;
}

- (void)testURLOpenNoURL {
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:nil];
    XCTAssert(queue, @"Database queue should be returned");
    queue = nil;
}

- (void)testInvalidURL {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
    
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
    XCTAssertNil(queue, @"Database queue should not be returned for invalid path");
    queue = nil;
}

- (void)testInvalidPath {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
    
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithPath:fileURL.path];
    XCTAssertNil(queue, @"Database queue should not be returned for invalid path");
    queue = nil;
}

- (void)testReopenFailure {
    NSFileManager *manager = [NSFileManager defaultManager];
    
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *folderURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    BOOL success = [manager createDirectoryAtURL:folderURL withIntermediateDirectories:true attributes:nil error:nil];
    NSAssert(success, @"Unable to create folder");
    
    NSURL *fileURL = [folderURL URLByAppendingPathComponent:@"test.sqlite"];
    
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
    XCTAssert(queue, @"Database queue was unable to be created");
    
    [queue close];
    
    success = [manager removeItemAtURL:fileURL error:nil];
    XCTAssert(success, @"Unable to remove database");
    
    success = [manager removeItemAtURL:folderURL error:nil];
    XCTAssert(success, @"Unable to remove folder");
    
    [queue inDatabase:^(FMDatabase *db) {
        XCTAssertNil(db, @"Should be `nil` or never have reached here because database couldn't be reopened");
    }];
    
    queue = nil;
}

- (void)testURLOpen {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithURL:fileURL];
    XCTAssert(queue, @"Database queue should be returned");
    queue = nil;
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}

- (void)testURLOpenInit {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL];
    XCTAssert(queue, @"Database queue should be returned");
    queue = nil;
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}

- (void)testURLOpenWithOptions {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
    XCTAssertNil(queue, @"Database queue should not have been created");
}

- (void)testURLOpenInitWithOptions {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE];
    XCTAssertNil(queue, @"Database queue should not have been created");

    queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE];
    XCTAssert(queue, @"Database queue should have been created");
    
    [queue inDatabase:^(FMDatabase * _Nonnull db) {
        BOOL success = [db executeUpdate:@"CREATE TABLE foo (bar INT)"];
        XCTAssert(success, @"Create failed");
        success = [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", @42];
        XCTAssert(success, @"Insert failed");
    }];
    queue = nil;

    queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READONLY];
    XCTAssert(queue, @"Now database queue should open have been created");
    [queue inDatabase:^(FMDatabase * _Nonnull db) {
        BOOL success = [db executeUpdate:@"CREATE TABLE baz (qux INT)"];
        XCTAssertFalse(success, @"But updates should fail on read only database");
    }];    
    queue = nil;
    
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}

- (void)testURLOpenWithOptionsVfs {
    sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
    vfs.zName = "MyCustomVFS";
    XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));

    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithURL:fileURL flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
    XCTAssert(queue, @"Database queue should not have been created");
    queue = nil;

    XCTAssertEqual(SQLITE_OK, sqlite3_vfs_unregister(&vfs));
}

- (void)testQueueSelect
{
    [self.queue inDatabase:^(FMDatabase *adb) {
        int count = 0;
        FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
        while ([rsl next]) {
            count++;
        }
        
        XCTAssertEqual(count, 2);
        
        count = 0;
        rsl = [adb executeQuery:@"select * from qfoo where foo like ?", @"h%"];
        while ([rsl next]) {
            count++;
        }
        
        XCTAssertEqual(count, 2);
    }];
}

- (void)testReadOnlyQueue
{
    FMDatabaseQueue *queue2 = [FMDatabaseQueue databaseQueueWithPath:self.databasePath flags:SQLITE_OPEN_READONLY];
    XCTAssertNotNil(queue2);

    {
        [queue2 inDatabase:^(FMDatabase *db2) {
            FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"];
            XCTAssertNotNil(rs1);

            [rs1 close];
            
            XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database");
        }];
        
        [queue2 close];
        
        // Check that when we re-open the database, it's still read-only
        [queue2 inDatabase:^(FMDatabase *db2) {
            FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"];
            XCTAssertNotNil(rs1);
            
            [rs1 close];
            
            XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database");
        }];
    }
}

- (void)testStressTest
{
    size_t ops = 16;
    
    dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(ops, dqueue, ^(size_t nby) {
        
        // just mix things up a bit for demonstration purposes.
        if (nby % 2 == 1) {
            [NSThread sleepForTimeInterval:.01];
            
            [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
                FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"];
                while ([rsl next]) {
                    ;// whatever.
                }
            }];
            
        }
        
        if (nby % 3 == 1) {
            [NSThread sleepForTimeInterval:.01];
        }
        
        [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
            XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]);
            XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('2')"]);
            XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('3')"]);
        }];
    });
    
    [self.queue close];
    
    [self.queue inDatabase:^(FMDatabase *adb) {
        XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]);
    }];
}

- (void)testTransaction
{
    [self.queue inDatabase:^(FMDatabase *adb) {
        [adb executeUpdate:@"create table transtest (a integer)"];
        XCTAssertTrue([adb executeUpdate:@"insert into transtest values (1)"]);
        XCTAssertTrue([adb executeUpdate:@"insert into transtest values (2)"]);
        
        int rowCount = 0;
        FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
        while ([ars next]) {
            rowCount++;
        }
        
        XCTAssertEqual(rowCount, 2);
    }];
    
    [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) {
        XCTAssertTrue([adb executeUpdate:@"insert into transtest values (3)"]);
        
        if (YES) {
            // uh oh!, something went wrong (not really, this is just a test
            *rollback = YES;
            return;
        }
        
        XCTFail(@"This shouldn't be reached");
    }];
    
    [self.queue inDatabase:^(FMDatabase *adb) {
        
        int rowCount = 0;
        FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
        while ([ars next]) {
            rowCount++;
        }
        
        XCTAssertFalse([adb hasOpenResultSets]);
        
        XCTAssertEqual(rowCount, 2);
    }];
    
}

- (void)testSavePoint
{
    [self.queue inDatabase:^(FMDatabase *adb) {
        [adb executeUpdate:@"create table transtest (a integer)"];
        XCTAssertTrue([adb executeUpdate:@"insert into transtest values (1)"]);
        XCTAssertTrue([adb executeUpdate:@"insert into transtest values (2)"]);
        
        int rowCount = 0;
        FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
        while ([ars next]) {
            rowCount++;
        }
        
        XCTAssertEqual(rowCount, 2);
    }];
    
    [self.queue inSavePoint:^(FMDatabase *adb, BOOL *rollback) {
        XCTAssertTrue([adb executeUpdate:@"insert into transtest values (3)"]);
        
        if (YES) {
            // uh oh!, something went wrong (not really, this is just a test
            *rollback = YES;
            return;
        }
        
        XCTFail(@"This shouldn't be reached");
    }];
    
    [self.queue inDatabase:^(FMDatabase *adb) {
        
        int rowCount = 0;
        FMResultSet *ars = [adb executeQuery:@"select * from transtest"];
        while ([ars next]) {
            rowCount++;
        }
        
        XCTAssertFalse([adb hasOpenResultSets]);
        
        XCTAssertEqual(rowCount, 2);
    }];
    
}

- (void)testClose
{
    [self.queue inDatabase:^(FMDatabase *adb) {
        XCTAssertTrue([adb executeUpdate:@"CREATE TABLE close_test (a INTEGER)"]);
        XCTAssertTrue([adb executeUpdate:@"INSERT INTO close_test VALUES (1)"]);
        
        [adb close];
    }];
    
    [self.queue inDatabase:^(FMDatabase *adb) {
        FMResultSet *ars = [adb executeQuery:@"select * from close_test"];
        XCTAssertNotNil(ars);
    }];
}

@end


================================================
FILE: Tests/FMDatabaseTests.m
================================================
//
//  Tests.m
//  Tests
//
//  Created by Graham Dennis on 24/11/2013.
//
//

#import "FMDBTempDBTests.h"
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"

#if FMDB_SQLITE_STANDALONE
#import <sqlite3/sqlite3.h>
#elif SQLCIPHER_CRYPTO
#import <SQLCipher/sqlite3.h>
#else
#import <sqlite3.h>
#endif


@interface FMDatabaseTests : FMDBTempDBTests

@end

@implementation FMDatabaseTests

+ (void)populateDatabase:(FMDatabase *)db {
    [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];
    
    [db executeUpdate:@"create table t3 (a somevalue)"];
    
    [db beginTransaction];
    for (int i=0; i < 20; i++) {
        [db executeUpdate:@"insert into t3 (a) values (?)", [NSNumber numberWithInt:i]];
    }
    [db commit];
}

- (void)setUp{
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testOpenWithVFS {
    // create custom vfs
    sqlite3_vfs vfs = *sqlite3_vfs_find(NULL);
    vfs.zName = "MyCustomVFS";
    XCTAssertEqual(SQLITE_OK, sqlite3_vfs_register(&vfs, 0));
    // use custom vfs to open a in memory database
    FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
    [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"MyCustomVFS"];
    XCTAssertFalse([db hadError], @"Open with a custom VFS should have succeeded");
    XCTAssertEqual(SQLITE_OK, sqlite3_vfs_unregister(&vfs));
}

- (void)testURLOpen {
    NSURL *tempFolder = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempFolder URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    
    FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
    XCTAssert(db, @"Database should be returned");
    XCTAssertTrue([db open], @"Open should succeed");
    XCTAssertEqualObjects([db databaseURL], fileURL);
    XCTAssertTrue([db close], @"close should succeed");
    [[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
}

- (void)testFailOnOpenWithUnknownVFS {
    FMDatabase *db = [[FMDatabase alloc] initWithPath:@":memory:"];
    [db openWithFlags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:@"UnknownVFS"];
    XCTAssertTrue([db hadError], @"Should have failed");    
}

- (void)testFailOnUnopenedDatabase {
    [self.db close];
    
    XCTAssertNil([self.db executeQuery:@"select * from table"], @"Shouldn't get results from an empty table");
    XCTAssertTrue([self.db hadError], @"Should have failed");
}

- (void)testFailOnBadStatement {
    XCTAssertFalse([self.db executeUpdate:@"blah blah blah"], @"Invalid statement should fail");
    XCTAssertTrue([self.db hadError], @"Should have failed");
}

- (void)testFailOnBadStatementWithError
{
    NSError *error = nil;
    XCTAssertFalse([self.db executeUpdate:@"blah blah blah" withErrorAndBindings:&error], @"Invalid statement should fail");
    XCTAssertNotNil(error, @"Should have a non-nil NSError");
    XCTAssertEqual([error code], (NSInteger)SQLITE_ERROR, @"Error should be SQLITE_ERROR");
}

- (void)testPragmaJournalMode
{
    FMResultSet *ps = [self.db executeQuery:@"pragma journal_mode=delete"];
    XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
    XCTAssertNotNil(ps, @"Result set should be non-nil");
    XCTAssertTrue([ps next], @"Result set should have a next result");
    [ps close];
}

- (void)testPragmaPageSize
{
    [self.db executeUpdate:@"PRAGMA page_size=2048"];
    XCTAssertFalse([self.db hadError], @"pragma should have succeeded");
}

- (void)testVacuum
{
    [self.db executeUpdate:@"VACUUM"];
    XCTAssertFalse([self.db hadError], @"VACUUM should have succeeded");
}

- (void)testSelectULL
{
    // Unsigned long long
    [self.db executeUpdate:@"create table ull (a integer)"];
    
    [self.db executeUpdate:@"insert into ull (a) values (?)", [NSNumber numberWithUnsignedLongLong:ULLONG_MAX]];
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
    
    FMResultSet *rs = [self.db executeQuery:@"select a from ull"];
    while ([rs next]) {
        XCTAssertEqual([rs unsignedLongLongIntForColumnIndex:0], ULLONG_MAX, @"Result should be ULLONG_MAX");
        XCTAssertEqual([rs unsignedLongLongIntForColumn:@"a"],   ULLONG_MAX, @"Result should be ULLONG_MAX");
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testSelectByColumnName
{
    FMResultSet *rs = [self.db executeQuery:@"select rowid,* from test where a = ?", @"hi"];
    
    XCTAssertNotNil(rs, @"Should have a non-nil result set");
    
    while ([rs next]) {
        [rs intForColumn:@"c"];
        XCTAssertNotNil([rs stringForColumn:@"b"], @"Should have non-nil string for 'b'");
        XCTAssertNotNil([rs stringForColumn:@"a"], @"Should have non-nil string for 'a'");
        XCTAssertNotNil([rs stringForColumn:@"rowid"], @"Should have non-nil string for 'rowid'");
        XCTAssertNotNil([rs dateForColumn:@"d"], @"Should have non-nil date for 'd'");
        [rs doubleForColumn:@"d"];
        [rs doubleForColumn:@"e"];
        
        XCTAssertEqualObjects([rs columnNameForIndex:0], @"rowid",  @"Wrong column name for result set column number");
        XCTAssertEqualObjects([rs columnNameForIndex:1], @"a",      @"Wrong column name for result set column number");
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testInvalidColumnNames
{
    FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
    
    XCTAssertNotNil(rs, @"Should have a non-nil result set");
    
    NSString *invalidColumnName = @"foobar";

    while ([rs next]) {
        XCTAssertNil(rs[invalidColumnName], @"Invalid column name should return nil");
        XCTAssertNil([rs stringForColumn:invalidColumnName], @"Invalid column name should return nil");
        XCTAssertEqual([rs UTF8StringForColumn:invalidColumnName], (const unsigned char *)0, @"Invalid column name should return nil");
        XCTAssertNil([rs dateForColumn:invalidColumnName], @"Invalid column name should return nil");
        XCTAssertNil([rs dataForColumn:invalidColumnName], @"Invalid column name should return nil");
        XCTAssertNil([rs dataNoCopyForColumn:invalidColumnName], @"Invalid column name should return nil");
        XCTAssertNil([rs objectForColumn:invalidColumnName], @"Invalid column name should return nil");
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testInvalidColumnIndexes
{
    FMResultSet *rs = [self.db executeQuery:@"select rowid, a, b, c from test"];
    
    XCTAssertNotNil(rs, @"Should have a non-nil result set");
    
    int invalidColumnIndex = 999;
    
    while ([rs next]) {
        XCTAssertNil(rs[invalidColumnIndex], @"Invalid column name should return nil");
        XCTAssertNil([rs stringForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
        XCTAssertEqual([rs UTF8StringForColumnIndex:invalidColumnIndex], (const unsigned char *)0, @"Invalid column name should return nil");
        XCTAssertNil([rs dateForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
        XCTAssertNil([rs dataForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
        XCTAssertNil([rs dataNoCopyForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
        XCTAssertNil([rs objectForColumnIndex:invalidColumnIndex], @"Invalid column name should return nil");
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testBusyRetryTimeout
{
    [self.db executeUpdate:@"create table t1 (a integer)"];
    [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]];
    
    [self.db setMaxBusyRetryTimeInterval:2];
    
    FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath];
    [newDB open];
    
    FMResultSet *rs = [newDB executeQuery:@"select rowid,* from test where a = ?", @"hi'"];
    [rs next]; // just grab one... which will keep the db locked
    
    XCTAssertFalse([self.db executeUpdate:@"insert into t1 values (5)"], @"Insert should fail because the db is locked by a read");
    XCTAssertEqual([self.db lastErrorCode], SQLITE_BUSY, @"SQLITE_BUSY should be the last error");
    
    [rs close];
    [newDB close];
    
    XCTAssertTrue([self.db executeUpdate:@"insert into t1 values (5)"], @"The database shouldn't be locked at this point");
}

- (void)testCaseSensitiveResultDictionary
{
    // case sensitive result dictionary test
    [self.db executeUpdate:@"create table cs (aRowName integer, bRowName text)"];
    [self.db executeUpdate:@"insert into cs (aRowName, bRowName) values (?, ?)", [NSNumber numberWithInt:1], @"hello"];

    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");

    FMResultSet *rs = [self.db executeQuery:@"select * from cs"];
    while ([rs next]) {
        NSDictionary *d = [rs resultDictionary];
        
        XCTAssertNotNil([d objectForKey:@"aRowName"], @"aRowName should be non-nil");
        XCTAssertNil([d objectForKey:@"arowname"], @"arowname should be nil");
        XCTAssertNotNil([d objectForKey:@"bRowName"], @"bRowName should be non-nil");
        XCTAssertNil([d objectForKey:@"browname"], @"browname should be nil");
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testBoolInsert
{
    [self.db executeUpdate:@"create table btest (aRowName integer)"];
    [self.db executeUpdate:@"insert into btest (aRowName) values (?)", [NSNumber numberWithBool:YES]];
    
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
    
    FMResultSet *rs = [self.db executeQuery:@"select * from btest"];
    while ([rs next]) {
        XCTAssertTrue([rs boolForColumnIndex:0], @"first column should be true.");
        XCTAssertTrue([rs intForColumnIndex:0] == 1, @"first column should be equal to 1 - it was %d.", [rs intForColumnIndex:0]);
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testNamedParametersCount
{
    XCTAssertTrue([self.db executeUpdate:@"create table namedparamcounttest (a text, b text, c integer, d double)"]);

    NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
    [dictionaryArgs setObject:@"Text1" forKey:@"a"];
    [dictionaryArgs setObject:@"Text2" forKey:@"b"];
    [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
    [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
    XCTAssertTrue([self.db executeUpdate:@"insert into namedparamcounttest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
    
    FMResultSet *rs = [self.db executeQuery:@"select * from namedparamcounttest"];
    
    XCTAssertNotNil(rs);
    
    [rs next];
    
    XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
    XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
    XCTAssertEqual([rs intForColumn:@"c"], 1);
    XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
    
    [rs close];
    
    // note that at this point, dictionaryArgs has way more values than we need, but the query should still work since
    // a is in there, and that's all we need.
    rs = [self.db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs];
    
    XCTAssertNotNil(rs);
    XCTAssertTrue([rs next]);
    [rs close];
    
    // ***** Please note the following codes *****
    
    dictionaryArgs = [NSMutableDictionary dictionary];
    
    [dictionaryArgs setObject:@"NewText1" forKey:@"a"];
    [dictionaryArgs setObject:@"NewText2" forKey:@"b"];
    [dictionaryArgs setObject:@"OneMoreText" forKey:@"OneMore"];
    
    XCTAssertTrue([self.db executeUpdate:@"update namedparamcounttest set a = :a, b = :b where b = 'Text2'" withParameterDictionary:dictionaryArgs]);
    
}

- (void)testBlobs
{
    [self.db executeUpdate:@"create table blobTable (a text, b blob)"];
    
    // let's read an image from safari's app bundle.
    NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
    if (safariCompass) {
        [self.db executeUpdate:@"insert into blobTable (a, b) values (?, ?)", @"safari's compass", safariCompass];
        
        FMResultSet *rs = [self.db executeQuery:@"select b from blobTable where a = ?", @"safari's compass"];
        XCTAssertTrue([rs next]);
        NSData *readData = [rs dataForColumn:@"b"];
        XCTAssertEqualObjects(readData, safariCompass);
        
        // ye shall read the header for this function, or suffer the consequences.
        NSData *readDataNoCopy = [rs dataNoCopyForColumn:@"b"];
        XCTAssertEqualObjects(readDataNoCopy, safariCompass);
        
        [rs close];
        XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
        XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
    }
}

- (void)testNullValues
{
    [self.db executeUpdate:@"create table t2 (a integer, b integer)"];
    
    BOOL result = [self.db executeUpdate:@"insert into t2 values (?, ?)", nil, [NSNumber numberWithInt:5]];
    XCTAssertTrue(result, @"Failed to insert a nil value");
    
    FMResultSet *rs = [self.db executeQuery:@"select * from t2"];
    while ([rs next]) {
        XCTAssertNil([rs stringForColumnIndex:0], @"Wasn't able to retrieve a null string");
        XCTAssertEqualObjects([rs stringForColumnIndex:1], @"5");
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testNestedResultSets
{
    FMResultSet *rs = [self.db executeQuery:@"select * from t3"];
    while ([rs next]) {
        int foo = [rs intForColumnIndex:0];
        
        int newVal = foo + 100;
        
        [self.db executeUpdate:@"update t3 set a = ? where a = ?", [NSNumber numberWithInt:newVal], [NSNumber numberWithInt:foo]];
        
        FMResultSet *rs2 = [self.db executeQuery:@"select a from t3 where a = ?", [NSNumber numberWithInt:newVal]];
        [rs2 next];
        
        XCTAssertEqual([rs2 intForColumnIndex:0], newVal);
        
        [rs2 close];
    }
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testNSNullInsertion
{
    [self.db executeUpdate:@"create table nulltest (a text, b text)"];
    
    [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", [NSNull null], @"a"];
    [self.db executeUpdate:@"insert into nulltest (a, b) values (?, ?)", nil, @"b"];
    
    FMResultSet *rs = [self.db executeQuery:@"select * from nulltest"];
    
    while ([rs next]) {
        XCTAssertNil([rs stringForColumnIndex:0]);
        XCTAssertNotNil([rs stringForColumnIndex:1]);
    }
    
    [rs close];
    
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testNullDates
{
    NSDate *date = [NSDate date];
    [self.db executeUpdate:@"create table datetest (a double, b double, c double)"];
    [self.db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date];
    
    FMResultSet *rs = [self.db executeQuery:@"select * from datetest"];
    
    XCTAssertNotNil(rs);
    
    while ([rs next]) {
        
        NSDate *b = [rs dateForColumnIndex:1];
        NSDate *c = [rs dateForColumnIndex:2];
        
        XCTAssertNil([rs dateForColumnIndex:0]);
        XCTAssertNotNil(c, @"zero date shouldn't be nil");
        
        XCTAssertEqualWithAccuracy([b timeIntervalSinceDate:date],  0.0, 1.0, @"Dates should be the same to within a second");
        XCTAssertEqualWithAccuracy([c timeIntervalSince1970],       0.0, 1.0, @"Dates should be the same to within a second");
    }
    [rs close];
    
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testLotsOfNULLs
{
    NSData *safariCompass = [NSData dataWithContentsOfFile:@"/Applications/Safari.app/Contents/Resources/compass.icns"];
    
    if (!safariCompass)
        return;
    
    [self.db executeUpdate:@"create table nulltest2 (s text, d data, i integer, f double, b integer)"];
    
    [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , @"Hi", safariCompass, [NSNumber numberWithInt:12], [NSNumber numberWithFloat:4.4f], [NSNumber numberWithBool:YES]];
    [self.db executeUpdate:@"insert into nulltest2 (s, d, i, f, b) values (?, ?, ?, ?, ?)" , nil, nil, nil, nil, [NSNull null]];
    
    FMResultSet *rs = [self.db executeQuery:@"select * from nulltest2"];
    
    while ([rs next]) {
        
        int i = [rs intForColumnIndex:2];
        
        if (i == 12) {
            // it's the first row we inserted.
            XCTAssertFalse([rs columnIndexIsNull:0]);
            XCTAssertFalse([rs columnIndexIsNull:1]);
            XCTAssertFalse([rs columnIndexIsNull:2]);
            XCTAssertFalse([rs columnIndexIsNull:3]);
            XCTAssertFalse([rs columnIndexIsNull:4]);
            XCTAssertTrue( [rs columnIndexIsNull:5]);
            
            XCTAssertEqualObjects([rs dataForColumn:@"d"], safariCompass);
            XCTAssertNil([rs dataForColumn:@"notthere"]);
            XCTAssertNil([rs stringForColumnIndex:-2], @"Negative columns should return nil results");
            XCTAssertTrue([rs boolForColumnIndex:4]);
            XCTAssertTrue([rs boolForColumn:@"b"]);
            
            XCTAssertEqualWithAccuracy(4.4, [rs doubleForColumn:@"f"], 0.0000001, @"Saving a float and returning it as a double shouldn't change the result much");
            
            XCTAssertEqual([rs intForColumn:@"i"], 12);
            XCTAssertEqual([rs intForColumnIndex:2], 12);
            
            XCTAssertEqual([rs intForColumnIndex:12],       0, @"Non-existent columns should return zero for ints");
            XCTAssertEqual([rs intForColumn:@"notthere"],   0, @"Non-existent columns should return zero for ints");
            
            XCTAssertEqual([rs longForColumn:@"i"], 12l);
            XCTAssertEqual([rs longLongIntForColumn:@"i"], 12ll);
        }
        else {
            // let's test various null things.
            
            XCTAssertTrue([rs columnIndexIsNull:0]);
            XCTAssertTrue([rs columnIndexIsNull:1]);
            XCTAssertTrue([rs columnIndexIsNull:2]);
            XCTAssertTrue([rs columnIndexIsNull:3]);
            XCTAssertTrue([rs columnIndexIsNull:4]);
            XCTAssertTrue([rs columnIndexIsNull:5]);
            
            
            XCTAssertNil([rs dataForColumn:@"d"]);
        }
    }
    
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testUTF8Strings
{
    [self.db executeUpdate:@"create table utest (a text)"];
    [self.db executeUpdate:@"insert into utest values (?)", @"/übertest"];
    
    FMResultSet *rs = [self.db executeQuery:@"select * from utest where a = ?", @"/übertest"];
    XCTAssertTrue([rs next]);
    [rs close];
    XCTAssertFalse([self.db hasOpenResultSets], @"Shouldn't have any open result sets");
    XCTAssertFalse([self.db hadError], @"Shouldn't have any errors");
}

- (void)testArgumentsInArray
{
    [self.db executeUpdate:@"create table testOneHundredTwelvePointTwo (a text, b integer)"];
    [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]];
    [self.db executeUpdate:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:3], nil]];
    
    
    FMResultSet *rs = [self.db executeQuery:@"select * from testOneHundredTwelvePointTwo where b > ?" withArgumentsInArray:[NSArray arrayWithObject:[NSNumber numberWithInteger:1]]];
    
    XCTAssertTrue([rs next]);
    
    XCTAssertTrue([rs hasAnotherRow]);
    XCTAssertFalse([self.db hadError]);
    
    XCTAssertEqualObjects([rs stringForColumnIndex:0], @"one");
    XCTAssertEqual([rs intForColumnIndex:1], 2);
    
    XCTAssertTrue([rs next]);
    
    XCTAssertEqual([rs intForColumnIndex:1], 3);
    
    XCTAssertFalse([rs next]);
    XCTAssertFalse([rs hasAnotherRow]);
}

- (void)testColumnNamesContainingPeriods
{
    XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]);
    [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)", @"one", @"two"];
    
    FMResultSet *rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;"];
    
    XCTAssertNotNil(rs);
    
    XCTAssertTrue([rs next]);
    
    XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
    XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
    
    XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumn:@"b"], "two"), 0, @"String comparison should return zero");
    
    [rs close];
    
    // let's try these again, with the withArgumentsInArray: variation
    XCTAssertTrue([self.db executeUpdate:@"drop table t4;" withArgumentsInArray:[NSArray array]]);
    XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)" withArgumentsInArray:[NSArray array]]);
    
    [self.db executeUpdate:@"insert into t4 (a, b) values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", @"two", nil]];
    
    rs = [self.db executeQuery:@"select t4.a as 't4.a', t4.b from t4;" withArgumentsInArray:[NSArray array]];
    
    XCTAssertNotNil(rs);
    
    XCTAssertTrue([rs next]);
    
    XCTAssertEqualObjects([rs stringForColumn:@"t4.a"], @"one");
    XCTAssertEqualObjects([rs stringForColumn:@"b"], @"two");
    
    XCTAssertEqual(strcmp((const char*)[rs UTF8StringForColumn:@"b"], "two"), 0, @"String comparison should return zero");
    
    [rs close];
}

- (void)testFormatStringParsing
{
    XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
    [self.db executeUpdateWithFormat:@"insert into t5 values (%s, %d, %@, %c, %lld)", "text", 42, @"BLOB", 'd', 12345678901234ll];
    
    FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t5 where a = %s and a = %@ and b = %d", "text", @"text", 42];
    XCTAssertNotNil(rs);
    
    XCTAssertTrue([rs next]);
    
    XCTAssertEqualObjects([rs stringForColumn:@"a"], @"text");
    XCTAssertEqual([rs intForColumn:@"b"], 42);
    XCTAssertEqualObjects([rs stringForColumn:@"c"], @"BLOB");
    XCTAssertEqualObjects([rs stringForColumn:@"d"], @"d");
    XCTAssertEqual([rs longLongIntForColumn:@"e"], 12345678901234ll);
    
    [rs close];
}

- (void)testFormatStringParsingWithSizePrefixes
{
    XCTAssertTrue([self.db executeUpdate:@"create table t55 (a text, b int, c float)"]);
    short testShort = -4;
    float testFloat = 5.5;
    [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hi, %g)", 'a', testShort, testFloat];
    
    unsigned short testUShort = 6;
    [self.db executeUpdateWithFormat:@"insert into t55 values (%c, %hu, %g)", 'a', testUShort, testFloat];
    
    
    FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from t55 where a = %s order by 2", "a"];
    XCTAssertNotNil(rs);
    
    XCTAssertTrue([rs next]);
    
    XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
    XCTAssertEqual([rs intForColumn:@"b"], -4);
    XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
    
    
    XCTAssertTrue([rs next]);
    
    XCTAssertEqualObjects([rs stringForColumn:@"a"], @"a");
    XCTAssertEqual([rs intForColumn:@"b"], 6);
    XCTAssertEqualObjects([rs stringForColumn:@"c"], @"5.5");
    
    [rs close];
}

- (void)testFormatStringParsingWithNilValue
{
    XCTAssertTrue([self.db executeUpdate:@"create table tatwhat (a text)"]);
    
    BOOL worked = [self.db executeUpdateWithFormat:@"insert into tatwhat values(%@)", nil];
    
    XCTAssertTrue(worked);
    
    FMResultSet *rs = [self.db executeQueryWithFormat:@"select * from tatwhat"];
    XCTAssertNotNil(rs);
    XCTAssertTrue([rs next]);
    XCTAssertTrue([rs columnIndexIsNull:0]);
    
    XCTAssertFalse([rs next]);
}

- (void)testUpdateWithErrorAndBindings
{
    XCTAssertTrue([self.db executeUpdate:@"create table t5 (a text, b int, c blob, d text, e text)"]);
    
    NSError *err = nil;
    BOOL result = [self.db executeUpdate:@"insert into t5 values (?, ?, ?, ?, ?)" withErrorAndBindings:&err, @"text", [NSNumber numberWithInt:42], @"BLOB", @"d", [NSNumber numberWithInt:0]];
    XCTAssertTrue(result);
}

- (void)testSelectWithEmptyArgumentsArray
{
    FMResultSet *rs = [self.db executeQuery:@"select * from test where a=?" withArgumentsInArray:@[]];
    XCTAssertNil(rs);
}

- (void)testDatabaseAttach
{
    NSFileManager *fileManager = [NSFileManager new];
    [fileManager removeItemAtPath:@"/tmp/attachme.db" error:nil];
    
    FMDatabase *dbB = [FMDatabase databaseWithPath:@"/tmp/attachme.db"];
    XCTAssertTrue([dbB open]);
    XCTAssertTrue([dbB executeUpdate:@"create table attached (a text)"]);
    XCTAssertTrue(([dbB executeUpdate:@"insert into attached values (?)", @"test"]));
    XCTAssertTrue([dbB close]);
    
    [self.db executeUpdate:@"attach database '/tmp/attachme.db' as attack"];
    
    FMResultSet *rs = [self.db executeQuery:@"select * from attack.attached"];
    XCTAssertNotNil(rs);
    XCTAssertTrue([rs next]);
    [rs close];
}

- (void)testNamedParameters
{
    // -------------------------------------------------------------------------------
    // Named parameters.
    XCTAssertTrue([self.db executeUpdate:@"create table namedparamtest (a text, b text, c integer, d double)"]);
    
    NSMutableDictionary *dictionaryArgs = [NSMutableDictionary dictionary];
    [dictionaryArgs setObject:@"Text1" forKey:@"a"];
    [dictionaryArgs setObject:@"Text2" forKey:@"b"];
    [dictionaryArgs setObject:[NSNumber numberWithInt:1] forKey:@"c"];
    [dictionaryArgs setObject:[NSNumber numberWithDouble:2.0] forKey:@"d"];
    XCTAssertTrue([self.db executeUpdate:@"insert into namedparamtest values (:a, :b, :c, :d)" withParameterDictionary:dictionaryArgs]);
    
    FMResultSet *rs = [self.db executeQuery:@"select * from namedparamtest"];
    
    XCTAssertNotNil(rs);
    XCTAssertTrue([rs next]);
    
    XCTAssertEqualObjects([rs stringForColumn:@"a"], @"Text1");
    XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
    XCTAssertEqual([rs intForColumn:@"c"], 1);
    XCTAssertEqual([rs doubleForColumn:@"d"], 2.0);
    
    [rs close];
    
    
    dictionaryArgs = [NSMutableDictionary dictionary];
    
    [dictionaryArgs setObject:@"Text2" forKey:@"blah"];
    
    rs = [self.db executeQuery:@"select * from namedparamtest where b = :blah" withParameterDictionary:dictionaryArgs];
    
    XCTAssertNotNil(rs);
    XCTAssertTrue([rs next]);
    
    XCTAssertEqualObjects([rs stringForColumn:@"b"], @"Text2");
    
    [rs close];
}

- (void)testPragmaDatabaseList
{
    FMResultSet *rs = [self.db executeQuery:@"pragma database_list"];
    int counter = 0;
    while ([rs next]) {
        counter++;
        XCTAssertEqualObjects([rs stringForColumn:@"file"], self.databasePath);
    }
    XCTAssertEqual(counter, 1, @"Only one database should be attached");
}

- (void)testCachedStatementsInUse
{
    [self.db setShouldCacheStatements:true];
    
    [self.db executeUpdate:@"CREATE TABLE testCacheStatements(key INTEGER PRIMARY KEY, value INTEGER)"];
    [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (1, 2)"];
    [self.db executeUpdate:@"INSERT INTO testCacheStatements (key, value) VALUES (2, 4)"];
    
    XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
    XCTAssertTrue([[self.db executeQuery:@"SELECT * FROM testCacheStatements WHERE key=1"] next]);
}

- (void)testStatementCachingWorks
{
    [self.db executeUpdate:@"CREATE TABLE testStatementCaching ( value INTEGER )"];
    [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
    [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (1)"];
    [self.db executeUpdate:@"INSERT INTO testStatementCaching( value ) VALUES (2)"];
    
    [self.db setShouldCacheStatements:YES];
    
    // two iterations.
    //  the first time through no statements will be from the cache.
    //  the second time through all statements come from the cache.
    for (int i = 1; i <= 2; i++ ) {
        
        FMResultSet* rs1 = [self.db executeQuery: @"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @1]; // results in 2 rows...
        XCTAssertNotNil(rs1);
        XCTAssertTrue([rs1 next]);
        
        // confirm that we're seeing the benefits of caching.
        XCTAssertEqual([[rs1 statement] useCount], (long)i);
        
        FMResultSet* rs2 = [self.db executeQuery:@"SELECT rowid, * FROM testStatementCaching WHERE value = ?", @2]; // results in 1 row
        XCTAssertNotNil(rs2);
        XCTAssertTrue([rs2 next]);
        XCTAssertEqual([[rs2 statement] useCount], (long)i);
        
        // This is the primary check - with the old implementation of statement caching, rs2 would have rejiggered the (cached) statement used by rs1, making this test fail to return the 2nd row in rs1.
        XCTAssertTrue([rs1 next]);
        
        [rs1 close];
        [rs2 close];
    }
    
}

/*
 Test the date format
 */

- (void)testDateFormat
{
    void (^testOneDateFormat)(FMDatabase *, NSDate *) = ^( FMDatabase *db, NSDate *testDate ){
        [db executeUpdate:@"DROP TABLE IF EXISTS test_format"];
        [db executeUpdate:@"CREATE TABLE test_format ( test TEXT )"];
        [db executeUpdate:@"INSERT INTO test_format(test) VALUES (?)", testDate];
        
        FMResultSet *rs = [db executeQuery:@"SELECT test FROM test_format"];
        XCTAssertNotNil(rs);
        XCTAssertTrue([rs next]);
        
        XCTAssertEqualObjects([rs dateForColumnIndex:0], testDate);

        [rs close];
    };
    
    NSDateFormatter *fmt = [FMDatabase storeableDateFormat:@"yyyy-MM-dd HH:mm:ss"];
    
    NSDate *testDate = [fmt dateFromString:@"2013-02-20 12:00:00"];
    
    // test timestamp dates (ensuring our change does not break those)
    testOneDateFormat(self.db,testDate);
    
    // now test the string-based timestamp
    [self.db setDateFormat:fmt];
    testOneDateFormat(self.db, testDate);
}

- (void)testColumnNameMap
{
    XCTAssertTrue([self.db executeUpdate:@"create table colNameTest (a, b, c, d)"]);
    XCTAssertTrue([self.db executeUpdate:@"insert into colNameTest values (1, 2, 3, 4)"]);
    
    FMResultSet *ars = [self.db executeQuery:@"select * from colNameTest"];
    XCTAssertNotNil(ars);
    
    NSDictionary *d = [ars columnNameToIndexMap];
    XCTAssertEqual([d count], (NSUInteger)4);
    
    XCTAssertEqualObjects([d objectForKey:@"a"], @0);
    XCTAssertEqualObjects([d objectForKey:@"b"], @1);
    XCTAssertEqualObjects([d objectForKey:@"c"], @2);
    XCTAssertEqualObjects([d objectForKey:@"d"], @3);
    
}

- (void)testCustomStringFunction {
    [self createCustomFunctions];
    
    FMResultSet *ars = [self.db executeQuery:@"SELECT RemoveDiacritics(?)", @"José"];
    if (![ars next]) {
        XCTFail("Should have returned value");
        return;
    }
    NSString *result = [ars stringForColumnIndex:0];
    XCTAssertEqualObjects(result, @"Jose");
}

- (void)testFailCustomStringFunction {
    [self createCustomFunctions];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT RemoveDiacritics(?)", @(M_PI)];
    XCTAssert(rs, @"Prepare should have succeeded");
    
    NSError *error;
    BOOL success = [rs nextWithError:&error];
    XCTAssertFalse(success, @"'next' should have failed");
    
    XCTAssertEqualObjects(error.localizedDescription, @"Expected text");

    rs = [self.db executeQuery:@"SELECT RemoveDiacritics('jose','ortega')"];
    XCTAssertNil(rs);

    error = [self.db lastError];

    XCTAssert([error.localizedDescription containsString:@"wrong number of arguments"], @"Should get wrong number of arguments error, but got '%@'", error.localizedDescription);
}

- (void)testCustomDoubleFunction {
    [self createCustomFunctions];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT Hypotenuse(?, ?)", @(3.0), @(4.0)];
    if (![rs next]) {
        XCTFail("Should have returned value");
        return;
    }
    double value = [rs doubleForColumnIndex:0];
    XCTAssertEqual(value, 5.0);
}

- (void)testCustomIntFunction {
    [self createCustomFunctions];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT Hypotenuse(?, ?)", @(3), @(4)];
    if (![rs next]) {
        XCTFail("Should have returned value");
        return;
    }
    int value = [rs intForColumnIndex:0];
    XCTAssertEqual(value, 5);
}

- (void)testFailCustomNumericFunction {
    [self createCustomFunctions];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT Hypotenuse(?, ?)", @"foo", @"bar"];
    NSError *error;
    if ([rs nextWithError:&error]) {
        XCTFail("Should have failed");
        return;
    }
    XCTAssertEqualObjects(error.localizedDescription, @"Expected numeric");
    
    rs = [self.db executeQuery:@"SELECT Hypotenuse(?)", @(3.0)];
    XCTAssertNil(rs, @"Should fail for wrong number of arguments");

    error = [self.db lastError];
    XCTAssert([error.localizedDescription containsString:@"wrong number of arguments"], @"Should get wrong number of arguments error, but got '%@'", error.localizedDescription);
}

- (void)testCustomDataFunction {
    [self createCustomFunctions];
    
    NSMutableData *data = [NSMutableData data];
    for (NSInteger i = 0; i < 256; i++) {
        uint8_t byte = i;
        [data appendBytes:&byte length:1];
    }
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT SetAlternatingByteToOne(?)", data];
    if (![rs next]) {
        XCTFail("Should have returned value");
        return;
    }
    NSData *result = [rs dataForColumnIndex:0];
    XCTAssert(result, @"should have result");
    XCTAssertEqual(result.length, (unsigned long)256);
    
    for (NSInteger i = 0; i < 256; i++) {
        uint8_t byte;
        [result getBytes:&byte range:NSMakeRange(i, 1)];
        if (i % 2 == 0) {
            XCTAssertEqual(byte, (uint8_t)1);
        } else {
            XCTAssertEqual(byte, (uint8_t)i);
        }
    }
}

- (void)testFailCustomDataFunction {
    [self createCustomFunctions];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT SetAlternatingByteToOne(?)", @"foo"];
    XCTAssert(rs, @"Query should succeed");
    NSError *error;
    BOOL success = [rs nextWithError:&error];
    XCTAssertFalse(success, @"Performing SetAlternatingByteToOne with string should fail");
    XCTAssertEqualObjects(error.localizedDescription, @"Expected blob");
}

- (void)testCustomFunctionNullValues {
    [self.db makeFunctionNamed:@"FunctionThatDoesntTestTypes" arguments:1 block:^(void *context, int argc, void **argv) {
        NSData *data = [self.db valueData:argv[0]];
        XCTAssertNil(data);
        NSString *string = [self.db valueString:argv[0]];
        XCTAssertNil(string);
        int intValue = [self.db valueInt:argv[0]];
        XCTAssertEqual(intValue, 0);
        long longValue = [self.db valueLong:argv[0]];
        XCTAssertEqual(longValue, 0L);
        double doubleValue = [self.db valueDouble:argv[0]];
        XCTAssertEqual(doubleValue, 0.0);
        
        [self.db resultInt:42 context:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT FunctionThatDoesntTestTypes(?)", [NSNull null]];
    XCTAssert(rs, @"Creating query should succeed");
    
    NSError *error = nil;
    if (rs) {
        BOOL success = [rs nextWithError:&error];
        XCTAssert(success, @"Performing query should succeed");
    }
}

- (void)testCustomFunctionIntResult {
    [self.db makeFunctionNamed:@"IntResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
        [self.db resultInt:42 context:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT IntResultFunction()"];
    XCTAssert(rs, @"Creating query should succeed");
    
    BOOL success = [rs next];
    XCTAssert(success, @"Performing query should succeed");
    
    XCTAssertEqual([rs intForColumnIndex:0], 42);
}

- (void)testCustomFunctionLongResult {
    [self.db makeFunctionNamed:@"LongResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
        [self.db resultLong:42 context:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT LongResultFunction()"];
    XCTAssert(rs, @"Creating query should succeed");
    
    BOOL success = [rs next];
    XCTAssert(success, @"Performing query should succeed");
    
    XCTAssertEqual([rs longForColumnIndex:0], (long)42);
}

- (void)testCustomFunctionDoubleResult {
    [self.db makeFunctionNamed:@"DoubleResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
        [self.db resultDouble:0.1 context:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT DoubleResultFunction()"];
    XCTAssert(rs, @"Creating query should succeed");
    
    BOOL success = [rs next];
    XCTAssert(success, @"Performing query should succeed");
    
    XCTAssertEqual([rs doubleForColumnIndex:0], 0.1);
}

- (void)testCustomFunctionNullResult {
    [self.db makeFunctionNamed:@"NullResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
        [self.db resultNullInContext:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT NullResultFunction()"];
    XCTAssert(rs, @"Creating query should succeed");
    
    BOOL success = [rs next];
    XCTAssert(success, @"Performing query should succeed");
    
    XCTAssertEqualObjects([rs objectForColumnIndex:0], [NSNull null]);
}

- (void)testCustomFunctionErrorResult {
    [self.db makeFunctionNamed:@"ErrorResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
        [self.db resultError:@"foo" context:context];
        [self.db resultErrorCode:42 context:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT ErrorResultFunction()"];
    XCTAssert(rs, @"Creating query should succeed");
    
    NSError *error = nil;
    BOOL success = [rs nextWithError:&error];
    XCTAssertFalse(success, @"Performing query should fail.");
    
    XCTAssertEqualObjects(error.localizedDescription, @"foo");
    XCTAssertEqual(error.code, 42);
}

- (void)testCustomFunctionTooBigErrorResult {
    [self.db makeFunctionNamed:@"TooBigErrorResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
        [self.db resultErrorTooBigInContext:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT TooBigErrorResultFunction()"];
    XCTAssert(rs, @"Creating query should succeed");
    
    NSError *error = nil;
    BOOL success = [rs nextWithError:&error];
    XCTAssertFalse(success, @"Performing query should fail.");
    
    XCTAssertEqualObjects(error.localizedDescription, @"string or blob too big");
    XCTAssertEqual(error.code, SQLITE_TOOBIG);
}

- (void)testCustomFunctionNoMemoryErrorResult {
    [self.db makeFunctionNamed:@"NoMemoryErrorResultFunction" arguments:0 block:^(void *context, int argc, void **argv) {
        [self.db resultErrorNoMemoryInContext:context];
    }];
    
    FMResultSet *rs = [self.db executeQuery:@"SELECT NoMemoryErrorResultFunction()"];
    XCTAssert(rs, @"Creating query should succeed");
    
    NSError *error = nil;
    BOOL success = [rs nextWithError:&error];
    XCTAssertFalse(success, @"Performing query should fail.");
    
    XCTAssertEqualObjects(error.localizedDescription, @"out of memory");
    XCTAssertEqual(error.code, SQLITE_NOMEM);
}

- (void)createCustomFunctions {
    [self.db makeFunctionNamed:@"RemoveDiacritics" arguments:1 block:^(void *context, int argc, void **argv) {
        SqliteValueType type = [self.db valueType:argv[0]];
        if (type == SqliteValueTypeNull) {
            [self.db resultNullInContext:context];
            return;
        }
        if (type != SqliteValueTypeText) {
            [self.db resultError:@"Expected text" context:context];
            return;
        }
        NSString *string = [self.db valueString:argv[0]];
        NSString *result = [string stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:nil];
        [self.db resultString:result context:context];
    }];

    [self.db makeFunctionNamed:@"Hypotenuse" arguments:2 block:^(void *context, int argc, void **argv) {
        SqliteValueType type1 = [self.db valueType:argv[0]];
        SqliteValueType type2 = [self.db valueType:argv[1]];
        if (type1 != SqliteValueTypeFloat && type1 != SqliteValueTypeInteger && type2 != SqliteValueTypeFloat && type2 != SqliteValueTypeInteger) {
            [self.db resultError:@"Expected numeric" context:context];
            return;
        }
        double value1 = [self.db valueDouble:argv[0]];
        double value2 = [self.db valueDouble:argv[1]];
        [self.db resultDouble:hypot(value1, value2) context:context];
    }];

    [self.db makeFunctionNamed:@"SetAlternatingByteToOne" arguments:1 block:^(void *context, int argc, void **argv) {
        SqliteValueType type = [self.db valueType:argv[0]];
        if (type != SqliteValueTypeBlob) {
            [self.db resultError:@"Expected blob" context:context];
            return;
        }
        NSMutableData *data = [[self.db valueData:argv[0]] mutableCopy];
        uint8_t byte = 1;
        for (NSUInteger i = 0; i < data.length; i += 2) {
            [data replaceBytesInRange:NSMakeRange(i, 1) withBytes:&byte];
        }
        [self.db resultData:data context:context];
    }];

}

/* This is deprecated, and as such, should be excluded from tests
 *
 * - (void)testVersionNumber {
 *     XCTAssertEqual([FMDatabase FMDBVersion], 0x0278); // this is going to break every time we bump it.
 * }
  *
 */

- (void)testUserVersion {
    NSComparisonResult result = [[FMDatabase FMDBUserVersion] compare:@"2.7.12" options:NSNumericSearch];
    XCTAssertEqual(result, NSOrderedSame);
}

- (void)testVersionStringAboveRequired {
    NSComparisonResult result = [[FMDatabase FMDBUserVersion] compare:@"1.100.42" options:NSNumericSearch];
    XCTAssertEqual(result, NSOrderedDescending);
}

- (void)testVersionStringBelowRequired {
    NSComparisonResult result = [[FMDatabase FMDBUserVersion] compare:@"10.0.42" options:NSNumericSearch];
    XCTAssertEqual(result, NSOrderedAscending);
}

- (void)testExecuteStatements {
    BOOL success;

    NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"
                     "create table bulktest2 (id integer primary key autoincrement, y text);"
                     "create table bulktest3 (id integer primary key autoincrement, z text);"
                     "insert into bulktest1 (x) values ('XXX');"
                     "insert into bulktest2 (y) values ('YYY');"
                     "insert into bulktest3 (z) values ('ZZZ');";

    success = [self.db executeStatements:sql];

    XCTAssertTrue(success, @"bulk create");

    sql = @"select count(*) as count from bulktest1;"
           "select count(*) as count from bulktest2;"
           "select count(*) as count from bulktest3;";

    success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {
        NSInteger count = [dictionary[@"count"] integerValue];
        XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);
        return 0;
    }];
    
    XCTAssertTrue(success, @"bulk select");

    // select blob type records
    [self.db executeUpdate:@"create table bulktest4 (id integer primary key autoincrement, b blob);"];
    NSData *blobData = [[NSData alloc] initWithContentsOfFile:@"/bin/bash"];
    [self.db executeUpdate:@"insert into bulktest4 (b) values (?)" values:@[blobData] error:nil];

    sql = @"select * from bulktest4";
    success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary * _Nonnull resultsDictionary) {
        return 0;
    }];
    XCTAssertTrue(success, @"bulk select");

    sql = @"drop table bulktest1;"
           "drop table bulktest2;"
           "drop table bulktest3;"
           "drop table bulktest4";

    success = [self.db executeStatements:sql];

    XCTAssertTrue(success, @"bulk drop");
}

- (void)testCharAndBoolTypes
{
    XCTAssertTrue([self.db executeUpdate:@"create table charBoolTest (a, b, c)"]);

    BOOL success = [self.db executeUpdate:@"insert into charBoolTest values (?, ?, ?)", @YES, @NO, @('x')];
    XCTAssertTrue(success, @"Unable to insert values");

    FMResultSet *rs = [self.db executeQuery:@"select * from charBoolTest"];
    XCTAssertNotNil(rs);

    XCTAssertTrue([rs next], @"Did not return row");

    XCTAssertEqual([rs boolForColumn:@"a"], true);
    XCTAssertEqualObjects([rs objectForColumn:@"a"], @YES);

    XCTAssertEqual([rs boolForColumn:@"b"], false);
    XCTAssertEqualObjects([rs objectForColumn:@"b"], @NO);

    XCTAssertEqual([rs intForColumn:@"c"], 'x');
    XCTAssertEqualObjects([rs objectForColumn:@"c"], @('x'));

    [rs close];

    XCTAssertTrue([self.db executeUpdate:@"drop table charBoolTest"], @"Did not drop table");

}

- (void)testSqliteLibVersion
{
    NSString *version = [FMDatabase sqliteLibVersion];
    XCTAssert([version compare:@"3.7" options:NSNumericSearch] == NSOrderedDescending, @"earlier than 3.7");
    XCTAssert([version compare:@"4.0" options:NSNumericSearch] == NSOrderedAscending, @"not earlier than 4.0");
}

- (void)testIsThreadSafe
{
    BOOL isThreadSafe = [FMDatabase isSQLiteThreadSafe];
    XCTAssert(isThreadSafe, @"not threadsafe");
}

- (void)testOpenNilPath
{
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
    NSString *value = @"baz";
    XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
    NSString *retrievedValue = [db stringForQuery:@"select bar from foo"];
    XCTAssert([value compare:retrievedValue] == NSOrderedSame, @"values didn't match");
}

- (void)testOpenZeroLengthPath
{
    FMDatabase *db = [[FMDatabase alloc] initWithPath:@""];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
    NSString *value = @"baz";
    XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
    NSString *retrievedValue = [db stringForQuery:@"select bar from foo"];
    XCTAssert([value compare:retrievedValue] == NSOrderedSame, @"values didn't match");
}

- (void)testOpenTwice
{
    FMDatabase *db = [[FMDatabase alloc] init];
    [db open];
    XCTAssert([db open], @"Double open failed");
}

- (void)testInvalid
{
    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *path          = [documentsPath stringByAppendingPathComponent:@"nonexistentfolder/test.sqlite"];

    FMDatabase *db = [[FMDatabase alloc] initWithPath:path];
    XCTAssertFalse([db open], @"open did NOT fail");
}

- (void)testChangingMaxBusyRetryTimeInterval
{
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");

    NSTimeInterval originalInterval = db.maxBusyRetryTimeInterval;
    NSTimeInterval updatedInterval = originalInterval > 0 ? originalInterval + 1 : 1;
    
    db.maxBusyRetryTimeInterval = updatedInterval;
    NSTimeInterval diff = fabs(db.maxBusyRetryTimeInterval - updatedInterval);
    
    XCTAssert(diff < 1e-5, @"interval should have changed %.1f", diff);
}

- (void)testChangingMaxBusyRetryTimeIntervalDatabaseNotOpened
{
    FMDatabase *db = [[FMDatabase alloc] init];
    // XCTAssert([db open], @"open failed");   // deliberately not opened

    NSTimeInterval originalInterval = db.maxBusyRetryTimeInterval;
    NSTimeInterval updatedInterval = originalInterval > 0 ? originalInterval + 1 : 1;
    
    db.maxBusyRetryTimeInterval = updatedInterval;
    XCTAssertNotEqual(originalInterval, db.maxBusyRetryTimeInterval, @"interval should not have changed");
}

- (void)testZeroMaxBusyRetryTimeInterval
{
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    
    NSTimeInterval updatedInterval = 0;
    
    db.maxBusyRetryTimeInterval = updatedInterval;
    XCTAssertEqual(db.maxBusyRetryTimeInterval, updatedInterval, @"busy handler not disabled");
}

- (void)testCloseOpenResultSets
{
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (bar text)"], @"create failed");
    NSString *value = @"baz";
    XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[value]], @"insert failed");
    FMResultSet *rs = [db executeQuery:@"select bar from foo"];
    [db closeOpenResultSets];
    XCTAssertFalse([rs next], @"step should have failed");
}

- (void)testGoodConnection
{
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db goodConnection], @"no good connection");
}

- (void)testBadConnection
{
    FMDatabase *db = [[FMDatabase alloc] init];
    // XCTAssert([db open], @"open failed");  // deliberately did not open
    XCTAssertFalse([db goodConnection], @"no good connection");
}

- (void)testLastRowId
{
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (foo_id integer primary key autoincrement, bar text)"], @"create failed");
    
    XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"baz"]], @"insert failed");
    sqlite3_int64 firstRowId = [db lastInsertRowId];
    
    XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"qux"]], @"insert failed");
    sqlite3_int64 secondRowId = [db lastInsertRowId];
    
    XCTAssertEqual(secondRowId - firstRowId, 1, @"rowid should have incremented");
}

- (void)testChanges
{
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (foo_id integer primary key autoincrement, bar text)"], @"create failed");
    
    XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"baz"]], @"insert failed");
    XCTAssert([db executeUpdate:@"insert into foo (bar) values (?)" withArgumentsInArray:@[@"qux"]], @"insert failed");
    XCTAssert([db executeUpdate:@"update foo set bar = ?" withArgumentsInArray:@[@"xxx"]], @"insert failed");
    int changes = [db changes];
    
    XCTAssertEqual(changes, 2, @"two rows should have incremented \(%ld)", (long)changes);
}

- (void)testBind {
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (id integer primary key autoincrement, a numeric)"], @"create failed");
    
    NSNumber *insertedValue;
    NSNumber *retrievedValue;
    
    insertedValue = [NSNumber numberWithChar:51];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithUnsignedChar:52];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");

    insertedValue = [NSNumber numberWithShort:53];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithUnsignedShort:54];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithInt:54];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithUnsignedInt:55];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithLong:56];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithUnsignedLong:57];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithLongLong:56];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithUnsignedLongLong:57];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithFloat:58];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
    
    insertedValue = [NSNumber numberWithDouble:59];
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");

    insertedValue = @TRUE;
    XCTAssert([db executeUpdate:@"insert into foo (a) values (?)" withArgumentsInArray:@[insertedValue]], @"insert failed");
    retrievedValue = @([db boolForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])]);
    XCTAssertEqualObjects(insertedValue, retrievedValue, @"values don't match");
}

- (void)testFormatStrings {
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (id integer primary key autoincrement, a numeric)"], @"create failed");
    
    BOOL success;
    
    char insertedChar = 'A';
    success = [db executeUpdateWithFormat:@"insert into foo (a) values (%c)", insertedChar];
    XCTAssert(success, @"insert failed");
    const char *retrievedChar = [[db stringForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])] UTF8String];
    XCTAssertEqual(insertedChar, retrievedChar[0], @"values don't match");
    
    const char *insertedString = "baz";
    success = [db executeUpdateWithFormat:@"insert into foo (a) values (%s)", insertedString];
    XCTAssert(success, @"insert failed");
    const char *retrievedString = [[db stringForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])] UTF8String];
    XCTAssert(strcmp(insertedString, retrievedString) == 0, @"values don't match");
    
    int insertedInt = 42;
    success = [db executeUpdateWithFormat:@"insert into foo (a) values (%d)", insertedInt];
    XCTAssert(success, @"insert failed");
    int retrievedInt = [db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
    XCTAssertEqual(insertedInt, retrievedInt, @"values don't match");

    char insertedUnsignedInt = 43;
    success = [db executeUpdateWithFormat:@"insert into foo (a) values (%u)", insertedUnsignedInt];
    XCTAssert(success, @"insert failed");
    char retrievedUnsignedInt = [db intForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
    XCTAssertEqual(insertedUnsignedInt, retrievedUnsignedInt, @"values don't match");
    
    float insertedFloat = 44;
    success = [db executeUpdateWithFormat:@"insert into foo (a) values (%f)", insertedFloat];
    XCTAssert(success, @"insert failed");
    float retrievedFloat = [db doubleForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
    XCTAssertEqual(insertedFloat, retrievedFloat, @"values don't match");
    
    unsigned long long insertedUnsignedLongLong = 45;
    success = [db executeUpdateWithFormat:@"insert into foo (a) values (%llu)", insertedUnsignedLongLong];
    XCTAssert(success, @"insert failed");
    unsigned long long retrievedUnsignedLongLong = [db longForQuery:@"select a from foo where id = ?", @([db lastInsertRowId])];
    XCTAssertEqual(insertedUnsignedLongLong, retrievedUnsignedLongLong, @"values don't match");
}

- (void)testStepError {
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssert([db open], @"open failed");
    XCTAssert([db executeUpdate:@"create table foo (id integer primary key)"], @"create failed");
    XCTAssert([db executeUpdate:@"insert into foo (id) values (?)" values:@[@1] error:nil], @"create failed");
    
    NSError *error;
    BOOL success = [db executeUpdate:@"insert into foo (id) values (?)" values:@[@1] error:&error];
    XCTAssertFalse(success, @"insert of duplicate key should have failed");
    XCTAssertNotNil(error, @"error object should have been generated");
    XCTAssertEqual(error.code, 19, @"error code 19 should have been generated");
}

- (void)testCheckpoint {
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssertTrue([db open], @"open failed");
    NSError *error = nil;
    int frameCount = 0;
    int checkpointCount = 0;
    [db checkpoint:FMDBCheckpointModeTruncate name:NULL logFrameCount:&frameCount checkpointCount:&checkpointCount error:&error];
    // Verify that we're calling the checkpoint interface, which is a decent scope for this test, without going so far as to verify what checkpoint does
    XCTAssertEqual(frameCount, -1, @"frameCount should be -1 (means not using WAL mode) to verify that we're using the proper checkpoint interface");
    XCTAssertEqual(checkpointCount, -1, @"checkpointCount should be -1 (means not using WAL mode) to verify that we're using the proper checkpoint interface");
}

- (void)testImmediateTransaction {
    FMDatabase *db = [[FMDatabase alloc] init];
    XCTAssertTrue([db open], @"open failed");
    [db beginImmediateTransaction];
    [db beginImmediateTransaction];

    // Verify that beginImmediateTransaction behaves as advertised and starts a transaction
    XCTAssertEqualObjects([db lastError].localizedDescription, @"cannot start a transaction within a transaction");
}

- (void)testOpenFailure {
    NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL1 = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSURL *fileURL2 = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSFileManager *manager = [NSFileManager defaultManager];
    
    // ok, first create one database
    
    FMDatabase *db = [FMDatabase databaseWithURL:fileURL1];
    BOOL success = [db open];
    XCTAssert(success, @"Database not created correctly for purposes of test");
    success = [db executeUpdate:@"create table if not exists foo (bar text)"];
    XCTAssert(success, @"Table created correctly for purposes of test");
    [db close];
    
    // now, try to create open second database even though it doesn't exist
    
    db = [FMDatabase databaseWithURL:fileURL2];
    success = [db openWithFlags:SQLITE_OPEN_READWRITE];
    XCTAssert(!success, @"Opening second database file that doesn't exist should not have succeeded");
    
    // OK, everything so far is fine, opening a db without CREATE option above should have failed,
    // but so fix the missing file issue and re-opening
    
    success = [manager copyItemAtURL:fileURL1 toURL:fileURL2 error:nil];
    XCTAssert(success, @"Copying of db should have succeeded");
    
    // now let's try opening it again
    
    success = [db openWithFlags:SQLITE_OPEN_READWRITE];
    XCTAssert(success, @"Opening second database should now succeed");

    // now let's try using it
    FMResultSet *rs = [db executeQuery:@"select * from foo"];
    XCTAssertNotNil(rs, @"Should successfully be able to use re-opened database");
    
    // let's clean up
    
    [rs close];
    [db close];
    [manager removeItemAtURL:fileURL1 error:nil];
    [manager removeItemAtURL:fileURL2 error:nil];
}

// These three utility methods used by `testTransient`, to illustrate dangers of SQLITE_STATIC

- (BOOL)utility1ForTestTransient:(FMDatabase *)db withValue:(long)value {
    @autoreleasepool {
        NSString *string = [[NSString alloc] initWithFormat:@"value %@", @(value)];
        return [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", [string dataUsingEncoding:NSUTF8StringEncoding]];
    }
}

- (FMResultSet *)utility2ForTestTransient:(FMDatabase *)db withValue:(long)value {
    @autoreleasepool {
        NSString *string = [[NSString alloc] initWithFormat:@"value %@", @(value)];
        return [db executeQuery:@"SELECT * FROM foo WHERE bar = ?", [string dataUsingEncoding:NSUTF8StringEncoding]];
    }
}

- (BOOL)utility3ForTestTransient:(FMResultSet *)rs withValue:(long)value {
    @autoreleasepool {
        NSString *string = [[NSString alloc] initWithFormat:@"xxxxx %@", @(value + 1)];
        XCTAssertEqualObjects(string, @"xxxxx 43"); // Just to ensure the above isn't optimized out
        return [rs next];
    }
}

- (void)testTransient {
    NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSFileManager *manager = [NSFileManager defaultManager];

    // ok, first create one database

    FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
    BOOL success = [db open];
    XCTAssert(success, @"Database not created correctly for purposes of test");
    success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (bar BLOB)"];
    XCTAssert(success, @"Table created correctly for purposes of test");

    long value = 42;
    success = [self utility1ForTestTransient:db withValue:value];
    XCTAssert(success, @"INSERT failed");

    FMResultSet *rs = [self utility2ForTestTransient:db withValue:value];
    XCTAssert(rs, @"Creating SELECT failed");

    // the following is the key test, namely if FMDB uses SQLITE_STATIC, the following may fail, but SQLITE_TRANSIENT ensures it will succeed

    success = [self utility3ForTestTransient:rs withValue:value];
    XCTAssert(success, @"Performing SELECT failed");

    // let's clean up

    [rs close];
    [db close];
    [manager removeItemAtURL:fileURL error:nil];
}

- (void)testBindFailure {
    NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSFileManager *manager = [NSFileManager defaultManager];

    // ok, first create one database

    FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
    BOOL success = [db open];
    XCTAssert(success, @"Database not created correctly for purposes of test");
    success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (bar BLOB)"];
    XCTAssert(success, @"Table created correctly for purposes of test");

    NSUInteger limit = (NSUInteger)[db limitFor:SQLITE_LIMIT_LENGTH value:-1] + 1;
    NSLog(@"%lu", (unsigned long)limit);
    NSData *data = [NSMutableData dataWithLength:limit];
    success = [db executeUpdate:@"INSERT INTO foo (bar) VALUES (?)", data];
    XCTAssertFalse(success, @"Table created correctly for purposes of test");

    // let's clean up

    [db close];
    [manager removeItemAtURL:fileURL error:nil];
}

- (void)testRebindingWithDictionary {
    NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSFileManager *manager = [NSFileManager defaultManager];
    [manager removeItemAtURL:fileURL error:nil];

    // ok, first create one database

    FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
    BOOL success = [db open];
    XCTAssert(success, @"Database not created correctly for purposes of test");
    success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY, bar TEXT)"];
    XCTAssert(success, @"Table created correctly for purposes of test");

    FMResultSet *rs = [db prepare:@"INSERT INTO foo (bar) VALUES (:bar)"];
    XCTAssert(rs, @"INSERT statement not prepared %@", [db lastErrorMessage]);

    NSString *value1 = @"foo";
    XCTAssert([rs bindWithDictionary:@{@"bar": value1}], @"Unable to bind");
    XCTAssert([rs step], @"Performing query failed");

    NSString *value2 = @"bar";
    XCTAssert([rs bindWithDictionary:@{@"bar": value2}], @"Unable to bind");
    XCTAssert([rs step], @"Performing query failed");

    XCTAssert([rs bindWithDictionary:@{@"bar": value2}], @"Unable to bind");
    XCTAssert([rs step], @"Performing query failed");

    [rs close];

    rs = [db prepare:@"SELECT bar FROM foo WHERE bar = :bar"];
    XCTAssert([rs bindWithDictionary:@{@"bar": value1}], @"Unable to bind");
    XCTAssert([rs next], @"No record found");
    XCTAssertEqualObjects([rs stringForColumnIndex:0], value1);
    XCTAssertFalse([rs next], @"There should have been only one record");

    XCTAssert([rs bindWithDictionary:@{@"bar": value2}], @"Unable to bind");
    XCTAssert([rs next], @"No record found");
    XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
    XCTAssert([rs next], @"No record found");
    XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
    XCTAssertFalse([rs next], @"There should have been only two records");

    // let's clean up

    [rs close];
    [db close];
    [manager removeItemAtURL:fileURL error:nil];
}

- (void)testRebindingWithArray {
    NSURL *tempURL = [NSURL fileURLWithPath:NSTemporaryDirectory()];
    NSURL *fileURL = [tempURL URLByAppendingPathComponent:[[NSUUID UUID] UUIDString]];
    NSFileManager *manager = [NSFileManager defaultManager];

    // ok, first create one database

    FMDatabase *db = [FMDatabase databaseWithURL:fileURL];
    BOOL success = [db open];
    XCTAssert(success, @"Database not created correctly for purposes of test");
    success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS foo (id INTEGER PRIMARY KEY, bar TEXT)"];
    XCTAssert(success, @"Table created correctly for purposes of test");

    FMResultSet *rs = [db prepare:@"INSERT INTO foo (bar) VALUES (?)"];
    XCTAssert(rs, @"INSERT statement not prepared %@", [db lastErrorMessage]);

    NSString *value1 = @"foo";
    XCTAssert([rs bindWithArray:@[value1]], @"Unable to bind");
    XCTAssert([rs step], @"Performing INSERT 1 failed");

    NSString *value2 = @"bar";
    XCTAssert([rs bindWithArray:@[value2]], @"Unable to bind");
    XCTAssert([rs step], @"Performing INSERT 2 failed");
    XCTAssert([rs bindWithArray:@[value2]], @"Unable to bind");
    XCTAssert([rs step], @"Performing INSERT 2 failed");

    [rs close];

    rs = [db prepare:@"SELECT bar FROM foo WHERE bar = ?"];
    XCTAssert([rs bindWithArray:@[value1]], @"Unable to bind");
    XCTAssert([rs next], @"No record found");
    XCTAssertEqualObjects([rs stringForColumnIndex:0], value1);
    XCTAssertFalse([rs next], @"There should have been only one record");

    XCTAssert([rs bindWithArray:@[value2]], @"Unable to bind");
    XCTAssert([rs next], @"No record found");
    XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
    XCTAssert([rs next], @"No record found");
    XCTAssertEqualObjects([rs stringForColumnIndex:0], value2);
    XCTAssertFalse([rs next], @"There should have been only two records");

    // let's clean up

    [rs close];
    [db close];
    [manager removeItemAtURL:fileURL error:nil];
}

@end


================================================
FILE: Tests/FMResultSetTests.m
================================================
//
//  FMResultSetTests.m
//  fmdb
//
//  Created by Muralidharan,Roshan on 10/6/14.
//
//

#import "FMDBTempDBTests.h"
#import "FMDatabase.h"

#if FMDB_SQLITE_STANDALONE
#import <sqlite3/sqlite3.h>
#elif SQLCIPHER_CRYPTO
#import <SQLCipher/sqlite3.h>
#else
#import <sqlite3.h>
#endif

@interface FMResultSetTests : FMDBTempDBTests

@end

@implementation FMResultSetTests

+ (void)populateDatabase:(FMDatabase *)db
{
    [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'",
         [NSString stringWithFormat:@"number %d", i],
         [NSNumber numberWithInt:i],
         [NSDate date],
         [NSNumber numberWithFloat:2.2f]];
    }
    [db commit];
}

- (void)testNextWithError_WithoutError
{
    [self.db executeUpdate:@"CREATE TABLE testTable(key INTEGER PRIMARY KEY, value INTEGER)"];
    [self.db executeUpdate:@"INSERT INTO testTable (key, value) VALUES (1, 2)"];
    [self.db executeUpdate:@"INSERT INTO testTable (key, value) VALUES (2, 4)"];
    
    FMResultSet *resultSet = [self.db executeQuery:@"SELECT * FROM testTable WHERE key=1"];
    XCTAssertNotNil(resultSet);
    NSError *error;
    XCTAssertTrue([resultSet nextWithError:&error]);
    XCTAssertNil(error);
    
    XCTAssertFalse([resultSet nextWithError:&error]);
    XCTAssertNil(error);
    
    [resultSet close];
}

- (void)testNextWithError_WithBusyError
{
    [self.db executeUpdate:@"CREATE TABLE testTable(key INTEGER PRIMARY KEY, value INTEGER)"];
    [self.db executeUpdate:@"INSERT INTO testTable (key, value) VALUES (1, 2)"];
    [self.db executeUpdate:@"INSERT INTO testTable (key, value) VALUES (2, 4)"];
    
    FMResultSet *resultSet = [self.db executeQuery:@"SELECT * FROM testTable WHERE key=1"];
    XCTAssertNotNil(resultSet);
    
    FMDatabase *newDB = [FMDatabase databaseWithPath:self.databasePath];
    [newDB open];
    
    [newDB beginExclusiveTransaction];
    NSError *error;
    XCTAssertFalse([resultSet nextWithError:&error]);
    [newDB commit];
    
    XCTAssertEqual(error.code, SQLITE_BUSY, @"SQLITE_BUSY should be the last error");
    [resultSet close];
}

- (void)testNextWithError_WithMisuseError
{
    [self.db executeUpdate:@"CREATE TABLE testTable(key INTEGER PRIMARY KEY, value INTEGER)"];
    [self.db executeUpdate:@"INSERT INTO testTable (key, value) VALUES (1, 2)"];
    [self.db executeUpdate:@"INSERT INTO testTable (key, value) VALUES (2, 4)"];
    
    FMResultSet *resultSet = [self.db executeQuery:@"SELECT * FROM testTable WHERE key=9"];
    XCTAssertNotNil(resultSet);
    XCTAssertFalse([resultSet next]);
    NSError *error;
    XCTAssertFalse([resultSet nextWithError:&error]);

    XCTAssertEqual(error.code, SQLITE_MISUSE, @"SQLITE_MISUSE should be the last error");
}

- (void)testColumnTypes
{
    [self.db executeUpdate:@"CREATE TABLE testTable (intValue INTEGER, floatValue FLOAT, textValue TEXT, blobValue BLOB)"];
    NSString *sql = @"INSERT INTO testTable (intValue, floatValue, textValue, blobValue) VALUES (?, ?, ?, ?)";
    NSError *error;
    NSData *data = [@"foo" dataUsingEncoding:NSUTF8StringEncoding];
    NSData *zeroLengthData = [NSData data];
    NSNull *null = [NSNull null];
    [self.db executeUpdate:sql values:@[@42, @M_PI, @"test", data] error:&error];
    [self.db executeUpdate:sql values:@[null, null, null, null] error:&error];
    [self.db executeUpdate:sql values:@[null, null, null, zeroLengthData] error:&error];

    FMResultSet *resultSet = [self.db executeQuery:@"SELECT * FROM testTable"];
    XCTAssertNotNil(resultSet);

    XCTAssertTrue([resultSet next]);
    XCTAssertEqual([resultSet typeForColumn:@"intValue"],   SqliteValueTypeInteger);
    XCTAssertEqual([resultSet typeForColumn:@"floatValue"], SqliteValueTypeFloat);
    XCTAssertEqual([resultSet typeForColumn:@"textValue"],  SqliteValueTypeText);
    XCTAssertEqual([resultSet typeForColumn:@"blobValue"],  SqliteValueTypeBlob);
    XCTAssertNotNil([resultSet dataForColumn:@"blobValue"]);

    XCTAssertTrue([resultSet next]);
    XCTAssertEqual([resultSet typeForColumn:@"intValue"],   SqliteValueTypeNull);
    XCTAssertEqual([resultSet typeForColumn:@"floatValue"], SqliteValueTypeNull);
    XCTAssertEqual([resultSet typeForColumn:@"textValue"],  SqliteValueTypeNull);
    XCTAssertEqual([resultSet typeForColumn:@"blobValue"],  SqliteValueTypeNull);
    XCTAssertNil([resultSet dataForColumn:@"blobValue"]);

    XCTAssertTrue([resultSet next]);
    XCTAssertEqual([resultSet typeForColumn:@"blobValue"],  SqliteValueTypeBlob);
    XCTAssertNil([resultSet dataForColumn:@"blobValue"]);

    [resultSet close];
}

@end


================================================
FILE: Tests/Schemes/Tests.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "0500"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "EE4290EE12B42F870088BD94"
               BuildableName = "libFMDB.a"
               BlueprintName = "FMDB"
               ReferencedContainer = "container:fmdb.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "NO"
            buildForArchiving = "NO"
            buildForAnalyzing = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "BF5D041518416BB2008C5AA9"
               BuildableName = "Tests.xctest"
               BlueprintName = "Tests"
               ReferencedContainer = "container:fmdb.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES"
      buildConfiguration = "Debug">
      <Testables>
         <TestableReference
            skipped = "NO">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "BF5D041518416BB2008C5AA9"
               BuildableName = "Tests.xctest"
               BlueprintName = "Tests"
               ReferencedContainer = "container:fmdb.xcodeproj">
            </BuildableReference>
         </TestableReference>
      </Testables>
   </TestAction>
   <LaunchAction
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      buildConfiguration = "Debug"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      allowLocationSimulation = "YES">
      <AdditionalOptions>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      buildConfiguration = "Release"
      debugDocumentVersioning = "YES">
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: Tests/Tests-Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>${EXECUTABLE_NAME}</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundlePackageType</key>
	<string>BNDL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>1</string>
</dict>
</plist>


================================================
FILE: Tests/Tests-Prefix.pch
================================================
//
//  Prefix header
//
//  The contents of this file are implicitly included at the beginning of every source file.
//

#ifdef __OBJC__
    #import <Cocoa/Cocoa.h>
    #import <XCTest/XCTest.h>
    #import "FMDBTempDBTests.h"
#endif


================================================
FILE: Tests/en.lproj/InfoPlist.strings
================================================
/* Localized versions of Info.plist keys */



================================================
FILE: fmdb.1
================================================
.\"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

================================================
FILE: fmdb.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 54;
	objects = {

/* Begin PBXBuildFile section */
		2CD2425B1FCC09CA00479FDE /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */; };
		2CD2425C1FCC09CA00479FDE /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */; };
		2CD2425D1FCC09CA00479FDE /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
		2CD2425E1FCC09CA00479FDE /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
		2CD2425F1FCC09CA00479FDE /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
		2CD242611FCC09CA00479FDE /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 83C73F2B1C326CF400FFC730 /* libsqlite3.tbd */; };
		2CD242631FCC09CA00479FDE /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 8314AF3218CD73D600EC0E25 /* FMDB.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2CD242641FCC09CA00479FDE /* FMDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2CD242651FCC09CA00479FDE /* FMResultSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2CD242661FCC09CA00479FDE /* FMDatabaseQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2CD242671FCC09CA00479FDE /* FMDatabaseAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
		2CD242681FCC09CA00479FDE /* FMDatabasePool.h in Headers */ = {isa = PBXBuildFile; fileRef = CC9E4EB713B31188005F9210 /* FMDatabasePool.h */; settings = {ATTRIBUTES = (Public, ); }; };
		40A145FE1BE5759400E5D35E /* FMDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
		40A146001BE575D000E5D35E /* FMDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
		40A146011BE575D600E5D35E /* FMResultSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
		40A146021BE575DD00E5D35E /* FMDatabaseQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
		40A146031BE575E400E5D35E /* FMDatabaseAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
		40A146041BE575EB00E5D35E /* FMDatabasePool.h in Headers */ = {isa = PBXBuildFile; fileRef = CC9E4EB713B31188005F9210 /* FMDatabasePool.h */; settings = {ATTRIBUTES = (Public, ); }; };
		40A146051BE6999800E5D35E /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 8314AF3218CD73D600EC0E25 /* FMDB.h */; settings = {ATTRIBUTES = (Public, ); }; };
		4C740718215084110003C17E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4C74070E215083C40003C17E /* InfoPlist.strings */; };
		4C74071A2150845D0003C17E /* FMDatabaseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C740716215083C40003C17E /* FMDatabaseTests.m */; };
		4C74071B2150845D0003C17E /* FMDatabaseAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C74070A215083C40003C17E /* FMDatabaseAdditionsTests.m */; };
		4C74071C2150845D0003C17E /* FMDatabaseQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C740711215083C40003C17E /* FMDatabaseQueueTests.m */; };
		4C74071D2150845D0003C17E /* FMDatabasePoolTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C74070D215083C40003C17E /* FMDatabasePoolTests.m */; };
		4C74071E2150845D0003C17E /* FMDatabaseFTS3Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C740713215083C40003C17E /* FMDatabaseFTS3Tests.m */; };
		4C74071F2150845D0003C17E /* FMDatabaseFTS3WithModuleNameTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C740712215083C40003C17E /* FMDatabaseFTS3WithModuleNameTests.m */; };
		4C7407202150845D0003C17E /* FMDBTempDBTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C74070C215083C40003C17E /* FMDBTempDBTests.m */; };
		4C7407212150845D0003C17E /* FMResultSetTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C740710215083C40003C17E /* FMResultSetTests.m */; };
		621721B21892BFE30006691F /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */; };
		621721B31892BFE30006691F /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */; };
		621721B41892BFE30006691F /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
		621721B51892BFE30006691F /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
		621721B61892BFE30006691F /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
		6290CBB7188FE836009790F8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6290CBB6188FE836009790F8 /* Foundation.framework */; };
		8314AF3318CD73D600EC0E25 /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 8314AF3218CD73D600EC0E25 /* FMDB.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F131C326B9400FFC730 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */; };
		83C73F141C326B9400FFC730 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */; };
		83C73F151C326B9400FFC730 /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
		83C73F161C326B9400FFC730 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
		83C73F171C326B9400FFC730 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
		83C73F181C326BAB00FFC730 /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 8314AF3218CD73D600EC0E25 /* FMDB.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F191C326BAB00FFC730 /* FMDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F1A1C326BAB00FFC730 /* FMResultSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F1B1C326BAB00FFC730 /* FMDatabaseQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F1C1C326BAB00FFC730 /* FMDatabaseAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F1D1C326BAB00FFC730 /* FMDatabasePool.h in Headers */ = {isa = PBXBuildFile; fileRef = CC9E4EB713B31188005F9210 /* FMDatabasePool.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F1E1C326BC100FFC730 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */; };
		83C73F1F1C326BC100FFC730 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */; };
		83C73F201C326BC100FFC730 /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
		83C73F211C326BC100FFC730 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
		83C73F221C326BC100FFC730 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
		83C73F231C326BD600FFC730 /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 8314AF3218CD73D600EC0E25 /* FMDB.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F241C326BD600FFC730 /* FMDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F251C326BD600FFC730 /* FMResultSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F261C326BD600FFC730 /* FMDatabaseQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F271C326BD600FFC730 /* FMDatabaseAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F281C326BD600FFC730 /* FMDatabasePool.h in Headers */ = {isa = PBXBuildFile; fileRef = CC9E4EB713B31188005F9210 /* FMDatabasePool.h */; settings = {ATTRIBUTES = (Public, ); }; };
		83C73F2A1C326CE800FFC730 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 83C73F291C326CE800FFC730 /* libsqlite3.tbd */; };
		83C73F2C1C326CF400FFC730 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 83C73F2B1C326CF400FFC730 /* libsqlite3.tbd */; };
		83C73F2F1C326D2F00FFC730 /* FMDB.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83C73F0B1C326ADA00FFC730 /* FMDB.framework */; };
		8DD76F9C0486AA7600D96B5E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08FB779EFE84155DC02AAC07 /* Foundation.framework */; };
		8DD76F9F0486AA7600D96B5E /* fmdb.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C6859EA3029092ED04C91782 /* fmdb.1 */; };
		A08C6BA72AF0F5B1004F3F28 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */; };
		A08C6BA82AF0F5B1004F3F28 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */; };
		A08C6BA92AF0F5B1004F3F28 /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
		A08C6BAA2AF0F5B1004F3F28 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
		A08C6BAB2AF0F5B1004F3F28 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
		A08C6BAD2AF0F5B1004F3F28 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 83C73F2B1C326CF400FFC730 /* libsqlite3.tbd */; };
		A08C6BAF2AF0F5B1004F3F28 /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = 8314AF3218CD73D600EC0E25 /* FMDB.h */; settings = {ATTRIBUTES = (Public, ); }; };
		A08C6BB02AF0F5B1004F3F28 /* FMDatabase.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBA0A13E34D00A6D3E3 /* FMDatabase.h */; settings = {ATTRIBUTES = (Public, ); }; };
		A08C6BB12AF0F5B1004F3F28 /* FMResultSet.h in Headers */ = {isa = PBXBuildFile; fileRef = CCC24EBF0A13E34D00A6D3E3 /* FMResultSet.h */; settings = {ATTRIBUTES = (Public, ); }; };
		A08C6BB22AF0F5B1004F3F28 /* FMDatabaseQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
		A08C6BB32AF0F5B1004F3F28 /* FMDatabaseAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CC50F2CC0DF9183600E4AAAE /* FMDatabaseAdditions.h */; settings = {ATTRIBUTES = (Public, ); }; };
		A08C6BB42AF0F5B1004F3F28 /* FMDatabasePool.h in Headers */ = {isa = PBXBuildFile; fileRef = CC9E4EB713B31188005F9210 /* FMDatabasePool.h */; settings = {ATTRIBUTES = (Public, ); }; };
		BF5D041918416BB2008C5AA9 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF5D041818416BB2008C5AA9 /* XCTest.framework */; };
		BFC152B118417F0D00605DF7 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
		CC47A00F148581E9002CCDAB /* FMDatabaseQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = CC47A00D148581E9002CCDAB /* FMDatabaseQueue.h */; settings = {ATTRIBUTES = (Public, ); }; };
		CC47A010148581E9002CCDAB /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
		CC47A011148581E9002CCDAB /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
		CC50F2CD0DF9183600E4AAAE /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */; };
		CC7CE42818F5C04600938264 /* FMDatabase+InMemoryOnDiskIO.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7CE42718F5C04600938264 /* FMDatabase+InMemoryOnDiskIO.m */; };
		CC9E4EB913B31188005F9210 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
		CC9E4EBA13B31188005F9210 /* FMDatabasePool.h in Headers */ = {isa = PBXBuildFile; fileRef = CC9E4EB713B31188005F9210 /* FMDatabasePool.h */; settings = {ATTRIBUTES = (Public, ); }; };
		CC9E4EBB13B31188005F9210 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = CC9E4EB813B31188005F9210 /* FMDatabasePool.m */; };
		CCA66A2D19C0CB1900EFDAC1 /* FMDatabase+FTS3.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA66A2919C0CB1900EFDAC1 /* FMDatabase+FTS3.m */; };
		CCA66A2E19C0CB1900EFDAC1 /* FMDatabase+FTS3.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA66A2919C0CB1900EFDAC1 /* FMDatabase+FTS3.m */; };
		CCA66A2F19C0CB1900EFDAC1 /* FMTokenizers.m
Download .txt
gitextract_7bzgmki1/

├── .gitignore
├── .travis.yml
├── CHANGES_AND_TODO_LIST.txt
├── COCOAPODS.md
├── CONTRIBUTORS.txt
├── FMDB.podspec
├── LICENSE.txt
├── Package.swift
├── README.markdown
├── Tests/
│   ├── Base.lproj/
│   │   └── InfoPlist.strings
│   ├── FMDBTempDBTests.h
│   ├── FMDBTempDBTests.m
│   ├── FMDatabaseAdditionsTests.m
│   ├── FMDatabaseFTS3Tests.m
│   ├── FMDatabaseFTS3WithModuleNameTests.m
│   ├── FMDatabasePoolTests.m
│   ├── FMDatabaseQueueTests.m
│   ├── FMDatabaseTests.m
│   ├── FMResultSetTests.m
│   ├── Schemes/
│   │   └── Tests.xcscheme
│   ├── Tests-Info.plist
│   ├── Tests-Prefix.pch
│   └── en.lproj/
│       └── InfoPlist.strings
├── fmdb.1
├── fmdb.xcodeproj/
│   ├── project.pbxproj
│   └── xcshareddata/
│       └── xcschemes/
│           ├── FMDB MacOS.xcscheme
│           ├── FMDB iOS.xcscheme
│           ├── FMDB watchOS.xcscheme
│           └── FMDB xrOS.xcscheme
├── privacy/
│   ├── PrivacyInfo.xcprivacy
│   └── README.md
└── src/
    ├── extra/
    │   ├── InMemoryOnDiskIO/
    │   │   ├── FMDatabase+InMemoryOnDiskIO.h
    │   │   └── FMDatabase+InMemoryOnDiskIO.m
    │   └── fts3/
    │       ├── FMDatabase+FTS3.h
    │       ├── FMDatabase+FTS3.m
    │       ├── FMTokenizers.h
    │       ├── FMTokenizers.m
    │       └── fts3_tokenizer.h
    ├── fmdb/
    │   ├── FMDB.h
    │   ├── FMDatabase+SQLCipher.h
    │   ├── FMDatabase+SQLCipher.m
    │   ├── FMDatabase.h
    │   ├── FMDatabase.m
    │   ├── FMDatabaseAdditions.h
    │   ├── FMDatabaseAdditions.m
    │   ├── FMDatabasePool.h
    │   ├── FMDatabasePool.m
    │   ├── FMDatabaseQueue.h
    │   ├── FMDatabaseQueue.m
    │   ├── FMResultSet.h
    │   ├── FMResultSet.m
    │   ├── Info.plist
    │   └── info-xrOS.plist
    └── sample/
        ├── fmdb_Prefix.pch
        └── main.m
Download .txt
SYMBOL INDEX (8 symbols across 3 files)

FILE: src/extra/fts3/FMDatabase+FTS3.h
  type FMTokenizerCursor (line 58) | typedef struct FMTokenizerCursor

FILE: src/extra/fts3/fts3_tokenizer.h
  type sqlite3_tokenizer_module (line 47) | typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module;
  type sqlite3_tokenizer (line 48) | typedef struct sqlite3_tokenizer sqlite3_tokenizer;
  type sqlite3_tokenizer_cursor (line 49) | typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor;
  type sqlite3_tokenizer_module (line 51) | struct sqlite3_tokenizer_module {
  type sqlite3_tokenizer (line 146) | struct sqlite3_tokenizer {
  type sqlite3_tokenizer_cursor (line 151) | struct sqlite3_tokenizer_cursor {

FILE: src/fmdb/FMDatabase+SQLCipher.h
  function NS_ASSUME_NONNULL_BEGIN (line 10) | NS_ASSUME_NONNULL_BEGIN
Condensed preview — 55 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (588K chars).
[
  {
    "path": ".gitignore",
    "chars": 202,
    "preview": ".DS_Store\nbuild\n*.xcodeproj/*.pbxuser\n*.xcodeproj/*.perspectivev3\n*.xcodeproj/xcuserdata\nfmdb.xcodeproj/*.mode1v3\n*.xcod"
  },
  {
    "path": ".travis.yml",
    "chars": 215,
    "preview": "language: objective-c\nxcode_project: fmdb.xcodeproj\nxcode_scheme: Tests\nbefore_install:\n    - mkdir -p \"fmdb.xcodeproj/x"
  },
  {
    "path": "CHANGES_AND_TODO_LIST.txt",
    "chars": 21798,
    "preview": "TODO:\nZip, nada, zilch.  Got any ideas?\n\nIf you would like to contribute some code ... awesome!  I just ask that you mak"
  },
  {
    "path": "COCOAPODS.md",
    "chars": 786,
    "preview": "# CocoaPods release process\n\n1. Update `s.version` in `FMDB.Podspec`.\n2. Tag the release (`git tag x.y.z && git push --t"
  },
  {
    "path": "CONTRIBUTORS.txt",
    "chars": 766,
    "preview": "Thanks to the following folks for patches and other contributions:\n\nD. Richard Hipp (The author of SQLite)\nDominic Yu\nZa"
  },
  {
    "path": "FMDB.podspec",
    "chars": 1993,
    "preview": "Pod::Spec.new do |s|\n  s.name = 'FMDB'\n  s.version = '2.7.12'\n  s.summary = 'A Cocoa / Objective-C wrapper around SQLite"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1399,
    "preview": "If you are using FMDB in your project, I'd love to hear about it.  Let Gus know\nby sending an email to gus@flyingmeat.co"
  },
  {
    "path": "Package.swift",
    "chars": 1810,
    "preview": "// swift-tools-version:6.1\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "README.markdown",
    "chars": 29573,
    "preview": "# FMDB v2.7\n<!--[![Platform](https://img.shields.io/cocoapods/p/FMDB.svg?style=flat)](http://cocoadocs.org/docsets/Alamo"
  },
  {
    "path": "Tests/Base.lproj/InfoPlist.strings",
    "chars": 45,
    "preview": "/* Localized versions of Info.plist keys */\n\n"
  },
  {
    "path": "Tests/FMDBTempDBTests.h",
    "chars": 378,
    "preview": "//\n//  FMDBTempDBTests.h\n//  fmdb\n//\n//  Created by Graham Dennis on 24/11/2013.\n//\n//\n\n#import <XCTest/XCTest.h>\n#impor"
  },
  {
    "path": "Tests/FMDBTempDBTests.m",
    "chars": 1454,
    "preview": "//\n//  FMDBTempDBTests.m\n//  fmdb\n//\n//  Created by Graham Dennis on 24/11/2013.\n//\n//\n\n#import \"FMDBTempDBTests.h\"\n\nsta"
  },
  {
    "path": "Tests/FMDatabaseAdditionsTests.m",
    "chars": 4298,
    "preview": "//\n//  FMDatabaseAdditionsTests.m\n//  fmdb\n//\n//  Created by Graham Dennis on 24/11/2013.\n//\n//\n\n#import <XCTest/XCTest."
  },
  {
    "path": "Tests/FMDatabaseFTS3Tests.m",
    "chars": 5423,
    "preview": "//\n//  FMDatabaseFTS3Tests.m\n//  fmdb\n//\n//  Created by Seaview Software on 8/26/14.\n//\n//\n\n#import \"FMDBTempDBTests.h\"\n"
  },
  {
    "path": "Tests/FMDatabaseFTS3WithModuleNameTests.m",
    "chars": 2827,
    "preview": "//\n//  FMDatabaseFTS3WithKeyTests.m\n//  fmdb\n//\n//  Created by Stephan Heilner on 1/21/15.\n//\n//\n\n#import \"FMDBTempDBTes"
  },
  {
    "path": "Tests/FMDatabasePoolTests.m",
    "chars": 13613,
    "preview": "//\n//  FMDatabasePoolTests.m\n//  fmdb\n//\n//  Created by Graham Dennis on 24/11/2013.\n//\n//\n\n#import <XCTest/XCTest.h>\n\n#"
  },
  {
    "path": "Tests/FMDatabaseQueueTests.m",
    "chars": 12515,
    "preview": "//\n//  FMDatabaseQueueTests.m\n//  fmdb\n//\n//  Created by Graham Dennis on 24/11/2013.\n//\n//\n\n#import <XCTest/XCTest.h>\n#"
  },
  {
    "path": "Tests/FMDatabaseTests.m",
    "chars": 71750,
    "preview": "//\n//  Tests.m\n//  Tests\n//\n//  Created by Graham Dennis on 24/11/2013.\n//\n//\n\n#import \"FMDBTempDBTests.h\"\n#import \"FMDa"
  },
  {
    "path": "Tests/FMResultSetTests.m",
    "chars": 4778,
    "preview": "//\n//  FMResultSetTests.m\n//  fmdb\n//\n//  Created by Muralidharan,Roshan on 10/6/14.\n//\n//\n\n#import \"FMDBTempDBTests.h\"\n"
  },
  {
    "path": "Tests/Schemes/Tests.xcscheme",
    "chars": 2996,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0500\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Tests/Tests-Info.plist",
    "chars": 674,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Tests/Tests-Prefix.pch",
    "chars": 234,
    "preview": "//\n//  Prefix header\n//\n//  The contents of this file are implicitly included at the beginning of every source file.\n//\n"
  },
  {
    "path": "Tests/en.lproj/InfoPlist.strings",
    "chars": 45,
    "preview": "/* Localized versions of Info.plist keys */\n\n"
  },
  {
    "path": "fmdb.1",
    "chars": 3114,
    "preview": ".\\\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples.\r\n.\\\"See Also:\r\n.\\\"man mdoc.samples for a"
  },
  {
    "path": "fmdb.xcodeproj/project.pbxproj",
    "chars": 79504,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "fmdb.xcodeproj/xcshareddata/xcschemes/FMDB MacOS.xcscheme",
    "chars": 2735,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1240\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "fmdb.xcodeproj/xcshareddata/xcschemes/FMDB iOS.xcscheme",
    "chars": 2729,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1240\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "fmdb.xcodeproj/xcshareddata/xcschemes/FMDB watchOS.xcscheme",
    "chars": 2379,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1240\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "fmdb.xcodeproj/xcshareddata/xcschemes/FMDB xrOS.xcscheme",
    "chars": 2385,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1510\"\n   version = \"1.7\">\n   <BuildAction\n      "
  },
  {
    "path": "privacy/PrivacyInfo.xcprivacy",
    "chars": 373,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "privacy/README.md",
    "chars": 9194,
    "preview": "\n# FMJS Privacy Practices\n\n\n<table id=\"definitions\" class=\"definitions\" border=\"0\">\n<tbody>\n<tr>\n<th colspan=\"2\" align=\""
  },
  {
    "path": "src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h",
    "chars": 982,
    "preview": "//\r\n//  FMDatabase+InMemoryOnDiskIO.h\r\n//  FMDB\r\n//\r\n//  Created by Peter Carr on 6/12/12.\r\n//\r\n//  I find there is a ma"
  },
  {
    "path": "src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m",
    "chars": 3482,
    "preview": "#import \"FMDatabase+InMemoryOnDiskIO.h\"\n#import <sqlite3.h>\n\n\n// https://sqlite.org/backup.html\nstatic\nint loadOrSaveDb("
  },
  {
    "path": "src/extra/fts3/FMDatabase+FTS3.h",
    "chars": 3519,
    "preview": "//\n//  FMDatabase+FTS3.h\n//  fmdb\n//\n//  Created by Andrew on 3/27/14.\n//  Copyright (c) 2014 Andrew Goodale. All rights"
  },
  {
    "path": "src/extra/fts3/FMDatabase+FTS3.m",
    "chars": 9886,
    "preview": "//\n//  FMDatabase+FTS3.m\n//  fmdb\n//\n//  Created by Andrew on 3/27/14.\n//  Copyright (c) 2014 Andrew Goodale. All rights"
  },
  {
    "path": "src/extra/fts3/FMTokenizers.h",
    "chars": 1737,
    "preview": "//\n//  FMTokenizers.h\n//  fmdb\n//\n//  Created by Andrew on 4/9/14.\n//  Copyright (c) 2014 Andrew Goodale. All rights res"
  },
  {
    "path": "src/extra/fts3/FMTokenizers.m",
    "chars": 3865,
    "preview": "//\n//  FMTokenizers.m\n//  fmdb\n//\n//  Created by Andrew on 4/9/14.\n//  Copyright (c) 2014 Andrew Goodale. All rights res"
  },
  {
    "path": "src/extra/fts3/fts3_tokenizer.h",
    "chars": 6383,
    "preview": "/*\n** 2006 July 10\n**\n** The author disclaims copyright to this source code.\n**\n****************************************"
  },
  {
    "path": "src/fmdb/FMDB.h",
    "chars": 306,
    "preview": "#import <Foundation/Foundation.h>\n\nFOUNDATION_EXPORT double FMDBVersionNumber;\nFOUNDATION_EXPORT const unsigned char FMD"
  },
  {
    "path": "src/fmdb/FMDatabase+SQLCipher.h",
    "chars": 4553,
    "preview": "//\n//  FMDatabase+SQLCipher.h\n//  FMDB\n//\n//  Created by Micah T. Moore on 9/29/25.\n//\n\n#import \"FMDatabase.h\"\n\nNS_ASSUM"
  },
  {
    "path": "src/fmdb/FMDatabase+SQLCipher.m",
    "chars": 4141,
    "preview": "//\n//  FMDatabase+SQLCipher.m\n//  FMDB\n//\n//  Created by Micah T. Moore on 9/29/25.\n//\n\n#import <Foundation/Foundation.h"
  },
  {
    "path": "src/fmdb/FMDatabase.h",
    "chars": 57324,
    "preview": "#import <Foundation/Foundation.h>\n#import \"FMResultSet.h\"\n#import \"FMDatabasePool.h\"\n\nNS_ASSUME_NONNULL_BEGIN\n\n#if ! __h"
  },
  {
    "path": "src/fmdb/FMDatabase.m",
    "chars": 45814,
    "preview": "#import \"FMDatabase.h\"\n#import <unistd.h>\n#import <objc/runtime.h>\n\n#if FMDB_SQLITE_STANDALONE\n#import <sqlite3/sqlite3."
  },
  {
    "path": "src/fmdb/FMDatabaseAdditions.h",
    "chars": 6644,
    "preview": "//\n//  FMDatabaseAdditions.h\n//  fmdb\n//\n//  Created by August Mueller on 10/30/05.\n//  Copyright 2005 Flying Meat Inc.."
  },
  {
    "path": "src/fmdb/FMDatabaseAdditions.m",
    "chars": 7762,
    "preview": "//\n//  FMDatabaseAdditions.m\n//  fmdb\n//\n//  Created by August Mueller on 10/30/05.\n//  Copyright 2005 Flying Meat Inc.."
  },
  {
    "path": "src/fmdb/FMDatabasePool.h",
    "chars": 8340,
    "preview": "//\n//  FMDatabasePool.h\n//  fmdb\n//\n//  Created by August Mueller on 6/22/11.\n//  Copyright 2011 Flying Meat Inc. All ri"
  },
  {
    "path": "src/fmdb/FMDatabasePool.m",
    "chars": 9490,
    "preview": "//\n//  FMDatabasePool.m\n//  fmdb\n//\n//  Created by August Mueller on 6/22/11.\n//  Copyright 2011 Flying Meat Inc. All ri"
  },
  {
    "path": "src/fmdb/FMDatabaseQueue.h",
    "chars": 10685,
    "preview": "//\n//  FMDatabaseQueue.h\n//  fmdb\n//\n//  Created by August Mueller on 6/22/11.\n//  Copyright 2011 Flying Meat Inc. All r"
  },
  {
    "path": "src/fmdb/FMDatabaseQueue.m",
    "chars": 9532,
    "preview": "//\n//  FMDatabaseQueue.m\n//  fmdb\n//\n//  Created by August Mueller on 6/22/11.\n//  Copyright 2011 Flying Meat Inc. All r"
  },
  {
    "path": "src/fmdb/FMResultSet.h",
    "chars": 14126,
    "preview": "#import <Foundation/Foundation.h>\n\nNS_ASSUME_NONNULL_BEGIN\n\n#ifndef __has_feature      // Optional.\n#define __has_featur"
  },
  {
    "path": "src/fmdb/FMResultSet.m",
    "chars": 14668,
    "preview": "#import \"FMResultSet.h\"\n#import \"FMDatabase.h\"\n#import <unistd.h>\n\n#if FMDB_SQLITE_STANDALONE\n#import <sqlite3/sqlite3.h"
  },
  {
    "path": "src/fmdb/Info.plist",
    "chars": 823,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "src/fmdb/info-xrOS.plist",
    "chars": 823,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "src/sample/fmdb_Prefix.pch",
    "chars": 150,
    "preview": "//\n// Prefix header for all source files of the 'fmdb' target in the 'fmdb' project.\n//\n\n#ifdef __OBJC__\n    #import <Fo"
  },
  {
    "path": "src/sample/main.m",
    "chars": 51807,
    "preview": "/* main.m\n *\n * Sample code to illustrate some of the basic FMDB classes and run them through their paces for illustrati"
  }
]

About this extraction

This page contains the full source code of the ccgus/fmdb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 55 files (545.7 KB), approximately 145.0k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!