clickhouse-activerecord 0.4.8 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: []