From 98bf3d1dfdca4cdc95bd0fa0b766e54eeeb4ca07 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 27 Jul 2026 11:15:18 +0800 Subject: [PATCH] fix(tdengine): generate data for stable tables --- .../generate/DataGenerateDialog.vue | 15 +++- apps/desktop/src/lib/dataGrid/dataGenerate.ts | 45 +++++++++-- packages/app-tests/dataGenerate.test.ts | 76 +++++++++++++++++++ 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/components/generate/DataGenerateDialog.vue b/apps/desktop/src/components/generate/DataGenerateDialog.vue index b31466350..633687e0f 100644 --- a/apps/desktop/src/components/generate/DataGenerateDialog.vue +++ b/apps/desktop/src/components/generate/DataGenerateDialog.vue @@ -71,6 +71,14 @@ function colKey(schema: string, table: string, column: string) { return `${schema}.${table}.${column}`; } +function tableInfo(schema: string, table: string): TableInfo | undefined { + return schemaTables[schema]?.find((item) => item.name === table); +} + +function isTdengineTagColumn(column: ColumnInfo): boolean { + return (column.extra ?? "").toUpperCase().includes("TAG") || (column.comment ?? "").toUpperCase() === "TAG"; +} + // Derived state for template const activeCfg = computed(() => { const k = panelTableKey.value; @@ -109,11 +117,13 @@ async function loadSchemas() { try { const tables = await api.listTables(cid, db, targetSchema); schemaTables[targetSchema] = tables; - if (tables.some((t: { name: string }) => t.name === props.prefillTable)) { + const prefillTableInfo = tables.find((table) => table.name === props.prefillTable); + if (prefillTableInfo) { const cols = await api.getColumns(cid, db, targetSchema, props.prefillTable); const key = tableKey(targetSchema, props.prefillTable); configs[key] = { tableName: props.prefillTable, + tableType: prefillTableInfo.table_type, schema: targetSchema, database: db, rowCount: 1000, @@ -138,6 +148,7 @@ async function loadSchemas() { gKey, ), isAutoIncrement: isAI, + isTag: isTdengineTagColumn(c), columnDefault: c.column_default, }; }), @@ -210,6 +221,7 @@ async function loadColumns(schema: string, table: string) { const cols = await api.getColumns(props.prefillConnectionId, props.prefillDatabase, schema, table); const cfg: TableGenerateConfig = { tableName: table, + tableType: tableInfo(schema, table)?.table_type, schema, database: props.prefillDatabase, rowCount: 1000, @@ -234,6 +246,7 @@ async function loadColumns(schema: string, table: string) { gKey, ), isAutoIncrement: isAI, + isTag: isTdengineTagColumn(c), columnDefault: c.column_default, }; }), diff --git a/apps/desktop/src/lib/dataGrid/dataGenerate.ts b/apps/desktop/src/lib/dataGrid/dataGenerate.ts index c9fb878ad..52fab7fed 100644 --- a/apps/desktop/src/lib/dataGrid/dataGenerate.ts +++ b/apps/desktop/src/lib/dataGrid/dataGenerate.ts @@ -204,11 +204,13 @@ export interface ColumnGenerateConfig { generatorCategoryLabel?: string; generatorParams?: GeneratorParams; isAutoIncrement?: boolean; + isTag?: boolean; columnDefault?: string | null; } export interface TableGenerateConfig { tableName: string; + tableType?: string; schema: string; database: string; rowCount: number; @@ -1791,20 +1793,53 @@ function buildOracleInsertStatements(targetTable: string, columnList: string, va return statements; } +function isTdengineStableGenerate(config: TableGenerateConfig, databaseType?: DatabaseType): boolean { + if (databaseType !== "tdengine") return false; + const tableType = config.tableType?.trim().toUpperCase(); + return tableType === "STABLE" || tableType === "SUPER TABLE" || tableType === "SUPERTABLE" || (!tableType && config.columns.some((column) => column.isTag)); +} + +function generateTdengineChildTableName(): string { + const randomSuffix = Math.random().toString(36).slice(2, 8).padEnd(6, "0"); + return `dbx_gen_${Date.now().toString(36)}_${randomSuffix}`; +} + export function generateTableData(config: TableGenerateConfig, databaseType?: DatabaseType): GenerateResult { - const colNames = config.columns.map((c) => c.columnName); + const isTdengineStable = isTdengineStableGenerate(config, databaseType); + const hasTbnameColumn = config.columns.some((column) => column.columnName.toLowerCase() === "tbname"); + const shouldAddTbname = isTdengineStable && !hasTbnameColumn; + const tdengineChildTableName = shouldAddTbname ? generateTdengineChildTableName() : null; + const tagValues = new Map(); + const colNames = shouldAddTbname ? ["tbname", ...config.columns.map((c) => c.columnName)] : config.columns.map((c) => c.columnName); const rows: unknown[][] = []; for (let i = 0; i < config.rowCount; i++) { - const row = config.columns.map((col) => generateValue(col.columnName, col.dataType, col.generatorKey, i, col.generatorParams, col.isAutoIncrement ? null : col.columnDefault)); - rows.push(row); + const row = config.columns.map((col) => { + if (isTdengineStable && col.isTag && tagValues.has(col.columnName)) { + return tagValues.get(col.columnName); + } + const value = generateValue(col.columnName, col.dataType, col.generatorKey, i, col.generatorParams, col.isAutoIncrement ? null : col.columnDefault); + if (isTdengineStable && col.isTag) { + tagValues.set(col.columnName, value); + } + return value; + }); + rows.push(shouldAddTbname ? [tdengineChildTableName, ...row] : row); } - const quotedCols = config.columns.map((c) => quoteTableIdentifier(databaseType, c.columnName)); + const quotedCols = colNames.map((column) => quoteTableIdentifier(databaseType, column)); const targetTable = qualifiedTableName({ databaseType, schema: config.schema, tableName: config.tableName, database: config.database }); const columnList = quotedCols.join(", "); const insertPrefix = `INSERT INTO ${targetTable} (${columnList}) VALUES`; - const valueRows = rows.map((row) => `(${row.map((value, index) => formatGeneratedValue(value, databaseType, config.columns[index]?.dataType)).join(", ")})`); + const valueRows = rows.map( + (row) => + `(${row + .map((value, index) => { + const configIndex = shouldAddTbname ? index - 1 : index; + return formatGeneratedValue(value, databaseType, configIndex >= 0 ? config.columns[configIndex]?.dataType : undefined); + }) + .join(", ")})`, + ); const statements = databaseType === "oracle" ? buildOracleInsertStatements(targetTable, columnList, valueRows) : supportsGeneratedMultiRowValues(databaseType) ? [`${insertPrefix}\n${valueRows.join(",\n")};`] : valueRows.map((values) => `${insertPrefix} ${values};`); const sql = statements.join("\n"); diff --git a/packages/app-tests/dataGenerate.test.ts b/packages/app-tests/dataGenerate.test.ts index b1f74ccc8..7a397a288 100644 --- a/packages/app-tests/dataGenerate.test.ts +++ b/packages/app-tests/dataGenerate.test.ts @@ -165,3 +165,79 @@ test("batches large Oracle data generation statements", () => { assert.equal(result.statements[0].match(/\n INTO /g)?.length, 100); assert.equal(result.statements[1].match(/\n INTO /g)?.length, 1); }); + +test("generates TDengine stable rows with one child table identity and stable tag values", () => { + const result = generateTableData( + { + tableName: "sensor_data", + tableType: "STABLE", + schema: "dbx_issue4512", + database: "dbx_issue4512", + rowCount: 2, + columns: [ + { + columnName: "ts", + dataType: "TIMESTAMP", + rowCount: 2, + generatorKey: "sequence", + generatorParams: { startValue: 1, increment: 1 }, + }, + { + columnName: "temperature", + dataType: "FLOAT", + rowCount: 2, + generatorKey: "sequence", + generatorParams: { startValue: 20, increment: 1 }, + }, + { + columnName: "device_id", + dataType: "BINARY(64)", + rowCount: 2, + generatorKey: "sequence", + generatorParams: { startValue: 100, increment: 1 }, + isTag: true, + }, + ], + }, + "tdengine", + ); + + assert.deepEqual(result.columns, ["tbname", "ts", "temperature", "device_id"]); + assert.match(String(result.rows[0][0]), /^dbx_gen_[a-z0-9]+_[a-z0-9]+$/); + assert.equal(result.rows[1][0], result.rows[0][0]); + assert.deepEqual( + result.rows.map((row) => row.slice(1)), + [ + [1, 20, 100], + [2, 21, 100], + ], + ); + assert.match(result.sql, /^INSERT INTO `sensor_data` \(`tbname`, `ts`, `temperature`, `device_id`\) VALUES\n/); + assert.equal(result.sql.match(/'dbx_gen_[a-z0-9]+_[a-z0-9]+'/g)?.length, 2); +}); + +test("keeps ordinary TDengine table generation unchanged", () => { + const result = generateTableData( + { + tableName: "sensor_data_001", + tableType: "TABLE", + schema: "dbx_issue4512", + database: "dbx_issue4512", + rowCount: 1, + columns: [ + { + columnName: "ts", + dataType: "TIMESTAMP", + rowCount: 1, + generatorKey: "sequence", + generatorParams: { startValue: 1, increment: 1 }, + }, + ], + }, + "tdengine", + ); + + assert.deepEqual(result.columns, ["ts"]); + assert.deepEqual(result.rows, [[1]]); + assert.doesNotMatch(result.sql, /tbname/); +});