clickhouse-activerecord 1.0.13 → 1.1.2

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: 71882da78e208681ad3f28c05bfad0ca4746f8e6b046a4b8cd14c2fa26a007b6
4
- data.tar.gz: b0daa57171963e98a6d0aa4284b7728731009960a07cbdd45a0cc104fcb14213
3
+ metadata.gz: 7c1652d6cf040ebaf43559441ccfa2496f4c408b232ca6d3d1c71da9bbc20209
4
+ data.tar.gz: 900d15344b100536b6f1e0ac1dd3e1df03b890fe5c1b6f7b6f9218283156c17f
5
5
  SHA512:
6
- metadata.gz: aea7e2146f23b6183e4583a1b5cc7e4a38131fabff83574415a1e6c8bd607089305265c53ddb54dcbe2c476144d21b4b97795123cd7bfa038ef7a975de7bde9d
7
- data.tar.gz: 2228a7f2a7e71005f2d2bb910c721373671bdbb7a6ea15e370ecd923a8fb47fa7cebaa548b50ed7ca9bf8d6d6c2ac27a95cca8c680604ba95934866b6488a8b9
6
+ metadata.gz: d9a74ee0ca1e865f821d5c8a1f8612e30dcd790cd79725344ab568b246d30d895711a69f5f05542ff1989f194605a70d7906ec432c90bf2c0688f5a5b120b3ab
7
+ data.tar.gz: ddb82724f85c5a2cfb6da4db250d20f9fc6eb7cddf91a8bada889f07eb717f8657ad011d71e2ab19cb625b83b74dad91a8adeb1bcf94886f33fbd2cd999cc833
@@ -19,8 +19,16 @@ jobs:
19
19
  fail-fast: true
20
20
  max-parallel: 1
21
21
  matrix:
22
- ruby-version: [ '2.7', '3.0', '3.2' ]
23
- clickhouse: [ '22.1' ]
22
+ version:
23
+ - ruby: 2.7
24
+ rails: 7.1.3
25
+ - ruby: 3.0
26
+ rails: 7.1.3
27
+ - ruby: 3.2
28
+ rails: 7.1.3
29
+ - ruby: 3.2
30
+ rails: 7.2.0
31
+ clickhouse: [ '22.1', '24.6' ]
24
32
 
25
33
  steps:
26
34
  - uses: actions/checkout@v4
@@ -33,10 +41,12 @@ jobs:
33
41
  compose-file: '.docker/docker-compose.yml'
34
42
  down-flags: '--volumes'
35
43
 
36
- - name: Set up Ruby ${{ matrix.ruby-version }}
44
+ - run: echo 'gem "activerecord", "~> ${{ matrix.version.rails }}"' >> Gemfile
45
+
46
+ - name: Set up Ruby ${{ matrix.version.ruby }}
37
47
  uses: ruby/setup-ruby@v1
38
48
  with:
39
- ruby-version: ${{ matrix.ruby-version }}
49
+ ruby-version: ${{ matrix.version.ruby }}
40
50
  bundler-cache: true
41
51
 
42
52
  - run: bundle exec rspec spec/single
@@ -54,8 +64,16 @@ jobs:
54
64
  fail-fast: true
55
65
  max-parallel: 1
56
66
  matrix:
57
- ruby-version: [ '2.7', '3.0', '3.2' ]
58
- clickhouse: [ '22.1' ]
67
+ version:
68
+ - ruby: 2.7
69
+ rails: 7.1.3
70
+ - ruby: 3.0
71
+ rails: 7.1.3
72
+ - ruby: 3.2
73
+ rails: 7.1.3
74
+ - ruby: 3.2
75
+ rails: 7.2.0
76
+ clickhouse: [ '22.1', '24.6' ]
59
77
 
60
78
  steps:
61
79
  - uses: actions/checkout@v4
@@ -68,10 +86,12 @@ jobs:
68
86
  compose-file: '.docker/docker-compose.cluster.yml'
69
87
  down-flags: '--volumes'
70
88
 
71
- - name: Set up Ruby ${{ matrix.ruby-version }}
89
+ - run: echo 'gem "activerecord", "~> ${{ matrix.version.rails }}"' >> Gemfile
90
+
91
+ - name: Set up Ruby ${{ matrix.version.ruby }}
72
92
  uses: ruby/setup-ruby@v1
73
93
  with:
74
- ruby-version: ${{ matrix.ruby-version }}
94
+ ruby-version: ${{ matrix.version.ruby }}
75
95
  bundler-cache: true
76
96
 
77
97
  - run: bundle exec rspec spec/cluster
data/.gitignore CHANGED
@@ -54,3 +54,4 @@ crashlytics.properties
54
54
  crashlytics-build.properties
55
55
  fabric.properties
56
56
  .rspec_status
57
+ .tool-versions
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ### Version 1.1.2 (Aug 27, 2024)
2
+ * 🎉 Support for rails 7.2 #156
3
+ * Add method `views` for getting table `View` list in #152
4
+ * Add support for Map datatype in #144
5
+ * Add support window named functions
6
+ * Fix schema dumper default values for number
7
+ * Normalize table name in schema dump in #148
8
+ * Noop savepoint functionality in #150
9
+ * Fix `#find_by` in #153
10
+ * Add RSpec configure
11
+ * Fix detect model primary key
12
+
1
13
  ### Version 1.0.7 (Apr 27, 2024)
2
14
 
3
15
  * Support table indexes
data/README.md CHANGED
@@ -207,6 +207,7 @@ false`. The default integer is `UInt32`
207
207
  | UInt64 | 0 to 18446744073709551615 | 5,6,7,8 |
208
208
  | UInt256 | 0 to ... | 8+ |
209
209
  | Array | ... | ... |
210
+ | Map | ... | ... |
210
211
 
211
212
  Example:
212
213
 
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.require_paths = ['lib']
25
25
 
26
26
  spec.add_runtime_dependency 'bundler', '>= 1.13.4'
27
- spec.add_runtime_dependency 'activerecord', '>= 7.1'
27
+ spec.add_runtime_dependency 'activerecord', '~> 7.1'
28
28
 
29
29
  spec.add_development_dependency 'rake', '~> 13.0'
30
30
  spec.add_development_dependency 'rspec', '~> 3.4'
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Clickhouse
6
+ module OID # :nodoc:
7
+ class Map < Type::Value # :nodoc:
8
+
9
+ def initialize(sql_type)
10
+ @subtype = case sql_type
11
+ when /U?Int\d+/
12
+ :integer
13
+ when /DateTime/
14
+ :datetime
15
+ when /Date/
16
+ :date
17
+ else
18
+ :string
19
+ end
20
+ end
21
+
22
+ def type
23
+ @subtype
24
+ end
25
+
26
+ def deserialize(value)
27
+ if value.is_a?(::Hash)
28
+ value.map { |k, item| [k.to_s, deserialize(item)] }.to_h
29
+ else
30
+ return value if value.nil?
31
+ case @subtype
32
+ when :integer
33
+ value.to_i
34
+ when :datetime
35
+ ::DateTime.parse(value)
36
+ when :date
37
+ ::Date.parse(value)
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+
44
+ def serialize(value)
45
+ if value.is_a?(::Hash)
46
+ value.map { |k, item| [k.to_s, serialize(item)] }.to_h
47
+ else
48
+ return value if value.nil?
49
+ case @subtype
50
+ when :integer
51
+ value.to_i
52
+ when :datetime
53
+ DateTime.new.serialize(value)
54
+ when :date
55
+ Date.new.serialize(value)
56
+ when :string
57
+ value.to_s
58
+ else
59
+ super
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ module Clickhouse
4
+ module Quoting
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods # :nodoc:
8
+ def quote_column_name(name)
9
+ name.to_s.include?('.') ? "`#{name}`" : name.to_s
10
+ end
11
+
12
+ def quote_table_name(name)
13
+ name.to_s
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -33,6 +33,9 @@ module ActiveRecord
33
33
  if options[:array]
34
34
  sql.gsub!(/\s+(.*)/, ' Array(\1)')
35
35
  end
36
+ if options[:map]
37
+ sql.gsub!(/\s+(.*)/, ' Map(String, \1)')
38
+ end
36
39
  sql.gsub!(/(\sString)\(\d+\)/, '\1')
37
40
  sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options)
38
41
  sql
@@ -97,7 +97,7 @@ module ActiveRecord
97
97
  private
98
98
 
99
99
  def valid_column_definition_options
100
- super + [:array, :low_cardinality, :fixed_string, :value, :type]
100
+ super + [:array, :low_cardinality, :fixed_string, :value, :type, :map]
101
101
  end
102
102
  end
103
103
 
@@ -18,9 +18,16 @@ module ActiveRecord
18
18
  true
19
19
  end
20
20
 
21
- def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false)
21
+ def internal_exec_query(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false)
22
22
  result = do_execute(sql, name)
23
- ActiveRecord::Result.new(result['meta'].map { |m| m['name'] }, result['data'], result['meta'].map { |m| [m['name'], type_map.lookup(m['type'])] }.to_h)
23
+ columns = result['meta'].map { |m| m['name'] }
24
+ types = {}
25
+ result['meta'].each_with_index do |m, i|
26
+ # need use column name and index after commit in 7.2:
27
+ # https://github.com/rails/rails/commit/24dbf7637b1d5cd6eb3d7100b8d0f6872c3fee3c
28
+ types[m['name']] = types[i] = type_map.lookup(m['type'])
29
+ end
30
+ ActiveRecord::Result.new(columns, result['data'], types)
24
31
  rescue ActiveRecord::ActiveRecordError => e
25
32
  raise e
26
33
  rescue StandardError => e
@@ -57,6 +64,12 @@ module ActiveRecord
57
64
  result['data'].flatten
58
65
  end
59
66
 
67
+ def views(name = nil)
68
+ result = do_system_execute("SHOW TABLES WHERE engine = 'View'", name)
69
+ return [] if result.nil?
70
+ result['data'].flatten
71
+ end
72
+
60
73
  def functions
61
74
  result = do_system_execute("SELECT name FROM system.functions WHERE origin = 'SQLUserDefined'")
62
75
  return [] if result.nil?
@@ -103,6 +116,20 @@ module ActiveRecord
103
116
  end
104
117
  end
105
118
 
119
+ if ::ActiveRecord::version >= Gem::Version.new('7.2')
120
+ def schema_migration
121
+ pool.schema_migration
122
+ end
123
+
124
+ def migration_context
125
+ pool.migration_context
126
+ end
127
+
128
+ def internal_metadata
129
+ pool.internal_metadata
130
+ end
131
+ end
132
+
106
133
  def assume_migrated_upto_version(version, migrations_paths = nil)
107
134
  version = version.to_i
108
135
  sm_table = quote_table_name(schema_migration.table_name)
@@ -126,7 +153,7 @@ module ActiveRecord
126
153
  # Fix insert_all method
127
154
  # https://github.com/PNixx/clickhouse-activerecord/issues/71#issuecomment-1923244983
128
155
  def with_yaml_fallback(value) # :nodoc:
129
- if value.is_a?(Array)
156
+ if value.is_a?(Array) || value.is_a?(Hash)
130
157
  value
131
158
  else
132
159
  super
@@ -191,9 +218,9 @@ module ActiveRecord
191
218
  def new_column_from_field(table_name, field, _definitions)
192
219
  sql_type = field[1]
193
220
  type_metadata = fetch_type_metadata(sql_type)
194
- default = field[3]
195
- default_value = extract_value_from_default(default)
196
- default_function = extract_default_function(default_value, default)
221
+ default_value = extract_value_from_default(field[3], field[2])
222
+ default_function = extract_default_function(field[3])
223
+ default_value = lookup_cast_type(sql_type).cast(default_value)
197
224
  ClickhouseColumn.new(field[0], default_value, type_metadata, field[1].include?('Nullable'), default_function)
198
225
  end
199
226
 
@@ -212,32 +239,22 @@ module ActiveRecord
212
239
  private
213
240
 
214
241
  # Extracts the value from a PostgreSQL column default definition.
215
- def extract_value_from_default(default)
216
- case default
217
- # Quoted types
218
- when /\Anow\(\)\z/m
219
- nil
220
- # Boolean types
221
- when "true".freeze, "false".freeze
222
- default
223
- # Object identifier types
224
- when "''"
225
- ''
226
- when /\A-?\d+\z/
227
- $1
228
- else
229
- # Anything else is blank, some user type, or some function
230
- # and we can't know the value of that, so return nil.
231
- nil
232
- end
242
+ def extract_value_from_default(default_expression, default_type)
243
+ return nil if default_type != 'DEFAULT' || default_expression.blank?
244
+ return nil if has_default_function?(default_expression)
245
+
246
+ # Convert string
247
+ return $1 if default_expression.match(/^'(.*?)'$/)
248
+
249
+ default_expression
233
250
  end
234
251
 
235
- def extract_default_function(default_value, default) # :nodoc:
236
- default if has_default_function?(default_value, default)
252
+ def extract_default_function(default) # :nodoc:
253
+ default if has_default_function?(default)
237
254
  end
238
255
 
239
- def has_default_function?(default_value, default) # :nodoc:
240
- !default_value && (%r{\w+\(.*\)} === default)
256
+ def has_default_function?(default) # :nodoc:
257
+ (%r{\w+\(.*\)} === default)
241
258
  end
242
259
 
243
260
  def format_body_response(body, format)
@@ -8,7 +8,9 @@ require 'active_record/connection_adapters/clickhouse/oid/array'
8
8
  require 'active_record/connection_adapters/clickhouse/oid/date'
9
9
  require 'active_record/connection_adapters/clickhouse/oid/date_time'
10
10
  require 'active_record/connection_adapters/clickhouse/oid/big_integer'
11
+ require 'active_record/connection_adapters/clickhouse/oid/map'
11
12
  require 'active_record/connection_adapters/clickhouse/oid/uuid'
13
+ require 'active_record/connection_adapters/clickhouse/quoting'
12
14
  require 'active_record/connection_adapters/clickhouse/schema_definitions'
13
15
  require 'active_record/connection_adapters/clickhouse/schema_creation'
14
16
  require 'active_record/connection_adapters/clickhouse/schema_statements'
@@ -22,30 +24,11 @@ module ActiveRecord
22
24
  def clickhouse_connection(config)
23
25
  config = config.symbolize_keys
24
26
 
25
- if config[:connection]
26
- connection = {
27
- connection: config[:connection]
28
- }
29
- else
30
- port = config[:port] || 8123
31
- connection = {
32
- host: config[:host] || 'localhost',
33
- port: port,
34
- ssl: config[:ssl].present? ? config[:ssl] : port == 443,
35
- sslca: config[:sslca],
36
- read_timeout: config[:read_timeout],
37
- write_timeout: config[:write_timeout],
38
- keep_alive_timeout: config[:keep_alive_timeout]
39
- }
40
- end
41
-
42
- if config.key?(:database)
43
- database = config[:database]
44
- else
27
+ unless config.key?(:database)
45
28
  raise ArgumentError, 'No database specified. Missing argument: database.'
46
29
  end
47
30
 
48
- ConnectionAdapters::ClickhouseAdapter.new(logger, connection, config)
31
+ ConnectionAdapters::ClickhouseAdapter.new(config)
49
32
  end
50
33
  end
51
34
  end
@@ -64,7 +47,7 @@ module ActiveRecord
64
47
 
65
48
  module ModelSchema
66
49
  module ClassMethods
67
- delegate :final, :final!, :settings, :settings!, to: :all
50
+ delegate :final, :final!, :settings, :settings!, :window, :window!, to: :all
68
51
 
69
52
  def is_view
70
53
  @is_view || false
@@ -82,11 +65,21 @@ module ActiveRecord
82
65
  end
83
66
 
84
67
  module ConnectionAdapters
85
- class ClickhouseColumn < Column
86
68
 
69
+ if ActiveRecord::version >= Gem::Version.new('7.2')
70
+ register "clickhouse", "ActiveRecord::ConnectionAdapters::ClickhouseAdapter", "active_record/connection_adapters/clickhouse_adapter"
71
+ end
72
+
73
+ class ClickhouseColumn < Column
74
+ private
75
+ def deduplicated
76
+ self
77
+ end
87
78
  end
88
79
 
89
80
  class ClickhouseAdapter < AbstractAdapter
81
+ include Clickhouse::Quoting
82
+
90
83
  ADAPTER_NAME = 'Clickhouse'.freeze
91
84
  NATIVE_DATABASE_TYPES = {
92
85
  string: { name: 'String' },
@@ -121,18 +114,39 @@ module ActiveRecord
121
114
  include Clickhouse::SchemaStatements
122
115
 
123
116
  # Initializes and connects a Clickhouse adapter.
124
- def initialize(logger, connection_parameters, config)
125
- super(nil, logger)
126
- @connection_parameters = connection_parameters
127
- @connection_config = { user: config[:username], password: config[:password], database: config[:database] }.compact
128
- @debug = config[:debug] || false
129
- @config = config
117
+ def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil)
118
+ super
119
+ if @config[:connection]
120
+ connection = {
121
+ connection: @config[:connection]
122
+ }
123
+ else
124
+ port = @config[:port] || 8123
125
+ connection = {
126
+ host: @config[:host] || 'localhost',
127
+ port: port,
128
+ ssl: @config[:ssl].present? ? @config[:ssl] : port == 443,
129
+ sslca: @config[:sslca],
130
+ read_timeout: @config[:read_timeout],
131
+ write_timeout: @config[:write_timeout],
132
+ keep_alive_timeout: @config[:keep_alive_timeout]
133
+ }
134
+ end
135
+ @connection_parameters = connection
136
+
137
+ @connection_config = { user: @config[:username], password: @config[:password], database: @config[:database] }.compact
138
+ @debug = @config[:debug] || false
130
139
 
131
140
  @prepared_statements = false
132
141
 
133
142
  connect
134
143
  end
135
144
 
145
+ # Return ClickHouse server version
146
+ def server_version
147
+ @server_version ||= do_system_execute('SELECT version()')['data'][0][0]
148
+ end
149
+
136
150
  # Savepoints are not supported, noop
137
151
  def create_savepoint(name)
138
152
  end
@@ -221,6 +235,10 @@ module ActiveRecord
221
235
  m.register_type(%r(Array)) do |sql_type|
222
236
  Clickhouse::OID::Array.new(sql_type)
223
237
  end
238
+
239
+ m.register_type(%r(Map)) do |sql_type|
240
+ Clickhouse::OID::Map.new(sql_type)
241
+ end
224
242
  end
225
243
  end
226
244
 
@@ -233,6 +251,8 @@ module ActiveRecord
233
251
  case value
234
252
  when Array
235
253
  '[' + value.map { |v| quote(v) }.join(', ') + ']'
254
+ when Hash
255
+ '{' + value.map { |k, v| "#{quote(k)}: #{quote(v)}" }.join(', ') + '}'
236
256
  else
237
257
  super
238
258
  end
@@ -261,10 +281,15 @@ module ActiveRecord
261
281
 
262
282
  # SCHEMA STATEMENTS ========================================
263
283
 
264
- def primary_key(table_name) #:nodoc:
284
+ def primary_keys(table_name)
285
+ if server_version.to_f >= 23.4
286
+ structure = do_system_execute("SHOW COLUMNS FROM `#{table_name}`")
287
+ return structure['data'].select {|m| m[3]&.include?('PRI') }.pluck(0)
288
+ end
289
+
265
290
  pk = table_structure(table_name).first
266
- return 'id' if pk.present? && pk[0] == 'id'
267
- false
291
+ return ['id'] if pk.present? && pk[0] == 'id'
292
+ []
268
293
  end
269
294
 
270
295
  def create_schema_dumper(options) # :nodoc:
@@ -517,6 +542,10 @@ module ActiveRecord
517
542
  @connection
518
543
  end
519
544
 
545
+ def reconnect
546
+ connect
547
+ end
548
+
520
549
  def apply_replica(table, options)
521
550
  if use_replica? && options[:options]
522
551
  if options[:options].match(/^Replicated/)
@@ -74,6 +74,14 @@ module Arel
74
74
  infix_value o, collector, op
75
75
  end
76
76
 
77
+ def visit_Arel_Nodes_Rows(o, collector)
78
+ if o.expr.is_a?(String)
79
+ collector << "ROWS #{o.expr}"
80
+ else
81
+ super
82
+ end
83
+ end
84
+
77
85
  def sanitize_as_setting_value(value)
78
86
  if value == :default
79
87
  'DEFAULT'
@@ -2,6 +2,12 @@
2
2
 
3
3
  module ClickhouseActiverecord
4
4
  class Schema < ::ActiveRecord::Schema
5
-
5
+ def define(...)
6
+ ActiveRecord.deprecator.warn(<<~MSG)
7
+ ClickhouseActiverecord::Schema is deprecated
8
+ and will be removed in 1.2 version. Use ActiveRecord::Schema instead.
9
+ MSG
10
+ super
11
+ end
6
12
  end
7
13
  end
@@ -14,25 +14,6 @@ module ClickhouseActiverecord
14
14
 
15
15
  private
16
16
 
17
- def header(stream)
18
- stream.puts <<HEADER
19
- # This file is auto-generated from the current state of the database. Instead
20
- # of editing this file, please use the migrations feature of Active Record to
21
- # incrementally modify your database, and then regenerate this schema definition.
22
- #
23
- # This file is the source Rails uses to define your schema when running `rails
24
- # #{simple ? 'db' : 'clickhouse'}:schema:load`. When creating a new database, `rails #{simple ? 'db' : 'clickhouse'}:schema:load` tends to
25
- # be faster and is potentially less error prone than running all of your
26
- # migrations from scratch. Old migrations may fail to apply correctly if those
27
- # migrations use external dependencies or application code.
28
- #
29
- # It's strongly recommended that you check this file into your version control system.
30
-
31
- #{simple ? 'ActiveRecord' : 'ClickhouseActiverecord'}::Schema.define(#{define_params}) do
32
-
33
- HEADER
34
- end
35
-
36
17
  def tables(stream)
37
18
  functions = @connection.functions
38
19
  functions.each do |function|
@@ -168,6 +149,10 @@ HEADER
168
149
  (column.sql_type =~ /Array?\(/).nil? ? nil : true
169
150
  end
170
151
 
152
+ def schema_map(column)
153
+ (column.sql_type =~ /Map?\(/).nil? ? nil : true
154
+ end
155
+
171
156
  def schema_low_cardinality(column)
172
157
  (column.sql_type =~ /LowCardinality?\(/).nil? ? nil : true
173
158
  end
@@ -176,6 +161,7 @@ HEADER
176
161
  spec = {}
177
162
  spec[:unsigned] = schema_unsigned(column)
178
163
  spec[:array] = schema_array(column)
164
+ spec[:map] = schema_map(column)
179
165
  spec[:low_cardinality] = schema_low_cardinality(column)
180
166
  spec.merge(super).compact
181
167
  end
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '1.0.13'
2
+ VERSION = '1.1.2'
3
3
  end
@@ -4,7 +4,7 @@ module CoreExtensions
4
4
 
5
5
  def create_table
6
6
  return super unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
7
- return if table_exists? || !enabled?
7
+ return if !enabled? || table_exists?
8
8
 
9
9
  key_options = connection.internal_string_options_for_primary_key
10
10
  table_options = {
@@ -31,14 +31,23 @@ module CoreExtensions
31
31
 
32
32
  private
33
33
 
34
- def update_entry(key, new_value)
35
- return super unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
36
-
37
- create_entry(key, new_value)
34
+ def update_entry(connection_or_key, key_or_new_value, new_value = nil)
35
+ if ::ActiveRecord::version >= Gem::Version.new('7.2')
36
+ return super unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
37
+ create_entry(connection_or_key, key_or_new_value, new_value)
38
+ else
39
+ return super(connection_or_key, key_or_new_value) unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
40
+ create_entry(connection_or_key, key_or_new_value)
41
+ end
38
42
  end
39
43
 
40
- def select_entry(key)
41
- return super unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
44
+ def select_entry(connection_or_key, key = nil)
45
+ if ::ActiveRecord::version >= Gem::Version.new('7.2')
46
+ return super unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
47
+ else
48
+ key = connection_or_key
49
+ return super(key) unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
50
+ end
42
51
 
43
52
  sm = ::Arel::SelectManager.new(arel_table)
44
53
  sm.final! if connection.table_options(table_name)[:options] =~ /^ReplacingMergeTree/
@@ -49,6 +58,14 @@ module CoreExtensions
49
58
 
50
59
  connection.select_one(sm, "#{self.class} Load")
51
60
  end
61
+
62
+ def connection
63
+ if ::ActiveRecord::version >= Gem::Version.new('7.2')
64
+ @pool.lease_connection
65
+ else
66
+ super
67
+ end
68
+ end
52
69
  end
53
70
  end
54
71
  end
@@ -25,7 +25,6 @@ module CoreExtensions
25
25
 
26
26
  # @param [Hash] opts
27
27
  def settings!(**opts)
28
- assert_mutability!
29
28
  check_command('SETTINGS')
30
29
  @values[:settings] = (@values[:settings] || {}).merge opts
31
30
  self
@@ -43,7 +42,6 @@ module CoreExtensions
43
42
  end
44
43
 
45
44
  def final!
46
- assert_mutability!
47
45
  check_command('FINAL')
48
46
  @values[:final] = true
49
47
  self
@@ -62,23 +60,44 @@ module CoreExtensions
62
60
 
63
61
  # @param [Array] opts
64
62
  def using!(*opts)
65
- assert_mutability!
66
63
  @values[:using] = opts
67
64
  self
68
65
  end
69
66
 
67
+ # Windows functions let you perform calculations across a set of rows that are related to the current row. For example:
68
+ #
69
+ # users = User.window('x', order: 'date', partition: 'name', rows: 'UNBOUNDED PRECEDING').select('sum(value) OVER x')
70
+ # # SELECT sum(value) OVER x FROM users WINDOW x AS (PARTITION BY name ORDER BY date ROWS UNBOUNDED PRECEDING)
71
+ #
72
+ # @param [String] name
73
+ # @param [Hash] opts
74
+ def window(name, **opts)
75
+ spawn.window!(name, **opts)
76
+ end
77
+
78
+ def window!(name, **opts)
79
+ @values[:windows] = [] unless @values[:windows]
80
+ @values[:windows] << [name, opts]
81
+ self
82
+ end
83
+
70
84
  private
71
85
 
72
86
  def check_command(cmd)
73
87
  raise ::ActiveRecord::ActiveRecordError, cmd + ' is a ClickHouse specific query clause' unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
74
88
  end
75
89
 
76
- def build_arel(aliases = nil)
77
- arel = super
90
+ def build_arel(connection_or_aliases = nil, aliases = nil)
91
+ if ::ActiveRecord::version >= Gem::Version.new('7.2')
92
+ arel = super
93
+ else
94
+ arel = super(connection_or_aliases)
95
+ end
78
96
 
79
97
  arel.final! if @values[:final].present?
80
98
  arel.settings(@values[:settings]) if @values[:settings].present?
81
99
  arel.using(@values[:using]) if @values[:using].present?
100
+ arel.windows(@values[:windows]) if @values[:windows].present?
82
101
 
83
102
  arel
84
103
  end
@@ -47,6 +47,14 @@ module CoreExtensions
47
47
 
48
48
  connection.select_values(sm, "#{self.class} Load")
49
49
  end
50
+
51
+ def connection
52
+ if ::ActiveRecord::version >= Gem::Version.new('7.2')
53
+ @pool.lease_connection
54
+ else
55
+ super
56
+ end
57
+ end
50
58
  end
51
59
  end
52
60
  end
@@ -13,6 +13,18 @@ module CoreExtensions
13
13
  self
14
14
  end
15
15
 
16
+ # @param [Array] windows
17
+ def windows(windows)
18
+ @ctx.windows = windows.map do |name, opts|
19
+ # https://github.com/rails/rails/blob/main/activerecord/test/cases/arel/select_manager_test.rb#L790
20
+ window = ::Arel::Nodes::NamedWindow.new(name)
21
+ opts.each do |key, value|
22
+ window.send(key, value)
23
+ end
24
+ window
25
+ end
26
+ end
27
+
16
28
  def using(*exprs)
17
29
  @ctx.source.right.last.right = ::Arel::Nodes::Using.new(::Arel.sql(exprs.join(',')))
18
30
  self
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: 1.0.13
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Odintsov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-01 00:00:00.000000000 Z
11
+ date: 2024-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -28,14 +28,14 @@ dependencies:
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '7.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '7.1'
41
41
  - !ruby/object:Gem::Dependency
@@ -110,7 +110,9 @@ files:
110
110
  - lib/active_record/connection_adapters/clickhouse/oid/big_integer.rb
111
111
  - lib/active_record/connection_adapters/clickhouse/oid/date.rb
112
112
  - lib/active_record/connection_adapters/clickhouse/oid/date_time.rb
113
+ - lib/active_record/connection_adapters/clickhouse/oid/map.rb
113
114
  - lib/active_record/connection_adapters/clickhouse/oid/uuid.rb
115
+ - lib/active_record/connection_adapters/clickhouse/quoting.rb
114
116
  - lib/active_record/connection_adapters/clickhouse/schema_creation.rb
115
117
  - lib/active_record/connection_adapters/clickhouse/schema_definitions.rb
116
118
  - lib/active_record/connection_adapters/clickhouse/schema_statements.rb