• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8Querying data from a large amount of data may take time or even cause application suspension. In this case, you can perform batch operations. For details, see [Batch Database Operations](../arkts-utils/batch-database-operations-guide.md). Moreover, observe the following:
9- The number of data records to be queried at a time should not exceed 5000.
10- Use [TaskPool](../reference/apis-arkts/js-apis-taskpool.md) if there is a large amount of data needs to be queried.
11- Keep concatenated SQL statements as concise as possible.
12- Query data in batches.
13
14## Basic Concepts
15
16- **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.
17
18- **ResultSet**: a set of query results, which allows access to the required data in flexible modes.
19
20
21## Working Principles
22
23**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.
24
25**Figure 1** Working mechanism
26
27![relationStore_local](figures/relationStore_local.jpg)
28
29
30## Constraints
31
32- The default logging mode is Write Ahead Log (WAL), and the default flushing mode is **FULL** mode.
33
34- 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.
35
36- To ensure data accuracy, only one write operation is allowed at a time.
37
38- Once an application is uninstalled, related database files and temporary files on the device are automatically deleted.
39
40- ArkTS supports the following basic data types: number, string, binary data, and boolean.
41
42- 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.
43
44## Available APIs
45
46The 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).
47
48| API| Description|
49| -------- | -------- |
50| 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.|
51| executeSql(sql: string, bindArgs: Array<ValueType>, callback: AsyncCallback<void>):void | Executes an SQL statement that contains specified arguments but returns no value.|
52| insert(table: string, values: ValuesBucket, callback: AsyncCallback<number>):void | Inserts a row of data into a table.|
53| update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback<number>):void | Updates data in the RDB store based on the specified **predicates** instance.|
54| delete(predicates: RdbPredicates, callback: AsyncCallback<number>):void | Deletes data from the RDB store based on the specified **predicates** instance.|
55| query(predicates: RdbPredicates, columns: Array<string>, callback: AsyncCallback<ResultSet>):void | Queries data in the RDB store based on specified conditions.|
56| deleteRdbStore(context: Context, name: string, callback: AsyncCallback<void>): void | Deletes an RDB store.|
57| isTokenizerSupported(tokenizer: Tokenizer): boolean | Checks whether the specified tokenizer is supported.|
58
59## How to Develop
60Unless otherwise specified, the sample code without "stage model" or "FA model" applies to both models.
61
62If error 14800011 is thrown, you need to rebuild the database and restore data to ensure normal application development. For details, see [Rebuilding a RDB Store](data-backup-and-restore.md#rebuilding-an-rdb-store).
63
641. Obtain an **RdbStore** instance, which includes operations of creating an RDB store and tables, and upgrading or downgrading the RDB store. <br>Example:
65
66   Stage model:
67
68   ```ts
69   import { relationalStore} from '@kit.ArkData'; // Import the relationalStore module.
70   import { UIAbility } from '@kit.AbilityKit';
71   import { BusinessError } from '@kit.BasicServicesKit';
72   import { window } from '@kit.ArkUI';
73
74   // In this example, Ability is used to obtain an RdbStore instance. You can use other implementations as required.
75   class EntryAbility extends UIAbility {
76     onWindowStageCreate(windowStage: window.WindowStage) {
77       // Before using a tokenizer, call isStorageTypeSupported to check whether the tokenizer is supported by the current platform.
78       let tokenType = relationalStore.Tokenizer.ICU_TOKENIZER;
79       let tokenTypeSupported = relationalStore.isTokenizerSupported(tokenType);
80       if (!tokenTypeSupported) {
81         console.error(`ICU_TOKENIZER is not supported on this platform.`);
82       }
83       const STORE_CONFIG :relationalStore.StoreConfig= {
84         name: 'RdbTest.db', // Database file name.
85         securityLevel: relationalStore.SecurityLevel.S3, // Database security level.
86         encrypt: false, // Whether to encrypt the database. This parameter is optional. By default, the database is not encrypted.
87         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.
88         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.
89         tokenizer: tokenType // (Optional) Type of the tokenizer used in full-text search (FTS). If this parameter is left blank, only English word segmentation is supported in FTS.
90       };
91
92       // Check the RDB store version. If the version is incorrect, upgrade or downgrade the RDB store.
93       // For example, the RDB store version is 3 and the table structure is EMPLOYEE (NAME, AGE, SALARY, CODES, IDENTITY).
94       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.
95
96       relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
97         if (err) {
98           console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
99           return;
100         }
101         console.info('Succeeded in getting RdbStore.');
102
103         // When the RDB store is created, the default version is 0.
104         if (store.version === 0) {
105           store.executeSql(SQL_CREATE_TABLE); // Create a data table.
106           // Set the RDB store version, which must be an integer greater than 0.
107           store.version = 3;
108         }
109
110         // If the RDB store version is not 0 and does not match the current version, upgrade or downgrade the RDB store.
111         // For example, upgrade the RDB store from version 1 to version 2.
112         if (store.version === 1) {
113           // 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).
114           (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER');
115           store.version = 2;
116         }
117
118         // For example, upgrade the RDB store from version 2 to version 3.
119         if (store.version === 2) {
120           // 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).
121           (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS TEXT');
122           store.version = 3;
123         }
124       });
125
126       // Before performing data operations on the database, obtain an RdbStore instance.
127     }
128   }
129   ```
130
131   FA model:
132
133   ```ts
134   import { relationalStore} from '@kit.ArkData'; // Import the relationalStore module.
135   import { featureAbility } from '@kit.AbilityKit';
136   import { BusinessError } from '@kit.BasicServicesKit';
137
138   let context = featureAbility.getContext();
139
140   const STORE_CONFIG :relationalStore.StoreConfig = {
141     name: 'RdbTest.db', // Database file name.
142     securityLevel: relationalStore.SecurityLevel.S3 // Database security level.
143   };
144
145   // For example, the RDB store version is 3 and the table structure is EMPLOYEE (NAME, AGE, SALARY, CODES, IDENTITY).
146   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.
147
148   relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => {
149     if (err) {
150       console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
151       return;
152     }
153     console.info('Succeeded in getting RdbStore.');
154
155     // When the RDB store is created, the default version is 0.
156     if (store.version === 0) {
157       store.executeSql(SQL_CREATE_TABLE); // Create a data table.
158       // Set the RDB store version, which must be an integer greater than 0.
159       store.version = 3;
160     }
161
162     // If the RDB store version is not 0 and does not match the current version, upgrade or downgrade the RDB store.
163     // For example, upgrade the RDB store from version 1 to version 2.
164     if (store.version === 1) {
165       // 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).
166       store.executeSql('ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER');
167       store.version = 2;
168     }
169
170     // For example, upgrade the RDB store from version 2 to version 3.
171     if (store.version === 2) {
172       // 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).
173       store.executeSql('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS TEXT');
174       store.version = 3;
175     }
176   });
177
178   // Before performing data operations on the database, obtain an RdbStore instance.
179   ```
180
181   > **NOTE**
182   >
183   > - 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.
184   >
185   > - 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.
186   >
187   > - 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).
188
1892. Use **insert()** to insert data to the RDB store. <br>Example:
190
191   ```ts
192   let store: relationalStore.RdbStore | undefined = undefined;
193
194   let value1 = 'Lisa';
195   let value2 = 18;
196   let value3 = 100.5;
197   let value4 = new Uint8Array([1, 2, 3, 4, 5]);
198   let value5 = BigInt('15822401018187971961171');
199   // You can use either of the following:
200   const valueBucket1: relationalStore.ValuesBucket = {
201     'NAME': value1,
202     'AGE': value2,
203     'SALARY': value3,
204     'CODES': value4,
205     'IDENTITY': value5,
206   };
207   const valueBucket2: relationalStore.ValuesBucket = {
208     NAME: value1,
209     AGE: value2,
210     SALARY: value3,
211     CODES: value4,
212     IDENTITY: value5,
213   };
214   const valueBucket3: relationalStore.ValuesBucket = {
215     "NAME": value1,
216     "AGE": value2,
217     "SALARY": value3,
218     "CODES": value4,
219     "IDENTITY": value5,
220   };
221
222   if (store !== undefined) {
223     (store as relationalStore.RdbStore).insert('EMPLOYEE', valueBucket1, (err: BusinessError, rowId: number) => {
224       if (err) {
225         console.error(`Failed to insert data. Code:${err.code}, message:${err.message}`);
226         return;
227       }
228       console.info(`Succeeded in inserting data. rowId:${rowId}`);
229     })
230   }
231   ```
232
233   > **NOTE**
234   >
235   > **RelationalStore** does not provide explicit flush operations for data persistence. The **insert()** method stores data persistently.
236
2373. Modify or delete data based on the specified **Predicates** instance.
238
239   Use **update()** to modify data and **delete()** to delete data. <br>Example:
240
241   ```ts
242   let value6 = 'Rose';
243   let value7 = 22;
244   let value8 = 200.5;
245   let value9 = new Uint8Array([1, 2, 3, 4, 5]);
246   let value10 = BigInt('15822401018187971967863');
247   // You can use either of the following:
248   const valueBucket4: relationalStore.ValuesBucket = {
249     'NAME': value6,
250     'AGE': value7,
251     'SALARY': value8,
252     'CODES': value9,
253     'IDENTITY': value10,
254   };
255   const valueBucket5: relationalStore.ValuesBucket = {
256     NAME: value6,
257     AGE: value7,
258     SALARY: value8,
259     CODES: value9,
260     IDENTITY: value10,
261   };
262   const valueBucket6: relationalStore.ValuesBucket = {
263     "NAME": value6,
264     "AGE": value7,
265     "SALARY": value8,
266     "CODES": value9,
267     "IDENTITY": value10,
268   };
269
270   // Modify data.
271   let predicates1 = new relationalStore.RdbPredicates('EMPLOYEE'); // Create predicates for the table named EMPLOYEE.
272   predicates1.equalTo('NAME', 'Lisa'); // Modify the data of Lisa in the EMPLOYEE table to the specified data.
273   if (store !== undefined) {
274     (store as relationalStore.RdbStore).update(valueBucket4, predicates1, (err: BusinessError, rows: number) => {
275       if (err) {
276         console.error(`Failed to update data. Code:${err.code}, message:${err.message}`);
277        return;
278      }
279      console.info(`Succeeded in updating data. row count: ${rows}`);
280     })
281   }
282
283   // Delete data.
284   predicates1 = new relationalStore.RdbPredicates('EMPLOYEE');
285   predicates1.equalTo('NAME', 'Lisa');
286   if (store !== undefined) {
287     (store as relationalStore.RdbStore).delete(predicates1, (err: BusinessError, rows: number) => {
288       if (err) {
289         console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
290         return;
291       }
292       console.info(`Delete rows: ${rows}`);
293     })
294   }
295   ```
296
2974. Query data based on the conditions specified by **Predicates**.
298
299   Use **query()** to query data. The data obtained is returned in a **ResultSet** object. <br>Example:
300
301   ```ts
302   let predicates2 = new relationalStore.RdbPredicates('EMPLOYEE');
303   predicates2.equalTo('NAME', 'Rose');
304   if (store !== undefined) {
305     (store as relationalStore.RdbStore).query(predicates2, ['ID', 'NAME', 'AGE', 'SALARY', 'IDENTITY'], (err: BusinessError, resultSet) => {
306       if (err) {
307         console.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
308         return;
309       }
310       console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
311       // resultSet is a cursor of a data set. By default, the cursor points to the -1st record. Valid data starts from 0.
312       while (resultSet.goToNextRow()) {
313         const id = resultSet.getLong(resultSet.getColumnIndex('ID'));
314         const name = resultSet.getString(resultSet.getColumnIndex('NAME'));
315         const age = resultSet.getLong(resultSet.getColumnIndex('AGE'));
316         const salary = resultSet.getDouble(resultSet.getColumnIndex('SALARY'));
317         const identity = resultSet.getValue(resultSet.getColumnIndex('IDENTITY'));
318         console.info(`id=${id}, name=${name}, age=${age}, salary=${salary}, identity=${identity}`);
319       }
320       // Release the data set memory.
321       resultSet.close();
322     })
323   }
324   ```
325
326   > **NOTE**
327   >
328   > Use **close()** to close the **ResultSet** that is no longer used in a timely manner so that the memory allocated can be released.
329
330   The RDB store also supports full-text search (FTS) in Chinese or English. The ICU tokenizer is supported.
331
332   The following example demonstrates how to perform FTS with Chinese keywords:
333
334   ```ts
335   let store: relationalStore.RdbStore | undefined = undefined;
336   if (store !== undefined) {
337     // Create an FTS table.
338     const  SQL_CREATE_TABLE = "CREATE VIRTUAL TABLE example USING fts4(name, content, tokenize=icu zh_CN)";
339     (store as relationalStore.RdbStore).executeSql(SQL_CREATE_TABLE, (err: BusinessError) => {
340       if (err) {
341         console.error(`Failed to creating fts table.`);
342         return;
343       }
344       console.info(`Succeeded in creating fts table.`);
345     })
346   }
347   if(store != undefined) {
348      (store as relationalStore.RdbStore).querySql("SELECT name FROM example WHERE example MATCH '测试'", (err, resultSet) => {
349        if (err) {
350          console.error(`Query failed.`);
351          return;
352        }
353        while (resultSet.goToNextRow()) {
354          const name = resultSet.getString(resultSet.getColumnIndex("name"));
355          console.info(`name=${name}`);
356        }
357        resultSet.close();
358      })
359   }
360   ```
361
3625. Back up the database in the same directory. <br>Two backup modes are available: manual backup and automatic backup (available only for system applications). For details, see [Backing Up an RDB Store](data-backup-and-restore.md#backing-up-an-rdb store).
363
364   Example: Perform manual backup of an RDB store.
365
366   ```ts
367   if (store !== undefined) {
368     // Backup.db indicates the name of the database backup file. By default, it is in the same directory as the RdbStore file. You can also specify the directory, which is in the customDir + backup.db format.
369     (store as relationalStore.RdbStore).backup("Backup.db", (err: BusinessError) => {
370       if (err) {
371         console.error(`Failed to backup RdbStore. Code:${err.code}, message:${err.message}`);
372         return;
373       }
374       console.info(`Succeeded in backing up RdbStore.`);
375     })
376   }
377   ```
378
3796. Restore data from the database backup. <br>You can restore an RDB store from the manual backup data or automatic backup data (available only for system applications). For details, see [Restoring RDB Store Data](data-backup-and-restore.md#restoring-rdb-store-data).
380
381   Example: Call [restore](../reference/apis-arkdata/js-apis-data-relationalStore.md#restore) to restore an RDB store from the data that is manually backed up.
382
383   ```ts
384   if (store !== undefined) {
385     (store as relationalStore.RdbStore).restore("Backup.db", (err: BusinessError) => {
386       if (err) {
387         console.error(`Failed to restore RdbStore. Code:${err.code}, message:${err.message}`);
388         return;
389       }
390       console.info(`Succeeded in restoring RdbStore.`);
391     })
392   }
393   ```
394
3957. Delete the RDB store.
396
397   Use **deleteRdbStore()** to delete the RDB store and related database files. <br>Example:
398
399   Stage model:
400
401   ```ts
402   relationalStore.deleteRdbStore(this.context, 'RdbTest.db', (err: BusinessError) => {
403    if (err) {
404       console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
405       return;
406     }
407     console.info('Succeeded in deleting RdbStore.');
408   });
409   ```
410
411   FA model:
412
413   ```ts
414   relationalStore.deleteRdbStore(context, 'RdbTest.db', (err: BusinessError) => {
415     if (err) {
416       console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
417       return;
418     }
419     console.info('Succeeded in deleting RdbStore.');
420   });
421   ```
422<!--no_check-->