FLEXSQLiteDatabaseManager.m 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. //
  2. // PTDatabaseManager.m
  3. // PTDatabaseReader
  4. //
  5. // Created by Peng Tao on 15/11/23.
  6. // Copyright © 2015年 Peng Tao. All rights reserved.
  7. //
  8. #import "FLEXSQLiteDatabaseManager.h"
  9. #import "FLEXManager.h"
  10. #import "NSArray+FLEX.h"
  11. #import "FLEXRuntimeConstants.h"
  12. #import <sqlite3.h>
  13. static NSString * const QUERY_TABLENAMES = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
  14. @interface FLEXSQLiteDatabaseManager ()
  15. @property (nonatomic) sqlite3 *db;
  16. @property (nonatomic, copy) NSString *path;
  17. @end
  18. @implementation FLEXSQLiteDatabaseManager
  19. #pragma mark - FLEXDatabaseManager
  20. + (instancetype)managerForDatabase:(NSString *)path {
  21. return [[self alloc] initWithPath:path];
  22. }
  23. - (instancetype)initWithPath:(NSString *)path {
  24. self = [super init];
  25. if (self) {
  26. self.path = path;;
  27. }
  28. return self;
  29. }
  30. - (void)dealloc {
  31. [self close];
  32. }
  33. - (BOOL)open {
  34. if (self.db) {
  35. return YES;
  36. }
  37. int err = sqlite3_open(self.path.UTF8String, &_db);
  38. #if SQLITE_HAS_CODEC
  39. NSString *defaultSqliteDatabasePassword = FLEXManager.sharedManager.defaultSqliteDatabasePassword;
  40. if (defaultSqliteDatabasePassword) {
  41. const char *key = defaultSqliteDatabasePassword.UTF8String;
  42. sqlite3_key(_db, key, (int)strlen(key));
  43. }
  44. #endif
  45. if (err != SQLITE_OK) {
  46. return [self storeErrorForLastTask:@"Open"];
  47. }
  48. return YES;
  49. }
  50. - (BOOL)close {
  51. if (!self.db) {
  52. return YES;
  53. }
  54. int rc;
  55. BOOL retry, triedFinalizingOpenStatements = NO;
  56. do {
  57. retry = NO;
  58. rc = sqlite3_close(_db);
  59. if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
  60. if (!triedFinalizingOpenStatements) {
  61. triedFinalizingOpenStatements = YES;
  62. sqlite3_stmt *pStmt;
  63. while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) {
  64. NSLog(@"Closing leaked statement");
  65. sqlite3_finalize(pStmt);
  66. retry = YES;
  67. }
  68. }
  69. } else if (SQLITE_OK != rc) {
  70. [self storeErrorForLastTask:@"Close"];
  71. self.db = nil;
  72. return NO;
  73. }
  74. } while (retry);
  75. self.db = nil;
  76. return YES;
  77. }
  78. - (NSInteger)lastRowID {
  79. return (NSInteger)sqlite3_last_insert_rowid(self.db);
  80. }
  81. - (NSArray<NSString *> *)queryAllTables {
  82. return [[self executeStatement:QUERY_TABLENAMES].rows flex_mapped:^id(NSArray *table, NSUInteger idx) {
  83. return table.firstObject;
  84. }] ?: @[];
  85. }
  86. - (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
  87. NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
  88. FLEXSQLResult *results = [self executeStatement:sql];
  89. return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
  90. return column[@"name"];
  91. }] ?: @[];
  92. }
  93. - (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
  94. return [self executeStatement:[@"SELECT * FROM "
  95. stringByAppendingString:tableName
  96. ]].rows ?: @[];
  97. }
  98. - (FLEXSQLResult *)executeStatement:(NSString *)sql {
  99. return [self executeStatement:sql arguments:nil];
  100. }
  101. - (FLEXSQLResult *)executeStatement:(NSString *)sql arguments:(NSDictionary *)args {
  102. [self open];
  103. FLEXSQLResult *result = nil;
  104. sqlite3_stmt *pstmt;
  105. int status;
  106. if ((status = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0)) == SQLITE_OK) {
  107. NSMutableArray<NSArray *> *rows = [NSMutableArray new];
  108. // Bind parameters, if any
  109. if (![self bindParameters:args toStatement:pstmt]) {
  110. return self.lastResult;
  111. }
  112. // Grab columns
  113. int columnCount = sqlite3_column_count(pstmt);
  114. NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
  115. return @(sqlite3_column_name(pstmt, (int)i));
  116. }];
  117. // Execute statement
  118. while ((status = sqlite3_step(pstmt)) == SQLITE_ROW) {
  119. // Grab rows if this is a selection query
  120. int dataCount = sqlite3_data_count(pstmt);
  121. if (dataCount > 0) {
  122. [rows addObject:[NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
  123. return [self objectForColumnIndex:(int)i stmt:pstmt];
  124. }]];
  125. }
  126. }
  127. if (status == SQLITE_DONE) {
  128. if (rows.count) {
  129. // We selected some rows
  130. result = _lastResult = [FLEXSQLResult columns:columns rows:rows];
  131. } else {
  132. // We executed a query like INSERT, UDPATE, or DELETE
  133. int rowsAffected = sqlite3_changes(_db);
  134. NSString *message = [NSString stringWithFormat:@"%d row(s) affected", rowsAffected];
  135. result = _lastResult = [FLEXSQLResult message:message];
  136. }
  137. } else {
  138. // An error occured executing the query
  139. result = _lastResult = [self errorResult:@"Execution"];
  140. }
  141. } else {
  142. // An error occurred creating the prepared statement
  143. result = _lastResult = [self errorResult:@"Prepared statement"];
  144. }
  145. sqlite3_finalize(pstmt);
  146. return result;
  147. }
  148. #pragma mark - Private
  149. /// @return YES on success, NO if an error was encountered and stored in \c lastResult
  150. - (BOOL)bindParameters:(NSDictionary *)args toStatement:(sqlite3_stmt *)pstmt {
  151. for (NSString *param in args.allKeys) {
  152. int status = SQLITE_OK, idx = sqlite3_bind_parameter_index(pstmt, param.UTF8String);
  153. id value = args[param];
  154. if (idx == 0) {
  155. // No parameter matching that arg
  156. @throw NSInternalInconsistencyException;
  157. }
  158. // Null
  159. if ([value isKindOfClass:[NSNull class]]) {
  160. status = sqlite3_bind_null(pstmt, idx);
  161. }
  162. // String params
  163. else if ([value isKindOfClass:[NSString class]]) {
  164. const char *str = [value UTF8String];
  165. status = sqlite3_bind_text(pstmt, idx, str, (int)strlen(str), SQLITE_TRANSIENT);
  166. }
  167. // Data params
  168. else if ([value isKindOfClass:[NSData class]]) {
  169. const void *blob = [value bytes];
  170. status = sqlite3_bind_blob64(pstmt, idx, blob, [value length], SQLITE_TRANSIENT);
  171. }
  172. // Primitive params
  173. else if ([value isKindOfClass:[NSNumber class]]) {
  174. FLEXTypeEncoding type = [value objCType][0];
  175. switch (type) {
  176. case FLEXTypeEncodingCBool:
  177. case FLEXTypeEncodingChar:
  178. case FLEXTypeEncodingUnsignedChar:
  179. case FLEXTypeEncodingShort:
  180. case FLEXTypeEncodingUnsignedShort:
  181. case FLEXTypeEncodingInt:
  182. case FLEXTypeEncodingUnsignedInt:
  183. case FLEXTypeEncodingLong:
  184. case FLEXTypeEncodingUnsignedLong:
  185. case FLEXTypeEncodingLongLong:
  186. case FLEXTypeEncodingUnsignedLongLong:
  187. status = sqlite3_bind_int64(pstmt, idx, (sqlite3_int64)[value longValue]);
  188. break;
  189. case FLEXTypeEncodingFloat:
  190. case FLEXTypeEncodingDouble:
  191. status = sqlite3_bind_double(pstmt, idx, [value doubleValue]);
  192. break;
  193. default:
  194. @throw NSInternalInconsistencyException;
  195. break;
  196. }
  197. }
  198. // Unsupported type
  199. else {
  200. @throw NSInternalInconsistencyException;
  201. }
  202. if (status != SQLITE_OK) {
  203. return [self storeErrorForLastTask:
  204. [NSString stringWithFormat:@"Binding param named '%@'", param]
  205. ];
  206. }
  207. }
  208. return YES;
  209. }
  210. - (BOOL)storeErrorForLastTask:(NSString *)action {
  211. _lastResult = [self errorResult:action];
  212. return NO;
  213. }
  214. - (FLEXSQLResult *)errorResult:(NSString *)description {
  215. const char *error = sqlite3_errmsg(_db);
  216. NSString *message = error ? @(error) : [NSString
  217. stringWithFormat:@"(%@: empty error", description
  218. ];
  219. return [FLEXSQLResult error:message];
  220. }
  221. - (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
  222. int columnType = sqlite3_column_type(stmt, columnIdx);
  223. switch (columnType) {
  224. case SQLITE_INTEGER:
  225. return @(sqlite3_column_int64(stmt, columnIdx)).stringValue;
  226. case SQLITE_FLOAT:
  227. return @(sqlite3_column_double(stmt, columnIdx)).stringValue;
  228. case SQLITE_BLOB:
  229. return [NSString stringWithFormat:@"Data (%@ bytes)",
  230. @([self dataForColumnIndex:columnIdx stmt:stmt].length)
  231. ];
  232. default:
  233. // Default to a string for everything else
  234. return [self stringForColumnIndex:columnIdx stmt:stmt] ?: NSNull.null;
  235. }
  236. }
  237. - (NSString *)stringForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
  238. if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || columnIdx < 0) {
  239. return nil;
  240. }
  241. const char *text = (const char *)sqlite3_column_text(stmt, columnIdx);
  242. return text ? @(text) : nil;
  243. }
  244. - (NSData *)dataForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
  245. if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
  246. return nil;
  247. }
  248. const void *blob = sqlite3_column_blob(stmt, columnIdx);
  249. NSInteger size = (NSInteger)sqlite3_column_bytes(stmt, columnIdx);
  250. return blob ? [NSData dataWithBytes:blob length:size] : nil;
  251. }
  252. @end