database_consistency 0.8.9 → 1.0.0
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.rb +5 -0
- data/lib/database_consistency/checkers/association_checkers/foreign_key_type_checker.rb +1 -1
- data/lib/database_consistency/checkers/index_checkers/index_checker.rb +23 -0
- data/lib/database_consistency/checkers/index_checkers/redundant_index_checker.rb +55 -0
- data/lib/database_consistency/checkers/index_checkers/unique_index_checker.rb +47 -0
- data/lib/database_consistency/checkers/validator_checkers/missing_unique_index_checker.rb +4 -34
- data/lib/database_consistency/helper.rb +34 -0
- data/lib/database_consistency/processors/base_processor.rb +2 -1
- data/lib/database_consistency/processors/indexes_processor.rb +30 -0
- data/lib/database_consistency/templates/rails_defaults.yml +2 -0
- data/lib/database_consistency/version.rb +1 -1
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b074df0ae4a1316afdb9874c811a02189fd664e00b85220730dd0ae985a36c76
|
4
|
+
data.tar.gz: f870f9f74117a6ea1b5592e7610becc5f854c375ee4a82f9720b287ae9288462
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99e298ec2e58c3bfcc5c10a9e294891b14eee23b1a4c9ccbf3e78f5e500480baf1c3ec397d0a19f5af6e83f05dbe9a297e062358eab9699d324b6b06a1255889
|
7
|
+
data.tar.gz: 7d742b1d5985792e2fa3b3a6d5195ccb91c4b49cdbcbce800de6db29a16ef1045f4bf786b3d88e981d23b573282d4ddb821ede90951890ab45682c16b37ede7b
|
data/lib/database_consistency.rb
CHANGED
@@ -33,11 +33,16 @@ require 'database_consistency/checkers/validator_checkers/missing_unique_index_c
|
|
33
33
|
require 'database_consistency/checkers/validators_fraction_checkers/validators_fraction_checker'
|
34
34
|
require 'database_consistency/checkers/validators_fraction_checkers/column_presence_checker'
|
35
35
|
|
36
|
+
require 'database_consistency/checkers/index_checkers/index_checker'
|
37
|
+
require 'database_consistency/checkers/index_checkers/unique_index_checker'
|
38
|
+
require 'database_consistency/checkers/index_checkers/redundant_index_checker'
|
39
|
+
|
36
40
|
require 'database_consistency/processors/base_processor'
|
37
41
|
require 'database_consistency/processors/associations_processor'
|
38
42
|
require 'database_consistency/processors/validators_processor'
|
39
43
|
require 'database_consistency/processors/columns_processor'
|
40
44
|
require 'database_consistency/processors/validators_fractions_processor'
|
45
|
+
require 'database_consistency/processors/indexes_processor'
|
41
46
|
|
42
47
|
# The root module
|
43
48
|
module DatabaseConsistency
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DatabaseConsistency
|
4
|
+
module Checkers
|
5
|
+
# The base class for table checkers
|
6
|
+
class IndexChecker < BaseChecker
|
7
|
+
attr_reader :model, :index
|
8
|
+
|
9
|
+
def initialize(model, index)
|
10
|
+
@model = model
|
11
|
+
@index = index
|
12
|
+
end
|
13
|
+
|
14
|
+
def column_or_attribute_name
|
15
|
+
@column_or_attribute_name ||= index.name.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def table_or_model_name
|
19
|
+
@table_or_model_name ||= model.name.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DatabaseConsistency
|
4
|
+
module Checkers
|
5
|
+
# This class checks redundant database indexes
|
6
|
+
class RedundantIndexChecker < IndexChecker
|
7
|
+
# Message templates
|
8
|
+
REDUNDANT_INDEX = 'index is redundant as (%index) covers it'
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# We skip check when:
|
13
|
+
# - index is unique
|
14
|
+
def preconditions
|
15
|
+
!index.unique
|
16
|
+
end
|
17
|
+
|
18
|
+
# Table of possible statuses
|
19
|
+
# | validation | status |
|
20
|
+
# | ---------- | ------ |
|
21
|
+
# | provided | ok |
|
22
|
+
# | redundant | fail |
|
23
|
+
#
|
24
|
+
def check
|
25
|
+
if covered_by_index
|
26
|
+
report_template(:fail, render_message)
|
27
|
+
else
|
28
|
+
report_template(:ok)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def render_message
|
33
|
+
REDUNDANT_INDEX.sub('%index', covered_by_index.name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def covered_by_index
|
37
|
+
@covered_by_index ||=
|
38
|
+
model.connection.indexes(model.table_name).find do |another_index|
|
39
|
+
next if index.name == another_index.name
|
40
|
+
|
41
|
+
include_index_as_prefix?(another_index)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def include_index_as_prefix?(another_index)
|
46
|
+
another_index_columns = Helper.extract_index_columns(another_index.columns)
|
47
|
+
index_columns == another_index_columns.first(index_columns.size)
|
48
|
+
end
|
49
|
+
|
50
|
+
def index_columns
|
51
|
+
@index_columns ||= Helper.extract_index_columns(index.columns)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DatabaseConsistency
|
4
|
+
module Checkers
|
5
|
+
# This class checks missing uniqueness validator
|
6
|
+
class UniqueIndexChecker < IndexChecker
|
7
|
+
# Message templates
|
8
|
+
VALIDATOR_MISSING = 'index is unique in the database but do not have uniqueness validator'
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
# We skip check when:
|
13
|
+
# - index is not unique
|
14
|
+
def preconditions
|
15
|
+
index.unique
|
16
|
+
end
|
17
|
+
|
18
|
+
# Table of possible statuses
|
19
|
+
# | validation | status |
|
20
|
+
# | ---------- | ------ |
|
21
|
+
# | provided | ok |
|
22
|
+
# | missing | fail |
|
23
|
+
#
|
24
|
+
def check
|
25
|
+
if valid?
|
26
|
+
report_template(:ok)
|
27
|
+
else
|
28
|
+
report_template(:fail, VALIDATOR_MISSING)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid?
|
33
|
+
uniqueness_validators = model.validators.select { |validator| validator.kind == :uniqueness }
|
34
|
+
|
35
|
+
uniqueness_validators.any? do |validator|
|
36
|
+
validator.attributes.any? do |attribute|
|
37
|
+
sorted_index_columns == Helper.sorted_uniqueness_validator_columns(attribute, validator, model)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def sorted_index_columns
|
43
|
+
@sorted_index_columns ||= Helper.extract_index_columns(index.columns).sort
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -7,7 +7,7 @@ module DatabaseConsistency
|
|
7
7
|
MISSING_INDEX = 'model should have proper unique index in the database'
|
8
8
|
|
9
9
|
def column_or_attribute_name
|
10
|
-
@column_or_attribute_name ||=
|
10
|
+
@column_or_attribute_name ||= Helper.uniqueness_validator_columns(attribute, validator, model).join('+')
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
@@ -33,42 +33,12 @@ module DatabaseConsistency
|
|
33
33
|
|
34
34
|
def unique_index
|
35
35
|
@unique_index ||= model.connection.indexes(model.table_name).find do |index|
|
36
|
-
index.unique && extract_index_columns(index.columns).sort ==
|
36
|
+
index.unique && Helper.extract_index_columns(index.columns).sort == sorted_uniqueness_validator_columns
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
return index_columns unless index_columns.is_a?(String)
|
43
|
-
|
44
|
-
index_columns.split(',')
|
45
|
-
.map(&:strip)
|
46
|
-
.map { |str| str.gsub(/lower\(/i, 'lower(') }
|
47
|
-
.map { |str| str.gsub(/\(([^)]+)\)::\w+/, '\1') }
|
48
|
-
.map { |str| str.gsub(/'([^)]+)'::\w+/, '\1') }
|
49
|
-
end
|
50
|
-
|
51
|
-
def index_columns
|
52
|
-
@index_columns ||= ([wrapped_attribute_name] + scope_columns).map(&:to_s)
|
53
|
-
end
|
54
|
-
|
55
|
-
def scope_columns
|
56
|
-
@scope_columns ||= Array.wrap(validator.options[:scope]).map do |scope_item|
|
57
|
-
model._reflect_on_association(scope_item)&.foreign_key || scope_item
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def sorted_index_columns
|
62
|
-
@sorted_index_columns ||= index_columns.sort
|
63
|
-
end
|
64
|
-
|
65
|
-
# @return [String]
|
66
|
-
def wrapped_attribute_name
|
67
|
-
if validator.options[:case_sensitive].nil? || validator.options[:case_sensitive]
|
68
|
-
attribute
|
69
|
-
else
|
70
|
-
"lower(#{attribute})"
|
71
|
-
end
|
40
|
+
def sorted_uniqueness_validator_columns
|
41
|
+
@sorted_uniqueness_validator_columns ||= Helper.sorted_uniqueness_validator_columns(attribute, validator, model)
|
72
42
|
end
|
73
43
|
end
|
74
44
|
end
|
@@ -34,5 +34,39 @@ module DatabaseConsistency
|
|
34
34
|
|
35
35
|
associations
|
36
36
|
end
|
37
|
+
|
38
|
+
# @return [Array<String>]
|
39
|
+
def extract_index_columns(index_columns)
|
40
|
+
return index_columns unless index_columns.is_a?(String)
|
41
|
+
|
42
|
+
index_columns.split(',')
|
43
|
+
.map(&:strip)
|
44
|
+
.map { |str| str.gsub(/lower\(/i, 'lower(') }
|
45
|
+
.map { |str| str.gsub(/\(([^)]+)\)::\w+/, '\1') }
|
46
|
+
.map { |str| str.gsub(/'([^)]+)'::\w+/, '\1') }
|
47
|
+
end
|
48
|
+
|
49
|
+
def sorted_uniqueness_validator_columns(attribute, validator, model)
|
50
|
+
uniqueness_validator_columns(attribute, validator, model).sort
|
51
|
+
end
|
52
|
+
|
53
|
+
def uniqueness_validator_columns(attribute, validator, model)
|
54
|
+
([wrapped_attribute_name(attribute, validator)] + scope_columns(validator, model)).map(&:to_s)
|
55
|
+
end
|
56
|
+
|
57
|
+
def scope_columns(validator, model)
|
58
|
+
Array.wrap(validator.options[:scope]).map do |scope_item|
|
59
|
+
model._reflect_on_association(scope_item)&.foreign_key || scope_item
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
def wrapped_attribute_name(attribute, validator)
|
65
|
+
if validator.options[:case_sensitive].nil? || validator.options[:case_sensitive]
|
66
|
+
attribute
|
67
|
+
else
|
68
|
+
"lower(#{attribute})"
|
69
|
+
end
|
70
|
+
end
|
37
71
|
end
|
38
72
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DatabaseConsistency
|
4
|
+
module Processors
|
5
|
+
# The class to process indexes
|
6
|
+
class IndexesProcessor < BaseProcessor
|
7
|
+
CHECKERS = [
|
8
|
+
Checkers::UniqueIndexChecker,
|
9
|
+
Checkers::RedundantIndexChecker
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def check # rubocop:disable Metrics/AbcSize
|
15
|
+
Helper.parent_models.flat_map do |model|
|
16
|
+
next unless configuration.enabled?(model.name.to_s)
|
17
|
+
|
18
|
+
indexes = ActiveRecord::Base.connection.indexes(model.table_name)
|
19
|
+
|
20
|
+
indexes.flat_map do |index|
|
21
|
+
enabled_checkers.map do |checker_class|
|
22
|
+
checker = checker_class.new(model, index)
|
23
|
+
checker.report_if_enabled?(configuration)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end.compact
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
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: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evgeniy Demin
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
- - "~>"
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '1.3'
|
125
|
-
description:
|
125
|
+
description:
|
126
126
|
email:
|
127
127
|
- lawliet.djez@gmail.com
|
128
128
|
executables:
|
@@ -140,6 +140,9 @@ files:
|
|
140
140
|
- lib/database_consistency/checkers/column_checkers/length_constraint_checker.rb
|
141
141
|
- lib/database_consistency/checkers/column_checkers/null_constraint_checker.rb
|
142
142
|
- lib/database_consistency/checkers/column_checkers/primary_key_type_checker.rb
|
143
|
+
- lib/database_consistency/checkers/index_checkers/index_checker.rb
|
144
|
+
- lib/database_consistency/checkers/index_checkers/redundant_index_checker.rb
|
145
|
+
- lib/database_consistency/checkers/index_checkers/unique_index_checker.rb
|
143
146
|
- lib/database_consistency/checkers/validator_checkers/belongs_to_presence_checker.rb
|
144
147
|
- lib/database_consistency/checkers/validator_checkers/missing_unique_index_checker.rb
|
145
148
|
- lib/database_consistency/checkers/validator_checkers/validator_checker.rb
|
@@ -154,6 +157,7 @@ files:
|
|
154
157
|
- lib/database_consistency/processors/associations_processor.rb
|
155
158
|
- lib/database_consistency/processors/base_processor.rb
|
156
159
|
- lib/database_consistency/processors/columns_processor.rb
|
160
|
+
- lib/database_consistency/processors/indexes_processor.rb
|
157
161
|
- lib/database_consistency/processors/validators_fractions_processor.rb
|
158
162
|
- lib/database_consistency/processors/validators_processor.rb
|
159
163
|
- lib/database_consistency/rescue_error.rb
|
@@ -165,7 +169,7 @@ homepage: https://github.com/djezzzl/database_consistency
|
|
165
169
|
licenses:
|
166
170
|
- MIT
|
167
171
|
metadata: {}
|
168
|
-
post_install_message:
|
172
|
+
post_install_message:
|
169
173
|
rdoc_options: []
|
170
174
|
require_paths:
|
171
175
|
- lib
|
@@ -181,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
181
185
|
version: '0'
|
182
186
|
requirements: []
|
183
187
|
rubygems_version: 3.0.8
|
184
|
-
signing_key:
|
188
|
+
signing_key:
|
185
189
|
specification_version: 4
|
186
190
|
summary: Provide an easy way to check the consistency of the database constraints
|
187
191
|
with the application validations.
|