blob: e5b3301968882a01d7b4486e55d26507bd8bdbbb [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.
-->
# DuckDB TsFile Extension
This guide is intended for data engineers, platform developers, and analytics developers who need to analyze or generate Apache TsFile data in DuckDB. The TsFile extension is available as a DuckDB Community Extension. It makes TsFile both a SQL data source and a `COPY` output format, providing a complete read-query-write workflow.
**Remember two things:** use `read_tsfile(path, table_name)` to read data, and use `COPY ... TO ... (FORMAT tsfile)` to write data. The current version supports local, Table-model TsFiles. Before writing, sort the data by the TAG columns and then by the time column.
## Quick Start
### Start DuckDB and Load the Extension
For regular use, install and load the DuckDB Community Extension:
```sql
INSTALL tsfile FROM community;
```
```sql
LOAD tsfile;
```
Install the Community Extension the first time you use it. After that, you only need to run `LOAD tsfile;` whenever you start DuckDB.
### Read Your First TsFile
```sql
SELECT *
FROM read_tsfile('/data/measurements.tsfile', 'sensors');
```
The first argument to `read_tsfile` is a local file path, and the second is the name of the table stored in the TsFile. Each call reads one Table-model table from one file.
## Reading and Querying
### Select Columns
Select the columns you need just as you would from a regular DuckDB table. Projection is pushed down to the TsFile scan whenever possible, reducing the amount of data read:
```sql
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
LIMIT 10;
```
### Filter by Time
The global TsFile time axis is exposed in DuckDB as a `BIGINT`. The time unit is determined by the file or protocol; milliseconds are common. The following operators can be pushed down to the scanner: `=`, `<`, `<=`, `>`, `>=`, and `BETWEEN`.
```sql
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
WHERE time BETWEEN 1700000000000 AND 1700003600000;
```
### Filter by TAG
String TAG columns support equality and range comparisons, null checks, and combinations using `AND` and `OR`. Supported operations include `=`, `!=`, `<`, `<=`, `>`, `>=`, inclusive `BETWEEN`, `IS NULL`, and `IS NOT NULL`.
```sql
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
WHERE (device_id = 'device-01' OR device_id = 'device-02')
AND time BETWEEN 1700000000000 AND 1700003600000;
```
FIELD filters and TAG expressions that cannot be pushed down are still evaluated by DuckDB after the scan. A `NOT (...)` expression in any form is not pushed down.
### Check Filter Pushdown
Use `EXPLAIN` to inspect the query plan. If pushdown succeeds, the `READ_TSFILE` node shows `Time Range` and `TAG Filter`:
```sql
EXPLAIN
SELECT time, device_id, temperature
FROM read_tsfile('/data/measurements.tsfile', 'sensors')
WHERE device_id = 'device-01'
AND time >= 1700000000000;
```
## Writing TsFile
### Write with COPY
Writing uses DuckDB's standard `COPY` interface. Clean, cast, and sort the data in a subquery before writing it to TsFile:
```sql
COPY (
SELECT time, device_id, temperature, humidity
FROM measurements
ORDER BY device_id, time
)
TO '/data/measurements.tsfile'
(
FORMAT tsfile,
TABLE_NAME sensors,
TIME_COLUMN time,
TAG_COLUMNS (device_id)
);
```
`TIME_COLUMN` identifies the time axis. Columns listed in `TAG_COLUMNS` are written as TAG columns, and all other columns are written as FIELD columns. Identifier syntax is recommended for `TABLE_NAME` and `TIME_COLUMN`; use double quotes when an identifier contains special characters.
### Write Options
|Option|Required|Description|
|---|---|---|
|`FORMAT tsfile`|Yes|Selects the TsFile `COPY` writer.|
|`TABLE_NAME`|No|The local table name in the output file. The default is `default_table`.|
|`TIME_COLUMN`|No|The input column used as the time axis. The default is `time`, and its type must be `BIGINT`.|
|`TAG_COLUMNS`|No|A list of TAG column names. If omitted, all non-time columns are written as FIELD columns.|
|`OVERWRITE true`|No|Use this option to replace an existing target. Keep the default temporary-file handling enabled.|
### TAG\_COLUMNS Constraints
`TAG_COLUMNS` accepts a list of column names, not a list of values:
```sql
TAG_COLUMNS (device_id, region)
```
- Each column must exist in the input query.
- Each column must have the `VARCHAR` type.
- A TAG column cannot also be the `TIME_COLUMN`.
- Column-name matching is case-insensitive.
If you do not need TAG columns, you can write only the time and FIELD columns:
```sql
COPY (
SELECT time, temperature, humidity
FROM measurements
ORDER BY time
)
TO '/data/field-only.tsfile'
(
FORMAT tsfile,
TABLE_NAME sensors,
TIME_COLUMN time
);
```
### Type Mapping and NULL Values
|DuckDB Type|TsFile Type|Notes|
|---|---|---|
|BOOLEAN|BOOLEAN|FIELD NULL values are preserved.|
|INTEGER|INT32|FIELD NULL values are preserved.|
|BIGINT|INT64|FIELD NULL values are preserved; this type is also used for the time axis.|
|FLOAT|FLOAT|FIELD NULL values are preserved.|
|DOUBLE|DOUBLE|FIELD NULL values are preserved.|
|VARCHAR|STRING|FIELD NULL values are preserved.|
|BLOB|BLOB|FIELD NULL values are preserved.|
|TIMESTAMP\_NS|TIMESTAMP|Read as DuckDB `TIMESTAMP_NS`.|
## End-to-End Example
The following workflow reads a time range for device `a` from an existing TsFile, renames columns, writes a new TsFile, and then reads it back for verification:
```sql
LOAD tsfile;
COPY (
SELECT time,
s0 AS device_id,
s2 AS value,
CAST(s8 AS VARCHAR) AS day
FROM read_tsfile('test/data/simple_table_t1.tsfile', 'test')
WHERE s0 = 'a'
AND time BETWEEN 1760106022000 AND 1760106024000
ORDER BY device_id, time
)
TO '/tmp/tsfile_subset.tsfile'
(
FORMAT tsfile,
TABLE_NAME subset,
TIME_COLUMN time,
TAG_COLUMNS (device_id)
);
SELECT time, device_id, value, day
FROM read_tsfile('/tmp/tsfile_subset.tsfile', 'subset')
ORDER BY device_id, time;
```
The query returns three rows with timestamps `1760106022000`, `1760106023000`, and `1760106024000`.
## Current Limitations and Troubleshooting
### Pre-Write Checklist
1. Confirm that `TIME_COLUMN` exists and has the `BIGINT` type.
2. Confirm that the TIME column contains no NULL values.
3. Confirm that the input is sorted by all TAG columns and then by the time column.
4. If the target file already exists, keep temporary-file handling enabled and set `OVERWRITE true`.
### Known Limitations
- The current version supports one Table-model table in one local TsFile.
- FIELD filters are not pushed down. A `NOT (...)` TAG expression in any form is not pushed down.
- Writing DATE FIELD columns is temporarily disabled to avoid conversions that depend on the local time zone. DATE columns in existing TsFiles can still be read. To write such a column, first cast it to `VARCHAR`.
- When writing directly with `USE_TMP_FILE false`, the target path must not already exist.
## Quick Reference
|Task|Entry Point|
|---|---|
|Read TsFile|`read_tsfile('/path/file.tsfile', 'table')`|
|Inspect columns and data|`SELECT ... FROM read_tsfile(...)`|
|Push down time and TAG conditions|Use supported comparisons and logical combinations in `WHERE`|
|Inspect pushdown|`EXPLAIN SELECT ...`|
|Write TsFile|`COPY (...) TO 'file.tsfile' (FORMAT tsfile, ...)`|
|Replace an existing file|Default temporary-file handling + `OVERWRITE true`|