pgdice 0.4.2 → 2.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/.codeclimate.yml +4 -1
- data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/workflows/gem-push.yml +20 -0
- data/.github/workflows/ruby.yml +62 -0
- data/.rubocop.yml +12 -2
- data/.ruby-version +1 -1
- data/CHANGELOG.md +21 -0
- data/README.md +22 -26
- data/SECURITY.md +15 -0
- data/examples/aws/README.md +28 -0
- data/examples/aws/cloudformation/scheduled_events.json +59 -0
- data/examples/aws/lib/sqs_listener/default_event_handler.rb +32 -0
- data/examples/aws/lib/sqs_listener/exceptions/unknown_task_error.rb +4 -0
- data/examples/aws/lib/sqs_listener/fallthrough_event_handler.rb +18 -0
- data/examples/aws/lib/sqs_listener/sqs_event_router.rb +32 -0
- data/examples/aws/lib/sqs_listener/typed_event_handler/task_event_handler.rb +46 -0
- data/examples/aws/lib/sqs_listener/typed_event_handler/tasks/database_tasks.rb +37 -0
- data/examples/aws/lib/sqs_listener.rb +47 -0
- data/examples/aws/lib/sqs_message_deleter.rb +32 -0
- data/examples/aws/lib/sqs_poller.rb +67 -0
- data/examples/aws/tasks/poll_sqs.rake +8 -0
- data/examples/aws/workers/pg_dice_worker.rb +54 -0
- data/lib/pgdice/approved_tables.rb +3 -2
- data/lib/pgdice/configuration.rb +5 -5
- data/lib/pgdice/configuration_file_loader.rb +1 -1
- data/lib/pgdice/database_connection_factory.rb +5 -6
- data/lib/pgdice/date_helper.rb +2 -2
- data/lib/pgdice/error.rb +2 -2
- data/lib/pgdice/partition_dropper.rb +1 -1
- data/lib/pgdice/partition_helper.rb +2 -2
- data/lib/pgdice/pg_slice_manager.rb +5 -5
- data/lib/pgdice/query_executor.rb +3 -3
- data/lib/pgdice/query_executor_factory.rb +20 -0
- data/lib/pgdice/table.rb +2 -2
- data/lib/pgdice/version.rb +1 -1
- data/lib/pgdice.rb +1 -0
- data/pgdice.gemspec +26 -22
- metadata +87 -49
- data/.circleci/config.yml +0 -66
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-sqs'
|
4
|
+
|
5
|
+
# READ_ONLY_SQS can be set to ensure we don't delete good messages
|
6
|
+
class SqsListener
|
7
|
+
DEFAULT_VISIBILITY_TIMEOUT ||= 600
|
8
|
+
attr_reader :logger, :queue_url, :visibility_timeout
|
9
|
+
|
10
|
+
def initialize(opts = {})
|
11
|
+
@logger = opts[:logger] ||= Sidekiq.logger
|
12
|
+
@queue_url = opts[:queue_url] ||= ENV['SqsQueueUrl']
|
13
|
+
@sqs_client = opts[:sqs_client] ||= Aws::SQS::Client.new
|
14
|
+
@sqs_event_router = opts[:sqs_event_router] ||= SqsEventRouter.new(logger: logger)
|
15
|
+
increase_timeout_resolver = opts[:increase_timeout_resolver] ||= -> { ENV['READ_ONLY_SQS'].to_s == 'true' }
|
16
|
+
@visibility_timeout = calculate_visibility_timeout(increase_timeout_resolver.call)
|
17
|
+
|
18
|
+
logger.debug { "Running in environment: #{ENV['RAILS_ENV']} and using sqs queue: #{queue_url}" }
|
19
|
+
end
|
20
|
+
|
21
|
+
# http://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/sqs-example-get-messages-with-long-polling.html
|
22
|
+
def call
|
23
|
+
# This uses long polling to retrieve sqs events so we can process them
|
24
|
+
response = @sqs_client.receive_message(queue_url: queue_url,
|
25
|
+
max_number_of_messages: 10,
|
26
|
+
wait_time_seconds: 20,
|
27
|
+
visibility_timeout: visibility_timeout)
|
28
|
+
|
29
|
+
if response.messages&.size&.positive?
|
30
|
+
logger.debug { "The number of messages received from the queue was: #{response.messages&.size}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Iterate over all the messages in the response (Response is a Struct which acts like an object with methods)
|
34
|
+
response.messages&.each do |message|
|
35
|
+
@sqs_event_router.handle_message(message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def calculate_visibility_timeout(increase_timeout)
|
42
|
+
visibility_timeout = increase_timeout ? DEFAULT_VISIBILITY_TIMEOUT * 4 : DEFAULT_VISIBILITY_TIMEOUT
|
43
|
+
|
44
|
+
logger.info { "Visibility timeout set to: #{visibility_timeout} seconds" }
|
45
|
+
visibility_timeout
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-sqs'
|
4
|
+
|
5
|
+
class SqsMessageDeleter
|
6
|
+
attr_reader :logger
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
@logger = opts[:logger] ||= Sidekiq.logger
|
10
|
+
@queue_url = opts[:queue_url] ||= ENV['SqsQueueUrl']
|
11
|
+
@sqs_client = opts[:sqs_client] ||= Aws::SQS::Client.new
|
12
|
+
@skip_delete_predicate = opts[:skip_delete_predicate] ||= proc do
|
13
|
+
Rails.env != 'production' || ENV['READ_ONLY_SQS'].to_s == 'true'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(sqs_message_receipt_handle)
|
18
|
+
if @skip_delete_predicate.call
|
19
|
+
logger.info { "Not destroying sqs message because environment is not prod or READ_ONLY_SQS was set to 'true'" }
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
logger.debug { "Destroying sqs message with handle: #{sqs_message_receipt_handle}" }
|
24
|
+
|
25
|
+
response = @sqs_client.delete_message(queue_url: @queue_url, receipt_handle: sqs_message_receipt_handle)
|
26
|
+
unless response.successful?
|
27
|
+
raise "Attempt to delete SQS message: #{sqs_message_receipt_handle} was not successful. Response: #{response}"
|
28
|
+
end
|
29
|
+
|
30
|
+
true
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-sqs'
|
4
|
+
|
5
|
+
class SqsPoller
|
6
|
+
attr_reader :logger, :queue_url
|
7
|
+
|
8
|
+
MAX_RETRIES ||= 3
|
9
|
+
DEFAULT_WAIT_TIME ||= 5
|
10
|
+
|
11
|
+
def initialize(opts = {})
|
12
|
+
@logger = opts[:logger] ||= ActiveSupport::TaggedLogging.new(Logger.new(ENV['POLL_SQS_LOG_OUTPUT'] || STDOUT))
|
13
|
+
@max_retries = opts[:max_retries] ||= MAX_RETRIES
|
14
|
+
@sleep_seconds = opts[:sleep_seconds] ||= DEFAULT_WAIT_TIME
|
15
|
+
@error_sleep_seconds = opts[:error_sleep_seconds] ||= @sleep_seconds * 2
|
16
|
+
@sqs_listener = opts[:sqs_listener] ||= SqsListener.new(logger: logger)
|
17
|
+
end
|
18
|
+
|
19
|
+
def poll(iterations = Float::INFINITY)
|
20
|
+
logger.info { "Starting loop to #{iterations}, press Ctrl-C to exit" }
|
21
|
+
|
22
|
+
retries = 0
|
23
|
+
i = 0
|
24
|
+
|
25
|
+
while i < iterations
|
26
|
+
begin
|
27
|
+
i += 1
|
28
|
+
execute_loop
|
29
|
+
rescue StandardError => e
|
30
|
+
if retries < MAX_RETRIES
|
31
|
+
retries = handle_retry(retries, e)
|
32
|
+
retry
|
33
|
+
else
|
34
|
+
die(e)
|
35
|
+
end
|
36
|
+
rescue Exception => e
|
37
|
+
die(e)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def execute_loop
|
45
|
+
@sqs_listener.call
|
46
|
+
sleep @sleep_seconds
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_retry(retries, error)
|
50
|
+
logger.error do
|
51
|
+
"Polling loop encountered an error. Will retry in #{@error_sleep_seconds} seconds. "\
|
52
|
+
"Error: #{error}. Retries: #{retries}"
|
53
|
+
end
|
54
|
+
retries += 1
|
55
|
+
|
56
|
+
# Handle error with error tracking service
|
57
|
+
# @error_handler.call(error)
|
58
|
+
|
59
|
+
sleep @error_sleep_seconds
|
60
|
+
retries
|
61
|
+
end
|
62
|
+
|
63
|
+
def die(error)
|
64
|
+
logger.fatal { "Polling loop is stopping due to exception: #{error}" }
|
65
|
+
raise error
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# https://github.com/mperham/sidekiq/wiki/Best-Practices
|
4
|
+
# Sidekiq job parameters must be JSON serializable. That means Ruby symbols are
|
5
|
+
# lost when they are sent through JSON!
|
6
|
+
class PgdiceWorker
|
7
|
+
include Sidekiq::Worker
|
8
|
+
attr_reader :logger
|
9
|
+
sidekiq_options queue: :default, backtrace: true, retry: 5
|
10
|
+
|
11
|
+
def initialize(opts = {})
|
12
|
+
@pgdice = opts[:pgdice] ||= PgDice
|
13
|
+
@logger = opts[:logger] ||= Sidekiq.logger
|
14
|
+
@validator = opts[:validator] ||= lambda do |table_name, params|
|
15
|
+
@pgdice.public_send(:assert_tables, table_name, params)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform(method, params)
|
20
|
+
table_names = params.delete('table_names')
|
21
|
+
validate = params.delete('validate').present?
|
22
|
+
# Don't pass in params to PgDice if the hash is empty. PgDice will behave differently when params are passed.
|
23
|
+
pgdice_params = params.keys.size.zero? ? nil : handle_pgdice_params(params)
|
24
|
+
|
25
|
+
logger.debug { "PgdiceWorker called with method: #{method} and table_names: #{table_names}. Validate: #{validate}" }
|
26
|
+
|
27
|
+
[table_names].flatten.compact.each do |table_name|
|
28
|
+
@pgdice.public_send(method, table_name, pgdice_params)
|
29
|
+
@validator.call(table_name, pgdice_params) if validate
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def handle_pgdice_params(pgdice_params)
|
36
|
+
convert_pgdice_param_values(pgdice_known_symbol_keys(pgdice_params))
|
37
|
+
end
|
38
|
+
|
39
|
+
def pgdice_known_symbol_keys(params)
|
40
|
+
convertable_keys = ['only']
|
41
|
+
params.keys.each do |key|
|
42
|
+
params[key.to_sym] = params.delete(key) if convertable_keys.include?(key)
|
43
|
+
end
|
44
|
+
params
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_pgdice_param_values(params)
|
48
|
+
symbolize_values_for_keys = [:only]
|
49
|
+
params.each do |key, value|
|
50
|
+
params[key] = value.to_sym if symbolize_values_for_keys.include?(key)
|
51
|
+
end
|
52
|
+
params
|
53
|
+
end
|
54
|
+
end
|
@@ -4,6 +4,7 @@ module PgDice
|
|
4
4
|
# Hash-like object to contain approved tables. Adds some convenience validation and a simpleish interface.
|
5
5
|
class ApprovedTables
|
6
6
|
attr_reader :tables
|
7
|
+
|
7
8
|
extend Forwardable
|
8
9
|
|
9
10
|
def_delegators :@tables, :size, :empty?, :map, :each, :each_with_index, :to_a
|
@@ -11,12 +12,12 @@ module PgDice
|
|
11
12
|
def initialize(*args)
|
12
13
|
@tables = args.flatten.compact
|
13
14
|
|
14
|
-
raise ArgumentError, 'Objects must be a PgDice::Table!' unless tables.all?
|
15
|
+
raise ArgumentError, 'Objects must be a PgDice::Table!' unless tables.all?(PgDice::Table)
|
15
16
|
end
|
16
17
|
|
17
18
|
def [](arg)
|
18
19
|
key = check_string_args(arg)
|
19
|
-
tables.
|
20
|
+
tables.find { |table| table.name == key }
|
20
21
|
end
|
21
22
|
|
22
23
|
def include?(arg)
|
data/lib/pgdice/configuration.rb
CHANGED
@@ -14,10 +14,10 @@ module PgDice
|
|
14
14
|
|
15
15
|
# Configuration class which holds all configurable values
|
16
16
|
class Configuration
|
17
|
-
DEFAULT_VALUES
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
DEFAULT_VALUES = { logger_factory: proc { Logger.new($stdout) },
|
18
|
+
database_url: nil,
|
19
|
+
dry_run: false,
|
20
|
+
batch_size: 7 }.freeze
|
21
21
|
|
22
22
|
attr_writer :logger,
|
23
23
|
:logger_factory,
|
@@ -63,7 +63,7 @@ module PgDice
|
|
63
63
|
raise PgDice::InvalidConfigurationError, 'approved_tables must be an instance of PgDice::ApprovedTables!'
|
64
64
|
end
|
65
65
|
|
66
|
-
if !config_file_loader.file_loaded? && config_file.
|
66
|
+
if !config_file_loader.file_loaded? && !config_file.nil?
|
67
67
|
config_file_loader.load_file
|
68
68
|
@approved_tables
|
69
69
|
end
|
@@ -17,7 +17,7 @@ module PgDice
|
|
17
17
|
end
|
18
18
|
@config_loader = opts[:config_loader] ||= lambda do |file|
|
19
19
|
logger.debug { "Loading PgDice configuration file: '#{config_file}'" }
|
20
|
-
YAML.safe_load(ERB.new(
|
20
|
+
YAML.safe_load(ERB.new(File.read(file)).result)
|
21
21
|
end
|
22
22
|
@file_loaded = opts[:file_loaded]
|
23
23
|
end
|
@@ -1,21 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Entry point
|
3
|
+
# Entry point
|
4
4
|
module PgDice
|
5
|
-
#
|
5
|
+
# DatabaseConnectionFactory is a class used to build DatabaseConnections
|
6
6
|
class DatabaseConnectionFactory
|
7
7
|
extend Forwardable
|
8
8
|
|
9
|
-
def_delegators :@configuration, :logger, :
|
9
|
+
def_delegators :@configuration, :logger, :dry_run
|
10
10
|
|
11
11
|
def initialize(configuration, opts = {})
|
12
12
|
@configuration = configuration
|
13
|
-
@
|
14
|
-
connection_supplier: -> { pg_connection })
|
13
|
+
@query_executor_factory = opts[:query_executor_factory] ||= PgDice::QueryExecutorFactory.new(configuration, opts)
|
15
14
|
end
|
16
15
|
|
17
16
|
def call
|
18
|
-
PgDice::DatabaseConnection.new(logger: logger, query_executor: @
|
17
|
+
PgDice::DatabaseConnection.new(logger: logger, query_executor: @query_executor_factory.call, dry_run: dry_run)
|
19
18
|
end
|
20
19
|
end
|
21
20
|
end
|
data/lib/pgdice/date_helper.rb
CHANGED
data/lib/pgdice/error.rb
CHANGED
@@ -25,7 +25,7 @@ module PgDice
|
|
25
25
|
class InsufficientTablesError < Error
|
26
26
|
def initialize(direction, table_name, expected, period, found_count)
|
27
27
|
super("Insufficient #{direction} tables exist for table: #{table_name}. "\
|
28
|
-
|
28
|
+
"Expected: #{expected} having period of: #{period} but found: #{found_count}")
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -51,7 +51,7 @@ module PgDice
|
|
51
51
|
class NotConfiguredError < ConfigurationError
|
52
52
|
def initialize(method_name)
|
53
53
|
super("Cannot use #{method_name} before PgDice has been configured! "\
|
54
|
-
|
54
|
+
'See README.md for configuration help.')
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
@@ -40,8 +40,8 @@ module PgDice
|
|
40
40
|
|
41
41
|
def undo_partitioning(table_name)
|
42
42
|
undo_partitioning!(table_name)
|
43
|
-
rescue PgDice::PgSliceError =>
|
44
|
-
logger.error { "Rescued PgSliceError: #{
|
43
|
+
rescue PgDice::PgSliceError => e
|
44
|
+
logger.error { "Rescued PgSliceError: #{e}" }
|
45
45
|
false
|
46
46
|
end
|
47
47
|
|
@@ -73,8 +73,8 @@ module PgDice
|
|
73
73
|
table_name = params.fetch(:table_name)
|
74
74
|
|
75
75
|
run_pgslice("unprep #{table_name}", params[:dry_run])
|
76
|
-
rescue PgSliceError =>
|
77
|
-
logger.error { "Rescued PgSliceError: #{
|
76
|
+
rescue PgSliceError => e
|
77
|
+
logger.error { "Rescued PgSliceError: #{e}" }
|
78
78
|
false
|
79
79
|
end
|
80
80
|
|
@@ -82,8 +82,8 @@ module PgDice
|
|
82
82
|
table_name = params.fetch(:table_name)
|
83
83
|
|
84
84
|
run_pgslice("unswap #{table_name}", params[:dry_run])
|
85
|
-
rescue PgSliceError =>
|
86
|
-
logger.error { "Rescued PgSliceError: #{
|
85
|
+
rescue PgSliceError => e
|
86
|
+
logger.error { "Rescued PgSliceError: #{e}" }
|
87
87
|
false
|
88
88
|
end
|
89
89
|
|
@@ -97,7 +97,7 @@ module PgDice
|
|
97
97
|
if status.to_i.positive?
|
98
98
|
raise PgDice::PgSliceError,
|
99
99
|
"pgslice with arguments: '#{argument_string}' failed with status: '#{status}' "\
|
100
|
-
|
100
|
+
"STDOUT: '#{stdout}' STDERR: '#{stderr}'"
|
101
101
|
end
|
102
102
|
true
|
103
103
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Entry point
|
3
|
+
# Entry point
|
4
4
|
module PgDice
|
5
5
|
# Wrapper class around pg_connection to reset connection on PG errors
|
6
6
|
class QueryExecutor
|
@@ -13,8 +13,8 @@ module PgDice
|
|
13
13
|
|
14
14
|
def call(query)
|
15
15
|
@connection_supplier.call.exec(query)
|
16
|
-
rescue PG::Error =>
|
17
|
-
logger.error { "Caught error: #{
|
16
|
+
rescue PG::Error => e
|
17
|
+
logger.error { "Caught error: #{e}. Going to reset connection and try again" }
|
18
18
|
@connection_supplier.call.reset
|
19
19
|
@connection_supplier.call.exec(query)
|
20
20
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Entry point
|
4
|
+
module PgDice
|
5
|
+
# QueryExecutorFactory is a class used to build QueryExecutors
|
6
|
+
class QueryExecutorFactory
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@configuration, :logger, :pg_connection
|
10
|
+
|
11
|
+
def initialize(configuration, opts = {})
|
12
|
+
@configuration = configuration
|
13
|
+
@connection_supplier = opts[:connection_supplier] ||= -> { pg_connection }
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
PgDice::QueryExecutor.new(logger: logger, connection_supplier: @connection_supplier)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/pgdice/table.rb
CHANGED
@@ -70,7 +70,7 @@ module PgDice
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def self.from_hash(hash)
|
73
|
-
Table.new(**hash.
|
73
|
+
Table.new(**hash.transform_keys(&:to_sym))
|
74
74
|
end
|
75
75
|
|
76
76
|
private
|
@@ -79,7 +79,7 @@ module PgDice
|
|
79
79
|
unless send(field).is_a?(expected_type)
|
80
80
|
raise ArgumentError,
|
81
81
|
"PgDice::Table: #{name} failed validation on field: #{field}. "\
|
82
|
-
|
82
|
+
"Expected type of: #{expected_type} but found #{send(field).class}"
|
83
83
|
end
|
84
84
|
true
|
85
85
|
end
|
data/lib/pgdice/version.rb
CHANGED
data/lib/pgdice.rb
CHANGED
data/pgdice.gemspec
CHANGED
@@ -5,39 +5,43 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
require 'pgdice/version'
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
|
-
spec.name
|
9
|
-
spec.version
|
10
|
-
spec.authors
|
11
|
-
spec.email
|
12
|
-
|
13
|
-
spec.
|
14
|
-
spec.
|
15
|
-
spec.
|
16
|
-
spec.license = 'MIT'
|
8
|
+
spec.name = 'pgdice'
|
9
|
+
spec.version = PgDice::VERSION
|
10
|
+
spec.authors = ['Andrew Newell']
|
11
|
+
spec.email = ['andrew@illuminusltd.com']
|
12
|
+
spec.summary = 'Postgres table partitioning with a Ruby API!'
|
13
|
+
spec.description = 'Postgres table partitioning with a Ruby API built on top of https://github.com/ankane/pgslice'
|
14
|
+
spec.homepage = 'https://github.com/IlluminusLimited/pgdice'
|
15
|
+
spec.license = 'MIT'
|
17
16
|
|
18
17
|
# Specify which files should be added to the gem when it is released.
|
19
18
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
-
spec.files
|
19
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
21
20
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
21
|
end
|
23
|
-
spec.bindir
|
24
|
-
spec.executables
|
22
|
+
spec.bindir = 'exe'
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
24
|
spec.require_paths = ['lib']
|
26
25
|
|
26
|
+
spec.required_ruby_version = '>= 3.0.0'
|
27
|
+
|
28
|
+
# It looks like there's a bug in pg 1.3+ that I don't have time to fix
|
29
|
+
spec.add_runtime_dependency 'pg', '~> 1.2.3', '>= 1.1.0'
|
27
30
|
# Locked because we depend on internal behavior for table commenting
|
28
|
-
spec.add_runtime_dependency '
|
29
|
-
spec.add_runtime_dependency 'pgslice', '0.4.5'
|
31
|
+
spec.add_runtime_dependency 'pgslice', '0.4.7'
|
30
32
|
|
31
|
-
spec.add_development_dependency 'bundler', '~>
|
32
|
-
spec.add_development_dependency '
|
33
|
-
spec.add_development_dependency 'guard', '~> 2.14.2', '>= 2.14.2'
|
33
|
+
spec.add_development_dependency 'bundler', '~> 2.3.6', '>= 1.16'
|
34
|
+
spec.add_development_dependency 'guard', '~> 2.18.0', '>= 2.14.2'
|
34
35
|
spec.add_development_dependency 'guard-minitest', '~> 2.4.6', '>= 2.4.6'
|
35
|
-
spec.add_development_dependency 'guard-rubocop', '~> 1.
|
36
|
+
spec.add_development_dependency 'guard-rubocop', '~> 1.5.0', '>= 1.3.0'
|
36
37
|
spec.add_development_dependency 'guard-shell', '~> 0.7.1', '>= 0.7.1'
|
37
38
|
spec.add_development_dependency 'minitest', '~> 5.0', '>= 5.0'
|
38
39
|
spec.add_development_dependency 'minitest-ci', '~> 3.4.0', '>= 3.4.0'
|
39
|
-
spec.add_development_dependency 'minitest-reporters', '~> 1.
|
40
|
-
spec.add_development_dependency 'rake', '~>
|
41
|
-
spec.add_development_dependency 'rubocop', '
|
42
|
-
spec.add_development_dependency '
|
40
|
+
spec.add_development_dependency 'minitest-reporters', '~> 1.5.0', '>= 1.3.4'
|
41
|
+
spec.add_development_dependency 'rake', '~> 13.0.6', '>= 10.0'
|
42
|
+
spec.add_development_dependency 'rubocop', '1.25.1'
|
43
|
+
spec.add_development_dependency 'rubocop-performance', '~> 1.13.2', '>= 1.13.2'
|
44
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.6.0', '>= 0.6.0'
|
45
|
+
spec.add_development_dependency 'simplecov', '~> 0.21.2', '>= 0.16.1'
|
46
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
43
47
|
end
|