clickhouse-activerecord 0.4.7 → 0.4.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed292c2d626374fcb26194a4ca28ca34d60f8c49105b7217bfc7654a5a1b08f0
4
- data.tar.gz: 97d4074d47e641e8202fd8796ea049f78bf76d86906ce08877f3eb96b236b116
3
+ metadata.gz: ad0ad1c7545498f275ff975d35ee9dde7d2f644516932ee33ab65796360426a0
4
+ data.tar.gz: 15b32b155c596c8cdd9e187681a128a4806ca7e2c457e0f833ffc66ba9c994af
5
5
  SHA512:
6
- metadata.gz: 23c4d834c3c3bbaa000b8bd9202064cbabf2c366a8984d1b2fd92dc51e4c6d2811301e39cd8d2b80141581dcedb8cb6647dcbe59d90eb0b0987afdd5f1b22efa
7
- data.tar.gz: 0ac017b6614a3072fdad422db66a36201d5e3a62269230f102fbe8b6c7085f48a139cd8c0753adab11ca53a25e91e6eb327298036ebc385eb26f302c4e5178f8
6
+ metadata.gz: 8f80ffc1c70dbbdd489ea59627e85a72f538276c80adc0fe5ee4319c222a61da2acd1680ea42bd8c4c4b713fb354f7498a3469cc3cfd17f9003c8dbd607719b2
7
+ data.tar.gz: 9c91108f18bb9b133708a682ee3dfec238cc54afac480fcada95e876bb051843a7269e3d81c6298299b1bbffbc5a8a47975add7919144d220845fd87c710122b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ### Version 0.4.10 (Mar 10, 2021)
2
+
3
+ * Support ClickHouse 20.9+
4
+ * Fix schema create / dump
5
+ * Support all integer types through :limit and :unsigned [@bdevel](https://github.com/bdevel)
6
+
1
7
  ### Version 0.4.4 (Sep 23, 2020)
2
8
 
3
9
  * Full support migration and rollback database
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 19.14 LTS.
4
+ Support ClickHouse version from 20.9 LTS.
5
5
 
6
6
  ## Installation
7
7
 
@@ -35,6 +35,21 @@ default: &default
35
35
  replica_name: '{replica}' # replica macros name, optional for creating replicated tables
36
36
  ```
37
37
 
38
+ Alternatively if you wish to pass a custom `Net::HTTP` transport (or any other
39
+ object which supports a `.post()` function with the same parameters as
40
+ `Net::HTTP`'s), you can do this directly instead of specifying
41
+ `host`/`port`/`ssl`:
42
+
43
+ ```ruby
44
+ class ActionView < ActiveRecord::Base
45
+ establish_connection(
46
+ adapter: 'clickhouse',
47
+ database: 'database',
48
+ connection: Net::HTTP.start('http://example.org', 8123)
49
+ )
50
+ end
51
+ ```
52
+
38
53
  ## Usage in Rails 5
39
54
 
40
55
  Add your `database.yml` connection information with postfix `_clickhouse` for you environment:
@@ -162,6 +177,44 @@ ActionView.maximum(:date)
162
177
  #=> 'Wed, 29 Nov 2017'
163
178
  ```
164
179
 
180
+
181
+ ### Migration Data Types
182
+
183
+ Integer types are unsigned by default. Specify signed values with `:unsigned =>
184
+ false`. The default integer is `UInt32`
185
+
186
+ | Type (bit size) | Range | :limit (byte size) |
187
+ | :--- | :----: | ---: |
188
+ | Int8 | -128 to 127 | 1 |
189
+ | Int16 | -32768 to 32767 | 2 |
190
+ | Int32 | -2147483648 to 2,147,483,647 | 3,4 |
191
+ | Int64 | -9223372036854775808 to 9223372036854775807] | 5,6,7,8 |
192
+ | Int128 | ... | 9 - 15 |
193
+ | Int256 | ... | 16+ |
194
+ | UInt8 | 0 to 255 | 1 |
195
+ | UInt16 | 0 to 65,535 | 2 |
196
+ | UInt32 | 0 to 4,294,967,295 | 3,4 |
197
+ | UInt64 | 0 to 18446744073709551615 | 5,6,7,8 |
198
+ | UInt256 | 0 to ... | 8+ |
199
+
200
+ Example:
201
+
202
+ ``` ruby
203
+ class CreateDataItems < ActiveRecord::Migration
204
+ def change
205
+ create_table "data_items", id: false, options: "VersionedCollapsingMergeTree(sign, version) PARTITION BY toYYYYMM(day) ORDER BY category", force: :cascade do |t|
206
+ t.date "day", null: false
207
+ t.string "category", null: false
208
+ t.integer "value_in", null: false
209
+ t.integer "sign", limit: 1, unsigned: false, default: -> { "CAST(1, 'Int8')" }, null: false
210
+ t.integer "version", limit: 8, default: -> { "CAST(toUnixTimestamp(now()), 'UInt64')" }, null: false
211
+ end
212
+ end
213
+ end
214
+
215
+ ```
216
+
217
+
165
218
  ### Using replica and cluster params in connection parameters
166
219
 
167
220
  ```yml
@@ -39,7 +39,9 @@ module ActiveRecord
39
39
  statements << accept(o.primary_keys) if o.primary_keys
40
40
 
41
41
  create_sql << "(#{statements.join(', ')})" if statements.present?
42
- add_table_options!(create_sql, table_options(o))
42
+ # Attach options for only table or materialized view
43
+ add_table_options!(create_sql, table_options(o)) if !o.view || o.view && o.materialized
44
+
43
45
  create_sql << " AS #{to_sql(o.as)}" if o.as
44
46
  create_sql
45
47
  end
@@ -35,13 +35,31 @@ module ActiveRecord
35
35
  end
36
36
 
37
37
  def integer(*args, **options)
38
- if options[:limit] == 8
39
- args.each { |name| column(name, :big_integer, options.except(:limit)) }
40
- else
41
- super
38
+ # default to unsigned
39
+ unsigned = options[:unsigned]
40
+ unsigned = true if unsigned.nil?
41
+
42
+ kind = :uint32 # default
43
+
44
+ if options[:limit]
45
+ if unsigned
46
+ kind = :uint8 if options[:limit] == 1
47
+ kind = :uint16 if options[:limit] == 2
48
+ kind = :uint32 if [3,4].include?(options[:limit])
49
+ kind = :uint64 if [5,6,7].include?(options[:limit])
50
+ kind = :big_integer if options[:limit] == 8
51
+ kind = :uint256 if options[:limit] > 8
52
+ else
53
+ kind = :int8 if options[:limit] == 1
54
+ kind = :int16 if options[:limit] == 2
55
+ kind = :int32 if [3,4].include?(options[:limit])
56
+ kind = :int64 if options[:limit] > 5 && options[:limit] <= 8
57
+ kind = :int128 if options[:limit] > 8 && options[:limit] <= 16
58
+ kind = :int256 if options[:limit] > 16
59
+ end
42
60
  end
61
+ args.each { |name| column(name, kind, options.except(:limit, :unsigned)) }
43
62
  end
44
-
45
63
  end
46
64
  end
47
65
  end
@@ -17,8 +17,10 @@ module ActiveRecord
17
17
  def exec_query(sql, name = nil, binds = [], prepare: false)
18
18
  result = do_execute(sql, name)
19
19
  ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'])
20
- rescue StandardError => _e
21
- raise ActiveRecord::ActiveRecordError, "Response: #{result}"
20
+ rescue ActiveRecord::ActiveRecordError => e
21
+ raise e
22
+ rescue StandardError => e
23
+ raise ActiveRecord::ActiveRecordError, "Response: #{e.message}"
22
24
  end
23
25
 
24
26
  def exec_update(_sql, _name = nil, _binds = [])
@@ -36,8 +38,8 @@ module ActiveRecord
36
38
  end
37
39
 
38
40
  def table_options(table)
39
- sql = do_system_execute("SHOW CREATE TABLE `#{table}`")['data'].try(:first).try(:first)
40
- { options: sql.gsub(/^(?:.*?)ENGINE = (.*?)$/, '\\1') }
41
+ sql = show_create_table(table)
42
+ { options: sql.gsub(/^(?:.*?)(?:ENGINE = (.*?))?( AS SELECT .*?)?$/, '\\1').presence, as: sql.match(/^CREATE (?:.*?) AS (SELECT .*?)$/).try(:[], 1) }.compact
41
43
  end
42
44
 
43
45
  # Not indexes on clickhouse
@@ -17,9 +17,18 @@ module ActiveRecord
17
17
  # Establishes a connection to the database that's used by all Active Record objects
18
18
  def clickhouse_connection(config)
19
19
  config = config.symbolize_keys
20
- host = config[:host] || 'localhost'
21
- port = config[:port] || 8123
22
- ssl = config[:ssl].present? ? config[:ssl] : port == 443
20
+ if config[:connection]
21
+ connection = {
22
+ connection: config[:connection]
23
+ }
24
+ else
25
+ port = config[:port] || 8123
26
+ connection = {
27
+ host: config[:host] || 'localhost',
28
+ port: port,
29
+ ssl: config[:ssl].present? ? config[:ssl] : port == 443,
30
+ }
31
+ end
23
32
 
24
33
  if config.key?(:database)
25
34
  database = config[:database]
@@ -27,7 +36,7 @@ module ActiveRecord
27
36
  raise ArgumentError, 'No database specified. Missing argument: database.'
28
37
  end
29
38
 
30
- ConnectionAdapters::ClickhouseAdapter.new(logger, [host, port, ssl], { user: config[:username], password: config[:password], database: database }.compact, config)
39
+ ConnectionAdapters::ClickhouseAdapter.new(logger, connection, { user: config[:username], password: config[:password], database: database }.compact, config)
31
40
  end
32
41
  end
33
42
  end
@@ -79,7 +88,6 @@ module ActiveRecord
79
88
 
80
89
  class ClickhouseAdapter < AbstractAdapter
81
90
  ADAPTER_NAME = 'Clickhouse'.freeze
82
-
83
91
  NATIVE_DATABASE_TYPES = {
84
92
  string: { name: 'String' },
85
93
  integer: { name: 'UInt32' },
@@ -88,7 +96,21 @@ module ActiveRecord
88
96
  decimal: { name: 'Decimal' },
89
97
  datetime: { name: 'DateTime' },
90
98
  date: { name: 'Date' },
91
- boolean: { name: 'UInt8' }
99
+ boolean: { name: 'UInt8' },
100
+
101
+ int8: { name: 'Int8' },
102
+ int16: { name: 'Int16' },
103
+ int32: { name: 'Int32' },
104
+ int64: { name: 'Int64' },
105
+ int128: { name: 'Int128' },
106
+ int256: { name: 'Int256' },
107
+
108
+ uint8: { name: 'UInt8' },
109
+ uint16: { name: 'UInt16' },
110
+ uint32: { name: 'UInt32' },
111
+ uint64: { name: 'UInt64' },
112
+ # uint128: { name: 'UInt128' }, not yet implemented in clickhouse
113
+ uint256: { name: 'UInt256' },
92
114
  }.freeze
93
115
 
94
116
  include Clickhouse::SchemaStatements
@@ -139,10 +161,12 @@ module ActiveRecord
139
161
  when /(Nullable)?\(?String\)?/
140
162
  super('String')
141
163
  when /(Nullable)?\(?U?Int8\)?/
142
- super('int2')
143
- when /(Nullable)?\(?U?Int(16|32)\)?/
144
- super('int4')
145
- when /(Nullable)?\(?U?Int(64)\)?/
164
+ 1
165
+ when /(Nullable)?\(?U?Int16\)?/
166
+ 2
167
+ when /(Nullable)?\(?U?Int32\)?/
168
+ nil
169
+ when /(Nullable)?\(?U?Int64\)?/
146
170
  8
147
171
  else
148
172
  super
@@ -154,14 +178,20 @@ module ActiveRecord
154
178
  register_class_with_limit m, %r(String), Type::String
155
179
  register_class_with_limit m, 'Date', Clickhouse::OID::Date
156
180
  register_class_with_limit m, 'DateTime', Clickhouse::OID::DateTime
157
- register_class_with_limit m, %r(Uint8), Type::UnsignedInteger
158
- m.alias_type 'UInt16', 'UInt8'
159
- m.alias_type 'UInt32', 'UInt8'
160
- register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
181
+
161
182
  register_class_with_limit m, %r(Int8), Type::Integer
162
- m.alias_type 'Int16', 'Int8'
163
- m.alias_type 'Int32', 'Int8'
183
+ register_class_with_limit m, %r(Int16), Type::Integer
184
+ register_class_with_limit m, %r(Int32), Type::Integer
164
185
  register_class_with_limit m, %r(Int64), Type::Integer
186
+ register_class_with_limit m, %r(Int128), Type::Integer
187
+ register_class_with_limit m, %r(Int256), Type::Integer
188
+
189
+ register_class_with_limit m, %r(Uint8), Type::UnsignedInteger
190
+ register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
191
+ register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
192
+ register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
193
+ #register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
194
+ register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
165
195
  end
166
196
 
167
197
  # Quoting time without microseconds
@@ -201,6 +231,12 @@ module ActiveRecord
201
231
  ClickhouseActiverecord::SchemaDumper.create(self, options)
202
232
  end
203
233
 
234
+ # @param [String] table
235
+ # @return [String]
236
+ def show_create_table(table)
237
+ do_system_execute("SHOW CREATE TABLE `#{table}`")['data'].try(:first).try(:first).gsub(/[\n\s]+/m, ' ')
238
+ end
239
+
204
240
  # Create a new ClickHouse database.
205
241
  def create_database(name)
206
242
  sql = apply_cluster "CREATE DATABASE #{quote_table_name(name)}"
@@ -298,7 +334,7 @@ module ActiveRecord
298
334
  private
299
335
 
300
336
  def connect
301
- @connection = Net::HTTP.start(@connection_parameters[0], @connection_parameters[1], use_ssl: @connection_parameters[2], verify_mode: OpenSSL::SSL::VERIFY_NONE)
337
+ @connection = @connection_parameters[:connection] || Net::HTTP.start(@connection_parameters[:host], @connection_parameters[:port], use_ssl: @connection_parameters[:ssl], verify_mode: OpenSSL::SSL::VERIFY_NONE)
302
338
  end
303
339
 
304
340
  def apply_replica(table, options)
@@ -27,7 +27,7 @@ module ClickhouseActiverecord
27
27
  unless table_exists?
28
28
  key_options = connection.internal_string_options_for_primary_key
29
29
 
30
- connection.create_table(table_name, id: false, options: 'MergeTree() PARTITION BY toDate(created_at) ORDER BY (created_at)', if_not_exists: true) do |t|
30
+ connection.create_table(table_name, id: false, options: connection.adapter_name.downcase == 'clickhouse' ? 'MergeTree() PARTITION BY toDate(created_at) ORDER BY (created_at)' : '', if_not_exists: true) do |t|
31
31
  t.string :key, key_options
32
32
  t.string :value
33
33
  t.timestamps
@@ -33,16 +33,24 @@ module ClickhouseActiverecord
33
33
  HEADER
34
34
  end
35
35
 
36
+ def tables(stream)
37
+ sorted_tables = @connection.tables.sort {|a,b| @connection.show_create_table(a).match(/^CREATE\s+(MATERIALIZED\s+)?VIEW/) ? 1 : a <=> b }
38
+
39
+ sorted_tables.each do |table_name|
40
+ table(table_name, stream) unless ignored?(table_name)
41
+ end
42
+ end
43
+
36
44
  def table(table, stream)
37
- if table.match(/^\.inner\./).nil?
45
+ if table.match(/^\.inner/).nil?
38
46
  unless simple
39
47
  stream.puts " # TABLE: #{table}"
40
- sql = @connection.do_system_execute("SHOW CREATE TABLE `#{table.gsub(/^\.inner\./, '')}`")['data'].try(:first).try(:first)
48
+ sql = @connection.show_create_table(table)
41
49
  stream.puts " # SQL: #{sql.gsub(/ENGINE = Replicated(.*?)\('[^']+',\s*'[^']+',?\s?([^\)]*)?\)/, "ENGINE = \\1(\\2)")}" if sql
42
50
  # super(table.gsub(/^\.inner\./, ''), stream)
43
51
 
44
52
  # detect view table
45
- match = sql.match(/^CREATE\s+(MATERIALIZED)\s+VIEW/)
53
+ match = sql.match(/^CREATE\s+(MATERIALIZED\s+)?VIEW/)
46
54
  end
47
55
 
48
56
  # Copy from original dumper
@@ -125,5 +133,21 @@ HEADER
125
133
  super
126
134
  end
127
135
  end
136
+
137
+ def schema_limit(column)
138
+ return nil if column.type == :float
139
+ super
140
+ end
141
+
142
+ def schema_unsigned(column)
143
+ return nil unless column.type == :integer && !simple
144
+ (column.sql_type =~ /(Nullable)?\(?UInt\d+\)?/).nil? ? false : nil
145
+ end
146
+
147
+ def prepare_column_options(column)
148
+ spec = {}
149
+ spec[:unsigned] = schema_unsigned(column)
150
+ spec.merge(super).compact
151
+ end
128
152
  end
129
153
  end
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '0.4.7'
2
+ VERSION = '0.4.12'
3
3
  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.7
4
+ version: 0.4.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Odintsov
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-25 00:00:00.000000000 Z
11
+ date: 2021-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -134,7 +134,7 @@ homepage: https://github.com/pnixx/clickhouse-activerecord
134
134
  licenses:
135
135
  - MIT
136
136
  metadata: {}
137
- post_install_message:
137
+ post_install_message:
138
138
  rdoc_options: []
139
139
  require_paths:
140
140
  - lib
@@ -149,8 +149,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
149
  - !ruby/object:Gem::Version
150
150
  version: '0'
151
151
  requirements: []
152
- rubygems_version: 3.0.1
153
- signing_key:
152
+ rubygems_version: 3.0.3
153
+ signing_key:
154
154
  specification_version: 4
155
155
  summary: ClickHouse ActiveRecord
156
156
  test_files: []