mirror of
https://github.com/hanzo-ds/go.git
synced 2026-10-30 19:45:01 +00:00
Source: the unexported clickhouse struct becomes client, the ch receiver and chproto import alias follow, error message prefixes read datastore, and the JSON serializer interface drops the vendor infix. resources' metadata key and its accessor are renamed together, with a test proving the embedded YAML still unmarshals — a silent drift there would have made every connection take the unsupported-server-version branch. Codegen: all three templates still emitted the upstream module paths, so regenerating would have quietly undone the rename. Note the templates have drifted from the files they produce (regenerating today drops QBit and scanTypeDuration), so the generated files are left as tracked and only the templates are corrected. Dead links: an earlier mechanical pass invented URLs that do not resolve — github.com/ClickHouse/datastore-go, hanzo-ds/driver issue numbers that belong to upstream, and mangled names of independent projects (mailru, uptrace, nikepan, EpicStep). Third-party names are not ours to rewrite, so those directories are removed rather than repaired, and provenance comments now point at real files. Dev infra: compose runs our own server image at the tag production runs, since the vendor's alpine tag does not exist in our registry. Cluster hostnames, nginx upstreams and server configs are renamed as one set. CHANGELOG.md is upstream's release history for versions this module never released; rewriting it would fabricate a lineage, so it is dropped and the upstream base recorded in README instead. The two long-standing vet findings are fixed by keying the struct literals rather than suppressed. LICENSE and contributors/list keep the vendor name on purpose: one is the copyright notice Apache-2.0 requires, the other is real people's names. CI now enforces build, vet, test, gofmt and a grep gate.
145 lines
3.5 KiB
Go
145 lines
3.5 KiB
Go
package col
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"encoding/json"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
)
|
|
|
|
// JSONSerializer interface allows a struct to be manually converted to an optimized JSON structure instead of relying
|
|
// on recursive reflection.
|
|
// Note that the struct must be a pointer in order for the interface to be matched, reflection will be used otherwise.
|
|
type JSONSerializer interface {
|
|
SerializeJSON() (*JSON, error)
|
|
}
|
|
|
|
// JSONDeserializer interface allows a struct to load its data from an optimized JSON structure instead of relying
|
|
// on recursive reflection to set its fields.
|
|
type JSONDeserializer interface {
|
|
DeserializeJSON(*JSON) error
|
|
}
|
|
|
|
// ExtractJSONPathAs is a convenience function for asserting a path to a specific type.
|
|
// The underlying value is also extracted from its Dynamic wrapper if present.
|
|
// T cannot be a Dynamic, if you want a Dynamic simply use ExtractJSONPathAsDynamic.
|
|
func ExtractJSONPathAs[T any](o *JSON, path string) (T, bool) {
|
|
value, ok := o.valuesByPath[path]
|
|
if !ok || value == nil {
|
|
var empty T
|
|
return empty, false
|
|
}
|
|
|
|
dynValue, ok := value.(Dynamic)
|
|
if !ok {
|
|
valueAs, ok := value.(T)
|
|
return valueAs, ok
|
|
}
|
|
|
|
valueAs, ok := dynValue.value.(T)
|
|
return valueAs, ok
|
|
}
|
|
|
|
// ExtractJSONPathAsDynamic is a convenience function for asserting a path to a Dynamic.
|
|
// If the value is not a Dynamic, the value is wrapped in an untyped Dynamic with false returned.
|
|
func ExtractJSONPathAsDynamic(o *JSON, path string) (Dynamic, bool) {
|
|
value, ok := o.valuesByPath[path]
|
|
if !ok || value == nil {
|
|
return Dynamic{}, false
|
|
}
|
|
|
|
if dynValue, ok := value.(Dynamic); ok {
|
|
return dynValue, true
|
|
}
|
|
|
|
return Dynamic{value: value}, false
|
|
}
|
|
|
|
// JSON represents a Datastore JSON type that can hold multiple possible types
|
|
type JSON struct {
|
|
valuesByPath map[string]any
|
|
}
|
|
|
|
// NewJSON creates a new empty JSON value
|
|
func NewJSON() *JSON {
|
|
return &JSON{
|
|
valuesByPath: make(map[string]any),
|
|
}
|
|
}
|
|
|
|
func (o *JSON) ValuesByPath() map[string]any {
|
|
return o.valuesByPath
|
|
}
|
|
|
|
func (o *JSON) SetValueAtPath(path string, value any) {
|
|
o.valuesByPath[path] = value
|
|
}
|
|
|
|
func (o *JSON) ValueAtPath(path string) (any, bool) {
|
|
value, ok := o.valuesByPath[path]
|
|
return value, ok
|
|
}
|
|
|
|
// NestedMap converts the flattened JSON data into a nested structure
|
|
func (o *JSON) NestedMap() map[string]any {
|
|
result := make(map[string]any)
|
|
|
|
sortedPaths := make([]string, 0, len(o.valuesByPath))
|
|
for path := range o.valuesByPath {
|
|
sortedPaths = append(sortedPaths, path)
|
|
}
|
|
slices.Sort(sortedPaths)
|
|
|
|
for _, path := range sortedPaths {
|
|
value := o.valuesByPath[path]
|
|
if vt, ok := value.(Variant); ok && vt.Nil() {
|
|
continue
|
|
}
|
|
|
|
parts := strings.Split(path, ".")
|
|
current := result
|
|
|
|
for i := 0; i < len(parts)-1; i++ {
|
|
part := parts[i]
|
|
|
|
if _, exists := current[part]; !exists {
|
|
current[part] = make(map[string]any)
|
|
}
|
|
|
|
if next, ok := current[part].(map[string]any); ok {
|
|
current = next
|
|
}
|
|
}
|
|
current[parts[len(parts)-1]] = value
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// MarshalJSON implements the json.Marshaler interface
|
|
func (o *JSON) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(o.NestedMap())
|
|
}
|
|
|
|
// Scan implements the sql.Scanner interface
|
|
func (o *JSON) Scan(value interface{}) error {
|
|
switch vv := value.(type) {
|
|
case JSON:
|
|
o.valuesByPath = vv.valuesByPath
|
|
case *JSON:
|
|
o.valuesByPath = vv.valuesByPath
|
|
case map[string]any:
|
|
o.valuesByPath = vv
|
|
default:
|
|
return fmt.Errorf("JSON Scan value must be datastore.JSON or map[string]any")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Value implements the driver.Valuer interface
|
|
func (o *JSON) Value() (driver.Value, error) {
|
|
return o, nil
|
|
}
|