Skip to content

Commit b05d2a4

Browse files
committed
Adjust table creation to work with materialized views on schema loading
1 parent 5f2fbf4 commit b05d2a4

2 files changed

Lines changed: 80 additions & 12 deletions

File tree

lib/active_record/connection_adapters/clickhouse/schema_creation.rb

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,19 @@ def assign_database_to_subquery!(subquery)
8282
"#{current_database}.#{match[:table_name].sub('.', '')}"
8383
end
8484

85+
def add_materialized_to_clause!(create_sql, options)
86+
if !options.to
87+
create_sql << " ENGINE = Memory()"
88+
else
89+
target_table = options.to.split('.').last
90+
table_structure = @conn.execute("DESCRIBE TABLE #{target_table}")['data']
91+
column_definitions = table_structure.map do |field|
92+
"`#{field[0]}` #{field[1]}"
93+
end
94+
create_sql << "TO #{options.to} (#{column_definitions.join(', ')}) "
95+
end
96+
end
97+
8598
def add_to_clause!(create_sql, options)
8699
# If you do not specify a database explicitly, ClickHouse will use the "default" database.
87100
return unless options.to
@@ -97,23 +110,34 @@ def visit_TableDefinition(o)
97110
create_sql = +"CREATE#{table_modifier_in_create(o)} #{o.view ? "VIEW" : "TABLE"} "
98111
create_sql << "IF NOT EXISTS " if o.if_not_exists
99112
create_sql << "#{quote_table_name(o.name)} "
100-
add_as_clause!(create_sql, o) if o.as && !o.view
101-
add_to_clause!(create_sql, o) if o.materialized
102113

103-
statements = o.columns.map { |c| accept c }
104-
statements << accept(o.primary_keys) if o.primary_keys
114+
# Add column definitions for regular tables only
115+
if !o.view && o.columns.present?
116+
statements = o.columns.map { |c| accept c }
117+
statements << accept(o.primary_keys) if o.primary_keys
105118

106-
if supports_indexes_in_create?
107-
indexes = o.indexes.map do |expression, options|
108-
accept(@conn.add_index_options(o.name, expression, **options))
119+
if supports_indexes_in_create?
120+
indexes = o.indexes.map do |expression, options|
121+
accept(@conn.add_index_options(o.name, expression, **options))
122+
end
123+
statements.concat(indexes)
109124
end
110-
statements.concat(indexes)
125+
126+
create_sql << "(#{statements.join(', ')})"
111127
end
112128

113-
create_sql << "(#{statements.join(', ')})" if statements.present?
114-
# Attach options for only table or materialized view without TO section
115-
add_table_options!(create_sql, o) if !o.view || o.view && o.materialized && !o.to
116-
add_as_clause!(create_sql, o) if o.as && o.view
129+
# Add TO clause for materialized views before AS clause
130+
add_materialized_to_clause!(create_sql, o) if o.materialized && o.view
131+
132+
# Add AS clause for all views
133+
add_as_clause!(create_sql, o) if o.as
134+
135+
# Add TO clause for regular views (non-materialized) after AS clause
136+
add_to_clause!(create_sql, o) if o.to && !o.materialized
137+
138+
# Add table options for regular tables
139+
add_table_options!(create_sql, o) if !o.view
140+
117141
create_sql
118142
end
119143

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe 'Materialized Views' do
4+
before do
5+
ActiveRecord::Schema.define do
6+
create_table "events", id: false, options: "Log", force: :cascade do |t|
7+
t.integer "quantity", default: -> { "CAST(1, 'Int8')" }, null: false
8+
t.string "name", null: false
9+
t.date "created_at", null: false
10+
end
11+
end
12+
end
13+
14+
after do
15+
ActiveRecord::Schema.define do
16+
drop_table :events if table_exists?(:events)
17+
drop_table :aggregated_events_mv if table_exists?(:aggregated_events_mv)
18+
drop_table :aggregated_events if table_exists?(:aggregated_events)
19+
end
20+
end
21+
22+
it 'creates a materialized view with TO clause and column definitions' do
23+
database = ActiveRecord::Base.connection_db_config.database
24+
25+
ActiveRecord::Schema.define do
26+
create_table "aggregated_events", id: false, options: "SummingMergeTree ORDER BY (name, date) SETTINGS index_granularity = 8192", force: :cascade do |t|
27+
t.string "name", null: false
28+
t.date "date", null: false
29+
t.integer "total_quantity", limit: 8, null: false
30+
t.integer "event_count", limit: 8, null: false
31+
end
32+
33+
create_table "aggregated_events_mv", view: true, materialized: true, to: "#{database}.aggregated_events", id: false, as: "SELECT name, created_at AS date, sum(quantity) AS total_quantity, count() AS event_count FROM #{database}.events GROUP BY name, created_at", force: :cascade do |t|
34+
end
35+
end
36+
37+
# Verify the view was created correctly
38+
result = ActiveRecord::Base.connection.do_system_execute(
39+
"SHOW CREATE TABLE #{database}.aggregated_events_mv"
40+
)['data'].first.first
41+
42+
expect(result.squish).to eq('CREATE MATERIALIZED VIEW default.aggregated_events_mv TO default.aggregated_events ( `name` String, `date` Date, `total_quantity` UInt64, `event_count` UInt64 ) AS SELECT name, created_at AS date, sum(quantity) AS total_quantity, count() AS event_count FROM default.events GROUP BY name, created_at')
43+
end
44+
end

0 commit comments

Comments
 (0)