pgdice 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +2 -2
- data/.codeclimate.yml +5 -0
- data/.rubocop.yml +6 -1
- data/CHANGELOG.md +14 -0
- data/Guardfile +6 -6
- data/README.md +111 -76
- data/Rakefile +3 -0
- data/examples/config.yml +13 -0
- data/lib/pgdice/approved_tables.rb +63 -0
- data/lib/pgdice/configuration.rb +69 -54
- data/lib/pgdice/configuration_file_loader.rb +62 -0
- data/lib/pgdice/database_connection.rb +17 -9
- data/lib/pgdice/database_connection_factory.rb +20 -0
- data/lib/pgdice/date_helper.rb +39 -0
- data/lib/pgdice/error.rb +71 -0
- data/lib/pgdice/log_helper.rb +37 -0
- data/lib/pgdice/partition_dropper.rb +34 -0
- data/lib/pgdice/partition_dropper_factory.rb +20 -0
- data/lib/pgdice/partition_helper.rb +18 -30
- data/lib/pgdice/partition_helper_factory.rb +24 -0
- data/lib/pgdice/partition_lister.rb +33 -0
- data/lib/pgdice/partition_lister_factory.rb +22 -0
- data/lib/pgdice/partition_manager.rb +62 -58
- data/lib/pgdice/partition_manager_factory.rb +52 -0
- data/lib/pgdice/period_fetcher.rb +31 -0
- data/lib/pgdice/period_fetcher_factory.rb +19 -0
- data/lib/pgdice/pg_slice_manager.rb +44 -29
- data/lib/pgdice/pg_slice_manager_factory.rb +35 -0
- data/lib/pgdice/table.rb +87 -0
- data/lib/pgdice/table_finder.rb +41 -0
- data/lib/pgdice/validation.rb +52 -79
- data/lib/pgdice/validation_factory.rb +21 -0
- data/lib/pgdice/version.rb +1 -1
- data/lib/pgdice.rb +34 -72
- data/pgdice.gemspec +3 -4
- metadata +27 -27
- data/lib/pgdice/table_dropper.rb +0 -26
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgDice
|
4
|
+
# ConfigurationFileLoader is a class used to load the PgDice configuration file
|
5
|
+
class ConfigurationFileLoader
|
6
|
+
include PgDice::LogHelper
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :config
|
10
|
+
|
11
|
+
def_delegators :@config, :config_file, :logger
|
12
|
+
|
13
|
+
def initialize(config = PgDice::Configuration.new, opts = {})
|
14
|
+
@config = config
|
15
|
+
@file_validator = opts[:file_validator] ||= lambda do |config_file|
|
16
|
+
validate_file(config_file)
|
17
|
+
end
|
18
|
+
@config_loader = opts[:config_loader] ||= lambda do |file|
|
19
|
+
logger.debug { "Loading PgDice configuration file: '#{config_file}'" }
|
20
|
+
YAML.safe_load(ERB.new(IO.read(file)).result)
|
21
|
+
end
|
22
|
+
@file_loaded = opts[:file_loaded]
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_file
|
26
|
+
return if @file_loaded
|
27
|
+
|
28
|
+
@file_loaded = true
|
29
|
+
|
30
|
+
@file_validator.call(config_file)
|
31
|
+
|
32
|
+
@config.approved_tables = @config_loader.call(config_file)
|
33
|
+
.fetch('approved_tables')
|
34
|
+
.reduce(tables(@config)) do |tables, hash|
|
35
|
+
tables << PgDice::Table.from_hash(hash)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def file_loaded?
|
40
|
+
@file_loaded
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def validate_file(config_file)
|
46
|
+
if blank?(config_file)
|
47
|
+
raise PgDice::InvalidConfigurationError,
|
48
|
+
'Cannot read in nil configuration file! You must set config_file if you leave approved_tables nil!'
|
49
|
+
end
|
50
|
+
|
51
|
+
raise PgDice::MissingConfigurationFileError, config_file unless File.exist?(config_file)
|
52
|
+
end
|
53
|
+
|
54
|
+
def tables(config)
|
55
|
+
if config.approved_tables(eager_load: true).is_a?(PgDice::ApprovedTables)
|
56
|
+
return config.approved_tables(eager_load: true)
|
57
|
+
end
|
58
|
+
|
59
|
+
PgDice::ApprovedTables.new
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -4,25 +4,33 @@
|
|
4
4
|
module PgDice
|
5
5
|
# Wrapper class around database connection handlers
|
6
6
|
class DatabaseConnection
|
7
|
-
|
8
|
-
def_delegators :@configuration, :logger, :dry_run, :pg_connection
|
7
|
+
include PgDice::LogHelper
|
9
8
|
|
10
|
-
|
11
|
-
|
9
|
+
attr_reader :logger, :query_executor, :dry_run
|
10
|
+
|
11
|
+
def initialize(logger:, query_executor:, dry_run: false)
|
12
|
+
@logger = logger
|
13
|
+
@dry_run = dry_run
|
14
|
+
@query_executor = query_executor
|
12
15
|
end
|
13
16
|
|
14
17
|
def execute(query)
|
18
|
+
query = squish(query)
|
19
|
+
|
20
|
+
if blank?(query) || dry_run
|
21
|
+
logger.debug { "DatabaseConnection skipping query. Query: '#{query}'. Dry run: #{dry_run}" }
|
22
|
+
return PgDice::PgResponse.new
|
23
|
+
end
|
24
|
+
|
15
25
|
logger.debug { "DatabaseConnection to execute query: #{query}" }
|
16
|
-
|
17
|
-
|
18
|
-
else
|
19
|
-
pg_connection.exec(query)
|
26
|
+
PgDice::LogHelper.log_duration('Executing query', logger) do
|
27
|
+
query_executor.call(query)
|
20
28
|
end
|
21
29
|
end
|
22
30
|
end
|
23
31
|
|
24
32
|
# Null-object pattern for PG::Result since that object isn't straightforward to initialize
|
25
|
-
class
|
33
|
+
class PgResponse
|
26
34
|
def values
|
27
35
|
[]
|
28
36
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Entry point for PartitionManager
|
4
|
+
module PgDice
|
5
|
+
# PartitionListerFactory is a class used to build PartitionListers
|
6
|
+
class DatabaseConnectionFactory
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@configuration, :logger, :pg_connection, :dry_run
|
10
|
+
|
11
|
+
def initialize(configuration, opts = {})
|
12
|
+
@configuration = configuration
|
13
|
+
@query_executor = opts[:query_executor] ||= ->(query) { pg_connection.exec(query) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
PgDice::DatabaseConnection.new(logger: logger, query_executor: @query_executor, dry_run: dry_run)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgDice
|
4
|
+
# Helper used to manipulate date objects
|
5
|
+
module DateHelper
|
6
|
+
def pad_date(numbers)
|
7
|
+
return numbers if numbers.size == 8
|
8
|
+
|
9
|
+
case numbers.size
|
10
|
+
when 6
|
11
|
+
return numbers + '01'
|
12
|
+
when 4
|
13
|
+
return numbers + '0101'
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Invalid date. Cannot parse date from #{numbers}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def truncate_date(date, period)
|
20
|
+
case period
|
21
|
+
when 'year'
|
22
|
+
Date.parse("#{date.year}0101")
|
23
|
+
when 'month'
|
24
|
+
Date.parse("#{date.year}#{date.month}01")
|
25
|
+
when 'day'
|
26
|
+
date
|
27
|
+
else
|
28
|
+
raise ArgumentError, "Invalid date. Cannot parse date from #{date}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def safe_date_builder(table_name)
|
33
|
+
matches = table_name.match(/\d+/)
|
34
|
+
raise ArgumentError, "Invalid date. Cannot parse date from #{table_name}" unless matches
|
35
|
+
|
36
|
+
Date.parse(pad_date(matches[0]))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/pgdice/error.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgDice
|
4
|
+
# PgDice parent error class
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Error thrown when PgSlice returns an error code
|
9
|
+
class PgSliceError < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
# Generic validation error
|
13
|
+
class ValidationError < Error
|
14
|
+
end
|
15
|
+
|
16
|
+
# Error thrown if a user tries to operate on a table that is not in the ApprovedTables object.
|
17
|
+
class IllegalTableError < ValidationError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Error thrown when a user attempts to manipulate partitions on a table that is not partitioned
|
21
|
+
class TableNotPartitionedError < Error
|
22
|
+
end
|
23
|
+
|
24
|
+
# Generic error for table counts
|
25
|
+
class InsufficientTablesError < Error
|
26
|
+
def initialize(direction, table_name, expected, period, found_count)
|
27
|
+
super("Insufficient #{direction} tables exist for table: #{table_name}. "\
|
28
|
+
"Expected: #{expected} having period of: #{period} but found: #{found_count}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Error thrown when the count of future tables is less than the expected amount
|
33
|
+
class InsufficientFutureTablesError < InsufficientTablesError
|
34
|
+
def initialize(table_name, expected, period, found_count)
|
35
|
+
super('future', table_name, expected, period, found_count)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Error thrown when the count of past tables is less than the expected amount
|
40
|
+
class InsufficientPastTablesError < InsufficientTablesError
|
41
|
+
def initialize(table_name, expected, period, found_count)
|
42
|
+
super('past', table_name, expected, period, found_count)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generic configuration error
|
47
|
+
class ConfigurationError < Error
|
48
|
+
end
|
49
|
+
|
50
|
+
# Error thrown if you call a method that requires configuration first
|
51
|
+
class NotConfiguredError < ConfigurationError
|
52
|
+
def initialize(method_name)
|
53
|
+
super("Cannot use #{method_name} before PgDice has been configured! "\
|
54
|
+
'See README.md for configuration help.')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Error thrown if you provide bad data in a configuration
|
59
|
+
class InvalidConfigurationError < ConfigurationError
|
60
|
+
def initialize(message)
|
61
|
+
super("PgDice is not configured properly. #{message}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Error thrown if the config file specified does not exist.
|
66
|
+
class MissingConfigurationFileError < ConfigurationError
|
67
|
+
def initialize(config_file)
|
68
|
+
super("File: '#{config_file}' could not be found or does not exist. Is this the correct file path?")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PgDice
|
4
|
+
# LogHelper provides a convenient wrapper block to log out the duration of an operation
|
5
|
+
module LogHelper
|
6
|
+
def blank?(string)
|
7
|
+
string.nil? || string.empty?
|
8
|
+
end
|
9
|
+
|
10
|
+
def squish(string)
|
11
|
+
string ||= ''
|
12
|
+
string.gsub(/\s+/, ' ')
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# If you want to pass the the result of your block into the message you can use '{}' and it will be
|
17
|
+
# substituted with the result of your block.
|
18
|
+
def log_duration(message, logger, options = {})
|
19
|
+
logger.error { 'log_duration called without a block. Cannot time the duration of nothing.' } unless block_given?
|
20
|
+
time_start = Time.now.utc
|
21
|
+
result = yield
|
22
|
+
time_end = Time.now.utc
|
23
|
+
|
24
|
+
formatted_message = format_message(time_end, time_start, message, result)
|
25
|
+
logger.public_send(options[:log_level] || :debug) { formatted_message }
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def format_message(time_end, time_start, message, result)
|
32
|
+
message = message.sub(/{}/, result.to_s)
|
33
|
+
"#{message} took: #{format('%.02f', (time_end - time_start))} seconds."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Entry point for TableDropperHelper
|
4
|
+
module PgDice
|
5
|
+
# Simple class used to provide a mechanism that users can hook into if they want to override this
|
6
|
+
# default behavior for dropping a table.
|
7
|
+
class PartitionDropper
|
8
|
+
attr_reader :logger, :query_executor
|
9
|
+
|
10
|
+
def initialize(logger:, query_executor:)
|
11
|
+
@logger = logger
|
12
|
+
@query_executor = query_executor
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(old_partitions)
|
16
|
+
logger.info { "Partitions to be deleted are: #{old_partitions}" }
|
17
|
+
|
18
|
+
query_executor.call(generate_drop_sql(old_partitions))
|
19
|
+
|
20
|
+
old_partitions
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def generate_drop_sql(old_partitions)
|
26
|
+
return if old_partitions.size.zero?
|
27
|
+
|
28
|
+
sql_query = old_partitions.reduce("BEGIN;\n") do |sql, table_name|
|
29
|
+
sql + "DROP TABLE IF EXISTS #{table_name} CASCADE;\n"
|
30
|
+
end
|
31
|
+
sql_query + 'COMMIT;'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Entry point for PartitionManager
|
4
|
+
module PgDice
|
5
|
+
# PartitionListerFactory is a class used to build PartitionListers
|
6
|
+
class PartitionDropperFactory
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@configuration, :logger, :database_connection
|
10
|
+
|
11
|
+
def initialize(configuration, opts = {})
|
12
|
+
@configuration = configuration
|
13
|
+
@query_executor = opts[:query_executor] ||= ->(sql) { database_connection.execute(sql) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
PgDice::PartitionDropper.new(logger: logger, query_executor: @query_executor)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -4,49 +4,37 @@
|
|
4
4
|
module PgDice
|
5
5
|
# Helps do high-level tasks like getting tables partitioned
|
6
6
|
class PartitionHelper
|
7
|
-
|
8
|
-
def_delegators :@configuration, :logger
|
7
|
+
attr_reader :logger, :approved_tables, :validation, :pg_slice_manager
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
@pg_slice_manager =
|
15
|
-
@validation_helper = PgDice::Validation.new(configuration)
|
9
|
+
def initialize(logger:, approved_tables:, validation:, pg_slice_manager:)
|
10
|
+
@logger = logger
|
11
|
+
@validation = validation
|
12
|
+
@approved_tables = approved_tables
|
13
|
+
@pg_slice_manager = pg_slice_manager
|
16
14
|
end
|
17
15
|
|
18
|
-
def partition_table
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
validation_helper.validate_parameters(params)
|
16
|
+
def partition_table(table_name, params = {})
|
17
|
+
table = approved_tables.fetch(table_name)
|
18
|
+
all_params = table.smash(params)
|
19
|
+
validation.validate_parameters(all_params)
|
23
20
|
|
24
|
-
logger.info { "Preparing database
|
21
|
+
logger.info { "Preparing database for table: #{table}. Using parameters: #{all_params}" }
|
25
22
|
|
26
|
-
prep_and_fill(
|
27
|
-
swap_and_fill(
|
23
|
+
prep_and_fill(all_params)
|
24
|
+
swap_and_fill(all_params)
|
28
25
|
end
|
29
26
|
|
30
|
-
def undo_partitioning!(
|
31
|
-
|
32
|
-
|
33
|
-
validation_helper.validate_parameters(params)
|
34
|
-
logger.info { "Cleaning up database with params: #{table_name}" }
|
27
|
+
def undo_partitioning!(table_name)
|
28
|
+
approved_tables.fetch(table_name)
|
29
|
+
logger.info { "Undoing partitioning for table: #{table_name}" }
|
35
30
|
|
36
31
|
pg_slice_manager.analyze(table_name: table_name, swapped: true)
|
37
32
|
pg_slice_manager.unswap!(table_name: table_name)
|
38
33
|
pg_slice_manager.unprep!(table_name: table_name)
|
39
34
|
end
|
40
35
|
|
41
|
-
def
|
42
|
-
|
43
|
-
rescue PgDice::PgSliceError => error
|
44
|
-
logger.error { "Rescued PgSliceError: #{error}" }
|
45
|
-
false
|
46
|
-
end
|
47
|
-
|
48
|
-
def undo_partitioning(params = {})
|
49
|
-
undo_partitioning!(params)
|
36
|
+
def undo_partitioning(table_name)
|
37
|
+
undo_partitioning!(table_name)
|
50
38
|
rescue PgDice::PgSliceError => error
|
51
39
|
logger.error { "Rescued PgSliceError: #{error}" }
|
52
40
|
false
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Entry point for PartitionManager
|
4
|
+
module PgDice
|
5
|
+
# PartitionManagerFactory is a class used to build PartitionManagers
|
6
|
+
class PartitionHelperFactory
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@configuration, :logger, :approved_tables
|
10
|
+
|
11
|
+
def initialize(configuration, opts = {})
|
12
|
+
@configuration = configuration
|
13
|
+
@validation_factory = opts[:validation_factory] ||= PgDice::ValidationFactory.new(configuration)
|
14
|
+
@pg_slice_manager_factory = opts[:pg_slice_manager_factory] ||= PgDice::PgSliceManagerFactory.new(configuration)
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
PgDice::PartitionHelper.new(logger: logger,
|
19
|
+
approved_tables: approved_tables,
|
20
|
+
validation: @validation_factory.call,
|
21
|
+
pg_slice_manager: @pg_slice_manager_factory.call)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Entry point for PartitionManager
|
4
|
+
module PgDice
|
5
|
+
# PartitionLister is used to list partitions
|
6
|
+
class PartitionLister
|
7
|
+
attr_reader :query_executor
|
8
|
+
|
9
|
+
def initialize(query_executor:)
|
10
|
+
@query_executor = query_executor
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(all_params)
|
14
|
+
sql = build_partition_table_fetch_sql(all_params)
|
15
|
+
query_executor.call(sql)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build_partition_table_fetch_sql(params = {})
|
21
|
+
schema = params.fetch(:schema, 'public')
|
22
|
+
base_table_name = params.fetch(:table_name)
|
23
|
+
|
24
|
+
<<~SQL
|
25
|
+
SELECT tablename
|
26
|
+
FROM pg_tables
|
27
|
+
WHERE schemaname = '#{schema}'
|
28
|
+
AND tablename ~ '^#{base_table_name}_\\d+$'
|
29
|
+
ORDER BY tablename;
|
30
|
+
SQL
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Entry point for PartitionManager
|
4
|
+
module PgDice
|
5
|
+
# PartitionListerFactory is a class used to build PartitionListers
|
6
|
+
class PartitionListerFactory
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@configuration, :database_connection
|
10
|
+
|
11
|
+
def initialize(configuration, opts = {})
|
12
|
+
@configuration = configuration
|
13
|
+
@query_executor = opts[:query_executor] ||= lambda do |sql|
|
14
|
+
database_connection.execute(sql).values.flatten
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
PgDice::PartitionLister.new(query_executor: @query_executor)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -4,83 +4,87 @@
|
|
4
4
|
module PgDice
|
5
5
|
# PartitionManager is a class used to fulfill high-level tasks for partitioning
|
6
6
|
class PartitionManager
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def initialize(
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
7
|
+
include PgDice::TableFinder
|
8
|
+
|
9
|
+
attr_reader :logger, :batch_size, :validation, :approved_tables, :partition_adder,
|
10
|
+
:partition_lister, :partition_dropper, :current_date_provider
|
11
|
+
|
12
|
+
def initialize(opts = {})
|
13
|
+
@logger = opts.fetch(:logger)
|
14
|
+
@batch_size = opts.fetch(:batch_size)
|
15
|
+
@validation = opts.fetch(:validation)
|
16
|
+
@approved_tables = opts.fetch(:approved_tables)
|
17
|
+
@partition_adder = opts.fetch(:partition_adder)
|
18
|
+
@partition_lister = opts.fetch(:partition_lister)
|
19
|
+
@partition_dropper = opts.fetch(:partition_dropper)
|
20
|
+
@current_date_provider = opts.fetch(:current_date_provider, proc { Time.now.utc.to_date })
|
17
21
|
end
|
18
22
|
|
19
|
-
def add_new_partitions(params = {})
|
20
|
-
|
21
|
-
|
22
|
-
validation.validate_parameters(
|
23
|
-
|
23
|
+
def add_new_partitions(table_name, params = {})
|
24
|
+
all_params = approved_tables.smash(table_name, params)
|
25
|
+
logger.debug { "add_new_partitions has been called with params: #{all_params}" }
|
26
|
+
validation.validate_parameters(all_params)
|
27
|
+
partition_adder.call(all_params)
|
24
28
|
end
|
25
29
|
|
26
|
-
def drop_old_partitions(params = {})
|
27
|
-
|
30
|
+
def drop_old_partitions(table_name, params = {})
|
31
|
+
all_params = approved_tables.smash(table_name, params)
|
32
|
+
all_params[:older_than] = current_date_provider.call
|
33
|
+
logger.debug { "drop_old_partitions has been called with params: #{all_params}" }
|
28
34
|
|
29
|
-
validation.validate_parameters(
|
30
|
-
|
31
|
-
|
35
|
+
validation.validate_parameters(all_params)
|
36
|
+
drop_partitions(all_params)
|
37
|
+
end
|
32
38
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
# Grabs only tables that start with the base_table_name and end in numbers
|
40
|
+
def list_partitions(table_name, params = {})
|
41
|
+
all_params = approved_tables.smash(table_name, params)
|
42
|
+
validation.validate_parameters(all_params)
|
43
|
+
partitions(all_params)
|
37
44
|
end
|
38
45
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
46
|
+
def list_droppable_partitions(table_name, params = {})
|
47
|
+
all_params = approved_tables.smash(table_name, params)
|
48
|
+
validation.validate_parameters(all_params)
|
49
|
+
droppable_partitions(all_params)
|
50
|
+
end
|
42
51
|
|
43
|
-
|
52
|
+
def list_batched_droppable_partitions(table_name, params = {})
|
53
|
+
all_params = approved_tables.smash(table_name, params)
|
54
|
+
validation.validate_parameters(all_params)
|
55
|
+
droppable_tables = batched_droppable_partitions(all_params)
|
56
|
+
logger.debug { "Batched partitions eligible for dropping are: #{droppable_tables}" }
|
57
|
+
droppable_tables
|
58
|
+
end
|
44
59
|
|
45
|
-
|
60
|
+
private
|
46
61
|
|
47
|
-
|
62
|
+
def partitions(all_params)
|
63
|
+
logger.info { "Fetching partition tables with params: #{all_params}" }
|
64
|
+
partition_lister.call(all_params)
|
48
65
|
end
|
49
66
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
67
|
+
def droppable_partitions(all_params)
|
68
|
+
older_than = current_date_provider.call
|
69
|
+
minimum_tables = all_params.fetch(:past)
|
70
|
+
period = all_params.fetch(:period)
|
54
71
|
|
55
|
-
|
72
|
+
eligible_partitions = partitions(all_params)
|
56
73
|
|
57
|
-
|
58
|
-
logger.debug { "
|
59
|
-
|
74
|
+
droppable_tables = find_droppable_partitions(eligible_partitions, older_than, minimum_tables, period)
|
75
|
+
logger.debug { "Partitions eligible for dropping older than: #{older_than} are: #{droppable_tables}" }
|
76
|
+
droppable_tables
|
60
77
|
end
|
61
78
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
partition_created_at_date = Date.parse(partition_name.gsub(/#{base_table_name}_/, '')).to_time
|
67
|
-
partition_created_at_date < partitions_older_than_time
|
68
|
-
end
|
79
|
+
def batched_droppable_partitions(all_params)
|
80
|
+
max_tables_to_drop_at_once = all_params.fetch(:batch_size, batch_size)
|
81
|
+
selected_partitions = droppable_partitions(all_params)
|
82
|
+
batched_tables(selected_partitions, max_tables_to_drop_at_once)
|
69
83
|
end
|
70
84
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
limit = params.fetch(:limit, table_drop_batch_size)
|
75
|
-
|
76
|
-
<<~SQL
|
77
|
-
SELECT tablename
|
78
|
-
FROM pg_tables
|
79
|
-
WHERE schemaname = '#{schema}'
|
80
|
-
AND tablename ~ '^#{base_table_name}_\\d+$'
|
81
|
-
ORDER BY tablename
|
82
|
-
LIMIT #{limit}
|
83
|
-
SQL
|
85
|
+
def drop_partitions(all_params)
|
86
|
+
old_partitions = batched_droppable_partitions(all_params)
|
87
|
+
partition_dropper.call(old_partitions)
|
84
88
|
end
|
85
89
|
end
|
86
90
|
end
|