ccgus / fmdb

A Cocoa / Objective-C wrapper around SQLite
Other
13.84k stars 2.76k forks source link

Async updates and queries in 3.0 #487

Open ccgus opened 8 years ago

ccgus commented 8 years ago

So, here's the official discussion for doing async stuff in FMDB 3.

There's a couple of ways to do this. The easiest is to add async operations to FMDatabaseQueue, similar to: https://github.com/ccgus/fmdb/pull/469

I'm all for this.

I've also experimented a little bit with adding async stuff directly to FMDatabase. And after using it for a little bit, I think while it's neat, it's probably the wrong way to do things.

For the heck of it, here's what it kind of looks like at the moment:

        NSError *err = nil;
        FMDBQuickCheck([[db u:@"create table test3 (a text, b text, c integer, d double, e double)"] executeUpdate:&err]);
        FMDBQuickCheck(err == nil);

        __block int allGoodCount = NO;

        [[db u:@"create table test3 (a text, b text, c integer, d double, e double)"] executeUpdateInBackground:^(NSError *error) {
            FMDBQuickCheck(error != nil);
            allGoodCount++;
        }];

        dispatch_apply(20, dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH), ^(size_t i) {

            NSLog(@"idx: %ld", i);

            [[db u:@"insert into test3 (a, b, c, d, e) values (?, ?, ?, ?, ?)" ,
              @"hi'", // look!  I put in a ', and I'm not escaping it!
              [NSString stringWithFormat:@"number %ld", i],
              @(i),
              [NSDate date],
              [NSNumber numberWithFloat:2.2f]] executeUpdateInBackground:^(NSError *error) {

                FMDBQuickCheck(error == nil);
                allGoodCount++;
            }];
        });```

I like the idea of separating the query from the parsing- something about it feels nice and clean to me. But, there's really no reason to do it if we just stick the async stuff in the queue.

@robertmryan  - any thoughts?
ccgus commented 8 years ago

Here's another way to do things, which is currently in the three branch:

[queue inDatabase:^(FMDatabase *adb) {
    [adb setShouldCacheStatements:YES];
    [adb setCrashOnErrors:YES];
    [adb executeUpdate:@"create table threeasync (foo text)"];
}];

static int32_t updateCount = 0;
static int32_t submitCount = 0;

dispatch_apply(40, dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^(size_t idx) {

    [queue inBackground:^(FMDatabase *adb) {

        if (idx % 2 == 0) {
            usleep(50); // artificial delay.
        }

        if (idx % 3 == 0) {
            usleep(100); // another artificial delay.
        }

        [adb executeUpdate:@"insert into threeasync (foo) values (?)", [NSString stringWithFormat:@"%ld", idx]];

        FMDBQuickCheck(![adb hadError]);

        OSAtomicIncrement32(&updateCount);

    }];

    OSAtomicIncrement32(&submitCount);
});

NSLog(@"submitCount: %d", submitCount);

FMDBQuickCheck(submitCount == 40);

// This will block till all the above async stuff is done.
[queue inDatabase:^(FMDatabase *adb) {
    NSLog(@"done?");
    FMDBQuickCheck(updateCount == 40);
}];

This is more like I advocate above.

groue commented 8 years ago

Hello @ccgus,

I'm answering a request for async access to the database in https://github.com/groue/GRDB.swift/issues/76. In my defense of synchronous accesses, among other arguments, I had to point that your FMDB has been synchronous for years.

I have nothing against asynchronous accesses, of course, but I was wondering why you were adding async methods in FMDB 3.

ccgus commented 8 years ago

I'm adding it because folks keep on asking for it.

groue commented 8 years ago

Thanks for the explanation 😄 I won't feel too bad resisting a little, since I have much less pressure than you do ;-)