DatabaseActor.swift 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. import SQLite3
  16. import FirebaseCoreExtension
  17. /// SQLite file name in versions 0, 1 and 2.
  18. private let RCNDatabaseName = "RemoteConfig.sqlite3"
  19. // Actor for database operations
  20. actor DatabaseActor {
  21. private var database: OpaquePointer?
  22. let dbPath: String
  23. init(dbPath: String) {
  24. self.dbPath = dbPath
  25. Task {
  26. await createOrOpenDatabase()
  27. }
  28. }
  29. func createOrOpenDatabase() {
  30. let oldV0DBPath = remoteConfigPathForOldDatabaseV0()
  31. // Backward Compatibility
  32. if FileManager.default.fileExists(atPath: oldV0DBPath) {
  33. RCLog.info("I-RCN000009",
  34. "Old database V0 exists, removed it and replace with the new one.")
  35. removeDatabase(atPath: oldV0DBPath)
  36. }
  37. RCLog.info("I-RCN000062", "Loading database at path \(dbPath)")
  38. let cDbPath = (dbPath as NSString).utf8String
  39. // Create or open database path.
  40. if !createFilePath(ifNotExist: dbPath) {
  41. return
  42. }
  43. let flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX |
  44. SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION
  45. if sqlite3_open_v2(cDbPath, &database, flags, nil) == SQLITE_OK {
  46. // Always try to create table if not exists for backward compatibility.
  47. if !createTableSchema() {
  48. // Remove database before failing.
  49. removeDatabase(atPath: dbPath)
  50. // If it failed again, there's nothing we can do here.
  51. RCLog.error("I-RCN000010", "Failed to create table.")
  52. // Create a new database if existing database file is corrupted.
  53. if !createFilePath(ifNotExist: dbPath) {
  54. return
  55. }
  56. if sqlite3_open_v2(cDbPath, &database, flags, nil) == SQLITE_OK {
  57. if !createTableSchema() {
  58. // Remove database before fail.
  59. removeDatabase(atPath: dbPath)
  60. // If it failed again, there's nothing we can do here.
  61. RCLog.error("I-RCN000010", "Failed to create table.")
  62. } else {
  63. // Exclude the app data used from iCloud backup.
  64. addSkipBackupAttribute(toItemAtPath: dbPath)
  65. }
  66. } else {
  67. logDatabaseError()
  68. }
  69. } else {
  70. // DB file already exists. Migrate any V1 namespace column entries to V2 fully qualified
  71. // 'namespace:FIRApp' entries.
  72. migrateV1NamespaceToV2Namespace()
  73. // Exclude the app data used from iCloud backup.
  74. addSkipBackupAttribute(toItemAtPath: dbPath)
  75. }
  76. } else {
  77. logDatabaseError()
  78. }
  79. }
  80. func insertMetadataTable(withValues columnNameToValue: [String: Any]) -> Bool {
  81. let sql = """
  82. INSERT into fetch_metadata_v2 (\
  83. bundle_identifier, \
  84. namespace, \
  85. fetch_time, \
  86. digest_per_ns, \
  87. device_context, \
  88. app_context, \
  89. success_fetch_time, \
  90. failure_fetch_time, \
  91. last_fetch_status, \
  92. last_fetch_error, \
  93. last_apply_time, \
  94. last_set_defaults_time\
  95. ) values (\
  96. ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?\
  97. )
  98. """
  99. var statement: OpaquePointer? = nil
  100. defer { sqlite3_finalize(statement) }
  101. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  102. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  103. }
  104. let columns = [
  105. RCNKeyBundleIdentifier, RCNKeyNamespace, RCNKeyFetchTime, RCNKeyDigestPerNamespace,
  106. RCNKeyDeviceContext, RCNKeyAppContext, RCNKeySuccessFetchTime, RCNKeyFailureFetchTime,
  107. RCNKeyLastFetchStatus, RCNKeyLastFetchError, RCNKeyLastApplyTime, RCNKeyLastSetDefaultsTime,
  108. ]
  109. var index = 0
  110. for column in columns {
  111. index += 1
  112. switch column {
  113. case RCNKeyBundleIdentifier, RCNKeyNamespace:
  114. let value = columnNameToValue[column] as? String ?? ""
  115. if bindText(statement, Int32(index), value) != SQLITE_OK {
  116. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  117. }
  118. case RCNKeyFetchTime, RCNKeyLastApplyTime, RCNKeyLastSetDefaultsTime:
  119. let value = columnNameToValue[column] as? Double ?? 0
  120. if sqlite3_bind_double(statement, Int32(index), value) != SQLITE_OK {
  121. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  122. }
  123. case RCNKeyLastFetchStatus, RCNKeyLastFetchError:
  124. let value = columnNameToValue[column] as? Int ?? 0
  125. if sqlite3_bind_int(statement, Int32(index), Int32(value)) != SQLITE_OK {
  126. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  127. }
  128. default:
  129. let data = columnNameToValue[column] as? Data ?? Data()
  130. if sqlite3_bind_blob(statement, Int32(index), (data as NSData).bytes, Int32(data.count),
  131. nil) != SQLITE_OK {
  132. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  133. }
  134. }
  135. }
  136. if sqlite3_step(statement) != SQLITE_DONE {
  137. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  138. }
  139. return true
  140. }
  141. func insertMainTable(withValues values: [Any], fromSource source: DBSource) -> Bool {
  142. guard values.count == 4,
  143. let bundleIdentifier = values[0] as? String,
  144. let namespace = values[1] as? String,
  145. let key = values[2] as? String,
  146. let value = values[3] as? Data
  147. else {
  148. RCLog.error("I-RCN000013",
  149. "Failed to insert config record. Wrong number of give parameters, current " +
  150. "number is \(values.count), correct number is 4.")
  151. return false
  152. }
  153. let sql =
  154. switch source {
  155. case .active:
  156. """
  157. INSERT INTO main_active (bundle_identifier, namespace, key, value) \
  158. VALUES (?, ?, ?, ?)
  159. """
  160. case .default:
  161. """
  162. INSERT INTO main_default (bundle_identifier, namespace, key, value) \
  163. VALUES (?, ?, ?, ?)
  164. """
  165. case .fetched:
  166. """
  167. INSERT INTO main (bundle_identifier, namespace, key, value) \
  168. VALUES (?, ?, ?, ?)
  169. """
  170. }
  171. var statement: OpaquePointer? = nil
  172. defer { sqlite3_finalize(statement) }
  173. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  174. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  175. }
  176. if bindText(statement, 1, bundleIdentifier) != SQLITE_OK ||
  177. bindText(statement, 2, namespace) != SQLITE_OK ||
  178. bindText(statement, 3, key) != SQLITE_OK ||
  179. sqlite3_bind_blob(statement, 4, (value as NSData).bytes, Int32(value.count), nil) !=
  180. SQLITE_OK {
  181. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  182. }
  183. if sqlite3_step(statement) != SQLITE_DONE {
  184. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  185. }
  186. return true
  187. }
  188. func insertInternalMetadataTable(withValues values: [Any]) -> Bool {
  189. guard values.count == 2,
  190. let key = values[0] as? String,
  191. let value = values[1] as? Data
  192. else {
  193. return false
  194. }
  195. let sql = """
  196. INSERT OR REPLACE INTO internal_metadata (key, value) \
  197. VALUES (?, ?)
  198. """
  199. var statement: OpaquePointer? = nil
  200. defer { sqlite3_finalize(statement) }
  201. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  202. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  203. }
  204. if bindText(statement, 1, key) != SQLITE_OK ||
  205. sqlite3_bind_blob(statement, 2, (value as NSData).bytes, Int32(value.count), nil) !=
  206. SQLITE_OK {
  207. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  208. }
  209. if sqlite3_step(statement) != SQLITE_DONE {
  210. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  211. }
  212. return true
  213. }
  214. func insertExperimentTable(withKey key: String, value dataValue: Data) -> Bool {
  215. let sql = "INSERT INTO experiment (key, value) values (?, ?)"
  216. var statement: OpaquePointer? = nil
  217. defer { sqlite3_finalize(statement) }
  218. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  219. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  220. }
  221. if bindText(statement, 1, key) != SQLITE_OK ||
  222. sqlite3_bind_blob(statement, 2, (dataValue as NSData).bytes, Int32(dataValue.count), nil)
  223. != SQLITE_OK {
  224. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  225. }
  226. if sqlite3_step(statement) != SQLITE_DONE {
  227. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  228. }
  229. return true
  230. }
  231. func insertOrUpdatePersonalizationConfig(_ payload: Data,
  232. fromSource source: DBSource) -> Bool {
  233. let sql = """
  234. INSERT OR REPLACE INTO personalization (_id, key, value) values ((
  235. SELECT _id from personalization WHERE key = ?
  236. ), ?, ?)
  237. """
  238. var statement: OpaquePointer? = nil
  239. defer { sqlite3_finalize(statement) }
  240. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  241. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  242. }
  243. if sqlite3_bind_int(statement, 1, Int32(source.rawValue)) != SQLITE_OK ||
  244. sqlite3_bind_int(statement, 2, Int32(source.rawValue)) != SQLITE_OK ||
  245. sqlite3_bind_blob(statement, 3, (payload as NSData).bytes, Int32(payload.count), nil)
  246. != SQLITE_OK {
  247. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  248. }
  249. if sqlite3_step(statement) != SQLITE_DONE {
  250. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  251. }
  252. return true
  253. }
  254. func update(experimentMetadata dataValue: Data) -> Bool {
  255. let sql = """
  256. INSERT OR REPLACE INTO experiment (_id, key, value) values ((
  257. SELECT _id from experiment WHERE key = ?), ?, ?)
  258. """
  259. var statement: OpaquePointer? = nil
  260. defer { sqlite3_finalize(statement) }
  261. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  262. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  263. }
  264. if bindText(statement, 1, ConfigConstants.experimentTableKeyMetadata) != SQLITE_OK ||
  265. bindText(statement, 2, ConfigConstants.experimentTableKeyMetadata) != SQLITE_OK ||
  266. sqlite3_bind_blob(statement, 3, (dataValue as NSData).bytes, Int32(dataValue.count), nil)
  267. != SQLITE_OK {
  268. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  269. }
  270. if sqlite3_step(statement) != SQLITE_DONE {
  271. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  272. }
  273. return true
  274. }
  275. func insertOrUpdateRolloutTable(withKey key: String,
  276. value arrayValue: [[String: Any]]) -> Bool {
  277. do {
  278. let dataValue = try JSONSerialization.data(withJSONObject: arrayValue,
  279. options: .prettyPrinted)
  280. let sql = """
  281. INSERT OR REPLACE INTO rollout (_id, key, value) \
  282. VALUES ((SELECT _id from rollout WHERE key = ?), ?, ?)
  283. """
  284. var statement: OpaquePointer? = nil
  285. defer { sqlite3_finalize(statement) }
  286. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  287. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  288. }
  289. if bindText(statement, 1, key) != SQLITE_OK ||
  290. bindText(statement, 2, key) != SQLITE_OK ||
  291. sqlite3_bind_blob(statement, 3, (dataValue as NSData).bytes, Int32(dataValue.count),
  292. nil) != SQLITE_OK {
  293. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  294. }
  295. if sqlite3_step(statement) != SQLITE_DONE {
  296. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  297. }
  298. return true
  299. } catch {
  300. return false
  301. }
  302. }
  303. func updateMetadataTable(withOption option: UpdateOption,
  304. namespace: String,
  305. values: [Any]) -> Bool {
  306. var sql: String
  307. switch option {
  308. case .applyTime:
  309. sql = "UPDATE fetch_metadata_v2 SET last_apply_time = ? WHERE namespace = ?"
  310. case .defaultTime:
  311. sql = "UPDATE fetch_metadata_v2 SET last_set_defaults_time = ? WHERE namespace = ?"
  312. case .fetchStatus:
  313. sql =
  314. "UPDATE fetch_metadata_v2 SET last_fetch_status = ?, last_fetch_error = ? WHERE namespace = ?"
  315. }
  316. var statement: OpaquePointer? = nil
  317. defer { sqlite3_finalize(statement) }
  318. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  319. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  320. }
  321. var index = 0
  322. if option == .applyTime || option == .defaultTime, values.count == 1 {
  323. index += 1
  324. let value = values[0] as? Double ?? 0
  325. if sqlite3_bind_double(statement, Int32(index), value) != SQLITE_OK {
  326. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  327. }
  328. } else if option == .fetchStatus, values.count == 2 {
  329. for i in 0 ..< 2 {
  330. index += 1
  331. let value = values[i] as? Int ?? 0
  332. if sqlite3_bind_int(statement, Int32(index), Int32(value)) != SQLITE_OK {
  333. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  334. }
  335. }
  336. }
  337. index += 1
  338. if bindText(statement, Int32(index), namespace) != SQLITE_OK {
  339. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  340. }
  341. if sqlite3_step(statement) != SQLITE_DONE {
  342. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  343. }
  344. return true
  345. }
  346. func loadMetadataTable(withBundleIdentifier bundleIdentifier: String,
  347. namespace: String) -> [String: Sendable] {
  348. let sql = """
  349. SELECT \
  350. bundle_identifier, \
  351. fetch_time, \
  352. digest_per_ns, \
  353. device_context, \
  354. app_context, \
  355. success_fetch_time, \
  356. failure_fetch_time, \
  357. last_fetch_status, \
  358. last_fetch_error, \
  359. last_apply_time, \
  360. last_set_defaults_time \
  361. FROM fetch_metadata_v2 \
  362. WHERE bundle_identifier = ? AND namespace = ?
  363. """
  364. var statement: OpaquePointer?
  365. defer { sqlite3_finalize(statement) }
  366. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  367. return logError(withSQL: sql, finalizeStatement: statement, returnValue: [:])
  368. }
  369. let params = [bundleIdentifier, namespace]
  370. if !bind(strings: params, toStatement: statement) {
  371. return logError(withSQL: sql, finalizeStatement: statement, returnValue: [:])
  372. }
  373. var result = [String: Any]()
  374. while sqlite3_step(statement) == SQLITE_ROW {
  375. let dbBundleIdentifier = String(cString: sqlite3_column_text(statement, 0))
  376. if dbBundleIdentifier != bundleIdentifier {
  377. RCLog.error("I-RCN000014",
  378. "Load Metadata from table error: Wrong package name \(dbBundleIdentifier), " +
  379. "should be \(bundleIdentifier).")
  380. return [:]
  381. }
  382. let fetchTime = sqlite3_column_double(statement, 1)
  383. let digestPerNamespace = Data(bytes: sqlite3_column_blob(statement, 2),
  384. count: Int(sqlite3_column_bytes(statement, 2)))
  385. let deviceContext = Data(bytes: sqlite3_column_blob(statement, 3),
  386. count: Int(sqlite3_column_bytes(statement, 3)))
  387. let appContext = Data(bytes: sqlite3_column_blob(statement, 4),
  388. count: Int(sqlite3_column_bytes(statement, 4)))
  389. let successTimeDigest = Data(bytes: sqlite3_column_blob(statement, 5),
  390. count: Int(sqlite3_column_bytes(statement, 5)))
  391. let failureTimeDigest = Data(bytes: sqlite3_column_blob(statement, 6),
  392. count: Int(sqlite3_column_bytes(statement, 6)))
  393. let lastFetchStatus = sqlite3_column_int(statement, 7)
  394. let lastFetchFailReason = sqlite3_column_int(statement, 8)
  395. let lastApplyTimestamp = sqlite3_column_double(statement, 9)
  396. let lastSetDefaultsTimestamp = sqlite3_column_double(statement, 10)
  397. let deviceContextDict = try? JSONSerialization.jsonObject(with: deviceContext,
  398. options: .mutableContainers) as? [
  399. String: Any
  400. ]
  401. let appContextDict = try? JSONSerialization.jsonObject(with: appContext,
  402. options: .mutableContainers) as? [
  403. String: Any
  404. ]
  405. let digestPerNamespaceDictionary = try? JSONSerialization.jsonObject(with: digestPerNamespace,
  406. options: .mutableContainers)
  407. as? [String: Any]
  408. let successTimes = try? JSONSerialization.jsonObject(with: successTimeDigest,
  409. options: .mutableContainers)
  410. as? [TimeInterval]
  411. let failureTimes = try? JSONSerialization.jsonObject(with: failureTimeDigest,
  412. options: .mutableContainers)
  413. as? [TimeInterval]
  414. result[RCNKeyBundleIdentifier] = dbBundleIdentifier
  415. result[RCNKeyFetchTime] = fetchTime
  416. result[RCNKeyDigestPerNamespace] = digestPerNamespaceDictionary
  417. result[RCNKeyDeviceContext] = deviceContextDict
  418. result[RCNKeyAppContext] = appContextDict
  419. result[RCNKeySuccessFetchTime] = successTimes
  420. result[RCNKeyFailureFetchTime] = failureTimes
  421. result[RCNKeyLastFetchStatus] = Int(lastFetchStatus)
  422. result[RCNKeyLastFetchError] = Int(lastFetchFailReason)
  423. result[RCNKeyLastApplyTime] = lastApplyTimestamp
  424. result[RCNKeyLastSetDefaultsTime] = lastSetDefaultsTimestamp
  425. break // Stop after the first row, as there should only be one.
  426. }
  427. return result
  428. }
  429. func loadMainTable(withBundleIdentifier bundleIdentifier: String,
  430. fromSource source: DBSource) -> [String: [String: RemoteConfigValue]] {
  431. var namespaceToConfig = [String: [String: RemoteConfigValue]]()
  432. let sql =
  433. switch source {
  434. case .active:
  435. "SELECT namespace, key, value FROM main_active WHERE bundle_identifier = ?"
  436. case .default:
  437. "SELECT namespace, key, value FROM main_default WHERE bundle_identifier = ?"
  438. case .fetched:
  439. "SELECT namespace, key, value FROM main WHERE bundle_identifier = ?"
  440. }
  441. var statement: OpaquePointer?
  442. defer { sqlite3_finalize(statement) }
  443. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  444. return logError(withSQL: sql, finalizeStatement: statement, returnValue: [:])
  445. }
  446. if bindText(statement, 1, bundleIdentifier) != SQLITE_OK {
  447. return logError(withSQL: sql, finalizeStatement: statement, returnValue: [:])
  448. }
  449. while sqlite3_step(statement) == SQLITE_ROW {
  450. let configNamespace = String(cString: sqlite3_column_text(statement, 0))
  451. let key = String(cString: sqlite3_column_text(statement, 1))
  452. let valueData = Data(bytes: sqlite3_column_blob(statement, 2),
  453. count: Int(sqlite3_column_bytes(statement, 2)))
  454. let value = RemoteConfigValue(
  455. data: valueData,
  456. source: source == .default ? .default : .remote
  457. )
  458. if namespaceToConfig[configNamespace] == nil {
  459. namespaceToConfig[configNamespace] = [:]
  460. }
  461. namespaceToConfig[configNamespace]?[key] = value
  462. }
  463. return namespaceToConfig
  464. }
  465. func loadExperimentTable(fromKey key: String) -> [Data]? {
  466. let sql = "SELECT value FROM experiment WHERE key = ?"
  467. var statement: OpaquePointer?
  468. defer { sqlite3_finalize(statement) }
  469. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  470. return logError(withSQL: sql, finalizeStatement: statement, returnValue: nil)
  471. }
  472. if bindText(statement, 1, key) != SQLITE_OK {
  473. return logError(withSQL: sql, finalizeStatement: statement, returnValue: nil)
  474. }
  475. var results = [Data]()
  476. while sqlite3_step(statement) == SQLITE_ROW {
  477. if let bytes = sqlite3_column_blob(statement, 0) {
  478. let valueData = Data(bytes: bytes, count: Int(sqlite3_column_bytes(statement, 0)))
  479. results.append(valueData)
  480. } else {
  481. results.append(Data())
  482. }
  483. }
  484. return results
  485. }
  486. func loadRolloutTable(fromKey key: String) -> [[String: Sendable]] {
  487. let sql = "SELECT value FROM rollout WHERE key = ?"
  488. var statement: OpaquePointer?
  489. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  490. logError(withSQL: sql, finalizeStatement: statement, returnValue: ())
  491. }
  492. defer { sqlite3_finalize(statement) }
  493. if bindText(statement, 1, key) != SQLITE_OK {
  494. logError(withSQL: sql, finalizeStatement: statement, returnValue: ())
  495. }
  496. var results = [Data]()
  497. while sqlite3_step(statement) == SQLITE_ROW {
  498. let valueData = Data(
  499. bytes: sqlite3_column_blob(statement, 0),
  500. count: Int(sqlite3_column_bytes(statement, 0))
  501. )
  502. results.append(valueData)
  503. }
  504. if let data = results.first {
  505. // Convert from NSData to NSArray
  506. if let rollout = try? JSONSerialization
  507. .jsonObject(with: data, options: []) as? [[String: Sendable]] {
  508. return rollout
  509. } else {
  510. RCLog.error("I-RCN000011",
  511. "Failed to convert NSData to NSArray for Rollout Metadata")
  512. }
  513. }
  514. return []
  515. }
  516. func loadPersonalizationTable(fromKey key: Int) -> Data? {
  517. let sql = "SELECT value FROM personalization WHERE key = ?"
  518. var statement: OpaquePointer?
  519. defer { sqlite3_finalize(statement) }
  520. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  521. return logError(withSQL: sql, finalizeStatement: statement, returnValue: nil)
  522. }
  523. if sqlite3_bind_int(statement, 1, Int32(key)) != SQLITE_OK {
  524. return logError(withSQL: sql, finalizeStatement: statement, returnValue: nil)
  525. }
  526. var results = [Data]()
  527. while sqlite3_step(statement) == SQLITE_ROW {
  528. let valueData = Data(bytes: sqlite3_column_blob(statement, 0),
  529. count: Int(sqlite3_column_bytes(statement, 0)))
  530. results.append(valueData)
  531. }
  532. // There should be only one entry in this table.
  533. if results.count == 1 {
  534. return results[0]
  535. }
  536. return nil
  537. }
  538. func loadInternalMetadataTableInternal() -> [String: Data] {
  539. var internalMetadata = [String: Data]()
  540. let sql = "SELECT key, value FROM internal_metadata"
  541. var statement: OpaquePointer?
  542. defer { sqlite3_finalize(statement) }
  543. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  544. logError(withSQL: sql, finalizeStatement: statement, returnValue: ())
  545. }
  546. while sqlite3_step(statement) == SQLITE_ROW {
  547. let key = String(cString: sqlite3_column_text(statement, 0))
  548. let valueData = Data(bytes: sqlite3_column_blob(statement, 1),
  549. count: Int(sqlite3_column_bytes(statement, 1)))
  550. internalMetadata[key] = valueData
  551. }
  552. return internalMetadata
  553. }
  554. private func migrateV1NamespaceToV2Namespace() {
  555. for table in ["main", "main_active", "main_default"] {
  556. let selectSQL = "SELECT namespace FROM \(table) WHERE namespace NOT LIKE '%%:%%'"
  557. var statement: OpaquePointer?
  558. if sqlite3_prepare_v2(database, selectSQL, -1, &statement, nil) != SQLITE_OK {
  559. logError(withSQL: selectSQL, finalizeStatement: statement, returnValue: ())
  560. return
  561. }
  562. var namespacesToUpdate = [String]()
  563. while sqlite3_step(statement) == SQLITE_ROW {
  564. let namespace = String(cString: sqlite3_column_text(statement, 0))
  565. namespacesToUpdate.append(namespace)
  566. }
  567. sqlite3_finalize(statement)
  568. var updateStatement: OpaquePointer?
  569. for namespaceToUpdate in namespacesToUpdate {
  570. let newNamespace = "\(namespaceToUpdate):\(kFIRDefaultAppName)"
  571. let updateSQL = "UPDATE \(table) SET namespace = ? WHERE namespace = ?"
  572. if sqlite3_prepare_v2(database, updateSQL, -1, &updateStatement, nil) != SQLITE_OK {
  573. logError(withSQL: updateSQL, finalizeStatement: updateStatement, returnValue: ())
  574. return
  575. }
  576. if bindText(updateStatement, 1, newNamespace) != SQLITE_OK ||
  577. bindText(updateStatement, 2, namespaceToUpdate) != SQLITE_OK {
  578. logError(withSQL: updateSQL, finalizeStatement: updateStatement, returnValue: ())
  579. return
  580. }
  581. if sqlite3_step(updateStatement) != SQLITE_DONE {
  582. logError(withSQL: updateSQL, finalizeStatement: updateStatement, returnValue: ())
  583. return
  584. }
  585. sqlite3_finalize(updateStatement)
  586. }
  587. }
  588. }
  589. private func createFilePath(ifNotExist filePath: String) -> Bool {
  590. if filePath.isEmpty {
  591. RCLog.error("I-RCN000018",
  592. "Failed to create subdirectory for an empty file path.")
  593. return false
  594. }
  595. let fileManager = FileManager.default
  596. if !fileManager.fileExists(atPath: filePath) {
  597. do {
  598. try fileManager.createDirectory(
  599. atPath: URL(fileURLWithPath: filePath).deletingLastPathComponent().path,
  600. withIntermediateDirectories: true,
  601. attributes: nil
  602. )
  603. } catch {
  604. RCLog.error("I-RCN000019",
  605. "Failed to create subdirectory for database file: \(error)")
  606. return false
  607. }
  608. }
  609. return true
  610. }
  611. private func createTableSchema() -> Bool {
  612. let createMain = """
  613. CREATE TABLE IF NOT EXISTS main (
  614. _id INTEGER PRIMARY KEY,
  615. bundle_identifier TEXT,
  616. namespace TEXT,
  617. key TEXT,
  618. value BLOB
  619. )
  620. """
  621. let createMainActive = """
  622. CREATE TABLE IF NOT EXISTS main_active (
  623. _id INTEGER PRIMARY KEY,
  624. bundle_identifier TEXT,
  625. namespace TEXT,
  626. key TEXT,
  627. value BLOB
  628. )
  629. """
  630. let createMainDefault = """
  631. CREATE TABLE IF NOT EXISTS main_default (
  632. _id INTEGER PRIMARY KEY,
  633. bundle_identifier TEXT,
  634. namespace TEXT,
  635. key TEXT,
  636. value BLOB
  637. )
  638. """
  639. let createMetadata = """
  640. CREATE TABLE IF NOT EXISTS fetch_metadata_v2 (
  641. _id INTEGER PRIMARY KEY,
  642. bundle_identifier TEXT,
  643. namespace TEXT,
  644. fetch_time INTEGER,
  645. digest_per_ns BLOB,
  646. device_context BLOB,
  647. app_context BLOB,
  648. success_fetch_time BLOB,
  649. failure_fetch_time BLOB,
  650. last_fetch_status INTEGER,
  651. last_fetch_error INTEGER,
  652. last_apply_time INTEGER,
  653. last_set_defaults_time INTEGER
  654. )
  655. """
  656. let createInternalMetadata = """
  657. CREATE TABLE IF NOT EXISTS internal_metadata (
  658. _id INTEGER PRIMARY KEY,
  659. key TEXT,
  660. value BLOB
  661. )
  662. """
  663. let createExperiment = """
  664. CREATE TABLE IF NOT EXISTS experiment (
  665. _id INTEGER PRIMARY KEY,
  666. key TEXT,
  667. value BLOB
  668. )
  669. """
  670. let createPersonalization = """
  671. CREATE TABLE IF NOT EXISTS personalization (
  672. _id INTEGER PRIMARY KEY,
  673. key INTEGER,
  674. value BLOB
  675. )
  676. """
  677. let createRollout = """
  678. CREATE TABLE IF NOT EXISTS rollout (
  679. _id INTEGER PRIMARY KEY,
  680. key TEXT,
  681. value BLOB
  682. )
  683. """
  684. return executeQuery(createMain) &&
  685. executeQuery(createMainActive) &&
  686. executeQuery(createMainDefault) &&
  687. executeQuery(createMetadata) &&
  688. executeQuery(createInternalMetadata) &&
  689. executeQuery(createExperiment) &&
  690. executeQuery(createPersonalization) &&
  691. executeQuery(createRollout)
  692. }
  693. // MARK: - Delete
  694. func deleteRecord(fromMainTableWithNamespace namespace: String,
  695. bundleIdentifier: String,
  696. fromSource source: DBSource) {
  697. let params = [bundleIdentifier, namespace]
  698. let sql =
  699. if source == .default {
  700. "DELETE FROM main_default WHERE bundle_identifier = ? and namespace = ?"
  701. } else if source == .active {
  702. "DELETE FROM main_active WHERE bundle_identifier = ? and namespace = ?"
  703. } else {
  704. "DELETE FROM main WHERE bundle_identifier = ? and namespace = ?"
  705. }
  706. executeQuery(sql, withParams: params)
  707. }
  708. func deleteRecord(withBundleIdentifier bundleIdentifier: String,
  709. namespace: String) {
  710. let sql = "DELETE FROM fetch_metadata_v2 WHERE bundle_identifier = ? and namespace = ?"
  711. let params = [bundleIdentifier, namespace]
  712. executeQuery(sql, withParams: params)
  713. }
  714. func deleteAllRecords(fromTableWithSource source: DBSource) {
  715. let sql =
  716. if source == .default {
  717. "DELETE FROM main_default"
  718. } else if source == .active {
  719. "DELETE FROM main_active"
  720. } else {
  721. "DELETE FROM main"
  722. }
  723. executeQuery(sql)
  724. }
  725. func deleteExperimentTable(forKey key: String) {
  726. let params = [key]
  727. let sql = "DELETE FROM experiment WHERE key = ?"
  728. executeQuery(sql, withParams: params)
  729. }
  730. func closeDatabase(atPath path: String) {
  731. if sqlite3_close(database) != SQLITE_OK {
  732. logDatabaseError()
  733. }
  734. database = nil
  735. }
  736. func removeDatabase(atPath path: String) {
  737. closeDatabase(atPath: path)
  738. do {
  739. try FileManager.default.removeItem(atPath: path)
  740. } catch {
  741. RCLog.error("I-RCN000011",
  742. "Failed to remove database at path \(path) for error \(error).")
  743. }
  744. }
  745. @discardableResult
  746. func executeQuery(_ sql: String) -> Bool {
  747. var error: UnsafeMutablePointer<Int8>?
  748. if sqlite3_exec(database, sql, nil, nil, &error) != SQLITE_OK {
  749. if let error {
  750. RCLog.error("I-RCN000012",
  751. "Failed to execute query with error \(error).")
  752. } else {
  753. RCLog.error("I-RCN000012",
  754. "Failed to execute query with no error.")
  755. }
  756. sqlite3_free(error)
  757. return false
  758. }
  759. return true
  760. }
  761. @discardableResult
  762. func executeQuery(_ sql: String, withParams params: [String]) -> Bool {
  763. var statement: OpaquePointer? = nil
  764. defer { sqlite3_finalize(statement) }
  765. if sqlite3_prepare_v2(database, sql, -1, &statement, nil) != SQLITE_OK {
  766. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  767. }
  768. if !bind(strings: params, toStatement: statement) {
  769. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  770. }
  771. if sqlite3_step(statement) != SQLITE_DONE {
  772. return logError(withSQL: sql, finalizeStatement: statement, returnValue: false)
  773. }
  774. return true
  775. }
  776. /// Params only accept TEXT format string.
  777. private func bind(strings: [String], toStatement statement: OpaquePointer?) -> Bool {
  778. var index = 1
  779. for param in strings {
  780. if bindText(statement, Int32(index), param) != SQLITE_OK {
  781. return logError(withSQL: nil, finalizeStatement: statement, returnValue: false)
  782. }
  783. index += 1
  784. }
  785. return true
  786. }
  787. private func addSkipBackupAttribute(toItemAtPath filePathString: String) {
  788. let url = URL(fileURLWithPath: filePathString)
  789. assert(FileManager.default.fileExists(atPath: url.path))
  790. do {
  791. try (url as NSURL).setResourceValue(true, forKey: .isExcludedFromBackupKey)
  792. } catch {
  793. RCLog.error("I-RCN000017",
  794. "Error excluding \(url.lastPathComponent) from backup \(error).")
  795. }
  796. }
  797. // MARK: Fileprivate Helpers
  798. fileprivate func remoteConfigPathForOldDatabaseV0() -> String {
  799. let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
  800. let docPath = dirPaths[0]
  801. return URL(fileURLWithPath: docPath).appendingPathComponent(RCNDatabaseName).path
  802. }
  803. // MARK: - Error Handling
  804. private func logError<T>(withSQL sql: String?,
  805. finalizeStatement statement: OpaquePointer?,
  806. returnValue: T) -> T {
  807. if let sql = sql {
  808. RCLog.error("I-RCN000016", "Failed with SQL: \(sql).")
  809. }
  810. logDatabaseError()
  811. if let statement = statement {
  812. sqlite3_finalize(statement)
  813. }
  814. return returnValue
  815. }
  816. private func logDatabaseError() {
  817. guard let database = database else { return }
  818. let msg = String(cString: sqlite3_errmsg(database))
  819. let code = sqlite3_errcode(database)
  820. RCLog.error("I-RCN000015", "Error message: \(msg). Error code: \(code).")
  821. }
  822. // MARK: Utility Functions
  823. private func bindText(_ statement: OpaquePointer!, _ index: Int32, _ value: String) -> Int32 {
  824. return sqlite3_bind_text(statement, index, (value as NSString).utf8String, -1, nil)
  825. }
  826. }