partitioned 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +17 -0
- data/LICENSE +30 -0
- data/PARTITIONING_EXPLAINED.txt +351 -0
- data/README +111 -0
- data/Rakefile +27 -0
- data/examples/README +23 -0
- data/examples/company_id.rb +417 -0
- data/examples/company_id_and_created_at.rb +689 -0
- data/examples/created_at.rb +590 -0
- data/examples/created_at_referencing_awards.rb +1000 -0
- data/examples/id.rb +475 -0
- data/examples/lib/by_company_id.rb +11 -0
- data/examples/lib/command_line_tool_mixin.rb +71 -0
- data/examples/lib/company.rb +29 -0
- data/examples/lib/get_options.rb +44 -0
- data/examples/lib/roman.rb +41 -0
- data/examples/start_date.rb +621 -0
- data/init.rb +1 -0
- data/lib/monkey_patch_activerecord.rb +92 -0
- data/lib/monkey_patch_postgres.rb +73 -0
- data/lib/partitioned.rb +26 -0
- data/lib/partitioned/active_record_overrides.rb +34 -0
- data/lib/partitioned/bulk_methods_mixin.rb +288 -0
- data/lib/partitioned/by_created_at.rb +13 -0
- data/lib/partitioned/by_foreign_key.rb +21 -0
- data/lib/partitioned/by_id.rb +35 -0
- data/lib/partitioned/by_integer_field.rb +32 -0
- data/lib/partitioned/by_monthly_time_field.rb +23 -0
- data/lib/partitioned/by_time_field.rb +65 -0
- data/lib/partitioned/by_weekly_time_field.rb +30 -0
- data/lib/partitioned/multi_level.rb +20 -0
- data/lib/partitioned/multi_level/configurator/data.rb +14 -0
- data/lib/partitioned/multi_level/configurator/dsl.rb +32 -0
- data/lib/partitioned/multi_level/configurator/reader.rb +162 -0
- data/lib/partitioned/multi_level/partition_manager.rb +47 -0
- data/lib/partitioned/partitioned_base.rb +354 -0
- data/lib/partitioned/partitioned_base/configurator.rb +6 -0
- data/lib/partitioned/partitioned_base/configurator/data.rb +62 -0
- data/lib/partitioned/partitioned_base/configurator/dsl.rb +628 -0
- data/lib/partitioned/partitioned_base/configurator/reader.rb +209 -0
- data/lib/partitioned/partitioned_base/partition_manager.rb +138 -0
- data/lib/partitioned/partitioned_base/sql_adapter.rb +286 -0
- data/lib/partitioned/version.rb +3 -0
- data/lib/tasks/desirable_tasks.rake +4 -0
- data/partitioned.gemspec +21 -0
- data/spec/dummy/.rspec +1 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +9 -0
- data/spec/dummy/app/assets/stylesheets/application.css +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +51 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +32 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +60 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/spec_helper.rb +27 -0
- data/spec/monkey_patch_posgres_spec.rb +176 -0
- data/spec/partitioned/bulk_methods_mixin_spec.rb +512 -0
- data/spec/partitioned/by_created_at_spec.rb +62 -0
- data/spec/partitioned/by_foreign_key_spec.rb +95 -0
- data/spec/partitioned/by_id_spec.rb +97 -0
- data/spec/partitioned/by_integer_field_spec.rb +143 -0
- data/spec/partitioned/by_monthly_time_field_spec.rb +100 -0
- data/spec/partitioned/by_time_field_spec.rb +182 -0
- data/spec/partitioned/by_weekly_time_field_spec.rb +100 -0
- data/spec/partitioned/multi_level/configurator/dsl_spec.rb +88 -0
- data/spec/partitioned/multi_level/configurator/reader_spec.rb +147 -0
- data/spec/partitioned/partitioned_base/configurator/dsl_spec.rb +459 -0
- data/spec/partitioned/partitioned_base/configurator/reader_spec.rb +513 -0
- data/spec/partitioned/partitioned_base/sql_adapter_spec.rb +204 -0
- data/spec/partitioned/partitioned_base_spec.rb +173 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/shared_example_spec_helper_for_integer_key.rb +137 -0
- data/spec/support/shared_example_spec_helper_for_time_key.rb +147 -0
- data/spec/support/tables_spec_helper.rb +47 -0
- metadata +250 -0
@@ -0,0 +1,13 @@
|
|
1
|
+
module Partitioned
|
2
|
+
#
|
3
|
+
# partition tables by created_at grouping them by week, with
|
4
|
+
# a week defined as seven days starting on Monday.
|
5
|
+
#
|
6
|
+
class ByCreatedAt < ByWeeklyTimeField
|
7
|
+
self.abstract_class = true
|
8
|
+
|
9
|
+
def self.partition_time_field
|
10
|
+
return :created_at
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Partitioned
|
2
|
+
class ByForeignKey < ByIntegerField
|
3
|
+
self.abstract_class = true
|
4
|
+
|
5
|
+
def self.partition_integer_field
|
6
|
+
return partition_foreign_key
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.partition_foreign_key
|
10
|
+
raise MethodNotImplemented.new(self, :partition_foreign_key)
|
11
|
+
end
|
12
|
+
|
13
|
+
partitioned do |partition|
|
14
|
+
partition.foreign_key lambda {|model, foreign_key_value|
|
15
|
+
return Configurator::Data::ForeignKey.new(model.partition_foreign_key,
|
16
|
+
ActiveSupport::Inflector::pluralize(model.partition_foreign_key.to_s.sub(/_id$/,'')),
|
17
|
+
:id)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Partitioned
|
2
|
+
#
|
3
|
+
# table partitioning by id. this partitioning breaks up data by
|
4
|
+
# the value of its primary key. a specific record's child table
|
5
|
+
# is determined by the number resulting from the integer math:
|
6
|
+
# ID / ById::partition_table_size * ById::partition_table_size
|
7
|
+
#
|
8
|
+
class ById < ByIntegerField
|
9
|
+
self.abstract_class = true
|
10
|
+
|
11
|
+
#
|
12
|
+
# specific to this partitioning, we need to prefetch the primary key (id)
|
13
|
+
# before we attempt to do the insert because the insert wants to know the
|
14
|
+
# name of the specific child table to access.
|
15
|
+
#
|
16
|
+
def self.prefetch_primary_key?
|
17
|
+
return true
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# the number of records in each child table.
|
22
|
+
#
|
23
|
+
def self.partition_table_size
|
24
|
+
return 10000000
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.partition_integer_field
|
28
|
+
return :id
|
29
|
+
end
|
30
|
+
|
31
|
+
partitioned do |partition|
|
32
|
+
partition.index :id, :unique => true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Partitioned
|
2
|
+
class ByIntegerField < PartitionedBase
|
3
|
+
self.abstract_class = true
|
4
|
+
|
5
|
+
def self.partition_table_size
|
6
|
+
return 1
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.partition_integer_field
|
10
|
+
raise MethodNotImplemented.new(self, :partition_integer_field)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.partition_normalize_key_value(integer_field_value)
|
14
|
+
return integer_field_value / partition_table_size * partition_table_size
|
15
|
+
end
|
16
|
+
|
17
|
+
partitioned do |partition|
|
18
|
+
partition.on lambda {|model| return model.partition_integer_field }
|
19
|
+
|
20
|
+
partition.order "substring(tablename, 2)::integer desc"
|
21
|
+
|
22
|
+
partition.check_constraint lambda { |model, id|
|
23
|
+
value = model.partition_normalize_key_value(id)
|
24
|
+
if model.partition_table_size == 1
|
25
|
+
return "( #{model.partition_integer_field} = #{value} )"
|
26
|
+
else
|
27
|
+
return "( #{model.partition_integer_field} >= #{value} and #{model.partition_integer_field} < #{value + model.partition_table_size} )"
|
28
|
+
end
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Partitioned
|
2
|
+
#
|
3
|
+
# partition tables by a time field grouping them by week, with
|
4
|
+
# a week defined as seven days starting on Monday.
|
5
|
+
#
|
6
|
+
class ByMonthlyTimeField < ByTimeField
|
7
|
+
self.abstract_class = true
|
8
|
+
|
9
|
+
def self.partition_normalize_key_value(time_value)
|
10
|
+
return time_value.at_beginning_of_month
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.partition_table_size
|
14
|
+
return 1.month
|
15
|
+
end
|
16
|
+
|
17
|
+
partitioned do |partition|
|
18
|
+
partition.base_name lambda { |model, time_field|
|
19
|
+
return model.partition_normalize_key_value(time_field).strftime('%Y%m')
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Partitioned
|
2
|
+
#
|
3
|
+
# partition tables by a time field grouping them by day
|
4
|
+
#
|
5
|
+
class ByTimeField < PartitionedBase
|
6
|
+
self.abstract_class = true
|
7
|
+
|
8
|
+
#
|
9
|
+
# generate an enumerable that represents all the dates between
|
10
|
+
# start_date and end_date skipping step
|
11
|
+
#
|
12
|
+
# this can be used to calls that take an enumerable like create_infrastructure
|
13
|
+
#
|
14
|
+
def self.partition_generate_range(start_date, end_date, step = :default)
|
15
|
+
step = partition_table_size if step == :default
|
16
|
+
current_date = partition_normalize_key_value(start_date)
|
17
|
+
dates = []
|
18
|
+
while current_date <= end_date
|
19
|
+
dates << current_date
|
20
|
+
current_date += step
|
21
|
+
end
|
22
|
+
return dates
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# normalize the value to the current day
|
27
|
+
#
|
28
|
+
def self.partition_normalize_key_value(time_value)
|
29
|
+
return time_value.to_date
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# the size of the partition, 1.day
|
34
|
+
#
|
35
|
+
def self.partition_table_size
|
36
|
+
return 1.day
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# abstract -- implement in a derived clas.
|
41
|
+
# the name of the time-related field we will use to partition child tables
|
42
|
+
#
|
43
|
+
def self.partition_time_field
|
44
|
+
raise MethodNotImplemented.new(self, :partition_time_field)
|
45
|
+
end
|
46
|
+
|
47
|
+
partitioned do |partition|
|
48
|
+
partition.on lambda {|model| model.partition_time_field}
|
49
|
+
|
50
|
+
partition.index lambda {|model, time_field|
|
51
|
+
return Configurator::Data::Index.new(model.partition_time_field, {})
|
52
|
+
}
|
53
|
+
|
54
|
+
partition.order 'tablename desc'
|
55
|
+
|
56
|
+
partition.base_name lambda { |model, time_field|
|
57
|
+
return model.partition_normalize_key_value(time_field).strftime('%Y%m%d')
|
58
|
+
}
|
59
|
+
partition.check_constraint lambda { |model, time_field|
|
60
|
+
date = model.partition_normalize_key_value(time_field)
|
61
|
+
return "#{model.partition_time_field} >= '#{date.strftime}' AND #{model.partition_time_field} < '#{(date + model.partition_table_size).strftime}'"
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Partitioned
|
2
|
+
#
|
3
|
+
# partition tables by a time field grouping them by week, with
|
4
|
+
# a week defined as seven days starting on Monday.
|
5
|
+
#
|
6
|
+
class ByWeeklyTimeField < ByTimeField
|
7
|
+
self.abstract_class = true
|
8
|
+
|
9
|
+
#
|
10
|
+
# normalize a partition key value by week. We've picked
|
11
|
+
# the begining of the week to key on, which is Monday.
|
12
|
+
#
|
13
|
+
def self.partition_normalize_key_value(time_value)
|
14
|
+
return time_value.at_beginning_of_week
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# The size of the partition table, 7 days (1.week)
|
19
|
+
#
|
20
|
+
def self.partition_table_size
|
21
|
+
return 1.week
|
22
|
+
end
|
23
|
+
|
24
|
+
partitioned do |partition|
|
25
|
+
partition.base_name lambda { |model, time_field|
|
26
|
+
return model.partition_normalize_key_value(time_field).strftime('%Y%m%d')
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Partitioned
|
2
|
+
#
|
3
|
+
# table partitioning by a referenced id column which itself is partitioned
|
4
|
+
# further weekly by a date column.
|
5
|
+
#
|
6
|
+
class MultiLevel < PartitionedBase
|
7
|
+
self.abstract_class = true
|
8
|
+
|
9
|
+
#
|
10
|
+
# Normalize the values for the each of using class.
|
11
|
+
#
|
12
|
+
def self.partition_normalize_key_value(values)
|
13
|
+
normalized_values = []
|
14
|
+
[*values].each_with_index do |value,index|
|
15
|
+
normalized_values << configurator.using_class(index).partition_normalize_key_value(value)
|
16
|
+
end
|
17
|
+
return normalized_values
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Partitioned
|
2
|
+
class MultiLevel
|
3
|
+
module Configurator
|
4
|
+
class Dsl < Partitioned::PartitionedBase::Configurator::Dsl
|
5
|
+
class InvalidForMultiLevelPartitioning < StandardError
|
6
|
+
def initialize(model, dsl_key, remedy)
|
7
|
+
super("#{model.name}: '#{dsl_key}' is not valid for multi-level partitioning. #{remedy}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :data, :model
|
12
|
+
|
13
|
+
def initialize(most_derived_activerecord_class)
|
14
|
+
super(most_derived_activerecord_class, Partitioned::MultiLevel::Configurator::Data)
|
15
|
+
@using_classes = []
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Definition of classes which will be used at multi level partitioning.
|
20
|
+
#
|
21
|
+
def using_classes(*classes)
|
22
|
+
data.using_classes += [*classes]
|
23
|
+
end
|
24
|
+
|
25
|
+
def on(*ignored)
|
26
|
+
raise InvalidForMultiLevelPartitioning.new(model, :on, "the partitioned keyword 'using' is used to define multi-level partitioned tables.")
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module Partitioned
|
2
|
+
class MultiLevel
|
3
|
+
module Configurator
|
4
|
+
class Reader < Partitioned::PartitionedBase::Configurator::Reader
|
5
|
+
UsingConfigurator = Struct.new(:model, :sliced_class, :dsl)
|
6
|
+
def initialize(most_derived_activerecord_class)
|
7
|
+
super
|
8
|
+
@using_classes = nil
|
9
|
+
@using_configurators = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# The field used to partition child tables.
|
14
|
+
#
|
15
|
+
def on_fields
|
16
|
+
unless @on_fields
|
17
|
+
@on_fields = using_collect(&:on_field).map(&:to_sym)
|
18
|
+
end
|
19
|
+
return @on_fields
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# The schema name of the table who is the direct ancestor of a child table.
|
24
|
+
#
|
25
|
+
def parent_table_schema_name(*partition_key_values)
|
26
|
+
if partition_key_values.length <= 1
|
27
|
+
return super
|
28
|
+
end
|
29
|
+
|
30
|
+
return schema_name
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# The table name of the table who is the direct ancestor of a child table.
|
35
|
+
#
|
36
|
+
def parent_table_name(*partition_key_values)
|
37
|
+
if partition_key_values.length <= 1
|
38
|
+
return super
|
39
|
+
end
|
40
|
+
|
41
|
+
# [0...-1] is here because the base name for this parent table is defined by the remove the leaf key value
|
42
|
+
# that is:
|
43
|
+
# current top level table name: public.foos
|
44
|
+
# child schema area: foos_partitions
|
45
|
+
# current partition classes: ByCompanyId then ByCreatedAt
|
46
|
+
# current key values:
|
47
|
+
# company_id: 42
|
48
|
+
# created_at: 2011-01-03
|
49
|
+
# child table name: foos_partitions.p42_20110103
|
50
|
+
# parent table: foos_partitions.p42
|
51
|
+
# grand parent table: public.foos
|
52
|
+
return parent_table_schema_name(*partition_key_values) + '.p' + base_name(*partition_key_values[0...-1])
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Define the check constraint for a given child table.
|
57
|
+
#
|
58
|
+
def check_constraint(*partition_key_values)
|
59
|
+
index = partition_key_values.length - 1
|
60
|
+
value = partition_key_values[index]
|
61
|
+
return using_configurator(index).check_constraint(value)
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# The name of the child table without the schema name or name prefix.
|
66
|
+
#
|
67
|
+
def base_name(*partition_key_values)
|
68
|
+
parts = []
|
69
|
+
partition_key_values.each_with_index do |value,index|
|
70
|
+
parts << using_configurator(index).base_name(value)
|
71
|
+
end
|
72
|
+
return parts.join('_')
|
73
|
+
end
|
74
|
+
|
75
|
+
def using_configurator(index)
|
76
|
+
return using_class(index).configurator
|
77
|
+
end
|
78
|
+
|
79
|
+
def using_class(index)
|
80
|
+
return using_classes[index]
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
def using_configurators
|
87
|
+
unless @using_configurators
|
88
|
+
@using_configurators = []
|
89
|
+
using_classes.each do |using_class|
|
90
|
+
using_class.ancestors.each do |ancestor|
|
91
|
+
next if ancestor.class == Module
|
92
|
+
@using_configurators << UsingConfigurator.new(using_class, ancestor, ancestor::configurator_dsl) if ancestor::configurator_dsl
|
93
|
+
break if ancestor == Partitioned::PartitionedBase
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
return @using_configurators
|
98
|
+
end
|
99
|
+
|
100
|
+
def using_classes
|
101
|
+
unless @using_classes
|
102
|
+
@using_classes = collect_from_collection(&:using_classes).inject([]) do |array,new_items|
|
103
|
+
array += [*new_items]
|
104
|
+
end.to_a
|
105
|
+
end
|
106
|
+
return @using_classes
|
107
|
+
end
|
108
|
+
|
109
|
+
def using_collect(*partition_key_values, &block)
|
110
|
+
values = []
|
111
|
+
using_configurators.each do |using_configurator|
|
112
|
+
data = using_configurator.dsl.data
|
113
|
+
intermediate_value = block.call(data) rescue nil
|
114
|
+
if intermediate_value.is_a? Proc
|
115
|
+
values << intermediate_value.call(using_configurator.model, *partition_key_values)
|
116
|
+
elsif intermediate_value.is_a? String
|
117
|
+
values << eval("\"#{intermediate_value}\"")
|
118
|
+
else
|
119
|
+
values << intermediate_value unless intermediate_value.blank?
|
120
|
+
end
|
121
|
+
end
|
122
|
+
return values
|
123
|
+
end
|
124
|
+
|
125
|
+
def using_collect_first(*partition_key_values, &block)
|
126
|
+
using_configurators.each do |using_configurator|
|
127
|
+
data = using_configurator.dsl.data
|
128
|
+
intermediate_value = block.call(data) rescue nil
|
129
|
+
if intermediate_value.is_a? Proc
|
130
|
+
return intermediate_value.call(using_configurator.model, *partition_key_values)
|
131
|
+
elsif intermediate_value.is_a? String
|
132
|
+
return eval("\"#{intermediate_value}\"")
|
133
|
+
else
|
134
|
+
return intermediate_value unless intermediate_value.nil?
|
135
|
+
end
|
136
|
+
end
|
137
|
+
return nil
|
138
|
+
end
|
139
|
+
|
140
|
+
def using_collect_from_collection(*partition_key_values, &block)
|
141
|
+
values = []
|
142
|
+
using_configurators.each do |using_configurator|
|
143
|
+
data = using_configurator.dsl.data
|
144
|
+
intermediate_values = []
|
145
|
+
intermediate_values = block.call(data) rescue nil
|
146
|
+
[*intermediate_values].each do |intermediate_value|
|
147
|
+
if intermediate_value.is_a? Proc
|
148
|
+
values << intermediate_value.call(using_configurator.model, *partition_key_values)
|
149
|
+
elsif intermediate_value.is_a? String
|
150
|
+
values << eval("\"#{intermediate_value}\"")
|
151
|
+
else
|
152
|
+
values << intermediate_value unless intermediate_value.blank?
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
return values
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|