Skip to content

Commit c4a41d1

Browse files
authored
Do not truncate engines that cannot be truncated (#226)
1 parent f521559 commit c4a41d1

File tree

5 files changed

+109
-5
lines changed

5 files changed

+109
-5
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,18 @@ Structure load from `db/clickhouse_structure.sql` file:
158158

159159
For auto truncate tables before each test add to `spec/rails_helper.rb` file:
160160

161-
```
161+
```ruby
162162
require 'clickhouse-activerecord/rspec'
163163
```
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+
165173
### Insert and select data
166174

167175
```ruby

lib/active_record/connection_adapters/clickhouse/schema_statements.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,38 @@ def settings_params(settings = {}, except: [])
299299
.except(*except)
300300
.to_param
301301
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
302334
end
303335
end
304336
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

lib/clickhouse-activerecord/rspec.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
config.before do
55
ActiveRecord::Base.configurations.configurations.select { |x| x.env_name == Rails.env && x.adapter == 'clickhouse' }.each do |config|
66
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)
108
end
119
end
1210
end
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe 'ActiveRecord::ConnectionAdapters::Clickhouse::SchemaStatements' do
4+
let(:connection) { ActiveRecord::Base.connection }
5+
6+
describe '#truncate_tables' do
7+
before do
8+
connection.execute('CREATE TABLE truncate_test (id UInt64, name String) ENGINE = MergeTree ORDER BY id')
9+
end
10+
11+
after do
12+
connection.execute('DROP DICTIONARY IF EXISTS truncate_test_dict')
13+
connection.execute('DROP TABLE IF EXISTS truncate_test')
14+
connection.execute('DROP TABLE IF EXISTS truncate_test2')
15+
connection.execute('DROP VIEW IF EXISTS truncate_test_view')
16+
end
17+
18+
it 'truncates multiple tables' do
19+
connection.execute('CREATE TABLE truncate_test2 (id UInt64, value Int32) ENGINE = MergeTree ORDER BY id')
20+
connection.exec_insert("INSERT INTO truncate_test (id, name) VALUES (1, 'Alice'), (2, 'Bob')")
21+
connection.exec_insert("INSERT INTO truncate_test2 (id, value) VALUES (1, 100), (2, 200)")
22+
23+
expect(connection.select_value('SELECT count() FROM truncate_test')).to eq(2)
24+
expect(connection.select_value('SELECT count() FROM truncate_test2')).to eq(2)
25+
26+
connection.truncate_tables('truncate_test', 'truncate_test2')
27+
28+
expect(connection.select_value('SELECT count() FROM truncate_test')).to eq(0)
29+
expect(connection.select_value('SELECT count() FROM truncate_test2')).to eq(0)
30+
end
31+
32+
it 'skips tables with unsupported engines' do
33+
connection.execute('CREATE VIEW truncate_test_view AS SELECT * FROM truncate_test')
34+
connection.execute(<<~SQL)
35+
CREATE DICTIONARY truncate_test_dict (
36+
id UInt64,
37+
name String
38+
)
39+
PRIMARY KEY id
40+
SOURCE(CLICKHOUSE(TABLE 'truncate_test'))
41+
LIFETIME(MIN 0 MAX 0)
42+
LAYOUT(FLAT())
43+
SQL
44+
connection.exec_insert("INSERT INTO truncate_test (id, name) VALUES (1, 'Alice')")
45+
46+
expect { connection.truncate_tables(*connection.tables) }.not_to raise_error
47+
48+
expect(connection.select_value('SELECT count() FROM truncate_test')).to eq(0)
49+
end
50+
end
51+
end

0 commit comments

Comments
 (0)