How to Enforce Constraints

23 views
Skip to first unread message

kon...@tomodyne.de

unread,
Aug 22, 2015, 4:42:02 AM8/22/15
to YapDatabase
Dear Robbie,

I'm wondering how to implement inter-object constraints? For instance, we may decide that, having a collection "users", a user name must be unique within its collection.

Robbie Hanson

unread,
Sep 9, 2015, 12:50:08 PM9/9/15
to yapda...@googlegroups.com
Most likely what you’re looking for is the Hooks extension.

You could add code for the willInsert hook to make that check.

-Robbie Hanson



On August 22, 2015 at 1:42:03 AM, kon...@tomodyne.de (kon...@tomodyne.de) wrote:

Dear Robbie,

I'm wondering how to implement inter-object constraints? For instance, we may decide that, having a collection "users", a user name must be unique within its collection.

--
You received this message because you are subscribed to the Google Groups "YapDatabase" group.
To unsubscribe from this group and stop receiving emails from it, send an email to yapdatabase...@googlegroups.com.
To post to this group, send email to yapda...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

kon...@tomodyne.de

unread,
Sep 23, 2015, 10:14:47 AM9/23/15
to YapDatabase
Yes, but as far as I can see the objects are saved in any case, or does the hooks extension give me a way to tell the db that (if a certain constraint is not met) the transaction should fail with an error? 

Robbie Hanson

unread,
Sep 25, 2015, 6:27:52 PM9/25/15
to yapda...@googlegroups.com
You’ve given me something to think about here...

YapDatabase’s policy concerning object constraints has thus far been based on some simple concepts.

First, there are basic policy enforcements one can do at the object level itself. For example, if you have a “percentValue” property, one might enforce a min/max of 0.0/1.0 by implementing the setPercentValue method, and making sure it doesn’t go out-of-range. (This could be done by automatically enforcing the floor/ceiling, or perhaps by throwing an exception.)

There is also YapDatabase.objectPreSanitizer. This is a block that allows you to perform any cleanup on an object *BEFORE* it gets serialized. In the docs (wiki pages), I’ve often given examples of using the objectPreSanitizer in order to make an object immutable. (This is done by flipping an “isImmutable” bit, and then using some neat KVO tricks to ensure an immutable object isn’t ever modified.)

The objectPreSanitizer even allows you to return a different object than the one that was given.

typedef id __nonnull (^YapDatabasePreSanitizer)(NSString *collection, NSString *key, id obj);



The PreSanitizer has been around in YapDatabase for a long time now. However, the Hooks are new. In fact, I just recently redid the Hooks API because I wasn’t happy with it. And I haven’t even written up a wiki page for it either.

> we may decide that, having a collection "users", a user name must be unique within its collection.

This is a very different kind of constraint. One that requires access to a YapDatabaseReadTransaction in order to check it. And as such, isn’t supported (currently) by the PreSanitizer (which lacks a transaction parameter).

> does the hooks extension give me a way to tell the db that (if a certain constraint is not met)
> the transaction should fail with an error? 

Great question !

A readWriteTransaction does have a “rollback” method, that will cause the transaction to not perform the commit. However... is this really the solution we want?

If a readWriteTransaction randomly fails in the middle of your running process, you may very well have a “corrupt” database. Not in the “corruption of the sqlite file” sense, but rather in the “what you think was saved to the database actually was not saved” sense. And I don’t see any way to recover from this. It’s very very dangerous. Metaphorically speaking, we’d be handing someone a gun named “constraints” which is loaded, safety off, and aimed directly at their feet.

So I’m thinking that there should be a well-documented place to perform these constraints checks. And, in order to do so, it will need access to a transaction. And the constraint-checking code should be able to throw an exception if a constraint fails (and cannot be fixed). The YapDatabase internals should properly be able to handle the exception. As in, it shouldn’t end up in a place where a sqlite3_stmt is in a bad state or anything. And the exception can bubble up to the caller to decide what do do about it. Something like this:

__block BOOL invalidUser = NO;
[databaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction){

    // other transaction stuff

    @try {
        [transaction setObject:user forKey:user.uuid inCollection:@“users”];
    } @catch (NSException *exception) {
        invalidUser = YES;
        [transaction rollback];
        return; // from block
    }

    // more transaction stuff
}];

if (invalidUser) {
    // Proper handling code here...
}

I think this is a lot more explicit. It is, after all, your code that gets to decide when to throw the exception. Seems only fitting that your code would be the proper place to catch the exception too.

Let me know your thoughts. I’m still rolling this around in my head.

-Robbie Hanson


Reply all
Reply to author
Forward
0 new messages