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![relationStore_local](figures/relationStore_local.jpg)
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