database_consistency 3.0.5 → 3.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f88fd56f01f639d5614fa82f5e76f6d4a4dfb05c04c246f5baeedf93fd01fe24
4
- data.tar.gz: 0bb41479e542c995569b54a6cfc188ea39103c30a074560f0bae15f8223c1128
3
+ metadata.gz: d1a3f79e718dbab025a1077436d34a99f96441d4c38ea6eeab44b48a9d47017b
4
+ data.tar.gz: 3d923752051575203683bf72e4fa91d538e668d0f3259be1a3c85f078bd35fc8
5
5
  SHA512:
6
- metadata.gz: 54baea922639aec00a1c1edb9aaef5e0d70ec91a2ce63552fcb2ab502b93c1a2779c3227fdb55c7cb03afd17a346624cfc192d84c07480583b44bba00554eea5
7
- data.tar.gz: 69a60f5b359e2bfc89765756115e510646d6167fd2819da7f25451302c52f1f54c2c590e1a487eb2291c9e747a54e80bf4e193b9db506bc13f8cc7a580b0be8c
6
+ metadata.gz: 483695e3e1bfb73d833f71832f770e6783bab926c785bb54e097a0f527adc66a33112e7c83f3e7f1f849d5f0015d8f16e01bcff135d55933c683f76dbb58626d
7
+ data.tar.gz: f0686a44bb3fe3e842ad8b9f72a4b11801cad4745c389665d015bc0255529709b4b4f843d4a97a5064e5ede8643c3f5d36b800c2c4f63ad7b0f4bf6119544f4b
@@ -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
- index_columns & another_index_columns == another_index_columns
63
+ (another_index_columns - index_columns).empty?
64
64
  end
65
65
 
66
66
  def index_columns
@@ -0,0 +1,71 @@
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
+ validators.any?
26
+ end
27
+
28
+ def check
29
+ if check_constraint_for_column?
30
+ report_template(:ok)
31
+ else
32
+ report_template(:fail, error_slug: :numericality_check_constraint_missing)
33
+ end
34
+ end
35
+
36
+ def check_constraint_for_column?
37
+ check_constraints.any? do |constraint|
38
+ constraint_columns(constraint.expression).include?(column.name.downcase)
39
+ end
40
+ end
41
+
42
+ def check_constraints
43
+ @check_constraints ||= model.connection.check_constraints(model.table_name)
44
+ rescue StandardError
45
+ []
46
+ end
47
+
48
+ def constraint_columns(expression)
49
+ expression_sql = expression.to_s
50
+ # Captures identifiers in three forms:
51
+ # 1) "double quoted", 2) `backtick quoted`, 3) plain SQL identifiers.
52
+ # Plain identifiers followed by `(` are excluded to skip SQL function names.
53
+ quoted_identifiers = expression_sql.scan(/"([^"]+)"|`([^`]+)`/).flatten.compact
54
+ # Only the column part is needed for comparison, so `table.column` is
55
+ # reduced to `column`.
56
+ # `(?!\s*\()` excludes function names such as `ABS(`.
57
+ plain_identifiers = expression_sql.scan(PLAIN_IDENTIFIER_PATTERN)
58
+ .flatten
59
+ .map { |identifier| identifier.split('.').last }
60
+
61
+ (quoted_identifiers + plain_identifiers).map(&:downcase).reject do |identifier|
62
+ SQL_KEYWORDS.include?(identifier)
63
+ end
64
+ end
65
+
66
+ def column
67
+ @column ||= model.columns.find { |field| field.name == attribute.to_s }
68
+ end
69
+ end
70
+ end
71
+ 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 by
160
- # combining its explicit `conditions` proc with implicit guards such as
161
- # `allow_nil` / `allow_blank`.
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?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatabaseConsistency
4
- VERSION = '3.0.5'
4
+ VERSION = '3.0.8'
5
5
  end
@@ -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
@@ -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.5
4
+ version: 3.0.8
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-05-23 00:00:00.000000000 Z
11
+ date: 2026-07-28 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