pg_spec_helper 1.0.0 → 1.1.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 +7 -0
- data/README.md +101 -5
- data/lib/pg_spec_helper/columns.rb +14 -7
- data/lib/pg_spec_helper/connection.rb +8 -14
- data/lib/pg_spec_helper/empty_database.rb +32 -0
- data/lib/pg_spec_helper/foreign_keys.rb +19 -9
- data/lib/pg_spec_helper/ignored_schemas.rb +16 -0
- data/lib/pg_spec_helper/indexes.rb +16 -6
- data/lib/pg_spec_helper/materialized_views.rb +95 -0
- data/lib/pg_spec_helper/primary_keys.rb +20 -7
- data/lib/pg_spec_helper/reset.rb +12 -0
- data/lib/pg_spec_helper/sanitize.rb +25 -0
- data/lib/pg_spec_helper/schemas.rb +21 -41
- data/lib/pg_spec_helper/tables.rb +14 -8
- data/lib/pg_spec_helper/track_changes.rb +68 -0
- data/lib/pg_spec_helper/unique_constraints.rb +17 -5
- data/lib/pg_spec_helper/validations.rb +17 -4
- data/lib/pg_spec_helper/version.rb +1 -1
- data/lib/pg_spec_helper.rb +35 -32
- metadata +8 -7
- data/lib/pg_spec_helper/configuration.rb +0 -51
- data/lib/pg_spec_helper/foreign_key_cache.rb +0 -26
- data/lib/pg_spec_helper/index_cache.rb +0 -26
- data/lib/pg_spec_helper/structure_cache.rb +0 -26
- data/lib/pg_spec_helper/validation_cache.rb +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 682ffdd64498aa86215c2af590284dfb04be55dcfaf1b70bfe5f5f88a5475d2c
|
4
|
+
data.tar.gz: 7fdd1581c726301dd48c23d9b386f78823177418e581ad76f042cea87d4583e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f977a9357f41a826585e15a9946d3769cbd33ecd9f592368cdc8b80276a243474a6cabb7d3ccdb170baf0565e62b1d5c2144ed75a4b5159620de7f65cdfc245c
|
7
|
+
data.tar.gz: c880c669479515f49910e7855ca8f90ad6f4a24cc5d48859bc9203fae22852ecdd9345622249f61ee6fcd6f859402ac3ce73d94372e0542b60ee8d847e4e7ba0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.1.0](https://github.com/craigulliott/pg_spec_helper/compare/v1.0.0...v1.1.0) (2023-07-10)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* documentation, get methods to retrieve current database structure, and full test coverage ([d6c6230](https://github.com/craigulliott/pg_spec_helper/commit/d6c623055d3ac2920bdc4f805973df7f25208329))
|
9
|
+
|
3
10
|
## 1.0.0 (2023-07-08)
|
4
11
|
|
5
12
|
|
data/README.md
CHANGED
@@ -1,17 +1,36 @@
|
|
1
1
|
# PGSpecHelper
|
2
2
|
|
3
|
-
Monitor and generate database migrations based on difference between current schema and configuration.
|
4
3
|
|
5
4
|
[![Gem Version](https://badge.fury.io/rb/pg_spec_helper.svg)](https://badge.fury.io/rb/pg_spec_helper)
|
6
5
|
[![Specs](https://github.com/craigulliott/pg_spec_helper/actions/workflows/specs.yml/badge.svg)](https://github.com/craigulliott/pg_spec_helper/actions/workflows/specs.yml)
|
7
6
|
[![Types](https://github.com/craigulliott/pg_spec_helper/actions/workflows/types.yml/badge.svg)](https://github.com/craigulliott/pg_spec_helper/actions/workflows/types.yml)
|
8
7
|
[![Coding Style](https://github.com/craigulliott/pg_spec_helper/actions/workflows/linter.yml/badge.svg)](https://github.com/craigulliott/pg_spec_helper/actions/workflows/linter.yml)
|
9
8
|
|
9
|
+
|
10
|
+
### What this gem is
|
11
|
+
|
12
|
+
A helper class for setting up and easily tearing down PostgreSQL database **structure** within a testing environment.
|
13
|
+
|
14
|
+
If you are building something which depends on specific database structure, and that structure changes depending on specific tests within your test suite, then this gem is for you.
|
15
|
+
|
16
|
+
For example, [platformer](https://www.github.com/craigulliott/platformer) and [dynamic_migrations](https://www.github.com/craigulliott/dynamic_migrations) are two packages which make use of this gem.
|
17
|
+
|
18
|
+
|
19
|
+
### What this gem is not
|
20
|
+
|
21
|
+
This gem is concerned with the **structure** of your database, not the data/records within your database. If you are looking for a gem which can add data/records to your database within your test suite, then check out [factory_bot](https://github.com/thoughtbot/factory_bot). If you are looking for a tool to reset the state of your database (clear records) after your test suite has completed, then check out [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner).
|
22
|
+
|
23
|
+
|
10
24
|
## Key Features
|
11
25
|
|
12
|
-
*
|
26
|
+
* Easily create basic tables, columns, constraints, indexes and primary/foreign keys for your specs
|
27
|
+
* Provides convenient methods for testing the presence of various database objects
|
13
28
|
* Resets your database after each spec, only if the spec made changes
|
14
|
-
|
29
|
+
* Ignores `information_schema` and any schemas or tables beginning with `pg_`
|
30
|
+
* Configurable to ignore other schemas (such as `postgis`)
|
31
|
+
* Automatically resets and recreates the `public` schema
|
32
|
+
* Can track and refresh materialized views
|
33
|
+
* Easily access the `PG::Connection` object via `pg_spec_helper.connection` to execute your own SQL
|
15
34
|
|
16
35
|
## Installation
|
17
36
|
|
@@ -29,11 +48,88 @@ Note, this gem depends on the postgres gem `pg`, which depends on the `libpq` pa
|
|
29
48
|
# required for pg gem on apple silicon
|
30
49
|
brew install libpq
|
31
50
|
export PATH="/opt/homebrew/opt/libpq/bin:$PATH"
|
32
|
-
|
51
|
+
```
|
33
52
|
|
34
53
|
## Getting Started
|
35
54
|
|
36
|
-
|
55
|
+
#### Setting up rspec
|
56
|
+
|
57
|
+
Install PG Spec Helper into your `spec/spec_helper.rb`
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
require "pg_spec_helper"
|
61
|
+
|
62
|
+
RSpec.configure do |config|
|
63
|
+
|
64
|
+
# make pg_spec_helper conveniently accessable within your test suite
|
65
|
+
config.add_setting :pg_spec_helper
|
66
|
+
config.pg_spec_helper = PGSpecHelper.new(database: :my_database, host: :localhost, port: 5432, username: 'username', password: '**********')
|
67
|
+
|
68
|
+
# optionally add additional schemas which should be ignored
|
69
|
+
config.pg_spec_helper.ignore_schema :postgis
|
70
|
+
config.pg_spec_helper.ignore_schema :some_other_schema
|
71
|
+
|
72
|
+
# If your package uses materialized views which need to be
|
73
|
+
# refreshed after structural changes have occured to your database,
|
74
|
+
# then you can track them and refresh them automatically
|
75
|
+
#
|
76
|
+
# here, the materialized view `my_materialized_view` will be refreshed
|
77
|
+
# automatically after any calls to create_schema, or create_table
|
78
|
+
config.pg_spec_helper.track_materialized_view :public, :my_materialized_view, [
|
79
|
+
:create_schema,
|
80
|
+
:create_table
|
81
|
+
]
|
82
|
+
|
83
|
+
# assert that your test suite is empty before running the test suite
|
84
|
+
config.before(:suite) do
|
85
|
+
# optionally provide DYNAMIC_MIGRATIONS_CLEAR_DB_ON_STARTUP=true to
|
86
|
+
# force reset your database structure
|
87
|
+
if ENV["DYNAMIC_MIGRATIONS_CLEAR_DB_ON_STARTUP"]
|
88
|
+
config.pg_spec_helper.reset! true
|
89
|
+
else
|
90
|
+
# raise an error unless your database structure is empty
|
91
|
+
config.pg_spec_helper.assert_database_empty!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# reset your database structure after each test (this deletes all
|
96
|
+
# schemas and tables and then recreates the `public` schema)
|
97
|
+
config.after(:each) do
|
98
|
+
config.pg_spec_helper.reset!
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
```
|
103
|
+
|
104
|
+
The configuration above will assert that your database is completely empty before the test suite runs. If your database is not empty, then an error will be raised.
|
105
|
+
|
106
|
+
If rspec crashed or exited prematurely on the last execution of your test suite, then you can tell pg_spec_helper to forcefully clear your database.
|
107
|
+
|
108
|
+
`DYNAMIC_MIGRATIONS_CLEAR_DB_ON_STARTUP=true bundle exec rspec`
|
109
|
+
|
110
|
+
#### An example test which requires some specific structure
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
RSpec.describe PGSpecHelper do
|
114
|
+
let(:pg_spec_helper) { RSpec.configuration.pg_spec_helper }
|
115
|
+
|
116
|
+
describe 'where the table `my_schema`.`my_table` exists in the database' do
|
117
|
+
before(:each) do
|
118
|
+
pg_spec_helper.create_schema :my_schema
|
119
|
+
pg_spec_helper.create_table :my_schema, :my_table
|
120
|
+
pg_spec_helper.create_column :my_schema, :my_table, :my_column, :integer
|
121
|
+
pg_spec_helper.create_primary_key :my_schema, :my_schema, :my_table, [:my_column]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "test something which required that table to exist" do
|
125
|
+
expect().to_not raise_error
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
|
132
|
+
See https://github.com/craigulliott/pg_spec_helper/tree/main/lib/pg_spec_helper for all the methods which are available on this class.
|
37
133
|
|
38
134
|
|
39
135
|
## Development
|
@@ -2,18 +2,25 @@
|
|
2
2
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module Columns
|
5
|
+
# create a column for the provided table
|
5
6
|
def create_column schema_name, table_name, column_name, type
|
6
|
-
# validate the type exists
|
7
|
-
PGSpecHelper::Postgres::DataTypes.validate_type_exists! type
|
8
7
|
# note the `type` is safe from sql_injection due to the validation above
|
9
8
|
connection.exec(<<-SQL)
|
10
9
|
ALTER TABLE #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s}
|
11
|
-
ADD COLUMN #{connection.quote_ident column_name.to_s} #{type}
|
10
|
+
ADD COLUMN #{connection.quote_ident column_name.to_s} #{sanitize_name type}
|
12
11
|
SQL
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
end
|
13
|
+
|
14
|
+
# return an array of column names for the provided table
|
15
|
+
def get_column_names schema_name, table_name
|
16
|
+
rows = connection.exec_params(<<-SQL, [schema_name.to_s, table_name.to_s])
|
17
|
+
SELECT column_name
|
18
|
+
FROM information_schema.columns
|
19
|
+
WHERE table_schema = $1
|
20
|
+
AND table_name = $2
|
21
|
+
ORDER BY ordinal_position;
|
22
|
+
SQL
|
23
|
+
rows.map { |row| row["column_name"].to_sym }
|
17
24
|
end
|
18
25
|
end
|
19
26
|
end
|
@@ -6,20 +6,14 @@ class PGSpecHelper
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def connection
|
9
|
-
|
10
|
-
@
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
)
|
18
|
-
# after initial connect, we refresh the cached representation of
|
19
|
-
# the database structure and constaints
|
20
|
-
refresh_structure_cache_materialized_view
|
21
|
-
refresh_validation_cache_materialized_view
|
22
|
-
end
|
9
|
+
@connection ||= PG.connect(
|
10
|
+
host: @host,
|
11
|
+
port: @port,
|
12
|
+
user: @username,
|
13
|
+
password: @password,
|
14
|
+
dbname: @database,
|
15
|
+
sslmode: "prefer"
|
16
|
+
)
|
23
17
|
@connection
|
24
18
|
end
|
25
19
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PGSpecHelper
|
4
|
+
module EmptyDatabase
|
5
|
+
class DatabaseNotEmptyError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# We assert there are no schemas before we run the test suite because it
|
9
|
+
# requires starting with an empty database.
|
10
|
+
#
|
11
|
+
# We could clear it automatically here, but don't want to risk deleting data
|
12
|
+
# in case the configuration is wrong and this is connected to an unexpected
|
13
|
+
# database.
|
14
|
+
def assert_database_empty!
|
15
|
+
public_schema_exists = false
|
16
|
+
get_schema_names.each do |schema_name|
|
17
|
+
# we expect the public schema to exist, but assert that it is empty later
|
18
|
+
if schema_name == :public
|
19
|
+
public_schema_exists = true
|
20
|
+
next
|
21
|
+
end
|
22
|
+
raise DatabaseNotEmptyError, "Expected no schemas to exist, but found `#{schema_name}` in `#{@database}` on `#{@host}`. Your test suite might have failed to complete the last time it was run. Please delete all schemas. If you are certain this is pointed at the correct database, then you can set DYNAMIC_MIGRATIONS_CLEAR_DB_ON_STARTUP=true on your console and execute these specs again to automatically clear the database."
|
23
|
+
end
|
24
|
+
# assert that the public schema exists
|
25
|
+
raise DatabaseNotEmptyError, "Public schema does not exist" unless public_schema_exists
|
26
|
+
# assert the public schema has no tables
|
27
|
+
raise DatabaseNotEmptyError, "Public schema is not empty" unless get_table_names(:public).empty?
|
28
|
+
# the database is empty, return true
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -2,20 +2,30 @@
|
|
2
2
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module ForeignKeys
|
5
|
+
# Create a foreign key
|
5
6
|
def create_foreign_key schema_name, table_name, column_names, foreign_schema_name, foreign_table_name, foreign_column_names, foreign_key_name
|
6
|
-
column_names_sql = column_names.map { |n|
|
7
|
-
foreign_column_names_sql =
|
7
|
+
column_names_sql = column_names.map { |n| sanitize_name n }.join(", ")
|
8
|
+
foreign_column_names_sql = foreign_column_names.map { |n| sanitize_name n }.join(", ")
|
8
9
|
# add the foreign key
|
9
10
|
connection.exec(<<-SQL)
|
10
|
-
ALTER TABLE #{
|
11
|
-
ADD CONSTRAINT #{
|
11
|
+
ALTER TABLE #{sanitize_name schema_name}.#{sanitize_name table_name}
|
12
|
+
ADD CONSTRAINT #{sanitize_name foreign_key_name}
|
12
13
|
FOREIGN KEY (#{column_names_sql})
|
13
|
-
REFERENCES #{
|
14
|
+
REFERENCES #{sanitize_name foreign_schema_name}.#{sanitize_name foreign_table_name} (#{foreign_column_names_sql})
|
14
15
|
SQL
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
end
|
17
|
+
|
18
|
+
# returns a list of foreign keys for the provided table
|
19
|
+
def get_foreign_key_names schema_name, table_name
|
20
|
+
rows = connection.exec_params(<<-SQL, [schema_name.to_s, table_name.to_s])
|
21
|
+
SELECT constraint_name
|
22
|
+
FROM information_schema.table_constraints
|
23
|
+
WHERE table_schema = $1
|
24
|
+
AND table_name = $2
|
25
|
+
AND constraint_type = 'FOREIGN KEY'
|
26
|
+
ORDER BY constraint_name;
|
27
|
+
SQL
|
28
|
+
rows.map { |row| row["constraint_name"].to_sym }
|
19
29
|
end
|
20
30
|
end
|
21
31
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PGSpecHelper
|
4
|
+
module IgnoredSchemas
|
5
|
+
# add a schema to the list of ignored schemas
|
6
|
+
def ignore_schema schema_name
|
7
|
+
@ignored_schemas ||= []
|
8
|
+
@ignored_schemas << schema_name.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
# get a list of ignored schemas
|
12
|
+
def ignored_schemas
|
13
|
+
([:information_schema] + (@ignored_schemas || []))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -2,15 +2,25 @@
|
|
2
2
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module Indexes
|
5
|
-
|
5
|
+
# Create an index
|
6
|
+
def create_index schema_name, table_name, column_names, index_name
|
7
|
+
column_names_sql = column_names.map { |n| sanitize_name n }.join(", ")
|
6
8
|
connection.exec(<<-SQL)
|
7
9
|
CREATE INDEX #{connection.quote_ident index_name.to_s}
|
8
|
-
ON #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s} (
|
10
|
+
ON #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s} (#{column_names_sql})
|
9
11
|
SQL
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
end
|
13
|
+
|
14
|
+
# get a list of index names for the provided table
|
15
|
+
def get_index_names schema_name, table_name
|
16
|
+
rows = connection.exec_params(<<-SQL, [schema_name.to_s, table_name.to_s])
|
17
|
+
SELECT indexname
|
18
|
+
FROM pg_indexes
|
19
|
+
WHERE schemaname = $1
|
20
|
+
AND tablename = $2
|
21
|
+
ORDER BY indexname;
|
22
|
+
SQL
|
23
|
+
rows.map { |row| row["indexname"].to_sym }
|
14
24
|
end
|
15
25
|
end
|
16
26
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PGSpecHelper
|
4
|
+
module MaterializedViews
|
5
|
+
class MaterializedViewNotTrackedError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# add the name of a materialized view to a list of views which will
|
9
|
+
# be refreshed after each type of change
|
10
|
+
def track_materialized_view schema_name, materialized_view_name, refresh_after
|
11
|
+
@materialized_views ||= {}
|
12
|
+
@materialized_views[schema_name.to_sym] ||= {}
|
13
|
+
|
14
|
+
# ensure the refresh_after contains at least one trackable method
|
15
|
+
unless refresh_after.is_a?(Array) && refresh_after.count
|
16
|
+
raise ArgumentError, "refresh_after must be an array of trackable method names"
|
17
|
+
end
|
18
|
+
|
19
|
+
# ensure each method in the refresh_after list is trackable
|
20
|
+
refresh_after.each do |method_name|
|
21
|
+
assert_trackable_method_name! method_name
|
22
|
+
end
|
23
|
+
|
24
|
+
@materialized_views[schema_name.to_sym][materialized_view_name.to_sym] = {
|
25
|
+
# assume does not exist until proven otherwise
|
26
|
+
exists: false,
|
27
|
+
# list of methods which should trigger a refresh of this
|
28
|
+
# materialized view
|
29
|
+
refresh_after: refresh_after
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# return true if the materialized view exists, otherwise false
|
36
|
+
def materialized_view_exists? schema_name, materialized_view_name
|
37
|
+
# assert this materialized view is being tracked
|
38
|
+
assert_materialized_view_tracked! schema_name, materialized_view_name
|
39
|
+
|
40
|
+
# return true if we've already determined that the materialized view exists
|
41
|
+
# otherwise, check the database and cache the result of the existance check
|
42
|
+
#
|
43
|
+
# the check will be made every time the method is called, until the first
|
44
|
+
# time the materialized view is found to exist, at which point the method
|
45
|
+
# will always return true
|
46
|
+
@materialized_views[schema_name.to_sym][materialized_view_name.to_sym][:exists] ||= connection.exec(<<~SQL).count > 0
|
47
|
+
SELECT TRUE AS exists FROM pg_matviews WHERE schemaname = '#{sanitize_name schema_name}' AND matviewname = '#{sanitize_name materialized_view_name}';
|
48
|
+
SQL
|
49
|
+
end
|
50
|
+
|
51
|
+
# refresh all materialized views that have been tracked
|
52
|
+
def refresh_all_materialized_views
|
53
|
+
@materialized_views&.each do |schema_name, views|
|
54
|
+
views.each do |materialized_view_name, view|
|
55
|
+
if materialized_view_exists? schema_name, materialized_view_name
|
56
|
+
refresh_materialized_view schema_name, materialized_view_name
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# whenever schema changes are made from within the test suite we need to
|
63
|
+
# rebuild the materialized views that hold a cached representation of the
|
64
|
+
# database structure
|
65
|
+
def refresh_materialized_view schema_name, materialized_view_name
|
66
|
+
# assert this materialized view is being tracked
|
67
|
+
assert_materialized_view_tracked! schema_name, materialized_view_name
|
68
|
+
|
69
|
+
# refresh the view if it exists
|
70
|
+
if materialized_view_exists? schema_name, materialized_view_name
|
71
|
+
connection.exec(<<~SQL)
|
72
|
+
REFRESH MATERIALIZED VIEW #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident materialized_view_name.to_s};
|
73
|
+
SQL
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# assert that the provided materialized view is being tracked
|
78
|
+
def assert_materialized_view_tracked! schema_name, materialized_view_name
|
79
|
+
# assert any materialized views are being tracked
|
80
|
+
if @materialized_views.nil?
|
81
|
+
raise MaterializedViewNotTrackedError, "no materialized views are being tracked"
|
82
|
+
end
|
83
|
+
# assert this materialized view is being tracked
|
84
|
+
unless @materialized_views.key? schema_name.to_sym
|
85
|
+
raise MaterializedViewNotTrackedError, "no materialized views for schema `#{schema_name}` are being tracked"
|
86
|
+
end
|
87
|
+
# assert this materialized view is being tracked
|
88
|
+
unless @materialized_views[schema_name.to_sym].key? materialized_view_name.to_sym
|
89
|
+
raise MaterializedViewNotTrackedError, "materialized view `#{schema_name}`.`#{materialized_view_name}` is not being tracked"
|
90
|
+
end
|
91
|
+
# the materialized view does exist, so return true
|
92
|
+
true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -3,17 +3,30 @@
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module PrimaryKeys
|
5
5
|
# add a primary_key to the provided table which covers the provided columns
|
6
|
-
def
|
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
9
|
connection.exec(<<-SQL)
|
10
|
-
ALTER TABLE #{
|
11
|
-
ADD
|
10
|
+
ALTER TABLE #{sanitize_name schema_name.to_s}.#{sanitize_name table_name.to_s}
|
11
|
+
ADD CONSTRAINT #{sanitize_name primary_key_name}
|
12
|
+
PRIMARY KEY (#{column_names_sql})
|
12
13
|
SQL
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
end
|
15
|
+
|
16
|
+
# get the primary_key name for the provided table
|
17
|
+
def get_primary_key_name schema_name, table_name
|
18
|
+
# get the primary_key name
|
19
|
+
rows = connection.exec(<<-SQL, [schema_name.to_s, table_name.to_s])
|
20
|
+
SELECT
|
21
|
+
constraint_name
|
22
|
+
FROM
|
23
|
+
information_schema.table_constraints
|
24
|
+
WHERE
|
25
|
+
table_schema = $1
|
26
|
+
AND table_name = $2
|
27
|
+
AND constraint_type = 'PRIMARY KEY'
|
28
|
+
SQL
|
29
|
+
rows.map { |r| r["constraint_name"].to_sym }.first
|
17
30
|
end
|
18
31
|
end
|
19
32
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PGSpecHelper
|
4
|
+
module Sanitize
|
5
|
+
class UnsafePostgresNameError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# returns a sanitized version of the provided name, raises an error
|
9
|
+
# if it is not valid
|
10
|
+
#
|
11
|
+
# this is probably unnessesary due to this gem being used within a test
|
12
|
+
# suite and not a production application, but it is here for completeness
|
13
|
+
# and an abundance of caution
|
14
|
+
def sanitize_name name
|
15
|
+
# ensure the name is a string
|
16
|
+
name = name.to_s
|
17
|
+
# ensure the name is not empty
|
18
|
+
raise UnsafePostgresNameError, "name cannot be empty" if name.empty?
|
19
|
+
# ensure the name does not contain invalid characters
|
20
|
+
raise UnsafePostgresNameError, "name contains invalid characters" unless /\A[a-zA-Z0-9_-]+\z/.match?(name)
|
21
|
+
# return the name
|
22
|
+
name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -2,66 +2,46 @@
|
|
2
2
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module Schemas
|
5
|
-
#
|
6
|
-
# requires starting with an empty database.
|
7
|
-
#
|
8
|
-
# We could clear it automatically here, but don't want to risk deleting data
|
9
|
-
# in case the configuration is wrong and this is connected to an unexpected
|
10
|
-
# database.
|
11
|
-
def assert_database_empty!
|
12
|
-
public_schema_exists = false
|
13
|
-
get_schema_names.each do |schema_name|
|
14
|
-
if schema_name == "public"
|
15
|
-
public_schema_exists = true
|
16
|
-
next
|
17
|
-
end
|
18
|
-
next if schema_name.start_with? "pg_"
|
19
|
-
next if schema_name == "postgis"
|
20
|
-
next if schema_name == "information_schema"
|
21
|
-
raise "Expected no schemas to exist, but found `#{schema_name}` in `#{@database}` on `#{@host}`. Your test suite might have failed to complete the last time it was run. Please delete all schemas. If you are certain this is pointed at the correct database, then you can set DYNAMIC_MIGRATIONS_CLEAR_DB_ON_STARTUP=true on your console and execute these specs again to automatically clear the database."
|
22
|
-
end
|
23
|
-
# assert that the public schema exists
|
24
|
-
raise "Public schema does not exist" unless public_schema_exists
|
25
|
-
# assert the public schema has no tables
|
26
|
-
raise "Public scheama is not empty" unless get_table_names(:public).empty?
|
27
|
-
end
|
28
|
-
|
5
|
+
# create a new schema in the database
|
29
6
|
def create_schema schema_name
|
30
7
|
connection.exec(<<-SQL)
|
31
8
|
CREATE SCHEMA #{connection.quote_ident schema_name.to_s};
|
32
9
|
SQL
|
33
|
-
# refresh the cached representation of the database structure
|
34
|
-
refresh_structure_cache_materialized_view
|
35
|
-
# note that the structure has changed, so that the database can be reset between tests
|
36
|
-
@has_changes = true
|
37
10
|
end
|
38
11
|
|
12
|
+
# return a list of the schema names in the database
|
39
13
|
def get_schema_names
|
14
|
+
ignored_schemas_sql = ignored_schemas.map { |n| sanitize_name n }.join("', '")
|
15
|
+
# return a list of the schema names from the database
|
40
16
|
results = connection.exec(<<-SQL)
|
41
17
|
SELECT schema_name
|
42
|
-
FROM information_schema.schemata
|
18
|
+
FROM information_schema.schemata
|
19
|
+
WHERE
|
20
|
+
schema_name NOT IN ('#{ignored_schemas_sql}')
|
21
|
+
AND schema_name NOT LIKE 'pg_%';
|
43
22
|
SQL
|
44
|
-
schema_names = results.map { |row| row["schema_name"] }
|
23
|
+
schema_names = results.map { |row| row["schema_name"].to_sym }
|
45
24
|
schema_names.sort
|
46
25
|
end
|
47
26
|
|
27
|
+
# delete all schemas in the database
|
48
28
|
def delete_all_schemas cascade: false
|
29
|
+
# delete all schemas
|
49
30
|
get_schema_names.each do |schema_name|
|
50
|
-
next if schema_name.start_with? "pg_"
|
51
|
-
next if schema_name == "postgis"
|
52
|
-
next if schema_name == "public"
|
53
|
-
next if schema_name == "information_schema"
|
54
31
|
connection.exec(<<-SQL)
|
32
|
+
-- temporarily set the client_min_messages to WARNING to
|
33
|
+
-- suppress the NOTICE messages about cascading deletes
|
34
|
+
SET client_min_messages TO WARNING;
|
55
35
|
DROP SCHEMA #{connection.quote_ident schema_name.to_s} #{cascade ? "CASCADE" : ""};
|
36
|
+
SET client_min_messages TO NOTICE;
|
56
37
|
SQL
|
57
38
|
end
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
@has_changes = true
|
39
|
+
# recreate the default `public` schema
|
40
|
+
create_schema :public
|
41
|
+
end
|
42
|
+
|
43
|
+
def schema_exists? schema_name
|
44
|
+
get_schema_names.include? schema_name.to_sym
|
65
45
|
end
|
66
46
|
end
|
67
47
|
end
|
@@ -2,30 +2,36 @@
|
|
2
2
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module Tables
|
5
|
+
# create a new table in the provided schema
|
5
6
|
def create_table schema_name, table_name
|
6
7
|
connection.exec(<<-SQL)
|
7
|
-
CREATE TABLE #{
|
8
|
+
CREATE TABLE #{sanitize_name schema_name.to_s}.#{sanitize_name table_name.to_s}(
|
8
9
|
-- tables are created empty, and have columns added to them later
|
9
10
|
);
|
10
11
|
SQL
|
11
|
-
# refresh the cached representation of the database structure
|
12
|
-
refresh_structure_cache_materialized_view
|
13
|
-
# note that the structure has changed, so that the database can be reset between tests
|
14
|
-
@has_changes = true
|
15
12
|
end
|
16
13
|
|
14
|
+
# return an array of table names for the provided schema
|
17
15
|
def get_table_names schema_name
|
18
16
|
rows = connection.exec_params(<<-SQL, [schema_name.to_s])
|
19
17
|
SELECT table_name FROM information_schema.tables
|
20
|
-
WHERE
|
18
|
+
WHERE
|
19
|
+
table_schema = $1
|
20
|
+
AND table_name NOT LIKE 'pg_%';
|
21
21
|
SQL
|
22
|
-
rows.map { |row| row["table_name"] }
|
22
|
+
table_names = rows.map { |row| row["table_name"].to_sym }
|
23
|
+
table_names.sort
|
23
24
|
end
|
24
25
|
|
26
|
+
# delete all tables in the provided schema
|
25
27
|
def delete_tables schema_name
|
26
28
|
get_table_names(schema_name).each do |table_name|
|
27
29
|
connection.exec(<<-SQL)
|
28
|
-
|
30
|
+
-- temporarily set the client_min_messages to WARNING to
|
31
|
+
-- suppress the NOTICE messages about cascading deletes
|
32
|
+
SET client_min_messages TO WARNING;
|
33
|
+
DROP TABLE #{sanitize_name schema_name.to_s}.#{sanitize_name table_name.to_s} CASCADE;
|
34
|
+
SET client_min_messages TO NOTICE;
|
29
35
|
SQL
|
30
36
|
end
|
31
37
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PGSpecHelper
|
4
|
+
module TrackChanges
|
5
|
+
class UntrackableMethodNameError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# this is a list of methods that have their useage tracked, this
|
9
|
+
# is used to determine what actions need to be taken when calling
|
10
|
+
# reset! between tests
|
11
|
+
TRACKED_METHOD_CALLS = [
|
12
|
+
:create_schema,
|
13
|
+
:create_table,
|
14
|
+
:create_column
|
15
|
+
]
|
16
|
+
|
17
|
+
# returns true if any changes have been made to the database structure
|
18
|
+
# optionally pass in a method name to check if that specific method was used
|
19
|
+
def has_changes? method_name = nil
|
20
|
+
if method_name.nil?
|
21
|
+
methods_used.keys.count > 0
|
22
|
+
else
|
23
|
+
assert_trackable_method_name! method_name
|
24
|
+
methods_used[method_name] || false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# track that the provided method name has been used, this allows us to determine later
|
31
|
+
# which methods have been used and what actions need to be taken to reset the database
|
32
|
+
def track_change method_name
|
33
|
+
assert_trackable_method_name! method_name
|
34
|
+
@methods_used ||= {}
|
35
|
+
@methods_used[method_name] ||= true
|
36
|
+
end
|
37
|
+
|
38
|
+
# returns a hash representation of methods which have been used
|
39
|
+
def methods_used
|
40
|
+
@methods_used || {}
|
41
|
+
end
|
42
|
+
|
43
|
+
# raises an error if the provided method name is not trackable, otherwise returns true
|
44
|
+
def assert_trackable_method_name! method_name
|
45
|
+
unless TRACKED_METHOD_CALLS.include? method_name.to_sym
|
46
|
+
raise UntrackableMethodNameError, "method `#{method_name}` is not trackable"
|
47
|
+
end
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# overide the trackable methods and record when they are used
|
52
|
+
# this allows us to determine what actions need to be taken
|
53
|
+
# to reset the datasbe between tests
|
54
|
+
def install_trackable_methods
|
55
|
+
TRACKED_METHOD_CALLS.each do |method_name|
|
56
|
+
# keep a pointer to the original method
|
57
|
+
original_method = self.class.instance_method(method_name)
|
58
|
+
# ovveride the original method
|
59
|
+
self.class.define_method(method_name) do |*args|
|
60
|
+
# note that this method was called
|
61
|
+
track_change method_name
|
62
|
+
# call the original method
|
63
|
+
original_method.bind_call(self, *args)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module UniqueConstraints
|
5
5
|
# add a unique constraint to the provided table and columns
|
6
|
-
def
|
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
9
|
connection.exec(<<-SQL)
|
@@ -11,10 +11,22 @@ class PGSpecHelper
|
|
11
11
|
ADD CONSTRAINT #{connection.quote_ident constraint_key_name.to_s}
|
12
12
|
UNIQUE (#{column_names_sql})
|
13
13
|
SQL
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
end
|
15
|
+
|
16
|
+
# get a list of unique constraints for the provided table
|
17
|
+
def get_unique_constraint_names schema_name, table_name
|
18
|
+
# get the unique constraint names
|
19
|
+
rows = connection.exec(<<-SQL, [schema_name.to_s, table_name.to_s])
|
20
|
+
SELECT
|
21
|
+
constraint_name
|
22
|
+
FROM
|
23
|
+
information_schema.table_constraints
|
24
|
+
WHERE
|
25
|
+
table_schema = $1
|
26
|
+
AND table_name = $2
|
27
|
+
AND constraint_type = 'UNIQUE'
|
28
|
+
SQL
|
29
|
+
rows.map { |r| r["constraint_name"].to_sym }
|
18
30
|
end
|
19
31
|
end
|
20
32
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
class PGSpecHelper
|
4
4
|
module Validations
|
5
|
+
# create a validation on the provided table and columns
|
5
6
|
def create_validation schema_name, table_name, validation_name, check_clause
|
6
7
|
# todo the check_clause is vulnerable to sql injection (although this is very low risk because
|
7
8
|
# it is only ever provided by the test suite, and is never provided by the user)
|
@@ -9,10 +10,22 @@ class PGSpecHelper
|
|
9
10
|
ALTER TABLE #{connection.quote_ident schema_name.to_s}.#{connection.quote_ident table_name.to_s}
|
10
11
|
ADD CONSTRAINT #{connection.quote_ident validation_name.to_s} CHECK (#{check_clause})
|
11
12
|
SQL
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
# return a list of validation names for the provided table
|
16
|
+
def get_validation_names schema_name, table_name
|
17
|
+
# get the validation names
|
18
|
+
rows = connection.exec(<<-SQL, [schema_name.to_s, table_name.to_s])
|
19
|
+
SELECT
|
20
|
+
constraint_name
|
21
|
+
FROM
|
22
|
+
information_schema.table_constraints
|
23
|
+
WHERE
|
24
|
+
table_schema = $1
|
25
|
+
AND table_name = $2
|
26
|
+
AND constraint_type = 'CHECK'
|
27
|
+
SQL
|
28
|
+
rows.map { |r| r["constraint_name"].to_sym }
|
16
29
|
end
|
17
30
|
end
|
18
31
|
end
|
data/lib/pg_spec_helper.rb
CHANGED
@@ -5,8 +5,9 @@ require "pg"
|
|
5
5
|
|
6
6
|
require "pg_spec_helper/version"
|
7
7
|
|
8
|
-
require "pg_spec_helper/configuration"
|
9
8
|
require "pg_spec_helper/connection"
|
9
|
+
require "pg_spec_helper/sanitize"
|
10
|
+
require "pg_spec_helper/ignored_schemas"
|
10
11
|
require "pg_spec_helper/schemas"
|
11
12
|
require "pg_spec_helper/tables"
|
12
13
|
require "pg_spec_helper/columns"
|
@@ -15,14 +16,18 @@ require "pg_spec_helper/foreign_keys"
|
|
15
16
|
require "pg_spec_helper/unique_constraints"
|
16
17
|
require "pg_spec_helper/primary_keys"
|
17
18
|
require "pg_spec_helper/indexes"
|
18
|
-
require "pg_spec_helper/
|
19
|
-
require "pg_spec_helper/
|
20
|
-
require "pg_spec_helper/
|
21
|
-
require "pg_spec_helper/
|
19
|
+
require "pg_spec_helper/materialized_views"
|
20
|
+
require "pg_spec_helper/reset"
|
21
|
+
require "pg_spec_helper/empty_database"
|
22
|
+
require "pg_spec_helper/track_changes"
|
22
23
|
|
23
24
|
class PGSpecHelper
|
24
|
-
|
25
|
+
class MissingRequiredOptionError < StandardError
|
26
|
+
end
|
27
|
+
|
25
28
|
include Connection
|
29
|
+
include Sanitize
|
30
|
+
include IgnoredSchemas
|
26
31
|
include Schemas
|
27
32
|
include Tables
|
28
33
|
include Columns
|
@@ -31,36 +36,34 @@ class PGSpecHelper
|
|
31
36
|
include UniqueConstraints
|
32
37
|
include PrimaryKeys
|
33
38
|
include Indexes
|
34
|
-
include
|
35
|
-
include
|
36
|
-
include
|
37
|
-
include
|
39
|
+
include MaterializedViews
|
40
|
+
include Reset
|
41
|
+
include EmptyDatabase
|
42
|
+
include TrackChanges
|
38
43
|
|
39
44
|
attr_reader :database, :username, :password, :host, :port
|
40
45
|
|
41
|
-
def initialize
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
@port = require_configuration_value :port
|
47
|
-
@username = require_configuration_value :username
|
48
|
-
@password = optional_configuration_value :password
|
46
|
+
def initialize database:, username:, host:, port:, password: nil
|
47
|
+
# assert that all required options are present
|
48
|
+
raise MissingRequiredOptionError, "database is required" if database.nil?
|
49
|
+
raise MissingRequiredOptionError, "host is required" if host.nil?
|
50
|
+
raise MissingRequiredOptionError, "username is required" if username.nil?
|
49
51
|
|
50
|
-
#
|
51
|
-
|
52
|
-
@
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
@
|
57
|
-
end
|
52
|
+
# record the configuration
|
53
|
+
@database = database
|
54
|
+
@host = host
|
55
|
+
@port = port || 5432
|
56
|
+
@username = username
|
57
|
+
# password is optional
|
58
|
+
@password = password
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
# the TrackChanges module is used to track high level changes which are
|
61
|
+
# made to the database using this class, this allows us to reach out to the
|
62
|
+
# database and reset it only when needed.
|
63
|
+
#
|
64
|
+
# The `install_trackable_methods` method will override the methods which
|
65
|
+
# we are tracking with a new proxy method which will record when the method
|
66
|
+
# is called before subsequently calling the original method.
|
67
|
+
install_trackable_methods
|
65
68
|
end
|
66
69
|
end
|
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.1.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-07-
|
11
|
+
date: 2023-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -52,18 +52,19 @@ files:
|
|
52
52
|
- README.md
|
53
53
|
- lib/pg_spec_helper.rb
|
54
54
|
- lib/pg_spec_helper/columns.rb
|
55
|
-
- lib/pg_spec_helper/configuration.rb
|
56
55
|
- lib/pg_spec_helper/connection.rb
|
57
|
-
- lib/pg_spec_helper/
|
56
|
+
- lib/pg_spec_helper/empty_database.rb
|
58
57
|
- lib/pg_spec_helper/foreign_keys.rb
|
59
|
-
- lib/pg_spec_helper/
|
58
|
+
- lib/pg_spec_helper/ignored_schemas.rb
|
60
59
|
- lib/pg_spec_helper/indexes.rb
|
60
|
+
- lib/pg_spec_helper/materialized_views.rb
|
61
61
|
- lib/pg_spec_helper/primary_keys.rb
|
62
|
+
- lib/pg_spec_helper/reset.rb
|
63
|
+
- lib/pg_spec_helper/sanitize.rb
|
62
64
|
- lib/pg_spec_helper/schemas.rb
|
63
|
-
- lib/pg_spec_helper/structure_cache.rb
|
64
65
|
- lib/pg_spec_helper/tables.rb
|
66
|
+
- lib/pg_spec_helper/track_changes.rb
|
65
67
|
- lib/pg_spec_helper/unique_constraints.rb
|
66
|
-
- lib/pg_spec_helper/validation_cache.rb
|
67
68
|
- lib/pg_spec_helper/validations.rb
|
68
69
|
- lib/pg_spec_helper/version.rb
|
69
70
|
homepage:
|
@@ -1,51 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class PGSpecHelper
|
4
|
-
module Configuration
|
5
|
-
class MissingConfigurationError < StandardError
|
6
|
-
end
|
7
|
-
|
8
|
-
class ConfigurationNotLoadedError < StandardError
|
9
|
-
end
|
10
|
-
|
11
|
-
class MissingRequiredNameError < StandardError
|
12
|
-
end
|
13
|
-
|
14
|
-
class MissingRequiredDatabaseTypeError < StandardError
|
15
|
-
end
|
16
|
-
|
17
|
-
def load_configuration_for database_type, name
|
18
|
-
@name = name.to_s
|
19
|
-
@database_type = database_type.to_s
|
20
|
-
|
21
|
-
raise MissingRequiredNameError unless @name
|
22
|
-
raise MissingRequiredDatabaseTypeError unless @database_type
|
23
|
-
|
24
|
-
configuration = load_configuration_file
|
25
|
-
|
26
|
-
if configuration[@database_type].nil?
|
27
|
-
raise MissingConfigurationError, "no database configuration found for #{name} in database.yaml"
|
28
|
-
end
|
29
|
-
|
30
|
-
if configuration[@database_type][@name].nil?
|
31
|
-
raise MissingConfigurationError, "no configuration found for #{database_type}.#{name} in database.yaml"
|
32
|
-
end
|
33
|
-
|
34
|
-
@configuration = configuration[@database_type][@name]
|
35
|
-
end
|
36
|
-
|
37
|
-
def optional_configuration_value key
|
38
|
-
@configuration[key.to_s]
|
39
|
-
end
|
40
|
-
|
41
|
-
def require_configuration_value key
|
42
|
-
raise ConfigurationNotLoadedError unless @configuration
|
43
|
-
|
44
|
-
@configuration[key.to_s] || raise(MissingConfigurationError, "no #{key} found for configuration #{@database_type}.#{@name}")
|
45
|
-
end
|
46
|
-
|
47
|
-
def load_configuration_file
|
48
|
-
YAML.load_file("config/database.yaml")
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class PGSpecHelper
|
4
|
-
module ForeignKeyCache
|
5
|
-
# whenever schema changes are made from within the test suite we need to
|
6
|
-
# rebuild the materialized views that hold a cached representation of the
|
7
|
-
# database foreign keys
|
8
|
-
def refresh_keys_and_unique_constraints_cache_materialized_view
|
9
|
-
# the first time we detect the presence of the marerialized view, we no longer need to
|
10
|
-
# check for it
|
11
|
-
@keys_and_unique_constraints_cache_exists ||= keys_and_unique_constraints_cache_exists?
|
12
|
-
if @keys_and_unique_constraints_cache_exists
|
13
|
-
connection.exec(<<~SQL)
|
14
|
-
REFRESH MATERIALIZED VIEW public.pg_spec_helper_keys_and_unique_constraints_cache;
|
15
|
-
SQL
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def keys_and_unique_constraints_cache_exists?
|
20
|
-
exists = connection.exec(<<~SQL)
|
21
|
-
SELECT TRUE AS exists FROM pg_matviews WHERE schemaname = 'public' AND matviewname = 'pg_spec_helper_keys_and_unique_constraints_cache';
|
22
|
-
SQL
|
23
|
-
exists.count > 0
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class PGSpecHelper
|
4
|
-
module IndexCache
|
5
|
-
# whenever schema changes are made from within the test suite we need to
|
6
|
-
# rebuild the materialized views that hold a cached representation of the
|
7
|
-
# database indexes
|
8
|
-
def refresh_index_cache_materialized_view
|
9
|
-
# the first time we detect the presence of the marerialized view, we no longer need to
|
10
|
-
# check for it
|
11
|
-
@index_cache_exists ||= index_cache_exists?
|
12
|
-
if @index_cache_exists
|
13
|
-
connection.exec(<<~SQL)
|
14
|
-
REFRESH MATERIALIZED VIEW public.pg_spec_helper_index_cache;
|
15
|
-
SQL
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def index_cache_exists?
|
20
|
-
exists = connection.exec(<<~SQL)
|
21
|
-
SELECT TRUE AS exists FROM pg_matviews WHERE schemaname = 'public' AND matviewname = 'pg_spec_helper_index_cache';
|
22
|
-
SQL
|
23
|
-
exists.count > 0
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class PGSpecHelper
|
4
|
-
module StructureCache
|
5
|
-
# whenever schema changes are made from within the test suite we need to
|
6
|
-
# rebuild the materialized views that hold a cached representation of the
|
7
|
-
# database structure
|
8
|
-
def refresh_structure_cache_materialized_view
|
9
|
-
# the first time we detect the presence of the marerialized view, we no longer need to
|
10
|
-
# check for it
|
11
|
-
@structure_cache_exists ||= structure_cache_exists?
|
12
|
-
if @structure_cache_exists
|
13
|
-
connection.exec(<<~SQL)
|
14
|
-
REFRESH MATERIALIZED VIEW public.pg_spec_helper_structure_cache;
|
15
|
-
SQL
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def structure_cache_exists?
|
20
|
-
exists = connection.exec(<<~SQL)
|
21
|
-
SELECT TRUE AS exists FROM pg_matviews WHERE schemaname = 'public' AND matviewname = 'pg_spec_helper_structure_cache';
|
22
|
-
SQL
|
23
|
-
exists.count > 0
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class PGSpecHelper
|
4
|
-
module ValidationCache
|
5
|
-
# whenever schema changes are made from within the test suite we need to
|
6
|
-
# rebuild the materialized views that hold a cached representation of the
|
7
|
-
# database structure
|
8
|
-
def refresh_validation_cache_materialized_view
|
9
|
-
# the first time we detect the presence of the marerialized view, we no longer need to
|
10
|
-
# check for it
|
11
|
-
@validations_cache_exists ||= validations_cache_exists?
|
12
|
-
if @validations_cache_exists
|
13
|
-
connection.exec(<<~SQL)
|
14
|
-
REFRESH MATERIALIZED VIEW public.pg_spec_helper_validations_cache;
|
15
|
-
SQL
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def validations_cache_exists?
|
20
|
-
exists = connection.exec(<<~SQL)
|
21
|
-
SELECT TRUE AS exists FROM pg_matviews WHERE schemaname = 'public' AND matviewname = 'pg_spec_helper_validations_cache';
|
22
|
-
SQL
|
23
|
-
exists.count > 0
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|