davibe / Phonegap-SQLitePlugin

A phonegap plugin to open and use sqlite databases on iOS.
192 stars 90 forks source link

NULL Values not saved or returned properly #15

Open ecanas opened 12 years ago

ecanas commented 12 years ago

It seems that null values are either not being saved or returned properly. Instead of saving a null value in a field I get 0 or '' depending on the datatype.

Below is code from QuickConnect which has a native client, maybe you can use some of the code, I noticed they have a blob section which you can use on your code and they have a function called "sqlite3_bind_null" not sure what it's doing exactly but could fix the null value problem.

/* Copyright (c) 2008, 2009 Lee Barney Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

The end-user documentation included with the redistribution, if any, must include the following acknowledgment: "This product was created using the QuickConnect framework. http://www.quickconnectfamily.org", in the same place and form as other third-party acknowledgments. Alternately, this acknowledgment may appear in the software itself, in the same form and location as other such third-party acknowledgments.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

import "SQLiteDataAccess.h"

import "QCParameter.h"

import

static NSString *dbSemaphore = @"dblock";

// Private interface for AppDelegate - internal only methods. @interface SQLiteDataAccess (Private)

@end

@implementation SQLiteDataAccess

}

// internal bind methods

@end

ns-1m commented 12 years ago

This is the function,

             else if(type == SQLITE_BLOB){
                    //NSLog(@"blob");
                    NSData *aData = [[NSData alloc]dataWithBytes:sqlite3_column_blob(statement, colNum) length:sqlite3_column_bytes(statement,colNum)];
                    [row addObject:aData];
                    [aData autorelease];
                }
                else{//SQLITE_NULL
                    //NSLog(@"column type: %d",type);
                    [row addObject:@"null"];
                }
ns-1m commented 12 years ago

case SQLITE_BLOB: columnValue = [NSString stringWithUTF8String:(char *)sqlite3_column_blob(statement, i)]; columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)]; [entry setObject:columnValue forKey:columnName];

                        break;
coomsie commented 12 years ago

yip, missing something to do for null and blob


switch (column_type) {
                        case SQLITE_INTEGER:
                            columnValue = [NSNumber numberWithDouble: sqlite3_column_double(statement, i)];
                            columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
                            [entry setObject:columnValue forKey:columnName];
                            break;
                        case SQLITE_TEXT:
                            columnValue = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, i)];
                            columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
                            [entry setObject:columnValue forKey:columnName];
                            break;
                        case SQLITE_BLOB:

                            break;
                        case SQLITE_FLOAT:
                            columnValue = [NSNumber numberWithFloat: sqlite3_column_double(statement, i)];
                            columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
                            [entry setObject:columnValue forKey:columnName];                            
                            break;
                        case SQLITE_NULL:
                            break;
                    }
                    i++;

ns-1m comments might work ...

ecanas commented 12 years ago

The problem is also when entering data it will not enter as null. Was able add the option for case SQLITE_NULL and SQLITE_BLOB as per my code above, however that's only part of the problem since data is not saved as null.

marcucio commented 12 years ago

This is a fix I came up with:

https://github.com/marcucio/Phonegap-SQLitePlugin/commit/8660ba16fd796ad764e0c8a1eda906c47dbf8b57

maybe it fixes your issue

ecanas commented 12 years ago

Looks like will work.. will give it a go. Can you also update the project and add the corresponding code for the case SQLITE_BLOB and SQLITE_NULL.. I updated my version with the following..

case SQLITE_ROW: i = 0; entry = [NSMutableDictionary dictionaryWithCapacity:0]; count = sqlite3_column_count(statement); NSMutableArray *row = [[NSMutableArray alloc] initWithCapacity:count];

            while (i < count) {
                column_type = sqlite3_column_type(statement, i);
                switch (column_type) {
                    case SQLITE_INTEGER:
                        //columnValue = [NSNumber numberWithInt: sqlite3_column_int(statement, i)];
                        //columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
                        //[entry setObject:columnValue forKey:columnName];
                        [row addObject:[NSNumber numberWithInt: sqlite3_column_int(statement, i)]];

                        break;
                    case SQLITE_TEXT:
                        //columnValue = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, i)];
                        //columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
                        //[entry setObject:columnValue forKey:columnName];
                        [row addObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, i)]];
                        break;
                    case SQLITE_BLOB:

                        //NSData *aData = [[NSData alloc]dataWithBytes:sqlite3_column_blob(statement, i) length:sqlite3_column_bytes(statement,i)];
                        [row addObject:[NSData dataWithBytes:sqlite3_column_blob(statement, i) length:sqlite3_column_bytes(statement,i)]];
                        break;
                    case SQLITE_FLOAT:
                        //columnValue = [NSNumber numberWithFloat: sqlite3_column_double(statement, i)];
                        //columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
                        //[entry setObject:columnValue forKey:columnName]; 
                        [row addObject:[NSNumber numberWithFloat: sqlite3_column_double(statement, i)]];
                        break;
                    //case SQLITE_NULL:
                    default:
                        [row addObject:@"null"];

                        break;
                }
                i++;

            }
            //[resultRows addObject:entry];
            [resultRows addObject:row];

            [row release];
            row = nil;
            break;
marcucio commented 12 years ago

@ecanas your code seems not to work for me, please post a diff or the entire file, maybe i missed something

ecanas commented 12 years ago

Here is the complete code, with the latest change you made. Will see if tomorrow I can post just the difference.. but the only changes are on the line "NSMutableArray *row = [[NSMutableArray alloc] initWithCapacity:count];"

/*

import "PGSQLitePlugin.h"

@implementation PGSQLitePlugin

@synthesize openDBs; @synthesize appDocsPath;

-(PGPlugin) initWithWebView:(UIWebView)theWebView { self = (PGSQLitePlugin*)[super initWithWebView:theWebView]; if (self) { openDBs = [NSMutableDictionary dictionaryWithCapacity:0]; [openDBs retain];

    PGFile* pgFile = [[self appDelegate] getCommandInstance: @"com.phonegap.file"];
    NSString *docs = [pgFile appDocsPath];
    [self setAppDocsPath:docs];

}
return self;

}

-(void) respond: (id)cb withString:(NSString )str withType:(NSString *)type { if (cb != NULL) { NSString jsString = [NSString stringWithFormat:@"PGSQLitePlugin.handleCallback('%@', '%@', %@);", cb, type, str ]; [self writeJavascript:jsString]; } }

-(id) getDBPath:(id)dbFile { if (dbFile == NULL) { return NULL; } NSString *dbPath = [NSString stringWithFormat:@"%@/%@", appDocsPath, dbFile]; return dbPath; }

-(void) open: (NSMutableArray)arguments withDict:(NSMutableDictionary)options { NSString callback = [options objectForKey:@"callback"]; NSString dbPath = [self getDBPath:[options objectForKey:@"path"]];

if (dbPath == NULL) {
    [self respond:callback withString:@"{ message: 'You must specify database path' }" withType:@"error"];
    return;
}

sqlite3 *db;
const char *path = [dbPath UTF8String];

if (sqlite3_open(path, &db) != SQLITE_OK) {
    [self respond:callback withString:@"{ message: 'Unable to open DB' }" withType:@"error"];
    return;
}

NSValue *dbPointer = [NSValue valueWithPointer:db];
[openDBs setObject:dbPointer forKey: dbPath];
[self respond:callback withString: @"{ message: 'Database opened' }" withType:@"success"];

}

-(void) backgroundExecuteSqlBatch: (NSMutableArray)arguments withDict:(NSMutableDictionary)options { [self performSelector:@selector(_executeSqlBatch:) withObject:options afterDelay:0.001]; }

-(void) backgroundExecuteSql: (NSMutableArray)arguments withDict:(NSMutableDictionary)options { [self performSelector:@selector(_executeSql:) withObject:options afterDelay:0.001]; }

-(void) _executeSqlBatch:(NSMutableDictionary*)options { [self executeSqlBatch:NULL withDict:options]; }

-(void) _executeSql:(NSMutableDictionary*)options { [self executeSql:NULL withDict:options]; }

-(void) executeSqlBatch: (NSMutableArray)arguments withDict:(NSMutableDictionary)options { NSMutableArray executes = [options objectForKey:@"executes"]; for (NSMutableDictionary dict in executes) { [self executeSql:NULL withDict:dict]; } }

-(void) executeSql: (NSMutableArray)arguments withDict:(NSMutableDictionary)options { NSString callback = [options objectForKey:@"callback"]; NSString dbPath = [self getDBPath:[options objectForKey:@"path"]]; NSMutableArray query_parts = [options objectForKey:@"query"]; NSString query = [query_parts objectAtIndex:0];

if (dbPath == NULL) {
    [self respond:callback withString:@"{ message: 'You must specify database path' }" withType:@"error"];
    return;
}
if (query == NULL) {
    [self respond:callback withString:@"{ message: 'You must specify a query to execute' }" withType:@"error"];
    return;
}

NSValue *dbPointer = [openDBs objectForKey:dbPath];
if (dbPointer == NULL) {
    [self respond:callback withString:@"{ message: 'No such database, you must open it first' }" withType:@"error"];
    return;
}
sqlite3 *db = [dbPointer pointerValue];

const char *sql_stmt = [query UTF8String];
char *errMsg = NULL;
sqlite3_stmt *statement;
int result, i, column_type, count;
int previousRowsAffected, nowRowsAffected, diffRowsAffected;
long long previousInsertId, nowInsertId;
BOOL keepGoing = YES;
BOOL hasInsertId;
NSMutableDictionary *resultSet = [NSMutableDictionary dictionaryWithCapacity:0];
NSMutableArray *resultRows = [NSMutableArray arrayWithCapacity:0];
NSMutableDictionary *entry;
//NSObject *columnValue;
//NSString *columnName;
NSString *bindval;
NSObject *insertId;
NSObject *rowsAffected;

hasInsertId = NO;
previousRowsAffected = sqlite3_total_changes(db);
previousInsertId = sqlite3_last_insert_rowid(db);

if (sqlite3_prepare_v2(db, sql_stmt, -1, &statement, NULL) != SQLITE_OK) {
    errMsg = (char *) sqlite3_errmsg (db);
    keepGoing = NO;
} else {
    for (int b = 1; b < query_parts.count; b++) {
        bindval = [NSString stringWithFormat:@"%@", [query_parts objectAtIndex:b]];
        if([bindval isEqualToString:@"<null>"])
            bindval = NULL;
        sqlite3_bind_text(statement, b, [bindval UTF8String], -1, SQLITE_TRANSIENT);
    }
}

while (keepGoing) {
    result = sqlite3_step (statement);
    switch (result) {

        case SQLITE_ROW:
            i = 0;
            entry = [NSMutableDictionary dictionaryWithCapacity:0];
            count = sqlite3_column_count(statement);
            NSMutableArray *row = [[NSMutableArray alloc] initWithCapacity:count];

            while (i < count) {
                column_type = sqlite3_column_type(statement, i);
                switch (column_type) {
                    case SQLITE_INTEGER:
                        [row addObject:[NSNumber numberWithInt: sqlite3_column_int(statement, i)]];
                        break;
                    case SQLITE_TEXT:
                        [row addObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, i)]];
                        break;
                    case SQLITE_BLOB:
                        [row addObject:[NSData dataWithBytes:sqlite3_column_blob(statement, i) length:sqlite3_column_bytes(statement,i)]];
                        break;
                    case SQLITE_FLOAT:
                        [row addObject:[NSNumber numberWithFloat: sqlite3_column_double(statement, i)]];
                        break;
                    case SQLITE_NULL:
                        [row addObject:@"null"];
                        break;
                    default:
                        [row addObject:@""];
                        break;
                }
                i++;

            }

            [resultRows addObject:row];
            [row release];
            row = nil;
            break;
        case SQLITE_DONE:
            nowRowsAffected = sqlite3_total_changes(db);
            diffRowsAffected = nowRowsAffected - previousRowsAffected;
            rowsAffected = [NSNumber numberWithInt:diffRowsAffected];
            nowInsertId = sqlite3_last_insert_rowid(db);
            if (previousInsertId != nowInsertId) {
                hasInsertId = YES;
                insertId = [NSNumber numberWithLongLong:sqlite3_last_insert_rowid(db)];
            }
            keepGoing = NO;
            break;

        default:
            errMsg = "SQL statement error";
            keepGoing = NO;
    }
}

sqlite3_finalize (statement);

if (errMsg != NULL) {
    [self respond:callback withString:[NSString stringWithFormat:@"{ message: 'SQL statement error : %s' }", errMsg] withType:@"error"];
} else {
    [resultSet setObject:resultRows forKey:@"rows"];
    [resultSet setObject:rowsAffected forKey:@"rowsAffected"];
    if (hasInsertId) {
        [resultSet setObject:insertId forKey:@"insertId"];
    }
    [self respond:callback withString:[resultSet JSONRepresentation] withType:@"success"];
}

}

-(void) close: (NSMutableArray)arguments withDict:(NSMutableDictionary)options { NSString callback = [options objectForKey:@"callback"]; NSString dbPath = [self getDBPath:[options objectForKey:@"path"]]; if (dbPath == NULL) { [self respond:callback withString:@"{ message: 'You must specify database path' }" withType:@"error"]; return; }

NSValue *val = [openDBs objectForKey:dbPath];
sqlite3 *db = [val pointerValue];
if (db == NULL) {
    [self respond:callback withString: @"{ message: 'Specified db was not open' }" withType:@"error"];
}
sqlite3_close (db);
[self respond:callback withString: @"{ message: 'db closed' }" withType:@"success"];

}

-(void)dealloc { int i; NSArray keys = [openDBs allKeys]; NSValue pointer; NSString key; sqlite3 db;

/* close db the user forgot */ 
for (i=0; i<[keys count]; i++) {
    key = [keys objectAtIndex:i];
    pointer = [openDBs objectForKey:key];
    db = [pointer pointerValue];
    sqlite3_close (db);
}

[openDBs release];
[appDocsPath release];
[super dealloc];

}

@end

ns-1m commented 12 years ago

I try to use this patch above Cordova 1.5, app is crashing.

ecanas commented 12 years ago

marcucio, I made a few other changes now it's 100% working with nulls. Where can I post the 2 updated files.

marcucio commented 12 years ago

If you are set up for github you can add it to your account if not you can use gist:

https://gist.github.com/

ecanas commented 12 years ago

Ok..used gist.. here is the link https://gist.github.com/2340433