• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Persisting Vector Store Data (ArkTS)
2
3
4## When to Use
5
6Vector stores are designed to store, manage, and retrieve vector data while also supporting relational data processing for scalar values. The data type **floatvector** is used to store data vectorization results, enabling rapid retrieval and similarity searches for such data.
7
8## Basic Concepts
9
10- **ResultSet**: a set of query results, which allows access to the required data in flexible modes.
11- **floatvector**: vector data, for example, **[1.0, 3.0, 2.4, 5.1, 6.2, 11.7]**.
12
13## Constraints
14
15- By default, the Write Ahead Log (WAL) and the **FULL** flushing mode are used.
16
17- A vector store supports a maximum of four read connections and one write connection at a time by default. A thread can perform the read operation when acquiring an idle read connection. If there is no idle read connection, a new read connection will be created.
18
19- To ensure data accuracy, the database supports only one write operation at a time. Concurrent write operations are performed in serial mode.
20
21- Once an application is uninstalled, related database files and temporary files are automatically deleted from the device.
22
23- ArkTS supports basic data types such as number, string, binary, and boolean, and the special data type ValueType.
24
25- To ensure successful data access, limit the size of a data record to 2 MB. Data records larger than this may be inserted correctly but fail to read.
26
27## Available APIs
28
29The following lists only the APIs for persisting vector store data. For details about more APIs and their usage, see [RDB Store](../reference/apis-arkdata/js-apis-data-relationalStore.md).
30
31| API| Description|
32| -------- | -------- |
33| getRdbStore(context: Context, config: StoreConfig): Promise<RdbStore> | Obtains an **RdbStore** instance for data operations.|
34| execute(sql: string, txId: number, args?: Array&lt;ValueType&gt;): Promise&lt;ValueType&gt; | Executes SQL statements that contain specified parameters. The number of operators (such as =, >, and <) in the SQL statements cannot exceed 1000.|
35| querySql(sql: string, bindArgs?: Array&lt;ValueType&gt;):Promise&lt;ResultSet&gt; | Queries data in the RDB store using the specified SQL statement. The number of operators (such as =, >, and <) in the SQL statements cannot exceed 1000.|
36| beginTrans(): Promise&lt;number&gt; | Starts the transaction before executing the SQL statements.|
37| commit(txId : number):Promise&lt;void&gt; | Commits the executed SQL statements. This API must be used together with **beginTrans**.|
38| rollback(txId : number):Promise&lt;void&gt; | Rolls back the executed SQL statements. This API must be used together with **beginTrans**.|
39| deleteRdbStore(context: Context, config: StoreConfig): Promise&lt;void&gt; | Deletes an RDB store.|
40| isVectorSupported(): boolean | Checks whether the system supports vector stores.|
41
42## How to Develop
43
441. Check whether the current system supports vector stores. The sample code is as follows:
45
46   ```ts
47   import { relationalStore } from '@kit.ArkData'; // Import the relationalStore module.
48   import { UIAbility } from '@kit.AbilityKit';
49   import { BusinessError } from '@kit.BasicServicesKit';
50   import { window } from '@kit.ArkUI';
51   // In this example, Ability is used to obtain an RdbStore instance. You can use other implementations as required.
52   class EntryAbility extends UIAbility {
53     async onWindowStageCreate(windowStage: window.WindowStage) {
54        // Check whether the current system supports vector stores.
55       let ret = relationalStore.isVectorSupported();
56       if (!ret) {
57         console.error(`vectorDB is not supported .`);
58         return;
59       }
60       // Open the database, and add, delete, and modify data.
61     }
62   }
63   ```
64
652. If the system supports vector stores, obtain an **RdbStore** instance. Call **getRdbStore()** to create a database and create a table.
66
67   > **NOTE**
68   >
69   > - 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.
70   >
71   > - 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.
72   >
73   > - 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).
74
75   The sample code is as follows:
76
77   ```ts
78   let store: relationalStore.RdbStore | undefined = undefined;
79   const STORE_CONFIG :relationalStore.StoreConfig= {
80     name: 'VectorTest.db', // Database file name.
81     securityLevel: relationalStore.SecurityLevel.S1 // Database security level.
82     vector: true // Optional. This parameter must be true for a vector store.
83   };
84
85   relationalStore.getRdbStore(this.context, STORE_CONFIG).then(async (rdbStore: relationalStore.RdbStore) => {
86     store = rdbStore;
87     // Create a table. floatvector (2) indicates that repr is 2-dimensional.
88     const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, repr floatvector(2));';
89     // The second parameter indicates that transaction is not enabled. The third parameter undefined indicates that parameter binding is not used.
90     await store!.execute(SQL_CREATE_TABLE, 0, undefined);
91   }).catch((err: BusinessError) => {
92     console.error(`Get RdbStore failed, code is ${err.code}, message is ${err.message}`);
93   });
94   ```
95
963. Call **execute()** to insert data to the vector store.
97
98   > **NOTE**
99   >
100   > **RelationalStore** does not provide explicit flush operations for data persistence. The data inserted is persisted.
101
102   The sample code is as follows:
103
104   ```ts
105   try {
106     // Use parameter binding.
107     const vectorValue: Float32Array = Float32Array.from([1.2, 2.3]);
108     await store!.execute("insert into test VALUES(?, ?);", 0, [0, vectorValue]);
109     // Do not use parameter binding.
110     await store!.execute("insert into test VALUES(1, '[1.3, 2.4]');", 0, undefined);
111   } catch (err) {
112     console.error(`execute insert failed, code is ${err.code}, message is ${err.message}`);
113   }
114   ```
115
1164. Call **execute()** to modify or delete data. The sample code is as follows:
117
118   ```ts
119   // Modify data.
120   try {
121     // Use parameter binding.
122     const vectorValue1: Float32Array = Float32Array.from([2.1, 3.2]);
123     await store!.execute("update test set repr = ? where id = ?", 0, [vectorValue1, 0]);
124     // Do not use parameter binding.
125     await store!.execute("update test set repr = '[5.1, 6.1]' where id = 0", 0, undefined);
126   } catch (err) {
127     console.error(`execute update failed, code is ${err.code}, message is ${err.message}`);
128   }
129
130   // Delete data.
131   try {
132     // Use parameter binding.
133     await store!.execute("delete from test where id = ?", 0, [0]);
134     // Do not use parameter binding.
135     await store!.execute("delete from test where id = 0", 0, undefined);
136   } catch (err) {
137     console.error(`execute delete failed, code is ${err.code}, message is ${err.message}`);
138   }
139   ```
140
1415. Call **querySql()** to query data. A **ResultSet** instance is returned.
142
143   > **NOTE**
144   >
145   > Use **close()** to close the **ResultSet** that is no longer used in a timely manner so that the memory allocated can be released.
146
147   The sample code is as follows:
148
149   ```ts
150   // Perform single-table queries.
151   try {
152     // Use parameter binding.
153     const QUERY_SQL = "select id, repr <-> ? as distance from test where id > ? order by repr <-> ? limit 5;";
154     const vectorValue2: Float32Array = Float32Array.from([6.2, 7.3]);
155     let resultSet = await store!.querySql(QUERY_SQL, [vectorValue2, 0, vectorValue2]);
156     while (resultSet!.goToNextRow()) {
157        let id = resultSet.getValue(0);
158        let dis = resultSet.getValue(1);
159     }
160     resultSet!.close();
161
162     // Do not use parameter binding.
163     const QUERY_SQL1 = "select id, repr <-> '[6.2, 7.3]' as distance from test where id > 0 order by repr <-> '[6.2, 7.3]' limit 5;";
164     resultSet = await store!.querySql(QUERY_SQL1);
165     resultSet!.close();
166   } catch (err) {
167     console.error(`query failed, code is ${err.code}, message is ${err.message}`);
168   }
169
170   // Perform subqueries.
171   try {
172     // Create the second table.
173     let CREATE_SQL = "CREATE TABLE IF NOT EXISTS test1(id text PRIMARY KEY);";
174     await store!.execute(CREATE_SQL);
175     let resultSet = await store!.querySql("select * from test where id in (select id from test1);");
176     resultSet!.close();
177   } catch (err) {
178     console.error(`query failed, code is ${err.code}, message is ${err.message}`);
179   }
180
181   // Perform aggregate queries.
182   try {
183     let resultSet = await store!.querySql("select * from test where repr <-> '[1.0, 1.0]' > 0 group by id having max(repr <=> '[1.0, 1.0]');");
184     resultSet!.close();
185   } catch (err) {
186     console.error(`query failed, code is ${err.code}, message is ${err.message}`);
187   }
188
189   // Perform multi-table queries.
190   try {
191     // Different union all, union will delete duplicate data.
192     let resultSet = await store!.querySql("select id, repr <-> '[1.5, 5.6]' as distance from test union select id, repr <-> '[1.5, 5.6]' as distance from test order by distance limit 5;");
193     resultSet!.close();
194   } catch (err) {
195     console.error(`query failed, code is ${err.code}, message is ${err.message}`);
196   }
197   ```
198
1996. Create a view and query data. The sample code is as follows:
200
201   ```ts
202   // Perform view queries.
203   try {
204     // Create a view.
205     await store!.execute("CREATE VIEW v1 as select * from test where id > 0;");
206     let resultSet = await store!.querySql("select * from v1;");
207     resultSet!.close();
208   } catch (err) {
209     console.error(`query failed, code is ${err.code}, message is ${err.message}`);
210   }
211   ```
212
2137. Query data using vector indexes.
214
215   The vector store uses vectors as keys to provide efficient and fast search capabilities.
216
217   It supports the basic syntax and extended syntax as follows:
218
219   - Basic syntax:
220
221     ```sql
222     // index_name indicates the index name, index_type indicates the index type, and dist_function indicates the type of distance function for similarity measurement.
223     CREATE INDEX [IF NOT EXISTS] index_name ON table_name USING index_type (column_name dist_function);
224
225     DROP INDEX table_name.index_name;
226     ```
227   - Extended syntax:
228
229     ```sql
230     CREATE INDEX [Basic syntax] [WITH(parameter = value [, ...])];
231     ```
232
233   **Table 1** index_type
234
235   | Type     | Description                                                    |
236   | --------- | ------------------------------------------------------------ |
237   | gsdiskann | Index for processing high-dimensional dense vector data, such as text embedding and image features.        |
238
239   **Table 2** dist_function
240
241   | Type  | Operator| Description  |
242   | ------ | -------- | ---------- |
243   | L2     | <->      | Euclidean distance.|
244   | COSINE | <=>      | Cosine distance.|
245
246   **Table 3** parameter (extended syntax parameters)
247
248   | Parameter  | Value Range| Description  |
249   | ------ | -------- | ---------- |
250   | QUEUE_SIZE | Value range: [10, 1000]<br>Default value: **20**    | Size of the candidate queue when an index is created for nearest neighbor search. A larger value indicates a lower construction speed and a slightly higher recall rate.|
251   | OUT_DEGREE | Value range: [1, 1200]<br>Default value: **60**     | Number of outgoing edges of a node in the graph. The value of **OUT_DEGREE** cannot exceed **pageSize**. Otherwise, the error GRD_INVALID_ARGS will be thrown.|
252
253   > **NOTE**
254   >
255   > - When deleting an index, you need to specify the table name, that is, **Drop Index table.index_name**.
256   >
257   > - The index created with a table cannot be deleted, for example, the primary key cannot be deleted.
258   >
259   > - When querying data based on vector indexes, you must use **ORDER BY** and **LIMIT**. **ORDER BY** has only one sorting condition, that is, the vector distance condition. If **ORDER BY** is used with **DESC**, vector indexes will not be used. The distance metric used for querying must match the metric used when the index is created. For example, if the vector index is created using **L2**, <-> must be used for the query. Otherwise, the index cannot be hit.
260
261   The sample code is as follows:
262
263   ```ts
264   // Basic syntax
265   try {
266     // Create an index using the basic syntax. The index name is diskann_l2_idx, index column is repr, type is gsdiskann, and the distance metric is L2.
267     await store!.execute("CREATE INDEX diskann_l2_idx ON test USING GSDISKANN(repr L2);");
268     // Delete the diskann_l2_idx index from the test table.
269     await store!.execute("DROP INDEX test.diskann_l2_idx;");
270   } catch (err) {
271     console.error(`create index failed, code is ${err.code}, message is ${err.message}`);
272   }
273
274   // Extended syntax
275   try {
276     // Set QUEUE_SIZE to 20 and OUT_DEGREE to 50.
277     await store!.execute("CREATE INDEX diskann_l2_idx ON test USING GSDISKANN(repr L2) WITH (queue_size=20, out_degree=50);");
278   } catch (err) {
279     console.error(`create ext index failed, code is ${err.code}, message is ${err.message}`);
280   }
281   ```
282
2838. Configure the data aging policy, which allows the application data to be automatically deleted by time or space.
284
285   The syntax is as follows:
286
287   ```sql
288   CREATE TABLE table_name(column_name type [, ...]) [WITH(parameter = value [, ...])];
289   ```
290
291   **parameter** specifies the parameter to set, and **value** specifies the value of the parameter. The following table describes the fields contained in **parameter**.
292
293   **Table 4** parameter (data aging policy parameters)
294
295   | Parameter| Mandatory| Value Range|
296   | ------ | -------- | ---------- |
297   | time_col | Yes| Column name. The value must be an integer and cannot be empty.|
298   | interval | No| Interval for executing the aging task thread. If a write operation is performed after the interval, an aging task will be triggered to delete the data that meets the aging conditions. If the write operation is performed within the interval, no aging task will be triggered. <br>Value range: [5 second, 1 year]<br>Default value: **1 day**<br>Time units supported include **second**, **minute**, **hour**, **day**, **month** and **year**. The value is case-insensitive and supports both singular and plural forms (for example, **2 hour** and **2 hours** are acceptable).|
299   | ttl | No| Data retention period. <br>Value range: [1 hour, 1 year]<br>Default value: **3 month**<br>Time units supported include **second**, **minute**, **hour**, **day**, **month** and **year**. The value is case-insensitive and supports both singular and plural forms (for example, **2 hour** and **2 hours** are acceptable).|
300   | max_num | No| Maximum data volume allowed. <br>Value range: [100, 1024]<br>Default value: **1024**<br> After the aging task deletes expired data, if the remaining data in the table exceeds the value of **max_num**, data tied to the nearest expiration-adjacent time point will be deleted until the total row count falls below **max_num**.|
301
302   Time-related parameters are converted into seconds as follows.
303
304   | Unit| Value in Seconds|
305   | ------ | -------- |
306   | year | 365 * 24 * 60 * 60 |
307   | month | 30 * 24 * 60 * 60 |
308   | day | 24 * 60 * 60 |
309   | hour | 60 * 60 |
310   | minute | 60 |
311
312   For example, if **ttl** is set to **3 months**, the value will be converted into 7,776,000 seconds (3 x (30 * 24 * 60 * 60)).
313
314   The sample code is as follows:
315
316   ```ts
317   try {
318     // The write operation performed every 5 minutes will trigger a data aging task.
319     await store!.execute("CREATE TABLE test2(rec_time integer not null) WITH (time_col = 'rec_time', interval = '5 minute');");
320   } catch (err) {
321     console.error(`configure data aging failed, code is ${err.code}, message is ${err.message}`);
322   }
323   ```
324
3259. Delete the database.
326
327   Call **deleteRdbStore()** to delete the vector store and related database files. The sample code is as follows:
328
329   ```ts
330   try {
331     await relationalStore.deleteRdbStore(this.context, STORE_CONFIG);
332   } catch (err) {
333     console.error(`delete rdbStore failed, code is ${err.code},message is ${err.message}`);
334   }
335   ```
336
337
338