fix(tdengine): generate data for stable tables

This commit is contained in:
t8y2 2026-07-27 11:15:18 +08:00
parent 34b7814c5c
commit 98bf3d1dfd
No known key found for this signature in database
3 changed files with 130 additions and 6 deletions

View File

@ -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,
};
}),

View File

@ -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<string, unknown>();
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");

View File

@ -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/);
});