├── .flowconfig ├── .gitignore ├── AIBSQLite.h ├── AIBSQLite.m ├── AIBSQLite.xcodeproj └── project.pbxproj ├── LICENSE.txt ├── README.md ├── package.json └── sqlite3.ios.js /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*.web.js 5 | .*/*.android.js 6 | 7 | # Some modules have their own node_modules with overlap 8 | .*/node_modules/node-haste/.* 9 | 10 | # Ignore react-tools where there are overlaps, but don't ignore anything that 11 | # react-native relies on 12 | .*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js 13 | .*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js 14 | .*/node_modules/react-tools/src/browser/ui/React.js 15 | .*/node_modules/react-tools/src/core/ReactInstanceHandles.js 16 | .*/node_modules/react-tools/src/event/EventPropagators.js 17 | 18 | # Ignore jest 19 | .*/react-native/node_modules/jest-cli/.* 20 | 21 | # Ignore Website 22 | .*/website/.* 23 | 24 | [include] 25 | 26 | [libs] 27 | node_modules/react-native/Libraries/react-native/react-native-interface.js 28 | 29 | [options] 30 | module.system=haste 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | AIBSQLite.xcodeproj/xcuserdata 5 | AIBSQLite.xcodeproj/project.xcworkspace -------------------------------------------------------------------------------- /AIBSQLite.h: -------------------------------------------------------------------------------- 1 | // 2 | // AIBSQLite.h 3 | // activeinbox 4 | // 5 | // Created by Thomas Parslow on 02/04/2015. 6 | // 7 | 8 | 9 | #import 10 | 11 | @interface AIBSQLite : NSObject 12 | 13 | - (void)openFromFilename:(NSString *)filename callback:(RCTResponseSenderBlock)callback; 14 | - (void)closeDatabase:(NSString *)databaseId callback:(RCTResponseSenderBlock)callback; 15 | - (void)prepareStatement: (NSString *)databaseId sql: (NSString *)sql andParams: (NSArray *)params callback: (RCTResponseSenderBlock)callback; 16 | - (void)stepStatement:(NSString *)databaseId statementId: (NSString *) statementId callback:(RCTResponseSenderBlock)callback; 17 | - (void)finalizeStatement:(NSString *)databaseId statementId: (NSString *) statementId callback:(RCTResponseSenderBlock)callback; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /AIBSQLite.m: -------------------------------------------------------------------------------- 1 | ;// 2 | // AIBSQLite.m 3 | // activeinbox 4 | // 5 | // Created by Thomas Parslow on 02/04/2015. 6 | // 7 | 8 | #import "AIBSQLite.h" 9 | #import "RCTLog.h" 10 | #import "RCTUtils.h" 11 | #import 12 | #import "RCTBridge.h" 13 | #import "RCTEventDispatcher.h" 14 | 15 | #import 16 | 17 | // From RCTAsyncLocalStorage, make a queue so we can serialise our interactions 18 | static dispatch_queue_t AIBSQLiteQueue(void) 19 | { 20 | static dispatch_queue_t sqliteQueue = NULL; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | // All JS is single threaded, so a serial queue is our only option. 24 | sqliteQueue = dispatch_queue_create("com.activeinboxhq.sqlite", DISPATCH_QUEUE_SERIAL); 25 | dispatch_set_target_queue(sqliteQueue, 26 | dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); 27 | }); 28 | 29 | return sqliteQueue; 30 | } 31 | 32 | // Private class to hold details of an open database 33 | @interface Database : NSObject 34 | @property (readonly) sqlite3 * db; 35 | @property (readonly) NSMutableDictionary *statements; 36 | @end 37 | 38 | @implementation Database 39 | @synthesize db = _db; 40 | @synthesize statements = _statements; 41 | 42 | - (id) initWithSqliteDb: (sqlite3 *) db 43 | { 44 | self = [super init]; 45 | if (self) { 46 | _db = db; 47 | _statements = [NSMutableDictionary dictionaryWithCapacity: 1]; 48 | } 49 | return self; 50 | } 51 | @end 52 | 53 | // Private class to hold details of an open database 54 | @interface Statement : NSObject 55 | @property (readonly) sqlite3_stmt * stmt; 56 | @end 57 | 58 | @implementation Statement 59 | @synthesize stmt = _stmt; 60 | 61 | - (id) initWithSqliteStmt: (sqlite3_stmt *) stmt 62 | { 63 | self = [super init]; 64 | if (self) { 65 | _stmt = stmt; 66 | } 67 | return self; 68 | } 69 | @end 70 | 71 | 72 | 73 | @implementation AIBSQLite 74 | { 75 | NSMutableDictionary *openDatabases; 76 | int nextId; 77 | } 78 | 79 | RCT_EXPORT_MODULE(); 80 | 81 | @synthesize bridge = _bridge; 82 | 83 | - (id) init 84 | { 85 | self = [super init]; 86 | if (self) { 87 | openDatabases = [NSMutableDictionary dictionaryWithCapacity: 1]; 88 | nextId = 0; 89 | } 90 | return self; 91 | } 92 | 93 | RCT_EXPORT_METHOD(openFromFilename:(NSString *)filename callback:(RCTResponseSenderBlock)callback) 94 | { 95 | if (!callback) { 96 | RCTLogError(@"Called openFromFilename without a callback."); 97 | return; 98 | } 99 | dispatch_async(AIBSQLiteQueue(), ^{ 100 | // TODO: Allow creation of database in Library or tmp 101 | // directories. Maybe also add an option to open read-only 102 | // direct from the bundle. 103 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 104 | NSString *documentsDirectory = [paths objectAtIndex:0]; 105 | NSString *dbPath = [documentsDirectory stringByAppendingPathComponent:filename]; 106 | 107 | if (![[NSFileManager defaultManager] fileExistsAtPath:dbPath]) { 108 | // If the db file doesn't exist in the documents directory 109 | // but it does exist in the bundle then copy it over now 110 | NSString *sourcePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:filename]; 111 | NSError *error; 112 | if ([[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) { 113 | [[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:dbPath error:&error]; 114 | if (error != nil) { 115 | callback(@[[error localizedDescription], [NSNull null]]); 116 | } 117 | } 118 | } 119 | 120 | sqlite3 *db; 121 | BOOL openDatabaseResult = sqlite3_open([dbPath UTF8String], &db); 122 | if(openDatabaseResult != SQLITE_OK) { 123 | callback(@[@"Couldn't open database", [NSNull null]]); 124 | return; 125 | } 126 | NSString *databaseId = [[NSNumber numberWithInt: nextId++] stringValue]; 127 | Database *database = [[Database alloc] initWithSqliteDb:db]; 128 | [openDatabases setValue:database forKey:databaseId]; 129 | callback(@[[NSNull null], databaseId]); 130 | }); 131 | } 132 | 133 | RCT_EXPORT_METHOD(closeDatabase:(NSString *)databaseId callback:(RCTResponseSenderBlock)callback) 134 | { 135 | if (!callback) { 136 | RCTLogError(@"Called openFromFilename without a callback."); 137 | return; 138 | } 139 | dispatch_async(AIBSQLiteQueue(), ^{ 140 | Database *database = [openDatabases valueForKey:databaseId]; 141 | if (database == nil) { 142 | callback(@[@"No open database found"]); 143 | return; 144 | } 145 | 146 | // Finalize any remaining statments 147 | for (NSString* statementId in [database statements]) { 148 | Statement *statement = [[database statements] objectForKey:statementId]; 149 | sqlite3_stmt *stmt = [statement stmt]; 150 | // We don't care about errors at this point, or at least there's nothing we can do about them 151 | sqlite3_finalize(stmt); 152 | } 153 | 154 | sqlite3 *db = [database db]; 155 | sqlite3_close(db); 156 | 157 | [openDatabases removeObjectForKey: databaseId]; 158 | callback(@[[NSNull null]]); 159 | }); 160 | } 161 | 162 | RCT_EXPORT_METHOD(prepareStatement: (NSString *)databaseId sql: (NSString *)sql andParams: (NSArray *)params callback: (RCTResponseSenderBlock)callback) 163 | { 164 | if (!callback) { 165 | RCTLogError(@"Called prepareStatement without a callback."); 166 | } 167 | 168 | dispatch_async(AIBSQLiteQueue(), ^{ 169 | Database *database = [openDatabases valueForKey:databaseId]; 170 | if (database == nil) { 171 | callback(@[@"No open database found", [NSNull null]]); 172 | return; 173 | } 174 | sqlite3 *db = [database db]; 175 | sqlite3_stmt *stmt; 176 | 177 | int rc = sqlite3_prepare_v2(db, [sql UTF8String], -1, &stmt, NULL); 178 | if (rc != SQLITE_OK) { 179 | callback(@[[NSString stringWithUTF8String:sqlite3_errmsg(db)]]); 180 | return; 181 | } 182 | 183 | for (int i=0; i < [params count]; i++){ 184 | NSObject *param = [params objectAtIndex: i]; 185 | if ([param isKindOfClass: [NSString class]]) { 186 | NSString *str = (NSString*) param; 187 | int strLength = (int) [str lengthOfBytesUsingEncoding: NSUTF8StringEncoding]; 188 | sqlite3_bind_text(stmt, i+1, [str UTF8String], strLength, SQLITE_TRANSIENT); 189 | } else if ([param isKindOfClass: [NSNumber class]]) { 190 | sqlite3_bind_double(stmt, i+1, [(NSNumber *)param doubleValue]); 191 | } else if ([param isKindOfClass: [NSNull class]]) { 192 | sqlite3_bind_null(stmt, i+1); 193 | } else { 194 | sqlite3_finalize(stmt); 195 | callback(@[@"Parameters must be either numbers or strings" ]); 196 | return; 197 | } 198 | } 199 | 200 | NSString *statementId = [[NSNumber numberWithInt: nextId++] stringValue]; 201 | Statement *statement = [[Statement alloc] initWithSqliteStmt: stmt]; 202 | [[database statements] setValue: statement forKey:statementId]; 203 | 204 | callback(@[[NSNull null], statementId]); 205 | }); 206 | } 207 | 208 | RCT_EXPORT_METHOD(stepStatement:(NSString *)databaseId statementId: (NSString *) statementId callback:(RCTResponseSenderBlock)callback) 209 | { 210 | if (!callback) { 211 | RCTLogError(@"Called step without a callback."); 212 | } 213 | 214 | dispatch_async(AIBSQLiteQueue(), ^{ 215 | Database *database = [openDatabases valueForKey:databaseId]; 216 | if (database == nil) { 217 | callback(@[@"No open database found", [NSNull null]]); 218 | return; 219 | } 220 | Statement *statement = [[database statements] objectForKey:statementId]; 221 | if (statement == nil) { 222 | callback(@[@"No statement found", [NSNull null]]); 223 | return; 224 | } 225 | 226 | sqlite3 *db = [database db]; 227 | sqlite3_stmt *stmt = [statement stmt]; 228 | 229 | int rc = sqlite3_step(stmt); 230 | if (rc == SQLITE_ROW) { 231 | int totalColumns = sqlite3_column_count(stmt); 232 | NSMutableDictionary *rowData = [NSMutableDictionary dictionaryWithCapacity: totalColumns]; 233 | // Go through all columns and fetch each column data. 234 | for (int i=0; i=0.6.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sqlite3.ios.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | var NativeModules = require('react-native').NativeModules; 3 | 4 | var nextId = 0; 5 | 6 | function SQLite3Error(message: any) { 7 | this.message = message; 8 | } 9 | 10 | SQLite3Error.prototype = new Error(); 11 | 12 | function Database(databaseName, openCallback) { 13 | this._databaseId = null; 14 | this._databaseName = databaseName; 15 | // List of actions pending database connection 16 | this._pendingActions = []; 17 | 18 | NativeModules.AIBSQLite.openFromFilename(databaseName, (error, databaseId) => { 19 | if (error) { 20 | error = new SQLite3Error(error); 21 | this._failPendingActions(error); 22 | return openCallback(error, null); 23 | } 24 | this._databaseId = databaseId; 25 | this._runPendingActions(); 26 | openCallback(null, this); 27 | }); 28 | } 29 | 30 | Database.prototype = { 31 | 32 | getName(): string { 33 | return this._databaseName; 34 | }, 35 | 36 | executeSQL ( 37 | sql: string, 38 | params: Array, 39 | rowCallback: ((row: Object) => void), 40 | completeCallback: ((error: ?SQLite3Error) => void) 41 | ) : void { 42 | this._addAction(completeCallback, (callback) => { 43 | NativeModules.AIBSQLite.prepareStatement(this._databaseId, sql, params, (error, statementId) => { 44 | if (error) { 45 | completeCallback(new SQLite3Error(error)); 46 | return; 47 | } 48 | var next = () => { 49 | NativeModules.AIBSQLite.stepStatement(this._databaseId, statementId, (error, row) => { 50 | if (error) { 51 | completeCallback(new SQLite3Error(error)); 52 | return; 53 | } 54 | if (row === null) { 55 | completeCallback(null); 56 | } else { 57 | try { 58 | rowCallback(row); 59 | } finally { 60 | next(); 61 | } 62 | } 63 | }); 64 | }; 65 | next(); 66 | }); 67 | }); 68 | }, 69 | 70 | close(callback: ?(error: ?SQLite3Error) => void) { 71 | NativeModules.AIBSQLite.closeDatabase(this._databaseId, (error) => { 72 | if (!callback) return; 73 | if (error) { 74 | callback(new SQLite3Error(error)); 75 | } else { 76 | callback(null); 77 | } 78 | }); 79 | }, 80 | 81 | _addAction(callback, action) { 82 | if (this._databaseId) { 83 | action(callback); 84 | } else { 85 | this._pendingActions.push({action, callback}); 86 | } 87 | }, 88 | 89 | _runPendingActions () { 90 | this._pendingActions.forEach( 91 | ({action, callback}) => { 92 | action(callback); 93 | }); 94 | }, 95 | 96 | _failPendingActions (error) { 97 | this._pendingActions.forEach( 98 | ({action, callback}) => { 99 | callback(error); 100 | }); 101 | } 102 | }; 103 | 104 | module.exports = { 105 | SQLite3Error: SQLite3Error, 106 | open (databaseName: string, callback: ?((error: ?SQLite3Error, database: ?Database) => void) ) : Database { 107 | return new Database(databaseName, callback || ((e) => null)); 108 | } 109 | }; 110 | --------------------------------------------------------------------------------