1# Persisting RDB Store Data 2 3 4## When to Use 5 6A relational database (RDB) store is used to store data in complex relational models, such as the student information including names, student IDs, and scores of each subject, or employee information including names, employee IDs, and positions, based on SQLite. The data is more complex than key-value (KV) pairs due to strict mappings. You can use **RelationalStore** to implement persistence of this type of data. 7 8 9## Basic Concepts 10 11- **Predicates**: a representation of the property or feature of a data entity, or the relationship between data entities. It is used to define operation conditions. 12 13- **ResultSet**: a set of query results, which allows access to the required data in flexible modes. 14 15 16## Working Principles 17 18**RelationalStore** provides APIs for applications to perform data operations. With SQLite as the underlying persistent storage engine, **RelationalStore** provides SQLite database features, including transactions, indexes, views, triggers, foreign keys, parameterized queries, prepared SQL statements, and more. 19 20**Figure 1** Working mechanism 21 22 23 24 25## Constraints 26 27- The default logging mode is Write Ahead Log (WAL), and the default flushing mode is **FULL** mode. 28 29- The RDB store supports a maximum of four read connections and one write connection. A thread performs the read operation when acquiring a read connection. When there is no read connection available but the write connection is idle, the write connection can be used to perform the read operation. 30 31- To ensure data accuracy, only one write operation is allowed at a time. 32 33- Once an application is uninstalled, related database files and temporary files on the device are automatically deleted. 34 35- ArkTS supports the following basic data types: number, string, binary data, and boolean. 36 37- The maximum size of a data record is 2 MB. If a data record exceeds 2 MB, it can be inserted successfully but cannot be read. 38 39## Available APIs 40 41The following table lists the APIs used for RDB data persistence. Most of the APIs are executed asynchronously, using a callback or promise to return the result. The following table uses the callback-based APIs as an example. For more information about the APIs, see [RDB Store](../reference/apis-arkdata/js-apis-data-relationalStore.md). 42 43| API| Description| 44| -------- | -------- | 45| getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback<RdbStore>): void | Obtains an **RdbStore** instance to implement RDB store operations. You can set **RdbStore** parameters based on actual requirements and use **RdbStore** APIs to perform data operations.| 46| executeSql(sql: string, bindArgs: Array<ValueType>, callback: AsyncCallback<void>):void | Executes an SQL statement that contains specified arguments but returns no value.| 47| insert(table: string, values: ValuesBucket, callback: AsyncCallback<number>):void | Inserts a row of data into a table.| 48| update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback<number>):void | Updates data in the RDB store based on the specified **predicates** instance.| 49| delete(predicates: RdbPredicates, callback: AsyncCallback<number>):void | Deletes data from the RDB store based on the specified **predicates** instance.| 50| query(predicates: RdbPredicates, columns: Array<string>, callback: AsyncCallback<ResultSet>):void | Queries data in the RDB store based on specified conditions.| 51| deleteRdbStore(context: Context, name: string, callback: AsyncCallback<void>): void | Deletes an RDB store.| 52 53 54## How to Develop 55Unless otherwise specified, the sample code without "stage model" or "FA model" applies to both models. 561. Obtain an **RdbStore** instance, which includes operations of creating an RDB store and tables, and upgrading or downgrading the RDB store. <br>Example: 57 58 Stage model: 59 60 ```ts 61 import { relationalStore} from '@kit.ArkData'; // Import the relationalStore module. 62 import { UIAbility } from '@kit.AbilityKit'; 63 import { BusinessError } from '@kit.BasicServicesKit'; 64 import { window } from '@kit.ArkUI'; 65 66 // In this example, Ability is used to obtain an RdbStore instance. You can use other implementations as required. 67 class EntryAbility extends UIAbility { 68 onWindowStageCreate(windowStage: window.WindowStage) { 69 const STORE_CONFIG :relationalStore.StoreConfig= { 70 name: 'RdbTest.db', // Database file name. 71 securityLevel: relationalStore.SecurityLevel.S3, // Database security level. 72 encrypt: false, // Whether to encrypt the database. This parameter is optional. By default, the database is not encrypted. 73 customDir: 'customDir/subCustomDir' // (Optional) Customized database path. The database is created in the context.databaseDir + '/rdb/' + customDir directory, where context.databaseDir indicates the application sandbox path, '/rdb/' indicates a relational database, and customDir indicates the customized path. If this parameter is not specified, an RdbStore instance is created in the sandbox directory of the application. 74 isReadOnly: false // (Optional) Specify whether the RDB store is opened in read-only mode. The default value is false, which means the RDB store is readable and writable. If this parameter is true, data can only be read from the RDB store. If write operation is performed, error code 801 is returned. 75 }; 76 77 // Check the RDB store version. If the version is incorrect, upgrade or downgrade the RDB store. 78 // For example, the RDB store version is 3 and the table structure is EMPLOYEE (NAME, AGE, SALARY, CODES, IDENTITY). 79 const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB, IDENTITY UNLIMITED INT)'; // SQL statement used to create a table. In the statement, the IDENTITY type bigint should be UNLIMITED INT. 80 81 relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => { 82 if (err) { 83 console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`); 84 return; 85 } 86 console.info('Succeeded in getting RdbStore.'); 87 88 // When the RDB store is created, the default version is 0. 89 if (store.version === 0) { 90 store.executeSql(SQL_CREATE_TABLE); // Create a data table. 91 // Set the RDB store version, which must be an integer greater than 0. 92 store.version = 3; 93 } 94 95 // If the RDB store version is not 0 and does not match the current version, upgrade or downgrade the RDB store. 96 // For example, upgrade the RDB store from version 1 to version 2. 97 if (store.version === 1) { 98 // Upgrade the RDB store from version 1 to version 2, and change the table structure from EMPLOYEE (NAME, SALARY, CODES, ADDRESS) to EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS). 99 if (store !== undefined) { 100 (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER'); 101 store.version = 2; 102 } 103 } 104 105 // For example, upgrade the RDB store from version 2 to version 3. 106 if (store.version === 2) { 107 // Upgrade the RDB store from version 2 to version 3, and change the table structure from EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS) to EMPLOYEE (NAME, AGE, SALARY, CODES). 108 if (store !== undefined) { 109 (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS TEXT'); 110 store.version = 3; 111 } 112 } 113 }); 114 115 // Before performing data operations on the database, obtain an RdbStore instance. 116 } 117 } 118 ``` 119 120 FA model: 121 122 ```ts 123 import { relationalStore} from '@kit.ArkData'; // Import the relationalStore module. 124 import { featureAbility } from '@kit.AbilityKit'; 125 import { BusinessError } from '@kit.BasicServicesKit'; 126 127 let context = featureAbility.getContext(); 128 129 const STORE_CONFIG :relationalStore.StoreConfig = { 130 name: 'RdbTest.db', // Database file name. 131 securityLevel: relationalStore.SecurityLevel.S3 // Database security level. 132 }; 133 134 // For example, the RDB store version is 3 and the table structure is EMPLOYEE (NAME, AGE, SALARY, CODES, IDENTITY). 135 const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB, IDENTITY UNLIMITED INT)'; // SQL statement used to create a table. In the statement, the IDENTITY type bigint should be UNLIMITED INT. 136 137 relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => { 138 if (err) { 139 console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`); 140 return; 141 } 142 console.info('Succeeded in getting RdbStore.'); 143 144 // When the RDB store is created, the default version is 0. 145 if (store.version === 0) { 146 store.executeSql(SQL_CREATE_TABLE); // Create a data table. 147 // Set the RDB store version, which must be an integer greater than 0. 148 store.version = 3; 149 } 150 151 // If the RDB store version is not 0 and does not match the current version, upgrade or downgrade the RDB store. 152 // For example, upgrade the RDB store from version 1 to version 2. 153 if (store.version === 1) { 154 // Upgrade the RDB store from version 1 to version 2, and change the table structure from EMPLOYEE (NAME, SALARY, CODES, ADDRESS) to EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS). 155 store.executeSql('ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER'); 156 store.version = 2; 157 } 158 159 // For example, upgrade the RDB store from version 2 to version 3. 160 if (store.version === 2) { 161 // Upgrade the RDB store from version 2 to version 3, and change the table structure from EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS) to EMPLOYEE (NAME, AGE, SALARY, CODES). 162 store.executeSql('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS TEXT'); 163 store.version = 3; 164 } 165 }); 166 167 // Before performing data operations on the database, obtain an RdbStore instance. 168 ``` 169 170 > **NOTE** 171 > 172 > - The RDB store created by an application varies with the context. Multiple RDB stores are created for the same database name with different application contexts. For example, each UIAbility has its own context. 173 > 174 > - When an application calls **getRdbStore()** to obtain an RDB store instance for the first time, the corresponding database file is generated in the application sandbox. When the RDB store is used, temporary files ended with **-wal** and **-shm** may be generated in the same directory as the database file. If you want to move the database files to other places, you must also move these temporary files. After the application is uninstalled, the database files and temporary files generated on the device are also removed. 175 > 176 > - For details about the error codes, see [Universal Error Codes](../reference/errorcode-universal.md) and [RDB Store Error Codes](../reference/apis-arkdata/errorcode-data-rdb.md). 177 1782. Use **insert()** to insert data to the RDB store. <br>Example: 179 180 ```ts 181 let store: relationalStore.RdbStore | undefined = undefined; 182 183 let value1 = 'Lisa'; 184 let value2 = 18; 185 let value3 = 100.5; 186 let value4 = new Uint8Array([1, 2, 3, 4, 5]); 187 let value5 = BigInt('15822401018187971961171'); 188 // You can use either of the following: 189 const valueBucket1: relationalStore.ValuesBucket = { 190 'NAME': value1, 191 'AGE': value2, 192 'SALARY': value3, 193 'CODES': value4, 194 'IDENTITY': value5, 195 }; 196 const valueBucket2: relationalStore.ValuesBucket = { 197 NAME: value1, 198 AGE: value2, 199 SALARY: value3, 200 CODES: value4, 201 IDENTITY: value5, 202 }; 203 const valueBucket3: relationalStore.ValuesBucket = { 204 "NAME": value1, 205 "AGE": value2, 206 "SALARY": value3, 207 "CODES": value4, 208 "IDENTITY": value5, 209 }; 210 211 if (store !== undefined) { 212 (store as relationalStore.RdbStore).insert('EMPLOYEE', valueBucket1, (err: BusinessError, rowId: number) => { 213 if (err) { 214 console.error(`Failed to insert data. Code:${err.code}, message:${err.message}`); 215 return; 216 } 217 console.info(`Succeeded in inserting data. rowId:${rowId}`); 218 }) 219 } 220 ``` 221 222 > **NOTE** 223 > 224 > **RelationalStore** does not provide explicit flush operations for data persistence. The **insert()** method stores data persistently. 225 2263. Modify or delete data based on the specified **Predicates** instance. 227 228 Use **update()** to modify data and **delete()** to delete data. <br>Example: 229 230 ```ts 231 let value6 = 'Rose'; 232 let value7 = 22; 233 let value8 = 200.5; 234 let value9 = new Uint8Array([1, 2, 3, 4, 5]); 235 let value10 = BigInt('15822401018187971967863'); 236 // You can use either of the following: 237 const valueBucket4: relationalStore.ValuesBucket = { 238 'NAME': value6, 239 'AGE': value7, 240 'SALARY': value8, 241 'CODES': value9, 242 'IDENTITY': value10, 243 }; 244 const valueBucket5: relationalStore.ValuesBucket = { 245 NAME: value6, 246 AGE: value7, 247 SALARY: value8, 248 CODES: value9, 249 IDENTITY: value10, 250 }; 251 const valueBucket6: relationalStore.ValuesBucket = { 252 "NAME": value6, 253 "AGE": value7, 254 "SALARY": value8, 255 "CODES": value9, 256 "IDENTITY": value10, 257 }; 258 259 // Modify data. 260 let predicates1 = new relationalStore.RdbPredicates('EMPLOYEE'); // Create predicates for the table named EMPLOYEE. 261 predicates1.equalTo('NAME', 'Lisa'); // Modify the data of Lisa in the EMPLOYEE table to the specified data. 262 if (store !== undefined) { 263 (store as relationalStore.RdbStore).update(valueBucket4, predicates1, (err: BusinessError, rows: number) => { 264 if (err) { 265 console.error(`Failed to update data. Code:${err.code}, message:${err.message}`); 266 return; 267 } 268 console.info(`Succeeded in updating data. row count: ${rows}`); 269 }) 270 } 271 272 // Delete data. 273 predicates1 = new relationalStore.RdbPredicates('EMPLOYEE'); 274 predicates1.equalTo('NAME', 'Lisa'); 275 if (store !== undefined) { 276 (store as relationalStore.RdbStore).delete(predicates1, (err: BusinessError, rows: number) => { 277 if (err) { 278 console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`); 279 return; 280 } 281 console.info(`Delete rows: ${rows}`); 282 }) 283 } 284 ``` 285 2864. Query data based on the conditions specified by **Predicates**. 287 288 Use **query()** to query data. The data obtained is returned in a **ResultSet** object. <br>Example: 289 290 ```ts 291 let predicates2 = new relationalStore.RdbPredicates('EMPLOYEE'); 292 predicates2.equalTo('NAME', 'Rose'); 293 if (store !== undefined) { 294 (store as relationalStore.RdbStore).query(predicates2, ['ID', 'NAME', 'AGE', 'SALARY', 'IDENTITY'], (err: BusinessError, resultSet) => { 295 if (err) { 296 console.error(`Failed to query data. Code:${err.code}, message:${err.message}`); 297 return; 298 } 299 console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`); 300 // resultSet is a cursor of a data set. By default, the cursor points to the -1st record. Valid data starts from 0. 301 while (resultSet.goToNextRow()) { 302 const id = resultSet.getLong(resultSet.getColumnIndex('ID')); 303 const name = resultSet.getString(resultSet.getColumnIndex('NAME')); 304 const age = resultSet.getLong(resultSet.getColumnIndex('AGE')); 305 const salary = resultSet.getDouble(resultSet.getColumnIndex('SALARY')); 306 const identity = resultSet.getValue(resultSet.getColumnIndex('IDENTITY')); 307 console.info(`id=${id}, name=${name}, age=${age}, salary=${salary}, identity=${identity}`); 308 } 309 // Release the data set memory. 310 resultSet.close(); 311 }) 312 } 313 ``` 314 315 > **NOTE** 316 > 317 > Use **close()** to close the **ResultSet** that is no longer used in a timely manner so that the memory allocated can be released. 318 3195. Back up the database in the same directory. <br>Example: 320 321 ```ts 322 if (store !== undefined) { 323 //Backup.db indicates the name of the backup database file. By default, the database file is backed up in the same path as the RdbStore file. You can also specify the path customDir + "backup.db". 324 (store as relationalStore.RdbStore).backup("Backup.db", (err: BusinessError) => { 325 if (err) { 326 console.error(`Failed to backup RdbStore. Code:${err.code}, message:${err.message}`); 327 return; 328 } 329 console.info(`Succeeded in backing up RdbStore.`); 330 }) 331 } 332 ``` 333 3346. Restore data from the database backup. <br>Example: 335 336 ```ts 337 if (store !== undefined) { 338 (store as relationalStore.RdbStore).restore("Backup.db", (err: BusinessError) => { 339 if (err) { 340 console.error(`Failed to restore RdbStore. Code:${err.code}, message:${err.message}`); 341 return; 342 } 343 console.info(`Succeeded in restoring RdbStore.`); 344 }) 345 } 346 ``` 347 3487. If the database file is corrupted, rebuild the database. 349 350 If error code 14800011 is reported when the RDB store is opened, or data is added, deleted, modified, or queried, the database file is corrupted. The sample code for rebuilding the RDB store is as follows: 351 352 ```ts 353 if (store !== undefined) { 354 // If the database file is corrupted, close all database connections and result sets. You can use store.close() or set the object to null. 355 (store as relationalStore.RdbStore).close(); 356 store = undefined; 357 // Set config.allowRebuild to true and call getRdbStore to open the RDB store again. 358 const STORE_CONFIG: relationalStore.StoreConfig = { 359 name: 'RdbTest.db', 360 securityLevel: relationalStore.SecurityLevel.S3, 361 allowRebuild: true 362 }; 363 364 relationalStore.getRdbStore(this.context, STORE_CONFIG).then(async (rdbStore: relationalStore.RdbStore) => { 365 store = rdbStore; 366 console.info('Get RdbStore successfully.') 367 }).catch((err: BusinessError) => { 368 console.error(`Get RdbStore failed, code is ${err.code},message is ${err.message}`); 369 }) 370 371 if (store !== undefined) { 372 // Check whether the RDB store is rebuilt successfully. 373 if ((store as relationalStore.RdbStore).rebuilt === relationalStore.RebuildType.REBUILT) { 374 console.info('Succeeded in rebuilding RdbStore.'); 375 // Use the database backup file before the corruption to restore data. 376 (store as relationalStore.RdbStore).restore("Backup.db", (err: BusinessError) => { 377 if (err) { 378 console.error(`Failed to restore RdbStore. Code:${err.code}, message:${err.message}`); 379 return; 380 } 381 console.info(`Succeeded in restoring RdbStore.`); 382 }) 383 } 384 } 385 } 386 ``` 387 3888. Delete the RDB store. 389 390 Use **deleteRdbStore()** to delete the RDB store and related database files. <br>Example: 391 392 Stage model: 393 394 ```ts 395 relationalStore.deleteRdbStore(this.context, 'RdbTest.db', (err: BusinessError) => { 396 if (err) { 397 console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`); 398 return; 399 } 400 console.info('Succeeded in deleting RdbStore.'); 401 }); 402 ``` 403 404 FA model: 405 406 ```ts 407 relationalStore.deleteRdbStore(context, 'RdbTest.db', (err: BusinessError) => { 408 if (err) { 409 console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`); 410 return; 411 } 412 console.info('Succeeded in deleting RdbStore.'); 413 }); 414 ``` 415