fx 0.1.0 → 0.11.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 +5 -5
- data/.github/scripts/check-eol +95 -0
- data/.github/workflows/ci.yml +61 -0
- data/.github/workflows/eol-check.yml +30 -0
- data/.rspec +1 -1
- data/.standard.yml +3 -0
- data/CHANGELOG.md +186 -0
- data/CONTRIBUTING.md +13 -4
- data/Gemfile +21 -2
- data/LICENSE +18 -0
- data/README.md +99 -14
- data/Rakefile +11 -1
- data/bin/rake +2 -3
- data/bin/rspec +13 -3
- data/bin/setup +1 -4
- data/bin/standardrb +27 -0
- data/bin/yard +13 -3
- data/fx.gemspec +20 -27
- data/lib/fx/adapters/postgres/connection.rb +6 -0
- data/lib/fx/adapters/postgres/functions.rb +16 -28
- data/lib/fx/adapters/postgres/query_executor.rb +34 -0
- data/lib/fx/adapters/postgres/triggers.rb +14 -28
- data/lib/fx/adapters/postgres.rb +30 -17
- data/lib/fx/command_recorder.rb +104 -13
- data/lib/fx/configuration.rb +10 -25
- data/lib/fx/definition.rb +32 -10
- data/lib/fx/function.rb +23 -9
- data/lib/fx/railtie.rb +15 -0
- data/lib/fx/schema_dumper.rb +33 -10
- data/lib/fx/statements.rb +213 -11
- data/lib/fx/trigger.rb +5 -3
- data/lib/fx/version.rb +1 -1
- data/lib/fx.rb +40 -0
- data/lib/generators/fx/function/USAGE +2 -0
- data/lib/generators/fx/function/function_generator.rb +54 -39
- data/lib/generators/fx/function/templates/db/migrate/create_function.erb +1 -1
- data/lib/generators/fx/function/templates/db/migrate/update_function.erb +1 -1
- data/lib/generators/fx/migration_helper.rb +45 -0
- data/lib/generators/fx/name_helper.rb +33 -0
- data/lib/generators/fx/trigger/USAGE +2 -0
- data/lib/generators/fx/trigger/templates/db/migrate/create_trigger.erb +1 -1
- data/lib/generators/fx/trigger/templates/db/migrate/update_trigger.erb +1 -1
- data/lib/generators/fx/trigger/trigger_generator.rb +46 -51
- data/lib/generators/fx/version_helper.rb +55 -0
- data/spec/acceptance/user_manages_functions_spec.rb +26 -6
- data/spec/acceptance/user_manages_triggers_spec.rb +11 -11
- data/spec/acceptance_helper.rb +17 -11
- data/spec/dummy/Rakefile +8 -1
- data/spec/dummy/bin/bundle +2 -2
- data/spec/dummy/bin/rails +3 -3
- data/spec/dummy/bin/rake +2 -2
- data/spec/dummy/config/application.rb +2 -0
- data/spec/dummy/config/database.yml +2 -0
- data/spec/dummy/config.ru +1 -1
- data/spec/features/functions/migrations_spec.rb +9 -9
- data/spec/features/functions/revert_spec.rb +13 -13
- data/spec/features/triggers/migrations_spec.rb +11 -11
- data/spec/features/triggers/revert_spec.rb +21 -21
- data/spec/fx/adapters/postgres/functions_spec.rb +40 -0
- data/spec/fx/adapters/postgres/query_executor_spec.rb +75 -0
- data/spec/fx/adapters/postgres/triggers_spec.rb +48 -0
- data/spec/fx/adapters/postgres_spec.rb +115 -111
- data/spec/fx/command_recorder_spec.rb +39 -53
- data/spec/fx/configuration_spec.rb +20 -9
- data/spec/fx/definition_spec.rb +53 -38
- data/spec/fx/function_spec.rb +143 -0
- data/spec/fx/schema_dumper_spec.rb +241 -0
- data/spec/fx/statements_spec.rb +219 -0
- data/spec/fx/trigger_spec.rb +52 -0
- data/spec/fx_spec.rb +28 -0
- data/spec/generators/fx/function/function_generator_spec.rb +22 -10
- data/spec/generators/fx/migration_helper_spec.rb +108 -0
- data/spec/generators/fx/name_helper_spec.rb +114 -0
- data/spec/generators/fx/trigger/trigger_generator_spec.rb +55 -20
- data/spec/generators/fx/version_helper_spec.rb +157 -0
- data/spec/spec_helper.rb +16 -4
- data/spec/support/database_reset.rb +24 -0
- data/spec/support/definition_helpers.rb +5 -9
- data/spec/support/generator_setup.rb +46 -5
- data/spec/support/migration_helpers.rb +9 -1
- data/spec/support/warning_helper.rb +5 -0
- metadata +43 -215
- data/.ruby-version +0 -1
- data/.travis.yml +0 -27
- data/Appraisals +0 -21
- data/bin/appraisal +0 -17
- data/gemfiles/rails40.gemfile +0 -8
- data/gemfiles/rails40.gemfile.lock +0 -111
- data/gemfiles/rails41.gemfile +0 -8
- data/gemfiles/rails41.gemfile.lock +0 -113
- data/gemfiles/rails42.gemfile +0 -8
- data/gemfiles/rails42.gemfile.lock +0 -130
- data/gemfiles/rails50.gemfile +0 -8
- data/gemfiles/rails50.gemfile.lock +0 -126
- data/lib/fx/command_recorder/arguments.rb +0 -43
- data/lib/fx/command_recorder/function.rb +0 -30
- data/lib/fx/command_recorder/trigger.rb +0 -30
- data/lib/fx/schema_dumper/function.rb +0 -29
- data/lib/fx/schema_dumper/trigger.rb +0 -29
- data/lib/fx/statements/function.rb +0 -105
- data/lib/fx/statements/trigger.rb +0 -133
- data/spec/fx/command_recorder/arguments_spec.rb +0 -41
- data/spec/fx/schema_dumper/function_spec.rb +0 -22
- data/spec/fx/schema_dumper/trigger_spec.rb +0 -40
- data/spec/fx/statements/function_spec.rb +0 -103
- data/spec/fx/statements/trigger_spec.rb +0 -132
data/bin/setup
CHANGED
data/bin/standardrb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# This file was generated by Bundler.
|
|
6
|
+
#
|
|
7
|
+
# The application 'standardrb' is installed as part of a gem, and
|
|
8
|
+
# this file is here to facilitate running it.
|
|
9
|
+
#
|
|
10
|
+
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
|
14
|
+
|
|
15
|
+
if File.file?(bundle_binstub)
|
|
16
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
17
|
+
load(bundle_binstub)
|
|
18
|
+
else
|
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require "rubygems"
|
|
25
|
+
require "bundler/setup"
|
|
26
|
+
|
|
27
|
+
load Gem.bin_path("standard", "standardrb")
|
data/bin/yard
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
|
+
|
|
3
4
|
#
|
|
4
5
|
# This file was generated by Bundler.
|
|
5
6
|
#
|
|
@@ -7,9 +8,18 @@
|
|
|
7
8
|
# this file is here to facilitate running it.
|
|
8
9
|
#
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
12
|
+
|
|
13
|
+
bundle_binstub = File.expand_path("bundle", __dir__)
|
|
14
|
+
|
|
15
|
+
if File.file?(bundle_binstub)
|
|
16
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
|
17
|
+
load(bundle_binstub)
|
|
18
|
+
else
|
|
19
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
|
20
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
13
23
|
|
|
14
24
|
require "rubygems"
|
|
15
25
|
require "bundler/setup"
|
data/fx.gemspec
CHANGED
|
@@ -1,38 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
3
|
require "fx/version"
|
|
5
4
|
|
|
6
5
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name
|
|
8
|
-
spec.version
|
|
9
|
-
spec.authors
|
|
10
|
-
spec.email
|
|
11
|
-
spec.summary
|
|
12
|
-
spec.description
|
|
6
|
+
spec.name = "fx"
|
|
7
|
+
spec.version = Fx::VERSION
|
|
8
|
+
spec.authors = ["Teo Ljungberg"]
|
|
9
|
+
spec.email = ["teo@teoljungberg.com"]
|
|
10
|
+
spec.summary = "Support for database functions and triggers in Rails migrations"
|
|
11
|
+
spec.description = <<~DESCRIPTION
|
|
13
12
|
Adds methods to ActiveRecord::Migration to create and manage database functions
|
|
14
13
|
and triggers in Rails
|
|
15
14
|
DESCRIPTION
|
|
16
|
-
spec.homepage
|
|
17
|
-
spec.license
|
|
15
|
+
spec.homepage = "https://github.com/teoljungberg/fx"
|
|
16
|
+
spec.license = "MIT"
|
|
17
|
+
spec.metadata = {
|
|
18
|
+
"bug_tracker_uri" => "#{spec.homepage}/issues",
|
|
19
|
+
"changelog_uri" => "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md",
|
|
20
|
+
"homepage_uri" => spec.homepage,
|
|
21
|
+
"source_code_uri" => spec.homepage
|
|
22
|
+
}
|
|
18
23
|
|
|
19
|
-
spec.files
|
|
20
|
-
spec.
|
|
21
|
-
spec.require_paths = ['lib']
|
|
24
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
25
|
+
spec.require_paths = ["lib"]
|
|
22
26
|
|
|
23
|
-
spec.
|
|
24
|
-
spec.
|
|
25
|
-
spec.add_development_dependency "database_cleaner"
|
|
26
|
-
spec.add_development_dependency "rake"
|
|
27
|
-
spec.add_development_dependency "rspec", '>= 3.3'
|
|
28
|
-
spec.add_development_dependency "pg"
|
|
29
|
-
spec.add_development_dependency "pry"
|
|
30
|
-
spec.add_development_dependency "ammeter", '>= 1.1.3'
|
|
31
|
-
spec.add_development_dependency "yard"
|
|
32
|
-
spec.add_development_dependency "redcarpet"
|
|
27
|
+
spec.add_dependency "activerecord", ">= 7.2"
|
|
28
|
+
spec.add_dependency "railties", ">= 7.2"
|
|
33
29
|
|
|
34
|
-
spec.
|
|
35
|
-
spec.add_dependency "railties", '>= 4.0.0'
|
|
36
|
-
|
|
37
|
-
spec.required_ruby_version = "~> 2.1"
|
|
30
|
+
spec.required_ruby_version = ">= 3.3"
|
|
38
31
|
end
|
|
@@ -10,6 +10,12 @@ module Fx
|
|
|
10
10
|
#
|
|
11
11
|
# @api private
|
|
12
12
|
class Connection < SimpleDelegator
|
|
13
|
+
# All supported PostgreSQL versions (14+) support DROP FUNCTION
|
|
14
|
+
# without argument lists.
|
|
15
|
+
# https://www.postgresql.org/docs/10/sql-dropfunction.html
|
|
16
|
+
def support_drop_function_without_args
|
|
17
|
+
true
|
|
18
|
+
end
|
|
13
19
|
end
|
|
14
20
|
end
|
|
15
21
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "fx/function"
|
|
2
|
+
require "fx/adapters/postgres/query_executor"
|
|
2
3
|
|
|
3
4
|
module Fx
|
|
4
5
|
module Adapters
|
|
@@ -8,46 +9,33 @@ module Fx
|
|
|
8
9
|
class Functions
|
|
9
10
|
# The SQL query used by F(x) to retrieve the functions considered
|
|
10
11
|
# dumpable into `db/schema.rb`.
|
|
11
|
-
FUNCTIONS_WITH_DEFINITIONS_QUERY = <<~SQL
|
|
12
|
+
FUNCTIONS_WITH_DEFINITIONS_QUERY = <<~SQL.freeze
|
|
12
13
|
SELECT
|
|
13
14
|
pp.proname AS name,
|
|
14
|
-
pg_get_functiondef(pp.oid) AS definition
|
|
15
|
+
pg_get_functiondef(pp.oid) AS definition,
|
|
16
|
+
pg_get_function_identity_arguments(pp.oid) AS arguments
|
|
15
17
|
FROM pg_proc pp
|
|
16
18
|
JOIN pg_namespace pn
|
|
17
19
|
ON pn.oid = pp.pronamespace
|
|
18
20
|
LEFT JOIN pg_depend pd
|
|
19
21
|
ON pd.objid = pp.oid AND pd.deptype = 'e'
|
|
20
|
-
|
|
22
|
+
LEFT JOIN pg_aggregate pa
|
|
23
|
+
ON pa.aggfnoid = pp.oid
|
|
24
|
+
WHERE pn.nspname = ANY (current_schemas(false))
|
|
25
|
+
AND pd.objid IS NULL
|
|
26
|
+
AND pa.aggfnoid IS NULL
|
|
27
|
+
ORDER BY pp.oid;
|
|
21
28
|
SQL
|
|
22
29
|
|
|
23
30
|
# Wraps #all as a static facade.
|
|
24
31
|
#
|
|
25
32
|
# @return [Array<Fx::Function>]
|
|
26
|
-
def self.all(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# All of the functions that this connection has defined.
|
|
35
|
-
#
|
|
36
|
-
# @return [Array<Fx::Function>]
|
|
37
|
-
def all
|
|
38
|
-
functions_from_postgres.map { |function| to_fx_function(function) }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
attr_reader :connection
|
|
44
|
-
|
|
45
|
-
def functions_from_postgres
|
|
46
|
-
connection.execute(FUNCTIONS_WITH_DEFINITIONS_QUERY)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def to_fx_function(result)
|
|
50
|
-
Fx::Function.new(result)
|
|
33
|
+
def self.all(connection)
|
|
34
|
+
Fx::Adapters::Postgres::QueryExecutor.call(
|
|
35
|
+
connection: connection,
|
|
36
|
+
query: FUNCTIONS_WITH_DEFINITIONS_QUERY,
|
|
37
|
+
model_class: Fx::Function
|
|
38
|
+
)
|
|
51
39
|
end
|
|
52
40
|
end
|
|
53
41
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Fx
|
|
2
|
+
module Adapters
|
|
3
|
+
class Postgres
|
|
4
|
+
# Executes database queries and maps results to domain objects.
|
|
5
|
+
# @api private
|
|
6
|
+
class QueryExecutor
|
|
7
|
+
def self.call(...)
|
|
8
|
+
new(...).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(connection:, query:, model_class:)
|
|
12
|
+
@connection = connection
|
|
13
|
+
@query = query
|
|
14
|
+
@model_class = model_class
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Executes the query and maps results to domain objects.
|
|
18
|
+
#
|
|
19
|
+
# @return [Array] Array of domain objects (Functions or Triggers)
|
|
20
|
+
def call
|
|
21
|
+
results_from_postgres.map { |result| model_class.new(result) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :connection, :query, :model_class
|
|
27
|
+
|
|
28
|
+
def results_from_postgres
|
|
29
|
+
connection.execute(query)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "fx/trigger"
|
|
2
|
+
require "fx/adapters/postgres/query_executor"
|
|
2
3
|
|
|
3
4
|
module Fx
|
|
4
5
|
module Adapters
|
|
@@ -8,7 +9,7 @@ module Fx
|
|
|
8
9
|
class Triggers
|
|
9
10
|
# The SQL query used by F(x) to retrieve the triggers considered
|
|
10
11
|
# dumpable into `db/schema.rb`.
|
|
11
|
-
TRIGGERS_WITH_DEFINITIONS_QUERY = <<~SQL
|
|
12
|
+
TRIGGERS_WITH_DEFINITIONS_QUERY = <<~SQL.freeze
|
|
12
13
|
SELECT
|
|
13
14
|
pt.tgname AS name,
|
|
14
15
|
pg_get_triggerdef(pt.oid) AS definition
|
|
@@ -17,38 +18,23 @@ module Fx
|
|
|
17
18
|
ON (pc.oid = pt.tgrelid)
|
|
18
19
|
JOIN pg_proc pp
|
|
19
20
|
ON (pp.oid = pt.tgfoid)
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
JOIN pg_namespace pn
|
|
22
|
+
ON pn.oid = pc.relnamespace
|
|
23
|
+
WHERE pn.nspname = ANY (current_schemas(false))
|
|
24
|
+
AND pt.tgname NOT ILIKE '%constraint%'
|
|
25
|
+
AND pt.tgname NOT ILIKE 'pg%'
|
|
26
|
+
ORDER BY pc.oid;
|
|
22
27
|
SQL
|
|
23
28
|
|
|
24
29
|
# Wraps #all as a static facade.
|
|
25
30
|
#
|
|
26
31
|
# @return [Array<Fx::Trigger>]
|
|
27
|
-
def self.all(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# All of the triggers that this connection has defined.
|
|
36
|
-
#
|
|
37
|
-
# @return [Array<Fx::Trigger>]
|
|
38
|
-
def all
|
|
39
|
-
triggers_from_postgres.map { |trigger| to_fx_trigger(trigger) }
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
attr_reader :connection
|
|
45
|
-
|
|
46
|
-
def triggers_from_postgres
|
|
47
|
-
connection.execute(TRIGGERS_WITH_DEFINITIONS_QUERY)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def to_fx_trigger(result)
|
|
51
|
-
Fx::Trigger.new(result)
|
|
32
|
+
def self.all(connection)
|
|
33
|
+
Fx::Adapters::Postgres::QueryExecutor.call(
|
|
34
|
+
connection: connection,
|
|
35
|
+
query: TRIGGERS_WITH_DEFINITIONS_QUERY,
|
|
36
|
+
model_class: Fx::Trigger
|
|
37
|
+
)
|
|
52
38
|
end
|
|
53
39
|
end
|
|
54
40
|
end
|
data/lib/fx/adapters/postgres.rb
CHANGED
|
@@ -23,6 +23,19 @@ module Fx
|
|
|
23
23
|
# config.adapter = Fx::Adapters::Postgres.new
|
|
24
24
|
# end
|
|
25
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
|
|
26
39
|
def initialize(connectable = ActiveRecord::Base)
|
|
27
40
|
@connectable = connectable
|
|
28
41
|
end
|
|
@@ -34,7 +47,7 @@ module Fx
|
|
|
34
47
|
#
|
|
35
48
|
# @return [Array<Fx::Function>]
|
|
36
49
|
def functions
|
|
37
|
-
Functions.all(connection)
|
|
50
|
+
Fx::Adapters::Postgres::Functions.all(connection)
|
|
38
51
|
end
|
|
39
52
|
|
|
40
53
|
# Returns an array of triggers in the database.
|
|
@@ -44,7 +57,7 @@ module Fx
|
|
|
44
57
|
#
|
|
45
58
|
# @return [Array<Fx::Trigger>]
|
|
46
59
|
def triggers
|
|
47
|
-
Triggers.all(connection)
|
|
60
|
+
Fx::Adapters::Postgres::Triggers.all(connection)
|
|
48
61
|
end
|
|
49
62
|
|
|
50
63
|
# Creates a function in the database.
|
|
@@ -52,11 +65,11 @@ module Fx
|
|
|
52
65
|
# This is typically called in a migration via
|
|
53
66
|
# {Fx::Statements::Function#create_function}.
|
|
54
67
|
#
|
|
55
|
-
# @param sql_definition The SQL schema for the function.
|
|
68
|
+
# @param sql_definition [String] The SQL schema for the function.
|
|
56
69
|
#
|
|
57
70
|
# @return [void]
|
|
58
71
|
def create_function(sql_definition)
|
|
59
|
-
execute
|
|
72
|
+
execute(sql_definition)
|
|
60
73
|
end
|
|
61
74
|
|
|
62
75
|
# Creates a trigger in the database.
|
|
@@ -64,11 +77,11 @@ module Fx
|
|
|
64
77
|
# This is typically called in a migration via
|
|
65
78
|
# {Fx::Statements::Trigger#create_trigger}.
|
|
66
79
|
#
|
|
67
|
-
# @param sql_definition The SQL schema for the trigger.
|
|
80
|
+
# @param sql_definition [String] The SQL schema for the trigger.
|
|
68
81
|
#
|
|
69
82
|
# @return [void]
|
|
70
83
|
def create_trigger(sql_definition)
|
|
71
|
-
execute
|
|
84
|
+
execute(sql_definition)
|
|
72
85
|
end
|
|
73
86
|
|
|
74
87
|
# Updates a function in the database.
|
|
@@ -76,8 +89,8 @@ module Fx
|
|
|
76
89
|
# This is typically called in a migration via
|
|
77
90
|
# {Fx::Statements::Function#update_function}.
|
|
78
91
|
#
|
|
79
|
-
# @param name The name of the function.
|
|
80
|
-
# @param sql_definition The SQL schema for the function.
|
|
92
|
+
# @param name [String, Symbol] The name of the function.
|
|
93
|
+
# @param sql_definition [String] The SQL schema for the function.
|
|
81
94
|
#
|
|
82
95
|
# @return [void]
|
|
83
96
|
def update_function(name, sql_definition)
|
|
@@ -93,9 +106,9 @@ module Fx
|
|
|
93
106
|
# This is typically called in a migration via
|
|
94
107
|
# {Fx::Statements::Function#update_trigger}.
|
|
95
108
|
#
|
|
96
|
-
# @param name The name of the trigger.
|
|
97
|
-
# @param on The associated table for the trigger to
|
|
98
|
-
# @param sql_definition The SQL schema for the
|
|
109
|
+
# @param name [String, Symbol] The name of the trigger.
|
|
110
|
+
# @param on [String, Symbol] The associated table for the trigger to update
|
|
111
|
+
# @param sql_definition [String] The SQL schema for the trigger.
|
|
99
112
|
#
|
|
100
113
|
# @return [void]
|
|
101
114
|
def update_trigger(name, on:, sql_definition:)
|
|
@@ -108,11 +121,11 @@ module Fx
|
|
|
108
121
|
# This is typically called in a migration via
|
|
109
122
|
# {Fx::Statements::Function#drop_function}.
|
|
110
123
|
#
|
|
111
|
-
# @param name The name of the function to drop
|
|
124
|
+
# @param name [String, Symbol] The name of the function to drop
|
|
112
125
|
#
|
|
113
126
|
# @return [void]
|
|
114
127
|
def drop_function(name)
|
|
115
|
-
execute
|
|
128
|
+
execute("DROP FUNCTION #{name};")
|
|
116
129
|
end
|
|
117
130
|
|
|
118
131
|
# Drops the trigger from the database
|
|
@@ -120,12 +133,12 @@ module Fx
|
|
|
120
133
|
# This is typically called in a migration via
|
|
121
134
|
# {Fx::Statements::Trigger#drop_trigger}.
|
|
122
135
|
#
|
|
123
|
-
# @param name The name of the trigger to drop
|
|
124
|
-
# @param on The associated table for the trigger to drop
|
|
136
|
+
# @param name [String, Symbol] The name of the trigger to drop
|
|
137
|
+
# @param on [String, Symbol] The associated table for the trigger to drop
|
|
125
138
|
#
|
|
126
139
|
# @return [void]
|
|
127
140
|
def drop_trigger(name, on:)
|
|
128
|
-
execute
|
|
141
|
+
execute("DROP TRIGGER #{name} ON #{on};")
|
|
129
142
|
end
|
|
130
143
|
|
|
131
144
|
private
|
|
@@ -135,7 +148,7 @@ module Fx
|
|
|
135
148
|
delegate :execute, to: :connection
|
|
136
149
|
|
|
137
150
|
def connection
|
|
138
|
-
Connection.new(connectable.connection)
|
|
151
|
+
Fx::Adapters::Postgres::Connection.new(connectable.connection)
|
|
139
152
|
end
|
|
140
153
|
end
|
|
141
154
|
end
|
data/lib/fx/command_recorder.rb
CHANGED
|
@@ -1,29 +1,120 @@
|
|
|
1
|
-
require "fx/command_recorder/arguments"
|
|
2
|
-
require "fx/command_recorder/function"
|
|
3
|
-
require "fx/command_recorder/trigger"
|
|
4
|
-
|
|
5
1
|
module Fx
|
|
6
2
|
# @api private
|
|
7
3
|
module CommandRecorder
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
def create_function(*args, &block)
|
|
5
|
+
record(:create_function, args, &block)
|
|
6
|
+
end
|
|
7
|
+
ruby2_keywords :create_function
|
|
8
|
+
|
|
9
|
+
def drop_function(*args, &block)
|
|
10
|
+
record(:drop_function, args, &block)
|
|
11
|
+
end
|
|
12
|
+
ruby2_keywords :drop_function
|
|
13
|
+
|
|
14
|
+
def update_function(*args, &block)
|
|
15
|
+
record(:update_function, args, &block)
|
|
16
|
+
end
|
|
17
|
+
ruby2_keywords :update_function
|
|
18
|
+
|
|
19
|
+
def invert_create_function(args)
|
|
20
|
+
[:drop_function, args]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def invert_drop_function(args)
|
|
24
|
+
perform_inversion(:create_function, args)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def invert_update_function(args)
|
|
28
|
+
perform_inversion(:update_function, args)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def create_trigger(*args, &block)
|
|
32
|
+
record(:create_trigger, args, &block)
|
|
33
|
+
end
|
|
34
|
+
ruby2_keywords :create_trigger
|
|
35
|
+
|
|
36
|
+
def drop_trigger(*args, &block)
|
|
37
|
+
record(:drop_trigger, args, &block)
|
|
38
|
+
end
|
|
39
|
+
ruby2_keywords :drop_trigger
|
|
40
|
+
|
|
41
|
+
def update_trigger(*args, &block)
|
|
42
|
+
record(:update_trigger, args, &block)
|
|
43
|
+
end
|
|
44
|
+
ruby2_keywords :update_trigger
|
|
45
|
+
|
|
46
|
+
def invert_create_trigger(args)
|
|
47
|
+
[:drop_trigger, args]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def invert_drop_trigger(args)
|
|
51
|
+
perform_inversion(:create_trigger, args)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def invert_update_trigger(args)
|
|
55
|
+
perform_inversion(:update_trigger, args)
|
|
56
|
+
end
|
|
10
57
|
|
|
11
58
|
private
|
|
12
59
|
|
|
60
|
+
MESSAGE_IRREVERSIBLE = "`%s` is reversible only if given a `revert_to_version`".freeze
|
|
61
|
+
|
|
13
62
|
def perform_inversion(method, args)
|
|
14
63
|
arguments = Arguments.new(args)
|
|
15
64
|
|
|
16
65
|
if arguments.revert_to_version.nil?
|
|
17
|
-
|
|
18
|
-
raise ActiveRecord::IrreversibleMigration, message
|
|
66
|
+
raise ActiveRecord::IrreversibleMigration, format(MESSAGE_IRREVERSIBLE, method)
|
|
19
67
|
end
|
|
20
68
|
|
|
21
69
|
[method, arguments.invert_version.to_a]
|
|
22
70
|
end
|
|
71
|
+
|
|
72
|
+
class Arguments
|
|
73
|
+
def initialize(args)
|
|
74
|
+
@args = args.freeze
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def function
|
|
78
|
+
args.fetch(0)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def version
|
|
82
|
+
options.fetch(:version)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def revert_to_version
|
|
86
|
+
options.fetch(:revert_to_version, nil)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def invert_version
|
|
90
|
+
self.class.new([function, options_for_revert])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def to_a
|
|
94
|
+
args.to_a
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
attr_reader :args
|
|
100
|
+
|
|
101
|
+
def options
|
|
102
|
+
@options ||= args.fetch(1, {}).dup
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def options_for_revert
|
|
106
|
+
opts = options.clone.tap do |revert_options|
|
|
107
|
+
revert_options[:version] = revert_to_version
|
|
108
|
+
revert_options.delete(:revert_to_version)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
keyword_hash(opts)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def keyword_hash(hash)
|
|
115
|
+
Hash.ruby2_keywords_hash(hash)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
private_constant :Arguments
|
|
23
119
|
end
|
|
24
120
|
end
|
|
25
|
-
|
|
26
|
-
ActiveRecord::Migration::CommandRecorder.send(
|
|
27
|
-
:include,
|
|
28
|
-
Fx::CommandRecorder,
|
|
29
|
-
)
|
data/lib/fx/configuration.rb
CHANGED
|
@@ -1,38 +1,23 @@
|
|
|
1
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
|
-
# end
|
|
21
|
-
# ```
|
|
22
|
-
def self.configure
|
|
23
|
-
yield configuration
|
|
24
|
-
end
|
|
25
|
-
|
|
26
2
|
# F(x)'s configuration object.
|
|
27
3
|
class Configuration
|
|
28
4
|
# The F(x) database adapter instance to use when executing SQL.
|
|
29
5
|
#
|
|
30
6
|
# Defaults to an instance of {Fx::Adapters::Postgres}
|
|
31
|
-
# @return Fx adapter
|
|
7
|
+
# @return [Fx::Adapters::Postgres] Fx adapter
|
|
32
8
|
attr_accessor :database
|
|
33
9
|
|
|
10
|
+
# Prioritizes the order in the schema.rb of functions before other
|
|
11
|
+
# statements in order to make directly schema load work when using functions
|
|
12
|
+
# in statements below, i.e.: default column values.
|
|
13
|
+
#
|
|
14
|
+
# Defaults to false
|
|
15
|
+
# @return [Boolean] Boolean
|
|
16
|
+
attr_accessor :dump_functions_at_beginning_of_schema
|
|
17
|
+
|
|
34
18
|
def initialize
|
|
35
19
|
@database = Fx::Adapters::Postgres.new
|
|
20
|
+
@dump_functions_at_beginning_of_schema = false
|
|
36
21
|
end
|
|
37
22
|
end
|
|
38
23
|
end
|
data/lib/fx/definition.rb
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
module Fx
|
|
2
2
|
# @api private
|
|
3
3
|
class Definition
|
|
4
|
-
|
|
4
|
+
FUNCTION = "function".freeze
|
|
5
|
+
TRIGGER = "trigger".freeze
|
|
6
|
+
|
|
7
|
+
def self.function(name:, version:)
|
|
8
|
+
new(name: name, version: version, type: FUNCTION)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.trigger(name:, version:)
|
|
12
|
+
new(name: name, version: version, type: TRIGGER)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(name:, version:, type:)
|
|
5
16
|
@name = name
|
|
6
|
-
@
|
|
17
|
+
@version_number = version.to_i
|
|
7
18
|
@type = type
|
|
8
19
|
end
|
|
9
20
|
|
|
10
21
|
def to_sql
|
|
11
|
-
File.read(full_path)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
22
|
+
content = File.read(find_file || full_path)
|
|
23
|
+
raise "Define #{type} in #{path} before migrating." if content.empty?
|
|
24
|
+
|
|
25
|
+
content
|
|
16
26
|
end
|
|
17
27
|
|
|
18
28
|
def full_path
|
|
@@ -20,17 +30,29 @@ module Fx
|
|
|
20
30
|
end
|
|
21
31
|
|
|
22
32
|
def path
|
|
23
|
-
File.join("db",
|
|
33
|
+
@_path ||= File.join("db", type.pluralize, filename)
|
|
24
34
|
end
|
|
25
35
|
|
|
26
36
|
def version
|
|
27
|
-
|
|
37
|
+
version_number.to_s.rjust(2, "0")
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
private
|
|
31
41
|
|
|
42
|
+
attr_reader :name, :version_number, :type
|
|
43
|
+
|
|
32
44
|
def filename
|
|
33
|
-
"#{
|
|
45
|
+
@_filename ||= "#{name}_v#{version}.sql"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def find_file
|
|
49
|
+
migration_paths.lazy
|
|
50
|
+
.map { |migration_path| File.expand_path(File.join("..", "..", path), migration_path) }
|
|
51
|
+
.find { |definition_path| File.exist?(definition_path) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def migration_paths
|
|
55
|
+
Rails.application.config.paths["db/migrate"].expanded
|
|
34
56
|
end
|
|
35
57
|
end
|
|
36
58
|
end
|