database_consistency 3.0.5 → 3.0.7
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/lib/database_consistency/checkers/association_checkers/polymorphic_association_nullability_checker.rb +47 -0
- data/lib/database_consistency/checkers/index_checkers/redundant_unique_index_checker.rb +1 -1
- data/lib/database_consistency/checkers/validators_fraction_checkers/numericality_constraint_checker.rb +70 -0
- data/lib/database_consistency/helper.rb +10 -4
- data/lib/database_consistency/version.rb +1 -1
- data/lib/database_consistency/writers/simple/numericality_check_constraint_missing.rb +22 -0
- data/lib/database_consistency/writers/simple/polymorphic_association_nullability_mismatch.rb +31 -0
- data/lib/database_consistency.rb +4 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3cf17a396eca47c805aaa0bb4da89132a7c890b122a44793678ea316cd4f627c
|
|
4
|
+
data.tar.gz: 84a0f6ce121e286275a17683259db3531bdef446e21fa7b2459b77db8566bfd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5796a54dd3a57c34e5743b919b5407c0642235e0bc39c1c6da63a6bf960ca39df350c3e37031229f396194f4abe8607850680459b3846a87a6e5f1f4180d6282
|
|
7
|
+
data.tar.gz: e5e448dcaae9070e70f6a30000dad04a4bea1f3f481c1dc2212adc71196c3170da7d75fdf111bba55d7de026f714b368445bcbdb32a513e5d6fe9b4535a9847d
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabaseConsistency
|
|
4
|
+
module Checkers
|
|
5
|
+
# This class checks that polymorphic association columns have matching null constraints
|
|
6
|
+
class PolymorphicAssociationNullabilityChecker < AssociationChecker
|
|
7
|
+
Report = ReportBuilder.define(
|
|
8
|
+
DatabaseConsistency::Report,
|
|
9
|
+
:foreign_key,
|
|
10
|
+
:foreign_type
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def preconditions
|
|
16
|
+
association.belongs_to? && association.polymorphic? && foreign_key_column && foreign_type_column
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def check
|
|
20
|
+
if foreign_key_column.null == foreign_type_column.null
|
|
21
|
+
report_template(:ok)
|
|
22
|
+
else
|
|
23
|
+
report_template(:fail, error_slug: :polymorphic_association_nullability_mismatch)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def report_template(status, error_slug: nil)
|
|
28
|
+
Report.new(
|
|
29
|
+
status: status,
|
|
30
|
+
error_slug: error_slug,
|
|
31
|
+
error_message: nil,
|
|
32
|
+
foreign_key: association.foreign_key.to_s,
|
|
33
|
+
foreign_type: association.foreign_type.to_s,
|
|
34
|
+
**report_attributes
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def foreign_key_column
|
|
39
|
+
@foreign_key_column ||= model.columns_hash[association.foreign_key.to_s]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def foreign_type_column
|
|
43
|
+
@foreign_type_column ||= model.columns_hash[association.foreign_type.to_s]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -60,7 +60,7 @@ module DatabaseConsistency
|
|
|
60
60
|
|
|
61
61
|
def contain_index?(another_index)
|
|
62
62
|
another_index_columns = Helper.extract_index_columns(another_index.columns)
|
|
63
|
-
|
|
63
|
+
(another_index_columns - index_columns).empty?
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def index_columns
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabaseConsistency
|
|
4
|
+
module Checkers
|
|
5
|
+
# This class checks if numericality validator has check constraint in the database
|
|
6
|
+
class NumericalityConstraintChecker < ValidatorsFractionChecker
|
|
7
|
+
SQL_KEYWORDS = %w[
|
|
8
|
+
and or not null is true false in like between case when then else end check
|
|
9
|
+
exists select where having order group limit offset distinct all any some
|
|
10
|
+
from join on using as by union into values set update delete insert with
|
|
11
|
+
].freeze
|
|
12
|
+
PLAIN_IDENTIFIER_PATTERN =
|
|
13
|
+
/\b([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)\b(?!\s*\()/.freeze
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def filter(validator)
|
|
18
|
+
validator.kind == :numericality
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def preconditions
|
|
22
|
+
model.connection.respond_to?(:check_constraints) &&
|
|
23
|
+
model.connection.table_exists?(model.table_name) &&
|
|
24
|
+
column
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def check
|
|
28
|
+
if check_constraint_for_column?
|
|
29
|
+
report_template(:ok)
|
|
30
|
+
else
|
|
31
|
+
report_template(:fail, error_slug: :numericality_check_constraint_missing)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def check_constraint_for_column?
|
|
36
|
+
check_constraints.any? do |constraint|
|
|
37
|
+
constraint_columns(constraint.expression).include?(column.name.downcase)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def check_constraints
|
|
42
|
+
@check_constraints ||= model.connection.check_constraints(model.table_name)
|
|
43
|
+
rescue StandardError
|
|
44
|
+
[]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def constraint_columns(expression)
|
|
48
|
+
expression_sql = expression.to_s
|
|
49
|
+
# Captures identifiers in three forms:
|
|
50
|
+
# 1) "double quoted", 2) `backtick quoted`, 3) plain SQL identifiers.
|
|
51
|
+
# Plain identifiers followed by `(` are excluded to skip SQL function names.
|
|
52
|
+
quoted_identifiers = expression_sql.scan(/"([^"]+)"|`([^`]+)`/).flatten.compact
|
|
53
|
+
# Only the column part is needed for comparison, so `table.column` is
|
|
54
|
+
# reduced to `column`.
|
|
55
|
+
# `(?!\s*\()` excludes function names such as `ABS(`.
|
|
56
|
+
plain_identifiers = expression_sql.scan(PLAIN_IDENTIFIER_PATTERN)
|
|
57
|
+
.flatten
|
|
58
|
+
.map { |identifier| identifier.split('.').last }
|
|
59
|
+
|
|
60
|
+
(quoted_identifiers + plain_identifiers).map(&:downcase).reject do |identifier|
|
|
61
|
+
SQL_KEYWORDS.include?(identifier)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def column
|
|
66
|
+
@column ||= model.columns.find { |field| field.name == attribute.to_s }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -156,12 +156,18 @@ module DatabaseConsistency
|
|
|
156
156
|
nil
|
|
157
157
|
end
|
|
158
158
|
|
|
159
|
-
# Builds the effective uniqueness constraint enforced by a validator
|
|
160
|
-
#
|
|
161
|
-
# `
|
|
159
|
+
# Builds the effective uniqueness constraint enforced by a validator.
|
|
160
|
+
#
|
|
161
|
+
# When the validator carries an explicit `conditions` proc, that proc is the
|
|
162
|
+
# authoritative partial predicate. The implicit `allow_nil` / `allow_blank`
|
|
163
|
+
# guard on the validated attribute is redundant against a unique index (which
|
|
164
|
+
# already treats NULLs as distinct), so it is only used as a fallback when no
|
|
165
|
+
# explicit conditions are present. Otherwise gems that always set `allow_nil`
|
|
166
|
+
# (e.g. database_validations) would append a duplicate or extra
|
|
167
|
+
# `attribute IS NOT NULL` clause and never match the partial index.
|
|
162
168
|
def uniqueness_validator_where_sql(model, attribute, validator)
|
|
163
169
|
conditions_sql = conditions_where_sql(model, validator.options[:conditions])
|
|
164
|
-
guard_sql = uniqueness_validator_guard_sql(model, attribute, validator)
|
|
170
|
+
guard_sql = conditions_sql ? nil : uniqueness_validator_guard_sql(model, attribute, validator)
|
|
165
171
|
|
|
166
172
|
sql_parts = [conditions_sql, guard_sql].reject { |part| part.nil? || part == '' }
|
|
167
173
|
return nil if sql_parts.empty?
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabaseConsistency
|
|
4
|
+
module Writers
|
|
5
|
+
module Simple
|
|
6
|
+
class NumericalityCheckConstraintMissing < Base # :nodoc:
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def template
|
|
10
|
+
'column has a numericality validator but does not have a CHECK constraint'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def unique_attributes
|
|
14
|
+
{
|
|
15
|
+
table_or_model_name: report.table_or_model_name,
|
|
16
|
+
column_or_attribute_name: report.column_or_attribute_name
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DatabaseConsistency
|
|
4
|
+
module Writers
|
|
5
|
+
module Simple
|
|
6
|
+
class PolymorphicAssociationNullabilityMismatch < Base # :nodoc:
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def template
|
|
10
|
+
'polymorphic association columns (%<foreign_key>s and %<foreign_type>s) should have matching null constraints'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def attributes
|
|
14
|
+
{
|
|
15
|
+
foreign_key: report.foreign_key,
|
|
16
|
+
foreign_type: report.foreign_type
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def unique_attributes
|
|
21
|
+
{
|
|
22
|
+
table_or_model_name: report.table_or_model_name,
|
|
23
|
+
column_or_attribute_name: report.column_or_attribute_name,
|
|
24
|
+
foreign_key: report.foreign_key,
|
|
25
|
+
foreign_type: report.foreign_type
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/database_consistency.rb
CHANGED
|
@@ -36,6 +36,7 @@ require 'database_consistency/writers/simple/missing_uniqueness_validation'
|
|
|
36
36
|
require 'database_consistency/writers/simple/null_constraint_misses_validator'
|
|
37
37
|
require 'database_consistency/writers/simple/null_constraint_missing'
|
|
38
38
|
require 'database_consistency/writers/simple/possible_null'
|
|
39
|
+
require 'database_consistency/writers/simple/numericality_check_constraint_missing'
|
|
39
40
|
require 'database_consistency/writers/simple/small_primary_key'
|
|
40
41
|
require 'database_consistency/writers/simple/inconsistent_enum_type'
|
|
41
42
|
require 'database_consistency/writers/simple/enum_values_inconsistent_with_ar_enum'
|
|
@@ -46,6 +47,7 @@ require 'database_consistency/writers/simple/missing_association_class'
|
|
|
46
47
|
require 'database_consistency/writers/simple/missing_table'
|
|
47
48
|
require 'database_consistency/writers/simple/implicit_order_column_missing'
|
|
48
49
|
require 'database_consistency/writers/simple/missing_dependent_destroy'
|
|
50
|
+
require 'database_consistency/writers/simple/polymorphic_association_nullability_mismatch'
|
|
49
51
|
require 'database_consistency/writers/simple/missing_index_find_by'
|
|
50
52
|
require 'database_consistency/writers/simple/view_missing_primary_key'
|
|
51
53
|
require 'database_consistency/writers/simple/view_primary_key_column_missing'
|
|
@@ -91,6 +93,7 @@ require 'database_consistency/checkers/association_checkers/foreign_key_type_che
|
|
|
91
93
|
require 'database_consistency/checkers/association_checkers/foreign_key_cascade_checker'
|
|
92
94
|
require 'database_consistency/checkers/association_checkers/missing_association_class_checker'
|
|
93
95
|
require 'database_consistency/checkers/association_checkers/missing_dependent_destroy_checker'
|
|
96
|
+
require 'database_consistency/checkers/association_checkers/polymorphic_association_nullability_checker'
|
|
94
97
|
|
|
95
98
|
require 'database_consistency/checkers/column_checkers/column_checker'
|
|
96
99
|
require 'database_consistency/checkers/column_checkers/null_constraint_checker'
|
|
@@ -107,6 +110,7 @@ require 'database_consistency/checkers/validator_checkers/case_sensitive_unique_
|
|
|
107
110
|
|
|
108
111
|
require 'database_consistency/checkers/validators_fraction_checkers/validators_fraction_checker'
|
|
109
112
|
require 'database_consistency/checkers/validators_fraction_checkers/column_presence_checker'
|
|
113
|
+
require 'database_consistency/checkers/validators_fraction_checkers/numericality_constraint_checker'
|
|
110
114
|
|
|
111
115
|
require 'database_consistency/checkers/index_checkers/index_checker'
|
|
112
116
|
require 'database_consistency/checkers/index_checkers/unique_index_checker'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: database_consistency
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgeniy Demin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -153,6 +153,7 @@ files:
|
|
|
153
153
|
- lib/database_consistency/checkers/association_checkers/missing_association_class_checker.rb
|
|
154
154
|
- lib/database_consistency/checkers/association_checkers/missing_dependent_destroy_checker.rb
|
|
155
155
|
- lib/database_consistency/checkers/association_checkers/missing_index_checker.rb
|
|
156
|
+
- lib/database_consistency/checkers/association_checkers/polymorphic_association_nullability_checker.rb
|
|
156
157
|
- lib/database_consistency/checkers/base_checker.rb
|
|
157
158
|
- lib/database_consistency/checkers/column_checkers/column_checker.rb
|
|
158
159
|
- lib/database_consistency/checkers/column_checkers/enum_value_checker.rb
|
|
@@ -175,6 +176,7 @@ files:
|
|
|
175
176
|
- lib/database_consistency/checkers/validator_checkers/missing_unique_index_checker.rb
|
|
176
177
|
- lib/database_consistency/checkers/validator_checkers/validator_checker.rb
|
|
177
178
|
- lib/database_consistency/checkers/validators_fraction_checkers/column_presence_checker.rb
|
|
179
|
+
- lib/database_consistency/checkers/validators_fraction_checkers/numericality_constraint_checker.rb
|
|
178
180
|
- lib/database_consistency/checkers/validators_fraction_checkers/validators_fraction_checker.rb
|
|
179
181
|
- lib/database_consistency/configuration.rb
|
|
180
182
|
- lib/database_consistency/databases/factory.rb
|
|
@@ -240,6 +242,8 @@ files:
|
|
|
240
242
|
- lib/database_consistency/writers/simple/null_constraint_association_misses_validator.rb
|
|
241
243
|
- lib/database_consistency/writers/simple/null_constraint_misses_validator.rb
|
|
242
244
|
- lib/database_consistency/writers/simple/null_constraint_missing.rb
|
|
245
|
+
- lib/database_consistency/writers/simple/numericality_check_constraint_missing.rb
|
|
246
|
+
- lib/database_consistency/writers/simple/polymorphic_association_nullability_mismatch.rb
|
|
243
247
|
- lib/database_consistency/writers/simple/possible_null.rb
|
|
244
248
|
- lib/database_consistency/writers/simple/redundant_case_insensitive_option.rb
|
|
245
249
|
- lib/database_consistency/writers/simple/redundant_index.rb
|