clickhouse-activerecord 0.5.14 → 0.6.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 +4 -4
- data/README.md +10 -2
- data/clickhouse-activerecord.gemspec +0 -1
- data/lib/active_record/connection_adapters/clickhouse/oid/date_time.rb +1 -2
- data/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +14 -4
- data/lib/active_record/connection_adapters/clickhouse_adapter.rb +11 -9
- data/lib/arel/nodes/settings.rb +11 -0
- data/lib/arel/visitors/clickhouse.rb +54 -0
- data/lib/clickhouse-activerecord/migration.rb +22 -2
- data/lib/clickhouse-activerecord/version.rb +1 -1
- data/lib/clickhouse-activerecord.rb +8 -0
- data/lib/core_extensions/active_record/relation.rb +29 -0
- data/lib/core_extensions/arel/nodes/select_statement.rb +18 -0
- data/lib/core_extensions/arel/select_manager.rb +12 -0
- data/lib/{clickhouse-activerecord → core_extensions}/arel/table.rb +4 -2
- metadata +8 -19
- data/lib/clickhouse-activerecord/arel/visitors/to_sql.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 182d838abbda9972bdfa59b1e6a76af247bd4e3b275a5e611dfe6cd8ceaaea5c
|
4
|
+
data.tar.gz: f987ee429692c24518a0ea54d24f52c65f96ddfe1e6d6770fe0c2dd4549c623e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90aac52260cd39eaa23dddb9be9290afa8ccb6731f4c84b6403d67fc56ddf1e24d075e9e33d9cc99ffdf29309608b0a8ae78a35db87d673731438dd5db50c57b
|
7
|
+
data.tar.gz: 505c299c7f657deb7b0685504c0f61754b801d0900238fa9a00a939b49873e379e2d2c84f9ce3661e0c26d405b9e94a31cbeb71c514f644cf4391059df44fa22
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Clickhouse::Activerecord
|
2
2
|
|
3
3
|
A Ruby database ActiveRecord driver for ClickHouse. Support Rails >= 5.2.
|
4
|
-
Support ClickHouse version from
|
4
|
+
Support ClickHouse version from 22.0 LTS.
|
5
5
|
|
6
6
|
## Installation
|
7
7
|
|
@@ -165,7 +165,7 @@ Structure load from `db/clickhouse_structure.sql` file:
|
|
165
165
|
|
166
166
|
```ruby
|
167
167
|
Action.where(url: 'http://example.com', date: Date.current).where.not(name: nil).order(created_at: :desc).limit(10)
|
168
|
-
# Clickhouse Action Load (10.3ms) SELECT
|
168
|
+
# Clickhouse Action Load (10.3ms) SELECT actions.* FROM actions WHERE actions.date = '2017-11-29' AND actions.url = 'http://example.com' AND (actions.name IS NOT NULL) ORDER BY actions.created_at DESC LIMIT 10
|
169
169
|
#=> #<ActiveRecord::Relation [#<Action *** >]>
|
170
170
|
|
171
171
|
Action.create(url: 'http://example.com', date: Date.yesterday)
|
@@ -175,6 +175,14 @@ Action.create(url: 'http://example.com', date: Date.yesterday)
|
|
175
175
|
ActionView.maximum(:date)
|
176
176
|
# Clickhouse (10.3ms) SELECT maxMerge(actions.date) FROM actions
|
177
177
|
#=> 'Wed, 29 Nov 2017'
|
178
|
+
|
179
|
+
Action.where(date: Date.current).final.limit(10)
|
180
|
+
# Clickhouse Action Load (10.3ms) SELECT actions.* FROM actions FINAL WHERE actions.date = '2017-11-29' LIMIT 10
|
181
|
+
#=> #<ActiveRecord::Relation [#<Action *** >]>
|
182
|
+
|
183
|
+
Action.settings(optimize_read_in_order: 1).where(date: Date.current).limit(10)
|
184
|
+
# Clickhouse Action Load (10.3ms) SELECT actions.* FROM actions FINAL WHERE actions.date = '2017-11-29' LIMIT 10 SETTINGS optimize_read_in_order = 1
|
185
|
+
#=> #<ActiveRecord::Relation [#<Action *** >]>
|
178
186
|
```
|
179
187
|
|
180
188
|
|
@@ -26,7 +26,6 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_runtime_dependency 'bundler', '>= 1.13.4'
|
27
27
|
spec.add_runtime_dependency 'activerecord', '>= 5.2'
|
28
28
|
|
29
|
-
spec.add_development_dependency 'bundler', '>= 1.15'
|
30
29
|
spec.add_development_dependency 'rake', '~> 13.0'
|
31
30
|
spec.add_development_dependency 'rspec', '~> 3.4'
|
32
31
|
spec.add_development_dependency 'pry', '~> 0.12'
|
@@ -9,9 +9,8 @@ module ActiveRecord
|
|
9
9
|
def serialize(value)
|
10
10
|
value = super
|
11
11
|
return unless value
|
12
|
-
return value.strftime('%Y-%m-%d %H:%M:%S') unless value.acts_like?(:time)
|
13
12
|
|
14
|
-
value.
|
13
|
+
value.strftime('%Y-%m-%d %H:%M:%S' + (@precision.present? && @precision > 0 ? ".%#{@precision}N" : ''))
|
15
14
|
end
|
16
15
|
|
17
16
|
def type_cast_from_database(value)
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
18
18
|
|
19
19
|
def exec_query(sql, name = nil, binds = [], prepare: false)
|
20
20
|
result = do_execute(sql, name)
|
21
|
-
ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'])
|
21
|
+
ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'], result['meta'].map { |m| [m['name'], type_map.lookup(m['type'])] }.to_h)
|
22
22
|
rescue ActiveRecord::ActiveRecordError => e
|
23
23
|
raise e
|
24
24
|
rescue StandardError => e
|
@@ -105,10 +105,20 @@ module ActiveRecord
|
|
105
105
|
def process_response(res)
|
106
106
|
case res.code.to_i
|
107
107
|
when 200
|
108
|
-
res.body.
|
108
|
+
if res.body.to_s.include?("DB::Exception")
|
109
|
+
raise ActiveRecord::ActiveRecordError, "Response code: #{res.code}:\n#{res.body}"
|
110
|
+
else
|
111
|
+
res.body.presence && JSON.parse(res.body)
|
112
|
+
end
|
109
113
|
else
|
110
|
-
|
111
|
-
|
114
|
+
case res.body
|
115
|
+
when /DB::Exception:.*\(UNKNOWN_DATABASE\)/
|
116
|
+
raise ActiveRecord::NoDatabaseError
|
117
|
+
when /DB::Exception:.*\(DATABASE_ALREADY_EXISTS\)/
|
118
|
+
raise ActiveRecord::DatabaseAlreadyExists
|
119
|
+
else
|
120
|
+
raise ActiveRecord::ActiveRecordError, "Response code: #{res.code}:\n#{res.body}"
|
121
|
+
end
|
112
122
|
end
|
113
123
|
rescue JSON::ParserError
|
114
124
|
res.body
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require '
|
3
|
+
require 'arel/visitors/clickhouse'
|
4
|
+
require 'arel/nodes/settings'
|
5
5
|
require 'clickhouse-activerecord/migration'
|
6
6
|
require 'active_record/connection_adapters/clickhouse/oid/array'
|
7
7
|
require 'active_record/connection_adapters/clickhouse/oid/date'
|
@@ -62,6 +62,8 @@ module ActiveRecord
|
|
62
62
|
|
63
63
|
module ModelSchema
|
64
64
|
module ClassMethods
|
65
|
+
delegate :final, :settings, to: :all
|
66
|
+
|
65
67
|
def is_view
|
66
68
|
@is_view || false
|
67
69
|
end
|
@@ -69,10 +71,10 @@ module ActiveRecord
|
|
69
71
|
def is_view=(value)
|
70
72
|
@is_view = value
|
71
73
|
end
|
72
|
-
|
73
|
-
def arel_table # :nodoc:
|
74
|
-
|
75
|
-
end
|
74
|
+
#
|
75
|
+
# def arel_table # :nodoc:
|
76
|
+
# @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
|
77
|
+
# end
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
@@ -92,7 +94,7 @@ module ActiveRecord
|
|
92
94
|
datetime: { name: 'DateTime' },
|
93
95
|
datetime64: { name: 'DateTime64' },
|
94
96
|
date: { name: 'Date' },
|
95
|
-
boolean: { name: '
|
97
|
+
boolean: { name: 'Bool' },
|
96
98
|
uuid: { name: 'UUID' },
|
97
99
|
|
98
100
|
enum8: { name: 'Enum8' },
|
@@ -145,7 +147,7 @@ module ActiveRecord
|
|
145
147
|
end
|
146
148
|
|
147
149
|
def arel_visitor # :nodoc:
|
148
|
-
|
150
|
+
Arel::Visitors::Clickhouse.new(self)
|
149
151
|
end
|
150
152
|
|
151
153
|
def native_database_types #:nodoc:
|
@@ -191,7 +193,7 @@ module ActiveRecord
|
|
191
193
|
super
|
192
194
|
register_class_with_limit m, %r(String), Type::String
|
193
195
|
register_class_with_limit m, 'Date', Clickhouse::OID::Date
|
194
|
-
|
196
|
+
register_class_with_precision m, %r(datetime)i, Clickhouse::OID::DateTime
|
195
197
|
|
196
198
|
register_class_with_limit m, %r(Int8), Type::Integer
|
197
199
|
register_class_with_limit m, %r(Int16), Type::Integer
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'arel/visitors/to_sql'
|
2
|
+
|
3
|
+
module Arel
|
4
|
+
module Visitors
|
5
|
+
class Clickhouse < ::Arel::Visitors::ToSql
|
6
|
+
|
7
|
+
def aggregate(name, o, collector)
|
8
|
+
# replacing function name for materialized view
|
9
|
+
if o.expressions.first && o.expressions.first != '*' && !o.expressions.first.is_a?(String) && o.expressions.first.relation&.is_view
|
10
|
+
super("#{name.downcase}Merge", o, collector)
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def visit_Arel_Table o, collector
|
17
|
+
collector = super
|
18
|
+
collector << ' FINAL ' if o.final
|
19
|
+
collector
|
20
|
+
end
|
21
|
+
|
22
|
+
def visit_Arel_Nodes_SelectOptions(o, collector)
|
23
|
+
maybe_visit o.settings, super
|
24
|
+
end
|
25
|
+
|
26
|
+
def visit_Arel_Nodes_Settings(o, collector)
|
27
|
+
return collector if o.expr.empty?
|
28
|
+
|
29
|
+
collector << "SETTINGS "
|
30
|
+
o.expr.each_with_index do |(key, value), i|
|
31
|
+
collector << ", " if i > 0
|
32
|
+
collector << key.to_s.gsub(/\W+/, "")
|
33
|
+
collector << " = "
|
34
|
+
collector << sanitize_as_setting_value(value)
|
35
|
+
end
|
36
|
+
collector
|
37
|
+
end
|
38
|
+
|
39
|
+
def sanitize_as_setting_value(value)
|
40
|
+
if value == :default
|
41
|
+
'DEFAULT'
|
42
|
+
else
|
43
|
+
quote(value)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def sanitize_as_setting_name(value)
|
48
|
+
return value if Arel::Nodes::SqlLiteral === value
|
49
|
+
@connection.sanitize_as_setting_name(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -28,20 +28,32 @@ module ClickhouseActiverecord
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def all_versions
|
31
|
-
|
31
|
+
final.where(active: 1).order(:version).pluck(:version)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
36
|
class InternalMetadata < ::ActiveRecord::InternalMetadata
|
37
37
|
class << self
|
38
|
+
|
39
|
+
def []=(key, value)
|
40
|
+
row = final.find_by(key: key)
|
41
|
+
if row.nil? || row.value != value
|
42
|
+
create!(key: key, value: value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def [](key)
|
47
|
+
final.where(key: key).pluck(:value).first
|
48
|
+
end
|
49
|
+
|
38
50
|
def create_table
|
39
51
|
return if table_exists?
|
40
52
|
|
41
53
|
key_options = connection.internal_string_options_for_primary_key
|
42
54
|
table_options = {
|
43
55
|
id: false,
|
44
|
-
options: connection.adapter_name.downcase == 'clickhouse' ? '
|
56
|
+
options: connection.adapter_name.downcase == 'clickhouse' ? 'ReplacingMergeTree(created_at) PARTITION BY key ORDER BY key' : '',
|
45
57
|
if_not_exists: true
|
46
58
|
}
|
47
59
|
full_config = connection.instance_variable_get(:@full_config) || {}
|
@@ -122,5 +134,13 @@ module ClickhouseActiverecord
|
|
122
134
|
super
|
123
135
|
end
|
124
136
|
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def record_environment
|
141
|
+
return if down?
|
142
|
+
ClickhouseActiverecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment
|
143
|
+
end
|
144
|
+
|
125
145
|
end
|
126
146
|
end
|
@@ -4,6 +4,10 @@ require 'active_record/connection_adapters/clickhouse_adapter'
|
|
4
4
|
|
5
5
|
require 'core_extensions/active_record/relation'
|
6
6
|
|
7
|
+
require 'core_extensions/arel/nodes/select_statement'
|
8
|
+
require 'core_extensions/arel/select_manager'
|
9
|
+
require 'core_extensions/arel/table'
|
10
|
+
|
7
11
|
require_relative '../core_extensions/active_record/migration/command_recorder'
|
8
12
|
ActiveRecord::Migration::CommandRecorder.include CoreExtensions::ActiveRecord::Migration::CommandRecorder
|
9
13
|
|
@@ -18,5 +22,9 @@ end
|
|
18
22
|
module ClickhouseActiverecord
|
19
23
|
def self.load
|
20
24
|
ActiveRecord::Relation.prepend(CoreExtensions::ActiveRecord::Relation)
|
25
|
+
|
26
|
+
Arel::Nodes::SelectStatement.prepend(CoreExtensions::Arel::Nodes::SelectStatement)
|
27
|
+
Arel::SelectManager.prepend(CoreExtensions::Arel::SelectManager)
|
28
|
+
Arel::Table.prepend(CoreExtensions::Arel::Table)
|
21
29
|
end
|
22
30
|
end
|
@@ -10,6 +10,35 @@ module CoreExtensions
|
|
10
10
|
self.order_values = (column_names & %w[date created_at]).map { |c| arel_table[c].desc }
|
11
11
|
self
|
12
12
|
end
|
13
|
+
|
14
|
+
# @param [Hash] opts
|
15
|
+
def settings(**opts)
|
16
|
+
check_command('SETTINGS')
|
17
|
+
@values[:settings] = (@values[:settings] || {}).merge opts
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [Boolean] final
|
22
|
+
def final(final = true)
|
23
|
+
check_command('FINAL')
|
24
|
+
@table = @table.dup
|
25
|
+
@table.final = final
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def check_command(cmd)
|
32
|
+
raise ::ActiveRecord::ActiveRecordError, cmd + ' is a ClickHouse specific query clause' unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_arel(aliases = nil)
|
36
|
+
arel = super
|
37
|
+
|
38
|
+
arel.settings(@values[:settings]) if @values[:settings].present?
|
39
|
+
|
40
|
+
arel
|
41
|
+
end
|
13
42
|
end
|
14
43
|
end
|
15
44
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CoreExtensions
|
2
|
+
module Arel # :nodoc: all
|
3
|
+
module Nodes
|
4
|
+
module SelectStatement
|
5
|
+
attr_accessor :settings
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
@settings = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def eql?(other)
|
13
|
+
super && settings == other.settings
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clickhouse-activerecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Odintsov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '5.2'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.15'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - ">="
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.15'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: rake
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,9 +107,9 @@ files:
|
|
121
107
|
- lib/active_record/connection_adapters/clickhouse/schema_definitions.rb
|
122
108
|
- lib/active_record/connection_adapters/clickhouse/schema_statements.rb
|
123
109
|
- lib/active_record/connection_adapters/clickhouse_adapter.rb
|
110
|
+
- lib/arel/nodes/settings.rb
|
111
|
+
- lib/arel/visitors/clickhouse.rb
|
124
112
|
- lib/clickhouse-activerecord.rb
|
125
|
-
- lib/clickhouse-activerecord/arel/table.rb
|
126
|
-
- lib/clickhouse-activerecord/arel/visitors/to_sql.rb
|
127
113
|
- lib/clickhouse-activerecord/migration.rb
|
128
114
|
- lib/clickhouse-activerecord/railtie.rb
|
129
115
|
- lib/clickhouse-activerecord/schema.rb
|
@@ -131,6 +117,9 @@ files:
|
|
131
117
|
- lib/clickhouse-activerecord/tasks.rb
|
132
118
|
- lib/clickhouse-activerecord/version.rb
|
133
119
|
- lib/core_extensions/active_record/relation.rb
|
120
|
+
- lib/core_extensions/arel/nodes/select_statement.rb
|
121
|
+
- lib/core_extensions/arel/select_manager.rb
|
122
|
+
- lib/core_extensions/arel/table.rb
|
134
123
|
- lib/generators/clickhouse_migration_generator.rb
|
135
124
|
- lib/tasks/clickhouse.rake
|
136
125
|
homepage: https://github.com/pnixx/clickhouse-activerecord
|
@@ -152,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
141
|
- !ruby/object:Gem::Version
|
153
142
|
version: '0'
|
154
143
|
requirements: []
|
155
|
-
rubygems_version: 3.
|
144
|
+
rubygems_version: 3.1.6
|
156
145
|
signing_key:
|
157
146
|
specification_version: 4
|
158
147
|
summary: ClickHouse ActiveRecord
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'arel/visitors/to_sql'
|
2
|
-
|
3
|
-
module ClickhouseActiverecord
|
4
|
-
module Arel
|
5
|
-
module Visitors
|
6
|
-
class ToSql < ::Arel::Visitors::ToSql
|
7
|
-
|
8
|
-
def aggregate(name, o, collector)
|
9
|
-
# replacing function name for materialized view
|
10
|
-
if o.expressions.first && o.expressions.first != '*' && !o.expressions.first.is_a?(String) && o.expressions.first.relation&.is_view
|
11
|
-
super("#{name.downcase}Merge", o, collector)
|
12
|
-
else
|
13
|
-
super
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|