backfiller 0.1.1 → 0.2.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/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +14 -0
- data/Changelog.md +8 -0
- data/Gemfile +2 -3
- data/README.md +1 -1
- data/Rakefile +8 -3
- data/backfiller.gemspec +23 -17
- data/bin/console +4 -10
- data/db/backfill/.keep +0 -0
- data/lib/backfiller.rb +6 -4
- data/lib/backfiller/configuration.rb +14 -3
- data/lib/backfiller/cursor.rb +3 -3
- data/lib/backfiller/cursor/postgresql.rb +21 -2
- data/lib/backfiller/railtie.rb +4 -2
- data/lib/backfiller/runner.rb +57 -17
- data/lib/backfiller/tasks/db.rake +4 -3
- data/log/.keep +0 -0
- metadata +48 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18b60be56e9153bca870e126330f666518c012c885c2e3d86d5c24625b395780
|
4
|
+
data.tar.gz: 06e10fa60228916766f7740231add1e1acc0c3f7dec1b9a1055785d908941a29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0315e6720c7ec5c15904696843caef797a5beceb2771934b01dcc61fddbbb4f65c28fb9b4a60ad94ea2c1d31161facc64f5016a8a236f506ea02f560b94e9262
|
7
|
+
data.tar.gz: 5a085ff3f03022926e6e188f1900f3697658ca78489e45273fce7fc6d6a78e93708c481c7bcfd09ddf049fe223c04f8ac6f625d7dc0fa103282070b1297d23f0
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Changelog.md
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|

|
2
2
|
|
3
|
-
# Backfiller
|
3
|
+
# Backfiller [](https://travis-ci.com/railsware/backfiller)
|
4
4
|
|
5
5
|
The backfill machine for null database columns.
|
6
6
|
This gem maybe handly for `no-downtime` deployment especially when you need to fill columns for table with huge amount for records without locking the table.
|
data/Rakefile
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rubocop/rake_task'
|
3
6
|
|
4
7
|
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
RuboCop::RakeTask.new
|
5
9
|
|
6
|
-
|
10
|
+
desc 'CI build'
|
11
|
+
task ci: %i[spec rubocop]
|
data/backfiller.gemspec
CHANGED
@@ -1,27 +1,33 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
|
5
6
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
7
|
+
spec.name = 'backfiller'
|
8
|
+
spec.version = '0.2.0'
|
9
|
+
spec.authors = ['Andriy Yanko']
|
10
|
+
spec.email = ['andriy.yanko@railsware.com']
|
11
|
+
|
12
|
+
spec.summary = 'Backfiller for null database columns'
|
13
|
+
spec.homepage = 'https://github.com/railsware/backfiller'
|
14
|
+
spec.license = 'MIT'
|
10
15
|
|
11
|
-
spec.
|
12
|
-
spec.homepage = "https://github.com/railsware/backfiller"
|
13
|
-
spec.license = "MIT"
|
16
|
+
spec.required_ruby_version = '>= 2.7.0'
|
14
17
|
|
15
|
-
spec.files
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
19
|
f.match(%r{^(test|spec|features)/})
|
17
20
|
end
|
18
|
-
spec.bindir
|
19
|
-
spec.executables
|
20
|
-
spec.require_paths = [
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'activerecord', '>= 6.0.0'
|
21
26
|
|
22
|
-
spec.
|
27
|
+
spec.add_development_dependency 'bundler', '~> 2.2.0'
|
28
|
+
spec.add_development_dependency 'rake', '~> 13.0.0'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.10.0'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 1.18.0'
|
23
31
|
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
32
|
+
spec.add_development_dependency 'pg', '~> 1.2.0'
|
27
33
|
end
|
data/bin/console
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'backfiller'
|
5
6
|
|
6
|
-
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
7
|
+
require 'irb'
|
14
8
|
IRB.start(__FILE__)
|
data/db/backfill/.keep
ADDED
File without changes
|
data/lib/backfiller.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
require 'backfiller/cursor'
|
3
|
-
require 'backfiller/runner'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
3
|
+
require_relative 'backfiller/configuration'
|
4
|
+
require_relative 'backfiller/cursor'
|
5
|
+
require_relative 'backfiller/runner'
|
6
|
+
|
7
|
+
require_relative 'backfiller/railtie' if defined?(Rails::Railtie)
|
@@ -1,25 +1,36 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Backfiller
|
3
4
|
class << self
|
4
5
|
def configure
|
5
6
|
yield self
|
6
7
|
end
|
7
8
|
|
9
|
+
# directory for backfill ruby classes
|
8
10
|
attr_accessor :task_directory
|
9
11
|
|
12
|
+
# ruby module of backfill classes
|
10
13
|
attr_accessor :task_namespace
|
11
14
|
|
15
|
+
# Max size of records in one cursor fetch
|
12
16
|
attr_accessor :batch_size
|
13
17
|
|
18
|
+
# Size of processed records after which cursor will be re-opened
|
19
|
+
attr_accessor :cursor_threshold
|
20
|
+
|
21
|
+
# Logger
|
14
22
|
attr_accessor :logger
|
15
23
|
|
24
|
+
# @param task_name [String] name of backfill task file
|
16
25
|
def run(task_name)
|
17
26
|
Backfiller::Runner.new(task_name).run
|
18
27
|
end
|
19
28
|
|
29
|
+
# @param message [String] log message
|
20
30
|
def log(message)
|
21
|
-
|
31
|
+
return unless logger
|
32
|
+
|
33
|
+
logger.info "[Backfiller] #{message}"
|
22
34
|
end
|
23
35
|
end
|
24
|
-
|
25
36
|
end
|
data/lib/backfiller/cursor.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'cursor/postgresql'
|
2
4
|
|
3
5
|
module Backfiller
|
4
6
|
module Cursor
|
5
|
-
|
6
7
|
def self.new(connection, *args)
|
7
8
|
case connection
|
8
9
|
when ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
|
@@ -11,6 +12,5 @@ module Backfiller
|
|
11
12
|
raise "Unsupported connection #{connection.inspect}"
|
12
13
|
end
|
13
14
|
end
|
14
|
-
|
15
15
|
end
|
16
16
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Backfiller
|
2
4
|
module Cursor
|
3
5
|
class Postgresql
|
4
|
-
|
5
6
|
attr_reader :connection
|
6
7
|
|
7
8
|
def initialize(connection, name, query)
|
@@ -10,6 +11,25 @@ module Backfiller
|
|
10
11
|
@query = query
|
11
12
|
end
|
12
13
|
|
14
|
+
# Open cursor, call black and close cursor in transaction.
|
15
|
+
#
|
16
|
+
# @return [Object] yielded block result.
|
17
|
+
def transaction
|
18
|
+
result = nil
|
19
|
+
|
20
|
+
@connection.transaction do
|
21
|
+
Backfiller.log 'Open cursor'
|
22
|
+
open
|
23
|
+
|
24
|
+
result = yield
|
25
|
+
|
26
|
+
Backfiller.log 'Close cursor'
|
27
|
+
close
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
13
33
|
def open
|
14
34
|
@connection.execute "DECLARE #{@name} NO SCROLL CURSOR WITHOUT HOLD FOR #{@query}"
|
15
35
|
end
|
@@ -21,7 +41,6 @@ module Backfiller
|
|
21
41
|
def close
|
22
42
|
@connection.execute "CLOSE #{@name}"
|
23
43
|
end
|
24
|
-
|
25
44
|
end
|
26
45
|
end
|
27
46
|
end
|
data/lib/backfiller/railtie.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Backfiller
|
2
4
|
class Railtie < Rails::Railtie
|
3
|
-
|
4
5
|
rake_tasks do
|
5
6
|
load 'backfiller/tasks/db.rake'
|
6
7
|
end
|
@@ -13,6 +14,8 @@ module Backfiller
|
|
13
14
|
|
14
15
|
config.batch_size = 1_000
|
15
16
|
|
17
|
+
config.cursor_threshold = 100_000
|
18
|
+
|
16
19
|
config.logger = defined?(ApplicationRecord) ? ApplicationRecord.logger : ActiveRecord::Base.logger
|
17
20
|
end
|
18
21
|
end
|
@@ -21,6 +24,5 @@ module Backfiller
|
|
21
24
|
task_module = Backfiller.task_namespace.classify
|
22
25
|
Object.const_set(task_module, Module.new) unless Object.const_defined?(task_module)
|
23
26
|
end
|
24
|
-
|
25
27
|
end
|
26
28
|
end
|
data/lib/backfiller/runner.rb
CHANGED
@@ -1,24 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Backfiller
|
2
4
|
class Runner
|
3
|
-
|
4
5
|
attr_reader \
|
5
6
|
:task,
|
6
7
|
:connection_pool,
|
7
8
|
:batch_size,
|
9
|
+
:cursor_threshold,
|
8
10
|
:process_method
|
9
11
|
|
10
12
|
def initialize(task_name)
|
11
13
|
@task = build_task(task_name)
|
12
14
|
@connection_pool = @task.respond_to?(:connection_pool) ? @task.connection_pool : default_connection_pool
|
13
15
|
@batch_size = @task.respond_to?(:batch_size) ? @task.batch_size : Backfiller.batch_size
|
14
|
-
@
|
16
|
+
@cursor_threshold = @task.respond_to?(:cursor_threshold) ? @task.cursor_threshold : Backfiller.cursor_threshold
|
17
|
+
@process_method = @task.respond_to?(:process_row) ? @task.method(:process_row) : method(:process_row)
|
15
18
|
end
|
16
19
|
|
20
|
+
# It uses two connections from pool:
|
21
|
+
# * master [M] - reads data using cursor in transaction
|
22
|
+
# * worker [W] - changes data based on record red from master
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# [M] BEGIN
|
26
|
+
# [M] DECLARE backfill_cursor SCROLL CURSOR WITHOUT HOLD FOR SELECT * FROM users
|
27
|
+
# // Start fetch and process loop:
|
28
|
+
# [M] FETCH 1000 backfill_cursor
|
29
|
+
# [W] UPDATE users SET full_name = '...' where id = 1
|
30
|
+
# [W] ...
|
31
|
+
# [W] UPDATE users SET full_name = '...' where id = 1000
|
32
|
+
# [M] FETCH 1000 backfill_cursor
|
33
|
+
# [W] UPDATE users SET full_name = '...' where id = 1001
|
34
|
+
# [W] ...
|
35
|
+
# [W] UPDATE users SET full_name = '...' where id = 2000
|
36
|
+
# // Records per cursor transaction threshold reached. Reopen transaction.
|
37
|
+
# [M] CLOSE backfill_cursor
|
38
|
+
# [M] COMMIT
|
39
|
+
# [M] BEGIN
|
40
|
+
# [M] DECLARE backfill_cursor SCROLL CURSOR WITHOUT HOLD FOR SELECT * FROM users
|
41
|
+
# [M] FETCH 1000 backfill_cursor
|
42
|
+
# // The end of cursor reached. Break cursor loop and exit.
|
43
|
+
# [M] CLOSE backfill_cursor
|
44
|
+
# [M] COMMIT
|
17
45
|
def run
|
18
46
|
master_connection = acquire_connection
|
19
47
|
worker_connection = acquire_connection
|
20
48
|
|
21
|
-
|
49
|
+
run_cursor_loop(master_connection) do |row|
|
22
50
|
process_method.call(worker_connection, row)
|
23
51
|
end
|
24
52
|
|
@@ -50,32 +78,34 @@ module Backfiller
|
|
50
78
|
|
51
79
|
###########################################################################
|
52
80
|
|
53
|
-
|
54
|
-
|
55
|
-
|
81
|
+
# Run loop that re-open cursor transaction on threshold
|
82
|
+
def run_cursor_loop(connection, &block)
|
83
|
+
Backfiller.log 'Start cursor loop'
|
56
84
|
|
57
|
-
|
58
|
-
cursor = build_cursor(
|
85
|
+
total_count = 0
|
86
|
+
cursor = build_cursor(connection)
|
59
87
|
|
60
|
-
|
61
|
-
|
62
|
-
|
88
|
+
loop do
|
89
|
+
finished, count = cursor.transaction do
|
90
|
+
run_fetch_loop(cursor, &block)
|
91
|
+
end
|
63
92
|
|
64
|
-
|
65
|
-
fetch_loop(cursor, &block)
|
93
|
+
total_count += count
|
66
94
|
|
67
|
-
Backfiller.log "
|
68
|
-
|
95
|
+
Backfiller.log "Total processed #{total_count}"
|
96
|
+
break if finished
|
69
97
|
end
|
70
98
|
end
|
71
99
|
|
72
|
-
|
100
|
+
# @return [Array<Boolean, Integer>] finished_status/processed_count
|
101
|
+
def run_fetch_loop(cursor, &block)
|
102
|
+
Backfiller.log 'Start fetch loop'
|
73
103
|
count = 0
|
74
104
|
|
75
105
|
loop do
|
76
106
|
result = cursor.fetch(batch_size)
|
77
107
|
|
78
|
-
|
108
|
+
return [true, count] if result.empty?
|
79
109
|
|
80
110
|
result.each do |row|
|
81
111
|
block.call(row)
|
@@ -83,9 +113,19 @@ module Backfiller
|
|
83
113
|
end
|
84
114
|
|
85
115
|
Backfiller.log "Processed #{count}"
|
116
|
+
|
117
|
+
return [false, count] if count > cursor_threshold
|
86
118
|
end
|
87
119
|
end
|
88
120
|
|
121
|
+
###########################################################################
|
122
|
+
|
123
|
+
# Build cursor object that will use master connection.
|
124
|
+
def build_cursor(connection)
|
125
|
+
Backfiller::Cursor.new(connection, 'backfill_cursor', task.select_sql)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Process row using worker connection.
|
89
129
|
def process_row(connection, row)
|
90
130
|
Array(task.execute_sql(connection, row)).each do |sql|
|
91
131
|
connection.execute(sql)
|
@@ -1,10 +1,11 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
namespace :db do
|
3
4
|
desc 'Run database backfill task'
|
4
|
-
task :backfill, [:name] => :environment do |
|
5
|
+
task :backfill, [:name] => :environment do |_, args|
|
5
6
|
raise 'Please specify backfill task name' unless args[:name]
|
7
|
+
|
6
8
|
Backfiller.logger.level = :info if Backfiller.logger
|
7
9
|
Backfiller.run(args[:name])
|
8
10
|
end
|
9
|
-
|
10
11
|
end
|
data/log/.keep
ADDED
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backfiller
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andriy Yanko
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,57 +16,85 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 6.0.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 6.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 2.2.0
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 2.2.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 13.0.0
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 13.0.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 3.10.0
|
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
|
-
version:
|
69
|
-
|
68
|
+
version: 3.10.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.18.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.18.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.2.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.2.0
|
97
|
+
description:
|
70
98
|
email:
|
71
99
|
- andriy.yanko@railsware.com
|
72
100
|
executables: []
|
@@ -75,6 +103,8 @@ extra_rdoc_files: []
|
|
75
103
|
files:
|
76
104
|
- ".gitignore"
|
77
105
|
- ".rspec"
|
106
|
+
- ".rubocop.yml"
|
107
|
+
- ".travis.yml"
|
78
108
|
- Changelog.md
|
79
109
|
- Gemfile
|
80
110
|
- README.md
|
@@ -82,6 +112,7 @@ files:
|
|
82
112
|
- backfiller.gemspec
|
83
113
|
- bin/console
|
84
114
|
- bin/setup
|
115
|
+
- db/backfill/.keep
|
85
116
|
- lib/backfiller.rb
|
86
117
|
- lib/backfiller/configuration.rb
|
87
118
|
- lib/backfiller/cursor.rb
|
@@ -89,11 +120,12 @@ files:
|
|
89
120
|
- lib/backfiller/railtie.rb
|
90
121
|
- lib/backfiller/runner.rb
|
91
122
|
- lib/backfiller/tasks/db.rake
|
123
|
+
- log/.keep
|
92
124
|
homepage: https://github.com/railsware/backfiller
|
93
125
|
licenses:
|
94
126
|
- MIT
|
95
127
|
metadata: {}
|
96
|
-
post_install_message:
|
128
|
+
post_install_message:
|
97
129
|
rdoc_options: []
|
98
130
|
require_paths:
|
99
131
|
- lib
|
@@ -101,15 +133,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
133
|
requirements:
|
102
134
|
- - ">="
|
103
135
|
- !ruby/object:Gem::Version
|
104
|
-
version:
|
136
|
+
version: 2.7.0
|
105
137
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
138
|
requirements:
|
107
139
|
- - ">="
|
108
140
|
- !ruby/object:Gem::Version
|
109
141
|
version: '0'
|
110
142
|
requirements: []
|
111
|
-
rubygems_version: 3.
|
112
|
-
signing_key:
|
143
|
+
rubygems_version: 3.1.4
|
144
|
+
signing_key:
|
113
145
|
specification_version: 4
|
114
146
|
summary: Backfiller for null database columns
|
115
147
|
test_files: []
|