Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Zero-length NSData parameter binds to SQL null value #73

Open
snej opened this Issue · 7 comments

2 participants

@snej

A zero-length NSData object passed as a parameter binding for a SQL statement results in a SQL 'null' value, not a zero-length blob as expected.

    // Test case: This assertion fails.
    NSAssert(([_fmdb boolForQuery: @"SELECT ? not null", [NSData data]]), @"Empty data should not be null");

This is significant because SQL 'null' values have odd behavior with respect to things like equality comparison. Additionally, my app schema has a blob column where a null value means "there is no data" as distinct from "the data is empty", but this distinction gets lost when the data is saved.

I'm not sure why this happens. The binding is done by the following lines in -[FMDatabase bindObject:toColumn: inStatement::

else if ([obj isKindOfClass:[NSData class]]) {
    sqlite3_bind_blob(pStmt, idx, [obj bytes], (int)[obj length], SQLITE_STATIC);
}

It may be that sqlite3_bind_blob binds a null value if the data pointer is NULL or the length is zero, but that seems like strange behavior since there is already a sqlite3_bind_null function.

@snej

I'm working around this by substituting an empty NSString (@""). This results in a zero-length string value in the column, which when I fetch it with -dataForColumn: results in an empty NSData. (The actual data type stored will be string not blob, but I don't do anything that checks the actual stored data type, I just ask for it as a blob.)

@ccgus
Owner

It looks like [[NSData data] bytes] returns a pointer to 0x00.

In bindText (which sqlite3_bind_blob calls), there's a check to make sure the data (zData) != 0. I'm guessing this is what is going on- so the part that inserts the data is skipped over.

I suppose FMDatabase could insert an empty string in the case of an empty data. It would at least be consistent. Can you think of any reason this might suck?

@ccgus
Owner

The fix is easy:

else if ([obj isKindOfClass:[NSData class]]) {
        if (![obj bytes]) {
            // it's an empty NSData object, aka [NSData data].
            // in cases like this, we want it to behave just like an empty string.
            sqlite3_bind_blob(pStmt, idx, "", 0, SQLITE_STATIC);
        }
        else {
            sqlite3_bind_blob(pStmt, idx, [obj bytes], (int)[obj length], SQLITE_STATIC);
        }
    }

I'll bring it up on the mailing list to see if anyone has potential problems.

@snej
@snej
@ccgus
Owner

And considering [NSMutableData data] behaves exactly the way you'd like- I don't think it's going to be a problem to make this change. If anything, it'll bring consistency.

@snej

Here's a slightly faster/smaller fix. I've tested it and it works for me:

else if ([obj isKindOfClass:[NSData class]]) {
    const void* bytes = [obj bytes];
    if (!bytes) {
        // it's an empty NSData object, aka [NSData data].
        // Don't pass a NULL pointer, or sqlite will bind a SQL null instead of a blob.
        bytes = "";
    }
    sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);
}
@tomjadams tomjadams referenced this issue from a commit in mogeneration/fmdb
@ccgus 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 ccgus#73 for a discussion on this).  Thanks to Jens Alfke for pointing this out!
8666d8f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.