nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: mailru/go-clickhouse
Pull request #77 (2019-06-20 10:57:03)

LowCardinality type dataparser support.
When I select rows with LowCardinality field type without CAST I have an error: 
`type LowCardinality is not supported`
This is a fix.


Patch:
diff --git a/README.md b/README.md
index a8f48a8..81b2eaf 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,7 @@ http://user:password@host:8123/clicks?read_timeout=10&write_timeout=20
 * Date
 * DateTime
 * Enum
+* LowCardinality(T)
 * [Array(T) (one-dimensional)](https://clickhouse.yandex/reference_en.html#Array(T))
 * [Nested(Name1 Type1, Name2 Type2, ...)](https://clickhouse.yandex/docs/en/data_types/nested_data_structures/nested/)
 
diff --git a/dataparser.go b/dataparser.go
index 88684c7..07e164a 100644
--- a/dataparser.go
+++ b/dataparser.go
@@ -230,6 +230,18 @@ func (p *arrayParser) Parse(s io.RuneScanner) (driver.Value, error) {
 	return slice.Interface(), nil
 }
 
+type lowCardinalityParser struct {
+	arg DataParser
+}
+
+func (p *lowCardinalityParser) Type() reflect.Type {
+	return p.arg.Type()
+}
+
+func (p *lowCardinalityParser) Parse(s io.RuneScanner) (driver.Value, error) {
+	return p.arg.Parse(s)
+}
+
 func newDateTimeParser(format, locname string, unquote bool) (DataParser, error) {
 	loc, err := time.LoadLocation(locname)
 	if err != nil {
@@ -431,6 +443,15 @@ func newDataParser(t *TypeDesc, unquote bool) (DataParser, error) {
 			subParsers[i] = subParser
 		}
 		return &tupleParser{subParsers}, nil
+	case "LowCardinality":
+		if len(t.Args) != 1 {
+			return nil, fmt.Errorf("element type not specified for LowCardinality")
+		}
+		subParser, err := newDataParser(t.Args[0], unquote)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create parser for LowCardinality elements: %v", err)
+		}
+		return &lowCardinalityParser{subParser}, nil
 	default:
 		return nil, fmt.Errorf("type %s is not supported", t.Name)
 	}


Test patch:
diff --git a/dataparser_test.go b/dataparser_test.go
index aacdbab..9694784 100644
--- a/dataparser_test.go
+++ b/dataparser_test.go
@@ -253,6 +253,30 @@ func TestParseData(t *testing.T) {
 			inputdata:     "(1,2",
 			failParseData: true,
 		},
+		{
+			name:      "low cardinality string",
+			inputtype: "LowCardinality(String)",
+			inputdata: "hello",
+			output:    "hello",
+		},
+		{
+			name:      "low cardinality string with escaping",
+			inputtype: "LowCardinality(String)",
+			inputdata: `hello \'world`,
+			output:    "hello 'world",
+		},
+		{
+			name:          "low cardinality string with incorrect escaping",
+			inputtype:     "LowCardinality(String)",
+			inputdata:     `hello world\`,
+			failParseData: true,
+		},
+		{
+			name:      "low cardinality UInt64",
+			inputtype: "LowCardinality(UInt64)",
+			inputdata: "123",
+			output:    uint64(123),
+		},
 	}
 
 	for _, tc := range testCases {
Repository: mailru/go-clickhouse
Pull request #81 (2019-08-06 16:11:43)

Nullable fields, go 1.12
this PR duplicates #67 and fixes the issues and also bumps the go version


Patch:
diff --git a/.travis.yml b/.travis.yml
index 8e4ac05..beb881f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,6 +6,7 @@ go:
   - '1.9'
   - '1.10'
   - '1.11'
+  - '1.12'
 
 services:
   - docker
diff --git a/README.md b/README.md
index 81b2eaf..19d7edf 100644
--- a/README.md
+++ b/README.md
@@ -142,7 +142,7 @@ func main() {
 	}
 
 	rows, err := connect.Query(`
-		SELECT 
+		SELECT
 			country_code,
 			os_id,
 			browser_id,
diff --git a/dataparser.go b/dataparser.go
index 330ea15..ed3565c 100644
--- a/dataparser.go
+++ b/dataparser.go
@@ -43,6 +43,23 @@ type dateTimeParser struct {
 	location *time.Location
 }
 
+type nullableParser struct {
+	DataParser
+}
+
+func (p *nullableParser) Parse(s io.RuneScanner) (driver.Value, error) {
+	// Clickhouse returns `\N` string for `null` in tsv format.
+	// For checking this value we need to check first two runes in `io.RuneScanner`, but we can't reset `io.RuneScanner` after it.
+	// Copy io.RuneScanner to `bytes.Buffer` and use it twice (1st for casting to string and checking to null, 2nd for passing to original parser)
+	d := readRaw(s)
+
+	if bytes.Equal(d.Bytes(), []byte(`\N`)) {
+		return nil, nil
+	}
+
+	return p.DataParser.Parse(d)
+}
+
 func readNumber(s io.RuneScanner) (string, error) {
 	var builder bytes.Buffer
 
@@ -384,7 +401,14 @@ func newDataParser(t *TypeDesc, unquote bool, opt *DataParserOptions) (DataParse
 	case "Nothing":
 		return &nothingParser{}, nil
 	case "Nullable":
-		return nil, fmt.Errorf("Nullable types are not supported")
+		if len(t.Args) == 0 {
+			return nil, fmt.Errorf("Nullable should pass original type")
+		}
+		p, err := newDataParser(t.Args[0], unquote, opt)
+		if err != nil {
+			return nil, err
+		}
+		return &nullableParser{p}, nil
 	case "Date":
 		loc := time.UTC
 		if opt != nil && opt.Location != nil {
diff --git a/tokenizer.go b/tokenizer.go
index e1bc497..3418986 100644
--- a/tokenizer.go
+++ b/tokenizer.go
@@ -60,6 +60,22 @@ func readEscaped(s io.RuneScanner) (rune, error) {
 	}
 }
 
+func readRaw(s io.RuneScanner) *bytes.Buffer {
+	var data bytes.Buffer
+
+	for {
+		r := read(s)
+
+		if r == eof {
+			break
+		}
+
+		data.WriteRune(r)
+	}
+
+	return &data
+}
+
 func readQuoted(s io.RuneScanner) (*token, error) {
 	var data bytes.Buffer
 


Test patch:
diff --git a/dataparser_test.go b/dataparser_test.go
index bd8cfe0..5c0ce86 100644
--- a/dataparser_test.go
+++ b/dataparser_test.go
@@ -32,10 +32,16 @@ func TestParseData(t *testing.T) {
 
 	testCases := []*testCase{
 		{
-			name:          "nullable not supported",
-			inputtype:     "Nullable(String)",
-			inputdata:     "NULL",
-			failNewParser: true,
+			name:      "nullable string",
+			inputtype: "Nullable(String)",
+			inputdata: `\N`,
+			output:    nil,
+		},
+		{
+			name:      "nullable int not null",
+			inputtype: "Nullable(UInt64)",
+			inputdata: "655",
+			output:    uint64(655),
 		},
 		{
 			name:      "string",
Repository: mailru/go-clickhouse
Pull request #116 (2021-04-13 06:41:48)

skip only not string
Sorry, I was wrong: we can leave only one value if it is of string type, otherwise it is a separator.


Patch:
diff --git a/rows.go b/rows.go
index 5268254..94eb036 100644
--- a/rows.go
+++ b/rows.go
@@ -84,7 +84,7 @@ func (r *textRows) Next(dest []driver.Value) error {
 
 	// skip row before WITH TOTALS,
 	// not but do not skip an empty line if it is part of the result
-	if len(row) == 1 && row[0] == "" && len(dest) != 1 {
+	if len(row) == 1 && row[0] == "" {
 		row, err = r.tsv.Read()
 		if err != nil {
 			return err


Test patch:
diff --git a/rows_test.go b/rows_test.go
index dd8e995..d514308 100644
--- a/rows_test.go
+++ b/rows_test.go
@@ -83,21 +83,6 @@ func TestTextRowsNewLine(t *testing.T) {
 	assert.Equal(t, []driver.Value{"Hello\nThere"}, dest)
 }
 
-func TestTextRowsEmpty(t *testing.T) {
-	buf := bytes.NewReader([]byte("text\nString\n\n"))
-	rows, err := newTextRows(&conn{}, &bufReadCloser{buf}, time.Local, false)
-	if !assert.NoError(t, err) {
-		return
-	}
-	assert.Equal(t, []string{"text"}, rows.Columns())
-	assert.Equal(t, []string{"String"}, rows.types)
-	dest := make([]driver.Value, 1)
-	if !assert.NoError(t, rows.Next(dest)) {
-		return
-	}
-	assert.Equal(t, []driver.Value{""}, dest)
-}
-
 func TestTextRowsWithStartsDoubleQuotes(t *testing.T) {
 	buf := bytes.NewReader([]byte("text\nString\n\"\n"))
 	rows, err := newTextRows(&conn{}, &bufReadCloser{buf}, time.Local, false)
@@ -146,3 +131,33 @@ func TestTextRowsWithEmptyQuotes(t *testing.T) {
 	}
 	assert.Equal(t, []driver.Value{`""`}, dest)
 }
+
+func TestInt32RowsEmpty(t *testing.T) {
+	buf := bytes.NewReader([]byte("text\nInt64\n\n1\n"))
+	rows, err := newTextRows(&conn{}, &bufReadCloser{buf}, time.Local, false)
+	if !assert.NoError(t, err) {
+		return
+	}
+	assert.Equal(t, []string{"text"}, rows.Columns())
+	assert.Equal(t, []string{"Int64"}, rows.types)
+	dest := make([]driver.Value, 1)
+	if !assert.NoError(t, rows.Next(dest)) {
+		return
+	}
+	assert.Equal(t, []driver.Value{int64(1)}, dest)
+}
+
+func TestFloat64RowsEmpty(t *testing.T) {
+	buf := bytes.NewReader([]byte("text\nFloat64\n\n1.0\n"))
+	rows, err := newTextRows(&conn{}, &bufReadCloser{buf}, time.Local, false)
+	if !assert.NoError(t, err) {
+		return
+	}
+	assert.Equal(t, []string{"text"}, rows.Columns())
+	assert.Equal(t, []string{"Float64"}, rows.types)
+	dest := make([]driver.Value, 1)
+	if !assert.NoError(t, rows.Next(dest)) {
+		return
+	}
+	assert.Equal(t, []driver.Value{float64(1)}, dest)
+}
Repository: mailru/go-clickhouse
Pull request #127 (2021-06-04 18:46:47)

Finnan444 fix/nullable date time parse


Patch:
diff --git a/README.md b/README.md
index ba3bbc0..0b86ff1 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,10 @@ Yet another Golang SQL database driver for [Yandex ClickHouse](https://clickhous
 ## Key features
 
 * Uses official http interface
+* Compatibility with database/sql
 * Compatibility with [dbr](https://github.com/mailru/dbr)
 * Compatibility with [chproxy](https://github.com/Vertamedia/chproxy)
+* Compatibility with [clickhouse-bulk](https://github.com/nikepan/clickhouse-bulk)
 
 ## DSN
 ```
@@ -183,7 +185,7 @@ func main() {
 	}
 
 	ctx := context.Background()
-	rows, err := connect.QueryContext(context.WithValue(ctx, clickhouse.QueryID, "dummy-query-id"), `
+	rows, err = connect.QueryContext(context.WithValue(ctx, clickhouse.QueryID, "dummy-query-id"), `
 		SELECT
 			country_code,
 			os_id,
diff --git a/dataparser.go b/dataparser.go
index 5d7311a..7d3c4b0 100644
--- a/dataparser.go
+++ b/dataparser.go
@@ -184,7 +184,10 @@ func (p *nullableParser) Parse(s io.RuneScanner) (driver.Value, error) {
 		for {
 			r, _, err := s.ReadRune()
 			if err != nil {
-				return nil, nil
+				if err != io.EOF {
+					return nil, fmt.Errorf("unexpected error on ReadRune: %v", err)
+				}
+				break
 			}
 
 			runes += string(r)


Test patch:
diff --git a/dataparser_test.go b/dataparser_test.go
index 2a922c9..4371f82 100644
--- a/dataparser_test.go
+++ b/dataparser_test.go
@@ -476,6 +476,24 @@ func TestParseDataNewNullableArray(t *testing.T) {
 			inputdata: `\N`,
 			output:    nil,
 		},
+		{
+			name:      "nullable null date",
+			inputtype: "Nullable(Date)",
+			inputdata: `\N`,
+			output:    nil,
+		},
+		{
+			name:      "nullable not null datetime",
+			inputtype: "Nullable(DateTime)",
+			inputdata: "2018-01-02 12:34:56",
+			output:    time.Date(2018, 1, 2, 12, 34, 56, 0, time.UTC),
+		},
+		{
+			name:      "nullable not null date",
+			inputtype: "Nullable(Date)",
+			inputdata: "2018-01-02",
+			output:    time.Date(2018, 1, 2, 0, 0, 0, 0, time.UTC),
+		},
 		{
 			name:      "nullable null enum",
 			inputtype: "Nullable(Enum8('hello' = 1, 'world' = 2))",
Repository: mailru/go-clickhouse
Pull request #135 (2021-07-26 19:24:35)

Support SimpleAggregateFunction types
Per Clickhouse documentation, the SimpleAggregateFunction type can be treated as an opaque wrapper around the inner type.


Patch:
diff --git a/dataparser.go b/dataparser.go
index 1bd44c7..f20bbda 100644
--- a/dataparser.go
+++ b/dataparser.go
@@ -364,6 +364,18 @@ func (p *lowCardinalityParser) Parse(s io.RuneScanner) (driver.Value, error) {
 	return p.arg.Parse(s)
 }
 
+type simpleAggregateFunctionParser struct {
+	arg DataParser
+}
+
+func (p *simpleAggregateFunctionParser) Type() reflect.Type {
+	return p.arg.Type()
+}
+
+func (p *simpleAggregateFunctionParser) Parse(s io.RuneScanner) (driver.Value, error) {
+	return p.arg.Parse(s)
+}
+
 func newDateTimeParser(format string, loc *time.Location, unquote bool) (DataParser, error) {
 	return &dateTimeParser{
 		unquote:  unquote,
@@ -594,6 +606,15 @@ func newDataParser(t *TypeDesc, unquote bool, opt *DataParserOptions) (DataParse
 			return nil, fmt.Errorf("failed to create parser for LowCardinality elements: %v", err)
 		}
 		return &lowCardinalityParser{subParser}, nil
+	case "SimpleAggregateFunction":
+		if len(t.Args) != 2 {
+			return nil, fmt.Errorf("incorrect number of arguments for SimpleAggregateFunction")
+		}
+		subParser, err := newDataParser(t.Args[1], unquote, opt)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create parser for SimpleAggregateFunction element: %v", err)
+		}
+		return &simpleAggregateFunctionParser{subParser}, nil
 	default:
 		return nil, fmt.Errorf("type %s is not supported", t.Name)
 	}


Test patch:
diff --git a/dataparser_test.go b/dataparser_test.go
index aa7d3dc..be82e58 100644
--- a/dataparser_test.go
+++ b/dataparser_test.go
@@ -334,6 +334,21 @@ func TestParseData(t *testing.T) {
 			inputdata: "123",
 			output:    uint64(123),
 		},
+		{
+			name:      "simple aggregate function of uint64",
+			inputtype: "SimpleAggregateFunction(max, UInt64)",
+			inputdata: "123",
+			output:    uint64(123),
+		},
+		{
+			name:      "simple aggregate function of mixed tuple",
+			inputtype: "SimpleAggregateFunction(avg, Tuple(String, UInt64))",
+			inputdata: "('abc',123)",
+			output: struct {
+				Field0 string
+				Field1 uint64
+			}{"abc", uint64(123)},
+		},
 		{
 			name:      "ipv4",
 			inputtype: "IPv4",
Repository: mailru/go-clickhouse
Pull request #136 (2021-08-05 11:14:06)

Support configuring TLSConfig in FormatDSN
This was not being configured in this method, even though `ParseDSN` supports it.


Patch:
diff --git a/config.go b/config.go
index e7d50d0..ccd527c 100644
--- a/config.go
+++ b/config.go
@@ -72,6 +72,9 @@ func (cfg *Config) FormatDSN() string {
 	if cfg.KillQueryTimeout != 0 {
 		query.Set("kill_query_timeout", cfg.KillQueryTimeout.String())
 	}
+	if cfg.TLSConfig != "" {
+		query.Set("tls_config", cfg.TLSConfig)
+	}
 
 	u.RawQuery = query.Encode()
 	return u.String()


Test patch:
diff --git a/config_test.go b/config_test.go
index 3b5d568..cc0667b 100644
--- a/config_test.go
+++ b/config_test.go
@@ -9,7 +9,7 @@ import (
 
 func TestParseDSN(t *testing.T) {
 	dsn := "http://username:password@localhost:8123/test?timeout=1s&idle_timeout=2s&read_timeout=3s" +
-		"&write_timeout=4s&location=Local&max_execution_time=10&debug=1&kill_query=1&kill_query_timeout=5s"
+		"&write_timeout=4s&location=Local&max_execution_time=10&debug=1&kill_query=1&kill_query_timeout=5s&tls_config=tls-settings"
 	cfg, err := ParseDSN(dsn)
 	if assert.NoError(t, err) {
 		assert.Equal(t, "username", cfg.User)
@@ -17,6 +17,7 @@ func TestParseDSN(t *testing.T) {
 		assert.Equal(t, "http", cfg.Scheme)
 		assert.Equal(t, "localhost:8123", cfg.Host)
 		assert.Equal(t, "test", cfg.Database)
+		assert.Equal(t, "tls-settings", cfg.TLSConfig)
 		assert.Equal(t, time.Second, cfg.Timeout)
 		assert.Equal(t, 2*time.Second, cfg.IdleTimeout)
 		assert.Equal(t, 3*time.Second, cfg.ReadTimeout)
@@ -39,6 +40,7 @@ func TestDefaultConfig(t *testing.T) {
 	assert.False(t, cfg.Debug)
 	assert.False(t, cfg.KillQueryOnErr)
 	assert.Equal(t, time.UTC, cfg.Location)
+	assert.Equal(t, "", cfg.TLSConfig)
 	assert.EqualValues(t, 0, cfg.ReadTimeout)
 	assert.EqualValues(t, 0, cfg.WriteTimeout)
 	assert.EqualValues(t, 0, cfg.KillQueryTimeout)
@@ -64,7 +66,7 @@ func TestParseWrongDSN(t *testing.T) {
 
 func TestFormatDSN(t *testing.T) {
 	dsn := "http://username:password@localhost:8123/test?timeout=1s&idle_timeout=2s&read_timeout=3s" +
-		"&write_timeout=4s&location=Europe%2FMoscow&max_execution_time=10&debug=1&kill_query=1&kill_query_timeout=5s"
+		"&write_timeout=4s&location=Europe%2FMoscow&max_execution_time=10&debug=1&kill_query=1&kill_query_timeout=5s&tls_config=tls-settings"
 	cfg, err := ParseDSN(dsn)
 	if assert.NoError(t, err) {
 		dsn2 := cfg.FormatDSN()
@@ -79,6 +81,7 @@ func TestFormatDSN(t *testing.T) {
 		assert.Contains(t, dsn2, "debug=1")
 		assert.Contains(t, dsn2, "kill_query=1")
 		assert.Contains(t, dsn2, "kill_query_timeout=5s")
+		assert.Contains(t, dsn2, "tls_config=tls-settings")
 	}
 }
Repository: mailru/go-clickhouse
Pull request #139 (2021-08-18 15:01:23)

Patch nullable empty strings parsing
Add fix for nullable empty strings parsing


Patch:
diff --git a/dataparser.go b/dataparser.go
index f20bbda..cb7ba62 100644
--- a/dataparser.go
+++ b/dataparser.go
@@ -135,13 +135,20 @@ func (p *nullableParser) Parse(s io.RuneScanner) (driver.Value, error) {
 
 		isNotString := false
 		for {
-			r, _, err := s.ReadRune()
+			r, size, err := s.ReadRune()
+			if err == io.EOF && size == 0 { 
+				break
+			}
+			
 			if err != nil {
 				return nil, fmt.Errorf("error: %v", err)
 			}
 
 			if r != '\'' && iter == 0 {
-				_ = s.UnreadRune()
+				err = s.UnreadRune()
+				if err != nil {
+					return nil, fmt.Errorf("error: %v", err)
+				}
 				d := readRaw(s)
 				dB = d
 				isNotString = true


Test patch:
diff --git a/dataparser_test.go b/dataparser_test.go
index be82e58..886a91b 100644
--- a/dataparser_test.go
+++ b/dataparser_test.go
@@ -37,6 +37,12 @@ func TestParseData(t *testing.T) {
 			inputdata: `\N`,
 			output:    nil,
 		},
+		{
+			name:      "nullable empty string",
+			inputtype: "Nullable(String)",
+			inputdata: "",
+			output:    "",
+		},
 		{
 			name:      "nullable int not null",
 			inputtype: "Nullable(UInt64)",
Repository: mailru/go-clickhouse
Pull request #142 (2021-11-03 11:22:48)

feat. impl map type
Implement Map clickhouse type.


Patch:
diff --git a/encoder.go b/encoder.go
index ae059c8..c80cb98 100644
--- a/encoder.go
+++ b/encoder.go
@@ -29,6 +29,8 @@ func (e *textEncoder) Encode(value driver.Value) ([]byte, error) {
 		return e.encodeArray(reflect.ValueOf(v.v))
 	case tuple:
 		return e.encodeTuple(reflect.ValueOf(v.v))
+	case mapp:
+		return e.encodeMap(reflect.ValueOf(v.v))
 	case []byte:
 		return v, nil
 	case time.Time:
@@ -164,3 +166,35 @@ func (e *textEncoder) encodeTuplePart(value reflect.Value) ([]byte, error) {
 	}
 	return res, nil
 }
+
+func (e *textEncoder) encodeMap(m reflect.Value) ([]byte, error) {
+	if m.Kind() != reflect.Map {
+		return nil, fmt.Errorf("expected map, got %s", m.Kind())
+	}
+
+	keys := m.MapKeys()
+	res := make([]byte, 0)
+	res = append(res, "map("...)
+	for i, key := range keys {
+		value := m.MapIndex(key)
+
+		tmpKey, err := e.Encode(key.Interface())
+		if err != nil {
+			return nil, err
+		}
+		res = append(res, tmpKey...)
+
+		res = append(res, ',')
+
+		tmpValue, err := e.Encode(value.Interface())
+		if err != nil {
+			return nil, err
+		}
+		res = append(res, tmpValue...)
+
+		if i < len(keys)-1 {
+			res = append(res, ',')
+		}
+	}
+	return append(res, ')'), nil
+}
diff --git a/types.go b/types.go
index 1e0067c..b874643 100644
--- a/types.go
+++ b/types.go
@@ -8,6 +8,18 @@ import (
 	"time"
 )
 
+func Map(v interface{}) driver.Valuer {
+	return mapp{v: v}
+}
+
+type mapp struct {
+	v interface{}
+}
+
+func (a mapp) Value() (driver.Value, error) {
+	return textEncode.Encode(a)
+}
+
 // Array wraps slice or array into driver.Valuer interface to allow pass through it from database/sql
 func Array(v interface{}) driver.Valuer {
 	return array{v: v}


Test patch:
diff --git a/encoder_test.go b/encoder_test.go
index 4f46c33..135145a 100644
--- a/encoder_test.go
+++ b/encoder_test.go
@@ -1,6 +1,7 @@
 package clickhouse
 
 import (
+	"regexp"
 	"testing"
 	"time"
 
@@ -69,3 +70,22 @@ func TestTextEncoder(t *testing.T) {
 		}
 	}
 }
+
+func TestTextEncoder_Map(t *testing.T) {
+	testCases := []struct {
+		value interface{}
+		reg   *regexp.Regexp
+	}{
+		{value: Map(map[string]string{"KEY1": "Value1", "Key2": "Value2"}), reg: regexp.MustCompile(`map\(('KEY1','Value1','Key2','Value2'|'Key2','Value2','KEY1','Value1')\)`)},
+		{value: Map(map[string]int{"KEY1": 1, "Key2": 2}), reg: regexp.MustCompile(`map\(('KEY1',1,'Key2',2|'Key2',2,'KEY1',1)\)`)},
+		{value: Map(map[string]bool{"KEY1": true, "Key2": false}), reg: regexp.MustCompile(`map\(('KEY1',1,'Key2',0|'Key2',0,'KEY1',1)\)`)},
+	}
+
+	enc := new(textEncoder)
+	for _, tc := range testCases {
+		v, err := enc.Encode(tc.value)
+		if assert.NoError(t, err) {
+			assert.True(t, tc.reg.Match(v))
+		}
+	}
+}
Repository: mailru/go-clickhouse
Pull request #165 (2022-11-30 08:28:51)

add settings_params pass from context for request to ClickHouse
This feature will allow you to pass parameters to the service through the context. 
For example, it will be possible to control caching or labels in the request if you uses [chproxy](https://pkg.go.dev/github.com/richard403/chproxy#readme-caching)



Patch:
diff --git a/conn.go b/conn.go
index b09d994..6358e64 100644
--- a/conn.go
+++ b/conn.go
@@ -27,6 +27,9 @@ const (
 	QueryID key = iota
 	// QuotaKey uses for setting quota_key request param for request to Clickhouse
 	QuotaKey
+	// RequestQueryParams uses for custom setting request params for request to Clickhouse
+	// presented as map[string]string -> key1=value1&key2=value2... etc
+	RequestQueryParams
 
 	quotaKeyParamName = "quota_key"
 	queryIDParamName  = "query_id"
@@ -340,7 +343,18 @@ func (c *conn) buildRequest(ctx context.Context, query string, params []driver.V
 			reqQuery.Add(queryIDParamName, queryID)
 		}
 
+		requestQueryParams, requestQueryParamsOk := ctx.Value(RequestQueryParams).(map[string]string)
+		if requestQueryParamsOk && len(requestQueryParams) != 0 {
+			if reqQuery == nil {
+				reqQuery = req.URL.Query()
+			}
+
+			for name, value := range requestQueryParams {
+				reqQuery.Add(name, value)
+			}
+		}
 	}
+
 	if reqQuery != nil {
 		req.URL.RawQuery = reqQuery.Encode()
 	}


Test patch:
diff --git a/conn_go18_test.go b/conn_go18_test.go
index 0abbe10..a054c5d 100644
--- a/conn_go18_test.go
+++ b/conn_go18_test.go
@@ -38,6 +38,18 @@ func (s *connSuite) TestPing() {
 	s.EqualError(s.conn.PingContext(ctx), "context canceled")
 }
 
+func (s *connSuite) TestPassRequestQueryParamsFromContext() {
+	ctx := context.WithValue(context.Background(), RequestQueryParams, map[string]string{
+		"max_read_buffer_size": "1",
+	})
+	s.NoError(s.conn.PingContext(ctx))
+
+	ctx = context.WithValue(context.Background(), RequestQueryParams, map[string]string{
+		"no_cache": "1",
+	})
+	s.EqualError(s.conn.PingContext(ctx), "Code: 115, Message: Unknown setting no_cache")
+}
+
 func (s *connSuite) TestColumnTypes() {
 	rows, err := s.conn.Query("SELECT * FROM data LIMIT 1")
 	s.Require().NoError(err)
Repository: mailru/go-clickhouse
Pull request #177 (2024-02-20 13:55:33)

Export driver
Exporting driver to enable sqltrace integration


Patch:
diff --git a/README.md b/README.md
index 0820f3c..61007f8 100644
--- a/README.md
+++ b/README.md
@@ -266,6 +266,36 @@ func main() {
 		)
 	}
 }
+```
+Use with [DataDog trace](https://pkg.go.dev/gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql)
+```go
+package main
+
+import (
+	"log"
+
+	sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql"
+
+	clickhouse "github.com/mailru/go-clickhouse/v2"
+)
+
+func main() {
+	// The first step is to register the clickhouse driver.
+	sqltrace.Register("chhttp", &clickhouse.Driver{})
+
+	// Followed by a call to Open.
+    db, err := sqltrace.Open("chhttp", "http://127.0.0.1:8123/default")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	rows, err := db.Query("SELECT name FROM users WHERE age=?", 27)
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer rows.Close()
+}
+
 ```
 
 ## Go versions
diff --git a/clickhouse.go b/clickhouse.go
index c39f355..059c9cb 100644
--- a/clickhouse.go
+++ b/clickhouse.go
@@ -6,15 +6,15 @@ import (
 )
 
 func init() {
-	sql.Register("chhttp", new(chDriver))
+	sql.Register("chhttp", new(Driver))
 }
 
-// chDriver implements sql.Driver interface
-type chDriver struct {
+// Driver implements sql.Driver interface
+type Driver struct {
 }
 
 // Open returns new db connection
-func (d *chDriver) Open(dsn string) (driver.Conn, error) {
+func (d *Driver) Open(dsn string) (driver.Conn, error) {
 	cfg, err := ParseDSN(dsn)
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/clickhouse_test.go b/clickhouse_test.go
index 7e5b3a6..bfd1be3 100644
--- a/clickhouse_test.go
+++ b/clickhouse_test.go
@@ -12,7 +12,7 @@ import (
 )
 
 var (
-	_ driver.Driver = new(chDriver)
+	_ driver.Driver = new(Driver)
 )
 
 var ddls = []string{