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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +172 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/clickhouse.gemspec +27 -0
- data/lib/clickhouse.rb +42 -0
- data/lib/clickhouse/connection.rb +23 -0
- data/lib/clickhouse/connection/client.rb +65 -0
- data/lib/clickhouse/connection/logger.rb +12 -0
- data/lib/clickhouse/connection/query.rb +160 -0
- data/lib/clickhouse/connection/query/result_row.rb +18 -0
- data/lib/clickhouse/connection/query/result_set.rb +101 -0
- data/lib/clickhouse/connection/query/table.rb +50 -0
- data/lib/clickhouse/error.rb +18 -0
- data/lib/clickhouse/version.rb +7 -0
- data/script/console +58 -0
- data/test/test_helper.rb +15 -0
- data/test/test_helper/coverage.rb +16 -0
- data/test/test_helper/minitest.rb +13 -0
- data/test/test_helper/simple_connection.rb +12 -0
- data/test/unit/connection/query/test_result_row.rb +36 -0
- data/test/unit/connection/query/test_result_set.rb +196 -0
- data/test/unit/connection/query/test_table.rb +39 -0
- data/test/unit/connection/test_client.rb +131 -0
- data/test/unit/connection/test_logger.rb +35 -0
- data/test/unit/connection/test_query.rb +391 -0
- data/test/unit/test_clickhouse.rb +91 -0
- data/test/unit/test_connection.rb +44 -0
- metadata +199 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
module Clickhouse
|
2
|
+
class Connection
|
3
|
+
module Query
|
4
|
+
class ResultRow < Array
|
5
|
+
|
6
|
+
def initialize(values = [], keys = nil)
|
7
|
+
super values
|
8
|
+
@keys = keys || (0..(values.size - 1)).collect{|i| "column#{i}"}
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
@hash ||= Hash[@keys.zip(self)]
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Clickhouse
|
2
|
+
class Connection
|
3
|
+
module Query
|
4
|
+
class ResultSet
|
5
|
+
include Enumerable
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :@rows, :size, :empty?
|
9
|
+
def_delegators :to_a, :first, :last, :flatten
|
10
|
+
|
11
|
+
def initialize(rows = [], names = nil, types = nil)
|
12
|
+
@rows = rows
|
13
|
+
@names = names
|
14
|
+
@types = types
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
(0..(size - 1)).collect do |index|
|
19
|
+
yield self[index]
|
20
|
+
self[index]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](index)
|
25
|
+
row = @rows[index]
|
26
|
+
row = @rows[index] = parse_row(row) if row.class == Array
|
27
|
+
row
|
28
|
+
end
|
29
|
+
|
30
|
+
def present?
|
31
|
+
!empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_hashes
|
35
|
+
collect(&:to_hash)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def parse_row(array)
|
41
|
+
values = array.each_with_index.to_a.collect do |value, i|
|
42
|
+
parse_value(@types[i], value) if @types
|
43
|
+
end
|
44
|
+
ResultRow.new values, @names
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_value(type, value)
|
48
|
+
if value
|
49
|
+
case type
|
50
|
+
when "UInt8", "UInt16", "UInt32", "UInt64", "Int8", "Int16", "Int32", "Int64"
|
51
|
+
parse_int_value value
|
52
|
+
when "Float32", "Float64"
|
53
|
+
parse_float_value value
|
54
|
+
when "String", "Enum8", "Enum16"
|
55
|
+
parse_string_value value
|
56
|
+
when /FixedString\(\d+\)/
|
57
|
+
parse_fixed_string_value value
|
58
|
+
when "Date"
|
59
|
+
parse_date_value value
|
60
|
+
when "DateTime"
|
61
|
+
parse_date_time_value value
|
62
|
+
when /Array\(/
|
63
|
+
parse_array_value value
|
64
|
+
else
|
65
|
+
raise NotImplementedError, "Cannot parse value of type #{type.inspect}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_int_value(value)
|
71
|
+
value.to_i
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_float_value(value)
|
75
|
+
value.to_f
|
76
|
+
end
|
77
|
+
|
78
|
+
def parse_string_value(value)
|
79
|
+
value.force_encoding("UTF-8")
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_fixed_string_value(value)
|
83
|
+
value.delete("\000").force_encoding("UTF-8")
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_date_value(value)
|
87
|
+
Date.parse(value)
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_date_time_value(value)
|
91
|
+
Time.parse(value)
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_array_value(value)
|
95
|
+
JSON.parse(value)
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Clickhouse
|
2
|
+
class Connection
|
3
|
+
module Query
|
4
|
+
class Table
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@columns = []
|
9
|
+
yield self
|
10
|
+
end
|
11
|
+
|
12
|
+
def engine(value)
|
13
|
+
@engine = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_sql
|
17
|
+
raise Clickhouse::InvalidQueryError, "Missing table engine" unless @engine
|
18
|
+
length = @columns.collect{|x| x[0].to_s.size}.max
|
19
|
+
|
20
|
+
sql = []
|
21
|
+
sql << "CREATE TABLE #{@name} ("
|
22
|
+
|
23
|
+
@columns.each_with_index do |(name, type), index|
|
24
|
+
sql << " #{name.ljust(length, " ")} #{type}#{"," unless index == @columns.size - 1}"
|
25
|
+
end
|
26
|
+
|
27
|
+
sql << ")"
|
28
|
+
sql << "ENGINE = #{@engine}"
|
29
|
+
|
30
|
+
sql.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def method_missing(name, *args)
|
36
|
+
type = name.to_s
|
37
|
+
.gsub(/(^.|_\w)/) {
|
38
|
+
$1.upcase
|
39
|
+
}
|
40
|
+
.gsub("Uint", "UInt")
|
41
|
+
.delete("_")
|
42
|
+
|
43
|
+
type << "(#{args[1]})" if args[1]
|
44
|
+
@columns << [args[0].to_s, type]
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "logger"
|
4
|
+
require "bundler"
|
5
|
+
Bundler.require :default, :development
|
6
|
+
|
7
|
+
def connect!(config = {})
|
8
|
+
Clickhouse.logger = Logger.new(STDOUT)
|
9
|
+
Clickhouse.establish_connection(config)
|
10
|
+
end
|
11
|
+
|
12
|
+
def conn(config = {})
|
13
|
+
connect!(config) unless Clickhouse.connection
|
14
|
+
Clickhouse.connection
|
15
|
+
end
|
16
|
+
|
17
|
+
def events
|
18
|
+
"events"
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_table
|
22
|
+
conn.create_table(events) do |t|
|
23
|
+
t.fixed_string :id, 16
|
24
|
+
t.uint16 :year
|
25
|
+
t.date :date
|
26
|
+
t.date_time :time
|
27
|
+
t.string :event
|
28
|
+
t.uint32 :user_id
|
29
|
+
t.float32 :revenue
|
30
|
+
t.engine "MergeTree(date, (year, date), 8192)"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def insert_rows
|
35
|
+
conn.insert_rows(events, :names => %w(id year date time event user_id revenue)) do |rows|
|
36
|
+
rows << [
|
37
|
+
"d91d1c90",
|
38
|
+
2016,
|
39
|
+
"2016-10-17",
|
40
|
+
"2016-10-17 23:14:28",
|
41
|
+
"click",
|
42
|
+
1982,
|
43
|
+
0.18
|
44
|
+
]
|
45
|
+
rows << [
|
46
|
+
"d91d2294",
|
47
|
+
2016,
|
48
|
+
"2016-10-17",
|
49
|
+
"2016-10-17 23:14:41",
|
50
|
+
"click",
|
51
|
+
1947,
|
52
|
+
0.203
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
puts "Loading Clickhouse development environment (#{Clickhouse::VERSION})"
|
58
|
+
Pry.start
|
data/test/test_helper.rb
ADDED
@@ -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,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
|