Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

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