| <!-- |
| |
| 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. |
| |
| --> |
| |
| # tsfile_sqlite 用户手册 |
| |
| `tsfile_sqlite` 是一个实验性的 SQLite loadable extension。它提供 |
| `tsfile_hybrid` 虚拟表,让一张逻辑表同时使用两种物理存储: |
| |
| - 尚未封存的近期数据保存在 SQLite shadow table 中,支持事务和 CRUD; |
| - 已封存的历史数据保存在不可变的 TsFile 段文件中; |
| - 应用继续对同一张虚拟表执行 SQL,扩展自动合并冷热数据。 |
| |
| 它适合以追加为主、近期数据偶尔需要修正、历史数据可以冻结的时序场景。 |
| 它不是 SQLite 通用表的替代存储引擎,也不会自动把已有 SQLite 表转换为 |
| TsFile。 |
| |
| ## 1. 环境要求 |
| |
| - Linux 或 macOS; |
| - SQLite 3.31 或更高版本; |
| - 支持加载扩展的 SQLite 构建; |
| - CMake 构建时启用共享版 `libtsfile`; |
| - TsFile 目录必须使用绝对路径,并由一张逻辑表独占。 |
| |
| 当前 MVP 不支持 Windows。 |
| |
| ## 2. 构建 |
| |
| 在仓库根目录执行: |
| |
| ```bash |
| cmake -S cpp -B cpp/build/sqlite \ |
| -DBUILD_SQLITE_EXTENSION=ON \ |
| -DTSFILE_BUILD_SHARED=ON \ |
| -DBUILD_TEST=ON |
| |
| cmake --build cpp/build/sqlite --target tsfile_sqlite -j |
| ``` |
| |
| 产物位于构建目录的 `lib` 子目录: |
| |
| - Linux:`cpp/build/sqlite/lib/tsfile_sqlite.so` |
| - macOS:`cpp/build/sqlite/lib/tsfile_sqlite.dylib` |
| |
| 扩展依赖同一次构建产生的 `libtsfile`。默认 RPATH 会从扩展所在目录寻找 |
| `libtsfile`,部署时建议把二者放在同一目录。 |
| |
| 运行扩展测试: |
| |
| ```bash |
| cmake --build cpp/build/sqlite --target TsFile_Sqlite_Test -j |
| ctest --test-dir cpp/build/sqlite/test -R TsFileSqliteTest \ |
| --output-on-failure |
| ``` |
| |
| ## 3. 加载扩展 |
| |
| ### 3.1 SQLite CLI |
| |
| ```sql |
| .load /absolute/path/to/tsfile_sqlite |
| ``` |
| |
| SQLite CLI 通常会根据平台自动补全 `.so` 或 `.dylib` 后缀。也可以传入完整 |
| 文件名。 |
| |
| ### 3.2 C/C++ 应用 |
| |
| ```c |
| sqlite3_enable_load_extension(db, 1); |
| |
| char *error = NULL; |
| int rc = sqlite3_load_extension( |
| db, "/absolute/path/to/tsfile_sqlite", NULL, &error); |
| |
| sqlite3_enable_load_extension(db, 0); |
| ``` |
| |
| 应用应在打开数据库连接后、访问 hybrid 表之前加载扩展。生产环境建议加载 |
| 完成后立即关闭动态扩展加载能力。 |
| |
| ## 4. 创建逻辑表 |
| |
| 以下按目标功能设计定义建表语法;示例表达待实现接口,不代表当前原型已支持。 |
| 列定义采用 `列名 类型 [类别]`,省略类别时默认为 `FIELD`;`TIME` 和 `TAG` 显式声明。 |
| |
| ```sql |
| CREATE VIRTUAL TABLE sensor USING tsfile_hybrid( |
| time TIMESTAMP TIME, |
| device STRING TAG, |
| region STRING TAG, |
| temperature DOUBLE FIELD, |
| status STRING FIELD, |
| payload BLOB FIELD, |
| directory='/var/lib/example/sensor', |
| timestamp_precision='ms' |
| ); |
| ``` |
| |
| 列定义按书写顺序组成 schema,表级选项使用 `key=value`。未知选项、重复的表级 |
| 选项和不合法的列定义在建表时返回明确错误。标识符支持双引号转义,例如 |
| `"sensor value" DOUBLE`;字符串选项使用单引号。 |
| |
| 模块参数如下: |
| |
| | 参数 | 要求 | |
| | --- | --- | |
| | `directory` | 必填、绝对路径、由当前逻辑表独占 | |
| | `timestamp_precision` | 必填,只能是 `ms`、`us` 或 `ns` | |
| | `column` | 可重复,格式为 `名称:类型:类别` | |
| |
| 列定义的目标规则如下: |
| |
| - 恰好一个 `TIME` 列,必须是第一列,类型为 `TIMESTAMP`,值不能为 `NULL`; |
| - `TAG` 列可以有零个或多个;存在时类型必须为 `STRING`,值允许为 SQL `NULL`; |
| - 有 TAG 时,全部 TAG 与 TIME 共同组成唯一键;无 TAG 时,TIME 单独组成唯一键; |
| - 不声明类别的列默认为 `FIELD`,FIELD 值可以为 `NULL`; |
| - 列名不能仅靠 ASCII 大小写区分,例如 `Temperature` 和 `temperature` 视为重名; |
| - 封存通过第 7 节的管理 UDF 发起,业务 schema 不需要声明或操作 |
| `_tsfile_command`、`_tsfile_cutoff` 控制列。 |
| |
| NULL TAG 的目标语义: |
| |
| - SQL `NULL`、空字符串 `''` 和字符串 `'null'` 是三个不同值,封存和查询必须保留区别; |
| - 判定逻辑唯一键时,相同位置的两个 NULL TAG 视为同一个键分量。例如同一 TIME 下, |
| 两条 `(device=NULL, region='cn-east')` 记录冲突; |
| - NULL 查询使用 `IS NULL`,普通 WHERE 表达式继续遵循 SQLite 的 NULL 语义, |
| 不把 `= NULL` 改成相等比较; |
| - 热数据的唯一键检查必须显式处理 NULL,不能仅依赖 SQLite 默认 UNIQUE 对 NULL |
| 的处理,也不能用可能与实际 TAG 冲突的字符串替换 NULL。 |
| |
| 无 TAG 表可按以下方式定义;每个时间戳最多对应一行: |
| |
| ```sql |
| CREATE VIRTUAL TABLE readings USING tsfile_hybrid( |
| time TIMESTAMP TIME, |
| value DOUBLE, |
| directory='/var/lib/example/readings', |
| timestamp_precision='ms' |
| ); |
| ``` |
| |
| 所有 TAG 列与 TIME 列共同组成唯一键。例如上表的唯一键是: |
| |
| ```text |
| (device, region, time) |
| ``` |
| |
| ### 4.1 数据类型映射 |
| |
| | TsFile 类型 | SQLite 表现 | 写入要求 | |
| | --- | --- | --- | |
| | `BOOLEAN` | INTEGER | 必须传 SQLite INTEGER;0 为假,非 0 为真 | |
| | `INT32` | INTEGER | 必须在 int32 范围内 | |
| | `INT64` | INTEGER | SQLite int64 | |
| | `FLOAT` | REAL | INTEGER 或 REAL | |
| | `DOUBLE` | REAL | INTEGER 或 REAL | |
| | `TEXT` | TEXT | SQLite TEXT | |
| | `STRING` | TEXT | SQLite TEXT | |
| | `BLOB` | BLOB | SQLite BLOB,保留长度和二进制零字节 | |
| | `DATE` | INTEGER | 必须在 int32 范围内 | |
| | `TIMESTAMP` | INTEGER | SQLite int64 | |
| |
| 扩展不会换算时间戳。`timestamp_precision` 仅声明整数时间戳的单位,并写入 |
| 非空 TsFile 段的 `tsfile_sqlite.timestamp_precision` property。 |
| |
| ### 4.2 创建后的固定配置 |
| |
| schema、目录和时间精度会记录在配置 shadow table 中。数据库重新打开时, |
| 扩展会验证这些信息是否与 `CREATE VIRTUAL TABLE` 中保存的参数一致。 |
| |
| 当前版本不支持修改 schema、目录或时间精度。需要变更时,应创建一张新的 |
| 逻辑表并迁移数据。 |
| |
| ## 5. 写入和修改热数据 |
| |
| 普通 DML 的用法与 SQLite 表一致: |
| |
| ```sql |
| INSERT INTO sensor(time, device, region, temperature, status) |
| VALUES (1700000000000, 'device-1', 'cn-east', 21.5, 'ok'); |
| |
| UPDATE sensor |
| SET temperature = 22.0 |
| WHERE device = 'device-1' |
| AND region = 'cn-east' |
| AND time = 1700000000000; |
| |
| DELETE FROM sensor |
| WHERE device = 'device-1' |
| AND region = 'cn-east' |
| AND time = 1700000000000; |
| ``` |
| |
| 热数据实际写入 `<虚拟表名>_data` shadow table,因此自动使用 SQLite 的 |
| rollback journal/WAL、锁、唯一约束、事务和 savepoint。 |
| |
| ```sql |
| BEGIN; |
| |
| INSERT INTO sensor(time, device, region, temperature) |
| VALUES (1700000001000, 'device-1', 'cn-east', 22.1); |
| |
| UPDATE sensor |
| SET status = 'checked' |
| WHERE device = 'device-1' AND time = 1700000001000; |
| |
| COMMIT; |
| ``` |
| |
| ## 6. 查询冷热数据 |
| |
| 无论数据位于 SQLite 还是 TsFile,都查询同一张虚拟表: |
| |
| ```sql |
| SELECT time, device, temperature, status |
| FROM sensor |
| WHERE device = 'device-1' |
| AND time >= 1700000000000 |
| AND time < 1700086400000 |
| ORDER BY time; |
| ``` |
| |
| 常规 SQLite SQL 仍然可用,包括 FIELD 条件、表达式、聚合、排序和分页: |
| |
| ```sql |
| SELECT device, avg(temperature) |
| FROM sensor |
| WHERE time >= 1700000000000 |
| AND temperature IS NOT NULL |
| GROUP BY device; |
| ``` |
| |
| 为了得到更好的 TsFile 扫描效率,查询应尽量包含: |
| |
| - 整数时间范围; |
| - 使用 `BINARY` collation 的 TAG 等值条件; |
| - 只选择需要的列。 |
| |
| 扩展会把这些条件和投影下推到冷热读取路径。FIELD 条件、排序、聚合、 |
| `LIMIT/OFFSET` 由 SQLite 在合并结果上处理。扩展不会声称原始输出已经满足 |
| `ORDER BY`,因此需要稳定顺序时必须显式写出 `ORDER BY`。 |
| |
| 所有已下推的约束仍由 SQLite 二次检查,以保证 SQL 结果正确。 |
| |
| ## 7. 封存历史数据 |
| |
| 使用两个隐藏列发送 `seal` 命令: |
| |
| ```sql |
| INSERT INTO sensor(_tsfile_command, _tsfile_cutoff) |
| VALUES ('seal', 1700086400000); |
| ``` |
| |
| `cutoff` 是不包含的上界。上述操作封存: |
| |
| ```text |
| 旧 watermark <= time < 1700086400000 |
| ``` |
| |
| 其中 `time == 1700086400000` 的行仍在热区,可以继续修改。 |
| |
| 一次非空 seal 会: |
| |
| 1. 按全部 TAG、TIME 排序读取待封存热数据; |
| 2. 使用 Tablet 批量写入临时 TsFile; |
| 3. 校验并将临时文件原子改名为 `.tsfile`; |
| 4. 在 manifest 中登记文件; |
| 5. 从热表删除已封存行; |
| 6. 将 watermark 推进到 cutoff。 |
| |
| 如果区间内没有数据,不生成 TsFile,但仍会推进 watermark。cutoff 不能小于 |
| 当前 watermark。 |
| |
| seal 是同步写操作,在完成期间会占用 SQLite 写事务。可以显式把它放入事务: |
| |
| 目标设计使用显式管理 UDF 封存数据: |
| |
| ```sql |
| SELECT tsfile_seal('main.sensor', 1700086400000); |
| ``` |
| |
| `tsfile_seal(table_name, cutoff)` 的接口契约: |
| |
| - `table_name` 指定一张 hybrid 逻辑表,示例中为 main schema 下的 sensor; |
| - `cutoff` 必须为整数,单位与该表的 timestamp_precision 相同; |
| - 封存 `[当前 watermark, cutoff)`,等于 cutoff 的行继续留在热区; |
| - cutoff 小于当前 watermark 时返回约束错误,等于 watermark 时返回 0; |
| - 成功返回本次封存的行数。空区间返回 0,但 cutoff 更大时仍推进 watermark; |
| - 取得写权限后重新读取 watermark。封存与普通写入使用同一事务协调; |
| - 自动提交模式下,独立语句完成时提交;显式事务内不擅自提交外层事务, |
| 返回行数仅表示本事务已执行封存,最终持久化取决于外层 COMMIT; |
| - 失败时撤销本次调用的元数据、热数据删除和待发布文件,不能部分封存。 |
| |
| 也可以由调用方显式控制事务: |
| |
| ```sql |
| BEGIN IMMEDIATE; |
| SELECT tsfile_seal('main.sensor', 1700086400000); |
| COMMIT; |
| ``` |
| |
| 该 UDF 属于有副作用的管理操作,规定以独立顶层 `SELECT` 调用,不支持放入 |
| 逐行查询、视图、触发器或其他 schema 表达式。注册时不标记为 deterministic, |
| 并限制间接调用。业务接口不再要求向隐藏控制列插入数据。 |
| |
| 如果事务回滚,manifest、watermark 和热数据删除都会回滚,扩展也会删除本次 |
| 事务产生的临时文件或已经改名的段文件。 |
| |
| ## 8. Watermark 和冷数据不可变性 |
| |
| watermark 把逻辑时间轴分成两部分: |
| |
| ```text |
| time < watermark 冷区:不可修改 |
| time >= watermark 热区:允许 INSERT/UPDATE/DELETE |
| ``` |
| |
| 以下操作会返回约束错误: |
| |
| - 插入 `time < watermark` 的行; |
| - 把热行的时间更新到 watermark 之前; |
| - 更新或删除已经位于 TsFile 的冷行; |
| - 写入重复的 `(所有 TAG, TIME)` 唯一键。 |
| |
| 当前版本没有 correction 或 tombstone。如果业务必须修正历史数据,需要重建 |
| 逻辑表或在业务层保留单独的修正数据。 |
| |
| ## 9. 内部状态与诊断 |
| |
| 每张名为 `sensor` 的 hybrid 表拥有三个 SQLite shadow table: |
| |
| | 表 | 内容 | |
| | --- | --- | |
| | `sensor_data` | 可变热数据 | |
| | `sensor_segments` | TsFile 路径、cutoff 和行数 manifest | |
| | `sensor_config` | watermark、精度、目录和 schema 签名 | |
| |
| 可以只读查看它们进行诊断: |
| |
| ```sql |
| SELECT watermark, precision, directory FROM sensor_config; |
| |
| SELECT path, cutoff, row_count |
| FROM sensor_segments |
| ORDER BY cutoff; |
| |
| SELECT count(*) AS hot_rows FROM sensor_data; |
| ``` |
| |
| 内部对象采用带符号的命名形式 `"<逻辑表名>_tsfile$<用途>"`。例如 sensor 对应: |
| |
| | 内部表 | 用途 | |
| | --- | --- | |
| | `"sensor_tsfile$hot"` | 可变热数据 | |
| | `"sensor_tsfile$segments"` | TsFile 文件登记信息 | |
| | `"sensor_tsfile$config"` | schema、精度、watermark 和目录配置 | |
| |
| 名字必须统一做标识符转义。建表前检查全部派生名;任何同名对象都使创建失败并明确 |
| 报告冲突,不覆盖或复用用户对象。符号用于提高辨识度,不能被当作绝不撞名的保证。 |
| 最后一个下划线之前保留完整逻辑表名,以维持 SQLite 的 shadow 表识别关系。 |
| |
| 这些名字属于内部实现,不作为业务 API。日常诊断通过公开状态接口完成;用户能够 |
| 在 schema 中查看内部对象,但不应直接修改它们。绕过虚拟表写入会破坏 watermark、 |
| 文件登记和实际文件之间的一致性。 |
| |
| 热行使用 SQLite 的正 rowid;冷行使用扩展生成的负 rowid。冷 rowid 是内部 |
| 实现标识,不应作为跨查询或跨版本稳定的业务主键。 |
| |
| ## 10. 数据目录和生命周期 |
| |
| 每张逻辑表必须使用独占目录。扩展会在 seal 前清理目录中未被 manifest 引用的 |
| `.tmp` 和 `.tsfile` 文件,用来恢复进程崩溃后遗留的孤儿文件。因此不要把手工 |
| 创建的 TsFile、其他表的段文件或任何同后缀文件放进该目录。 |
| |
| 执行: |
| |
| ```sql |
| DROP TABLE sensor; |
| ``` |
| |
| 会删除虚拟表及其三个 shadow table,但不会删除已经导出的 TsFile。删除或归档 |
| 这些文件需要由运维流程显式完成。 |
| |
| ## 11. TsFile 导出、导入与文件校验(功能设计草案) |
| |
| 本节定义面向标准 TsFile 的交换和查询接口。导出产物只有 `.tsfile` 文件,不导出 |
| SQLite 数据库、热表、WAL、shadow 表或独立的 JSON 清单,也不承诺恢复源表的可写状态。 |
| 导入接口及只读语义在 11.2 节定义。 |
| |
| ### 11.1 仅导出 TsFile |
| |
| ```sql |
| SELECT tsfile_export('main.sensor', '/export/sensor-001'); |
| ``` |
| |
| `tsfile_export(table_name, output_directory)` 导出指定逻辑表已经落到 TsFile 的 |
| 数据,成功返回产出的文件数。SQLite 中尚未封存的热数据不在导出范围内。 |
| 如需导出这些热数据,调用方先显式 seal 到选定 cutoff,提交后再执行 export。 |
| export 本身不封存、不修改源数据、不推进 watermark。 |
| |
| 目标目录必须为绝对路径且尚不存在,并且不得与源数据目录重合或互相嵌套。 |
| 输出示例: |
| |
| ```text |
| sensor-001/ |
| part-000001.tsfile |
| part-000002.tsfile |
| ``` |
| |
| - 表名、列类型和类别从 TsFile 自身元数据读取;可用的时间精度放在文件 Properties。 |
| - 输出文件只包含所选逻辑表的数据。如果输入段同时含其他表,需要导出所选表为独立 |
| TsFile,不能直接复制而意外带出其他表。文件内部表名使用该逻辑表的导出名称。 |
| - 文件可由标准 TsFile Reader 独立读取,不依赖本扩展的 JSON 清单或 SQLite 文件。 |
| - 没有已封存数据时返回 0,目标目录为空;不会制造可恢复空表或热数据的假象。 |
| |
| 第一版采用保守的一致性方式:取得源 SQLite 数据库的写保留锁,在同一事务视图内 |
| 固定该表的文件登记集合,复制或重写完成后释放锁。期间阻塞该数据库的其他写入。 |
| 对外部只读文件也要检查读取错误和文件变化;外部修改不属于 SQLite 锁的保护范围。 |
| |
| 先在目标旁的专用暂存目录生成文件,校验 footer、schema 和已知精度,完成文件同步, |
| 再原子发布整个目录并同步父目录。失败不发布完整目标,源文件不变。 |
| 仅允许独立管理调用,不接受用户已有的显式事务;成功返回时输出目录已发布。 |
| |
| ### 11.2 扫描目录并建立只读查询表 |
| |
| ```sql |
| SELECT tsfile_import('/data/archive'); |
| ``` |
| |
| `tsfile_import(directory [, target_schema])` 默认把发现的表注册到 `main`;可显式 |
| 指定一个已经存在的 SQLite schema。成功返回新注册的逻辑表数量。 |
| |
| ```sql |
| SELECT tsfile_import('/data/archive', 'archive'); |
| ``` |
| |
| 这里的导入指登记外部 TsFile 并建立查询入口:默认直接引用原文件,不复制、移动、 |
| 重写文件,也不把行装入 SQLite 热表。源目录必须是可持续访问的绝对路径,文件需 |
| 由调用方保持不可变。SQLite 中只保存查询所需的文件登记和 schema 元数据。 |
| |
| 扫描与分组规则: |
| |
| 1. 第一版扫描指定目录当前层的普通 `.tsfile` 文件,不递归子目录,不跟随符号链接; |
| 非 TsFile 文件忽略,扩展名匹配但无法读取的文件使本次导入失败并报告路径。 |
| 2. 从每个文件的元数据枚举全部表名;一个文件包含多张表时分别登记,不按文件名猜 |
| 表名。同一张表出现在多个文件中时,组成同一个只读逻辑表。 |
| 3. 同名表要求列定义、类型、类别和顺序兼容;第一版要求完全一致,不自动补列、 |
| 类型提升或统一不同 schema。按 SQLite 标识符比较产生的大小写冲突应明确报错。 |
| 4. 接受普通 TsFile,不要求来自本扩展,也不要求携带私有快照清单。时间精度属性 |
| 存在时读取;缺失时标记为 unknown 并按原始整数时间查询,不默认为 ms。 |
| 同名表的已知精度必须一致;已知/未知混合也拒绝自动合并,避免混淆时间单位。 |
| 5. 同名表跨文件的结果按 `UNION ALL` 语义读取,重叠时间和重复逻辑键保留,不自动 |
| 覆盖、去重或取最新值。只读导入不对外部数据强加可写 hybrid 表的唯一键约束。 |
| 6. 保存“SQLite 逻辑表 → 文件路径集合 → 各文件内部表名”的映射,所有路径和表名 |
| 都按数据处理并正确转义。文件内部表名不会因 SQL 名称变化而被重写。 |
| |
| 用户通过普通 SQL 查询,例如目录内包含 sensor 和 meter 两个表时: |
| |
| ```sql |
| SELECT time, device, temperature FROM sensor ORDER BY time; |
| SELECT count(*) FROM meter; |
| ``` |
| |
| 所有导入表默认且固定为只读:拒绝 INSERT、UPDATE、DELETE 和 seal,即使当前表为空 |
| 也不接受写入。只读表不创建可写热区,不恢复源库 watermark,也不因为时间较新就 |
| 自动允许修改。持续追加写入仍使用另行创建的可写 hybrid 表。 |
| |
| 注册时先完整枚举、验证并建立映射,再在同一个 SQLite 事务中发布所有表。目标 |
| schema 中任何同名用户表、虚拟表或所需内部对象冲突,都使整批失败;不覆盖、不 |
| 合并到已存在的表。对同一目录重复调用也遵循此规则,不会重复追加登记。 |
| |
| 导入要求独立管理调用,不能嵌入已有用户显式事务。取得目标写锁后再次检查对象 |
| 冲突;失败回滚本次新建的所有登记,源文件保持不变。空目录或未发现表时返回 0。 |
| 本次扫描后新加入目录的文件不会自动进入已注册表,第一版不提供后台监控或刷新; |
| 如需重新登记,可删除相关只读逻辑表后再导入,删除逻辑表不会删除外部文件。 |
| |
| ### 11.3 文件所有权、移动与校验 |
| |
| ```sql |
| SELECT * FROM tsfile_verify('main.sensor'); |
| ``` |
| |
| `tsfile_verify(table_name)` 是只读表值接口,返回 |
| `segment_id, path, status, detail`。状态包括 `OK`、`MISSING`、`CORRUPT`、 |
| `MISMATCH` 和 `UNREGISTERED`。导入时建立文件指纹,verify 对比文件内容、schema |
| 和可用精度;普通文件不需要预先存储本扩展的校验属性。 |
| |
| - 自有 hybrid 段与外部只读引用必须有明确的所有权区别。导入不取得源文件的删除权, |
| DROP TABLE、seal 清理和失败恢复都不得删除外部引用文件。 |
| - 文件被移走或删除时,访问该文件的查询失败并报告表名、文件标识和原路径,不能 |
| 静默跳过;verify 可集中报告问题,不承诺持续监控或自动搜索新路径。 |
| - 同路径文件被替换或修改时,完整 verify 对比导入指纹并报告差异;普通查询的轻量 |
| 检查不宣称能够发现所有字节修改。调用方必须保证登记后外部文件保持不可变。 |
| - 外部目录新增文件不会自动导入;verify 报告发现的未登记 TsFile,且不删除它们。 |
| 自有目录中的未知文件也不能仅凭 `.tsfile` 后缀被孤儿清理删除。 |
| - 复制到其他位置且保留源文件不影响已有查询;移动目录后应在保留源数据的前提下 |
| 重新建立登记,不直接改写内部表路径。 |
| |
| 导出目录只包含 TsFile,导入后得到只读数据集;这一过程不是恢复完整 SQLite 数据库 |
| 或可写 hybrid 状态的备份协议。 |
| |
| ### 11.4 验收场景 |
| |
| - 导出目录只含标准 `.tsfile`;热数据不被导出,watermark 不改变,无冷数据返回 0; |
| - 目录内多文件、多表能被完整发现,同表跨文件汇成一个只读表; |
| - 不带本扩展私有 Properties 的普通 TsFile 可以查询,缺失精度标为 unknown; |
| - 多文件重叠时间和重复键按 UNION ALL 保留;NULL TAG 与空字符串不混淆; |
| - schema 不兼容、精度冲突、表名冲突及损坏文件使批量导入失败且不留下部分注册; |
| - 对导入表执行 INSERT、UPDATE、DELETE、seal 均被拒绝,源文件字节保持不变; |
| - 文件移走、替换和新增分别被诊断为缺失、变化和未登记;verify 不修改任何文件; |
| - 删除只读逻辑表、导入失败及进程重启清理都不会删除外部文件; |
| - 对导出复制、文件同步、目录发布和导入登记提交注入故障,验证源数据不变及 |
| 目标没有部分可见的发布结果。 |
| |
| ## 12. 常见问题 |
| |
| ### 加载时报 `not authorized` 或扩展加载被禁用 |
| |
| 确认 SQLite 构建允许 loadable extension,并在应用连接上调用 |
| `sqlite3_enable_load_extension()`。CLI 使用 `.load` 即可。 |
| |
| ### 加载时报找不到 `libtsfile` |
| |
| 把当前构建对应的 `libtsfile` 与 `tsfile_sqlite` 放到同一目录,避免混用不同 |
| 版本的库。必要时检查平台动态链接器的搜索路径。 |
| |
| ### 创建表时报目录错误 |
| |
| `directory` 必须是绝对路径,父目录必须可创建或可写。连接已有数据库时,该 |
| 目录必须仍然存在。 |
| |
| ### seal 后无法更新某些行 |
| |
| 这是 watermark 的预期行为。任何 `time < watermark` 的数据已经进入不可变 |
| 冷区。 |
| |
| ### 查询没有固定顺序 |
| |
| 虚拟表会合并多个来源,但不承诺自然顺序。需要顺序时使用显式 `ORDER BY`。 |
| |
| ## 13. MVP 限制 |
| |
| - seal 只能显式、同步执行; |
| - 冷数据不可更新或删除; |
| - 不支持后台自动封存、compaction 和冷段删除; |
| - 不支持原地 schema 演进; |
| - 查询游标当前会在内存中汇集冷热结果; |
| - 不跨冷热来源下推排序和 `LIMIT/OFFSET`; |
| - Windows 尚未支持; |
| - 这仍是实验性扩展,接口和内部格式可能继续演进。 |