mirror of
https://github.com/hanzo-ds/native.git
synced 2026-10-30 20:00:34 +00:00
proto.Name is sent in ClientHello and lands in the server's system.query_log, so it is wire visible; it now reads datastore/native and is covered by a test. Verified against the live server: the handshake succeeds and the new name appears in query_log. goimports' local-prefixes still named the upstream module, so it was not grouping this module's own imports. Doc comments, the deprecated-error markers in proto/error_codes.go and the package prose are swept. Dead references are removed rather than repointed: go-faster.org/docs/clickhouse and the mangled Datastore/clickhouse-cpp link both 404, and the list of other vendors' clients does not belong here. Four things deliberately keep the vendor name, and CI records why: LICENSE and AUTHORS are the copyright notices Apache-2.0 requires; db.system in query.go is an OpenTelemetry enum whose registered value for this DBMS is fixed by the specification, so emitting anything else would be non-conformant; and README names clickhouse-local, which is genuinely the binary our own server image ships. Numeric error codes and protocol revision constants are untouched.
383 lines
9.4 KiB
Go
383 lines
9.4 KiB
Go
package proto
|
|
|
|
import (
|
|
"math"
|
|
|
|
"github.com/go-faster/errors"
|
|
)
|
|
|
|
// Compile-time assertions for ColLowCardinality.
|
|
var (
|
|
_ ColInput = (*ColLowCardinality[string])(nil)
|
|
_ ColResult = (*ColLowCardinality[string])(nil)
|
|
_ Column = (*ColLowCardinality[string])(nil)
|
|
)
|
|
|
|
//go:generate go run github.com/dmarkham/enumer -type CardinalityKey -trimprefix Key -output col_low_cardinality_enum.go
|
|
|
|
// CardinalityKey is integer type of ColLowCardinality.Keys column.
|
|
type CardinalityKey byte
|
|
|
|
// Possible integer types for ColLowCardinality.Keys.
|
|
const (
|
|
KeyUInt8 CardinalityKey = 0
|
|
KeyUInt16 CardinalityKey = 1
|
|
KeyUInt32 CardinalityKey = 2
|
|
KeyUInt64 CardinalityKey = 3
|
|
)
|
|
|
|
// Constants for low cardinality metadata value that is represented as int64
|
|
// consisted of bitflags and key type.
|
|
//
|
|
// Mirrors the reference C++ client's lowcardinality column implementation.
|
|
//
|
|
// NB: shared dictionaries and on-the-fly dictionary update is not supported,
|
|
// because it is not currently used in client protocol.
|
|
const (
|
|
cardinalityKeyMask = 0b0000_1111_1111 // last byte
|
|
|
|
// Need to read dictionary if it wasn't.
|
|
cardinalityNeedGlobalDictionaryBit = 1 << 8
|
|
// Need to read additional keys.
|
|
// Additional keys are stored before indexes as value N and N keys
|
|
// after them.
|
|
cardinalityHasAdditionalKeysBit = 1 << 9
|
|
// Need to update dictionary. It means that previous granule has different dictionary.
|
|
cardinalityNeedUpdateDictionary = 1 << 10
|
|
|
|
// cardinalityUpdateAll sets both flags (update index, has additional keys)
|
|
cardinalityUpdateAll = cardinalityHasAdditionalKeysBit | cardinalityNeedUpdateDictionary
|
|
)
|
|
|
|
type keySerializationVersion byte
|
|
|
|
// sharedDictionariesWithAdditionalKeys is default key serialization.
|
|
const sharedDictionariesWithAdditionalKeys keySerializationVersion = 1
|
|
|
|
// ColLowCardinality is generic LowCardinality(T) column.
|
|
//
|
|
// ColLowCardinality contains index and keys columns.
|
|
//
|
|
// Index (i.e. dictionary) column contains unique values, Keys column contains
|
|
// sequence of indexes in Index column that represent actual values.
|
|
//
|
|
// For example, ["Eko", "Eko", "Amadela", "Amadela", "Amadela", "Amadela"] can
|
|
// be encoded as:
|
|
//
|
|
// Index: ["Eko", "Amadela"] (String)
|
|
// Keys: [0, 0, 1, 1, 1, 1] (UInt8)
|
|
//
|
|
// The CardinalityKey is chosen depending on Index size, i.e. maximum value
|
|
// of chosen type should be able to represent any index of Index element.
|
|
type ColLowCardinality[T comparable] struct {
|
|
Values []T
|
|
|
|
index ColumnOf[T]
|
|
key CardinalityKey
|
|
|
|
// Keeping all key column variants as fields to reuse
|
|
// memory more efficiently.
|
|
|
|
// Values[T], kv and keys columns adds memory overhead, but simplifies
|
|
// implementation.
|
|
// TODO(ernado): revisit tradeoffs
|
|
|
|
keys8 ColUInt8
|
|
keys16 ColUInt16
|
|
keys32 ColUInt32
|
|
keys64 ColUInt64
|
|
|
|
kv map[T]int
|
|
keys []int
|
|
}
|
|
|
|
// DecodeState implements StateDecoder, ensuring state for index column.
|
|
func (c *ColLowCardinality[T]) DecodeState(r *Reader) error {
|
|
keySerialization, err := r.Int64()
|
|
if err != nil {
|
|
return errors.Wrap(err, "version")
|
|
}
|
|
if keySerialization != int64(sharedDictionariesWithAdditionalKeys) {
|
|
return errors.Errorf("got version %d, expected %d",
|
|
keySerialization, sharedDictionariesWithAdditionalKeys,
|
|
)
|
|
}
|
|
if s, ok := c.index.(StateDecoder); ok {
|
|
if err := s.DecodeState(r); err != nil {
|
|
return errors.Wrap(err, "index state")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EncodeState implements StateEncoder, ensuring state for index column.
|
|
func (c ColLowCardinality[T]) EncodeState(b *Buffer) {
|
|
// Writing key serialization version.
|
|
b.PutInt64(int64(sharedDictionariesWithAdditionalKeys))
|
|
if s, ok := c.index.(StateEncoder); ok {
|
|
s.EncodeState(b)
|
|
}
|
|
}
|
|
|
|
func (c *ColLowCardinality[T]) DecodeColumn(r *Reader, rows int) error {
|
|
if rows == 0 {
|
|
// Skipping entirely of no rows.
|
|
return nil
|
|
}
|
|
meta, err := r.Int64()
|
|
if err != nil {
|
|
return errors.Wrap(err, "meta")
|
|
}
|
|
if (meta & cardinalityNeedGlobalDictionaryBit) == 1 {
|
|
return errors.New("global dictionary is not supported")
|
|
}
|
|
if (meta & cardinalityHasAdditionalKeysBit) == 0 {
|
|
return errors.New("additional keys bit is missing")
|
|
}
|
|
|
|
key := CardinalityKey(meta & cardinalityKeyMask)
|
|
if !key.IsACardinalityKey() {
|
|
return errors.Errorf("invalid low cardinality keys type %d", key)
|
|
}
|
|
c.key = key
|
|
|
|
indexRows, err := r.Int64()
|
|
if err != nil {
|
|
return errors.Wrap(err, "index size")
|
|
}
|
|
if err := checkRows(int(indexRows)); err != nil {
|
|
return errors.Wrap(err, "index size")
|
|
}
|
|
if err := c.index.DecodeColumn(r, int(indexRows)); err != nil {
|
|
return errors.Wrap(err, "index column")
|
|
}
|
|
|
|
keyRows, err := r.Int64()
|
|
if err != nil {
|
|
return errors.Wrap(err, "keys size")
|
|
}
|
|
if err := checkRows(int(keyRows)); err != nil {
|
|
return errors.Wrap(err, "index size")
|
|
}
|
|
switch c.key {
|
|
case KeyUInt8:
|
|
if err := c.keys8.DecodeColumn(r, rows); err != nil {
|
|
return errors.Wrap(err, "keys")
|
|
}
|
|
c.keys = fillValues(c.keys, c.keys8)
|
|
case KeyUInt16:
|
|
if err := c.keys16.DecodeColumn(r, rows); err != nil {
|
|
return errors.Wrap(err, "keys")
|
|
}
|
|
c.keys = fillValues(c.keys, c.keys16)
|
|
case KeyUInt32:
|
|
if err := c.keys32.DecodeColumn(r, rows); err != nil {
|
|
return errors.Wrap(err, "keys")
|
|
}
|
|
c.keys = fillValues(c.keys, c.keys32)
|
|
case KeyUInt64:
|
|
if err := c.keys64.DecodeColumn(r, rows); err != nil {
|
|
return errors.Wrap(err, "keys")
|
|
}
|
|
c.keys = fillValues(c.keys, c.keys64)
|
|
default:
|
|
return errors.Errorf("invalid key format %s", c.key)
|
|
}
|
|
|
|
c.Values = c.Values[:0]
|
|
for _, idx := range c.keys {
|
|
if int64(idx) >= indexRows || idx < 0 {
|
|
return errors.Errorf("key index out of range [%d] with length %d", idx, indexRows)
|
|
}
|
|
c.Values = append(c.Values, c.index.Row(idx))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c ColLowCardinality[T]) Type() ColumnType {
|
|
return ColumnTypeLowCardinality.Sub(c.index.Type())
|
|
}
|
|
|
|
func (c *ColLowCardinality[T]) EncodeColumn(b *Buffer) {
|
|
// Using pointer receiver as Prepare() is expected to be called before
|
|
// encoding.
|
|
|
|
if c.Rows() == 0 {
|
|
// Skipping encoding entirely.
|
|
return
|
|
}
|
|
|
|
// Meta encodes whether reader should update
|
|
// low cardinality metadata and keys column type.
|
|
meta := cardinalityUpdateAll | int64(c.key)
|
|
b.PutInt64(meta)
|
|
|
|
// Writing index (dictionary).
|
|
b.PutInt64(int64(c.index.Rows()))
|
|
c.index.EncodeColumn(b)
|
|
|
|
b.PutInt64(int64(c.Rows()))
|
|
switch c.key {
|
|
case KeyUInt8:
|
|
c.keys8.EncodeColumn(b)
|
|
case KeyUInt16:
|
|
c.keys16.EncodeColumn(b)
|
|
case KeyUInt32:
|
|
c.keys32.EncodeColumn(b)
|
|
case KeyUInt64:
|
|
c.keys64.EncodeColumn(b)
|
|
}
|
|
}
|
|
|
|
func (c *ColLowCardinality[T]) WriteColumn(w *Writer) {
|
|
// Using pointer receiver as Prepare() is expected to be called before
|
|
// encoding.
|
|
|
|
if c.Rows() == 0 {
|
|
// Skipping encoding entirely.
|
|
return
|
|
}
|
|
|
|
w.ChainBuffer(func(b *Buffer) {
|
|
// Meta encodes whether reader should update
|
|
// low cardinality metadata and keys column type.
|
|
meta := cardinalityUpdateAll | int64(c.key)
|
|
b.PutInt64(meta)
|
|
|
|
// Writing index (dictionary).
|
|
b.PutInt64(int64(c.index.Rows()))
|
|
})
|
|
c.index.WriteColumn(w)
|
|
|
|
w.ChainBuffer(func(b *Buffer) {
|
|
b.PutInt64(int64(c.Rows()))
|
|
})
|
|
switch c.key {
|
|
case KeyUInt8:
|
|
c.keys8.WriteColumn(w)
|
|
case KeyUInt16:
|
|
c.keys16.WriteColumn(w)
|
|
case KeyUInt32:
|
|
c.keys32.WriteColumn(w)
|
|
case KeyUInt64:
|
|
c.keys64.WriteColumn(w)
|
|
}
|
|
}
|
|
|
|
func (c *ColLowCardinality[T]) Reset() {
|
|
for k := range c.kv {
|
|
delete(c.kv, k)
|
|
}
|
|
c.keys = c.keys[:0]
|
|
|
|
c.keys8 = c.keys8[:0]
|
|
c.keys16 = c.keys16[:0]
|
|
c.keys32 = c.keys32[:0]
|
|
c.keys64 = c.keys64[:0]
|
|
c.Values = c.Values[:0]
|
|
|
|
c.index.Reset()
|
|
}
|
|
|
|
type cardinalityKeyValue interface {
|
|
~uint8 | ~uint16 | ~uint32 | ~uint64
|
|
}
|
|
|
|
func fillKeys[K cardinalityKeyValue](values []int, keys []K) []K {
|
|
keys = keys[:0]
|
|
for _, v := range values {
|
|
keys = append(keys, K(v))
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func fillValues[K cardinalityKeyValue](values []int, keys []K) []int {
|
|
for _, v := range keys {
|
|
values = append(values, int(v))
|
|
}
|
|
return values
|
|
}
|
|
|
|
// Append value to column.
|
|
func (c *ColLowCardinality[T]) Append(v T) {
|
|
c.Values = append(c.Values, v)
|
|
}
|
|
|
|
// AppendArr appends slice to column.
|
|
func (c *ColLowCardinality[T]) AppendArr(v []T) {
|
|
c.Values = append(c.Values, v...)
|
|
}
|
|
|
|
// Row returns i-th row.
|
|
func (c ColLowCardinality[T]) Row(i int) T {
|
|
return c.Values[i]
|
|
}
|
|
|
|
// Rows returns rows count.
|
|
func (c ColLowCardinality[T]) Rows() int {
|
|
return len(c.Values)
|
|
}
|
|
|
|
// Prepare column for ingestion.
|
|
func (c *ColLowCardinality[T]) Prepare() error {
|
|
// Allocate keys slice.
|
|
c.keys = append(c.keys[:0], make([]int, len(c.Values))...)
|
|
if c.kv == nil {
|
|
c.kv = map[T]int{}
|
|
c.index.Reset()
|
|
}
|
|
|
|
// Fill keys with value indexes.
|
|
var last int
|
|
for i, v := range c.Values {
|
|
idx, ok := c.kv[v]
|
|
if !ok {
|
|
c.index.Append(v)
|
|
c.kv[v] = last
|
|
idx = last
|
|
last++
|
|
}
|
|
c.keys[i] = idx
|
|
}
|
|
|
|
// Select minimum possible size for key.
|
|
if n := last; n < math.MaxUint8 {
|
|
c.key = KeyUInt8
|
|
} else if n < math.MaxUint16 {
|
|
c.key = KeyUInt16
|
|
} else if uint32(n) < math.MaxUint32 {
|
|
c.key = KeyUInt32
|
|
} else {
|
|
c.key = KeyUInt64
|
|
}
|
|
|
|
// Fill key column with key indexes.
|
|
switch c.key {
|
|
case KeyUInt8:
|
|
c.keys8 = fillKeys(c.keys, c.keys8)
|
|
case KeyUInt16:
|
|
c.keys16 = fillKeys(c.keys, c.keys16)
|
|
case KeyUInt32:
|
|
c.keys32 = fillKeys(c.keys, c.keys32)
|
|
case KeyUInt64:
|
|
c.keys64 = fillKeys(c.keys, c.keys64)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Array is helper that creates Array(ColLowCardinality(T)).
|
|
func (c *ColLowCardinality[T]) Array() *ColArr[T] {
|
|
return &ColArr[T]{
|
|
Data: c,
|
|
}
|
|
}
|
|
|
|
// NewLowCardinality creates new LowCardinality column from another column for T.
|
|
func NewLowCardinality[T comparable](c ColumnOf[T]) *ColLowCardinality[T] {
|
|
return &ColLowCardinality[T]{
|
|
index: c,
|
|
}
|
|
}
|