clickhouse 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ module Unit
4
+ module Connection
5
+ module Query
6
+ class TestTable < MiniTest::Test
7
+
8
+ describe Clickhouse::Connection::Query::Table do
9
+ it "generates a 'CREATE TABLE' statement" do
10
+ table = Clickhouse::Connection::Query::Table.new("logs_test") do |t|
11
+ t.uint8 :id
12
+ t.float32 :price
13
+ t.string :name
14
+ t.date :date
15
+ t.date_time :time
16
+ t.fixed_string :hex_id, 8
17
+ t.engine "MergeTree(date, 8192)"
18
+ end
19
+
20
+ sql = <<-SQL
21
+ CREATE TABLE logs_test (
22
+ id UInt8,
23
+ price Float32,
24
+ name String,
25
+ date Date,
26
+ time DateTime,
27
+ hex_id FixedString(8)
28
+ )
29
+ ENGINE = MergeTree(date, 8192)
30
+ SQL
31
+
32
+ assert_equal sql.strip, table.to_sql.strip
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,131 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module Connection
5
+ class TestClient < MiniTest::Test
6
+
7
+ class Connection < SimpleConnection
8
+ include Clickhouse::Connection::Client
9
+ end
10
+
11
+ describe Clickhouse::Connection::Client do
12
+ before do
13
+ @connection = Connection.new
14
+ end
15
+
16
+ describe "#connect!" do
17
+ describe "when failed to connect" do
18
+ it "returns true" do
19
+ Faraday::Connection.any_instance.expects(:get).raises(Faraday::ConnectionFailed.new("Failed to connect"))
20
+ assert_raises Clickhouse::ConnectionError do
21
+ @connection.connect!
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "when receiving 200" do
27
+ it "returns true" do
28
+ Faraday::Connection.any_instance.expects(:get).returns(stub(:status => 200))
29
+ assert_equal true, @connection.connect!
30
+ end
31
+ end
32
+
33
+ describe "when receiving 500" do
34
+ it "raises a Clickhouse::ConnectionError" do
35
+ Faraday::Connection.any_instance.expects(:get).returns(stub(:status => 500))
36
+ assert_raises Clickhouse::ConnectionError do
37
+ @connection.connect!
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "when already connected" do
43
+ it "returns nil" do
44
+ @connection.instance_variable_set :@client, mock
45
+ assert_nil @connection.connect!
46
+ end
47
+ end
48
+ end
49
+
50
+ describe "#connected?" do
51
+ it "returns whether it has an connected socket" do
52
+ assert_equal false, @connection.connected?
53
+ @connection.instance_variable_set :@client, mock
54
+ assert_equal true, @connection.connected?
55
+ @connection.instance_variable_set :@client, nil
56
+ assert_equal false, @connection.connected?
57
+ end
58
+ end
59
+
60
+ describe "#get" do
61
+ it "sends a GET request the server" do
62
+ @connection.instance_variable_set :@client, (client = mock)
63
+ client.expects(:get).with("/?query=foo", nil).returns(stub(:status => 200, :body => ""))
64
+ @connection.stubs(:log)
65
+ @connection.get(:foo)
66
+ end
67
+ end
68
+
69
+ describe "#post" do
70
+ it "sends a POST request the server" do
71
+ @connection.instance_variable_set :@client, (client = mock)
72
+ client.expects(:post).with("/?query=foo", "body").returns(stub(:status => 200, :body => ""))
73
+ @connection.stubs(:log)
74
+ @connection.post(:foo, "body")
75
+ end
76
+ end
77
+
78
+ describe "#request" do
79
+ before do
80
+ @connection.expects(:log)
81
+ end
82
+
83
+ it "connects to the server first" do
84
+ @connection.instance_variable_set :@client, (client = mock)
85
+ @connection.expects(:connect!)
86
+ client.stubs(:get).returns(stub(:status => 200, :body => ""))
87
+ @connection.send :request, :get, "/", "query"
88
+ end
89
+
90
+ it "queries the server returning the response" do
91
+ @connection.instance_variable_set :@client, (client = mock)
92
+ client.expects(:get).with("/?query=SELECT+1", nil).returns(response = stub(:status => 200, :body => ""))
93
+ assert_equal response, @connection.send(:request, :get, "SELECT 1")
94
+ end
95
+
96
+ describe "when not receiving status 200" do
97
+ it "raises a Clickhouse::QueryError" do
98
+ @connection.instance_variable_set :@client, (client = mock)
99
+ client.expects(:get).with("/?query=SELECT+1", nil).returns(stub(:status => 500, :body => ""))
100
+ assert_raises Clickhouse::QueryError do
101
+ @connection.send(:request, :get, "SELECT 1")
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ describe "configuration" do
108
+ describe "database" do
109
+ it "includes the database in the querystring" do
110
+ @connection.expects(:log)
111
+ @connection.instance_variable_get(:@config)[:database] = "system"
112
+ @connection.instance_variable_set(:@client, (client = mock))
113
+ client.expects(:get).with("/?database=system&query=SELECT+1", nil).returns(response = stub(:status => 200, :body => ""))
114
+ assert_equal response, @connection.send(:request, :get, "SELECT 1")
115
+ end
116
+ end
117
+
118
+ describe "authentication" do
119
+ it "includes the credentials in the request headers" do
120
+ connection = Clickhouse::Connection.new :password => "awesomepassword"
121
+ connection.expects(:ping!)
122
+ connection.connect!
123
+ assert_equal "Basic ZGVmYXVsdDphd2Vzb21lcGFzc3dvcmQ=", connection.send(:client).headers["Authorization"].force_encoding("UTF-8")
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,35 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module Connection
5
+ class TestLogger < MiniTest::Test
6
+
7
+ class Connection < SimpleConnection
8
+ include Clickhouse::Connection::Logger
9
+ end
10
+
11
+ describe Clickhouse::Connection::Logger do
12
+ before do
13
+ @connection = Connection.new
14
+ end
15
+
16
+ describe "#log" do
17
+ describe "when having specified a logger" do
18
+ it "delegates to logger" do
19
+ (logger = mock).expects(:info, "Hello world!")
20
+ Clickhouse.expects(:logger).returns(logger).twice
21
+ @connection.send(:log, :info, "Hello world!")
22
+ end
23
+ end
24
+
25
+ describe "when not having specified a logger" do
26
+ it "does nothing" do
27
+ assert_nil @connection.send(:log, :info, "Boo!")
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,391 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module Connection
5
+ class TestQuery < MiniTest::Test
6
+
7
+ class Connection < SimpleConnection
8
+ include Clickhouse::Connection::Query
9
+ include Clickhouse::Connection::Logger
10
+ end
11
+
12
+ describe Clickhouse::Connection::Query do
13
+ before do
14
+ @connection = Connection.new
15
+ end
16
+
17
+ describe "#execute" do
18
+ it "sends a POST request" do
19
+ @connection.expects(:post).with("sql", nil).returns(stub(:status => 200, :body => ""))
20
+ assert_equal true, @connection.execute("sql")
21
+ end
22
+
23
+ describe "when server returns a non-empty body" do
24
+ it "returns the body of the response" do
25
+ @connection.expects(:post).with("sql", "body").returns(stub(:status => 200, :body => "Ok."))
26
+ assert_equal "Ok.", @connection.execute("sql", "body")
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "#query" do
32
+ it "sends a GET request requesting a TSV response including names and types" do
33
+ @connection.expects(:get).with("sql FORMAT TabSeparatedWithNamesAndTypes").returns(stub(:status => 200, :body => ""))
34
+ assert_equal [], @connection.query("sql").to_a
35
+ end
36
+ end
37
+
38
+ describe "#databases" do
39
+ it "sends a 'SHOW DATABASES' query" do
40
+ @connection.expects(:get).with("SHOW DATABASES FORMAT TabSeparatedWithNamesAndTypes").returns(stub(:status => 200, :body => ""))
41
+ @connection.databases
42
+ end
43
+ end
44
+
45
+ describe "#tables" do
46
+ it "sends a 'SHOW TABLES' query" do
47
+ @connection.expects(:get).with("SHOW TABLES FORMAT TabSeparatedWithNamesAndTypes").returns(stub(:status => 200, :body => ""))
48
+ @connection.tables
49
+ end
50
+ end
51
+
52
+ describe "#create_table" do
53
+ it "sends a 'CREATE TABLE' query" do
54
+ sql = <<-SQL
55
+ CREATE TABLE logs_test (
56
+ id UInt8,
57
+ price Float32,
58
+ name String,
59
+ date Date,
60
+ time DateTime,
61
+ hex_id FixedString(8)
62
+ )
63
+ ENGINE = MergeTree(date, 8192)
64
+ SQL
65
+ @connection.expects(:post).with(sql.strip, nil).returns(stub(:status => 200, :body => ""))
66
+ @connection.create_table("logs_test") do |t|
67
+ t.uint8 :id
68
+ t.float32 :price
69
+ t.string :name
70
+ t.date :date
71
+ t.date_time :time
72
+ t.fixed_string :hex_id, 8
73
+ t.engine "MergeTree(date, 8192)"
74
+ end
75
+ end
76
+ end
77
+
78
+ describe "#describe_table" do
79
+ it "sends a 'DESCRIBE TABLE <name>' query" do
80
+ @connection.expects(:get).with("DESCRIBE TABLE logs FORMAT TabSeparatedWithNamesAndTypes").returns(stub(:status => 200, :body => ""))
81
+ @connection.describe_table("logs")
82
+ end
83
+ end
84
+
85
+ describe "#rename_table" do
86
+ describe "when passing an array with an even number of names" do
87
+ it "sends a POST request containing a RENAME TABLE statement" do
88
+ @connection.expects(:post).with("RENAME TABLE foo TO bar, baz TO qux", nil).returns(stub(:status => 200, :body => "")).twice
89
+ assert_equal true, @connection.rename_table("foo", "bar", "baz", "qux")
90
+ assert_equal true, @connection.rename_table(["foo", "bar"], ["baz", "qux"])
91
+ end
92
+ end
93
+
94
+ describe "when passing an array with an odd number of names" do
95
+ it "raises an Clickhouse::InvalidQueryError" do
96
+ assert_raises Clickhouse::InvalidQueryError do
97
+ @connection.rename_table "foo"
98
+ end
99
+ assert_raises Clickhouse::InvalidQueryError do
100
+ @connection.rename_table ["foo"]
101
+ end
102
+ end
103
+ end
104
+
105
+ describe "when passing a hash" do
106
+ it "sends a POST request containing a RENAME TABLE statement" do
107
+ @connection.expects(:post).with("RENAME TABLE foo TO bar, baz TO qux", nil).returns(stub(:status => 200, :body => ""))
108
+ assert_equal true, @connection.rename_table(:foo => "bar", :baz => "qux")
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "#drop_table" do
114
+ it "sends a POST request containing a 'DROP TABLE' statement" do
115
+ @connection.expects(:post).with("DROP TABLE logs", nil).returns(stub(:status => 200, :body => ""))
116
+ assert_equal true, @connection.drop_table("logs")
117
+ end
118
+ end
119
+
120
+ describe "#insert_rows" do
121
+ before do
122
+ @csv = <<-CSV
123
+ id,first_name,last_name
124
+ 12345,Paul,Engel
125
+ 67890,Bruce,Wayne
126
+ CSV
127
+ @csv.gsub!(/^\s+/, "")
128
+ end
129
+
130
+ describe "when using hashes" do
131
+ it "sends a POST request containing a 'INSERT INTO' statement using CSV" do
132
+ @connection.expects(:post).with("INSERT INTO logs FORMAT CSVWithNames", @csv).returns(stub(:status => 200, :body => ""))
133
+ assert_equal true, @connection.insert_rows("logs") { |rows|
134
+ rows << {:id => 12345, :first_name => "Paul", :last_name => "Engel"}
135
+ rows << {:id => 67890, :first_name => "Bruce", :last_name => "Wayne"}
136
+ }
137
+ end
138
+ end
139
+
140
+ describe "when using arrays" do
141
+ it "sends a POST request containing a 'INSERT INTO' statement using CSV" do
142
+ @connection.expects(:post).with("INSERT INTO logs FORMAT CSVWithNames", @csv).returns(stub(:status => 200, :body => ""))
143
+ assert_equal true, @connection.insert_rows("logs", :names => %w(id first_name last_name)) { |rows|
144
+ rows << [12345, "Paul", "Engel"]
145
+ rows << [67890, "Bruce", "Wayne"]
146
+ }
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "#select_rows" do
152
+ it "sends a GET request and parses the result set" do
153
+ body = <<-TSV
154
+ year\tname
155
+ UInt16\tString
156
+ 1982\tPaul
157
+ 1947\tAnna
158
+ TSV
159
+
160
+ @connection.expects(:to_select_query).with(options = {:from => "logs"})
161
+ @connection.expects(:get).returns(stub(:body => body.gsub(/^\s+/, "")))
162
+
163
+ assert_equal [
164
+ [1982, "Paul"],
165
+ [1947, "Anna"]
166
+ ], @connection.select_rows(options).to_a
167
+ end
168
+ end
169
+
170
+ describe "#select_row" do
171
+ it "returns an empty array" do
172
+ @connection.expects(:select_rows).returns([["Paul", "Engel"], ["Bruce", "Wayne"]])
173
+ assert_equal ["Paul", "Engel"], @connection.select_row({})
174
+ end
175
+ end
176
+
177
+ describe "#select_values" do
178
+ describe "when empty result set" do
179
+ it "returns an empty array" do
180
+ @connection.expects(:to_select_query)
181
+ @connection.expects(:get).returns(stub(:body => ""))
182
+ assert_equal [], @connection.select_values({})
183
+ end
184
+ end
185
+
186
+ describe "when getting data" do
187
+ it "returns every first value of every row" do
188
+ body = <<-TSV
189
+ year\tname
190
+ UInt16\tString
191
+ 1982\tPaul
192
+ 1947\tAnna
193
+ TSV
194
+
195
+ @connection.expects(:to_select_query)
196
+ @connection.expects(:get).returns(stub(:body => body.gsub(/^\s+/, "")))
197
+ assert_equal [
198
+ 1982,
199
+ 1947
200
+ ], @connection.select_values({})
201
+ end
202
+ end
203
+ end
204
+
205
+ describe "#select_value" do
206
+ describe "when empty result set" do
207
+ it "returns nil" do
208
+ @connection.expects(:select_values).with(options = {:foo => "bar"}).returns([])
209
+ assert_nil @connection.select_value(options)
210
+ end
211
+ end
212
+
213
+ describe "when getting data" do
214
+ it "returns the first value of the first row" do
215
+ @connection.expects(:select_values).with(options = {:foo => "bar"}).returns([1982])
216
+ assert_equal 1982, @connection.select_value(options)
217
+ end
218
+ end
219
+ end
220
+
221
+ describe "#count" do
222
+ it "returns the first value of the first row" do
223
+ @connection.expects(:select_value).with(:select => "COUNT(*)", :from => "logs").returns(1982)
224
+ assert_equal 1982, @connection.count(:from => "logs")
225
+ end
226
+ end
227
+
228
+ describe "#to_select_query" do
229
+ describe "when passing :from option" do
230
+ it "generates a simple 'SELECT * FROM <table>' query" do
231
+ query = <<-SQL
232
+ SELECT *
233
+ FROM logs
234
+ SQL
235
+ options = {
236
+ :from => "logs"
237
+ }
238
+ assert_query(query, @connection.send(:to_select_query, options))
239
+ end
240
+ end
241
+
242
+ describe "when passing :from and :select option" do
243
+ describe "when passing a single column" do
244
+ it "respects the single column in the SELECT statement" do
245
+ query = <<-SQL
246
+ SELECT MIN(date)
247
+ FROM logs
248
+ SQL
249
+ options = {
250
+ :select => "MIN(date)",
251
+ :from => "logs"
252
+ }
253
+ assert_query(query, @connection.send(:to_select_query, options))
254
+ end
255
+ end
256
+
257
+ describe "when passing multiple columns" do
258
+ it "only includes the passed columns in the SELECT statement" do
259
+ query = <<-SQL
260
+ SELECT MIN(date), MAX(date)
261
+ FROM logs
262
+ SQL
263
+ options = {
264
+ :select => ["MIN(date)", "MAX(date)"],
265
+ :from => "logs"
266
+ }
267
+ assert_query(query, @connection.send(:to_select_query, options))
268
+ end
269
+ end
270
+
271
+ describe "when filtering on value is empty" do
272
+ it "uses the empty() function in the WHERE statement" do
273
+ query = <<-SQL
274
+ SELECT *
275
+ FROM logs
276
+ WHERE empty(parent_id)
277
+ SQL
278
+ options = {
279
+ :from => "logs",
280
+ :where => {
281
+ :parent_id => :empty
282
+ }
283
+ }
284
+ assert_query(query, @connection.send(:to_select_query, options))
285
+ end
286
+ end
287
+
288
+ describe "when filtering on value is within a certain range" do
289
+ it "includes the range in the WHERE statement" do
290
+ query = <<-SQL
291
+ SELECT *
292
+ FROM logs
293
+ WHERE code >= 6 AND code <= 10
294
+ SQL
295
+ options = {
296
+ :from => "logs",
297
+ :where => {
298
+ :code => 6..10
299
+ }
300
+ }
301
+ assert_query(query, @connection.send(:to_select_query, options))
302
+ end
303
+ end
304
+
305
+ describe "when filtering on value in array" do
306
+ it "uses an IN operator in the WHERE statement" do
307
+ query = <<-SQL
308
+ SELECT *
309
+ FROM logs
310
+ WHERE code IN (6, 7, 8, 9, 10)
311
+ SQL
312
+ options = {
313
+ :from => "logs",
314
+ :where => {
315
+ :code => [6, 7, 8, 9, 10]
316
+ }
317
+ }
318
+ assert_query(query, @connection.send(:to_select_query, options))
319
+ end
320
+ end
321
+
322
+ describe "when filtering using backticks" do
323
+ it "uses the specified SQL as is" do
324
+ query = <<-SQL
325
+ SELECT *
326
+ FROM logs
327
+ WHERE id != 'cb5a67d2932911e6'
328
+ SQL
329
+ options = {
330
+ :from => "logs",
331
+ :where => {
332
+ :id => "`!= 'cb5a67d2932911e6'`"
333
+ }
334
+ }
335
+ assert_query(query, @connection.send(:to_select_query, options))
336
+ end
337
+ end
338
+
339
+ describe "when filtering on a string" do
340
+ it "uses a single quoted string" do
341
+ query = <<-SQL
342
+ SELECT *
343
+ FROM logs
344
+ WHERE id = 'cb5a67d2932911e6'
345
+ SQL
346
+ options = {
347
+ :from => "logs",
348
+ :where => {
349
+ :id => "cb5a67d2932911e6"
350
+ }
351
+ }
352
+ assert_query(query, @connection.send(:to_select_query, options))
353
+ end
354
+ end
355
+
356
+ describe "when using all options" do
357
+ it "generates the complex query" do
358
+ query = <<-SQL
359
+ SELECT date, COUNT(id), groupUniqArray(severity), SUM(clicks)
360
+ FROM logs
361
+ WHERE date >= '2016-08-01' AND hidden = 0
362
+ GROUP BY date
363
+ HAVING MIN(severity) = 2
364
+ ORDER BY MIN(time) DESC
365
+ LIMIT 120, 60
366
+ SQL
367
+ options = {
368
+ :select => ["date", "COUNT(id)", "groupUniqArray(severity)", "SUM(clicks)"],
369
+ :from => "logs",
370
+ :where => {
371
+ :date => "`>= '2016-08-01'`",
372
+ :hidden => 0
373
+ },
374
+ :group => "date",
375
+ :having => {
376
+ "MIN(severity)" => 2
377
+ },
378
+ :order => "MIN(time) DESC",
379
+ :limit => 60,
380
+ :offset => 120
381
+ }
382
+ assert_query(query, @connection.send(:to_select_query, options))
383
+ end
384
+ end
385
+ end
386
+ end
387
+ end
388
+
389
+ end
390
+ end
391
+ end