2022-01-23 09:36:24 +02:00
|
|
|
package chschema
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"reflect"
|
2022-04-29 18:51:14 +03:00
|
|
|
"strconv"
|
2022-01-23 09:36:24 +02:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2022-07-14 10:41:44 +03:00
|
|
|
"github.com/uptrace/go-clickhouse/ch/bfloat16"
|
2022-01-23 09:36:24 +02:00
|
|
|
"github.com/uptrace/go-clickhouse/ch/chtype"
|
|
|
|
"github.com/uptrace/go-clickhouse/ch/internal"
|
|
|
|
)
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
var (
|
|
|
|
boolType = reflect.TypeOf(false)
|
|
|
|
int8Type = reflect.TypeOf(int8(0))
|
|
|
|
int16Type = reflect.TypeOf(int16(0))
|
|
|
|
int32Type = reflect.TypeOf(int32(0))
|
|
|
|
int64Type = reflect.TypeOf(int64(0))
|
|
|
|
uint8Type = reflect.TypeOf(uint8(0))
|
|
|
|
uint16Type = reflect.TypeOf(uint16(0))
|
|
|
|
uint32Type = reflect.TypeOf(uint32(0))
|
|
|
|
uint64Type = reflect.TypeOf(uint64(0))
|
|
|
|
float32Type = reflect.TypeOf(float32(0))
|
|
|
|
float64Type = reflect.TypeOf(float64(0))
|
|
|
|
|
|
|
|
stringType = reflect.TypeOf("")
|
|
|
|
bytesType = reflect.TypeOf((*[]byte)(nil)).Elem()
|
|
|
|
uuidType = reflect.TypeOf((*UUID)(nil)).Elem()
|
|
|
|
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
|
|
|
ipType = reflect.TypeOf((*net.IP)(nil)).Elem()
|
|
|
|
ipNetType = reflect.TypeOf((*net.IPNet)(nil)).Elem()
|
|
|
|
bfloat16MapType = reflect.TypeOf((*bfloat16.Map)(nil)).Elem()
|
|
|
|
|
|
|
|
sliceUint64Type = reflect.TypeOf((*[]uint64)(nil)).Elem()
|
|
|
|
sliceFloat32Type = reflect.TypeOf((*[]float32)(nil)).Elem()
|
|
|
|
)
|
|
|
|
|
|
|
|
var chTypes = [...]string{
|
2022-01-23 09:36:24 +02:00
|
|
|
reflect.Bool: chtype.UInt8,
|
|
|
|
reflect.Int: chtype.Int64,
|
|
|
|
reflect.Int8: chtype.Int8,
|
|
|
|
reflect.Int16: chtype.Int16,
|
|
|
|
reflect.Int32: chtype.Int32,
|
|
|
|
reflect.Int64: chtype.Int64,
|
|
|
|
reflect.Uint: chtype.UInt64,
|
|
|
|
reflect.Uint8: chtype.UInt8,
|
|
|
|
reflect.Uint16: chtype.UInt16,
|
|
|
|
reflect.Uint32: chtype.UInt32,
|
|
|
|
reflect.Uint64: chtype.UInt64,
|
|
|
|
reflect.Uintptr: "",
|
|
|
|
reflect.Float32: chtype.Float32,
|
|
|
|
reflect.Float64: chtype.Float64,
|
|
|
|
reflect.Complex64: "",
|
|
|
|
reflect.Complex128: "",
|
|
|
|
reflect.Array: "",
|
|
|
|
reflect.Chan: "",
|
|
|
|
reflect.Func: "",
|
|
|
|
reflect.Interface: chtype.Any,
|
|
|
|
reflect.Map: chtype.String,
|
|
|
|
reflect.Ptr: "",
|
|
|
|
reflect.Slice: "",
|
|
|
|
reflect.String: chtype.String,
|
|
|
|
reflect.Struct: chtype.String,
|
|
|
|
reflect.UnsafePointer: "",
|
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
type NewColumnFunc func() Columnar
|
2022-01-23 09:36:24 +02:00
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
func NewColumn(chType string, typ reflect.Type) Columnar {
|
|
|
|
col := ColumnFactory(chType, typ)()
|
|
|
|
col.Init(chType)
|
|
|
|
return col
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
func ColumnFactory(chType string, typ reflect.Type) NewColumnFunc {
|
2022-01-23 09:36:24 +02:00
|
|
|
switch chType {
|
|
|
|
case chtype.Int8:
|
|
|
|
return NewInt8Column
|
|
|
|
case chtype.Int16:
|
|
|
|
return NewInt16Column
|
|
|
|
case chtype.Int32:
|
|
|
|
return NewInt32Column
|
|
|
|
case chtype.Int64:
|
|
|
|
return NewInt64Column
|
|
|
|
case chtype.UInt8:
|
2022-04-30 10:30:34 +03:00
|
|
|
return NewUInt8Column
|
2022-01-23 09:36:24 +02:00
|
|
|
case chtype.UInt16:
|
2022-04-30 10:30:34 +03:00
|
|
|
return NewUInt16Column
|
2022-01-23 09:36:24 +02:00
|
|
|
case chtype.UInt32:
|
2022-04-30 10:30:34 +03:00
|
|
|
return NewUInt32Column
|
2022-01-23 09:36:24 +02:00
|
|
|
case chtype.UInt64:
|
2022-04-30 10:30:34 +03:00
|
|
|
return NewUInt64Column
|
2022-01-23 09:36:24 +02:00
|
|
|
case chtype.Float32:
|
|
|
|
return NewFloat32Column
|
|
|
|
case chtype.Float64:
|
|
|
|
return NewFloat64Column
|
2023-01-21 12:14:00 +02:00
|
|
|
|
|
|
|
case chtype.String:
|
|
|
|
if typ == bytesType {
|
|
|
|
return NewBytesColumn
|
|
|
|
}
|
|
|
|
return NewStringColumn
|
|
|
|
case "LowCardinality(String)":
|
|
|
|
return NewLCStringColumn
|
|
|
|
case chtype.Bool:
|
|
|
|
return NewBoolColumn
|
|
|
|
case chtype.UUID:
|
|
|
|
return NewUUIDColumn
|
|
|
|
case chtype.IPv6:
|
|
|
|
return NewIPColumn
|
|
|
|
|
2022-01-23 09:36:24 +02:00
|
|
|
case chtype.DateTime:
|
|
|
|
return NewDateTimeColumn
|
2022-04-29 18:51:14 +03:00
|
|
|
case chtype.DateTime64:
|
|
|
|
return NewDateTime64Column
|
2022-01-23 09:36:24 +02:00
|
|
|
case chtype.Date:
|
|
|
|
return NewDateColumn
|
2023-01-21 12:14:00 +02:00
|
|
|
|
|
|
|
case "Array(Int8)":
|
|
|
|
return NewArrayInt8Column
|
|
|
|
case "Array(UInt8)":
|
|
|
|
return NewArrayUInt8Column
|
|
|
|
case "Array(Int16)":
|
|
|
|
return NewArrayInt16Column
|
|
|
|
case "Array(UInt16)":
|
|
|
|
return NewArrayUInt16Column
|
|
|
|
case "Array(Int32)":
|
|
|
|
return NewArrayInt32Column
|
|
|
|
case "Array(UInt32)":
|
|
|
|
return NewArrayUInt32Column
|
|
|
|
case "Array(Int64)":
|
|
|
|
return NewArrayInt64Column
|
|
|
|
case "Array(UInt64)":
|
|
|
|
return NewArrayUInt64Column
|
|
|
|
case "Array(Float32)":
|
|
|
|
return NewArrayFloat32Column
|
|
|
|
case "Array(Float64)":
|
|
|
|
return NewArrayFloat64Column
|
|
|
|
|
|
|
|
case "Array(String)":
|
|
|
|
return NewArrayStringColumn
|
|
|
|
case "Array(LowCardinality(String))":
|
|
|
|
return NewArrayLCStringColumn
|
|
|
|
case "Array(DateTime)":
|
|
|
|
return NewArrayDateTimeColumn
|
|
|
|
|
|
|
|
case "Array(Array(String))":
|
|
|
|
return NewArrayArrayStringColumn
|
|
|
|
case chtype.Any:
|
2022-01-23 09:36:24 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
if chType := chEnumType(chType); chType != "" {
|
|
|
|
return NewEnumColumn
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
if chType := chArrayElemType(chType); chType != "" {
|
|
|
|
if chType := chEnumType(chType); chType != "" {
|
|
|
|
return NewArrayEnumColumn
|
|
|
|
}
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
if isDateTime64Type(chType) {
|
|
|
|
return NewDateTime64Column
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
if chType := chDateTimeType(chType); chType != "" {
|
|
|
|
return ColumnFactory(chType, typ)
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
if chType := chNullableType(chType); chType != "" {
|
|
|
|
if typ != nil {
|
|
|
|
typ = typ.Elem()
|
|
|
|
}
|
|
|
|
return NewNullableColumnFunc(ColumnFactory(chType, typ))
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
|
|
|
|
if chType := chSimpleAggFunc(chType); chType != "" {
|
|
|
|
return ColumnFactory(chType, typ)
|
2022-04-29 18:51:14 +03:00
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
|
|
|
|
if funcName, _ := aggFuncNameAndType(chType); funcName != "" {
|
|
|
|
switch funcName {
|
|
|
|
case "quantileBFloat16", "quantilesBFloat16":
|
|
|
|
return NewBFloat16HistColumn
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("unsupported ClickHouse type: %s", chType))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if typ == nil {
|
|
|
|
panic(fmt.Errorf("unsupported ClickHouse column: %s", chType))
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
|
|
|
|
kind := typ.Kind()
|
|
|
|
|
|
|
|
switch kind {
|
|
|
|
case reflect.Ptr:
|
|
|
|
if typ.Elem().Kind() == reflect.Struct {
|
|
|
|
return NewJSONColumn
|
|
|
|
}
|
|
|
|
return NewNullableColumnFunc(ColumnFactory(chNullableType(chType), typ.Elem()))
|
|
|
|
case reflect.Slice:
|
|
|
|
switch elem := typ.Elem(); elem.Kind() {
|
|
|
|
case reflect.Ptr:
|
|
|
|
if elem.Elem().Kind() == reflect.Struct {
|
|
|
|
return NewJSONColumn
|
|
|
|
}
|
|
|
|
case reflect.Struct:
|
|
|
|
if elem != timeType {
|
|
|
|
return NewJSONColumn
|
|
|
|
}
|
|
|
|
}
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
panic(fmt.Errorf("unsupported ClickHouse column: %s", chType))
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
func chType(typ reflect.Type) string {
|
2022-07-14 10:41:44 +03:00
|
|
|
switch typ {
|
|
|
|
case timeType:
|
|
|
|
return chtype.DateTime
|
|
|
|
case ipType:
|
|
|
|
return chtype.IPv6
|
|
|
|
}
|
|
|
|
|
|
|
|
kind := typ.Kind()
|
|
|
|
switch kind {
|
|
|
|
case reflect.Ptr:
|
|
|
|
if typ.Elem().Kind() == reflect.Struct {
|
|
|
|
return chtype.String
|
|
|
|
}
|
2023-01-21 12:14:00 +02:00
|
|
|
return fmt.Sprintf("Nullable(%s)", chType(typ.Elem()))
|
2022-07-14 10:41:44 +03:00
|
|
|
case reflect.Slice:
|
|
|
|
switch elem := typ.Elem(); elem.Kind() {
|
|
|
|
case reflect.Ptr:
|
|
|
|
if elem.Elem().Kind() == reflect.Struct {
|
|
|
|
return chtype.String // json
|
|
|
|
}
|
|
|
|
case reflect.Struct:
|
|
|
|
if elem != timeType {
|
|
|
|
return chtype.String // json
|
|
|
|
}
|
|
|
|
case reflect.Uint8:
|
|
|
|
return chtype.String // []byte
|
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
return "Array(" + chType(typ.Elem()) + ")"
|
2022-07-14 10:41:44 +03:00
|
|
|
case reflect.Array:
|
|
|
|
if isUUID(typ) {
|
|
|
|
return chtype.UUID
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
if s := chTypes[kind]; s != "" {
|
2022-07-14 10:41:44 +03:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
panic(fmt.Errorf("ch: unsupported Go type: %s", typ))
|
|
|
|
}
|
|
|
|
|
2022-01-23 09:36:24 +02:00
|
|
|
func chArrayElemType(s string) string {
|
2023-01-21 12:14:00 +02:00
|
|
|
if s := chSubType(s, "SimpleAggregateFunction("); s != "" {
|
|
|
|
if i := strings.Index(s, ", "); i >= 0 {
|
|
|
|
s = s[i+2:]
|
|
|
|
}
|
|
|
|
return chSubType(s, "Array(")
|
|
|
|
}
|
|
|
|
|
2022-01-23 09:36:24 +02:00
|
|
|
s = chSubType(s, "Array(")
|
|
|
|
if s == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
elemType := s
|
|
|
|
|
|
|
|
s = chSubType(s, "SimpleAggregateFunction(")
|
|
|
|
if s == "" {
|
|
|
|
return elemType
|
|
|
|
}
|
|
|
|
|
|
|
|
if i := strings.Index(s, ", "); i >= 0 {
|
|
|
|
return s[i+2:]
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
func chEnumType(s string) string {
|
|
|
|
return chSubType(s, "Enum8(")
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
func chSimpleAggFunc(s string) string {
|
|
|
|
s = chSubType(s, "SimpleAggregateFunction(")
|
|
|
|
if s == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
i := strings.Index(s, ", ")
|
|
|
|
if i == -1 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return s[i+2:]
|
2022-01-23 09:36:24 +02:00
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
func chDateTimeType(s string) string {
|
2022-01-23 09:36:24 +02:00
|
|
|
s = chSubType(s, "DateTime(")
|
|
|
|
if s == "" {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
if s != "'UTC'" {
|
|
|
|
internal.Logger.Printf("DateTime has timezeone=%q, expected UTC", s)
|
|
|
|
}
|
|
|
|
return chtype.DateTime
|
|
|
|
}
|
|
|
|
|
2022-04-29 18:51:14 +03:00
|
|
|
func isDateTime64Type(s string) bool {
|
|
|
|
return chSubType(s, "DateTime64(") != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseDateTime64Prec(s string) int {
|
|
|
|
s = chSubType(s, "DateTime64(")
|
|
|
|
if s == "" {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
prec, err := strconv.Atoi(s)
|
|
|
|
if err != nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return prec
|
|
|
|
}
|
|
|
|
|
2023-01-21 12:14:00 +02:00
|
|
|
func chNullableType(s string) string {
|
2022-01-23 09:36:24 +02:00
|
|
|
return chSubType(s, "Nullable(")
|
|
|
|
}
|
|
|
|
|
|
|
|
func aggFuncNameAndType(chType string) (funcName, funcType string) {
|
2022-07-14 10:41:44 +03:00
|
|
|
var s string
|
|
|
|
|
|
|
|
for _, prefix := range []string{"SimpleAggregateFunction(", "AggregateFunction("} {
|
|
|
|
s = chSubType(chType, prefix)
|
|
|
|
if s != "" {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-23 09:36:24 +02:00
|
|
|
if s == "" {
|
|
|
|
return "", ""
|
|
|
|
}
|
|
|
|
|
|
|
|
const sep = ", "
|
|
|
|
idx := strings.LastIndex(s, sep)
|
|
|
|
if idx == -1 {
|
|
|
|
return "", ""
|
|
|
|
}
|
|
|
|
|
|
|
|
funcName = s[:idx]
|
|
|
|
funcType = s[idx+len(sep):]
|
|
|
|
|
|
|
|
if idx := strings.IndexByte(funcName, '('); idx >= 0 {
|
|
|
|
funcName = funcName[:idx]
|
|
|
|
}
|
|
|
|
|
|
|
|
return funcName, funcType
|
|
|
|
}
|
|
|
|
|
|
|
|
func chSubType(s, prefix string) string {
|
|
|
|
if strings.HasPrefix(s, prefix) && strings.HasSuffix(s, ")") {
|
|
|
|
return s[len(prefix) : len(s)-1]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func isUUID(typ reflect.Type) bool {
|
|
|
|
return typ.Len() == 16 && typ.Elem().Kind() == reflect.Uint8
|
|
|
|
}
|