Passing in nil resets the DB to use a default unix format. At the moment calling
this method with nil raises a warning in Xcode because it is marked as nonnull
because of the NS_ASSUME_NONNULL_BEGIN marcro.
This commit marks it correctly as _Nullable.
- If you call `[db close]`, it should reset `isOpen` to false;
- When you try to use `[queue database]`, it should not only check that the `FMDatabase` is non-nil, but also whether the database is open or not;
- Added `testClose` test to `FMDatabaseQueue` logic if the queue's `FMDatabase` was closed.
`FMDatabase` was previously checking `_db` to determine whether the opening of a database was successful or not. But that's not a valid assumption. If you had previously attempted to open a database and it failed, _it still returns a `sqlite*` pointer._ As the [`sqlite_open` documentation](http://sqlite.org/c3ref/open.html) says (emphasis added):
> _Whether or not an error occurs_ when it is opened, resources associated with the [database connection](http://sqlite.org/c3ref/sqlite3.html) handle should be released by passing it to [sqlite3_close()](http://sqlite.org/c3ref/close.html) when it is no longer required.
The reason this is important is that if an `openWithOptions` failed (e.g. opening from application support directory without `SQLITE_OPEN_CREATE` option), you want to be able to fix the problem (copy database from bundle to destination directory) and then try `openWithOptions` again. But because the `open` method was checking the existence of `_db` as evidence whether it was successfully opened or not, it was falsely reporting that the second attempt to reopen the database was successful, even though it didn't even try.
So, I created a test case that manifested this problem, `testOpenFailure`, and then modified `FMDatabase` accordingly. I could have made this a private property, but I saw no downside in exposing this in the public interface.
As a more general note, the reason I'm doing this is I've been seeing examples on the interwebs where people were (a) checking for existence of database; (b) if not there, copying one from bundle; and (c) then opening database. But this is not a practice that Apple advocates. One should really just (a) try to open (without create option) and if it failed, only then (b) copy from bundle and try again. As [they say](https://developer.apple.com/documentation/foundation/nsfilemanager/1415645-fileexistsatpath?language=objc):
> Note
>
> Attempting to predicate behavior based on the current state of the file system or a particular file on the file system is not recommended. Doing so can cause odd behavior or race conditions. It’s far better to attempt an operation (such as loading a file or creating a directory), check for errors, and handle those errors gracefully than it is to try to figure out ahead of time whether the operation will succeed. For more information on file-system race conditions, see [Race Conditions and Secure File Operations](https://developer.apple.com/library/content/documentation/Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html#//apple_ref/doc/uid/TP40002585) in [Secure Coding Guide](https://developer.apple.com/library/content/documentation/Security/Conceptual/SecureCodingGuide/Introduction.html#//apple_ref/doc/uid/TP40002415).
- Add explicit exclusive transaction code
- Add warning to beginTransaction and inTransaction calls that while it's currently exclusive, we reserve the right to change this in the future
- Add a few tests while I was here
- Eliminate discussion of `FMDatabaseQueue` no longer being optional in 2.7, because it actually is.
- Given new Apple guidance regarding "Documents" vs "Application Support", changed Swift example to use the latter instead of the former.