click_house 1.0.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 +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
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/click_house.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'click_house/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'click_house'
|
7
|
+
spec.version = ClickHouse::VERSION
|
8
|
+
spec.authors = ['Aliaksandr Shylau']
|
9
|
+
spec.email = ['alex.shilov.by@gmail.com']
|
10
|
+
spec.summary = 'Modern Ruby database driver for ClickHouse'
|
11
|
+
spec.description = 'Yandex ClickHouse database interface for Ruby'
|
12
|
+
spec.homepage = 'https://github.com/shlima/click_house'
|
13
|
+
|
14
|
+
# Specify which files should be added to the gem when it is released.
|
15
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
16
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
17
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'faraday'
|
24
|
+
spec.add_dependency 'faraday_middleware'
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.17'
|
26
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
|
+
spec.add_development_dependency 'pry'
|
29
|
+
spec.add_development_dependency 'rubocop'
|
30
|
+
spec.add_development_dependency 'rubocop-performance'
|
31
|
+
end
|
data/doc/logo.svg
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<svg width="150px" height="170px" viewBox="0 0 15 17" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
3
|
+
<!-- Generator: Sketch 54.1 (76490) - https://sketchapp.com -->
|
4
|
+
<title>Group</title>
|
5
|
+
<desc>Created with Sketch.</desc>
|
6
|
+
<defs>
|
7
|
+
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
|
8
|
+
<stop stop-color="#FFE800" offset="0%"></stop>
|
9
|
+
<stop stop-color="#FFCC00" stop-opacity="0" offset="100%"></stop>
|
10
|
+
</linearGradient>
|
11
|
+
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
|
12
|
+
<stop stop-color="#FFE800" stop-opacity="0.99" offset="0%"></stop>
|
13
|
+
<stop stop-color="#FFCC00" stop-opacity="0" offset="100%"></stop>
|
14
|
+
</linearGradient>
|
15
|
+
</defs>
|
16
|
+
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
17
|
+
<g id="Group">
|
18
|
+
<g transform="translate(3.000000, 5.000000)" id="Path">
|
19
|
+
<polygon fill="url(#linearGradient-1)" fill-rule="nonzero" points="0 0 2 0 2 12 0 12"></polygon>
|
20
|
+
<polygon fill="url(#linearGradient-2)" fill-rule="nonzero" points="3 0 5 0 5 12 3 12"></polygon>
|
21
|
+
<polygon fill="url(#linearGradient-1)" fill-rule="nonzero" points="6 0 8 0 8 12 6 12"></polygon>
|
22
|
+
</g>
|
23
|
+
<g id="68747470733a2f2f63646e2e7261776769742e636f6d2f636c61726976652f636c612d727562792d706c7567696e2f6d61737465722f7075626c69632f69636f6e2f727562792e7376673f73616e6974697a653d74727565">
|
24
|
+
<g id="Group-2" fill-rule="nonzero">
|
25
|
+
<polygon id="Path" fill="#CE0000" points="4.5 3.2 9.8 3.2 7.1 11.2"></polygon>
|
26
|
+
<polygon id="Path" fill="#7C0000" points="9.7 3.2 14.1 3.2 7.1 11.1"></polygon>
|
27
|
+
<polygon id="Path" fill="#F87274" points="4.5 3.2 0.1 3.2 7.1 11.1"></polygon>
|
28
|
+
<polygon id="Path" fill="#F87274" points="2.4 0.1 4.5 3.3 7.1 0.1"></polygon>
|
29
|
+
<polygon id="Path" fill="#FB191D" points="7.1 0.1 9.7 3.3 11.7 0.1"></polygon>
|
30
|
+
<polygon id="Path" fill="#CC0001" points="9.7 3.2 11.7 4.4408921e-16 14.2 3.2"></polygon>
|
31
|
+
<polygon id="Path" fill="#FB191D" points="-8.8817842e-16 3.2 2.4 4.4408921e-16 4.6 3.2"></polygon>
|
32
|
+
<polygon id="Path" fill="#900000" points="4.5 3.2 7.1 4.4408921e-16 9.8 3.2"></polygon>
|
33
|
+
</g>
|
34
|
+
</g>
|
35
|
+
</g>
|
36
|
+
</g>
|
37
|
+
</svg>
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
version: '3.5'
|
2
|
+
|
3
|
+
services:
|
4
|
+
clickhouse:
|
5
|
+
image: yandex/clickhouse-server
|
6
|
+
ports:
|
7
|
+
- "8123:8123"
|
8
|
+
- "9000:9000"
|
9
|
+
- "9009:9009"
|
10
|
+
ulimits:
|
11
|
+
nproc: 65535
|
12
|
+
nofile:
|
13
|
+
soft: 262144
|
14
|
+
hard: 262144
|
15
|
+
volumes:
|
16
|
+
- ./tmp/clickhouse-data:/opt/clickhouse/data
|
17
|
+
networks:
|
18
|
+
- default
|
19
|
+
|
20
|
+
networks:
|
21
|
+
default:
|
data/lib/click_house.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'json'
|
5
|
+
require 'csv'
|
6
|
+
require 'uri'
|
7
|
+
require 'logger'
|
8
|
+
require 'faraday'
|
9
|
+
require 'forwardable'
|
10
|
+
require 'bigdecimal'
|
11
|
+
require 'faraday_middleware'
|
12
|
+
require 'click_house/version'
|
13
|
+
require 'click_house/errors'
|
14
|
+
require 'click_house/response'
|
15
|
+
require 'click_house/type'
|
16
|
+
require 'click_house/middleware'
|
17
|
+
require 'click_house/extend'
|
18
|
+
require 'click_house/util'
|
19
|
+
require 'click_house/definition'
|
20
|
+
|
21
|
+
module ClickHouse
|
22
|
+
extend Extend::TypeDefinition
|
23
|
+
extend Extend::Configurable
|
24
|
+
extend Extend::Connectible
|
25
|
+
|
26
|
+
autoload :Config, 'click_house/config'
|
27
|
+
autoload :Connection, 'click_house/connection'
|
28
|
+
|
29
|
+
%w[Date].each do |column|
|
30
|
+
add_type column, Type::DateType.new
|
31
|
+
add_type "Nullable(#{column})", Type::NullableType.new(Type::DateType.new)
|
32
|
+
end
|
33
|
+
|
34
|
+
['DateTime(%s)'].each do |column|
|
35
|
+
add_type column, Type::DateTimeType.new
|
36
|
+
add_type "Nullable(#{column})", Type::NullableType.new(Type::DateTimeType.new)
|
37
|
+
end
|
38
|
+
|
39
|
+
['Decimal(%s, %s)', 'Decimal32(%s)', 'Decimal64(%s)', 'Decimal128(%s)'].each do |column|
|
40
|
+
add_type column, Type::DecimalType.new
|
41
|
+
add_type "Nullable(#{column})", Type::NullableType.new(Type::DecimalType.new)
|
42
|
+
end
|
43
|
+
|
44
|
+
%w[UInt8 UInt16 UInt32 UInt64 Int8 Int16 Int32 Int64].each do |column|
|
45
|
+
add_type column, Type::IntegerType.new
|
46
|
+
add_type "Nullable(#{column})", Type::NullableType.new(Type::IntegerType.new)
|
47
|
+
end
|
48
|
+
|
49
|
+
%w[Float32 Float64].each do |column|
|
50
|
+
add_type column, Type::FloatType.new
|
51
|
+
add_type "Nullable(#{column})", Type::NullableType.new(Type::IntegerType.new)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClickHouse
|
4
|
+
class Config
|
5
|
+
DEFAULT_SCHEME = 'http'
|
6
|
+
DEFAULT_HOST = 'localhost'
|
7
|
+
DEFAULT_PORT = '8123'
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
adapter: Faraday.default_adapter,
|
11
|
+
url: nil,
|
12
|
+
scheme: 'http',
|
13
|
+
host: 'localhost',
|
14
|
+
port: '8123',
|
15
|
+
logger: nil,
|
16
|
+
database: nil,
|
17
|
+
username: nil,
|
18
|
+
password: nil,
|
19
|
+
timeout: nil,
|
20
|
+
open_timeout: nil,
|
21
|
+
ssl_verify: false
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
attr_accessor :adapter
|
25
|
+
attr_accessor :logger
|
26
|
+
attr_accessor :scheme
|
27
|
+
attr_accessor :host
|
28
|
+
attr_accessor :port
|
29
|
+
attr_accessor :database
|
30
|
+
attr_accessor :url
|
31
|
+
attr_accessor :username
|
32
|
+
attr_accessor :password
|
33
|
+
attr_accessor :timeout
|
34
|
+
attr_accessor :open_timeout
|
35
|
+
attr_accessor :ssl_verify
|
36
|
+
|
37
|
+
def initialize(params = {})
|
38
|
+
assign(DEFAULTS.merge(params))
|
39
|
+
yield(self) if block_given?
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [self]
|
43
|
+
def assign(params = {})
|
44
|
+
params.each { |k, v| public_send("#{k}=", v) }
|
45
|
+
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def auth?
|
50
|
+
!username.nil? || !password.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
def logger!
|
54
|
+
@logger || Logger.new('/dev/null')
|
55
|
+
end
|
56
|
+
|
57
|
+
def url!
|
58
|
+
@url || "#{scheme}://#{host}:#{port}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClickHouse
|
4
|
+
class Connection
|
5
|
+
include Extend::ConnectionHealthy
|
6
|
+
include Extend::ConnectionDatabase
|
7
|
+
include Extend::ConnectionTable
|
8
|
+
include Extend::ConnectionSelective
|
9
|
+
include Extend::ConnectionInserting
|
10
|
+
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
def initialize(config)
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute(query, body = nil, database: config.database)
|
18
|
+
post(body, query: { query: query }, database: database)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(path = '/', query: {}, database: config.database)
|
22
|
+
transport.get(compose(path, query.merge(database: database)))
|
23
|
+
end
|
24
|
+
|
25
|
+
def post(body = nil, query: {}, database: config.database)
|
26
|
+
transport.post(compose('/', query.merge(database: database)), body)
|
27
|
+
end
|
28
|
+
|
29
|
+
def transport
|
30
|
+
@transport ||= Faraday.new(config.url!) do |conn|
|
31
|
+
conn.options.timeout = config.timeout
|
32
|
+
conn.options.open_timeout = config.open_timeout
|
33
|
+
conn.ssl.verify = config.ssl_verify
|
34
|
+
conn.basic_auth(config.username, config.password) if config.auth?
|
35
|
+
conn.response Middleware::Logging, logger: config.logger!
|
36
|
+
conn.response Middleware::RaiseError
|
37
|
+
conn.response :json, content_type: %r{application/json}
|
38
|
+
conn.response Middleware::ParseCsv, content_type: %r{text/csv}
|
39
|
+
conn.adapter config.adapter
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def compose(path, query = {})
|
44
|
+
# without <query.compact> "DB::Exception: Empty query" error will occur
|
45
|
+
"#{path}?#{URI.encode_www_form({ send_progress_in_http_headers: 1 }.merge(query).compact)}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClickHouse
|
4
|
+
module Definition
|
5
|
+
class Column
|
6
|
+
attr_accessor :name
|
7
|
+
attr_accessor :type
|
8
|
+
attr_accessor :nullable
|
9
|
+
attr_accessor :extensions
|
10
|
+
attr_accessor :default
|
11
|
+
attr_accessor :materialized
|
12
|
+
attr_accessor :ttl
|
13
|
+
|
14
|
+
def initialize(params = {})
|
15
|
+
params.each { |k, v| public_send("#{k}=", v) }
|
16
|
+
yield(self) if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
nullable ? "#{name} Nullable(#{extension_type}) #{opts}" : "#{name} #{extension_type} #{opts}"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def opts
|
26
|
+
options = {
|
27
|
+
DEFAULT: Util::Statement.ensure(default, default),
|
28
|
+
MATERIALIZED: Util::Statement.ensure(materialized, materialized),
|
29
|
+
TTL: Util::Statement.ensure(ttl, ttl)
|
30
|
+
}.compact
|
31
|
+
|
32
|
+
result = options.each_with_object([]) do |(key, value), object|
|
33
|
+
object << "#{key} #{value}"
|
34
|
+
end
|
35
|
+
|
36
|
+
result.join(' ')
|
37
|
+
end
|
38
|
+
|
39
|
+
def extension_type
|
40
|
+
extensions.nil? ? type : format(type, *extensions)
|
41
|
+
rescue TypeError, ArgumentError
|
42
|
+
raise Exception, "please provide extensions for <#{type}>"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ClickHouse
|
4
|
+
module Definition
|
5
|
+
class ColumnSet
|
6
|
+
TYPES = [
|
7
|
+
'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Int8', 'Int16', 'Int32', 'Int64',
|
8
|
+
'Float32', 'Float64',
|
9
|
+
'Decimal(%d, %d)', 'Decimal32(%d)', 'Decimal64(%d)', 'Decimal128(%d)',
|
10
|
+
'String',
|
11
|
+
'FixedString(%d)',
|
12
|
+
'UUID',
|
13
|
+
'Date',
|
14
|
+
"DateTime('%s')"
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# @input "DateTime('%s')"
|
19
|
+
# @output "DateTime"
|
20
|
+
def method_name_for_type(type)
|
21
|
+
type.sub(/\(.+/, '')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
TYPES.each do |type|
|
26
|
+
method_name = method_name_for_type(type)
|
27
|
+
|
28
|
+
# t.Decimal :customer_id, nullable: true, default: ''
|
29
|
+
# t.Decimal :money, 1, 2, nullable: true, default: ''
|
30
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
31
|
+
def #{method_name}(*definition)
|
32
|
+
name = definition[0]
|
33
|
+
extentions = []
|
34
|
+
options = {}
|
35
|
+
extensions = Array(definition[1..-1]).each do |el|
|
36
|
+
el.is_a?(Hash) ? options.merge!(el) : extentions.push(el)
|
37
|
+
end
|
38
|
+
|
39
|
+
columns << Column.new(type: "#{type}", name: name, extensions: extensions, **options)
|
40
|
+
end
|
41
|
+
METHODS
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
yield(self) if block_given?
|
46
|
+
end
|
47
|
+
|
48
|
+
def columns
|
49
|
+
@columns ||= []
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
<<~SQL
|
54
|
+
( #{columns.map(&:to_s).join(', ')} )
|
55
|
+
SQL
|
56
|
+
end
|
57
|
+
|
58
|
+
# @example
|
59
|
+
# t.Nested :json do |n|
|
60
|
+
# n.UInt8 :city_id
|
61
|
+
# end
|
62
|
+
def nested(name, &block)
|
63
|
+
columns << "#{name} Nested #{ColumnSet.new(&block)}"
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :Nested, :nested
|
67
|
+
|
68
|
+
def push(sql)
|
69
|
+
columns << sql
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method :<<, :push
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
__END__
|
78
|
+
|
79
|
+
data = ClickHouse::Definition::ColumnSet.new do |t|
|
80
|
+
t << "words Enum('hello' = 1, 'world' = 2)"
|
81
|
+
end
|
82
|
+
|
83
|
+
puts data.to_s
|
84
|
+
|
85
|
+
data = ClickHouse::Definition::ColumnSet.new do |t|
|
86
|
+
t.Decimal :money
|
87
|
+
t.Float32 :client_id, default: 0
|
88
|
+
t.Float32 :city_id, default: 0, nullable: true
|
89
|
+
t.Nested :json do |n|
|
90
|
+
n.Date :created_at
|
91
|
+
n.Date :updated_at
|
92
|
+
end
|
93
|
+
|
94
|
+
t << "CUSTOM SQL"
|
95
|
+
end
|