pg_spec_helper 1.5.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|