clickhouse-activerecord 1.3.1 → 1.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 +4 -4
- data/.docker/clickhouse/cluster/server1_config.xml +10 -2
- data/.docker/clickhouse/cluster/server2_config.xml +10 -2
- data/.docker/clickhouse/single/config.xml +9 -2
- data/.docker/clickhouse/users.xml +10 -1
- data/.docker/docker-compose.cluster.yml +2 -2
- data/.docker/docker-compose.yml +1 -1
- data/.github/workflows/testing.yml +12 -8
- data/CHANGELOG.md +51 -1
- data/README.md +5 -2
- data/lib/active_record/connection_adapters/clickhouse/column.rb +1 -1
- data/lib/active_record/connection_adapters/clickhouse/schema_creation.rb +6 -1
- data/lib/active_record/connection_adapters/clickhouse/schema_statements.rb +105 -118
- data/lib/active_record/connection_adapters/clickhouse/statement/format_manager.rb +46 -0
- data/lib/active_record/connection_adapters/clickhouse/statement/response_processor.rb +99 -0
- data/lib/active_record/connection_adapters/clickhouse/statement.rb +30 -0
- data/lib/active_record/connection_adapters/clickhouse_adapter.rb +33 -25
- data/lib/arel/visitors/clickhouse.rb +7 -1
- data/lib/clickhouse-activerecord/schema_dumper.rb +3 -1
- data/lib/clickhouse-activerecord/tasks.rb +2 -3
- data/lib/clickhouse-activerecord/version.rb +1 -1
- data/lib/core_extensions/active_record/relation.rb +56 -11
- data/lib/core_extensions/arel/nodes/select_core.rb +7 -0
- data/lib/core_extensions/arel/nodes/select_statement.rb +4 -0
- metadata +6 -7
- data/lib/tasks/clickhouse.rake +0 -90
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module Clickhouse
|
|
6
|
+
class Statement
|
|
7
|
+
class FormatManager
|
|
8
|
+
|
|
9
|
+
def initialize(sql, format:)
|
|
10
|
+
@sql = sql.strip
|
|
11
|
+
@format = format
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def apply
|
|
15
|
+
return @sql if skip_format? || @format.blank?
|
|
16
|
+
|
|
17
|
+
"#{@sql} FORMAT #{@format}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def skip_format?
|
|
21
|
+
system_command? || schema_command? || format_specified? || delete?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def system_command?
|
|
27
|
+
/\Asystem|\Aoptimize/i.match?(@sql)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def schema_command?
|
|
31
|
+
/\Acreate|\Aalter|\Adrop|\Arename/i.match?(@sql)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def format_specified?
|
|
35
|
+
/format [a-z]+\z/i.match?(@sql)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def delete?
|
|
39
|
+
/\Adelete from/i.match?(@sql)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module Clickhouse
|
|
6
|
+
class Statement
|
|
7
|
+
class ResponseProcessor
|
|
8
|
+
|
|
9
|
+
DB_EXCEPTION_REGEXP = /\ACode:\s+\d+\.\s+DB::Exception:/.freeze
|
|
10
|
+
|
|
11
|
+
def initialize(raw_response, format, sql)
|
|
12
|
+
@raw_response = raw_response
|
|
13
|
+
@body = raw_response.body
|
|
14
|
+
@format = format
|
|
15
|
+
@sql = sql
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process
|
|
19
|
+
if success?
|
|
20
|
+
process_successful_response
|
|
21
|
+
else
|
|
22
|
+
raise_database_error!
|
|
23
|
+
end
|
|
24
|
+
rescue JSON::ParserError
|
|
25
|
+
@body
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def success?
|
|
31
|
+
@raw_response.code.to_i == 200
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def process_successful_response
|
|
35
|
+
raise_generic!(@sql) if @body.include?('DB::Exception') && @body.match?(DB_EXCEPTION_REGEXP)
|
|
36
|
+
|
|
37
|
+
format_body_response
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def raise_generic!(sql = nil)
|
|
41
|
+
raise ActiveRecord::ActiveRecordError, "Response code: #{@raw_response.code}:\n#{@body}#{"\nQuery: #{sql}" if sql}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def format_body_response
|
|
45
|
+
return @body if @body.blank?
|
|
46
|
+
|
|
47
|
+
case @format
|
|
48
|
+
when 'JSONCompact'
|
|
49
|
+
format_from_json_compact(@body)
|
|
50
|
+
when 'JSONCompactEachRowWithNamesAndTypes'
|
|
51
|
+
format_from_json_compact_each_row_with_names_and_types(@body)
|
|
52
|
+
else
|
|
53
|
+
@body
|
|
54
|
+
end
|
|
55
|
+
rescue JSON::ParserError
|
|
56
|
+
@body
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def format_from_json_compact(body)
|
|
60
|
+
parse_json_payload(body)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def format_from_json_compact_each_row_with_names_and_types(body)
|
|
64
|
+
rows = body.each_line.map { |row| parse_json_payload(row) }
|
|
65
|
+
names, types, *data = rows
|
|
66
|
+
|
|
67
|
+
meta = names.zip(types).map do |name, type|
|
|
68
|
+
{
|
|
69
|
+
'name' => name,
|
|
70
|
+
'type' => type
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
'meta' => meta,
|
|
76
|
+
'data' => data
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def parse_json_payload(payload)
|
|
81
|
+
JSON.parse(payload, decimal_class: BigDecimal)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def raise_database_error!
|
|
85
|
+
case @body
|
|
86
|
+
when /DB::Exception:.*\(UNKNOWN_DATABASE\)/
|
|
87
|
+
raise ActiveRecord::NoDatabaseError
|
|
88
|
+
when /DB::Exception:.*\(DATABASE_ALREADY_EXISTS\)/
|
|
89
|
+
raise ActiveRecord::DatabaseAlreadyExists
|
|
90
|
+
else
|
|
91
|
+
raise_generic!
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_record/connection_adapters/clickhouse/statement/format_manager'
|
|
4
|
+
require 'active_record/connection_adapters/clickhouse/statement/response_processor'
|
|
5
|
+
|
|
6
|
+
module ActiveRecord
|
|
7
|
+
module ConnectionAdapters
|
|
8
|
+
module Clickhouse
|
|
9
|
+
class Statement
|
|
10
|
+
|
|
11
|
+
attr_reader :format
|
|
12
|
+
attr_writer :response
|
|
13
|
+
|
|
14
|
+
def initialize(sql, format:)
|
|
15
|
+
@sql = sql
|
|
16
|
+
@format = format
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def formatted_sql
|
|
20
|
+
@formatted_sql ||= FormatManager.new(@sql, format: @format).apply
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def processed_response
|
|
24
|
+
ResponseProcessor.new(@response, @format, @sql).process
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -16,6 +16,7 @@ require 'active_record/connection_adapters/clickhouse/column'
|
|
|
16
16
|
require 'active_record/connection_adapters/clickhouse/quoting'
|
|
17
17
|
require 'active_record/connection_adapters/clickhouse/schema_creation'
|
|
18
18
|
require 'active_record/connection_adapters/clickhouse/schema_statements'
|
|
19
|
+
require 'active_record/connection_adapters/clickhouse/statement'
|
|
19
20
|
require 'active_record/connection_adapters/clickhouse/table_definition'
|
|
20
21
|
require 'net/http'
|
|
21
22
|
require 'openssl'
|
|
@@ -82,6 +83,8 @@ module ActiveRecord
|
|
|
82
83
|
include Clickhouse::Quoting
|
|
83
84
|
|
|
84
85
|
ADAPTER_NAME = 'Clickhouse'.freeze
|
|
86
|
+
DEFAULT_RESPONSE_FORMAT = 'JSONCompactEachRowWithNamesAndTypes'.freeze
|
|
87
|
+
USER_AGENT = "ClickHouse ActiveRecord #{ClickhouseActiverecord::VERSION}"
|
|
85
88
|
NATIVE_DATABASE_TYPES = {
|
|
86
89
|
string: { name: 'String' },
|
|
87
90
|
integer: { name: 'UInt32' },
|
|
@@ -137,6 +140,7 @@ module ActiveRecord
|
|
|
137
140
|
|
|
138
141
|
@connection_config = { user: @config[:username], password: @config[:password], database: @config[:database] }.compact
|
|
139
142
|
@debug = @config[:debug] || false
|
|
143
|
+
@response_format = @config[:format] || DEFAULT_RESPONSE_FORMAT
|
|
140
144
|
|
|
141
145
|
@prepared_statements = false
|
|
142
146
|
|
|
@@ -145,7 +149,7 @@ module ActiveRecord
|
|
|
145
149
|
|
|
146
150
|
# Return ClickHouse server version
|
|
147
151
|
def server_version
|
|
148
|
-
@server_version ||=
|
|
152
|
+
@server_version ||= select_value('SELECT version()')
|
|
149
153
|
end
|
|
150
154
|
|
|
151
155
|
# Savepoints are not supported, noop
|
|
@@ -310,10 +314,7 @@ module ActiveRecord
|
|
|
310
314
|
# Create a new ClickHouse database.
|
|
311
315
|
def create_database(name)
|
|
312
316
|
sql = apply_cluster "CREATE DATABASE #{quote_table_name(name)}"
|
|
313
|
-
|
|
314
|
-
res = @connection.post("/?#{@connection_config.except(:database).to_param}", sql)
|
|
315
|
-
process_response(res, DEFAULT_RESPONSE_FORMAT)
|
|
316
|
-
end
|
|
317
|
+
do_system_execute sql, adapter_name, except_params: [:database]
|
|
317
318
|
end
|
|
318
319
|
|
|
319
320
|
def create_view(table_name, request_settings: {}, **options)
|
|
@@ -326,7 +327,7 @@ module ActiveRecord
|
|
|
326
327
|
drop_table(table_name, options.merge(if_exists: true))
|
|
327
328
|
end
|
|
328
329
|
|
|
329
|
-
|
|
330
|
+
execute(schema_creation.accept(td), settings: request_settings)
|
|
330
331
|
end
|
|
331
332
|
|
|
332
333
|
def create_table(table_name, request_settings: {}, **options, &block)
|
|
@@ -343,7 +344,7 @@ module ActiveRecord
|
|
|
343
344
|
drop_table(table_name, options.merge(if_exists: true))
|
|
344
345
|
end
|
|
345
346
|
|
|
346
|
-
|
|
347
|
+
execute(schema_creation.accept(td), settings: request_settings)
|
|
347
348
|
|
|
348
349
|
if options[:with_distributed]
|
|
349
350
|
distributed_table_name = options.delete(:with_distributed)
|
|
@@ -358,16 +359,13 @@ module ActiveRecord
|
|
|
358
359
|
|
|
359
360
|
def create_function(name, body, **options)
|
|
360
361
|
fd = "CREATE#{' OR REPLACE' if options[:force]} FUNCTION #{apply_cluster(quote_table_name(name))} AS #{body}"
|
|
361
|
-
|
|
362
|
+
execute(fd)
|
|
362
363
|
end
|
|
363
364
|
|
|
364
365
|
# Drops a ClickHouse database.
|
|
365
366
|
def drop_database(name) #:nodoc:
|
|
366
367
|
sql = apply_cluster "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
|
|
367
|
-
|
|
368
|
-
res = @connection.post("/?#{@connection_config.except(:database).to_param}", sql)
|
|
369
|
-
process_response(res, DEFAULT_RESPONSE_FORMAT)
|
|
370
|
-
end
|
|
368
|
+
do_system_execute sql, adapter_name, except_params: [:database]
|
|
371
369
|
end
|
|
372
370
|
|
|
373
371
|
def drop_functions
|
|
@@ -377,7 +375,7 @@ module ActiveRecord
|
|
|
377
375
|
end
|
|
378
376
|
|
|
379
377
|
def rename_table(table_name, new_name)
|
|
380
|
-
|
|
378
|
+
execute apply_cluster "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
|
381
379
|
end
|
|
382
380
|
|
|
383
381
|
def drop_table(table_name, options = {}) # :nodoc:
|
|
@@ -387,7 +385,7 @@ module ActiveRecord
|
|
|
387
385
|
query = apply_cluster(query)
|
|
388
386
|
query = "#{query} SYNC" if options[:sync]
|
|
389
387
|
|
|
390
|
-
|
|
388
|
+
execute(query)
|
|
391
389
|
|
|
392
390
|
if options[:with_distributed]
|
|
393
391
|
distributed_table_name = options.delete(:with_distributed)
|
|
@@ -402,25 +400,19 @@ module ActiveRecord
|
|
|
402
400
|
query = apply_cluster(query)
|
|
403
401
|
query = "#{query} SYNC" if options[:sync]
|
|
404
402
|
|
|
405
|
-
|
|
403
|
+
execute(query)
|
|
406
404
|
end
|
|
407
405
|
|
|
408
406
|
def add_column(table_name, column_name, type, **options)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
at = create_alter_table table_name
|
|
412
|
-
at.add_column(column_name, type, **options)
|
|
413
|
-
execute(schema_creation.accept(at), nil, settings: {wait_end_of_query: 1, send_progress_in_http_headers: 1})
|
|
407
|
+
with_settings(wait_end_of_query: 1, send_progress_in_http_headers: 1) { super }
|
|
414
408
|
end
|
|
415
409
|
|
|
416
410
|
def remove_column(table_name, column_name, type = nil, **options)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}", nil, settings: {wait_end_of_query: 1, send_progress_in_http_headers: 1})
|
|
411
|
+
with_settings(wait_end_of_query: 1, send_progress_in_http_headers: 1) { super }
|
|
420
412
|
end
|
|
421
413
|
|
|
422
414
|
def change_column(table_name, column_name, type, **options)
|
|
423
|
-
result =
|
|
415
|
+
result = execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, **options)}", nil, settings: {wait_end_of_query: 1, send_progress_in_http_headers: 1})
|
|
424
416
|
raise "Error parse json response: #{result}" if result.presence && !result.is_a?(Hash)
|
|
425
417
|
end
|
|
426
418
|
|
|
@@ -479,6 +471,13 @@ module ActiveRecord
|
|
|
479
471
|
@config[:database]
|
|
480
472
|
end
|
|
481
473
|
|
|
474
|
+
# Returns the shard name from the configuration.
|
|
475
|
+
# This is used to identify the shard in replication paths when using both sharding and replication.
|
|
476
|
+
# Required when you have multiple shards with replication to ensure unique paths for each shard's replication metadata.
|
|
477
|
+
def shard
|
|
478
|
+
@config[:shard_name]
|
|
479
|
+
end
|
|
480
|
+
|
|
482
481
|
def use_default_replicated_merge_tree_params?
|
|
483
482
|
database_engine_atomic? && @config[:use_default_replicated_merge_tree_params]
|
|
484
483
|
end
|
|
@@ -487,8 +486,17 @@ module ActiveRecord
|
|
|
487
486
|
(replica || use_default_replicated_merge_tree_params?) && cluster
|
|
488
487
|
end
|
|
489
488
|
|
|
489
|
+
# Returns the path for replication metadata.
|
|
490
|
+
# When sharding is enabled (shard_name is set), the path includes the shard identifier
|
|
491
|
+
# to ensure unique paths for each shard's replication metadata.
|
|
492
|
+
# Format with sharding: /clickhouse/tables/{cluster}/{shard}/{database}.{table}
|
|
493
|
+
# Format without sharding: /clickhouse/tables/{cluster}/{database}.{table}
|
|
490
494
|
def replica_path(table)
|
|
491
|
-
|
|
495
|
+
if shard
|
|
496
|
+
"/clickhouse/tables/#{cluster}/#{shard}/#{@connection_config[:database]}.#{table}"
|
|
497
|
+
else
|
|
498
|
+
"/clickhouse/tables/#{cluster}/#{@connection_config[:database]}.#{table}"
|
|
499
|
+
end
|
|
492
500
|
end
|
|
493
501
|
|
|
494
502
|
def database_engine_atomic?
|
|
@@ -51,8 +51,14 @@ module Arel
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def visit_Arel_Nodes_Final(o, collector)
|
|
54
|
-
visit o.expr, collector
|
|
54
|
+
visit o.expr.left, collector
|
|
55
55
|
collector << ' FINAL'
|
|
56
|
+
|
|
57
|
+
o.expr.right.each do |join|
|
|
58
|
+
collector << ' '
|
|
59
|
+
visit join, collector
|
|
60
|
+
end
|
|
61
|
+
|
|
56
62
|
collector
|
|
57
63
|
end
|
|
58
64
|
|
|
@@ -31,6 +31,8 @@ module ClickhouseActiverecord
|
|
|
31
31
|
|
|
32
32
|
def table(table, stream)
|
|
33
33
|
if table.match(/^\.inner/).nil?
|
|
34
|
+
sql= ""
|
|
35
|
+
simple ||= ENV['simple'] == 'true'
|
|
34
36
|
unless simple
|
|
35
37
|
stream.puts " # TABLE: #{table}"
|
|
36
38
|
sql = @connection.show_create_table(table)
|
|
@@ -126,7 +128,7 @@ module ClickhouseActiverecord
|
|
|
126
128
|
sql = @connection.show_create_function(function)
|
|
127
129
|
if sql
|
|
128
130
|
stream.puts " # SQL: #{sql}"
|
|
129
|
-
stream.puts " create_function \"#{function}\", \"#{sql.
|
|
131
|
+
stream.puts " create_function \"#{function}\", \"#{sql.sub(/\ACREATE(OR REPLACE)? FUNCTION .*? AS/, '').strip}\", force: true"
|
|
130
132
|
stream.puts
|
|
131
133
|
end
|
|
132
134
|
end
|
|
@@ -39,7 +39,6 @@ module ClickhouseActiverecord
|
|
|
39
39
|
|
|
40
40
|
# get all tables
|
|
41
41
|
tables = connection.execute("SHOW TABLES FROM #{@configuration.database} WHERE name NOT LIKE '.inner_id.%'")['data'].flatten.map do |table|
|
|
42
|
-
next if %w[schema_migrations ar_internal_metadata].include?(table)
|
|
43
42
|
connection.show_create_table(table, single_line: false).gsub("#{@configuration.database}.", '')
|
|
44
43
|
end.compact
|
|
45
44
|
|
|
@@ -66,9 +65,9 @@ module ClickhouseActiverecord
|
|
|
66
65
|
if sql.gsub(/[a-z]/i, '').blank?
|
|
67
66
|
next
|
|
68
67
|
elsif sql =~ /^INSERT INTO/
|
|
69
|
-
connection.
|
|
68
|
+
connection.execute(sql, nil, format: nil)
|
|
70
69
|
elsif sql =~ /^CREATE .*?FUNCTION/
|
|
71
|
-
connection.
|
|
70
|
+
connection.execute(sql, nil, format: nil)
|
|
72
71
|
else
|
|
73
72
|
connection.execute(sql)
|
|
74
73
|
end
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
module CoreExtensions
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
module Relation
|
|
4
|
+
|
|
5
|
+
def self.prepended(base)
|
|
6
|
+
base::VALID_UNSCOPING_VALUES << :final << :settings
|
|
7
|
+
end
|
|
8
|
+
|
|
4
9
|
def reverse_order!
|
|
5
10
|
return super unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
|
|
6
11
|
|
|
@@ -19,17 +24,39 @@ module CoreExtensions
|
|
|
19
24
|
#
|
|
20
25
|
# An <tt>ActiveRecord::ActiveRecordError</tt> will be raised if database not ClickHouse.
|
|
21
26
|
# @param [Hash] opts
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Specify settings to be used for this single query.
|
|
30
|
+
# For example:
|
|
31
|
+
#
|
|
32
|
+
# users = User.settings(use_skip_indexes: true).where(name: 'John')
|
|
33
|
+
# # SELECT "users".* FROM "users"
|
|
34
|
+
# # WHERE "users"."name" = 'John'
|
|
35
|
+
# # SETTINGS use_skip_indexes = 1
|
|
22
36
|
def settings(**opts)
|
|
23
37
|
spawn.settings!(**opts)
|
|
24
38
|
end
|
|
25
39
|
|
|
26
40
|
# @param [Hash] opts
|
|
27
41
|
def settings!(**opts)
|
|
28
|
-
check_command('SETTINGS')
|
|
29
|
-
|
|
42
|
+
check_command!('SETTINGS')
|
|
43
|
+
self.settings_values = settings_values.merge opts
|
|
30
44
|
self
|
|
31
45
|
end
|
|
32
46
|
|
|
47
|
+
def settings_values
|
|
48
|
+
@values.fetch(:settings, ::ActiveRecord::QueryMethods::FROZEN_EMPTY_HASH)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def settings_values=(value)
|
|
52
|
+
if ::ActiveRecord::version >= Gem::Version.new('7.2')
|
|
53
|
+
assert_modifiable!
|
|
54
|
+
else
|
|
55
|
+
assert_mutability!
|
|
56
|
+
end
|
|
57
|
+
@values[:settings] = value
|
|
58
|
+
end
|
|
59
|
+
|
|
33
60
|
# When FINAL is specified, ClickHouse fully merges the data before returning the result and thus performs all data transformations that happen during merges for the given table engine.
|
|
34
61
|
# For example:
|
|
35
62
|
#
|
|
@@ -37,16 +64,32 @@ module CoreExtensions
|
|
|
37
64
|
# # SELECT users.* FROM users FINAL
|
|
38
65
|
#
|
|
39
66
|
# An <tt>ActiveRecord::ActiveRecordError</tt> will be raised if database not ClickHouse.
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
#
|
|
68
|
+
# @param [Boolean] final
|
|
69
|
+
def final(final = true)
|
|
70
|
+
spawn.final!(final)
|
|
42
71
|
end
|
|
43
72
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
73
|
+
# @param [Boolean] final
|
|
74
|
+
def final!(final = true)
|
|
75
|
+
check_command!('FINAL')
|
|
76
|
+
self.final_value = final
|
|
47
77
|
self
|
|
48
78
|
end
|
|
49
79
|
|
|
80
|
+
def final_value=(value)
|
|
81
|
+
if ::ActiveRecord::version >= Gem::Version.new('7.2')
|
|
82
|
+
assert_modifiable!
|
|
83
|
+
else
|
|
84
|
+
assert_mutability!
|
|
85
|
+
end
|
|
86
|
+
@values[:final] = value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def final_value
|
|
90
|
+
@values.fetch(:final, nil)
|
|
91
|
+
end
|
|
92
|
+
|
|
50
93
|
# GROUPING SETS allows you to specify multiple groupings in the GROUP BY clause.
|
|
51
94
|
# Whereas GROUP BY CUBE generates all possible groupings, GROUP BY GROUPING SETS generates only the specified groupings.
|
|
52
95
|
# For example:
|
|
@@ -128,20 +171,22 @@ module CoreExtensions
|
|
|
128
171
|
|
|
129
172
|
private
|
|
130
173
|
|
|
131
|
-
def check_command(cmd)
|
|
174
|
+
def check_command!(cmd)
|
|
132
175
|
raise ::ActiveRecord::ActiveRecordError, cmd + ' is a ClickHouse specific query clause' unless connection.is_a?(::ActiveRecord::ConnectionAdapters::ClickhouseAdapter)
|
|
133
176
|
end
|
|
134
177
|
|
|
135
178
|
def build_arel(connection_or_aliases = nil, aliases = nil)
|
|
136
|
-
|
|
179
|
+
requirement = Gem::Requirement.new('>= 7.2', '< 8.1')
|
|
180
|
+
|
|
181
|
+
if requirement.satisfied_by?(::ActiveRecord::version)
|
|
137
182
|
arel = super
|
|
138
183
|
else
|
|
139
184
|
arel = super(connection_or_aliases)
|
|
140
185
|
end
|
|
141
186
|
|
|
142
|
-
arel.final! if
|
|
187
|
+
arel.final! if final_value
|
|
143
188
|
arel.limit_by(*@values[:limit_by]) if @values[:limit_by].present?
|
|
144
|
-
arel.settings(
|
|
189
|
+
arel.settings(settings_values) unless settings_values.empty?
|
|
145
190
|
arel.using(@values[:using]) if @values[:using].present?
|
|
146
191
|
arel.windows(@values[:windows]) if @values[:windows].present?
|
|
147
192
|
|
|
@@ -10,6 +10,13 @@ module CoreExtensions
|
|
|
10
10
|
::Arel::Nodes::Final.new(super)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
+
def hash
|
|
14
|
+
[
|
|
15
|
+
@source, @set_quantifier, @projections, @optimizer_hints,
|
|
16
|
+
@wheres, @groups, @havings, @windows, @comment, @final
|
|
17
|
+
].hash
|
|
18
|
+
end
|
|
19
|
+
|
|
13
20
|
def eql?(other)
|
|
14
21
|
super && final == other.final
|
|
15
22
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clickhouse-activerecord
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergey Odintsov
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: bundler
|
|
@@ -122,6 +121,9 @@ files:
|
|
|
122
121
|
- lib/active_record/connection_adapters/clickhouse/quoting.rb
|
|
123
122
|
- lib/active_record/connection_adapters/clickhouse/schema_creation.rb
|
|
124
123
|
- lib/active_record/connection_adapters/clickhouse/schema_statements.rb
|
|
124
|
+
- lib/active_record/connection_adapters/clickhouse/statement.rb
|
|
125
|
+
- lib/active_record/connection_adapters/clickhouse/statement/format_manager.rb
|
|
126
|
+
- lib/active_record/connection_adapters/clickhouse/statement/response_processor.rb
|
|
125
127
|
- lib/active_record/connection_adapters/clickhouse/table_definition.rb
|
|
126
128
|
- lib/active_record/connection_adapters/clickhouse_adapter.rb
|
|
127
129
|
- lib/arel/nodes/final.rb
|
|
@@ -146,12 +148,10 @@ files:
|
|
|
146
148
|
- lib/core_extensions/arel/select_manager.rb
|
|
147
149
|
- lib/core_extensions/arel/table.rb
|
|
148
150
|
- lib/generators/clickhouse_migration_generator.rb
|
|
149
|
-
- lib/tasks/clickhouse.rake
|
|
150
151
|
homepage: https://github.com/pnixx/clickhouse-activerecord
|
|
151
152
|
licenses:
|
|
152
153
|
- MIT
|
|
153
154
|
metadata: {}
|
|
154
|
-
post_install_message:
|
|
155
155
|
rdoc_options: []
|
|
156
156
|
require_paths:
|
|
157
157
|
- lib
|
|
@@ -166,8 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
166
166
|
- !ruby/object:Gem::Version
|
|
167
167
|
version: '0'
|
|
168
168
|
requirements: []
|
|
169
|
-
rubygems_version: 3.
|
|
170
|
-
signing_key:
|
|
169
|
+
rubygems_version: 3.6.9
|
|
171
170
|
specification_version: 4
|
|
172
171
|
summary: ClickHouse ActiveRecord
|
|
173
172
|
test_files: []
|