database_consistency 1.1.6 → 1.1.11
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/bin/database_consistency +37 -12
- data/lib/database_consistency/checkers/association_checkers/foreign_key_checker.rb +1 -1
- data/lib/database_consistency/checkers/association_checkers/foreign_key_type_checker.rb +29 -38
- data/lib/database_consistency/checkers/validators_fraction_checkers/column_presence_checker.rb +10 -5
- data/lib/database_consistency/configuration.rb +32 -9
- data/lib/database_consistency/databases/types/base.rb +12 -0
- data/lib/database_consistency/databases/types/sqlite.rb +3 -2
- data/lib/database_consistency/helper.rb +8 -0
- data/lib/database_consistency/version.rb +1 -1
- data/lib/database_consistency.rb +2 -2
- metadata +19 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f837d273af4845f139d3dc53fd3131d4224383576939984865480b65a022ad07
|
|
4
|
+
data.tar.gz: acba58e69f8a8a8b2083318e983bac226c0681fb5925e927a412fee573ccdc7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 386730f70655e59069f9d90a42a8dcba7d4ed9fefb61ac0d6abcd56bce725be7a699441f7d8b357964c773e06f3d362c6d289d1dfc19c1e334d1b2e956e64875
|
|
7
|
+
data.tar.gz: b37bee82b425c808658369824084fa92a93ff9a733464303b0343bfbf42f6c493286edafd87d051b78810c1d6b5d6eb85ba2fb09d637f10dca9bb22e35dce283
|
data/bin/database_consistency
CHANGED
|
@@ -1,27 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
+
require_relative '../lib/database_consistency/configuration'
|
|
4
|
+
|
|
5
|
+
default_config = DatabaseConsistency::Configuration::DEFAULT_PATH
|
|
6
|
+
|
|
3
7
|
if ARGV.include?('install')
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
file_exists = File.exists?(
|
|
8
|
+
require 'pathname'
|
|
9
|
+
|
|
10
|
+
file_exists = File.exists?(default_config)
|
|
7
11
|
rules = Pathname.new(__FILE__).dirname.join('..', 'lib', 'database_consistency', 'templates', 'rails_defaults.yml').read
|
|
8
|
-
if file_exists && File.foreach(
|
|
9
|
-
puts "#{
|
|
12
|
+
if file_exists && File.foreach(default_config).grep(Regexp.new(rules.lines.first.chomp)).any?
|
|
13
|
+
puts "#{default_config} is already present"
|
|
10
14
|
else
|
|
11
|
-
File.open(
|
|
15
|
+
File.open(default_config, 'a') do |file|
|
|
12
16
|
file << "\n" * 2 if file_exists
|
|
13
17
|
file << rules
|
|
14
18
|
end
|
|
15
|
-
puts "#{
|
|
19
|
+
puts "#{default_config} #{file_exists ? 'updated' : 'added'}"
|
|
16
20
|
end
|
|
17
21
|
exit 0
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require 'optparse'
|
|
25
|
+
|
|
26
|
+
config = [default_config]
|
|
27
|
+
opt_parser = OptionParser.new do |opts|
|
|
28
|
+
opts.banner = <<-DESC
|
|
29
|
+
Usage: database_consistency install - run installation
|
|
30
|
+
database_consistency [options]
|
|
31
|
+
DESC
|
|
32
|
+
|
|
33
|
+
opts.on('-cFILE', '--config=FILE', 'Use additional configuration file') do |f|
|
|
34
|
+
config << f
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
opts.on('-h', '--help', 'Prints this help') do
|
|
38
|
+
puts opts
|
|
39
|
+
exit
|
|
22
40
|
end
|
|
23
41
|
end
|
|
24
42
|
|
|
43
|
+
opt_parser.parse!
|
|
44
|
+
|
|
45
|
+
base_dir = File.join(Dir.pwd, ARGV.first.to_s)
|
|
46
|
+
unless File.realpath(base_dir).start_with?(Dir.pwd)
|
|
47
|
+
puts "\nWarning! You are going out of current directory, ruby version may be wrong and some gems may be missing.\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
25
50
|
# Load Rails project
|
|
26
51
|
begin
|
|
27
52
|
require File.join(base_dir, 'config', 'boot')
|
|
@@ -41,5 +66,5 @@ $LOAD_PATH.unshift(File.expand_path('lib', __dir__))
|
|
|
41
66
|
require 'database_consistency'
|
|
42
67
|
|
|
43
68
|
# Process checks
|
|
44
|
-
code = DatabaseConsistency.run
|
|
69
|
+
code = DatabaseConsistency.run(config)
|
|
45
70
|
exit code
|
|
@@ -16,7 +16,7 @@ module DatabaseConsistency
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def supported?
|
|
19
|
-
return false if ActiveRecord::VERSION::MAJOR < 5 &&
|
|
19
|
+
return false if ActiveRecord::VERSION::MAJOR < 5 && Helper.adapter == 'sqlite3'
|
|
20
20
|
|
|
21
21
|
true
|
|
22
22
|
end
|
|
@@ -2,18 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module DatabaseConsistency
|
|
4
4
|
module Checkers
|
|
5
|
-
# This class checks if association's foreign key type
|
|
5
|
+
# This class checks if association's foreign key type covers associated model's primary key (same or bigger)
|
|
6
6
|
class ForeignKeyTypeChecker < AssociationChecker
|
|
7
|
-
INCONSISTENT_TYPE =
|
|
8
|
-
|
|
9
|
-
TYPES = {
|
|
10
|
-
'serial' => 'integer',
|
|
11
|
-
'integer' => 'integer',
|
|
12
|
-
'int' => 'integer',
|
|
13
|
-
'bigserial' => 'bigint',
|
|
14
|
-
'bigint' => 'bigint',
|
|
15
|
-
'bigint unsigned' => 'bigint unsigned'
|
|
16
|
-
}.freeze
|
|
7
|
+
INCONSISTENT_TYPE = "foreign key (%a_f) with type (%a_t) doesn't cover primary key (%b_f) with type (%b_t)"
|
|
17
8
|
|
|
18
9
|
private
|
|
19
10
|
|
|
@@ -32,12 +23,12 @@ module DatabaseConsistency
|
|
|
32
23
|
end
|
|
33
24
|
|
|
34
25
|
# Table of possible statuses
|
|
35
|
-
# | type
|
|
36
|
-
# |
|
|
37
|
-
# |
|
|
38
|
-
# |
|
|
26
|
+
# | type | status |
|
|
27
|
+
# | ------------- | ------ |
|
|
28
|
+
# | covers | ok |
|
|
29
|
+
# | doesn't cover | fail |
|
|
39
30
|
def check
|
|
40
|
-
if converted_type(
|
|
31
|
+
if converted_type(associated_column).cover?(converted_type(primary_column))
|
|
41
32
|
report_template(:ok)
|
|
42
33
|
else
|
|
43
34
|
report_template(:fail, render_text)
|
|
@@ -51,40 +42,40 @@ module DatabaseConsistency
|
|
|
51
42
|
INCONSISTENT_TYPE
|
|
52
43
|
.gsub('%a_t', type(associated_column))
|
|
53
44
|
.gsub('%a_f', associated_key)
|
|
54
|
-
.gsub('%b_t', type(
|
|
55
|
-
.gsub('%b_f',
|
|
45
|
+
.gsub('%b_t', type(primary_column))
|
|
46
|
+
.gsub('%b_f', primary_key)
|
|
56
47
|
end
|
|
57
48
|
|
|
58
49
|
# @return [String]
|
|
59
|
-
def
|
|
60
|
-
@
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end
|
|
66
|
-
).to_s
|
|
50
|
+
def primary_key
|
|
51
|
+
@primary_key ||= if belongs_to_association?
|
|
52
|
+
association.association_primary_key
|
|
53
|
+
else
|
|
54
|
+
association.active_record_primary_key
|
|
55
|
+
end.to_s
|
|
67
56
|
end
|
|
68
57
|
|
|
69
58
|
# @return [String]
|
|
70
59
|
def associated_key
|
|
71
|
-
@associated_key ||=
|
|
72
|
-
if belongs_to_association?
|
|
73
|
-
association.association_primary_key
|
|
74
|
-
else
|
|
75
|
-
association.foreign_key
|
|
76
|
-
end
|
|
77
|
-
).to_s
|
|
60
|
+
@associated_key ||= association.foreign_key.to_s
|
|
78
61
|
end
|
|
79
62
|
|
|
80
63
|
# @return [ActiveRecord::ConnectionAdapters::Column]
|
|
81
|
-
def
|
|
82
|
-
@
|
|
64
|
+
def primary_column
|
|
65
|
+
@primary_column ||= if belongs_to_association?
|
|
66
|
+
column(association.klass, primary_key)
|
|
67
|
+
else
|
|
68
|
+
column(association.active_record, primary_key)
|
|
69
|
+
end
|
|
83
70
|
end
|
|
84
71
|
|
|
85
72
|
# @return [ActiveRecord::ConnectionAdapters::Column]
|
|
86
73
|
def associated_column
|
|
87
|
-
@associated_column ||=
|
|
74
|
+
@associated_column ||= if belongs_to_association?
|
|
75
|
+
column(association.active_record, associated_key)
|
|
76
|
+
else
|
|
77
|
+
column(association.klass, associated_key)
|
|
78
|
+
end
|
|
88
79
|
end
|
|
89
80
|
|
|
90
81
|
# @return [DatabaseConsistency::Databases::Factory]
|
|
@@ -116,9 +107,9 @@ module DatabaseConsistency
|
|
|
116
107
|
|
|
117
108
|
# @param [ActiveRecord::ConnectionAdapters::Column]
|
|
118
109
|
#
|
|
119
|
-
# @return [
|
|
110
|
+
# @return [DatabaseConsistency::Databases::Types::Base]
|
|
120
111
|
def converted_type(column)
|
|
121
|
-
database_factory.type(type(column))
|
|
112
|
+
database_factory.type(type(column))
|
|
122
113
|
end
|
|
123
114
|
|
|
124
115
|
# @return [Boolean]
|
data/lib/database_consistency/checkers/validators_fraction_checkers/column_presence_checker.rb
CHANGED
|
@@ -18,7 +18,11 @@ module DatabaseConsistency
|
|
|
18
18
|
|
|
19
19
|
# We skip the check when there are no presence validators
|
|
20
20
|
def preconditions
|
|
21
|
-
validators.any?
|
|
21
|
+
validators.any? && !association?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def association?
|
|
25
|
+
model._reflect_on_association(attribute)&.macro == :has_one
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
# Table of possible statuses
|
|
@@ -34,13 +38,13 @@ module DatabaseConsistency
|
|
|
34
38
|
report_template(:fail, e.message)
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
def
|
|
41
|
+
def weak_option?
|
|
38
42
|
validators.all? { |validator| validator.options.slice(*WEAK_OPTIONS).any? }
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def report_message
|
|
42
46
|
can_be_null = column.null
|
|
43
|
-
has_weak_option =
|
|
47
|
+
has_weak_option = weak_option?
|
|
44
48
|
|
|
45
49
|
return report_template(:ok) if can_be_null == has_weak_option
|
|
46
50
|
return report_template(:fail, POSSIBLE_NULL) unless can_be_null
|
|
@@ -53,8 +57,9 @@ module DatabaseConsistency
|
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
def column
|
|
56
|
-
@column ||= regular_column ||
|
|
57
|
-
|
|
60
|
+
@column ||= regular_column ||
|
|
61
|
+
association_reference_column ||
|
|
62
|
+
(raise Errors::MissingField, "column (#{attribute}) is missing in table (#{model.table_name}) but used for presence validation") # rubocop:disable Layout/LineLength
|
|
58
63
|
end
|
|
59
64
|
|
|
60
65
|
def regular_column
|
|
@@ -5,15 +5,20 @@ require 'yaml'
|
|
|
5
5
|
module DatabaseConsistency
|
|
6
6
|
# The class to access configuration options
|
|
7
7
|
class Configuration
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def initialize(
|
|
11
|
-
@configuration =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
DEFAULT_PATH = '.database_consistency.yml'
|
|
9
|
+
|
|
10
|
+
def initialize(filepaths = DEFAULT_PATH)
|
|
11
|
+
@configuration = Array(filepaths).each_with_object({}) do |filepath, result|
|
|
12
|
+
content =
|
|
13
|
+
if filepath && File.exist?(filepath)
|
|
14
|
+
data = load_yaml_config_file(filepath)
|
|
15
|
+
data.is_a?(Hash) ? data : {}
|
|
16
|
+
else
|
|
17
|
+
{}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
combine_configs!(result, content)
|
|
21
|
+
end
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
def debug?
|
|
@@ -48,6 +53,24 @@ module DatabaseConsistency
|
|
|
48
53
|
|
|
49
54
|
attr_reader :configuration
|
|
50
55
|
|
|
56
|
+
def load_yaml_config_file(filepath)
|
|
57
|
+
if YAML.respond_to?(:safe_load_file)
|
|
58
|
+
YAML.safe_load_file(filepath, aliases: true)
|
|
59
|
+
else
|
|
60
|
+
YAML.load_file(filepath)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def combine_configs!(config, new_config)
|
|
65
|
+
config.merge!(new_config) do |_key, val, new_val|
|
|
66
|
+
if val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
67
|
+
combine_configs!(val, new_val)
|
|
68
|
+
else
|
|
69
|
+
new_val
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
51
74
|
def settings
|
|
52
75
|
@settings ||= configuration['DatabaseConsistencySettings']
|
|
53
76
|
end
|
|
@@ -7,6 +7,11 @@ module DatabaseConsistency
|
|
|
7
7
|
class Base
|
|
8
8
|
attr_reader :type
|
|
9
9
|
|
|
10
|
+
COVERED_TYPES = {
|
|
11
|
+
'bigint' => %w[integer bigint],
|
|
12
|
+
'integer' => %w[integer smallint]
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
10
15
|
# @param [String] type
|
|
11
16
|
def initialize(type)
|
|
12
17
|
@type = type
|
|
@@ -16,6 +21,13 @@ module DatabaseConsistency
|
|
|
16
21
|
def convert
|
|
17
22
|
type
|
|
18
23
|
end
|
|
24
|
+
|
|
25
|
+
# @param [DatabaseConsistency::Databases::Types::Base]
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean]
|
|
28
|
+
def cover?(other_type)
|
|
29
|
+
(COVERED_TYPES[convert] || [convert]).include?(other_type.convert)
|
|
30
|
+
end
|
|
19
31
|
end
|
|
20
32
|
end
|
|
21
33
|
end
|
|
@@ -7,9 +7,10 @@ module DatabaseConsistency
|
|
|
7
7
|
class Sqlite < Base
|
|
8
8
|
TYPES = {
|
|
9
9
|
'bigserial' => 'bigint',
|
|
10
|
-
'bigint' => 'bigint',
|
|
11
10
|
'serial' => 'integer',
|
|
12
|
-
'integer' => '
|
|
11
|
+
'integer(8)' => 'bigint',
|
|
12
|
+
'integer(4)' => 'integer',
|
|
13
|
+
'integer(2)' => 'smallint'
|
|
13
14
|
}.freeze
|
|
14
15
|
|
|
15
16
|
# @return [String]
|
|
@@ -5,6 +5,14 @@ module DatabaseConsistency
|
|
|
5
5
|
module Helper
|
|
6
6
|
module_function
|
|
7
7
|
|
|
8
|
+
def adapter
|
|
9
|
+
if ActiveRecord::Base.respond_to?(:connection_config)
|
|
10
|
+
ActiveRecord::Base.connection_config[:adapter]
|
|
11
|
+
else
|
|
12
|
+
ActiveRecord::Base.connection_db_config.configuration_hash[:adapter]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
8
16
|
# Returns list of models to check
|
|
9
17
|
def models
|
|
10
18
|
ActiveRecord::Base.descendants.delete_if(&:abstract_class?).delete_if do |klass|
|
data/lib/database_consistency.rb
CHANGED
|
@@ -48,8 +48,8 @@ require 'database_consistency/processors/indexes_processor'
|
|
|
48
48
|
# The root module
|
|
49
49
|
module DatabaseConsistency
|
|
50
50
|
class << self
|
|
51
|
-
def run
|
|
52
|
-
configuration = Configuration.new
|
|
51
|
+
def run(*args)
|
|
52
|
+
configuration = Configuration.new(*args)
|
|
53
53
|
reports = Processors.reports(configuration)
|
|
54
54
|
|
|
55
55
|
Writers::SimpleWriter.write(
|
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: 1.1.
|
|
4
|
+
version: 1.1.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgeniy Demin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2022-01-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -56,14 +56,14 @@ dependencies:
|
|
|
56
56
|
name: pg
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - "
|
|
59
|
+
- - ">="
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
61
|
version: '0.2'
|
|
62
62
|
type: :development
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- - "
|
|
66
|
+
- - ">="
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
68
|
version: '0.2'
|
|
69
69
|
- !ruby/object:Gem::Dependency
|
|
@@ -94,6 +94,20 @@ dependencies:
|
|
|
94
94
|
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
96
|
version: '3.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rspec_junit_formatter
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0.4'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0.4'
|
|
97
111
|
- !ruby/object:Gem::Dependency
|
|
98
112
|
name: rubocop
|
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -185,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
185
199
|
- !ruby/object:Gem::Version
|
|
186
200
|
version: '0'
|
|
187
201
|
requirements: []
|
|
188
|
-
rubygems_version: 3.
|
|
202
|
+
rubygems_version: 3.2.22
|
|
189
203
|
signing_key:
|
|
190
204
|
specification_version: 4
|
|
191
205
|
summary: Provide an easy way to check the consistency of the database constraints
|