clickhouse-activerecord 0.4.8 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd2cc6144472b64d9ffe1c8b61bc86e75cc74863566aa65ff9f86ffd526d9ff4
4
- data.tar.gz: 34e95e5728a4d90c69e7f604eac303174a0c873cae7e503f7edcca36e8ab9c5f
3
+ metadata.gz: 1e17583f619debbb02952aa4299fedebbdd0de5ff6ae5c4a604071e42381c33a
4
+ data.tar.gz: d9d642a756c2ff7791081f1298917d96758dd5de4f07781dc7c9e587100536b4
5
5
  SHA512:
6
- metadata.gz: 3638e95511941d90a06a9df8c2d640ef68742fb54474ddf289d1d136a077bae0e1510cfd9752029d6654f4e2777b865ea5ed0d6703fdb31f64ab23fca7a500b1
7
- data.tar.gz: 650b30cfc25416e6d0cce0fe4b48146c55a256e7a9a0df61b2addaf55ca5e7533f0e8facc5599030ddaaa93bc5a07cea498390d9ad750ba77650fec914a5f476
6
+ metadata.gz: cca1c4ce673a6622855a860e51b85952b0b09e3cbaaafe6b817a5c95ff1bb90d33da4974908acecaed6a658f7574fbaf9f71c1497d1c527006eb36bc3aaefab3
7
+ data.tar.gz: 344cc941b595fe40179c8974dfc371dd3732cbf8994999d75976506582d23abbda39340e7b2232cc7b7c25f1c6271d3f26325c409cfd7aa21f1acfc017fb8d32
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
@@ -1,9 +1,16 @@
1
1
  # frozen_string_literal: true
2
+ begin
3
+ require "active_record/connection_adapters/deduplicable"
4
+ rescue LoadError => e
5
+ # Rails < 6.1 does not have this file in this location, ignore
6
+ end
7
+
8
+ require "active_record/connection_adapters/abstract/schema_creation"
2
9
 
3
10
  module ActiveRecord
4
11
  module ConnectionAdapters
5
12
  module Clickhouse
6
- class SchemaCreation < AbstractAdapter::SchemaCreation# :nodoc:
13
+ class SchemaCreation < ConnectionAdapters::SchemaCreation# :nodoc:
7
14
 
8
15
  def visit_AddColumnDefinition(o)
9
16
  sql = +"ADD COLUMN #{accept(o.column)}"
@@ -21,8 +28,14 @@ module ActiveRecord
21
28
  end
22
29
 
23
30
  def add_table_options!(create_sql, options)
24
- if options[:options].present?
25
- create_sql << " ENGINE = #{options[:options]}"
31
+ opts = options[:options]
32
+ if options.respond_to?(:options)
33
+ # rails 6.1
34
+ opts ||= options.options
35
+ end
36
+
37
+ if opts.present?
38
+ create_sql << " ENGINE = #{opts}"
26
39
  else
27
40
  create_sql << " ENGINE = Log()"
28
41
  end
@@ -37,9 +50,9 @@ module ActiveRecord
37
50
 
38
51
  statements = o.columns.map { |c| accept c }
39
52
  statements << accept(o.primary_keys) if o.primary_keys
40
-
41
53
  create_sql << "(#{statements.join(', ')})" if statements.present?
42
- add_table_options!(create_sql, table_options(o))
54
+ # Attach options for only table or materialized view
55
+ add_table_options!(create_sql, o) if !o.view || o.view && o.materialized
43
56
  create_sql << " AS #{to_sql(o.as)}" if o.as
44
57
  create_sql
45
58
  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 = [])
@@ -37,7 +39,7 @@ module ActiveRecord
37
39
 
38
40
  def table_options(table)
39
41
  sql = show_create_table(table)
40
- { options: sql.gsub(/^(?:.*?)ENGINE = (.*?)$/, '\\1') }
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
@@ -114,8 +116,8 @@ module ActiveRecord
114
116
  Clickhouse::SchemaCreation.new(self)
115
117
  end
116
118
 
117
- def create_table_definition(*args)
118
- Clickhouse::TableDefinition.new(self, *args)
119
+ def create_table_definition(table_name, options)
120
+ Clickhouse::TableDefinition.new(self, table_name, **options)
119
121
  end
120
122
 
121
123
  def new_column_from_field(table_name, field)
@@ -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
@@ -50,7 +59,11 @@ module ActiveRecord
50
59
  module TypeCaster
51
60
  class Map
52
61
  def is_view
53
- types.is_view
62
+ if @klass.respond_to?(:is_view)
63
+ @klass.is_view # rails 6.1
64
+ else
65
+ types.is_view # less than 6.1
66
+ end
54
67
  end
55
68
  end
56
69
  end
@@ -79,7 +92,6 @@ module ActiveRecord
79
92
 
80
93
  class ClickhouseAdapter < AbstractAdapter
81
94
  ADAPTER_NAME = 'Clickhouse'.freeze
82
-
83
95
  NATIVE_DATABASE_TYPES = {
84
96
  string: { name: 'String' },
85
97
  integer: { name: 'UInt32' },
@@ -88,7 +100,21 @@ module ActiveRecord
88
100
  decimal: { name: 'Decimal' },
89
101
  datetime: { name: 'DateTime' },
90
102
  date: { name: 'Date' },
91
- boolean: { name: 'UInt8' }
103
+ boolean: { name: 'UInt8' },
104
+
105
+ int8: { name: 'Int8' },
106
+ int16: { name: 'Int16' },
107
+ int32: { name: 'Int32' },
108
+ int64: { name: 'Int64' },
109
+ int128: { name: 'Int128' },
110
+ int256: { name: 'Int256' },
111
+
112
+ uint8: { name: 'UInt8' },
113
+ uint16: { name: 'UInt16' },
114
+ uint32: { name: 'UInt32' },
115
+ uint64: { name: 'UInt64' },
116
+ # uint128: { name: 'UInt128' }, not yet implemented in clickhouse
117
+ uint256: { name: 'UInt256' },
92
118
  }.freeze
93
119
 
94
120
  include Clickhouse::SchemaStatements
@@ -139,10 +165,12 @@ module ActiveRecord
139
165
  when /(Nullable)?\(?String\)?/
140
166
  super('String')
141
167
  when /(Nullable)?\(?U?Int8\)?/
142
- super('int2')
143
- when /(Nullable)?\(?U?Int(16|32)\)?/
144
- super('int4')
145
- when /(Nullable)?\(?U?Int(64)\)?/
168
+ 1
169
+ when /(Nullable)?\(?U?Int16\)?/
170
+ 2
171
+ when /(Nullable)?\(?U?Int32\)?/
172
+ nil
173
+ when /(Nullable)?\(?U?Int64\)?/
146
174
  8
147
175
  else
148
176
  super
@@ -154,14 +182,20 @@ module ActiveRecord
154
182
  register_class_with_limit m, %r(String), Type::String
155
183
  register_class_with_limit m, 'Date', Clickhouse::OID::Date
156
184
  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
185
+
161
186
  register_class_with_limit m, %r(Int8), Type::Integer
162
- m.alias_type 'Int16', 'Int8'
163
- m.alias_type 'Int32', 'Int8'
187
+ register_class_with_limit m, %r(Int16), Type::Integer
188
+ register_class_with_limit m, %r(Int32), Type::Integer
164
189
  register_class_with_limit m, %r(Int64), Type::Integer
190
+ register_class_with_limit m, %r(Int128), Type::Integer
191
+ register_class_with_limit m, %r(Int256), Type::Integer
192
+
193
+ register_class_with_limit m, %r(Uint8), Type::UnsignedInteger
194
+ register_class_with_limit m, %r(UInt16), Type::UnsignedInteger
195
+ register_class_with_limit m, %r(UInt32), Type::UnsignedInteger
196
+ register_class_with_limit m, %r(UInt64), Type::UnsignedInteger
197
+ #register_class_with_limit m, %r(UInt128), Type::UnsignedInteger #not implemnted in clickhouse
198
+ register_class_with_limit m, %r(UInt256), Type::UnsignedInteger
165
199
  end
166
200
 
167
201
  # Quoting time without microseconds
@@ -304,7 +338,7 @@ module ActiveRecord
304
338
  private
305
339
 
306
340
  def connect
307
- @connection = Net::HTTP.start(@connection_parameters[0], @connection_parameters[1], use_ssl: @connection_parameters[2], verify_mode: OpenSSL::SSL::VERIFY_NONE)
341
+ @connection = @connection_parameters[:connection] || Net::HTTP.start(@connection_parameters[:host], @connection_parameters[:port], use_ssl: @connection_parameters[:ssl], verify_mode: OpenSSL::SSL::VERIFY_NONE)
308
342
  end
309
343
 
310
344
  def apply_replica(table, options)
@@ -1,3 +1,5 @@
1
+ require 'active_record/migration'
2
+
1
3
  module ClickhouseActiverecord
2
4
 
3
5
  class SchemaMigration < ::ActiveRecord::SchemaMigration
@@ -8,7 +10,7 @@ module ClickhouseActiverecord
8
10
  version_options = connection.internal_string_options_for_primary_key
9
11
 
10
12
  connection.create_table(table_name, id: false, options: 'ReplacingMergeTree(ver) PARTITION BY version ORDER BY (version)', if_not_exists: true) do |t|
11
- t.string :version, version_options
13
+ t.string :version, **version_options
12
14
  t.column :active, 'Int8', null: false, default: '1'
13
15
  t.datetime :ver, null: false, default: -> { 'now()' }
14
16
  end
@@ -27,7 +29,7 @@ module ClickhouseActiverecord
27
29
  unless table_exists?
28
30
  key_options = connection.internal_string_options_for_primary_key
29
31
 
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|
32
+ 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
33
  t.string :key, key_options
32
34
  t.string :value
33
35
  t.timestamps
@@ -34,7 +34,7 @@ HEADER
34
34
  end
35
35
 
36
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 }
37
+ sorted_tables = @connection.tables.sort {|a,b| @connection.show_create_table(a).match(/^CREATE\s+(MATERIALIZED\s+)?VIEW/) ? 1 : a <=> b }
38
38
 
39
39
  sorted_tables.each do |table_name|
40
40
  table(table_name, stream) unless ignored?(table_name)
@@ -50,7 +50,7 @@ HEADER
50
50
  # super(table.gsub(/^\.inner\./, ''), stream)
51
51
 
52
52
  # detect view table
53
- match = sql.match(/^CREATE\s+(MATERIALIZED)\s+VIEW/)
53
+ match = sql.match(/^CREATE\s+(MATERIALIZED\s+)?VIEW/)
54
54
  end
55
55
 
56
56
  # Copy from original dumper
@@ -133,5 +133,21 @@ HEADER
133
133
  super
134
134
  end
135
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
136
152
  end
137
153
  end
@@ -6,7 +6,7 @@ module ClickhouseActiverecord
6
6
  delegate :connection, :establish_connection, :clear_active_connections!, to: ActiveRecord::Base
7
7
 
8
8
  def initialize(configuration)
9
- @configuration = configuration
9
+ @configuration = configuration.with_indifferent_access
10
10
  end
11
11
 
12
12
  def create
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '0.4.8'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -13,7 +13,7 @@ class ClickhouseMigrationGenerator < ActiveRecord::Generators::MigrationGenerato
13
13
 
14
14
  def db_migrate_path
15
15
  if defined?(Rails.application) && Rails.application && respond_to?(:configured_migrate_path, true)
16
- configured_migrate_path
16
+ configured_migrate_path || default_migrate_path
17
17
  else
18
18
  default_migrate_path
19
19
  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.8
4
+ version: 0.5.0
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-12-14 00:00:00.000000000 Z
11
+ date: 2021-06-10 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: []