clickhouse-activerecord 1.0.13 → 1.1.2

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: 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