6 Commits

Author SHA1 Message Date
ad6e4c7491 perf(scan): flattenDataToPtrs
减少了循环次数
优化了名称
2025-11-28 13:45:19 +08:00
b9413d6c40 perf(scan): 使用缓存减少对象扫描
抽取函数,优化变量命名
支持了传入数组类型
2025-11-27 18:24:36 +08:00
581b0b60eb feat: 补充了 readme.md 文件
1. 新增集成调试说明
2. 新增tag提交说明
2025-11-27 18:20:38 +08:00
ac1054d833 feat(ScanRows): 新增 scanRows 转map数组 2025-06-25 17:42:22 +08:00
340deecefb feat(ToInsert): 支持数组直接传入 2025-06-20 14:13:24 +08:00
09770b07dc fix(insert_build): 修复在生成sql语句时,时间对象精度损失问题 2025-06-19 17:10:47 +08:00
8 changed files with 466 additions and 186 deletions

2
.gitignore vendored
View File

@@ -22,3 +22,5 @@
go.work go.work
.idea .idea
*mise*.toml

110
README.md
View File

@@ -4,6 +4,10 @@
## 一、简单使用
### struct 标签 ### struct 标签
``` ```
@@ -81,7 +85,7 @@ func QueryOne(db *sql.DB) (*User,error){
if err!=nil { if err!=nil {
return err return err
} }
def rows.Close() defer rows.Close()
var user User var user User
if err:=ScanRows(&user,rows);err!=nil { if err:=ScanRows(&user,rows);err!=nil {
@@ -98,7 +102,7 @@ func QueryAll(db *sql.DB)([]*User, error) {
if err!=nil { if err!=nil {
return err return err
} }
def rows.Close() defer rows.Close()
var users []*User var users []*User
if err:=ScanRows(&users,rows);err!=nil { if err:=ScanRows(&users,rows);err!=nil {
@@ -110,5 +114,107 @@ func QueryAll(db *sql.DB)([]*User, error) {
## 二、嵌入其他项目调试
### 源码集成调试
例如 想要在 UserProject 中进行调试
我们可以使用 go wrok 来集成
现在 tdengine-mapper-go 和 UserProject 位于同一个目录
```text
cd UserProject
go work init
go work add .
go work add ../tdengine-mapper-go
```
这样将会得到一个 go.work 文件
```text
go 1.24.10
use (
.
../tdengine-mapper-go
)
```
接下来 就可以直接在 UserProject 修改 tdengine-mapper-go中的源码了当然在 tdengine-mapper-go 项目中直接修改也是可以的
#### 善后
调试完成后,在 tdengine-mapper-go 将代码提交
删除 UserProject 中的 go.work 文件
## 三、打标签提交
在其他项目中使用,实际上拉去的是 tdengine-mapper-go 标签版本,也就是说当我们完成之后,只是提交了 master 分支还不够,必须要将代码打上版本标签才然后推送出去,别人才能拉去到项目。
现在在假设你已经完成了代码的提交。
**git tag 操作**
```bash
# 查看现有 tag
git tag # 展示所有tag
git tag -n # 展示tag和提交信息
git show 标签名 # 查看某个 tag
# 创建 tag
git tag -a '版本号' -m '提交信息' # 打标签
git tag -am '提交信息' 版本号
# tag 重命名
git tag 老标签名 新标签名
git tag -d 老标签名
# 推送 tag
git push origin 标签名 # 推送单个 tag
git push origin --tags # 推送全部 tag
# 删除 tag
git tag -d 标签名 # 本地删除 tag
git push origin --delete 标签名 # 远程删除 tag
# 检出 tag
git checkout -b 分支名称 标签名
git checkout 标签名
```
开始操作
```bash
// 先看下现在已经拥有的tag标签
$ git tag
v0.0.1 v0.0.2 v0.0.3 v0.0.4 v0.0.5 v0.0.6 v0.0.7 v0.0.8 v0.0.9 v0.1.0
// 命名新的tag标签 并写上标签的 说明
// 比如提交内容是这样的:
// 1. 补充了 readme.md 上的调试说明
// 2. 修复了空指针的 BUG
$ git tag -a 'v0.1.1' -m "1. 补充了 readme.md 上的调试说明
2. 修复了空指针的 BUG"
// 验证 查看 tag 信息
git show v0.1.1
// 然后推送到服务器
$ git push origin v0.1.1
```

View File

@@ -11,14 +11,14 @@ import (
// buildInsertStatementForSuperTable 构建超级表插入语句 // buildInsertStatementForSuperTable 构建超级表插入语句
// @param rows 同一超级表下的数据行 // @param rows 同一超级表下的数据行
// @return 插入语句 // @return 插入语句
func buildInsertStatementForSuperTable(rows ...*TableRowMateria) (string, error) { func buildInsertStatementForSuperTable(rows ...*TableRowMaterial) (string, error) {
if len(rows) == 0 { if len(rows) == 0 {
return "", fmt.Errorf("no rows provided for super table insert") return "", fmt.Errorf("no rows provided for super table insert")
} }
var sb strings.Builder var sb strings.Builder
// 格式化标签和值 // 开始格式化标签和值
formattedTagColumns := formatColumns(rows[0].TagColumns...) formattedTagColumns := formatColumns(rows[0].TagColumns...)
formattedTagValues := formatRowValues(rows[0].TagValues...) formattedTagValues := formatRowValues(rows[0].TagValues...)
formattedColumns := formatColumns(rows[0].Columns...) formattedColumns := formatColumns(rows[0].Columns...)
@@ -56,7 +56,7 @@ func buildInsertStatementForSuperTable(rows ...*TableRowMateria) (string, error)
// buildInsertStatementForNormalTable 普通表插入构建 // buildInsertStatementForNormalTable 普通表插入构建
// @param data 数据 当前应该是同一个表的 // @param data 数据 当前应该是同一个表的
// @return 插入语句 表名1 (列名) VALUES (),() // @return 插入语句 表名1 (列名) VALUES (),()
func buildInsertStatementForNormalTable(rows ...*TableRowMateria) (string, error) { func buildInsertStatementForNormalTable(rows ...*TableRowMaterial) (string, error) {
if len(rows) == 0 { if len(rows) == 0 {
return "", fmt.Errorf("no data provided for normal table insert") return "", fmt.Errorf("no data provided for normal table insert")
} }
@@ -137,7 +137,7 @@ func formatRowValues(values ...any) string {
} }
case sql.NullTime: case sql.NullTime:
if v.Valid { if v.Valid {
formattedValues[i] = fmt.Sprintf("'%s'", v.Time.Format(time.RFC3339)) formattedValues[i] = fmt.Sprintf("'%s'", v.Time.Format(time.RFC3339Nano))
} else { } else {
formattedValues[i] = "null" formattedValues[i] = "null"
} }
@@ -148,7 +148,7 @@ func formatRowValues(values ...any) string {
case nil: case nil:
formattedValues[i] = "null" formattedValues[i] = "null"
case time.Time: case time.Time:
formattedValues[i] = fmt.Sprintf("'%s'", v.Format(time.RFC3339)) formattedValues[i] = fmt.Sprintf("'%s'", v.Format(time.RFC3339Nano))
default: default:
if reflect.TypeOf(val).Kind() == reflect.Ptr && reflect.ValueOf(val).IsNil() { if reflect.TypeOf(val).Kind() == reflect.Ptr && reflect.ValueOf(val).IsNil() {
formattedValues[i] = "null" formattedValues[i] = "null"

View File

@@ -19,35 +19,141 @@ type TableNamer interface {
} }
type Mapper struct { type Mapper struct {
structMateMap SyncMap[string, *StructMate] // 结构体 元信息缓存 key: struct 的唯一类型名称 value: 元信息 structMateMap SyncMap[string, *StructMeta] // 结构体 元信息缓存 key: struct 的唯一类型名称 value: 元信息
} }
func (b *Mapper) scanStruct(data ...any) error { func (b *Mapper) scanStruct(data ...any) error {
for _, datum := range data { for _, datum := range data {
if reflect.TypeOf(datum).Kind() != reflect.Ptr { if reflect.TypeOf(datum).Kind() != reflect.Ptr {
//return fmt.Errorf("需要指针类型:%v", reflect.TypeOf(datum)) return fmt.Errorf("need a pointer type: %v", reflect.TypeOf(data))
return fmt.Errorf("need a pointer type: %v", reflect.TypeOf(datum))
} }
if mate, err := scan(datum); err != nil { // 分析结构体并缓存结果
if _, err := b.analyzeStructWithCache(datum); err != nil {
return err return err
} else {
b.structMateMap.Store(mate.UniqueTypeName, mate)
} }
} }
return nil return nil
} }
// analyzeStructWithCache 带缓存的结构体分析
func (b *Mapper) analyzeStructWithCache(data any) (*StructMeta, error) {
// 获取结构体的类型
t, v := extractReflectInfo(data)
// 确保是结构体类型
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("input data is not a struct")
}
// 获取类型的唯一标识符
uniqueTypeName := buildTypeKey(t)
// 先检查缓存
if cached, ok := b.structMateMap.Load(uniqueTypeName); ok {
return cached, nil
}
// 初始化结果结构体
sr := StructMeta{
UniqueTypeName: uniqueTypeName,
DBName2IndexCache: make(map[string][]int, t.NumField()),
Field2IndexCache: make(map[string][]int, t.NumField()),
Field2DBNameCache: make(map[string]string, t.NumField()),
DBAnnotatedNames: make([]string, 0, t.NumField()),
TaggedFieldNames: make([]string, 0, t.NumField()),
SuperTableName: "",
}
// 遍历结构体的字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldValue := v.Field(i)
if field.Anonymous {
// 处理匿名结构体
if field.Type.Kind() == reflect.Ptr {
if fieldValue.IsNil() {
// 如果指针是 nil创建一个该类型的零值实例
zeroValue := reflect.Zero(field.Type.Elem())
fieldValue = zeroValue
} else {
fieldValue = fieldValue.Elem()
}
}
if !fieldValue.CanInterface() {
continue
}
// 递归分析匿名结构体,使用相同的缓存
subResult, err := b.analyzeStructWithCache(fieldValue.Interface())
if err != nil {
return nil, err
}
// 合并分析结果
for k, v := range subResult.DBName2IndexCache {
sr.DBName2IndexCache[k] = append(sr.DBName2IndexCache[k], i)
sr.DBName2IndexCache[k] = append(sr.DBName2IndexCache[k], v...)
}
for k, v := range subResult.Field2IndexCache {
sr.Field2IndexCache[k] = append(sr.Field2IndexCache[k], i)
sr.Field2IndexCache[k] = append(sr.Field2IndexCache[k], v...)
}
for k, v := range subResult.Field2DBNameCache {
sr.Field2DBNameCache[k] = v
}
sr.DBAnnotatedNames = append(sr.DBAnnotatedNames, subResult.DBAnnotatedNames...)
sr.TaggedFieldNames = append(sr.TaggedFieldNames, subResult.TaggedFieldNames...)
continue
}
// 处理普通字段
columnName := field.Tag.Get("db")
if columnName == "-" || columnName == "" {
continue
}
if len(sr.Field2IndexCache[field.Name]) > 0 {
return nil, fmt.Errorf("duplicate field [%s %s `db:%s`]", field.Name, field.Type.Name(), columnName)
}
sr.Field2IndexCache[field.Name] = append([]int{}, i)
sr.DBName2IndexCache[columnName] = append(sr.DBName2IndexCache[columnName], i)
sr.Field2DBNameCache[field.Name] = columnName
// 检查字段是否有taos注解
if field.Tag.Get("taos") == "tag" {
sr.TaggedFieldNames = append(sr.TaggedFieldNames, field.Name)
} else {
sr.DBAnnotatedNames = append(sr.DBAnnotatedNames, field.Name)
}
}
if sTableName, ok := tryGetSuperTableName(data); ok {
sr.SuperTableName = sTableName
}
// 将结果存入缓存
b.structMateMap.Store(uniqueTypeName, &sr)
return &sr, nil
}
// 提取 struct 内的信息 // 提取 struct 内的信息
// @param data 包含 struct 类型的数据 // @param data 包含 struct 类型的数据
// @return 返回一个 map[表名][]*TableRowMateria 如果超级表名为空,则表示普通表 // @return 返回一个 map[表名][]*TableRowMaterial 如果超级表名为空,则表示普通表
func (b *Mapper) extractStructData(data ...any) (map[string][]*TableRowMateria, error) { func (b *Mapper) extractTableMaterial(data ...any) (map[string][]*TableRowMaterial, error) {
result := make(map[string][]*TableRowMateria) result := make(map[string][]*TableRowMaterial)
for _, item := range data { for _, item := range data {
tf, vf := getReflectTypeAndValue(item) tf, vf := extractReflectInfo(item)
uniqueTypeName := getUniqueTypeName(tf) uniqueTypeName := buildTypeKey(tf)
// 获取表名 // 获取表名
var tableName string var tableName string
@@ -74,7 +180,7 @@ func (b *Mapper) extractStructData(data ...any) (map[string][]*TableRowMateria,
return nil, fmt.Errorf("not found struct type: %s", uniqueTypeName) return nil, fmt.Errorf("not found struct type: %s", uniqueTypeName)
} }
materia := &TableRowMateria{ trMaterial := &TableRowMaterial{
SuperTableName: mate.SuperTableName, SuperTableName: mate.SuperTableName,
TableName: tableName, TableName: tableName,
TagColumns: make([]string, 0, len(mate.TaggedFieldNames)), TagColumns: make([]string, 0, len(mate.TaggedFieldNames)),
@@ -85,7 +191,7 @@ func (b *Mapper) extractStructData(data ...any) (map[string][]*TableRowMateria,
for _, name := range mate.DBAnnotatedNames { for _, name := range mate.DBAnnotatedNames {
// db tag 名称 -- 数据库列名 // db tag 名称 -- 数据库列名
dbColumn := mate.Filed2DBNameCache[name] dbColumn := mate.Field2DBNameCache[name]
field := vf.FieldByIndex(mate.Field2IndexCache[name]) field := vf.FieldByIndex(mate.Field2IndexCache[name])
for field.Kind() == reflect.Ptr { for field.Kind() == reflect.Ptr {
@@ -97,13 +203,13 @@ func (b *Mapper) extractStructData(data ...any) (map[string][]*TableRowMateria,
// 字段值 // 字段值
dbValue := field.Interface() dbValue := field.Interface()
materia.Columns = append(materia.Columns, dbColumn) trMaterial.Columns = append(trMaterial.Columns, dbColumn)
materia.Values = append(materia.Values, dbValue) trMaterial.Values = append(trMaterial.Values, dbValue)
} }
for _, name := range mate.TaggedFieldNames { for _, name := range mate.TaggedFieldNames {
// db tag 名称 -- 数据库列名 // db tag 名称 -- 数据库列名
tagColumn := mate.Filed2DBNameCache[name] tagColumn := mate.Field2DBNameCache[name]
field := vf.FieldByIndex(mate.Field2IndexCache[name]) field := vf.FieldByIndex(mate.Field2IndexCache[name])
for field.Kind() == reflect.Ptr { for field.Kind() == reflect.Ptr {
if field.IsNil() { if field.IsNil() {
@@ -114,25 +220,77 @@ func (b *Mapper) extractStructData(data ...any) (map[string][]*TableRowMateria,
// 字段值 // 字段值
tagValue := field.Interface() tagValue := field.Interface()
materia.TagColumns = append(materia.TagColumns, tagColumn) trMaterial.TagColumns = append(trMaterial.TagColumns, tagColumn)
materia.TagValues = append(materia.TagValues, tagValue) trMaterial.TagValues = append(trMaterial.TagValues, tagValue)
} }
// 添加到结果 // 添加到结果
key := materia.TableName key := trMaterial.TableName
result[key] = append(result[key], materia) result[key] = append(result[key], trMaterial)
} }
return result, nil return result, nil
} }
// flattenDataToPtrs 对输入数据中不同类型的数据统一转换为指针形式,并展平切片/数组类型的数据
// 支持输入为任意数量的参数,参数可以是单个结构体指针或结构体,也可以是结构体切片或指针切片
// 处理逻辑包括:
// 1. 将切片类型的参数拆开放入一个统一的一维切片中(展平)
// 2. 跳过其中的 nil 元素,确保数据有效性
// 3. 将所有非指针类型的元素转换为对应的指针类型,方便后续统一处理
func (b *Mapper) flattenDataToPtrs(data ...any) ([]any, error) {
var flattenedData = make([]any, 0, len(data))
for _, item := range data {
if isNil(item) { // 跳过nil值避免后续反射调用panic
continue
}
itemType := reflect.TypeOf(item)
if itemType.Kind() == reflect.Ptr || itemType.Kind() == reflect.Struct {
// 非切片类型直接追加 指针
flattenedData = append(flattenedData, ensurePtr(item))
continue
}
if itemType.Kind() == reflect.Slice || itemType.Kind() == reflect.Array {
// 处理切片类型参数,展平成单个元素
sliceValue := reflect.ValueOf(item)
for i := 0; i < sliceValue.Len(); i++ {
elem := sliceValue.Index(i).Interface()
if isNil(elem) { // 跳过nil元素
continue
}
// 非切片类型直接追加 指针
flattenedData = append(flattenedData, ensurePtr(elem))
}
continue
}
return nil, fmt.Errorf("unsupported data type: %s, expected struct, pointer, slice or array", itemType.String())
}
return flattenedData, nil
}
func (b *Mapper) ToInsertSQL(data ...any) (string, error) { func (b *Mapper) ToInsertSQL(data ...any) (string, error) {
// 展平数据为指针切片
flattenedData, err := b.flattenDataToPtrs(data...)
if err != nil {
return "", fmt.Errorf("failed to flatten data: %w", err)
}
if len(flattenedData) == 0 {
return "", fmt.Errorf("data is empty")
}
// 扫描 struct 类型数据 // 扫描 struct 类型数据
if err := b.scanStruct(data...); err != nil { if err := b.scanStruct(flattenedData...); err != nil {
return "", fmt.Errorf("failed to scan struct: %w", err) return "", fmt.Errorf("failed to analyzeStruct struct: %w", err)
} }
// 提取 struct 内的信息 // 提取 struct 内的信息
tableMap, err := b.extractStructData(data...) tableMap, err := b.extractTableMaterial(flattenedData...)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to extract struct data: %w", err) return "", fmt.Errorf("failed to extract struct data: %w", err)
} }
@@ -189,7 +347,7 @@ func (b *Mapper) ScanRows(target any, rows *sql.Rows) error {
// @return 返回错误 // @return 返回错误
func (b *Mapper) ScanRowsWithContext(ctx context.Context, target any, rows *sql.Rows) error { func (b *Mapper) ScanRowsWithContext(ctx context.Context, target any, rows *sql.Rows) error {
// 确保rows不为nil // 确保rows不为nil
if rows == nil { if isNil(rows) {
return errors.New("rows cannot be nil") return errors.New("rows cannot be nil")
} }
@@ -218,8 +376,8 @@ func (b *Mapper) ScanRowsWithContext(ctx context.Context, target any, rows *sql.
return sql.ErrNoRows return sql.ErrNoRows
} }
return b.scanRow(vf, rows) return b.scanRow(vf, rows)
case reflect.Slice: case reflect.Slice, reflect.Array:
// target是slice的指针 // target是slice或array扫描多行数据
sliceElementType := vf.Type().Elem() sliceElementType := vf.Type().Elem()
if sliceElementType.Kind() != reflect.Ptr || sliceElementType.Elem().Kind() != reflect.Struct { if sliceElementType.Kind() != reflect.Ptr || sliceElementType.Elem().Kind() != reflect.Struct {
return errors.New("target must be a pointer to a slice of structs") return errors.New("target must be a pointer to a slice of structs")
@@ -257,7 +415,7 @@ func (b *Mapper) ScanRowsWithContext(ctx context.Context, target any, rows *sql.
func (b *Mapper) scanRow(target reflect.Value, rows *sql.Rows) error { func (b *Mapper) scanRow(target reflect.Value, rows *sql.Rows) error {
target = reflect.Indirect(target) target = reflect.Indirect(target)
uniqueTypeName := getUniqueTypeName(target.Type()) uniqueTypeName := buildTypeKey(target.Type())
mate, ok := b.structMateMap.Load(uniqueTypeName) mate, ok := b.structMateMap.Load(uniqueTypeName)
if !ok { if !ok {
return fmt.Errorf("not found struct type mate: %s", uniqueTypeName) return fmt.Errorf("not found struct type mate: %s", uniqueTypeName)
@@ -283,3 +441,52 @@ func (b *Mapper) scanRow(target reflect.Value, rows *sql.Rows) error {
// 扫描数据 // 扫描数据
return rows.Scan(dest...) return rows.Scan(dest...)
} }
// ScanRowsToMap 扫描多行数据到map
// @param rows 数据库返回的行数据
// @return 返回map数组和错误
func (b *Mapper) ScanRowsToMap(rows *sql.Rows) ([]map[string]any, error) {
return b.ScanRowsToMapWithContext(context.Background(), rows)
}
// ScanRowsToMapWithContext 扫描多行数据到map
// @param ctx 上下文
// @param rows 数据库返回的行数据
// @return 返回map数组和错误
func (b *Mapper) ScanRowsToMapWithContext(ctx context.Context, rows *sql.Rows) ([]map[string]any, error) {
columns, err := rows.Columns()
if err != nil {
return nil, err
}
result := make([]map[string]any, 0)
for rows.Next() {
// 检查上下文是否已取消
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// 创建一个切片来存储列的值
values := make([]any, len(columns))
for i := range values {
values[i] = new(any)
}
// 扫描行数据到切片中
if err := rows.Scan(values...); err != nil {
return nil, err
}
// 构建 结果映射
rowMap := make(map[string]any, len(columns))
for i := range values {
column := columns[i]
value := *(values[i].(*any))
rowMap[column] = value
}
result = append(result, rowMap)
}
return result, nil
}

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"time" "time"
//_ "github.com/taosdata/driver-go/v3/taosWS"
) )
type TaosTAG struct { type TaosTAG struct {
@@ -69,7 +70,7 @@ func TestBuilderInsert(t *testing.T) {
}, },
} }
insertSql, err := tdMapper.ToInsertSQL(data...) insertSql, err := tdMapper.ToInsertSQL(data)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -93,10 +94,10 @@ func TestSimpleInsert(t *testing.T) {
tdMapper := NewMapper() tdMapper := NewMapper()
data := []any{ data := []any{
&User{Name: "张三", Age: 18}, &User{Name: "张三", Age: 18},
&User{Name: "李四", Age: 20}, User{Name: "李四", Age: 20},
} }
insertSql, err := tdMapper.ToInsertSQL(data...) insertSql, err := tdMapper.ToInsertSQL(data)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -108,6 +109,10 @@ type SuperDevTAG struct {
DevType string `db:"dev_type" taos:"tag"` DevType string `db:"dev_type" taos:"tag"`
} }
func (s *SuperDevTAG) SuperTableName() string {
return "SuperDevTAG"
}
type SuperDev struct { type SuperDev struct {
SuperDevTAG SuperDevTAG
Ts time.Time `db:"ts"` // 时间戳 Ts time.Time `db:"ts"` // 时间戳
@@ -124,11 +129,11 @@ func (s *SuperDev) TableName() string {
func TestSuperDevInsert(t *testing.T) { func TestSuperDevInsert(t *testing.T) {
var data = []any{ var data = []any{
&SuperDev{ SuperDev{
SuperDevTAG: SuperDevTAG{DevId: "SN001", DevType: "模拟设备"}, SuperDevTAG: SuperDevTAG{DevId: "SN001", DevType: "模拟设备"},
Ts: time.Now(), AppSn: "a0001", Ct: 1.0, Ts: time.Now(), AppSn: "a0001", Ct: 1.0,
}, },
&SuperDev{ SuperDev{
SuperDevTAG: SuperDevTAG{DevId: "SN001", DevType: "模拟设备"}, SuperDevTAG: SuperDevTAG{DevId: "SN001", DevType: "模拟设备"},
Ts: time.Now().Add(time.Second), AppSn: "a0002", Ct: 2.0, Ts: time.Now().Add(time.Second), AppSn: "a0002", Ct: 2.0,
}, },
@@ -195,3 +200,47 @@ func TestScanRows(t *testing.T) {
//marshal, _ := json.MarshalIndent(sdArray, "", " ") //marshal, _ := json.MarshalIndent(sdArray, "", " ")
//fmt.Println(len(sdArray), string(marshal)) //fmt.Println(len(sdArray), string(marshal))
} }
func TestScanRowsToMap(t *testing.T) {
/*
文档参考: https://docs.taosdata.com/reference/connector/go/#websocket-%E8%BF%9E%E6%8E%A5
go get github.com/taosdata/driver-go/v3
import _ "github.com/taosdata/driver-go/v3/taosWS"
超级表创建
CREATE STABLE `super_dev` (`ts` TIMESTAMP , `app_sn` VARCHAR(500) , `ct` INT ) TAGS (`dev_id` VARCHAR(50), `dev_type` VARCHAR(50))
批量插入
INSERT INTO
`dev_SN001` USING `super_dev` (`dev_id`,`dev_type`) TAGS ('SN001','模拟设备') (`ts`,`app_sn`,`ct`)
VALUES ('2024-09-18T16:22:17+08:00','a0001',1),('2024-09-18T16:22:18+08:00','a0002',2)
`dev_SN002` USING `super_dev` (`dev_id`,`dev_type`) TAGS ('SN002','模拟设备') (`ts`,`app_sn`,`ct`)
VALUES ('2024-09-18T16:22:17+08:00','a0003',3)
*/
var taosUri = "root:taosdata@localhost:6041/test"
db, err := sql.Open("taosWS", taosUri)
if err != nil {
t.Fatal(err)
}
rows, err := db.Query("select * from super_dev order by ts desc limit 100")
if err != nil {
t.Fatal(err)
}
defer func() { _ = rows.Close() }()
tdMapper := NewMapper()
toMap, err := tdMapper.ScanRowsToMap(rows)
if err != nil {
t.Fatal(err)
}
indent, _ := json.MarshalIndent(toMap, "", " ")
fmt.Println(string(indent))
fmt.Println("len:", len(toMap))
}

58
reflect_utils.go Normal file
View File

@@ -0,0 +1,58 @@
package tdmap
import (
"reflect"
)
// extractReflectInfo 提取反射信息(自动解引用到基础类型)
func extractReflectInfo(data any) (reflect.Type, reflect.Value) {
t := reflect.TypeOf(data)
v := reflect.ValueOf(data)
// 处理结构体和结构体指针
for t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
return t, v
}
// buildTypeKey 构建类型键(包路径.类型名)
func buildTypeKey(t reflect.Type) string {
return t.PkgPath() + "." + t.Name()
}
// tryGetSuperTableName 调用对象的 SuperTableName 方法(如果存在)
func tryGetSuperTableName(obj any) (string, bool) {
ptr := ensurePtr(obj)
if it, ok := ptr.(interface{ SuperTableName() string }); ok {
return it.SuperTableName(), true
}
return "", false
}
// ensurePtr 确保返回的是指针类型(不是指针则转换为指针)
func ensurePtr(obj any) any {
if v := reflect.ValueOf(obj); v.Kind() != reflect.Ptr {
// 创建对应类型的指针
ptr := reflect.New(v.Type())
// 设置指针指向的值为元素本身
ptr.Elem().Set(v)
// 返回 指针
return ptr.Interface()
}
return obj
}
// isNil 判断值是否为 nil包括指针类型
func isNil(d any) bool {
if d == nil {
return true
}
// 反射判断指针类型是否为nil
if v := reflect.ValueOf(d); v.Kind() == reflect.Ptr {
return v.IsNil()
}
return false
}

143
scan.go
View File

@@ -1,143 +0,0 @@
package tdmap
import (
"fmt"
"reflect"
)
func scan(data interface{}) (*StructMate, error) {
// 获取结构体的类型
t, v := getReflectTypeAndValue(data)
// 确保是结构体类型
if t.Kind() != reflect.Struct {
return nil, fmt.Errorf("input data is not a struct")
}
// 获取包路径和类型名称
uniqueTypeName := getUniqueTypeName(t)
// 初始化结果结构体
sr := StructMate{
UniqueTypeName: uniqueTypeName,
DBName2IndexCache: make(map[string][]int, t.NumField()),
Field2IndexCache: make(map[string][]int, t.NumField()),
Filed2DBNameCache: make(map[string]string, t.NumField()),
DBAnnotatedNames: make([]string, 0, t.NumField()),
TaggedFieldNames: make([]string, 0, t.NumField()),
SuperTableName: "",
}
//timeType := reflect.TypeOf(time.Time{})
// 遍历结构体的字段
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldValue := v.Field(i)
if field.Anonymous {
if field.Type.Kind() == reflect.Ptr {
if fieldValue.IsNil() {
// 如果指针是 nil创建一个该类型的零值实例
zeroValue := reflect.Zero(field.Type.Elem())
fieldValue = zeroValue
} else {
fieldValue = fieldValue.Elem()
}
}
if !fieldValue.CanInterface() {
continue
}
subResult, err := scan(fieldValue.Interface())
if err != nil {
return nil, err
}
for k, v := range subResult.DBName2IndexCache {
sr.DBName2IndexCache[k] = append(sr.DBName2IndexCache[k], i)
sr.DBName2IndexCache[k] = append(sr.DBName2IndexCache[k], v...)
}
for k, v := range subResult.Field2IndexCache {
sr.Field2IndexCache[k] = append(sr.Field2IndexCache[k], i)
sr.Field2IndexCache[k] = append(sr.Field2IndexCache[k], v...)
}
for k, v := range subResult.Filed2DBNameCache {
sr.Filed2DBNameCache[k] = v
}
sr.DBAnnotatedNames = append(sr.DBAnnotatedNames, subResult.DBAnnotatedNames...)
sr.TaggedFieldNames = append(sr.TaggedFieldNames, subResult.TaggedFieldNames...)
continue
}
// 检查字段是否有db注解
columnName := field.Tag.Get("db")
if columnName == "-" || columnName == "" {
continue
}
if len(sr.Field2IndexCache[field.Name]) > 0 {
return nil, fmt.Errorf("duplicate field [%s %s `db:%s`]", field.Name, field.Type.Name(), columnName)
}
sr.Field2IndexCache[field.Name] = append([]int{}, i)
sr.DBName2IndexCache[columnName] = append(sr.DBName2IndexCache[columnName], i)
sr.Filed2DBNameCache[field.Name] = columnName
// 检查字段是否有taos注解
if field.Tag.Get("taos") == "tag" {
sr.TaggedFieldNames = append(sr.TaggedFieldNames, field.Name)
} else {
sr.DBAnnotatedNames = append(sr.DBAnnotatedNames, field.Name)
}
}
sr.SuperTableName = callSuperTableName(data)
return &sr, nil
}
// 获取包路径和类型名称
func getUniqueTypeName(t reflect.Type) string {
pkgPath := t.PkgPath()
typeName := t.Name()
uniqueTypeName := fmt.Sprintf("%s.%s", pkgPath, typeName)
return uniqueTypeName
}
func getReflectTypeAndValue(data any) (reflect.Type, reflect.Value) {
t := reflect.TypeOf(data)
v := reflect.ValueOf(data)
// 处理结构体和结构体指针
for t.Kind() == reflect.Ptr {
t = t.Elem()
v = v.Elem()
}
return t, v
}
func callSuperTableName(obj any) string {
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Struct {
return ""
}
// 检查是否可以调用 SuperTableName 方法
superTableNameMethod := v.MethodByName("SuperTableName")
if !superTableNameMethod.IsValid() {
return ""
}
// 调用 SuperTableName 方法
results := superTableNameMethod.Call(nil)
if len(results) == 1 && results[0].Kind() == reflect.String {
return results[0].String()
}
return ""
}

View File

@@ -1,6 +1,7 @@
package tdmap package tdmap
type TableRowMateria struct { // TableRowMaterial 表行数据
type TableRowMaterial struct {
SuperTableName string SuperTableName string
TableName string TableName string
@@ -11,13 +12,13 @@ type TableRowMateria struct {
Values []any // 值 Values []any // 值
} }
// StructMate 静态化的结构体信息 // StructMeta 静态化的结构体信息
type StructMate struct { type StructMeta struct {
UniqueTypeName string // 结构体的唯一标识符 UniqueTypeName string // 结构体的唯一标识符
DBName2IndexCache map[string][]int // db 注解的名称到索引的映射缓存 DBName2IndexCache map[string][]int // db 注解的名称到索引的映射缓存
Field2IndexCache map[string][]int // 字段名到索引的映射缓存 Field2IndexCache map[string][]int // 字段名到索引的映射缓存
Filed2DBNameCache map[string]string // 字段名到 db 注解的名称的映射缓存 Field2DBNameCache map[string]string // 字段名到 db 注解的名称的映射缓存
DBAnnotatedNames []string // 包含 db 注解的 属性的名称 DBAnnotatedNames []string // 包含 db 注解的 属性的名称
TaggedFieldNames []string // 包含的 tag 注解的 属性的名称 TaggedFieldNames []string // 包含的 tag 注解的 属性的名称