backend/pkg/library/cassandra/metadata.go

137 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package cassandra
import (
"fmt"
"reflect"
"sync"
"unicode"
"github.com/scylladb/gocqlx/v3/table"
)
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)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
// 取得表名稱
tableName := doc.TableName()
if tableName == "" {
return table.Metadata{}, ErrMissingTableName
}
// 構建快取 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
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:"-" 則跳過
if tag := field.Tag.Get("db"); tag == "-" {
continue
}
// 取得欄位名稱
colName := field.Tag.Get("db")
if colName == "" {
colName = toSnakeCase(field.Name)
}
columns = append(columns, colName)
// 若有 partition_key:"true" 標記,加入 PartKey
if field.Tag.Get("partition_key") == "true" {
partKeys = append(partKeys, colName)
}
// 若有 clustering_key:"true" 標記,加入 SortKey
if field.Tag.Get("clustering_key") == "true" {
sortKeys = append(sortKeys, colName)
}
}
if len(partKeys) == 0 {
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)),
}
copy(cachedMeta.columns, columns)
copy(cachedMeta.partKeys, partKeys)
copy(cachedMeta.sortKeys, sortKeys)
metadataCache.Store(cacheKey, cachedMeta)
// 組合並返回 Metadata包含 keyspace
meta := table.Metadata{
Name: fmt.Sprintf("%s.%s", keyspace, tableName),
Columns: columns,
PartKey: partKeys,
SortKey: sortKeys,
}
return meta, nil
}
// 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)
}