From 5cb4ea7fc4f1c15c049dff717e8d4d5dfdc4b279 Mon Sep 17 00:00:00 2001 From: Daniel Westendorf Date: Tue, 8 Oct 2024 23:22:55 -0600 Subject: [PATCH] Add support for force `CREATE FUNCTION` (#146) * Switch from `CREATE FUNCTION` to `CREATE OR REPLACE FUNCTION` In a rails app, with existing Clickhouse schema w/a function defined, a `rails db:schema:load` will not always succeed; this is because the function may already exist. ``` Code: 609. DB::Exception: User-defined function 'uuid7ToDateTime' already exists. (FUNCTION_ALREADY_EXISTS) (version 23.9.6.20 (official build)) ``` --- lib/active_record/connection_adapters/clickhouse_adapter.rb | 4 ++-- lib/clickhouse-activerecord/schema_dumper.rb | 2 +- spec/cluster/migration_spec.rb | 5 ++++- .../migrations/dsl_create_function/1_create_some_function.rb | 1 + spec/single/migration_spec.rb | 5 ++++- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/active_record/connection_adapters/clickhouse_adapter.rb b/lib/active_record/connection_adapters/clickhouse_adapter.rb index 8d7257e2..2b31c3bf 100644 --- a/lib/active_record/connection_adapters/clickhouse_adapter.rb +++ b/lib/active_record/connection_adapters/clickhouse_adapter.rb @@ -356,8 +356,8 @@ def create_table(table_name, **options, &block) end end - def create_function(name, body) - fd = "CREATE FUNCTION #{apply_cluster(quote_table_name(name))} AS #{body}" + def create_function(name, body, **options) + fd = "CREATE#{' OR REPLACE' if options[:force]} FUNCTION #{apply_cluster(quote_table_name(name))} AS #{body}" do_execute(fd, format: nil) end diff --git a/lib/clickhouse-activerecord/schema_dumper.rb b/lib/clickhouse-activerecord/schema_dumper.rb index 44a2bd8c..61fe71c1 100644 --- a/lib/clickhouse-activerecord/schema_dumper.rb +++ b/lib/clickhouse-activerecord/schema_dumper.rb @@ -112,7 +112,7 @@ def function(function, stream) stream.puts " # FUNCTION: #{function}" sql = @connection.show_create_function(function) stream.puts " # SQL: #{sql}" if sql - stream.puts " create_function \"#{function}\", \"#{sql.gsub(/^CREATE FUNCTION (.*?) AS/, '').strip}\"" if sql + stream.puts " create_function \"#{function}\", \"#{sql.gsub(/^CREATE FUNCTION (.*?) AS/, '').strip}\", force: true" if sql end def format_options(options) diff --git a/spec/cluster/migration_spec.rb b/spec/cluster/migration_spec.rb index eb60f93a..09edf193 100644 --- a/spec/cluster/migration_spec.rb +++ b/spec/cluster/migration_spec.rb @@ -70,9 +70,12 @@ let(:directory) { 'dsl_create_function' } it 'creates a function' do + ActiveRecord::Base.connection.do_execute('CREATE FUNCTION forced_fun AS (x, k, b) -> k*x + b', format: nil) + subject - expect(ActiveRecord::Base.connection.functions).to match_array(['some_fun']) + expect(ActiveRecord::Base.connection.functions).to match_array(['some_fun', 'forced_fun']) + expect(ActiveRecord::Base.connection.show_create_function('forced_fun').chomp).to eq('CREATE FUNCTION forced_fun AS (x, y) -> (x + y)') end end end diff --git a/spec/fixtures/migrations/dsl_create_function/1_create_some_function.rb b/spec/fixtures/migrations/dsl_create_function/1_create_some_function.rb index f49bd4df..940a1ee6 100644 --- a/spec/fixtures/migrations/dsl_create_function/1_create_some_function.rb +++ b/spec/fixtures/migrations/dsl_create_function/1_create_some_function.rb @@ -3,5 +3,6 @@ class CreateSomeFunction < ActiveRecord::Migration[7.1] def up create_function :some_fun, "(x,y) -> x + y" + create_function :forced_fun, "(x,y) -> x + y", force: true end end diff --git a/spec/single/migration_spec.rb b/spec/single/migration_spec.rb index c82fb32c..7f6565c3 100644 --- a/spec/single/migration_spec.rb +++ b/spec/single/migration_spec.rb @@ -369,9 +369,12 @@ context 'dsl' do let(:directory) { 'dsl_create_function' } it 'creates a function' do + ActiveRecord::Base.connection.do_execute('CREATE FUNCTION forced_fun AS (x, k, b) -> k*x + b', format: nil) + subject - expect(ActiveRecord::Base.connection.functions).to match_array(['some_fun']) + expect(ActiveRecord::Base.connection.functions).to match_array(['some_fun', 'forced_fun']) + expect(ActiveRecord::Base.connection.show_create_function('forced_fun').chomp).to eq('CREATE FUNCTION forced_fun AS (x, y) -> (x + y)') end end end