Files
zeekay ef352c1051 debrand: rename the wire client name, prose and linter config
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.
2026-07-26 11:50:36 -07:00

319 lines
7.1 KiB
Go

package proto
import (
"fmt"
"strconv"
"strings"
"github.com/go-faster/errors"
)
// BlockInfo describes block.
type BlockInfo struct {
Overflows bool
BucketNum int
}
func (i BlockInfo) String() string {
return fmt.Sprintf("overflows: %v, buckets: %d", i.Overflows, i.BucketNum)
}
const endField = 0 // end of field pairs
// fields of BlockInfo.
const (
blockInfoOverflows = 1
blockInfoBucketNum = 2
)
// Encode to Buffer.
func (i BlockInfo) Encode(b *Buffer) {
b.PutUVarInt(blockInfoOverflows)
b.PutBool(i.Overflows)
b.PutUVarInt(blockInfoBucketNum)
b.PutInt32(int32(i.BucketNum))
b.PutUVarInt(endField)
}
func (i *BlockInfo) Decode(r *Reader) error {
for {
f, err := r.UVarInt()
if err != nil {
return errors.Wrap(err, "field id")
}
switch f {
case blockInfoOverflows:
v, err := r.Bool()
if err != nil {
return errors.Wrap(err, "overflows")
}
i.Overflows = v
case blockInfoBucketNum:
v, err := r.Int32()
if err != nil {
return errors.Wrap(err, "bucket number")
}
i.BucketNum = int(v)
case endField:
return nil
default:
return errors.Errorf("unknown field %d", f)
}
}
}
// Input of query.
type Input []InputColumn
// Reset all columns that implement proto.Resettable.
func (i Input) Reset() {
for _, c := range i {
if col, ok := c.Data.(Resettable); ok {
col.Reset()
}
}
}
// Into returns INSERT INTO table (c0, c..., cn) VALUES query.
func (i Input) Into(table string) string {
return fmt.Sprintf("INSERT INTO %s %s VALUES", strconv.QuoteToASCII(table), i.Columns())
}
// Columns returns "(foo, bar, baz)" formatted list of Input column names.
func (i Input) Columns() string {
var (
b strings.Builder
buf [64]byte
)
b.WriteRune('(')
for idx, v := range i {
escaped := strconv.AppendQuoteToASCII(buf[:0], v.Name)
b.Write(escaped)
if idx != len(i)-1 {
b.WriteRune(',')
}
}
b.WriteRune(')')
return b.String()
}
type InputColumn struct {
Name string
Data ColInput
}
// ResultColumn can be uses as part of Results or as single Result.
type ResultColumn struct {
Name string // Name of column. Inferred if not provided.
Data ColResult // Data of column, required.
}
// DecodeResult implements Result as "single result" helper.
func (c ResultColumn) DecodeResult(r *Reader, version int, b Block) error {
v := Results{c}
return v.DecodeResult(r, version, b)
}
// AutoResult is ResultColumn with type inference.
func AutoResult(name string) ResultColumn {
return ResultColumn{
Name: name,
Data: &ColAuto{},
}
}
func (c InputColumn) EncodeStart(buf *Buffer, version int) {
buf.PutString(c.Name)
buf.PutString(string(c.Data.Type()))
if FeatureCustomSerialization.In(version) {
buf.PutBool(false) // no custom serialization
}
}
type Block struct {
Info BlockInfo
Columns int
Rows int
}
func (b Block) EncodeAware(buf *Buffer, version int) {
if FeatureBlockInfo.In(version) {
b.Info.Encode(buf)
}
buf.PutInt(b.Columns)
buf.PutInt(b.Rows)
}
func (b Block) EncodeBlock(buf *Buffer, version int, input []InputColumn) error {
if FeatureBlockInfo.In(version) {
b.Info.Encode(buf)
}
if err := b.EncodeRawBlock(buf, version, input); err != nil {
return errors.Wrap(err, "raw block")
}
return nil
}
func (b Block) EncodeRawBlock(buf *Buffer, version int, input []InputColumn) error {
buf.PutInt(b.Columns)
buf.PutInt(b.Rows)
for _, col := range input {
if r := col.Data.Rows(); r != b.Rows {
return errors.Errorf("%q has %d rows, expected %d", col.Name, r, b.Rows)
}
col.EncodeStart(buf, version)
if v, ok := col.Data.(Preparable); ok {
if err := v.Prepare(); err != nil {
return errors.Wrapf(err, "prepare %q", col.Name)
}
}
if col.Data.Rows() == 0 {
continue
}
if v, ok := col.Data.(StateEncoder); ok {
v.EncodeState(buf)
}
col.Data.EncodeColumn(buf)
}
return nil
}
func (b Block) WriteBlock(w *Writer, version int, input []InputColumn) error {
w.ChainBuffer(func(buf *Buffer) {
if FeatureBlockInfo.In(version) {
b.Info.Encode(buf)
}
buf.PutInt(b.Columns)
buf.PutInt(b.Rows)
})
for _, col := range input {
if r := col.Data.Rows(); r != b.Rows {
return errors.Errorf("%q has %d rows, expected %d", col.Name, r, b.Rows)
}
w.ChainBuffer(func(buf *Buffer) {
col.EncodeStart(buf, version)
})
if v, ok := col.Data.(Preparable); ok {
if err := v.Prepare(); err != nil {
return errors.Wrapf(err, "prepare %q", col.Name)
}
}
if col.Data.Rows() == 0 {
continue
}
if v, ok := col.Data.(StateEncoder); ok {
w.ChainBuffer(v.EncodeState)
}
col.Data.WriteColumn(w)
}
return nil
}
// This constrains can prevent accidental OOM and allow early detection
// of erroneous column or row count.
//
// Just empirical values, there are no such limits in spec or in Datastore,
// so is subject to change if false-positives occur.
const (
maxColumnsInBlock = 1_000_000
maxRowsInBLock = 100_000_000
)
func checkRows(n int) error {
if n < 0 {
return errors.New("negative")
}
if n > maxRowsInBLock {
// Most blocks should be less than 100M values, but technically
// there is no limit (can be several billions).
// 1B rows is too big and probably several gigabytes in RSS.
//
// The 100M UInt64 block is ~655MB RSS, should be pretty safe and
// protect from accidental (e.g. cosmic rays) rows count corruption.
return errors.Errorf("%d is suspiciously big, maximum is %d (preventing possible OOM)", n, maxRowsInBLock)
}
return nil
}
func (b *Block) End() bool {
return b.Columns == 0 && b.Rows == 0
}
func (b *Block) DecodeRawBlock(r *Reader, version int, target Result) error {
{
v, err := r.Int()
if err != nil {
return errors.Wrap(err, "columns")
}
if v > maxColumnsInBlock || v < 0 {
return errors.Errorf("invalid columns number %d", v)
}
b.Columns = v
}
{
v, err := r.Int()
if err != nil {
return errors.Wrap(err, "rows")
}
if err := checkRows(v); err != nil {
return errors.Wrap(err, "rows count")
}
b.Rows = v
}
if b.End() {
// End of data, special case.
return nil
}
if target == nil && b.Rows > 0 {
return errors.New("got rows without target")
}
if target == nil {
// Just skipping rows and types.
for i := 0; i < b.Columns; i++ {
// Name.
if _, err := r.Str(); err != nil {
return errors.Wrapf(err, "column [%d] name", i)
}
// Type.
if _, err := r.Str(); err != nil {
return errors.Wrapf(err, "column [%d] type", i)
}
if FeatureCustomSerialization.In(version) {
// Custom serialization flag.
v, err := r.Bool()
if err != nil {
return errors.Wrapf(err, "column [%d] custom serialization flag", i)
}
if v {
return errors.Errorf("column [%d] has custom serialization (not supported)", i)
}
}
}
return nil
}
if err := target.DecodeResult(r, version, *b); err != nil {
return errors.Wrap(err, "target")
}
return nil
}
func (b *Block) DecodeBlock(r *Reader, version int, target Result) error {
if FeatureBlockInfo.In(version) {
if err := b.Info.Decode(r); err != nil {
return errors.Wrap(err, "info")
}
}
if err := b.DecodeRawBlock(r, version, target); err != nil {
return errors.Wrap(err, "raw block")
}
return nil
}