123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- /*
- * Copyright (c) 2012-present Christopher J. Brody (aka Chris Brody)
- * Copyright (C) 2011 Davide Bertola
- *
- * This library is available under the terms of the MIT License (2008).
- * See http://opensource.org/licenses/alphabetical for full text.
- */
-
- #import "SQLitePlugin.h"
-
- #import "sqlite3.h"
-
- // Defines Macro to only log lines when in DEBUG mode
- #ifdef DEBUG
- # define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
- #else
- # define DLog(...)
- #endif
-
- #if !__has_feature(objc_arc)
- # error "Missing objc_arc feature"
- #endif
-
- // CustomPSPDFThreadSafeMutableDictionary interface copied from
- // CustomPSPDFThreadSafeMutableDictionary.m:
- //
- // Dictionary-Subclasss whose primitive operations are thread safe.
- @interface CustomPSPDFThreadSafeMutableDictionary : NSMutableDictionary
- @end
-
- @implementation SQLitePlugin
-
- @synthesize openDBs;
- @synthesize appDBPaths;
-
- -(void)pluginInitialize
- {
- DLog(@"Initializing SQLitePlugin");
-
- {
- openDBs = [CustomPSPDFThreadSafeMutableDictionary dictionaryWithCapacity:0];
- appDBPaths = [NSMutableDictionary dictionaryWithCapacity:0];
-
- NSString *docs = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
- DLog(@"Detected docs path: %@", docs);
- [appDBPaths setObject: docs forKey:@"docs"];
-
- NSString *libs = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
- DLog(@"Detected Library path: %@", libs);
- [appDBPaths setObject: libs forKey:@"libs"];
-
- NSString *nosync = [libs stringByAppendingPathComponent:@"LocalDatabase"];
- NSError *err;
-
- // GENERAL NOTE: no `nosync` directory path entry to be added
- // to appDBPaths map in case of any isses creating the
- // required directory or setting the resource value for
- // NSURLIsExcludedFromBackupKey
- //
- // This is to avoid potential for issue raised here:
- // https://github.com/xpbrew/cordova-sqlite-storage/issues/907
-
- if ([[NSFileManager defaultManager] fileExistsAtPath: nosync])
- {
- DLog(@"no cloud sync directory already exists at path: %@", nosync);
- }
- else
- {
- if ([[NSFileManager defaultManager] createDirectoryAtPath: nosync withIntermediateDirectories:NO attributes: nil error:&err])
- {
- DLog(@"no cloud sync directory created with path: %@", nosync);
- }
- else
- {
- // STOP HERE & LOG WITH INTERNAL PLUGIN ERROR:
- NSLog(@"INTERNAL PLUGIN ERROR: could not create no cloud sync directory at path: %@", nosync);
- return;
- }
- }
-
- {
- {
- // Set the resource value for NSURLIsExcludedFromBackupKey
- NSURL *nosyncURL = [ NSURL fileURLWithPath: nosync];
- if (![nosyncURL setResourceValue: [NSNumber numberWithBool: YES] forKey: NSURLIsExcludedFromBackupKey error: &err])
- {
- // STOP HERE & LOG WITH INTERNAL PLUGIN ERROR:
- NSLog(@"INTERNAL PLUGIN ERROR: error setting nobackup flag in LocalDatabase directory: %@", err);
- return;
- }
-
- // now ready to add `nosync` entry to appDBPaths:
- DLog(@"no cloud sync at path: %@", nosync);
- [appDBPaths setObject: nosync forKey:@"nosync"];
- }
- }
- }
- }
-
- -(id) getDBPath:(NSString *)dbFile at:(NSString *)atkey {
- if (dbFile == NULL) {
- return NULL;
- }
-
- NSString *dbdir = [appDBPaths objectForKey:atkey];
- if (dbdir == NULL) {
- // INTERNAL PLUGIN ERROR:
- return NULL;
- }
-
- NSString *dbPath = [dbdir stringByAppendingPathComponent: dbFile];
- return dbPath;
- }
-
- -(void)echoStringValue: (CDVInvokedUrlCommand*)command
- {
- CDVPluginResult * pluginResult = nil;
- NSMutableDictionary * options = [command.arguments objectAtIndex:0];
-
- NSString * string_value = [options objectForKey:@"value"];
-
- DLog(@"echo string value: %@", string_value);
-
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:string_value];
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
- }
-
- -(void)open: (CDVInvokedUrlCommand*)command
- {
- [self.commandDelegate runInBackground:^{
- [self openNow: command];
- }];
- }
-
- -(void)openNow: (CDVInvokedUrlCommand*)command
- {
- CDVPluginResult* pluginResult = nil;
- NSMutableDictionary *options = [command.arguments objectAtIndex:0];
-
- NSString *dbfilename = [options objectForKey:@"name"];
-
- NSString *dblocation = [options objectForKey:@"dblocation"];
- if (dblocation == NULL) dblocation = @"docs";
- // DLog(@"using db location: %@", dblocation);
-
- NSString *dbname = [self getDBPath:dbfilename at:dblocation];
-
- if (!sqlite3_threadsafe()) {
- // INTERNAL PLUGIN ERROR:
- NSLog(@"INTERNAL PLUGIN ERROR: sqlite3_threadsafe() returns false value");
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"INTERNAL PLUGIN ERROR: sqlite3_threadsafe() returns false value"];
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
- return;
- } else if (dbname == NULL) {
- // INTERNAL PLUGIN ERROR - NOT EXPECTED:
- NSLog(@"INTERNAL PLUGIN ERROR (NOT EXPECTED): open with database name missing");
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"INTERNAL PLUGIN ERROR: open with database name missing"];
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
- return;
- } else {
- NSValue *dbPointer = [openDBs objectForKey:dbfilename];
-
- if (dbPointer != NULL) {
- // NO LONGER EXPECTED due to BUG 666 workaround solution:
- // DLog(@"Reusing existing database connection for db name %@", dbfilename);
- NSLog(@"INTERNAL PLUGIN ERROR: database already open for db name: %@ (db file name: %@)", dbname, dbfilename);
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"INTERNAL PLUGIN ERROR: database already open"];
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
- return;
- }
-
- @synchronized(self) {
- const char *name = [dbname UTF8String];
- sqlite3 *db;
-
- DLog(@"open full db path: %@", dbname);
-
- if (sqlite3_open(name, &db) != SQLITE_OK) {
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Unable to open DB"];
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
- return;
- } else {
- sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL);
-
- // for SQLCipher version:
- // NSString *dbkey = [options objectForKey:@"key"];
- // const char *key = NULL;
- // if (dbkey != NULL) key = [dbkey UTF8String];
- // if (key != NULL) sqlite3_key(db, key, strlen(key));
-
- // Attempt to read the SQLite master table [to support SQLCipher version]:
- if(sqlite3_exec(db, (const char*)"SELECT count(*) FROM sqlite_master;", NULL, NULL, NULL) == SQLITE_OK) {
- dbPointer = [NSValue valueWithPointer:db];
- [openDBs setObject: dbPointer forKey: dbfilename];
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"Database opened"];
- } else {
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Unable to open DB with key"];
- // XXX TODO: close the db handle & [perhaps] remove from openDBs!!
- }
- }
- }
- }
-
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
-
- // DLog(@"open cb finished ok");
- }
-
- -(void) close: (CDVInvokedUrlCommand*)command
- {
- [self.commandDelegate runInBackground:^{
- [self closeNow: command];
- }];
- }
-
- -(void)closeNow: (CDVInvokedUrlCommand*)command
- {
- CDVPluginResult* pluginResult = nil;
- NSMutableDictionary *options = [command.arguments objectAtIndex:0];
-
- NSString *dbFileName = [options objectForKey:@"path"];
-
- if (dbFileName == NULL) {
- // Should not happen:
- DLog(@"No db name specified for close");
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: You must specify database path"];
- } else {
- NSValue *val = [openDBs objectForKey:dbFileName];
- sqlite3 *db = [val pointerValue];
-
- if (db == NULL) {
- // Should not happen:
- DLog(@"close: db name was not open: %@", dbFileName);
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: Specified db was not open"];
- }
- else {
- DLog(@"close db name: %@", dbFileName);
- sqlite3_close (db);
- [openDBs removeObjectForKey:dbFileName];
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"DB closed"];
- }
- }
-
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
- }
-
- -(void) delete: (CDVInvokedUrlCommand*)command
- {
- [self.commandDelegate runInBackground:^{
- [self deleteNow: command];
- }];
- }
-
- -(void)deleteNow: (CDVInvokedUrlCommand*)command
- {
- CDVPluginResult* pluginResult = nil;
- NSMutableDictionary *options = [command.arguments objectAtIndex:0];
-
- NSString *dbFileName = [options objectForKey:@"path"];
-
- NSString *dblocation = [options objectForKey:@"dblocation"];
- if (dblocation == NULL) dblocation = @"docs";
-
- if (dbFileName==NULL) {
- // Should not happen:
- DLog(@"No db name specified for delete");
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: You must specify database path"];
- } else {
- NSString *dbPath = [self getDBPath:dbFileName at:dblocation];
-
- if (dbPath == NULL) {
- // INTERNAL PLUGIN ERROR - NOT EXPECTED:
- NSLog(@"INTERNAL PLUGIN ERROR (NOT EXPECTED): delete with no valid database path found");
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString: @"INTERNAL PLUGIN ERROR: delete with no valid database path found"];
- [self.commandDelegate sendPluginResult:pluginResult callbackId: command.callbackId];
- return;
- }
-
- if ([[NSFileManager defaultManager]fileExistsAtPath:dbPath]) {
- DLog(@"delete full db path: %@", dbPath);
- [[NSFileManager defaultManager]removeItemAtPath:dbPath error:nil];
- [openDBs removeObjectForKey:dbFileName];
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"DB deleted"];
- } else {
- DLog(@"delete: db was not found: %@", dbPath);
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"The database does not exist on that path"];
- }
- }
- [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
- }
-
-
- -(void) backgroundExecuteSqlBatch: (CDVInvokedUrlCommand*)command
- {
- [self.commandDelegate runInBackground:^{
- [self executeSqlBatchNow: command];
- }];
- }
-
- -(void) executeSqlBatchNow: (CDVInvokedUrlCommand*)command
- {
- NSMutableDictionary *options = [command.arguments objectAtIndex:0];
- NSMutableArray *results = [NSMutableArray arrayWithCapacity:0];
- NSMutableDictionary *dbargs = [options objectForKey:@"dbargs"];
- NSMutableArray *executes = [options objectForKey:@"executes"];
-
- CDVPluginResult* pluginResult;
-
- {
- for (NSMutableDictionary *dict in executes) {
- CDVPluginResult *result = [self executeSqlWithDict:dict andArgs:dbargs];
- if ([result.status intValue] == CDVCommandStatus_ERROR) {
- /* add error with result.message: */
- NSMutableDictionary *r = [NSMutableDictionary dictionaryWithCapacity:0];
- [r setObject:@"error" forKey:@"type"];
- [r setObject:result.message forKey:@"error"];
- [r setObject:result.message forKey:@"result"];
- [results addObject: r];
- } else {
- /* add result with result.message: */
- NSMutableDictionary *r = [NSMutableDictionary dictionaryWithCapacity:0];
- [r setObject:@"success" forKey:@"type"];
- [r setObject:result.message forKey:@"result"];
- [results addObject: r];
- }
- }
-
- pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:results];
- }
-
- [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
- }
-
- -(void) executeSql: (CDVInvokedUrlCommand*)command
- {
- NSMutableDictionary *options = [command.arguments objectAtIndex:0];
- NSMutableDictionary *dbargs = [options objectForKey:@"dbargs"];
- NSMutableDictionary *ex = [options objectForKey:@"ex"];
-
- CDVPluginResult * pluginResult = [self executeSqlWithDict: ex andArgs: dbargs];
-
- [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
- }
-
- -(CDVPluginResult*) executeSqlWithDict: (NSMutableDictionary*)options andArgs: (NSMutableDictionary*)dbargs
- {
- NSString *dbFileName = [dbargs objectForKey:@"dbname"];
- if (dbFileName == NULL) {
- return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: You must specify database path"];
- }
-
- NSMutableArray *params = [options objectForKey:@"params"]; // optional
-
- NSValue *dbPointer = [openDBs objectForKey:dbFileName];
- if (dbPointer == NULL) {
- return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: No such database, you must open it first"];
- }
- sqlite3 *db = [dbPointer pointerValue];
-
- NSString *sql = [options objectForKey:@"sql"];
- if (sql == NULL) {
- return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"INTERNAL PLUGIN ERROR: You must specify a sql query to execute"];
- }
-
- const char *sql_stmt = [sql UTF8String];
- NSDictionary *error = nil;
- 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;
- 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) {
- error = [SQLitePlugin captureSQLiteErrorFromDb:db];
- keepGoing = NO;
- } else if (params != NULL) {
- for (int b = 0; b < params.count; b++) {
- result = [self bindStatement:statement withArg:[params objectAtIndex:b] atIndex:(b+1)];
- if (result != SQLITE_OK) {
- error = [SQLitePlugin captureSQLiteErrorFromDb:db];
- keepGoing = NO;
- break;
- }
- }
- }
-
- while (keepGoing) {
- result = sqlite3_step (statement);
- switch (result) {
-
- case SQLITE_ROW:
- i = 0;
- entry = [NSMutableDictionary dictionaryWithCapacity:0];
- count = sqlite3_column_count(statement);
-
- while (i < count) {
- columnValue = nil;
- columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
-
- column_type = sqlite3_column_type(statement, i);
- switch (column_type) {
- case SQLITE_INTEGER:
- columnValue = [NSNumber numberWithLongLong: sqlite3_column_int64(statement, i)];
- break;
- case SQLITE_FLOAT:
- columnValue = [NSNumber numberWithDouble: sqlite3_column_double(statement, i)];
- break;
- case SQLITE_BLOB:
- case SQLITE_TEXT:
- columnValue = [[NSString alloc] initWithBytes:(char *)sqlite3_column_text(statement, i)
- length:sqlite3_column_bytes(statement, i)
- encoding:NSUTF8StringEncoding];
- break;
- case SQLITE_NULL:
- // just in case (should not happen):
- default:
- columnValue = [NSNull null];
- break;
- }
-
- if (columnValue) {
- [entry setObject:columnValue forKey:columnName];
- }
-
- i++;
- }
- [resultRows addObject:entry];
- break;
-
- case SQLITE_DONE:
- nowRowsAffected = sqlite3_total_changes(db);
- diffRowsAffected = nowRowsAffected - previousRowsAffected;
- rowsAffected = [NSNumber numberWithInt:diffRowsAffected];
- nowInsertId = sqlite3_last_insert_rowid(db);
- if (diffRowsAffected > 0 && nowInsertId != 0) {
- hasInsertId = YES;
- insertId = [NSNumber numberWithLongLong:sqlite3_last_insert_rowid(db)];
- }
- keepGoing = NO;
- break;
-
- default:
- error = [SQLitePlugin captureSQLiteErrorFromDb:db];
- keepGoing = NO;
- }
- }
-
- sqlite3_finalize (statement);
-
- if (error) {
- return [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:error];
- }
-
- [resultSet setObject:resultRows forKey:@"rows"];
- [resultSet setObject:rowsAffected forKey:@"rowsAffected"];
- if (hasInsertId) {
- [resultSet setObject:insertId forKey:@"insertId"];
- }
- return [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultSet];
- }
-
- -(int)bindStatement:(sqlite3_stmt *)statement withArg:(NSObject *)arg atIndex:(int)argIndex
- {
- int bindResult = SQLITE_ERROR;
-
- if ([arg isEqual:[NSNull null]]) {
- // bind null:
- bindResult = sqlite3_bind_null(statement, argIndex);
- } else if ([arg isKindOfClass:[NSNumber class]]) {
- // bind NSNumber (int64 or double):
- NSNumber *numberArg = (NSNumber *)arg;
- const char *numberType = [numberArg objCType];
-
- // Bind each number as INTEGER (long long int) or REAL (double):
- if (strcmp(numberType, @encode(int)) == 0 ||
- strcmp(numberType, @encode(long long int)) == 0) {
- bindResult = sqlite3_bind_int64(statement, argIndex, [numberArg longLongValue]);
- } else {
- bindResult = sqlite3_bind_double(statement, argIndex, [numberArg doubleValue]);
- }
- } else {
- // bind NSString (text):
- NSString *stringArg;
-
- if ([arg isKindOfClass:[NSString class]]) {
- stringArg = (NSString *)arg;
- } else {
- stringArg = [arg description]; // convert to text
- }
-
- // always bind text string as UTF-8 (sqlite does internal conversion if necessary):
- NSData *data = [stringArg dataUsingEncoding:NSUTF8StringEncoding];
- bindResult = sqlite3_bind_text(statement, argIndex, data.bytes, (int)data.length, SQLITE_TRANSIENT);
- }
-
- return bindResult;
- }
-
- -(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);
- }
- }
-
- +(NSDictionary *)captureSQLiteErrorFromDb:(struct sqlite3 *)db
- {
- int code = sqlite3_errcode(db);
- int webSQLCode = [SQLitePlugin mapSQLiteErrorCode:code];
- #if INCLUDE_SQLITE_ERROR_INFO
- int extendedCode = sqlite3_extended_errcode(db);
- #endif
- const char *message = sqlite3_errmsg(db);
-
- NSMutableDictionary *error = [NSMutableDictionary dictionaryWithCapacity:4];
-
- [error setObject:[NSNumber numberWithInt:webSQLCode] forKey:@"code"];
- [error setObject:[NSString stringWithUTF8String:message] forKey:@"message"];
-
- #if INCLUDE_SQLITE_ERROR_INFO
- [error setObject:[NSNumber numberWithInt:code] forKey:@"sqliteCode"];
- [error setObject:[NSNumber numberWithInt:extendedCode] forKey:@"sqliteExtendedCode"];
- [error setObject:[NSString stringWithUTF8String:message] forKey:@"sqliteMessage"];
- #endif
-
- return error;
- }
-
- +(int)mapSQLiteErrorCode:(int)code
- {
- // map the sqlite error code to
- // the websql error code
- switch(code) {
- case SQLITE_ERROR:
- return SYNTAX_ERR_;
- case SQLITE_FULL:
- return QUOTA_ERR;
- case SQLITE_CONSTRAINT:
- return CONSTRAINT_ERR;
- default:
- return UNKNOWN_ERR;
- }
- }
-
- @end /* vim: set expandtab : */
|