database_consistency 1.1.5 → 1.1.10
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 +33 -40
- data/lib/database_consistency/checkers/validators_fraction_checkers/column_presence_checker.rb +14 -3
- 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: 1b191ca6be5af749e9c3e7bd634696e5ecc648972f5b882954990bcc9e13dde2
|
4
|
+
data.tar.gz: 460e7edbac351faf21032e9d71c768d0e98b12ddafad792516bc5f4158122525
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b1da4a7207245abc904aa28fcbbf2d8fe7c15683f86ad55557ba10b6d7699103404c42ed33a6fda82a413e4a6ed73681579dfd9512c7480d60a5f3da7d9d335
|
7
|
+
data.tar.gz: a4a13d8527677783f144448bfbe892d282086fffbe6dd04121aa50052d435c2d370eee32ee7c378047a2cf47817fcee4b74b9e7d0f9ed4b652d80431948196cc
|
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,16 +23,18 @@ 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)
|
44
35
|
end
|
36
|
+
rescue Errors::MissingField => e
|
37
|
+
report_template(:fail, e.message)
|
45
38
|
end
|
46
39
|
|
47
40
|
# @return [String]
|
@@ -49,40 +42,40 @@ module DatabaseConsistency
|
|
49
42
|
INCONSISTENT_TYPE
|
50
43
|
.gsub('%a_t', type(associated_column))
|
51
44
|
.gsub('%a_f', associated_key)
|
52
|
-
.gsub('%b_t', type(
|
53
|
-
.gsub('%b_f',
|
45
|
+
.gsub('%b_t', type(primary_column))
|
46
|
+
.gsub('%b_f', primary_key)
|
54
47
|
end
|
55
48
|
|
56
49
|
# @return [String]
|
57
|
-
def
|
58
|
-
@
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
).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
|
65
56
|
end
|
66
57
|
|
67
58
|
# @return [String]
|
68
59
|
def associated_key
|
69
|
-
@associated_key ||=
|
70
|
-
if belongs_to_association?
|
71
|
-
association.association_primary_key
|
72
|
-
else
|
73
|
-
association.foreign_key
|
74
|
-
end
|
75
|
-
).to_s
|
60
|
+
@associated_key ||= association.foreign_key.to_s
|
76
61
|
end
|
77
62
|
|
78
63
|
# @return [ActiveRecord::ConnectionAdapters::Column]
|
79
|
-
def
|
80
|
-
@
|
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
|
81
70
|
end
|
82
71
|
|
83
72
|
# @return [ActiveRecord::ConnectionAdapters::Column]
|
84
73
|
def associated_column
|
85
|
-
@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
|
86
79
|
end
|
87
80
|
|
88
81
|
# @return [DatabaseConsistency::Databases::Factory]
|
@@ -101,8 +94,8 @@ module DatabaseConsistency
|
|
101
94
|
|
102
95
|
# @return [String]
|
103
96
|
def missing_field_error(table_name, column_name)
|
104
|
-
"
|
105
|
-
"field (#{column_name}) of table (#{table_name}) but it is missing
|
97
|
+
"association (#{association.name}) of class (#{association.active_record}) relies on "\
|
98
|
+
"field (#{column_name}) of table (#{table_name}) but it is missing"
|
106
99
|
end
|
107
100
|
|
108
101
|
# @param [ActiveRecord::ConnectionAdapters::Column] column
|
@@ -114,9 +107,9 @@ module DatabaseConsistency
|
|
114
107
|
|
115
108
|
# @param [ActiveRecord::ConnectionAdapters::Column]
|
116
109
|
#
|
117
|
-
# @return [
|
110
|
+
# @return [DatabaseConsistency::Databases::Types::Base]
|
118
111
|
def converted_type(column)
|
119
|
-
database_factory.type(type(column))
|
112
|
+
database_factory.type(type(column))
|
120
113
|
end
|
121
114
|
|
122
115
|
# @return [Boolean]
|
data/lib/database_consistency/checkers/validators_fraction_checkers/column_presence_checker.rb
CHANGED
@@ -29,8 +29,18 @@ module DatabaseConsistency
|
|
29
29
|
# | all missing | required | ok |
|
30
30
|
# | all missing | optional | fail |
|
31
31
|
def check
|
32
|
+
report_message
|
33
|
+
rescue Errors::MissingField => e
|
34
|
+
report_template(:fail, e.message)
|
35
|
+
end
|
36
|
+
|
37
|
+
def weak_option?
|
38
|
+
validators.all? { |validator| validator.options.slice(*WEAK_OPTIONS).any? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def report_message
|
32
42
|
can_be_null = column.null
|
33
|
-
has_weak_option =
|
43
|
+
has_weak_option = weak_option?
|
34
44
|
|
35
45
|
return report_template(:ok) if can_be_null == has_weak_option
|
36
46
|
return report_template(:fail, POSSIBLE_NULL) unless can_be_null
|
@@ -43,8 +53,9 @@ module DatabaseConsistency
|
|
43
53
|
end
|
44
54
|
|
45
55
|
def column
|
46
|
-
@column ||= regular_column ||
|
47
|
-
|
56
|
+
@column ||= regular_column ||
|
57
|
+
association_reference_column ||
|
58
|
+
(raise Errors::MissingField, "column (#{attribute}) is missing in table (#{model.table_name}) but used for presence validation") # rubocop:disable Layout/LineLength
|
48
59
|
end
|
49
60
|
|
50
61
|
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.10
|
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
|