RCNConfigDBManager.m 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046
  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <sqlite3.h>
  17. #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
  18. #import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h"
  19. #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
  20. #import <FirebaseCore/FIRAppInternal.h>
  21. #import <FirebaseCore/FIRLogger.h>
  22. /// Using macro for securely preprocessing string concatenation in query before runtime.
  23. #define RCNTableNameMain "main"
  24. #define RCNTableNameMainActive "main_active"
  25. #define RCNTableNameMainDefault "main_default"
  26. #define RCNTableNameMetadata "fetch_metadata"
  27. #define RCNTableNameInternalMetadata "internal_metadata"
  28. #define RCNTableNameExperiment "experiment"
  29. static BOOL gIsNewDatabase;
  30. /// SQLite file name in versions 0, 1 and 2.
  31. static NSString *const RCNDatabaseName = @"RemoteConfig.sqlite3";
  32. /// The application support sub-directory that the Remote Config database resides in.
  33. static NSString *const RCNRemoteConfigApplicationSupportSubDirectory = @"Google/RemoteConfig";
  34. /// Remote Config database path for deprecated V0 version.
  35. static NSString *RemoteConfigPathForOldDatabaseV0() {
  36. NSArray *dirPaths =
  37. NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  38. NSString *docPath = dirPaths.firstObject;
  39. return [docPath stringByAppendingPathComponent:RCNDatabaseName];
  40. }
  41. /// Remote Config database path for current database.
  42. static NSString *RemoteConfigPathForDatabase(void) {
  43. NSArray *dirPaths =
  44. NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  45. NSString *appSupportPath = dirPaths.firstObject;
  46. NSArray *components =
  47. @[ appSupportPath, RCNRemoteConfigApplicationSupportSubDirectory, RCNDatabaseName ];
  48. return [NSString pathWithComponents:components];
  49. }
  50. static BOOL RemoteConfigAddSkipBackupAttributeToItemAtPath(NSString *filePathString) {
  51. NSURL *URL = [NSURL fileURLWithPath:filePathString];
  52. assert([[NSFileManager defaultManager] fileExistsAtPath:[URL path]]);
  53. NSError *error = nil;
  54. BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES]
  55. forKey:NSURLIsExcludedFromBackupKey
  56. error:&error];
  57. if (!success) {
  58. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000017", @"Error excluding %@ from backup %@.",
  59. [URL lastPathComponent], error);
  60. }
  61. return success;
  62. }
  63. static BOOL RemoteConfigCreateFilePathIfNotExist(NSString *filePath) {
  64. if (!filePath || !filePath.length) {
  65. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000018",
  66. @"Failed to create subdirectory for an empty file path.");
  67. return NO;
  68. }
  69. NSFileManager *fileManager = [NSFileManager defaultManager];
  70. if (![fileManager fileExistsAtPath:filePath]) {
  71. gIsNewDatabase = YES;
  72. NSError *error;
  73. [fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
  74. withIntermediateDirectories:YES
  75. attributes:nil
  76. error:&error];
  77. if (error) {
  78. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000019",
  79. @"Failed to create subdirectory for database file: %@.", error);
  80. return NO;
  81. }
  82. }
  83. return YES;
  84. }
  85. static NSArray *RemoteConfigMetadataTableColumnsInOrder() {
  86. return @[
  87. RCNKeyBundleIdentifier, RCNKeyFetchTime, RCNKeyDigestPerNamespace, RCNKeyDeviceContext,
  88. RCNKeyAppContext, RCNKeySuccessFetchTime, RCNKeyFailureFetchTime, RCNKeyLastFetchStatus,
  89. RCNKeyLastFetchError, RCNKeyLastApplyTime, RCNKeyLastSetDefaultsTime
  90. ];
  91. }
  92. @interface RCNConfigDBManager () {
  93. /// Database storing all the config information.
  94. sqlite3 *_database;
  95. /// Serial queue for database read/write operations.
  96. dispatch_queue_t _databaseOperationQueue;
  97. }
  98. @end
  99. @implementation RCNConfigDBManager
  100. + (instancetype)sharedInstance {
  101. static dispatch_once_t onceToken;
  102. static RCNConfigDBManager *sharedInstance;
  103. dispatch_once(&onceToken, ^{
  104. sharedInstance = [[RCNConfigDBManager alloc] init];
  105. });
  106. return sharedInstance;
  107. }
  108. /// Returns the current version of the Remote Config database.
  109. + (NSString *)remoteConfigPathForDatabase {
  110. return RemoteConfigPathForDatabase();
  111. }
  112. - (instancetype)init {
  113. self = [super init];
  114. if (self) {
  115. _databaseOperationQueue =
  116. dispatch_queue_create("com.google.GoogleConfigService.database", DISPATCH_QUEUE_SERIAL);
  117. [self createOrOpenDatabase];
  118. }
  119. return self;
  120. }
  121. #pragma mark - database
  122. - (void)migrateV1NamespaceToV2Namespace {
  123. for (int table = 0; table < 3; table++) {
  124. NSString *tableName = @"" RCNTableNameMain;
  125. switch (table) {
  126. case 1:
  127. tableName = @"" RCNTableNameMainActive;
  128. break;
  129. case 2:
  130. tableName = @"" RCNTableNameMainDefault;
  131. break;
  132. default:
  133. break;
  134. }
  135. NSString *SQLString = [NSString
  136. stringWithFormat:@"SELECT namespace FROM %@ WHERE namespace NOT LIKE '%%:%%'", tableName];
  137. const char *SQL = [SQLString UTF8String];
  138. sqlite3_stmt *statement = [self prepareSQL:SQL];
  139. if (!statement) {
  140. return;
  141. }
  142. NSMutableArray<NSString *> *namespaceArray = [[NSMutableArray alloc] init];
  143. while (sqlite3_step(statement) == SQLITE_ROW) {
  144. NSString *configNamespace =
  145. [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
  146. [namespaceArray addObject:configNamespace];
  147. }
  148. sqlite3_finalize(statement);
  149. // Update.
  150. for (NSString *namespaceToUpdate in namespaceArray) {
  151. NSString *newNamespace =
  152. [NSString stringWithFormat:@"%@:%@", namespaceToUpdate, kFIRDefaultAppName];
  153. NSString *updateSQLString =
  154. [NSString stringWithFormat:@"UPDATE %@ SET namespace = ? WHERE namespace = ?", tableName];
  155. const char *updateSQL = [updateSQLString UTF8String];
  156. sqlite3_stmt *updateStatement = [self prepareSQL:updateSQL];
  157. if (!updateStatement) {
  158. return;
  159. }
  160. NSArray<NSString *> *updateParams = @[ newNamespace, namespaceToUpdate ];
  161. [self bindStringsToStatement:updateStatement stringArray:updateParams];
  162. int result = sqlite3_step(updateStatement);
  163. if (result != SQLITE_DONE) {
  164. [self logErrorWithSQL:SQL finalizeStatement:updateStatement returnValue:NO];
  165. return;
  166. }
  167. sqlite3_finalize(updateStatement);
  168. }
  169. }
  170. }
  171. - (void)createOrOpenDatabase {
  172. __weak RCNConfigDBManager *weakSelf = self;
  173. dispatch_async(_databaseOperationQueue, ^{
  174. RCNConfigDBManager *strongSelf = weakSelf;
  175. if (!strongSelf) {
  176. return;
  177. }
  178. NSString *oldV0DBPath = RemoteConfigPathForOldDatabaseV0();
  179. // Backward Compatibility
  180. if ([[NSFileManager defaultManager] fileExistsAtPath:oldV0DBPath]) {
  181. FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000009",
  182. @"Old database V0 exists, removed it and replace with the new one.");
  183. [strongSelf removeDatabase:oldV0DBPath];
  184. }
  185. NSString *dbPath = [RCNConfigDBManager remoteConfigPathForDatabase];
  186. FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000062", @"Loading database at path %@", dbPath);
  187. const char *databasePath = dbPath.UTF8String;
  188. // Create or open database path.
  189. if (!RemoteConfigCreateFilePathIfNotExist(dbPath)) {
  190. return;
  191. }
  192. int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FILEPROTECTION_COMPLETE |
  193. SQLITE_OPEN_FULLMUTEX;
  194. if (sqlite3_open_v2(databasePath, &strongSelf->_database, flags, NULL) == SQLITE_OK) {
  195. // Always try to create table if not exists for backward compatibility.
  196. if (![strongSelf createTableSchema]) {
  197. // Remove database before fail.
  198. [strongSelf removeDatabase:dbPath];
  199. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000010", @"Failed to create table.");
  200. // Create a new database if existing database file is corrupted.
  201. if (!RemoteConfigCreateFilePathIfNotExist(dbPath)) {
  202. return;
  203. }
  204. if (sqlite3_open_v2(databasePath, &strongSelf->_database, flags, NULL) == SQLITE_OK) {
  205. if (![strongSelf createTableSchema]) {
  206. // Remove database before fail.
  207. [strongSelf removeDatabase:dbPath];
  208. // If it failed again, there's nothing we can do here.
  209. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000010", @"Failed to create table.");
  210. } else {
  211. // Exclude the app data used from iCloud backup.
  212. RemoteConfigAddSkipBackupAttributeToItemAtPath(dbPath);
  213. }
  214. } else {
  215. [strongSelf logDatabaseError];
  216. }
  217. } else {
  218. // DB file already exists. Migrate any V1 namespace column entries to V2 fully qualified
  219. // 'namespace:FIRApp' entries.
  220. [self migrateV1NamespaceToV2Namespace];
  221. // Exclude the app data used from iCloud backup.
  222. RemoteConfigAddSkipBackupAttributeToItemAtPath(dbPath);
  223. }
  224. } else {
  225. [strongSelf logDatabaseError];
  226. }
  227. });
  228. }
  229. - (BOOL)createTableSchema {
  230. RCN_MUST_NOT_BE_MAIN_THREAD();
  231. static const char *createTableMain =
  232. "create TABLE IF NOT EXISTS " RCNTableNameMain
  233. " (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)";
  234. static const char *createTableMainActive =
  235. "create TABLE IF NOT EXISTS " RCNTableNameMainActive
  236. " (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)";
  237. static const char *createTableMainDefault =
  238. "create TABLE IF NOT EXISTS " RCNTableNameMainDefault
  239. " (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)";
  240. static const char *createTableMetadata =
  241. "create TABLE IF NOT EXISTS " RCNTableNameMetadata
  242. " (_id INTEGER PRIMARY KEY, bundle_identifier"
  243. " TEXT, fetch_time INTEGER, digest_per_ns BLOB, device_context BLOB, app_context BLOB, "
  244. "success_fetch_time BLOB, failure_fetch_time BLOB, last_fetch_status INTEGER, "
  245. "last_fetch_error INTEGER, last_apply_time INTEGER, last_set_defaults_time INTEGER)";
  246. static const char *createTableInternalMetadata =
  247. "create TABLE IF NOT EXISTS " RCNTableNameInternalMetadata
  248. " (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)";
  249. static const char *createTableExperiment = "create TABLE IF NOT EXISTS " RCNTableNameExperiment
  250. " (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)";
  251. return [self executeQuery:createTableMain] && [self executeQuery:createTableMainActive] &&
  252. [self executeQuery:createTableMainDefault] && [self executeQuery:createTableMetadata] &&
  253. [self executeQuery:createTableInternalMetadata] &&
  254. [self executeQuery:createTableExperiment];
  255. }
  256. - (void)removeDatabaseOnDatabaseQueueAtPath:(NSString *)path {
  257. __weak RCNConfigDBManager *weakSelf = self;
  258. dispatch_sync(_databaseOperationQueue, ^{
  259. RCNConfigDBManager *strongSelf = weakSelf;
  260. if (!strongSelf) {
  261. return;
  262. }
  263. if (sqlite3_close(strongSelf->_database) != SQLITE_OK) {
  264. [self logDatabaseError];
  265. }
  266. strongSelf->_database = nil;
  267. NSFileManager *fileManager = [NSFileManager defaultManager];
  268. NSError *error;
  269. if (![fileManager removeItemAtPath:path error:&error]) {
  270. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
  271. @"Failed to remove database at path %@ for error %@.", path, error);
  272. }
  273. });
  274. }
  275. - (void)removeDatabase:(NSString *)path {
  276. if (sqlite3_close(_database) != SQLITE_OK) {
  277. [self logDatabaseError];
  278. }
  279. _database = nil;
  280. NSFileManager *fileManager = [NSFileManager defaultManager];
  281. NSError *error;
  282. if (![fileManager removeItemAtPath:path error:&error]) {
  283. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
  284. @"Failed to remove database at path %@ for error %@.", path, error);
  285. }
  286. }
  287. #pragma mark - execute
  288. - (BOOL)executeQuery:(const char *)SQL {
  289. RCN_MUST_NOT_BE_MAIN_THREAD();
  290. char *error;
  291. if (sqlite3_exec(_database, SQL, nil, nil, &error) != SQLITE_OK) {
  292. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000012", @"Failed to execute query with error %s.",
  293. error);
  294. return NO;
  295. }
  296. return YES;
  297. }
  298. #pragma mark - insert
  299. - (void)insertMetadataTableWithValues:(NSDictionary *)columnNameToValue
  300. completionHandler:(RCNDBCompletion)handler {
  301. __weak RCNConfigDBManager *weakSelf = self;
  302. dispatch_async(_databaseOperationQueue, ^{
  303. BOOL success = [weakSelf insertMetadataTableWithValues:columnNameToValue];
  304. if (handler) {
  305. dispatch_async(dispatch_get_main_queue(), ^{
  306. handler(success, nil);
  307. });
  308. }
  309. });
  310. }
  311. - (BOOL)insertMetadataTableWithValues:(NSDictionary *)columnNameToValue {
  312. RCN_MUST_NOT_BE_MAIN_THREAD();
  313. static const char *SQL =
  314. "INSERT INTO " RCNTableNameMetadata
  315. " (bundle_identifier, fetch_time, digest_per_ns, device_context, "
  316. "app_context, success_fetch_time, failure_fetch_time, last_fetch_status, "
  317. "last_fetch_error, last_apply_time, last_set_defaults_time) values (?, ?, ?, ?, ?, "
  318. "?, ?, ?, ?, ?, ?)";
  319. sqlite3_stmt *statement = [self prepareSQL:SQL];
  320. if (!statement) {
  321. [self logErrorWithSQL:SQL finalizeStatement:nil returnValue:NO];
  322. return NO;
  323. }
  324. NSArray *columns = RemoteConfigMetadataTableColumnsInOrder();
  325. int index = 0;
  326. for (NSString *columnName in columns) {
  327. if ([columnName isEqualToString:RCNKeyBundleIdentifier]) {
  328. NSString *value = columnNameToValue[columnName];
  329. if (![self bindStringToStatement:statement index:++index string:value]) {
  330. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  331. }
  332. } else if ([columnName isEqualToString:RCNKeyFetchTime] ||
  333. [columnName isEqualToString:RCNKeyLastApplyTime] ||
  334. [columnName isEqualToString:RCNKeyLastSetDefaultsTime]) {
  335. double value = [columnNameToValue[columnName] doubleValue];
  336. if (sqlite3_bind_double(statement, ++index, value) != SQLITE_OK) {
  337. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  338. }
  339. } else if ([columnName isEqualToString:RCNKeyLastFetchStatus] ||
  340. [columnName isEqualToString:RCNKeyLastFetchError]) {
  341. int value = [columnNameToValue[columnName] intValue];
  342. if (sqlite3_bind_int(statement, ++index, value) != SQLITE_OK) {
  343. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  344. }
  345. } else {
  346. NSData *data = columnNameToValue[columnName];
  347. if (sqlite3_bind_blob(statement, ++index, data.bytes, (int)data.length, NULL) != SQLITE_OK) {
  348. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  349. }
  350. }
  351. }
  352. if (sqlite3_step(statement) != SQLITE_DONE) {
  353. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  354. }
  355. sqlite3_finalize(statement);
  356. return YES;
  357. }
  358. - (void)insertMainTableWithValues:(NSArray *)values
  359. fromSource:(RCNDBSource)source
  360. completionHandler:(RCNDBCompletion)handler {
  361. __weak RCNConfigDBManager *weakSelf = self;
  362. dispatch_async(_databaseOperationQueue, ^{
  363. BOOL success = [weakSelf insertMainTableWithValues:values fromSource:source];
  364. if (handler) {
  365. dispatch_async(dispatch_get_main_queue(), ^{
  366. handler(success, nil);
  367. });
  368. }
  369. });
  370. }
  371. - (BOOL)insertMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
  372. RCN_MUST_NOT_BE_MAIN_THREAD();
  373. if (values.count != 4) {
  374. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000013",
  375. @"Failed to insert config record. Wrong number of give parameters, current "
  376. @"number is %ld, correct number is 4.",
  377. (long)values.count);
  378. return NO;
  379. }
  380. const char *SQL = "INSERT INTO " RCNTableNameMain
  381. " (bundle_identifier, namespace, key, value) values (?, ?, ?, ?)";
  382. if (source == RCNDBSourceDefault) {
  383. SQL = "INSERT INTO " RCNTableNameMainDefault
  384. " (bundle_identifier, namespace, key, value) values (?, ?, ?, ?)";
  385. } else if (source == RCNDBSourceActive) {
  386. SQL = "INSERT INTO " RCNTableNameMainActive
  387. " (bundle_identifier, namespace, key, value) values (?, ?, ?, ?)";
  388. }
  389. sqlite3_stmt *statement = [self prepareSQL:SQL];
  390. if (!statement) {
  391. return NO;
  392. }
  393. NSString *aString = values[0];
  394. if (![self bindStringToStatement:statement index:1 string:aString]) {
  395. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  396. }
  397. aString = values[1];
  398. if (![self bindStringToStatement:statement index:2 string:aString]) {
  399. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  400. }
  401. aString = values[2];
  402. if (![self bindStringToStatement:statement index:3 string:aString]) {
  403. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  404. }
  405. NSData *blobData = values[3];
  406. if (sqlite3_bind_blob(statement, 4, blobData.bytes, (int)blobData.length, NULL) != SQLITE_OK) {
  407. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  408. }
  409. if (sqlite3_step(statement) != SQLITE_DONE) {
  410. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  411. }
  412. sqlite3_finalize(statement);
  413. return YES;
  414. }
  415. - (void)insertInternalMetadataTableWithValues:(NSArray *)values
  416. completionHandler:(RCNDBCompletion)handler {
  417. __weak RCNConfigDBManager *weakSelf = self;
  418. dispatch_async(_databaseOperationQueue, ^{
  419. BOOL success = [weakSelf insertInternalMetadataWithValues:values];
  420. if (handler) {
  421. dispatch_async(dispatch_get_main_queue(), ^{
  422. handler(success, nil);
  423. });
  424. }
  425. });
  426. }
  427. - (BOOL)insertInternalMetadataWithValues:(NSArray *)values {
  428. RCN_MUST_NOT_BE_MAIN_THREAD();
  429. if (values.count != 2) {
  430. return NO;
  431. }
  432. const char *SQL =
  433. "INSERT OR REPLACE INTO " RCNTableNameInternalMetadata " (key, value) values (?, ?)";
  434. sqlite3_stmt *statement = [self prepareSQL:SQL];
  435. if (!statement) {
  436. return NO;
  437. }
  438. NSString *aString = values[0];
  439. if (![self bindStringToStatement:statement index:1 string:aString]) {
  440. [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  441. return NO;
  442. }
  443. NSData *blobData = values[1];
  444. if (sqlite3_bind_blob(statement, 2, blobData.bytes, (int)blobData.length, NULL) != SQLITE_OK) {
  445. [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  446. return NO;
  447. }
  448. if (sqlite3_step(statement) != SQLITE_DONE) {
  449. [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  450. return NO;
  451. }
  452. sqlite3_finalize(statement);
  453. return YES;
  454. }
  455. - (void)insertExperimentTableWithKey:(NSString *)key
  456. value:(NSData *)serializedValue
  457. completionHandler:(RCNDBCompletion)handler {
  458. dispatch_async(_databaseOperationQueue, ^{
  459. BOOL success = [self insertExperimentTableWithKey:key value:serializedValue];
  460. if (handler) {
  461. dispatch_async(dispatch_get_main_queue(), ^{
  462. handler(success, nil);
  463. });
  464. }
  465. });
  466. }
  467. - (BOOL)insertExperimentTableWithKey:(NSString *)key value:(NSData *)dataValue {
  468. if ([key isEqualToString:@RCNExperimentTableKeyMetadata]) {
  469. return [self updateExperimentMetadata:dataValue];
  470. }
  471. RCN_MUST_NOT_BE_MAIN_THREAD();
  472. const char *SQL = "INSERT INTO " RCNTableNameExperiment " (key, value) values (?, ?)";
  473. sqlite3_stmt *statement = [self prepareSQL:SQL];
  474. if (!statement) {
  475. return NO;
  476. }
  477. if (![self bindStringToStatement:statement index:1 string:key]) {
  478. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  479. }
  480. if (sqlite3_bind_blob(statement, 2, dataValue.bytes, (int)dataValue.length, NULL) != SQLITE_OK) {
  481. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  482. }
  483. if (sqlite3_step(statement) != SQLITE_DONE) {
  484. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  485. }
  486. sqlite3_finalize(statement);
  487. return YES;
  488. }
  489. - (BOOL)updateExperimentMetadata:(NSData *)dataValue {
  490. RCN_MUST_NOT_BE_MAIN_THREAD();
  491. const char *SQL = "INSERT OR REPLACE INTO " RCNTableNameExperiment
  492. " (_id, key, value) values ((SELECT _id from " RCNTableNameExperiment
  493. " WHERE key = ?), ?, ?)";
  494. sqlite3_stmt *statement = [self prepareSQL:SQL];
  495. if (!statement) {
  496. return NO;
  497. }
  498. if (![self bindStringToStatement:statement index:1 string:@RCNExperimentTableKeyMetadata]) {
  499. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  500. }
  501. if (![self bindStringToStatement:statement index:2 string:@RCNExperimentTableKeyMetadata]) {
  502. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  503. }
  504. if (sqlite3_bind_blob(statement, 3, dataValue.bytes, (int)dataValue.length, NULL) != SQLITE_OK) {
  505. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  506. }
  507. if (sqlite3_step(statement) != SQLITE_DONE) {
  508. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  509. }
  510. sqlite3_finalize(statement);
  511. return YES;
  512. }
  513. #pragma mark - update
  514. - (void)updateMetadataWithOption:(RCNUpdateOption)option
  515. values:(NSArray *)values
  516. completionHandler:(RCNDBCompletion)handler {
  517. dispatch_async(_databaseOperationQueue, ^{
  518. BOOL success = [self updateMetadataTableWithOption:option andValues:values];
  519. if (handler) {
  520. dispatch_async(dispatch_get_main_queue(), ^{
  521. handler(success, nil);
  522. });
  523. }
  524. });
  525. }
  526. - (BOOL)updateMetadataTableWithOption:(RCNUpdateOption)option andValues:(NSArray *)values {
  527. RCN_MUST_NOT_BE_MAIN_THREAD();
  528. static const char *SQL =
  529. "UPDATE " RCNTableNameMetadata " (last_fetch_status, last_fetch_error, last_apply_time, "
  530. "last_set_defaults_time) values (?, ?, ?, ?)";
  531. if (option == RCNUpdateOptionFetchStatus) {
  532. SQL = "UPDATE " RCNTableNameMetadata " SET last_fetch_status = ?, last_fetch_error = ?";
  533. } else if (option == RCNUpdateOptionApplyTime) {
  534. SQL = "UPDATE " RCNTableNameMetadata " SET last_apply_time = ?";
  535. } else if (option == RCNUpdateOptionDefaultTime) {
  536. SQL = "UPDATE " RCNTableNameMetadata " SET last_set_defaults_time = ?";
  537. } else {
  538. return NO;
  539. }
  540. sqlite3_stmt *statement = [self prepareSQL:SQL];
  541. if (!statement) {
  542. return NO;
  543. }
  544. int index = 0;
  545. if ((option == RCNUpdateOptionApplyTime || option == RCNUpdateOptionDefaultTime) &&
  546. values.count == 1) {
  547. double value = [values[0] doubleValue];
  548. if (sqlite3_bind_double(statement, ++index, value) != SQLITE_OK) {
  549. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  550. }
  551. } else if (option == RCNUpdateOptionFetchStatus && values.count == 2) {
  552. int value = [values[0] intValue];
  553. if (sqlite3_bind_int(statement, ++index, value) != SQLITE_OK) {
  554. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  555. }
  556. value = [values[1] intValue];
  557. if (sqlite3_bind_int(statement, ++index, value) != SQLITE_OK) {
  558. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  559. }
  560. }
  561. if (sqlite3_step(statement) != SQLITE_DONE) {
  562. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  563. }
  564. sqlite3_finalize(statement);
  565. return YES;
  566. }
  567. #pragma mark - read from DB
  568. - (NSDictionary *)loadMetadataWithBundleIdentifier:(NSString *)bundleIdentifier {
  569. __block NSDictionary *metadataTableResult;
  570. __weak RCNConfigDBManager *weakSelf = self;
  571. dispatch_sync(_databaseOperationQueue, ^{
  572. metadataTableResult = [weakSelf loadMetadataTableWithBundleIdentifier:bundleIdentifier];
  573. });
  574. if (metadataTableResult) {
  575. return metadataTableResult;
  576. }
  577. return [[NSDictionary alloc] init];
  578. }
  579. - (NSMutableDictionary *)loadMetadataTableWithBundleIdentifier:(NSString *)bundleIdentifier {
  580. NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
  581. const char *SQL =
  582. "SELECT bundle_identifier, fetch_time, digest_per_ns, device_context, app_context, "
  583. "success_fetch_time, failure_fetch_time , last_fetch_status, "
  584. "last_fetch_error, last_apply_time, last_set_defaults_time FROM " RCNTableNameMetadata
  585. " WHERE bundle_identifier = ?";
  586. sqlite3_stmt *statement = [self prepareSQL:SQL];
  587. if (!statement) {
  588. return nil;
  589. }
  590. NSArray *params = @[ bundleIdentifier ];
  591. [self bindStringsToStatement:statement stringArray:params];
  592. while (sqlite3_step(statement) == SQLITE_ROW) {
  593. NSString *dbBundleIdentifier =
  594. [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
  595. if (dbBundleIdentifier && ![dbBundleIdentifier isEqualToString:bundleIdentifier]) {
  596. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000014",
  597. @"Load Metadata from table error: Wrong package name %@, should be %@.",
  598. dbBundleIdentifier, bundleIdentifier);
  599. return nil;
  600. }
  601. double fetchTime = sqlite3_column_double(statement, 1);
  602. NSData *digestPerNamespace = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 2)
  603. length:sqlite3_column_bytes(statement, 2)];
  604. NSData *deviceContext = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 3)
  605. length:sqlite3_column_bytes(statement, 3)];
  606. NSData *appContext = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 4)
  607. length:sqlite3_column_bytes(statement, 4)];
  608. NSData *successTimeDigest = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 5)
  609. length:sqlite3_column_bytes(statement, 5)];
  610. NSData *failureTimeDigest = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 6)
  611. length:sqlite3_column_bytes(statement, 6)];
  612. int lastFetchStatus = sqlite3_column_int(statement, 7);
  613. int lastFetchFailReason = sqlite3_column_int(statement, 8);
  614. double lastApplyTimestamp = sqlite3_column_double(statement, 9);
  615. double lastSetDefaultsTimestamp = sqlite3_column_double(statement, 10);
  616. NSError *error;
  617. NSMutableDictionary *deviceContextDict = nil;
  618. if (deviceContext) {
  619. deviceContextDict = [NSJSONSerialization JSONObjectWithData:deviceContext
  620. options:NSJSONReadingMutableContainers
  621. error:&error];
  622. }
  623. NSMutableDictionary *appContextDict = nil;
  624. if (appContext) {
  625. appContextDict = [NSJSONSerialization JSONObjectWithData:appContext
  626. options:NSJSONReadingMutableContainers
  627. error:&error];
  628. }
  629. NSMutableDictionary<NSString *, id> *digestPerNamespaceDictionary = nil;
  630. if (digestPerNamespace) {
  631. digestPerNamespaceDictionary =
  632. [NSJSONSerialization JSONObjectWithData:digestPerNamespace
  633. options:NSJSONReadingMutableContainers
  634. error:&error];
  635. }
  636. NSMutableArray *successTimes = nil;
  637. if (successTimeDigest) {
  638. successTimes = [NSJSONSerialization JSONObjectWithData:successTimeDigest
  639. options:NSJSONReadingMutableContainers
  640. error:&error];
  641. }
  642. NSMutableArray *failureTimes = nil;
  643. if (failureTimeDigest) {
  644. failureTimes = [NSJSONSerialization JSONObjectWithData:failureTimeDigest
  645. options:NSJSONReadingMutableContainers
  646. error:&error];
  647. }
  648. dict[RCNKeyBundleIdentifier] = bundleIdentifier;
  649. dict[RCNKeyFetchTime] = @(fetchTime);
  650. dict[RCNKeyDigestPerNamespace] = digestPerNamespaceDictionary;
  651. dict[RCNKeyDeviceContext] = deviceContextDict;
  652. dict[RCNKeyAppContext] = appContextDict;
  653. dict[RCNKeySuccessFetchTime] = successTimes;
  654. dict[RCNKeyFailureFetchTime] = failureTimes;
  655. dict[RCNKeyLastFetchStatus] = @(lastFetchStatus);
  656. dict[RCNKeyLastFetchError] = @(lastFetchFailReason);
  657. dict[RCNKeyLastApplyTime] = @(lastApplyTimestamp);
  658. dict[RCNKeyLastSetDefaultsTime] = @(lastSetDefaultsTimestamp);
  659. break;
  660. }
  661. sqlite3_finalize(statement);
  662. return dict;
  663. }
  664. - (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler {
  665. __weak RCNConfigDBManager *weakSelf = self;
  666. dispatch_async(_databaseOperationQueue, ^{
  667. RCNConfigDBManager *strongSelf = weakSelf;
  668. if (!strongSelf) {
  669. return;
  670. }
  671. NSMutableArray *experimentPayloads =
  672. [strongSelf loadExperimentTableFromKey:@RCNExperimentTableKeyPayload];
  673. if (!experimentPayloads) {
  674. experimentPayloads = [[NSMutableArray alloc] init];
  675. }
  676. NSMutableDictionary *experimentMetadata;
  677. NSMutableArray *experiments =
  678. [strongSelf loadExperimentTableFromKey:@RCNExperimentTableKeyMetadata];
  679. // There should be only one entry for experiment metadata.
  680. if (experiments.count > 0) {
  681. NSError *error;
  682. experimentMetadata = [NSJSONSerialization JSONObjectWithData:experiments[0]
  683. options:NSJSONReadingMutableContainers
  684. error:&error];
  685. }
  686. if (!experimentMetadata) {
  687. experimentMetadata = [[NSMutableDictionary alloc] init];
  688. }
  689. if (handler) {
  690. dispatch_async(dispatch_get_main_queue(), ^{
  691. handler(
  692. YES, @{
  693. @RCNExperimentTableKeyPayload : [experimentPayloads copy],
  694. @RCNExperimentTableKeyMetadata : [experimentMetadata copy]
  695. });
  696. });
  697. }
  698. });
  699. }
  700. - (NSMutableArray<NSData *> *)loadExperimentTableFromKey:(NSString *)key {
  701. RCN_MUST_NOT_BE_MAIN_THREAD();
  702. NSMutableArray *results = [[NSMutableArray alloc] init];
  703. const char *SQL = "SELECT value FROM " RCNTableNameExperiment " WHERE key = ?";
  704. sqlite3_stmt *statement = [self prepareSQL:SQL];
  705. if (!statement) {
  706. return nil;
  707. }
  708. NSArray *params = @[ key ];
  709. [self bindStringsToStatement:statement stringArray:params];
  710. NSData *experimentData;
  711. while (sqlite3_step(statement) == SQLITE_ROW) {
  712. experimentData = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 0)
  713. length:sqlite3_column_bytes(statement, 0)];
  714. if (experimentData) {
  715. [results addObject:experimentData];
  716. }
  717. }
  718. sqlite3_finalize(statement);
  719. return results;
  720. }
  721. - (NSDictionary *)loadInternalMetadataTable {
  722. __block NSMutableDictionary *internalMetadataTableResult;
  723. __weak RCNConfigDBManager *weakSelf = self;
  724. dispatch_sync(_databaseOperationQueue, ^{
  725. internalMetadataTableResult = [weakSelf loadInternalMetadataTableInternal];
  726. });
  727. return internalMetadataTableResult;
  728. }
  729. - (NSMutableDictionary *)loadInternalMetadataTableInternal {
  730. NSMutableDictionary *internalMetadata = [[NSMutableDictionary alloc] init];
  731. const char *SQL = "SELECT key, value FROM " RCNTableNameInternalMetadata;
  732. sqlite3_stmt *statement = [self prepareSQL:SQL];
  733. if (!statement) {
  734. return nil;
  735. }
  736. while (sqlite3_step(statement) == SQLITE_ROW) {
  737. NSString *key = [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
  738. NSData *dataValue = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 1)
  739. length:sqlite3_column_bytes(statement, 1)];
  740. internalMetadata[key] = dataValue;
  741. }
  742. sqlite3_finalize(statement);
  743. return internalMetadata;
  744. }
  745. /// This method is only meant to be called at init time. The underlying logic will need to be
  746. /// revaluated if the assumption changes at a later time.
  747. - (void)loadMainWithBundleIdentifier:(NSString *)bundleIdentifier
  748. completionHandler:(RCNDBLoadCompletion)handler {
  749. __weak RCNConfigDBManager *weakSelf = self;
  750. dispatch_async(_databaseOperationQueue, ^{
  751. RCNConfigDBManager *strongSelf = weakSelf;
  752. if (!strongSelf) {
  753. return;
  754. }
  755. __block NSDictionary *fetchedConfig =
  756. [strongSelf loadMainTableWithBundleIdentifier:bundleIdentifier
  757. fromSource:RCNDBSourceFetched];
  758. __block NSDictionary *activeConfig =
  759. [strongSelf loadMainTableWithBundleIdentifier:bundleIdentifier
  760. fromSource:RCNDBSourceActive];
  761. __block NSDictionary *defaultConfig =
  762. [strongSelf loadMainTableWithBundleIdentifier:bundleIdentifier
  763. fromSource:RCNDBSourceDefault];
  764. if (handler) {
  765. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  766. fetchedConfig = fetchedConfig ? fetchedConfig : [[NSDictionary alloc] init];
  767. activeConfig = activeConfig ? activeConfig : [[NSDictionary alloc] init];
  768. defaultConfig = defaultConfig ? defaultConfig : [[NSDictionary alloc] init];
  769. handler(YES, fetchedConfig, activeConfig, defaultConfig);
  770. });
  771. }
  772. });
  773. }
  774. - (NSMutableDictionary *)loadMainTableWithBundleIdentifier:(NSString *)bundleIdentifier
  775. fromSource:(RCNDBSource)source {
  776. NSMutableDictionary *namespaceToConfig = [[NSMutableDictionary alloc] init];
  777. const char *SQL = "SELECT bundle_identifier, namespace, key, value FROM " RCNTableNameMain
  778. " WHERE bundle_identifier = ?";
  779. if (source == RCNDBSourceDefault) {
  780. SQL = "SELECT bundle_identifier, namespace, key, value FROM " RCNTableNameMainDefault
  781. " WHERE bundle_identifier = ?";
  782. } else if (source == RCNDBSourceActive) {
  783. SQL = "SELECT bundle_identifier, namespace, key, value FROM " RCNTableNameMainActive
  784. " WHERE bundle_identifier = ?";
  785. }
  786. NSArray *params = @[ bundleIdentifier ];
  787. sqlite3_stmt *statement = [self prepareSQL:SQL];
  788. if (!statement) {
  789. return nil;
  790. }
  791. [self bindStringsToStatement:statement stringArray:params];
  792. while (sqlite3_step(statement) == SQLITE_ROW) {
  793. NSString *configNamespace =
  794. [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
  795. NSString *key = [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
  796. NSData *value = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 3)
  797. length:sqlite3_column_bytes(statement, 3)];
  798. if (!namespaceToConfig[configNamespace]) {
  799. namespaceToConfig[configNamespace] = [[NSMutableDictionary alloc] init];
  800. }
  801. if (source == RCNDBSourceDefault) {
  802. namespaceToConfig[configNamespace][key] =
  803. [[FIRRemoteConfigValue alloc] initWithData:value source:FIRRemoteConfigSourceDefault];
  804. } else {
  805. namespaceToConfig[configNamespace][key] =
  806. [[FIRRemoteConfigValue alloc] initWithData:value source:FIRRemoteConfigSourceRemote];
  807. }
  808. }
  809. sqlite3_finalize(statement);
  810. return namespaceToConfig;
  811. }
  812. #pragma mark - delete
  813. - (void)deleteRecordFromMainTableWithNamespace:(NSString *)namespace_p
  814. bundleIdentifier:(NSString *)bundleIdentifier
  815. fromSource:(RCNDBSource)source {
  816. __weak RCNConfigDBManager *weakSelf = self;
  817. dispatch_async(_databaseOperationQueue, ^{
  818. RCNConfigDBManager *strongSelf = weakSelf;
  819. if (!strongSelf) {
  820. return;
  821. }
  822. NSArray *params = @[ bundleIdentifier, namespace_p ];
  823. const char *SQL =
  824. "DELETE FROM " RCNTableNameMain " WHERE bundle_identifier = ? and namespace = ?";
  825. if (source == RCNDBSourceDefault) {
  826. SQL = "DELETE FROM " RCNTableNameMainDefault " WHERE bundle_identifier = ? and namespace = ?";
  827. } else if (source == RCNDBSourceActive) {
  828. SQL = "DELETE FROM " RCNTableNameMainActive " WHERE bundle_identifier = ? and namespace = ?";
  829. }
  830. [strongSelf executeQuery:SQL withParams:params];
  831. });
  832. }
  833. - (void)deleteRecordWithBundleIdentifier:(NSString *)bundleIdentifier
  834. isInternalDB:(BOOL)isInternalDB {
  835. __weak RCNConfigDBManager *weakSelf = self;
  836. dispatch_async(_databaseOperationQueue, ^{
  837. RCNConfigDBManager *strongSelf = weakSelf;
  838. if (!strongSelf) {
  839. return;
  840. }
  841. const char *SQL = "DELETE FROM " RCNTableNameInternalMetadata " WHERE key LIKE ?";
  842. if (!isInternalDB) {
  843. SQL = "DELETE FROM " RCNTableNameMetadata " WHERE bundle_identifier = ?";
  844. }
  845. NSArray *params = @[ bundleIdentifier ];
  846. [strongSelf executeQuery:SQL withParams:params];
  847. });
  848. }
  849. - (void)deleteAllRecordsFromTableWithSource:(RCNDBSource)source {
  850. __weak RCNConfigDBManager *weakSelf = self;
  851. dispatch_async(_databaseOperationQueue, ^{
  852. RCNConfigDBManager *strongSelf = weakSelf;
  853. if (!strongSelf) {
  854. return;
  855. }
  856. const char *SQL = "DELETE FROM " RCNTableNameMain;
  857. if (source == RCNDBSourceDefault) {
  858. SQL = "DELETE FROM " RCNTableNameMainDefault;
  859. } else if (source == RCNDBSourceActive) {
  860. SQL = "DELETE FROM " RCNTableNameMainActive;
  861. }
  862. [strongSelf executeQuery:SQL];
  863. });
  864. }
  865. - (void)deleteExperimentTableForKey:(NSString *)key {
  866. __weak RCNConfigDBManager *weakSelf = self;
  867. dispatch_async(_databaseOperationQueue, ^{
  868. RCNConfigDBManager *strongSelf = weakSelf;
  869. if (!strongSelf) {
  870. return;
  871. }
  872. NSArray *params = @[ key ];
  873. const char *SQL = "DELETE FROM " RCNTableNameExperiment " WHERE key = ?";
  874. [strongSelf executeQuery:SQL withParams:params];
  875. });
  876. }
  877. #pragma mark - helper
  878. - (BOOL)executeQuery:(const char *)SQL withParams:(NSArray *)params {
  879. RCN_MUST_NOT_BE_MAIN_THREAD();
  880. sqlite3_stmt *statement = [self prepareSQL:SQL];
  881. if (!statement) {
  882. return NO;
  883. }
  884. [self bindStringsToStatement:statement stringArray:params];
  885. if (sqlite3_step(statement) != SQLITE_DONE) {
  886. return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  887. }
  888. sqlite3_finalize(statement);
  889. return YES;
  890. }
  891. /// Params only accept TEXT format string.
  892. - (BOOL)bindStringsToStatement:(sqlite3_stmt *)statement stringArray:(NSArray *)array {
  893. int index = 1;
  894. for (NSString *param in array) {
  895. if (![self bindStringToStatement:statement index:index string:param]) {
  896. return [self logErrorWithSQL:nil finalizeStatement:statement returnValue:NO];
  897. }
  898. index++;
  899. }
  900. return YES;
  901. }
  902. - (BOOL)bindStringToStatement:(sqlite3_stmt *)statement index:(int)index string:(NSString *)value {
  903. if (sqlite3_bind_text(statement, index, [value UTF8String], -1, SQLITE_TRANSIENT) != SQLITE_OK) {
  904. return [self logErrorWithSQL:nil finalizeStatement:statement returnValue:NO];
  905. }
  906. return YES;
  907. }
  908. - (sqlite3_stmt *)prepareSQL:(const char *)SQL {
  909. sqlite3_stmt *statement = nil;
  910. if (sqlite3_prepare_v2(_database, SQL, -1, &statement, NULL) != SQLITE_OK) {
  911. [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
  912. return nil;
  913. }
  914. return statement;
  915. }
  916. - (NSString *)errorMessage {
  917. return [NSString stringWithFormat:@"%s", sqlite3_errmsg(_database)];
  918. }
  919. - (int)errorCode {
  920. return sqlite3_errcode(_database);
  921. }
  922. - (void)logDatabaseError {
  923. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000015", @"Error message: %@. Error code: %d.",
  924. [self errorMessage], [self errorCode]);
  925. }
  926. - (BOOL)logErrorWithSQL:(const char *)SQL
  927. finalizeStatement:(sqlite3_stmt *)statement
  928. returnValue:(BOOL)returnValue {
  929. if (SQL) {
  930. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000016", @"Failed with SQL: %s.", SQL);
  931. }
  932. [self logDatabaseError];
  933. if (statement) {
  934. sqlite3_finalize(statement);
  935. }
  936. return returnValue;
  937. }
  938. - (BOOL)isNewDatabase {
  939. return gIsNewDatabase;
  940. }
  941. @end