pgdice 0.1.0 → 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/.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
|