fix: update vendor master 20220701 (#14563)

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
Jian Qiu
2022-07-01 21:11:47 +08:00
committed by GitHub
parent dfcba16fda
commit 8aecc4e547
223 changed files with 7596 additions and 20085 deletions
+4 -6
View File
@@ -105,7 +105,7 @@ require (
yunion.io/x/ovsdb v0.0.0-20200526071744-27bf0940cbc7
yunion.io/x/pkg v1.0.1-0.20220630095420-9925accd7c5e
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e
yunion.io/x/sqlchemy v1.1.2-0.20220701050442-4a04411dc606
yunion.io/x/sqlchemy v1.1.2-0.20220701113246-498f1b8c1941
yunion.io/x/structarg v0.0.0-20220312084958-9c6c79c7d1c6
)
@@ -117,7 +117,7 @@ require (
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/Azure/go-autorest/logger v0.1.0 // indirect
github.com/Azure/go-autorest/tracing v0.5.0 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.1.0 // indirect
github.com/ClickHouse/clickhouse-go v1.5.4 // indirect
github.com/DataDog/dd-trace-go v0.6.1 // indirect
github.com/DataDog/zstd v1.3.4 // indirect
github.com/Masterminds/goutils v1.1.0 // indirect
@@ -143,6 +143,7 @@ require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -173,6 +174,7 @@ require (
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
@@ -212,7 +214,6 @@ require (
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect
github.com/opentracing/opentracing-go v1.0.2 // indirect
github.com/openzipkin/zipkin-go-opentracing v0.3.4 // indirect
github.com/paulmach/orb v0.7.1 // indirect
github.com/philhofer/fwd v1.0.0 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pkg/term v1.0.0 // indirect
@@ -225,7 +226,6 @@ require (
github.com/rivo/uniseg v0.2.0 // indirect
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
@@ -240,8 +240,6 @@ require (
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
go.opencensus.io v0.22.4 // indirect
go.opentelemetry.io/otel v1.7.0 // indirect
go.opentelemetry.io/otel/trace v1.7.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.17.0 // indirect
+5 -22
View File
@@ -71,9 +71,8 @@ github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VY
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
github.com/ClickHouse/clickhouse-go/v2 v2.1.0 h1:X53a5FzRna9TLGGYm1A7T+3kEnrfEYl15BNsL6sw81s=
github.com/ClickHouse/clickhouse-go/v2 v2.1.0/go.mod h1:nOBMOlMUGQJ2eb6PtECHYldbEHmDJFzfIrtaDXMjrb4=
github.com/DataDog/dd-trace-go v0.6.1 h1:nsZ2lohbSw1CKtfNRu3wPh1jFirv6XSz8vqNpuIYWbM=
github.com/DataDog/dd-trace-go v0.6.1/go.mod h1:SmQTTcC37XMyEm75HV0AWiZIYxDiaNhRi49zorIpW+o=
github.com/DataDog/zstd v1.3.4 h1:LAGHkXuvC6yky+C2CUG2tD7w8QlrUwpue8XwIh0X4AY=
@@ -99,7 +98,6 @@ github.com/Shopify/sarama v1.20.0 h1:wAMHhl1lGRlobeoV/xOKpbqD2OQsOvY4A/vIOGroIe8
github.com/Shopify/sarama v1.20.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
@@ -169,6 +167,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
@@ -189,6 +188,7 @@ github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/o
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coredns/coredns v1.3.0 h1:Ubjr8ZrOpEj95/A9dqQqQAG2k5DWEFdg2ZB3tUXuULA=
@@ -272,8 +272,6 @@ github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
@@ -389,7 +387,6 @@ github.com/googollee/go-socket.io v0.0.0-20181214084611-0ad7206c347a/go.mod h1:f
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
@@ -539,7 +536,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -573,9 +569,6 @@ github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go-opentracing v0.3.4 h1:x/pBv/5VJNWkcHF1G9xqhug8Iw7X1y1zOMzDmyuvP2g=
github.com/openzipkin/zipkin-go-opentracing v0.3.4/go.mod h1:js2AbwmHW0YD9DwIw2JhQWmbfFi/UnWyYwdVhqbCDOE=
github.com/paulmach/orb v0.7.1 h1:Zha++Z5OX/l168sqHK3k4z18LDvr+YAO/VjK0ReQ9rU=
github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
@@ -637,12 +630,8 @@ github.com/serialx/hashring v0.0.0-20180504054112-49a4782e9908 h1:RRpyb4kheanCQV
github.com/serialx/hashring v0.0.0-20180504054112-49a4782e9908/go.mod h1:/yeG0My1xr/u+HZrFQ1tOQQQQrOawfyMUH13ai5brBc=
github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk=
github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
@@ -675,7 +664,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/syncthing/syncthing v0.14.48-rc.4/go.mod h1:nw3siZwHPA6M8iSfjDCWQ402eqvEIasMQOE8nFOxy7M=
@@ -735,10 +723,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
@@ -885,7 +869,6 @@ golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1152,7 +1135,7 @@ yunion.io/x/pkg v1.0.1-0.20220630095420-9925accd7c5e h1:p/Cb7z6gyJbZKnGMeOPL3pTm
yunion.io/x/pkg v1.0.1-0.20220630095420-9925accd7c5e/go.mod h1:nYOMflWG1wQC6301rGVglQva9bYbJxHThEJUxRAgMFc=
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e h1:v+EzIadodSwkdZ/7bremd7J8J50Cise/HCylsOJngmo=
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
yunion.io/x/sqlchemy v1.1.2-0.20220701050442-4a04411dc606 h1:pJxI4+XTxna3e+gcGfuX2KNEBPrTqFXGcFLhqiEPqzA=
yunion.io/x/sqlchemy v1.1.2-0.20220701050442-4a04411dc606/go.mod h1:OoWrAW7qeaJmLlgMtwwMrxV10+91HLngPq0cZFtj9ig=
yunion.io/x/sqlchemy v1.1.2-0.20220701113246-498f1b8c1941 h1:ZSopMA3JfDGhjGw8fBzvxJeu9vAkdwk1tstMmkLfm40=
yunion.io/x/sqlchemy v1.1.2-0.20220701113246-498f1b8c1941/go.mod h1:xXVfJLIxxTRTnPEaVIIoRZXqWyO8+takIOYhc+zO5KE=
yunion.io/x/structarg v0.0.0-20220312084958-9c6c79c7d1c6 h1:WuWXhY3DvhdRTzWCJ/kwt3Ss6KIq7+KqJwb+esvNGwU=
yunion.io/x/structarg v0.0.0-20220312084958-9c6c79c7d1c6/go.mod h1:EP6NSv2C0zzqBDTKumv8hPWLb3XvgMZDHQRfyuOrQng=
@@ -2,7 +2,6 @@
*.o
*.a
*.so
*.cap
# Folders
_obj
@@ -26,5 +25,4 @@ _testmain.go
*.prof
coverage.txt
.idea/**
dev/*
.idea/**
+20
View File
@@ -0,0 +1,20 @@
sudo: required
language: go
go:
- 1.15.x
- 1.16.x
go_import_path: github.com/ClickHouse/clickhouse-go
services:
- docker
install:
- export GO111MODULE="on"
- go mod vendor
before_install:
- docker --version
- docker-compose --version
- docker-compose up -d
script:
- ./go.test.sh
after_success:
- bash <(curl -s https://codecov.io/bash)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-2020 Kirill Shvakov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+8
View File
@@ -0,0 +1,8 @@
test:
go install -race -v
go test -i -v
go test -race -timeout 30s -v .
coverage:
go test -coverprofile=coverage.out -v .
go tool cover -html=coverage.out
+307
View File
@@ -0,0 +1,307 @@
# ClickHouse [![Build Status](https://travis-ci.org/ClickHouse/clickhouse-go.svg?branch=master)](https://travis-ci.org/ClickHouse/clickhouse-go) [![Go Report Card](https://goreportcard.com/badge/github.com/ClickHouse/clickhouse-go)](https://goreportcard.com/report/github.com/ClickHouse/clickhouse-go) [![codecov](https://codecov.io/gh/ClickHouse/clickhouse-go/branch/master/graph/badge.svg)](https://codecov.io/gh/ClickHouse/clickhouse-go)
Golang SQL database driver for [Yandex ClickHouse](https://clickhouse.yandex/)
## Key features
* Uses native ClickHouse TCP client-server protocol
* Compatibility with `database/sql`
* Round Robin load-balancing
* Bulk write support : `begin->prepare->(in loop exec)->commit`
* LZ4 compression support (default is pure go lz4 or switch to use cgo lz4 by turning clz4 build tags on)
* External Tables support
## DSN
* username/password - auth credentials
* database - select the current default database
* read_timeout/write_timeout - timeout in second
* no_delay - disable/enable the Nagle Algorithm for tcp socket (default is 'true' - disable)
* alt_hosts - comma-separated list of single address hosts for load-balancing
* connection_open_strategy - random/in_order (default random).
* random - choose a random server from the set
* in_order - first live server is chosen in specified order
* time_random - choose random (based on the current time) server from the set. This option differs from `random` because randomness is based on the current time rather than on the number of previous connections.
* block_size - maximum rows in block (default is 1000000). If the rows are larger, the data will be split into several blocks to send to the server. If one block was sent to the server, the data would be persisted on the server disk, and we can't roll back the transaction. So always keep in mind that the batch size is no larger than the block_size if you want an atomic batch insert.
* pool_size - the maximum amount of preallocated byte chunks used in queries (default is 100). Decrease this if you experience memory problems at the expense of more GC pressure and vice versa.
* debug - enable debug output (boolean value)
* compress - enable lz4 compression (integer value, default is '0')
* check_connection_liveness - on supported platforms non-secure connections retrieved from the connection pool are checked in beginTx() for liveness before using them. If the check fails, the respective connection is marked as bad and the query retried with another connection. (boolean value, default is 'true')
SSL/TLS parameters:
* secure - establish secure connection (default is false)
* skip_verify - skip certificate verification (default is false)
* tls_config - name of a TLS config with client certificates, registered using `clickhouse.RegisterTLSConfig()`; implies secure to be true, unless explicitly specified
Example:
```sh
tcp://host1:9000?username=user&password=qwerty&database=clicks&read_timeout=10&write_timeout=20&alt_hosts=host2:9000,host3:9000
```
## Supported data types
* UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64
* Float32, Float64
* String
* FixedString(N)
* Date
* DateTime
* IPv4
* IPv6
* Enum
* UUID
* Nullable(T)
* [Array(T)](https://clickhouse.yandex/reference_en.html#Array(T)) [godoc](https://godoc.org/github.com/ClickHouse/clickhouse-go#Array)
* Array(Nullable(T))
* Tuple(...T)
## TODO
* Support other compression methods(zstd ...)
## Install
```sh
go get -u github.com/ClickHouse/clickhouse-go
```
## Examples
```go
package main
import (
"database/sql"
"fmt"
"log"
"time"
"github.com/ClickHouse/clickhouse-go"
)
func main() {
connect, err := sql.Open("clickhouse", "tcp://127.0.0.1:9000?debug=true")
if err != nil {
log.Fatal(err)
}
if err := connect.Ping(); err != nil {
if exception, ok := err.(*clickhouse.Exception); ok {
fmt.Printf("[%d] %s \n%s\n", exception.Code, exception.Message, exception.StackTrace)
} else {
fmt.Println(err)
}
return
}
_, err = connect.Exec(`
CREATE TABLE IF NOT EXISTS example (
country_code FixedString(2),
os_id UInt8,
browser_id UInt8,
categories Array(Int16),
action_day Date,
action_time DateTime
) engine=Memory
`)
if err != nil {
log.Fatal(err)
}
var (
tx, _ = connect.Begin()
stmt, _ = tx.Prepare("INSERT INTO example (country_code, os_id, browser_id, categories, action_day, action_time) VALUES (?, ?, ?, ?, ?, ?)")
)
defer stmt.Close()
for i := 0; i < 100; i++ {
if _, err := stmt.Exec(
"RU",
10+i,
100+i,
clickhouse.Array([]int16{1, 2, 3}),
time.Now(),
time.Now(),
); err != nil {
log.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
log.Fatal(err)
}
rows, err := connect.Query("SELECT country_code, os_id, browser_id, categories, action_day, action_time FROM example")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var (
country string
os, browser uint8
categories []int16
actionDay, actionTime time.Time
)
if err := rows.Scan(&country, &os, &browser, &categories, &actionDay, &actionTime); err != nil {
log.Fatal(err)
}
log.Printf("country: %s, os: %d, browser: %d, categories: %v, action_day: %s, action_time: %s", country, os, browser, categories, actionDay, actionTime)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
if _, err := connect.Exec("DROP TABLE example"); err != nil {
log.Fatal(err)
}
}
```
### Use [sqlx](https://github.com/jmoiron/sqlx)
```go
package main
import (
"log"
"time"
"github.com/jmoiron/sqlx"
_ "github.com/ClickHouse/clickhouse-go"
)
func main() {
connect, err := sqlx.Open("clickhouse", "tcp://127.0.0.1:9000?debug=true")
if err != nil {
log.Fatal(err)
}
var items []struct {
CountryCode string `db:"country_code"`
OsID uint8 `db:"os_id"`
BrowserID uint8 `db:"browser_id"`
Categories []int16 `db:"categories"`
ActionTime time.Time `db:"action_time"`
}
if err := connect.Select(&items, "SELECT country_code, os_id, browser_id, categories, action_time FROM example"); err != nil {
log.Fatal(err)
}
for _, item := range items {
log.Printf("country: %s, os: %d, browser: %d, categories: %v, action_time: %s", item.CountryCode, item.OsID, item.BrowserID, item.Categories, item.ActionTime)
}
}
```
### External tables support
```go
package main
import (
"database/sql"
"database/sql/driver"
"fmt"
"github.com/ClickHouse/clickhouse-go/lib/column"
"log"
"time"
"github.com/ClickHouse/clickhouse-go"
)
func main() {
connect, err := sql.Open("clickhouse", "tcp://127.0.0.1:9000?debug=true")
if err != nil {
log.Fatal(err)
}
if err := connect.Ping(); err != nil {
if exception, ok := err.(*clickhouse.Exception); ok {
fmt.Printf("[%d] %s \n%s\n", exception.Code, exception.Message, exception.StackTrace)
} else {
fmt.Println(err)
}
return
}
_, err = connect.Exec(`
CREATE TABLE IF NOT EXISTS example (
country_code FixedString(2),
os_id UInt8,
browser_id UInt8,
categories Array(Int16),
action_day Date,
action_time DateTime
) engine=Memory
`)
if err != nil {
log.Fatal(err)
}
var (
tx, _ = connect.Begin()
stmt, _ = tx.Prepare("INSERT INTO example (country_code, os_id, browser_id, categories, action_day, action_time) VALUES (?, ?, ?, ?, ?, ?)")
)
defer stmt.Close()
for i := 0; i < 100; i++ {
if _, err := stmt.Exec(
"RU",
10+i,
100+i,
clickhouse.Array([]int16{1, 2, 3}),
time.Now(),
time.Now(),
); err != nil {
log.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
log.Fatal(err)
}
col, err := column.Factory("country_code", "String", nil)
if err != nil {
log.Fatal(err)
}
countriesExternalTable := clickhouse.ExternalTable{
Name: "countries",
Values: [][]driver.Value{
{"RU"},
},
Columns: []column.Column{col},
}
rows, err := connect.Query("SELECT country_code, os_id, browser_id, categories, action_day, action_time "+
"FROM example WHERE country_code IN ?", countriesExternalTable)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var (
country string
os, browser uint8
categories []int16
actionDay, actionTime time.Time
)
if err := rows.Scan(&country, &os, &browser, &categories, &actionDay, &actionTime); err != nil {
log.Fatal(err)
}
log.Printf("country: %s, os: %d, browser: %d, categories: %v, action_day: %s, action_time: %s", country, os, browser, categories, actionDay, actionTime)
}
if err := rows.Err(); err != nil {
log.Fatal(err)
}
if _, err := connect.Exec("DROP TABLE example"); err != nil {
log.Fatal(err)
}
}
```
+21
View File
@@ -0,0 +1,21 @@
package clickhouse
import (
"time"
)
func Array(v interface{}) interface{} {
return v
}
func ArrayFixedString(len int, v interface{}) interface{} {
return v
}
func ArrayDate(v []time.Time) interface{} {
return v
}
func ArrayDateTime(v []time.Time) interface{} {
return v
}
+255
View File
@@ -0,0 +1,255 @@
package clickhouse
import (
"bufio"
"database/sql"
"database/sql/driver"
"fmt"
"io"
"log"
"net/url"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
"github.com/ClickHouse/clickhouse-go/lib/data"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
)
const (
// DefaultDatabase when connecting to ClickHouse
DefaultDatabase = "default"
// DefaultUsername when connecting to ClickHouse
DefaultUsername = "default"
// DefaultConnTimeout when connecting to ClickHouse
DefaultConnTimeout = 5 * time.Second
// DefaultReadTimeout when reading query results
DefaultReadTimeout = time.Minute
// DefaultWriteTimeout when sending queries
DefaultWriteTimeout = time.Minute
)
var (
unixtime int64
logOutput io.Writer = os.Stdout
hostname, _ = os.Hostname()
poolInit sync.Once
)
func init() {
sql.Register("clickhouse", &bootstrap{})
go func() {
for tick := time.Tick(time.Second); ; {
select {
case <-tick:
atomic.AddInt64(&unixtime, int64(time.Second))
}
}
}()
}
func now() time.Time {
return time.Unix(0, atomic.LoadInt64(&unixtime))
}
type bootstrap struct{}
func (d *bootstrap) Open(dsn string) (driver.Conn, error) {
return Open(dsn)
}
// SetLogOutput allows to change output of the default logger
func SetLogOutput(output io.Writer) {
logOutput = output
}
// Open the connection
func Open(dsn string) (driver.Conn, error) {
clickhouse, err := open(dsn)
if err != nil {
return nil, err
}
return clickhouse, err
}
func open(dsn string) (*clickhouse, error) {
url, err := url.Parse(dsn)
if err != nil {
return nil, err
}
var (
hosts = []string{url.Host}
query = url.Query()
secure = false
skipVerify = false
tlsConfigName = query.Get("tls_config")
noDelay = true
compress = false
database = query.Get("database")
username = query.Get("username")
password = query.Get("password")
blockSize = 1000000
connTimeout = DefaultConnTimeout
readTimeout = DefaultReadTimeout
writeTimeout = DefaultWriteTimeout
connOpenStrategy = connOpenRandom
checkConnLiveness = true
)
if len(database) == 0 {
database = DefaultDatabase
}
if len(username) == 0 {
username = DefaultUsername
}
if v, err := strconv.ParseBool(query.Get("no_delay")); err == nil {
noDelay = v
}
tlsConfig := getTLSConfigClone(tlsConfigName)
if tlsConfigName != "" && tlsConfig == nil {
return nil, fmt.Errorf("invalid tls_config - no config registered under name %s", tlsConfigName)
}
secure = tlsConfig != nil
if v, err := strconv.ParseBool(query.Get("secure")); err == nil {
secure = v
}
if v, err := strconv.ParseBool(query.Get("skip_verify")); err == nil {
skipVerify = v
}
if duration, err := strconv.ParseFloat(query.Get("timeout"), 64); err == nil {
connTimeout = time.Duration(duration * float64(time.Second))
}
if duration, err := strconv.ParseFloat(query.Get("read_timeout"), 64); err == nil {
readTimeout = time.Duration(duration * float64(time.Second))
}
if duration, err := strconv.ParseFloat(query.Get("write_timeout"), 64); err == nil {
writeTimeout = time.Duration(duration * float64(time.Second))
}
if size, err := strconv.ParseInt(query.Get("block_size"), 10, 64); err == nil {
blockSize = int(size)
}
if altHosts := strings.Split(query.Get("alt_hosts"), ","); len(altHosts) != 0 {
for _, host := range altHosts {
if len(host) != 0 {
hosts = append(hosts, host)
}
}
}
switch query.Get("connection_open_strategy") {
case "random":
connOpenStrategy = connOpenRandom
case "in_order":
connOpenStrategy = connOpenInOrder
case "time_random":
connOpenStrategy = connOpenTimeRandom
}
settings, err := makeQuerySettings(query)
if err != nil {
return nil, err
}
if v, err := strconv.ParseBool(query.Get("compress")); err == nil {
compress = v
}
if v, err := strconv.ParseBool(query.Get("check_connection_liveness")); err == nil {
checkConnLiveness = v
}
if secure {
// There is no way to check the liveness of a secure connection, as long as there is no access to raw TCP net.Conn
checkConnLiveness = false
}
var (
ch = clickhouse{
logf: func(string, ...interface{}) {},
settings: settings,
compress: compress,
blockSize: blockSize,
checkConnLiveness: checkConnLiveness,
ServerInfo: data.ServerInfo{
Timezone: time.Local,
},
}
logger = log.New(logOutput, "[clickhouse]", 0)
)
if debug, err := strconv.ParseBool(url.Query().Get("debug")); err == nil && debug {
ch.logf = logger.Printf
}
ch.logf("host(s)=%s, database=%s, username=%s",
strings.Join(hosts, ", "),
database,
username,
)
options := connOptions{
secure: secure,
tlsConfig: tlsConfig,
skipVerify: skipVerify,
hosts: hosts,
connTimeout: connTimeout,
readTimeout: readTimeout,
writeTimeout: writeTimeout,
noDelay: noDelay,
openStrategy: connOpenStrategy,
logf: ch.logf,
}
if ch.conn, err = dial(options); err != nil {
return nil, err
}
logger.SetPrefix(fmt.Sprintf("[clickhouse][connect=%d]", ch.conn.ident))
ch.buffer = bufio.NewWriter(ch.conn)
ch.decoder = binary.NewDecoderWithCompress(ch.conn)
ch.encoder = binary.NewEncoderWithCompress(ch.buffer)
if err := ch.hello(database, username, password); err != nil {
ch.conn.Close()
return nil, err
}
return &ch, nil
}
func (ch *clickhouse) hello(database, username, password string) error {
ch.logf("[hello] -> %s", ch.ClientInfo)
{
ch.encoder.Uvarint(protocol.ClientHello)
if err := ch.ClientInfo.Write(ch.encoder); err != nil {
return err
}
{
ch.encoder.String(database)
ch.encoder.String(username)
ch.encoder.String(password)
}
if err := ch.encoder.Flush(); err != nil {
return err
}
}
{
packet, err := ch.decoder.Uvarint()
if err != nil {
return err
}
switch packet {
case protocol.ServerException:
return ch.exception()
case protocol.ServerHello:
if err := ch.ServerInfo.Read(ch.decoder); err != nil {
return err
}
case protocol.ServerEndOfStream:
ch.logf("[bootstrap] <- end of stream")
return nil
default:
return fmt.Errorf("[hello] unexpected packet [%d] from server", packet)
}
}
ch.logf("[hello] <- %s", ch.ServerInfo)
return nil
}
+354
View File
@@ -0,0 +1,354 @@
package clickhouse
import (
"bufio"
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"net"
"reflect"
"regexp"
"sync"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
"github.com/ClickHouse/clickhouse-go/lib/column"
"github.com/ClickHouse/clickhouse-go/lib/data"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
"github.com/ClickHouse/clickhouse-go/lib/types"
)
type (
Date = types.Date
DateTime = types.DateTime
UUID = types.UUID
)
type ExternalTable struct {
Name string
Values [][]driver.Value
Columns []column.Column
}
var (
ErrInsertInNotBatchMode = errors.New("insert statement supported only in the batch mode (use begin/commit)")
ErrLimitDataRequestInTx = errors.New("data request has already been prepared in transaction")
)
var (
splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(`)
)
type logger func(format string, v ...interface{})
type clickhouse struct {
sync.Mutex
data.ServerInfo
data.ClientInfo
logf logger
conn *connect
block *data.Block
buffer *bufio.Writer
decoder *binary.Decoder
encoder *binary.Encoder
settings *querySettings
compress bool
blockSize int
inTransaction bool
checkConnLiveness bool
}
func (ch *clickhouse) Prepare(query string) (driver.Stmt, error) {
return ch.prepareContext(context.Background(), query)
}
func (ch *clickhouse) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
return ch.prepareContext(ctx, query)
}
func (ch *clickhouse) prepareContext(ctx context.Context, query string) (driver.Stmt, error) {
ch.logf("[prepare] %s", query)
switch {
case ch.conn.closed:
return nil, driver.ErrBadConn
case ch.block != nil:
return nil, ErrLimitDataRequestInTx
case isInsert(query):
if !ch.inTransaction {
return nil, ErrInsertInNotBatchMode
}
return ch.insert(ctx, query)
}
return &stmt{
ch: ch,
query: query,
numInput: numInput(query),
}, nil
}
func (ch *clickhouse) insert(ctx context.Context, query string) (_ driver.Stmt, err error) {
if err := ch.sendQuery(ctx, splitInsertRe.Split(query, -1)[0]+" VALUES ", nil); err != nil {
return nil, err
}
if ch.block, err = ch.readMeta(); err != nil {
return nil, err
}
return &stmt{
ch: ch,
isInsert: true,
}, nil
}
func (ch *clickhouse) Begin() (driver.Tx, error) {
return ch.beginTx(context.Background(), txOptions{})
}
func (ch *clickhouse) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
return ch.beginTx(ctx, txOptions{
Isolation: int(opts.Isolation),
ReadOnly: opts.ReadOnly,
})
}
type txOptions struct {
Isolation int
ReadOnly bool
}
func (ch *clickhouse) beginTx(ctx context.Context, opts txOptions) (*clickhouse, error) {
ch.logf("[begin] tx=%t, data=%t", ch.inTransaction, ch.block != nil)
switch {
case ch.inTransaction:
return nil, sql.ErrTxDone
case ch.conn.closed:
return nil, driver.ErrBadConn
}
// Perform a stale connection check. We only perform this check in beginTx,
// because database/sql retries driver.ErrBadConn only for first request,
// but beginTx doesn't perform any other network interaction.
if ch.checkConnLiveness {
if err := ch.conn.connCheck(); err != nil {
ch.logf("[begin] closing bad idle connection: %w", err)
ch.Close()
return ch, driver.ErrBadConn
}
}
if finish := ch.watchCancel(ctx); finish != nil {
defer finish()
}
ch.block = nil
ch.inTransaction = true
return ch, nil
}
func (ch *clickhouse) Commit() error {
ch.logf("[commit] tx=%t, data=%t", ch.inTransaction, ch.block != nil)
defer func() {
if ch.block != nil {
ch.block.Reset()
ch.block = nil
}
ch.inTransaction = false
}()
switch {
case !ch.inTransaction:
return sql.ErrTxDone
case ch.conn.closed:
return driver.ErrBadConn
}
if ch.block != nil {
if err := ch.writeBlock(ch.block, ""); err != nil {
return err
}
// Send empty block as marker of end of data.
if err := ch.writeBlock(&data.Block{}, ""); err != nil {
return err
}
if err := ch.encoder.Flush(); err != nil {
return err
}
return ch.process()
}
return nil
}
func (ch *clickhouse) Rollback() error {
ch.logf("[rollback] tx=%t, data=%t", ch.inTransaction, ch.block != nil)
if !ch.inTransaction {
return sql.ErrTxDone
}
if ch.block != nil {
ch.block.Reset()
}
ch.block = nil
ch.buffer = nil
ch.inTransaction = false
return ch.conn.Close()
}
func (ch *clickhouse) CheckNamedValue(nv *driver.NamedValue) error {
switch nv.Value.(type) {
case ExternalTable, column.IP, column.UUID:
return nil
case nil, []byte, int8, int16, int32, int64, uint8, uint16, uint32, uint64, float32, float64, string, time.Time:
return nil
}
switch v := nv.Value.(type) {
case
[]int, []int8, []int16, []int32, []int64,
[]uint, []uint8, []uint16, []uint32, []uint64,
[]float32, []float64,
[]string:
return nil
case net.IP, *net.IP:
return nil
case driver.Valuer:
value, err := v.Value()
if err != nil {
return err
}
nv.Value = value
default:
switch value := reflect.ValueOf(nv.Value); value.Kind() {
case reflect.Slice:
return nil
case reflect.Bool:
nv.Value = uint8(0)
if value.Bool() {
nv.Value = uint8(1)
}
case reflect.Int8:
nv.Value = int8(value.Int())
case reflect.Int16:
nv.Value = int16(value.Int())
case reflect.Int32:
nv.Value = int32(value.Int())
case reflect.Int64:
nv.Value = value.Int()
case reflect.Uint8:
nv.Value = uint8(value.Uint())
case reflect.Uint16:
nv.Value = uint16(value.Uint())
case reflect.Uint32:
nv.Value = uint32(value.Uint())
case reflect.Uint64:
nv.Value = uint64(value.Uint())
case reflect.Float32:
nv.Value = float32(value.Float())
case reflect.Float64:
nv.Value = float64(value.Float())
case reflect.String:
nv.Value = value.String()
}
}
return nil
}
func (ch *clickhouse) Close() error {
ch.block = nil
return ch.conn.Close()
}
func (ch *clickhouse) process() error {
packet, err := ch.decoder.Uvarint()
if err != nil {
return err
}
for {
switch packet {
case protocol.ServerPong:
ch.logf("[process] <- pong")
return nil
case protocol.ServerException:
ch.logf("[process] <- exception")
return ch.exception()
case protocol.ServerProgress:
progress, err := ch.progress()
if err != nil {
return err
}
ch.logf("[process] <- progress: rows=%d, bytes=%d, total rows=%d",
progress.rows,
progress.bytes,
progress.totalRows,
)
case protocol.ServerProfileInfo:
profileInfo, err := ch.profileInfo()
if err != nil {
return err
}
ch.logf("[process] <- profiling: rows=%d, bytes=%d, blocks=%d", profileInfo.rows, profileInfo.bytes, profileInfo.blocks)
case protocol.ServerData:
block, err := ch.readBlock()
if err != nil {
return err
}
ch.logf("[process] <- data: packet=%d, columns=%d, rows=%d", packet, block.NumColumns, block.NumRows)
case protocol.ServerEndOfStream:
ch.logf("[process] <- end of stream")
return nil
default:
ch.conn.Close()
return fmt.Errorf("[process] unexpected packet [%d] from server", packet)
}
if packet, err = ch.decoder.Uvarint(); err != nil {
return err
}
}
}
func (ch *clickhouse) cancel() error {
ch.logf("[cancel request]")
// even if we fail to write the cancel, we still need to close
err := ch.encoder.Uvarint(protocol.ClientCancel)
if err == nil {
err = ch.encoder.Flush()
}
// return the close error if there was one, otherwise return the write error
if cerr := ch.conn.Close(); cerr != nil {
return cerr
}
return err
}
func (ch *clickhouse) watchCancel(ctx context.Context) func() {
if done := ctx.Done(); done != nil {
finished := make(chan struct{})
go func() {
select {
case <-done:
ch.cancel()
finished <- struct{}{}
ch.logf("[cancel] <- done")
case <-finished:
ch.logf("[cancel] <- finished")
}
}()
return func() {
select {
case <-finished:
case finished <- struct{}{}:
}
}
}
return func() {}
}
func (ch *clickhouse) ExecContext(ctx context.Context, query string,
args []driver.NamedValue) (driver.Result, error) {
finish := ch.watchCancel(ctx)
defer finish()
stmt, err := ch.PrepareContext(ctx, query)
if err != nil {
return nil, err
}
dargs := make([]driver.Value, len(args))
for i, nv := range args {
dargs[i] = nv.Value
}
return stmt.Exec(dargs)
}
+46
View File
@@ -0,0 +1,46 @@
package clickhouse
import (
"fmt"
"strings"
)
type Exception struct {
Code int32
Name string
Message string
StackTrace string
nested error
}
func (e *Exception) Error() string {
return fmt.Sprintf("code: %d, message: %s", e.Code, e.Message)
}
func (ch *clickhouse) exception() error {
var (
e Exception
err error
hasNested bool
)
if e.Code, err = ch.decoder.Int32(); err != nil {
return err
}
if e.Name, err = ch.decoder.String(); err != nil {
return err
}
if e.Message, err = ch.decoder.String(); err != nil {
return err
}
e.Message = strings.TrimSpace(strings.TrimPrefix(e.Message, e.Name+":"))
if e.StackTrace, err = ch.decoder.String(); err != nil {
return err
}
if hasNested, err = ch.decoder.Bool(); err != nil {
return err
}
if hasNested {
e.nested = ch.exception()
}
return &e
}
+28
View File
@@ -0,0 +1,28 @@
package clickhouse
import (
"context"
"database/sql/driver"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
)
func (ch *clickhouse) Ping(ctx context.Context) error {
return ch.ping(ctx)
}
func (ch *clickhouse) ping(ctx context.Context) error {
if ch.conn.closed {
return driver.ErrBadConn
}
ch.logf("-> ping")
finish := ch.watchCancel(ctx)
defer finish()
if err := ch.encoder.Uvarint(protocol.ClientPing); err != nil {
return err
}
if err := ch.encoder.Flush(); err != nil {
return err
}
return ch.process()
}
+37
View File
@@ -0,0 +1,37 @@
package clickhouse
type profileInfo struct {
rows uint64
bytes uint64
blocks uint64
appliedLimit bool
rowsBeforeLimit uint64
calculatedRowsBeforeLimit bool
}
func (ch *clickhouse) profileInfo() (*profileInfo, error) {
var (
p profileInfo
err error
)
if p.rows, err = ch.decoder.Uvarint(); err != nil {
return nil, err
}
if p.blocks, err = ch.decoder.Uvarint(); err != nil {
return nil, err
}
if p.bytes, err = ch.decoder.Uvarint(); err != nil {
return nil, err
}
if p.appliedLimit, err = ch.decoder.Bool(); err != nil {
return nil, err
}
if p.rowsBeforeLimit, err = ch.decoder.Uvarint(); err != nil {
return nil, err
}
if p.calculatedRowsBeforeLimit, err = ch.decoder.Bool(); err != nil {
return nil, err
}
return &p, nil
}
+26
View File
@@ -0,0 +1,26 @@
package clickhouse
type progress struct {
rows uint64
bytes uint64
totalRows uint64
}
func (ch *clickhouse) progress() (*progress, error) {
var (
p progress
err error
)
if p.rows, err = ch.decoder.Uvarint(); err != nil {
return nil, err
}
if p.bytes, err = ch.decoder.Uvarint(); err != nil {
return nil, err
}
if p.totalRows, err = ch.decoder.Uvarint(); err != nil {
return nil, err
}
return &p, nil
}
+19
View File
@@ -0,0 +1,19 @@
package clickhouse
import (
"github.com/ClickHouse/clickhouse-go/lib/data"
)
func (ch *clickhouse) readBlock() (*data.Block, error) {
if _, err := ch.decoder.String(); err != nil { // temporary table
return nil, err
}
ch.decoder.SelectCompress(ch.compress)
var block data.Block
if err := block.Read(&ch.ServerInfo, ch.decoder); err != nil {
return nil, err
}
ch.decoder.SelectCompress(false)
return &block, nil
}
+53
View File
@@ -0,0 +1,53 @@
package clickhouse
import (
"fmt"
"github.com/ClickHouse/clickhouse-go/lib/data"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
)
func (ch *clickhouse) readMeta() (*data.Block, error) {
for {
packet, err := ch.decoder.Uvarint()
if err != nil {
return nil, err
}
switch packet {
case protocol.ServerException:
ch.logf("[read meta] <- exception")
return nil, ch.exception()
case protocol.ServerProgress:
progress, err := ch.progress()
if err != nil {
return nil, err
}
ch.logf("[read meta] <- progress: rows=%d, bytes=%d, total rows=%d",
progress.rows,
progress.bytes,
progress.totalRows,
)
case protocol.ServerProfileInfo:
profileInfo, err := ch.profileInfo()
if err != nil {
return nil, err
}
ch.logf("[read meta] <- profiling: rows=%d, bytes=%d, blocks=%d", profileInfo.rows, profileInfo.bytes, profileInfo.blocks)
case protocol.ServerData:
block, err := ch.readBlock()
if err != nil {
return nil, err
}
ch.logf("[read meta] <- data: packet=%d, columns=%d, rows=%d", packet, block.NumColumns, block.NumRows)
return block, nil
case protocol.ServerEndOfStream:
_, err := ch.readBlock()
ch.logf("[process] <- end of stream")
return nil, err
default:
ch.conn.Close()
return nil, fmt.Errorf("[read meta] unexpected packet [%d] from server", packet)
}
}
}
@@ -0,0 +1,35 @@
package clickhouse
import "github.com/ClickHouse/clickhouse-go/lib/data"
func (ch *clickhouse) sendExternalTables(externalTables []ExternalTable) error {
ch.logf("[send external tables] count %d", len(externalTables))
if externalTables == nil || len(externalTables) == 0 {
return nil
}
block := &data.Block{}
sentTables := make(map[string]bool, 0)
for _, externalTable := range externalTables {
if _, ok := sentTables[externalTable.Name]; ok {
continue
}
ch.logf("[send external table] name %s", externalTable.Name)
sentTables[externalTable.Name] = true
block.Columns = externalTable.Columns
block.NumColumns = uint64(len(externalTable.Columns))
for _, row := range externalTable.Values {
err := block.AppendRow(row)
if err != nil {
return err
}
}
if err := ch.writeBlock(block, externalTable.Name); err != nil {
return err
}
if err := ch.encoder.Flush(); err != nil {
return err
}
block.Reset()
}
return nil
}
+71
View File
@@ -0,0 +1,71 @@
package clickhouse
import (
"context"
"github.com/ClickHouse/clickhouse-go/lib/data"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
)
func (ch *clickhouse) sendQuery(ctx context.Context, query string, externalTables []ExternalTable) error {
ch.logf("[send query] %s", query)
if err := ch.encoder.Uvarint(protocol.ClientQuery); err != nil {
return err
}
var queryID string
queryIDValue := ctx.Value(queryIDKey)
if queryIDValue != nil {
if queryIdStr, ok := queryIDValue.(string); ok {
queryID = queryIdStr
}
}
if err := ch.encoder.String(queryID); err != nil {
return err
}
{ // client info
ch.encoder.Uvarint(1)
ch.encoder.String("")
ch.encoder.String("")
ch.encoder.String("[::ffff:127.0.0.1]:0")
ch.encoder.Uvarint(1) // iface type TCP
ch.encoder.String(hostname)
ch.encoder.String(hostname)
}
if err := ch.ClientInfo.Write(ch.encoder); err != nil {
return err
}
if ch.ServerInfo.Revision >= protocol.DBMS_MIN_REVISION_WITH_QUOTA_KEY_IN_CLIENT_INFO {
ch.encoder.String("")
}
// the settings are written as list of contiguous name-value pairs, finished with empty name
if !ch.settings.IsEmpty() {
ch.logf("[query settings] %s", ch.settings.settingsStr)
if err := ch.settings.Serialize(ch.encoder); err != nil {
return err
}
}
// empty string is a marker of the end of the settings
if err := ch.encoder.String(""); err != nil {
return err
}
if err := ch.encoder.Uvarint(protocol.StateComplete); err != nil {
return err
}
compress := protocol.CompressDisable
if ch.compress {
compress = protocol.CompressEnable
}
if err := ch.encoder.Uvarint(compress); err != nil {
return err
}
if err := ch.encoder.String(query); err != nil {
return err
}
if err := ch.sendExternalTables(externalTables); err != nil {
return err
}
if err := ch.writeBlock(&data.Block{}, ""); err != nil {
return err
}
return ch.encoder.Flush()
}
+40
View File
@@ -0,0 +1,40 @@
package clickhouse
import (
"github.com/ClickHouse/clickhouse-go/lib/data"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
)
func (ch *clickhouse) writeBlock(block *data.Block, tableName string) error {
ch.Lock()
defer ch.Unlock()
if err := ch.encoder.Uvarint(protocol.ClientData); err != nil {
return err
}
if err := ch.encoder.String(tableName); err != nil { // temporary table
return err
}
// implement CityHash v 1.0.2 and add LZ4 compression
/*
From Alexey Milovidov
Насколько я помню, сжимаются блоки с данными Native формата, а всё остальное (всякие номера пакетов и т. п.) передаётся без сжатия.
Сжатые данные устроены так. Они представляют собой набор сжатых фреймов.
Каждый фрейм имеет следующий вид:
чексумма (16 байт),
идентификатор алгоритма сжатия (1 байт),
размер сжатых данных (4 байта, little endian, размер не включает в себя чексумму, но включает в себя остальные 9 байт заголовка),
размер несжатых данных (4 байта, little endian), затем сжатые данные.
Идентификатор алгоритма: 0x82 - lz4, 0x90 - zstd.
Чексумма - CityHash128 из CityHash версии 1.0.2, вычисленный от сжатых данных с учётом 9 байт заголовка.
См. CompressedReadBufferBase, CompressedWriteBuffer,
utils/compressor, TCPHandler.
*/
ch.encoder.SelectCompress(ch.compress)
err := block.Write(&ch.ServerInfo, ch.encoder)
ch.encoder.SelectCompress(false)
return err
}
+225
View File
@@ -0,0 +1,225 @@
package clickhouse
import (
"bufio"
"crypto/tls"
"database/sql/driver"
"net"
"sync"
"sync/atomic"
"time"
)
var tick int32
type openStrategy int8
func (s openStrategy) String() string {
switch s {
case connOpenInOrder:
return "in_order"
case connOpenTimeRandom:
return "time_random"
}
return "random"
}
const (
connOpenRandom openStrategy = iota + 1
connOpenInOrder
connOpenTimeRandom
)
type connOptions struct {
secure, skipVerify bool
tlsConfig *tls.Config
hosts []string
connTimeout, readTimeout, writeTimeout time.Duration
noDelay bool
openStrategy openStrategy
logf func(string, ...interface{})
}
// DialFunc is a function which can be used to establish the network connection.
// Custom dial functions must be registered with RegisterDial
type DialFunc func(network, address string, timeout time.Duration, config *tls.Config) (net.Conn, error)
var (
customDialLock sync.RWMutex
customDial DialFunc
)
// RegisterDial registers a custom dial function.
func RegisterDial(dial DialFunc) {
customDialLock.Lock()
customDial = dial
customDialLock.Unlock()
}
// DeregisterDial deregisters the custom dial function.
func DeregisterDial() {
customDialLock.Lock()
customDial = nil
customDialLock.Unlock()
}
func dial(options connOptions) (*connect, error) {
var (
err error
abs = func(v int) int {
if v < 0 {
return -1 * v
}
return v
}
conn net.Conn
ident = abs(int(atomic.AddInt32(&tick, 1)))
)
tlsConfig := options.tlsConfig
if options.secure {
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
tlsConfig.InsecureSkipVerify = options.skipVerify
}
checkedHosts := make(map[int]struct{}, len(options.hosts))
for i := range options.hosts {
var num int
switch options.openStrategy {
case connOpenInOrder:
num = i
case connOpenRandom:
num = (ident + i) % len(options.hosts)
case connOpenTimeRandom:
// select host based on milliseconds
num = int((time.Now().UnixNano()/1000)%1000) % len(options.hosts)
for _, ok := checkedHosts[num]; ok; _, ok = checkedHosts[num] {
num = int(time.Now().UnixNano()) % len(options.hosts)
}
checkedHosts[num] = struct{}{}
}
customDialLock.RLock()
cd := customDial
customDialLock.RUnlock()
switch {
case options.secure:
if cd != nil {
conn, err = cd("tcp", options.hosts[num], options.connTimeout, tlsConfig)
} else {
conn, err = tls.DialWithDialer(
&net.Dialer{
Timeout: options.connTimeout,
},
"tcp",
options.hosts[num],
tlsConfig,
)
}
default:
if cd != nil {
conn, err = cd("tcp", options.hosts[num], options.connTimeout, nil)
} else {
conn, err = net.DialTimeout("tcp", options.hosts[num], options.connTimeout)
}
}
if err == nil {
options.logf(
"[dial] secure=%t, skip_verify=%t, strategy=%s, ident=%d, server=%d -> %s",
options.secure,
options.skipVerify,
options.openStrategy,
ident,
num,
conn.RemoteAddr(),
)
if tcp, ok := conn.(*net.TCPConn); ok {
err = tcp.SetNoDelay(options.noDelay) // Disable or enable the Nagle Algorithm for this tcp socket
if err != nil {
return nil, err
}
}
return &connect{
Conn: conn,
logf: options.logf,
ident: ident,
buffer: bufio.NewReader(conn),
readTimeout: options.readTimeout,
writeTimeout: options.writeTimeout,
}, nil
} else {
options.logf(
"[dial err] secure=%t, skip_verify=%t, strategy=%s, ident=%d, addr=%s\n%#v",
options.secure,
options.skipVerify,
options.openStrategy,
ident,
options.hosts[num],
err,
)
}
}
return nil, err
}
type connect struct {
net.Conn
logf func(string, ...interface{})
ident int
buffer *bufio.Reader
closed bool
readTimeout time.Duration
writeTimeout time.Duration
lastReadDeadlineTime time.Time
lastWriteDeadlineTime time.Time
}
func (conn *connect) Read(b []byte) (int, error) {
var (
n int
err error
total int
dstLen = len(b)
)
if currentTime := now(); conn.readTimeout != 0 && currentTime.Sub(conn.lastReadDeadlineTime) > (conn.readTimeout>>2) {
conn.SetReadDeadline(time.Now().Add(conn.readTimeout))
conn.lastReadDeadlineTime = currentTime
}
for total < dstLen {
if n, err = conn.buffer.Read(b[total:]); err != nil {
conn.logf("[connect] read error: %v", err)
conn.Close()
return n, driver.ErrBadConn
}
total += n
}
return total, nil
}
func (conn *connect) Write(b []byte) (int, error) {
var (
n int
err error
total int
srcLen = len(b)
)
if currentTime := now(); conn.writeTimeout != 0 && currentTime.Sub(conn.lastWriteDeadlineTime) > (conn.writeTimeout>>2) {
conn.SetWriteDeadline(time.Now().Add(conn.writeTimeout))
conn.lastWriteDeadlineTime = currentTime
}
for total < srcLen {
if n, err = conn.Conn.Write(b[total:]); err != nil {
conn.logf("[connect] write error: %v", err)
conn.Close()
return n, driver.ErrBadConn
}
total += n
}
return n, nil
}
func (conn *connect) Close() error {
if !conn.closed {
conn.closed = true
return conn.Conn.Close()
}
return nil
}
+57
View File
@@ -0,0 +1,57 @@
// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
package clickhouse
import (
"errors"
"fmt"
"io"
"syscall"
"time"
)
var errUnexpectedRead = errors.New("unexpected read from socket")
func (conn *connect) connCheck() error {
var sysErr error
sysConn, ok := conn.Conn.(syscall.Conn)
if !ok {
return nil
}
rawConn, err := sysConn.SyscallConn()
if err != nil {
return err
}
// If this connection has a ReadTimeout which we've been setting on
// reads, reset it to zero value before we attempt a non-blocking
// read, otherwise we may get os.ErrDeadlineExceeded for the cached
// connection from the pool with an expired timeout.
if conn.readTimeout != 0 {
err = conn.SetReadDeadline(time.Time{})
if err != nil {
return fmt.Errorf("set read deadline: %w", err)
}
conn.lastReadDeadlineTime = time.Time{}
}
err = rawConn.Read(func(fd uintptr) bool {
var buf [1]byte
n, err := syscall.Read(int(fd), buf[:])
switch {
case n == 0 && err == nil:
sysErr = io.EOF
case n > 0:
sysErr = errUnexpectedRead
case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
sysErr = nil
default:
sysErr = err
}
return true
})
if err != nil {
return err
}
return sysErr
}
+7
View File
@@ -0,0 +1,7 @@
// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
package clickhouse
func (conn *connect) connCheck() error {
return nil
}
+9
View File
@@ -0,0 +1,9 @@
---
version: '3'
services:
clickhouse:
image: yandex/clickhouse-server
ports:
- 127.0.0.1:8123:8123
- 127.0.0.1:9000:9000
- 127.0.0.1:9009:9009
+144
View File
@@ -0,0 +1,144 @@
package clickhouse
import (
"bytes"
"database/sql/driver"
"fmt"
"reflect"
"regexp"
"strings"
"time"
)
func numInput(query string) int {
var (
count int
args = make(map[string]struct{})
reader = bytes.NewReader([]byte(query))
quote, gravis bool
escape bool
keyword bool
inBetween bool
like = newMatcher("like")
limit = newMatcher("limit")
offset = newMatcher("offset")
between = newMatcher("between")
in = newMatcher("in")
and = newMatcher("and")
from = newMatcher("from")
join = newMatcher("join")
subSelect = newMatcher("select")
)
for {
if char, _, err := reader.ReadRune(); err == nil {
if escape {
escape = false
continue
}
switch char {
case '\\':
if gravis || quote {
escape = true
}
case '\'':
if !gravis {
quote = !quote
}
case '`':
if !quote {
gravis = !gravis
}
}
if quote || gravis {
continue
}
switch {
case char == '?' && keyword:
count++
case char == '@':
if param := paramParser(reader); len(param) != 0 {
if _, found := args[param]; !found {
args[param] = struct{}{}
count++
}
}
case
char == '=',
char == '<',
char == '>',
char == '(',
char == ',',
char == '[',
char == '%':
keyword = true
default:
if limit.matchRune(char) || offset.matchRune(char) || like.matchRune(char) ||
in.matchRune(char) || from.matchRune(char) || join.matchRune(char) || subSelect.matchRune(char) {
keyword = true
} else if between.matchRune(char) {
keyword = true
inBetween = true
} else if inBetween && and.matchRune(char) {
keyword = true
inBetween = false
} else {
keyword = keyword && (char == ' ' || char == '\t' || char == '\n')
}
}
} else {
break
}
}
return count
}
func paramParser(reader *bytes.Reader) string {
var name bytes.Buffer
for {
if char, _, err := reader.ReadRune(); err == nil {
if char == '_' || char >= '0' && char <= '9' || 'a' <= char && char <= 'z' || 'A' <= char && char <= 'Z' {
name.WriteRune(char)
} else {
reader.UnreadRune()
break
}
} else {
break
}
}
return name.String()
}
var selectRe = regexp.MustCompile(`\s+SELECT\s+`)
func isInsert(query string) bool {
if f := strings.Fields(query); len(f) > 2 {
return strings.EqualFold("INSERT", f[0]) && strings.EqualFold("INTO", f[1]) && !selectRe.MatchString(strings.ToUpper(query))
}
return false
}
func quote(v driver.Value) string {
switch v := reflect.ValueOf(v); v.Kind() {
case reflect.Slice:
values := make([]string, 0, v.Len())
for i := 0; i < v.Len(); i++ {
values = append(values, quote(v.Index(i).Interface()))
}
return strings.Join(values, ", ")
}
switch v := v.(type) {
case string:
return "'" + strings.NewReplacer(`\`, `\\`, `'`, `\'`).Replace(v) + "'"
case time.Time:
return formatTime(v)
case nil:
return "null"
}
return fmt.Sprint(v)
}
func formatTime(v time.Time) string {
return v.Format("toDateTime('2006-01-02 15:04:05', '" + v.Location().String() + "')")
}
@@ -0,0 +1,107 @@
// +build !clz4
package binary
import (
"encoding/binary"
"fmt"
"io"
"github.com/ClickHouse/clickhouse-go/lib/lz4"
)
type compressReader struct {
reader io.Reader
// data uncompressed
data []byte
// data position
pos int
// data compressed
zdata []byte
// lz4 headers
header []byte
}
// NewCompressReader wrap the io.Reader
func NewCompressReader(r io.Reader) *compressReader {
p := &compressReader{
reader: r,
header: make([]byte, HeaderSize),
}
p.data = make([]byte, BlockMaxSize, BlockMaxSize)
zlen := lz4.CompressBound(BlockMaxSize) + HeaderSize
p.zdata = make([]byte, zlen, zlen)
p.pos = len(p.data)
return p
}
func (cr *compressReader) Read(buf []byte) (n int, err error) {
var bytesRead = 0
n = len(buf)
if cr.pos < len(cr.data) {
copyedSize := copy(buf, cr.data[cr.pos:])
bytesRead += copyedSize
cr.pos += copyedSize
}
for bytesRead < n {
if err = cr.readCompressedData(); err != nil {
return bytesRead, err
}
copyedSize := copy(buf[bytesRead:], cr.data)
bytesRead += copyedSize
cr.pos = copyedSize
}
return n, nil
}
func (cr *compressReader) readCompressedData() (err error) {
cr.pos = 0
var n int
n, err = cr.reader.Read(cr.header)
if err != nil {
return
}
if n != len(cr.header) {
return fmt.Errorf("Lz4 decompression header EOF")
}
compressedSize := int(binary.LittleEndian.Uint32(cr.header[17:])) - 9
decompressedSize := int(binary.LittleEndian.Uint32(cr.header[21:]))
if compressedSize > cap(cr.zdata) {
cr.zdata = make([]byte, compressedSize)
}
if decompressedSize > cap(cr.data) {
cr.data = make([]byte, decompressedSize)
}
cr.zdata = cr.zdata[:compressedSize]
cr.data = cr.data[:decompressedSize]
// @TODO checksum
if cr.header[16] == LZ4 {
n, err = cr.reader.Read(cr.zdata)
if err != nil {
return
}
if n != len(cr.zdata) {
return fmt.Errorf("Decompress read size not match")
}
_, err = lz4.Decode(cr.data, cr.zdata)
if err != nil {
return
}
} else {
return fmt.Errorf("Unknown compression method: 0x%02x ", cr.header[16])
}
return nil
}
@@ -0,0 +1,107 @@
// +build clz4
package binary
import (
"encoding/binary"
"fmt"
"io"
lz4 "github.com/cloudflare/golz4"
)
type compressReader struct {
reader io.Reader
// data uncompressed
data []byte
// data position
pos int
// data compressed
zdata []byte
// lz4 headers
header []byte
}
// NewCompressReader wrap the io.Reader
func NewCompressReader(r io.Reader) *compressReader {
p := &compressReader{
reader: r,
header: make([]byte, HeaderSize),
}
p.data = make([]byte, BlockMaxSize, BlockMaxSize)
zlen := lz4.CompressBound(p.data) + HeaderSize
p.zdata = make([]byte, zlen, zlen)
p.pos = len(p.data)
return p
}
func (cr *compressReader) Read(buf []byte) (n int, err error) {
var bytesRead = 0
n = len(buf)
if cr.pos < len(cr.data) {
copyedSize := copy(buf, cr.data[cr.pos:])
bytesRead += copyedSize
cr.pos += copyedSize
}
for bytesRead < n {
if err = cr.readCompressedData(); err != nil {
return bytesRead, err
}
copyedSize := copy(buf[bytesRead:], cr.data)
bytesRead += copyedSize
cr.pos = copyedSize
}
return n, nil
}
func (cr *compressReader) readCompressedData() (err error) {
cr.pos = 0
var n int
n, err = cr.reader.Read(cr.header)
if err != nil {
return
}
if n != len(cr.header) {
return fmt.Errorf("Lz4 decompression header EOF")
}
compressedSize := int(binary.LittleEndian.Uint32(cr.header[17:])) - 9
decompressedSize := int(binary.LittleEndian.Uint32(cr.header[21:]))
if compressedSize > cap(cr.zdata) {
cr.zdata = make([]byte, compressedSize)
}
if decompressedSize > cap(cr.data) {
cr.data = make([]byte, decompressedSize)
}
cr.zdata = cr.zdata[:compressedSize]
cr.data = cr.data[:decompressedSize]
// @TODO checksum
if cr.header[16] == LZ4 {
n, err = cr.reader.Read(cr.zdata)
if err != nil {
return
}
if n != len(cr.zdata) {
return fmt.Errorf("Decompress read size not match")
}
err = lz4.Uncompress(cr.zdata, cr.data)
if err != nil {
return
}
} else {
return fmt.Errorf("Unknown compression method: 0x%02x ", cr.header[16])
}
return nil
}
@@ -0,0 +1,21 @@
package binary
type CompressionMethodByte byte
const (
NONE CompressionMethodByte = 0x02
LZ4 = 0x82
ZSTD = 0x90
)
const (
// ChecksumSize is 128bits for cityhash102 checksum
ChecksumSize = 16
// CompressHeader magic + compressed_size + uncompressed_size
CompressHeaderSize = 1 + 4 + 4
// HeaderSize
HeaderSize = ChecksumSize + CompressHeaderSize
// BlockMaxSize 1MB
BlockMaxSize = 1 << 20
)
@@ -0,0 +1,79 @@
// +build !clz4
package binary
import (
"encoding/binary"
"io"
"github.com/ClickHouse/clickhouse-go/lib/cityhash102"
"github.com/ClickHouse/clickhouse-go/lib/lz4"
)
type compressWriter struct {
writer io.Writer
// data uncompressed
data []byte
// data position
pos int
// data compressed
zdata []byte
}
// NewCompressWriter wrap the io.Writer
func NewCompressWriter(w io.Writer) *compressWriter {
p := &compressWriter{writer: w}
p.data = make([]byte, BlockMaxSize, BlockMaxSize)
zlen := lz4.CompressBound(BlockMaxSize) + HeaderSize
p.zdata = make([]byte, zlen, zlen)
return p
}
func (cw *compressWriter) Write(buf []byte) (int, error) {
var n int
for len(buf) > 0 {
// Accumulate the data to be compressed.
m := copy(cw.data[cw.pos:], buf)
cw.pos += m
buf = buf[m:]
if cw.pos == len(cw.data) {
err := cw.Flush()
if err != nil {
return n, err
}
}
n += m
}
return n, nil
}
func (cw *compressWriter) Flush() (err error) {
if cw.pos == 0 {
return
}
// write the headers
compressedSize, err := lz4.Encode(cw.zdata[HeaderSize:], cw.data[:cw.pos])
if err != nil {
return err
}
compressedSize += CompressHeaderSize
// fill the header, compressed_size_32 + uncompressed_size_32
cw.zdata[16] = LZ4
binary.LittleEndian.PutUint32(cw.zdata[17:], uint32(compressedSize))
binary.LittleEndian.PutUint32(cw.zdata[21:], uint32(cw.pos))
// fill the checksum
checkSum := cityhash102.CityHash128(cw.zdata[16:], uint32(compressedSize))
binary.LittleEndian.PutUint64(cw.zdata[0:], checkSum.Lower64())
binary.LittleEndian.PutUint64(cw.zdata[8:], checkSum.Higher64())
cw.writer.Write(cw.zdata[:compressedSize+ChecksumSize])
if w, ok := cw.writer.(WriteFlusher); ok {
err = w.Flush()
}
cw.pos = 0
return
}
@@ -0,0 +1,78 @@
// +build clz4
package binary
import (
"encoding/binary"
"io"
lz4 "github.com/cloudflare/golz4"
"github.com/ClickHouse/clickhouse-go/lib/cityhash102"
)
type compressWriter struct {
writer io.Writer
// data uncompressed
data []byte
// data position
pos int
// data compressed
zdata []byte
}
// NewCompressWriter wrap the io.Writer
func NewCompressWriter(w io.Writer) *compressWriter {
p := &compressWriter{writer: w}
p.data = make([]byte, BlockMaxSize, BlockMaxSize)
zlen := lz4.CompressBound(p.data) + HeaderSize
p.zdata = make([]byte, zlen, zlen)
return p
}
func (cw *compressWriter) Write(buf []byte) (int, error) {
var n int
for len(buf) > 0 {
// Accumulate the data to be compressed.
m := copy(cw.data[cw.pos:], buf)
cw.pos += m
buf = buf[m:]
if cw.pos == len(cw.data) {
err := cw.Flush()
if err != nil {
return n, err
}
}
n += m
}
return n, nil
}
func (cw *compressWriter) Flush() (err error) {
if cw.pos == 0 {
return
}
// write the headers
compressedSize, err := lz4.Compress(cw.data[:cw.pos], cw.zdata[HeaderSize:])
if err != nil {
return err
}
compressedSize += CompressHeaderSize
// fill the header, compressed_size_32 + uncompressed_size_32
cw.zdata[16] = LZ4
binary.LittleEndian.PutUint32(cw.zdata[17:], uint32(compressedSize))
binary.LittleEndian.PutUint32(cw.zdata[21:], uint32(cw.pos))
// fill the checksum
checkSum := cityhash102.CityHash128(cw.zdata[16:], uint32(compressedSize))
binary.LittleEndian.PutUint64(cw.zdata[0:], checkSum.Lower64())
binary.LittleEndian.PutUint64(cw.zdata[8:], checkSum.Higher64())
cw.writer.Write(cw.zdata[:compressedSize+ChecksumSize])
if w, ok := cw.writer.(WriteFlusher); ok {
err = w.Flush()
}
cw.pos = 0
return
}
@@ -1,20 +1,3 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package binary
import (
@@ -23,26 +6,35 @@ import (
"math"
)
func NewDecoder(r io.Reader) *Decoder {
func NewDecoder(input io.Reader) *Decoder {
return &Decoder{
input: r,
input: input,
}
}
func NewDecoderWithCompress(input io.Reader) *Decoder {
return &Decoder{
input: input,
compressInput: NewCompressReader(input),
}
}
type Decoder struct {
input io.Reader
scratch [binary.MaxVarintLen64]byte
compress bool
input io.Reader
compressInput io.Reader
scratch [binary.MaxVarintLen64]byte
}
func (decoder *Decoder) Raw(b []byte) error {
n, err := decoder.input.Read(b)
if err != nil {
return nil
func (decoder *Decoder) SelectCompress(compress bool) {
decoder.compress = compress
}
func (decoder *Decoder) Get() io.Reader {
if decoder.compress && decoder.compressInput != nil {
return decoder.compressInput
}
if n != len(b) {
return io.EOF
}
return nil
return decoder.input
}
func (decoder *Decoder) Bool() (bool, error) {
@@ -98,14 +90,14 @@ func (decoder *Decoder) UInt8() (uint8, error) {
}
func (decoder *Decoder) UInt16() (uint16, error) {
if _, err := decoder.input.Read(decoder.scratch[:2]); err != nil {
if _, err := decoder.Get().Read(decoder.scratch[:2]); err != nil {
return 0, err
}
return uint16(decoder.scratch[0]) | uint16(decoder.scratch[1])<<8, nil
}
func (decoder *Decoder) UInt32() (uint32, error) {
if _, err := decoder.input.Read(decoder.scratch[:4]); err != nil {
if _, err := decoder.Get().Read(decoder.scratch[:4]); err != nil {
return 0, err
}
return uint32(decoder.scratch[0]) |
@@ -115,7 +107,7 @@ func (decoder *Decoder) UInt32() (uint32, error) {
}
func (decoder *Decoder) UInt64() (uint64, error) {
if _, err := decoder.input.Read(decoder.scratch[:8]); err != nil {
if _, err := decoder.Get().Read(decoder.scratch[:8]); err != nil {
return 0, err
}
return uint64(decoder.scratch[0]) |
@@ -145,11 +137,11 @@ func (decoder *Decoder) Float64() (float64, error) {
}
func (decoder *Decoder) Fixed(ln int) ([]byte, error) {
if reader, ok := decoder.input.(interface{ Fixed(ln int) ([]byte, error) }); ok {
if reader, ok := decoder.Get().(FixedReader); ok {
return reader.Fixed(ln)
}
buf := make([]byte, ln)
if _, err := decoder.input.Read(buf); err != nil {
if _, err := decoder.Get().Read(buf); err != nil {
return nil, err
}
return buf, nil
@@ -167,9 +159,19 @@ func (decoder *Decoder) String() (string, error) {
return string(str), nil
}
func (decoder *Decoder) Decimal128() ([]byte, error) {
bytes := make([]byte, 16)
_, err := decoder.Get().Read(bytes)
return bytes, err
}
func (decoder *Decoder) ReadByte() (byte, error) {
if _, err := decoder.input.Read(decoder.scratch[:1]); err != nil {
if _, err := decoder.Get().Read(decoder.scratch[:1]); err != nil {
return 0x0, err
}
return decoder.scratch[0], nil
}
type FixedReader interface {
Fixed(ln int) ([]byte, error)
}
+199
View File
@@ -0,0 +1,199 @@
package binary
import (
"encoding/binary"
"io"
"math"
"reflect"
"unsafe"
)
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{
output: w,
}
}
func NewEncoderWithCompress(w io.Writer) *Encoder {
return &Encoder{
output: w,
compressOutput: NewCompressWriter(w),
}
}
type Encoder struct {
compress bool
output io.Writer
compressOutput io.Writer
scratch [binary.MaxVarintLen64]byte
}
func (enc *Encoder) SelectCompress(compress bool) {
if enc.compressOutput == nil {
return
}
if enc.compress && !compress {
enc.Flush()
}
enc.compress = compress
}
func (enc *Encoder) Get() io.Writer {
if enc.compress && enc.compressOutput != nil {
return enc.compressOutput
}
return enc.output
}
func (enc *Encoder) Uvarint(v uint64) error {
ln := binary.PutUvarint(enc.scratch[:binary.MaxVarintLen64], v)
if _, err := enc.Get().Write(enc.scratch[0:ln]); err != nil {
return err
}
return nil
}
func (enc *Encoder) Bool(v bool) error {
if v {
return enc.UInt8(1)
}
return enc.UInt8(0)
}
func (enc *Encoder) Int8(v int8) error {
return enc.UInt8(uint8(v))
}
func (enc *Encoder) Int16(v int16) error {
return enc.UInt16(uint16(v))
}
func (enc *Encoder) Int32(v int32) error {
return enc.UInt32(uint32(v))
}
func (enc *Encoder) Int64(v int64) error {
return enc.UInt64(uint64(v))
}
func (enc *Encoder) UInt8(v uint8) error {
enc.scratch[0] = v
if _, err := enc.Get().Write(enc.scratch[:1]); err != nil {
return err
}
return nil
}
func (enc *Encoder) UInt16(v uint16) error {
enc.scratch[0] = byte(v)
enc.scratch[1] = byte(v >> 8)
if _, err := enc.Get().Write(enc.scratch[:2]); err != nil {
return err
}
return nil
}
func (enc *Encoder) UInt32(v uint32) error {
enc.scratch[0] = byte(v)
enc.scratch[1] = byte(v >> 8)
enc.scratch[2] = byte(v >> 16)
enc.scratch[3] = byte(v >> 24)
if _, err := enc.Get().Write(enc.scratch[:4]); err != nil {
return err
}
return nil
}
func (enc *Encoder) UInt64(v uint64) error {
enc.scratch[0] = byte(v)
enc.scratch[1] = byte(v >> 8)
enc.scratch[2] = byte(v >> 16)
enc.scratch[3] = byte(v >> 24)
enc.scratch[4] = byte(v >> 32)
enc.scratch[5] = byte(v >> 40)
enc.scratch[6] = byte(v >> 48)
enc.scratch[7] = byte(v >> 56)
if _, err := enc.Get().Write(enc.scratch[:8]); err != nil {
return err
}
return nil
}
func (enc *Encoder) Float32(v float32) error {
return enc.UInt32(math.Float32bits(v))
}
func (enc *Encoder) Float64(v float64) error {
return enc.UInt64(math.Float64bits(v))
}
func (enc *Encoder) String(v string) error {
str := Str2Bytes(v)
if err := enc.Uvarint(uint64(len(str))); err != nil {
return err
}
if _, err := enc.Get().Write(str); err != nil {
return err
}
return nil
}
func (enc *Encoder) RawString(str []byte) error {
if err := enc.Uvarint(uint64(len(str))); err != nil {
return err
}
if _, err := enc.Get().Write(str); err != nil {
return err
}
return nil
}
func (enc *Encoder) Decimal128(bytes []byte) error {
_, err := enc.Get().Write(bytes)
return err
}
func (enc *Encoder) Write(b []byte) (int, error) {
return enc.Get().Write(b)
}
func (enc *Encoder) Flush() error {
if w, ok := enc.Get().(WriteFlusher); ok {
return w.Flush()
}
return nil
}
type WriteFlusher interface {
Flush() error
}
func Str2Bytes(str string) []byte {
// Copied from https://github.com/m3db/m3/blob/master/src/x/unsafe/string.go#L62
if len(str) == 0 {
return nil
}
// We need to declare a real byte slice so internally the compiler
// knows to use an unsafe.Pointer to keep track of the underlying memory so that
// once the slice's array pointer is updated with the pointer to the string's
// underlying bytes, the compiler won't prematurely GC the memory when the string
// goes out of scope.
var b []byte
byteHeader := (*reflect.SliceHeader)(unsafe.Pointer(&b))
// This makes sure that even if GC relocates the string's underlying
// memory after this assignment, the corresponding unsafe.Pointer in the internal
// slice struct will be updated accordingly to reflect the memory relocation.
byteHeader.Data = (*reflect.StringHeader)(unsafe.Pointer(&str)).Data
// It is important that we access str after we assign the Data
// pointer of the string header to the Data pointer of the slice header to
// make sure the string (and the underlying bytes backing the string) don't get
// GC'ed before the assignment happens.
l := len(str)
byteHeader.Len = l
byteHeader.Cap = l
return b
}
+45
View File
@@ -0,0 +1,45 @@
package cityhash102
import (
"encoding/binary"
"hash"
)
type City64 struct {
s []byte
}
var _ hash.Hash64 = (*City64)(nil)
var _ hash.Hash = (*City64)(nil)
func New64() hash.Hash64 {
return &City64{}
}
func (this *City64) Sum(b []byte) []byte {
b2 := make([]byte, 8)
binary.BigEndian.PutUint64(b2, this.Sum64())
b = append(b, b2...)
return b
}
func (this *City64) Sum64() uint64 {
return CityHash64(this.s, uint32(len(this.s)))
}
func (this *City64) Reset() {
this.s = this.s[0:0]
}
func (this *City64) BlockSize() int {
return 1
}
func (this *City64) Write(s []byte) (n int, err error) {
this.s = append(this.s, s...)
return len(s), nil
}
func (this *City64) Size() int {
return 8
}
@@ -1,20 +1,3 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
/*
* Go implementation of Google city hash (MIT license)
* https://code.google.com/p/cityhash/
+5
View File
@@ -0,0 +1,5 @@
/** COPY from https://github.com/zentures/cityhash/
NOTE: The code is modified to be compatible with CityHash128 used in ClickHouse
*/
package cityhash102
+270
View File
@@ -0,0 +1,270 @@
package column
import (
"errors"
"fmt"
"net"
"reflect"
"strings"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type columnDecoder func() (interface{}, error)
var unsupportedArrayTypeErrTemp = "unsupported Array type '%s'"
// If you add Nullable type, that can be used in Array(Nullable(T)) add this type to ../codegen/nullable_appender/main.go in structure values.Types.
// Run code generation.
//go:generate go run ../codegen/nullable_appender -package $GOPACKAGE -file nullable_appender.go
type Array struct {
base
depth int
column Column
nullable bool
}
func (array *Array) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
return nil, fmt.Errorf("do not use Read method for Array(T) column")
}
func (array *Array) WriteNull(nulls, encoder *binary.Encoder, v interface{}) error {
if array.nullable {
column, ok := array.column.(*Nullable)
if !ok {
return fmt.Errorf("cannot convert to nullable type")
}
return column.WriteNull(nulls, encoder, v)
}
return fmt.Errorf("write null to not nullable array")
}
func (array *Array) Write(encoder *binary.Encoder, v interface{}) error {
return array.column.Write(encoder, v)
}
func (array *Array) ReadArray(decoder *binary.Decoder, rows int) (_ []interface{}, err error) {
var (
offsets = make([][]uint64, array.depth)
values = make([]interface{}, rows)
)
// Read offsets
lastOffset := uint64(rows)
for i := 0; i < array.depth; i++ {
offset := make([]uint64, lastOffset)
for j := uint64(0); j < lastOffset; j++ {
if offset[j], err = decoder.UInt64(); err != nil {
return nil, err
}
}
offsets[i] = offset
lastOffset = 0
if len(offset) > 0 {
lastOffset = offset[len(offset)-1]
}
}
var cd columnDecoder
switch column := array.column.(type) {
case *Nullable:
nullRows, err := column.ReadNull(decoder, int(lastOffset))
if err != nil {
return nil, err
}
cd = func(rows []interface{}) columnDecoder {
i := 0
return func() (interface{}, error) {
if i > len(rows) {
return nil, errors.New("not enough rows to return while parsing Null column")
}
ret := rows[i]
i++
return ret, nil
}
}(nullRows)
case *Tuple:
tupleRows, err := column.ReadTuple(decoder, int(lastOffset))
if err != nil {
return nil, err
}
// closure to return fully assembled tuple values as if they
// were decoded one at a time
cd = func(rows []interface{}) columnDecoder {
i := 0
return func() (interface{}, error) {
if i > len(rows) {
return nil, errors.New("not enough rows to return while parsing Tuple column")
}
ret := rows[i]
i++
return ret, nil
}
}(tupleRows)
default:
cd = func(decoder *binary.Decoder) columnDecoder {
return func() (interface{}, error) { return array.column.Read(decoder, array.nullable) }
}(decoder)
}
// Read values
for i := 0; i < rows; i++ {
if values[i], err = array.read(cd, offsets, uint64(i), 0); err != nil {
return nil, err
}
}
return values, nil
}
func (array *Array) read(readColumn columnDecoder, offsets [][]uint64, index uint64, level int) (interface{}, error) {
end := offsets[level][index]
start := uint64(0)
if index > 0 {
start = offsets[level][index-1]
}
scanT := array.column.ScanType()
slice := reflect.MakeSlice(array.arrayType(level), 0, int(end-start))
for i := start; i < end; i++ {
var (
value interface{}
err error
)
if level == array.depth-1 {
value, err = readColumn()
} else {
value, err = array.read(readColumn, offsets, i, level+1)
}
if err != nil {
return nil, err
}
if array.nullable && level == array.depth-1 {
f, ok := nullableAppender[scanT.String()]
if !ok {
return nil, fmt.Errorf(unsupportedArrayTypeErrTemp, scanT.String())
}
cSlice, err := f(value, slice)
if err != nil {
return nil, err
}
slice = cSlice
} else {
slice = reflect.Append(slice, reflect.ValueOf(value))
}
}
return slice.Interface(), nil
}
func (array *Array) arrayType(level int) reflect.Type {
t := array.column.ScanType()
for i := 0; i < array.depth-level; i++ {
t = reflect.SliceOf(t)
}
return t
}
func (array *Array) Depth() int {
return array.depth
}
func parseArray(name, chType string, timezone *time.Location) (*Array, error) {
if len(chType) < 11 {
return nil, fmt.Errorf("invalid Array column type: %s", chType)
}
var (
depth int
columnType = chType
)
loop:
for _, str := range strings.Split(chType, "Array(") {
switch {
case len(str) == 0:
depth++
default:
chType = str[:len(str)-depth]
break loop
}
}
column, err := Factory(name, chType, timezone)
if err != nil {
return nil, fmt.Errorf("Array(T): %v", err)
}
var scanType interface{}
switch t := column.ScanType(); t {
case arrayBaseTypes[int8(0)]:
scanType = []int8{}
case arrayBaseTypes[int16(0)]:
scanType = []int16{}
case arrayBaseTypes[int32(0)]:
scanType = []int32{}
case arrayBaseTypes[int64(0)]:
scanType = []int64{}
case arrayBaseTypes[uint8(0)]:
scanType = []uint8{}
case arrayBaseTypes[uint16(0)]:
scanType = []uint16{}
case arrayBaseTypes[uint32(0)]:
scanType = []uint32{}
case arrayBaseTypes[uint64(0)]:
scanType = []uint64{}
case arrayBaseTypes[float32(0)]:
scanType = []float32{}
case arrayBaseTypes[float64(0)]:
scanType = []float64{}
case arrayBaseTypes[string("")]:
scanType = []string{}
case arrayBaseTypes[time.Time{}]:
scanType = []time.Time{}
case arrayBaseTypes[IPv4{}], arrayBaseTypes[IPv6{}]:
scanType = []net.IP{}
case reflect.ValueOf([]interface{}{}).Type():
scanType = [][]interface{}{}
//nullable
case arrayBaseTypes[ptrInt8T]:
scanType = []*int8{}
case arrayBaseTypes[ptrInt16T]:
scanType = []*int16{}
case arrayBaseTypes[ptrInt32T]:
scanType = []*int32{}
case arrayBaseTypes[ptrInt64T]:
scanType = []*int64{}
case arrayBaseTypes[ptrUInt8T]:
scanType = []*uint8{}
case arrayBaseTypes[ptrUInt16T]:
scanType = []*uint16{}
case arrayBaseTypes[ptrUInt32T]:
scanType = []*uint32{}
case arrayBaseTypes[ptrUInt64T]:
scanType = []*uint64{}
case arrayBaseTypes[ptrFloat32]:
scanType = []*float32{}
case arrayBaseTypes[ptrFloat64]:
scanType = []*float64{}
case arrayBaseTypes[ptrString]:
scanType = []*string{}
case arrayBaseTypes[ptrTime]:
scanType = []*time.Time{}
case arrayBaseTypes[ptrIPv4], arrayBaseTypes[ptrIPv6]:
scanType = []*net.IP{}
default:
return nil, fmt.Errorf(unsupportedArrayTypeErrTemp, column.ScanType().Name())
}
return &Array{
base: base{
name: name,
chType: columnType,
valueOf: reflect.ValueOf(scanType),
},
depth: depth,
column: column,
nullable: strings.HasPrefix(column.CHType(), "Nullable"),
}, nil
}
+205
View File
@@ -0,0 +1,205 @@
package column
import (
"fmt"
"reflect"
"strings"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Column interface {
Name() string
CHType() string
ScanType() reflect.Type
Read(*binary.Decoder, bool) (interface{}, error)
Write(*binary.Encoder, interface{}) error
defaultValue() interface{}
Depth() int
}
func Factory(name, chType string, timezone *time.Location) (Column, error) {
switch chType {
case "Int8":
return &Int8{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[int8(0)],
},
}, nil
case "Int16":
return &Int16{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[int16(0)],
},
}, nil
case "Int32":
return &Int32{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[int32(0)],
},
}, nil
case "Int64":
return &Int64{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[int64(0)],
},
}, nil
case "UInt8":
return &UInt8{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[uint8(0)],
},
}, nil
case "UInt16":
return &UInt16{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[uint16(0)],
},
}, nil
case "UInt32":
return &UInt32{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[uint32(0)],
},
}, nil
case "UInt64":
return &UInt64{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[uint64(0)],
},
}, nil
case "Float32":
return &Float32{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[float32(0)],
},
}, nil
case "Float64":
return &Float64{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[float64(0)],
},
}, nil
case "String":
return &String{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[string("")],
},
}, nil
case "UUID":
return &UUID{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[string("")],
},
}, nil
case "Date":
_, offset := time.Unix(0, 0).In(timezone).Zone()
return &Date{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[time.Time{}],
},
Timezone: timezone,
offset: int64(offset),
}, nil
case "IPv4":
return &IPv4{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[IPv4{}],
},
}, nil
case "IPv6":
return &IPv6{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[IPv6{}],
},
}, nil
}
switch {
case strings.HasPrefix(chType, "DateTime") && !strings.HasPrefix(chType, "DateTime64"):
return &DateTime{
base: base{
name: name,
chType: "DateTime",
valueOf: columnBaseTypes[time.Time{}],
},
Timezone: timezone,
}, nil
case strings.HasPrefix(chType, "DateTime64"):
return &DateTime64{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[time.Time{}],
},
Timezone: timezone,
}, nil
case strings.HasPrefix(chType, "Array"):
return parseArray(name, chType, timezone)
case strings.HasPrefix(chType, "Nullable"):
return parseNullable(name, chType, timezone)
case strings.HasPrefix(chType, "FixedString"):
return parseFixedString(name, chType)
case strings.HasPrefix(chType, "Enum8"), strings.HasPrefix(chType, "Enum16"):
return parseEnum(name, chType)
case strings.HasPrefix(chType, "Decimal"):
return parseDecimal(name, chType)
case strings.HasPrefix(chType, "SimpleAggregateFunction"):
if nestedType, err := getNestedType(chType, "SimpleAggregateFunction"); err != nil {
return nil, err
} else {
return Factory(name, nestedType, timezone)
}
case strings.HasPrefix(chType, "Tuple"):
return parseTuple(name, chType, timezone)
}
return nil, fmt.Errorf("column: unhandled type %v", chType)
}
func getNestedType(chType string, wrapType string) (string, error) {
prefixLen := len(wrapType) + 1
suffixLen := 1
if len(chType) > prefixLen+suffixLen {
nested := strings.Split(chType[prefixLen:len(chType)-suffixLen], ",")
if len(nested) == 2 {
return strings.TrimSpace(nested[1]), nil
}
if len(nested) == 3 {
return strings.TrimSpace(strings.Join(nested[1:], ",")), nil
}
}
return "", fmt.Errorf("column: invalid %s type (%s)", wrapType, chType)
}
+115
View File
@@ -0,0 +1,115 @@
package column
import (
"fmt"
"net"
"reflect"
"time"
)
type ErrUnexpectedType struct {
Column Column
T interface{}
}
func (err *ErrUnexpectedType) Error() string {
return fmt.Sprintf("%s: unexpected type %T", err.Column, err.T)
}
var columnBaseTypes = map[interface{}]reflect.Value{
int8(0): reflect.ValueOf(int8(0)),
int16(0): reflect.ValueOf(int16(0)),
int32(0): reflect.ValueOf(int32(0)),
int64(0): reflect.ValueOf(int64(0)),
uint8(0): reflect.ValueOf(uint8(0)),
uint16(0): reflect.ValueOf(uint16(0)),
uint32(0): reflect.ValueOf(uint32(0)),
uint64(0): reflect.ValueOf(uint64(0)),
float32(0): reflect.ValueOf(float32(0)),
float64(0): reflect.ValueOf(float64(0)),
string(""): reflect.ValueOf(string("")),
time.Time{}: reflect.ValueOf(time.Time{}),
IPv4{}: reflect.ValueOf(net.IPv4zero),
IPv6{}: reflect.ValueOf(net.IPv6unspecified),
}
type ptrTo uint8
const (
ptrInt8T ptrTo = iota
ptrInt16T
ptrInt32T
ptrInt64T
ptrUInt8T
ptrUInt16T
ptrUInt32T
ptrUInt64T
ptrFloat32
ptrFloat64
ptrString
ptrTime
ptrIPv4
ptrIPv6
)
var arrayBaseTypes = map[interface{}]reflect.Type{
int8(0): reflect.ValueOf(int8(0)).Type(),
int16(0): reflect.ValueOf(int16(0)).Type(),
int32(0): reflect.ValueOf(int32(0)).Type(),
int64(0): reflect.ValueOf(int64(0)).Type(),
uint8(0): reflect.ValueOf(uint8(0)).Type(),
uint16(0): reflect.ValueOf(uint16(0)).Type(),
uint32(0): reflect.ValueOf(uint32(0)).Type(),
uint64(0): reflect.ValueOf(uint64(0)).Type(),
float32(0): reflect.ValueOf(float32(0)).Type(),
float64(0): reflect.ValueOf(float64(0)).Type(),
string(""): reflect.ValueOf(string("")).Type(),
time.Time{}: reflect.ValueOf(time.Time{}).Type(),
IPv4{}: reflect.ValueOf(net.IPv4zero).Type(),
IPv6{}: reflect.ValueOf(net.IPv6unspecified).Type(),
// nullable
ptrInt8T: reflect.PtrTo(reflect.ValueOf(int8(0)).Type()),
ptrInt16T: reflect.PtrTo(reflect.ValueOf(int16(0)).Type()),
ptrInt32T: reflect.PtrTo(reflect.ValueOf(int32(0)).Type()),
ptrInt64T: reflect.PtrTo(reflect.ValueOf(int64(0)).Type()),
ptrUInt8T: reflect.PtrTo(reflect.ValueOf(uint8(0)).Type()),
ptrUInt16T: reflect.PtrTo(reflect.ValueOf(uint16(0)).Type()),
ptrUInt32T: reflect.PtrTo(reflect.ValueOf(uint32(0)).Type()),
ptrUInt64T: reflect.PtrTo(reflect.ValueOf(uint64(0)).Type()),
ptrFloat32: reflect.PtrTo(reflect.ValueOf(float32(0)).Type()),
ptrFloat64: reflect.PtrTo(reflect.ValueOf(float64(0)).Type()),
ptrString: reflect.PtrTo(reflect.ValueOf(string("")).Type()),
ptrTime: reflect.PtrTo(reflect.ValueOf(time.Time{}).Type()),
ptrIPv4: reflect.PtrTo(reflect.ValueOf(net.IPv4zero).Type()),
ptrIPv6: reflect.PtrTo(reflect.ValueOf(net.IPv6unspecified).Type()),
}
type base struct {
name, chType string
valueOf reflect.Value
}
func (base *base) Name() string {
return base.name
}
func (base *base) CHType() string {
return base.chType
}
func (base *base) ScanType() reflect.Type {
return base.valueOf.Type()
}
func (base *base) defaultValue() interface{} {
return base.valueOf.Interface()
}
func (base *base) String() string {
return fmt.Sprintf("%s (%s)", base.name, base.chType)
}
func (base *base) Depth() int {
return 0
}
+84
View File
@@ -0,0 +1,84 @@
package column
import (
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Date struct {
base
Timezone *time.Location
offset int64
}
func (dt *Date) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
sec, err := decoder.Int16()
if err != nil {
return nil, err
}
return time.Unix(int64(sec)*24*3600-dt.offset, 0).In(dt.Timezone), nil
}
func (dt *Date) Write(encoder *binary.Encoder, v interface{}) error {
var timestamp int64
switch value := v.(type) {
case time.Time:
_, offset := value.Zone()
timestamp = value.Unix() + int64(offset)
case int16:
return encoder.Int16(value)
case int32:
timestamp = int64(value) + dt.offset
case uint32:
timestamp = int64(value) + dt.offset
case uint64:
timestamp = int64(value) + dt.offset
case int64:
timestamp = value + dt.offset
case string:
var err error
timestamp, err = dt.parse(value)
if err != nil {
return err
}
// this relies on Nullable never sending nil values through
case *time.Time:
_, offset := value.Zone()
timestamp = (*value).Unix() + int64(offset)
case *int16:
return encoder.Int16(*value)
case *int32:
timestamp = int64(*value) + dt.offset
case *int64:
timestamp = *value + dt.offset
case *string:
var err error
timestamp, err = dt.parse(*value)
if err != nil {
return err
}
default:
return &ErrUnexpectedType{
T: v,
Column: dt,
}
}
return encoder.Int16(int16(timestamp / 24 / 3600))
}
func (dt *Date) parse(value string) (int64, error) {
tv, err := time.Parse("2006-01-02", value)
if err != nil {
return 0, err
}
return time.Date(
time.Time(tv).Year(),
time.Time(tv).Month(),
time.Time(tv).Day(),
0, 0, 0, 0, time.UTC,
).Unix(), nil
}
+87
View File
@@ -0,0 +1,87 @@
package column
import (
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type DateTime struct {
base
Timezone *time.Location
}
func (dt *DateTime) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
sec, err := decoder.Int32()
if err != nil {
return nil, err
}
return time.Unix(int64(sec), 0).In(dt.Timezone), nil
}
func (dt *DateTime) Write(encoder *binary.Encoder, v interface{}) error {
var timestamp int64
switch value := v.(type) {
case time.Time:
if !value.IsZero() {
timestamp = value.Unix()
}
case int16:
timestamp = int64(value)
case int32:
timestamp = int64(value)
case uint32:
timestamp = int64(value)
case uint64:
timestamp = int64(value)
case int64:
timestamp = value
case string:
var err error
timestamp, err = dt.parse(value)
if err != nil {
return err
}
case *time.Time:
if value != nil && !(*value).IsZero() {
timestamp = (*value).Unix()
}
case *int16:
timestamp = int64(*value)
case *int32:
timestamp = int64(*value)
case *int64:
timestamp = *value
case *string:
var err error
timestamp, err = dt.parse(*value)
if err != nil {
return err
}
default:
return &ErrUnexpectedType{
T: v,
Column: dt,
}
}
return encoder.Int32(int32(timestamp))
}
func (dt *DateTime) parse(value string) (int64, error) {
tv, err := time.Parse("2006-01-02 15:04:05", value)
if err != nil {
return 0, err
}
return time.Date(
time.Time(tv).Year(),
time.Time(tv).Month(),
time.Time(tv).Day(),
time.Time(tv).Hour(),
time.Time(tv).Minute(),
time.Time(tv).Second(),
0, time.Local, //use local timzone when insert into clickhouse
).Unix(), nil
}
+100
View File
@@ -0,0 +1,100 @@
package column
import (
"math"
"strconv"
"strings"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type DateTime64 struct {
base
Timezone *time.Location
}
func (dt *DateTime64) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
value, err := decoder.Int64()
if err != nil {
return nil, err
}
precision, err := dt.getPrecision()
if err != nil {
return nil, err
}
var nano int64
if precision < 19 {
nano = value * int64(math.Pow10(9-precision))
}
sec := nano / int64(10e8)
nsec := nano - sec*10e8
return time.Unix(sec, nsec).In(dt.Timezone), nil
}
func (dt *DateTime64) Write(encoder *binary.Encoder, v interface{}) error {
var timestamp int64
switch value := v.(type) {
case time.Time:
if !value.IsZero() {
timestamp = value.UnixNano()
}
case uint64:
timestamp = int64(value)
case int64:
timestamp = value
case string:
var err error
timestamp, err = dt.parse(value)
if err != nil {
return err
}
case *time.Time:
if value != nil && !(*value).IsZero() {
timestamp = (*value).UnixNano()
}
case *int64:
timestamp = *value
case *string:
var err error
timestamp, err = dt.parse(*value)
if err != nil {
return err
}
default:
return &ErrUnexpectedType{
T: v,
Column: dt,
}
}
precision, err := dt.getPrecision()
if err != nil {
return err
}
timestamp = timestamp / int64(math.Pow10(9-precision))
return encoder.Int64(timestamp)
}
func (dt *DateTime64) parse(value string) (int64, error) {
tv, err := time.Parse("2006-01-02 15:04:05.999", value)
if err != nil {
return 0, err
}
return tv.UnixNano(), nil
}
func (dt *DateTime64) getPrecision() (int, error) {
dtParams := dt.base.chType[11 : len(dt.base.chType)-1]
precision, err := strconv.Atoi(strings.Split(dtParams, ",")[0])
if err != nil {
return 0, err
}
return precision, nil
}
+377
View File
@@ -0,0 +1,377 @@
package column
import (
b "encoding/binary"
"errors"
"fmt"
"math"
"reflect"
"strconv"
"strings"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
// Table of powers of 10 for fast casting from floating types to decimal type
// representations.
var factors10 = []float64{
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13,
1e14, 1e15, 1e16, 1e17, 1e18,
}
// Decimal represents Decimal(P, S) ClickHouse. Decimal is represented as
// integral. Also floating-point types are supported for query parameters.
//
// Since there is no support for int128 in Golang, decimals with precision 19
// through 38 are represented as 16 little-endian bytes.
type Decimal struct {
base
nobits int // its domain is {32, 64}
precision int
scale int
}
func (d *Decimal) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
switch d.nobits {
case 32:
return decoder.Int32()
case 64:
return decoder.Int64()
case 128:
return decoder.Decimal128()
default:
return nil, errors.New("unachievable execution path")
}
}
func (d *Decimal) Write(encoder *binary.Encoder, v interface{}) error {
switch d.nobits {
case 32:
return d.write32(encoder, v)
case 64:
return d.write64(encoder, v)
case 128:
return d.write128(encoder, v)
default:
return errors.New("unachievable execution path")
}
}
func (d *Decimal) float2int32(floating float64) int32 {
fixed := int32(floating * factors10[d.scale])
return fixed
}
func (d *Decimal) float2int64(floating float64) int64 {
fixed := int64(floating * factors10[d.scale])
return fixed
}
func (d *Decimal) write32(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case int8:
return encoder.Int32(int32(v))
case int16:
return encoder.Int32(int32(v))
case int32:
return encoder.Int32(int32(v))
case int64:
if v > math.MaxInt32 || v < math.MinInt32 {
return errors.New("overflow when narrowing type conversion from int64 to int32")
}
return encoder.Int32(int32(v))
case uint8:
return encoder.Int32(int32(v))
case uint16:
return encoder.Int32(int32(v))
case uint32:
if v > math.MaxInt32 {
return errors.New("overflow when narrowing type conversion from uint32 to int32")
}
return encoder.Int32(int32(v))
case uint64:
if v > math.MaxInt32 {
return errors.New("overflow when narrowing type conversion from uint64 to int32")
}
return encoder.Int32(int32(v))
case float32:
fixed := d.float2int32(float64(v))
return encoder.Int32(fixed)
case float64:
fixed := d.float2int32(float64(v))
return encoder.Int32(fixed)
// this relies on Nullable never sending nil values through
case *int8:
return encoder.Int32(int32(*v))
case *int16:
return encoder.Int32(int32(*v))
case *int32:
return encoder.Int32(int32(*v))
case *int64:
if *v > math.MaxInt32 || *v < math.MinInt32 {
return errors.New("overflow when narrowing type conversion from int64 to int32")
}
return encoder.Int32(int32(*v))
case *uint8:
return encoder.Int32(int32(*v))
case *uint16:
return encoder.Int32(int32(*v))
case *uint32:
if *v > math.MaxInt32 {
return errors.New("overflow when narrowing type conversion from uint34 to int32")
}
return encoder.Int32(int32(*v))
case *uint64:
if *v > math.MaxInt32 {
return errors.New("overflow when narrowing type conversion from uint64 to int32")
}
return encoder.Int32(int32(*v))
case *float32:
fixed := d.float2int32(float64(*v))
return encoder.Int32(fixed)
case *float64:
fixed := d.float2int32(float64(*v))
return encoder.Int32(fixed)
}
return &ErrUnexpectedType{
T: v,
Column: d,
}
}
func (d *Decimal) write64(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case int:
return encoder.Int64(int64(v))
case int8:
return encoder.Int64(int64(v))
case int16:
return encoder.Int64(int64(v))
case int32:
return encoder.Int64(int64(v))
case int64:
return encoder.Int64(int64(v))
case uint8:
return encoder.Int64(int64(v))
case uint16:
return encoder.Int64(int64(v))
case uint32:
return encoder.Int64(int64(v))
case uint64:
if v > math.MaxInt64 {
return errors.New("overflow when narrowing type conversion from uint64 to int64")
}
return encoder.Int64(int64(v))
case float32:
fixed := d.float2int64(float64(v))
return encoder.Int64(fixed)
case float64:
fixed := d.float2int64(float64(v))
return encoder.Int64(fixed)
// this relies on Nullable never sending nil values through
case *int:
return encoder.Int64(int64(*v))
case *int8:
return encoder.Int64(int64(*v))
case *int16:
return encoder.Int64(int64(*v))
case *int32:
return encoder.Int64(int64(*v))
case *int64:
return encoder.Int64(int64(*v))
case *uint8:
return encoder.Int64(int64(*v))
case *uint16:
return encoder.Int64(int64(*v))
case *uint32:
return encoder.Int64(int64(*v))
case *uint64:
if *v > math.MaxInt64 {
return errors.New("overflow when narrowing type conversion from uint64 to int64")
}
return encoder.Int64(int64(*v))
case *float32:
fixed := d.float2int64(float64(*v))
return encoder.Int64(fixed)
case *float64:
fixed := d.float2int64(float64(*v))
return encoder.Int64(fixed)
}
return &ErrUnexpectedType{
T: v,
Column: d,
}
}
// Turns an int64 into 16 little-endian bytes.
func int64ToDecimal128(v int64) []byte {
bytes := make([]byte, 16)
b.LittleEndian.PutUint64(bytes[:8], uint64(v))
sign := 0
if v < 0 {
sign = -1
}
b.LittleEndian.PutUint64(bytes[8:], uint64(sign))
return bytes
}
// Turns a uint64 into 16 little-endian bytes.
func uint64ToDecimal128(v uint64) []byte {
bytes := make([]byte, 16)
b.LittleEndian.PutUint64(bytes[:8], uint64(v))
return bytes
}
func (d *Decimal) write128(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case int:
return encoder.Decimal128(int64ToDecimal128(int64(v)))
case int8:
return encoder.Decimal128(int64ToDecimal128(int64(v)))
case int16:
return encoder.Decimal128(int64ToDecimal128(int64(v)))
case int32:
return encoder.Decimal128(int64ToDecimal128(int64(v)))
case int64:
return encoder.Decimal128(int64ToDecimal128(v))
case uint8:
return encoder.Decimal128(uint64ToDecimal128(uint64(v)))
case uint16:
return encoder.Decimal128(uint64ToDecimal128(uint64(v)))
case uint32:
return encoder.Decimal128(uint64ToDecimal128(uint64(v)))
case uint64:
return encoder.Decimal128(uint64ToDecimal128(v))
case float32:
fixed := d.float2int64(float64(v))
return encoder.Decimal128(int64ToDecimal128(fixed))
case float64:
fixed := d.float2int64(float64(v))
return encoder.Decimal128(int64ToDecimal128(fixed))
case []byte:
if len(v) != 16 {
return errors.New("expected 16 bytes")
}
return encoder.Decimal128(v)
// this relies on Nullable never sending nil values through
case *int:
return encoder.Decimal128(int64ToDecimal128(int64(*v)))
case *int8:
return encoder.Decimal128(int64ToDecimal128(int64(*v)))
case *int16:
return encoder.Decimal128(int64ToDecimal128(int64(*v)))
case *int32:
return encoder.Decimal128(int64ToDecimal128(int64(*v)))
case *int64:
return encoder.Decimal128(int64ToDecimal128(*v))
case *uint8:
return encoder.Decimal128(uint64ToDecimal128(uint64(*v)))
case *uint16:
return encoder.Decimal128(uint64ToDecimal128(uint64(*v)))
case *uint32:
return encoder.Decimal128(uint64ToDecimal128(uint64(*v)))
case *uint64:
return encoder.Decimal128(uint64ToDecimal128(*v))
case *float32:
fixed := d.float2int64(float64(*v))
return encoder.Decimal128(int64ToDecimal128(fixed))
case *float64:
fixed := d.float2int64(float64(*v))
return encoder.Decimal128(int64ToDecimal128(fixed))
case *[]byte:
if len(*v) != 16 {
return errors.New("expected 16 bytes")
}
return encoder.Decimal128(*v)
}
return &ErrUnexpectedType{
T: v,
Column: d,
}
}
func parseDecimal(name, chType string) (Column, error) {
switch {
case len(chType) < 12:
fallthrough
case !strings.HasPrefix(chType, "Decimal"):
fallthrough
case chType[7] != '(':
fallthrough
case chType[len(chType)-1] != ')':
return nil, fmt.Errorf("invalid Decimal format: '%s'", chType)
}
var params = strings.Split(chType[8:len(chType)-1], ",")
if len(params) != 2 {
return nil, fmt.Errorf("invalid Decimal format: '%s'", chType)
}
params[0] = strings.TrimSpace(params[0])
params[1] = strings.TrimSpace(params[1])
var err error
var decimal = &Decimal{
base: base{
name: name,
chType: chType,
},
}
if decimal.precision, err = strconv.Atoi(params[0]); err != nil {
return nil, fmt.Errorf("'%s' is not Decimal type: %s", chType, err)
} else if decimal.precision < 1 {
return nil, errors.New("wrong precision of Decimal type")
}
if decimal.scale, err = strconv.Atoi(params[1]); err != nil {
return nil, fmt.Errorf("'%s' is not Decimal type: %s", chType, err)
} else if decimal.scale < 0 || decimal.scale > decimal.precision {
return nil, errors.New("wrong scale of Decimal type")
}
switch {
case decimal.precision <= 9:
decimal.nobits = 32
decimal.valueOf = columnBaseTypes[int32(0)]
case decimal.precision <= 18:
decimal.nobits = 64
decimal.valueOf = columnBaseTypes[int64(0)]
case decimal.precision <= 38:
decimal.nobits = 128
decimal.valueOf = reflect.ValueOf([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
default:
return nil, errors.New("precision of Decimal exceeds max bound")
}
return decimal, nil
}
func (d *Decimal) GetPrecision() int {
return d.precision
}
func (d *Decimal) GetScale() int {
return d.scale
}
+175
View File
@@ -0,0 +1,175 @@
package column
import (
"fmt"
"strconv"
"strings"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Enum struct {
iv map[string]interface{}
vi map[interface{}]string
base
baseType interface{}
}
func (enum *Enum) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
var (
err error
ident interface{}
)
switch enum.baseType.(type) {
case int16:
if ident, err = decoder.Int16(); err != nil {
return nil, err
}
default:
if ident, err = decoder.Int8(); err != nil {
return nil, err
}
}
if ident, found := enum.vi[ident]; found || isNull {
return ident, nil
}
return nil, fmt.Errorf("invalid Enum value: %v", ident)
}
func (enum *Enum) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case string:
return enum.encodeFromString(v, encoder)
case uint8:
if _, ok := enum.baseType.(int8); ok {
return encoder.Int8(int8(v))
}
case int8:
if _, ok := enum.baseType.(int8); ok {
return encoder.Int8(v)
}
case uint16:
if _, ok := enum.baseType.(int16); ok {
return encoder.Int16(int16(v))
}
case int16:
if _, ok := enum.baseType.(int16); ok {
return encoder.Int16(v)
}
case int64:
switch enum.baseType.(type) {
case int8:
return encoder.Int8(int8(v))
case int16:
return encoder.Int16(int16(v))
}
// nullable enums
case *string:
return enum.encodeFromString(*v, encoder)
case *uint8:
if _, ok := enum.baseType.(int8); ok {
return encoder.Int8(int8(*v))
}
case *int8:
if _, ok := enum.baseType.(int8); ok {
return encoder.Int8(*v)
}
case *uint16:
if _, ok := enum.baseType.(int16); ok {
return encoder.Int16(int16(*v))
}
case *int16:
if _, ok := enum.baseType.(int16); ok {
return encoder.Int16(*v)
}
case *int64:
switch enum.baseType.(type) {
case int8:
return encoder.Int8(int8(*v))
case int16:
return encoder.Int16(int16(*v))
}
}
return &ErrUnexpectedType{
T: v,
Column: enum,
}
}
func (enum *Enum) encodeFromString(v string, encoder *binary.Encoder) error {
ident, found := enum.iv[v]
if !found {
return fmt.Errorf("invalid Enum ident: %s", v)
}
switch ident := ident.(type) {
case int8:
return encoder.Int8(ident)
case int16:
return encoder.Int16(ident)
default:
return &ErrUnexpectedType{
T: ident,
Column: enum,
}
}
}
func (enum *Enum) defaultValue() interface{} {
return enum.baseType
}
func parseEnum(name, chType string) (*Enum, error) {
var (
data string
isEnum16 bool
)
if len(chType) < 8 {
return nil, fmt.Errorf("invalid Enum format: %s", chType)
}
switch {
case strings.HasPrefix(chType, "Enum8"):
data = chType[6:]
case strings.HasPrefix(chType, "Enum16"):
data = chType[7:]
isEnum16 = true
default:
return nil, fmt.Errorf("'%s' is not Enum type", chType)
}
enum := Enum{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[string("")],
},
iv: make(map[string]interface{}),
vi: make(map[interface{}]string),
}
for _, block := range strings.Split(data[:len(data)-1], ",") {
parts := strings.Split(block, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid Enum format: %s", chType)
}
var (
ident = strings.TrimSpace(parts[0])
value, err = strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 16)
)
if err != nil {
return nil, fmt.Errorf("invalid Enum value: %v", chType)
}
{
var (
ident = ident[1 : len(ident)-1]
value interface{} = int16(value)
)
if !isEnum16 {
value = int8(value.(int16))
}
if enum.baseType == nil {
enum.baseType = value
}
enum.iv[ident] = value
enum.vi[value] = ident
}
}
return &enum, nil
}
+71
View File
@@ -0,0 +1,71 @@
package column
import (
"encoding"
"fmt"
"reflect"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type FixedString struct {
base
len int
scanType reflect.Type
}
func (str *FixedString) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Fixed(str.len)
if err != nil {
return "", err
}
return string(v), nil
}
func (str *FixedString) Write(encoder *binary.Encoder, v interface{}) error {
var fixedString []byte
switch v := v.(type) {
case string:
fixedString = binary.Str2Bytes(v)
case []byte:
fixedString = v
case encoding.BinaryMarshaler:
bytes, err := v.MarshalBinary()
if err != nil {
return err
}
fixedString = bytes
default:
return &ErrUnexpectedType{
T: v,
Column: str,
}
}
switch {
case len(fixedString) > str.len:
return fmt.Errorf("too large value '%s' (expected %d, got %d)", fixedString, str.len, len(fixedString))
case len(fixedString) < str.len:
tmp := make([]byte, str.len)
copy(tmp, fixedString)
fixedString = tmp
}
if _, err := encoder.Write(fixedString); err != nil {
return err
}
return nil
}
func parseFixedString(name, chType string) (*FixedString, error) {
var strLen int
if _, err := fmt.Sscanf(chType, "FixedString(%d)", &strLen); err != nil {
return nil, err
}
return &FixedString{
base: base{
name: name,
chType: chType,
valueOf: columnBaseTypes[string("")],
},
len: strLen,
}, nil
}
+35
View File
@@ -0,0 +1,35 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Float32 struct{ base }
func (Float32) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Float32()
if err != nil {
return float32(0), err
}
return v, nil
}
func (float *Float32) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case float32:
return encoder.Float32(v)
case float64:
return encoder.Float32(float32(v))
// this relies on Nullable never sending nil values through
case *float32:
return encoder.Float32(*v)
case *float64:
return encoder.Float32(float32(*v))
}
return &ErrUnexpectedType{
T: v,
Column: float,
}
}
+35
View File
@@ -0,0 +1,35 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Float64 struct{ base }
func (Float64) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Float64()
if err != nil {
return float64(0), err
}
return v, nil
}
func (float *Float64) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case float32:
return encoder.Float64(float64(v))
case float64:
return encoder.Float64(v)
// this relies on Nullable never sending nil values through
case *float32:
return encoder.Float64(float64(*v))
case *float64:
return encoder.Float64(*v)
}
return &ErrUnexpectedType{
T: v,
Column: float,
}
}
+39
View File
@@ -0,0 +1,39 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Int16 struct{ base }
func (Int16) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Int16()
if err != nil {
return int16(0), err
}
return v, nil
}
func (i *Int16) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case int16:
return encoder.Int16(v)
case int64:
return encoder.Int16(int16(v))
case int:
return encoder.Int16(int16(v))
// this relies on Nullable never sending nil values through
case *int16:
return encoder.Int16(*v)
case *int64:
return encoder.Int16(int16(*v))
case *int:
return encoder.Int16(int16(*v))
}
return &ErrUnexpectedType{
T: v,
Column: i,
}
}
+39
View File
@@ -0,0 +1,39 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Int32 struct{ base }
func (Int32) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Int32()
if err != nil {
return int32(0), err
}
return v, nil
}
func (i *Int32) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case int32:
return encoder.Int32(v)
case int64:
return encoder.Int32(int32(v))
case int:
return encoder.Int32(int32(v))
// this relies on Nullable never sending nil values through
case *int32:
return encoder.Int32(*v)
case *int64:
return encoder.Int32(int32(*v))
case *int:
return encoder.Int32(int32(*v))
}
return &ErrUnexpectedType{
T: v,
Column: i,
}
}
+40
View File
@@ -0,0 +1,40 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Int64 struct{ base }
func (Int64) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Int64()
if err != nil {
return int64(0), err
}
return v, nil
}
func (i *Int64) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case int:
return encoder.Int64(int64(v))
case int64:
return encoder.Int64(v)
case []byte:
if _, err := encoder.Write(v); err != nil {
return err
}
return nil
// this relies on Nullable never sending nil values through
case *int:
return encoder.Int64(int64(*v))
case *int64:
return encoder.Int64(*v)
}
return &ErrUnexpectedType{
T: v,
Column: i,
}
}
+49
View File
@@ -0,0 +1,49 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Int8 struct{ base }
func (Int8) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Int8()
if err != nil {
return int8(0), err
}
return v, nil
}
func (i *Int8) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case int8:
return encoder.Int8(v)
case int64:
return encoder.Int8(int8(v))
case int:
return encoder.Int8(int8(v))
case bool:
if v {
return encoder.Int8(int8(1))
}
return encoder.Int8(int8(0))
// this relies on Nullable never sending nil values through
case *int8:
return encoder.Int8(*v)
case *int64:
return encoder.Int8(int8(*v))
case *int:
return encoder.Int8(int8(*v))
case *bool:
if *v {
return encoder.Int8(int8(1))
}
return encoder.Int8(int8(0))
}
return &ErrUnexpectedType{
T: v,
Column: i,
}
}
+84
View File
@@ -0,0 +1,84 @@
/*
IP type supporting for clickhouse as FixedString(16)
*/
package column
import (
"database/sql/driver"
"errors"
"net"
"strings"
)
var (
errInvalidScanType = errors.New("Invalid scan types")
errInvalidScanValue = errors.New("Invalid scan value")
)
// IP column type
type IP net.IP
// Value implements the driver.Valuer interface, json field interface
// Alignment on the right side
func (ip IP) Value() (driver.Value, error) {
return ip.MarshalBinary()
}
func (ip IP) MarshalBinary() ([]byte, error) {
if len(ip) < 16 {
var (
buff = make([]byte, 16)
j = 0
)
for i := 16 - len(ip); i < 16; i++ {
buff[i] = ip[j]
j++
}
for i := 0; i < 16-len(ip); i++ {
buff[i] = '\x00'
}
if len(ip) == 4 {
buff[11] = '\xff'
buff[10] = '\xff'
}
return buff, nil
}
return []byte(ip), nil
}
// Scan implements the driver.Valuer interface, json field interface
func (ip *IP) Scan(value interface{}) (err error) {
switch v := value.(type) {
case []byte:
if len(v) == 4 || len(v) == 16 {
*ip = IP(v)
} else {
err = errInvalidScanValue
}
case string:
if v == "" {
err = errInvalidScanValue
return
}
if (len(v) == 4 || len(v) == 16) && !strings.Contains(v, ".") && !strings.Contains(v, ":"){
*ip = IP([]byte(v))
return
}
if strings.Contains(v, ":") {
*ip = IP(net.ParseIP(v))
return
}
*ip = IP(net.ParseIP(v).To4())
case net.IP:
*ip = IP(v)
default:
err = errInvalidScanType
}
return
}
// String implements the fmt.Stringer interface
func (ip IP) String() string {
return net.IP(ip).String()
}
+54
View File
@@ -0,0 +1,54 @@
package column
import (
"net"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type IPv4 struct {
base
}
func (*IPv4) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Fixed(4)
if err != nil {
return nil, err
}
return net.IPv4(v[3], v[2], v[1], v[0]), nil
}
func (ip *IPv4) Write(encoder *binary.Encoder, v interface{}) error {
var netIP net.IP
switch v.(type) {
case string:
netIP = net.ParseIP(v.(string))
case net.IP:
netIP = v.(net.IP)
case *net.IP:
netIP = *(v.(*net.IP))
default:
return &ErrUnexpectedType{
T: v,
Column: ip,
}
}
if netIP == nil {
return &ErrUnexpectedType{
T: v,
Column: ip,
}
}
ip4 := netIP.To4()
if ip4 == nil {
return &ErrUnexpectedType{
T: v,
Column: ip,
}
}
if _, err := encoder.Write([]byte{ip4[3], ip4[2], ip4[1], ip4[0]}); err != nil {
return err
}
return nil
}
+47
View File
@@ -0,0 +1,47 @@
package column
import (
"net"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type IPv6 struct {
base
}
func (*IPv6) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.Fixed(16)
if err != nil {
return nil, err
}
return net.IP(v), nil
}
func (ip *IPv6) Write(encoder *binary.Encoder, v interface{}) error {
var netIP net.IP
switch v.(type) {
case string:
netIP = net.ParseIP(v.(string))
case net.IP:
netIP = v.(net.IP)
case *net.IP:
netIP = *(v.(*net.IP))
default:
return &ErrUnexpectedType{
T: v,
Column: ip,
}
}
if netIP == nil {
return &ErrUnexpectedType{
T: v,
Column: ip,
}
}
if _, err := encoder.Write([]byte(netIP.To16())); err != nil {
return err
}
return nil
}
+96
View File
@@ -0,0 +1,96 @@
package column
import (
"fmt"
"reflect"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type Nullable struct {
base
column Column
}
func (null *Nullable) ScanType() reflect.Type {
return reflect.PtrTo(null.column.ScanType())
}
func (null *Nullable) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
return null.column.Read(decoder, isNull)
}
func (null *Nullable) Write(encoder *binary.Encoder, v interface{}) error {
return nil
}
func (null *Nullable) ReadNull(decoder *binary.Decoder, rows int) (_ []interface{}, err error) {
var (
isNull byte
value interface{}
nulls = make([]byte, rows)
values = make([]interface{}, rows)
)
for i := 0; i < rows; i++ {
if isNull, err = decoder.ReadByte(); err != nil {
return nil, err
}
nulls[i] = isNull
}
for i, isNull := range nulls {
switch value, err = null.column.Read(decoder, isNull != 0); true {
case err != nil:
return nil, err
case isNull == 0:
values[i] = value
default:
values[i] = nil
}
}
return values, nil
}
func (null *Nullable) WriteNull(nulls, encoder *binary.Encoder, v interface{}) error {
if isNil(v) {
if _, err := nulls.Write([]byte{1}); err != nil {
return err
}
return null.column.Write(encoder, null.column.defaultValue())
}
if _, err := nulls.Write([]byte{0}); err != nil {
return err
}
return null.column.Write(encoder, v)
}
func parseNullable(name, chType string, timezone *time.Location) (*Nullable, error) {
if len(chType) < 14 {
return nil, fmt.Errorf("invalid Nullable column type: %s", chType)
}
column, err := Factory(name, chType[9:][:len(chType)-10], timezone)
if err != nil {
return nil, fmt.Errorf("Nullable(T): %v", err)
}
return &Nullable{
base: base{
name: name,
chType: chType,
},
column: column,
}, nil
}
func (null *Nullable) GetColumn() Column {
return null.column
}
func isNil(v interface{}) bool {
if v == nil {
return true
}
switch val := reflect.ValueOf(v); val.Type().Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Ptr, reflect.Slice:
return val.IsNil()
}
return false
}
@@ -0,0 +1,173 @@
// DANGER! This code was autogenerated from template by clickhouse-go/lib/codegen/nullable_appender.
// You shouldn't change it manually.
// For more info check clickhouse-go/lib/codegen/nullable_appender/main.go
package column
import (
"fmt"
"net"
"reflect"
"time"
)
var nullableAppender = map[string]func(v interface{}, slice reflect.Value) (reflect.Value, error){
"*int8": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(int8)
if !ok {
return slice, fmt.Errorf("cannot assert to type int8")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *int8
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*int16": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(int16)
if !ok {
return slice, fmt.Errorf("cannot assert to type int16")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *int16
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*int32": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(int32)
if !ok {
return slice, fmt.Errorf("cannot assert to type int32")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *int32
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*int64": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(int64)
if !ok {
return slice, fmt.Errorf("cannot assert to type int64")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *int64
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*uint8": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(uint8)
if !ok {
return slice, fmt.Errorf("cannot assert to type uint8")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *uint8
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*uint16": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(uint16)
if !ok {
return slice, fmt.Errorf("cannot assert to type uint16")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *uint16
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*uint32": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(uint32)
if !ok {
return slice, fmt.Errorf("cannot assert to type uint32")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *uint32
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*uint64": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(uint64)
if !ok {
return slice, fmt.Errorf("cannot assert to type uint64")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *uint64
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*float32": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(float32)
if !ok {
return slice, fmt.Errorf("cannot assert to type float32")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *float32
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*float64": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(float64)
if !ok {
return slice, fmt.Errorf("cannot assert to type float64")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *float64
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*string": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(string)
if !ok {
return slice, fmt.Errorf("cannot assert to type string")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *string
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*time.Time": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(time.Time)
if !ok {
return slice, fmt.Errorf("cannot assert to type time.Time")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *time.Time
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
"*net.IP": func(v interface{}, slice reflect.Value) (reflect.Value, error) {
if v != nil {
v, ok := v.(net.IP)
if !ok {
return slice, fmt.Errorf("cannot assert to type net.IP")
}
return reflect.Append(slice, reflect.ValueOf(&v)), nil
}
var vNil *net.IP
return reflect.Append(slice, reflect.ValueOf(vNil)), nil
},
}
+35
View File
@@ -0,0 +1,35 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type String struct{ base }
func (String) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.String()
if err != nil {
return "", err
}
return v, nil
}
func (str *String) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case string:
return encoder.String(v)
case []byte:
return encoder.RawString(v)
// this relies on Nullable never sending nil values through
case *string:
return encoder.String(*v)
case *[]byte:
return encoder.RawString(*v)
}
return &ErrUnexpectedType{
T: v,
Column: str,
}
}
+110
View File
@@ -0,0 +1,110 @@
package column
import (
"fmt"
"github.com/ClickHouse/clickhouse-go/lib/binary"
"reflect"
"strconv"
"time"
)
type Tuple struct {
base
columns []Column
}
func (tuple *Tuple) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
return nil, fmt.Errorf("do not use Read method for Tuple(T) column")
}
func (tuple *Tuple) ReadTuple(decoder *binary.Decoder, rows int) ([]interface{}, error) {
var values = make([][]interface{}, rows)
for _, c := range tuple.columns {
switch column := c.(type) {
case *Array:
cols, err := column.ReadArray(decoder, rows)
if err != nil {
return nil, err
}
for i := 0; i < rows; i++ {
values[i] = append(values[i], cols[i])
}
case *Nullable:
cols, err := column.ReadNull(decoder, rows)
if err != nil {
return nil, err
}
for i := 0; i < rows; i++ {
values[i] = append(values[i], cols[i])
}
case *Tuple:
cols, err := column.ReadTuple(decoder, rows)
if err != nil {
return nil, err
}
for i := 0; i < rows; i++ {
values[i] = append(values[i], cols[i])
}
default:
for i := 0; i < rows; i++ {
value, err := c.Read(decoder, false)
if err != nil {
return nil, err
}
values[i] = append(values[i], value)
}
}
}
var ret = make([]interface{}, rows)
for i := range values {
ret[i] = values[i]
}
return ret, nil
}
func (tuple *Tuple) Write(encoder *binary.Encoder, v interface{}) (err error) {
return fmt.Errorf("unsupported Tuple(T) type [%T]", v)
}
func parseTuple(name, chType string, timezone *time.Location) (Column, error) {
var columnType = chType
chType = chType[6 : len(chType)-1]
var types []string
var last, diff int
for i, b := range chType + "," {
if b == '(' {
diff++
} else if b == ')' {
diff--
} else if b == ',' && diff == 0 {
types = append(types, chType[last:i])
last = i + 2
}
}
var columns = make([]Column, 0, len(types))
for i, chType := range types {
column, err := Factory(name+"."+strconv.Itoa(i+1), chType, timezone)
if err != nil {
return nil, fmt.Errorf("%s: %v", chType, err)
}
columns = append(columns, column)
}
return &Tuple{
base: base{
name: name,
chType: columnType,
valueOf: reflect.ValueOf([]interface{}{}),
},
columns: columns,
}, nil
}
+43
View File
@@ -0,0 +1,43 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type UInt16 struct{ base }
func (UInt16) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.UInt16()
if err != nil {
return uint16(0), err
}
return v, nil
}
func (u *UInt16) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case uint16:
return encoder.UInt16(v)
case int64:
return encoder.UInt16(uint16(v))
case uint64:
return encoder.UInt16(uint16(v))
case int:
return encoder.UInt16(uint16(v))
// this relies on Nullable never sending nil values through
case *uint16:
return encoder.UInt16(*v)
case *int64:
return encoder.UInt16(uint16(*v))
case *uint64:
return encoder.UInt16(uint16(*v))
case *int:
return encoder.UInt16(uint16(*v))
}
return &ErrUnexpectedType{
T: v,
Column: u,
}
}
+43
View File
@@ -0,0 +1,43 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type UInt32 struct{ base }
func (UInt32) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.UInt32()
if err != nil {
return uint32(0), err
}
return v, nil
}
func (u *UInt32) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case uint32:
return encoder.UInt32(v)
case uint64:
return encoder.UInt32(uint32(v))
case int64:
return encoder.UInt32(uint32(v))
case int:
return encoder.UInt32(uint32(v))
// this relies on Nullable never sending nil values through
case *uint64:
return encoder.UInt32(uint32(*v))
case *uint32:
return encoder.UInt32(*v)
case *int64:
return encoder.UInt32(uint32(*v))
case *int:
return encoder.UInt32(uint32(*v))
}
return &ErrUnexpectedType{
T: v,
Column: u,
}
}
+44
View File
@@ -0,0 +1,44 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type UInt64 struct{ base }
func (UInt64) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.UInt64()
if err != nil {
return uint64(0), err
}
return v, nil
}
func (u *UInt64) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case []byte:
if _, err := encoder.Write(v); err != nil {
return err
}
return nil
case uint64:
return encoder.UInt64(v)
case int64:
return encoder.UInt64(uint64(v))
case int:
return encoder.UInt64(uint64(v))
// this relies on Nullable never sending nil values through
case *uint64:
return encoder.UInt64(*v)
case *int64:
return encoder.UInt64(uint64(*v))
case *int:
return encoder.UInt64(uint64(*v))
}
return &ErrUnexpectedType{
T: v,
Column: u,
}
}
+47
View File
@@ -0,0 +1,47 @@
package column
import (
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type UInt8 struct{ base }
func (UInt8) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
v, err := decoder.UInt8()
if err != nil {
return uint8(0), err
}
return v, nil
}
func (u *UInt8) Write(encoder *binary.Encoder, v interface{}) error {
switch v := v.(type) {
case bool:
return encoder.Bool(v)
case uint8:
return encoder.UInt8(v)
case int64:
return encoder.UInt8(uint8(v))
case uint64:
return encoder.UInt8(uint8(v))
case int:
return encoder.UInt8(uint8(v))
// this relies on Nullable never sending nil values through
case *bool:
return encoder.Bool(*v)
case *uint8:
return encoder.UInt8(*v)
case *int64:
return encoder.UInt8(uint8(*v))
case *uint64:
return encoder.UInt8(uint8(*v))
case *int:
return encoder.UInt8(uint8(*v))
}
return &ErrUnexpectedType{
T: v,
Column: u,
}
}
+139
View File
@@ -0,0 +1,139 @@
package column
import (
"encoding/hex"
"errors"
"fmt"
"reflect"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
const (
UUIDLen = 16
NullUUID = "00000000-0000-0000-0000-000000000000"
)
var ErrInvalidUUIDFormat = errors.New("invalid UUID format")
type UUID struct {
base
scanType reflect.Type
}
func (*UUID) Read(decoder *binary.Decoder, isNull bool) (interface{}, error) {
src, err := decoder.Fixed(UUIDLen)
if err != nil {
return "", err
}
src = swap(src)
var uuid [36]byte
{
hex.Encode(uuid[:], src[:4])
uuid[8] = '-'
hex.Encode(uuid[9:13], src[4:6])
uuid[13] = '-'
hex.Encode(uuid[14:18], src[6:8])
uuid[18] = '-'
hex.Encode(uuid[19:23], src[8:10])
uuid[23] = '-'
hex.Encode(uuid[24:], src[10:])
}
return string(uuid[:]), nil
}
func (u *UUID) Write(encoder *binary.Encoder, v interface{}) (err error) {
var uuid []byte
switch v := v.(type) {
case string:
if uuid, err = uuid2bytes(v); err != nil {
return err
}
case []byte:
if len(v) != UUIDLen {
return fmt.Errorf("invalid raw UUID len '%s' (expected %d, got %d)", uuid, UUIDLen, len(uuid))
}
uuid = make([]byte, 16)
copy(uuid, v)
default:
return &ErrUnexpectedType{
T: v,
Column: u,
}
}
uuid = swap(uuid)
if _, err := encoder.Write(uuid); err != nil {
return err
}
return nil
}
func swap(src []byte) []byte {
_ = src[15]
src[0], src[7] = src[7], src[0]
src[1], src[6] = src[6], src[1]
src[2], src[5] = src[5], src[2]
src[3], src[4] = src[4], src[3]
src[8], src[15] = src[15], src[8]
src[9], src[14] = src[14], src[9]
src[10], src[13] = src[13], src[10]
src[11], src[12] = src[12], src[11]
return src
}
func uuid2bytes(str string) ([]byte, error) {
var uuid [16]byte
strLength := len(str)
if strLength == 0 {
str = NullUUID
} else if strLength != 36 {
return nil, ErrInvalidUUIDFormat
}
if str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-' {
return nil, ErrInvalidUUIDFormat
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11, 14, 16,
19, 21, 24, 26,
28, 30, 32, 34,
} {
if v, ok := xtob(str[x], str[x+1]); !ok {
return nil, ErrInvalidUUIDFormat
} else {
uuid[i] = v
}
}
return uuid[:], nil
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts hex characters x1 and x2 into a byte.
func xtob(x1, x2 byte) (byte, bool) {
b1 := xvalues[x1]
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}
+326
View File
@@ -0,0 +1,326 @@
package data
import (
"bytes"
"database/sql/driver"
"fmt"
"io"
"reflect"
"strings"
"github.com/ClickHouse/clickhouse-go/lib/binary"
"github.com/ClickHouse/clickhouse-go/lib/column"
)
type offset [][]int
type Block struct {
Values [][]interface{}
Columns []column.Column
NumRows uint64
NumColumns uint64
offsets []offset
buffers []*buffer
info blockInfo
}
func (block *Block) Copy() *Block {
return &Block{
Columns: block.Columns,
NumColumns: block.NumColumns,
info: block.info,
}
}
func (block *Block) ColumnNames() []string {
names := make([]string, 0, len(block.Columns))
for _, column := range block.Columns {
names = append(names, column.Name())
}
return names
}
func (block *Block) Read(serverInfo *ServerInfo, decoder *binary.Decoder) (err error) {
if serverInfo.Revision > 0 {
if err = block.info.read(decoder); err != nil {
return err
}
}
if block.NumColumns, err = decoder.Uvarint(); err != nil {
return err
}
if block.NumRows, err = decoder.Uvarint(); err != nil {
return err
}
block.Values = make([][]interface{}, block.NumColumns)
if block.NumRows > 10 {
for i := 0; i < int(block.NumColumns); i++ {
block.Values[i] = make([]interface{}, 0, block.NumRows)
}
}
for i := 0; i < int(block.NumColumns); i++ {
var (
value interface{}
columnName string
columnType string
)
if columnName, err = decoder.String(); err != nil {
return err
}
if columnType, err = decoder.String(); err != nil {
return err
}
c, err := column.Factory(columnName, columnType, serverInfo.Timezone)
if err != nil {
return err
}
block.Columns = append(block.Columns, c)
switch column := c.(type) {
case *column.Array:
if block.Values[i], err = column.ReadArray(decoder, int(block.NumRows)); err != nil {
return err
}
case *column.Nullable:
if block.Values[i], err = column.ReadNull(decoder, int(block.NumRows)); err != nil {
return err
}
case *column.Tuple:
if block.Values[i], err = column.ReadTuple(decoder, int(block.NumRows)); err != nil {
return err
}
default:
for row := 0; row < int(block.NumRows); row++ {
if value, err = column.Read(decoder, false); err != nil {
return err
}
block.Values[i] = append(block.Values[i], value)
}
}
}
return nil
}
func (block *Block) writeArray(col column.Column, value Value, num, level int) error {
if level > col.Depth() {
arrColumn, ok := col.(*column.Array)
if ok && strings.Contains(col.CHType(), "Nullable") {
return arrColumn.WriteNull(block.buffers[num].Offset, block.buffers[num].Column, value.Interface())
}
return col.Write(block.buffers[num].Column, value.Interface())
}
switch {
case value.Kind() == reflect.Slice:
if len(block.offsets[num]) < level {
block.offsets[num] = append(block.offsets[num], []int{value.Len()})
} else {
block.offsets[num][level-1] = append(
block.offsets[num][level-1],
block.offsets[num][level-1][len(block.offsets[num][level-1])-1]+value.Len(),
)
}
for i := 0; i < value.Len(); i++ {
if err := block.writeArray(col, value.Index(i), num, level+1); err != nil {
return err
}
}
default:
if err := col.Write(block.buffers[num].Column, value.Interface()); err != nil {
return err
}
}
return nil
}
func (block *Block) AppendRow(args []driver.Value) error {
if len(block.Columns) != len(args) {
return fmt.Errorf("block: expected %d arguments (columns: %s), got %d", len(block.Columns), strings.Join(block.ColumnNames(), ", "), len(args))
}
block.Reserve()
{
block.NumRows++
}
for num, c := range block.Columns {
switch column := c.(type) {
case *column.Array:
if args[num] == nil {
return fmt.Errorf("unsupported [nil] value is passed in argument %d, column is not Nullable", num)
}
value := reflect.ValueOf(args[num])
if value.Kind() != reflect.Slice {
return fmt.Errorf("unsupported Array(T) type [%T]", value.Interface())
}
if err := block.writeArray(c, newValue(value), num, 1); err != nil {
return err
}
case *column.Nullable:
if err := column.WriteNull(block.buffers[num].Offset, block.buffers[num].Column, args[num]); err != nil {
return err
}
default:
if err := column.Write(block.buffers[num].Column, args[num]); err != nil {
return err
}
}
}
return nil
}
func (block *Block) Reserve() {
if len(block.buffers) == 0 {
block.buffers = make([]*buffer, len(block.Columns))
block.offsets = make([]offset, len(block.Columns))
for i := 0; i < len(block.Columns); i++ {
var (
offsetBuffer = new(bytes.Buffer)
columnBuffer = new(bytes.Buffer)
)
block.buffers[i] = &buffer{
Offset: binary.NewEncoder(offsetBuffer),
Column: binary.NewEncoder(columnBuffer),
offsetBuffer: offsetBuffer,
columnBuffer: columnBuffer,
}
}
}
}
func (block *Block) Reset() {
block.NumRows = 0
block.NumColumns = 0
block.Values = block.Values[:0]
block.Columns = block.Columns[:0]
block.info.reset()
for _, buffer := range block.buffers {
buffer.reset()
}
{
block.offsets = nil
block.buffers = nil
}
}
func (block *Block) Write(serverInfo *ServerInfo, encoder *binary.Encoder) error {
if serverInfo.Revision > 0 {
if err := block.info.write(encoder); err != nil {
return err
}
}
if err := encoder.Uvarint(block.NumColumns); err != nil {
return err
}
encoder.Uvarint(block.NumRows)
defer func() {
block.NumRows = 0
for i := range block.offsets {
block.offsets[i] = offset{}
}
}()
for i, column := range block.Columns {
encoder.String(column.Name())
encoder.String(column.CHType())
if len(block.buffers) == len(block.Columns) {
for _, offsets := range block.offsets[i] {
for _, offset := range offsets {
if err := encoder.UInt64(uint64(offset)); err != nil {
return err
}
}
}
if _, err := block.buffers[i].WriteTo(encoder); err != nil {
return err
}
}
}
return nil
}
type blockInfo struct {
num1 uint64
isOverflows bool
num2 uint64
bucketNum int32
num3 uint64
}
func (info *blockInfo) reset() {
info.num1 = 0
info.isOverflows = false
info.num2 = 0
info.bucketNum = 0
info.num3 = 0
}
func (info *blockInfo) read(decoder *binary.Decoder) error {
var err error
if info.num1, err = decoder.Uvarint(); err != nil {
return err
}
if info.isOverflows, err = decoder.Bool(); err != nil {
return err
}
if info.num2, err = decoder.Uvarint(); err != nil {
return err
}
if info.bucketNum, err = decoder.Int32(); err != nil {
return err
}
if info.num3, err = decoder.Uvarint(); err != nil {
return err
}
return nil
}
func (info *blockInfo) write(encoder *binary.Encoder) error {
if err := encoder.Uvarint(1); err != nil {
return err
}
if err := encoder.Bool(info.isOverflows); err != nil {
return err
}
if err := encoder.Uvarint(2); err != nil {
return err
}
if info.bucketNum == 0 {
info.bucketNum = -1
}
if err := encoder.Int32(info.bucketNum); err != nil {
return err
}
if err := encoder.Uvarint(0); err != nil {
return err
}
return nil
}
type buffer struct {
Offset *binary.Encoder
Column *binary.Encoder
offsetBuffer *bytes.Buffer
columnBuffer *bytes.Buffer
}
func (buf *buffer) WriteTo(w io.Writer) (int64, error) {
var size int64
{
ln, err := buf.offsetBuffer.WriteTo(w)
if err != nil {
return size, err
}
size += ln
}
{
ln, err := buf.columnBuffer.WriteTo(w)
if err != nil {
return size, err
}
size += ln
}
return size, nil
}
func (buf *buffer) reset() {
buf.offsetBuffer.Reset()
buf.columnBuffer.Reset()
}
@@ -0,0 +1,277 @@
package data
import (
"fmt"
"github.com/ClickHouse/clickhouse-go/lib/column"
"net"
"reflect"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
func (block *Block) WriteDate(c int, v time.Time) error {
_, offset := v.Zone()
nday := (v.Unix() + int64(offset)) / 24 / 3600
return block.buffers[c].Column.UInt16(uint16(nday))
}
func (block *Block) WriteDateNullable(c int, v *time.Time) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.UInt16(0)
}
return block.WriteDate(c, *v)
}
func (block *Block) WriteDateTime(c int, v time.Time) error {
return block.buffers[c].Column.UInt32(uint32(v.Unix()))
}
func (block *Block) WriteDateTimeNullable(c int, v *time.Time) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.UInt32(0)
}
return block.buffers[c].Column.UInt32(uint32(v.Unix()))
}
func (block *Block) WriteBool(c int, v bool) error {
if v {
return block.buffers[c].Column.UInt8(1)
}
return block.buffers[c].Column.UInt8(0)
}
func (block *Block) WriteBoolNullable(c int, v *bool) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil || !(*v) {
return block.buffers[c].Column.UInt8(0)
}
return block.buffers[c].Column.UInt8(1)
}
func (block *Block) WriteInt8(c int, v int8) error {
return block.buffers[c].Column.Int8(v)
}
func (block *Block) WriteInt8Nullable(c int, v *int8) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.Int8(0)
}
return block.buffers[c].Column.Int8(*v)
}
func (block *Block) WriteInt16(c int, v int16) error {
return block.buffers[c].Column.Int16(v)
}
func (block *Block) WriteInt16Nullable(c int, v *int16) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.Int16(0)
}
return block.buffers[c].Column.Int16(*v)
}
func (block *Block) WriteInt32(c int, v int32) error {
return block.buffers[c].Column.Int32(v)
}
func (block *Block) WriteInt32Nullable(c int, v *int32) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.Int32(0)
}
return block.buffers[c].Column.Int32(*v)
}
func (block *Block) WriteInt64(c int, v int64) error {
return block.buffers[c].Column.Int64(v)
}
func (block *Block) WriteInt64Nullable(c int, v *int64) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.Int64(0)
}
return block.buffers[c].Column.Int64(*v)
}
func (block *Block) WriteUInt8(c int, v uint8) error {
return block.buffers[c].Column.UInt8(v)
}
func (block *Block) WriteUInt8Nullable(c int, v *uint8) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.UInt8(0)
}
return block.buffers[c].Column.UInt8(*v)
}
func (block *Block) WriteUInt16(c int, v uint16) error {
return block.buffers[c].Column.UInt16(v)
}
func (block *Block) WriteUInt16Nullable(c int, v *uint16) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.UInt16(0)
}
return block.buffers[c].Column.UInt16(*v)
}
func (block *Block) WriteUInt32(c int, v uint32) error {
return block.buffers[c].Column.UInt32(v)
}
func (block *Block) WriteUInt32Nullable(c int, v *uint32) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.UInt32(0)
}
return block.buffers[c].Column.UInt32(*v)
}
func (block *Block) WriteUInt64(c int, v uint64) error {
return block.buffers[c].Column.UInt64(v)
}
func (block *Block) WriteUInt64Nullable(c int, v *uint64) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.UInt64(0)
}
return block.buffers[c].Column.UInt64(*v)
}
func (block *Block) WriteFloat32(c int, v float32) error {
return block.buffers[c].Column.Float32(v)
}
func (block *Block) WriteFloat32Nullable(c int, v *float32) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.Float32(0)
}
return block.buffers[c].Column.Float32(*v)
}
func (block *Block) WriteFloat64(c int, v float64) error {
return block.buffers[c].Column.Float64(v)
}
func (block *Block) WriteFloat64Nullable(c int, v *float64) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.buffers[c].Column.Float64(0)
}
return block.buffers[c].Column.Float64(*v)
}
func (block *Block) WriteBytes(c int, v []byte) error {
if err := block.buffers[c].Column.Uvarint(uint64(len(v))); err != nil {
return err
}
if _, err := block.buffers[c].Column.Write(v); err != nil {
return err
}
return nil
}
func (block *Block) WriteBytesNullable(c int, v *[]byte) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.WriteBytes(c, []byte{})
}
return block.WriteBytes(c, *v)
}
func (block *Block) WriteString(c int, v string) error {
if err := block.buffers[c].Column.Uvarint(uint64(len(v))); err != nil {
return err
}
if _, err := block.buffers[c].Column.Write(binary.Str2Bytes(v)); err != nil {
return err
}
return nil
}
func (block *Block) WriteStringNullable(c int, v *string) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.WriteString(c, "")
}
return block.WriteString(c, *v)
}
func (block *Block) WriteFixedString(c int, v []byte) error {
return block.Columns[c].Write(block.buffers[c].Column, v)
}
func (block *Block) WriteFixedStringNullable(c int, v *[]byte) error {
writer := block.Columns[c].(*column.Nullable)
return writer.WriteNull(block.buffers[c].Offset, block.buffers[c].Column, v)
}
func (block *Block) WriteIP(c int, v net.IP) error {
return block.Columns[c].Write(block.buffers[c].Column, v)
}
func (block *Block) WriteIPNullable(c int, v net.IP) error {
writer := block.Columns[c].(*column.Nullable)
return writer.WriteNull(block.buffers[c].Offset, block.buffers[c].Column, v)
}
func (block *Block) WriteArray(c int, v interface{}) error {
return block.WriteArrayWithValue(c, newValue(reflect.ValueOf(v)))
}
func (block *Block) WriteArrayWithValue(c int, value Value) error {
if value.Kind() != reflect.Slice {
return fmt.Errorf("unsupported Array(T) type [%T]", value.Interface())
}
return block.writeArray(block.Columns[c], value, c, 1)
}
func (block *Block) WriteArrayNullable(c int, v *interface{}) error {
if err := block.buffers[c].Offset.Bool(v == nil); err != nil {
return err
}
if v == nil {
return block.Columns[c].Write(block.buffers[c].Column, []string{})
}
return block.WriteArray(c, *v)
}
+29
View File
@@ -0,0 +1,29 @@
package data
import (
"fmt"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
const ClientName = "Golang SQLDriver"
const (
ClickHouseRevision = 54213
ClickHouseDBMSVersionMajor = 1
ClickHouseDBMSVersionMinor = 1
)
type ClientInfo struct{}
func (ClientInfo) Write(encoder *binary.Encoder) error {
encoder.String(ClientName)
encoder.Uvarint(ClickHouseDBMSVersionMajor)
encoder.Uvarint(ClickHouseDBMSVersionMinor)
encoder.Uvarint(ClickHouseRevision)
return nil
}
func (ClientInfo) String() string {
return fmt.Sprintf("%s %d.%d.%d", ClientName, ClickHouseDBMSVersionMajor, ClickHouseDBMSVersionMinor, ClickHouseRevision)
}
+47
View File
@@ -0,0 +1,47 @@
package data
import (
"fmt"
//"io"
"time"
"github.com/ClickHouse/clickhouse-go/lib/binary"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
)
type ServerInfo struct {
Name string
Revision uint64
MinorVersion uint64
MajorVersion uint64
Timezone *time.Location
}
func (srv *ServerInfo) Read(decoder *binary.Decoder) (err error) {
if srv.Name, err = decoder.String(); err != nil {
return fmt.Errorf("could not read server name: %v", err)
}
if srv.MajorVersion, err = decoder.Uvarint(); err != nil {
return fmt.Errorf("could not read server major version: %v", err)
}
if srv.MinorVersion, err = decoder.Uvarint(); err != nil {
return fmt.Errorf("could not read server minor version: %v", err)
}
if srv.Revision, err = decoder.Uvarint(); err != nil {
return fmt.Errorf("could not read server revision: %v", err)
}
if srv.Revision >= protocol.DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE {
timezone, err := decoder.String()
if err != nil {
return fmt.Errorf("could not read server timezone: %v", err)
}
if srv.Timezone, err = time.LoadLocation(timezone); err != nil {
return fmt.Errorf("could not load time location: %v", err)
}
}
return nil
}
func (srv ServerInfo) String() string {
return fmt.Sprintf("%s %d.%d.%d (%s)", srv.Name, srv.MajorVersion, srv.MinorVersion, srv.Revision, srv.Timezone)
}
+33
View File
@@ -0,0 +1,33 @@
package data
import "reflect"
// Value is a writable value.
type Value interface {
// Kind returns value's Kind.
Kind() reflect.Kind
// Len returns value's length.
// It panics if value's Kind is not Array, Chan, Map, Slice, or String.
Len() int
// Index returns value's i'th element.
// It panics if value's Kind is not Array, Slice, or String or i is out of range.
Index(i int) Value
// Interface returns value's current value as an interface{}.
Interface() interface{}
}
// value is a wrapper that wraps reflect.Value to comply with Value interface.
type value struct {
reflect.Value
}
func newValue(v reflect.Value) Value {
return value{Value: v}
}
func (v value) Index(i int) Value {
return newValue(v.Value.Index(i))
}
+23
View File
@@ -0,0 +1,23 @@
Copyright 2011-2012 Branimir Karadzic. All rights reserved.
Copyright 2013 Damian Gryski. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
+7
View File
@@ -0,0 +1,7 @@
// Copyright 2011-2012 Branimir Karadzic. All rights reserved.
// Copyright 2013 Damian Gryski. All rights reserved.
// @LINK: https://github.com/bkaradzic/go-lz4
// @NOTE: The code is modified to be high performance and less memory usage
package lz4
+23
View File
@@ -0,0 +1,23 @@
// +build gofuzz
package lz4
import "encoding/binary"
func Fuzz(data []byte) int {
if len(data) < 4 {
return 0
}
ln := binary.LittleEndian.Uint32(data)
if ln > (1 << 21) {
return 0
}
if _, err := Decode(nil, data); err != nil {
return 0
}
return 1
}
+179
View File
@@ -0,0 +1,179 @@
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package lz4
import (
"errors"
"io"
)
var (
// ErrCorrupt indicates the input was corrupt
ErrCorrupt = errors.New("corrupt input")
)
const (
mlBits = 4
mlMask = (1 << mlBits) - 1
runBits = 8 - mlBits
runMask = (1 << runBits) - 1
)
type decoder struct {
src []byte
dst []byte
spos uint32
dpos uint32
ref uint32
}
func (d *decoder) readByte() (uint8, error) {
if int(d.spos) == len(d.src) {
return 0, io.EOF
}
b := d.src[d.spos]
d.spos++
return b, nil
}
func (d *decoder) getLen() (uint32, error) {
length := uint32(0)
ln, err := d.readByte()
if err != nil {
return 0, ErrCorrupt
}
for ln == 255 {
length += 255
ln, err = d.readByte()
if err != nil {
return 0, ErrCorrupt
}
}
length += uint32(ln)
return length, nil
}
func (d *decoder) cp(length, decr uint32) {
if int(d.ref+length) < int(d.dpos) {
copy(d.dst[d.dpos:], d.dst[d.ref:d.ref+length])
} else {
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.dst[d.ref+ii]
}
}
d.dpos += length
d.ref += length - decr
}
func (d *decoder) finish(err error) error {
if err == io.EOF {
return nil
}
return err
}
// Decode returns the decoded form of src. The returned slice may be a
// subslice of dst if it was large enough to hold the entire decoded block.
func Decode(dst, src []byte) (int, error) {
d := decoder{src: src, dst: dst, spos: 0}
decr := []uint32{0, 3, 2, 3}
for {
code, err := d.readByte()
if err != nil {
return len(d.dst), d.finish(err)
}
length := uint32(code >> mlBits)
if length == runMask {
ln, err := d.getLen()
if err != nil {
return 0, ErrCorrupt
}
length += ln
}
if int(d.spos+length) > len(d.src) || int(d.dpos+length) > len(d.dst) {
return 0, ErrCorrupt
}
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.src[d.spos+ii]
}
d.spos += length
d.dpos += length
if int(d.spos) == len(d.src) {
return len(d.dst), nil
}
if int(d.spos+2) >= len(d.src) {
return 0, ErrCorrupt
}
back := uint32(d.src[d.spos]) | uint32(d.src[d.spos+1])<<8
if back > d.dpos {
return 0, ErrCorrupt
}
d.spos += 2
d.ref = d.dpos - back
length = uint32(code & mlMask)
if length == mlMask {
ln, err := d.getLen()
if err != nil {
return 0, ErrCorrupt
}
length += ln
}
literal := d.dpos - d.ref
if literal < 4 {
if int(d.dpos+4) > len(d.dst) {
return 0, ErrCorrupt
}
d.cp(4, decr[literal])
} else {
length += 4
}
if int(d.dpos+length) > len(d.dst) {
return 0, ErrCorrupt
}
d.cp(length, 0)
}
}
+203
View File
@@ -0,0 +1,203 @@
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package lz4
import (
"errors"
"sync"
)
const (
minMatch = 4
hashLog = 16
hashTableSize = 1 << hashLog
hashShift = (minMatch * 8) - hashLog
incompressible uint32 = 128
uninitHash = 0x88888888
mfLimit = 8 + minMatch // The last match cannot start within the last 12 bytes.
// MaxInputSize is the largest buffer than can be compressed in a single block
MaxInputSize = 0x7E000000
)
var (
// ErrTooLarge indicates the input buffer was too large
ErrTooLarge = errors.New("input too large")
ErrEncodeTooSmall = errors.New("encode buffer too small")
hashPool = sync.Pool{
New: func() interface{} {
return make([]uint32, hashTableSize)
},
}
)
type encoder struct {
src []byte
dst []byte
hashTable []uint32
pos uint32
anchor uint32
dpos uint32
}
// CompressBound returns the maximum length of a lz4 block
func CompressBound(isize int) int {
if isize > MaxInputSize {
return 0
}
return isize + ((isize) / 255) + 16
}
func (e *encoder) writeLiterals(length, mlLen, pos uint32) {
ln := length
var code byte
if ln > runMask-1 {
code = runMask
} else {
code = byte(ln)
}
if mlLen > mlMask-1 {
e.dst[e.dpos] = (code << mlBits) + byte(mlMask)
} else {
e.dst[e.dpos] = (code << mlBits) + byte(mlLen)
}
e.dpos++
if code == runMask {
ln -= runMask
for ; ln > 254; ln -= 255 {
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(ln)
e.dpos++
}
for ii := uint32(0); ii < length; ii++ {
e.dst[e.dpos+ii] = e.src[pos+ii]
}
e.dpos += length
}
// Encode returns the encoded form of src. The returned array may be a
// sub-slice of dst if it was large enough to hold the entire output.
func Encode(dst, src []byte) (compressedSize int, error error) {
if len(src) >= MaxInputSize {
return 0, ErrTooLarge
}
if n := CompressBound(len(src)); len(dst) < n {
return 0, ErrEncodeTooSmall
}
hashTable := hashPool.Get().([]uint32)
for i := range hashTable {
hashTable[i] = 0
}
e := encoder{src: src, dst: dst, hashTable: hashTable}
defer func() {
hashPool.Put(hashTable)
}()
// binary.LittleEndian.PutUint32(dst, uint32(len(src)))
// e.dpos = 0
var (
step uint32 = 1
limit = incompressible
)
for {
if int(e.pos)+12 >= len(e.src) {
e.writeLiterals(uint32(len(e.src))-e.anchor, 0, e.anchor)
return int(e.dpos), nil
}
sequence := uint32(e.src[e.pos+3])<<24 | uint32(e.src[e.pos+2])<<16 | uint32(e.src[e.pos+1])<<8 | uint32(e.src[e.pos+0])
hash := (sequence * 2654435761) >> hashShift
ref := e.hashTable[hash] + uninitHash
e.hashTable[hash] = e.pos - uninitHash
if ((e.pos-ref)>>16) != 0 || uint32(e.src[ref+3])<<24|uint32(e.src[ref+2])<<16|uint32(e.src[ref+1])<<8|uint32(e.src[ref+0]) != sequence {
if e.pos-e.anchor > limit {
limit <<= 1
step += 1 + (step >> 2)
}
e.pos += step
continue
}
if step > 1 {
e.hashTable[hash] = ref - uninitHash
e.pos -= step - 1
step = 1
continue
}
limit = incompressible
ln := e.pos - e.anchor
back := e.pos - ref
anchor := e.anchor
e.pos += minMatch
ref += minMatch
e.anchor = e.pos
for int(e.pos) < len(e.src)-5 && e.src[e.pos] == e.src[ref] {
e.pos++
ref++
}
mlLen := e.pos - e.anchor
e.writeLiterals(ln, mlLen, anchor)
e.dst[e.dpos] = uint8(back)
e.dst[e.dpos+1] = uint8(back >> 8)
e.dpos += 2
if mlLen > mlMask-1 {
mlLen -= mlMask
for mlLen > 254 {
mlLen -= 255
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(mlLen)
e.dpos++
}
e.anchor = e.pos
}
}
+4
View File
@@ -0,0 +1,4 @@
# ClickHouse Native protocol
# Handshake
+35
View File
@@ -0,0 +1,35 @@
package protocol
const (
DBMS_MIN_REVISION_WITH_SERVER_TIMEZONE = 54058
DBMS_MIN_REVISION_WITH_QUOTA_KEY_IN_CLIENT_INFO = 54060
)
const (
ClientHello = 0
ClientQuery = 1
ClientData = 2
ClientCancel = 3
ClientPing = 4
)
const (
CompressEnable uint64 = 1
CompressDisable uint64 = 0
)
const (
StateComplete = 2
)
const (
ServerHello = 0
ServerData = 1
ServerException = 2
ServerProgress = 3
ServerPong = 4
ServerEndOfStream = 5
ServerProfileInfo = 6
ServerTotals = 7
ServerExtremes = 8
)
+48
View File
@@ -0,0 +1,48 @@
// Timezoneless date/datetime types
package types
import (
"database/sql/driver"
"time"
)
// Truncate timezone
//
// clickhouse.Date(time.Date(2017, 1, 1, 0, 0, 0, 0, time.Local)) -> time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC)
type Date time.Time
func (date Date) Value() (driver.Value, error) {
return date.convert(), nil
}
func (date Date) convert() time.Time {
return time.Date(time.Time(date).Year(), time.Time(date).Month(), time.Time(date).Day(), 0, 0, 0, 0, time.UTC)
}
// Truncate timezone
//
// clickhouse.DateTime(time.Date(2017, 1, 1, 0, 0, 0, 0, time.Local)) -> time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC)
type DateTime time.Time
func (datetime DateTime) Value() (driver.Value, error) {
return datetime.convert(), nil
}
func (datetime DateTime) convert() time.Time {
return time.Date(
time.Time(datetime).Year(),
time.Time(datetime).Month(),
time.Time(datetime).Day(),
time.Time(datetime).Hour(),
time.Time(datetime).Minute(),
time.Time(datetime).Second(),
1,
time.UTC,
)
}
var (
_ driver.Valuer = Date{}
_ driver.Valuer = DateTime{}
)
+99
View File
@@ -0,0 +1,99 @@
package types
import (
"database/sql/driver"
"encoding/hex"
"errors"
"fmt"
)
var InvalidUUIDFormatError = errors.New("invalid UUID format")
// this type will be deprecated because the ClickHouse server (>=1.1.54276) has a built-in type UUID
type UUID string
func (str UUID) Value() (driver.Value, error) {
return uuid2bytes(string(str))
}
func (str UUID) MarshalBinary() ([]byte, error) {
return uuid2bytes(string(str))
}
func (str *UUID) Scan(v interface{}) error {
var src []byte
switch v := v.(type) {
case string:
src = []byte(v)
case []byte:
src = v
}
if len(src) != 16 {
return fmt.Errorf("invalid UUID length: %d", len(src))
}
var uuid [36]byte
{
hex.Encode(uuid[:], src[:4])
uuid[8] = '-'
hex.Encode(uuid[9:13], src[4:6])
uuid[13] = '-'
hex.Encode(uuid[14:18], src[6:8])
uuid[18] = '-'
hex.Encode(uuid[19:23], src[8:10])
uuid[23] = '-'
hex.Encode(uuid[24:], src[10:])
}
*str = UUID(uuid[:])
return nil
}
func uuid2bytes(str string) ([]byte, error) {
var uuid [16]byte
if str[8] != '-' || str[13] != '-' || str[18] != '-' || str[23] != '-' {
return nil, InvalidUUIDFormatError
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11, 14, 16,
19, 21, 24, 26,
28, 30, 32, 34,
} {
if v, ok := xtob(str[x], str[x+1]); !ok {
return nil, InvalidUUIDFormatError
} else {
uuid[i] = v
}
}
return uuid[:], nil
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts hex characters x1 and x2 into a byte.
func xtob(x1, x2 byte) (byte, bool) {
b1 := xvalues[x1]
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}
var _ driver.Valuer = UUID("")
+286
View File
@@ -0,0 +1,286 @@
package clickhouse
import (
"fmt"
"net/url"
"strconv"
"github.com/ClickHouse/clickhouse-go/lib/binary"
)
type querySettingType int
// all possible query setting's data type
const (
uintQS querySettingType = iota + 1
intQS
boolQS
timeQS
)
// description of single query setting
type querySettingInfo struct {
name string
qsType querySettingType
}
// all possible query settings
var querySettingList = []querySettingInfo{
{"min_compress_block_size", uintQS},
{"max_compress_block_size", uintQS},
{"max_block_size", uintQS},
{"max_insert_block_size", uintQS},
{"min_insert_block_size_rows", uintQS},
{"min_insert_block_size_bytes", uintQS},
{"max_read_buffer_size", uintQS},
{"max_distributed_connections", uintQS},
{"max_query_size", uintQS},
{"interactive_delay", uintQS},
{"poll_interval", uintQS},
{"distributed_connections_pool_size", uintQS},
{"connections_with_failover_max_tries", uintQS},
{"background_pool_size", uintQS},
{"background_schedule_pool_size", uintQS},
{"replication_alter_partitions_sync", uintQS},
{"replication_alter_columns_timeout", uintQS},
{"min_count_to_compile", uintQS},
{"min_count_to_compile_expression", uintQS},
{"group_by_two_level_threshold", uintQS},
{"group_by_two_level_threshold_bytes", uintQS},
{"aggregation_memory_efficient_merge_threads", uintQS},
{"max_parallel_replicas", uintQS},
{"parallel_replicas_count", uintQS},
{"parallel_replica_offset", uintQS},
{"merge_tree_min_rows_for_concurrent_read", uintQS},
{"merge_tree_min_bytes_for_concurrent_read", uintQS},
{"merge_tree_min_rows_for_seek", uintQS},
{"merge_tree_min_bytes_for_seek", uintQS},
{"merge_tree_coarse_index_granularity", uintQS},
{"merge_tree_max_rows_to_use_cache", uintQS},
{"merge_tree_max_bytes_to_use_cache", uintQS},
{"mysql_max_rows_to_insert", uintQS},
{"optimize_min_equality_disjunction_chain_length", uintQS},
{"min_bytes_to_use_direct_io", uintQS},
{"mark_cache_min_lifetime", uintQS},
{"priority", uintQS},
{"log_queries_cut_to_length", uintQS},
{"max_concurrent_queries_for_user", uintQS},
{"insert_quorum", uintQS},
{"select_sequential_consistency", uintQS},
{"table_function_remote_max_addresses", uintQS},
{"read_backoff_max_throughput", uintQS},
{"read_backoff_min_events", uintQS},
{"output_format_pretty_max_rows", uintQS},
{"output_format_pretty_max_column_pad_width", uintQS},
{"output_format_parquet_row_group_size", uintQS},
{"http_headers_progress_interval_ms", uintQS},
{"input_format_allow_errors_num", uintQS},
{"preferred_block_size_bytes", uintQS},
{"max_replica_delay_for_distributed_queries", uintQS},
{"preferred_max_column_in_block_size_bytes", uintQS},
{"insert_distributed_timeout", uintQS},
{"odbc_max_field_size", uintQS},
{"max_rows_to_read", uintQS},
{"max_bytes_to_read", uintQS},
{"max_rows_to_group_by", uintQS},
{"max_bytes_before_external_group_by", uintQS},
{"max_rows_to_sort", uintQS},
{"max_bytes_to_sort", uintQS},
{"max_bytes_before_external_sort", uintQS},
{"max_bytes_before_remerge_sort", uintQS},
{"max_result_rows", uintQS},
{"max_result_bytes", uintQS},
{"min_execution_speed", uintQS},
{"max_execution_speed", uintQS},
{"min_execution_speed_bytes", uintQS},
{"max_execution_speed_bytes", uintQS},
{"max_columns_to_read", uintQS},
{"max_temporary_columns", uintQS},
{"max_temporary_non_const_columns", uintQS},
{"max_subquery_depth", uintQS},
{"max_pipeline_depth", uintQS},
{"max_ast_depth", uintQS},
{"max_ast_elements", uintQS},
{"max_expanded_ast_elements", uintQS},
{"readonly", uintQS},
{"max_rows_in_set", uintQS},
{"max_bytes_in_set", uintQS},
{"max_rows_in_join", uintQS},
{"max_bytes_in_join", uintQS},
{"max_rows_to_transfer", uintQS},
{"max_bytes_to_transfer", uintQS},
{"max_rows_in_distinct", uintQS},
{"max_bytes_in_distinct", uintQS},
{"max_memory_usage", uintQS},
{"max_memory_usage_for_user", uintQS},
{"max_memory_usage_for_all_queries", uintQS},
{"max_network_bandwidth", uintQS},
{"max_network_bytes", uintQS},
{"max_network_bandwidth_for_user", uintQS},
{"max_network_bandwidth_for_all_users", uintQS},
{"low_cardinality_max_dictionary_size", uintQS},
{"max_fetch_partition_retries_count", uintQS},
{"http_max_multipart_form_data_size", uintQS},
{"max_partitions_per_insert_block", uintQS},
{"max_threads", uintQS},
{"optimize_skip_unused_shards_nesting", uintQS},
{"force_optimize_skip_unused_shards", uintQS},
{"force_optimize_skip_unused_shards_nesting", uintQS},
{"network_zstd_compression_level", intQS},
{"http_zlib_compression_level", intQS},
{"distributed_ddl_task_timeout", intQS},
{"extremes", boolQS},
{"use_uncompressed_cache", boolQS},
{"replace_running_query", boolQS},
{"distributed_directory_monitor_batch_inserts", boolQS},
{"optimize_move_to_prewhere", boolQS},
{"compile", boolQS},
{"allow_suspicious_low_cardinality_types", boolQS},
{"compile_expressions", boolQS},
{"distributed_aggregation_memory_efficient", boolQS},
{"skip_unavailable_shards", boolQS},
{"distributed_group_by_no_merge", boolQS},
{"optimize_skip_unused_shards", boolQS},
{"merge_tree_uniform_read_distribution", boolQS},
{"force_index_by_date", boolQS},
{"force_primary_key", boolQS},
{"log_queries", boolQS},
{"insert_deduplicate", boolQS},
{"enable_http_compression", boolQS},
{"http_native_compression_disable_checksumming_on_decompress", boolQS},
{"output_format_write_statistics", boolQS},
{"add_http_cors_header", boolQS},
{"input_format_skip_unknown_fields", boolQS},
{"input_format_with_names_use_header", boolQS},
{"input_format_import_nested_json", boolQS},
{"input_format_defaults_for_omitted_fields", boolQS},
{"input_format_values_interpret_expressions", boolQS},
{"output_format_json_quote_64bit_integers", boolQS},
{"output_format_json_quote_denormals", boolQS},
{"output_format_json_escape_forward_slashes", boolQS},
{"output_format_pretty_color", boolQS},
{"use_client_time_zone", boolQS},
{"send_progress_in_http_headers", boolQS},
{"fsync_metadata", boolQS},
{"join_use_nulls", boolQS},
{"fallback_to_stale_replicas_for_distributed_queries", boolQS},
{"insert_distributed_sync", boolQS},
{"insert_allow_materialized_columns", boolQS},
{"optimize_throw_if_noop", boolQS},
{"use_index_for_in_with_subqueries", boolQS},
{"empty_result_for_aggregation_by_empty_set", boolQS},
{"allow_distributed_ddl", boolQS},
{"join_any_take_last_row", boolQS},
{"format_csv_allow_single_quotes", boolQS},
{"format_csv_allow_double_quotes", boolQS},
{"log_profile_events", boolQS},
{"log_query_settings", boolQS},
{"log_query_threads", boolQS},
{"enable_optimize_predicate_expression", boolQS},
{"low_cardinality_use_single_dictionary_for_part", boolQS},
{"decimal_check_overflow", boolQS},
{"prefer_localhost_replica", boolQS},
//{"asterisk_left_columns_only", boolQS},
{"calculate_text_stack_trace", boolQS},
{"allow_ddl", boolQS},
{"parallel_view_processing", boolQS},
{"enable_debug_queries", boolQS},
{"enable_unaligned_array_join", boolQS},
{"low_cardinality_allow_in_native_format", boolQS},
{"allow_experimental_multiple_joins_emulation", boolQS},
{"allow_experimental_cross_to_join_conversion", boolQS},
{"cancel_http_readonly_queries_on_client_close", boolQS},
{"external_table_functions_use_nulls", boolQS},
{"allow_experimental_data_skipping_indices", boolQS},
{"allow_hyperscan", boolQS},
{"allow_simdjson", boolQS},
{"connect_timeout", timeQS},
{"connect_timeout_with_failover_ms", timeQS},
{"receive_timeout", timeQS},
{"send_timeout", timeQS},
{"tcp_keep_alive_timeout", timeQS},
{"queue_max_wait_ms", timeQS},
{"distributed_directory_monitor_sleep_time_ms", timeQS},
{"insert_quorum_timeout", timeQS},
{"read_backoff_min_latency_ms", timeQS},
{"read_backoff_min_interval_between_events_ms", timeQS},
{"stream_flush_interval_ms", timeQS},
{"stream_poll_timeout_ms", timeQS},
{"http_connection_timeout", timeQS},
{"http_send_timeout", timeQS},
{"http_receive_timeout", timeQS},
{"max_execution_time", timeQS},
{"timeout_before_checking_execution_speed", timeQS},
}
type querySettingValueEncoder func(enc *binary.Encoder) error
type querySettings struct {
settings map[string]querySettingValueEncoder
settingsStr string // used for debug output
}
func makeQuerySettings(query url.Values) (*querySettings, error) {
qs := &querySettings{
settings: make(map[string]querySettingValueEncoder),
settingsStr: "",
}
for _, info := range querySettingList {
valueStr := query.Get(info.name)
if valueStr == "" {
continue
}
switch info.qsType {
case uintQS, intQS, timeQS:
value, err := strconv.ParseUint(valueStr, 10, 64)
if err != nil {
return nil, err
}
qs.settings[info.name] = func(enc *binary.Encoder) error { return enc.Uvarint(value) }
case boolQS:
valueBool, err := strconv.ParseBool(valueStr)
if err != nil {
return nil, err
}
value := uint64(0)
if valueBool {
value = 1
}
qs.settings[info.name] = func(enc *binary.Encoder) error { return enc.Uvarint(value) }
default:
err := fmt.Errorf("query setting %s has unsupported data type", info.name)
return nil, err
}
if qs.settingsStr != "" {
qs.settingsStr += "&"
}
qs.settingsStr += info.name + "=" + valueStr
}
return qs, nil
}
func (qs *querySettings) IsEmpty() bool {
return len(qs.settings) == 0
}
func (qs *querySettings) Serialize(enc *binary.Encoder) error {
for name, fn := range qs.settings {
if err := enc.String(name); err != nil {
return err
}
if err := fn(enc); err != nil {
return err
}
}
return nil
}
+8
View File
@@ -0,0 +1,8 @@
package clickhouse
import "errors"
type result struct{}
func (*result) LastInsertId() (int64, error) { return 0, errors.New("LastInsertId is not supported") }
func (*result) RowsAffected() (int64, error) { return 0, errors.New("RowsAffected is not supported") }
+182
View File
@@ -0,0 +1,182 @@
package clickhouse
import (
"database/sql/driver"
"fmt"
"io"
"reflect"
"sync"
"time"
"github.com/ClickHouse/clickhouse-go/lib/column"
"github.com/ClickHouse/clickhouse-go/lib/data"
"github.com/ClickHouse/clickhouse-go/lib/protocol"
)
type rows struct {
ch *clickhouse
err error
mutex sync.RWMutex
finish func()
offset int
block *data.Block
totals *data.Block
extremes *data.Block
stream chan *data.Block
columns []string
blockColumns []column.Column
}
func (rows *rows) Columns() []string {
return rows.columns
}
func (rows *rows) ColumnTypeScanType(idx int) reflect.Type {
return rows.blockColumns[idx].ScanType()
}
func (rows *rows) ColumnTypeDatabaseTypeName(idx int) string {
return rows.blockColumns[idx].CHType()
}
func (rows *rows) Next(dest []driver.Value) error {
if rows.block == nil || int(rows.block.NumRows) <= rows.offset {
switch block, ok := <-rows.stream; true {
case !ok:
if err := rows.error(); err != nil {
return err
}
return io.EOF
default:
rows.block = block
rows.offset = 0
}
}
for i := range dest {
dest[i] = rows.block.Values[i][rows.offset]
}
rows.offset++
return nil
}
func (rows *rows) HasNextResultSet() bool {
return rows.totals != nil || rows.extremes != nil
}
func (rows *rows) NextResultSet() error {
switch {
case rows.totals != nil:
rows.block = rows.totals
rows.offset = 0
rows.totals = nil
case rows.extremes != nil:
rows.block = rows.extremes
rows.offset = 0
rows.extremes = nil
default:
return io.EOF
}
return nil
}
func (rows *rows) receiveData() error {
defer close(rows.stream)
var (
err error
packet uint64
progress *progress
profileInfo *profileInfo
)
for {
if packet, err = rows.ch.decoder.Uvarint(); err != nil {
return rows.setError(err)
}
switch packet {
case protocol.ServerException:
rows.ch.logf("[rows] <- exception")
return rows.setError(rows.ch.exception())
case protocol.ServerProgress:
if progress, err = rows.ch.progress(); err != nil {
return rows.setError(err)
}
rows.ch.logf("[rows] <- progress: rows=%d, bytes=%d, total rows=%d",
progress.rows,
progress.bytes,
progress.totalRows,
)
case protocol.ServerProfileInfo:
if profileInfo, err = rows.ch.profileInfo(); err != nil {
return rows.setError(err)
}
rows.ch.logf("[rows] <- profiling: rows=%d, bytes=%d, blocks=%d", profileInfo.rows, profileInfo.bytes, profileInfo.blocks)
case protocol.ServerData, protocol.ServerTotals, protocol.ServerExtremes:
var (
block *data.Block
begin = time.Now()
)
if block, err = rows.ch.readBlock(); err != nil {
return rows.setError(err)
}
rows.ch.logf("[rows] <- data: packet=%d, columns=%d, rows=%d, elapsed=%s", packet, block.NumColumns, block.NumRows, time.Since(begin))
if block.NumRows == 0 {
continue
}
switch packet {
case protocol.ServerData:
rows.stream <- block
case protocol.ServerTotals:
rows.totals = block
case protocol.ServerExtremes:
rows.extremes = block
}
case protocol.ServerEndOfStream:
rows.ch.logf("[rows] <- end of stream")
return nil
default:
rows.ch.conn.Close()
rows.ch.logf("[rows] unexpected packet [%d]", packet)
return rows.setError(fmt.Errorf("[rows] unexpected packet [%d] from server", packet))
}
}
}
func (rows *rows) Close() error {
rows.ch.logf("[rows] close")
rows.columns = nil
for range rows.stream {
}
rows.finish()
return nil
}
func (rows *rows) error() error {
rows.mutex.RLock()
defer rows.mutex.RUnlock()
return rows.err
}
func (rows *rows) setError(err error) error {
rows.mutex.Lock()
rows.err = err
rows.mutex.Unlock()
return err
}
func (rows *rows) ColumnTypeNullable(idx int) (nullable, ok bool) {
_, ok = rows.blockColumns[idx].(*column.Nullable)
return ok, true
}
func (rows *rows) ColumnTypePrecisionScale(idx int) (precision, scale int64, ok bool) {
decimalVal, ok := rows.blockColumns[idx].(*column.Decimal)
if !ok {
if nullable, nullOk := rows.blockColumns[idx].(*column.Nullable); nullOk {
decimalVal, ok = nullable.GetColumn().(*column.Decimal)
}
}
if ok {
return int64(decimalVal.GetPrecision()), int64(decimalVal.GetScale()), ok
}
return 0, 0, false
}
+215
View File
@@ -0,0 +1,215 @@
package clickhouse
import (
"bytes"
"context"
"database/sql/driver"
"unicode"
"github.com/ClickHouse/clickhouse-go/lib/data"
)
type stmt struct {
ch *clickhouse
query string
counter int
numInput int
isInsert bool
}
var emptyResult = &result{}
type key string
var queryIDKey key
//Put query ID into context and use it in ExecContext or QueryContext
func WithQueryID(ctx context.Context, queryID string) context.Context {
return context.WithValue(ctx, queryIDKey, queryID)
}
func (stmt *stmt) NumInput() int {
switch {
case stmt.ch.block != nil:
return len(stmt.ch.block.Columns)
case stmt.numInput < 0:
return 0
}
return stmt.numInput
}
func (stmt *stmt) Exec(args []driver.Value) (driver.Result, error) {
return stmt.execContext(context.Background(), args)
}
func (stmt *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
dargs := make([]driver.Value, len(args))
for i, nv := range args {
dargs[i] = nv.Value
}
return stmt.execContext(ctx, dargs)
}
func (stmt *stmt) execContext(ctx context.Context, args []driver.Value) (driver.Result, error) {
if stmt.isInsert {
stmt.counter++
if err := stmt.ch.block.AppendRow(args); err != nil {
return nil, err
}
if (stmt.counter % stmt.ch.blockSize) == 0 {
stmt.ch.logf("[exec] flush block")
if err := stmt.ch.writeBlock(stmt.ch.block, ""); err != nil {
return nil, err
}
if err := stmt.ch.encoder.Flush(); err != nil {
return nil, err
}
}
return emptyResult, nil
}
query, externalTables := stmt.bind(convertOldArgs(args))
if err := stmt.ch.sendQuery(ctx, query, externalTables); err != nil {
return nil, err
}
if err := stmt.ch.process(); err != nil {
return nil, err
}
return emptyResult, nil
}
func (stmt *stmt) Query(args []driver.Value) (driver.Rows, error) {
return stmt.queryContext(context.Background(), convertOldArgs(args))
}
func (stmt *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
return stmt.queryContext(ctx, args)
}
func (stmt *stmt) queryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
finish := stmt.ch.watchCancel(ctx)
query, externalTables := stmt.bind(args)
if err := stmt.ch.sendQuery(ctx, query, externalTables); err != nil {
finish()
return nil, err
}
meta, err := stmt.ch.readMeta()
if err != nil {
finish()
return nil, err
}
rows := rows{
ch: stmt.ch,
finish: finish,
stream: make(chan *data.Block, 50),
columns: meta.ColumnNames(),
blockColumns: meta.Columns,
}
go rows.receiveData()
return &rows, nil
}
func (stmt *stmt) Close() error {
stmt.ch.logf("[stmt] close")
return nil
}
func (stmt *stmt) bind(args []driver.NamedValue) (string, []ExternalTable) {
var (
buf bytes.Buffer
index int
keyword bool
inBetween bool
like = newMatcher("like")
limit = newMatcher("limit")
offset = newMatcher("offset")
between = newMatcher("between")
and = newMatcher("and")
in = newMatcher("in")
from = newMatcher("from")
join = newMatcher("join")
subSelect = newMatcher("select")
externalTables = make([]ExternalTable, 0)
)
switch {
case stmt.NumInput() != 0:
reader := bytes.NewReader([]byte(stmt.query))
for {
if char, _, err := reader.ReadRune(); err == nil {
switch char {
case '@':
if param := paramParser(reader); len(param) != 0 {
for _, v := range args {
if len(v.Name) != 0 && v.Name == param {
switch v := v.Value.(type) {
case ExternalTable:
buf.WriteString(v.Name)
externalTables = append(externalTables, v)
default:
buf.WriteString(quote(v))
}
}
}
}
case '?':
if keyword && index < len(args) && len(args[index].Name) == 0 {
switch v := args[index].Value.(type) {
case ExternalTable:
buf.WriteString(v.Name)
externalTables = append(externalTables, v)
default:
buf.WriteString(quote(v))
}
index++
} else {
buf.WriteRune(char)
}
default:
switch {
case
char == '=',
char == '<',
char == '>',
char == '(',
char == ',',
char == '+',
char == '-',
char == '*',
char == '/',
char == '[':
keyword = true
default:
if limit.matchRune(char) || offset.matchRune(char) || like.matchRune(char) ||
in.matchRune(char) || from.matchRune(char) || join.matchRune(char) || subSelect.matchRune(char) {
keyword = true
} else if between.matchRune(char) {
keyword = true
inBetween = true
} else if inBetween && and.matchRune(char) {
keyword = true
inBetween = false
} else {
keyword = keyword && unicode.IsSpace(char)
}
}
buf.WriteRune(char)
}
} else {
break
}
}
default:
buf.WriteString(stmt.query)
}
return buf.String(), externalTables
}
func convertOldArgs(args []driver.Value) []driver.NamedValue {
dargs := make([]driver.NamedValue, len(args))
for i, v := range args {
dargs[i] = driver.NamedValue{
Ordinal: i + 1,
Value: v,
}
}
return dargs
}
+44
View File
@@ -0,0 +1,44 @@
package clickhouse
import (
"crypto/tls"
"sync"
)
// Based on the original implementation in the project go-sql-driver/mysql:
// https://github.com/go-sql-driver/mysql/blob/master/utils.go
var (
tlsConfigLock sync.RWMutex
tlsConfigRegistry map[string]*tls.Config
)
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
func RegisterTLSConfig(key string, config *tls.Config) error {
tlsConfigLock.Lock()
if tlsConfigRegistry == nil {
tlsConfigRegistry = make(map[string]*tls.Config)
}
tlsConfigRegistry[key] = config
tlsConfigLock.Unlock()
return nil
}
// DeregisterTLSConfig removes the tls.Config associated with key.
func DeregisterTLSConfig(key string) {
tlsConfigLock.Lock()
if tlsConfigRegistry != nil {
delete(tlsConfigRegistry, key)
}
tlsConfigLock.Unlock()
}
func getTLSConfigClone(key string) (config *tls.Config) {
tlsConfigLock.RLock()
if v, ok := tlsConfigRegistry[key]; ok {
config = v.Clone()
}
tlsConfigLock.RUnlock()
return
}
-202
View File
@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-30
View File
@@ -1,30 +0,0 @@
up:
@docker compose up -d
down:
@docker compose down
cli:
docker run -it --rm --net clickhouse-go_clickhouse --link clickhouse:clickhouse-server yandex/clickhouse-client --host clickhouse-server
test:
@go install -race -v
@go test -race -timeout 30s -count=1 -v .
@go test -race -timeout 30s -count=1 -v ./tests/...
lint:
golangci-lint run || :
gocritic check -disable=singleCaseSwitch ./... || :
contributors:
@git log --pretty="%an <%ae>%n%cn <%ce>" | sort -u -t '<' -k 2,2 | LC_ALL=C sort | \
grep -v "users.noreply.github.com\|GitHub <noreply@github.com>" \
> contributors/list
staticcheck:
staticcheck ./...
codegen: contributors
@cd lib/column && go run codegen/main.go
@go-licenser -licensor "ClickHouse, Inc."
.PHONY: contributors
-162
View File
@@ -1,162 +0,0 @@
# ClickHouse [![run-tests](https://github.com/ClickHouse/clickhouse-go/actions/workflows/run-tests.yml/badge.svg?branch=v2)](https://github.com/ClickHouse/clickhouse-go/actions/workflows/run-tests.yml) [![Go Reference](https://pkg.go.dev/badge/github.com/ClickHouse/clickhouse-go/v2.svg)](https://pkg.go.dev/github.com/ClickHouse/clickhouse-go/v2)
Golang SQL database driver for [ClickHouse](https://clickhouse.com/).
## Versions
There are two version of this driver, v1 and v2, available as separate branches.
**v1 is now in a state of a maintenance - we will only accept PRs for bug and security fixes.**
Users should use v2 which is production ready and [significantly faster than v1](#benchmark).
## Supported ClickHouse Versions
The driver is tested against the currently [supported versions](https://github.com/ClickHouse/ClickHouse/blob/master/SECURITY.md) of ClickHouse
## Key features
* Uses native ClickHouse TCP client-server protocol
* Compatibility with [`database/sql`](#std-databasesql-interface) ([slower](#benchmark) than [native interface](#native-interface)!)
* Marshal rows into structs ([ScanStruct](tests/scan_struct_test.go), [Select](examples/native/scan_struct/main.go))
* Unmarshal struct to row ([AppendStruct](benchmark/v2/write-native-struct/main.go))
* Connection pool
* Failover and load balancing
* [Bulk write support](examples/native/batch/main.go) (for `database/sql` [use](examples/std/batch/main.go) `begin->prepare->(in loop exec)->commit`)
* [AsyncInsert](benchmark/v2/write-async/main.go)
* Named and numeric placeholders support
* LZ4 compression support
* External data
Support for the ClickHouse protocol advanced features using `Context`:
* Query ID
* Quota Key
* Settings
* OpenTelemetry
* Execution events:
* Logs
* Progress
* Profile info
* Profile events
# `database/sql` interface
## OpenDB
```go
conn := clickhouse.OpenDB(&clickhouse.Options{
Addr: []string{"127.0.0.1:9999"},
Auth: clickhouse.Auth{
Database: "default",
Username: "default",
Password: "",
},
TLS: &tls.Config{
InsecureSkipVerify: true,
},
Settings: clickhouse.Settings{
"max_execution_time": 60,
},
DialTimeout: 5 * time.Second,
Compression: &clickhouse.Compression{
clickhouse.CompressionLZ4,
},
Debug: true,
})
conn.SetMaxIdleConns(5)
conn.SetMaxOpenConns(10)
conn.SetConnMaxLifetime(time.Hour)
```
## DSN
* hosts - comma-separated list of single address hosts for load-balancing and failover
* username/password - auth credentials
* database - select the current default database
* dial_timeout - a duration string is a possibly signed sequence of decimal numbers, each with optional fraction and a unit suffix such as "300ms", "1s". Valid time units are "ms", "s", "m".
* connection_open_strategy - random/in_order (default random).
* round-robin - choose a round-robin server from the set
* in_order - first live server is chosen in specified order
* debug - enable debug output (boolean value)
* compress - enable lz4 compression (boolean value)
SSL/TLS parameters:
* secure - establish secure connection (default is false)
* skip_verify - skip certificate verification (default is false)
Example:
```sh
clickhouse://username:password@host1:9000,host2:9000/database?dial_timeout=200ms&max_execution_time=60
```
## Benchmark
| [V1 (READ)](benchmark/v1/read/main.go) | [V2 (READ) std](benchmark/v2/read/main.go) | [V2 (READ) native](benchmark/v2/read-native/main.go) |
| -------------------------------------- | ------------------------------------------ | ---------------------------------------------------- |
| 1.218s | 924.390ms | 675.721ms |
| [V1 (WRITE)](benchmark/v1/write/main.go) | [V2 (WRITE) std](benchmark/v2/write/main.go) | [V2 (WRITE) native](benchmark/v2/write-native/main.go) | [V2 (WRITE) by column](benchmark/v2/write-native-columnar/main.go) |
| ---------------------------------------- | -------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------ |
| 1.899s | 1.177s | 699.203ms | 661.973ms |
## Install
```sh
go get -u github.com/ClickHouse/clickhouse-go/v2
```
## Examples
### native interface
* [batch](examples/native/batch/main.go)
* [async insert](examples/native/write-async)
* [batch struct](examples/native/write-struct/main.go)
* [columnar](examples/native/write-columnar/main.go)
* [scan struct](examples/native/scan_struct/main.go)
* [bind params](examples/native/bind/main.go)
### std `database/sql` interface
* [batch](examples/std/batch/main.go)
* [async insert](examples/std/write-async)
* [open db](examples/std/open_db/main.go)
* [bind params](examples/std/bind/main.go)
#### A Note on TLS/SSL
At a low level all driver connect methods (DSN/OpenDB/Open) will use the [Go tls package](https://pkg.go.dev/crypto/tls) to establish a secure connection. The driver knows to use TLS if the Options struct contains a non-nil tls.Config pointer.
Setting secure in the DSN creates a minimal tls.Config struct with only the InsecureSkipVerify field set (either true or false). It is equivalent to this code:
```go
conn := clickhouse.OpenDB(&clickhouse.Options{
...
TLS: &tls.Config{
InsecureSkipVerify: false
}
...
})
```
This minimal tls.Config is normally all that is necessary to connect to the secure native port (normally 9440) on a ClickHouse server. If the ClickHouse server does not have a valid certificate (expired, wrong host name, not signed by a publicly recognized root Certificate Authority), InsecureSkipVerify can be to `true`, but that is strongly discouraged.
If additional TLS parameters are necessary the application code should set the desired fields in the tls.Config struct. That can include specific cipher suites, forcing a particular TLS version (like 1.2 or 1.3), adding an internal CA certificate chain, adding a client certificate (and private key) if required by the ClickHouse server, and most of the other options that come with a more specialized security setup.
## Third-party alternatives
* Database drivers:
* [mailru/go-clickhouse](https://github.com/mailru/go-clickhouse) (uses the HTTP protocol)
* [uptrace/go-clickhouse](https://github.com/uptrace/go-clickhouse) (uses the native TCP protocol with `database/sql`-like API)
* Drivers with columnar interface:
* [vahid-sohrabloo/chconn](https://github.com/vahid-sohrabloo/chconn)
* [go-faster/ch](https://github.com/go-faster/ch)
* Insert collectors:
* [KittenHouse](https://github.com/YuriyNasretdinov/kittenhouse)
* [nikepan/clickhouse-bulk](https://github.com/nikepan/clickhouse-bulk)
-268
View File
@@ -1,268 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
std_driver "database/sql/driver"
"fmt"
"reflect"
"regexp"
"strings"
"time"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
)
func Named(name string, value interface{}) driver.NamedValue {
return driver.NamedValue{
Name: name,
Value: value,
}
}
var bindNumericRe = regexp.MustCompile(`\$[0-9]+`)
var bindPositionalRe = regexp.MustCompile(`[^\\][?]`)
func bind(tz *time.Location, query string, args ...interface{}) (string, error) {
if len(args) == 0 {
return query, nil
}
var (
haveNamed bool
haveNumeric bool
havePositional bool
)
haveNumeric = bindNumericRe.MatchString(query)
havePositional = bindPositionalRe.MatchString(query)
if haveNumeric && havePositional {
return "", ErrBindMixedParamsFormats
}
for _, v := range args {
switch v.(type) {
case driver.NamedValue:
haveNamed = true
default:
}
if haveNamed && (haveNumeric || havePositional) {
return "", ErrBindMixedParamsFormats
}
}
if haveNamed {
return bindNamed(tz, query, args...)
}
if haveNumeric {
return bindNumeric(tz, query, args...)
}
return bindPositional(tz, query, args...)
}
var bindPositionCharRe = regexp.MustCompile(`[?]`)
func bindPositional(tz *time.Location, query string, args ...interface{}) (_ string, err error) {
var (
unbind = make(map[int]struct{})
params = make([]string, len(args))
)
for i, v := range args {
if fn, ok := v.(std_driver.Valuer); ok {
if v, err = fn.Value(); err != nil {
return "", nil
}
}
params[i], err = format(tz, v)
if err != nil {
return "", err
}
}
i := 0
query = bindPositionalRe.ReplaceAllStringFunc(query, func(n string) string {
if i >= len(params) {
unbind[i] = struct{}{}
return ""
}
val := params[i]
i++
return bindPositionCharRe.ReplaceAllStringFunc(n, func(m string) string {
return val
})
})
for param := range unbind {
return "", fmt.Errorf("have no arg for param ? at position %d", param)
}
// replace \? escape sequence
return strings.ReplaceAll(query, "\\?", "?"), nil
}
func bindNumeric(tz *time.Location, query string, args ...interface{}) (_ string, err error) {
var (
unbind = make(map[string]struct{})
params = make(map[string]string)
)
for i, v := range args {
if fn, ok := v.(std_driver.Valuer); ok {
if v, err = fn.Value(); err != nil {
return "", nil
}
}
val, err := format(tz, v)
if err != nil {
return "", err
}
params[fmt.Sprintf("$%d", i+1)] = val
}
query = bindNumericRe.ReplaceAllStringFunc(query, func(n string) string {
if _, found := params[n]; !found {
unbind[n] = struct{}{}
return ""
}
return params[n]
})
for param := range unbind {
return "", fmt.Errorf("have no arg for %s param", param)
}
return query, nil
}
var bindNamedRe = regexp.MustCompile(`@[a-zA-Z0-9\_]+`)
func bindNamed(tz *time.Location, query string, args ...interface{}) (_ string, err error) {
var (
unbind = make(map[string]struct{})
params = make(map[string]string)
)
for _, v := range args {
switch v := v.(type) {
case driver.NamedValue:
value := v.Value
if fn, ok := v.Value.(std_driver.Valuer); ok {
if value, err = fn.Value(); err != nil {
return "", err
}
}
val, err := format(tz, value)
if err != nil {
return "", err
}
params["@"+v.Name] = val
}
}
query = bindNamedRe.ReplaceAllStringFunc(query, func(n string) string {
if _, found := params[n]; !found {
unbind[n] = struct{}{}
return ""
}
return params[n]
})
for param := range unbind {
return "", fmt.Errorf("have no arg for %q param", param)
}
return query, nil
}
func format(tz *time.Location, v interface{}) (string, error) {
quote := func(v string) string {
return "'" + strings.NewReplacer(`\`, `\\`, `'`, `\'`).Replace(v) + "'"
}
switch v := v.(type) {
case nil:
return "NULL", nil
case string:
return quote(v), nil
case time.Time:
switch v.Location().String() {
case "Local":
return fmt.Sprintf("toDateTime(%d)", v.Unix()), nil
case tz.String():
return v.Format("toDateTime('2006-01-02 15:04:05')"), nil
}
return v.Format("toDateTime('2006-01-02 15:04:05', '" + v.Location().String() + "')"), nil
case []interface{}: // tuple
elements := make([]string, 0, len(v))
for _, e := range v {
val, err := format(tz, e)
if err != nil {
return "", err
}
elements = append(elements, val)
}
return "(" + strings.Join(elements, ", ") + ")", nil
case [][]interface{}:
items := make([]string, 0, len(v))
for _, t := range v {
val, err := format(tz, t)
if err != nil {
return "", err
}
items = append(items, val)
}
return strings.Join(items, ", "), nil
case fmt.Stringer:
return quote(v.String()), nil
}
switch v := reflect.ValueOf(v); v.Kind() {
case reflect.String:
return quote(v.String()), nil
case reflect.Slice:
values := make([]string, 0, v.Len())
for i := 0; i < v.Len(); i++ {
val, err := format(tz, v.Index(i).Interface())
if err != nil {
return "", err
}
values = append(values, val)
}
return strings.Join(values, ", "), nil
case reflect.Map: // map
values := make([]string, 0, len(v.MapKeys()))
for _, key := range v.MapKeys() {
name := fmt.Sprint(key.Interface())
if key.Kind() == reflect.String {
name = fmt.Sprintf("'%s'", name)
}
val, err := format(tz, v.MapIndex(key).Interface())
if err != nil {
return "", err
}
if v.MapIndex(key).Kind() == reflect.Slice {
// assume slices in maps are arrays
val = fmt.Sprintf("[%s]", val)
}
values = append(values, fmt.Sprintf("%s : %s", name, val))
}
return "{" + strings.Join(values, ", ") + "}", nil
}
return fmt.Sprint(v), nil
}
func rebind(in []std_driver.NamedValue) []interface{} {
args := make([]interface{}, 0, len(in))
for _, v := range in {
switch {
case len(v.Name) != 0:
args = append(args, driver.NamedValue{
Name: v.Name,
Value: v.Value,
})
default:
args = append(args, v.Value)
}
}
return args
}
-279
View File
@@ -1,279 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
"errors"
"fmt"
"sync/atomic"
"time"
"github.com/ClickHouse/clickhouse-go/v2/contributors"
"github.com/ClickHouse/clickhouse-go/v2/lib/column"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
)
type Conn = driver.Conn
type (
Progress = proto.Progress
Exception = proto.Exception
ProfileInfo = proto.ProfileInfo
ServerVersion = proto.ServerHandshake
)
var (
ErrBatchAlreadySent = errors.New("clickhouse: batch has already been sent")
ErrAcquireConnTimeout = errors.New("clickhouse: acquire conn timeout. you can increase the number of max open conn or the dial timeout")
ErrUnsupportedServerRevision = errors.New("clickhouse: unsupported server revision")
ErrBindMixedParamsFormats = errors.New("clickhouse [bind]: mixed named, numeric or positional parameters")
ErrAcquireConnNoAddress = errors.New("clickhouse: no valid address supplied")
)
type OpError struct {
Op string
ColumnName string
Err error
}
func (e *OpError) Error() string {
switch err := e.Err.(type) {
case *column.Error:
return fmt.Sprintf("clickhouse [%s]: (%s %s) %s", e.Op, e.ColumnName, err.ColumnType, err.Err)
case *column.ColumnConverterError:
var hint string
if len(err.Hint) != 0 {
hint += ". " + err.Hint
}
return fmt.Sprintf("clickhouse [%s]: (%s) converting %s to %s is unsupported%s",
err.Op, e.ColumnName,
err.From, err.To,
hint,
)
}
return fmt.Sprintf("clickhouse [%s]: %s", e.Op, e.Err)
}
func Open(opt *Options) (driver.Conn, error) {
opt.setDefaults()
return &clickhouse{
opt: opt,
idle: make(chan *connect, opt.MaxIdleConns),
open: make(chan struct{}, opt.MaxOpenConns),
}, nil
}
type clickhouse struct {
opt *Options
idle chan *connect
open chan struct{}
connID int64
}
func (clickhouse) Contributors() []string {
list := contributors.List
if len(list[len(list)-1]) == 0 {
return list[:len(list)-1]
}
return list
}
func (ch *clickhouse) ServerVersion() (*driver.ServerVersion, error) {
var (
ctx, cancel = context.WithTimeout(context.Background(), ch.opt.DialTimeout)
conn, err = ch.acquire(ctx)
)
defer cancel()
if err != nil {
return nil, err
}
ch.release(conn, nil)
return &conn.server, nil
}
func (ch *clickhouse) Query(ctx context.Context, query string, args ...interface{}) (rows driver.Rows, err error) {
conn, err := ch.acquire(ctx)
if err != nil {
return nil, err
}
return conn.query(ctx, ch.release, query, args...)
}
func (ch *clickhouse) QueryRow(ctx context.Context, query string, args ...interface{}) (rows driver.Row) {
conn, err := ch.acquire(ctx)
if err != nil {
return &row{
err: err,
}
}
return conn.queryRow(ctx, ch.release, query, args...)
}
func (ch *clickhouse) Exec(ctx context.Context, query string, args ...interface{}) error {
conn, err := ch.acquire(ctx)
if err != nil {
return err
}
if err := conn.exec(ctx, query, args...); err != nil {
ch.release(conn, err)
return err
}
ch.release(conn, nil)
return nil
}
func (ch *clickhouse) PrepareBatch(ctx context.Context, query string) (driver.Batch, error) {
conn, err := ch.acquire(ctx)
if err != nil {
return nil, err
}
batch, err := conn.prepareBatch(ctx, query, ch.release)
if err != nil {
return nil, err
}
return batch, nil
}
func (ch *clickhouse) AsyncInsert(ctx context.Context, query string, wait bool) error {
conn, err := ch.acquire(ctx)
if err != nil {
return err
}
if err := conn.asyncInsert(ctx, query, wait); err != nil {
ch.release(conn, err)
return err
}
ch.release(conn, nil)
return nil
}
func (ch *clickhouse) Ping(ctx context.Context) (err error) {
conn, err := ch.acquire(ctx)
if err != nil {
return err
}
if err := conn.ping(ctx); err != nil {
ch.release(conn, err)
return err
}
ch.release(conn, nil)
return nil
}
func (ch *clickhouse) Stats() driver.Stats {
return driver.Stats{
Open: len(ch.open),
Idle: len(ch.idle),
MaxOpenConns: cap(ch.open),
MaxIdleConns: cap(ch.idle),
}
}
func (ch *clickhouse) dial(ctx context.Context) (conn *connect, err error) {
connID := int(atomic.AddInt64(&ch.connID, 1))
for i := range ch.opt.Addr {
var num int
switch ch.opt.ConnOpenStrategy {
case ConnOpenInOrder:
num = i
case ConnOpenRoundRobin:
num = (int(connID) + i) % len(ch.opt.Addr)
}
if conn, err = dial(ctx, ch.opt.Addr[num], connID, ch.opt); err == nil {
return conn, nil
}
}
if err == nil {
err = ErrAcquireConnNoAddress
}
return nil, err
}
func (ch *clickhouse) acquire(ctx context.Context) (conn *connect, err error) {
timer := time.NewTimer(ch.opt.DialTimeout)
defer timer.Stop()
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
select {
case <-timer.C:
return nil, ErrAcquireConnTimeout
case ch.open <- struct{}{}:
}
select {
case <-timer.C:
return nil, ErrAcquireConnTimeout
case conn := <-ch.idle:
if conn.isBad() {
conn.close()
if conn, err = ch.dial(ctx); err != nil {
select {
case <-ch.open:
default:
}
return nil, err
}
}
conn.released = false
return conn, nil
default:
}
if conn, err = ch.dial(ctx); err != nil {
select {
case <-ch.open:
default:
}
return nil, err
}
return conn, nil
}
func (ch *clickhouse) release(conn *connect, err error) {
if conn.released {
return
}
conn.released = true
select {
case <-ch.open:
default:
}
if err != nil || time.Since(conn.connectedAt) >= ch.opt.ConnMaxLifetime {
conn.close()
return
}
select {
case ch.idle <- conn:
default:
conn.close()
}
}
func (ch *clickhouse) Close() error {
for {
select {
case c := <-ch.idle:
c.close()
default:
return nil
}
}
}
-162
View File
@@ -1,162 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
"github.com/ClickHouse/clickhouse-go/v2/lib/compress"
)
var CompressionLZ4 compress.Method = compress.LZ4
type Auth struct { // has_control_character
Database string
Username string
Password string
}
type Compression struct {
Method compress.Method
}
type ConnOpenStrategy uint8
const (
ConnOpenInOrder ConnOpenStrategy = iota
ConnOpenRoundRobin
)
func ParseDSN(dsn string) (*Options, error) {
opt := &Options{}
if err := opt.fromDSN(dsn); err != nil {
return nil, err
}
return opt, nil
}
type Options struct {
TLS *tls.Config
Addr []string
Auth Auth
DialContext func(ctx context.Context, addr string) (net.Conn, error)
Debug bool
Debugf func(format string, v ...interface{}) // only works when Debug is true
Settings Settings
Compression *Compression
DialTimeout time.Duration // default 1 second
MaxOpenConns int // default MaxIdleConns + 5
MaxIdleConns int // default 5
ConnMaxLifetime time.Duration // default 1 hour
ConnOpenStrategy ConnOpenStrategy
}
func (o *Options) fromDSN(in string) error {
dsn, err := url.Parse(in)
if err != nil {
return err
}
if o.Settings == nil {
o.Settings = make(Settings)
}
if dsn.User != nil {
o.Auth.Username = dsn.User.Username()
o.Auth.Password, _ = dsn.User.Password()
}
o.Addr = append(o.Addr, strings.Split(dsn.Host, ",")...)
var (
secure bool
params = dsn.Query()
skipVerify bool
)
o.Auth.Database = strings.TrimPrefix(dsn.Path, "/")
for v := range params {
switch v {
case "debug":
o.Debug, _ = strconv.ParseBool(params.Get(v))
case "compress":
if on, _ := strconv.ParseBool(params.Get(v)); on {
o.Compression = &Compression{
Method: CompressionLZ4,
}
}
case "dial_timeout":
duration, err := time.ParseDuration(params.Get(v))
if err != nil {
return fmt.Errorf("clickhouse [dsn parse]: dial timeout: %s", err)
}
o.DialTimeout = duration
case "secure":
secure = true
case "skip_verify":
skipVerify = true
case "connection_open_strategy":
switch params.Get(v) {
case "in_order":
o.ConnOpenStrategy = ConnOpenInOrder
case "round_robin":
o.ConnOpenStrategy = ConnOpenRoundRobin
}
default:
switch p := strings.ToLower(params.Get(v)); p {
case "true":
o.Settings[v] = int(1)
case "false":
o.Settings[v] = int(0)
default:
if n, err := strconv.Atoi(p); err == nil {
o.Settings[v] = n
}
}
}
}
if secure {
o.TLS = &tls.Config{
InsecureSkipVerify: skipVerify,
}
}
return nil
}
func (o *Options) setDefaults() {
if len(o.Auth.Database) == 0 {
o.Auth.Database = "default"
}
if len(o.Auth.Username) == 0 {
o.Auth.Username = "default"
}
if o.DialTimeout == 0 {
o.DialTimeout = time.Second
}
if o.MaxIdleConns <= 0 {
o.MaxIdleConns = 5
}
if o.MaxOpenConns <= 0 {
o.MaxOpenConns = o.MaxIdleConns + 5
}
if o.ConnMaxLifetime == 0 {
o.ConnMaxLifetime = time.Hour
}
}
-161
View File
@@ -1,161 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"database/sql"
"io"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
)
type rows struct {
err error
row int
block *proto.Block
totals *proto.Block
errors chan error
stream chan *proto.Block
columns []string
structMap *structMap
}
func (r *rows) Next() (result bool) {
defer func() {
if !result {
r.Close()
}
}()
if r.block == nil {
return false
}
next:
if r.row >= r.block.Rows() {
select {
case err := <-r.errors:
if err != nil {
r.err = err
return false
}
goto next
case block := <-r.stream:
if block == nil {
return false
}
if block.Packet == proto.ServerTotals {
r.row, r.block, r.totals = 0, nil, block
return false
}
r.row, r.block = 0, block
}
}
r.row++
return r.row <= r.block.Rows()
}
func (r *rows) Scan(dest ...interface{}) error {
if r.block == nil || (r.row == 0 && r.row >= r.block.Rows()) { // call without next when result is empty
return io.EOF
}
return scan(r.block, r.row, dest...)
}
func (r *rows) ScanStruct(dest interface{}) error {
values, err := r.structMap.Map("ScanStruct", r.columns, dest, true)
if err != nil {
return err
}
return r.Scan(values...)
}
func (r *rows) Totals(dest ...interface{}) error {
if r.totals == nil {
return sql.ErrNoRows
}
return scan(r.totals, 1, dest...)
}
func (r *rows) Columns() []string {
return r.columns
}
func (r *rows) Close() error {
active := 2
for {
select {
case _, ok := <-r.stream:
if !ok {
active--
if active == 0 {
return r.err
}
}
case err, ok := <-r.errors:
if err != nil {
r.err = err
}
if !ok {
active--
if active == 0 {
return r.err
}
}
}
}
}
func (r *rows) Err() error {
return r.err
}
type row struct {
err error
rows *rows
}
func (r *row) Err() error {
return r.err
}
func (r *row) ScanStruct(dest interface{}) error {
if r.err != nil {
return r.err
}
values, err := r.rows.structMap.Map("ScanStruct", r.rows.columns, dest, true)
if err != nil {
return err
}
return r.Scan(values...)
}
func (r *row) Scan(dest ...interface{}) error {
if r.err != nil {
return r.err
}
if !r.rows.Next() {
r.rows.Close()
if err := r.rows.Err(); err != nil {
return err
}
return sql.ErrNoRows
}
if err := r.rows.Scan(dest...); err != nil {
return err
}
return r.rows.Close()
}
@@ -1,62 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"reflect"
"github.com/ClickHouse/clickhouse-go/v2/lib/column"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
)
type columnType struct {
name string
chType string
nullable bool
scanType reflect.Type
}
func (c *columnType) Name() string {
return c.name
}
func (c *columnType) Nullable() bool {
return c.nullable
}
func (c *columnType) ScanType() reflect.Type {
return c.scanType
}
func (c *columnType) DatabaseTypeName() string {
return c.chType
}
func (r *rows) ColumnTypes() []driver.ColumnType {
types := make([]driver.ColumnType, 0, len(r.columns))
for i, c := range r.block.Columns {
_, nullable := c.(*column.Nullable)
types = append(types, &columnType{
name: r.columns[i],
chType: string(c.Type()),
nullable: nullable,
scanType: c.ScanType(),
})
}
return types
}
-289
View File
@@ -1,289 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"io"
"reflect"
"strings"
"sync/atomic"
"github.com/ClickHouse/clickhouse-go/v2/lib/column"
)
var globalConnID int64
type stdConnOpener struct {
err error
opt *Options
}
func (o *stdConnOpener) Driver() driver.Driver {
return &stdDriver{}
}
func (o *stdConnOpener) Connect(ctx context.Context) (_ driver.Conn, err error) {
if o.err != nil {
return nil, o.err
}
var (
conn *connect
connID = int(atomic.AddInt64(&globalConnID, 1))
)
for i := range o.opt.Addr {
var num int
switch o.opt.ConnOpenStrategy {
case ConnOpenInOrder:
num = i
case ConnOpenRoundRobin:
num = (int(connID) + i) % len(o.opt.Addr)
}
if conn, err = dial(ctx, o.opt.Addr[num], connID, o.opt); err == nil {
return &stdDriver{
conn: conn,
}, nil
}
}
return nil, err
}
func init() {
sql.Register("clickhouse", &stdDriver{})
}
func OpenDB(opt *Options) *sql.DB {
var settings []string
if opt.MaxIdleConns > 0 {
settings = append(settings, "SetMaxIdleConns")
}
if opt.MaxOpenConns > 0 {
settings = append(settings, "SetMaxOpenConns")
}
if opt.ConnMaxLifetime > 0 {
settings = append(settings, "SetConnMaxLifetime")
}
if len(settings) != 0 {
return sql.OpenDB(&stdConnOpener{
err: fmt.Errorf("cannot connect. invalid settings. use %s (see https://pkg.go.dev/database/sql)", strings.Join(settings, ",")),
})
}
opt.setDefaults()
return sql.OpenDB(&stdConnOpener{
opt: opt,
})
}
type stdDriver struct {
conn *connect
commit func() error
}
func (d *stdDriver) Open(dsn string) (_ driver.Conn, err error) {
var opt Options
if err := opt.fromDSN(dsn); err != nil {
return nil, err
}
opt.setDefaults()
return (&stdConnOpener{opt: &opt}).Connect(context.Background())
}
func (std *stdDriver) ResetSession(ctx context.Context) error {
if std.conn.isBad() {
return driver.ErrBadConn
}
return nil
}
func (std *stdDriver) Ping(ctx context.Context) error { return std.conn.ping(ctx) }
func (std *stdDriver) Begin() (driver.Tx, error) { return std, nil }
func (std *stdDriver) Commit() error {
if std.commit == nil {
return nil
}
defer func() {
std.commit = nil
}()
return std.commit()
}
func (std *stdDriver) Rollback() error {
std.commit = nil
std.conn.close()
return nil
}
func (std *stdDriver) CheckNamedValue(nv *driver.NamedValue) error { return nil }
func (std *stdDriver) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
if options := queryOptions(ctx); options.async.ok {
if len(args) != 0 {
return nil, errors.New("clickhouse: you can't use parameters in an asynchronous insert")
}
return driver.RowsAffected(0), std.conn.asyncInsert(ctx, query, options.async.wait)
}
if err := std.conn.exec(ctx, query, rebind(args)...); err != nil {
return nil, err
}
return driver.RowsAffected(0), nil
}
func (std *stdDriver) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
r, err := std.conn.query(ctx, func(*connect, error) {}, query, rebind(args)...)
if err != nil {
return nil, err
}
return &stdRows{
rows: r,
}, nil
}
func (std *stdDriver) Prepare(query string) (driver.Stmt, error) {
return std.PrepareContext(context.Background(), query)
}
func (std *stdDriver) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
batch, err := std.conn.prepareBatch(ctx, query, func(*connect, error) {})
if err != nil {
return nil, err
}
std.commit = batch.Send
return &stdBatch{
batch: batch,
}, nil
}
func (std *stdDriver) Close() error { return std.conn.close() }
type stdBatch struct {
batch *batch
}
func (s *stdBatch) NumInput() int { return -1 }
func (s *stdBatch) Exec(args []driver.Value) (driver.Result, error) {
values := make([]interface{}, 0, len(args))
for _, v := range args {
values = append(values, v)
}
if err := s.batch.Append(values...); err != nil {
return nil, err
}
return driver.RowsAffected(0), nil
}
func (s *stdBatch) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
values := make([]driver.Value, 0, len(args))
for _, v := range args {
values = append(values, v.Value)
}
return s.Exec(values)
}
func (s *stdBatch) Query(args []driver.Value) (driver.Rows, error) {
return nil, errors.New("only Exec method supported in batch mode")
}
func (s *stdBatch) Close() error { return nil }
type stdRows struct {
rows *rows
}
func (r *stdRows) Columns() []string {
return r.rows.Columns()
}
func (r *stdRows) ColumnTypeScanType(idx int) reflect.Type {
return r.rows.block.Columns[idx].ScanType()
}
func (r *stdRows) ColumnTypeDatabaseTypeName(idx int) string {
return string(r.rows.block.Columns[idx].Type())
}
func (r *stdRows) ColumnTypeNullable(idx int) (nullable, ok bool) {
_, ok = r.rows.block.Columns[idx].(*column.Nullable)
return ok, true
}
func (r *stdRows) ColumnTypePrecisionScale(idx int) (precision, scale int64, ok bool) {
switch col := r.rows.block.Columns[idx].(type) {
case *column.Decimal:
return col.Precision(), col.Scale(), true
case interface{ Base() column.Interface }:
switch col := col.Base().(type) {
case *column.Decimal:
return col.Precision(), col.Scale(), true
}
}
return 0, 0, false
}
func (r *stdRows) Next(dest []driver.Value) error {
if len(r.rows.block.Columns) != len(dest) {
return &OpError{
Op: "Next",
Err: fmt.Errorf("expected %d destination arguments in Next, not %d", len(r.rows.block.Columns), len(dest)),
}
}
if r.rows.Next() {
for i := range dest {
nullable, ok := r.ColumnTypeNullable(i)
switch value := r.rows.block.Columns[i].Row(r.rows.row-1, nullable && ok).(type) {
case driver.Valuer:
v, err := value.Value()
if err != nil {
return err
}
dest[i] = v
default:
dest[i] = value
}
}
return nil
}
if err := r.rows.Err(); err != nil {
return err
}
return io.EOF
}
func (r *stdRows) HasNextResultSet() bool {
return r.rows.totals != nil
}
func (r *stdRows) NextResultSet() error {
switch {
case r.rows.totals != nil:
r.rows.block = r.rows.totals
r.rows.totals = nil
default:
return io.EOF
}
return nil
}
func (r *stdRows) Close() error {
return r.rows.Close()
}
-197
View File
@@ -1,197 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
"crypto/tls"
"fmt"
"log"
"net"
"os"
"time"
"github.com/ClickHouse/clickhouse-go/v2/lib/binary"
"github.com/ClickHouse/clickhouse-go/v2/lib/io"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
)
func dial(ctx context.Context, addr string, num int, opt *Options) (*connect, error) {
var (
err error
conn net.Conn
debugf = func(format string, v ...interface{}) {}
)
switch {
case opt.DialContext != nil:
conn, err = opt.DialContext(ctx, addr)
default:
switch {
case opt.TLS != nil:
conn, err = tls.DialWithDialer(&net.Dialer{Timeout: opt.DialTimeout}, "tcp", addr, opt.TLS)
default:
conn, err = net.DialTimeout("tcp", addr, opt.DialTimeout)
}
}
if err != nil {
return nil, err
}
if opt.Debug {
if opt.Debugf != nil {
debugf = opt.Debugf
} else {
debugf = log.New(os.Stdout, fmt.Sprintf("[clickhouse][conn=%d][%s]", num, conn.RemoteAddr()), 0).Printf
}
}
var compression bool
if opt.Compression != nil {
compression = opt.Compression.Method == CompressionLZ4
}
var (
stream = io.NewStream(conn)
connect = &connect{
opt: opt,
conn: conn,
debugf: debugf,
stream: stream,
encoder: binary.NewEncoder(stream),
decoder: binary.NewDecoder(stream),
revision: proto.ClientTCPProtocolVersion,
structMap: &structMap{},
compression: compression,
connectedAt: time.Now(),
}
)
if err := connect.handshake(opt.Auth.Database, opt.Auth.Username, opt.Auth.Password); err != nil {
return nil, err
}
return connect, nil
}
// https://github.com/ClickHouse/ClickHouse/blob/master/src/Client/Connection.cpp
type connect struct {
opt *Options
conn net.Conn
debugf func(format string, v ...interface{})
server ServerVersion
stream *io.Stream
closed bool
encoder *binary.Encoder
decoder *binary.Decoder
released bool
revision uint64
structMap *structMap
compression bool
// lastUsedIn time.Time
connectedAt time.Time
}
func (c *connect) settings(querySettings Settings) []proto.Setting {
settings := make([]proto.Setting, 0, len(c.opt.Settings)+len(querySettings))
for k, v := range c.opt.Settings {
settings = append(settings, proto.Setting{
Key: k,
Value: v,
})
}
for k, v := range querySettings {
settings = append(settings, proto.Setting{
Key: k,
Value: v,
})
}
return settings
}
func (c *connect) isBad() bool {
switch {
case c.closed:
return true
}
if err := c.connCheck(); err != nil {
return true
}
return false
}
func (c *connect) close() error {
if c.closed {
return nil
}
c.closed = true
c.encoder = nil
c.decoder = nil
c.stream.Close()
if err := c.conn.Close(); err != nil {
return err
}
return nil
}
func (c *connect) progress() (*Progress, error) {
var progress proto.Progress
if err := progress.Decode(c.decoder, c.revision); err != nil {
return nil, err
}
c.debugf("[progress] %s", &progress)
return &progress, nil
}
func (c *connect) exception() error {
var e Exception
if err := e.Decode(c.decoder); err != nil {
return err
}
c.debugf("[exception] %s", e.Error())
return &e
}
func (c *connect) sendData(block *proto.Block, name string) error {
c.debugf("[send data] compression=%t", c.compression)
if err := c.encoder.Byte(proto.ClientData); err != nil {
return err
}
if err := c.encoder.String(name); err != nil {
return err
}
if c.compression {
c.stream.Compress(true)
defer func() {
c.stream.Compress(false)
c.encoder.Flush()
}()
}
return block.Encode(c.encoder, c.revision)
}
func (c *connect) readData(packet byte, compressible bool) (*proto.Block, error) {
if _, err := c.decoder.String(); err != nil {
return nil, err
}
if compressible && c.compression {
c.stream.Compress(true)
defer c.stream.Compress(false)
}
var block proto.Block
if err := block.Decode(c.decoder, c.revision); err != nil {
return nil, err
}
block.Packet = packet
c.debugf("[read data] compression=%t. block: columns=%d, rows=%d", c.compression, len(block.Columns), block.Rows())
return &block, nil
}
-37
View File
@@ -1,37 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
)
func (c *connect) asyncInsert(ctx context.Context, query string, wait bool) error {
options := queryOptions(ctx)
{
options.settings["async_insert"] = 1
options.settings["wait_for_async_insert"] = 0
if wait {
options.settings["wait_for_async_insert"] = 1
}
}
if err := c.sendQuery(query, &options); err != nil {
return err
}
return c.process(ctx, options.onProcess())
}
-181
View File
@@ -1,181 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
"fmt"
"os"
"regexp"
"strings"
"time"
"github.com/ClickHouse/clickhouse-go/v2/lib/column"
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
)
var splitInsertRe = regexp.MustCompile(`(?i)\sVALUES\s*\(`)
func (c *connect) prepareBatch(ctx context.Context, query string, release func(*connect, error)) (*batch, error) {
query = splitInsertRe.Split(query, -1)[0]
if !strings.HasSuffix(strings.TrimSpace(strings.ToUpper(query)), "VALUES") {
query += " VALUES"
}
options := queryOptions(ctx)
if deadline, ok := ctx.Deadline(); ok {
c.conn.SetDeadline(deadline)
defer c.conn.SetDeadline(time.Time{})
}
if err := c.sendQuery(query, &options); err != nil {
release(c, err)
return nil, err
}
var (
onProcess = options.onProcess()
block, err = c.firstBlock(ctx, onProcess)
)
if err != nil {
release(c, err)
return nil, err
}
return &batch{
ctx: ctx,
conn: c,
block: block,
release: func(err error) {
release(c, err)
},
onProcess: onProcess,
}, nil
}
type batch struct {
err error
ctx context.Context
conn *connect
sent bool
block *proto.Block
release func(error)
onProcess *onProcess
}
func (b *batch) Abort() error {
defer func() {
b.sent = true
b.release(os.ErrProcessDone)
}()
if b.sent {
return ErrBatchAlreadySent
}
return nil
}
func (b *batch) Append(v ...interface{}) error {
if b.sent {
return ErrBatchAlreadySent
}
if err := b.block.Append(v...); err != nil {
b.release(err)
return err
}
return nil
}
func (b *batch) AppendStruct(v interface{}) error {
values, err := b.conn.structMap.Map("AppendStruct", b.block.ColumnsNames(), v, false)
if err != nil {
return err
}
return b.Append(values...)
}
func (b *batch) Column(idx int) driver.BatchColumn {
if len(b.block.Columns) <= idx {
b.release(nil)
return &batchColumn{
err: &OpError{
Op: "batch.Column",
Err: fmt.Errorf("invalid column index %d", idx),
},
}
}
return &batchColumn{
batch: b,
column: b.block.Columns[idx],
release: func(err error) {
b.err = err
b.release(err)
},
}
}
func (b *batch) Send() (err error) {
defer func() {
b.sent = true
b.release(err)
}()
if b.sent {
return ErrBatchAlreadySent
}
if b.err != nil {
return b.err
}
if b.block.Rows() != 0 {
if err = b.conn.sendData(b.block, ""); err != nil {
return err
}
}
if err = b.conn.sendData(&proto.Block{}, ""); err != nil {
return err
}
if err = b.conn.encoder.Flush(); err != nil {
return err
}
if err = b.conn.process(b.ctx, b.onProcess); err != nil {
return err
}
return nil
}
type batchColumn struct {
err error
batch *batch
column column.Interface
release func(error)
}
func (b *batchColumn) Append(v interface{}) (err error) {
if b.batch.sent {
return ErrBatchAlreadySent
}
if b.err != nil {
b.release(b.err)
return b.err
}
if _, err = b.column.Append(v); err != nil {
b.release(err)
return err
}
return nil
}
var (
_ (driver.Batch) = (*batch)(nil)
_ (driver.BatchColumn) = (*batchColumn)(nil)
)
-60
View File
@@ -1,60 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//go:build linux || darwin || dragonfly || freebsd || netbsd || openbsd || solaris || illumos
// +build linux darwin dragonfly freebsd netbsd openbsd solaris illumos
package clickhouse
import (
"errors"
"io"
"syscall"
)
func (c *connect) connCheck() error {
var sysErr error
sysConn, ok := c.conn.(syscall.Conn)
if !ok {
return nil
}
rawConn, err := sysConn.SyscallConn()
if err != nil {
return err
}
err = rawConn.Read(func(fd uintptr) bool {
var buf [1]byte
n, err := syscall.Read(int(fd), buf[:])
switch {
case n == 0 && err == nil:
sysErr = io.EOF
case n > 0:
sysErr = errors.New("unexpected read from socket")
case err == syscall.EAGAIN || err == syscall.EWOULDBLOCK:
sysErr = nil
default:
sysErr = err
}
return true
})
if err != nil {
return err
}
return sysErr
}
-35
View File
@@ -1,35 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//go:build !linux && !darwin && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !illumos
// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
package clickhouse
import (
"context"
"time"
)
func (c *connect) connCheck() error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second))
defer cancel()
if err := c.ping(ctx); err != nil {
return err
}
return nil
}
-41
View File
@@ -1,41 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
"time"
)
func (c *connect) exec(ctx context.Context, query string, args ...interface{}) error {
var (
options = queryOptions(ctx)
body, err = bind(c.server.Timezone, query, args...)
)
if err != nil {
return err
}
if deadline, ok := ctx.Deadline(); ok {
c.conn.SetDeadline(deadline)
defer c.conn.SetDeadline(time.Time{})
}
if err := c.sendQuery(body, &options); err != nil {
return err
}
return c.process(ctx, options.onProcess())
}
-79
View File
@@ -1,79 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"fmt"
"time"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
)
func (c *connect) handshake(database, username, password string) error {
c.debugf("[handshake] -> %s", proto.ClientHandshake{})
c.conn.SetDeadline(time.Now().Add(c.opt.DialTimeout))
defer c.conn.SetDeadline(time.Time{})
{
c.encoder.Byte(proto.ClientHello)
if err := (&proto.ClientHandshake{}).Encode(c.encoder); err != nil {
return err
}
{
if err := c.encoder.String(database); err != nil {
return err
}
if err := c.encoder.String(username); err != nil {
return err
}
if err := c.encoder.String(password); err != nil {
return err
}
}
if err := c.encoder.Flush(); err != nil {
return err
}
}
{
packet, err := c.decoder.ReadByte()
if err != nil {
return err
}
switch packet {
case proto.ServerException:
return c.exception()
case proto.ServerHello:
if err := c.server.Decode(c.decoder); err != nil {
return err
}
case proto.ServerEndOfStream:
c.debugf("[handshake] <- end of stream")
return nil
default:
return fmt.Errorf("[handshake] unexpected packet [%d] from server", packet)
}
}
if c.server.Revision < proto.DBMS_MIN_REVISION_WITH_CLIENT_INFO {
return ErrUnsupportedServerRevision
}
if c.revision > c.server.Revision {
c.revision = c.server.Revision
c.debugf("[handshake] downgrade client proto")
}
c.debugf("[handshake] <- %s", c.server)
return nil
}
-88
View File
@@ -1,88 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"time"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
)
type Log struct {
Time time.Time
TimeMicro uint32
Hostname string
QueryID string
ThreadID uint64
Priority int8
Source string
Text string
}
func (c *connect) logs() ([]Log, error) {
block, err := c.readData(proto.ServerLog, false)
if err != nil {
return nil, err
}
c.debugf("[logs] rows=%d", block.Rows())
var (
logs []Log
names = block.ColumnsNames()
)
for r := 0; r < block.Rows(); r++ {
var log Log
for i, b := range block.Columns {
switch names[i] {
case "event_time":
if err := b.ScanRow(&log.Time, r); err != nil {
return nil, err
}
case "event_time_microseconds":
if err := b.ScanRow(&log.TimeMicro, r); err != nil {
return nil, err
}
case "host_name":
if err := b.ScanRow(&log.Hostname, r); err != nil {
return nil, err
}
case "query_id":
if err := b.ScanRow(&log.QueryID, r); err != nil {
return nil, err
}
case "thread_id":
if err := b.ScanRow(&log.ThreadID, r); err != nil {
return nil, err
}
case "priority":
if err := b.ScanRow(&log.Priority, r); err != nil {
return nil, err
}
case "source":
if err := b.ScanRow(&log.Source, r); err != nil {
return nil, err
}
case "text":
if err := b.ScanRow(&log.Text, r); err != nil {
return nil, err
}
}
}
logs = append(logs, log)
}
return logs, nil
}
-61
View File
@@ -1,61 +0,0 @@
// Licensed to ClickHouse, Inc. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. ClickHouse, Inc. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package clickhouse
import (
"context"
"fmt"
"time"
"github.com/ClickHouse/clickhouse-go/v2/lib/proto"
)
// Connection::ping
// https://github.com/ClickHouse/ClickHouse/blob/master/src/Client/Connection.cpp
func (c *connect) ping(ctx context.Context) (err error) {
if deadline, ok := ctx.Deadline(); ok {
c.conn.SetDeadline(deadline)
defer c.conn.SetDeadline(time.Time{})
}
c.debugf("[ping] -> ping")
if err := c.encoder.Byte(proto.ClientPing); err != nil {
return err
}
if err := c.encoder.Flush(); err != nil {
return err
}
var packet byte
for {
if packet, err = c.decoder.ReadByte(); err != nil {
return err
}
switch packet {
case proto.ServerException:
return c.exception()
case proto.ServerProgress:
if _, err = c.progress(); err != nil {
return err
}
case proto.ServerPong:
c.debugf("[ping] <- pong")
return nil
default:
return fmt.Errorf("unexpected packet %d", packet)
}
}
}

Some files were not shown because too many files have changed in this diff Show More