Skip to content

Commit

Permalink
Merge branch 'robertmryan-robertmryan/allow-rebinding'
Browse files Browse the repository at this point in the history
  • Loading branch information
ccgus committed May 7, 2020
2 parents 4859460 + 4009be0 commit abbaa01
Show file tree
Hide file tree
Showing 11 changed files with 466 additions and 364 deletions.
5 changes: 4 additions & 1 deletion CHANGES_AND_TODO_LIST.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ 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.

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.
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.
Expand Down
17 changes: 7 additions & 10 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
[![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: http://sqlite.org/
This is an Objective-C wrapper around [SQLite](https://sqlite.org/).

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

## Read the SQLite FAQ:
http://www.sqlite.org/faq.html
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: http://www.sqlite.org/docs.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.
Expand All @@ -20,9 +20,6 @@ Do you have an awesome idea that deserves to be in FMDB? You might consider pin

### CocoaPods

[![Dependency Status](https://www.versioneye.com/objective-c/fmdb/2.3/badge.svg?style=flat)](https://www.versioneye.com/objective-c/fmdb/2.3)
[![Reference Status](https://www.versioneye.com/objective-c/fmdb/reference_badge.svg?style=flat)](https://www.versioneye.com/objective-c/fmdb/references)

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:
Expand Down Expand Up @@ -75,7 +72,7 @@ $ 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").

## FMDB Class Reference:
http://ccgus.github.io/fmdb/html/index.html
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.
Expand Down Expand Up @@ -182,7 +179,7 @@ An `FMDatabase` is created with a path to a SQLite database file. This path can
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: http://www.sqlite.org/inmemorydb.html)
(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"];
Expand Down Expand Up @@ -436,7 +433,7 @@ 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`](http://ccgus.github.io/fmdb/html/Classes/FMDatabase.html) and [`FMResultSet`](http://ccgus.github.io/fmdb/html/Classes/FMResultSet.html) at a minimum. [`FMDatabaseAdditions`](http://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`](http://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.
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).

Expand Down
186 changes: 185 additions & 1 deletion Tests/FMDatabaseTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1124,7 +1124,12 @@ - (void)createCustomFunctions {
}

- (void)testVersionNumber {
XCTAssertEqual([FMDatabase FMDBVersion], 0x0276); // this is going to break everytime we bump it.
XCTAssertEqual([FMDatabase FMDBVersion], 0x0277); // this is going to break everytime we bump it.
}

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

- (void)testVersionStringAboveRequired {
Expand Down Expand Up @@ -1551,4 +1556,183 @@ - (void)testOpenFailure {
[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
2 changes: 1 addition & 1 deletion src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// support for this functionality via its "Backup" API. Here, we extend the FMBD wrapper
// to include this functionality.
//
// http://www.sqlite.org/backup.html
// https://sqlite.org/backup.html

#import "FMDatabase.h"

Expand Down
2 changes: 1 addition & 1 deletion src/extra/InMemoryOnDiskIO/FMDatabase+InMemoryOnDiskIO.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#import <sqlite3.h>


// http://www.sqlite.org/backup.html
// https://sqlite.org/backup.html
static
int loadOrSaveDb(sqlite3 *pInMemory, const char *zFilename, int isSave)
{
Expand Down
Loading

0 comments on commit abbaa01

Please sign in to comment.