mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-30 17:13:08 +08:00
fix: update vendor master 20220701 (#14563)
Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
+1
-3
@@ -2,7 +2,6 @@
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.cap
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
@@ -26,5 +25,4 @@ _testmain.go
|
||||
*.prof
|
||||
|
||||
coverage.txt
|
||||
.idea/**
|
||||
dev/*
|
||||
.idea/**
|
||||
+20
@@ -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)
|
||||
Generated
Vendored
+21
@@ -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
@@ -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
@@ -0,0 +1,307 @@
|
||||
# ClickHouse [](https://travis-ci.org/ClickHouse/clickhouse-go) [](https://goreportcard.com/report/github.com/ClickHouse/clickhouse-go) [](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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
|
||||
// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!illumos
|
||||
|
||||
package clickhouse
|
||||
|
||||
func (conn *connect) connCheck() error {
|
||||
return nil
|
||||
}
|
||||
+9
@@ -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
|
||||
Generated
Vendored
+144
@@ -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() + "')")
|
||||
}
|
||||
+107
@@ -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
|
||||
}
|
||||
+107
@@ -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
|
||||
}
|
||||
+21
@@ -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
|
||||
)
|
||||
+79
@@ -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
|
||||
}
|
||||
+78
@@ -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
|
||||
}
|
||||
Generated
Vendored
+37
-35
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
Generated
Vendored
-17
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+173
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
+277
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
|
||||
# ClickHouse Native protocol
|
||||
|
||||
# Handshake
|
||||
|
||||
+35
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,162 +0,0 @@
|
||||
# ClickHouse [](https://github.com/ClickHouse/clickhouse-go/actions/workflows/run-tests.yml) [](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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
-62
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user