rein 5.0.0 → 5.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 +6 -0
- data/README.md +45 -0
- data/lib/rein.rb +2 -0
- data/lib/rein/constraint/check.rb +1 -1
- data/lib/rein/constraint/foreign_key.rb +1 -1
- data/lib/rein/constraint/inclusion.rb +2 -1
- data/lib/rein/constraint/length.rb +2 -1
- data/lib/rein/constraint/match.rb +2 -1
- data/lib/rein/constraint/null.rb +2 -1
- data/lib/rein/constraint/numericality.rb +2 -1
- data/lib/rein/constraint/presence.rb +2 -1
- data/lib/rein/constraint/validate.rb +25 -0
- data/lib/rein/util.rb +6 -0
- data/lib/rein/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4688e25fef8600352e4ba1cd54a5ace2eeca7f827a7ca3ad023de66b93f46d7f
|
4
|
+
data.tar.gz: 918d01449019e40a5d01f858755dbc3f10d4673f936ce0c9f2578de081f1f26c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a27510401ee3dbb708f41cda16b2c697a7e2de643923c35c4657eb3b783885ffeb09186a6fd6c0683b78f27c667d82b15893b2d1d051b9656042ab9a7f1fe85
|
7
|
+
data.tar.gz: e82ea6c4ed180a76471419d99316ea75b80f040c8576bacfb6ed671a6ba12bdd3adc4d5cc36a15f3632bd77a16459b93e0872e081e12f07c6ab352a8b28a8615
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -19,6 +19,7 @@ advantage of reversible Rails migrations.
|
|
19
19
|
|
20
20
|
* [Getting Started](#getting-started)
|
21
21
|
* [Constraint Types](#constraint-types)
|
22
|
+
* [Summary](#summary)
|
22
23
|
* [Foreign Key Constraints](#foreign-key-constraints)
|
23
24
|
* [Unique Constraints](#unique-constraints)
|
24
25
|
* [Exclusion Constraints](#exclusion-constraints)
|
@@ -29,6 +30,7 @@ advantage of reversible Rails migrations.
|
|
29
30
|
* [Presence Constraints](#presence-constraints)
|
30
31
|
* [Null Constraints](#null-constraints)
|
31
32
|
* [Check Constraints](#check-constraints)
|
33
|
+
* [Validate Constraints](#validate-constraints)
|
32
34
|
* [Data Types](#data-types)
|
33
35
|
* [Enumerated Types](#enumerated-types)
|
34
36
|
* [Views](#views)
|
@@ -62,6 +64,23 @@ end
|
|
62
64
|
|
63
65
|
## Constraint Types
|
64
66
|
|
67
|
+
### Summary
|
68
|
+
|
69
|
+
The table below summarises the constraint operations provided by Rein and whether they support [validation](#validate-constraints).
|
70
|
+
|
71
|
+
| Rein name | Rein method | SQL | Supports `NOT VALID`? |
|
72
|
+
| ----------- | ----------- | --- | --------------------- |
|
73
|
+
| Foreign Key | `add_foreign_key_constraint` | `FOREIGN KEY` | yes |
|
74
|
+
| Unique | `add_unique_constraint` | `UNIQUE` | no |
|
75
|
+
| Exclusion | `add_exclusion_constraint` | `EXCLUDE` | no |
|
76
|
+
| Inclusion | `add_inclusion_constraint` | `CHECK` | yes |
|
77
|
+
| Length | `add_length_constraint` | `CHECK` | yes |
|
78
|
+
| Match | `add_match_constraint` | `CHECK` | yes |
|
79
|
+
| Numericality | `add_numericality_constraint` | `CHECK` | yes |
|
80
|
+
| Presence | `add_presence_constraint` | `CHECK` | yes |
|
81
|
+
| Null | `add_null_constraint` | `CHECK` | yes |
|
82
|
+
| Check | `add_check_constraint` | `CHECK` | yes |
|
83
|
+
|
65
84
|
### Foreign Key Constraints
|
66
85
|
|
67
86
|
A foreign key constraint specifies that the values in a column must match the
|
@@ -461,6 +480,32 @@ To remove a check constraint:
|
|
461
480
|
remove_check_constraint :books, "substring(title FROM 1 FOR 1) IS DISTINCT FROM 'r'", name: 'no_r_titles'
|
462
481
|
```
|
463
482
|
|
483
|
+
### Validate Constraints
|
484
|
+
|
485
|
+
Adding a constraint can be a very costly operation, especially on larger tables, as the database has to scan all rows in the table to check for violations of the new constraint. During this time, concurrent writes are blocked as an [`ACCESS EXCLUSIVE`](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-TABLES) table lock is taken. In addition, adding a foreign key constraint obtains a `SHARE ROW EXCLUSIVE` lock on the referenced table. See the [docs](https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES) for more details.
|
486
|
+
|
487
|
+
In order to allow constraints to be added concurrently on larger tables, and to allow the addition of constraints on tables containing rows with existing violations, Postgres supports adding constraints using the `NOT VALID` option (currently only for `CHECK` and foreign key constraints).
|
488
|
+
|
489
|
+
This allows the constraint to be added immediately, without validating existing rows, but enforcing the constraint for any new rows and updates. After that, a `VALIDATE CONSTRAINT` command can be issued to verify that existing rows satisfy the constraint, which is done in a way that does not lock out concurrent updates and "with the least impact on other work".
|
490
|
+
|
491
|
+
Rein supports adding `CHECK` and foreign key constraints with the `NOT VALID` option by passing `validate: false` to the options of the supported Rein DSL methods, [summarised above](#summary).
|
492
|
+
|
493
|
+
```ruby
|
494
|
+
add_null_constraint :books, :due_date, if: "state = 'on_loan'", validate: false
|
495
|
+
```
|
496
|
+
|
497
|
+
With Rails 5.2 or later, you can use [`validate_constraint`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQL/SchemaStatements.html#method-i-validate_constraint) in a subsequent migration to validate a `NOT VALID` constraint. If you are using versions of Rails below 5.2, you can use Rein's `validate_table_constraint` method:
|
498
|
+
|
499
|
+
```ruby
|
500
|
+
validate_table_constraint :books, "no_r_titles"
|
501
|
+
```
|
502
|
+
|
503
|
+
It's safe (a no-op) to validate a constraint that is already marked as valid.
|
504
|
+
|
505
|
+
### Side note on `lock_timeout`
|
506
|
+
|
507
|
+
It's advisable to set a [sensibly low `lock_timeout`](https://gocardless.com/blog/zero-downtime-postgres-migrations-the-hard-parts/) in your database migrations, otherwise existing long-running transactions can prevent your migration from acquiring the required locks, resulting in a lock queue that prevents even selects on the target table, potentially brining your production database grinding to a halt.
|
508
|
+
|
464
509
|
## Data Types
|
465
510
|
|
466
511
|
### Enumerated Types
|
data/lib/rein.rb
CHANGED
@@ -10,6 +10,7 @@ require 'rein/constraint/presence'
|
|
10
10
|
require 'rein/constraint/primary_key'
|
11
11
|
require 'rein/constraint/unique'
|
12
12
|
require 'rein/constraint/exclusion'
|
13
|
+
require 'rein/constraint/validate'
|
13
14
|
require 'rein/schema'
|
14
15
|
require 'rein/type/enum'
|
15
16
|
require 'rein/view'
|
@@ -27,6 +28,7 @@ module ActiveRecord
|
|
27
28
|
include Rein::Constraint::PrimaryKey
|
28
29
|
include Rein::Constraint::Unique
|
29
30
|
include Rein::Constraint::Exclusion
|
31
|
+
include Rein::Constraint::Validate
|
30
32
|
include Rein::Schema
|
31
33
|
include Rein::Type::Enum
|
32
34
|
include Rein::View
|
@@ -27,7 +27,7 @@ module Rein
|
|
27
27
|
sql = "ALTER TABLE #{Util.wrap_identifier(table_name)}"
|
28
28
|
sql << " ADD CONSTRAINT #{name}"
|
29
29
|
sql << " CHECK (#{predicate})"
|
30
|
-
execute(sql)
|
30
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
31
31
|
end
|
32
32
|
|
33
33
|
def _remove_check_constraint(table_name, _predicate, options = {})
|
@@ -31,7 +31,7 @@ module Rein
|
|
31
31
|
sql << " REFERENCES #{referenced_table} (#{Util.wrap_identifier(referenced_attribute)})"
|
32
32
|
sql << " ON DELETE #{referential_action(options[:on_delete])}" if options[:on_delete].present?
|
33
33
|
sql << " ON UPDATE #{referential_action(options[:on_update])}" if options[:on_update].present?
|
34
|
-
execute(sql)
|
34
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
35
35
|
add_index(referencing_table, referencing_attribute) if options[:index] == true
|
36
36
|
end
|
37
37
|
|
@@ -28,7 +28,8 @@ module Rein
|
|
28
28
|
values = options[:in].map { |value| quote(value) }.join(', ')
|
29
29
|
attribute = Util.wrap_identifier(attribute)
|
30
30
|
conditions = Util.conditions_with_if("#{attribute} IN (#{values})", options)
|
31
|
-
|
31
|
+
sql = "ALTER TABLE #{table} ADD CONSTRAINT #{name} CHECK (#{conditions})"
|
32
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
32
33
|
end
|
33
34
|
|
34
35
|
def _remove_inclusion_constraint(table, attribute, options = {})
|
@@ -39,7 +39,8 @@ module Rein
|
|
39
39
|
[attribute_length, operator, value].join(' ')
|
40
40
|
end.join(' AND ')
|
41
41
|
conditions = Util.conditions_with_if(conditions, options)
|
42
|
-
|
42
|
+
sql = "ALTER TABLE #{table} ADD CONSTRAINT #{name} CHECK (#{conditions})"
|
43
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
43
44
|
end
|
44
45
|
|
45
46
|
def _remove_length_constraint(table, attribute, options = {})
|
@@ -36,7 +36,8 @@ module Rein
|
|
36
36
|
[attribute, operator, "'#{value}'"].join(' ')
|
37
37
|
end.join(' AND ')
|
38
38
|
conditions = Util.conditions_with_if(conditions, options)
|
39
|
-
|
39
|
+
sql = "ALTER TABLE #{table} ADD CONSTRAINT #{name} CHECK (#{conditions})"
|
40
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
40
41
|
end
|
41
42
|
|
42
43
|
def _remove_match_constraint(table, attribute, options = {})
|
data/lib/rein/constraint/null.rb
CHANGED
@@ -27,7 +27,8 @@ module Rein
|
|
27
27
|
table = Util.wrap_identifier(table)
|
28
28
|
attribute = Util.wrap_identifier(attribute)
|
29
29
|
conditions = Util.conditions_with_if("#{attribute} IS NOT NULL", options)
|
30
|
-
|
30
|
+
sql = "ALTER TABLE #{table} ADD CONSTRAINT #{name} CHECK (#{conditions})"
|
31
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
31
32
|
end
|
32
33
|
|
33
34
|
def _remove_null_constraint(table, attribute, options = {})
|
@@ -38,7 +38,8 @@ module Rein
|
|
38
38
|
[attribute, operator, value].join(' ')
|
39
39
|
end.join(' AND ')
|
40
40
|
conditions = Util.conditions_with_if(conditions, options)
|
41
|
-
|
41
|
+
sql = "ALTER TABLE #{table} ADD CONSTRAINT #{name} CHECK (#{conditions})"
|
42
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
42
43
|
end
|
43
44
|
|
44
45
|
def _remove_numericality_constraint(table, attribute, options = {})
|
@@ -30,7 +30,8 @@ module Rein
|
|
30
30
|
"(#{attribute} IS NOT NULL) AND (#{attribute} !~ '^\\s*$')",
|
31
31
|
options
|
32
32
|
)
|
33
|
-
|
33
|
+
sql = "ALTER TABLE #{table} ADD CONSTRAINT #{name} CHECK (#{conditions})"
|
34
|
+
execute(Util.add_not_valid_suffix_if_required(sql, options))
|
34
35
|
end
|
35
36
|
|
36
37
|
def _remove_presence_constraint(table, attribute, options = {})
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rein/util'
|
2
|
+
|
3
|
+
module Rein
|
4
|
+
module Constraint
|
5
|
+
# This module contains methods for validating constraints.
|
6
|
+
module Validate
|
7
|
+
def validate_table_constraint(*args)
|
8
|
+
reversible do |dir|
|
9
|
+
dir.up { _validate_table_constraint(*args) }
|
10
|
+
dir.down do
|
11
|
+
# No-op - it's safe to validate an already validated constraint
|
12
|
+
# https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
|
13
|
+
# "Nothing happens if the constraint is already marked valid."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def _validate_table_constraint(table, constraint_name)
|
21
|
+
execute("ALTER TABLE #{Util.wrap_identifier(table)} VALIDATE CONSTRAINT #{constraint_name}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rein/util.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
module Rein
|
2
2
|
# The {Util} module provides utility methods for handling options.
|
3
3
|
module Util
|
4
|
+
# Returns a new string with the suffix appended if required
|
5
|
+
def self.add_not_valid_suffix_if_required(sql, options)
|
6
|
+
suffix = options[:validate] == false ? ' NOT VALID' : ''
|
7
|
+
"#{sql}#{suffix}"
|
8
|
+
end
|
9
|
+
|
4
10
|
def self.conditions_with_if(conditions, options = {})
|
5
11
|
if options[:if].present?
|
6
12
|
"NOT (#{options[:if]}) OR (#{conditions})"
|
data/lib/rein/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rein
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Bassett
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-01-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -144,6 +144,7 @@ files:
|
|
144
144
|
- lib/rein/constraint/presence.rb
|
145
145
|
- lib/rein/constraint/primary_key.rb
|
146
146
|
- lib/rein/constraint/unique.rb
|
147
|
+
- lib/rein/constraint/validate.rb
|
147
148
|
- lib/rein/schema.rb
|
148
149
|
- lib/rein/type/enum.rb
|
149
150
|
- lib/rein/util.rb
|