package cassandra import ( "fmt" "reflect" "sync" "unicode" "github.com/scylladb/gocqlx/v2/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(DBFiledName); tag == "-" { continue } // 取得欄位名稱 colName := field.Tag.Get(DBFiledName) if colName == "" { colName = toSnakeCase(field.Name) } columns = append(columns, colName) // 若有 partition_key:"true" 標記,加入 PartKey if field.Tag.Get(Pk) == "true" { partKeys = append(partKeys, colName) } // 若有 clustering_key:"true" 標記,加入 SortKey if field.Tag.Get(ClusterKey) == "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) }