feat: 完成 issue #29 TDengine 超级表 schema 依据点位字典自动生成

This commit is contained in:
2026-08-04 18:14:37 +08:00
parent 5d937e8efd
commit 691a812fcf
5 changed files with 125 additions and 1 deletions
@@ -10,8 +10,10 @@ import _bootstrap # noqa: F401
from data_bus.tdengine_schema import (
PointSpec,
build_batch_insert,
generate_schema,
generate_subtable_ddls,
generate_supertable_ddl,
specs_from_point_dict_rows,
)
from data_bus.templating import TemplateNaming
@@ -22,6 +24,19 @@ def sample_point(device_id="CLF-01", point_id="CLF-01.TEMP", unit="℃", data_ty
return PointSpec(device_id=device_id, point_id=point_id, unit=unit, data_type=data_type)
# 与 edge-gateway 点位字典 CSV 表头对齐的样例行
SAMPLE_POINT_DICT_ROWS = [
{"device_id": "CLF-01", "point_id": "CLF-01.TEMP", "name": "炉温", "unit": "℃",
"dataType": "float", "sampleRate": "1000", "qualityCode": "true", "opcNode": "ns=2;s=CLF.Temp"},
{"device_id": "CLF-01", "point_id": "CLF-01.PRES", "name": "炉压", "unit": "kPa",
"dataType": "float", "sampleRate": "1000", "qualityCode": "true"},
{"device_id": "CLF-02", "point_id": "CLF-02.RUN", "name": "运行状态", "unit": "%",
"dataType": "bool", "sampleRate": "1000"},
{"device_id": "S7-01", "point_id": "S7-01.PUMP_A", "name": "泵A频率", "unit": "Hz",
"dataType": "float", "sampleRate": "1000", "protocol": "s7"},
]
class SupertableDDLTest(unittest.TestCase):
def test_ddl_shape(self):
ddl = generate_supertable_ddl(NAMING)
@@ -72,5 +87,49 @@ class BatchInsertTest(unittest.TestCase):
self.assertEqual(build_batch_insert(NAMING, []), [])
class PointDictSchemaTest(unittest.TestCase):
"""依据点位字典自动生成完整 schema(子任务 #29 交付口径)。"""
def test_specs_from_point_dict_rows(self):
specs = specs_from_point_dict_rows(SAMPLE_POINT_DICT_ROWS)
self.assertEqual(len(specs), 4)
self.assertEqual(specs[0].device_id, "CLF-01")
self.assertEqual(specs[0].point_id, "CLF-01.TEMP")
self.assertEqual(specs[0].unit, "℃")
self.assertEqual(specs[0].data_type, "float")
# 兼容小写 data_type 键
specs2 = specs_from_point_dict_rows(
[{"device_id": "CLF-01", "point_id": "P1", "unit": "℃", "data_type": "int"}]
)
self.assertEqual(specs2[0].data_type, "int")
# 缺失列宽松兜底
specs3 = specs_from_point_dict_rows([{"device_id": "D1", "point_id": "P2"}])
self.assertEqual(specs3[0].unit, "")
self.assertEqual(specs3[0].data_type, "float")
def test_generate_schema_full(self):
specs = specs_from_point_dict_rows(SAMPLE_POINT_DICT_ROWS)
ddls = generate_schema(NAMING, specs)
# 第 0 条是超级表,其余是子表;4 个测点 → 1 + 4 条
self.assertEqual(len(ddls), 5)
self.assertTrue(ddls[0].startswith("CREATE STABLE IF NOT EXISTS ti_cl4_points"))
self.assertTrue(all(d.startswith("CREATE TABLE IF NOT EXISTS ti_cl4_pt_") for d in ddls[1:]))
# 子表均 USING 同一超级表,且含点位字典标签
self.assertIn("USING ti_cl4_points", ddls[1])
self.assertIn("'CLF-01'", ddls[1])
# bool 点位标签保留 data_type
self.assertTrue(any("'bool'" in d for d in ddls))
def test_generate_schema_retention(self):
specs = specs_from_point_dict_rows(SAMPLE_POINT_DICT_ROWS)
ddls = generate_schema(NAMING, specs, retention_days=365)
self.assertIn("KEEP(365)", ddls[0])
def test_generate_schema_empty_points(self):
ddls = generate_schema(NAMING, [])
self.assertEqual(len(ddls), 1) # 只有超级表,无子表
self.assertIn("CREATE STABLE", ddls[0])
if __name__ == "__main__":
unittest.main()