diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000000..a655b33ee97f --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,123 @@ +# Copilot Instructions for TDengine + +## Build Commands + +```bash +# Generate build (Debug, with tests enabled) +./build.sh gen + +# Build +./build.sh bld + +# Install (required before running tests) +./build.sh install + +# Quick alternative (from existing debug/ directory) +cd debug && make -j4 && make install + +# Build with specific cmake options +cmake -B debug -DBUILD_TEST=true -DBUILD_TOOLS=true -DBUILD_CONTRIB=true +cmake --build debug -j$(nproc) +``` + +## Testing + +```bash +# Run all unit tests (Google Test based, C++) +cd debug/build/bin && ./osTimeTests # single unit test binary + +# Run a single Python system test +cd tests/system-test && python3 ./test.py -f 2-query/avg.py + +# Run a single legacy TSIM test +cd tests/script && ./test.sh -f tsim/db/basic1.sim + +# Run all CI cases +cd tests && ./run_all_ci_cases.sh -b main +``` + +## Architecture + +TDengine is a distributed time-series database. The core is written in C with Python-based system tests. + +### Source Layout (`source/`) + +- **client/** – Client library (libtaos), CLI (`taos`), stmt2 API +- **dnode/** – Data node process (taosd) + - **mnode/** – Management node (metadata, DDL, cluster coordination) + - **vnode/** – Virtual node (data storage & query execution) + - `meta/` – Table metadata storage + - `tsdb/` – Time-series data engine + - `tq/` – Message queue (subscription/TMQ) + - **qnode/** – Query node (distributed query execution) +- **libs/** – Shared libraries used across components + - **parser/** – SQL parsing & semantic translation (`parTranslater.c` is the main translator) + - **planner/** – Query plan generation (logic → physi → split → scale-out) + - **executor/** – Physical plan operators (scan, join, aggregate, etc.) + - **nodes/** – AST/plan node definitions, clone/serialize/traverse utilities + - **catalog/** – Metadata cache between client and mnode + - **transport/** – RPC framework + - **sync/** – Raft consensus + - **function/** – Built-in and UDF function implementations +- **common/** – Shared data types, time utilities, message definitions +- **os/** – OS abstraction layer +- **util/** – General utilities (hash, array, queue, etc.) + +### Headers (`include/`) + +- `include/common/tmsg.h` – All inter-node message types and `ENodeType` enum +- `include/libs/nodes/` – Node type definitions: + - `nodes.h` – Base node, list macros (FOREACH, WHERE_EACH, etc.) + - `querynodes.h` – AST expression/statement nodes + - `plannodes.h` – Logical and physical plan nodes + - `cmdnodes.h` – DDL command nodes + +### Query Pipeline + +SQL → Parser (`parAstCreater.c`) → Translator (`parTranslater.c`) → Logic Plan (`planLogicCreater.c`) → Optimizer (`planOptimizer.c`) → Physical Plan (`planPhysiCreater.c`) → Splitter (`planSpliter.c`) → Scale Out (`planScaleOut.c`) → Executor operators + +### Tools (`tools/`) + +- **tdgpt/** – AI agent for time-series analytics (Python) +- **shell/** – Interactive CLI (taos) +- **taos-tools/** – taosBenchmark, taosdump (submodule) + +## Key Conventions + +### Error Handling Pattern +Functions return `int32_t` error codes. Use `TSDB_CODE_SUCCESS` (0) for success. Common pattern: +```c +int32_t code = TSDB_CODE_SUCCESS; +// ... operations ... +if (TSDB_CODE_SUCCESS != code) { + goto _end; +} +``` + +### Node System +When adding a new node type: +1. Add enum to `ENodeType` in `include/common/tmsg.h` +2. Define struct in the appropriate header (`querynodes.h`, `plannodes.h`, or `cmdnodes.h`) +3. Add clone logic in `nodesCloneFuncs.c` (use `COPY_SCALAR_FIELD`, `CLONE_NODE_FIELD`, etc.) +4. Add serialization in `nodesCodeFuncs.c` +5. Add creation in `nodesUtilFuncs.c` +6. Add name mapping in `nodesCodeFuncs.c` (`nodesNodeName()`) + +### Naming Conventions +- Prefix: `t` or `taos` for public APIs, `td` for internal types +- Structs: `S` prefix (e.g., `SLogicNode`, `SSelectStmt`) +- Enums: `E` prefix (e.g., `ENodeType`, `EDataOrderLevel`) +- Pointer params: `p` prefix (e.g., `pNode`, `pCxt`) +- Output params: double-pointer `pp` prefix or single pointer with comment + +### Code Style +- Configured via `.clang-format` (Google-based, 2-space indent, 120 column limit) +- Braces on same line (Attach style) +- Pointer alignment: right-aligned (`char *p`) + +### Test Integration +To add a new test case to CI, edit `tests/parallel_test/cases.task`: +``` +#priority,rerunTimes,Run with Sanitizer,casePath,caseCommand +,,n,system-test, python3 ./test.py -f 2-query/your_test.py +``` diff --git a/17-vst-inheritance-design.md b/17-vst-inheritance-design.md new file mode 100644 index 000000000000..c7bccb256965 --- /dev/null +++ b/17-vst-inheritance-design.md @@ -0,0 +1,643 @@ +# 技术设计文档: 虚拟超级表继承 (VST Inheritance) + +> 版本: 1.0 +> 日期: 2026-04-30 +> 对应 FS: 17-vst-inheritance-fs.md + +--- + +## 1. 概述 + +### 1.1 背景 + +当前 TDengine 中的虚拟超级表 (VST) 是扁平结构,每个 VST 独立定义自己的 schema。FS 要求实现 VST 之间的继承关系,使子 VST 可以继承父 VST 的列/Tag 定义并添加新列,形成树状继承层次。查询时支持通过 `EXPAND(N)` 语法展开子孙 VST 的 VCT 数据。 + +### 1.2 现状分析 + +| 组件 | 现状 | 需改造 | +|------|------|--------| +| `SStbObj` (mndDef.h:919) | 有 `virtualStb` 标志位,**无** `parentSuid` 等继承字段 | 是 | +| `SMCreateStbReq` (tmsg.h:1290) | 有 `virtualStb` 标志位,**无**继承字段 | 是 | +| `SVCreateStbReq` (tmsg.h:4902) | MNode→VNode 的 STB 创建请求,**无**继承字段 | 是 | +| Parser (parAstCreater.c) | 支持 `CREATE VIRTUAL STABLE`/`TABLE`,**无** `BASE ON`/`EXPAND` 语法 | 是 | +| Planner (planLogicCreater.c) | `SVirtualScanLogicNode` 只扫描当前 VST 的 VCT | 是 | +| Executor (dynqueryctrloperator.c) | `buildVirtualSuperTableScanChildTableMap()` 只查当前 VST 的 VCT | 是 | +| System Table | 有 `ins_virtual_child_columns`、`ins_virtual_tables_referencing`,**无** `ins_inherits` | 是 | +| SDB 版本 | `STB_VER_NUMBER = STB_VER_SUPPORT_OWNER(4)` | 需升至 5 | + +--- + +## 2. 元数据层 (MNode) + +### 2.1 SStbObj 扩展 + +**文件**: `source/dnode/mnode/impl/inc/mndDef.h` + +```c +typedef struct { + // ... 现有字段 ... + int8_t virtualStb; + int8_t secureDelete; + + // ---- 新增继承字段 ---- + int64_t parentSuid; // 父 VST 的 UID,0 表示无父(根节点) + int64_t parentDbUid; // 父 VST 所在数据库 UID(支持跨库继承) + int32_t inheritDepth; // 当前继承深度,根节点为 0 + int32_t ownColStart; // 自身新增列的起始 index(前面是继承来的列) + int32_t ownTagStart; // 自身新增 Tag 的起始 index +} SStbObj; +``` + +**设计要点**: +- `parentSuid = 0` 表示根 VST 或非继承 VST,保持向后兼容 +- `ownColStart` 用于区分继承列和自身新增列,支持 ALTER CASCADE 时精确定位 +- `inheritDepth` 创建时计算(父节点 depth + 1),限制最大值为 10 + +### 2.2 SDB 版本升级 + +**文件**: `source/dnode/mnode/impl/src/mndStb.c` + +```c +#define STB_VER_SUPPORT_INHERIT 5 +#define STB_VER_NUMBER STB_VER_SUPPORT_INHERIT +``` + +在 `mndStbActionDecode()` 中: +```c +if (sver < STB_VER_SUPPORT_INHERIT) { + pStb->parentSuid = 0; + pStb->parentDbUid = 0; + pStb->inheritDepth = 0; + pStb->ownColStart = 0; + pStb->ownTagStart = 0; +} else { + SDB_GET_INT64(pRaw, dataPos, &pStb->parentSuid, _OVER) + SDB_GET_INT64(pRaw, dataPos, &pStb->parentDbUid, _OVER) + SDB_GET_INT32(pRaw, dataPos, &pStb->inheritDepth, _OVER) + SDB_GET_INT32(pRaw, dataPos, &pStb->ownColStart, _OVER) + SDB_GET_INT32(pRaw, dataPos, &pStb->ownTagStart, _OVER) +} +``` + +`mndStbActionEncode()` 中对应添加 `SDB_SET_INT64/INT32` 写入。 + +### 2.3 消息结构扩展 + +**文件**: `include/common/tmsg.h` + +`SMCreateStbReq` 新增字段: +```c +typedef struct { + // ... 现有字段 ... + int8_t virtualStb; + int8_t secureDelete; + + // ---- 新增 ---- + int64_t parentSuid; // 父 VST UID + char parentDbFName[TSDB_DB_FNAME_LEN]; // 父 VST 所在库全名 +} SMCreateStbReq; +``` + +序列化函数 `tSerializeSMCreateStbReq()` / `tDeserializeSMCreateStbReq()` 需追加字段。采用尾部追加策略,反序列化时若 buffer 不足则默认为 0,兼容旧版本。 + +`SVCreateStbReq` (MNode→VNode) 同样追加 `parentSuid`、`inheritDepth`,使 VNode 侧感知继承关系(用于后续 meta 查询优化)。 + +--- + +## 3. DDL 实现 + +### 3.1 CREATE VIRTUAL STABLE ... BASE ON + +#### 3.1.1 语法解析 (Parser) + +**文件**: `source/libs/parser/src/parAstCreater.c` + +新增 AST 创建函数: +```c +SNode* createCreateVTableInheritStmt(SAstCreateContext* pCxt, + SToken* pDbName, + SToken* pTableName, + SToken* pParentDbName, + SToken* pParentTableName, + SNodeList* pCols, + SNodeList* pTags, + STableOptions* pOptions); +``` + +**文件**: `include/libs/nodes/cmdnodes.h` + +新增或扩展 `SCreateVTableStmt`: +```c +typedef struct SCreateVTableStmt { + ENodeType type; // QUERY_NODE_CREATE_VIRTUAL_TABLE_STMT + char dbName[TSDB_DB_NAME_LEN]; + char tableName[TSDB_TABLE_NAME_LEN]; + bool ignoreExists; + SNodeList* pCols; + + // ---- 新增继承字段 ---- + bool hasParent; // 是否有 BASE ON + char parentDbName[TSDB_DB_NAME_LEN]; // 父 VST 数据库 + char parentTableName[TSDB_TABLE_NAME_LEN]; // 父 VST 表名 + SNodeList* pNewCols; // 子 VST 自身新增列 + SNodeList* pNewTags; // 子 VST 自身新增 Tag +} SCreateVTableStmt; +``` + +#### 3.1.2 语义校验 (Translator) + +**文件**: `source/libs/parser/src/parTranslater.c` + +新增 `translateCreateVirtualStableInherit()` 函数,在 `translateVirtualTable()` 中根据 `hasParent` 路由: + +``` +translateVirtualTable() + ├── hasParent == false → 原有逻辑 translateVirtualSuperTable() + └── hasParent == true → translateCreateVirtualStableInherit() +``` + +`translateCreateVirtualStableInherit()` 核心逻辑: + +1. **父表存在性校验**:通过 catalog 获取父表 meta,检查 `virtualStb == 1` +2. **深度校验**:父表 `inheritDepth + 1 <= 10`,否则报 `TSDB_CODE_MND_VST_INHERIT_DEPTH_EXCEED` +3. **循环检测**:沿 `parentSuid` 链回溯到根,确保不出现当前 suid +4. **列名冲突检测**:新增列名不可与父表已有列/Tag 重名 +5. **Schema 合并**:`finalCols = parentCols + newCols`,`finalTags = parentTags + newTags` +6. **colId 分配**:继承列保留父表的 colId,新增列从父表 `nextColId` 开始递增 +7. **构建 `SMCreateStbReq`**:带上 `parentSuid`、`parentDbFName`、合并后的完整 schema + +#### 3.1.3 MNode 处理 + +**文件**: `source/dnode/mnode/impl/src/mndStb.c` + +在 `mndProcessCreateStbReq()` 中增加继承处理分支: + +```c +if (createReq.parentSuid != 0) { + // 1. 获取父 SStbObj + SStbObj *pParent = mndAcquireStb(pMnode, parentFullName); + // 2. 校验父表是 virtualStb + // 3. 校验深度 + // 4. 循环检测 + // 5. 构建子 SStbObj,设置 parentSuid/parentDbUid/inheritDepth/ownColStart/ownTagStart + // 6. 权限继承:从父表复制权限到子表 + mndInheritStbPrivilege(pMnode, pTrans, pParent, pNewStb); +} +``` + +### 3.2 DROP 带继承检查 + +**文件**: `source/dnode/mnode/impl/src/mndStb.c` + +在 `mndProcessDropStbReq()` 中,DROP 前新增子表检查: + +```c +static int32_t mndCheckDropStbForChildren(SMnode *pMnode, SStbObj *pStb) { + // 遍历 SDB 中所有 SStbObj,检查是否有 parentSuid == pStb->uid + // 若找到,返回 TSDB_CODE_MND_VST_HAS_CHILDREN + SSdb *pSdb = pMnode->pSdb; + void *pIter = NULL; + while (1) { + SStbObj *pChild = NULL; + pIter = sdbFetch(pSdb, SDB_STB, pIter, (void **)&pChild); + if (pIter == NULL) break; + if (pChild->parentSuid == pStb->uid) { + sdbCancelFetch(pSdb, pIter); + sdbRelease(pSdb, pChild); + return TSDB_CODE_MND_VST_HAS_CHILDREN; + } + sdbRelease(pSdb, pChild); + } + return TSDB_CODE_SUCCESS; +} +``` + +在 `mndProcessDropStbReq()` 的 `pStb->virtualStb == 1` 分支中调用此检查。 + +### 3.3 ALTER CASCADE(父表 ADD COLUMN 级联到子孙) + +**文件**: `source/dnode/mnode/impl/src/mndStb.c` + +新增 `mndCascadeAlterAddColumn()`: + +``` +mndProcessAlterStbReq() + └── TSDB_ALTER_TABLE_ADD_COLUMN && pStb->virtualStb + └── mndCascadeAlterAddColumn(pMnode, pTrans, pDb, pStb, pAlter) + ├── 对当前 VST 执行 mndAddSuperTableColumn() + └── 遍历 SDB 找所有 parentSuid == pStb->uid 的子 VST + ├── 在子 VST schema 的 ownColStart 位置插入新列 + ├── 子 VST 的 ownColStart += ncols + ├── 递归处理子 VST 的子孙 + └── 为每个子 VST 生成 mndSetAlterStbRedoActions +``` + +**关键细节**: +- 新增列在子 VST 中的 colId 与父表一致(不重新分配) +- 列插入位置在 `ownColStart` 前(即继承区域末尾) +- 事务内所有子孙 VST 的 ALTER 操作打包为同一个 `STrans` + +**DROP COLUMN / MODIFY COLUMN**: +- `DROP COLUMN`:若 `pStb->virtualStb && hasChildren(pStb)` → 拒绝,返回 `TSDB_CODE_MND_VST_HAS_CHILDREN` +- `MODIFY COLUMN`(兼容类型变更):同 ADD COLUMN 的级联逻辑,递归修改所有子孙中对应 colId 的类型 + +--- + +## 4. 查询链路实现 (EXPAND) + +### 4.1 语法解析 + +#### 4.1.1 Lemon 语法扩展 + +**文件**: `source/libs/parser/src/taos_lemon_sql.tab.c` (由 .y 文件生成) + +在 `FROM` 子句中增加 EXPAND 修饰: + +``` +from_clause ::= FROM virtual_table_ref. +virtual_table_ref ::= full_table_name EXPAND. +virtual_table_ref ::= full_table_name EXPAND NK_LP NK_INTEGER NK_RP. +``` + +#### 4.1.2 AST 节点 + +**文件**: `include/libs/nodes/querynodes.h` + +扩展 `SVirtualTableNode`: +```c +typedef struct SVirtualTableNode { + STableNode table; + struct STableMeta* pMeta; + SVgroupsInfo* pVgroupList; + SNodeList* refTables; + + // ---- 新增 ---- + int32_t expandLevel; // -1=全部, 0=不展开, N=N层 + bool hasExpand; // 是否使用了 EXPAND 关键字 +} SVirtualTableNode; +``` + +### 4.2 Translator 处理 + +**文件**: `source/libs/parser/src/parTranslater.c` + +在 `translateVirtualSuperTable()` 中处理 EXPAND: + +```c +static int32_t translateVirtualSuperTableWithExpand(STranslateContext* pCxt, + SVirtualTableNode* pVTable) { + int32_t code = TSDB_CODE_SUCCESS; + int32_t expandLevel = pVTable->expandLevel; + + if (expandLevel == 0) { + // 不展开,走原有逻辑 + return translateVirtualSuperTable(pCxt, pVTable); + } + + // 1. 获取当前 VST 的继承树(通过 catalog 查询 ins_inherits) + SArray* pDescendants = NULL; // SArray + code = catalogGetVstDescendants(pCxt->pParseCxt->pCatalog, + pVTable->pMeta->suid, + expandLevel, + &pDescendants); + + // 2. 计算列并集(所有子孙 VST 列的 union),不含 VCT 私有列 + SNodeList* pUnionCols = NULL; + code = buildExpandedColumnList(pCxt, pVTable, pDescendants, &pUnionCols); + + // 3. 将展开信息传递给 planner + pVTable->pExpandDescendants = pDescendants; + pVTable->pExpandCols = pUnionCols; + + return code; +} +``` + +**列并集规则**: +- 所有子孙 VST 的列取并集,相同 colId 只出现一次 +- 某 VCT 不具有某列时填 NULL +- VCT 私有列(PRIVATE 定义的列)在 VST EXPAND 查询中不可见 + +### 4.3 Planner 扩展 + +**文件**: `source/libs/planner/src/planLogicCreater.c` + +#### 4.3.1 SVirtualScanLogicNode 扩展 + +**文件**: `include/libs/nodes/plannodes.h` + +```c +typedef struct SVirtualScanLogicNode { + // ... 现有字段 ... + + // ---- 新增 ---- + int32_t expandLevel; // EXPAND 层数 + SNodeList* pExpandSuids; // 展开的子孙 VST suid 列表 + SNodeList* pExpandCols; // 列并集 schema +} SVirtualScanLogicNode; +``` + +**注意**:在 `nodesCloneFuncs.c` 中添加对应 `COPY_SCALAR_FIELD(expandLevel)` 和 `CLONE_NODE_LIST_FIELD(pExpandSuids)` / `CLONE_NODE_LIST_FIELD(pExpandCols)`。 + +#### 4.3.2 Logic Plan 创建 + +在 `createVirtualSuperTableLogicNode()` 中: + +```c +if (pVTable->expandLevel != 0) { + pScanNode->expandLevel = pVTable->expandLevel; + pScanNode->pExpandSuids = pVTable->pExpandDescendants; + pScanNode->pExpandCols = pVTable->pExpandCols; +} +``` + +#### 4.3.3 SDynQueryCtrlVtbScan 扩展 + +**文件**: `include/libs/nodes/plannodes.h` + +```c +typedef struct SDynQueryCtrlVtbScan { + // ... 现有字段 ... + + // ---- 新增 ---- + int32_t expandLevel; // EXPAND 层数 + SNodeList* pExpandSuids; // 要展开的子孙 suid 列表 +} SDynQueryCtrlVtbScan; +``` + +#### 4.3.4 Physical Plan 创建 + +`SVirtualScanPhysiNode` 同步增加 `expandLevel` 和 `pExpandSuids`。在 `planPhysiCreater.c` 的 `createVirtualTableScanPhysiNode()` 中传递这些字段。 + +### 4.4 Executor 扩展 + +**文件**: `source/libs/executor/src/dynqueryctrloperator.c` + +#### 4.4.1 Child Table Map 构建 + +核心改造在 `buildVirtualSuperTableScanChildTableMap()` 中。当 `expandLevel != 0` 时: + +``` +buildVirtualSuperTableScanChildTableMap() + ├── 查询当前 VST 的 ins_virtual_child_columns → 当前 VST 的 VCT 列表 + └── if expandLevel != 0: + ├── 获取 pExpandSuids 列表中每个子孙 VST + ├── 对每个子孙 VST 查询 ins_virtual_child_columns → 子孙 VCT 列表 + ├── 合并到 childTableList / childTableMap + └── 对每个 VCT 的 colRef 信息做列映射: + ├── 子孙 VST 有而父 VST 没有的列 → 输出时填 NULL + └── 父 VST 有而子孙 VST 没有的列 → 输出时填 NULL +``` + +#### 4.4.2 列映射与 NULL 填充 + +新增 `SVtbExpandColMapping` 结构: + +```c +typedef struct SVtbExpandColMapping { + int32_t outputSlotId; // 在输出 DataBlock 中的 slot + int32_t sourceSlotId; // 在源 VCT DataBlock 中的 slot,-1 表示需填 NULL + int8_t dataType; // 列类型 + int32_t dataBytes; // 列长度 +} SVtbExpandColMapping; +``` + +在 `vtbScanNext()` 中: +- 若非 EXPAND 模式,行为不变 +- 若 EXPAND 模式,从 VCT 获取的 DataBlock 需经过列映射,缺失列填 NULL 后输出 + +#### 4.4.3 VCT 私有列过滤 + +EXPAND 查询时,构建 childTableList 的 colRef 信息时需排除 VCT 私有列。 +在 `sysTableScanUserVcCols()` 的查询结果中,VCT 私有列有 `isPrivate` 标记(或通过 colId 范围区分),EXPAND 逻辑中跳过这些列。 + +--- + +## 5. 系统表 ins_inherits + +### 5.1 定义 + +**文件**: `include/common/systable.h` + +```c +#define TSDB_INS_TABLE_INHERITS "ins_inherits" +``` + +**Schema**: + +| 列名 | 类型 | 说明 | +|------|------|------| +| parent_db | VARCHAR(64) | 父 VST 数据库名 | +| parent_stable | VARCHAR(192) | 父 VST 名 | +| parent_uid | BIGINT | 父 VST UID | +| child_db | VARCHAR(64) | 子 VST 数据库名 | +| child_stable | VARCHAR(192) | 子 VST 名 | +| child_uid | BIGINT | 子 VST UID | +| depth | INT | 子 VST 继承深度 | +| create_time | TIMESTAMP | 子 VST 创建时间 | + +### 5.2 实现 + +**文件**: `source/dnode/mnode/impl/src/mndInfoSchema.c` + +注册 `ins_inherits` 系统表 schema。数据源为遍历 SDB 中所有 `SStbObj`,筛选 `parentSuid != 0` 的记录。 + +**文件**: `source/libs/executor/src/sysscanoperator.c` + +新增 `sysTableScanVstInherits()` 函数处理 `ins_inherits` 的查询请求。 + +### 5.3 SHOW 语法 + +```sql +SHOW VSTABLE INHERITS; +-- 等价于 SELECT * FROM information_schema.ins_inherits; +``` + +在 `include/common/tmsg.h` 新增: +```c +QUERY_NODE_SHOW_VST_INHERITS_STMT, +``` + +在 Parser 中将 `SHOW VSTABLE INHERITS` 重写为对 `ins_inherits` 的 SELECT。 + +--- + +## 6. DCL 权限继承 + +### 6.1 创建时继承 + +**文件**: `source/dnode/mnode/impl/src/mndUser.c` + +新增 `mndInheritStbPrivilege()`: + +```c +int32_t mndInheritStbPrivilege(SMnode *pMnode, STrans *pTrans, + SStbObj *pParent, SStbObj *pChild) { + // 遍历所有用户,找出对 pParent->name 有权限的用户 + // 为每个用户对 pChild->name 复制相同权限 + // 将权限变更加入事务 pTrans +} +``` + +### 6.2 父表权限变更时覆盖 + +在现有的 `mndProcessGrantReq()` / `mndProcessRevokeReq()` 中,若目标是 VST 且该 VST 有子 VST,需级联更新: + +```c +if (pStb->virtualStb) { + mndCascadePrivilegeChange(pMnode, pTrans, pStb, grantType); +} +``` + +递归遍历所有 `parentSuid == pStb->uid` 的子孙 VST,对每个执行相同的权限变更。 + +--- + +## 7. Catalog 缓存 + +**文件**: `source/libs/catalog/` + +### 7.1 缓存继承信息 + +在 `STableMeta` 或辅助结构中缓存: +- `parentSuid`、`inheritDepth` +- 继承链的列映射关系 + +### 7.2 新增 Catalog API + +```c +// 获取 VST 的所有子孙(递归或按层数) +int32_t catalogGetVstDescendants(SCatalog* pCtg, uint64_t suid, + int32_t expandLevel, + SArray** ppDescendants); + +// 获取继承树的列并集 +int32_t catalogGetVstExpandedSchema(SCatalog* pCtg, uint64_t suid, + int32_t expandLevel, + SArray** ppColumns); +``` + +实现方式:查询 `ins_inherits` 系统表获取子孙列表,再逐个获取 schema 并合并。 + +--- + +## 8. 错误码 + +**文件**: `include/util/taoserror.h` + +```c +#define TSDB_CODE_MND_VST_HAS_CHILDREN TAOS_DEF_ERROR_CODE(0, 0x03E0) // "VST has child virtual stables" +#define TSDB_CODE_MND_VST_INHERIT_DEPTH_EXCEED TAOS_DEF_ERROR_CODE(0, 0x03E1) // "VST inheritance depth exceeds limit" +#define TSDB_CODE_MND_VST_CIRCULAR_INHERIT TAOS_DEF_ERROR_CODE(0, 0x03E2) // "Circular VST inheritance detected" +#define TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL TAOS_DEF_ERROR_CODE(0, 0x03E3) // "Parent table is not a virtual stable" +#define TSDB_CODE_MND_VST_COL_NAME_CONFLICT TAOS_DEF_ERROR_CODE(0, 0x03E4) // "Column name conflicts with parent VST" +#define TSDB_CODE_PAR_VST_EXPAND_NO_INHERIT TAOS_DEF_ERROR_CODE(0, 0x2680) // "EXPAND used on non-inherited VST" +``` + +--- + +## 9. 序列化兼容性 + +所有新增消息字段采用尾部追加策略: + +| 组件 | 策略 | +|------|------| +| `SStbObj` SDB 编解码 | `sver < STB_VER_SUPPORT_INHERIT` 时新字段默认 0 | +| `SMCreateStbReq` 网络协议 | 反序列化时 buffer 不足则默认 0 | +| `SVCreateStbReq` 编解码 | `tEncodeSVCreateStbReq` 追加字段,解码端兼容缺失 | +| `nodesCloneFuncs.c` | 新增字段对应 `COPY_SCALAR_FIELD` / `CLONE_NODE_LIST_FIELD` | +| `nodesCodeFuncs.c` | 新增字段的 JSON 序列化/反序列化 | + +--- + +## 10. 核心流程总结 + +### 10.1 CREATE VIRTUAL STABLE child BASE ON parent + +``` +Client + │ SQL: CREATE VIRTUAL STABLE child BASE ON parent (extra INT) TAGS (loc NCHAR(64)) + ▼ +Parser (parAstCreater.c) + │ 创建 SCreateVTableStmt { hasParent=true, parentTableName="parent", pNewCols=[extra], pNewTags=[loc] } + ▼ +Translator (parTranslater.c) + │ translateCreateVirtualStableInherit() + │ ├── catalog 获取 parent meta + │ ├── 校验:virtualStb、depth <= 10、无循环、无列名冲突 + │ ├── 合并 schema:finalCols = parent.cols + [extra], finalTags = parent.tags + [loc] + │ └── 构建 SMCreateStbReq { parentSuid=parent.uid, ... } + ▼ +MNode (mndStb.c) + │ mndProcessCreateStbReq() + │ ├── 获取父 SStbObj,再次校验 + │ ├── 构建子 SStbObj { parentSuid, parentDbUid, inheritDepth=parent.depth+1, ownColStart, ownTagStart } + │ ├── 权限继承 mndInheritStbPrivilege() + │ └── 事务提交(写 SDB + 下发 VNode) + ▼ +VNode + │ metaCreateSTable():存储带继承字段的 STB 元数据 + ▼ +完成 +``` + +### 10.2 SELECT ... FROM vst EXPAND(-1) + +``` +Client + │ SQL: SELECT * FROM vst_root EXPAND(-1) + ▼ +Parser + │ SVirtualTableNode { expandLevel=-1, hasExpand=true } + ▼ +Translator + │ translateVirtualSuperTableWithExpand() + │ ├── catalogGetVstDescendants(suid, -1) → [vst_mid, vst_mid2] + │ ├── buildExpandedColumnList() → {ts, val, extra, temp} (列并集) + │ └── 设置 pExpandDescendants、pExpandCols + ▼ +Planner + │ createVirtualSuperTableLogicNode() + │ ├── SVirtualScanLogicNode { expandLevel=-1, pExpandSuids=[mid_uid, mid2_uid] } + │ └── SDynQueryCtrlVtbScan { expandLevel=-1, pExpandSuids=[...] } + ▼ +Executor + │ buildVirtualSuperTableScanChildTableMap() + │ ├── 查询 vst_root 的 VCT → vct_r1, vct_r2 + │ ├── 查询 vst_mid 的 VCT → vct_m1 + │ ├── 查询 vst_mid2 的 VCT → vct_m2 + │ └── 合并 childTableList = [vct_r1, vct_r2, vct_m1, vct_m2] + │ + │ vtbScanNext() 循环 + │ ├── 逐个 VCT 获取数据 + │ ├── 经列映射(缺失列填 NULL) + │ └── 输出 DataBlock {ts, val, extra, temp} + ▼ +结果返回 +``` + +--- + +## 11. 涉及文件清单 + +| 文件 | 改动类型 | 说明 | +|------|----------|------| +| `include/common/tmsg.h` | 修改 | `SMCreateStbReq`/`SVCreateStbReq` 追加字段,新增 `QUERY_NODE_SHOW_VST_INHERITS_STMT` | +| `include/common/systable.h` | 修改 | 新增 `TSDB_INS_TABLE_INHERITS` | +| `include/common/taoserror.h` | 修改 | 新增继承相关错误码 | +| `include/libs/nodes/cmdnodes.h` | 修改 | `SCreateVTableStmt` 扩展继承字段 | +| `include/libs/nodes/querynodes.h` | 修改 | `SVirtualTableNode` 增加 `expandLevel` | +| `include/libs/nodes/plannodes.h` | 修改 | `SVirtualScanLogicNode`/`SDynQueryCtrlVtbScan`/`SVirtualScanPhysiNode` 扩展 | +| `source/dnode/mnode/impl/inc/mndDef.h` | 修改 | `SStbObj` 新增继承字段 | +| `source/dnode/mnode/impl/src/mndStb.c` | 修改 | 编解码、CREATE/DROP/ALTER 继承逻辑 | +| `source/dnode/mnode/impl/src/mndUser.c` | 修改 | 权限继承逻辑 | +| `source/dnode/mnode/impl/src/mndInfoSchema.c` | 修改 | 注册 `ins_inherits` | +| `source/libs/parser/src/parAstCreater.c` | 修改 | `BASE ON` / `EXPAND` AST 创建 | +| `source/libs/parser/src/parTranslater.c` | 修改 | 继承校验、EXPAND schema 合并 | +| `source/libs/planner/src/planLogicCreater.c` | 修改 | EXPAND 逻辑计划 | +| `source/libs/planner/src/planPhysiCreater.c` | 修改 | EXPAND 物理计划 | +| `source/libs/nodes/src/nodesCloneFuncs.c` | 修改 | 新增字段 clone | +| `source/libs/nodes/src/nodesCodeFuncs.c` | 修改 | 新增字段序列化 | +| `source/libs/executor/src/dynqueryctrloperator.c` | 修改 | EXPAND childTableMap 构建、列映射 | +| `source/libs/executor/src/sysscanoperator.c` | 修改 | `ins_inherits` 查询实现 | +| `source/libs/catalog/` | 修改 | 新增 `catalogGetVstDescendants()` 等 API | diff --git a/17-vst-inheritance-fs.md b/17-vst-inheritance-fs.md new file mode 100644 index 000000000000..be92d858b470 --- /dev/null +++ b/17-vst-inheritance-fs.md @@ -0,0 +1,668 @@ +# FS: 虚拟超级表继承 (VST Inheritance) + +## 1. 修订记录 + +| 日期 | 版本 | 负责人 | 主要修改内容 | +|------|------|--------|-------------| +| 2026-05-07 | 0.1 | 邓怡豪 | 初稿 | +| 2026-05-08 | 0.2 | 邓怡豪 | 重写:多继承、叶子专有VCT、去除EXPAND、统一CREATE STABLE语法 | +| 2026-05-08 | 0.3 | 邓怡豪 | 去除PRIVATE关键字 | +| 2026-05-08 | 0.4 | 邓怡豪 | 补充:一对多继承、冲突提示含列名及来源、DROP BASE ON 级联细化、需求清单对照 | + +## 2. 背景 + +TDengine 虚拟超级表(VST)当前仅支持单层结构。为支持更复杂的数据建模场景(如多维度设备分类、组合指标体系),需要为 VST 引入**多继承**机制: + +- 子 VST 可继承多个父 VST 的列和 Tag 定义,形成 schema 组合(宽表) +- 只有**叶子 VST**(无子 VST 的 VST)可以拥有 VCT(数据) +- 查询非叶 VST 时,自动下推到所有叶子后代,投影该 VST 的列子集 + +## 3. 定义 + +| 术语 | 定义 | +|------|------| +| VST(虚拟超级表) | 带有 `VIRTUAL 1` 标记的超级表,通过 VCT 的 colRef 从源表获取数据 | +| VCT(虚拟子表) | 属于某个**叶子 VST** 的子表,通过列引用(colRef)映射到源表的具体列 | +| 父 VST | 被其他 VST 通过 `BASE ON` 引用的 VST,自身不能拥有 VCT | +| 叶子 VST | 没有子 VST 的 VST,**唯一**可以拥有 VCT 的层级 | +| 继承 | 通过 `BASE ON` 声明的父子关系,子 VST 自动包含所有父 VST 的全部列和 Tag | +| 源表 | 被虚拟表引用的物理表,是 VCT 数据与标签的原始来源 | + +**数据模型**: + +``` +base_device (cols: ts, status INT) TAGS (region INT) ← 非叶,无VCT +base_metric (cols: ts, value FLOAT) TAGS (unit BINARY(8)) ← 非叶,无VCT + +sensor_vst BASE ON base_device, base_metric ← 叶子,有VCT + schema: (ts, status, value, accuracy INT) + TAGS: (region, unit, sensor_id INT) + +actuator_vst BASE ON base_device ← 叶子,有VCT + schema: (ts, status, cmd INT) + TAGS: (region, actuator_type INT) +``` + +- 子 VST 自动继承所有父 VST 的全部列和 Tag +- 子 VST 可新增自有列和自有 Tag(也可不新增) +- **只有叶子 VST 可以拥有 VCT**,父 VST 不能有 VCT +- 已有 VCT 的 VST 不能被后续继承(不能成为父 VST) +- **一个父 VST 可被多个子 VST 同时继承**(一对多关系),不限制继承扇出 + +**新增系统表** `ins_vstable_inherits`: + +| 字段 | 含义 | 类型 | +|------|------|------| +| db_name | 所在数据库名 | VARCHAR | +| parent_stable | 父 VST 名称 | VARCHAR | +| parent_uid | 父 VST UID | BIGINT | +| child_stable | 子 VST 名称 | VARCHAR | +| child_uid | 子 VST UID | BIGINT | +| create_time | 创建时间 | TIMESTAMP | + +> 注:多继承时一个子 VST 会对应多条记录(每个父一条)。 + +## 4. 行为说明 + +### 4.1 DDL:创建继承 VST + +**语法**: + +```sql +-- 有新增列和 Tag +CREATE STABLE [db_name.]childVstName ( + colName dataType [, colName dataType ...] +) TAGS ( + tagName dataType [, tagName dataType ...] +) BASE ON [db_name.]parentVst1 [, [db_name.]parentVst2 ...] VIRTUAL 1; + +-- 有新增 Tag,无新增普通列(仍需声明 TS) +CREATE STABLE [db_name.]childVstName ( + ts TIMESTAMP +) TAGS (tagName dataType [, ...]) + BASE ON [db_name.]parentVst1 [, ...] VIRTUAL 1; + +-- 无新增列也无新增 Tag(仍需声明 TS) +CREATE STABLE [db_name.]childVstName ( + ts TIMESTAMP +) BASE ON [db_name.]parentVst1 [, ...] VIRTUAL 1; +``` + +行为:创建一个继承自一个或多个父 VST 的子 VST。子 VST 自动继承所有父 VST 的全部列和 Tag,括号内仅声明新增列/Tag。 + +**规则**: + +| 规则 | 说明 | +|------|------| +| 父表必须是 VST | `BASE ON` 目标必须是 `virtualStb=1` | +| 父表不能有 VCT | 已拥有 VCT 的 VST 不能被继承 | +| 全量继承 | 子 VST **自动继承**所有父 VST 的全部列和 Tag,SQL 中仅声明新增列 | +| 列顺序 | 结果 schema = TS列 + 父1列(不含TS) + 父2列(不含TS) + ... + 自有列(不含TS)(各父按 BASE ON 声明顺序) | +| Tag 顺序 | 结果 Tags = 父1Tags + 父2Tags + ... + 自有Tags | +| colId 独立 | 子 VST 有**独立的 colId 命名空间**,与父表 colId 无关 | +| 列名不可冲突 | 所有父 VST 之间、以及新增列与所有父列之间,列名不可重复。冲突时**在解析期报错**,错误消息须包含冲突列名及来源父 VST 名称(例如:`Column 'status' conflicts between parent 'base_device' and parent 'base_sensor'`) | +| Tag名不可冲突 | 所有父 VST 之间、以及新增 Tag 与所有父 Tag 之间,Tag 名不可重复。冲突时同样报错并提示冲突 Tag 名及来源 | +| 最大父表数 | 最多继承 **10** 个父 VST | +| 同一 DB | 父子 VST 必须在同一数据库内 | +| 禁止循环 | 创建时通过 DAG 遍历检测环路 | +| 可无新增列 | 允许不声明新增普通列,但**必须声明 TS 列**(`ts TIMESTAMP`) | +| 权限 | 使用子 VST 自身所在 DB 的权限体系 | + +### 4.2 DDL:创建 VCT + +**语法**: + +```sql +CREATE TABLE [IF NOT EXISTS] [db_name.]vctName USING [db_name.]vstName (tag_cols) TAGS (tagValue [, ...]) + [(colName FROM srcDb.srcTable.srcCol [, ...])] +``` + +行为:在 vstName 下创建一个虚拟子表 vctName。 + +**约束**:vstName 必须是**叶子 VST**(没有子 VST)。若 vstName 已有子 VST,则拒绝创建 VCT。 + +**列引用与 Tag 规则**: + +| 规则 | 说明 | +|------|------| +| 完整 schema | VCT 的 colRef 可引用 VST 完整 schema 中的所有列,包括**从父 VST 继承的列**和**自有列** | +| Tag 赋值 | VCT 的 TAGS 值列表按 VST 完整 Tag schema 的顺序赋值,包括**从父 VST 继承的 Tag**和**自有 Tag** | +| 继承列引用 | 继承自父 VST 的列,同样通过 `colName FROM srcDb.srcTable.srcCol` 语法建立 colRef 映射 | + +### 4.3 DDL:DROP + +| 场景 | 行为 | +|------|------| +| DROP 有子 VST 的父 VST | **拒绝**(返回 TSDB_CODE_MND_VST_HAS_CHILDREN) | +| DROP 叶子 VST(有 VCT) | 先删 VCT 再删 VST,或直接删除 | +| DROP 叶子 VST(无 VCT) | 正常删除 | + +### 4.4 DDL:ALTER 列级联 + +| 操作 | 行为 | +|------|------| +| 父 VST `ADD COLUMN` | **自动级联**到所有子孙 VST(新列追加到对应父的继承区域末尾) | +| 父 VST `DROP COLUMN` | **自动级联**删除所有子孙 VST 中对应的继承列 | +| 子 VST `ADD COLUMN` | 不影响父 VST,仅子 VST 及其子孙 | + +### 4.5 DDL:ALTER 继承关系 + +#### 4.5.1 添加继承 + +**语法**: + +```sql +ALTER STABLE [db_name.]vstName ADD BASE ON [db_name.]parentVst1 [, [db_name.]parentVst2 ...]; +``` + +行为:为已有 VST 添加父继承关系。 + +**规则**: + +| 规则 | 说明 | +|------|------| +| 目标 VST 必须是 VST | `virtualStb=1` | +| 父表必须是 VST 且无 VCT | 父 VST 不能拥有 VCT | +| 列名不可冲突 | 新增父的列/Tag 不能与 VST 已有列/Tag 重名,冲突时**在解析期报错**并提示冲突列名及来源 | +| 最大父表数 | 添加后总父表数不超过 10 | +| 同一 DB | 父子必须在同一数据库 | +| 禁止循环 | DAG 环路检测 | +| 已有 VCT 影响 | 若该 VST 已有 VCT,需要考虑 VCT 的 colRef 补全(新继承列无映射,查询返回 NULL) | + +#### 4.5.2 取消继承 + +**语法**: + +```sql +ALTER STABLE [db_name.]vstName DROP BASE ON [db_name.]parentVst1 [, [db_name.]parentVst2 ...]; +``` + +行为:解除与指定父 VST 的继承关系,移除来自该父的继承列和 Tag。 + +**约束**: + +| 约束 | 说明 | +|------|------| +| 不能完全取消 | 解除后 VST 的可用列(含 TS)至少 **2 列** | +| Tag 最少 1 个 | 解除后 VST 至少保留 **1 个 Tag** | +| 已有 VCT 影响 | 若该 VST 已有 VCT,被移除列的 colRef **级联删除**——对应列的 colRef 映射从所有 VCT 中移除,后续查询这些列返回 NULL | + +**VCT 级联行为详细说明**: + +当 `DROP BASE ON parentX` 移除了继承列 C1、C2 和继承 Tag T1 时: +1. VST schema 中移除 C1、C2、T1 +2. 该 VST 下所有已有 VCT 中,C1、C2 对应的 colRef 映射被删除 +3. T1 对应的 Tag 值被删除 +4. 后续对这些 VCT 的查询不再包含 C1、C2、T1(schema 已变更) + +### 4.6 DQL:查询 VST + +#### 4.6.1 查询叶子 VST + +```sql +SELECT ... FROM [db_name.]leafVstName [WHERE ...] [ORDER BY ...]; +``` + +行为:正常的虚拟超级表扫描,schema 为该叶子 VST 的完整 schema(所有继承列 + 自有列)。可使用从父 VST 继承的列和 Tag 进行 SELECT、WHERE、ORDER BY 等操作。与普通 VST 查询行为一致。 + +#### 4.6.2 查询非叶 VST(隐式下推) + +```sql +SELECT ... FROM [db_name.]parentVstName [WHERE ...] [ORDER BY ...]; +``` + +行为:自动下推到所有以 parentVstName 为祖先的**叶子 VST**,扫描其 VCT 数据,**只投影** parentVstName 自身 schema 中的列。多个叶子 VST 的结果行合并返回。 + +**规则**: + +| 规则 | 说明 | +|------|------| +| 投影裁剪 | 结果 schema = 被查询 VST 自身的列和 Tag(不包含子孙的扩展列) | +| 列引用限制 | SELECT/WHERE/ORDER BY 只能引用被查询 VST schema 中的列 | +| 无需特殊语法 | 查询非叶 VST 自动触发下推,无需 EXPAND 等关键字 | +| 保证列完整 | 所有叶子后代一定包含祖先的全部列(继承保证),无需 NULL 填充 | + +#### 4.6.3 列可见性规则 + +**核心原则**:查询结果 schema 始终 = 被查询 VST 自身的 schema。叶子 VST 的扩展列在查询祖先 VST 时不可见。 + +### 4.7 DQL:查询继承关系 + +**语法**: + +```sql +SHOW VSTABLE INHERITS; +``` + +行为:查询所有 VST 之间的继承关系。等价于 `SELECT * FROM information_schema.ins_vstable_inherits`。 + +返回列:db_name, parent_stable, parent_uid, child_stable, child_uid, create_time。 + +### 4.8 DQL:SHOW CREATE STABLE + +**语法**: + +```sql +SHOW CREATE STABLE [db_name.]vstName; +``` + +行为:显示 vstName 的建表语句。 +- 无继承 VST → 输出标准 `CREATE STABLE ... VIRTUAL 1` +- 有继承 VST → 输出 `CREATE STABLE ... BASE ON parent1, parent2 VIRTUAL 1` + +### 4.9 DCL:权限 + +| 场景 | 行为 | +|------|------| +| 创建子 VST | 使用子 VST 所在 DB 的标准权限检查 | +| 查询叶子 VST | 需要对该 VST 本身的 SELECT 权限 | +| 查询非叶 VST(下推) | 只需要对 FROM 子句中的 VST 的 SELECT 权限即可 | + +## 5. 性能 + +- 叶子 VST 查询与普通 VST 查询性能一致 +- 非叶 VST 查询性能与叶子后代数量成线性关系(每个叶子产生一组 VCT 扫描) +- 投影裁剪在执行器层完成,只选取祖先列对应的 slot,无额外 I/O 开销 +- `SHOW VSTABLE INHERITS` 查询系统表,性能取决于继承关系数量(通常很小) + +## 6. 安全 + +- 权限管控:非叶 VST 查询下推仅需要对 FROM 子句中 VST 的 SELECT 权限,不需要对叶子 VST 的额外授权 +- 系统表安全:`ins_vstable_inherits` 仅支持查询操作,继承关系元数据不可被篡改 + +## 7. 兼容性 + +- 无继承的 VST 查询行为完全向后兼容 +- 现有 VST(无 BASE ON)不受影响 +- 已有 VCT 的 VST 继续正常工作,只是不能再被其他 VST 继承 + +## 8. 运维 + +1. 删除父 VST 前需先删除所有子 VST(系统会拒绝直接删除有子的 VST) +2. 父 VST `ADD COLUMN` 会自动级联到所有子孙,运维人员需注意变更影响范围 +3. 通过 `SHOW VSTABLE INHERITS` 可随时查看继承关系 +4. 单个 VST 最多继承 10 个父 VST + +## 9. 使用场景 + +**场景 1:多维度设备建模(多继承)** + +工厂设备同时具备"设备通用属性"和"电力指标属性"两个维度。通过多继承将两个维度组合: + +```sql +CREATE STABLE base_device (ts TIMESTAMP, status INT) TAGS (region INT) VIRTUAL 1; +CREATE STABLE base_power (ts TIMESTAMP, voltage FLOAT, current FLOAT) TAGS (phase INT) VIRTUAL 1; + +-- 叶子 VST 继承两个维度 +CREATE STABLE transformer (ts TIMESTAMP, capacity INT) TAGS (model BINARY(16)) + BASE ON base_device, base_power VIRTUAL 1; +-- transformer schema: (ts, status, voltage, current, capacity) TAGS(region, phase, model) +``` + +查询 `base_device` → 自动下推到 `transformer` 等所有叶子后代,只投影 `(ts, status)` 列。 + +**场景 2:纯 schema 复用(无新增列)** + +多个叶子 VST 共享同一个父 VST 的 schema,不添加新列: + +```sql +CREATE STABLE common_metrics (ts TIMESTAMP, cpu FLOAT, mem FLOAT) TAGS (host BINARY(32)) VIRTUAL 1; + +CREATE STABLE app_server_metrics (ts TIMESTAMP) TAGS (app_name BINARY(32)) + BASE ON common_metrics VIRTUAL 1; + +CREATE STABLE db_server_metrics (ts TIMESTAMP) TAGS (db_type BINARY(16)) + BASE ON common_metrics VIRTUAL 1; +``` + +**场景 3:动态添加继承** + +已有 VST 后期需要扩展能力: + +```sql +-- 初始创建 +CREATE STABLE my_sensor (ts TIMESTAMP, temp FLOAT) TAGS (loc INT) VIRTUAL 1; + +-- 后期增加继承 +ALTER STABLE my_sensor ADD BASE ON base_device; +-- my_sensor schema 扩展为: (ts, temp, status) TAGS(loc, region) +``` + +## 10. 约束和限制 + +| 约束 | 说明 | +|------|------| +| 最大父 VST 数量 | 10 | +| 循环继承 | 禁止,创建时通过 DAG 遍历检测 | +| DROP 父 VST | 有子时拒绝 | +| 跨 DB 继承 | 不支持,父子 VST 必须在同一 DB | +| 父 VST 有 VCT | 已有 VCT 的 VST 不能被继承 | +| 叶子 VST 才有 VCT | 有子 VST 的 VST 不能创建 VCT | +| 多父列名冲突 | 所有父之间列名/Tag名冲突在解析期报错,错误消息包含冲突列名及来源父 VST | +| 一对多继承 | 一个父 VST 可被多个子 VST 同时继承,不限制继承扇出 | +| 取消继承最少保留 | 取消后至少保留 2 列(含 TS)+ 1 个 Tag | + +## 11. 常见错误和排查 + +| 错误码 | 含义 | 排查方法 | +|--------|------|----------| +| `TSDB_CODE_MND_VST_HAS_CHILDREN` | DROP/ALTER 有子 VST 的父表 | 先通过 SHOW VSTABLE INHERITS 查看子代,逐级删除 | +| `TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL` | BASE ON 目标不是虚拟表 | 确认父表是 VIRTUAL 1 | +| `TSDB_CODE_MND_VST_COL_NAME_CONFLICT` | 列名/Tag名与父表冲突或多父之间冲突。消息格式:`Column '' conflicts between '' and ''` | 根据错误消息中指出的冲突列名及来源修改列名 | +| `TSDB_CODE_MND_VST_CIRCULAR_INHERIT` | 检测到循环继承 | 检查 BASE ON 目标是否形成环 | +| `TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED` | 父 VST 数量超过 10 | 减少继承的父 VST 数量 | +| `TSDB_CODE_MND_VST_PARENT_HAS_VCT` | 父 VST 已有 VCT,不能被继承 | 先删除父 VST 的 VCT | +| `TSDB_CODE_MND_VST_NOT_LEAF` | 非叶子 VST 不能创建 VCT | 确认目标 VST 没有子 VST | +| `TSDB_CODE_MND_VST_DROP_BASE_MIN_COLS` | 取消继承后列/Tag不满足最小要求 | 保留更多继承关系或先添加自有列 | + +## 12. 可观测性 + +- `SHOW VSTABLE INHERITS`:查看继承关系 +- `DESCRIBE vst_name`:查看完整 schema(含继承列) +- `SHOW CREATE STABLE vst_name`:查看建表语句(含 BASE ON) + +## 13. 安装和卸载 + +无特殊安装步骤,随 TDengine 版本发布。 + +## 14. 文档 + +- 需在官网文档新增"虚拟超级表继承"章节,涵盖 DDL/DQL 语法、多继承用法 +- 需在系统表文档中补充 `ins_vstable_inherits` 的 schema 定义与查询示例 +- 需在 SHOW 命令文档中补充 `SHOW VSTABLE INHERITS` 说明 + +## 15. 参考文档 + +无 + +## 16. 附录:SQL 示例 + +> 以下示例基于 §3 数据模型。 + +### 16.1 DDL 示例:创建继承关系 + +```sql +CREATE DATABASE demo VGROUPS 2; +USE demo; + +-- 创建源数据表 +CREATE STABLE src_stb (ts TIMESTAMP, c1 INT, c2 FLOAT, c3 INT) TAGS (region INT); + +CREATE TABLE src_s1 USING src_stb TAGS(1); +INSERT INTO src_s1 VALUES ('2023-01-01 00:00:01', 10, 1.1, 100); +INSERT INTO src_s1 VALUES ('2023-01-01 00:00:02', 11, 1.2, 101); + +CREATE TABLE src_s2 USING src_stb TAGS(2); +INSERT INTO src_s2 VALUES ('2023-01-01 00:00:03', 20, 2.1, 200); +INSERT INTO src_s2 VALUES ('2023-01-01 00:00:04', 21, 2.2, 201); + +CREATE TABLE src_a1 USING src_stb TAGS(3); +INSERT INTO src_a1 VALUES ('2023-01-01 00:00:05', 30, 3.1, 300); +INSERT INTO src_a1 VALUES ('2023-01-01 00:00:06', 31, 3.2, 301); + +-- 创建父 VST(无 VCT,纯 schema 定义) +CREATE STABLE base_device (ts TIMESTAMP, status INT) TAGS (region INT) VIRTUAL 1; +CREATE STABLE base_metric (ts TIMESTAMP, value FLOAT) TAGS (unit BINARY(8)) VIRTUAL 1; + +-- 创建叶子 VST(多继承,自有列 accuracy) +CREATE STABLE sensor_vst (ts TIMESTAMP, accuracy INT) TAGS (sensor_id INT) + BASE ON base_device, base_metric VIRTUAL 1; +-- sensor_vst 完整 schema: (ts, status, value, accuracy) TAGS(region, unit, sensor_id) + +-- 创建叶子 VST(单继承,自有列 cmd) +CREATE STABLE actuator_vst (ts TIMESTAMP, cmd INT) TAGS (actuator_type INT) + BASE ON base_device VIRTUAL 1; +-- actuator_vst 完整 schema: (ts, status, cmd) TAGS(region, actuator_type) + +-- 在叶子 VST 下创建 VCT(colRef 覆盖继承列 + 自有列) +CREATE TABLE vct_s1 USING sensor_vst TAGS(1, 'celsius', 101) + (status FROM demo.src_s1.c1, value FROM demo.src_s1.c2, accuracy FROM demo.src_s1.c3); +CREATE TABLE vct_s2 USING sensor_vst TAGS(2, 'celsius', 102) + (status FROM demo.src_s2.c1, value FROM demo.src_s2.c2, accuracy FROM demo.src_s2.c3); + +CREATE TABLE vct_a1 USING actuator_vst TAGS(3, 1) + (status FROM demo.src_a1.c1, cmd FROM demo.src_a1.c3); +``` + +### 16.2 DQL 示例:查询叶子 VST + +```sql +-- 查询 sensor_vst(叶子),返回完整宽表 schema +SELECT * FROM sensor_vst ORDER BY ts; +``` + +结果(schema = ts, status, value, accuracy): + +``` +ts | status | value | accuracy +----------------------|--------|-------|-------- +2023-01-01 00:00:01 | 10 | 1.1 | 100 ← vct_s1 +2023-01-01 00:00:02 | 11 | 1.2 | 101 ← vct_s1 +2023-01-01 00:00:03 | 20 | 2.1 | 200 ← vct_s2 +2023-01-01 00:00:04 | 21 | 2.2 | 201 ← vct_s2 +``` + +```sql +-- 查询 actuator_vst(叶子),返回完整 schema +SELECT * FROM actuator_vst ORDER BY ts; +``` + +结果(schema = ts, status, cmd): + +``` +ts | status | cmd +----------------------|--------|----- +2023-01-01 00:00:05 | 30 | 300 ← vct_a1 +2023-01-01 00:00:06 | 31 | 301 ← vct_a1 +``` + +### 16.3 DQL 示例:查询非叶 VST(隐式下推) + +```sql +-- 查询 base_device → 自动下推到 sensor_vst + actuator_vst 的所有 VCT +-- 只投影 base_device 的 schema (ts, status) +SELECT * FROM base_device ORDER BY ts; +``` + +结果: + +``` +ts | status +----------------------|------- +2023-01-01 00:00:01 | 10 ← vct_s1 +2023-01-01 00:00:02 | 11 ← vct_s1 +2023-01-01 00:00:03 | 20 ← vct_s2 +2023-01-01 00:00:04 | 21 ← vct_s2 +2023-01-01 00:00:05 | 30 ← vct_a1 +2023-01-01 00:00:06 | 31 ← vct_a1 +``` + +共 6 行(3 个 VCT 合并,只投影 base_device 的列,accuracy/value/cmd 不可见)。 + +```sql +-- 查询 base_metric → 只下推到 sensor_vst(actuator_vst 不继承 base_metric) +-- 只投影 base_metric 的 schema (ts, value) +SELECT * FROM base_metric ORDER BY ts; +``` + +结果: + +``` +ts | value +----------------------|------ +2023-01-01 00:00:01 | 1.1 ← vct_s1 +2023-01-01 00:00:02 | 1.2 ← vct_s1 +2023-01-01 00:00:03 | 2.1 ← vct_s2 +2023-01-01 00:00:04 | 2.2 ← vct_s2 +``` + +共 4 行(actuator_vst 不继承 base_metric,不参与)。 + +### 16.4 DQL 示例:Tag 查询 + +```sql +-- 通过 base_device 查 Tag(只有 region) +SELECT tbname, region FROM base_device ORDER BY tbname; +``` + +结果: + +``` +tbname | region +--------|------- +vct_a1 | 3 +vct_s1 | 1 +vct_s2 | 2 +``` + +```sql +-- 通过 sensor_vst 查 Tag(继承 region + unit,自有 sensor_id) +SELECT tbname, region, unit, sensor_id FROM sensor_vst ORDER BY tbname; +``` + +结果: + +``` +tbname | region | unit | sensor_id +--------|--------|---------|---------- +vct_s1 | 1 | celsius | 101 +vct_s2 | 2 | celsius | 102 +``` + +### 16.5 DQL 示例:聚合 + +```sql +-- base_device 下推聚合 +SELECT COUNT(*) FROM base_device; +-- 结果:6 + +SELECT SUM(status) FROM base_device; +-- 结果:10 + 11 + 20 + 21 + 30 + 31 = 123 + +-- base_metric 下推聚合 +SELECT AVG(value) FROM base_metric; +-- 结果:(1.1 + 1.2 + 2.1 + 2.2) / 4 = 1.65 + +-- GROUP BY tag(通过非叶 VST) +SELECT region, COUNT(*) AS cnt FROM base_device GROUP BY region ORDER BY region; +``` + +结果: + +``` +region | cnt +-------|---- +1 | 2 ← vct_s1 +2 | 2 ← vct_s2 +3 | 2 ← vct_a1 +``` + +### 16.6 DDL 示例:ALTER 继承关系 + +```sql +-- 已有 VST 添加继承 +CREATE STABLE my_sensor (ts TIMESTAMP, temp FLOAT) TAGS (loc INT) VIRTUAL 1; +ALTER STABLE my_sensor ADD BASE ON base_device; +-- my_sensor schema 扩展为: (ts, temp, status) TAGS(loc, region) + +-- 取消继承(需保留 ≥2列 + ≥1 Tag) +ALTER STABLE my_sensor DROP BASE ON base_device; +-- my_sensor schema 恢复为: (ts, temp) TAGS(loc) +``` + +### 16.7 DQL 示例:SHOW 语句 + +```sql +-- 查看继承关系 +SHOW VSTABLE INHERITS; +``` + +结果: + +``` +db_name | parent_stable | parent_uid | child_stable | child_uid | create_time +--------|---------------|------------|--------------|-----------|------------------- +demo | base_device | 100001 | sensor_vst | 200001 | 2023-01-01 ... +demo | base_metric | 100002 | sensor_vst | 200001 | 2023-01-01 ... +demo | base_device | 100001 | actuator_vst | 200002 | 2023-01-01 ... +``` + +```sql +-- 查看建表语句 +SHOW CREATE STABLE sensor_vst; +``` + +结果: + +``` +Stable | Create Stable +----------------|------------------------------------------------------ +sensor_vst | CREATE STABLE `sensor_vst` (`ts` TIMESTAMP, `accuracy` INT) + | TAGS (`sensor_id` INT) + | BASE ON `base_device`, `base_metric` VIRTUAL 1 +``` + +```sql +-- 查看完整 schema(含继承列) +DESCRIBE sensor_vst; +``` + +结果: + +``` +Field | Type | Length | Note +----------|-----------|--------|------------------ +ts | TIMESTAMP | 8 | +status | INT | 4 | inherited from base_device +value | FLOAT | 4 | inherited from base_metric +accuracy | INT | 4 | +region | INT | 4 | TAG, inherited from base_device +unit | BINARY | 8 | TAG, inherited from base_metric +sensor_id | INT | 4 | TAG +``` + +### 16.8 DQL 示例:错误用法 + +```sql +-- ❌ 引用不在被查询 VST schema 中的列 +SELECT accuracy FROM base_device; +-- 错误:accuracy 不在 base_device schema 中 + +-- ❌ 在非叶 VST 下创建 VCT +CREATE TABLE vct_bad USING base_device TAGS(99) + (status FROM demo.src_s1.c1); +-- 错误:TSDB_CODE_MND_VST_NOT_LEAF(base_device 有子 VST) + +-- ❌ 继承已有 VCT 的 VST +CREATE STABLE child_of_sensor (ts TIMESTAMP) TAGS (extra_tag INT) + BASE ON sensor_vst VIRTUAL 1; +-- 错误:TSDB_CODE_MND_VST_PARENT_HAS_VCT(sensor_vst 已有 VCT) + +-- ❌ 多父列名冲突(假设两个父都有同名非 TS 列) +CREATE STABLE base_x (ts TIMESTAMP, dup_col INT) TAGS (tx INT) VIRTUAL 1; +CREATE STABLE base_y (ts TIMESTAMP, dup_col INT) TAGS (ty INT) VIRTUAL 1; +CREATE STABLE bad_vst (ts TIMESTAMP) BASE ON base_x, base_y VIRTUAL 1; +-- 错误:TSDB_CODE_MND_VST_COL_NAME_CONFLICT +-- 消息:Column 'dup_col' conflicts between 'base_x' and 'base_y' +``` + +## 17. 需求对照清单 + +> 以下列出本期全部需求点及其在文档中的对应位置,确保无遗漏。 + +| # | 需求 | 覆盖章节 | 状态 | +|---|------|---------|------| +| 1 | 支持多继承:子 VST 同时继承多个父 VST | §2 背景、§4.1 规则(最大父表数 10、DAG) | ✅ | +| 2 | 列定义可选:允许不定义新增列,也允许定义额外列 | §4.1 规则(可无新增列,但须声明 TS) | ✅ | +| 3 | 列名冲突检测:解析期报错,提示冲突列名及来源 | §4.1 规则(列名/Tag名不可冲突,含错误消息格式)、§11 错误码 | ✅ | +| 4 | 支持被多个 VST 继承:一对多关系 | §3 定义、§10 约束(一对多继承,不限扇出) | ✅ | +| 5 | 继承关系动态变更:ADD/DROP BASE ON,含 VCT 级联 | §4.5.1 添加继承、§4.5.2 取消继承(含 VCT 级联行为详细说明) | ✅ | +| 6 | 主键列必须显式指定 | §4.1 规则(可无新增列,但**必须声明 TS 列**) | ✅ | +| 7 | 继承来源上限:最多 10 个父 VST | §4.1 规则(最大父表数 10)、§10 约束 | ✅ | +| 8 | 不支持 PRIVATE 列 | v0.3 修订已移除,文档中无 PRIVATE 相关内容 | ✅ | +| 9 | 仅叶子节点 VST 可创建 VCT | §3 定义(叶子 VST)、§4.2(约束)、§10 约束 | ✅ | +| 10 | 不需要 EXPAND 语法 | v0.2 修订已移除,文档中无 EXPAND 相关内容 | ✅ | + +## 18. 结论 + +VST 继承特性通过 `BASE ON` 语法实现多继承 schema 组合,非叶 VST 查询自动下推到叶子后代并投影裁剪。整体设计保持向后兼容,对现有 VST 查询无影响。 \ No newline at end of file diff --git a/17-vst-inheritance-plan.md b/17-vst-inheritance-plan.md new file mode 100644 index 000000000000..e95e76124fa8 --- /dev/null +++ b/17-vst-inheritance-plan.md @@ -0,0 +1,537 @@ +# VST 继承功能实现计划 + +## 问题描述 + +根据 `17-vst-inheritance-fs.md`(v0.4)实现虚拟超级表(VST)多继承功能。 +当前 3.0 分支**没有任何继承实现**,仅有基础的 VST/VCT 支持(virtualStb 标志、CREATE VTABLE/VTABLE USING 语法)。 + +### 核心需求(来自 FS v0.4) + +- 通过 `BASE ON parent1, parent2` 实现多继承(最多 10 个父表,DAG 结构,同 DB) +- 仅**叶子 VST**(无子 VST)可拥有 VCT +- 已有 VCT 的 VST 不能被继承 +- **一个父 VST 可被多个子 VST 同时继承**(一对多,不限扇出) +- 查询非叶 VST → 自动下推到叶子后代,投影裁剪 +- 列顺序:ts → 父1列(无ts) → 父2列(无ts) → ... → 自有列(无ts) +- Tag 顺序:父1Tags → 父2Tags → ... → 自有Tags +- 多父之间列名/Tag名冲突 = **解析期报错,错误消息须包含冲突列名及来源父 VST** +- 子 VST 必须声明 TS 列(主键列不隐式继承) +- ALTER STABLE ADD/DROP BASE ON(继承关系动态变更) +- DROP BASE ON 时,VCT 中被移除列的 colRef **级联删除** +- 父 VST ALTER ADD/DROP COLUMN 自动级联到后代 +- 无 PRIVATE、无 EXPAND、无 MODIFY COLUMN 级联 +- VCT 创建沿用现有 `CREATE VTABLE ... USING ...` 语法(不变) +- 新系统表 `ins_vstable_inherits` + +## 当前基线(3.0 分支) + +| 组件 | 当前状态 | +|------|---------| +| `SStbObj`(mndDef.h:950) | 无继承相关字段 | +| `SMCreateStbReq`(tmsg.h:1287) | 无继承相关字段 | +| `SVCreateStbReq`(tmsg.h:4903) | 无继承相关字段 | +| 语法(sql.y:1054) | `CREATE STABLE ... table_options` — 无 BASE ON | +| SDB 版本 | STB_VER_NUMBER = 4(STB_VER_SUPPORT_OWNER) | +| 错误码 | 无 VST 继承相关错误码 | +| 系统表 | `ins_virtual_tables_referencing` 存在(VCT 引用,非继承) | +| 翻译器 | `translateCreateSuperTable`(parTranslater.c:15024)— 无继承逻辑 | +| 计划器/执行器 | VSTB 扫描支持已有,无继承展开逻辑 | + +--- + +## 阶段 1:基础 — 错误码与数据结构 + +### 1.1 错误码 + +**文件**:`include/util/taoserror.h`、`source/util/src/terror.c` + +新增错误码(分配在 0x03Cx-0x03Dx 范围,靠近现有 MND_STB 错误码): + +``` +TSDB_CODE_MND_VST_HAS_CHILDREN — 拒绝 DROP/ALTER 有子 VST 的父表 +TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL — BASE ON 目标不是 virtualStb=1 +TSDB_CODE_MND_VST_COL_NAME_CONFLICT — 列名/Tag名冲突(消息须含冲突列名及来源父 VST) +TSDB_CODE_MND_VST_CIRCULAR_INHERIT — 检测到 DAG 环路 +TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED — 父 VST 数量超过 10 +TSDB_CODE_MND_VST_PARENT_HAS_VCT — 父 VST 已有 VCT,不能被继承 +TSDB_CODE_MND_VST_NOT_LEAF — 非叶 VST 不能创建 VCT +TSDB_CODE_MND_VST_DROP_BASE_MIN_COLS — 取消继承后列/Tag不满足最小要求 +TSDB_CODE_MND_VST_CROSS_DB — 父子 VST 不在同一 DB +``` + +**列名冲突错误消息格式**: +- 多父之间冲突:`Column '' conflicts between parent '' and parent ''` +- 自有列与父列冲突:`Column '' conflicts with parent ''` +- Tag 冲突同理,将 `Column` 替换为 `Tag` + +### 1.2 常量 + +**文件**:`include/common/tmsgdef.h` 或 `tmsg.h` + +```c +#define TSDB_MAX_VST_PARENTS 10 +``` + +### 1.3 SStbObj 扩展 + +**文件**:`source/dnode/mnode/impl/inc/mndDef.h` + +在 `SStbObj` 中新增: +```c +int8_t numParents; // 0 = 无继承 +int64_t parentSuids[TSDB_MAX_VST_PARENTS]; // 父 VST UID 数组 +int16_t ownColStart; // 自有列起始位置(继承列之后) +int16_t ownTagStart; // 自有 Tag 起始位置(继承 Tag 之后) +``` + +### 1.4 SMCreateStbReq 扩展 + +**文件**:`include/common/tmsg.h` + +在 `SMCreateStbReq` 中新增: +```c +int8_t numParents; +char parentStbFNames[TSDB_MAX_VST_PARENTS][TSDB_TABLE_FNAME_LEN]; +int16_t ownColStart; +int16_t ownTagStart; +``` + +### 1.5 SVCreateStbReq 扩展 + +**文件**:`include/common/tmsg.h` + +在 `SVCreateStbReq` 中新增: +```c +int8_t numParents; +int64_t parentSuids[TSDB_MAX_VST_PARENTS]; +int16_t ownColStart; +int16_t ownTagStart; +``` + +### 1.6 消息序列化 + +**文件**:`source/common/src/msg/tmsg.c` + +- `tSerializeSMCreateStbReq` / `tDeserializeSMCreateStbReq`:在现有字段之后追加继承字段。反序列化时若旧消息则默认 numParents=0。 +- `tSerializeSVCreateStbReq` / `tDeserializeSVCreateStbReq`:同样处理。 + +### 1.7 SDB 编解码 + +**文件**:`source/dnode/mnode/impl/src/mndStb.c` + +- 新增 `#define STB_VER_SUPPORT_INHERIT 5`,更新 `STB_VER_NUMBER` +- `mndStbActionEncode`:写入 numParents、parentSuids[]、ownColStart、ownTagStart +- `mndStbActionDecode`:读取新字段;`if (sver < STB_VER_SUPPORT_INHERIT)` 默认 numParents=0 + +--- + +## 阶段 2:语法与 AST + +### 2.1 Token:TK_BASE + +**文件**:`source/libs/parser/inc/sql.y`(token 声明区域) + +将 `BASE` 添加为非保留关键字,用于 `BASE ON` 复合关键字。 + +### 2.2 语法:CREATE STABLE ... BASE ON + +**文件**:`source/libs/parser/inc/sql.y` + +在现有 CREATE STABLE 规则旁新增: + +```yacc +cmd ::= CREATE STABLE not_exists_opt(A) full_table_name(B) + NK_LP column_def_list(C) NK_RP tags_def(D) BASE ON base_on_list(F) table_options(E). + { pCxt->pRootNode = createCreateInheritedStableStmt(pCxt, A, B, C, D, E, F); } + +cmd ::= CREATE STABLE not_exists_opt(A) full_table_name(B) + NK_LP column_def_list(C) NK_RP BASE ON base_on_list(F) table_options(E). + { pCxt->pRootNode = createCreateInheritedStableStmt(pCxt, A, B, C, NULL, E, F); } + +base_on_list(A) ::= full_table_name(B). + { A = createNodeList(pCxt, B); } +base_on_list(A) ::= base_on_list(B) NK_COMMA full_table_name(C). + { A = addNodeToList(pCxt, B, C); } +``` + +说明: +- `table_options` 已处理 `VIRTUAL NK_INTEGER`,无需额外修改 +- `tags_def` 可选(第二条规则无 TAGS 子句) +- BASE ON 放在 table_options 之前,避免与 VIRTUAL 产生歧义 + +### 2.3 语法:ALTER STABLE ADD/DROP BASE ON + +**文件**:`source/libs/parser/inc/sql.y` + +```yacc +alter_table_clause(A) ::= full_table_name(B) ADD BASE ON base_on_list(C). + { A = createAlterTableAddBaseOn(pCxt, B, C); } +alter_table_clause(A) ::= full_table_name(B) DROP BASE ON base_on_list(C). + { A = createAlterTableDropBaseOn(pCxt, B, C); } +``` + +新增 alter 类型: +- `TSDB_ALTER_TABLE_ADD_BASE_ON` +- `TSDB_ALTER_TABLE_DROP_BASE_ON` + +### 2.4 语法:SHOW VSTABLE INHERITS + +**文件**:`source/libs/parser/inc/sql.y` + +```yacc +cmd ::= SHOW VSTABLE INHERITS. + { pCxt->pRootNode = createShowStmt(pCxt, QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT); } +``` + +### 2.5 AST 节点:SCreateTableStmt 扩展 + +**文件**:`include/libs/nodes/cmdnodes.h` + +扩展 `SCreateTableStmt`: +```c +typedef struct SCreateTableStmt { + ENodeType type; + char dbName[TSDB_DB_NAME_LEN]; + char tableName[TSDB_TABLE_NAME_LEN]; + bool ignoreExists; + SNodeList* pCols; + SNodeList* pTags; + STableOptions* pOptions; + SNodeList* pBaseOnList; // 新增:父 VST full_table_name 节点列表(NULL = 无继承) +} SCreateTableStmt; +``` + +### 2.6 ENodeType(SHOW 语句) + +**文件**:`include/common/tmsg.h` + +在 `ENodeType` 枚举中新增 `QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT`。 + +### 2.7 Parser AST 构造函数 + +**文件**:`source/libs/parser/src/parAstCreater.c` + +- `createCreateInheritedStableStmt()`:创建带 pBaseOnList 的 SCreateTableStmt +- `createAlterTableAddBaseOn()`:创建 TSDB_ALTER_TABLE_ADD_BASE_ON 类型的 SAlterTableStmt +- `createAlterTableDropBaseOn()`:创建 TSDB_ALTER_TABLE_DROP_BASE_ON 类型的 SAlterTableStmt + +--- + +## 阶段 3:翻译器 — 语义分析 + +### 3.1 CREATE STABLE with BASE ON + +**文件**:`source/libs/parser/src/parTranslater.c` + +新增函数 `translateCreateInheritedStable()` 或扩展 `translateCreateSuperTable()`: + +1. **校验 VIRTUAL**:pOptions->virtualStb 必须为 true(继承仅适用于 VST) +2. **遍历每个父 VST(pBaseOnList)**: + a. 通过 catalog 获取父表元数据(`catalogGetTableMeta`) + b. 校验父表是 VST(`virtualStb == 1`)→ 否则 `TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL` + c. 校验父表无 VCT → 否则 `TSDB_CODE_MND_VST_PARENT_HAS_VCT` + d. 校验同一 DB → 否则 `TSDB_CODE_MND_VST_CROSS_DB` +3. **检查父表数量上限**(≤10)→ 否则 `TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED` +4. **列名冲突检测**: + - 收集所有父列(排除 TS)+ 自有列(排除 TS),同时记录每列来源父 VST + - 若有重名列 → `TSDB_CODE_MND_VST_COL_NAME_CONFLICT` + - **错误消息须包含冲突列名及来源**: + - 多父冲突:`Column '' conflicts between parent '' and parent ''` + - 自有列与父列冲突:`Column '' conflicts with parent ''` + - Tag 冲突同理,将 `Column` 替换为 `Tag` +5. **TS 列校验**:自有列列表必须以 `ts TIMESTAMP` 开头 +6. **环路检测**:从每个父表向上 BFS/DFS 遍历其祖先 → `TSDB_CODE_MND_VST_CIRCULAR_INHERIT` +7. **Schema 合并**:构建合并后的列和 Tag 列表: + - 列:ts + 父1列(无ts) + 父2列(无ts) + ... + 自有列(无ts) + - Tags:父1Tags + 父2Tags + ... + 自有Tags +8. **构建 SMCreateStbReq**: + - 合并后的 pColumns/pTags 数组 + - numParents、parentStbFNames[] + - ownColStart(自有列起始索引)、ownTagStart + +### 3.2 ALTER STABLE ADD BASE ON + +翻译 `TSDB_ALTER_TABLE_ADD_BASE_ON`: +1. 获取当前 VST 元数据 +2. 执行与 3.1 相同的校验(父表检查、冲突、上限、环路) +3. 构建包含父表信息的 alter 请求 + +### 3.3 ALTER STABLE DROP BASE ON + +翻译 `TSDB_ALTER_TABLE_DROP_BASE_ON`: +1. 获取当前 VST 元数据 +2. 计算移除父表列/Tag 后的剩余 schema +3. 校验剩余列 ≥ 2(含 TS)+ 剩余 Tag ≥ 1 → 否则 `TSDB_CODE_MND_VST_DROP_BASE_MIN_COLS` + +### 3.4 VCT 创建校验 + +在现有 VCT 创建翻译路径中: +- 解析目标 VST 后,检查其无子 VST → `TSDB_CODE_MND_VST_NOT_LEAF` +- 此检查也可在 mnode 侧完成(翻译器可能没有子表信息) + +### 3.5 DROP STABLE 校验 + +在翻译器或 mnode 中:检查目标 VST 无子表 → `TSDB_CODE_MND_VST_HAS_CHILDREN` + +--- + +## 阶段 4:Mnode — DDL 处理 + +### 4.1 创建继承 VST + +**文件**:`source/dnode/mnode/impl/src/mndStb.c` + +扩展 `mndCheckCreateStbReq()`: +- 若 numParents > 0: + - 对每个 parentStbFName,通过 `mndAcquireStb()` 查找 SStbObj + - 校验父表 virtualStb == 1 + - 校验父表无 VCT(`mndStbHasVCT()`) + - 注意:父表可以有子 VST(非叶父表是允许的;只有叶子才能有 VCT) + +扩展 `mndBuildStbFromReq()`: +- 将 numParents、parentSuids[]、ownColStart、ownTagStart 复制到 SStbObj + +### 4.2 工具函数 + +在 `mndStb.c` 中新增函数: + +```c +// 检查 VST 是否有子 VST(用于 VCT 创建门控 + DROP 门控) +bool mndStbHasChildren(SMnode *pMnode, int64_t suid); + +// 检查 VST 是否有 VCT(用于继承门控) +bool mndStbHasVCT(SMnode *pMnode, int64_t suid); + +// 获取所有叶子后代 VST(用于查询下推) +int32_t mndGetLeafDescendants(SMnode *pMnode, int64_t suid, SArray **ppLeaves); + +// DAG 环路检测 +bool mndCheckCyclicInherit(SMnode *pMnode, int64_t childSuid, int64_t *parentSuids, int8_t numParents); +``` + +`mndStbHasChildren` 实现方式: +- 遍历 SDB 中所有 STB,检查是否有 `parentSuids[]` 包含给定 suid +- 或维护反向索引(子表列表)— 性能与复杂度的权衡 +- 初始实现使用 SDB 遍历即可(STB 数量通常较少) + +### 4.3 DROP 校验 + +在 `mndDropStb()` 或 `mndProcessDropStbReq()` 中: +- 删除前调用 `mndStbHasChildren()` → 拒绝并返回 `TSDB_CODE_MND_VST_HAS_CHILDREN` + +### 4.4 VCT 创建门控 + +在 VCT 创建路径(mnode 侧): +- 创建 VCT 时检查目标 VST 是否有子表 `mndStbHasChildren()` → 拒绝并返回 `TSDB_CODE_MND_VST_NOT_LEAF` + +### 4.5 ALTER 级联:ADD/DROP COLUMN + +在 `mndAlterStb()` 中: +- 父 VST 添加/删除列后: + - 查找所有直接子 VST(扫描 SDB,找 parentSuids 包含该 suid 的 STB) + - 对每个子 VST: + - 在正确位置(父表继承区域内)添加/删除列 + - 调整受影响子 VST 的 ownColStart / ownTagStart + - 递归处理孙代 + - 为每个后代构建 `SMAlterStbReq` 并应用 + +### 4.6 ALTER ADD BASE ON + +`TSDB_ALTER_TABLE_ADD_BASE_ON` 的 mnode 处理: +1. 获取目标 SStbObj +2. 获取每个新父 SStbObj +3. 校验所有约束(与创建类似,但为增量式) +4. 将父表列/Tag 合并到目标 schema 中(插入到 ownColStart/ownTagStart 之前) +5. 更新 parentSuids[]、numParents、ownColStart、ownTagStart +6. 发送 vnode alter 请求以更新 schema + +### 4.7 ALTER DROP BASE ON + +`TSDB_ALTER_TABLE_DROP_BASE_ON` 的 mnode 处理: +1. 找到要移除的父表 +2. 确定来自该父表的列/Tag(在父表继承区域边界之间) +3. 校验剩余 schema ≥ 2 列 + ≥ 1 Tag +4. 从 schema 中移除父表的列/Tag +5. 更新 parentSuids[]、numParents、ownColStart、ownTagStart +6. **VCT 级联删除**(若该 VST 已有 VCT): + a. 遍历该 VST 下所有 VCT + b. 从每个 VCT 中移除被移除列的 colRef 映射 + c. 从每个 VCT 中移除被移除 Tag 的值 + d. 发送 vnode alter 请求更新 VCT schema + e. 后续查询这些 VCT 不再包含被移除的列和 Tag + +--- + +## 阶段 5:系统表与 SHOW + +### 5.1 系统表:ins_vstable_inherits + +**文件**:`include/common/systable.h` + +```c +#define TSDB_INS_TABLE_VSTABLE_INHERITS "ins_vstable_inherits" +``` + +**文件**:`source/common/src/systable.c` + +定义列 schema: +``` +db_name VARCHAR(64) +parent_stable VARCHAR(192) +parent_uid BIGINT +child_stable VARCHAR(192) +child_uid BIGINT +create_time TIMESTAMP +``` + +### 5.2 Mnode 检索函数 + +**文件**:`source/dnode/mnode/impl/src/mndStb.c` + +新增函数 `mndRetrieveVstableInherits()`: +- 遍历 SDB 中所有 SStbObj +- 对每个 numParents > 0 的,每个父表输出一行 +- 通过 `mndSetMsgHandle()` 在 `mndInitStb()` 中注册 + +### 5.3 SHOW VSTABLE INHERITS + +**文件**:`source/libs/parser/src/parTranslater.c` + +将 `QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT` 翻译为 `SELECT * FROM information_schema.ins_vstable_inherits` + +### 5.4 SHOW CREATE STABLE + +**文件**:`source/dnode/mnode/impl/src/mndStb.c`(或 SHOW CREATE STABLE 的处理位置) + +当 numParents > 0 时,输出中包含 `BASE ON parent1, parent2` 子句。 + +### 5.5 DESCRIBE 增强 + +在 DESCRIBE 输出中,为继承列标注来源父 VST 名称(Note/comment 字段)。 + +--- + +## 阶段 6:查询 — 非叶 VST 下推 + +### 6.1 Catalog:叶子后代发现 + +**文件**:`source/libs/catalog/` + +计划器需要知道非叶 VST 的所有叶子后代。方案: +- **方案 A**:Mnode 提供消息类型,返回给定 suid 的叶子后代 +- **方案 B**:Catalog 获取所有继承关系后本地计算 +- **推荐**:方案 A — 新增消息 `TDMT_MND_GET_VST_LEAF_DESCENDANTS`,返回叶子 SStbObj 元数据数组 + +### 6.2 计划器:逻辑计划创建 + +**文件**:`source/libs/planner/src/planLogicCreater.c` + +在 `createScanLogicNode()` 或等效函数中: +- 当扫描一个 numParents=0 但有子 VST 的 VST(非叶)时: + - 通过 catalog 获取叶子后代 + - 为每个叶子创建虚拟扫描逻辑节点 + - 用 MERGE/UNION 逻辑节点组合(所有叶子都包含祖先的列) + - 应用投影裁剪:仅输出被查询祖先 VST 自身 schema 的列 + +### 6.3 计划器:物理计划创建 + +**文件**:`source/libs/planner/src/planPhysiCreater.c` + +- 将多叶子扫描逻辑计划转换为物理 exchange + scan 节点 + +### 6.4 执行器:投影裁剪 + +**文件**:`source/libs/executor/` + +- 每个叶子 VCT 扫描输出叶子 VST 的所有列 +- 执行器通过 slot 映射裁剪为仅查询的祖先列 +- 类似现有的列裁剪 — 将祖先 colId 映射到叶子 slot 位置 + +--- + +## 阶段 7:测试 + +### 7.1 单元测试 + +- Schema 合并逻辑(列顺序、Tag 顺序) +- 环路检测(DAG) +- 列名冲突检测 +- SDB 编解码往返测试(含继承字段) + +### 7.2 系统测试(Python) + +**文件**:`tests/system-test/2-query/vst_inherit.py`(新建) + +测试用例: +1. 创建继承 VST(单父、多父) +2. 在叶子 VST 下创建 VCT +3. 在非叶 VST 下创建 VCT → 报错 +4. 继承已有 VCT 的 VST → 报错 +5. 列名冲突 → 报错 +6. 环路检测 → 报错 +7. 超过最大父表数 → 报错 +8. 查询叶子 VST(完整 schema) +9. 查询非叶 VST(下推 + 投影裁剪) +10. ALTER 父表 ADD COLUMN → 级联 +11. ALTER 父表 DROP COLUMN → 级联 +12. ALTER ADD BASE ON +13. ALTER DROP BASE ON(最少列/Tag 检查) +14. DROP 有子表的父 VST → 报错 +15. SHOW VSTABLE INHERITS +16. SHOW CREATE STABLE +17. DESCRIBE 继承标注 +18. 跨 DB 继承 → 报错 +19. 通过非叶 VST 查询 Tag + +--- + +## 依赖关系图 + +``` +阶段 1(基础) + ├── 阶段 2(语法与 AST)──依赖── 阶段 1 + │ └── 阶段 3(翻译器)──依赖── 阶段 1, 2 + │ └── 阶段 4(Mnode DDL)──依赖── 阶段 1, 3 + │ ├── 阶段 5(系统表)──依赖── 阶段 4 + │ └── 阶段 6(查询下推)──依赖── 阶段 4, 5 + └── 阶段 7(测试)──依赖── 所有阶段 +``` + +阶段 5 和阶段 6 可在阶段 4 稳定后并行开发。 + +--- + +## 推荐实现顺序 + +1. **阶段 1**:错误码、常量、结构体扩展、序列化 → 确保编译链接通过 +2. **阶段 2**:语法 + AST → 解析器编译通过,新语法可解析 +3. **阶段 3**:翻译器 → 语义校验 + schema 合并,CREATE 返回正确的 SMCreateStbReq +4. **阶段 4.1-4.4**:Mnode 创建/删除/VCT 门控 → 基础继承端到端可用 +5. **阶段 5.1-5.3**:系统表 + SHOW → 继承关系可查询 +6. **阶段 4.5-4.7**:ALTER 级联 + ADD/DROP BASE ON → 完整 DDL 支持 +7. **阶段 6**:查询下推 → 非叶 VST 查询可用 +8. **阶段 7**:测试 +9. **阶段 5.4-5.5**:SHOW CREATE STABLE / DESCRIBE 增强 + +## 需修改的关键文件 + +| 文件 | 修改内容 | +|------|---------| +| `include/util/taoserror.h` | 新增错误码 | +| `source/util/src/terror.c` | 错误码字符串 | +| `source/dnode/mnode/impl/inc/mndDef.h` | SStbObj 继承字段 | +| `include/common/tmsg.h` | SMCreateStbReq/SVCreateStbReq 字段、ENodeType、常量 | +| `source/common/src/msg/tmsg.c` | 序列化/反序列化 | +| `include/libs/nodes/cmdnodes.h` | SCreateTableStmt.pBaseOnList | +| `source/libs/parser/inc/sql.y` | 语法规则 | +| `source/libs/parser/src/parAstCreater.c` | AST 构造函数 | +| `source/libs/parser/src/parTranslater.c` | 翻译逻辑 | +| `source/dnode/mnode/impl/src/mndStb.c` | Mnode DDL + 系统表 | +| `include/common/systable.h` | ins_vstable_inherits 定义 | +| `source/common/src/systable.c` | 系统表 schema | +| `source/libs/planner/src/planLogicCreater.c` | 非叶展开 | +| `source/libs/planner/src/planPhysiCreater.c` | 物理计划 | +| `source/libs/executor/` | 投影裁剪 | +| `source/libs/nodes/src/nodesCloneFuncs.c` | 克隆 pBaseOnList | +| `source/libs/nodes/src/nodesCodeFuncs.c` | 新节点类型序列化/命名 | +| `source/libs/nodes/src/nodesUtilFuncs.c` | 节点创建工具 | diff --git a/compare.md b/compare.md new file mode 100644 index 000000000000..3ec83c1e7941 --- /dev/null +++ b/compare.md @@ -0,0 +1,841 @@ +# VST 继承查询:EXPAND 方案 vs UNION ALL 内部改写方案 全面对比 + +> 基于 [17-vst-inheritance-fs.md](./17-vst-inheritance-fs.md) 的设计规格。 +> +> **场景**:用户写 `SELECT * FROM parent_vst EXPAND(-1)`,引擎内部如何实现? +> +> **当前实现状态**:方案 B(UNION ALL 改写)已实施。`expandFromAncestor` 字段已移除(PRIVATE 由 schema 投影天然排除,无需运行时判断)。 + +--- + +## 1. FS 核心行为要点 + +### 1.1 数据模型 + +``` +parent_vst (ts, val INT) TAGS (t1 INT) + ├── child_vst (ts, val, extra FLOAT) TAGS (t1, t2 BINARY(16)) + │ └── grandchild_vst (ts, val, extra, deep INT) TAGS (t1, t2, t3 INT) + └── child_vst2 (ts, val, temp FLOAT) TAGS (t1, t4 INT) +``` + +VCT 列表: +- parent_vst: vct_p1, vct_p2 +- child_vst: vct_c1, vct_c2, vct_c3, vct_c4 +- grandchild_vst: vct_g1 +- child_vst2: vct_m1 +- **总计 8 个 VCT** + +### 1.2 EXPAND 核心规则 + +| 规则 | 说明 | +|------|------| +| **Schema = 被查询 VST** | `SELECT * FROM parent_vst EXPAND(-1)` 的 schema 永远是 (ts, val),不含子孙私有列 | +| **EXPAND 只扩展行** | 纳入子孙 VCT 的数据行,不扩展列 | +| **只能引用自身 schema** | `SELECT extra FROM parent_vst EXPAND(-1)` → 语义错误 | +| **PRIVATE 是 VCT 私有列** | VCT 的私有列不在 VST schema 中,由 schema 投影天然排除 | +| **Tag 同理** | 只能引用被查询 VST 自身 Tag | + +### 1.3 关键查询行为(FS §3.4) + +```sql +-- 1. parent_vst EXPAND(-1): schema=(ts, val), 含全部 8 个 VCT, 16 行 +-- 子孙额外列(extra, deep, temp)不在结果中,由 schema 投影排除 +SELECT * FROM parent_vst EXPAND(-1); + +-- 2. child_vst EXPAND(-1): schema=(ts, val, extra), 含 vct_c1~c4 + vct_g1, 10 行 +-- grandchild 的 deep 不在结果中 +SELECT * FROM child_vst EXPAND(-1); + +-- 3. INTERVAL 直接支持 +SELECT _wstart, COUNT(*) FROM parent_vst EXPAND(-1) INTERVAL(5s); + +-- 4. Stream 直接支持 +CREATE STREAM s INTO out AS +SELECT _wstart, SUM(val) FROM parent_vst EXPAND(-1) INTERVAL(10s); +``` + +--- + +## 2. 两种实现方案定义 + +### 方案 A:当前 EXPAND 实现(单算子 + 运行时) + +```sql +SELECT * FROM parent_vst EXPAND(-1) WHERE val > 10; +``` + +引擎生成一个紧凑的执行计划: +``` +Project (schema: ts, val) + └── DynQueryCtrl (expandLevel=-1, descendants=[child_vst, grandchild_vst, child_vst2]) + ├── VirtualScanNode (parent_vst 模板) + │ ├── RealTableScan (源表扫描模板) + │ └── TagScan + └── SysTableScan (ins_vc_cols, 全量扫描不过滤 stableId) +``` + +运行时行为: +1. 一次 sys scan 返回所有 VCT 的 colRef(不按 stableId 过滤) +2. 用 `pExpandDescendantStbs` hash 过滤:仅保留属于 parent/child/grandchild/child2 的 VCT +3. 输出 schema = parent_vst 自身 (ts, val),只取这些列的值 +4. 对每个 VCT 逐个构建 Exchange 参数,发 RPC 到源表取 val 值 +5. VCT 的私有列不在 VST schema 中,由 schema 投影天然排除 + +### 方案 B:Translator/Planner 改写为 UNION ALL + +用户写同样的 SQL,Translator 在编译时将其改写为内部等价查询: +``` +Project (输出 schema: ts, val) + └── SetOperator (UNION ALL) + ├── DynQueryCtrl (parent_vst, SELECT ts, val) + ├── DynQueryCtrl (child_vst, SELECT ts, val) + ├── DynQueryCtrl (grandchild_vst, SELECT ts, val) + └── DynQueryCtrl (child_vst2, SELECT ts, val) +``` + +每个分支是独立的虚拟表查询: +- 投影列 = 父 VST schema 列(ts, val) +- 只扫描该 VST **自身** VCT(不含子孙) +- VCT 私有列不在投影列中,天然排除 + +--- + +## 3. 架构层面对比 + +``` + 方案 A(EXPAND) 方案 B(UNION 改写) + ┌─────────────────────┐ ┌────────────────────────────────┐ + Translator │ 获取 descendants │ │ 获取 descendants │ + │ schema = 被查询 VST │ │ 为每个 VST 生成独立 SSelectStmt │ + │ → 传给 Planner │ │ 投影列 = 父 schema 列 │ + └─────────┬───────────┘ │ → SSetOperator 二叉树 │ + │ └───────────────┬────────────────┘ + │ │ + Planner │ 1 个 DynQueryCtrl │ │ N 个 DynQueryCtrl │ + │ + 父 VST schema │ │ + SetOpProject (UNION ALL) │ + └─────────┬───────────┘ └───────────────┬────────────────┘ + │ │ + Splitter │ virtualTableSplit │ │ unionAllSplit (拆 N 分支) │ + │ 1 次分裂 │ │ + 每分支内 virtualTableSplit │ + └─────────┬───────────┘ └───────────────┬────────────────┘ + │ │ + Executor │ 1 个 DynQueryCtrl │ │ N 个 DynQueryCtrl 算子 │ + │ 内部循环所有 VCT │ │ + Exchange + Project(合并) │ + │ 仅取父 schema 列 │ │ 各分支仅取父 schema 列 │ + └─────────────────────┘ └────────────────────────────────┘ + + 智能位置: Executor (运行时) Planner (编译时) +``` + +--- + +## 4. UNION 改写具体实现(基于 TDengine 代码库) + +### 4.1 Translator 改写入口(parTranslater.c) + +当前 `translateVirtualTable()`(L6834-L6875)流程: +```c +// 现在做的: +pVTable->expandLevel = pRealTable->expandLevel; // L6839 +catalogGetVstDescendants(..., &pDescArr); // L6856 +// 将后代列表存入 pVTable->pExpandDescendants 传给 Planner +``` + +**UNION 改写需替换为:** + +```c +// 新逻辑:不再传 pExpandDescendants 给 Planner, +// 而是在 Translator 阶段直接改写 AST +static int32_t rewriteExpandToUnionAll(STranslateContext* pCxt, + SSelectStmt* pSelect, + SVirtualTableNode* pVTable) { + int32_t code = TSDB_CODE_SUCCESS; + + // 1. 获取后代 VST 列表 + SArray* pDescArr = NULL; + int32_t maxLvl = (pVTable->expandLevel == -1) ? -1 : pVTable->expandLevel; + code = catalogGetVstDescendants(pCatalog, &conn, dbFName, pName->tname, maxLvl, &pDescArr); + if (code != TSDB_CODE_SUCCESS || !pDescArr || taosArrayGetSize(pDescArr) == 0) { + return code; // 无后代 → EXPAND 等于 no-op + } + + // 2. 提取被查询 VST 的 schema 列(用于每个分支的 SELECT list) + // schema = parent_vst 自身列: [ts, val] + SNodeList* pParentSchemaExprs = extractProjectExprsFromSchema(pVTable->pMeta); + + // 3. 构建 N+1 个分支的 SSelectStmt(自身 + N 个后代) + SNodeList* pBranches = NULL; + nodesMakeList(&pBranches); + + // 3a. 自身分支(parent_vst, expandLevel=0 即不再展开) + SSelectStmt* pSelfBranch = buildBranchSelect(pCxt, pVTable->tableName, + pParentSchemaExprs, + pSelect->pWhere, + /*expandLevel=*/0); + nodesListAppend(pBranches, (SNode*)pSelfBranch); + + // 3b. 每个后代 VST 分支 + for (int32_t i = 0; i < taosArrayGetSize(pDescArr); i++) { + char* descName = *(char**)taosArrayGet(pDescArr, i); + // 需要 catalogGetTableMeta 获取后代 meta 来验证列存在性 + SSelectStmt* pDescBranch = buildBranchSelect(pCxt, descName, + pParentSchemaExprs, + pSelect->pWhere, + /*expandLevel=*/0); + nodesListAppend(pBranches, (SNode*)pDescBranch); + } + + // 4. 构建 SSetOperator 二叉树(UNION ALL) + SNode* pUnionRoot = buildUnionAllBinaryTree(pBranches); + + // 5. 替换原始 pSelect 为子查询包装 + // 原始 SELECT ... FROM parent_vst EXPAND(-1) WHERE ... ORDER BY ... LIMIT ... + // → SELECT ... FROM (UNION ALL 子查询) ORDER BY ... LIMIT ... + pSelect->pFromTable = createTempTableNode(pUnionRoot); + pSelect->pWhere = NULL; // WHERE 已下推到各分支 + + return code; +} +``` + +### 4.2 分支 SELECT 构建细节 + +每个分支是一个完整的虚拟表查询,但 **expandLevel=0**(仅查自身 VCT): + +```c +// buildBranchSelect 为单个 VST 生成: +// SELECT ts, val FROM [WHERE ...] +static SSelectStmt* buildBranchSelect(STranslateContext* pCxt, + const char* vstName, + SNodeList* pProjectExprs, + SNode* pWhere, + int32_t expandLevel) { + SSelectStmt* pStmt = nodesMakeNode(QUERY_NODE_SELECT_STMT); + + // 1. FROM clause: 虚拟超级表(expandLevel=0,不再递归展开) + SRealTableNode* pTable = createRealTableNode(vstName); + pTable->expandLevel = expandLevel; + pTable->hasExpand = (expandLevel != 0); + pStmt->pFromTable = (STableNode*)pTable; + + // 2. SELECT list: 只取父 schema 列 + // 对每个列名在后代 VST 的 schema 中查找对应 colId + pStmt->pProjectionList = nodesCloneList(pProjectExprs); + + // 3. WHERE: 深拷贝原始条件 + if (pWhere) { + nodesCloneNode(pWhere, &pStmt->pWhere); + } + + return pStmt; +} +``` + +**每个分支独立翻译**(translateQuery)→ 走现有虚拟表路径 → 生成各自的 DynQueryCtrl。 + +### 4.3 SSetOperator 二叉树构建 + +TDengine 的 `SSetOperator` 是二元结构(pLeft + pRight)。N 个分支需要递归嵌套: + +```c +// 4 个分支的结果: +// UNION(UNION(UNION(parent, child), grandchild), child2) +// 即左倾树,深度 = N-1 +static SNode* buildUnionAllBinaryTree(SNodeList* pBranches) { + SNode* pResult = nodesListGetNode(pBranches, 0); + for (int i = 1; i < nodeListGetSize(pBranches); i++) { + SSetOperator* pSet = nodesMakeNode(QUERY_NODE_SET_OPERATOR); + pSet->opType = SET_OP_TYPE_UNION_ALL; + pSet->pLeft = pResult; + pSet->pRight = nodesListGetNode(pBranches, i); + pResult = (SNode*)pSet; + } + return pResult; +} +``` + +**深度问题**: +- 4 个子孙 → 3 层嵌套 +- 10 个子孙 → 9 层嵌套 +- FS 允许 10 级深层继承 → 可能 100+ 个后代 → 99 层嵌套 + +### 4.4 Logic Plan(planLogicCreater.c) + +UNION 改写后直接复用现有路径: + +``` +createSetOperatorLogicNode() [L5111-L5126] + └── createSetOpLogicNode() [L5071-L5109] + ├── SET_OP_TYPE_UNION_ALL → createSetOpProjectLogicNode() + ├── createQueryLogicNode(pLeft) → 虚拟表查询子树 + └── createQueryLogicNode(pRight) → UNION 子树(递归) +``` + +**无需修改**:现有 UNION ALL 逻辑计划创建完全适用。 + +每个分支内部的虚拟表查询仍走 `createDynQueryCtrlLogicNode()`,但 expandLevel=0(不展开),只查自身 VCT。 + +### 4.5 Splitter(planSpliter.c) + +利用现有 `unionAllSplit()`(L1911): + +``` +splitLogicPlan + └── unionAllSplit() ← 检测到 SetOpProject 节点 + ├── 分支 1 subplan → virtualTableSplit (parent_vst) + ├── 分支 2 subplan → virtualTableSplit (child_vst) + ├── 分支 3 subplan → virtualTableSplit (grandchild_vst) + └── 分支 4 subplan → virtualTableSplit (child_vst2) +``` + +**无需额外代码**:现有 unionAllSplit 已经能递归处理。 + +### 4.6 Executor 阶段改动 + +**删除 DynQueryCtrl 中的 EXPAND 逻辑**: + +```c +// 需要删除的代码(dynqueryctrloperator.c): +// 1. pExpandDescendantStbs hash 构建 (L4281-L4296) +// 2. tableInfoNeedCollectForExpand() 函数 (L3624-L3639) +// 3. needExpand 条件分支 (L4321-L4322) +// 4. pExpandDescendantStbs 清理 (L287-L289) +// 5. expandLevel 相关字段初始化 (L5465-L5466) +``` + +**依赖现有设施**: +- SetOperator executor 合并各分支结果 +- Exchange 算子处理跨节点传输 +- 每个分支的 DynQueryCtrl 只负责自身 VCT + +### 4.7 PRIVATE 列处理 + +**PRIVATE 列 = VCT 的私有列**,不属于任何 VST 的 schema。 + +``` +在 UNION ALL 方案中,每个分支只 SELECT 父 VST schema 的列: + SELECT ts, val FROM child_vst -- 只投影 parent_vst 的 (ts, val) + +child_vst 的 VCT 可能有私有列 (secret_col): + vct_c1: 有 secret_col → 但不在投影列中 → 不出现在结果中 + vct_c2: 无私有列 → 不影响 +``` + +**结论**:UNION ALL 改写的 schema 投影**天然排除** VCT 私有列,无需任何运行时判断。这是 UNION ALL 方案相对于方案 A 的一个简化优势。 + +### 4.8 完整代码改动清单(已实施) + +| 文件 | 改动类型 | 实际改动 | +|------|----------|----------| +| `source/libs/parser/src/parTranslater.c` | 新增 `rewriteVstExpandToUnionAll()` + 辅助函数 | +~120 行 | +| `source/libs/parser/src/parTranslater.c` | 删除 expandFromAncestor 传递逻辑 | -20 行 | +| `source/libs/planner/src/planLogicCreater.c` | 删除 expandFromAncestor 传递 | -2 行 | +| `source/libs/planner/src/planPhysiCreater.c` | 删除 expandFromAncestor 传递 | -2 行 | +| `source/libs/executor/src/dynqueryctrloperator.c` | 删除 expandFromAncestor 初始化 | -1 行 | +| `source/libs/executor/inc/dynqueryctrl.h` | 删除 expandFromAncestor 字段 | -1 行 | +| `source/libs/nodes/src/nodesCloneFuncs.c` | 删除 expandFromAncestor clone | -4 行 | +| `source/libs/nodes/src/nodesCodeFuncs.c` | 删除 expandFromAncestor 序列化 | -11 行 | +| `include/libs/nodes/plannodes.h` | 删除 expandFromAncestor 字段 | -4 行 | +| `include/libs/nodes/querynodes.h` | 删除 expandFromAncestor 字段 | -2 行 | + +> 注:PRIVATE 由 schema 投影天然排除,无需 expandFromAncestor 运行时判断。 + +--- + +## 5. FS 行为逐项对比实现 + +### 5.1 基础查询(FS §3.4 示例 1) + +```sql +SELECT * FROM parent_vst EXPAND(-1) ORDER BY ts; +-- 期望:16 行, schema=(ts, val) +``` + +| 步骤 | 方案 A (EXPAND) | 方案 B (UNION) | +|------|----------------|----------------| +| 计划结构 | 1 个 DynQueryCtrl | 4 个 DynQueryCtrl + UNION ALL | +| VCT 发现 | 1 次 sys scan 全量 + hash 过滤 | 4 次 sys scan(每分支各自) | +| Schema 确定 | Translator 传入 parent_vst meta | 每个分支 SELECT ts, val | +| PRIVATE 列 | schema 投影排除 | schema 投影天然排除 | +| ORDER BY | 单一结果集排序 | UNION ALL 结果再排序(需外层 Sort) | +| 结果相同? | ✅ | ✅ | + +### 5.2 分层 EXPAND(FS §3.2) + +```sql +SELECT * FROM parent_vst EXPAND(1); +-- 期望:14 行(不含 grandchild_vst 的 vct_g1) +``` + +| 方案 | 实现 | +|------|------| +| A | `expandLevel=1` → catalogGetVstDescendants(maxLvl=1) → 只返回 [child_vst, child_vst2] | +| B | 同样调 catalogGetVstDescendants(maxLvl=1) → 只生成 3 个分支(parent + child + child2) | + +两种方案层级控制实现难度相同。 + +### 5.3 WHERE 过滤(FS §3.4 示例 4) + +```sql +SELECT val FROM parent_vst EXPAND(-1) WHERE val > 35 ORDER BY val; +-- 期望:40, 41, 50, 50, 51, 51, 60, 61 (8 行) +``` + +| 方案 | WHERE 处理 | +|------|------------| +| A | DynQueryCtrl 内部对每个 VCT 查源表时下推 WHERE val > 35 | +| B | 每个分支独立 WHERE val > 35(WHERE 在外层 SELECT 上,各分支自动继承) | + +**差异**:方案 B 需要在 Translator 阶段深拷贝 WHERE 到每个分支。由于 schema 以父 VST 为准,所有分支都有 val 列(继承列),WHERE 可以直接复制,无列名冲突。 + +### 5.4 聚合查询(FS §3.4 示例 5) + +```sql +SELECT COUNT(*), SUM(val), AVG(val) FROM parent_vst EXPAND(-1); +-- 期望:16, 457, 32.64 +``` + +| 方案 | 实现方式 | +|------|----------| +| A | DynQueryCtrl 收集全部 16 行 → 单一聚合算子 | +| B | **问题出现** → 见下 | + +方案 B 的聚合处理: + +**选择 1:聚合在外层** +```sql +SELECT COUNT(*), SUM(val), AVG(val) FROM ( + SELECT ts, val FROM parent_vst + UNION ALL SELECT ts, val FROM child_vst + UNION ALL SELECT ts, val FROM grandchild_vst + UNION ALL SELECT ts, val FROM child_vst2 +); +-- ✅ 语义正确:外层对 UNION 子查询做聚合 +-- ⚠️ 需要 Translator 将聚合上提到外层,内部分支只做投影 +``` + +**选择 2:聚合下推到分支** +```sql +SELECT SUM(branch_count), SUM(branch_sum), SUM(branch_sum)/SUM(branch_count) +FROM ( + SELECT COUNT(*) AS branch_count, SUM(val) AS branch_sum FROM parent_vst + UNION ALL SELECT COUNT(*), SUM(val) FROM child_vst + UNION ALL ... +); +-- ⚠️ 对 COUNT/SUM 可行(可加聚合) +-- ❌ 对 AVG 需拆分为 SUM/COUNT 再合并 +-- ❌ 对 PERCENTILE/APERCENTILE 完全不可拆分 +``` + +**方案 B 必须选择 1**:将聚合保留在外层,内部 UNION ALL 只返回原始行。 + +### 5.5 INTERVAL 窗口查询(FS §3.4 示例 8)⚠️ 关键差异 + +```sql +SELECT _wstart, COUNT(*), SUM(val) FROM parent_vst EXPAND(-1) INTERVAL(5s); +``` + +**方案 A**:DynQueryCtrl 收集全部行 → INTERVAL 算子在统一时间线上切窗口 → ✅ 直接支持 + +**方案 B**: + +```sql +-- 选择 1:INTERVAL 在外层 +SELECT _wstart, COUNT(*), SUM(val) FROM ( + SELECT ts, val FROM parent_vst + UNION ALL SELECT ts, val FROM child_vst + UNION ALL ... +) INTERVAL(5s); +-- ❌ TDengine 不支持对 UNION 子查询使用 INTERVAL +-- 原因:INTERVAL 需要单一表源的时间线,子查询不满足 +``` + +```sql +-- 选择 2:INTERVAL 下推到每个分支 +SELECT _wstart, COUNT(*), SUM(val) FROM parent_vst INTERVAL(5s) +UNION ALL +SELECT _wstart, COUNT(*), SUM(val) FROM child_vst INTERVAL(5s) +UNION ALL ... +-- ❌ 语义错误:不同分支的相同时间窗口产生多行 +-- 例:[00:00:00, 00:00:05) parent分支=62, child分支=122 +-- 应该合并为 184,但 UNION ALL 输出两行 +``` + +```sql +-- 选择 3:下推 + 外层再聚合 +SELECT _wstart, SUM(cnt), SUM(s) FROM ( + SELECT _wstart, COUNT(*) AS cnt, SUM(val) AS s FROM parent_vst INTERVAL(5s) + UNION ALL + SELECT _wstart, COUNT(*), SUM(val) FROM child_vst INTERVAL(5s) + UNION ALL ... +) GROUP BY _wstart; +-- ⚠️ 语义近似正确,但: +-- 1. _wend, _wduration 等窗口伪列丢失 +-- 2. 需要在 Translator 中做复杂的聚合拆分/合并改写 +-- 3. 对不可拆分聚合(PERCENTILE 等)无解 +-- 4. SLIDING 语义更复杂(滑动窗口对齐问题) +-- 5. 不再是真正的 INTERVAL 查询 → 失去流式计算兼容性 +``` + +**结论:INTERVAL 在方案 B 中没有完美解决方案。** + +### 5.6 PARTITION BY + INTERVAL(FS §3.4 示例 8.3) + +```sql +SELECT tbname, _wstart, COUNT(*) FROM parent_vst EXPAND(-1) + PARTITION BY tbname INTERVAL(10s); +``` + +**方案 A**:每个 VCT 独立 partition → INTERVAL 切窗 → ✅ 直接支持 + +**方案 B**: +```sql +-- PARTITION BY 在 UNION 子查询上的行为未定义 +-- 需要将 PARTITION BY 同时下推到每个分支 + 外层合并 +-- 如果不同 VCT 有相同 tbname(不可能但设计上需考虑)→ 行为不确定 +``` + +### 5.7 Stream 流计算(FS §3.4 示例 15)❌ 致命不兼容 + +```sql +CREATE STREAM expand_stream INTO expand_result AS +SELECT _wstart, COUNT(*), SUM(val) FROM parent_vst EXPAND(-1) INTERVAL(10s); +``` + +**方案 A**:DynQueryCtrl 注册为流计算源 → 监听所有子孙 VCT 的源表变更 → ✅ + +**方案 B**: +``` +TDengine Stream 的硬约束: + 1. FROM 必须是单一 SRealTableNode 或 SVirtualTableNode + 2. 不允许 FROM 子查询中包含 UNION(parTranslater.c L12210 检查) + 3. INTERVAL 不支持 UNION 子查询 + +改写后等价: + CREATE STREAM ... AS SELECT ... FROM (... UNION ALL ...) INTERVAL(10s); + → ❌ 语法错误 + 语义不支持 +``` + +**无解决方案**,除非彻底改造 Stream 引擎: +- 涉及 `tqSink`, `tqPush`, `streamDispatch`, `streamState` 等模块 +- 需要支持多源表订阅 + 跨源合并 +- 工程量 >> EXPAND 方案本身 + +### 5.8 LAST/FIRST(FS §3.4 示例 13) + +```sql +SELECT LAST(val) FROM parent_vst EXPAND(-1); +-- 期望:61(所有 VCT 中最新的非 NULL val) +``` + +| 方案 | 实现 | +|------|------| +| A | DynQueryCtrl 扫描全部 VCT → LAST 函数在全局时间线找最新非 NULL 值 → ✅ | +| B | 每分支 LAST(val) → UNION ALL → 外层再取 MAX → ⚠️ 需要改写为 `SELECT MAX(branch_last) FROM (SELECT LAST(val) AS branch_last FROM parent_vst UNION ALL ...)` → 非标改写 | + +### 5.9 ORDER BY + LIMIT(FS §3.4 示例 7) + +```sql +SELECT val FROM parent_vst EXPAND(-1) ORDER BY val DESC LIMIT 3; +-- 期望:61, 60, 51 +``` + +| 方案 | 实现 | +|------|------| +| A | 单一结果集 → Sort → Limit → ✅ | +| B | UNION ALL 结果无序 → 需外层 Sort + Limit → ✅ 可行但多一层算子 | + +方案 B 可以将 ORDER BY + LIMIT 保留在外层 SELECT(不下推到各分支)。 + +但 **LIMIT 不能下推**: +```sql +-- ❌ 错误做法:每分支各取 LIMIT 3 再合并 +SELECT val FROM parent_vst ORDER BY val DESC LIMIT 3 +UNION ALL +SELECT val FROM child_vst ORDER BY val DESC LIMIT 3 +-- 结果 > 3 行,需要外层再 LIMIT → 效率低 +``` + +### 5.10 GROUP BY tbname(FS §3.4 示例 6) + +```sql +SELECT tbname, COUNT(*), MAX(val) FROM parent_vst EXPAND(-1) GROUP BY tbname; +``` + +| 方案 | 实现 | +|------|------| +| A | 全局分组 → ✅ | +| B | 每分支 GROUP BY tbname → UNION ALL → 外层无需再聚合(因为 tbname 在各分支内唯一) → ✅ 可行 | + +GROUP BY 是少数方案 B 可以下推的场景。 + +--- + +## 6. 改成 UNION 会碰到的具体问题汇总 + +### 6.1 ⚠️ N 倍 Catalog RPC + +``` +方案 A: + catalogGetVstDescendants() → 1 次 RPC + sys scan ins_vc_cols (全量) → 1 次 scan + 总计: ~2 次 + +方案 B: + catalogGetVstDescendants() → 1 次 RPC + catalogGetTableMeta(每个后代) → N 次 RPC(构建各分支 meta) + catalogGetTableVgroupList(每个) → N 次 RPC + sys scan per VST → N 次 scan + 每个 VST 的 refTables 处理 → N 次 + 总计: ~4N+1 次 RPC +``` + +### 6.2 ⚠️ 二叉树嵌套深度 + +``` +FS 允许 10 级深度继承,假设每级 3 个子孙: + level 1: 3 VSTs + level 2: 9 VSTs + ... + level 10: 59049 VSTs (理论极端) + +实际案例:50 个后代 VST → SSetOperator 嵌套 49 层 +→ 递归翻译/计划创建可能栈溢出 +→ plan 序列化/反序列化耗时 O(N²) +``` + +### 6.3 ⚠️ 计划膨胀 + +``` +每个分支 ≈ 5 个节点(DynQueryCtrl + VirtualScan + RealTableScan + SysTableScan + TagScan) + +子孙数 N | 方案 A 节点数 | 方案 B 节点数 +---------|-------------|------------- + 1 | ~5 | ~11 (2×5 + 1×SetOp) + 4 | ~5 | ~29 (5×5 + 4×SetOp) + 10 | ~5 | ~64 (11×5 + 10×SetOp) + 50 | ~5 | ~314 (51×5 + 50×SetOp) +``` + +### 6.4 ⚠️ 计划缓存失效 + +| 事件 | 方案 A | 方案 B | +|------|--------|--------| +| 新增子孙 VST | 更新 descendant 列表,计划结构不变 | 计划结构改变(新增分支),缓存失效 | +| 新增 VCT | 无影响 | 无影响 | +| ALTER 子 VST ADD COLUMN | 无影响(父 schema 不变) | 无影响 | +| DROP 子 VST | 更新 descendant 列表 | 计划结构改变,缓存失效 | +| ALTER 继承关系 | 更新 descendant 列表 | 计划完全重建 | + +### 6.5 ~~PRIVATE 列的复杂交互~~ (已简化) + +在新的 PRIVATE 语义下(VCT 私有列不属于 VST schema),PRIVATE 列不会出现在投影列表中: + +```sql +SELECT val FROM parent_vst EXPAND(-1) WHERE val IS NULL; +-- val 是 parent_vst schema 的列 +-- 所有后代 VST 的 VCT 都有 val(非私有),正常参与查询 +-- VCT 的私有列(如 secret_col)不在投影中,完全不参与 +``` + +**结论**:UNION ALL + schema 投影天然解决了 PRIVATE 问题,无需运行时 NULL 替换。这是 UNION ALL 方案的一个**优势**。 + +--- + +## 7. UNION 方案的优势 + +### 7.1 ✅ 天然并行执行 + +``` +方案 A (串行):DynQueryCtrl 内部 for 循环逐 VCT 处理 + VCT_1 → scan → output → VCT_2 → scan → output → ... + +方案 B (并行):unionAllSplit 后各分支成独立 subplan + 分支_1(parent) ──→ scan ──┐ + 分支_2(child) ──→ scan ──┼→ Project(合并) + 分支_3(grand) ──→ scan ──┘ + 各分支无依赖,可在不同 qnode 并发 +``` + +### 7.2 ✅ 分支级别剪枝 + +```sql +SELECT * FROM parent_vst EXPAND(-1) WHERE val > 100; +``` +- 优化器可以为每个分支做统计信息判断 +- 如果某个 VST 下没有 VCT → 该分支在编译时直接消除 +- 方案 A 需要运行时 sys scan 后才能发现空 VST + +### 7.3 ✅ 调试直观 + EXPLAIN 友好 + +```sql +EXPLAIN SELECT * FROM parent_vst EXPAND(-1); + +-- 方案 A: +-- DynQueryCtrl (expandLevel=-1, descendants=[child,grand,child2]) +-- VirtualScan (parent_vst) +-- ... + +-- 方案 B: +-- Project +-- SetOperator (UNION ALL) +-- DynQueryCtrl (parent_vst) +-- DynQueryCtrl (child_vst) +-- DynQueryCtrl (grandchild_vst) +-- DynQueryCtrl (child_vst2) +-- 每个分支清晰可见,便于定位性能瓶颈 +``` + +### 7.4 ✅ 代码复用现有设施 + +- 复用 `translateSetOperator()`(L12201) +- 复用 `createSetOpLogicNode()`(L5071) +- 复用 `unionAllSplit()`(L1911) +- 复用 SetOperator executor +- 不需要在 DynQueryCtrl 中维护 EXPAND 特殊路径 + +--- + +## 8. 执行效率对比 + +### 8.1 资源消耗 + +| 维度 | 方案 A (EXPAND) | 方案 B (UNION) | +|------|----------------|----------------| +| Sys Scan 次数 | 1 次(全量) | N 次(每 VST 一次) | +| Catalog RPC | ~2 次 | ~4N+1 次 | +| 计划序列化大小 | O(1) | O(N) | +| 内存峰值 | ~1 份 output buffer | ~N 份 buffer | +| 数据拷贝 | VCT→输出 1 次 | VCT→分支→UNION→输出 2 次 | +| 编译耗时 | O(1) | O(N) (每分支独立翻译) | + +### 8.2 延迟对比(估算) + +``` +场景:4 个子孙 VST,每个 VST 有 50 个 VCT,每个 VCT 源表查询 10ms + +方案 A (串行): + 编译: ~5ms + Sys scan: ~20ms (1 次全量) + 数据扫描: 200 VCT × 10ms = 2000ms (串行) + 总延迟: ~2025ms + +方案 B (4 分支并行): + 编译: ~50ms (4N+1 次 RPC) + Sys scan: ~20ms × 4 = 并行后 ~20ms + 数据扫描: 每分支 50 × 10ms = 500ms (4 分支并行 → wall time 500ms) + 总延迟: ~570ms + +加速比: ~3.5x (理想情况) +``` + +但实际受限于: +- 编译时 catalog RPC 串行 +- 可用 qnode 数量有限 +- 内存压力(N 份 buffer 同时存在) +- UNION ALL 后排序开销 + +### 8.3 方案 A 并行化改进 + +方案 A 的串行执行瓶颈可以通过 **DynQueryCtrl 内部多线程** 解决: +``` +DynQueryCtrl (expandLevel=-1) + ├── Thread Pool (size=min(numVCT, maxWorkers)) + │ ├── Worker 1: VCT_1 → scan → queue + │ ├── Worker 2: VCT_2 → scan → queue + │ └── Worker N: VCT_N → scan → queue + └── Merge thread: dequeue → output +``` + +改动量约 200 行,不改变计划结构,保留所有优势。 + +--- + +## 9. 功能兼容性总表(基于 FS 规格) + +| FS 功能要求 | 方案 A (EXPAND) | 方案 B (UNION) | 说明 | +|------------|:---:|:---:|------| +| EXPAND(-1) 全部子孙 | ✅ | ✅ | | +| EXPAND(N) 层级控制 | ✅ | ✅ | | +| Schema = 被查询 VST (§3.3) | ✅ | ✅ | | +| PRIVATE VCT 私有列排除 (§3.3) | ✅ | ✅ schema 投影天然排除 | §4.7 | +| Tag 以被查询 VST 为准 (§3.3) | ✅ | ✅ | | +| WHERE 过滤 | ✅ | ✅ (需深拷贝 WHERE) | §5.3 | +| 聚合 COUNT/SUM/AVG | ✅ 直接 | ⚠️ 需外层聚合 | §5.4 | +| INTERVAL 窗口 | ✅ 直接 | ❌ 不支持 | §5.5 致命 | +| INTERVAL + SLIDING | ✅ 直接 | ❌ 不支持 | §5.5 | +| PARTITION BY + INTERVAL | ✅ 直接 | ❌ 不支持 | §5.6 | +| SESSION/STATE_WINDOW | ✅ 直接 | ❌ 不支持 | | +| ORDER BY + LIMIT | ✅ 直接 | ⚠️ 外层排序 | §5.9 | +| GROUP BY | ✅ 直接 | ⚠️ 可下推但需验证 | §5.10 | +| LAST/FIRST | ✅ 直接 | ⚠️ 需改写合并 | §5.8 | +| DISTINCT | ✅ 直接 | ✅ UNION ALL + 外层 DISTINCT | | +| JOIN 其他表 | ✅ 直接 | ⚠️ 需子查询包装 | | +| 子查询嵌套 | ✅ 直接 | ✅ | | +| Stream (§3.4 示例15) | ✅ | ❌ 致命 | §5.7 | +| Prepared Stmt 缓存 | ✅ 稳定 | ⚠️ 继承树变→失效 | §6.4 | +| 多分支继承 (A←B, A←C) | ✅ | ✅ | | +| 10 级深层继承 (§6) | ✅ O(1) | ⚠️ O(N) 膨胀 | §6.2/6.3 | + +--- + +## 10. 灵活性对比 + +### 从用户角度 + +| 能力 | 方案 A | 方案 B | +|------|--------|--------| +| 一条 SQL 搞定全部 | ✅ | ✅ (内部透明) | +| 自动发现新增子孙 | ✅ | ✅ | +| 直接 INTERVAL/窗口 | ✅ | ❌ | +| 直接聚合 | ✅ | ⚠️ 仅简单聚合 | +| 直接 JOIN | ✅ | ❌ (需嵌套) | +| Stream 支持 | ✅ | ❌ | +| EXPLAIN 可读性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | + +### 从引擎开发者角度 + +| 能力 | 方案 A | 方案 B | +|------|--------|--------| +| 并行执行 | 需加多线程 (~200行) | ✅ 天然 | +| 分支剪枝 | 运行时 | ✅ 编译时 | +| PRIVATE 实现 | ✅ schema 投影排除 | ✅ schema 投影排除(天然) | +| 代码量 | 已实现 ~150 行 | 需 ~500 行改写 + 无法解决 Stream/INTERVAL | +| 复用现有设施 | 少(专用逻辑) | ✅ 多(UNION + Exchange) | +| 计划缓存 | ✅ 稳定 | ⚠️ 易失效 | +| 新增 VST 时的影响 | 无(运行时动态) | 缓存失效需重编译 | + +--- + +## 11. 总结 + +### 评分表 + +| 维度 | 方案 A (EXPAND) | 方案 B (UNION) | +|------|:---:|:---:| +| FS 功能完整性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | +| 并行执行能力 | ⭐⭐⭐ (可改进到⭐⭐⭐⭐) | ⭐⭐⭐⭐⭐ | +| 元数据效率 | ⭐⭐⭐⭐⭐ | ⭐⭐ | +| 计划紧凑性 | ⭐⭐⭐⭐⭐ | ⭐⭐ | +| PRIVATE 列支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| 深层继承 (10级) | ⭐⭐⭐⭐⭐ | ⭐⭐ | +| 代码简洁/复用 | ⭐⭐⭐ | ⭐⭐⭐⭐ | +| EXPLAIN 直观性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | +| 计划缓存稳定 | ⭐⭐⭐⭐⭐ | ⭐⭐ | +| Stream/INTERVAL | ⭐⭐⭐⭐⭐ | ⭐ (致命缺陷) | + +### 结论 + +**方案 A(EXPAND)是唯一满足 FS 全部规格的方案**: + +1. **Stream 致命** → UNION 完全无法支持(§5.7),TDengine Stream 架构不支持多源 UNION +2. **INTERVAL 致命** → 无法对 UNION 子查询做窗口计算(§5.5),任何 workaround 都有语义损失 +3. **PRIVATE VCT 私有列** → 两种方案都通过 schema 投影天然排除,无运行时开销 +4. **元数据效率** → 4N+1 次 RPC 在深层继承时不可接受(§6.1) +5. **并行可后补** → DynQueryCtrl 内部加线程池即可达到方案 B 的并行度(§8.3) +6. **实现代价** → 方案 A 已完成且仅 ~150 行,方案 B 需 ~500 行且仍无法解决致命问题 + +### 一句话 + +> **EXPAND 在运行时做智能调度(代价:串行,可通过线程池补齐);UNION 在编译时做静态展开(代价:丧失窗口/流计算,无法修复)。对时序数据库,窗口计算和流计算是核心能力,不可放弃,因此 EXPAND 是正确且唯一的选择。** diff --git a/include/common/systable.h b/include/common/systable.h index d0290c99990c..0ada25319ad7 100644 --- a/include/common/systable.h +++ b/include/common/systable.h @@ -88,6 +88,7 @@ extern "C" { #define TSDB_INS_TABLE_ROLE_PRIVILEGES "ins_role_privileges" #define TSDB_INS_TABLE_ROLE_COL_PRIVILEGES "ins_role_column_privileges" #define TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING "ins_virtual_tables_referencing" +#define TSDB_INS_TABLE_VSTABLE_INHERITS "ins_vstable_inherits" #define TSDB_INS_TABLE_SECURITY_POLICIES "ins_security_policies" #define TSDB_PERFORMANCE_SCHEMA_DB "performance_schema" diff --git a/include/common/tmsg.h b/include/common/tmsg.h index bc26985b49c6..4cf4d99db6c4 100644 --- a/include/common/tmsg.h +++ b/include/common/tmsg.h @@ -217,6 +217,7 @@ typedef enum _mgmt_table { TSDB_MGMT_TABLE_XNODE_FULL, TSDB_MGMT_TABLE_VIRTUAL_TABLES_REFERENCING, TSDB_MGMT_TABLE_SECURITY_POLICIES, + TSDB_MGMT_TABLE_VSTABLE_INHERITS, TSDB_MGMT_TABLE_MAX, } EShowType; @@ -251,6 +252,8 @@ typedef enum { #define TSDB_ALTER_TABLE_ADD_COLUMN_WITH_COLUMN_REF 18 #define TSDB_ALTER_TABLE_UPDATE_MULTI_TABLE_TAG_VAL 19 // alter multiple tag values of multi tables #define TSDB_ALTER_TABLE_UPDATE_CHILD_TABLE_TAG_VAL 20 // alter multiple tag values of the child tables of a stable +#define TSDB_ALTER_TABLE_ADD_BASE_ON 21 +#define TSDB_ALTER_TABLE_DROP_BASE_ON 22 #define TSDB_FILL_NONE 0 #define TSDB_FILL_NULL 1 @@ -569,6 +572,7 @@ typedef enum ENodeType { QUERY_NODE_SHOW_XNODE_AGENTS_STMT, QUERY_NODE_SHOW_XNODE_JOBS_STMT, QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT, + QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT, QUERY_NODE_SHOW_SECURITY_POLICIES_STMT, QUERY_NODE_SHOW_CPU_ALLOCATION_STMT, @@ -899,6 +903,7 @@ typedef struct { int32_t numOfTagRefs; SColRef* pTagRefs; int8_t secureDelete; + int8_t hasInheritors; // 1 if other VSTs inherit from this STB (non-leaf) } STableMetaRsp; typedef struct { @@ -1317,6 +1322,11 @@ typedef struct { int8_t virtualStb; int8_t secureDelete; int8_t securityLevel; + // VST inheritance + int8_t numParents; + char parentStbFNames[TSDB_MAX_VST_PARENTS][TSDB_TABLE_FNAME_LEN]; + int16_t ownColStart; + int16_t ownTagStart; } SMCreateStbReq; int32_t tSerializeSMCreateStbReq(void* buf, int32_t bufLen, SMCreateStbReq* pReq); @@ -1359,6 +1369,9 @@ typedef struct { SArray* pTypeMods; int8_t secureDelete; int8_t securityLevel; + // VST inheritance: for ADD_BASE_ON / DROP_BASE_ON + int8_t numParents; + char parentStbFNames[TSDB_MAX_VST_PARENTS][TSDB_TABLE_FNAME_LEN]; } SMAlterStbReq; int32_t tSerializeSMAlterStbReq(void* buf, int32_t bufLen, SMAlterStbReq* pReq); @@ -2124,6 +2137,8 @@ typedef struct { int32_t numOfTagRefs; SColRef* pTagRefs; int8_t secureDelete; + int8_t numParents; + char parentStbNames[TSDB_MAX_VST_PARENTS][TSDB_TABLE_NAME_LEN]; } STableCfg; typedef STableCfg STableCfgRsp; @@ -2135,6 +2150,29 @@ int32_t tSerializeSTableCfgRsp(void* buf, int32_t bufLen, STableCfgRsp* pRsp); int32_t tDeserializeSTableCfgRsp(void* buf, int32_t bufLen, STableCfgRsp* pRsp); void tFreeSTableCfgRsp(STableCfgRsp* pRsp); +// VST leaf descendants query +typedef struct { + char dbFName[TSDB_DB_FNAME_LEN]; + int64_t suid; +} SVstLeavesReq; + +typedef struct { + char dbFName[TSDB_DB_FNAME_LEN]; + char stbName[TSDB_TABLE_NAME_LEN]; + int64_t suid; +} SVstLeafInfo; + +typedef struct { + int32_t numLeaves; + SVstLeafInfo* pLeaves; +} SVstLeavesRsp; + +int32_t tSerializeSVstLeavesReq(void* buf, int32_t bufLen, SVstLeavesReq* pReq); +int32_t tDeserializeSVstLeavesReq(void* buf, int32_t bufLen, SVstLeavesReq* pReq); +int32_t tSerializeSVstLeavesRsp(void* buf, int32_t bufLen, SVstLeavesRsp* pRsp); +int32_t tDeserializeSVstLeavesRsp(void* buf, int32_t bufLen, SVstLeavesRsp* pRsp); +void tFreeSVstLeavesRsp(SVstLeavesRsp* pRsp); + typedef struct { SMsgHead header; tb_uid_t suid; @@ -4918,6 +4956,11 @@ typedef struct SVCreateStbReq { int8_t virtualStb; int8_t secureDelete; int8_t securityLevel; + // VST inheritance + int8_t numParents; + int64_t parentSuids[TSDB_MAX_VST_PARENTS]; + int16_t ownColStart; + int16_t ownTagStart; } SVCreateStbReq; int tEncodeSVCreateStbReq(SEncoder* pCoder, const SVCreateStbReq* pReq); @@ -4932,6 +4975,14 @@ typedef struct SVDropStbReq { int32_t tEncodeSVDropStbReq(SEncoder* pCoder, const SVDropStbReq* pReq); int32_t tDecodeSVDropStbReq(SDecoder* pCoder, SVDropStbReq* pReq); +// TDMT_VND_CHECK_HAS_CTB ============== +typedef struct SVCheckHasCtbReq { + int64_t suid; +} SVCheckHasCtbReq; + +int32_t tSerializeSVCheckHasCtbReq(void* buf, int32_t bufLen, const SVCheckHasCtbReq* pReq); +int32_t tDeserializeSVCheckHasCtbReq(const void* buf, int32_t bufLen, SVCheckHasCtbReq* pReq); + // TDMT_VND_CREATE_TABLE ============== #define TD_CREATE_IF_NOT_EXISTS 0x1 #define TD_CREATE_NORMAL_TB_IN_STREAM 0x2 diff --git a/include/common/tmsgdef.h b/include/common/tmsgdef.h index 8bc1acd3e9d7..388106fe8b55 100644 --- a/include/common/tmsgdef.h +++ b/include/common/tmsgdef.h @@ -286,7 +286,7 @@ TD_DEF_MSG_TYPE(TDMT_MND_GET_TABLE_TSMA, "get-table-tsma", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_MND_GET_TSMA, "get-tsma", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_MND_DROP_TB_WITH_TSMA, "drop-tb-with-tsma", NULL, NULL) - TD_DEF_MSG_TYPE(TDMT_MND_UNUSED7, "mnd-unused7", NULL, NULL) + TD_DEF_MSG_TYPE(TDMT_MND_GET_VST_LEAVES, "get-vst-leaves", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_MND_UNUSED8, "mnd-unused8", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_MND_UNUSED9, "mnd-unused9", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_MND_UNUSEDa, "mnd-unuseda", NULL, NULL) @@ -354,6 +354,7 @@ TD_DEF_MSG_TYPE(TDMT_VND_TABLE_NAME, "vnode-table-name", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_VND_VSUBTABLES_META, "vnode-virtual_stables-meta", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_VND_VSTB_REF_DBS, "vnode-virtual-stables-ref-dbs", NULL, NULL) + TD_DEF_MSG_TYPE(TDMT_VND_CHECK_HAS_CTB, "vnode-check-has-ctb", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_VND_QUERY_SSMIGRATE_PROGRESS, "vnode-query-ssmigrate-progress", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_VND_SSMIGRATE_FILESET, "vnode-ssmigrate-fileset", NULL, NULL) TD_DEF_MSG_TYPE(TDMT_VND_FOLLOWER_SSMIGRATE, "vnode-follower-ssmigrate", NULL, NULL) diff --git a/include/libs/catalog/catalog.h b/include/libs/catalog/catalog.h index b032e52608ea..34a32e608a44 100644 --- a/include/libs/catalog/catalog.h +++ b/include/libs/catalog/catalog.h @@ -395,6 +395,9 @@ int32_t catalogGetExpiredTsmas(SCatalog* pCtg, STSMAVersion** tsmas, uint32_t* n int32_t catalogGetDBCfg(SCatalog* pCtg, SRequestConnInfo* pConn, const char* dbFName, SDbCfgInfo* pDbCfg); +int32_t catalogGetVstLeaves(SCatalog* pCtg, SRequestConnInfo* pConn, const char* dbFName, int64_t suid, + SVstLeavesRsp* pRsp); + int32_t catalogGetIndexMeta(SCatalog* pCtg, SRequestConnInfo* pConn, const char* indexName, SIndexInfo* pInfo); int32_t catalogGetTableIndex(SCatalog* pCtg, SRequestConnInfo* pConn, const SName* pTableName, SArray** pRes); diff --git a/include/libs/nodes/cmdnodes.h b/include/libs/nodes/cmdnodes.h index be46caf8a99a..d0963f5ed555 100644 --- a/include/libs/nodes/cmdnodes.h +++ b/include/libs/nodes/cmdnodes.h @@ -318,6 +318,7 @@ typedef struct SCreateTableStmt { SNodeList* pCols; SNodeList* pTags; STableOptions* pOptions; + SNodeList* pBaseOnList; // list of SRealTableNode for BASE ON parents (NULL = no inheritance) } SCreateTableStmt; typedef struct SCreateVTableStmt { diff --git a/include/libs/qcom/query.h b/include/libs/qcom/query.h index 69100d39a7ef..8bffa296efe3 100644 --- a/include/libs/qcom/query.h +++ b/include/libs/qcom/query.h @@ -153,7 +153,8 @@ typedef struct STableMeta { uint8_t virtualStb : 1; uint8_t isAudit : 1; uint8_t secLvl : 3; // security level (0-4), mapped from STableMetaRsp.secLvl - uint8_t reserved : 3; + uint8_t hasInheritors : 1; // 1 if other VSTs inherit from this STB + uint8_t reserved : 2; }; }; int64_t ownerId; diff --git a/include/util/taoserror.h b/include/util/taoserror.h index 25082513f913..73dce6f197ac 100644 --- a/include/util/taoserror.h +++ b/include/util/taoserror.h @@ -446,6 +446,17 @@ int32_t taosGetErrSize(); #define TSDB_CODE_MND_FIELD_CONFLICT_WITH_TSMA TAOS_DEF_ERROR_CODE(0, 0x03C8) #define TSDB_CODE_MND_EXCEED_MAX_COL_ID TAOS_DEF_ERROR_CODE(0, 0x03C9) +// mnode-stable-vst-inherit +#define TSDB_CODE_MND_VST_HAS_CHILDREN TAOS_DEF_ERROR_CODE(0, 0x04A2) +#define TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL TAOS_DEF_ERROR_CODE(0, 0x04A3) +#define TSDB_CODE_MND_VST_COL_NAME_CONFLICT TAOS_DEF_ERROR_CODE(0, 0x04A4) +#define TSDB_CODE_MND_VST_CIRCULAR_INHERIT TAOS_DEF_ERROR_CODE(0, 0x04A5) +#define TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED TAOS_DEF_ERROR_CODE(0, 0x04A6) +#define TSDB_CODE_MND_VST_PARENT_HAS_VCT TAOS_DEF_ERROR_CODE(0, 0x04A7) +#define TSDB_CODE_MND_VST_NOT_LEAF TAOS_DEF_ERROR_CODE(0, 0x04A8) +#define TSDB_CODE_MND_VST_DROP_BASE_MIN_COLS TAOS_DEF_ERROR_CODE(0, 0x04A9) +#define TSDB_CODE_MND_VST_CROSS_DB TAOS_DEF_ERROR_CODE(0, 0x04AA) + // mnode-trans #define TSDB_CODE_MND_TRANS_ALREADY_EXIST TAOS_DEF_ERROR_CODE(0, 0x03D0) #define TSDB_CODE_MND_TRANS_NOT_EXIST TAOS_DEF_ERROR_CODE(0, 0x03D1) diff --git a/include/util/tdef.h b/include/util/tdef.h index c51c3ad5d66f..53da63565180 100644 --- a/include/util/tdef.h +++ b/include/util/tdef.h @@ -358,6 +358,7 @@ typedef enum { #define TSDB_MAX_BYTES_PER_ROW_VIRTUAL 524283 #define TSDB_MAX_TAGS_LEN 16384 #define TSDB_MAX_TAGS 128 +#define TSDB_MAX_VST_PARENTS 10 #define TSDB_MAX_COL_TAG_NUM (TSDB_MAX_COLUMNS + TSDB_MAX_TAGS) #define TSDB_MAX_JSON_TAG_LEN 16384 diff --git a/source/common/src/msg/tmsg.c b/source/common/src/msg/tmsg.c index 0be81707cfe3..68853e7aaed9 100644 --- a/source/common/src/msg/tmsg.c +++ b/source/common/src/msg/tmsg.c @@ -844,6 +844,14 @@ int32_t tSerializeSMCreateStbReq(void *buf, int32_t bufLen, SMCreateStbReq *pReq TAOS_CHECK_EXIT(tEncodeI8(&encoder, pReq->secureDelete)); TAOS_CHECK_EXIT(tEncodeI8(&encoder, pReq->securityLevel)); + // VST inheritance + TAOS_CHECK_EXIT(tEncodeI8(&encoder, pReq->numParents)); + for (int32_t i = 0; i < pReq->numParents; ++i) { + TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pReq->parentStbFNames[i])); + } + TAOS_CHECK_EXIT(tEncodeI16(&encoder, pReq->ownColStart)); + TAOS_CHECK_EXIT(tEncodeI16(&encoder, pReq->ownTagStart)); + tEndEncode(&encoder); _exit: @@ -975,6 +983,18 @@ int32_t tDeserializeSMCreateStbReq(void *buf, int32_t bufLen, SMCreateStbReq *pR pReq->securityLevel = TSDB_DEFAULT_SECURITY_LEVEL; } + // VST inheritance + if (!tDecodeIsEnd(&decoder)) { + TAOS_CHECK_EXIT(tDecodeI8(&decoder, &pReq->numParents)); + for (int32_t i = 0; i < pReq->numParents; ++i) { + TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pReq->parentStbFNames[i])); + } + TAOS_CHECK_EXIT(tDecodeI16(&decoder, &pReq->ownColStart)); + TAOS_CHECK_EXIT(tDecodeI16(&decoder, &pReq->ownTagStart)); + } else { + pReq->numParents = 0; + } + tEndDecode(&decoder); _exit: @@ -1098,6 +1118,13 @@ int32_t tSerializeSMAlterStbReq(void *buf, int32_t bufLen, SMAlterStbReq *pReq) TAOS_CHECK_EXIT(tEncodeI8(&encoder, pReq->secureDelete)); TAOS_CHECK_EXIT(tEncodeI8(&encoder, pReq->securityLevel)); } + // VST inheritance: ADD/DROP BASE ON + if (pReq->alterType == TSDB_ALTER_TABLE_ADD_BASE_ON || pReq->alterType == TSDB_ALTER_TABLE_DROP_BASE_ON) { + TAOS_CHECK_EXIT(tEncodeI8(&encoder, pReq->numParents)); + for (int32_t i = 0; i < pReq->numParents; ++i) { + TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pReq->parentStbFNames[i])); + } + } tEndEncode(&encoder); _exit: @@ -1192,6 +1219,15 @@ int32_t tDeserializeSMAlterStbReq(void *buf, int32_t bufLen, SMAlterStbReq *pReq TAOS_CHECK_EXIT(tDecodeI8(&decoder, &pReq->securityLevel)); } } + // VST inheritance: ADD/DROP BASE ON + pReq->numParents = 0; + if ((pReq->alterType == TSDB_ALTER_TABLE_ADD_BASE_ON || pReq->alterType == TSDB_ALTER_TABLE_DROP_BASE_ON) && + !tDecodeIsEnd(&decoder)) { + TAOS_CHECK_EXIT(tDecodeI8(&decoder, &pReq->numParents)); + for (int32_t i = 0; i < pReq->numParents; ++i) { + TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pReq->parentStbFNames[i])); + } + } tEndDecode(&decoder); _exit: @@ -6936,6 +6972,12 @@ int32_t tSerializeSTableCfgRsp(void *buf, int32_t bufLen, STableCfgRsp *pRsp) { TAOS_CHECK_EXIT(tEncodeI64v(&encoder, pRsp->ownerId)); TAOS_CHECK_EXIT(tEncodeI8(&encoder, pRsp->secureDelete)); + // VST inheritance + TAOS_CHECK_EXIT(tEncodeI8(&encoder, pRsp->numParents)); + for (int8_t i = 0; i < pRsp->numParents; ++i) { + TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pRsp->parentStbNames[i])); + } + tEndEncode(&encoder); _exit: @@ -7072,6 +7114,16 @@ int32_t tDeserializeSTableCfgRsp(void *buf, int32_t bufLen, STableCfgRsp *pRsp) pRsp->secureDelete = 0; } + // VST inheritance + if (!tDecodeIsEnd(&decoder)) { + TAOS_CHECK_EXIT(tDecodeI8(&decoder, &pRsp->numParents)); + for (int8_t i = 0; i < pRsp->numParents; ++i) { + TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pRsp->parentStbNames[i])); + } + } else { + pRsp->numParents = 0; + } + tEndDecode(&decoder); _exit: @@ -7094,6 +7146,95 @@ void tFreeSTableCfgRsp(STableCfgRsp *pRsp) { taosArrayDestroy(pRsp->pFuncs); } +int32_t tSerializeSVstLeavesReq(void *buf, int32_t bufLen, SVstLeavesReq *pReq) { + SEncoder encoder = {0}; + int32_t code = 0; + int32_t lino; + int32_t tlen; + tEncoderInit(&encoder, buf, bufLen); + TAOS_CHECK_EXIT(tStartEncode(&encoder)); + TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pReq->dbFName)); + TAOS_CHECK_EXIT(tEncodeI64(&encoder, pReq->suid)); + tEndEncode(&encoder); +_exit: + if (code) { + tlen = code; + } else { + tlen = encoder.pos; + } + tEncoderClear(&encoder); + return tlen; +} + +int32_t tDeserializeSVstLeavesReq(void *buf, int32_t bufLen, SVstLeavesReq *pReq) { + SDecoder decoder = {0}; + int32_t code = 0; + int32_t lino; + tDecoderInit(&decoder, buf, bufLen); + TAOS_CHECK_EXIT(tStartDecode(&decoder)); + TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pReq->dbFName)); + TAOS_CHECK_EXIT(tDecodeI64(&decoder, &pReq->suid)); + tEndDecode(&decoder); +_exit: + tDecoderClear(&decoder); + return code; +} + +int32_t tSerializeSVstLeavesRsp(void *buf, int32_t bufLen, SVstLeavesRsp *pRsp) { + SEncoder encoder = {0}; + int32_t code = 0; + int32_t lino; + int32_t tlen; + tEncoderInit(&encoder, buf, bufLen); + TAOS_CHECK_EXIT(tStartEncode(&encoder)); + TAOS_CHECK_EXIT(tEncodeI32(&encoder, pRsp->numLeaves)); + for (int32_t i = 0; i < pRsp->numLeaves; ++i) { + TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pRsp->pLeaves[i].dbFName)); + TAOS_CHECK_EXIT(tEncodeCStr(&encoder, pRsp->pLeaves[i].stbName)); + TAOS_CHECK_EXIT(tEncodeI64(&encoder, pRsp->pLeaves[i].suid)); + } + tEndEncode(&encoder); +_exit: + if (code) { + tlen = code; + } else { + tlen = encoder.pos; + } + tEncoderClear(&encoder); + return tlen; +} + +int32_t tDeserializeSVstLeavesRsp(void *buf, int32_t bufLen, SVstLeavesRsp *pRsp) { + SDecoder decoder = {0}; + int32_t code = 0; + int32_t lino; + tDecoderInit(&decoder, buf, bufLen); + TAOS_CHECK_EXIT(tStartDecode(&decoder)); + TAOS_CHECK_EXIT(tDecodeI32(&decoder, &pRsp->numLeaves)); + if (pRsp->numLeaves > 0) { + pRsp->pLeaves = taosMemoryCalloc(pRsp->numLeaves, sizeof(SVstLeafInfo)); + if (NULL == pRsp->pLeaves) { + code = terrno; + goto _exit; + } + for (int32_t i = 0; i < pRsp->numLeaves; ++i) { + TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pRsp->pLeaves[i].dbFName)); + TAOS_CHECK_EXIT(tDecodeCStrTo(&decoder, pRsp->pLeaves[i].stbName)); + TAOS_CHECK_EXIT(tDecodeI64(&decoder, &pRsp->pLeaves[i].suid)); + } + } + tEndDecode(&decoder); +_exit: + tDecoderClear(&decoder); + return code; +} + +void tFreeSVstLeavesRsp(SVstLeavesRsp *pRsp) { + if (pRsp) { + taosMemoryFreeClear(pRsp->pLeaves); + } +} + int32_t tSerializeSCreateDbReq(void *buf, int32_t bufLen, SCreateDbReq *pReq) { SEncoder encoder = {0}; int32_t code = 0; @@ -10006,6 +10147,9 @@ static int32_t tEncodeSTableMetaRsp(SEncoder *pEncoder, STableMetaRsp *pRsp) { } } + // Encode hasInheritors (VST inheritance) + TAOS_CHECK_RETURN(tEncodeI8(pEncoder, pRsp->hasInheritors)); + return 0; } @@ -10104,6 +10248,12 @@ static int32_t tDecodeSTableMetaRsp(SDecoder *pDecoder, STableMetaRsp *pRsp) { } } + // Decode hasInheritors (VST inheritance, backward compatible) + pRsp->hasInheritors = 0; + if (!tDecodeIsEnd(pDecoder)) { + TAOS_CHECK_RETURN(tDecodeI8(pDecoder, &pRsp->hasInheritors)); + } + return 0; } @@ -14973,6 +15123,15 @@ int tEncodeSVCreateStbReq(SEncoder *pCoder, const SVCreateStbReq *pReq) { TAOS_CHECK_EXIT(tEncodeI64v(pCoder, pReq->ownerId)); TAOS_CHECK_EXIT(tEncodeI8(pCoder, pReq->secureDelete)); TAOS_CHECK_EXIT(tEncodeI8(pCoder, pReq->securityLevel)); + + // VST inheritance + TAOS_CHECK_EXIT(tEncodeI8(pCoder, pReq->numParents)); + for (int32_t i = 0; i < pReq->numParents; ++i) { + TAOS_CHECK_EXIT(tEncodeI64(pCoder, pReq->parentSuids[i])); + } + TAOS_CHECK_EXIT(tEncodeI16(pCoder, pReq->ownColStart)); + TAOS_CHECK_EXIT(tEncodeI16(pCoder, pReq->ownTagStart)); + tEndEncode(pCoder); _exit: @@ -15034,6 +15193,19 @@ int tDecodeSVCreateStbReq(SDecoder *pCoder, SVCreateStbReq *pReq) { } else { pReq->securityLevel = 0; } + + // VST inheritance + if (!tDecodeIsEnd(pCoder)) { + TAOS_CHECK_EXIT(tDecodeI8(pCoder, &pReq->numParents)); + for (int32_t i = 0; i < pReq->numParents; ++i) { + TAOS_CHECK_EXIT(tDecodeI64(pCoder, &pReq->parentSuids[i])); + } + TAOS_CHECK_EXIT(tDecodeI16(pCoder, &pReq->ownColStart)); + TAOS_CHECK_EXIT(tDecodeI16(pCoder, &pReq->ownTagStart)); + } else { + pReq->numParents = 0; + } + tEndDecode(pCoder); _exit: @@ -15454,6 +15626,27 @@ int32_t tDecodeSVDropStbReq(SDecoder *pCoder, SVDropStbReq *pReq) { return 0; } +int32_t tSerializeSVCheckHasCtbReq(void *buf, int32_t bufLen, const SVCheckHasCtbReq *pReq) { + SEncoder encoder = {0}; + tEncoderInit(&encoder, buf, bufLen); + TAOS_CHECK_RETURN(tStartEncode(&encoder)); + TAOS_CHECK_RETURN(tEncodeI64(&encoder, pReq->suid)); + tEndEncode(&encoder); + int32_t tlen = encoder.pos; + tEncoderClear(&encoder); + return tlen; +} + +int32_t tDeserializeSVCheckHasCtbReq(const void *buf, int32_t bufLen, SVCheckHasCtbReq *pReq) { + SDecoder decoder = {0}; + tDecoderInit(&decoder, (char *)buf, bufLen); + TAOS_CHECK_RETURN(tStartDecode(&decoder)); + TAOS_CHECK_RETURN(tDecodeI64(&decoder, &pReq->suid)); + tEndDecode(&decoder); + tDecoderClear(&decoder); + return 0; +} + static int32_t tEncodeSSubmitBlkRsp(SEncoder *pEncoder, const SSubmitBlkRsp *pBlock) { int32_t code = 0; int32_t lino; diff --git a/source/common/src/systable.c b/source/common/src/systable.c index bef4a85fb9b5..cc0a86a82028 100644 --- a/source/common/src/systable.c +++ b/source/common/src/systable.c @@ -801,6 +801,14 @@ static const SSysDbTableSchema virtualTablesReferencing[] = { {.name = "err_msg", .bytes = TSDB_SHOW_VALIDATE_VIRTUAL_TABLE_ERROR + VARSTR_HEADER_SIZE, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, }; +static const SSysDbTableSchema vstableInheritsSchema[] = { + {.name = "db_name", .bytes = SYSTABLE_SCH_DB_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "parent_stable_name", .bytes = SYSTABLE_SCH_TABLE_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "parent_uid", .bytes = 8, .type = TSDB_DATA_TYPE_BIGINT, .sysInfo = false}, + {.name = "child_stable_name", .bytes = SYSTABLE_SCH_TABLE_NAME_LEN, .type = TSDB_DATA_TYPE_VARCHAR, .sysInfo = false}, + {.name = "child_uid", .bytes = 8, .type = TSDB_DATA_TYPE_BIGINT, .sysInfo = false}, + {.name = "create_time", .bytes = 8, .type = TSDB_DATA_TYPE_TIMESTAMP, .sysInfo = false}, +}; /** @@ -874,6 +882,7 @@ static const SSysTableMeta infosMeta[] = { {TSDB_INS_TABLE_XNODE_JOBS, xnodeTaskJobSchema, tListLen(xnodeTaskJobSchema), true, PRIV_CAT_PRIVILEGED}, {TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, virtualTablesReferencing, tListLen(virtualTablesReferencing), true, PRIV_CAT_PRIVILEGED}, {TSDB_INS_TABLE_SECURITY_POLICIES, securityPoliciesSchema, tListLen(securityPoliciesSchema), true, PRIV_CAT_SECURITY}, + {TSDB_INS_TABLE_VSTABLE_INHERITS, vstableInheritsSchema, tListLen(vstableInheritsSchema), false, PRIV_CAT_BASIC}, }; static const SSysDbTableSchema connectionsSchema[] = { diff --git a/source/dnode/mgmt/mgmt_mnode/src/mmHandle.c b/source/dnode/mgmt/mgmt_mnode/src/mmHandle.c index 82e18b6b852c..d118d33ecfeb 100644 --- a/source/dnode/mgmt/mgmt_mnode/src/mmHandle.c +++ b/source/dnode/mgmt/mgmt_mnode/src/mmHandle.c @@ -323,6 +323,7 @@ SArray *mmGetMsgHandles() { if (dmSetMgmtHandle(pArray, TDMT_SCH_MERGE_FETCH, mmPutMsgToFetchQueue, 1) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_SCH_TASK_NOTIFY, mmPutMsgToFetchQueue, 1) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_CREATE_STB_RSP, mmPutMsgToWriteQueue, 0) == NULL) goto _OVER; + if (dmSetMgmtHandle(pArray, TDMT_VND_CHECK_HAS_CTB_RSP, mmPutMsgToWriteQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_ALTER_STB_RSP, mmPutMsgToWriteQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_DROP_STB_RSP, mmPutMsgToWriteQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_DROP_TTL_TABLE_RSP, mmPutMsgToWriteQueue, 0) == NULL) goto _OVER; diff --git a/source/dnode/mgmt/mgmt_vnode/src/vmHandle.c b/source/dnode/mgmt/mgmt_vnode/src/vmHandle.c index abc3c78d1246..35f562773761 100644 --- a/source/dnode/mgmt/mgmt_vnode/src/vmHandle.c +++ b/source/dnode/mgmt/mgmt_vnode/src/vmHandle.c @@ -1976,6 +1976,7 @@ SArray *vmGetMsgHandles() { if (dmSetMgmtHandle(pArray, TDMT_VND_TABLE_META, vmPutMsgToFetchQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_VSUBTABLES_META, vmPutMsgToFetchQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_VSTB_REF_DBS, vmPutMsgToFetchQueue, 0) == NULL) goto _OVER; + if (dmSetMgmtHandle(pArray, TDMT_VND_CHECK_HAS_CTB, vmPutMsgToFetchQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_TABLE_CFG, vmPutMsgToFetchQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_BATCH_META, vmPutMsgToFetchQueue, 0) == NULL) goto _OVER; if (dmSetMgmtHandle(pArray, TDMT_VND_TABLES_META, vmPutMsgToFetchQueue, 0) == NULL) goto _OVER; diff --git a/source/dnode/mnode/impl/inc/mndDef.h b/source/dnode/mnode/impl/inc/mndDef.h index 5561fd7f9f0e..49f1db996f8b 100644 --- a/source/dnode/mnode/impl/inc/mndDef.h +++ b/source/dnode/mnode/impl/inc/mndDef.h @@ -989,6 +989,11 @@ typedef struct { uint32_t padding : 5; }; }; + // VST inheritance + int8_t numParents; + int64_t parentSuids[TSDB_MAX_VST_PARENTS]; + int16_t ownColStart; + int16_t ownTagStart; } SStbObj; typedef struct { diff --git a/source/dnode/mnode/impl/src/mndShow.c b/source/dnode/mnode/impl/src/mndShow.c index cbc6196f525d..b22338deb400 100644 --- a/source/dnode/mnode/impl/src/mndShow.c +++ b/source/dnode/mnode/impl/src/mndShow.c @@ -195,6 +195,8 @@ static int32_t convertToRetrieveType(char *name, int32_t len) { type = TSDB_MGMT_TABLE_SECURITY_POLICIES; } else if (strncasecmp(name, TSDB_INS_TABLE_VIRTUAL_TABLES_REFERENCING, len) == 0) { type = TSDB_MGMT_TABLE_VIRTUAL_TABLES_REFERENCING; + } else if (strncasecmp(name, TSDB_INS_TABLE_VSTABLE_INHERITS, len) == 0) { + type = TSDB_MGMT_TABLE_VSTABLE_INHERITS; } else { mError("invalid show name:%s len:%d", name, len); } diff --git a/source/dnode/mnode/impl/src/mndStb.c b/source/dnode/mnode/impl/src/mndStb.c index cbb189610f7f..68cf204daae0 100644 --- a/source/dnode/mnode/impl/src/mndStb.c +++ b/source/dnode/mnode/impl/src/mndStb.c @@ -38,7 +38,8 @@ #define STB_VER_SUPPORT_COMP 2 #define STB_VER_SUPPORT_VIRTUAL 3 #define STB_VER_SUPPORT_OWNER 4 -#define STB_VER_NUMBER STB_VER_SUPPORT_OWNER +#define STB_VER_SUPPORT_INHERIT 5 +#define STB_VER_NUMBER STB_VER_SUPPORT_INHERIT #define STB_RESERVE_SIZE 51 static int32_t mndStbActionInsert(SSdb *pSdb, SStbObj *pStb); @@ -55,6 +56,7 @@ static int32_t mndProcessTrimDbWalRsp(SRpcMsg *pReq); static int32_t mndProcessTableMetaReq(SRpcMsg *pReq); static int32_t mndRetrieveStb(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows); static int32_t mndRetrieveStbCol(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows); +static int32_t mndRetrieveVstableInherits(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows); static void mndCancelGetNextStb(SMnode *pMnode, void *pIter); static int32_t mndProcessTableCfgReq(SRpcMsg *pReq); static int32_t mndAlterStbImp(SMnode *pMnode, SRpcMsg *pReq, SDbObj *pDb, SStbObj *pStb, bool needRsp, @@ -69,6 +71,7 @@ static int32_t mndProcessDropStbReqFromMNode(SRpcMsg *pReq); static int32_t mndProcessDropTbWithTsma(SRpcMsg *pReq); static int32_t mndProcessFetchTtlExpiredTbs(SRpcMsg *pReq); static int32_t mndProcessAuditRecordRsp(SRpcMsg *pRsp); +static int32_t mndProcessGetVstLeavesReq(SRpcMsg *pReq); int32_t mndInitStb(SMnode *pMnode) { SSdbTable table = { @@ -85,6 +88,7 @@ int32_t mndInitStb(SMnode *pMnode) { mndSetMsgHandle(pMnode, TDMT_MND_ALTER_STB, mndProcessAlterStbReq); mndSetMsgHandle(pMnode, TDMT_MND_DROP_STB, mndProcessDropStbReq); mndSetMsgHandle(pMnode, TDMT_VND_CREATE_STB_RSP, mndTransProcessRsp); + mndSetMsgHandle(pMnode, TDMT_VND_CHECK_HAS_CTB_RSP, mndTransProcessRsp); mndSetMsgHandle(pMnode, TDMT_VND_DROP_TTL_TABLE_RSP, mndProcessDropTtltbRsp); mndSetMsgHandle(pMnode, TDMT_VND_TRIM_RSP, mndTransProcessRsp); mndSetMsgHandle(pMnode, TDMT_VND_TRIM_WAL_RSP, mndProcessTrimDbWalRsp); @@ -108,6 +112,7 @@ int32_t mndInitStb(SMnode *pMnode) { // mndSetMsgHandle(pMnode, TDMT_VND_CREATE_INDEX_RSP, mndTransProcessRsp); // mndSetMsgHandle(pMnode, TDMT_VND_DROP_INDEX_RSP, mndTransProcessRsp); mndSetMsgHandle(pMnode, TDMT_VND_AUDIT_RECORD_RSP, mndProcessAuditRecordRsp); + mndSetMsgHandle(pMnode, TDMT_MND_GET_VST_LEAVES, mndProcessGetVstLeavesReq); mndAddShowRetrieveHandle(pMnode, TSDB_MGMT_TABLE_STB, mndRetrieveStb); mndAddShowFreeIterHandle(pMnode, TSDB_MGMT_TABLE_STB, mndCancelGetNextStb); @@ -115,6 +120,9 @@ int32_t mndInitStb(SMnode *pMnode) { mndAddShowRetrieveHandle(pMnode, TSDB_MGMT_TABLE_COL, mndRetrieveStbCol); mndAddShowFreeIterHandle(pMnode, TSDB_MGMT_TABLE_COL, mndCancelGetNextStb); + mndAddShowRetrieveHandle(pMnode, TSDB_MGMT_TABLE_VSTABLE_INHERITS, mndRetrieveVstableInherits); + mndAddShowFreeIterHandle(pMnode, TSDB_MGMT_TABLE_VSTABLE_INHERITS, mndCancelGetNextStb); + return sdbSetTable(pMnode->pSdb, table); } @@ -128,7 +136,8 @@ SSdbRaw *mndStbActionEncode(SStbObj *pStb) { int32_t size = sizeof(SStbObj) + (pStb->numOfColumns + pStb->numOfTags) * sizeof(SSchema) + pStb->commentLen + pStb->ast1Len + pStb->ast2Len + pStb->numOfColumns * sizeof(SColCmpr) + STB_RESERVE_SIZE + - taosArrayGetSize(pStb->pFuncs) * TSDB_FUNC_NAME_LEN + sizeof(int32_t) * pStb->numOfColumns; + taosArrayGetSize(pStb->pFuncs) * TSDB_FUNC_NAME_LEN + sizeof(int32_t) * pStb->numOfColumns + + sizeof(int8_t) + TSDB_MAX_VST_PARENTS * sizeof(int64_t) + 2 * sizeof(int16_t); SSdbRaw *pRaw = sdbAllocRaw(SDB_STB, STB_VER_NUMBER, size); if (pRaw == NULL) goto _OVER; @@ -212,6 +221,15 @@ SSdbRaw *mndStbActionEncode(SStbObj *pStb) { SDB_SET_INT64(pRaw, dataPos, pStb->ownerId, _OVER) SDB_SET_INT8(pRaw, dataPos, pStb->secureDelete, _OVER) SDB_SET_UINT32(pRaw, dataPos, pStb->flags, _OVER) + + // since 3.x.x - STB_VER_SUPPORT_INHERIT + SDB_SET_INT8(pRaw, dataPos, pStb->numParents, _OVER) + for (int32_t i = 0; i < TSDB_MAX_VST_PARENTS; ++i) { + SDB_SET_INT64(pRaw, dataPos, pStb->parentSuids[i], _OVER) + } + SDB_SET_INT16(pRaw, dataPos, pStb->ownColStart, _OVER) + SDB_SET_INT16(pRaw, dataPos, pStb->ownTagStart, _OVER) + SDB_SET_RESERVE(pRaw, dataPos, STB_RESERVE_SIZE, _OVER) SDB_SET_DATALEN(pRaw, dataPos, _OVER) @@ -377,6 +395,21 @@ SSdbRow *mndStbActionDecode(SSdbRaw *pRaw) { pStb->flags = 0; } + // since 3.x.x - STB_VER_SUPPORT_INHERIT + if (sver >= STB_VER_SUPPORT_INHERIT) { + SDB_GET_INT8(pRaw, dataPos, &pStb->numParents, _OVER) + for (int32_t i = 0; i < TSDB_MAX_VST_PARENTS; ++i) { + SDB_GET_INT64(pRaw, dataPos, &pStb->parentSuids[i], _OVER) + } + SDB_GET_INT16(pRaw, dataPos, &pStb->ownColStart, _OVER) + SDB_GET_INT16(pRaw, dataPos, &pStb->ownTagStart, _OVER) + } else { + pStb->numParents = 0; + memset(pStb->parentSuids, 0, sizeof(pStb->parentSuids)); + pStb->ownColStart = 0; + pStb->ownTagStart = 0; + } + SDB_GET_RESERVE(pRaw, dataPos, STB_RESERVE_SIZE, _OVER) terrno = 0; @@ -524,6 +557,12 @@ static int32_t mndStbActionUpdate(SSdb *pSdb, SStbObj *pOld, SStbObj *pNew) { memcpy(pOld->pExtSchemas, pNew->pExtSchemas, pNew->numOfColumns * sizeof(SExtSchema)); } + // VST inheritance fields + pOld->numParents = pNew->numParents; + memcpy(pOld->parentSuids, pNew->parentSuids, sizeof(pNew->parentSuids)); + pOld->ownColStart = pNew->ownColStart; + pOld->ownTagStart = pNew->ownTagStart; + END: taosWUnLockLatch(&pOld->lock); return terrno; @@ -553,6 +592,76 @@ SDbObj *mndAcquireDbByStb(SMnode *pMnode, const char *stbName) { return mndAcquireDb(pMnode, db); } +// VST inheritance utility: check if a VST is a parent of any other VST +bool mndStbHasChildren(SMnode *pMnode, int64_t suid) { + SSdb *pSdb = pMnode->pSdb; + void *pIter = NULL; + SStbObj *pStb = NULL; + bool hasChildren = false; + while (1) { + pIter = sdbFetch(pSdb, SDB_STB, pIter, (void **)&pStb); + if (pIter == NULL) break; + for (int8_t i = 0; i < pStb->numParents; ++i) { + if (pStb->parentSuids[i] == suid) { + hasChildren = true; + sdbRelease(pSdb, pStb); + sdbCancelFetch(pSdb, pIter); + return true; + } + } + sdbRelease(pSdb, pStb); + } + return hasChildren; +} + +// VST inheritance utility: check if a VST has any VCTs (virtual child tables) +bool mndStbHasVCT(SMnode *pMnode, int64_t suid) { + // VCTs are stored as child tables of the VST; check via vnode meta. + // For simplicity in mnode, we check if any vgroup has ctables for this suid. + // TODO: implement proper VCT existence check via vgroup query + // For now, return false (conservative: allow inheritance) + return false; +} + +// VST inheritance utility: DAG cycle detection +// Returns true if adding parentSuids as parents to childSuid creates a cycle +bool mndCheckCyclicInherit(SMnode *pMnode, int64_t childSuid, int64_t *parentSuids, int8_t numParents) { + // BFS: starting from each proposed parent, walk up through their parents + // If we ever reach childSuid, there's a cycle + int64_t queue[128]; + int32_t head = 0, tail = 0; + + for (int8_t i = 0; i < numParents; ++i) { + queue[tail++] = parentSuids[i]; + } + + while (head < tail && tail < 128) { + int64_t curSuid = queue[head++]; + if (curSuid == childSuid) return true; + + // Find the SStbObj for curSuid + SSdb *pSdb = pMnode->pSdb; + void *pIter = NULL; + SStbObj *pStb = NULL; + while (1) { + pIter = sdbFetch(pSdb, SDB_STB, pIter, (void **)&pStb); + if (pIter == NULL) break; + if (pStb->uid == curSuid) { + for (int8_t j = 0; j < pStb->numParents; ++j) { + if (tail < 128) { + queue[tail++] = pStb->parentSuids[j]; + } + } + sdbRelease(pSdb, pStb); + sdbCancelFetch(pSdb, pIter); + break; + } + sdbRelease(pSdb, pStb); + } + } + return false; +} + static FORCE_INLINE int32_t schemaExColIdCompare(const void *colId, const void *pSchema) { if (*(col_id_t *)colId < ((SSchema *)pSchema)->colId) { return -1; @@ -790,12 +899,74 @@ int32_t mndSetCreateStbCommitLogs(SMnode *pMnode, STrans *pTrans, SDbObj *pDb, S TAOS_RETURN(code); } +// Build VCT check actions (group 1) for specified parent suids. +// Sends TDMT_VND_CHECK_HAS_CTB to ALL vgroups in the DB for each parent. +// If any vgroup has a child table for a parent suid, the transaction fails with ROLLBACK. +static int32_t mndSetCheckHasCtbRedoActions(SMnode *pMnode, STrans *pTrans, SDbObj *pDb, + int64_t *parentSuids, int8_t numParents) { + int32_t code = 0; + SSdb *pSdb = pMnode->pSdb; + + for (int8_t p = 0; p < numParents; ++p) { + SVCheckHasCtbReq checkReq = {.suid = parentSuids[p]}; + int32_t reqLen = tSerializeSVCheckHasCtbReq(NULL, 0, &checkReq); + if (reqLen < 0) { + TAOS_RETURN(TSDB_CODE_OUT_OF_MEMORY); + } + int32_t contLen = reqLen + sizeof(SMsgHead); + + SVgObj *pVgroup = NULL; + void *pIter = NULL; + while (1) { + pIter = sdbFetch(pSdb, SDB_VGROUP, pIter, (void **)&pVgroup); + if (pIter == NULL) break; + if (!mndVgroupInDb(pVgroup, pDb->uid)) { + sdbRelease(pSdb, pVgroup); + continue; + } + + SMsgHead *pHead = taosMemoryCalloc(1, contLen); + if (pHead == NULL) { + sdbCancelFetch(pSdb, pIter); + sdbRelease(pSdb, pVgroup); + TAOS_RETURN(terrno); + } + pHead->contLen = htonl(contLen); + pHead->vgId = htonl(pVgroup->vgId); + void *pBuf = POINTER_SHIFT(pHead, sizeof(SMsgHead)); + if (tSerializeSVCheckHasCtbReq(pBuf, reqLen, &checkReq) < 0) { + taosMemoryFree(pHead); + sdbCancelFetch(pSdb, pIter); + sdbRelease(pSdb, pVgroup); + TAOS_RETURN(TSDB_CODE_OUT_OF_MEMORY); + } + + STransAction action = {0}; + action.mTraceId = pTrans->mTraceId; + action.epSet = mndGetVgroupEpset(pMnode, pVgroup); + action.pCont = pHead; + action.contLen = contLen; + action.msgType = TDMT_VND_CHECK_HAS_CTB; + if ((code = mndTransAppendRedoAction(pTrans, &action)) != 0) { + taosMemoryFree(pHead); + sdbCancelFetch(pSdb, pIter); + sdbRelease(pSdb, pVgroup); + TAOS_RETURN(code); + } + sdbRelease(pSdb, pVgroup); + } + } + + TAOS_RETURN(code); +} + static int32_t mndSetCreateStbRedoActions(SMnode *pMnode, STrans *pTrans, SDbObj *pDb, SStbObj *pStb) { int32_t code = 0; SSdb *pSdb = pMnode->pSdb; SVgObj *pVgroup = NULL; void *pIter = NULL; int32_t contLen; + int32_t groupId = (pTrans->exec == TRN_EXEC_GROUP_PARALLEL) ? 2 : 0; while (1) { pIter = sdbFetch(pSdb, SDB_VGROUP, pIter, (void **)&pVgroup); @@ -822,6 +993,7 @@ static int32_t mndSetCreateStbRedoActions(SMnode *pMnode, STrans *pTrans, SDbObj action.msgType = TDMT_VND_CREATE_STB; action.acceptableCode = TSDB_CODE_TDB_STB_ALREADY_EXIST; action.retryCode = TSDB_CODE_TDB_STB_NOT_EXIST; + action.groupId = groupId; mInfo("trans:%d, add create stb to redo action", pTrans->id); if ((code = mndTransAppendRedoAction(pTrans, &action)) != 0) { taosMemoryFree(pReq); @@ -947,6 +1119,22 @@ int32_t mndBuildStbFromReq(SMnode *pMnode, SStbObj *pDst, SMCreateStbReq *pCreat pDst->secureDelete = pCreate->secureDelete; pCreate->pFuncs = NULL; + // VST inheritance + pDst->numParents = pCreate->numParents; + pDst->ownColStart = pCreate->ownColStart; + pDst->ownTagStart = pCreate->ownTagStart; + if (pDst->numParents > 0) { + for (int32_t i = 0; i < pDst->numParents; ++i) { + SStbObj *pParentStb = mndAcquireStb(pMnode, pCreate->parentStbFNames[i]); + if (pParentStb == NULL) { + code = TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL; + TAOS_RETURN(code); + } + pDst->parentSuids[i] = pParentStb->uid; + mndReleaseStb(pMnode, pParentStb); + } + } + if (pDst->commentLen > 0) { pDst->comment = taosMemoryCalloc(pDst->commentLen + 1, 1); if (pDst->comment == NULL) { @@ -1062,6 +1250,94 @@ static int32_t mndCreateStb(SMnode *pMnode, SRpcMsg *pReq, SMCreateStbReq *pCrea memcpy(stbObj.createUser, pOperUser->name, TSDB_USER_LEN); stbObj.ownerId = pOperUser->uid; + // Merge parent columns/tags into child schema during CREATE with BASE ON + if (stbObj.numParents > 0) { + int32_t addCols = 0, addTags = 0; + SStbObj *pParents[TSDB_MAX_VST_PARENTS] = {0}; + for (int8_t i = 0; i < stbObj.numParents; ++i) { + pParents[i] = mndAcquireStb(pMnode, pCreate->parentStbFNames[i]); + if (!pParents[i]) { code = TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL; goto _OVER; } + addCols += (pParents[i]->numOfColumns > 1) ? (pParents[i]->numOfColumns - 1) : 0; + addTags += pParents[i]->numOfTags; + } + + int32_t ownNumCols = stbObj.numOfColumns; + int32_t ownNumTags = stbObj.numOfTags; + int32_t mergedNumCols = 1 + addCols + (ownNumCols - 1); // ts + parent_cols + own_cols(no ts) + int32_t mergedNumTags = addTags + ownNumTags; + + SSchema *mergedCols = taosMemoryCalloc(mergedNumCols, sizeof(SSchema)); + SSchema *mergedTags = taosMemoryCalloc(mergedNumTags, sizeof(SSchema)); + SColCmpr *mergedCmpr = taosMemoryCalloc(mergedNumCols, sizeof(SColCmpr)); + if (!mergedCols || !mergedTags || !mergedCmpr) { + taosMemoryFree(mergedCols); taosMemoryFree(mergedTags); taosMemoryFree(mergedCmpr); + for (int8_t i = 0; i < stbObj.numParents; ++i) mndReleaseStb(pMnode, pParents[i]); + code = terrno; goto _OVER; + } + + // [0] = ts from child + int32_t dst = 0; + mergedCols[0] = stbObj.pColumns[0]; + mergedCmpr[0] = stbObj.pCmpr[0]; + dst = 1; + + col_id_t nextId = stbObj.nextColId; + // Append parent columns (skip ts at index 0) + for (int8_t p = 0; p < stbObj.numParents; ++p) { + for (int32_t c = 1; c < pParents[p]->numOfColumns; ++c) { + mergedCols[dst] = pParents[p]->pColumns[c]; + mergedCols[dst].colId = nextId++; + SColCmpr cmpr = {.id = mergedCols[dst].colId, + .alg = createDefaultColCmprByType(mergedCols[dst].type)}; + mergedCmpr[dst] = cmpr; + dst++; + } + } + int16_t newOwnColStart = (int16_t)dst; + // Append own columns (skip ts at index 0) + for (int32_t i = 1; i < ownNumCols; ++i) { + mergedCols[dst] = stbObj.pColumns[i]; + mergedCols[dst].colId = nextId++; + mergedCmpr[dst] = stbObj.pCmpr[i]; + mergedCmpr[dst].id = mergedCols[dst].colId; + dst++; + } + + // Tags: [parent_tags][own_tags] + dst = 0; + for (int8_t p = 0; p < stbObj.numParents; ++p) { + for (int32_t t = 0; t < pParents[p]->numOfTags; ++t) { + mergedTags[dst] = pParents[p]->pTags[t]; + mergedTags[dst].colId = nextId++; + dst++; + } + } + int16_t newOwnTagStart = (int16_t)dst; + for (int32_t i = 0; i < ownNumTags; ++i) { + mergedTags[dst] = stbObj.pTags[i]; + mergedTags[dst].colId = nextId++; + dst++; + } + + for (int8_t i = 0; i < stbObj.numParents; ++i) mndReleaseStb(pMnode, pParents[i]); + + taosMemoryFree(stbObj.pColumns); + taosMemoryFree(stbObj.pTags); + taosMemoryFree(stbObj.pCmpr); + stbObj.pColumns = mergedCols; + stbObj.pTags = mergedTags; + stbObj.pCmpr = mergedCmpr; + stbObj.numOfColumns = mergedNumCols; + stbObj.numOfTags = mergedNumTags; + stbObj.ownColStart = newOwnColStart; + stbObj.ownTagStart = newOwnTagStart; + stbObj.nextColId = nextId; + + mInfo("stb:%s, merged %d parent(s) during create, cols %d->%d, tags %d->%d, ownColStart=%d, ownTagStart=%d", + pCreate->name, stbObj.numParents, ownNumCols, mergedNumCols, ownNumTags, mergedNumTags, + newOwnColStart, newOwnTagStart); + } + #ifdef TD_ENTERPRISE // MAC: reject CREATE STABLE if user.maxSecLevel < db.securityLevel (NRU: low-priv user // should not create objects in high-level DBs; in practice, USE DB already blocks this) @@ -1141,6 +1417,13 @@ static int32_t mndCreateStb(SMnode *pMnode, SRpcMsg *pReq, SMCreateStbReq *pCrea TAOS_CHECK_GOTO(mndSetCreateIdxCommitLogs(pMnode, pTrans, &idxObj), NULL, _OVER); + // If inherited VST, use SERIAL execution: check actions first, then DDL actions + if (stbObj.numParents > 0) { + mndTransSetSerial(pTrans); + TAOS_CHECK_GOTO(mndSetCheckHasCtbRedoActions(pMnode, pTrans, pDb, + stbObj.parentSuids, stbObj.numParents), NULL, _OVER); + } + TAOS_CHECK_GOTO(mndAddStbToTrans(pMnode, pTrans, pDb, &stbObj), NULL, _OVER); TAOS_CHECK_GOTO(mndTransPrepare(pMnode, pTrans), NULL, _OVER); code = 0; @@ -1792,6 +2075,48 @@ static int32_t mndProcessCreateStbReq(SRpcMsg *pReq) { goto _OVER; } + // VST inheritance validation + if (createReq.numParents > 0) { + if (createReq.numParents > TSDB_MAX_VST_PARENTS) { + code = TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED; + goto _OVER; + } + if (!createReq.virtualStb) { + code = TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL; + goto _OVER; + } + + int64_t parentSuids[TSDB_MAX_VST_PARENTS] = {0}; + for (int8_t i = 0; i < createReq.numParents; ++i) { + SStbObj *pParentStb = mndAcquireStb(pMnode, createReq.parentStbFNames[i]); + if (pParentStb == NULL) { + code = TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL; + goto _OVER; + } + if (!pParentStb->virtualStb) { + mndReleaseStb(pMnode, pParentStb); + code = TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL; + goto _OVER; + } + // VCT check is done via TDMT_VND_CHECK_HAS_CTB in transaction group 1 + // Same DB check + if (strncmp(pParentStb->db, pDb->name, TSDB_DB_FNAME_LEN) != 0) { + mndReleaseStb(pMnode, pParentStb); + code = TSDB_CODE_MND_VST_CROSS_DB; + goto _OVER; + } + parentSuids[i] = pParentStb->uid; + mndReleaseStb(pMnode, pParentStb); + } + + // Cycle detection (use uid=0 for new table since it doesn't exist yet) + int64_t newSuid = mndGenerateUid(createReq.name, TSDB_TABLE_FNAME_LEN); + if (mndCheckCyclicInherit(pMnode, newSuid, parentSuids, createReq.numParents)) { + code = TSDB_CODE_MND_VST_CIRCULAR_INHERIT; + goto _OVER; + } + } + if (isAlter) { bool needRsp = false; SStbObj pDst = {0}; @@ -1858,6 +2183,15 @@ static int32_t mndCheckAlterStbReq(SMAlterStbReq *pAlter) { if (pAlter->keep != -1) return 0; if (pAlter->secureDelete >= 0) return 0; + // BASE ON alter types don't use pFields + if (pAlter->alterType == TSDB_ALTER_TABLE_ADD_BASE_ON || pAlter->alterType == TSDB_ALTER_TABLE_DROP_BASE_ON) { + if (pAlter->numParents < 1 || pAlter->numParents > TSDB_MAX_VST_PARENTS) { + code = TSDB_CODE_MND_INVALID_STB_OPTION; + TAOS_RETURN(code); + } + return 0; + } + if (pAlter->numOfFields < 1 || pAlter->numOfFields != (int32_t)taosArrayGetSize(pAlter->pFields)) { code = TSDB_CODE_MND_INVALID_STB_OPTION; TAOS_RETURN(code); @@ -2443,6 +2777,7 @@ static int32_t mndSetAlterStbRedoActions(SMnode *pMnode, STrans *pTrans, SDbObj SVgObj *pVgroup = NULL; void *pIter = NULL; int32_t contLen; + int32_t groupId = (pTrans->exec == TRN_EXEC_GROUP_PARALLEL) ? 2 : 0; while (1) { pIter = sdbFetch(pSdb, SDB_VGROUP, pIter, (void **)&pVgroup); @@ -2465,6 +2800,7 @@ static int32_t mndSetAlterStbRedoActions(SMnode *pMnode, STrans *pTrans, SDbObj action.pCont = pReq; action.contLen = contLen; action.msgType = TDMT_VND_ALTER_STB; + action.groupId = groupId; if ((code = mndTransAppendRedoAction(pTrans, &action)) != 0) { taosMemoryFree(pReq); sdbCancelFetch(pSdb, pIter); @@ -2554,6 +2890,7 @@ static int32_t mndBuildStbSchemaImp(SMnode *pMnode, SDbObj *pDb, SStbObj *pStb, pRsp->isAudit = pDb->cfg.isAudit ? 1 : 0; pRsp->secLvl = pStb->securityLevel; pRsp->secureDelete = pStb->secureDelete; + pRsp->hasInheritors = (pMnode != NULL && pStb->virtualStb && mndStbHasChildren(pMnode, pStb->uid)) ? 1 : 0; for (int32_t i = 0; i < pStb->numOfColumns; ++i) { SSchema *pSchema = &pRsp->pSchemas[i]; @@ -2593,7 +2930,7 @@ static int32_t mndBuildStbSchemaImp(SMnode *pMnode, SDbObj *pDb, SStbObj *pStb, TAOS_RETURN(code); } -static int32_t mndBuildStbCfgImp(SDbObj *pDb, SStbObj *pStb, const char *tbName, STableCfgRsp *pRsp) { +static int32_t mndBuildStbCfgImp(SMnode *pMnode, SDbObj *pDb, SStbObj *pStb, const char *tbName, STableCfgRsp *pRsp) { int32_t code = 0; taosRLockLatch(&pStb->lock); @@ -2662,6 +2999,28 @@ static int32_t mndBuildStbCfgImp(SDbObj *pDb, SStbObj *pStb, const char *tbName, pRsp->secureDelete = pStb->secureDelete; pRsp->securityLevel = pStb->securityLevel; + // VST inheritance info + pRsp->numParents = pStb->numParents; + if (pStb->numParents > 0) { + SSdb *pSdb = pMnode->pSdb; + for (int8_t i = 0; i < pStb->numParents; ++i) { + pRsp->parentStbNames[i][0] = '\0'; + void *pIter2 = NULL; + SStbObj *pParent = NULL; + while (1) { + pIter2 = sdbFetch(pSdb, SDB_STB, pIter2, (void **)&pParent); + if (pIter2 == NULL) break; + if (pParent->uid == pStb->parentSuids[i]) { + mndExtractTbNameFromStbFullName(pParent->name, pRsp->parentStbNames[i], TSDB_TABLE_NAME_LEN); + sdbRelease(pSdb, pParent); + sdbCancelFetch(pSdb, pIter2); + break; + } + sdbRelease(pSdb, pParent); + } + } + } + taosRUnLockLatch(&pStb->lock); TAOS_RETURN(code); } @@ -2753,7 +3112,7 @@ static int32_t mndBuildStbCfg(SMnode *pMnode, const char *dbFName, const char *t TAOS_RETURN(code); } - code = mndBuildStbCfgImp(pDb, pStb, tbName, pRsp); + code = mndBuildStbCfgImp(pMnode, pDb, pStb, tbName, pRsp); mndReleaseDb(pMnode, pDb); mndReleaseStb(pMnode, pStb); @@ -2908,6 +3267,46 @@ static int32_t mndAlterStbImp(SMnode *pMnode, SRpcMsg *pReq, SDbObj *pDb, SStbOb TAOS_RETURN(code); } +// ALTER ADD BASE ON uses ROLLBACK + SERIAL: check actions first, then DDL actions +static int32_t mndAlterStbAddBaseOnImp(SMnode *pMnode, SRpcMsg *pReq, SDbObj *pDb, SStbObj *pStb, + int64_t *newParentSuids, int8_t numNewParents, + void *alterOriData, int32_t alterOriDataLen) { + int32_t code = -1; + STrans *pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_DB_INSIDE, pReq, "alter-stb-add-baseon"); + if (pTrans == NULL) { + code = TSDB_CODE_MND_RETURN_VALUE_NULL; + if (terrno != 0) code = terrno; + goto _OVER; + } + + mInfo("trans:%d, used to alter stb add base on:%s", pTrans->id, pStb->name); + mndTransSetDbName(pTrans, pDb->name, pStb->name); + TAOS_CHECK_GOTO(mndTransCheckConflict(pMnode, pTrans), NULL, _OVER); + + mndTransSetSerial(pTrans); + + // VCT check actions (executed first in serial order) + TAOS_CHECK_GOTO(mndSetCheckHasCtbRedoActions(pMnode, pTrans, pDb, + newParentSuids, numNewParents), NULL, _OVER); + + // ALTER STB actions (executed after checks pass) + void *pCont = NULL; + int32_t contLen = 0; + TAOS_CHECK_GOTO(mndBuildSMAlterStbRsp(pDb, pStb, &pCont, &contLen), NULL, _OVER); + mndTransSetRpcRsp(pTrans, pCont, contLen); + + TAOS_CHECK_GOTO(mndSetAlterStbPrepareLogs(pMnode, pTrans, pDb, pStb), NULL, _OVER); + TAOS_CHECK_GOTO(mndSetAlterStbCommitLogs(pMnode, pTrans, pDb, pStb), NULL, _OVER); + TAOS_CHECK_GOTO(mndSetAlterStbRedoActions(pMnode, pTrans, pDb, pStb, alterOriData, alterOriDataLen), NULL, _OVER); + TAOS_CHECK_GOTO(mndTransPrepare(pMnode, pTrans), NULL, _OVER); + + code = 0; + +_OVER: + mndTransDrop(pTrans); + TAOS_RETURN(code); +} + static int32_t mndAlterStbAndUpdateTagIdxImp(SMnode *pMnode, SRpcMsg *pReq, SDbObj *pDb, SStbObj *pStb, bool needRsp, void *alterOriData, int32_t alterOriDataLen, const SMAlterStbReq *pAlter) { int32_t code = -1; @@ -2980,6 +3379,369 @@ static int32_t mndAlterStbAndUpdateTagIdxImp(SMnode *pMnode, SRpcMsg *pReq, SDbO TAOS_RETURN(code); } +// ALTER STABLE ADD BASE ON - add parent VSTs to inheritance list +static int32_t mndAlterStbAddBaseOn(SMnode *pMnode, SStbObj *pOld, SStbObj *pNew, const SMAlterStbReq *pAlter, + SDbObj *pDb) { + int32_t code = 0; + if (pAlter->numParents <= 0) { + TAOS_RETURN(TSDB_CODE_MND_INVALID_STB_OPTION); + } + int8_t totalParents = pOld->numParents + pAlter->numParents; + if (totalParents > TSDB_MAX_VST_PARENTS) { + TAOS_RETURN(TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED); + } + if (!pOld->virtualStb) { + TAOS_RETURN(TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL); + } + + // Copy existing parent list + pNew->numParents = pOld->numParents; + memcpy(pNew->parentSuids, pOld->parentSuids, sizeof(pOld->parentSuids)); + + // Validate and collect new parents + SStbObj *pAddParents[TSDB_MAX_VST_PARENTS] = {0}; + int8_t numAdd = pAlter->numParents; + int32_t addCols = 0, addTags = 0; + + for (int8_t i = 0; i < numAdd; ++i) { + pAddParents[i] = mndAcquireStb(pMnode, (char *)pAlter->parentStbFNames[i]); + if (pAddParents[i] == NULL) { + code = TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL; + goto _ADD_OVER; + } + if (!pAddParents[i]->virtualStb) { + code = TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL; + goto _ADD_OVER; + } + // VCT check is done via TDMT_VND_CHECK_HAS_CTB in transaction group 1 + if (strncmp(pAddParents[i]->db, pDb->name, TSDB_DB_FNAME_LEN) != 0) { + code = TSDB_CODE_MND_VST_CROSS_DB; + goto _ADD_OVER; + } + + // Check column name conflicts with existing child schema (skip ts at index 0) + for (int32_t c = 1; c < pAddParents[i]->numOfColumns; ++c) { + if (mndFindSuperTableColumnIndex(pOld, pAddParents[i]->pColumns[c].name) >= 0 || + mndFindSuperTableTagIndex(pOld, pAddParents[i]->pColumns[c].name) >= 0) { + mError("stb:%s, column conflict: %s from parent %s", pOld->name, pAddParents[i]->pColumns[c].name, + pAlter->parentStbFNames[i]); + code = TSDB_CODE_MND_COLUMN_ALREADY_EXIST; + goto _ADD_OVER; + } + } + for (int32_t t = 0; t < pAddParents[i]->numOfTags; ++t) { + if (mndFindSuperTableColumnIndex(pOld, pAddParents[i]->pTags[t].name) >= 0 || + mndFindSuperTableTagIndex(pOld, pAddParents[i]->pTags[t].name) >= 0) { + mError("stb:%s, tag conflict: %s from parent %s", pOld->name, pAddParents[i]->pTags[t].name, + pAlter->parentStbFNames[i]); + code = TSDB_CODE_MND_TAG_ALREADY_EXIST; + goto _ADD_OVER; + } + } + + // Skip ts column (index 0) from parent — child already has its own ts + addCols += (pAddParents[i]->numOfColumns > 1) ? (pAddParents[i]->numOfColumns - 1) : 0; + addTags += pAddParents[i]->numOfTags; + + pNew->parentSuids[pNew->numParents] = pAddParents[i]->uid; + pNew->numParents++; + } + + // Cycle detection + if (mndCheckCyclicInherit(pMnode, pOld->uid, pNew->parentSuids, pNew->numParents)) { + code = TSDB_CODE_MND_VST_CIRCULAR_INHERIT; + goto _ADD_OVER; + } + + // Merge new parent columns into schema + { + int16_t oldOwnColStart = pOld->ownColStart; + int16_t oldOwnTagStart = pOld->ownTagStart; + int32_t newNumCols = pOld->numOfColumns + addCols; + int32_t newNumTags = pOld->numOfTags + addTags; + + pNew->numOfColumns = newNumCols; + pNew->numOfTags = newNumTags; + pNew->pColumns = taosMemoryCalloc(newNumCols, sizeof(SSchema)); + pNew->pTags = taosMemoryCalloc(newNumTags, sizeof(SSchema)); + pNew->pCmpr = taosMemoryCalloc(newNumCols, sizeof(SColCmpr)); + if (!pNew->pColumns || !pNew->pTags || !pNew->pCmpr) { + code = terrno; + goto _ADD_OVER; + } + if (pOld->pExtSchemas) { + pNew->pExtSchemas = taosMemoryCalloc(newNumCols, sizeof(SExtSchema)); + if (!pNew->pExtSchemas) { + code = terrno; + goto _ADD_OVER; + } + } + + // Layout: [ts][old inherited cols][new parent cols][own cols (no ts)] + // For standalone VST (ownColStart=0, numParents=0), ts is at index 0 and own cols start at 1. + int16_t effectiveOwnColStart = (oldOwnColStart <= 1) ? 1 : oldOwnColStart; + + // Position 0: always ts column (never moves) + int32_t dst = 0; + pNew->pColumns[0] = pOld->pColumns[0]; + pNew->pCmpr[0] = pOld->pCmpr[0]; + if (pOld->pExtSchemas) pNew->pExtSchemas[0] = pOld->pExtSchemas[0]; + dst = 1; + + // Copy old inherited columns [1, effectiveOwnColStart) + for (int32_t i = 1; i < effectiveOwnColStart; ++i) { + pNew->pColumns[dst] = pOld->pColumns[i]; + pNew->pCmpr[dst] = pOld->pCmpr[i]; + if (pOld->pExtSchemas) pNew->pExtSchemas[dst] = pOld->pExtSchemas[i]; + dst++; + } + // Append new parent columns (skip ts col at index 0) + col_id_t maxColId = 0; + for (int32_t i = 0; i < pOld->numOfColumns; ++i) { + if (pOld->pColumns[i].colId > maxColId) maxColId = pOld->pColumns[i].colId; + } + for (int32_t i = 0; i < pOld->numOfTags; ++i) { + if (pOld->pTags[i].colId > maxColId) maxColId = pOld->pTags[i].colId; + } + for (int8_t p = 0; p < numAdd; ++p) { + for (int32_t c = 1; c < pAddParents[p]->numOfColumns; ++c) { + pNew->pColumns[dst] = pAddParents[p]->pColumns[c]; + pNew->pColumns[dst].colId = ++maxColId; + SColCmpr cmpr = {.id = pNew->pColumns[dst].colId, + .alg = createDefaultColCmprByType(pNew->pColumns[dst].type)}; + pNew->pCmpr[dst] = cmpr; + if (pNew->pExtSchemas) memset(&pNew->pExtSchemas[dst], 0, sizeof(SExtSchema)); + dst++; + } + } + // Copy own columns [effectiveOwnColStart, numOfColumns) — own non-ts columns + for (int32_t i = effectiveOwnColStart; i < pOld->numOfColumns; ++i) { + pNew->pColumns[dst] = pOld->pColumns[i]; + pNew->pCmpr[dst] = pOld->pCmpr[i]; + if (pOld->pExtSchemas) pNew->pExtSchemas[dst] = pOld->pExtSchemas[i]; + dst++; + } + + // Tags: [old inherited tags][new parent tags][own tags] + dst = 0; + for (int32_t i = 0; i < oldOwnTagStart; ++i) { + pNew->pTags[dst++] = pOld->pTags[i]; + } + for (int8_t p = 0; p < numAdd; ++p) { + for (int32_t t = 0; t < pAddParents[p]->numOfTags; ++t) { + pNew->pTags[dst] = pAddParents[p]->pTags[t]; + pNew->pTags[dst].colId = ++maxColId; + dst++; + } + } + for (int32_t i = oldOwnTagStart; i < pOld->numOfTags; ++i) { + pNew->pTags[dst++] = pOld->pTags[i]; + } + + pNew->ownColStart = effectiveOwnColStart + (int16_t)addCols; + pNew->ownTagStart = oldOwnTagStart + (int16_t)addTags; + pNew->colVer++; + pNew->tagVer++; + + mInfo("stb:%s, added %d parent(s), cols %d->%d, tags %d->%d, ownColStart %d->%d, ownTagStart %d->%d", + pOld->name, numAdd, pOld->numOfColumns, newNumCols, pOld->numOfTags, newNumTags, + oldOwnColStart, pNew->ownColStart, oldOwnTagStart, pNew->ownTagStart); + } + +_ADD_OVER: + for (int8_t r = 0; r < numAdd; ++r) { + if (pAddParents[r]) mndReleaseStb(pMnode, pAddParents[r]); + } + TAOS_RETURN(code); +} + +// ALTER STABLE DROP BASE ON - remove parent VSTs from inheritance list +static bool mndIsColFromParent(const SStbObj *pParent, const char *colName) { + for (int32_t i = 0; i < pParent->numOfColumns; ++i) { + if (strcmp(pParent->pColumns[i].name, colName) == 0) return true; + } + return false; +} + +static bool mndIsTagFromParent(const SStbObj *pParent, const char *tagName) { + for (int32_t i = 0; i < pParent->numOfTags; ++i) { + if (strcmp(pParent->pTags[i].name, tagName) == 0) return true; + } + return false; +} + +static int32_t mndAlterStbDropBaseOn(SMnode *pMnode, SStbObj *pOld, SStbObj *pNew, const SMAlterStbReq *pAlter) { + int32_t code = 0; + if (pAlter->numParents <= 0) { + TAOS_RETURN(TSDB_CODE_MND_INVALID_STB_OPTION); + } + if (!pOld->virtualStb || pOld->numParents == 0) { + TAOS_RETURN(TSDB_CODE_MND_INVALID_STB_OPTION); + } + + // Collect parent STBs to drop (need their schemas to identify inherited columns) + SStbObj *pDropParents[TSDB_MAX_VST_PARENTS] = {0}; + int8_t numDrop = pAlter->numParents; + + for (int8_t i = 0; i < numDrop; ++i) { + pDropParents[i] = mndAcquireStb(pMnode, (char *)pAlter->parentStbFNames[i]); + if (pDropParents[i] == NULL) { + for (int8_t r = 0; r < i; ++r) mndReleaseStb(pMnode, pDropParents[r]); + TAOS_RETURN(TSDB_CODE_MND_STB_NOT_EXIST); + } + } + + // Copy existing parent list, then remove dropped parents + pNew->numParents = pOld->numParents; + memcpy(pNew->parentSuids, pOld->parentSuids, sizeof(pOld->parentSuids)); + + for (int8_t i = 0; i < numDrop; ++i) { + bool found = false; + for (int8_t j = 0; j < pNew->numParents; ++j) { + if (pNew->parentSuids[j] == pDropParents[i]->uid) { + for (int8_t k = j; k < pNew->numParents - 1; ++k) { + pNew->parentSuids[k] = pNew->parentSuids[k + 1]; + } + pNew->parentSuids[pNew->numParents - 1] = 0; + pNew->numParents--; + found = true; + break; + } + } + if (!found) { + for (int8_t r = 0; r < numDrop; ++r) mndReleaseStb(pMnode, pDropParents[r]); + TAOS_RETURN(TSDB_CODE_MND_INVALID_STB_OPTION); + } + } + + // Build keep-flags for inherited columns [0, ownColStart) and tags [0, ownTagStart) + // A column is dropped if it belongs to ANY of the dropped parents + int16_t oldOwnColStart = pOld->ownColStart; + int16_t oldOwnTagStart = pOld->ownTagStart; + + bool *keepCol = taosMemoryCalloc(pOld->numOfColumns, sizeof(bool)); + bool *keepTag = taosMemoryCalloc(pOld->numOfTags, sizeof(bool)); + if (!keepCol || !keepTag) { + taosMemoryFree(keepCol); + taosMemoryFree(keepTag); + for (int8_t r = 0; r < numDrop; ++r) mndReleaseStb(pMnode, pDropParents[r]); + TAOS_RETURN(terrno); + } + + // ts column (index 0) is always kept + keepCol[0] = true; + // Own columns/tags are always kept + for (int32_t i = oldOwnColStart; i < pOld->numOfColumns; ++i) keepCol[i] = true; + for (int32_t i = oldOwnTagStart; i < pOld->numOfTags; ++i) keepTag[i] = true; + + // Inherited columns [1, ownColStart): keep unless from a dropped parent + for (int32_t i = 1; i < oldOwnColStart; ++i) { + bool fromDropped = false; + for (int8_t d = 0; d < numDrop; ++d) { + if (mndIsColFromParent(pDropParents[d], pOld->pColumns[i].name)) { + fromDropped = true; + break; + } + } + keepCol[i] = !fromDropped; + } + for (int32_t i = 0; i < oldOwnTagStart; ++i) { + bool fromDropped = false; + for (int8_t d = 0; d < numDrop; ++d) { + if (mndIsTagFromParent(pDropParents[d], pOld->pTags[i].name)) { + fromDropped = true; + break; + } + } + keepTag[i] = !fromDropped; + } + + // Release parent refs + for (int8_t r = 0; r < numDrop; ++r) mndReleaseStb(pMnode, pDropParents[r]); + + // Count surviving columns/tags + int32_t newNumCols = 0, newInheritCols = 0; + for (int32_t i = 0; i < pOld->numOfColumns; ++i) { + if (keepCol[i]) { + if (i < oldOwnColStart) newInheritCols++; + newNumCols++; + } + } + int32_t newNumTags = 0, newInheritTags = 0; + for (int32_t i = 0; i < pOld->numOfTags; ++i) { + if (keepTag[i]) { + if (i < oldOwnTagStart) newInheritTags++; + newNumTags++; + } + } + + // Validate: ≥2 columns (TS + at least 1 more), ≥1 tag + if (newNumCols < 2) { + taosMemoryFree(keepCol); + taosMemoryFree(keepTag); + TAOS_RETURN(TSDB_CODE_PAR_INVALID_DROP_COL); + } + if (newNumTags < 1) { + taosMemoryFree(keepCol); + taosMemoryFree(keepTag); + TAOS_RETURN(TSDB_CODE_MND_INVALID_STB_OPTION); + } + + // Allocate new schema arrays + pNew->numOfColumns = newNumCols; + pNew->numOfTags = newNumTags; + pNew->pColumns = taosMemoryCalloc(newNumCols, sizeof(SSchema)); + pNew->pTags = taosMemoryCalloc(newNumTags, sizeof(SSchema)); + pNew->pCmpr = taosMemoryCalloc(newNumCols, sizeof(SColCmpr)); + if (!pNew->pColumns || !pNew->pTags || !pNew->pCmpr) { + taosMemoryFree(keepCol); + taosMemoryFree(keepTag); + TAOS_RETURN(terrno); + } + if (pOld->pExtSchemas) { + pNew->pExtSchemas = taosMemoryCalloc(newNumCols, sizeof(SExtSchema)); + if (!pNew->pExtSchemas) { + taosMemoryFree(keepCol); + taosMemoryFree(keepTag); + TAOS_RETURN(terrno); + } + } + + // Copy surviving columns + int32_t dst = 0; + for (int32_t i = 0; i < pOld->numOfColumns; ++i) { + if (keepCol[i]) { + pNew->pColumns[dst] = pOld->pColumns[i]; + pNew->pCmpr[dst] = pOld->pCmpr[i]; + if (pOld->pExtSchemas) pNew->pExtSchemas[dst] = pOld->pExtSchemas[i]; + dst++; + } + } + + // Copy surviving tags + dst = 0; + for (int32_t i = 0; i < pOld->numOfTags; ++i) { + if (keepTag[i]) { + pNew->pTags[dst] = pOld->pTags[i]; + dst++; + } + } + + pNew->ownColStart = (int16_t)newInheritCols; + pNew->ownTagStart = (int16_t)newInheritTags; + pNew->colVer++; + pNew->tagVer++; + + taosMemoryFree(keepCol); + taosMemoryFree(keepTag); + + mInfo("stb:%s, dropped %d parent(s), cols %d->%d, tags %d->%d, ownColStart %d->%d, ownTagStart %d->%d", + pOld->name, numDrop, pOld->numOfColumns, newNumCols, pOld->numOfTags, newNumTags, + oldOwnColStart, pNew->ownColStart, oldOwnTagStart, pNew->ownTagStart); + + TAOS_RETURN(code); +} + static int32_t mndAlterStb(SMnode *pMnode, SRpcMsg *pReq, const SMAlterStbReq *pAlter, SDbObj *pDb, SStbObj *pOld) { bool needRsp = true; int32_t code = -1; @@ -3045,6 +3807,12 @@ static int32_t mndAlterStb(SMnode *pMnode, SRpcMsg *pReq, const SMAlterStbReq *p case TSDB_ALTER_TABLE_ADD_COLUMN_WITH_COMPRESS_OPTION: code = mndAddSuperTableColumn(pOld, &stbObj, pAlter, pAlter->numOfFields, 1); break; + case TSDB_ALTER_TABLE_ADD_BASE_ON: + code = mndAlterStbAddBaseOn(pMnode, pOld, &stbObj, pAlter, pDb); + break; + case TSDB_ALTER_TABLE_DROP_BASE_ON: + code = mndAlterStbDropBaseOn(pMnode, pOld, &stbObj, pAlter); + break; default: needRsp = false; terrno = TSDB_CODE_OPS_NOT_SUPPORT; @@ -3052,7 +3820,13 @@ static int32_t mndAlterStb(SMnode *pMnode, SRpcMsg *pReq, const SMAlterStbReq *p } if (code != 0) goto _OVER; - if (updateTagIndex == false) { + if (pAlter->alterType == TSDB_ALTER_TABLE_ADD_BASE_ON) { + // Only check newly added parents (starting at pOld->numParents) + int8_t numNew = stbObj.numParents - pOld->numParents; + code = mndAlterStbAddBaseOnImp(pMnode, pReq, pDb, &stbObj, + &stbObj.parentSuids[pOld->numParents], numNew, + pReq->pCont, pReq->contLen); + } else if (updateTagIndex == false) { code = mndAlterStbImp(pMnode, pReq, pDb, &stbObj, needRsp, pReq->pCont, pReq->contLen); } else { code = mndAlterStbAndUpdateTagIdxImp(pMnode, pReq, pDb, &stbObj, needRsp, pReq->pCont, pReq->contLen, pAlter); @@ -3368,6 +4142,12 @@ static int32_t mndProcessDropStbReq(SRpcMsg *pReq) { goto _OVER; } + // VST inheritance: refuse DROP if this VST has child VSTs + if (pStb->virtualStb && mndStbHasChildren(pMnode, pStb->uid)) { + code = TSDB_CODE_MND_VST_HAS_CHILDREN; + goto _OVER; + } + code = mndDropStb(pMnode, pReq, pDb, pStb); if (code == 0) code = TSDB_CODE_ACTION_IN_PROGRESS; @@ -4146,11 +4926,177 @@ static int32_t mndRetrieveStbCol(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pB return numOfRows; } +// Retrieve inheritance relationships for ins_vstable_inherits system table +static int32_t mndRetrieveVstableInherits(SRpcMsg *pReq, SShowObj *pShow, SSDataBlock *pBlock, int32_t rows) { + SMnode *pMnode = pReq->info.node; + SSdb *pSdb = pMnode->pSdb; + int32_t numOfRows = 0; + SStbObj *pStb = NULL; + int32_t code = 0; + + while (numOfRows < rows) { + pShow->pIter = sdbFetch(pSdb, SDB_STB, pShow->pIter, (void **)&pStb); + if (pShow->pIter == NULL) break; + + if (pStb->numParents <= 0) { + sdbRelease(pSdb, pStb); + continue; + } + + // Extract child db name and table name + char childDbName[TSDB_DB_FNAME_LEN] = {0}; + char childStbName[TSDB_TABLE_NAME_LEN] = {0}; + mndExtractDbNameFromStbFullName(pStb->name, childDbName); + mndExtractTbNameFromStbFullName(pStb->name, childStbName, TSDB_TABLE_NAME_LEN); + + for (int8_t i = 0; i < pStb->numParents && numOfRows < rows; ++i) { + // Look up parent name by uid + char parentName[TSDB_TABLE_NAME_LEN] = {0}; + void *pIter2 = NULL; + SStbObj *pParent = NULL; + while (1) { + pIter2 = sdbFetch(pSdb, SDB_STB, pIter2, (void **)&pParent); + if (pIter2 == NULL) break; + if (pParent->uid == pStb->parentSuids[i]) { + mndExtractTbNameFromStbFullName(pParent->name, parentName, TSDB_TABLE_NAME_LEN); + sdbRelease(pSdb, pParent); + sdbCancelFetch(pSdb, pIter2); + break; + } + sdbRelease(pSdb, pParent); + } + + int32_t cols = 0; + SColumnInfoData *pColInfo; + char buf[TSDB_TABLE_NAME_LEN + VARSTR_HEADER_SIZE] = {0}; + + // db_name + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + STR_TO_VARSTR(buf, childDbName); + colDataSetVal(pColInfo, numOfRows, buf, false); + + // parent_stable_name + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + STR_TO_VARSTR(buf, parentName); + colDataSetVal(pColInfo, numOfRows, buf, false); + + // parent_uid + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetVal(pColInfo, numOfRows, (const char *)&pStb->parentSuids[i], false); + + // child_stable_name + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + STR_TO_VARSTR(buf, childStbName); + colDataSetVal(pColInfo, numOfRows, buf, false); + + // child_uid + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetVal(pColInfo, numOfRows, (const char *)&pStb->uid, false); + + // create_time + pColInfo = taosArrayGet(pBlock->pDataBlock, cols++); + colDataSetVal(pColInfo, numOfRows, (const char *)&pStb->createdTime, false); + + numOfRows++; + } + sdbRelease(pSdb, pStb); + } + + pShow->numOfRows += numOfRows; + return numOfRows; +} + static void mndCancelGetNextStb(SMnode *pMnode, void *pIter) { SSdb *pSdb = pMnode->pSdb; sdbCancelFetchByType(pSdb, pIter, SDB_STB); } +static int32_t mndProcessGetVstLeavesReq(SRpcMsg *pReq) { + SMnode *pMnode = pReq->info.node; + SSdb *pSdb = pMnode->pSdb; + int32_t code = TSDB_CODE_SUCCESS; + SVstLeavesReq req = {0}; + SVstLeavesRsp rsp = {0}; + + if (tDeserializeSVstLeavesReq(pReq->pCont, pReq->contLen, &req) != 0) { + code = TSDB_CODE_INVALID_MSG; + goto _OVER; + } + + // BFS: find all descendants, collect leaves (those without children) + int64_t queue[256]; + int32_t qHead = 0, qTail = 0; + queue[qTail++] = req.suid; + + int64_t allDescs[256]; + int32_t numDescs = 0; + + while (qHead < qTail && qTail < 256) { + int64_t curSuid = queue[qHead++]; + void *pIter = NULL; + SStbObj *pStb = NULL; + while (1) { + pIter = sdbFetch(pSdb, SDB_STB, pIter, (void **)&pStb); + if (pIter == NULL) break; + for (int8_t i = 0; i < pStb->numParents; ++i) { + if (pStb->parentSuids[i] == curSuid) { + if (numDescs < 256 && qTail < 256) { + allDescs[numDescs++] = pStb->uid; + queue[qTail++] = pStb->uid; + } + break; + } + } + sdbRelease(pSdb, pStb); + } + } + + // Among descendants, find leaves (those with no children of their own) + SVstLeafInfo leaves[256]; + int32_t numLeaves = 0; + + for (int32_t d = 0; d < numDescs; ++d) { + bool hasChild = mndStbHasChildren(pMnode, allDescs[d]); + if (!hasChild) { + void *pIter = NULL; + SStbObj *pStb = NULL; + while (1) { + pIter = sdbFetch(pSdb, SDB_STB, pIter, (void **)&pStb); + if (pIter == NULL) break; + if (pStb->uid == allDescs[d]) { + if (numLeaves < 256) { + mndExtractDbNameFromStbFullName(pStb->name, leaves[numLeaves].dbFName); + mndExtractTbNameFromStbFullName(pStb->name, leaves[numLeaves].stbName, TSDB_TABLE_NAME_LEN); + leaves[numLeaves].suid = pStb->uid; + numLeaves++; + } + sdbRelease(pSdb, pStb); + sdbCancelFetch(pSdb, pIter); + break; + } + sdbRelease(pSdb, pStb); + } + } + } + + rsp.numLeaves = numLeaves; + rsp.pLeaves = leaves; + + int32_t rspLen = tSerializeSVstLeavesRsp(NULL, 0, &rsp); + void *pRsp = rpcMallocCont(rspLen); + if (pRsp == NULL) { + code = terrno; + goto _OVER; + } + tSerializeSVstLeavesRsp(pRsp, rspLen, &rsp); + + pReq->info.rsp = pRsp; + pReq->info.rspLen = rspLen; + +_OVER: + return code; +} + const char *mndGetStbStr(const char *src) { char *posDb = strstr(src, TS_PATH_DELIMITER); if (posDb != NULL) ++posDb; diff --git a/source/dnode/mnode/impl/src/mndTrans.c b/source/dnode/mnode/impl/src/mndTrans.c index 8e0080faecf2..006d1fcfc3f5 100644 --- a/source/dnode/mnode/impl/src/mndTrans.c +++ b/source/dnode/mnode/impl/src/mndTrans.c @@ -2077,7 +2077,9 @@ static int32_t mndTransExecuteActionsSerial(SMnode *pMnode, STrans *pTrans, SArr char str[200] = {0}; if (mndCannotExecuteTransWithInfo(pMnode, topHalf, str, 200)) { pTrans->lastErrorNo = code; - pTrans->code = code; + if (pTrans->code == 0) { + pTrans->code = code; + } mInfo("trans:%d, %s:%d cannot execute next action, stop execution, %s", pTrans->id, mndTransStr(pAction->stage), action, str); break; @@ -2205,7 +2207,9 @@ static int32_t mndTransExecuteActionsSerialGroup(SMnode *pMnode, STrans *pTrans, char str[200] = {0}; if (mndCannotExecuteTransWithInfo(pMnode, topHalf, str, 200)) { pTrans->lastErrorNo = code; - pTrans->code = code; + if (pTrans->code == 0) { + pTrans->code = code; + } if (code == TSDB_CODE_MND_TRANS_CTX_SWITCH) { mInfo( "trans:%d, %s:%d (%d/%d at group %d) not able to execute since %s, current state(" diff --git a/source/dnode/vnode/src/inc/vnd.h b/source/dnode/vnode/src/inc/vnd.h index 4e88f84811df..447582176a69 100644 --- a/source/dnode/vnode/src/inc/vnd.h +++ b/source/dnode/vnode/src/inc/vnd.h @@ -130,6 +130,7 @@ int vnodeGetTableCfg(SVnode* pVnode, SRpcMsg* pMsg, bool direct); int32_t vnodeGetBatchMeta(SVnode* pVnode, SRpcMsg* pMsg); int32_t vnodeGetVSubtablesMeta(SVnode *pVnode, SRpcMsg *pMsg); int32_t vnodeGetVStbRefDbs(SVnode *pVnode, SRpcMsg *pMsg); +int32_t vnodeProcessCheckHasCtbReq(SVnode *pVnode, SRpcMsg *pMsg); // vnodeCommit.c int32_t vnodeBegin(SVnode* pVnode); diff --git a/source/dnode/vnode/src/vnd/vnodeQuery.c b/source/dnode/vnode/src/vnd/vnodeQuery.c index a54539c71b40..46ad0988bab2 100644 --- a/source/dnode/vnode/src/vnd/vnodeQuery.c +++ b/source/dnode/vnode/src/vnd/vnodeQuery.c @@ -940,6 +940,33 @@ int32_t vnodeGetVSubtablesMeta(SVnode *pVnode, SRpcMsg *pMsg) { return code; } +int32_t vnodeProcessCheckHasCtbReq(SVnode *pVnode, SRpcMsg *pMsg) { + SVCheckHasCtbReq req = {0}; + int32_t code = 0; + void *pBuf = POINTER_SHIFT(pMsg->pCont, sizeof(SMsgHead)); + int32_t bufLen = pMsg->contLen - sizeof(SMsgHead); + + code = tDeserializeSVCheckHasCtbReq(pBuf, bufLen, &req); + if (code != 0) { + vError("vgId:%d, failed to deserialize check-has-ctb req", TD_VID(pVnode)); + goto _exit; + } + + SMCtbCursor *pCur = metaOpenCtbCursor(pVnode, req.suid, 0); + if (pCur != NULL) { + tb_uid_t id = metaCtbCursorNext(pCur); + metaCloseCtbCursor(pCur); + if (id != 0) { + code = TSDB_CODE_MND_VST_PARENT_HAS_VCT; + } + } + +_exit:; + SRpcMsg rspMsg = {.info = pMsg->info, .code = code}; + tmsgSendRsp(&rspMsg); + return 0; // return 0 so fetch worker doesn't double-send response +} + int32_t vnodeGetVStbRefDbs(SVnode *pVnode, SRpcMsg *pMsg) { int32_t code = 0; int32_t rspSize = 0; diff --git a/source/dnode/vnode/src/vnd/vnodeSvr.c b/source/dnode/vnode/src/vnd/vnodeSvr.c index 0ef691860e4f..5f5753787b60 100644 --- a/source/dnode/vnode/src/vnd/vnodeSvr.c +++ b/source/dnode/vnode/src/vnd/vnodeSvr.c @@ -1103,7 +1103,8 @@ int32_t vnodeProcessFetchMsg(SVnode *pVnode, SRpcMsg *pMsg, SQueueInfo *pInfo) { pVnode->config.vgId, TMSG_INFO(pMsg->msgType), pMsg, TRACE_GET_ROOTID(trace), TRACE_GET_MSGID(trace)); if ((pMsg->msgType == TDMT_SCH_FETCH || pMsg->msgType == TDMT_VND_TABLE_META || pMsg->msgType == TDMT_VND_TABLE_CFG || pMsg->msgType == TDMT_VND_BATCH_META || pMsg->msgType == TDMT_VND_TABLE_NAME || - pMsg->msgType == TDMT_VND_VSUBTABLES_META || pMsg->msgType == TDMT_VND_VSTB_REF_DBS) && + pMsg->msgType == TDMT_VND_VSUBTABLES_META || pMsg->msgType == TDMT_VND_VSTB_REF_DBS || + pMsg->msgType == TDMT_VND_CHECK_HAS_CTB) && !syncIsReadyForRead(pVnode->sync)) { vnodeRedirectRpcMsg(pVnode, pMsg, terrno); return 0; @@ -1138,6 +1139,8 @@ int32_t vnodeProcessFetchMsg(SVnode *pVnode, SRpcMsg *pMsg, SQueueInfo *pInfo) { return vnodeGetVSubtablesMeta(pVnode, pMsg); case TDMT_VND_VSTB_REF_DBS: return vnodeGetVStbRefDbs(pVnode, pMsg); + case TDMT_VND_CHECK_HAS_CTB: + return vnodeProcessCheckHasCtbReq(pVnode, pMsg); case TDMT_VND_QUERY_SCAN_PROGRESS: return vnodeQueryScanProgress(pVnode, pMsg); #ifdef TD_ENTERPRISE diff --git a/source/libs/catalog/src/catalog.c b/source/libs/catalog/src/catalog.c index 95e3543ab392..f6776465e655 100644 --- a/source/libs/catalog/src/catalog.c +++ b/source/libs/catalog/src/catalog.c @@ -1771,6 +1771,40 @@ int32_t catalogGetDBCfg(SCatalog* pCtg, SRequestConnInfo* pConn, const char* dbF CTG_API_LEAVE(ctgGetDBCfg(pCtg, pConn, dbFName, pDbCfg)); } +int32_t catalogGetVstLeaves(SCatalog* pCtg, SRequestConnInfo* pConn, const char* dbFName, int64_t suid, + SVstLeavesRsp* pRsp) { + CTG_API_ENTER(); + + if (NULL == pCtg || NULL == pConn || NULL == dbFName || NULL == pRsp) { + CTG_API_LEAVE(TSDB_CODE_CTG_INVALID_INPUT); + } + + SVstLeavesReq req = {0}; + tstrncpy(req.dbFName, dbFName, sizeof(req.dbFName)); + req.suid = suid; + + int32_t reqLen = tSerializeSVstLeavesReq(NULL, 0, &req); + char *msg = rpcMallocCont(reqLen); + if (NULL == msg) { + CTG_API_LEAVE(terrno); + } + tSerializeSVstLeavesReq(msg, reqLen, &req); + + SRpcMsg rpcMsg = {.msgType = TDMT_MND_GET_VST_LEAVES, .pCont = msg, .contLen = reqLen}; + SRpcMsg rpcRsp = {0}; + + int32_t code = rpcSendRecv(pConn->pTrans, &pConn->mgmtEps, &rpcMsg, &rpcRsp); + if (TSDB_CODE_SUCCESS == code) { + code = rpcRsp.code; + } + if (TSDB_CODE_SUCCESS == code) { + code = tDeserializeSVstLeavesRsp(rpcRsp.pCont, rpcRsp.contLen, pRsp); + } + rpcFreeCont(rpcRsp.pCont); + + CTG_API_LEAVE(code); +} + int32_t catalogGetIndexMeta(SCatalog* pCtg, SRequestConnInfo* pConn, const char* indexName, SIndexInfo* pInfo) { CTG_API_ENTER(); diff --git a/source/libs/command/src/command.c b/source/libs/command/src/command.c index 3243f60a6e3c..c0082a434fc0 100644 --- a/source/libs/command/src/command.c +++ b/source/libs/command/src/command.c @@ -944,7 +944,22 @@ static int32_t setCreateTBResultIntoDataBlock(SSDataBlock* pBlock, SDbCfgInfo* p ") TAGS ("); appendTagFields(buf2, &len, pCfg); len += snprintf(buf2 + VARSTR_HEADER_SIZE + len, SHOW_CREATE_TB_RESULT_FIELD2_LEN - (VARSTR_HEADER_SIZE + len), - ") SECURITY_LEVEL %d", pCfg->securityLevel); + ")"); + if (pCfg->numParents > 0) { + len += snprintf(buf2 + VARSTR_HEADER_SIZE + len, SHOW_CREATE_TB_RESULT_FIELD2_LEN - (VARSTR_HEADER_SIZE + len), + " BASE ON "); + for (int8_t i = 0; i < pCfg->numParents; ++i) { + if (i > 0) { + len += snprintf(buf2 + VARSTR_HEADER_SIZE + len, + SHOW_CREATE_TB_RESULT_FIELD2_LEN - (VARSTR_HEADER_SIZE + len), ", "); + } + len += snprintf(buf2 + VARSTR_HEADER_SIZE + len, + SHOW_CREATE_TB_RESULT_FIELD2_LEN - (VARSTR_HEADER_SIZE + len), "`%s`", + pCfg->parentStbNames[i]); + } + } + len += snprintf(buf2 + VARSTR_HEADER_SIZE + len, SHOW_CREATE_TB_RESULT_FIELD2_LEN - (VARSTR_HEADER_SIZE + len), + " SECURITY_LEVEL %d", pCfg->securityLevel); appendTableOptions(buf2, &len, pDbCfg, pCfg); } else if (TSDB_CHILD_TABLE == pCfg->tableType) { len += snprintf(buf2 + VARSTR_HEADER_SIZE, SHOW_CREATE_TB_RESULT_FIELD2_LEN - VARSTR_HEADER_SIZE, diff --git a/source/libs/nodes/src/nodesCodeFuncs.c b/source/libs/nodes/src/nodesCodeFuncs.c index 83c836e11ad5..57c3c77a589f 100644 --- a/source/libs/nodes/src/nodesCodeFuncs.c +++ b/source/libs/nodes/src/nodesCodeFuncs.c @@ -431,6 +431,8 @@ const char* nodesNodeName(ENodeType type) { return "ShowInstancesStmt"; case QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT: return "ShowValidateVirtualTableStmt"; + case QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT: + return "ShowVstableInheritsStmt"; case QUERY_NODE_SHOW_RETENTION_DETAILS_STMT: return "ShowRetentionDetailsStmt"; case QUERY_NODE_SHOW_ENCRYPT_ALGORITHMS_STMT: @@ -8340,6 +8342,7 @@ static const char* jkCreateTableStmtIgnoreExists = "IgnoreExists"; static const char* jkCreateTableStmtCols = "Cols"; static const char* jkCreateTableStmtTags = "Tags"; static const char* jkCreateTableStmtOptions = "Options"; +static const char* jkCreateTableStmtBaseOnList = "BaseOnList"; static int32_t createTableStmtToJson(const void* pObj, SJson* pJson) { const SCreateTableStmt* pNode = (const SCreateTableStmt*)pObj; @@ -8360,6 +8363,9 @@ static int32_t createTableStmtToJson(const void* pObj, SJson* pJson) { if (TSDB_CODE_SUCCESS == code) { code = tjsonAddObject(pJson, jkCreateTableStmtOptions, nodeToJson, pNode->pOptions); } + if (TSDB_CODE_SUCCESS == code) { + code = nodeListToJson(pJson, jkCreateTableStmtBaseOnList, pNode->pBaseOnList); + } return code; } @@ -8383,6 +8389,9 @@ static int32_t jsonToCreateTableStmt(const SJson* pJson, void* pObj) { if (TSDB_CODE_SUCCESS == code) { code = jsonToNodeObject(pJson, jkCreateTableStmtOptions, (SNode**)&pNode->pOptions); } + if (TSDB_CODE_SUCCESS == code) { + code = jsonToNodeList(pJson, jkCreateTableStmtBaseOnList, &pNode->pBaseOnList); + } return code; } diff --git a/source/libs/nodes/src/nodesUtilFuncs.c b/source/libs/nodes/src/nodesUtilFuncs.c index 8905d5c1a492..077294f5c1ff 100644 --- a/source/libs/nodes/src/nodesUtilFuncs.c +++ b/source/libs/nodes/src/nodesUtilFuncs.c @@ -880,6 +880,7 @@ int32_t nodesMakeNode(ENodeType type, SNode** ppNodeOut) { case QUERY_NODE_SHOW_INSTANCES_STMT: case QUERY_NODE_SHOW_ENCRYPT_ALGORITHMS_STMT: case QUERY_NODE_SHOW_ENCRYPT_STATUS_STMT: + case QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT: code = makeNode(type, sizeof(SShowStmt), &pNode); break; case QUERY_NODE_SHOW_TABLE_TAGS_STMT: @@ -1795,6 +1796,7 @@ void nodesDestroyNode(SNode* pNode) { nodesDestroyList(pStmt->pCols); nodesDestroyList(pStmt->pTags); nodesDestroyNode((SNode*)pStmt->pOptions); + nodesDestroyList(pStmt->pBaseOnList); break; } case QUERY_NODE_CREATE_SUBTABLE_CLAUSE: { @@ -2091,7 +2093,8 @@ void nodesDestroyNode(SNode* pNode) { case QUERY_NODE_SHOW_MOUNTS_STMT: case QUERY_NODE_SHOW_RSMAS_STMT: case QUERY_NODE_SHOW_RETENTIONS_STMT: - case QUERY_NODE_SHOW_INSTANCES_STMT: { + case QUERY_NODE_SHOW_INSTANCES_STMT: + case QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT: { SShowStmt* pStmt = (SShowStmt*)pNode; nodesDestroyNode(pStmt->pDbName); nodesDestroyNode(pStmt->pTbName); diff --git a/source/libs/parser/inc/parAst.h b/source/libs/parser/inc/parAst.h index 1ccc2843b8c3..a56fe138fdfc 100644 --- a/source/libs/parser/inc/parAst.h +++ b/source/libs/parser/inc/parAst.h @@ -322,6 +322,9 @@ SNode* setColumnReference(SAstCreateContext* pCxt, SNode* pOptions, SNod SNode* createDefaultColumnOptions(SAstCreateContext* pCxt); SNode* createCreateTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, SNodeList* pCols, SNodeList* pTags, SNode* pOptions); +SNode* createCreateInheritedStableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, + SNodeList* pCols, SNodeList* pTags, SNodeList* pBaseOnList, + SNode* pOptions); SNode* createCreateSubTableClause(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, SNode* pUseRealTable, SNodeList* pSpecificTags, SNodeList* pValsOfTags, SNode* pOptions); SNode* createCreateVTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, SNodeList* pCols); @@ -352,6 +355,7 @@ SNode* createAlterTableAlterColRef(SAstCreateContext* pCxt, SNode* pRealTable, i SNode* pRef); SNode* createAlterTableRemoveColRef(SAstCreateContext* pCxt, SNode* pRealTable, int8_t alterType, SToken* pColName, const SToken* pLiteral); +SNode* createAlterTableBaseOn(SAstCreateContext* pCxt, SNode* pRealTable, int8_t alterType, SNodeList* pBaseOnList); SNode* createAlterTableUpdateTagValClause(SAstCreateContext* pCxt, SNode* pRealTable, SNodeList* pTagList); SNode* createAlterMultiTableUpdateTagValStmt(SAstCreateContext* pCxt, SNodeList* pTableList); SNode* createAlterChildTableUpdateTagValStmt(SAstCreateContext* pCxt, SNode* pRealTable, SNodeList* pTagList, SNode* pWhere); diff --git a/source/libs/parser/inc/sql.y b/source/libs/parser/inc/sql.y index 8ccaf7f936c2..cf240dfdec55 100755 --- a/source/libs/parser/inc/sql.y +++ b/source/libs/parser/inc/sql.y @@ -1053,6 +1053,12 @@ cmd ::= CREATE TABLE not_exists_opt(B) USING full_table_name(C) NK_LP tag_list_opt(D) NK_RP FILE NK_STRING(E). { pCxt->pRootNode = createCreateSubTableFromFileClause(pCxt, B, C, D, &E); } cmd ::= CREATE STABLE not_exists_opt(A) full_table_name(B) NK_LP column_def_list(C) NK_RP tags_def(D) table_options(E). { pCxt->pRootNode = createCreateTableStmt(pCxt, A, B, C, D, E); } +cmd ::= CREATE STABLE not_exists_opt(A) full_table_name(B) + NK_LP column_def_list(C) NK_RP tags_def(D) BASE ON base_on_list(F) table_options(E). + { pCxt->pRootNode = createCreateInheritedStableStmt(pCxt, A, B, C, D, F, E); } +cmd ::= CREATE STABLE not_exists_opt(A) full_table_name(B) + NK_LP column_def_list(C) NK_RP BASE ON base_on_list(F) table_options(E). + { pCxt->pRootNode = createCreateInheritedStableStmt(pCxt, A, B, C, NULL, F, E); } cmd ::= CREATE VTABLE not_exists_opt(A) full_table_name(B) NK_LP column_def_list(C) NK_RP. { pCxt->pRootNode = createCreateVTableStmt(pCxt, A, B, C); } cmd ::= CREATE VTABLE not_exists_opt(A) full_table_name(B) @@ -1096,6 +1102,11 @@ alter_table_clause(A) ::= alter_table_clause(A) ::= full_table_name(B) ALTER COLUMN column_name(C) SET NULL(D). { A = createAlterTableRemoveColRef(pCxt, B, TSDB_ALTER_TABLE_REMOVE_COLUMN_REF, &C, &D); } +alter_table_clause(A) ::= + full_table_name(B) ADD BASE ON base_on_list(C). { A = createAlterTableBaseOn(pCxt, B, TSDB_ALTER_TABLE_ADD_BASE_ON, C); } +alter_table_clause(A) ::= + full_table_name(B) DROP BASE ON base_on_list(C). { A = createAlterTableBaseOn(pCxt, B, TSDB_ALTER_TABLE_DROP_BASE_ON, C); } + /* update multi table tag values */ %type column_tag_value_list { SNodeList* } %destructor column_tag_value_list { nodesDestroyList($$); } @@ -1236,6 +1247,11 @@ tags_def_opt(A) ::= tags_def(B). %destructor tags_def { nodesDestroyList($$); } tags_def(A) ::= TAGS NK_LP tag_def_list(B) NK_RP. { A = B; } +%type base_on_list { SNodeList* } +%destructor base_on_list { nodesDestroyList($$); } +base_on_list(A) ::= full_table_name(B). { A = createNodeList(pCxt, B); } +base_on_list(A) ::= base_on_list(B) NK_COMMA full_table_name(C). { A = addNodeToList(pCxt, B, C); } + table_options(A) ::= . { A = createDefaultTableOptions(pCxt); } table_options(A) ::= table_options(B) COMMENT NK_STRING(C). { A = setTableOption(pCxt, B, TABLE_OPTION_COMMENT, &C); } table_options(A) ::= table_options(B) MAX_DELAY duration_list(C). { A = setTableOption(pCxt, B, TABLE_OPTION_MAXDELAY, C); } @@ -1385,6 +1401,7 @@ cmd ::= SHOW SSMIGRATES. cmd ::= SHOW TOKENS. { pCxt->pRootNode = createShowTokensStmt(pCxt, QUERY_NODE_SHOW_TOKENS_STMT); } cmd ::= SHOW VTABLE VALIDATE FOR full_table_name(A). { pCxt->pRootNode = createShowValidateVirtualTableStmt(pCxt, QUERY_NODE_SHOW_VALIDATE_VTABLE_STMT, A); } +cmd ::= SHOW VTABLE INHERITS. { pCxt->pRootNode = createShowStmt(pCxt, QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT); } %type table_kind_db_name_cond_opt { SShowTablesOption } %destructor table_kind_db_name_cond_opt { } diff --git a/source/libs/parser/src/parAstCreater.c b/source/libs/parser/src/parAstCreater.c index f58bc89ed3ab..a1c08e722192 100644 --- a/source/libs/parser/src/parAstCreater.c +++ b/source/libs/parser/src/parAstCreater.c @@ -3747,6 +3747,31 @@ SNode* createCreateTableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* return NULL; } +SNode* createCreateInheritedStableStmt(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, + SNodeList* pCols, SNodeList* pTags, SNodeList* pBaseOnList, + SNode* pOptions) { + CHECK_PARSER_STATUS(pCxt); + SCreateTableStmt* pStmt = NULL; + pCxt->errCode = nodesMakeNode(QUERY_NODE_CREATE_TABLE_STMT, (SNode**)&pStmt); + CHECK_MAKE_NODE(pStmt); + tstrncpy(pStmt->dbName, ((SRealTableNode*)pRealTable)->table.dbName, TSDB_DB_NAME_LEN); + tstrncpy(pStmt->tableName, ((SRealTableNode*)pRealTable)->table.tableName, TSDB_TABLE_NAME_LEN); + pStmt->ignoreExists = ignoreExists; + pStmt->pCols = pCols; + pStmt->pTags = pTags; + pStmt->pOptions = (STableOptions*)pOptions; + pStmt->pBaseOnList = pBaseOnList; + nodesDestroyNode(pRealTable); + return (SNode*)pStmt; +_err: + nodesDestroyNode(pRealTable); + nodesDestroyList(pCols); + nodesDestroyList(pTags); + nodesDestroyList(pBaseOnList); + nodesDestroyNode(pOptions); + return NULL; +} + SNode* createCreateSubTableClause(SAstCreateContext* pCxt, bool ignoreExists, SNode* pRealTable, SNode* pUseRealTable, SNodeList* pSpecificTags, SNodeList* pValsOfTags, SNode* pOptions) { CHECK_PARSER_STATUS(pCxt); @@ -4034,6 +4059,20 @@ SNode* createAlterTableRemoveColRef(SAstCreateContext* pCxt, SNode* pRealTable, return NULL; } +SNode* createAlterTableBaseOn(SAstCreateContext* pCxt, SNode* pRealTable, int8_t alterType, SNodeList* pBaseOnList) { + CHECK_PARSER_STATUS(pCxt); + SAlterTableStmt* pStmt = NULL; + pCxt->errCode = nodesMakeNode(QUERY_NODE_ALTER_TABLE_STMT, (SNode**)&pStmt); + CHECK_MAKE_NODE(pStmt); + pStmt->alterType = alterType; + pStmt->pList = pBaseOnList; + return createAlterTableStmtFinalize(pRealTable, pStmt); +_err: + nodesDestroyNode(pRealTable); + nodesDestroyList(pBaseOnList); + return NULL; +} + SNode* createAlterTagValueNode(SAstCreateContext* pCxt, SToken* pTagName, SNode* pVal) { diff --git a/source/libs/parser/src/parTokenizer.c b/source/libs/parser/src/parTokenizer.c index 0eb2b4dd24d3..02cb69c21132 100644 --- a/source/libs/parser/src/parTokenizer.c +++ b/source/libs/parser/src/parTokenizer.c @@ -50,6 +50,7 @@ static SKeyword keywordTable[] = { {"ASC", TK_ASC}, {"ASOF", TK_ASOF}, {"BALANCE", TK_BALANCE}, + {"BASE", TK_BASE}, {"BATCH_SCAN", TK_BATCH_SCAN}, {"BETWEEN", TK_BETWEEN}, {"BIGINT", TK_BIGINT}, @@ -157,6 +158,7 @@ static SKeyword keywordTable[] = { {"IN", TK_IN}, {"INDEX", TK_INDEX}, {"INDEXES", TK_INDEXES}, + {"INHERITS", TK_INHERITS}, {"INNER", TK_INNER}, {"INSERT", TK_INSERT}, {"INSTANCES", TK_INSTANCES}, diff --git a/source/libs/parser/src/parTranslater.c b/source/libs/parser/src/parTranslater.c index eeeddfb287df..a04c413d1ccc 100644 --- a/source/libs/parser/src/parTranslater.c +++ b/source/libs/parser/src/parTranslater.c @@ -534,6 +534,13 @@ static const SSysTableShowAdapter sysTableShowAdapter[] = { .numOfShowCols = 1, .pShowCols = {"*"} }, + { + .showType = QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT, + .pDbName = TSDB_INFORMATION_SCHEMA_DB, + .pTableName = TSDB_INS_TABLE_VSTABLE_INHERITS, + .numOfShowCols = 1, + .pShowCols = {"*"} + }, { .showType = QUERY_NODE_SHOW_SECURITY_POLICIES_STMT, .pDbName = TSDB_INFORMATION_SCHEMA_DB, @@ -6728,6 +6735,153 @@ static int32_t makeVtableMetaScanTable(STranslateContext* pCxt, SRealTableNode** return code; } +// Build SELECT col1, col2, ... FROM using the parent's schema columns +static int32_t buildLeafSelectStmt(STranslateContext* pCxt, const char* dbName, const char* tableName, + STableMeta* pParentMeta, SNode** ppStmt) { + int32_t code = TSDB_CODE_SUCCESS; + SSelectStmt* pSelect = NULL; + PAR_ERR_RET(nodesMakeNode(QUERY_NODE_SELECT_STMT, (SNode**)&pSelect)); + + // Create FROM: SRealTableNode + SRealTableNode* pFromTable = NULL; + code = nodesMakeNode(QUERY_NODE_REAL_TABLE, (SNode**)&pFromTable); + if (TSDB_CODE_SUCCESS != code) { + nodesDestroyNode((SNode*)pSelect); + return code; + } + tstrncpy(pFromTable->table.dbName, dbName, sizeof(pFromTable->table.dbName)); + tstrncpy(pFromTable->table.tableName, tableName, sizeof(pFromTable->table.tableName)); + tstrncpy(pFromTable->table.tableAlias, tableName, sizeof(pFromTable->table.tableAlias)); + pSelect->pFromTable = (SNode*)pFromTable; + + // Build projection list from parent's schema columns + int32_t numCols = pParentMeta->tableInfo.numOfColumns; + SSchema* pSchema = pParentMeta->schema; + for (int32_t i = 0; i < numCols; ++i) { + SColumnNode* pCol = NULL; + code = nodesMakeNode(QUERY_NODE_COLUMN, (SNode**)&pCol); + if (TSDB_CODE_SUCCESS != code) { + nodesDestroyNode((SNode*)pSelect); + return code; + } + tstrncpy(pCol->colName, pSchema[i].name, sizeof(pCol->colName)); + tstrncpy(pCol->tableAlias, tableName, sizeof(pCol->tableAlias)); + code = nodesListMakeAppend(&pSelect->pProjectionList, (SNode*)pCol); + if (TSDB_CODE_SUCCESS != code) { + nodesDestroyNode((SNode*)pSelect); + return code; + } + } + + *ppStmt = (SNode*)pSelect; + return TSDB_CODE_SUCCESS; +} + +// Rewrite a non-leaf VST query to UNION ALL of leaf descendants (projecting parent's schema) +static int32_t rewriteNonLeafVstQuery(STranslateContext* pCxt, SNode** pTable, SRealTableNode* pRealTable) { + int32_t code = TSDB_CODE_SUCCESS; + SParseContext* pParCxt = pCxt->pParseCxt; + SVstLeavesRsp rsp = {0}; + + // Get leaf descendants from mnode + char dbFName[TSDB_DB_FNAME_LEN] = {0}; + SName name = {0}; + toName(pParCxt->acctId, pRealTable->table.dbName, pRealTable->table.tableName, &name); + (void)tNameGetFullDbName(&name, dbFName); + + SRequestConnInfo conn = {.pTrans = pParCxt->pTransporter, + .requestId = pParCxt->requestId, + .requestObjRefId = pParCxt->requestRid, + .mgmtEps = pParCxt->mgmtEpSet}; + + code = catalogGetVstLeaves(pParCxt->pCatalog, &conn, dbFName, pRealTable->pMeta->suid, &rsp); + if (TSDB_CODE_SUCCESS != code) { + return code; + } + + if (rsp.numLeaves == 0) { + // No leaf descendants - query returns empty + tFreeSVstLeavesRsp(&rsp); + return TSDB_CODE_SUCCESS; + } + + STableMeta* pParentMeta = pRealTable->pMeta; + + if (rsp.numLeaves == 1) { + // Single leaf: build SELECT FROM leaf as subquery + SNode* pSubquery = NULL; + code = buildLeafSelectStmt(pCxt, rsp.pLeaves[0].dbFName, rsp.pLeaves[0].stbName, pParentMeta, &pSubquery); + if (TSDB_CODE_SUCCESS != code) { + tFreeSVstLeavesRsp(&rsp); + return code; + } + + STempTableNode* pTempTable = NULL; + code = nodesMakeNode(QUERY_NODE_TEMP_TABLE, (SNode**)&pTempTable); + if (TSDB_CODE_SUCCESS != code) { + nodesDestroyNode(pSubquery); + tFreeSVstLeavesRsp(&rsp); + return code; + } + pTempTable->pSubquery = pSubquery; + tstrncpy(pTempTable->table.tableAlias, pRealTable->table.tableAlias, sizeof(pTempTable->table.tableAlias)); + tstrncpy(pTempTable->table.dbName, pRealTable->table.dbName, sizeof(pTempTable->table.dbName)); + + nodesDestroyNode(*pTable); + *pTable = (SNode*)pTempTable; + tFreeSVstLeavesRsp(&rsp); + return TSDB_CODE_SUCCESS; + } + + // Multiple leaves: build UNION ALL + SNode* pSetOp = NULL; + SNode* pLeft = NULL; + SNode* pRight = NULL; + PAR_ERR_JRET(buildLeafSelectStmt(pCxt, rsp.pLeaves[0].dbFName, rsp.pLeaves[0].stbName, pParentMeta, &pLeft)); + PAR_ERR_JRET(buildLeafSelectStmt(pCxt, rsp.pLeaves[1].dbFName, rsp.pLeaves[1].stbName, pParentMeta, &pRight)); + + SSetOperator* pOp = NULL; + PAR_ERR_JRET(nodesMakeNode(QUERY_NODE_SET_OPERATOR, (SNode**)&pOp)); + pOp->opType = SET_OP_TYPE_UNION_ALL; + pOp->pLeft = pLeft; + pOp->pRight = pRight; + pLeft = NULL; + pRight = NULL; + pSetOp = (SNode*)pOp; + + for (int32_t i = 2; i < rsp.numLeaves; ++i) { + SNode* pNext = NULL; + PAR_ERR_JRET(buildLeafSelectStmt(pCxt, rsp.pLeaves[i].dbFName, rsp.pLeaves[i].stbName, pParentMeta, &pNext)); + SSetOperator* pNewOp = NULL; + PAR_ERR_JRET(nodesMakeNode(QUERY_NODE_SET_OPERATOR, (SNode**)&pNewOp)); + pNewOp->opType = SET_OP_TYPE_UNION_ALL; + pNewOp->pLeft = pSetOp; + pNewOp->pRight = pNext; + pSetOp = (SNode*)pNewOp; + } + + // Wrap in STempTableNode + STempTableNode* pTempTable = NULL; + PAR_ERR_JRET(nodesMakeNode(QUERY_NODE_TEMP_TABLE, (SNode**)&pTempTable)); + pTempTable->pSubquery = pSetOp; + pSetOp = NULL; + tstrncpy(pTempTable->table.tableAlias, pRealTable->table.tableAlias, sizeof(pTempTable->table.tableAlias)); + tstrncpy(pTempTable->table.dbName, pRealTable->table.dbName, sizeof(pTempTable->table.dbName)); + + nodesDestroyNode(*pTable); + *pTable = (SNode*)pTempTable; + + tFreeSVstLeavesRsp(&rsp); + return TSDB_CODE_SUCCESS; + +_return: + nodesDestroyNode(pSetOp); + nodesDestroyNode(pLeft); + nodesDestroyNode(pRight); + tFreeSVstLeavesRsp(&rsp); + return code; +} + static int32_t translateVirtualSuperTable(STranslateContext* pCxt, SNode** pTable, SName* pName, SVirtualTableNode* pVTable) { SRealTableNode* pRealTable = (SRealTableNode*)*pTable; @@ -7351,6 +7505,18 @@ static int32_t translateRealTable(STranslateContext* pCxt, SNode** pTable, bool // create stream trigger's plan will treat virtual table as real table if (pRealTable->placeholderType != SP_PARTITION_ROWS && !inStreamTriggerClause(pCxt) && (isVirtualTable(pRealTable->pMeta) || isVirtualSTable(pRealTable->pMeta))) { + // Non-leaf VST: rewrite to UNION ALL of leaf descendants + if (pRealTable->pMeta->tableType == TSDB_SUPER_TABLE && pRealTable->pMeta->hasInheritors) { + code = rewriteNonLeafVstQuery(pCxt, pTable, pRealTable); + if (TSDB_CODE_SUCCESS != code) { + goto _return; + } + // Rewritten to temp table - translate it and return + if (nodeType(*pTable) == QUERY_NODE_TEMP_TABLE) { + PAR_RET(translateTable(pCxt, pTable, inJoin)); + } + // No leaves found - fall through to normal virtual table path (will return empty) + } PAR_RET(translateVirtualTable(pCxt, pTable, &name)); } @@ -15027,6 +15193,27 @@ static int32_t translateCreateSuperTable(STranslateContext* pCxt, SCreateTableSt if (TSDB_CODE_SUCCESS == code) { code = buildCreateStbReq(pCxt, pStmt, &createReq); } + // Handle BASE ON inheritance fields + if (TSDB_CODE_SUCCESS == code && pStmt->pBaseOnList != NULL) { + int32_t numParents = LIST_LENGTH(pStmt->pBaseOnList); + if (numParents > TSDB_MAX_VST_PARENTS) { + code = TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED; + } else { + createReq.numParents = (int8_t)numParents; + createReq.ownColStart = createReq.numOfColumns; + createReq.ownTagStart = createReq.numOfTags; + int32_t idx = 0; + SNode* pNode = NULL; + FOREACH(pNode, pStmt->pBaseOnList) { + SRealTableNode* pParent = (SRealTableNode*)pNode; + SName parentName = {0}; + toName(pCxt->pParseCxt->acctId, pParent->table.dbName, pParent->table.tableName, &parentName); + code = tNameExtractFullName(&parentName, createReq.parentStbFNames[idx]); + if (TSDB_CODE_SUCCESS != code) break; + idx++; + } + } + } if (TSDB_CODE_SUCCESS == code) { code = buildCmdMsg(pCxt, TDMT_MND_CREATE_STB, (FSerializeFunc)tSerializeSMCreateStbReq, &createReq); } @@ -15189,6 +15376,26 @@ static int32_t buildAlterSuperTableReq(STranslateContext* pCxt, SAlterTableStmt* } pAlterReq->numOfFields = taosArrayGetSize(pAlterReq->pFields); + + // Handle BASE ON alter types + if (pStmt->alterType == TSDB_ALTER_TABLE_ADD_BASE_ON || pStmt->alterType == TSDB_ALTER_TABLE_DROP_BASE_ON) { + int32_t numParents = LIST_LENGTH(pStmt->pList); + if (numParents > TSDB_MAX_VST_PARENTS) { + return TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED; + } + pAlterReq->numParents = (int8_t)numParents; + int32_t idx = 0; + SNode* pListNode = NULL; + FOREACH(pListNode, pStmt->pList) { + SRealTableNode* pParent = (SRealTableNode*)pListNode; + SName parentName = {0}; + toName(pCxt->pParseCxt->acctId, pParent->table.dbName, pParent->table.tableName, &parentName); + int32_t ret = tNameExtractFullName(&parentName, pAlterReq->parentStbFNames[idx]); + if (TSDB_CODE_SUCCESS != ret) return ret; + idx++; + } + } + return TSDB_CODE_SUCCESS; } @@ -27365,6 +27572,11 @@ static int32_t rewriteCreateVirtualSubTable(STranslateContext* pCxt, SQuery* pQu PAR_ERR_JRET(TSDB_CODE_VTABLE_NOT_VIRTUAL_SUPER_TABLE); } + // Non-leaf VST (has child VSTs inheriting from it) cannot have VCT + if (pSuperTableMeta->hasInheritors) { + PAR_ERR_JRET(TSDB_CODE_MND_VST_NOT_LEAF); + } + toName(pCxt->pParseCxt->acctId, pStmt->dbName, pStmt->tableName, &name); if (pStmt->pSpecificColRefs) { @@ -28201,6 +28413,7 @@ static int32_t rewriteQuery(STranslateContext* pCxt, SQuery* pQuery) { case QUERY_NODE_SHOW_RSMAS_STMT: case QUERY_NODE_SHOW_RETENTIONS_STMT: case QUERY_NODE_SHOW_ROLES_STMT: + case QUERY_NODE_SHOW_VSTABLE_INHERITS_STMT: code = rewriteShow(pCxt, pQuery); break; case QUERY_NODE_SHOW_STREAMS_STMT: diff --git a/source/libs/qcom/src/querymsg.c b/source/libs/qcom/src/querymsg.c index 59616fa6fea6..7976c95a975e 100644 --- a/source/libs/qcom/src/querymsg.c +++ b/source/libs/qcom/src/querymsg.c @@ -737,13 +737,16 @@ int32_t queryCreateTableMetaFromMsg(STableMetaRsp *msg, bool isStb, STableMeta * pTableMeta->secureDelete = msg->secureDelete; if (msg->virtualStb) { pTableMeta->virtualStb = 1; + pTableMeta->hasInheritors = msg->hasInheritors ? 1 : 0; pTableMeta->numOfColRefs = 0; } else { if (msg->tableType == TSDB_VIRTUAL_CHILD_TABLE && isStb) { pTableMeta->virtualStb = 1; + pTableMeta->hasInheritors = 0; pTableMeta->numOfColRefs = 0; } else { pTableMeta->virtualStb = 0; + pTableMeta->hasInheritors = 0; pTableMeta->numOfColRefs = msg->numOfColRefs; } } @@ -825,6 +828,7 @@ int32_t queryCreateTableMetaExFromMsg(STableMetaRsp *msg, bool isStb, STableMeta pTableMeta->tversion = msg->tversion; pTableMeta->rversion = msg->rversion; pTableMeta->virtualStb = msg->virtualStb; + pTableMeta->hasInheritors = msg->hasInheritors ? 1 : 0; pTableMeta->numOfColRefs = msg->numOfColRefs; pTableMeta->ownerId = msg->ownerId; pTableMeta->secureDelete = msg->secureDelete; diff --git a/source/util/src/terror.c b/source/util/src/terror.c index d7c0d12532b6..364372531429 100644 --- a/source/util/src/terror.c +++ b/source/util/src/terror.c @@ -463,6 +463,17 @@ TAOS_DEFINE_ERROR(TSDB_CODE_MND_RSMA_FUNC_CONFLICT, "Rsma func already spe TAOS_DEFINE_ERROR(TSDB_CODE_MND_VIEW_ALREADY_EXIST, "view already exists in db") TAOS_DEFINE_ERROR(TSDB_CODE_MND_VIEW_NOT_EXIST, "view not exists in db") +// mnode-vst-inherit +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_HAS_CHILDREN, "VST has child VSTs, cannot drop or alter") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_PARENT_NOT_VIRTUAL, "BASE ON target is not a virtual stable") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_COL_NAME_CONFLICT, "Column or tag name conflict in VST inheritance") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_CIRCULAR_INHERIT, "Circular inheritance detected in VST DAG") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_MAX_PARENTS_EXCEED, "Exceed max number of parent VSTs") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_PARENT_HAS_VCT, "Parent VST already has VCT, cannot be inherited") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_NOT_LEAF, "Non-leaf VST cannot have VCT") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_DROP_BASE_MIN_COLS, "After dropping base, remaining cols or tags below minimum") +TAOS_DEFINE_ERROR(TSDB_CODE_MND_VST_CROSS_DB, "Parent and child VST must be in the same database") + //mnode-compact TAOS_DEFINE_ERROR(TSDB_CODE_MND_INVALID_COMPACT_ID, "Invalid compact id") TAOS_DEFINE_ERROR(TSDB_CODE_MND_COMPACT_DETAIL_NOT_EXIST, "compact detail doesn't exist") diff --git a/test/cases/05-VirtualTables/test_vst_inheritance_cascade.py b/test/cases/05-VirtualTables/test_vst_inheritance_cascade.py new file mode 100644 index 000000000000..fb094e6862ae --- /dev/null +++ b/test/cases/05-VirtualTables/test_vst_inheritance_cascade.py @@ -0,0 +1,824 @@ +################################################################### +# Copyright (c) 2016 by TAOS Technologies, Inc. +# All rights reserved. +# +# This file is proprietary and confidential to TAOS Technologies. +# No part of this file may be reproduced, stored, transmitted, +# disclosed or used in any form or by any means other than as +# expressly provided by the written permission from Jianhui Tao +# +################################################################### + +# -*- coding: utf-8 -*- +from new_test_framework.utils import tdLog, tdSql, etool, tdCom + + +DB = "test_cascade" + + +class TestVstInheritanceCascade: + + def setup_class(cls): + tdLog.info("prepare database and source tables") + tdSql.execute(f"drop database if exists {DB}") + tdSql.execute(f"create database {DB}") + tdSql.execute(f"use {DB}") + + # Source tables for VCT column references + tdSql.execute( + f"create stable src_stb (ts timestamp, c1 int, c2 float, c3 double) " + f"tags (loc int)" + ) + tdSql.execute(f"create table src_t1 using src_stb tags (1)") + tdSql.execute(f"insert into src_t1 values (now, 10, 1.5, 3.14)") + tdSql.execute(f"insert into src_t1 values (now+1s, 20, 2.5, 6.28)") + tdSql.execute(f"insert into src_t1 values (now+2s, 30, 3.5, 9.42)") + + tdSql.execute(f"create table src_t2 using src_stb tags (2)") + tdSql.execute(f"insert into src_t2 values (now, 100, 10.0, 99.9)") + tdSql.execute(f"insert into src_t2 values (now+1s, 200, 20.0, 88.8)") + + # Parent VSTs + tdSql.execute( + f"create stable p_device (ts timestamp, status int, temp float) " + f"tags (region int, site binary(32)) virtual 1" + ) + tdSql.execute( + f"create stable p_metric (ts timestamp, val double) " + f"tags (unit nchar(8)) virtual 1" + ) + + # -- helpers ------------------------------------------------- + + @staticmethod + def _check_inherit_rows(child_name, expected): + tdSql.query( + f"select * from information_schema.ins_vstable_inherits " + f"where child_stable_name = '{child_name}'" + ) + tdSql.checkRows(expected) + + @staticmethod + def _check_show_create(stb_name, should_have_base_on, parents=None): + tdSql.query(f"show create stable {DB}.{stb_name}") + stmt = tdSql.queryResult[0][1] + tdLog.info(f"SHOW CREATE {stb_name}: {stmt}") + if should_have_base_on: + assert "BASE ON" in stmt, f"Expected BASE ON in: {stmt}" + if parents: + for p in parents: + assert p in stmt, f"Expected '{p}' in: {stmt}" + else: + assert "BASE ON" not in stmt, f"Unexpected BASE ON in: {stmt}" + + # ============================================================ + # Test 1: CREATE with single / multi parent + # ============================================================ + def test_create_with_inheritance(self): + """VST Inheritance: CREATE with BASE ON + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable leaf_a (ts timestamp, accuracy int) " + f"tags (sensor_id int) base on {DB}.p_device virtual 1" + ) + self._check_inherit_rows("leaf_a", 1) + + tdSql.execute( + f"create stable leaf_b (ts timestamp, quality int) " + f"tags (device_id int) base on {DB}.p_device, {DB}.p_metric virtual 1" + ) + self._check_inherit_rows("leaf_b", 2) + + # ============================================================ + # Test 2: ALTER ADD BASE ON + # ============================================================ + def test_alter_add_base_on(self): + """VST Inheritance: ALTER ADD BASE ON + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, alter + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable standalone (ts timestamp, own_col int) " + f"tags (own_tag int) virtual 1" + ) + self._check_inherit_rows("standalone", 0) + + tdSql.execute(f"alter stable {DB}.standalone add base on {DB}.p_device") + self._check_inherit_rows("standalone", 1) + self._check_show_create("standalone", True, ["p_device"]) + + tdSql.execute(f"alter stable {DB}.standalone add base on {DB}.p_metric") + self._check_inherit_rows("standalone", 2) + self._check_show_create("standalone", True, ["p_device", "p_metric"]) + + # ============================================================ + # Test 3: ALTER DROP BASE ON + # ============================================================ + def test_alter_drop_base_on(self): + """VST Inheritance: ALTER DROP BASE ON + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, alter + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute(f"alter stable {DB}.standalone drop base on {DB}.p_metric") + self._check_inherit_rows("standalone", 1) + self._check_show_create("standalone", True, ["p_device"]) + + tdSql.execute(f"alter stable {DB}.standalone drop base on {DB}.p_device") + self._check_inherit_rows("standalone", 0) + + # ============================================================ + # Test 4: Repeated ADD / DROP cycles + # ============================================================ + def test_add_drop_cycles(self): + """VST Inheritance: repeated ADD/DROP cycles + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable cycled (ts timestamp, c1 int) tags (t1 int) virtual 1" + ) + + for i in range(3): + tdLog.info(f"cycle {i + 1}") + tdSql.execute(f"alter stable {DB}.cycled add base on {DB}.p_device") + self._check_inherit_rows("cycled", 1) + + tdSql.execute(f"alter stable {DB}.cycled add base on {DB}.p_metric") + self._check_inherit_rows("cycled", 2) + + tdSql.execute(f"alter stable {DB}.cycled drop base on {DB}.p_device") + self._check_inherit_rows("cycled", 1) + + tdSql.execute(f"alter stable {DB}.cycled drop base on {DB}.p_metric") + self._check_inherit_rows("cycled", 0) + + # ============================================================ + # Test 5: Column conflict on ADD BASE ON + # ============================================================ + def test_add_conflict(self): + """VST Inheritance: column name conflict + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable p_conflict (ts timestamp, status int) " + f"tags (conf_tag int) virtual 1" + ) + tdSql.error(f"alter stable {DB}.leaf_a add base on {DB}.p_conflict") + self._check_inherit_rows("leaf_a", 1) + + # ============================================================ + # Test 6: Tag conflict on ADD BASE ON + # ============================================================ + def test_add_tag_conflict(self): + """VST Inheritance: tag name conflict + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable p_tag_conflict (ts timestamp, tc_col int) " + f"tags (region int) virtual 1" + ) + tdSql.error(f"alter stable {DB}.leaf_a add base on {DB}.p_tag_conflict") + self._check_inherit_rows("leaf_a", 1) + + # ============================================================ + # Test 7: Circular inheritance detection + # ============================================================ + def test_circular_detection(self): + """VST Inheritance: circular dependency detection + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + # Direct cycle: leaf_a → p_device, try p_device → leaf_a + tdSql.error(f"alter stable {DB}.p_device add base on {DB}.leaf_a") + + # Indirect cycle: A→B→C, try C→A + tdSql.execute( + f"create stable chain_a (ts timestamp, ca int) tags (ta int) virtual 1" + ) + tdSql.execute( + f"create stable chain_b (ts timestamp, cb int) tags (tb int) " + f"base on {DB}.chain_a virtual 1" + ) + tdSql.execute( + f"create stable chain_c (ts timestamp, cc int) tags (tc int) " + f"base on {DB}.chain_b virtual 1" + ) + tdSql.error(f"alter stable {DB}.chain_a add base on {DB}.chain_c") + + # ============================================================ + # Test 8: Max parents limit (10) + # ============================================================ + def test_max_parents(self): + """VST Inheritance: max 10 parents + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + for i in range(10): + tdSql.execute( + f"create stable mp_{i} (ts timestamp, mp_c{i} int) " + f"tags (mp_t{i} int) virtual 1" + ) + + parent_list = ", ".join([f"{DB}.mp_{i}" for i in range(10)]) + tdSql.execute( + f"create stable max_child (ts timestamp, mc int) " + f"tags (mt int) base on {parent_list} virtual 1" + ) + self._check_inherit_rows("max_child", 10) + + tdSql.execute( + f"create stable mp_extra (ts timestamp, mp_extra_c int) " + f"tags (mp_extra_t int) virtual 1" + ) + tdSql.error(f"alter stable {DB}.max_child add base on {DB}.mp_extra") + + # ============================================================ + # Test 9: Non-leaf cannot have VCT + # ============================================================ + def test_nonleaf_no_vct(self): + """VST Inheritance: non-leaf rejects VCT creation + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.error( + f"create vtable nonleaf_vct " + f"({DB}.src_t1.ts, {DB}.src_t1.c1, {DB}.src_t1.c2) " + f"using {DB}.p_device " + f"tags (1, 'test')" + ) + + # ============================================================ + # Test 10: VCT on leaf, insert data, query + # ============================================================ + def test_leaf_vct_query(self): + """VST Inheritance: leaf VCT query + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, query + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create vtable vct_a1 " + f"(status FROM {DB}.src_t1.c1, " + f" temp FROM {DB}.src_t1.c2, " + f" accuracy FROM {DB}.src_t1.c1) " + f"using {DB}.leaf_a " + f"tags (1, 'beijing', 100)" + ) + + tdSql.query(f"select * from {DB}.leaf_a") + tdSql.checkRows(3) + + tdSql.query(f"select status, temp from {DB}.leaf_a") + tdSql.checkRows(3) + + tdSql.query(f"select accuracy from {DB}.leaf_a") + tdSql.checkRows(3) + + tdSql.query(f"select sensor_id, region from {DB}.leaf_a limit 1") + tdSql.checkRows(1) + + # ============================================================ + # Test 11: Query leaf_b with VCT + # ============================================================ + def test_parent_vst_query(self): + """VST Inheritance: leaf_b VCT query + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, query + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create vtable vct_b1 " + f"(status FROM {DB}.src_t2.c1, " + f" temp FROM {DB}.src_t2.c2, " + f" val FROM {DB}.src_t2.c3, " + f" quality FROM {DB}.src_t2.c1) " + f"using {DB}.leaf_b " + f"tags (2, 'shanghai', 'celsius', 200)" + ) + + tdSql.query(f"select * from {DB}.leaf_b") + tdSql.checkRows(2) + + # ============================================================ + # Test 12: ADD parent then query + # ============================================================ + def test_add_parent_then_query(self): + """VST Inheritance: add parent and verify query + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, query + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable p_extra (ts timestamp, extra_val int) " + f"tags (extra_tag binary(16)) virtual 1" + ) + + tdSql.execute(f"alter stable {DB}.leaf_a add base on {DB}.p_extra") + self._check_inherit_rows("leaf_a", 2) + + tdSql.query(f"select ts, status, accuracy from {DB}.leaf_a") + tdSql.checkRows(3) + + # ============================================================ + # Test 13: DROP parent then query + # ============================================================ + def test_drop_parent_then_query(self): + """VST Inheritance: drop parent and verify query + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, query + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute(f"alter stable {DB}.leaf_a drop base on {DB}.p_extra") + self._check_inherit_rows("leaf_a", 1) + + tdSql.query(f"select ts, status, accuracy from {DB}.leaf_a") + tdSql.checkRows(3) + + # ============================================================ + # Test 14: DROP BASE ON with VCT — colRef cascade + # ============================================================ + def test_drop_base_on_with_vct(self): + """VST Inheritance: drop parent cascades column removal + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, alter + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute(f"alter stable {DB}.leaf_b drop base on {DB}.p_metric") + self._check_inherit_rows("leaf_b", 1) + + tdSql.query(f"select ts, status, quality from {DB}.leaf_b") + tdSql.checkRows(2) + + tdSql.error(f"select val from {DB}.leaf_b") + + # ============================================================ + # Test 15: Re-add dropped parent + # ============================================================ + def test_readd_parent(self): + """VST Inheritance: re-add previously dropped parent + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, alter + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute(f"alter stable {DB}.leaf_b add base on {DB}.p_metric") + self._check_inherit_rows("leaf_b", 2) + + tdSql.query(f"select ts, status, quality from {DB}.leaf_b") + tdSql.checkRows(2) + + # ============================================================ + # Test 16: Non-virtual parent rejected + # ============================================================ + def test_add_non_virtual_parent(self): + """VST Inheritance: reject non-virtual parent + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable regular_stb (ts timestamp, c1 int) tags (t1 int)" + ) + tdSql.error(f"alter stable {DB}.standalone add base on {DB}.regular_stb") + + # ============================================================ + # Test 17: Parent with VCT cannot be inherited (full coverage) + # ============================================================ + def test_parent_with_vct(self): + """VST Inheritance: parent with VCT rejected + + Tests all scenarios: + a) ALTER ADD BASE ON parent with VCT + b) CREATE STABLE BASE ON parent with VCT + c) CREATE multi-parent, one has VCT + d) ALTER multi-parent, one has VCT + e) Drop partial VCTs, still has VCT + f) Drop ALL VCTs, CREATE succeeds + g) Drop ALL VCTs, ALTER succeeds + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + ERR_MSG = "Parent VST already has VCT, cannot be inherited" + + tdSql.execute( + f"create stable vct_parent (ts timestamp, vp_col int) " + f"tags (vp_tag int) virtual 1" + ) + tdSql.execute( + f"create vtable vct_on_parent1 " + f"(vp_col FROM {DB}.src_t1.c1) " + f"using {DB}.vct_parent tags (1)" + ) + tdSql.execute( + f"create vtable vct_on_parent2 " + f"(vp_col FROM {DB}.src_t1.c1) " + f"using {DB}.vct_parent tags (2)" + ) + + # 17a: ALTER path + tdSql.execute( + f"create stable attempt_alter (ts timestamp, aa_col int) " + f"tags (aa_tag int) virtual 1" + ) + tdSql.error( + f"alter stable {DB}.attempt_alter add base on {DB}.vct_parent", + expectErrInfo=ERR_MSG, + ) + + # 17b: CREATE path + tdSql.error( + f"create stable attempt_create (ts timestamp, ac_col int) " + f"tags (ac_tag int) base on {DB}.vct_parent virtual 1", + expectErrInfo=ERR_MSG, + ) + + # 17c: CREATE multi-parent + tdSql.execute( + f"create stable clean_parent (ts timestamp, cp_col float) " + f"tags (cp_tag int) virtual 1" + ) + tdSql.error( + f"create stable attempt_multi (ts timestamp, am_col int) " + f"tags (am_tag int) base on {DB}.clean_parent, {DB}.vct_parent virtual 1", + expectErrInfo=ERR_MSG, + ) + + # 17d: ALTER multi-parent + tdSql.execute( + f"create stable attempt_alter_multi (ts timestamp, aam_col int) " + f"tags (aam_tag int) virtual 1" + ) + tdSql.error( + f"alter stable {DB}.attempt_alter_multi " + f"add base on {DB}.clean_parent, {DB}.vct_parent", + expectErrInfo=ERR_MSG, + ) + + # 17e: Drop one VCT, still has another + tdSql.execute(f"drop table vct_on_parent1") + tdSql.error( + f"create stable attempt_partial (ts timestamp, ap_col int) " + f"tags (ap_tag int) base on {DB}.vct_parent virtual 1", + expectErrInfo=ERR_MSG, + ) + + # 17f: Drop ALL VCTs, CREATE succeeds + tdSql.execute(f"drop table vct_on_parent2") + tdSql.execute( + f"create stable child_after_drop (ts timestamp, cad_col int) " + f"tags (cad_tag int) base on {DB}.vct_parent virtual 1" + ) + self._check_inherit_rows("child_after_drop", 1) + + # 17g: ALTER succeeds after all VCTs dropped + tdSql.execute( + f"create stable alt_after_drop (ts timestamp, aad_col int) " + f"tags (aad_tag int) virtual 1" + ) + tdSql.execute( + f"alter stable {DB}.alt_after_drop add base on {DB}.vct_parent" + ) + self._check_inherit_rows("alt_after_drop", 1) + + # ============================================================ + # Test 18: Cross-DB inheritance rejected + # ============================================================ + def test_cross_db(self): + """VST Inheritance: cross-database rejected + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, error + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute(f"create database if not exists cross_db_test") + tdSql.execute( + f"create stable cross_db_test.xdb_parent (ts timestamp, xc int) " + f"tags (xt int) virtual 1" + ) + tdSql.error( + f"alter stable {DB}.standalone add base on cross_db_test.xdb_parent" + ) + tdSql.execute(f"drop database cross_db_test") + + # ============================================================ + # Test 19: Multi-level inheritance (grandparent → parent → leaf) + # ============================================================ + def test_multi_level_query(self): + """VST Inheritance: multi-level chain query + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance, query + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable gp (ts timestamp, gp_col int) " + f"tags (gp_tag int) virtual 1" + ) + tdSql.execute( + f"create stable mid (ts timestamp, mid_col int) " + f"tags (mid_tag int) base on {DB}.gp virtual 1" + ) + tdSql.execute( + f"create stable leaf_deep (ts timestamp, ld_col int) " + f"tags (ld_tag int) base on {DB}.mid virtual 1" + ) + + tdSql.execute(f"create table src_deep using src_stb tags (99)") + tdSql.execute(f"insert into src_deep values (now, 1, 2.0, 3.0)") + tdSql.execute(f"insert into src_deep values (now+1s, 4, 5.0, 6.0)") + + tdSql.execute( + f"create vtable vct_deep " + f"(gp_col FROM {DB}.src_deep.c1, " + f" mid_col FROM {DB}.src_deep.c1, " + f" ld_col FROM {DB}.src_deep.c1) " + f"using {DB}.leaf_deep " + f"tags (3, 2, 1)" + ) + + tdSql.query(f"select * from {DB}.leaf_deep") + tdSql.checkRows(2) + + # ============================================================ + # Test 20: Diamond inheritance + # ============================================================ + def test_diamond_inheritance(self): + """VST Inheritance: diamond topology + + Catalog: + - VirtualTable + + Since: v3.4.1.0 + + Labels: virtual, inheritance + + Jira: None + + History: + - 2025-5-10 Created + """ + tdSql.execute(f"use {DB}") + + tdSql.execute( + f"create stable dia_a (ts timestamp, da_col int) " + f"tags (da_tag int) virtual 1" + ) + tdSql.execute( + f"create stable dia_b (ts timestamp, db_col int) " + f"tags (db_tag int) virtual 1" + ) + tdSql.execute( + f"create stable dia_leaf1 (ts timestamp, dl1_col int) " + f"tags (dl1_tag int) base on {DB}.dia_a, {DB}.dia_b virtual 1" + ) + tdSql.execute( + f"create stable dia_leaf2 (ts timestamp, dl2_col int) " + f"tags (dl2_tag int) base on {DB}.dia_a, {DB}.dia_b virtual 1" + ) + + self._check_inherit_rows("dia_leaf1", 2) + self._check_inherit_rows("dia_leaf2", 2) + + tdSql.execute(f"create table src_dia1 using src_stb tags (10)") + tdSql.execute(f"insert into src_dia1 values (now, 111, 1.1, 11.1)") + + tdSql.execute(f"create table src_dia2 using src_stb tags (20)") + tdSql.execute(f"insert into src_dia2 values (now, 222, 2.2, 22.2)") + tdSql.execute(f"insert into src_dia2 values (now+1s, 333, 3.3, 33.3)") + + tdSql.execute( + f"create vtable vct_dia1 " + f"(da_col FROM {DB}.src_dia1.c1, " + f" db_col FROM {DB}.src_dia1.c1, " + f" dl1_col FROM {DB}.src_dia1.c1) " + f"using {DB}.dia_leaf1 " + f"tags (10, 100, 1)" + ) + tdSql.execute( + f"create vtable vct_dia2 " + f"(da_col FROM {DB}.src_dia2.c1, " + f" db_col FROM {DB}.src_dia2.c1, " + f" dl2_col FROM {DB}.src_dia2.c1) " + f"using {DB}.dia_leaf2 " + f"tags (20, 200, 2)" + ) diff --git a/test/ci/cases.task b/test/ci/cases.task index 1d96e96ba549..2e796c4e6d0e 100644 --- a/test/ci/cases.task +++ b/test/ci/cases.task @@ -236,6 +236,7 @@ #,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_performance.py #,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_stress.py ,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vtable_batch_set_tag_vals.py +,,y,.,./ci/pytest.sh pytest cases/05-VirtualTables/test_vst_inheritance_cascade.py # 06-DataIngestion ## 01-SQL diff --git a/tests/system-test/0-others/vst_inheritance_ddl.py b/tests/system-test/0-others/vst_inheritance_ddl.py new file mode 100644 index 000000000000..c899a870692f --- /dev/null +++ b/tests/system-test/0-others/vst_inheritance_ddl.py @@ -0,0 +1,182 @@ +import taos +import sys + +from util.log import * +from util.sql import * +from util.cases import * + + +class TDTestCase: + + def init(self, conn, logSql, replicaVar=1): + self.replicaVar = int(replicaVar) + tdLog.debug(f"start to execute {__file__}") + tdSql.init(conn.cursor()) + + def run(self): + dbname = "test_vst_inherit" + + tdLog.printNoPrefix("==========step0: create database") + tdSql.execute(f"drop database if exists {dbname}") + tdSql.execute(f"create database {dbname}") + tdSql.execute(f"use {dbname}") + + tdLog.printNoPrefix("==========step1: create parent VSTs") + tdSql.execute( + f"create stable parent_a (ts timestamp, col_a1 int, col_a2 float) " + f"tags (tag_a1 int) virtual 1" + ) + tdSql.execute( + f"create stable parent_b (ts timestamp, col_b1 bigint) " + f"tags (tag_b1 binary(32)) virtual 1" + ) + + tdLog.printNoPrefix("==========step2: create child VST inheriting one parent") + tdSql.execute( + f"create stable child_single (ts timestamp, own_col int) " + f"tags (own_tag nchar(16)) base on {dbname}.parent_a virtual 1" + ) + + tdLog.printNoPrefix("==========step3: create child VST inheriting multiple parents") + tdSql.execute( + f"create stable child_multi (ts timestamp, own_c1 double) " + f"tags (own_t1 int) base on {dbname}.parent_a, {dbname}.parent_b virtual 1" + ) + + tdLog.printNoPrefix("==========step4: verify ins_vstable_inherits") + tdSql.query(f"select * from information_schema.ins_vstable_inherits") + # child_single inherits from parent_a (1 row) + # child_multi inherits from parent_a and parent_b (2 rows) + tdSql.checkRows(3) + + tdLog.printNoPrefix("==========step5: error - inherit from non-virtual table") + tdSql.execute( + f"create stable normal_stb (ts timestamp, c1 int) tags (t1 int)" + ) + tdSql.error( + f"create stable bad_child (ts timestamp, c1 int) " + f"tags (t1 int) base on {dbname}.normal_stb virtual 1" + ) + + tdLog.printNoPrefix("==========step6: error - exceed max parents (>10)") + # We only have 2 parents, so create 9 more to test the limit + for i in range(2, 11): + tdSql.execute( + f"create stable parent_{i} (ts timestamp, col_{i} int) " + f"tags (tag_{i} int) virtual 1" + ) + # Now try to inherit from 11 parents (exceeds max 10) + parent_list = ", ".join([f"{dbname}.parent_{i}" for i in range(2, 12)]) + # parent_12 doesn't exist, but the count check should fail first + # Actually let's create the 11th: + tdSql.execute( + f"create stable parent_11 (ts timestamp, col_11 int) " + f"tags (tag_11 int) virtual 1" + ) + parent_list = ", ".join([f"{dbname}.parent_a", f"{dbname}.parent_b"] + + [f"{dbname}.parent_{i}" for i in range(2, 11)]) + # That's 11 parents - should fail + tdSql.error( + f"create stable too_many (ts timestamp, c1 int) " + f"tags (t1 int) base on {parent_list} virtual 1" + ) + + tdLog.printNoPrefix("==========step7: error - cross DB inheritance") + tdSql.execute(f"create database other_db") + tdSql.execute(f"create stable other_db.other_parent (ts timestamp, c1 int) tags (t1 int) virtual 1") + tdSql.error( + f"create stable {dbname}.cross_child (ts timestamp, c1 int) " + f"tags (t1 int) base on other_db.other_parent virtual 1" + ) + tdSql.execute(f"drop database other_db") + + tdLog.printNoPrefix("==========step8: error - drop parent with children") + tdSql.error(f"drop stable {dbname}.parent_a") + + tdLog.printNoPrefix("==========step9: SHOW VTABLE INHERITS") + tdSql.query(f"show vtable inherits") + tdSql.checkRows(3) + + tdLog.printNoPrefix("==========step10: error - column name conflict") + tdSql.execute( + f"create stable conflict_parent (ts timestamp, col_a1 int) " + f"tags (tag_conf int) virtual 1" + ) + # col_a1 conflicts with parent_a.col_a1 + tdSql.error( + f"create stable conflict_child (ts timestamp, own_col int) " + f"tags (own_tag int) base on {dbname}.parent_a, {dbname}.conflict_parent virtual 1" + ) + + tdLog.printNoPrefix("==========step11: error - tag name conflict") + tdSql.execute( + f"create stable tag_conflict_parent (ts timestamp, col_tc int) " + f"tags (tag_a1 int) virtual 1" + ) + # tag_a1 conflicts with parent_a.tag_a1 + tdSql.error( + f"create stable tag_conflict_child (ts timestamp, own_col int) " + f"tags (own_tag int) base on {dbname}.parent_a, {dbname}.tag_conflict_parent virtual 1" + ) + + tdLog.printNoPrefix("==========step12: error - circular inheritance") + # parent_a already exists, child_single inherits from parent_a + # Try to make parent_a inherit from child_single → cycle + tdSql.error( + f"alter stable {dbname}.parent_a add base on {dbname}.child_single" + ) + + tdLog.printNoPrefix("==========step13: ALTER ADD BASE ON") + tdSql.execute( + f"create stable parent_new (ts timestamp, col_new int) " + f"tags (tag_new binary(8)) virtual 1" + ) + tdSql.execute( + f"alter stable {dbname}.child_single add base on {dbname}.parent_new" + ) + tdSql.query(f"select * from information_schema.ins_vstable_inherits " + f"where child_stable_name = 'child_single'") + tdSql.checkRows(2) + + tdLog.printNoPrefix("==========step14: ALTER DROP BASE ON") + tdSql.execute( + f"alter stable {dbname}.child_single drop base on {dbname}.parent_new" + ) + tdSql.query(f"select * from information_schema.ins_vstable_inherits " + f"where child_stable_name = 'child_single'") + tdSql.checkRows(1) + + tdLog.printNoPrefix("==========step15: SHOW CREATE STABLE with BASE ON") + tdSql.query(f"show create stable {dbname}.child_single") + tdSql.checkRows(1) + create_stmt = tdSql.queryResult[0][1] + tdLog.info(f"SHOW CREATE STABLE child_single: {create_stmt}") + if "BASE ON" not in create_stmt: + tdLog.exit("SHOW CREATE STABLE should contain BASE ON clause") + + tdLog.printNoPrefix("==========step16: non-leaf VST cannot have VCT") + # parent_a has child_single as a child - it's non-leaf + # Try creating a VCT under parent_a - should fail + tdSql.error( + f"create vtable vct_on_nonleaf using {dbname}.parent_a " + f"tags (tag_a1 1) (ts `{dbname}`.`parent_a`.`ts`, col_a1 `{dbname}`.`parent_a`.`col_a1`)" + ) + + tdLog.printNoPrefix("==========step17: verify leaf VST can still have VCT") + # child_multi is a leaf - should be able to create VCT + # (This tests positive VCT creation on leaf) + tdSql.execute( + f"create vtable vct_on_leaf using {dbname}.child_multi " + f"tags (own_t1 100) (ts `{dbname}`.`child_multi`.`ts`, own_c1 `{dbname}`.`child_multi`.`own_c1`)" + ) + + tdLog.printNoPrefix("==========step18: cleanup") + tdSql.execute(f"drop database {dbname}") + + def stop(self): + tdSql.close() + tdLog.success(f"{__file__} successfully executed") + + +tdCases.addLinux(__file__, TDTestCase()) +tdCases.addWindows(__file__, TDTestCase())