click_house 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +125 -0
- data/.travis.yml +16 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +67 -0
- data/Makefile +9 -0
- data/README.md +413 -0
- data/Rakefile +8 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/click_house.gemspec +31 -0
- data/doc/logo.svg +37 -0
- data/docker-compose.yml +21 -0
- data/lib/click_house.rb +53 -0
- data/lib/click_house/config.rb +61 -0
- data/lib/click_house/connection.rb +48 -0
- data/lib/click_house/definition.rb +8 -0
- data/lib/click_house/definition/column.rb +46 -0
- data/lib/click_house/definition/column_set.rb +95 -0
- data/lib/click_house/errors.rb +7 -0
- data/lib/click_house/extend.rb +14 -0
- data/lib/click_house/extend/configurable.rb +11 -0
- data/lib/click_house/extend/connectible.rb +15 -0
- data/lib/click_house/extend/connection_database.rb +37 -0
- data/lib/click_house/extend/connection_healthy.rb +16 -0
- data/lib/click_house/extend/connection_inserting.rb +13 -0
- data/lib/click_house/extend/connection_selective.rb +23 -0
- data/lib/click_house/extend/connection_table.rb +81 -0
- data/lib/click_house/extend/type_definition.rb +15 -0
- data/lib/click_house/middleware.rb +9 -0
- data/lib/click_house/middleware/logging.rb +61 -0
- data/lib/click_house/middleware/parse_csv.rb +17 -0
- data/lib/click_house/middleware/raise_error.rb +25 -0
- data/lib/click_house/response.rb +8 -0
- data/lib/click_house/response/factory.rb +17 -0
- data/lib/click_house/response/result_set.rb +79 -0
- data/lib/click_house/type.rb +16 -0
- data/lib/click_house/type/base_type.rb +15 -0
- data/lib/click_house/type/boolean_type.rb +18 -0
- data/lib/click_house/type/date_time_type.rb +15 -0
- data/lib/click_house/type/date_type.rb +15 -0
- data/lib/click_house/type/decimal_type.rb +15 -0
- data/lib/click_house/type/fixed_string_type.rb +15 -0
- data/lib/click_house/type/float_type.rb +15 -0
- data/lib/click_house/type/integer_type.rb +15 -0
- data/lib/click_house/type/nullable_type.rb +21 -0
- data/lib/click_house/type/undefined_type.rb +15 -0
- data/lib/click_house/util.rb +8 -0
- data/lib/click_house/util/pretty.rb +30 -0
- data/lib/click_house/util/statement.rb +21 -0
- data/lib/click_house/version.rb +5 -0
- data/log/.keep +0 -0
- data/tmp/.keep +1 -0
- metadata +208 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 05f0f9426500a803ec5d2d37b1a2a06d2eb2d3e4b934eac19a434acc5086dc5c
|
4
|
+
data.tar.gz: c079c080dedbcc3342bbfde789099ddacf714a16029779bddbd8352d1912f1b0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f2f1d0d8f438b317ee475499e24f9d3ecae9f2b6a872a21695f11e0f8d744693790cd6f1caa50beab7e2c11f008952e453ca37d9282caeed18a95f54e99dff44
|
7
|
+
data.tar.gz: 17f7aff9dad97b3ad4384618b8cf8ce92f550ec9ee5b7ee524413458dce48f7eb08a69d4f78abf5862ac01acd33f1257de37403f6ce42595b152435819bc2e2f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-performance
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
AutoCorrect: false
|
6
|
+
Exclude:
|
7
|
+
- 'click_house.gemspec'
|
8
|
+
- 'bin/*'
|
9
|
+
- 'spec/**/*'
|
10
|
+
- 'vendor/**/*'
|
11
|
+
TargetRubyVersion: 2.6.5
|
12
|
+
|
13
|
+
Bundler/OrderedGems:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
# ============================== Documentation ======================
|
17
|
+
Style/Documentation:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
# ============================== Metrics ============================
|
21
|
+
Metrics/ClassLength:
|
22
|
+
Max: 180
|
23
|
+
Metrics/BlockLength:
|
24
|
+
Enabled: true
|
25
|
+
Metrics/MethodLength:
|
26
|
+
Max: 25
|
27
|
+
Metrics/LineLength:
|
28
|
+
Max: 140
|
29
|
+
Metrics/AbcSize:
|
30
|
+
Max: 40
|
31
|
+
|
32
|
+
# ============================== Naming =============================
|
33
|
+
Naming/PredicateName:
|
34
|
+
NamePrefixBlacklist:
|
35
|
+
- is_
|
36
|
+
Naming/FileName:
|
37
|
+
Enabled: true
|
38
|
+
Exclude:
|
39
|
+
- 'Gemfile'
|
40
|
+
Naming/UncommunicativeMethodParamName:
|
41
|
+
Enabled: false
|
42
|
+
Naming/AccessorMethodName:
|
43
|
+
Enabled: false
|
44
|
+
|
45
|
+
# ============================== Layout =============================
|
46
|
+
Layout/AlignHash:
|
47
|
+
EnforcedHashRocketStyle: key
|
48
|
+
EnforcedColonStyle: key
|
49
|
+
Layout/AlignParameters:
|
50
|
+
EnforcedStyle: with_fixed_indentation
|
51
|
+
Layout/CaseIndentation:
|
52
|
+
EnforcedStyle: case
|
53
|
+
IndentOneStep: false
|
54
|
+
Layout/MultilineMethodCallIndentation:
|
55
|
+
Enabled: true
|
56
|
+
EnforcedStyle: indented
|
57
|
+
Layout/SpaceBeforeBlockBraces:
|
58
|
+
EnforcedStyle: space
|
59
|
+
EnforcedStyleForEmptyBraces: space
|
60
|
+
Layout/EmptyLines:
|
61
|
+
Enabled: true
|
62
|
+
Layout/EmptyLineAfterMagicComment:
|
63
|
+
Enabled: false
|
64
|
+
Layout/EmptyLinesAroundBlockBody:
|
65
|
+
Enabled: true
|
66
|
+
Layout/EndAlignment:
|
67
|
+
EnforcedStyleAlignWith: variable
|
68
|
+
Layout/IndentFirstHashElement:
|
69
|
+
EnforcedStyle: consistent
|
70
|
+
Layout/IndentHeredoc:
|
71
|
+
Enabled: false
|
72
|
+
Layout/RescueEnsureAlignment:
|
73
|
+
Enabled: false
|
74
|
+
|
75
|
+
# ============================== Style ==============================
|
76
|
+
Style/RescueModifier:
|
77
|
+
Enabled: true
|
78
|
+
Style/PercentLiteralDelimiters:
|
79
|
+
PreferredDelimiters:
|
80
|
+
default: '[]'
|
81
|
+
'%i': '[]'
|
82
|
+
'%w': '[]'
|
83
|
+
Exclude:
|
84
|
+
- 'config/routes.rb'
|
85
|
+
Style/StringLiterals:
|
86
|
+
Enabled: true
|
87
|
+
Style/AsciiComments:
|
88
|
+
Enabled: false
|
89
|
+
Style/Copyright:
|
90
|
+
Enabled: false
|
91
|
+
Style/SafeNavigation:
|
92
|
+
Enabled: false
|
93
|
+
Style/Lambda:
|
94
|
+
Enabled: false
|
95
|
+
Style/Alias:
|
96
|
+
Enabled: true
|
97
|
+
EnforcedStyle: prefer_alias_method
|
98
|
+
Style/ClassAndModuleChildren:
|
99
|
+
Enabled: true
|
100
|
+
EnforcedStyle: nested
|
101
|
+
Style/TrailingCommaInArrayLiteral:
|
102
|
+
Enabled: true
|
103
|
+
EnforcedStyleForMultiline: no_comma
|
104
|
+
Style/RescueStandardError:
|
105
|
+
Enabled: true
|
106
|
+
EnforcedStyle: explicit
|
107
|
+
Style/InverseMethods:
|
108
|
+
AutoCorrect: false
|
109
|
+
Enabled: true
|
110
|
+
Style/IfUnlessModifier:
|
111
|
+
Enabled: false
|
112
|
+
Style/SpecialGlobalVars:
|
113
|
+
Enabled: false
|
114
|
+
Style/BlockComments:
|
115
|
+
Enabled: false
|
116
|
+
Style/GuardClause:
|
117
|
+
Enabled: false
|
118
|
+
Style/TrailingCommaInHashLiteral:
|
119
|
+
Enabled: false
|
120
|
+
|
121
|
+
# ============================== Lint ==============================
|
122
|
+
Lint/DuplicateMethods:
|
123
|
+
Enabled: false
|
124
|
+
Lint/AmbiguousOperator:
|
125
|
+
Enabled: false
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 2.6.5
|
5
|
+
|
6
|
+
services:
|
7
|
+
- docker
|
8
|
+
|
9
|
+
before_install:
|
10
|
+
- docker pull yandex/clickhouse-server
|
11
|
+
- docker run -d -p 127.0.0.1:8123:8123 yandex/clickhouse-server
|
12
|
+
- bundle install
|
13
|
+
|
14
|
+
script:
|
15
|
+
- bundle exec rubocop
|
16
|
+
- bundle exec rspec
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
click_house (1.0.0)
|
5
|
+
faraday
|
6
|
+
faraday_middleware
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ast (2.4.0)
|
12
|
+
coderay (1.1.2)
|
13
|
+
diff-lcs (1.3)
|
14
|
+
faraday (0.17.0)
|
15
|
+
multipart-post (>= 1.2, < 3)
|
16
|
+
faraday_middleware (0.13.1)
|
17
|
+
faraday (>= 0.7.4, < 1.0)
|
18
|
+
jaro_winkler (1.5.4)
|
19
|
+
method_source (0.9.2)
|
20
|
+
multipart-post (2.1.1)
|
21
|
+
parallel (1.18.0)
|
22
|
+
parser (2.6.5.0)
|
23
|
+
ast (~> 2.4.0)
|
24
|
+
pry (0.12.2)
|
25
|
+
coderay (~> 1.1.0)
|
26
|
+
method_source (~> 0.9.0)
|
27
|
+
rainbow (3.0.0)
|
28
|
+
rake (13.0.0)
|
29
|
+
rspec (3.9.0)
|
30
|
+
rspec-core (~> 3.9.0)
|
31
|
+
rspec-expectations (~> 3.9.0)
|
32
|
+
rspec-mocks (~> 3.9.0)
|
33
|
+
rspec-core (3.9.0)
|
34
|
+
rspec-support (~> 3.9.0)
|
35
|
+
rspec-expectations (3.9.0)
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
37
|
+
rspec-support (~> 3.9.0)
|
38
|
+
rspec-mocks (3.9.0)
|
39
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
40
|
+
rspec-support (~> 3.9.0)
|
41
|
+
rspec-support (3.9.0)
|
42
|
+
rubocop (0.76.0)
|
43
|
+
jaro_winkler (~> 1.5.1)
|
44
|
+
parallel (~> 1.10)
|
45
|
+
parser (>= 2.6)
|
46
|
+
rainbow (>= 2.2.2, < 4.0)
|
47
|
+
ruby-progressbar (~> 1.7)
|
48
|
+
unicode-display_width (>= 1.4.0, < 1.7)
|
49
|
+
rubocop-performance (1.5.0)
|
50
|
+
rubocop (>= 0.71.0)
|
51
|
+
ruby-progressbar (1.10.1)
|
52
|
+
unicode-display_width (1.6.0)
|
53
|
+
|
54
|
+
PLATFORMS
|
55
|
+
ruby
|
56
|
+
|
57
|
+
DEPENDENCIES
|
58
|
+
bundler (~> 1.17)
|
59
|
+
click_house!
|
60
|
+
pry
|
61
|
+
rake (~> 13.0)
|
62
|
+
rspec (~> 3.0)
|
63
|
+
rubocop
|
64
|
+
rubocop-performance
|
65
|
+
|
66
|
+
BUNDLED WITH
|
67
|
+
1.17.3
|
data/Makefile
ADDED
data/README.md
ADDED
@@ -0,0 +1,413 @@
|
|
1
|
+
![](./doc/logo.svg?sanitize=true)
|
2
|
+
|
3
|
+
# ClickHouse Ruby driver
|
4
|
+
|
5
|
+
[![pipeline status](https://travis-ci.com/shlima/click_house.svg?branch=master)](https://travis-ci.com/shlima/click_house)
|
6
|
+
|
7
|
+
```bash
|
8
|
+
gem install click_house
|
9
|
+
```
|
10
|
+
|
11
|
+
A modern Ruby database driver for ClickHouse. [ClickHouse](https://clickhouse.yandex)
|
12
|
+
is a high-performance column-oriented database management system developed by
|
13
|
+
[Yandex](https://yandex.com/company) which operates Russia's most popular search engine.
|
14
|
+
|
15
|
+
> This development was inspired by currently [unmaintainable alternative](https://github.com/archan937/clickhouse)
|
16
|
+
> but rewritten and well tested. Requires modern Ruby (>= 2.6) and Yandex ClickHouse
|
17
|
+
|
18
|
+
### Why use the HTTP interface and not the TCP interface?
|
19
|
+
|
20
|
+
Well, the developers of ClickHouse themselves [discourage](https://github.com/yandex/ClickHouse/issues/45#issuecomment-231194134) using the TCP interface.
|
21
|
+
|
22
|
+
> TCP transport is more specific, we don't want to expose details.
|
23
|
+
Despite we have full compatibility of protocol of different versions of client and server, we want to keep the ability to "break" it for very old clients. And that protocol is not too clean to make a specification.
|
24
|
+
|
25
|
+
# TOC
|
26
|
+
|
27
|
+
* [Configuration](#configuration)
|
28
|
+
* [Usage](#usage)
|
29
|
+
* [Queries](#queries)
|
30
|
+
* [Insert](#insert)
|
31
|
+
* [Create a table](#create-a-table)
|
32
|
+
* [Type casting](#type-casting)
|
33
|
+
* [Using with a connection pool](#using-with-a-connection-pool)
|
34
|
+
* [Using with Rails](#using-with-rails)
|
35
|
+
|
36
|
+
## Configuration
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
ClickHouse.config do |config|
|
40
|
+
config.logger = Logger.new(STDOUT)
|
41
|
+
config.adapter = :net_http
|
42
|
+
config.database = 'metrics'
|
43
|
+
config.url = 'http://localhost:8123'
|
44
|
+
config.timeout = 60
|
45
|
+
config.open_timeout = 3
|
46
|
+
config.ssl_verify = false
|
47
|
+
|
48
|
+
# or provide connection options separately
|
49
|
+
config.scheme = 'http'
|
50
|
+
config.host = 'localhost'
|
51
|
+
config.port = 'port'
|
52
|
+
|
53
|
+
# if you use HTTP basic Auth
|
54
|
+
config.username = 'user'
|
55
|
+
config.password = 'password'
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
Alternative, you can assign configuration parameters via a hash
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
ClickHouse.config.assign(logger: Logger.new(STDOUT))
|
63
|
+
```
|
64
|
+
|
65
|
+
Now you are able to communicate with ClickHouse:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
ClickHouse.connection.ping #=> true
|
69
|
+
```
|
70
|
+
You can easily build a new raw connection
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
@connection = ClickHouse::Connection.new(ClickHouse::Config.new(logger: Rails.logger))
|
74
|
+
@connection.ping
|
75
|
+
```
|
76
|
+
|
77
|
+
## Usage
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
ClickHouse.connection.ping #=> true
|
81
|
+
ClickHouse.connection.replicas_status #=> true
|
82
|
+
|
83
|
+
ClickHouse.connection.databases #=> ["default", "system"]
|
84
|
+
ClickHouse.connection.create_database('metrics', if_not_exists: true, engine: nil, cluster: nil)
|
85
|
+
ClickHouse.connection.drop_database('metrics', if_exists: true, cluster: nil)
|
86
|
+
|
87
|
+
ClickHouse.connection.tables #=> ["visits"]
|
88
|
+
ClickHouse.connection.describe_table('visits') #=> [{"name"=>"id", "type"=>"FixedString(16)", "default_type"=>""}]
|
89
|
+
ClickHouse.connection.table_exists?('visits', temporary: nil) #=> true
|
90
|
+
ClickHouse.connection.drop_table('visits', if_exists: true, temporary: nil, cluster: nil)
|
91
|
+
ClickHouse.connection.create_table(*) # see <Create a table> section
|
92
|
+
```
|
93
|
+
|
94
|
+
## Queries
|
95
|
+
### Select All
|
96
|
+
|
97
|
+
Select all type-casted result set
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
@result = ClickHouse.connection.select_all('SELECT * FROM visits')
|
101
|
+
|
102
|
+
# all enumerable methods are delegated like #each, #map, #select etc
|
103
|
+
@result.to_a #=> [{"date"=>#<Date: 2000-01-01>, "id"=>1}]
|
104
|
+
|
105
|
+
# you can access raw data
|
106
|
+
@result.meta #=> [{"name"=>"date", "type"=>"Date"}, {"name"=>"id", "type"=>"UInt32"}]
|
107
|
+
@result.data #=> [{"date"=>"2000-01-01", "id"=>1}, {"date"=>"2000-01-02", "id"=>2}]
|
108
|
+
@result.statistics #=> {"elapsed"=>0.0002271, "rows_read"=>2, "bytes_read"=>12}
|
109
|
+
```
|
110
|
+
|
111
|
+
### Select Value
|
112
|
+
|
113
|
+
Select value returns exactly one type-casted value
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
ClickHouse.connection.select_value('SELECT COUNT(*) from visits') #=> 0
|
117
|
+
ClickHouse.connection.select_value("SELECT toDate('2019-01-01')") #=> #<Date: 2019-01-01>
|
118
|
+
ClickHouse.connection.select_value("SELECT toDateOrZero(NULL)") #=> nil
|
119
|
+
```
|
120
|
+
|
121
|
+
### Select One
|
122
|
+
|
123
|
+
Returns a record hash with the column names as keys and column values as values.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
ClickHouse.connection.select_one('SELECT date, SUM(id) AS sum FROM visits GROUP BY date')
|
127
|
+
#=> {"date"=>#<Date: 2000-01-01>, "sum"=>1}
|
128
|
+
```
|
129
|
+
|
130
|
+
### Execute Raw SQL
|
131
|
+
|
132
|
+
By default, gem provides parser for `JSON` and `CSV` response formats. Type conversion
|
133
|
+
available for the `JSON`.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# format not specified
|
137
|
+
response = ClickHouse.connection.execute <<~SQL
|
138
|
+
SELECT count(*) AS counter FROM rspec
|
139
|
+
SQL
|
140
|
+
|
141
|
+
response.body #=> "2\n"
|
142
|
+
|
143
|
+
# JSON
|
144
|
+
response = ClickHouse.connection.execute <<~SQL
|
145
|
+
SELECT count(*) AS counter FROM rspec FORMAT JSON
|
146
|
+
SQL
|
147
|
+
|
148
|
+
response.body #=> {"meta"=>[{"name"=>"counter", "type"=>"UInt64"}], "data"=>[{"counter"=>"2"}], "rows"=>1, "statistics"=>{"elapsed"=>0.0002412, "rows_read"=>2, "bytes_read"=>4}}
|
149
|
+
|
150
|
+
# CSV
|
151
|
+
response = ClickHouse.connection.execute <<~SQL
|
152
|
+
SELECT count(*) AS counter FROM rspec FORMAT CSV
|
153
|
+
SQL
|
154
|
+
|
155
|
+
response.body #=> [["2"]]
|
156
|
+
|
157
|
+
# You may use any format supported by ClickHouse
|
158
|
+
response = ClickHouse.connection.execute <<~SQL
|
159
|
+
SELECT count(*) AS counter FROM rspec FORMAT RowBinary
|
160
|
+
SQL
|
161
|
+
|
162
|
+
response.body #=> "\u0002\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
163
|
+
```
|
164
|
+
|
165
|
+
## Insert
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
ClickHouse.connection.insert('table', columns: %i[id name]) do |buffer|
|
169
|
+
buffer << [1, 'Mercury']
|
170
|
+
buffer << [2, 'Venus']
|
171
|
+
end
|
172
|
+
|
173
|
+
ClickHouse.connection.insert('table', columns: %i[id name], values: [[1, 'Mercury'], [2, 'Venus']])
|
174
|
+
#=> true
|
175
|
+
```
|
176
|
+
|
177
|
+
## Create a table
|
178
|
+
### Create table using DSL
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
ClickHouse.connection.create_table('visits', if_not_exists: true, engine: 'MergeTree(date, (year, date), 8192)') do |t|
|
182
|
+
t.FixedString :id, 16
|
183
|
+
t.UInt16 :year
|
184
|
+
t.Date :date
|
185
|
+
t.DateTime :time, 'UTC'
|
186
|
+
t.Decimal :money, 5, 4
|
187
|
+
t.String :event
|
188
|
+
t.UInt32 :user_id
|
189
|
+
t.UInt32 :Float32
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
### Create nullable columns
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
ClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|
|
197
|
+
t.UInt16 :id, 16, nullable: true
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
### Set column options
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
ClickHouse.connection.create_table('visits', engine: 'MergeTree(date, (year, date), 8192)') do |t|
|
205
|
+
t.UInt16 :year
|
206
|
+
t.Date :date
|
207
|
+
t.UInt16 :id, 16, default: 0, ttl: 'date + INTERVAL 1 DAY'
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
### Define column with custom SQL
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
ClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|
|
215
|
+
t << "vendor Enum('microsoft' = 1, 'apple' = 2)"
|
216
|
+
t << "tags Array(String)"
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
### Define nested structures
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
ClickHouse.connection.create_table('visits', engine: 'TinyLog') do |t|
|
224
|
+
t.UInt8 :id
|
225
|
+
t.Nested :json do |n|
|
226
|
+
n.UInt8 :cid
|
227
|
+
n.Date :created_at
|
228
|
+
n.Date :updated_at
|
229
|
+
end
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
### Set table options
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
ClickHouse.connection.create_table('visits',
|
237
|
+
order: 'year',
|
238
|
+
ttl: 'date + INTERVAL 1 DAY',
|
239
|
+
sample: 'year',
|
240
|
+
settings: 'index_granularity=8192',
|
241
|
+
primary_key: 'year',
|
242
|
+
engine: 'MergeTree') do |t|
|
243
|
+
t.UInt16 :year
|
244
|
+
t.Date :date
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
### Create table with raw SQL
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
ClickHouse.connection.execute <<~SQL
|
252
|
+
CREATE TABLE visits(int Nullable(Int8), date Nullable(Date)) ENGINE TinyLog
|
253
|
+
SQL
|
254
|
+
```
|
255
|
+
|
256
|
+
## Type casting
|
257
|
+
|
258
|
+
By default gem provides all necessary type casting, but you may overwrite or define
|
259
|
+
your own logic
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
class DateType
|
263
|
+
def cast(value)
|
264
|
+
Date.parse(value)
|
265
|
+
end
|
266
|
+
|
267
|
+
def serialize(value)
|
268
|
+
value.strftime('%Y-%m-%d')
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
ClickHouse.add_type('Date', DateType.new)
|
273
|
+
```
|
274
|
+
|
275
|
+
Actually `serialize` function is not used for now, but you may use it manually:
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
time_type = DateTimeType.new
|
279
|
+
string_type = FixedStringType.new
|
280
|
+
|
281
|
+
ClickHouse.connection.insert('table', columns: %i[name time]) do |buffer|
|
282
|
+
buffer << [string_type.serialize('a' * 1000, 20), time.serialize(Time.current, 'Europe/Moscow')]
|
283
|
+
end
|
284
|
+
```
|
285
|
+
|
286
|
+
If native type supports arguments, define type with `%s` argument:
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
class DateTimeType
|
290
|
+
def cast(value, time_zone)
|
291
|
+
Time.parse("#{value} #{time_zone}")
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
ClickHouse.add_type('DateTime(%s)', DateTimeType.new)
|
296
|
+
```
|
297
|
+
|
298
|
+
## Using with a connection pool
|
299
|
+
|
300
|
+
```ruby
|
301
|
+
require 'connection_pool'
|
302
|
+
|
303
|
+
ClickHouse.connection = ConnectionPool.new(size: 2) do
|
304
|
+
ClickHouse::Connection.new(ClickHouse::Config.new(url: 'http://replica.example.com'))
|
305
|
+
end
|
306
|
+
|
307
|
+
ClickHouse.connection.with do |conn|
|
308
|
+
conn.tables
|
309
|
+
end
|
310
|
+
```
|
311
|
+
|
312
|
+
## Using with Rails
|
313
|
+
|
314
|
+
```yml
|
315
|
+
# config/click_house.yml
|
316
|
+
|
317
|
+
default: &default
|
318
|
+
url: http://localhost:8123
|
319
|
+
timeout: 60
|
320
|
+
open_timeout: 3
|
321
|
+
|
322
|
+
development:
|
323
|
+
database: ecliptic_development
|
324
|
+
<<: *default
|
325
|
+
|
326
|
+
test:
|
327
|
+
database: ecliptic_test
|
328
|
+
<<: *default
|
329
|
+
```
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
# config/initializers/click_house.rb
|
333
|
+
|
334
|
+
ClickHouse.config do |config|
|
335
|
+
config.logger = Rails.logger
|
336
|
+
config.assign(Rails.application.config_for('click_house'))
|
337
|
+
end
|
338
|
+
```
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
# lib/tasks/click_house.rake
|
342
|
+
namespace :click_house do
|
343
|
+
task prepare: :environment do
|
344
|
+
@environments = Rails.env.development? ? %w[development test] : [Rails.env]
|
345
|
+
end
|
346
|
+
|
347
|
+
task drop: :prepare do
|
348
|
+
@environments.each do |env|
|
349
|
+
config = ClickHouse.config.clone.assign(Rails.application.config_for('click_house', env: env))
|
350
|
+
connection = ClickHouse::Connection.new(config)
|
351
|
+
connection.drop_database(config.database, if_exists: true)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
task create: :prepare do
|
356
|
+
@environments.each do |env|
|
357
|
+
config = ClickHouse.config.clone.assign(Rails.application.config_for('click_house', env: env))
|
358
|
+
connection = ClickHouse::Connection.new(config)
|
359
|
+
connection.create_database(config.database, if_not_exists: true)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
```
|
364
|
+
|
365
|
+
Prepare the ClickHouse database:
|
366
|
+
|
367
|
+
```bash
|
368
|
+
rake click_house:drop click_house:create
|
369
|
+
```
|
370
|
+
|
371
|
+
If your are using SQL Database in Rails, you can manage ClickHouse migrations
|
372
|
+
using `ActiveRecord::Migration` mechanism
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
class CreateAdvertVisits < ActiveRecord::Migration[6.0]
|
376
|
+
def up
|
377
|
+
ClickHouse.connection.create_table('visits', engine: 'MergeTree(date, (account_id, advert_id), 512)') do |t|
|
378
|
+
t.UInt16 :account_id
|
379
|
+
t.UInt16 :user_id
|
380
|
+
t.Date :date
|
381
|
+
t.DateTime :time, 'UTC'
|
382
|
+
t.UInt32 :ipv4
|
383
|
+
t.UInt64 :ipv6
|
384
|
+
t.UInt32 :device_type_id
|
385
|
+
t.UInt32 :os_family_id
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def down
|
390
|
+
ClickHouse.connection.drop_table('visits')
|
391
|
+
end
|
392
|
+
end
|
393
|
+
```
|
394
|
+
|
395
|
+
if you use `ActiveRecord`, you can use the ORM query builder by using fake models
|
396
|
+
|
397
|
+
````ruby
|
398
|
+
# FAKE MODEL FOR ClickHouse
|
399
|
+
class Visit < ApplicationRecord
|
400
|
+
scope :with_os, -> { where.not(os_family_id: nil) }
|
401
|
+
end
|
402
|
+
|
403
|
+
scope = Visit.with_os.select('COUNT(*) as counter').group(:ipv4)
|
404
|
+
ClickHouse.connection.select_all(scope.to_sql)
|
405
|
+
````
|
406
|
+
|
407
|
+
## Development
|
408
|
+
|
409
|
+
```bash
|
410
|
+
make dockerize
|
411
|
+
rspec
|
412
|
+
rubocop
|
413
|
+
```
|