pg_spec_helper 1.5.0 → 1.7.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 +4 -4
- data/CHANGELOG.md +14 -0
- data/lib/pg_spec_helper/columns.rb +3 -3
- data/lib/pg_spec_helper/foreign_keys.rb +2 -2
- data/lib/pg_spec_helper/functions.rb +30 -0
- data/lib/pg_spec_helper/indexes.rb +2 -2
- data/lib/pg_spec_helper/primary_keys.rb +2 -2
- data/lib/pg_spec_helper/schemas.rb +3 -3
- data/lib/pg_spec_helper/tables.rb +3 -3
- data/lib/pg_spec_helper/triggers.rb +75 -0
- data/lib/pg_spec_helper/unique_constraints.rb +2 -2
- data/lib/pg_spec_helper/validations.rb +2 -2
- data/lib/pg_spec_helper/version.rb +1 -1
- data/lib/pg_spec_helper.rb +4 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2030740ae096125c106b944c0984652488f3b48f811aa5c4a4caa9f972099d6
|
4
|
+
data.tar.gz: b3e24f7888d0302b98b69aa7b6c2b52db6d4d3708c9cb060c30d5b41d0ab9bb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d72899156ddd1196b5e1984c6ec3efb12eb1ea1372efe6975d44f9fb4181c948f65ab6b6b63e0b3cf2dc19743c4c7a14b1bb3057e94a2f424be13b2c3684472
|
7
|
+
data.tar.gz: c679e87bfd3fcdd08cec7516bf0388d02b159c4641ec023e08d1f1bd1e187726d74182d14f0a8ba15ca718e12ca7de967c0223a8c17fcc991413a9be6b9aa12f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.7.0](https://github.com/craigulliott/pg_spec_helper/compare/v1.6.0...v1.7.0) (2023-08-06)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* adding support for "instead of" in triggers ([488b4ab](https://github.com/craigulliott/pg_spec_helper/commit/488b4ab5fb458db2c7e2f60395b12dcfa9559459))
|
9
|
+
|
10
|
+
## [1.6.0](https://github.com/craigulliott/pg_spec_helper/compare/v1.5.0...v1.6.0) (2023-08-06)
|
11
|
+
|
12
|
+
|
13
|
+
### Features
|
14
|
+
|
15
|
+
* added support for triggers and functions ([f89bf5e](https://github.com/craigulliott/pg_spec_helper/commit/f89bf5e3afa6fc411e9d1f16cb62db74fc8dc987))
|
16
|
+
|
3
17
|
## [1.5.0](https://github.com/craigulliott/pg_spec_helper/compare/v1.4.0...v1.5.0) (2023-08-01)
|
4
18
|
|
5
19
|
|
@@ -8,7 +8,7 @@ class PGSpecHelper
|
|
8
8
|
# create a column for the provided table
|
9
9
|
def create_column schema_name, table_name, column_name, type, null = true
|
10
10
|
# note the `type` is safe from sql_injection due to the validation above
|
11
|
-
connection.exec(
|
11
|
+
connection.exec(<<~SQL)
|
12
12
|
ALTER TABLE #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s}
|
13
13
|
ADD COLUMN #{connection.quote_ident column_name.to_s} #{sanitize_name type} #{null ? "" : "NOT NULL"}
|
14
14
|
SQL
|
@@ -16,7 +16,7 @@ class PGSpecHelper
|
|
16
16
|
|
17
17
|
# return an array of column names for the provided table
|
18
18
|
def get_column_names schema_name, table_name
|
19
|
-
rows = connection.exec_params(
|
19
|
+
rows = connection.exec_params(<<~SQL, [schema_name.to_s, table_name.to_s])
|
20
20
|
SELECT column_name
|
21
21
|
FROM information_schema.columns
|
22
22
|
WHERE table_schema = $1
|
@@ -28,7 +28,7 @@ class PGSpecHelper
|
|
28
28
|
|
29
29
|
# return an array of column names for the provided table
|
30
30
|
def is_column_nullable schema_name, table_name, column_name
|
31
|
-
rows = connection.exec_params(
|
31
|
+
rows = connection.exec_params(<<~SQL, [schema_name.to_s, table_name.to_s, column_name.to_s])
|
32
32
|
SELECT is_nullable
|
33
33
|
FROM information_schema.columns
|
34
34
|
WHERE table_schema = $1
|
@@ -7,7 +7,7 @@ class PGSpecHelper
|
|
7
7
|
column_names_sql = column_names.map { |n| sanitize_name n }.join(", ")
|
8
8
|
foreign_column_names_sql = foreign_column_names.map { |n| sanitize_name n }.join(", ")
|
9
9
|
# add the foreign key
|
10
|
-
connection.exec(
|
10
|
+
connection.exec(<<~SQL)
|
11
11
|
ALTER TABLE #{sanitize_name schema_name}.#{sanitize_name table_name}
|
12
12
|
ADD CONSTRAINT #{sanitize_name foreign_key_name}
|
13
13
|
FOREIGN KEY (#{column_names_sql})
|
@@ -17,7 +17,7 @@ class PGSpecHelper
|
|
17
17
|
|
18
18
|
# returns a list of foreign keys for the provided table
|
19
19
|
def get_foreign_key_names schema_name, table_name
|
20
|
-
rows = connection.exec_params(
|
20
|
+
rows = connection.exec_params(<<~SQL, [schema_name.to_s, table_name.to_s])
|
21
21
|
SELECT constraint_name
|
22
22
|
FROM information_schema.table_constraints
|
23
23
|
WHERE table_schema = $1
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PGSpecHelper
|
4
|
+
module Functions
|
5
|
+
# create a function
|
6
|
+
def create_function schema_name, function_name, function_definition
|
7
|
+
connection.exec <<~SQL
|
8
|
+
CREATE FUNCTION #{schema_name}.#{function_name}() returns trigger language plpgsql AS $$
|
9
|
+
BEGIN
|
10
|
+
#{function_definition};
|
11
|
+
RETURN NEW;
|
12
|
+
END $$;
|
13
|
+
SQL
|
14
|
+
end
|
15
|
+
|
16
|
+
# return a list of function names for the provided schema
|
17
|
+
def get_function_names schema_name
|
18
|
+
# get the function names
|
19
|
+
rows = connection.exec(<<~SQL, [schema_name.to_s])
|
20
|
+
SELECT
|
21
|
+
routine_name
|
22
|
+
FROM
|
23
|
+
information_schema.routines
|
24
|
+
WHERE
|
25
|
+
routine_schema = $1
|
26
|
+
SQL
|
27
|
+
rows.map { |r| r["routine_name"].to_sym }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -5,7 +5,7 @@ class PGSpecHelper
|
|
5
5
|
# Create an index
|
6
6
|
def create_index schema_name, table_name, column_names, index_name
|
7
7
|
column_names_sql = column_names.map { |n| sanitize_name n }.join(", ")
|
8
|
-
connection.exec(
|
8
|
+
connection.exec(<<~SQL)
|
9
9
|
CREATE INDEX #{connection.quote_ident index_name.to_s}
|
10
10
|
ON #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s} (#{column_names_sql})
|
11
11
|
SQL
|
@@ -13,7 +13,7 @@ class PGSpecHelper
|
|
13
13
|
|
14
14
|
# get a list of index names for the provided table
|
15
15
|
def get_index_names schema_name, table_name
|
16
|
-
rows = connection.exec_params(
|
16
|
+
rows = connection.exec_params(<<~SQL, [schema_name.to_s, table_name.to_s])
|
17
17
|
SELECT indexname
|
18
18
|
FROM pg_indexes
|
19
19
|
WHERE schemaname = $1
|
@@ -6,7 +6,7 @@ class PGSpecHelper
|
|
6
6
|
def create_primary_key schema_name, table_name, column_names, primary_key_name
|
7
7
|
column_names_sql = column_names.map { |n| connection.quote_ident n.to_s }.join(", ")
|
8
8
|
# add the primary_key
|
9
|
-
connection.exec(
|
9
|
+
connection.exec(<<~SQL)
|
10
10
|
ALTER TABLE #{sanitize_name schema_name.to_s}.#{sanitize_name table_name.to_s}
|
11
11
|
ADD CONSTRAINT #{sanitize_name primary_key_name}
|
12
12
|
PRIMARY KEY (#{column_names_sql})
|
@@ -16,7 +16,7 @@ class PGSpecHelper
|
|
16
16
|
# get the primary_key name for the provided table
|
17
17
|
def get_primary_key_name schema_name, table_name
|
18
18
|
# get the primary_key name
|
19
|
-
rows = connection.exec(
|
19
|
+
rows = connection.exec(<<~SQL, [schema_name.to_s, table_name.to_s])
|
20
20
|
SELECT
|
21
21
|
constraint_name
|
22
22
|
FROM
|
@@ -4,7 +4,7 @@ class PGSpecHelper
|
|
4
4
|
module Schemas
|
5
5
|
# create a new schema in the database
|
6
6
|
def create_schema schema_name
|
7
|
-
connection.exec(
|
7
|
+
connection.exec(<<~SQL)
|
8
8
|
CREATE SCHEMA #{connection.quote_ident schema_name.to_s};
|
9
9
|
SQL
|
10
10
|
end
|
@@ -13,7 +13,7 @@ class PGSpecHelper
|
|
13
13
|
def get_schema_names
|
14
14
|
ignored_schemas_sql = ignored_schemas.map { |n| sanitize_name n }.join("', '")
|
15
15
|
# return a list of the schema names from the database
|
16
|
-
results = connection.exec(
|
16
|
+
results = connection.exec(<<~SQL)
|
17
17
|
SELECT schema_name
|
18
18
|
FROM information_schema.schemata
|
19
19
|
WHERE
|
@@ -28,7 +28,7 @@ class PGSpecHelper
|
|
28
28
|
def delete_all_schemas
|
29
29
|
# delete all schemas except public
|
30
30
|
get_schema_names.reject { |schema_name| schema_name == :public }.each do |schema_name|
|
31
|
-
connection.exec(
|
31
|
+
connection.exec(<<~SQL)
|
32
32
|
-- temporarily set the client_min_messages to WARNING to
|
33
33
|
-- suppress the NOTICE messages about cascading deletes
|
34
34
|
SET client_min_messages TO WARNING;
|
@@ -4,7 +4,7 @@ class PGSpecHelper
|
|
4
4
|
module Tables
|
5
5
|
# create a new table in the provided schema
|
6
6
|
def create_table schema_name, table_name
|
7
|
-
connection.exec(
|
7
|
+
connection.exec(<<~SQL)
|
8
8
|
CREATE TABLE #{sanitize_name schema_name.to_s}.#{sanitize_name table_name.to_s}(
|
9
9
|
-- tables are created empty, and have columns added to them later
|
10
10
|
);
|
@@ -13,7 +13,7 @@ class PGSpecHelper
|
|
13
13
|
|
14
14
|
# return an array of table names for the provided schema
|
15
15
|
def get_table_names schema_name
|
16
|
-
rows = connection.exec_params(
|
16
|
+
rows = connection.exec_params(<<~SQL, [schema_name.to_s])
|
17
17
|
SELECT table_name FROM information_schema.tables
|
18
18
|
WHERE
|
19
19
|
table_schema = $1
|
@@ -26,7 +26,7 @@ class PGSpecHelper
|
|
26
26
|
# delete all tables in the provided schema
|
27
27
|
def delete_tables schema_name
|
28
28
|
get_table_names(schema_name).each do |table_name|
|
29
|
-
connection.exec(
|
29
|
+
connection.exec(<<~SQL)
|
30
30
|
-- temporarily set the client_min_messages to WARNING to
|
31
31
|
-- suppress the NOTICE messages about cascading deletes
|
32
32
|
SET client_min_messages TO WARNING;
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PGSpecHelper
|
4
|
+
module Triggers
|
5
|
+
# create a trigger
|
6
|
+
class UnexpectedEventManipulationError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class UnexpectedActionOrientationError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class UnexpectedActionTimingError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class UnexpectedConditionsError < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
# create a postgres trigger
|
19
|
+
def create_trigger schema_name, table_name, name, action_timing:, event_manipulation:, action_orientation:, routine_schema:, routine_name:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil
|
20
|
+
unless [:insert, :delete, :update].include? event_manipulation
|
21
|
+
raise UnexpectedEventManipulationError, event_manipulation
|
22
|
+
end
|
23
|
+
|
24
|
+
unless action_condition.nil? || action_condition.is_a?(String)
|
25
|
+
raise UnexpectedConditionsError, "expected String but got `#{action_condition}`"
|
26
|
+
end
|
27
|
+
|
28
|
+
unless [:row, :statement].include? action_orientation
|
29
|
+
raise UnexpectedActionOrientationError, action_orientation
|
30
|
+
end
|
31
|
+
|
32
|
+
unless [:before, :after, :instead_of].include? action_timing
|
33
|
+
raise UnexpectedActionTimingError, action_timing
|
34
|
+
end
|
35
|
+
|
36
|
+
# "INSTEAD OF/BEFORE/AFTER" "INSERT/UPDATE/DELETE"
|
37
|
+
timing_sql = "#{action_timing.to_s.sub("_", " ")} #{event_manipulation}".upcase
|
38
|
+
|
39
|
+
condition_sql = action_condition.nil? ? "" : "WHEN (#{action_condition})"
|
40
|
+
|
41
|
+
temp_tables = []
|
42
|
+
unless action_reference_old_table.nil?
|
43
|
+
temp_tables << "OLD TABLE AS #{action_reference_old_table}"
|
44
|
+
end
|
45
|
+
unless action_reference_new_table.nil?
|
46
|
+
temp_tables << "NEW TABLE AS #{action_reference_new_table}"
|
47
|
+
end
|
48
|
+
temp_tables_sql = temp_tables.any? ? "REFERENCING #{temp_tables.join(" ")}" : ""
|
49
|
+
|
50
|
+
connection.exec <<~SQL
|
51
|
+
-- trigger names only need to be unique for this table
|
52
|
+
CREATE TRIGGER #{name}
|
53
|
+
#{timing_sql} ON #{schema_name}.#{table_name} #{temp_tables_sql}
|
54
|
+
FOR EACH #{action_orientation}
|
55
|
+
#{condition_sql}
|
56
|
+
EXECUTE PROCEDURE #{routine_schema}.#{routine_name}();
|
57
|
+
SQL
|
58
|
+
end
|
59
|
+
|
60
|
+
# return a list of trigger names for the provided table
|
61
|
+
def get_trigger_names schema_name, table_name
|
62
|
+
# get the trigger names
|
63
|
+
rows = connection.exec(<<~SQL, [schema_name.to_s, table_name.to_s])
|
64
|
+
SELECT
|
65
|
+
trigger_name
|
66
|
+
FROM
|
67
|
+
information_schema.triggers
|
68
|
+
WHERE
|
69
|
+
event_object_schema = $1
|
70
|
+
AND event_object_table = $2
|
71
|
+
SQL
|
72
|
+
rows.map { |r| r["trigger_name"].to_sym }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -6,7 +6,7 @@ class PGSpecHelper
|
|
6
6
|
def create_unique_constraint schema_name, table_name, column_names, constraint_key_name
|
7
7
|
column_names_sql = column_names.map { |n| connection.quote_ident n.to_s }.join(", ")
|
8
8
|
# add the constraint key
|
9
|
-
connection.exec(
|
9
|
+
connection.exec(<<~SQL)
|
10
10
|
ALTER TABLE #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s}
|
11
11
|
ADD CONSTRAINT #{connection.quote_ident constraint_key_name.to_s}
|
12
12
|
UNIQUE (#{column_names_sql})
|
@@ -16,7 +16,7 @@ class PGSpecHelper
|
|
16
16
|
# get a list of unique constraints for the provided table
|
17
17
|
def get_unique_constraint_names schema_name, table_name
|
18
18
|
# get the unique constraint names
|
19
|
-
rows = connection.exec(
|
19
|
+
rows = connection.exec(<<~SQL, [schema_name.to_s, table_name.to_s])
|
20
20
|
SELECT
|
21
21
|
constraint_name
|
22
22
|
FROM
|
@@ -6,7 +6,7 @@ class PGSpecHelper
|
|
6
6
|
def create_validation schema_name, table_name, validation_name, check_clause
|
7
7
|
# todo the check_clause is vulnerable to sql injection (although this is very low risk because
|
8
8
|
# it is only ever provided by the test suite, and is never provided by the user)
|
9
|
-
connection.exec(
|
9
|
+
connection.exec(<<~SQL)
|
10
10
|
ALTER TABLE #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s}
|
11
11
|
ADD CONSTRAINT #{connection.quote_ident validation_name.to_s} CHECK (#{check_clause})
|
12
12
|
SQL
|
@@ -15,7 +15,7 @@ class PGSpecHelper
|
|
15
15
|
# return a list of validation names for the provided table
|
16
16
|
def get_validation_names schema_name, table_name
|
17
17
|
# get the validation names
|
18
|
-
rows = connection.exec(
|
18
|
+
rows = connection.exec(<<~SQL, [schema_name.to_s, table_name.to_s])
|
19
19
|
SELECT
|
20
20
|
constraint_name
|
21
21
|
FROM
|
data/lib/pg_spec_helper.rb
CHANGED
@@ -16,6 +16,8 @@ require "pg_spec_helper/foreign_keys"
|
|
16
16
|
require "pg_spec_helper/unique_constraints"
|
17
17
|
require "pg_spec_helper/primary_keys"
|
18
18
|
require "pg_spec_helper/indexes"
|
19
|
+
require "pg_spec_helper/triggers"
|
20
|
+
require "pg_spec_helper/functions"
|
19
21
|
require "pg_spec_helper/models"
|
20
22
|
require "pg_spec_helper/materialized_views"
|
21
23
|
require "pg_spec_helper/reset"
|
@@ -39,6 +41,8 @@ class PGSpecHelper
|
|
39
41
|
include UniqueConstraints
|
40
42
|
include PrimaryKeys
|
41
43
|
include Indexes
|
44
|
+
include Triggers
|
45
|
+
include Functions
|
42
46
|
include Models
|
43
47
|
include MaterializedViews
|
44
48
|
include Reset
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_spec_helper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Craig Ulliott
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-08-
|
11
|
+
date: 2023-08-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -55,6 +55,7 @@ files:
|
|
55
55
|
- lib/pg_spec_helper/connection.rb
|
56
56
|
- lib/pg_spec_helper/empty_database.rb
|
57
57
|
- lib/pg_spec_helper/foreign_keys.rb
|
58
|
+
- lib/pg_spec_helper/functions.rb
|
58
59
|
- lib/pg_spec_helper/ignored_schemas.rb
|
59
60
|
- lib/pg_spec_helper/indexes.rb
|
60
61
|
- lib/pg_spec_helper/materialized_views.rb
|
@@ -66,6 +67,7 @@ files:
|
|
66
67
|
- lib/pg_spec_helper/table_executer.rb
|
67
68
|
- lib/pg_spec_helper/tables.rb
|
68
69
|
- lib/pg_spec_helper/track_changes.rb
|
70
|
+
- lib/pg_spec_helper/triggers.rb
|
69
71
|
- lib/pg_spec_helper/unique_constraints.rb
|
70
72
|
- lib/pg_spec_helper/validations.rb
|
71
73
|
- lib/pg_spec_helper/version.rb
|