fx-jets 0.6.3s

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.hound.yml +2 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +648 -0
  6. data/.travis.yml +60 -0
  7. data/.yardopts +4 -0
  8. data/Appraisals +45 -0
  9. data/CONTRIBUTING.md +15 -0
  10. data/Gemfile +4 -0
  11. data/LICENSE +18 -0
  12. data/README.md +1 -0
  13. data/Rakefile +23 -0
  14. data/bin/appraisal +17 -0
  15. data/bin/console +14 -0
  16. data/bin/rake +17 -0
  17. data/bin/rspec +17 -0
  18. data/bin/setup +13 -0
  19. data/bin/yard +17 -0
  20. data/fx.gemspec +39 -0
  21. data/gemfiles/rails42.gemfile +10 -0
  22. data/gemfiles/rails50.gemfile +8 -0
  23. data/gemfiles/rails51.gemfile +8 -0
  24. data/gemfiles/rails52.gemfile +8 -0
  25. data/gemfiles/rails60.gemfile +8 -0
  26. data/gemfiles/rails61.gemfile +8 -0
  27. data/gemfiles/rails_edge.gemfile +8 -0
  28. data/lib/fx/adapters/postgres/connection.rb +16 -0
  29. data/lib/fx/adapters/postgres/functions.rb +59 -0
  30. data/lib/fx/adapters/postgres/triggers.rb +57 -0
  31. data/lib/fx/adapters/postgres.rb +167 -0
  32. data/lib/fx/command_recorder/arguments.rb +43 -0
  33. data/lib/fx/command_recorder/function.rb +30 -0
  34. data/lib/fx/command_recorder/trigger.rb +30 -0
  35. data/lib/fx/command_recorder.rb +24 -0
  36. data/lib/fx/configuration.rb +48 -0
  37. data/lib/fx/definition.rb +46 -0
  38. data/lib/fx/function.rb +26 -0
  39. data/lib/fx/railtie.rb +15 -0
  40. data/lib/fx/schema_dumper/function.rb +38 -0
  41. data/lib/fx/schema_dumper/trigger.rb +29 -0
  42. data/lib/fx/schema_dumper.rb +10 -0
  43. data/lib/fx/statements/function.rb +115 -0
  44. data/lib/fx/statements/trigger.rb +146 -0
  45. data/lib/fx/statements.rb +11 -0
  46. data/lib/fx/trigger.rb +26 -0
  47. data/lib/fx/version.rb +4 -0
  48. data/lib/fx.rb +43 -0
  49. data/lib/generators/fx/function/USAGE +11 -0
  50. data/lib/generators/fx/function/function_generator.rb +120 -0
  51. data/lib/generators/fx/function/templates/db/migrate/create_function.erb +5 -0
  52. data/lib/generators/fx/function/templates/db/migrate/update_function.erb +5 -0
  53. data/lib/generators/fx/trigger/USAGE +20 -0
  54. data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +5 -0
  55. data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +5 -0
  56. data/lib/generators/fx/trigger/trigger_generator.rb +130 -0
  57. data/lib/generators.rb +11 -0
  58. data/spec/acceptance/user_manages_functions_spec.rb +57 -0
  59. data/spec/acceptance/user_manages_triggers_spec.rb +51 -0
  60. data/spec/acceptance_helper.rb +62 -0
  61. data/spec/dummy/.gitignore +16 -0
  62. data/spec/dummy/Rakefile +13 -0
  63. data/spec/dummy/bin/bundle +3 -0
  64. data/spec/dummy/bin/rails +4 -0
  65. data/spec/dummy/bin/rake +4 -0
  66. data/spec/dummy/config/application.rb +15 -0
  67. data/spec/dummy/config/boot.rb +5 -0
  68. data/spec/dummy/config/database.yml +9 -0
  69. data/spec/dummy/config/environment.rb +5 -0
  70. data/spec/dummy/config.ru +4 -0
  71. data/spec/dummy/db/migrate/.keep +0 -0
  72. data/spec/features/functions/migrations_spec.rb +65 -0
  73. data/spec/features/functions/revert_spec.rb +75 -0
  74. data/spec/features/triggers/migrations_spec.rb +56 -0
  75. data/spec/features/triggers/revert_spec.rb +95 -0
  76. data/spec/fx/adapters/postgres/functions_spec.rb +37 -0
  77. data/spec/fx/adapters/postgres/triggers_spec.rb +45 -0
  78. data/spec/fx/adapters/postgres_spec.rb +146 -0
  79. data/spec/fx/command_recorder/arguments_spec.rb +41 -0
  80. data/spec/fx/command_recorder_spec.rb +171 -0
  81. data/spec/fx/configuration_spec.rb +21 -0
  82. data/spec/fx/definition_spec.rb +134 -0
  83. data/spec/fx/function_spec.rb +68 -0
  84. data/spec/fx/schema_dumper/function_spec.rb +80 -0
  85. data/spec/fx/schema_dumper/trigger_spec.rb +40 -0
  86. data/spec/fx/statements/function_spec.rb +103 -0
  87. data/spec/fx/statements/trigger_spec.rb +132 -0
  88. data/spec/fx/trigger_spec.rb +55 -0
  89. data/spec/generators/fx/function/function_generator_spec.rb +46 -0
  90. data/spec/generators/fx/trigger/trigger_generator_spec.rb +59 -0
  91. data/spec/spec_helper.rb +21 -0
  92. data/spec/support/definition_helpers.rb +37 -0
  93. data/spec/support/generator_setup.rb +11 -0
  94. data/spec/support/migration_helpers.rb +25 -0
  95. metadata +357 -0
@@ -0,0 +1,167 @@
1
+ require "fx/adapters/postgres/connection"
2
+ require "fx/adapters/postgres/functions"
3
+ require "fx/adapters/postgres/triggers"
4
+
5
+ module Fx
6
+ # F(x) database adapters.
7
+ #
8
+ # F(x) ships with a Postgres adapter only but can be extended with
9
+ # additional adapters. The {Fx::Adapters::Postgres} adapter provides the
10
+ # interface.
11
+ module Adapters
12
+ # Creates an instance of the F(x) Postgres adapter.
13
+ #
14
+ # This is the default adapter for F(x). Configuring it via
15
+ # {Fx.configure} is not required, but the example below shows how one
16
+ # would explicitly set it.
17
+ #
18
+ # @param [#connection] connectable An object that returns the connection
19
+ # for F(x) to use. Defaults to `ActiveRecord::Base`.
20
+ #
21
+ # @example
22
+ # Fx.configure do |config|
23
+ # config.adapter = Fx::Adapters::Postgres.new
24
+ # end
25
+ class Postgres
26
+ # Creates an instance of the F(x) Postgres adapter.
27
+ #
28
+ # This is the default adapter for F(x). Configuring it via
29
+ # {Fx.configure} is not required, but the example below shows how one
30
+ # would explicitly set it.
31
+ #
32
+ # @param [#connection] connectable An object that returns the connection
33
+ # for F(x) to use. Defaults to `ActiveRecord::Base`.
34
+ #
35
+ # @example
36
+ # Fx.configure do |config|
37
+ # config.adapter = Fx::Adapters::Postgres.new
38
+ # end
39
+ def initialize(connectable = ActiveRecord::Base)
40
+ @connectable = connectable
41
+ end
42
+
43
+ # Returns an array of functions in the database.
44
+ #
45
+ # This collection of functions is used by the [Fx::SchemaDumper] to
46
+ # populate the `schema.rb` file.
47
+ #
48
+ # @return [Array<Fx::Function>]
49
+ def functions
50
+ Functions.all(connection)
51
+ end
52
+
53
+ # Returns an array of triggers in the database.
54
+ #
55
+ # This collection of triggers is used by the [Fx::SchemaDumper] to
56
+ # populate the `schema.rb` file.
57
+ #
58
+ # @return [Array<Fx::Trigger>]
59
+ def triggers
60
+ Triggers.all(connection)
61
+ end
62
+
63
+ # Creates a function in the database.
64
+ #
65
+ # This is typically called in a migration via
66
+ # {Fx::Statements::Function#create_function}.
67
+ #
68
+ # @param sql_definition The SQL schema for the function.
69
+ #
70
+ # @return [void]
71
+ def create_function(sql_definition)
72
+ execute sql_definition
73
+ end
74
+
75
+ # Creates a trigger in the database.
76
+ #
77
+ # This is typically called in a migration via
78
+ # {Fx::Statements::Trigger#create_trigger}.
79
+ #
80
+ # @param sql_definition The SQL schema for the trigger.
81
+ #
82
+ # @return [void]
83
+ def create_trigger(sql_definition)
84
+ execute sql_definition
85
+ end
86
+
87
+ # Updates a function in the database.
88
+ #
89
+ # This is typically called in a migration via
90
+ # {Fx::Statements::Function#update_function}.
91
+ #
92
+ # @param name The name of the function.
93
+ # @param sql_definition The SQL schema for the function.
94
+ #
95
+ # @return [void]
96
+ def update_function(name, sql_definition)
97
+ drop_function(name)
98
+ create_function(sql_definition)
99
+ end
100
+
101
+ # Updates a trigger in the database.
102
+ #
103
+ # The existing trigger is dropped and recreated using the supplied `on`
104
+ # and `version` parameter.
105
+ #
106
+ # This is typically called in a migration via
107
+ # {Fx::Statements::Function#update_trigger}.
108
+ #
109
+ # @param name The name of the trigger.
110
+ # @param on The associated table for the trigger to drop
111
+ # @param sql_definition The SQL schema for the function.
112
+ #
113
+ # @return [void]
114
+ def update_trigger(name, on:, sql_definition:)
115
+ drop_trigger(name, on: on)
116
+ create_trigger(sql_definition)
117
+ end
118
+
119
+ # Drops the function from the database
120
+ #
121
+ # This is typically called in a migration via
122
+ # {Fx::Statements::Function#drop_function}.
123
+ #
124
+ # @param name The name of the function to drop
125
+ #
126
+ # @return [void]
127
+ def drop_function(name)
128
+ if support_drop_function_without_args
129
+ execute "DROP FUNCTION #{name};"
130
+ else
131
+ execute "DROP FUNCTION #{name}();"
132
+ end
133
+ end
134
+
135
+ # Drops the trigger from the database
136
+ #
137
+ # This is typically called in a migration via
138
+ # {Fx::Statements::Trigger#drop_trigger}.
139
+ #
140
+ # @param name The name of the trigger to drop
141
+ # @param on The associated table for the trigger to drop
142
+ #
143
+ # @return [void]
144
+ def drop_trigger(name, on:)
145
+ execute "DROP TRIGGER #{name} ON #{on};"
146
+ end
147
+
148
+ private
149
+
150
+ attr_reader :connectable
151
+
152
+ delegate :execute, to: :connection
153
+
154
+ def connection
155
+ Connection.new(connectable.connection)
156
+ end
157
+
158
+ def support_drop_function_without_args
159
+ # https://www.postgresql.org/docs/9.6/sql-dropfunction.html
160
+ # https://www.postgresql.org/docs/10/sql-dropfunction.html
161
+
162
+ pg_connection = connectable.connection.raw_connection
163
+ pg_connection.server_version >= 10_00_00
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,43 @@
1
+ module Fx
2
+ module CommandRecorder
3
+ # @api private
4
+ class Arguments
5
+ def initialize(args)
6
+ @args = args.freeze
7
+ end
8
+
9
+ def function
10
+ @args[0]
11
+ end
12
+
13
+ def version
14
+ options[:version]
15
+ end
16
+
17
+ def revert_to_version
18
+ options[:revert_to_version]
19
+ end
20
+
21
+ def invert_version
22
+ Arguments.new([function, options_for_revert])
23
+ end
24
+
25
+ def to_a
26
+ @args.to_a
27
+ end
28
+
29
+ private
30
+
31
+ def options
32
+ @options ||= @args[1] || {}
33
+ end
34
+
35
+ def options_for_revert
36
+ options.clone.tap do |revert_options|
37
+ revert_options[:version] = revert_to_version
38
+ revert_options.delete(:revert_to_version)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,30 @@
1
+ module Fx
2
+ module CommandRecorder
3
+ # @api private
4
+ module Function
5
+ def create_function(*args)
6
+ record(:create_function, args)
7
+ end
8
+
9
+ def drop_function(*args)
10
+ record(:drop_function, args)
11
+ end
12
+
13
+ def update_function(*args)
14
+ record(:update_function, args)
15
+ end
16
+
17
+ def invert_create_function(args)
18
+ [:drop_function, args]
19
+ end
20
+
21
+ def invert_drop_function(args)
22
+ perform_inversion(:create_function, args)
23
+ end
24
+
25
+ def invert_update_function(args)
26
+ perform_inversion(:update_function, args)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ module Fx
2
+ module CommandRecorder
3
+ # @api private
4
+ module Trigger
5
+ def create_trigger(*args)
6
+ record(:create_trigger, args)
7
+ end
8
+
9
+ def drop_trigger(*args)
10
+ record(:drop_trigger, args)
11
+ end
12
+
13
+ def update_trigger(*args)
14
+ record(:update_trigger, args)
15
+ end
16
+
17
+ def invert_create_trigger(args)
18
+ [:drop_trigger, args]
19
+ end
20
+
21
+ def invert_drop_trigger(args)
22
+ perform_inversion(:create_trigger, args)
23
+ end
24
+
25
+ def invert_update_trigger(args)
26
+ perform_inversion(:update_trigger, args)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ require "fx/command_recorder/arguments"
2
+ require "fx/command_recorder/function"
3
+ require "fx/command_recorder/trigger"
4
+
5
+ module Fx
6
+ # @api private
7
+ module CommandRecorder
8
+ include Function
9
+ include Trigger
10
+
11
+ private
12
+
13
+ def perform_inversion(method, args)
14
+ arguments = Arguments.new(args)
15
+
16
+ if arguments.revert_to_version.nil?
17
+ message = "`#{method}` is reversible only if given a `revert_to_version`"
18
+ raise ActiveRecord::IrreversibleMigration, message
19
+ end
20
+
21
+ [method, arguments.invert_version.to_a]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ module Fx
2
+ # @return [Fx::Configuration] F(x)'s current configuration
3
+ def self.configuration
4
+ @_configuration ||= Configuration.new
5
+ end
6
+
7
+ # Set F(x)'s configuration
8
+ #
9
+ # @param config [Fx::Configuration]
10
+ def self.configuration=(config)
11
+ @_configuration = config
12
+ end
13
+
14
+ # Modify F(x)'s current configuration
15
+ #
16
+ # @yieldparam [Fx::Configuration] config current F(x) config
17
+ # ```
18
+ # Fx.configure do |config|
19
+ # config.database = Fx::Adapters::Postgres
20
+ # config.dump_functions_at_beginning_of_schema = true
21
+ # end
22
+ # ```
23
+ def self.configure
24
+ yield configuration
25
+ end
26
+
27
+ # F(x)'s configuration object.
28
+ class Configuration
29
+ # The F(x) database adapter instance to use when executing SQL.
30
+ #
31
+ # Defaults to an instance of {Fx::Adapters::Postgres}
32
+ # @return Fx adapter
33
+ attr_accessor :database
34
+
35
+ # Prioritizes the order in the schema.rb of functions before other
36
+ # statements in order to make directly schema load work when using functions
37
+ # in statements below, i.e.: default column values.
38
+ #
39
+ # Defaults to false
40
+ # @return Boolean
41
+ attr_accessor :dump_functions_at_beginning_of_schema
42
+
43
+ def initialize
44
+ @database = Fx::Adapters::Postgres.new
45
+ @dump_functions_at_beginning_of_schema = false
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ module Fx
2
+ # @api private
3
+ class Definition
4
+ def initialize(name:, version:, type: "function")
5
+ @name = name
6
+ @version = version.to_i
7
+ @type = type
8
+ end
9
+
10
+ def to_sql
11
+ File.read(find_file || full_path).tap do |content|
12
+ if content.empty?
13
+ raise "Define #{@type} in #{path} before migrating."
14
+ end
15
+ end
16
+ end
17
+
18
+ def full_path
19
+ Jets.root.join(path)
20
+ end
21
+
22
+ def path
23
+ @_path ||= File.join("db", @type.pluralize, filename)
24
+ end
25
+
26
+ def version
27
+ @version.to_s.rjust(2, "0")
28
+ end
29
+
30
+ private
31
+
32
+ def filename
33
+ @_filename ||= "#{@name}_v#{version}.sql"
34
+ end
35
+
36
+ def find_file
37
+ migration_paths.lazy
38
+ .map { |migration_path| File.expand_path(File.join("..", "..", path), migration_path) }
39
+ .find { |definition_path| File.exist?(definition_path) }
40
+ end
41
+
42
+ def migration_paths
43
+ [Jets.root.join('db', 'migrate').to_s]
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,26 @@
1
+ module Fx
2
+ # @api private
3
+ class Function
4
+ include Comparable
5
+
6
+ attr_reader :name, :definition
7
+ delegate :<=>, to: :name
8
+
9
+ def initialize(function_row)
10
+ @name = function_row.fetch("name")
11
+ @definition = function_row.fetch("definition")
12
+ end
13
+
14
+ def ==(other)
15
+ name == other.name && definition == other.definition
16
+ end
17
+
18
+ def to_schema
19
+ <<-SCHEMA.indent(2)
20
+ create_function :#{name}, sql_definition: <<-'SQL'
21
+ #{definition.indent(4).rstrip}
22
+ SQL
23
+ SCHEMA
24
+ end
25
+ end
26
+ end
data/lib/fx/railtie.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "rails/railtie"
2
+
3
+ module Fx
4
+ # Automatically initializes Fx in the context of a Rails application when
5
+ # ActiveRecord is loaded.
6
+ #
7
+ # @see Fx.load
8
+ class Railtie < Rails::Railtie
9
+ initializer "fx.load" do
10
+ ActiveSupport.on_load :active_record do
11
+ Fx.load
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ require "rails"
2
+
3
+ module Fx
4
+ module SchemaDumper
5
+ # @api private
6
+ module Function
7
+ def tables(stream)
8
+ if Fx.configuration.dump_functions_at_beginning_of_schema
9
+ functions(stream)
10
+ empty_line(stream)
11
+ end
12
+
13
+ super
14
+
15
+ unless Fx.configuration.dump_functions_at_beginning_of_schema
16
+ functions(stream)
17
+ empty_line(stream)
18
+ end
19
+ end
20
+
21
+ def empty_line(stream)
22
+ stream.puts if dumpable_functions_in_database.any?
23
+ end
24
+
25
+ def functions(stream)
26
+ dumpable_functions_in_database.each do |function|
27
+ stream.puts(function.to_schema)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def dumpable_functions_in_database
34
+ @_dumpable_functions_in_database ||= Fx.database.functions
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,29 @@
1
+ require "rails"
2
+
3
+ module Fx
4
+ module SchemaDumper
5
+ # @api private
6
+ module Trigger
7
+ def tables(stream)
8
+ super
9
+ triggers(stream)
10
+ end
11
+
12
+ def triggers(stream)
13
+ if dumpable_triggers_in_database.any?
14
+ stream.puts
15
+ end
16
+
17
+ dumpable_triggers_in_database.each do |trigger|
18
+ stream.puts(trigger.to_schema)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def dumpable_triggers_in_database
25
+ @_dumpable_triggers_in_database ||= Fx.database.triggers
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ require "fx/schema_dumper/function"
2
+ require "fx/schema_dumper/trigger"
3
+
4
+ module Fx
5
+ # @api private
6
+ module SchemaDumper
7
+ include Function
8
+ include Trigger
9
+ end
10
+ end
@@ -0,0 +1,115 @@
1
+ require "rails"
2
+
3
+ module Fx
4
+ module Statements
5
+ # Methods that are made available in migrations for managing Fx functions.
6
+ module Function
7
+ # Create a new database function.
8
+ #
9
+ # @param name [String, Symbol] The name of the database function.
10
+ # @param version [Fixnum] The version number of the function, used to
11
+ # find the definition file in `db/functions`. This defaults to `1` if
12
+ # not provided.
13
+ # @param sql_definition [String] The SQL query for the function schema.
14
+ # If both `sql_defintion` and `version` are provided,
15
+ # `sql_definition` takes prescedence.
16
+ # @return The database response from executing the create statement.
17
+ #
18
+ # @example Create from `db/functions/uppercase_users_name_v02.sql`
19
+ # create_function(:uppercase_users_name, version: 2)
20
+ #
21
+ # @example Create from provided SQL string
22
+ # create_function(:uppercase_users_name, sql_definition: <<-SQL)
23
+ # CREATE OR REPLACE FUNCTION uppercase_users_name()
24
+ # RETURNS trigger AS $$
25
+ # BEGIN
26
+ # NEW.upper_name = UPPER(NEW.name);
27
+ # RETURN NEW;
28
+ # END;
29
+ # $$ LANGUAGE plpgsql;
30
+ # SQL
31
+ #
32
+ def create_function(name, options = {})
33
+ version = options.fetch(:version, 1)
34
+ sql_definition = options[:sql_definition]
35
+
36
+ if version.nil? && sql_definition.nil?
37
+ raise(
38
+ ArgumentError,
39
+ "version or sql_definition must be specified",
40
+ )
41
+ end
42
+ sql_definition = sql_definition.strip_heredoc if sql_definition
43
+ sql_definition ||= Fx::Definition.new(name: name, version: version).to_sql
44
+
45
+ Fx.database.create_function(sql_definition)
46
+ end
47
+
48
+ # Drop a database function by name.
49
+ #
50
+ # @param name [String, Symbol] The name of the database function.
51
+ # @param revert_to_version [Fixnum] Used to reverse the `drop_function`
52
+ # command on `rake db:rollback`. The provided version will be passed as
53
+ # the `version` argument to {#create_function}.
54
+ # @return The database response from executing the drop statement.
55
+ #
56
+ # @example Drop a function, rolling back to version 2 on rollback
57
+ # drop_function(:uppercase_users_name, revert_to_version: 2)
58
+ #
59
+ def drop_function(name, options = {})
60
+ revert_to_version = options[:revert_to_version]
61
+ Fx.database.drop_function(name)
62
+ end
63
+
64
+ # Update a database function.
65
+ #
66
+ # @param name [String, Symbol] The name of the database function.
67
+ # @param version [Fixnum] The version number of the function, used to
68
+ # find the definition file in `db/functions`. This defaults to `1` if
69
+ # not provided.
70
+ # @param sql_definition [String] The SQL query for the function schema.
71
+ # If both `sql_defintion` and `version` are provided,
72
+ # `sql_definition` takes prescedence.
73
+ # @return The database response from executing the create statement.
74
+ #
75
+ # @example Update function to a given version
76
+ # update_function(
77
+ # :uppercase_users_name,
78
+ # version: 3,
79
+ # revert_to_version: 2,
80
+ # )
81
+ #
82
+ # @example Update function from provided SQL string
83
+ # update_function(:uppercase_users_name, sql_definition: <<-SQL)
84
+ # CREATE OR REPLACE FUNCTION uppercase_users_name()
85
+ # RETURNS trigger AS $$
86
+ # BEGIN
87
+ # NEW.upper_name = UPPER(NEW.name);
88
+ # RETURN NEW;
89
+ # END;
90
+ # $$ LANGUAGE plpgsql;
91
+ # SQL
92
+ #
93
+ def update_function(name, options = {})
94
+ version = options[:version]
95
+ sql_definition = options[:sql_definition]
96
+ revert_to_version = options[:revert_to_version]
97
+
98
+ if version.nil? && sql_definition.nil?
99
+ raise(
100
+ ArgumentError,
101
+ "version or sql_definition must be specified",
102
+ )
103
+ end
104
+
105
+ sql_definition = sql_definition.strip_heredoc if sql_definition
106
+ sql_definition ||= Fx::Definition.new(
107
+ name: name,
108
+ version: version,
109
+ ).to_sql
110
+
111
+ Fx.database.update_function(name, sql_definition)
112
+ end
113
+ end
114
+ end
115
+ end