blob: e7db82f542ddc9b468c23a8eacb1370f1a7af9ce [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# SQLite TsFile Extension
This guide is intended for data engineers and platform developers who need to read and write Apache TsFile in SQLite. `tsfile_sqlite` is a SQLite loadable extension that registers the `tsfile_hybrid` virtual table module. It combines SQLite hot data and TsFile cold data behind a single logical table: newly written rows stay in SQLite and can be updated or deleted, while sealed rows are written to TsFile and remain queryable but immutable.
**Remember three things:** load the extension with `.load`, create tables with `CREATE VIRTUAL TABLE ... USING tsfile_hybrid(...)`, and seal hot data with `tsfile_seal`. The current version supports local, Table-model TsFiles.
## Quick Start
### Load the Extension
Load the extension in the SQLite CLI or your application. It must be reloaded on every new connection:
```sql
.load /path/to/tsfile_sqlite
```
### Create a Writable Table and Write Rows
```sql
CREATE VIRTUAL TABLE sensor USING tsfile_hybrid(
time TIMESTAMP TIME,
device STRING TAG,
temperature DOUBLE FIELD,
directory='/data/sensor-segments',
timestamp_precision='ms'
);
INSERT INTO sensor VALUES
(1000, 'd1', 21.5),
(2000, 'd1', 22.0),
(3000, 'd2', 19.0);
```
`time` stores millisecond timestamps, `device` identifies devices, and `temperature` stores measurements. `directory` is the exclusive directory used to seal this table's data; the extension creates it. At this point all three rows are still in SQLite and not yet sealed.
### Seal into TsFile
```sql
SELECT tsfile_seal('main.sensor', 200);
```
Returns the number of sealed rows. The argument is the size limit for a single output file, in MB. After sealing, the rows remain queryable but can no longer be modified.
### Query
```sql
SELECT time, device, temperature FROM sensor WHERE device = 'd1';
```
## Reading and Querying
### Open an Existing TsFile (Read-Only)
```sql
CREATE VIRTUAL TABLE history USING tsfile_hybrid(
file='/data/export/sensor-0001.tsfile',
source_table='sensor'
);
SELECT * FROM history;
```
`file` is a file path (or a directory), and `source_table` is the table name stored inside the TsFile. A table without `directory` is read-only and needs no column declarations.
### Continue Writing on Top of an Existing TsFile
```sql
CREATE VIRTUAL TABLE continued USING tsfile_hybrid(
file='/data/export/sensor-0001.tsfile',
source_table='sensor',
directory='/data/continued-segments'
);
INSERT INTO continued VALUES (4000, 'd1', 23.0);
```
Historical rows are read from the TsFile, and new rows are written to the SQLite hot region. The write boundary is per device: a new time must be no earlier than that device's maximum time in the source file plus one. New devices are not constrained by old device boundaries.
### Filters and Pushdown
Time ranges, TAG equality conditions, and FIELD comparison conditions are pushed down to the TsFile read layer; expressions that cannot be pushed down are evaluated by SQLite over the result set.
Filtering by `device` or another device condition is the most effective pruning: data is organized by device, so a device filter can skip other devices' data, whereas a global time-range query may scan a large amount of data even when it returns few rows. Business queries should include a device condition whenever possible.
## Writing and Sealing
### Table Modes
- **Start from scratch**: declare columns plus an exclusive `directory` and a time precision; writable.
- **Read-only query**: `file=` without `directory`.
- **Continue writing**: `file=` plus `directory=`, appending on top of an existing TsFile.
### Hot Writes, Updates, and Deletes
```sql
BEGIN;
INSERT INTO sensor VALUES (4000, 'd2', 20.0);
UPDATE sensor SET temperature = 22.5 WHERE time = 2000 AND device = 'd1';
DELETE FROM sensor WHERE time = 3000;
COMMIT;
```
Rows that are not yet sealed can be modified. If an UPDATE or DELETE actually hits a sealed row, the whole statement fails.
### Seal
```sql
SELECT tsfile_seal('main.sensor', 200);
```
Seals all current hot rows and returns the number of sealed rows. Seal runs synchronously and can participate in an explicit transaction (`BEGIN IMMEDIATE; ... ROLLBACK/COMMIT;`). After sealing, each device's new data must be no earlier than that device's new maximum time plus one.
### Compact Cold Segments
```sql
SELECT tsfile_compact('main.sensor', 50, 200);
```
`50` is the small-file threshold and `200` is the target output size limit, both in MB. Only files smaller than the threshold are compacted; the target size cannot be smaller than the threshold.
### Export
```sql
SELECT tsfile_export('main.sensor', '/data/export');
```
Export copies sealed cold segments directly and does not copy hot-region data. Run seal first when you need to deliver current hot data. The output directory must be an absolute path that does not yet exist.
## Type Mapping and NULL
| Declared type | Written value |
| --- | --- |
| BOOLEAN | integer; 0 is false, non-zero is true |
| INT32, DATE | integer within int32 range |
| INT64, TIMESTAMP | integer within int64 range |
| FLOAT, DOUBLE | integer or decimal |
| STRING, TEXT | text |
| BLOB | binary value, preserving length and zero bytes |
TIME cannot be NULL. TAG and FIELD may be NULL; TAG must be STRING. New rows with the same time and TAG combination conflict. NULL, the empty string, and the text `'null'` are all distinct; use `IS NULL` to find NULLs.
## End-to-End Example
Collect from scratch, seal, export, and read back:
```sql
-- Create a table and write rows
CREATE VIRTUAL TABLE sensor USING tsfile_hybrid(
time TIMESTAMP TIME,
device STRING TAG,
temperature DOUBLE FIELD,
directory='/tmp/sqlite-demo/segments',
timestamp_precision='ms'
);
INSERT INTO sensor VALUES (1000,'d1',21.5), (2000,'d1',22.0), (3000,'d2',19.0);
-- Seal and export
SELECT tsfile_seal('main.sensor', 200);
SELECT tsfile_export('main.sensor', '/tmp/sqlite-demo/export');
-- Read back as read-only
CREATE VIRTUAL TABLE history USING tsfile_hybrid(
file='/tmp/sqlite-demo/export', source_table='sensor'
);
SELECT time, device, temperature FROM history ORDER BY time;
```
## Current Limitations and Troubleshooting
### Before Writing
1. Confirm the table is writable (provides `directory`) or intended as read-only.
2. Confirm the TIME column is non-NULL and new times are no earlier than the device's watermark.
3. Confirm TAG columns are STRING and column names are not duplicated.
### Known Limitations
- The current version supports one Table-model table in one local TsFile file or directory.
- ASCII uppercase TAG/FIELD names may be lowercased in a read-only view; the TIME column name is preserved. SQLite identifiers are case-insensitive, but be aware when downstream systems map by exact measurement name.
- A FIELD column name whose UTF-8 byte length reaches 1018 may fail cold reads; keep it at or below 1017 bytes.
- Global time-range queries without a device condition can be slow; include a device condition whenever possible.
- A long read transaction that continues querying an old snapshot after a compact may report a missing or changed file; end and restart the transaction.
## Capability Quick Reference
| Task | Entry point |
| --- | --- |
| Load the extension | `.load /path/to/tsfile_sqlite` |
| Create a writable table | `CREATE VIRTUAL TABLE ... USING tsfile_hybrid(...)` |
| Query a TsFile read-only | `file=...` + `source_table=...` |
| Continue writing | `file=...` + `directory=...` |
| Seal | `tsfile_seal('main.t', max_mb)` |
| Compact cold segments | `tsfile_compact('main.t', small_mb, target_mb)` |
| Export | `tsfile_export('main.t', '/path')` |
| Inspect status | `tsfile_table_info` / `tsfile_device_info` / `tsfile_schema` |
| Verify files | `tsfile_verify('main.t')` |