backend/pkg/library/cassandra/metadata.go

137 lines
3.5 KiB
Go
Raw Normal View History

2025-11-17 09:31:58 +00:00
package cassandra
import (
"fmt"
"reflect"
2025-11-18 09:45:38 +00:00
"sync"
"unicode"
2025-11-17 09:31:58 +00:00
2025-11-19 05:33:06 +00:00
"github.com/scylladb/gocqlx/v2/table"
2025-11-17 09:31:58 +00:00
)
2025-11-18 09:45:38 +00:00
var (
// metadataCache 快取已生成的 Metadata避免重複反射解析
// key: tableName + ":" + structType (不包含 keyspace因為同一個 struct 在不同 keyspace 結構相同)
metadataCache sync.Map
)
type cachedMetadata struct {
columns []string
partKeys []string
sortKeys []string
err error
}
// generateMetadata 根據傳入的 struct 產生 table.Metadata
// 使用快取機制避免重複反射解析,提升效能
func generateMetadata[T Table](doc T, keyspace string) (table.Metadata, error) {
// 取得型別資訊
t := reflect.TypeOf(doc)
2025-11-17 09:31:58 +00:00
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
2025-11-18 09:45:38 +00:00
// 取得表名稱
tableName := doc.TableName()
if tableName == "" {
2025-11-17 09:31:58 +00:00
return table.Metadata{}, ErrMissingTableName
}
2025-11-18 09:45:38 +00:00
// 構建快取 key: tableName:structType (不包含 keyspace)
cacheKey := fmt.Sprintf("%s:%s", tableName, t.String())
// 檢查快取
if cached, ok := metadataCache.Load(cacheKey); ok {
cachedMeta := cached.(cachedMetadata)
if cachedMeta.err != nil {
return table.Metadata{}, cachedMeta.err
}
// 從快取構建 metadata動態加上 keyspace
meta := table.Metadata{
Name: fmt.Sprintf("%s.%s", keyspace, tableName),
Columns: make([]string, len(cachedMeta.columns)),
PartKey: make([]string, len(cachedMeta.partKeys)),
SortKey: make([]string, len(cachedMeta.sortKeys)),
}
copy(meta.Columns, cachedMeta.columns)
copy(meta.PartKey, cachedMeta.partKeys)
copy(meta.SortKey, cachedMeta.sortKeys)
return meta, nil
}
// 快取未命中,生成 metadata
2025-11-17 09:31:58 +00:00
columns := make([]string, 0, t.NumField())
partKeys := make([]string, 0, t.NumField())
sortKeys := make([]string, 0, t.NumField())
// 遍歷所有 exported 欄位
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// 跳過 unexported 欄位
if field.PkgPath != "" {
continue
}
// 如果欄位有標記 db:"-" 則跳過
2025-11-19 05:33:06 +00:00
if tag := field.Tag.Get(DBFiledName); tag == "-" {
2025-11-17 09:31:58 +00:00
continue
}
// 取得欄位名稱
2025-11-19 05:33:06 +00:00
colName := field.Tag.Get(DBFiledName)
2025-11-17 09:31:58 +00:00
if colName == "" {
colName = toSnakeCase(field.Name)
}
columns = append(columns, colName)
2025-11-18 09:45:38 +00:00
// 若有 partition_key:"true" 標記,加入 PartKey
2025-11-19 05:33:06 +00:00
if field.Tag.Get(Pk) == "true" {
2025-11-17 09:31:58 +00:00
partKeys = append(partKeys, colName)
}
2025-11-18 09:45:38 +00:00
// 若有 clustering_key:"true" 標記,加入 SortKey
2025-11-19 05:33:06 +00:00
if field.Tag.Get(ClusterKey) == "true" {
2025-11-17 09:31:58 +00:00
sortKeys = append(sortKeys, colName)
}
}
if len(partKeys) == 0 {
2025-11-18 09:45:38 +00:00
err := ErrNoPartitionKey
// 快取錯誤結果
metadataCache.Store(cacheKey, cachedMetadata{err: err})
return table.Metadata{}, err
}
// 快取成功結果(只存結構資訊,不包含 keyspace
cachedMeta := cachedMetadata{
columns: make([]string, len(columns)),
partKeys: make([]string, len(partKeys)),
sortKeys: make([]string, len(sortKeys)),
2025-11-17 09:31:58 +00:00
}
2025-11-18 09:45:38 +00:00
copy(cachedMeta.columns, columns)
copy(cachedMeta.partKeys, partKeys)
copy(cachedMeta.sortKeys, sortKeys)
metadataCache.Store(cacheKey, cachedMeta)
2025-11-17 09:31:58 +00:00
2025-11-18 09:45:38 +00:00
// 組合並返回 Metadata包含 keyspace
2025-11-17 09:31:58 +00:00
meta := table.Metadata{
2025-11-18 09:45:38 +00:00
Name: fmt.Sprintf("%s.%s", keyspace, tableName),
2025-11-17 09:31:58 +00:00
Columns: columns,
PartKey: partKeys,
SortKey: sortKeys,
}
return meta, nil
}
2025-11-18 09:45:38 +00:00
// toSnakeCase 將 CamelCase 字串轉換為 snake_case
func toSnakeCase(s string) string {
var result []rune
for i, r := range s {
if unicode.IsUpper(r) {
if i > 0 {
result = append(result, '_')
}
result = append(result, unicode.ToLower(r))
} else {
result = append(result, r)
}
}
return string(result)
}