clickhouse-activerecord 1.5.1 → 1.6.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: 313cad6f9f4c4887d92e8bc07b3d67c9b5e5ed2e404f3f46d0eb56dc6ff238a5
4
- data.tar.gz: f63f1db5435d34e0fce894eedf473dae91a65936b5db8dcea41355636d13c206
3
+ metadata.gz: b247a8916fb85dbbc5392da80096fca8d2bea2f822d3e60ed96dced9395d9ebd
4
+ data.tar.gz: dd1ed15c3367463fdc870f5ead578a8af775b9423fe0b851a2eb5be7782e65ba
5
5
  SHA512:
6
- metadata.gz: b82c4e53376baafb4c5258ae57ecc31556e72ffef45fd4ad2f4194a170ea3e48068268b09bd56c6e462ac070e0635d2e8c30af4e6cfd66ed81bd23879a428956
7
- data.tar.gz: e20b6246ce0ef5f967289c832dc96649b6aa733dbb3ed27f746f9c01baa29a4dfc55bc50c527a84a3f7b96b53fb4dc4cec2f2908e70586d7d8030e97633551ae
6
+ metadata.gz: eddec177c686f387fc062689e472a90310a2b8a45f7e20ab416ab865cb39c8f6d52ef5f35433d435372c917ba5bbf211c9a60e00075b50ad6e7cfd7f70322291
7
+ data.tar.gz: acacc953e84579b76da29a1cbf29337de6bd33c72857abdc65484222f0a485b62bc5a9e2305c4c999cd4966ccc99d0c16cbf0d92434ff881d4ce572b10970f46
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### Version 1.6.0 (Jan 19, 2026)
2
+
3
+ * Support CSE (Common Scalar Expressions) in the `WITH` clause
4
+ * Fix regex to match FROM keyword, not column names containing 'from' #220
5
+ * Add JSON column type support #209
6
+ * Support execute_batch #216
7
+ * Add disconnect method to adapter #186
8
+ * Add check_current_protected_environment! to Tasks #222
9
+ * Do not truncate engines that cannot be truncated #226
10
+
1
11
  ### Version 1.5.1 (Nov 6, 2025)
2
12
 
3
13
  * Fix rake tasks
data/README.md CHANGED
@@ -158,10 +158,18 @@ Structure load from `db/clickhouse_structure.sql` file:
158
158
 
159
159
  For auto truncate tables before each test add to `spec/rails_helper.rb` file:
160
160
 
161
- ```
161
+ ```ruby
162
162
  require 'clickhouse-activerecord/rspec'
163
163
  ```
164
-
164
+
165
+ ### Minitest
166
+
167
+ For auto truncate tables before each test add to `test/test_helper.rb` file:
168
+
169
+ ```ruby
170
+ require 'clickhouse-activerecord/minitest'
171
+ ```
172
+
165
173
  ### Insert and select data
166
174
 
167
175
  ```ruby
@@ -79,7 +79,9 @@ module ActiveRecord
79
79
  # If you do not specify a database explicitly, ClickHouse will use the "default" database.
80
80
  return unless subquery
81
81
 
82
- match = subquery.match(/(?<=from)[^.\w]+(?<database>\w+(?=\.))?(?<table_name>[.\w]+)/i)
82
+ # Match FROM as a keyword (with word boundary), not as part of a column name
83
+ # \b ensures we only match 'from' as a whole word
84
+ match = subquery.match(/\bfrom\s+(?<database>\w+(?=\.))?(?<table_name>[.\w]+)/i)
83
85
  return unless match
84
86
  return if match[:database]
85
87
 
@@ -47,6 +47,12 @@ module ActiveRecord
47
47
  end
48
48
  end
49
49
 
50
+ def execute_batch(statements, name = nil, **kwargs)
51
+ statements.each do |statement|
52
+ execute(statement, name, **kwargs)
53
+ end
54
+ end
55
+
50
56
  def exec_insert(sql, name = nil, _binds = [], _pk = nil, _sequence_name = nil, returning: nil)
51
57
  new_sql = sql.sub(/ (DEFAULT )?VALUES/, " VALUES")
52
58
  with_response_format(nil) { execute(new_sql, name) }
@@ -293,6 +299,38 @@ module ActiveRecord
293
299
  .except(*except)
294
300
  .to_param
295
301
  end
302
+
303
+ # Returns a hash of table names to their engine types
304
+ def table_engines(table_names = nil)
305
+ table_names_sql = if table_names.present?
306
+ "AND name IN (#{table_names.map { |name| "'#{name}'" }.join(', ')})"
307
+ end
308
+
309
+ sql = <<~SQL
310
+ SELECT name, engine FROM system.tables
311
+ WHERE database = currentDatabase()
312
+ #{table_names_sql}
313
+ SQL
314
+
315
+ result = do_system_execute(sql)
316
+ return {} if result.nil?
317
+
318
+ result['data'].to_h { |row| [row[0], row[1]] }
319
+ end
320
+
321
+ # @see https://clickhouse.com/docs/sql-reference/statements/truncate
322
+ # Additionally add 'Dictionary' because it is returned from 'show tables'.
323
+ TRUNCATE_UNSUPPORTED_ENGINES = %w[View File URL Buffer Null Dictionary].freeze
324
+
325
+ def build_truncate_statements(table_names)
326
+ engines = table_engines(table_names)
327
+ tables_to_truncate = table_names.select do |table_name|
328
+ engine = engines[table_name]
329
+ engine.nil? || !TRUNCATE_UNSUPPORTED_ENGINES.include?(engine)
330
+ end
331
+
332
+ super(tables_to_truncate)
333
+ end
296
334
  end
297
335
  end
298
336
  end
@@ -113,6 +113,8 @@ module ActiveRecord
113
113
  uint64: { name: 'UInt64' },
114
114
  # uint128: { name: 'UInt128' }, not yet implemented in clickhouse
115
115
  uint256: { name: 'UInt256' },
116
+
117
+ json: { name: 'JSON' },
116
118
  }.freeze
117
119
 
118
120
  include Clickhouse::SchemaStatements
@@ -147,6 +149,11 @@ module ActiveRecord
147
149
  connect
148
150
  end
149
151
 
152
+ def disconnect!
153
+ @connection.finish if @connection&.started?
154
+ super
155
+ end
156
+
150
157
  # Return ClickHouse server version
151
158
  def server_version
152
159
  @server_version ||= select_value('SELECT version()')
@@ -246,6 +253,8 @@ module ActiveRecord
246
253
  m.register_type(%r(Map)) do |sql_type|
247
254
  Clickhouse::OID::Map.new(sql_type)
248
255
  end
256
+
257
+ m.register_type %r(JSON)i, ActiveRecord::Type::Json.new
249
258
  end
250
259
  end
251
260
 
@@ -18,6 +18,33 @@ module Arel
18
18
  end
19
19
  end
20
20
 
21
+ # https://clickhouse.com/docs/sql-reference/statements/select/with
22
+ def visit_Arel_Nodes_Cte(o, collector)
23
+ is_cse = o.relation.is_a?(Symbol)
24
+
25
+ if is_cse && o.name.is_a?(String)
26
+ collector << quote(o.name)
27
+ elsif is_cse && o.name.is_a?(ActiveRecord::Relation)
28
+ visit o.name.arel, collector
29
+ else
30
+ collector << quote_table_name(o.name)
31
+ end
32
+ collector << " AS "
33
+
34
+ case o.materialized
35
+ when true
36
+ collector << "MATERIALIZED "
37
+ when false
38
+ collector << "NOT MATERIALIZED "
39
+ end
40
+
41
+ if is_cse
42
+ collector << o.relation.to_s
43
+ else
44
+ visit o.relation, collector
45
+ end
46
+ end
47
+
21
48
  # https://clickhouse.com/docs/en/sql-reference/statements/delete
22
49
  # DELETE and UPDATE in ClickHouse working only without table name
23
50
  def visit_Arel_Attributes_Attribute(o, collector)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClickhouseActiverecord
4
+ module TestHelper
5
+ def before_setup
6
+ super
7
+ ActiveRecord::Base.configurations.configurations.select { |x| x.env_name == Rails.env && x.adapter == 'clickhouse' }.each do |config|
8
+ ActiveRecord::Base.establish_connection(config)
9
+ ActiveRecord::Base.connection.truncate_tables(*ActiveRecord::Base.connection.tables)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ ActiveSupport::TestCase.include(ClickhouseActiverecord::TestHelper) if defined?(ActiveSupport::TestCase)
@@ -4,9 +4,7 @@ RSpec.configure do |config|
4
4
  config.before do
5
5
  ActiveRecord::Base.configurations.configurations.select { |x| x.env_name == Rails.env && x.adapter == 'clickhouse' }.each do |config|
6
6
  ActiveRecord::Base.establish_connection(config)
7
- ActiveRecord::Base.connection.tables.each do |table|
8
- ActiveRecord::Base.connection.execute("TRUNCATE TABLE #{table}")
9
- end
7
+ ActiveRecord::Base.connection.truncate_tables(*ActiveRecord::Base.connection.tables)
10
8
  end
11
9
  end
12
10
  end
@@ -88,8 +88,34 @@ module ClickhouseActiverecord
88
88
  ActiveRecord::Migration.verbose = verbose_was
89
89
  end
90
90
 
91
+ def check_current_protected_environment!(db_config, migration_class = ActiveRecord::Migration)
92
+ with_temporary_pool(db_config, migration_class) do |pool|
93
+ migration_context = pool.migration_context
94
+ current = migration_context.current_environment
95
+ stored = migration_context.last_stored_environment
96
+
97
+ if migration_context.protected_environment?
98
+ raise ActiveRecord::ProtectedEnvironmentError.new(stored)
99
+ end
100
+
101
+ if stored && stored != current
102
+ raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
103
+ end
104
+ rescue ActiveRecord::NoDatabaseError
105
+ end
106
+ end
107
+
91
108
  private
92
109
 
110
+ def with_temporary_pool(db_config, migration_class, clobber: false)
111
+ original_db_config = migration_class.connection_db_config
112
+ pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
113
+
114
+ yield pool
115
+ ensure
116
+ migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber)
117
+ end
118
+
93
119
  def establish_master_connection
94
120
  establish_connection @configuration
95
121
  end
@@ -1,3 +1,3 @@
1
1
  module ClickhouseActiverecord
2
- VERSION = '1.5.1'
2
+ VERSION = '1.6.0'
3
3
  end
@@ -192,6 +192,32 @@ module CoreExtensions
192
192
 
193
193
  arel
194
194
  end
195
+
196
+ def build_with_value_from_hash(hash)
197
+ return super if ::ActiveRecord::version >= Gem::Version.new('7.2')
198
+
199
+ # Redefine for ActiveRecord < 7.2
200
+ hash.map do |name, value|
201
+ expression =
202
+ case value
203
+ when ::Arel::Nodes::SqlLiteral then ::Arel::Nodes::Grouping.new(value)
204
+ when ::ActiveRecord::Relation then value.arel
205
+ when ::Arel::SelectManager then value
206
+ when Symbol then value
207
+ else
208
+ raise ArgumentError, "Unsupported argument type: `#{value}` #{value.class}"
209
+ end
210
+ ::Arel::Nodes::TableAlias.new(expression, name)
211
+ end
212
+ end
213
+
214
+ def build_with_expression_from_value(value, nested = false)
215
+ case value
216
+ when Symbol then value
217
+ else
218
+ super
219
+ end
220
+ end
195
221
  end
196
222
  end
197
223
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clickhouse-activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Odintsov
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-01-19 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: bundler
@@ -133,6 +134,7 @@ files:
133
134
  - lib/arel/nodes/using.rb
134
135
  - lib/arel/visitors/clickhouse.rb
135
136
  - lib/clickhouse-activerecord.rb
137
+ - lib/clickhouse-activerecord/minitest.rb
136
138
  - lib/clickhouse-activerecord/railtie.rb
137
139
  - lib/clickhouse-activerecord/rspec.rb
138
140
  - lib/clickhouse-activerecord/schema.rb
@@ -152,6 +154,7 @@ homepage: https://github.com/pnixx/clickhouse-activerecord
152
154
  licenses:
153
155
  - MIT
154
156
  metadata: {}
157
+ post_install_message:
155
158
  rdoc_options: []
156
159
  require_paths:
157
160
  - lib
@@ -166,7 +169,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
169
  - !ruby/object:Gem::Version
167
170
  version: '0'
168
171
  requirements: []
169
- rubygems_version: 3.6.9
172
+ rubygems_version: 3.1.6
173
+ signing_key:
170
174
  specification_version: 4
171
175
  summary: ClickHouse ActiveRecord
172
176
  test_files: []