fx 0.9.0 → 0.10.1
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 +4 -4
- data/.github/workflows/ci.yml +4 -4
- data/CHANGELOG.md +43 -1
- data/Gemfile +5 -2
- data/README.md +16 -0
- data/bin/rake +2 -3
- data/bin/rspec +13 -3
- data/bin/yard +13 -3
- data/fx.gemspec +3 -3
- data/lib/fx/adapters/postgres/connection.rb +12 -4
- data/lib/fx/adapters/postgres/functions.rb +11 -28
- data/lib/fx/adapters/postgres/query_executor.rb +34 -0
- data/lib/fx/adapters/postgres/triggers.rb +14 -29
- data/lib/fx/adapters/postgres.rb +13 -13
- data/lib/fx/command_recorder.rb +5 -3
- data/lib/fx/configuration.rb +2 -2
- data/lib/fx/definition.rb +7 -5
- data/lib/fx/schema_dumper.rb +5 -17
- data/lib/fx/statements.rb +61 -58
- data/lib/fx/version.rb +1 -1
- data/lib/fx.rb +1 -1
- data/lib/generators/fx/function/function_generator.rb +48 -55
- 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/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 +43 -66
- data/lib/generators/fx/version_helper.rb +55 -0
- data/spec/acceptance/user_manages_functions_spec.rb +6 -6
- data/spec/acceptance/user_manages_triggers_spec.rb +10 -10
- data/spec/acceptance_helper.rb +17 -14
- data/spec/dummy/config/application.rb +1 -5
- data/spec/features/functions/migrations_spec.rb +4 -4
- data/spec/features/functions/revert_spec.rb +4 -4
- data/spec/features/triggers/migrations_spec.rb +6 -6
- data/spec/features/triggers/revert_spec.rb +8 -8
- data/spec/fx/adapters/postgres/functions_spec.rb +12 -5
- data/spec/fx/adapters/postgres/query_executor_spec.rb +75 -0
- data/spec/fx/adapters/postgres/triggers_spec.rb +14 -7
- data/spec/fx/adapters/postgres_spec.rb +62 -20
- data/spec/fx/definition_spec.rb +6 -6
- data/spec/fx/schema_dumper_spec.rb +132 -14
- data/spec/fx_spec.rb +1 -1
- data/spec/generators/fx/function/function_generator_spec.rb +10 -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 +42 -19
- data/spec/generators/fx/version_helper_spec.rb +157 -0
- data/spec/spec_helper.rb +11 -4
- data/spec/support/database_reset.rb +24 -0
- data/spec/support/generator_setup.rb +46 -5
- metadata +30 -12
|
@@ -1,39 +1,43 @@
|
|
|
1
1
|
require "rails/generators"
|
|
2
2
|
require "rails/generators/active_record"
|
|
3
|
+
require "generators/fx/version_helper"
|
|
4
|
+
require "generators/fx/migration_helper"
|
|
5
|
+
require "generators/fx/name_helper"
|
|
3
6
|
|
|
4
7
|
module Fx
|
|
5
8
|
module Generators
|
|
6
9
|
# @api private
|
|
7
10
|
class TriggerGenerator < Rails::Generators::NamedBase
|
|
8
11
|
include Rails::Generators::Migration
|
|
12
|
+
|
|
9
13
|
source_root File.expand_path("../templates", __FILE__)
|
|
10
14
|
argument :table_name, type: :hash, required: true
|
|
11
15
|
|
|
16
|
+
DEFINITION_PATH = %w[db triggers].freeze
|
|
17
|
+
|
|
12
18
|
class_option :migration, type: :boolean
|
|
13
19
|
|
|
14
20
|
def create_triggers_directory
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
return if trigger_definition_path.exist?
|
|
22
|
+
|
|
23
|
+
empty_directory(trigger_definition_path)
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def create_trigger_definition
|
|
21
|
-
create_file
|
|
27
|
+
create_file(definition.path)
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def create_migration_file
|
|
25
|
-
return if
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
end
|
|
31
|
+
return if migration_helper.skip_creation?
|
|
32
|
+
|
|
33
|
+
template_info = migration_helper.migration_template_info(
|
|
34
|
+
object_type: :trigger,
|
|
35
|
+
file_name: file_name,
|
|
36
|
+
updating_existing: version_helper.updating_existing?,
|
|
37
|
+
version: version_helper.current_version
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
migration_template(template_info[:template], template_info[:filename])
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
def self.next_migration_number(dir)
|
|
@@ -42,84 +46,57 @@ module Fx
|
|
|
42
46
|
|
|
43
47
|
no_tasks do
|
|
44
48
|
def previous_version
|
|
45
|
-
|
|
46
|
-
.map { |name| version_regex.match(name).try(:[], "version").to_i }
|
|
47
|
-
.max
|
|
49
|
+
version_helper.previous_version
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def version
|
|
51
|
-
|
|
53
|
+
version_helper.current_version
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
def migration_class_name
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
+
if version_helper.updating_existing?
|
|
58
|
+
migration_helper.update_migration_class_name(
|
|
59
|
+
object_type: :trigger,
|
|
60
|
+
class_name: class_name,
|
|
61
|
+
version: version
|
|
62
|
+
)
|
|
57
63
|
else
|
|
58
64
|
super
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
|
|
62
|
-
def activerecord_migration_class
|
|
63
|
-
if ActiveRecord::Migration.respond_to?(:current_version)
|
|
64
|
-
"ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
|
|
65
|
-
else
|
|
66
|
-
"ActiveRecord::Migration"
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
68
|
def formatted_name
|
|
71
|
-
|
|
72
|
-
"\"#{singular_name}\""
|
|
73
|
-
else
|
|
74
|
-
":#{singular_name}"
|
|
75
|
-
end
|
|
69
|
+
Fx::Generators::NameHelper.format_for_migration(singular_name)
|
|
76
70
|
end
|
|
77
71
|
|
|
78
72
|
def formatted_table_name
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if name.nil?
|
|
82
|
-
raise(
|
|
83
|
-
ArgumentError,
|
|
84
|
-
"Either `table_name:NAME` or `on:NAME` must be specified"
|
|
85
|
-
)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
if name.include?(".")
|
|
89
|
-
"\"#{name}\""
|
|
90
|
-
else
|
|
91
|
-
":#{name}"
|
|
92
|
-
end
|
|
73
|
+
Fx::Generators::NameHelper.format_table_name_from_hash(table_name)
|
|
93
74
|
end
|
|
94
75
|
end
|
|
95
76
|
|
|
96
77
|
private
|
|
97
78
|
|
|
98
|
-
def
|
|
99
|
-
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def updating_existing_trigger?
|
|
103
|
-
previous_version > 0
|
|
79
|
+
def trigger_definition_path
|
|
80
|
+
@_trigger_definition_path ||= Rails.root.join(*DEFINITION_PATH)
|
|
104
81
|
end
|
|
105
82
|
|
|
106
|
-
def
|
|
107
|
-
Fx::
|
|
83
|
+
def version_helper
|
|
84
|
+
@_version_helper ||= Fx::Generators::VersionHelper.new(
|
|
85
|
+
file_name: file_name,
|
|
86
|
+
definition_path: trigger_definition_path
|
|
87
|
+
)
|
|
108
88
|
end
|
|
109
89
|
|
|
110
|
-
def
|
|
111
|
-
@
|
|
90
|
+
def migration_helper
|
|
91
|
+
@_migration_helper ||= Fx::Generators::MigrationHelper.new(options)
|
|
112
92
|
end
|
|
113
93
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def skip_migration_creation?
|
|
117
|
-
!migration
|
|
94
|
+
def definition
|
|
95
|
+
version_helper.definition_for_version(version: version, type: :trigger)
|
|
118
96
|
end
|
|
119
97
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
options[:migration] != false
|
|
98
|
+
def updating_existing_trigger?
|
|
99
|
+
version_helper.updating_existing?
|
|
123
100
|
end
|
|
124
101
|
end
|
|
125
102
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Fx
|
|
2
|
+
module Generators
|
|
3
|
+
# @api private
|
|
4
|
+
class VersionHelper
|
|
5
|
+
def initialize(file_name:, definition_path:)
|
|
6
|
+
@file_name = file_name
|
|
7
|
+
@definition_path = definition_path
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def previous_version
|
|
11
|
+
@previous_version ||= existing_versions.max || 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def current_version
|
|
15
|
+
previous_version.next
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def updating_existing?
|
|
19
|
+
previous_version > 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def creating_new?
|
|
23
|
+
previous_version == 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def definition_for_version(version:, type:)
|
|
27
|
+
case type
|
|
28
|
+
when :function
|
|
29
|
+
Fx::Definition.function(name: file_name, version: version)
|
|
30
|
+
when :trigger
|
|
31
|
+
Fx::Definition.trigger(name: file_name, version: version)
|
|
32
|
+
else
|
|
33
|
+
raise(
|
|
34
|
+
ArgumentError,
|
|
35
|
+
"Unknown type: #{type}. Must be :function or :trigger"
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
VERSION_PATTERN = /v(\d+)/
|
|
43
|
+
private_constant :VERSION_PATTERN
|
|
44
|
+
|
|
45
|
+
attr_reader :file_name, :definition_path
|
|
46
|
+
|
|
47
|
+
def existing_versions
|
|
48
|
+
Dir
|
|
49
|
+
.glob("#{file_name}_v*.sql", base: definition_path)
|
|
50
|
+
.map { |file| file[VERSION_PATTERN, 1].to_i }
|
|
51
|
+
.compact
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -3,14 +3,14 @@ require "acceptance_helper"
|
|
|
3
3
|
RSpec.describe "User manages functions" do
|
|
4
4
|
it "handles simple functions" do
|
|
5
5
|
successfully "rails generate fx:function test"
|
|
6
|
-
write_function_definition "test_v01", <<~
|
|
6
|
+
write_function_definition "test_v01", <<~SQL
|
|
7
7
|
CREATE OR REPLACE FUNCTION test()
|
|
8
8
|
RETURNS text AS $$
|
|
9
9
|
BEGIN
|
|
10
10
|
RETURN 'test';
|
|
11
11
|
END;
|
|
12
12
|
$$ LANGUAGE plpgsql;
|
|
13
|
-
|
|
13
|
+
SQL
|
|
14
14
|
successfully "rake db:migrate"
|
|
15
15
|
|
|
16
16
|
result = execute("SELECT * FROM test() AS result")
|
|
@@ -21,14 +21,14 @@ RSpec.describe "User manages functions" do
|
|
|
21
21
|
"db/functions/test_v01.sql",
|
|
22
22
|
"db/functions/test_v02.sql"
|
|
23
23
|
)
|
|
24
|
-
write_function_definition "test_v02", <<~
|
|
24
|
+
write_function_definition "test_v02", <<~SQL
|
|
25
25
|
CREATE OR REPLACE FUNCTION test()
|
|
26
26
|
RETURNS text AS $$
|
|
27
27
|
BEGIN
|
|
28
28
|
RETURN 'testest';
|
|
29
29
|
END;
|
|
30
30
|
$$ LANGUAGE plpgsql;
|
|
31
|
-
|
|
31
|
+
SQL
|
|
32
32
|
successfully "rake db:migrate"
|
|
33
33
|
|
|
34
34
|
result = execute("SELECT * FROM test() AS result")
|
|
@@ -37,14 +37,14 @@ RSpec.describe "User manages functions" do
|
|
|
37
37
|
|
|
38
38
|
it "handles functions with arguments" do
|
|
39
39
|
successfully "rails generate fx:function adder"
|
|
40
|
-
write_function_definition "adder_v01", <<~
|
|
40
|
+
write_function_definition "adder_v01", <<~SQL
|
|
41
41
|
CREATE FUNCTION adder(x int, y int)
|
|
42
42
|
RETURNS int AS $$
|
|
43
43
|
BEGIN
|
|
44
44
|
RETURN $1 + $2;
|
|
45
45
|
END;
|
|
46
46
|
$$ LANGUAGE plpgsql;
|
|
47
|
-
|
|
47
|
+
SQL
|
|
48
48
|
successfully "rake db:migrate"
|
|
49
49
|
|
|
50
50
|
result = execute("SELECT * FROM adder(1, 2) AS result")
|
|
@@ -4,7 +4,7 @@ RSpec.describe "User manages triggers" do
|
|
|
4
4
|
it "handles simple triggers" do
|
|
5
5
|
successfully "rails generate model user name:string upper_name:string"
|
|
6
6
|
successfully "rails generate fx:function uppercase_users_name"
|
|
7
|
-
write_function_definition "uppercase_users_name_v01", <<~
|
|
7
|
+
write_function_definition "uppercase_users_name_v01", <<~SQL
|
|
8
8
|
CREATE OR REPLACE FUNCTION uppercase_users_name()
|
|
9
9
|
RETURNS trigger AS $$
|
|
10
10
|
BEGIN
|
|
@@ -12,38 +12,38 @@ RSpec.describe "User manages triggers" do
|
|
|
12
12
|
RETURN NEW;
|
|
13
13
|
END;
|
|
14
14
|
$$ LANGUAGE plpgsql;
|
|
15
|
-
|
|
15
|
+
SQL
|
|
16
16
|
successfully "rails generate fx:trigger uppercase_users_name table_name:users"
|
|
17
|
-
write_trigger_definition "uppercase_users_name_v01", <<~
|
|
17
|
+
write_trigger_definition "uppercase_users_name_v01", <<~SQL
|
|
18
18
|
CREATE TRIGGER uppercase_users_name
|
|
19
19
|
BEFORE INSERT ON users
|
|
20
20
|
FOR EACH ROW
|
|
21
21
|
EXECUTE FUNCTION uppercase_users_name();
|
|
22
|
-
|
|
22
|
+
SQL
|
|
23
23
|
successfully "rake db:migrate"
|
|
24
24
|
|
|
25
|
-
execute <<~
|
|
25
|
+
execute <<~SQL
|
|
26
26
|
INSERT INTO users
|
|
27
27
|
(name, created_at, updated_at)
|
|
28
28
|
VALUES
|
|
29
29
|
('Bob', NOW(), NOW());
|
|
30
|
-
|
|
30
|
+
SQL
|
|
31
31
|
result = execute("SELECT upper_name FROM users WHERE name = 'Bob';")
|
|
32
32
|
expect(result).to eq("upper_name" => "BOB")
|
|
33
33
|
|
|
34
34
|
successfully "rails generate fx:trigger uppercase_users_name table_name:users"
|
|
35
|
-
write_trigger_definition "uppercase_users_name_v02", <<~
|
|
35
|
+
write_trigger_definition "uppercase_users_name_v02", <<~SQL
|
|
36
36
|
CREATE TRIGGER uppercase_users_name
|
|
37
37
|
BEFORE UPDATE ON users
|
|
38
38
|
FOR EACH ROW
|
|
39
39
|
EXECUTE FUNCTION uppercase_users_name();
|
|
40
|
-
|
|
40
|
+
SQL
|
|
41
41
|
successfully "rake db:migrate"
|
|
42
|
-
execute <<~
|
|
42
|
+
execute <<~SQL
|
|
43
43
|
UPDATE users
|
|
44
44
|
SET name = 'Alice'
|
|
45
45
|
WHERE id = 1;
|
|
46
|
-
|
|
46
|
+
SQL
|
|
47
47
|
|
|
48
48
|
result = execute("SELECT upper_name FROM users WHERE name = 'Alice';")
|
|
49
49
|
expect(result).to eq("upper_name" => "ALICE")
|
data/spec/acceptance_helper.rb
CHANGED
|
@@ -5,32 +5,35 @@ ENV["RAILS_ENV"] = "test"
|
|
|
5
5
|
RSpec.configure do |config|
|
|
6
6
|
config.around(:each) do |example|
|
|
7
7
|
Dir.chdir("spec/dummy") do
|
|
8
|
+
DatabaseReset.call
|
|
9
|
+
|
|
8
10
|
example.run
|
|
11
|
+
|
|
12
|
+
DatabaseReset.call
|
|
9
13
|
end
|
|
10
14
|
end
|
|
11
15
|
|
|
12
16
|
config.before(:suite) do
|
|
13
17
|
Dir.chdir("spec/dummy") do
|
|
14
|
-
system
|
|
15
|
-
git init -b master 1>/dev/null
|
|
16
|
-
git config user.email
|
|
17
|
-
git config user.name
|
|
18
|
-
git add -A
|
|
19
|
-
git commit --no-gpg-sign --message 'initial' 1>/dev/null
|
|
20
|
-
|
|
18
|
+
system [
|
|
19
|
+
"git init -b master 1>/dev/null",
|
|
20
|
+
"git config user.email 'fx@example.com'",
|
|
21
|
+
"git config user.name 'Fx'",
|
|
22
|
+
"git add -A",
|
|
23
|
+
"git commit --no-gpg-sign --message 'initial' 1>/dev/null"
|
|
24
|
+
].join(" && ")
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
config.after(:suite) do
|
|
25
29
|
Dir.chdir("spec/dummy") do
|
|
26
30
|
ActiveRecord::Base.connection.disconnect!
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
git
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
CMD
|
|
31
|
+
DatabaseReset.call
|
|
32
|
+
system [
|
|
33
|
+
"git add -A",
|
|
34
|
+
"git reset --hard HEAD 1>/dev/null",
|
|
35
|
+
"rm -rf .git/ 1>/dev/null"
|
|
36
|
+
].join(" && ")
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
39
|
|
|
@@ -12,10 +12,6 @@ module Dummy
|
|
|
12
12
|
config.eager_load = false
|
|
13
13
|
config.active_support.deprecation = :stderr
|
|
14
14
|
|
|
15
|
-
config.load_defaults 7.
|
|
16
|
-
|
|
17
|
-
if Rails.version >= "8.0"
|
|
18
|
-
config.active_support.to_time_preserves_timezone = :zone
|
|
19
|
-
end
|
|
15
|
+
config.load_defaults 7.2
|
|
20
16
|
end
|
|
21
17
|
end
|
|
@@ -2,14 +2,14 @@ require "spec_helper"
|
|
|
2
2
|
|
|
3
3
|
RSpec.describe "Function migrations", :db do
|
|
4
4
|
around do |example|
|
|
5
|
-
sql_definition = <<~
|
|
5
|
+
sql_definition = <<~SQL
|
|
6
6
|
CREATE OR REPLACE FUNCTION test()
|
|
7
7
|
RETURNS text AS $$
|
|
8
8
|
BEGIN
|
|
9
9
|
RETURN 'test';
|
|
10
10
|
END;
|
|
11
11
|
$$ LANGUAGE plpgsql;
|
|
12
|
-
|
|
12
|
+
SQL
|
|
13
13
|
with_function_definition(name: :test, sql_definition: sql_definition) do
|
|
14
14
|
example.run
|
|
15
15
|
end
|
|
@@ -40,14 +40,14 @@ RSpec.describe "Function migrations", :db do
|
|
|
40
40
|
it "can run migrations that updates functions" do
|
|
41
41
|
connection.create_function(:test)
|
|
42
42
|
|
|
43
|
-
sql_definition = <<~
|
|
43
|
+
sql_definition = <<~SQL
|
|
44
44
|
CREATE OR REPLACE FUNCTION test()
|
|
45
45
|
RETURNS text AS $$
|
|
46
46
|
BEGIN
|
|
47
47
|
RETURN 'testest';
|
|
48
48
|
END;
|
|
49
49
|
$$ LANGUAGE plpgsql;
|
|
50
|
-
|
|
50
|
+
SQL
|
|
51
51
|
with_function_definition(
|
|
52
52
|
name: :test,
|
|
53
53
|
version: 2,
|
|
@@ -2,14 +2,14 @@ require "spec_helper"
|
|
|
2
2
|
|
|
3
3
|
RSpec.describe "Reverting migrations", :db do
|
|
4
4
|
around do |example|
|
|
5
|
-
sql_definition = <<~
|
|
5
|
+
sql_definition = <<~SQL
|
|
6
6
|
CREATE OR REPLACE FUNCTION test()
|
|
7
7
|
RETURNS text AS $$
|
|
8
8
|
BEGIN
|
|
9
9
|
RETURN 'test';
|
|
10
10
|
END;
|
|
11
11
|
$$ LANGUAGE plpgsql;
|
|
12
|
-
|
|
12
|
+
SQL
|
|
13
13
|
with_function_definition(name: :test, sql_definition: sql_definition) do
|
|
14
14
|
example.run
|
|
15
15
|
end
|
|
@@ -50,14 +50,14 @@ RSpec.describe "Reverting migrations", :db do
|
|
|
50
50
|
it "can run reversible migrations for updating functions" do
|
|
51
51
|
connection.create_function(:test)
|
|
52
52
|
|
|
53
|
-
sql_definition = <<~
|
|
53
|
+
sql_definition = <<~SQL
|
|
54
54
|
CREATE OR REPLACE FUNCTION test()
|
|
55
55
|
RETURNS text AS $$
|
|
56
56
|
BEGIN
|
|
57
57
|
RETURN 'bar';
|
|
58
58
|
END;
|
|
59
59
|
$$ LANGUAGE plpgsql;
|
|
60
|
-
|
|
60
|
+
SQL
|
|
61
61
|
with_function_definition(
|
|
62
62
|
name: :test,
|
|
63
63
|
version: 2,
|
|
@@ -2,14 +2,14 @@ require "spec_helper"
|
|
|
2
2
|
|
|
3
3
|
RSpec.describe "Trigger migrations", :db do
|
|
4
4
|
around do |example|
|
|
5
|
-
connection.execute <<~
|
|
5
|
+
connection.execute <<~SQL
|
|
6
6
|
CREATE TABLE users (
|
|
7
7
|
id int PRIMARY KEY,
|
|
8
8
|
name varchar(256),
|
|
9
9
|
upper_name varchar(256)
|
|
10
10
|
);
|
|
11
|
-
|
|
12
|
-
Fx.database.create_function <<~
|
|
11
|
+
SQL
|
|
12
|
+
Fx.database.create_function <<~SQL
|
|
13
13
|
CREATE OR REPLACE FUNCTION uppercase_users_name()
|
|
14
14
|
RETURNS trigger AS $$
|
|
15
15
|
BEGIN
|
|
@@ -17,13 +17,13 @@ RSpec.describe "Trigger migrations", :db do
|
|
|
17
17
|
RETURN NEW;
|
|
18
18
|
END;
|
|
19
19
|
$$ LANGUAGE plpgsql;
|
|
20
|
-
|
|
21
|
-
sql_definition = <<~
|
|
20
|
+
SQL
|
|
21
|
+
sql_definition = <<~SQL
|
|
22
22
|
CREATE TRIGGER uppercase_users_name
|
|
23
23
|
BEFORE INSERT ON users
|
|
24
24
|
FOR EACH ROW
|
|
25
25
|
EXECUTE FUNCTION uppercase_users_name();
|
|
26
|
-
|
|
26
|
+
SQL
|
|
27
27
|
with_trigger_definition(
|
|
28
28
|
name: :uppercase_users_name,
|
|
29
29
|
sql_definition: sql_definition
|
|
@@ -2,14 +2,14 @@ require "spec_helper"
|
|
|
2
2
|
|
|
3
3
|
RSpec.describe "Reverting migrations", :db do
|
|
4
4
|
around do |example|
|
|
5
|
-
connection.execute <<~
|
|
5
|
+
connection.execute <<~SQL
|
|
6
6
|
CREATE TABLE users (
|
|
7
7
|
id int PRIMARY KEY,
|
|
8
8
|
name varchar(256),
|
|
9
9
|
upper_name varchar(256)
|
|
10
10
|
);
|
|
11
|
-
|
|
12
|
-
Fx.database.create_function <<~
|
|
11
|
+
SQL
|
|
12
|
+
Fx.database.create_function <<~SQL
|
|
13
13
|
CREATE OR REPLACE FUNCTION uppercase_users_name()
|
|
14
14
|
RETURNS trigger AS $$
|
|
15
15
|
BEGIN
|
|
@@ -17,13 +17,13 @@ RSpec.describe "Reverting migrations", :db do
|
|
|
17
17
|
RETURN NEW;
|
|
18
18
|
END;
|
|
19
19
|
$$ LANGUAGE plpgsql;
|
|
20
|
-
|
|
21
|
-
sql_definition = <<~
|
|
20
|
+
SQL
|
|
21
|
+
sql_definition = <<~SQL
|
|
22
22
|
CREATE TRIGGER uppercase_users_name
|
|
23
23
|
BEFORE INSERT ON users
|
|
24
24
|
FOR EACH ROW
|
|
25
25
|
EXECUTE FUNCTION uppercase_users_name();
|
|
26
|
-
|
|
26
|
+
SQL
|
|
27
27
|
with_trigger_definition(
|
|
28
28
|
name: :uppercase_users_name,
|
|
29
29
|
sql_definition: sql_definition
|
|
@@ -67,12 +67,12 @@ RSpec.describe "Reverting migrations", :db do
|
|
|
67
67
|
it "can run reversible migrations for updating triggers" do
|
|
68
68
|
connection.create_trigger(:uppercase_users_name)
|
|
69
69
|
|
|
70
|
-
sql_definition = <<~
|
|
70
|
+
sql_definition = <<~SQL
|
|
71
71
|
CREATE TRIGGER uppercase_users_name
|
|
72
72
|
BEFORE UPDATE ON users
|
|
73
73
|
FOR EACH ROW
|
|
74
74
|
EXECUTE FUNCTION uppercase_users_name();
|
|
75
|
-
|
|
75
|
+
SQL
|
|
76
76
|
with_trigger_definition(
|
|
77
77
|
name: :uppercase_users_name,
|
|
78
78
|
sql_definition: sql_definition,
|
|
@@ -4,21 +4,21 @@ RSpec.describe Fx::Adapters::Postgres::Functions, :db do
|
|
|
4
4
|
describe ".all" do
|
|
5
5
|
it "returns `Function` objects" do
|
|
6
6
|
connection = ActiveRecord::Base.connection
|
|
7
|
-
connection.execute <<~
|
|
7
|
+
connection.execute <<~SQL
|
|
8
8
|
CREATE OR REPLACE FUNCTION test()
|
|
9
9
|
RETURNS text AS $$
|
|
10
10
|
BEGIN
|
|
11
11
|
RETURN 'test';
|
|
12
12
|
END;
|
|
13
13
|
$$ LANGUAGE plpgsql;
|
|
14
|
-
|
|
14
|
+
SQL
|
|
15
15
|
|
|
16
|
-
functions = Fx::Adapters::Postgres::Functions.
|
|
16
|
+
functions = Fx::Adapters::Postgres::Functions.all(connection)
|
|
17
17
|
|
|
18
18
|
first = functions.first
|
|
19
19
|
expect(functions.size).to eq(1)
|
|
20
20
|
expect(first.name).to eq("test")
|
|
21
|
-
expect(first.definition).to eq(<<~
|
|
21
|
+
expect(first.definition).to eq(<<~SQL)
|
|
22
22
|
CREATE OR REPLACE FUNCTION public.test()
|
|
23
23
|
RETURNS text
|
|
24
24
|
LANGUAGE plpgsql
|
|
@@ -27,7 +27,14 @@ RSpec.describe Fx::Adapters::Postgres::Functions, :db do
|
|
|
27
27
|
RETURN 'test';
|
|
28
28
|
END;
|
|
29
29
|
$function$
|
|
30
|
-
|
|
30
|
+
SQL
|
|
31
|
+
|
|
32
|
+
connection.execute "CREATE SCHEMA IF NOT EXISTS other;"
|
|
33
|
+
connection.execute "SET search_path = 'other';"
|
|
34
|
+
|
|
35
|
+
functions = Fx::Adapters::Postgres::Functions.all(connection)
|
|
36
|
+
|
|
37
|
+
expect(functions).to be_empty
|
|
31
38
|
end
|
|
32
39
|
end
|
|
33
40
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe Fx::Adapters::Postgres::QueryExecutor, :db do
|
|
4
|
+
it "executes the query and maps results to objects" do
|
|
5
|
+
connection = ActiveRecord::Base.connection
|
|
6
|
+
query = "SELECT 'Hello World' as message, 'english' as language"
|
|
7
|
+
greeter = Class.new do
|
|
8
|
+
attr_reader :message, :language
|
|
9
|
+
|
|
10
|
+
def initialize(row)
|
|
11
|
+
@message = row.fetch("message")
|
|
12
|
+
@language = row.fetch("language")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
results = described_class.call(
|
|
17
|
+
connection: connection,
|
|
18
|
+
query: query,
|
|
19
|
+
model_class: greeter
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
expect(results.size).to eq(1)
|
|
23
|
+
expect(results.first).to be_a(greeter)
|
|
24
|
+
expect(results.first.message).to eq("Hello World")
|
|
25
|
+
expect(results.first.language).to eq("english")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "executes query with multiple results" do
|
|
29
|
+
connection = ActiveRecord::Base.connection
|
|
30
|
+
query = <<~SQL
|
|
31
|
+
SELECT 'first' as name
|
|
32
|
+
UNION ALL
|
|
33
|
+
SELECT 'second' as name
|
|
34
|
+
ORDER BY name
|
|
35
|
+
SQL
|
|
36
|
+
simple_name = Class.new do
|
|
37
|
+
attr_reader :name
|
|
38
|
+
|
|
39
|
+
def initialize(row)
|
|
40
|
+
@name = row.fetch("name")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
results = described_class.call(
|
|
45
|
+
connection: connection,
|
|
46
|
+
query: query,
|
|
47
|
+
model_class: simple_name
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
expect(results.size).to eq(2)
|
|
51
|
+
expect(results).to all(be_a(simple_name))
|
|
52
|
+
expect(results.first.name).to eq("first")
|
|
53
|
+
expect(results.last.name).to eq("second")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it "returns an empty array when query returns no results" do
|
|
57
|
+
connection = ActiveRecord::Base.connection
|
|
58
|
+
query = "SELECT 'test' as name WHERE 1 = 0"
|
|
59
|
+
simple_name = Class.new do
|
|
60
|
+
attr_reader :name
|
|
61
|
+
|
|
62
|
+
def initialize(row)
|
|
63
|
+
@name = row.fetch("name")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
results = described_class.call(
|
|
68
|
+
connection: connection,
|
|
69
|
+
query: query,
|
|
70
|
+
model_class: simple_name
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
expect(results).to eq([])
|
|
74
|
+
end
|
|
75
|
+
end
|