clickhouse 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 382f431988af06b9879728821c0c002676cb930d
4
- data.tar.gz: 7fc6c4674ef83c45989bd8d3759ad85629beecee
3
+ metadata.gz: 99ef193e0967f146e206ce76d41d1b5893948ae9
4
+ data.tar.gz: 47a7ba893cb391215495c80384146947be07b62d
5
5
  SHA512:
6
- metadata.gz: 83868f46b3dbbd9e977c6ac14a4badf252f9cc213cfbab9545a777ff241003357bd90241bbea1cedd58871457e0a4c43598385839a827e1815a897848bc5bcab
7
- data.tar.gz: 39d8d2ab4a094e5e5f7b3d269d40d6fad75619fd97a3bee5c04b7bd766eff2096398a5c798fad6cf29d6ee0bafbe33d908cd7068e5c0a98e68c37a051d9a79f5
6
+ metadata.gz: 774e1ae3330a758d3dd5bd0609d4f4819a06050a35f2f66b520fbfced6baab89ab376e5c855079486dccc841e627bfb9db19af48ca5005c08154d9e95974180d
7
+ data.tar.gz: 06dd73764247a86c0651f50b1a10c27f5b8f8e13e1b9198b5f1386f73edb597c9c51afb8f28f08999a4feaf4f6fc7fefe4ae967af93d29682ead965d34293f67
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## Clickhouse CHANGELOG
2
2
 
3
+ ### Version 0.1.2 (October 20, 2016)
4
+
5
+ * Being able to specify a URL
6
+ * Raising a Clickhouse::ConnectionError when getting a Faraday::Error
7
+ * Made Clickhouse::Connection::Client#ping! public
8
+ * Supporting cluster connections using Pond (thanks @chanks)
9
+
3
10
  ### Version 0.1.1 (October 19, 2016)
4
11
 
5
12
  * Using the JSONCompact format as query output which does not brake when having a JSON string within the data
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Clickhouse [![Build Status](https://travis-ci.org/archan937/clickhouse.svg?branch=master)](https://travis-ci.org/archan937/clickhouse) [![Code Climate](https://codeclimate.com/github/archan937/clickhouse/badges/gpa.svg)](https://codeclimate.com/github/archan937/clickhouse) [![Test Coverage](https://codeclimate.com/github/archan937/clickhouse/badges/coverage.svg)](https://codeclimate.com/github/archan937/clickhouse/coverage) [![Gem](https://img.shields.io/gem/v/clickhouse.svg)](https://rubygems.org/gems/clickhouse) [![Gem](https://img.shields.io/gem/dt/clickhouse.svg)](https://rubygems.org/gems/clickhouse)
2
2
 
3
- A Ruby database driver for Clickhouse.
3
+ A Ruby database driver for ClickHouse.
4
4
 
5
5
  ## Introduction
6
6
 
7
- [Clickhouse](https://clickhouse.yandex) is a high-performance column-oriented database management system developed by [Yandex](https://yandex.com/company) which operates Russia's most popular search engine.
7
+ [ClickHouse](https://clickhouse.yandex) is a high-performance column-oriented database management system developed by [Yandex](https://yandex.com/company) which operates Russia's most popular search engine.
8
8
 
9
9
  > ClickHouse manages extremely large volumes of data in a stable and sustainable manner. It currently powers Yandex.Metrica, world’s second largest web analytics platform, with over 13 trillion database records and over 20 billion events a day, generating customized reports on-the-fly, directly from non-aggregated data. This system was successfully implemented at CERN’s LHCb experiment to store and process metadata on 10bn events with over 1000 attributes per event registered in 2011.
10
10
 
@@ -12,14 +12,14 @@ On June 15th 2016, [Yandex open-sourced their awesome project](https://news.ycom
12
12
 
13
13
  ### Why use the HTTP interface and not the TCP interface?
14
14
 
15
- Well, the developers of Clickhouse themselves [discourage](https://github.com/yandex/ClickHouse/issues/45#issuecomment-231194134) using the TCP interface.
15
+ Well, the developers of ClickHouse themselves [discourage](https://github.com/yandex/ClickHouse/issues/45#issuecomment-231194134) using the TCP interface.
16
16
 
17
17
  > TCP transport is more specific, we don't want to expose details.
18
18
  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.
19
19
 
20
20
  ### Why use the JSONCompact format and not the native format?
21
21
 
22
- Despite of it being the most efficient format, using the native format is also [discouraged] by the Clickhouse developers.
22
+ Despite of it being the most efficient format, using the native format is also [discouraged](https://clickhouse.yandex/reference_en.html#Native) by the ClickHouse developers.
23
23
 
24
24
  > The most efficient format. Data is written and read by blocks in binary format. For each block, the number of rows, number of columns, column names and types, and parts of columns in this block are recorded one after another. In other words, this format is "columnar" - it doesn't convert columns to rows. This is the format used in the native interface for interaction between servers, for using the command-line client, and for C++ clients.
25
25
  >
@@ -48,7 +48,7 @@ require "logger"
48
48
  Clickhouse.logger = Logger.new(STDOUT)
49
49
  ```
50
50
 
51
- Establish the connection with the Clickhouse server (using the default config).
51
+ Establish the connection with the ClickHouse server (using the default config).
52
52
 
53
53
  ```ruby
54
54
  Clickhouse.establish_connection
@@ -147,7 +147,30 @@ To see what more the `Clickhouse` gem has to offer, please take a look at the un
147
147
 
148
148
  ## Using the console
149
149
 
150
- As you probably already noticed, the `Clickhouse` repo is provided with a `script/console` file which you can use for development / testing purposes. Please note that you need to have a Clickhouse server running.
150
+ As you probably already noticed, the `Clickhouse` repo is provided with a `script/console` file which you can use for development / testing purposes. Please note that you need to have a ClickHouse server running.
151
+
152
+ ### Running a ClickHouse server on your Mac or Windows computer
153
+
154
+ Despite that the ClickHouse build is not intended to work on Mac OS X or Windows (only x86_64 with SSE 4.2 is supported), you can still run a ClickHouse server instance on both the operating systems using the [ClickHouse Server Docker Image](https://hub.docker.com/r/yandex/clickhouse-server/) hosted on [https://hub.docker.com/](Docker Hub).
155
+
156
+ The installation process is just a matter of two simple steps:
157
+
158
+ * Download and install [Kitematic](https://kitematic.com/) (Docker Toolbox) on your computer
159
+ * Install the [clickhouse-server](https://hub.docker.com/r/yandex/clickhouse-server/) container using Kitematic
160
+
161
+ Et voilà! Your ClickHouse server instance is up and running locally. Please make sure to use the proper IP address and port to connect with. You can find it at the container details within Kitematic (it is the `Access URL` corresponded with the `8123/tcp Docker port`).
162
+
163
+ ### Example
164
+
165
+ $ script/console
166
+ Loading Clickhouse development environment (0.1.1)
167
+ [1] pry(main)> connect! host: "192.168.99.100", port: 32770
168
+ => true
169
+ [2] pry(main)> conn.databases
170
+ I, [2016-10-19T20:54:53.081388 #29847] INFO -- :
171
+ SQL (3.1ms) SHOW DATABASES;
172
+ => ["default", "system"]
173
+ [3] pry(main)>
151
174
 
152
175
  ## Testing
153
176
 
@@ -163,10 +186,6 @@ You can also run a single test file:
163
186
 
164
187
  For support, remarks and requests, please mail me at [pm_engel@icloud.com](mailto:pm_engel@icloud.com).
165
188
 
166
- ## TODO
167
-
168
- * Support cluster connections
169
-
170
189
  ## License
171
190
 
172
191
  Copyright (c) 2016 Paul Engel, released under the MIT license
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
data/clickhouse.gemspec CHANGED
@@ -4,8 +4,8 @@ require File.expand_path("../lib/clickhouse/version", __FILE__)
4
4
  Gem::Specification.new do |gem|
5
5
  gem.authors = ["Paul Engel"]
6
6
  gem.email = ["pm_engel@icloud.com"]
7
- gem.summary = %q{A Ruby database driver for Clickhouse}
8
- gem.description = %q{A Ruby database driver for Clickhouse}
7
+ gem.summary = %q{A Ruby database driver for ClickHouse}
8
+ gem.description = %q{A Ruby database driver for ClickHouse}
9
9
  gem.homepage = "https://github.com/archan937/clickhouse"
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -16,6 +16,7 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Clickhouse::VERSION
17
17
 
18
18
  gem.add_dependency "faraday"
19
+ gem.add_dependency "pond"
19
20
 
20
21
  gem.add_development_dependency "rake"
21
22
  gem.add_development_dependency "pry"
data/lib/clickhouse.rb CHANGED
@@ -1,8 +1,11 @@
1
+ require "uri"
1
2
  require "forwardable"
2
3
  require "csv"
3
4
 
4
5
  require "faraday"
6
+ require "pond"
5
7
 
8
+ require "clickhouse/cluster"
6
9
  require "clickhouse/connection"
7
10
  require "clickhouse/error"
8
11
  require "clickhouse/version"
@@ -28,8 +31,7 @@ module Clickhouse
28
31
  def self.establish_connection(arg = {})
29
32
  config = arg.is_a?(Hash) ? arg : (configurations || {})[arg.to_s]
30
33
  if config
31
- @connection = Connection.new(config)
32
- @connection.connect!
34
+ connect!(config)
33
35
  else
34
36
  raise InvalidConnectionError, "Invalid connection specified: #{arg.inspect}"
35
37
  end
@@ -39,4 +41,14 @@ module Clickhouse
39
41
  @connection if instance_variables.include?(:@connection)
40
42
  end
41
43
 
44
+ # private
45
+
46
+ def self.connect!(config)
47
+ klass = (config[:urls] || config["urls"]) ? Cluster : Connection
48
+ @connection = klass.new(config)
49
+ @connection.connect!
50
+ end
51
+
52
+ private_class_method :connect!
53
+
42
54
  end
@@ -0,0 +1,41 @@
1
+ module Clickhouse
2
+ class Cluster < BasicObject
3
+
4
+ attr_reader :pond
5
+
6
+ def initialize(config)
7
+ urls = config.delete(:urls) || config.delete("urls")
8
+ @pond = ::Pond.new :maximum_size => urls.size, :timeout => 0.1
9
+
10
+ block = ::Proc.new do
11
+ url = (urls - pond.available.collect(&:url)).first || urls.sample
12
+ ::Clickhouse::Connection.new(config.merge(:url => url))
13
+ end
14
+
15
+ pond.instance_variable_set :@block, block
16
+ pond.maximum_size.times do
17
+ pond.available << block.call
18
+ end
19
+
20
+ pond.detach_if = ::Proc.new do |connection|
21
+ begin
22
+ connection.ping!
23
+ false
24
+ rescue
25
+ true
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def method_missing(*args, &block)
33
+ pond.checkout do |connection|
34
+ connection.send(*args, &block)
35
+ end
36
+ rescue
37
+ retry if pond.available.any?
38
+ end
39
+
40
+ end
41
+ end
@@ -5,18 +5,37 @@ require "clickhouse/connection/query"
5
5
  module Clickhouse
6
6
  class Connection
7
7
 
8
+ DEFAULT_CONFIG = {
9
+ :scheme => "http",
10
+ :host => "localhost",
11
+ :port => 8123
12
+ }
13
+
8
14
  include Client
9
15
  include Logger
10
16
  include Query
11
17
 
12
18
  def initialize(config = {})
13
- @config = {
14
- :scheme => "http",
15
- :host => "localhost",
16
- :port => 8123
17
- }.merge(
18
- config.inject({}){|h, (k, v)| h[k.to_sym] = v; h}
19
- )
19
+ @config = normalize_config(config)
20
+ end
21
+
22
+ private
23
+
24
+ def normalize_config(config)
25
+ config = config.inject({}) do |hash, (key, value)|
26
+ hash[key.to_sym] = value
27
+ hash
28
+ end
29
+
30
+ if config[:url]
31
+ uri = URI config[:url]
32
+ config[:scheme] = uri.scheme
33
+ config[:host] = uri.host
34
+ config[:port] = uri.port
35
+ config.delete(:url)
36
+ end
37
+
38
+ DEFAULT_CONFIG.merge(config)
20
39
  end
21
40
 
22
41
  end
@@ -3,9 +3,18 @@ module Clickhouse
3
3
  module Client
4
4
 
5
5
  def connect!
6
- return if connected?
6
+ ping! unless connected?
7
+ end
8
+
9
+ def ping!
7
10
  ensure_authentication
8
- ping!
11
+ status = client.get("/").status
12
+ if status != 200
13
+ raise ConnectionError, "Unexpected response status: #{status}"
14
+ end
15
+ true
16
+ rescue Faraday::Error => e
17
+ raise ConnectionError, e.message
9
18
  end
10
19
 
11
20
  def connected?
@@ -20,12 +29,12 @@ module Clickhouse
20
29
  request(:post, query, body)
21
30
  end
22
31
 
23
- private
24
-
25
32
  def url
26
33
  "#{@config[:scheme]}://#{@config[:host]}:#{@config[:port]}"
27
34
  end
28
35
 
36
+ private
37
+
29
38
  def path(query)
30
39
  database = "database=#{@config[:database]}&" if @config[:database]
31
40
  "/?#{database}query=#{CGI.escape(query)}"
@@ -40,16 +49,6 @@ module Clickhouse
40
49
  client.basic_auth(username || "default", password) if username || password
41
50
  end
42
51
 
43
- def ping!
44
- status = client.get("/").status
45
- if status != 200
46
- raise ConnectionError, "Unexpected response status: #{status}"
47
- end
48
- true
49
- rescue Faraday::ConnectionFailed => e
50
- raise ConnectionError, e.message
51
- end
52
-
53
52
  def request(method, query, body = nil)
54
53
  connect!
55
54
  query = query.to_s.strip
@@ -58,6 +57,8 @@ module Clickhouse
58
57
  log :info, "\n SQL (#{((Time.now - start) * 1000).round(1)}ms) #{query.gsub(/( FORMAT \w+|;$)/, "")};"
59
58
  raise QueryError, response.body unless response.status == 200
60
59
  end
60
+ rescue Faraday::Error => e
61
+ raise ConnectionError, e.message
61
62
  end
62
63
 
63
64
  end
@@ -1,7 +1,7 @@
1
1
  module Clickhouse
2
2
  MAJOR = 0
3
3
  MINOR = 1
4
- TINY = 1
4
+ TINY = 2
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join(".")
7
7
  end
@@ -77,7 +77,7 @@ module Unit
77
77
 
78
78
  describe "#request" do
79
79
  before do
80
- @connection.expects(:log)
80
+ @connection.stubs(:log)
81
81
  end
82
82
 
83
83
  it "connects to the server first" do
@@ -102,6 +102,16 @@ module Unit
102
102
  end
103
103
  end
104
104
  end
105
+
106
+ describe "when getting Faraday::Error" do
107
+ it "raises a Clickhouse::ConnectionError" do
108
+ @connection.instance_variable_set :@client, (client = mock)
109
+ client.expects(:get).raises(Faraday::ConnectionFailed.new("Failed to connect"))
110
+ assert_raises Clickhouse::ConnectionError do
111
+ @connection.send(:request, :get, "SELECT 1")
112
+ end
113
+ end
114
+ end
105
115
  end
106
116
 
107
117
  describe "configuration" do
@@ -117,8 +127,8 @@ module Unit
117
127
 
118
128
  describe "authentication" do
119
129
  it "includes the credentials in the request headers" do
130
+ Faraday::Connection.any_instance.expects(:get).returns(stub(status: 200))
120
131
  connection = Clickhouse::Connection.new :password => "awesomepassword"
121
- connection.expects(:ping!)
122
132
  connection.connect!
123
133
  assert_equal "Basic ZGVmYXVsdDphd2Vzb21lcGFzc3dvcmQ=", connection.send(:client).headers["Authorization"].force_encoding("UTF-8")
124
134
  end
@@ -0,0 +1,51 @@
1
+ require_relative "../../test_helper"
2
+
3
+ module Unit
4
+ module Connection
5
+ class TestCluser < MiniTest::Test
6
+
7
+ describe Clickhouse::Cluster do
8
+ it "creates a connection pond" do
9
+ cluster = Clickhouse::Cluster.new :urls => %w(localhost:1234 localhost:1235 localhost:1236)
10
+ assert_equal true, cluster.pond.is_a?(Pond)
11
+ end
12
+
13
+ describe "when connection succeeds" do
14
+ it "keeps valid connections from the pond" do
15
+ Clickhouse::Connection.any_instance.expects(:tables)
16
+ Clickhouse::Connection.any_instance.expects(:ping!)
17
+
18
+ cluster = Clickhouse::Cluster.new :urls => %w(http://localhost:1234 http://localhost:1235 http://localhost:1236)
19
+ assert_equal %w(
20
+ http://localhost:1234
21
+ http://localhost:1235
22
+ http://localhost:1236
23
+ ), cluster.pond.available.collect(&:url)
24
+
25
+ cluster.tables
26
+ assert_equal %w(
27
+ http://localhost:1235
28
+ http://localhost:1236
29
+ http://localhost:1234
30
+ ), cluster.pond.available.collect(&:url)
31
+ end
32
+ end
33
+
34
+ describe "when connection fails" do
35
+ it "removes invalid connections from the pond" do
36
+ cluster = Clickhouse::Cluster.new :urls => %w(http://localhost:1234 http://localhost:1235 http://localhost:1236)
37
+ assert_equal %w(
38
+ http://localhost:1234
39
+ http://localhost:1235
40
+ http://localhost:1236
41
+ ), cluster.pond.available.collect(&:url)
42
+
43
+ cluster.tables
44
+ assert_equal [], cluster.pond.available.collect(&:url)
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -62,6 +62,14 @@ module Unit
62
62
  Clickhouse::Connection.expects(:new).with(config).returns(@connection)
63
63
  Clickhouse.establish_connection "foo"
64
64
  end
65
+
66
+ describe "cluster connections" do
67
+ it "creates a connection pool" do
68
+ config = {:urls => %w(localhost:1234 localhost:1235 localhost:1236)}
69
+ Clickhouse::Cluster.expects(:new).with(config).returns(@connection)
70
+ Clickhouse.establish_connection config
71
+ end
72
+ end
65
73
  end
66
74
 
67
75
  describe "when invalid" do
@@ -37,6 +37,17 @@ module Unit
37
37
  }, connection.instance_variable_get(:@config))
38
38
  end
39
39
  end
40
+
41
+ describe "when passing a URL" do
42
+ it "derives the scheme, host and port" do
43
+ connection = Clickhouse::Connection.new :url => "https://19.82.8.1:1947"
44
+ assert_equal({
45
+ :scheme => "https",
46
+ :host => "19.82.8.1",
47
+ :port => 1947
48
+ }, connection.instance_variable_get(:@config))
49
+ end
50
+ end
40
51
  end
41
52
  end
42
53
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clickhouse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Engel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-19 00:00:00.000000000 Z
11
+ date: 2016-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pond
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -122,7 +136,7 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
- description: A Ruby database driver for Clickhouse
139
+ description: A Ruby database driver for ClickHouse
126
140
  email:
127
141
  - pm_engel@icloud.com
128
142
  executables: []
@@ -139,6 +153,7 @@ files:
139
153
  - VERSION
140
154
  - clickhouse.gemspec
141
155
  - lib/clickhouse.rb
156
+ - lib/clickhouse/cluster.rb
142
157
  - lib/clickhouse/connection.rb
143
158
  - lib/clickhouse/connection/client.rb
144
159
  - lib/clickhouse/connection/logger.rb
@@ -157,6 +172,7 @@ files:
157
172
  - test/unit/connection/query/test_result_set.rb
158
173
  - test/unit/connection/query/test_table.rb
159
174
  - test/unit/connection/test_client.rb
175
+ - test/unit/connection/test_cluster.rb
160
176
  - test/unit/connection/test_logger.rb
161
177
  - test/unit/connection/test_query.rb
162
178
  - test/unit/test_clickhouse.rb
@@ -183,7 +199,7 @@ rubyforge_project:
183
199
  rubygems_version: 2.4.5.1
184
200
  signing_key:
185
201
  specification_version: 4
186
- summary: A Ruby database driver for Clickhouse
202
+ summary: A Ruby database driver for ClickHouse
187
203
  test_files:
188
204
  - test/test_helper.rb
189
205
  - test/test_helper/coverage.rb
@@ -193,6 +209,7 @@ test_files:
193
209
  - test/unit/connection/query/test_result_set.rb
194
210
  - test/unit/connection/query/test_table.rb
195
211
  - test/unit/connection/test_client.rb
212
+ - test/unit/connection/test_cluster.rb
196
213
  - test/unit/connection/test_logger.rb
197
214
  - test/unit/connection/test_query.rb
198
215
  - test/unit/test_clickhouse.rb