ch-client 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +22 -0
  3. data/.gitignore +9 -0
  4. data/.travis.yml +3 -0
  5. data/CHANGELOG.md +58 -0
  6. data/Gemfile +3 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +262 -0
  9. data/Rakefile +15 -0
  10. data/VERSION +1 -0
  11. data/bin/clickhouse +9 -0
  12. data/clickhouse.gemspec +36 -0
  13. data/lib/clickhouse.rb +60 -0
  14. data/lib/clickhouse/cli.rb +46 -0
  15. data/lib/clickhouse/cli/client.rb +149 -0
  16. data/lib/clickhouse/cli/console.rb +73 -0
  17. data/lib/clickhouse/cli/server.rb +37 -0
  18. data/lib/clickhouse/cli/server/assets/css/clickhouse.css +177 -0
  19. data/lib/clickhouse/cli/server/assets/css/codemirror.css +341 -0
  20. data/lib/clickhouse/cli/server/assets/css/datatables.css +1 -0
  21. data/lib/clickhouse/cli/server/assets/css/normalize.css +427 -0
  22. data/lib/clickhouse/cli/server/assets/css/skeleton.css +418 -0
  23. data/lib/clickhouse/cli/server/assets/js/clickhouse.js +188 -0
  24. data/lib/clickhouse/cli/server/assets/js/codemirror.js +9096 -0
  25. data/lib/clickhouse/cli/server/assets/js/datatables.js +166 -0
  26. data/lib/clickhouse/cli/server/assets/js/disableswipeback.js +97 -0
  27. data/lib/clickhouse/cli/server/assets/js/jquery.js +11015 -0
  28. data/lib/clickhouse/cli/server/assets/js/sql.js +232 -0
  29. data/lib/clickhouse/cli/server/views/index.erb +46 -0
  30. data/lib/clickhouse/cluster.rb +43 -0
  31. data/lib/clickhouse/connection.rb +42 -0
  32. data/lib/clickhouse/connection/client.rb +135 -0
  33. data/lib/clickhouse/connection/logger.rb +12 -0
  34. data/lib/clickhouse/connection/query.rb +160 -0
  35. data/lib/clickhouse/connection/query/result_row.rb +36 -0
  36. data/lib/clickhouse/connection/query/result_set.rb +103 -0
  37. data/lib/clickhouse/connection/query/table.rb +50 -0
  38. data/lib/clickhouse/error.rb +18 -0
  39. data/lib/clickhouse/utils.rb +23 -0
  40. data/lib/clickhouse/version.rb +7 -0
  41. data/script/console +58 -0
  42. data/test/test_helper.rb +15 -0
  43. data/test/test_helper/coverage.rb +16 -0
  44. data/test/test_helper/minitest.rb +13 -0
  45. data/test/test_helper/simple_connection.rb +12 -0
  46. data/test/unit/connection/query/test_result_row.rb +36 -0
  47. data/test/unit/connection/query/test_result_set.rb +196 -0
  48. data/test/unit/connection/query/test_table.rb +39 -0
  49. data/test/unit/connection/test_client.rb +206 -0
  50. data/test/unit/connection/test_cluster.rb +81 -0
  51. data/test/unit/connection/test_logger.rb +35 -0
  52. data/test/unit/connection/test_query.rb +410 -0
  53. data/test/unit/test_clickhouse.rb +99 -0
  54. data/test/unit/test_connection.rb +55 -0
  55. data/test/unit/test_utils.rb +39 -0
  56. metadata +326 -0
@@ -0,0 +1,15 @@
1
+ require_relative "test_helper/coverage"
2
+
3
+ require "minitest"
4
+ require "minitest/autorun"
5
+ require "mocha/setup"
6
+
7
+ def path(path)
8
+ File.expand_path "../../#{path}", __FILE__
9
+ end
10
+
11
+ require "bundler"
12
+ Bundler.require :default, :development, :test
13
+
14
+ require_relative "test_helper/minitest"
15
+ require_relative "test_helper/simple_connection"
@@ -0,0 +1,16 @@
1
+ if Dir.pwd == File.expand_path("../../..", __FILE__)
2
+ if ENV["REPORT"].to_i == 1
3
+ require "dotenv"
4
+ Dotenv.load
5
+
6
+ require "codeclimate-test-reporter"
7
+ CodeClimate::TestReporter.start
8
+ end
9
+
10
+ require "simplecov"
11
+ SimpleCov.coverage_dir "test/coverage"
12
+ SimpleCov.start do
13
+ add_group "Clickhouse", "lib"
14
+ add_group "Test suite", "test"
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ class MiniTest::Test
2
+ def teardown
3
+ Clickhouse.instance_variables.each do |name|
4
+ Clickhouse.instance_variable_set name, nil
5
+ end
6
+ end
7
+ end
8
+
9
+ class MiniTest::Spec
10
+ def assert_query(expected, actual)
11
+ assert_equal(expected.strip.gsub(/^\s+/, ""), actual)
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ class SimpleConnection
2
+ attr_reader :config
3
+
4
+ def initialize
5
+ @config = {
6
+ :scheme => "http",
7
+ :host => "localhost",
8
+ :port => 8123
9
+ }
10
+ end
11
+
12
+ end
@@ -0,0 +1,36 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ module Unit
4
+ module Connection
5
+ module Query
6
+ class TestResultRow < MiniTest::Test
7
+
8
+ describe Clickhouse::Connection::Query::ResultRow do
9
+ describe "#to_hash" do
10
+ describe "when passing names" do
11
+ it "uses the names as hash keys" do
12
+ result_row = Clickhouse::Connection::Query::ResultRow.new([1, 2, 3], [:a, :b, :c])
13
+ assert_equal({:a => 1, :b => 2, :c => 3}, result_row.to_hash)
14
+ end
15
+ end
16
+
17
+ describe "when not passing names" do
18
+ it "uses 'column<i>' as hash keys" do
19
+ result_row = Clickhouse::Connection::Query::ResultRow.new([1, 2, 3])
20
+ assert_equal({"column0" => 1, "column1" => 2, "column2" => 3}, result_row.to_hash)
21
+ end
22
+ end
23
+
24
+ describe "memoization" do
25
+ it "memoizes the resulting hash" do
26
+ result_row = Clickhouse::Connection::Query::ResultRow.new([1, 2, 3])
27
+ assert_equal result_row.to_hash.object_id, result_row.to_hash.object_id
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,196 @@
1
+ require_relative "../../../test_helper"
2
+
3
+ module Unit
4
+ module Connection
5
+ module Query
6
+ class TestResultSet < MiniTest::Test
7
+
8
+ describe Clickhouse::Connection::Query::ResultSet do
9
+ before do
10
+ @empty_set = Clickhouse::Connection::Query::ResultSet.new
11
+ @result_set = Clickhouse::Connection::Query::ResultSet.new(
12
+ [
13
+ [
14
+ "1072649",
15
+ "142.94",
16
+ "badrequest.io",
17
+ "d91d1c90\u0000\u0000\u0000",
18
+ "2016-03-20",
19
+ "2016-03-20 23:49:11",
20
+ [4, 2, 5, 7]
21
+ ], [
22
+ "12948140",
23
+ "9320.11",
24
+ "engel.pm",
25
+ "d91d217c\u0000\u0000",
26
+ "2016-03-20",
27
+ "2016-03-20 23:58:34",
28
+ [6, 2, 9, 8, 1]
29
+ ], [
30
+ "319384",
31
+ "101.02",
32
+ "archan937.com",
33
+ "d91d2294\u0000\u0000\u0000",
34
+ "2016-03-20",
35
+ "2016-03-20 22:55:39",
36
+ [3, 1, 2]
37
+ ]
38
+ ],
39
+ %w(
40
+ SUM(clicks)
41
+ AVG(price)
42
+ domain
43
+ id
44
+ date
45
+ MAX(time)
46
+ groupUniqArray(code)
47
+ ),
48
+ %w(
49
+ UInt32
50
+ Float32
51
+ String
52
+ FixedString(16)
53
+ Date
54
+ DateTime
55
+ Array(8)
56
+ )
57
+ )
58
+ end
59
+
60
+ describe "#size" do
61
+ it "returns the size of the result set" do
62
+ assert_equal 3, @result_set.size
63
+ end
64
+ end
65
+
66
+ describe "#empty?" do
67
+ it "returns whether the result set is empty or not" do
68
+ assert_equal true, @empty_set.empty?
69
+ assert_equal false, @result_set.empty?
70
+ end
71
+ end
72
+
73
+ describe "#present?" do
74
+ it "returns whether the result set contains rows or not" do
75
+ assert_equal false, @empty_set.present?
76
+ assert_equal true, @result_set.present?
77
+ end
78
+ end
79
+
80
+ describe "#first" do
81
+ it "returns the first row of the result set" do
82
+ assert_equal [
83
+ 1072649,
84
+ 142.94,
85
+ "badrequest.io",
86
+ "d91d1c90",
87
+ Date.new(2016, 3, 20),
88
+ Time.new(2016, 3, 20, 23, 49, 11),
89
+ [4, 2, 5, 7]
90
+ ], @result_set.first
91
+ end
92
+ end
93
+
94
+ describe "#last" do
95
+ it "returns the size of the result set" do
96
+ assert_equal [
97
+ 319384,
98
+ 101.02,
99
+ "archan937.com",
100
+ "d91d2294",
101
+ Date.new(2016, 3, 20),
102
+ Time.new(2016, 3, 20, 22, 55, 39),
103
+ [3, 1, 2]
104
+ ], @result_set.last
105
+ end
106
+ end
107
+
108
+ describe "#flatten" do
109
+ it "returns the size of the result set" do
110
+ assert_equal [
111
+ 1072649,
112
+ 142.94,
113
+ "badrequest.io",
114
+ "d91d1c90",
115
+ Date.new(2016, 3, 20),
116
+ Time.new(2016, 3, 20, 23, 49, 11),
117
+ 4,
118
+ 2,
119
+ 5,
120
+ 7,
121
+ 12948140,
122
+ 9320.11,
123
+ "engel.pm",
124
+ "d91d217c",
125
+ Date.new(2016, 3, 20),
126
+ Time.new(2016, 3, 20, 23, 58, 34),
127
+ 6,
128
+ 2,
129
+ 9,
130
+ 8,
131
+ 1,
132
+ 319384,
133
+ 101.02,
134
+ "archan937.com",
135
+ "d91d2294",
136
+ Date.new(2016, 3, 20),
137
+ Time.new(2016, 3, 20, 22, 55, 39),
138
+ 3,
139
+ 1,
140
+ 2
141
+ ], @result_set.flatten
142
+ end
143
+ end
144
+
145
+ describe "#to_hashes" do
146
+ it "returns an array containing the rows as hashes" do
147
+ assert_equal [
148
+ {
149
+ "SUM(clicks)" => 1072649,
150
+ "AVG(price)" => 142.94,
151
+ "domain" => "badrequest.io",
152
+ "id" => "d91d1c90",
153
+ "date" => Date.new(2016, 3, 20),
154
+ "MAX(time)" => Time.new(2016, 3, 20, 23, 49, 11),
155
+ "groupUniqArray(code)" => [4, 2, 5, 7]
156
+ }, {
157
+ "SUM(clicks)" => 12948140,
158
+ "AVG(price)" => 9320.11,
159
+ "domain" => "engel.pm",
160
+ "id" => "d91d217c",
161
+ "date" => Date.new(2016, 3, 20),
162
+ "MAX(time)" => Time.new(2016, 3, 20, 23, 58, 34),
163
+ "groupUniqArray(code)" => [6, 2, 9, 8, 1]
164
+ }, {
165
+ "SUM(clicks)" => 319384,
166
+ "AVG(price)" => 101.02,
167
+ "domain" => "archan937.com",
168
+ "id" => "d91d2294",
169
+ "date" => Date.new(2016, 3, 20),
170
+ "MAX(time)" => Time.new(2016, 3, 20, 22, 55, 39),
171
+ "groupUniqArray(code)" => [3, 1, 2]
172
+ }
173
+ ], @result_set.to_hashes
174
+ end
175
+ end
176
+
177
+ describe "memoization" do
178
+ it "memoizes the parsed rows" do
179
+ assert_equal @result_set.to_a[-1].object_id, @result_set.each{}[-1].object_id
180
+ assert_equal @result_set.first.object_id, @result_set[0].object_id
181
+ end
182
+ end
183
+
184
+ describe "non-supported data types" do
185
+ it "raises a NotImplementedError error" do
186
+ assert_raises NotImplementedError do
187
+ Clickhouse::Connection::Query::ResultSet.new([[1]], ["Foo"], ["Bar"])[0]
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ end
194
+ end
195
+ end
196
+ end
@@ -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,206 @@
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
+ @connection.stubs(:parse_stats)
15
+ @connection.stubs(:write_log)
16
+ end
17
+
18
+ describe "#connect!" do
19
+ describe "when failed to connect" do
20
+ it "returns true" do
21
+ Faraday::Connection.any_instance.expects(:get).raises(Faraday::ConnectionFailed.new("Failed to connect"))
22
+ assert_raises Clickhouse::ConnectionError do
23
+ @connection.connect!
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "when receiving 200" do
29
+ it "returns true" do
30
+ Faraday::Connection.any_instance.expects(:get).returns(stub(:status => 200))
31
+ assert_equal true, @connection.connect!
32
+ end
33
+ end
34
+
35
+ describe "when receiving 500" do
36
+ it "raises a Clickhouse::ConnectionError" do
37
+ Faraday::Connection.any_instance.expects(:get).returns(stub(:status => 500))
38
+ assert_raises Clickhouse::ConnectionError do
39
+ @connection.connect!
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "when already connected" do
45
+ it "returns nil" do
46
+ @connection.instance_variable_set :@client, mock
47
+ assert_nil @connection.connect!
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#connected?" do
53
+ it "returns whether it has an connected socket" do
54
+ assert_equal false, @connection.connected?
55
+ @connection.instance_variable_set :@client, mock
56
+ assert_equal true, @connection.connected?
57
+ @connection.instance_variable_set :@client, nil
58
+ assert_equal false, @connection.connected?
59
+ end
60
+ end
61
+
62
+ describe "#get" do
63
+ it "sends a GET request the server" do
64
+ @connection.instance_variable_set :@client, (client = mock)
65
+ client.expects(:get).with("/?query=foo&output_format_write_statistics=1", nil).returns(stub(:status => 200, :body => ""))
66
+ @connection.stubs(:log)
67
+ @connection.get("foo")
68
+ end
69
+ end
70
+
71
+ describe "#post" do
72
+ it "sends a POST request the server" do
73
+ @connection.instance_variable_set :@client, (client = mock)
74
+ client.expects(:post).with("/?query=foo&output_format_write_statistics=1", "body").returns(stub(:status => 200, :body => ""))
75
+ @connection.stubs(:log)
76
+ @connection.post("foo", "body")
77
+ end
78
+ end
79
+
80
+ describe "#request" do
81
+ before do
82
+ @connection.stubs(:log)
83
+ end
84
+
85
+ it "connects to the server first" do
86
+ @connection.instance_variable_set :@client, (client = mock)
87
+ @connection.expects(:connect!)
88
+ client.stubs(:get).returns(stub(:status => 200, :body => ""))
89
+ @connection.send :request, :get, "/", "query"
90
+ end
91
+
92
+ it "queries the server returning the response" do
93
+ @connection.instance_variable_set :@client, (client = mock)
94
+ client.expects(:get).with("/?query=SELECT+1&output_format_write_statistics=1", nil).returns(stub(:status => 200, :body => ""))
95
+ @connection.expects(:parse_body).returns(data = mock)
96
+ assert_equal data, @connection.send(:request, :get, "SELECT 1")
97
+ end
98
+
99
+ describe "when not receiving status 200" do
100
+ it "raises a Clickhouse::QueryError" do
101
+ @connection.instance_variable_set :@client, (client = mock)
102
+ client.expects(:get).with("/?query=SELECT+1&output_format_write_statistics=1", nil).returns(stub(:status => 500, :body => ""))
103
+ assert_raises Clickhouse::QueryError do
104
+ @connection.send(:request, :get, "SELECT 1")
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "when getting Faraday::Error" do
110
+ it "raises a Clickhouse::ConnectionError" do
111
+ @connection.instance_variable_set :@client, (client = mock)
112
+ client.expects(:get).raises(Faraday::ConnectionFailed.new("Failed to connect"))
113
+ assert_raises Clickhouse::ConnectionError do
114
+ @connection.send(:request, :get, "SELECT 1")
115
+ end
116
+ end
117
+ end
118
+
119
+ it "parses the body" do
120
+ json = <<-JSON
121
+ {"meta": []}
122
+ JSON
123
+ @connection.instance_variable_set :@client, (client = mock)
124
+ client.expects(:get).with("/?query=SELECT+1+FORMAT+JSONCompact&output_format_write_statistics=1", nil).returns(stub(:status => 200, :body => json))
125
+ assert_equal({"meta" => []}, @connection.send(:request, :get, "SELECT 1 FORMAT JSONCompact"))
126
+ end
127
+ end
128
+
129
+ describe "configuration" do
130
+ describe "database" do
131
+ it "includes the database in the querystring" do
132
+ @connection.instance_variable_get(:@config)[:database] = "system"
133
+ @connection.instance_variable_set(:@client, (client = mock))
134
+ client.expects(:get).with("/?database=system&query=SELECT+1&output_format_write_statistics=1", nil).returns(stub(:status => 200, :body => ""))
135
+ @connection.expects(:parse_body).returns(data = mock)
136
+ assert_equal data, @connection.send(:request, :get, "SELECT 1")
137
+ end
138
+ end
139
+
140
+ describe "authentication" do
141
+ it "includes the credentials in the request headers" do
142
+ Faraday::Connection.any_instance.expects(:get).returns(stub(status: 200))
143
+ connection = Clickhouse::Connection.new :password => "awesomepassword"
144
+ connection.connect!
145
+ assert_equal "Basic ZGVmYXVsdDphd2Vzb21lcGFzc3dvcmQ=", connection.send(:client).headers["Authorization"].force_encoding("UTF-8")
146
+ end
147
+ end
148
+ end
149
+
150
+ describe "statistics" do
151
+ before do
152
+ @connection = Connection.new
153
+ @json = <<-JSON
154
+ {
155
+ "rows": 1947,
156
+ "statistics": {
157
+ "elapsed": 0.1882,
158
+ "rows_read": 1982,
159
+ "bytes_read": 2003
160
+ }
161
+ }
162
+ JSON
163
+ end
164
+
165
+ it "parses the statistics" do
166
+ @connection.stubs(:log)
167
+ @connection.instance_variable_set :@client, (client = mock)
168
+ Time.expects(:now).returns(1882).twice
169
+
170
+ client.expects(:get).with("/?query=SELECT+1+FORMAT+JSONCompact&output_format_write_statistics=1", nil).returns(stub(:status => 200, :body => @json))
171
+ @connection.expects(:write_log).with(
172
+ 0, "SELECT 1", {
173
+ "elapsed" => "188.2ms",
174
+ "rows_read" => "1.98 thousand",
175
+ "bytes_read" => 2003,
176
+ "rows" => 1947,
177
+ "rows_per_second" => "10.53 thousand",
178
+ "data_per_second" => "10.39 KB",
179
+ "data_read" => "1.96 KB"
180
+ }
181
+ )
182
+ @connection.send(:request, :get, "SELECT 1 FORMAT JSONCompact")
183
+ end
184
+
185
+ it "write the expected logs" do
186
+ @connection.instance_variable_set :@client, (client = mock)
187
+ Time.expects(:now).returns(1882).twice
188
+
189
+ client.expects(:get).with("/?query=SELECT+1+FORMAT+JSONCompact&output_format_write_statistics=1", nil).returns(stub(:status => 200, :body => @json))
190
+ log = "\n \e[1m\e[35mSQL (0.0ms)\e\e[0m SELECT 1;\e\n \e[1m\e[36m1947 rows in set. Elapsed: 188.2ms. Processed: 1.98 thousand rows, 1.96 KB (10.53 thousand rows/s, 10.39 KB/s)\e[0m "
191
+
192
+ @connection.expects(:log).with(:debug, log)
193
+ @connection.send(:request, :get, "SELECT 1 FORMAT JSONCompact")
194
+ end
195
+
196
+ describe "#number_to_human_duration" do
197
+ it "returns in seconds when more than 1 seconds" do
198
+ assert_equal "2.0s", @connection.send(:number_to_human_duration, 2)
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ end
205
+ end
206
+ end