Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad6e4c7491 | |||
| b9413d6c40 | |||
| 581b0b60eb |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -22,3 +22,5 @@
|
|||||||
go.work
|
go.work
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
*mise*.toml
|
||||||
110
README.md
110
README.md
@@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
219
mapping.go
219
mapping.go
@@ -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,82 +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
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeData 对输入数据进行统一处理和规范化
|
// flattenDataToPtrs 对输入数据中不同类型的数据统一转换为指针形式,并展平切片/数组类型的数据
|
||||||
// 支持输入为任意数量的参数,参数可以是单个结构体指针或结构体,也可以是结构体切片或指针切片
|
// 支持输入为任意数量的参数,参数可以是单个结构体指针或结构体,也可以是结构体切片或指针切片
|
||||||
// 处理逻辑包括:
|
// 处理逻辑包括:
|
||||||
// 1. 将切片类型的参数拆开放入一个统一的一维切片中(展平)
|
// 1. 将切片类型的参数拆开放入一个统一的一维切片中(展平)
|
||||||
// 2. 跳过其中的 nil 元素,确保数据有效性
|
// 2. 跳过其中的 nil 元素,确保数据有效性
|
||||||
// 3. 将所有非指针类型的元素转换为对应的指针类型,方便后续统一处理
|
// 3. 将所有非指针类型的元素转换为对应的指针类型,方便后续统一处理
|
||||||
func (b *Mapper) normalizeData(data ...any) []any {
|
func (b *Mapper) flattenDataToPtrs(data ...any) ([]any, error) {
|
||||||
var normalizedData []any
|
var flattenedData = make([]any, 0, len(data))
|
||||||
|
|
||||||
for _, item := range data {
|
for _, item := range data {
|
||||||
if item == nil {
|
if isNil(item) { // 跳过nil值,避免后续反射调用panic
|
||||||
// 跳过nil值,避免后续反射调用panic
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
itemType := reflect.TypeOf(item)
|
itemType := reflect.TypeOf(item)
|
||||||
if itemType.Kind() == reflect.Slice {
|
|
||||||
|
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)
|
sliceValue := reflect.ValueOf(item)
|
||||||
for i := 0; i < sliceValue.Len(); i++ {
|
for i := 0; i < sliceValue.Len(); i++ {
|
||||||
elem := sliceValue.Index(i).Interface()
|
elem := sliceValue.Index(i).Interface()
|
||||||
if elem == nil {
|
if isNil(elem) { // 跳过nil元素
|
||||||
// 跳过nil元素
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
normalizedData = append(normalizedData, elem)
|
// 非切片类型直接追加 指针
|
||||||
}
|
flattenedData = append(flattenedData, ensurePtr(elem))
|
||||||
} else {
|
|
||||||
// 非切片类型直接追加
|
|
||||||
normalizedData = append(normalizedData, item)
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换所有元素为指针类型,便于后续一致性处理
|
return nil, fmt.Errorf("unsupported data type: %s, expected struct, pointer, slice or array", itemType.String())
|
||||||
for i, item := range normalizedData {
|
|
||||||
if item == nil {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
itemType := reflect.TypeOf(item)
|
|
||||||
if itemType.Kind() != reflect.Ptr {
|
return flattenedData, nil
|
||||||
// 创建对应类型的指针
|
|
||||||
itemPtr := reflect.New(itemType)
|
|
||||||
// 设置指针指向的值为元素本身
|
|
||||||
itemPtr.Elem().Set(reflect.ValueOf(item))
|
|
||||||
// 替换切片中的元素为指针类型
|
|
||||||
normalizedData[i] = itemPtr.Interface()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return normalizedData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Mapper) ToInsertSQL(data ...any) (string, error) {
|
func (b *Mapper) ToInsertSQL(data ...any) (string, error) {
|
||||||
// 统一规范化处理输入数据,将切片拆平并转换元素为指针
|
// 展平数据为指针切片
|
||||||
normalizedData := b.normalizeData(data...)
|
flattenedData, err := b.flattenDataToPtrs(data...)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to flatten data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if len(normalizedData) == 0 {
|
if len(flattenedData) == 0 {
|
||||||
return "", fmt.Errorf("data is empty")
|
return "", fmt.Errorf("data is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 扫描 struct 类型数据
|
// 扫描 struct 类型数据
|
||||||
if err := b.scanStruct(normalizedData...); 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(normalizedData...)
|
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)
|
||||||
}
|
}
|
||||||
@@ -246,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")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,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")
|
||||||
@@ -314,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)
|
||||||
@@ -387,5 +488,5 @@ func (b *Mapper) ScanRowsToMapWithContext(ctx context.Context, rows *sql.Rows) (
|
|||||||
}
|
}
|
||||||
result = append(result, rowMap)
|
result = append(result, rowMap)
|
||||||
}
|
}
|
||||||
return result, err
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
//_ "github.com/taosdata/driver-go/v3/taosWS"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
//_ "github.com/taosdata/driver-go/v3/taosWS"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TaosTAG struct {
|
type TaosTAG struct {
|
||||||
@@ -92,9 +92,9 @@ func (u *User) SuperTableName() string {
|
|||||||
|
|
||||||
func TestSimpleInsert(t *testing.T) {
|
func TestSimpleInsert(t *testing.T) {
|
||||||
tdMapper := NewMapper()
|
tdMapper := NewMapper()
|
||||||
data := []User{
|
data := []any{
|
||||||
{Name: "张三", Age: 18},
|
&User{Name: "张三", Age: 18},
|
||||||
{Name: "李四", Age: 20},
|
User{Name: "李四", Age: 20},
|
||||||
}
|
}
|
||||||
|
|
||||||
insertSql, err := tdMapper.ToInsertSQL(data)
|
insertSql, err := tdMapper.ToInsertSQL(data)
|
||||||
@@ -109,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"` // 时间戳
|
||||||
@@ -133,7 +137,7 @@ func TestSuperDevInsert(t *testing.T) {
|
|||||||
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,
|
||||||
},
|
},
|
||||||
SuperDev{
|
&SuperDev{
|
||||||
SuperDevTAG: SuperDevTAG{DevId: "SN002", DevType: "模拟设备"},
|
SuperDevTAG: SuperDevTAG{DevId: "SN002", DevType: "模拟设备"},
|
||||||
Ts: time.Now(), AppSn: "a0003", Ct: 3.0,
|
Ts: time.Now(), AppSn: "a0003", Ct: 3.0,
|
||||||
},
|
},
|
||||||
|
|||||||
58
reflect_utils.go
Normal file
58
reflect_utils.go
Normal 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
143
scan.go
@@ -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 ""
|
|
||||||
}
|
|
||||||
9
types.go
9
types.go
@@ -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 注解的 属性的名称
|
||||||
|
|||||||
Reference in New Issue
Block a user