activerecord-turntable 3.0.0.alpha3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.gitmodules +3 -0
  3. data/.rubocop.yml +18 -0
  4. data/.rubocop_todo.yml +153 -0
  5. data/.travis.yml +20 -4
  6. data/CHANGELOG.md +32 -0
  7. data/Guardfile +2 -2
  8. data/README.md +68 -14
  9. data/Rakefile +42 -0
  10. data/activerecord-turntable.gemspec +13 -3
  11. data/gemfiles/rails_edge.gemfile +8 -0
  12. data/lib/active_record/turntable/active_record_ext/abstract_adapter.rb +3 -1
  13. data/lib/active_record/turntable/active_record_ext/activerecord_import_ext.rb +5 -7
  14. data/lib/active_record/turntable/active_record_ext/acts_as_archive_extension.rb +2 -2
  15. data/lib/active_record/turntable/active_record_ext/association.rb +3 -3
  16. data/lib/active_record/turntable/active_record_ext/clever_load.rb +2 -2
  17. data/lib/active_record/turntable/active_record_ext/database_tasks.rb +10 -8
  18. data/lib/active_record/turntable/active_record_ext/fixtures.rb +15 -13
  19. data/lib/active_record/turntable/active_record_ext/log_subscriber.rb +6 -0
  20. data/lib/active_record/turntable/active_record_ext/persistence.rb +25 -23
  21. data/lib/active_record/turntable/active_record_ext/schema_dumper.rb +8 -75
  22. data/lib/active_record/turntable/algorithm/range_algorithm.rb +6 -7
  23. data/lib/active_record/turntable/algorithm/range_bsearch_algorithm.rb +6 -7
  24. data/lib/active_record/turntable/base.rb +2 -17
  25. data/lib/active_record/turntable/cluster_helper_methods.rb +7 -4
  26. data/lib/active_record/turntable/connection_proxy.rb +4 -2
  27. data/lib/active_record/turntable/migration.rb +3 -5
  28. data/lib/active_record/turntable/mixer.rb +20 -19
  29. data/lib/active_record/turntable/pool_proxy.rb +20 -14
  30. data/lib/active_record/turntable/query_cache.rb +1 -1
  31. data/lib/active_record/turntable/railties/databases.rake +12 -12
  32. data/lib/active_record/turntable/seq_shard.rb +1 -1
  33. data/lib/active_record/turntable/sequencer/barrage.rb +3 -2
  34. data/lib/active_record/turntable/sequencer.rb +33 -29
  35. data/lib/active_record/turntable/shard.rb +8 -8
  36. data/lib/active_record/turntable/sharding_condition.rb +14 -14
  37. data/lib/active_record/turntable/sql_tree_patch.rb +7 -3
  38. data/lib/active_record/turntable/util.rb +4 -2
  39. data/lib/active_record/turntable/version.rb +1 -1
  40. data/lib/active_record/turntable.rb +6 -5
  41. data/lib/activerecord-turntable.rb +1 -0
  42. metadata +120 -101
  43. data/lib/active_record/turntable/helpers/test_helper.rb +0 -25
  44. data/lib/active_record/turntable/helpers.rb +0 -9
  45. data/spec/active_record/turntable/active_record_ext/association_preloader_spec.rb +0 -78
  46. data/spec/active_record/turntable/active_record_ext/association_spec.rb +0 -81
  47. data/spec/active_record/turntable/active_record_ext/clever_load_spec.rb +0 -72
  48. data/spec/active_record/turntable/active_record_ext/fixture_set_spec.rb +0 -27
  49. data/spec/active_record/turntable/active_record_ext/locking_optimistic_spec.rb +0 -28
  50. data/spec/active_record/turntable/active_record_ext/migration_spec.rb +0 -38
  51. data/spec/active_record/turntable/active_record_ext/persistence_spec.rb +0 -211
  52. data/spec/active_record/turntable/active_record_ext/sequencer_spec.rb +0 -22
  53. data/spec/active_record/turntable/active_record_ext/test_fixtures_spec.rb +0 -34
  54. data/spec/active_record/turntable/algorithm/modulo_algorithm_spec.rb +0 -34
  55. data/spec/active_record/turntable/algorithm/range_algorithm_spec.rb +0 -34
  56. data/spec/active_record/turntable/algorithm/range_bsearch_algorithm_spec.rb +0 -34
  57. data/spec/active_record/turntable/algorithm_spec.rb +0 -100
  58. data/spec/active_record/turntable/base_spec.rb +0 -13
  59. data/spec/active_record/turntable/cluster_spec.rb +0 -48
  60. data/spec/active_record/turntable/config_spec.rb +0 -17
  61. data/spec/active_record/turntable/connection_proxy_spec.rb +0 -252
  62. data/spec/active_record/turntable/finder_spec.rb +0 -40
  63. data/spec/active_record/turntable/mixer/fader_spec.rb +0 -4
  64. data/spec/active_record/turntable/mixer_spec.rb +0 -112
  65. data/spec/active_record/turntable/query_cache_spec.rb +0 -28
  66. data/spec/active_record/turntable/sequencer/api_spec.rb +0 -38
  67. data/spec/active_record/turntable/sequencer/barrage_spec.rb +0 -22
  68. data/spec/active_record/turntable/sequencer/mysql_spec.rb +0 -22
  69. data/spec/active_record/turntable/shard_spec.rb +0 -21
  70. data/spec/active_record/turntable/sql_tree_patch_spec.rb +0 -34
  71. data/spec/active_record/turntable/transaction_spec.rb +0 -35
  72. data/spec/active_record/turntable_spec.rb +0 -30
  73. data/spec/config/database.yml +0 -35
  74. data/spec/config/turntable.yml +0 -56
  75. data/spec/fabricators/.gitkeep +0 -0
  76. data/spec/fabricators/turntable_fabricator.rb +0 -12
  77. data/spec/fixtures/cards.yml +0 -11
  78. data/spec/migrations/.gitkeep +0 -0
  79. data/spec/migrations/001_create_users.rb +0 -17
  80. data/spec/migrations/002_create_user_statuses.rb +0 -16
  81. data/spec/migrations/003_create_cards.rb +0 -14
  82. data/spec/migrations/004_create_cards_users.rb +0 -15
  83. data/spec/models/card.rb +0 -3
  84. data/spec/models/cards_user.rb +0 -10
  85. data/spec/models/cards_users_histories.rb +0 -7
  86. data/spec/models/events_users_history.rb +0 -7
  87. data/spec/models/user.rb +0 -7
  88. data/spec/models/user_status.rb +0 -6
  89. data/spec/spec_helper.rb +0 -38
  90. data/spec/support/matchers/be_saved_to.rb +0 -6
  91. data/spec/support/turntable_helper.rb +0 -30
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  end
19
19
 
20
20
  def create_current_turntable_cluster(environment = env)
21
- each_current_turntable_cluster_configuration(true, environment) { |_name, configuration|
21
+ each_current_turntable_cluster_configuration(environment) { |_name, configuration|
22
22
  puts "[turntable] *** executing to database: #{configuration['database']}"
23
23
  create configuration
24
24
  }
@@ -26,26 +26,27 @@ module ActiveRecord
26
26
  end
27
27
 
28
28
  def drop_current_turntable_cluster(environment = env)
29
- each_current_turntable_cluster_configuration(true, environment) { |_name, configuration|
29
+ each_current_turntable_cluster_configuration(environment) { |_name, configuration|
30
30
  puts "[turntable] *** executing to database: #{configuration['database']}"
31
31
  drop configuration
32
32
  }
33
33
  end
34
34
 
35
- def each_current_turntable_cluster_connected(with_test = false, environment = env)
36
- each_current_turntable_cluster_configuration(with_test, environment) do |name, configuration|
35
+ def each_current_turntable_cluster_connected(environment)
36
+ old_connection_pool = ActiveRecord::Base.connection_pool
37
+ each_current_turntable_cluster_configuration(environment) do |name, configuration|
37
38
  ActiveRecord::Base.clear_active_connections!
38
39
  ActiveRecord::Base.establish_connection(configuration)
39
40
  ActiveRecord::Migration.current_shard = name
40
41
  yield(name, configuration)
41
42
  end
42
43
  ActiveRecord::Base.clear_active_connections!
43
- ActiveRecord::Base.establish_connection environment.to_sym
44
+ ActiveRecord::Base.establish_connection old_connection_pool.spec.config
44
45
  end
45
46
 
46
- def each_current_turntable_cluster_configuration(with_test = false, environment = env)
47
+ def each_current_turntable_cluster_configuration(environment)
47
48
  environments = [environment]
48
- environments << "test" if with_test && environment == "development"
49
+ environments << "test" if environment == "development"
49
50
 
50
51
  current_turntable_cluster_configurations(*environments).each do |name, configuration|
51
52
  yield(name, configuration) unless configuration["database"].blank?
@@ -70,8 +71,9 @@ module ActiveRecord
70
71
  configurations = []
71
72
  environments.each do |environ|
72
73
  config = ActiveRecord::Base.configurations[environ]
74
+ next unless config
73
75
  %w(shards seq).each do |name|
74
- configurations += config[name].to_a
76
+ configurations += config[name].to_a if config.has_key?(name)
75
77
  end
76
78
  end
77
79
  configurations
@@ -7,6 +7,7 @@ module ActiveRecord
7
7
  class FixtureSet
8
8
  extend ActiveRecord::Turntable::Util
9
9
 
10
+ # rubocop:disable Style/MultilineMethodCallBraceLayout
10
11
  def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
11
12
  fixture_set_names = Array(fixture_set_names).map(&:to_s)
12
13
  class_names = ClassCache.new class_names, config
@@ -24,7 +25,7 @@ module ActiveRecord
24
25
 
25
26
  fixture_sets = files_to_read.map do |fs_name|
26
27
  klass = class_names[fs_name]
27
- conn = klass.is_a?(String) ? connection : klass.connection
28
+ conn = klass ? klass.connection : connection
28
29
  fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
29
30
  conn,
30
31
  fs_name,
@@ -35,12 +36,16 @@ module ActiveRecord
35
36
  update_all_loaded_fixtures fixtures_map
36
37
 
37
38
  ActiveRecord::Base.force_transaction_all_shards!(requires_new: true) do
39
+ deleted_tables = Hash.new { |h, k| h[k] = Set.new }
38
40
  fixture_sets.each do |fs|
39
41
  conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
40
42
  table_rows = fs.table_rows
41
43
 
42
44
  table_rows.each_key do |table|
43
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", "Fixture Delete"
45
+ unless deleted_tables[conn].include? table
46
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", "Fixture Delete"
47
+ end
48
+ deleted_tables[conn] << table
44
49
  end
45
50
 
46
51
  table_rows.each do |fixture_set_name, rows|
@@ -48,11 +53,9 @@ module ActiveRecord
48
53
  conn.insert_fixture(row, fixture_set_name)
49
54
  end
50
55
  end
51
- end
52
56
 
53
- # Cap primary key sequences to max(pk).
54
- if connection.respond_to?(:reset_pk_sequence!)
55
- fixture_sets.each do |fs|
57
+ # Cap primary key sequences to max(pk).
58
+ if connection.respond_to?(:reset_pk_sequence!)
56
59
  connection.reset_pk_sequence!(fs.table_name)
57
60
  end
58
61
  end
@@ -63,16 +66,14 @@ module ActiveRecord
63
66
  end
64
67
  cached_fixtures(connection, fixture_set_names)
65
68
  end
69
+ # rubocop:enable Style/MultilineMethodCallLayout
66
70
  end
67
71
 
68
72
  module TestFixtures
69
- include ActiveRecord::Turntable::Util
70
-
73
+ # rubocop:disable Style/ClassVars, Style/RedundantException
71
74
  def setup_fixtures(config = ActiveRecord::Base)
72
- return if ActiveRecord::Base.configurations.blank?
73
-
74
75
  if pre_loaded_fixtures && !use_transactional_fixtures
75
- raise "pre_loaded_fixtures requires use_transactional_fixtures"
76
+ raise RuntimeError, "pre_loaded_fixtures requires use_transactional_fixtures"
76
77
  end
77
78
 
78
79
  @fixture_cache = {}
@@ -94,14 +95,15 @@ module ActiveRecord
94
95
  end
95
96
  # Load fixtures for every test.
96
97
  else
97
- ActiveRecord::Fixtures.reset_cache
98
+ ActiveRecord::FixtureSet.reset_cache
98
99
  @@already_loaded_fixtures[self.class] = nil
99
100
  @loaded_fixtures = load_fixtures(config)
100
101
  end
101
102
 
102
103
  # Instantiate fixtures for every test if requested.
103
- instantiate_fixtures(config) if use_instantiated_fixtures
104
+ instantiate_fixtures if use_instantiated_fixtures
104
105
  end
106
+ # rubocop:enable Style/ClassVars, Style/RedundantException
105
107
 
106
108
  def enlist_fixture_connections
107
109
  ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) +
@@ -6,6 +6,12 @@ module ActiveRecord::Turntable
6
6
  # @note prepend to add shard name logging
7
7
  def sql(event)
8
8
  payload = event.payload
9
+
10
+ if self.class::IGNORE_PAYLOAD_NAMES.include?(payload[:name])
11
+ self.class.runtime += event.duration
12
+ return
13
+ end
14
+
9
15
  if payload[:turntable_shard_name]
10
16
  payload[:name] = "#{payload[:name]} [Shard: #{payload[:turntable_shard_name]}]"
11
17
  end
@@ -27,8 +27,9 @@ module ActiveRecord::Turntable
27
27
  end
28
28
 
29
29
  # @note Override to add sharding scope on `touch`
30
+ # rubocop:disable Style/UnlessElse
30
31
  def touch(*names, time: nil)
31
- raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
32
+ raise ActiveRecord::ActiveRecordError, "cannot touch on a new record object" unless persisted?
32
33
 
33
34
  time ||= current_time_from_proper_timezone
34
35
  attributes = timestamp_attributes_for_update_in_model
@@ -68,11 +69,12 @@ module ActiveRecord::Turntable
68
69
  true
69
70
  end
70
71
  end
72
+ # rubocop:enable Style/UnlessElse
71
73
 
72
74
  # @note Override to add sharding scope on `update_columns`
73
75
  def update_columns(attributes)
74
- raise ActiveRecordError, "cannot update a new record" if new_record?
75
- raise ActiveRecordError, "cannot update a destroyed record" if destroyed?
76
+ raise ActiveRecord::ActiveRecordError, "cannot update a new record" if new_record?
77
+ raise ActiveRecord::ActiveRecordError, "cannot update a destroyed record" if destroyed?
76
78
 
77
79
  attributes.each_key do |key|
78
80
  verify_readonly_attribute(key.to_s)
@@ -95,30 +97,30 @@ module ActiveRecord::Turntable
95
97
 
96
98
  private
97
99
 
98
- # @note Override to add sharding scope on destroying
99
- def relation_for_destroy
100
- klass = self.class
101
- relation = klass.unscoped.where(klass.primary_key => id)
100
+ # @note Override to add sharding scope on destroying
101
+ def relation_for_destroy
102
+ klass = self.class
103
+ relation = klass.unscoped.where(klass.primary_key => id)
102
104
 
103
- if klass.turntable_enabled? && klass.primary_key != klass.turntable_shard_key.to_s
104
- relation = relation.where(klass.turntable_shard_key => self[klass.turntable_shard_key])
105
+ if klass.turntable_enabled? && klass.primary_key != klass.turntable_shard_key.to_s
106
+ relation = relation.where(klass.turntable_shard_key => self[klass.turntable_shard_key])
107
+ end
108
+ relation
105
109
  end
106
- relation
107
- end
108
110
 
109
- # @note Override to add sharding scope on updating
110
- def _update_record(attribute_names = self.attribute_names)
111
- klass = self.class
112
- attributes_values = arel_attributes_with_values_for_update(attribute_names)
113
- if attributes_values.empty?
114
- 0
115
- else
116
- scope = if klass.turntable_enabled? and klass.primary_key != klass.turntable_shard_key.to_s
117
- klass.unscoped.where(klass.turntable_shard_key => self.send(turntable_shard_key))
118
- end
119
- klass.unscoped._update_record attributes_values, id, id_was, scope
111
+ # @note Override to add sharding scope on updating
112
+ def _update_record(attribute_names = self.attribute_names)
113
+ klass = self.class
114
+ attributes_values = arel_attributes_with_values_for_update(attribute_names)
115
+ if attributes_values.empty?
116
+ 0
117
+ else
118
+ scope = if klass.turntable_enabled? && (klass.primary_key != klass.turntable_shard_key.to_s)
119
+ klass.unscoped.where(klass.turntable_shard_key => self.send(turntable_shard_key))
120
+ end
121
+ klass.unscoped._update_record attributes_values, id, id_was, scope
122
+ end
120
123
  end
121
- end
122
124
  end
123
125
  end
124
126
  end
@@ -2,93 +2,26 @@
2
2
  module ActiveRecord::Turntable
3
3
  module ActiveRecordExt
4
4
  module SchemaDumper
5
+ SEQUENCE_TABLE_REGEXP = /\A(.*)_id_seq\z/
5
6
 
6
7
  private
7
8
 
8
9
  # @note Override to dump database sequencer method
9
10
  def table(table, stream)
10
- columns = @connection.columns(table)
11
+ unless matchdata = table.match(SEQUENCE_TABLE_REGEXP)
12
+ return super
13
+ end
14
+
11
15
  begin
12
16
  tbl = StringIO.new
13
17
 
14
- # first dump primary key column
15
- if @connection.respond_to?(:primary_keys)
16
- pk = @connection.primary_keys(table)
17
- pk = pk.first unless pk.size > 1
18
- else
19
- pk = @connection.primary_key(table)
20
- end
21
-
22
- if table =~ /\A(.*)_id_seq\z/
23
- tbl.print " create_sequence_for #{remove_prefix_and_suffix(Regexp.last_match(1)).inspect}"
24
- else
25
- tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
26
- end
27
-
28
- case pk
29
- when String
30
- tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id'
31
- pkcol = columns.detect { |c| c.name == pk }
32
- pkcolspec = @connection.column_spec_for_primary_key(pkcol)
33
- if pkcolspec.present?
34
- pkcolspec.each do |key, value|
35
- tbl.print ", #{key}: #{value}"
36
- end
37
- end
38
- when Array
39
- tbl.print ", primary_key: #{pk.inspect}"
40
- else
41
- tbl.print ", id: false"
42
- end
18
+ tbl.print " create_sequence_for #{remove_prefix_and_suffix(matchdata[1]).inspect}"
43
19
  tbl.print ", force: :cascade"
44
20
 
45
21
  table_options = @connection.table_options(table)
46
- tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
47
-
48
- if comment = @connection.table_comment(table).presence
49
- tbl.print ", comment: #{comment.inspect}"
50
- end
51
-
52
- tbl.puts " do |t|"
53
-
54
- # then dump all non-primary key columns
55
- column_specs = columns.map do |column|
56
- raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
57
- next if column.name == pk
58
- @connection.column_spec(column)
59
- end.compact
60
-
61
- # find all migration keys used in this table
62
- keys = @connection.migration_keys
63
-
64
- # figure out the lengths for each column based on above keys
65
- lengths = keys.map { |key|
66
- column_specs.map { |spec|
67
- spec[key] ? spec[key].length + 2 : 0
68
- }.max
69
- }
70
-
71
- # the string we're going to sprintf our values against, with standardized column widths
72
- format_string = lengths.map{ |len| "%-#{len}s" }
73
-
74
- # find the max length for the 'type' column, which is special
75
- type_length = column_specs.map{ |column| column[:type].length }.max
76
-
77
- # add column type definition to our format string
78
- format_string.unshift " t.%-#{type_length}s "
79
-
80
- format_string *= ''
81
-
82
- column_specs.each do |colspec|
83
- values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
84
- values.unshift colspec[:type]
85
- tbl.print((format_string % values).gsub(/,\s*$/, ''))
86
- tbl.puts
22
+ if table_options.present?
23
+ tbl.print ", #{format_options(table_options)}"
87
24
  end
88
-
89
- indexes_in_create(table, tbl)
90
-
91
- tbl.puts " end"
92
25
  tbl.puts
93
26
 
94
27
  tbl.rewind
@@ -18,17 +18,16 @@ module ActiveRecord::Turntable::Algorithm
18
18
 
19
19
  # { connection_name => weight, ... }
20
20
  def calculate_used_shards_with_weight(sequence_value)
21
- idx = calculate_idx(sequence_value)
22
- last_connection = calculate(sequence_value)
23
- shards = @config[:shards][0..idx]
21
+ current_shard_idx = calculate_idx(sequence_value)
22
+ shards = @config[:shards][0..current_shard_idx]
24
23
  weighted_hash = Hash.new { |h, k| h[k] = 0 }
25
24
  prev_max = 0
26
25
  shards.each_with_index do |h, idx|
27
26
  weighted_hash[h[:connection]] += if idx < shards.size - 1
28
- h[:less_than] - prev_max - 1
29
- else
30
- sequence_value - prev_max
31
- end
27
+ h[:less_than] - prev_max - 1
28
+ else
29
+ sequence_value - prev_max
30
+ end
32
31
  prev_max = h[:less_than] - 1
33
32
  end
34
33
  weighted_hash
@@ -22,17 +22,16 @@ module ActiveRecord::Turntable::Algorithm
22
22
 
23
23
  # { connection_name => weight, ... }
24
24
  def calculate_used_shards_with_weight(sequence_value)
25
- idx = calculate_idx(sequence_value)
26
- last_connection = calculate(sequence_value)
27
- shards = @config[:shards][0..idx]
25
+ current_shard_idx = calculate_idx(sequence_value)
26
+ shards = @config[:shards][0..current_shard_idx]
28
27
  weighted_hash = Hash.new { |h, k| h[k] = 0 }
29
28
  prev_max = 0
30
29
  shards.each_with_index do |h, idx|
31
30
  weighted_hash[h[:connection]] += if idx < shards.size - 1
32
- h[:less_than] - prev_max - 1
33
- else
34
- sequence_value - prev_max
35
- end
31
+ h[:less_than] - prev_max - 1
32
+ else
33
+ sequence_value - prev_max
34
+ end
36
35
  prev_max = h[:less_than] - 1
37
36
  end
38
37
  weighted_hash
@@ -22,21 +22,6 @@ module ActiveRecord::Turntable
22
22
  include ClusterHelperMethods
23
23
  end
24
24
 
25
- module ConnectionExtension
26
- module ClassMethods
27
- def connection_specification_name
28
- return super unless turntable_enabled?
29
- self.connection_specification_name = "turntable_pool_proxy::#{name}"
30
- end
31
- end
32
-
33
- def self.prepended(base)
34
- class << base
35
- prepend ClassMethods
36
- end
37
- end
38
- end
39
-
40
25
  module ClassMethods
41
26
  # @param [Symbol] cluster_name cluster name for this class
42
27
  # @param [Symbol] shard_key_name shard key attribute name
@@ -52,8 +37,7 @@ module ActiveRecord::Turntable
52
37
  self.turntable_clusters[cluster_name] ||= Cluster.new(
53
38
  turntable_config[:clusters][cluster_name],
54
39
  options
55
- )
56
- prepend ConnectionExtension
40
+ )
57
41
  turntable_replace_connection_pool
58
42
  end
59
43
 
@@ -62,6 +46,7 @@ module ActiveRecord::Turntable
62
46
  cp = ConnectionProxy.new(self, turntable_cluster)
63
47
  pp = PoolProxy.new(cp)
64
48
 
49
+ self.connection_specification_name = "turntable_pool_proxy::#{name}"
65
50
  ch.send(:owner_to_pool)[connection_specification_name] = pp
66
51
  end
67
52
 
@@ -30,7 +30,9 @@ module ActiveRecord::Turntable
30
30
  end
31
31
 
32
32
  def force_connect_all_shards!
33
- conf = configurations[Rails.env]
33
+ conf = configurations[::ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_sym]
34
+ return unless conf
35
+
34
36
  shards = HashWithIndifferentAccess.new
35
37
  shards = shards.merge(conf[:shards]) if conf[:shards]
36
38
  shards = shards.merge(conf[:seq]) if conf[:seq]
@@ -44,20 +46,21 @@ module ActiveRecord::Turntable
44
46
  shards_weight = self.turntable_cluster.weighted_shards(self.current_sequence)
45
47
  sum = shards_weight.values.inject(&:+)
46
48
  idx = rand(sum)
47
- shard, weight = shards_weight.find {|_k, v|
49
+ shard, _weight = shards_weight.find { |_k, v|
48
50
  (idx -= v) < 0
49
51
  }
52
+ shard ||= shards_weight.keys.first
50
53
  self.connection.with_recursive_shards(shard.name, *klasses, &block)
51
54
  end
52
55
 
53
56
  def all_cluster_transaction(options = {})
54
57
  clusters = turntable_clusters.values
55
- recursive_cluster_transaction(clusters) { yield }
58
+ recursive_cluster_transaction(clusters, options) { yield }
56
59
  end
57
60
 
58
61
  def recursive_cluster_transaction(clusters, options = {}, &block)
59
62
  current_cluster = clusters.shift
60
- current_cluster.shards_transaction do
63
+ current_cluster.shards_transaction([], options) do
61
64
  if clusters.present?
62
65
  recursive_cluster_transaction(clusters, options, &block)
63
66
  else
@@ -53,6 +53,7 @@ module ActiveRecord::Turntable
53
53
  end
54
54
  end
55
55
 
56
+ # rubocop:disable Style/MethodMissing
56
57
  def method_missing(method, *args, &block)
57
58
  clear_query_cache_if_needed(method)
58
59
  if shard_fixed?
@@ -69,6 +70,7 @@ module ActiveRecord::Turntable
69
70
  connection.send(method, *args, &block)
70
71
  end
71
72
  end
73
+ # rubocop:enable Style/MethodMissing
72
74
 
73
75
  def respond_to_missing?(method, include_private = false)
74
76
  connection.send(:respond_to?, method, include_private)
@@ -198,7 +200,7 @@ module ActiveRecord::Turntable
198
200
  end
199
201
 
200
202
  delegate :connected?, :automatic_reconnect, :automatic_reconnect=, :checkout_timeout, :dead_connection_timeout,
201
- :spec, :connections, :size, :reaper, :table_exists?, to: :connection_pool
203
+ :spec, :connections, :size, :reaper, to: :connection_pool
202
204
 
203
205
  %w(columns columns_hash column_defaults primary_keys).each do |name|
204
206
  define_method(name.to_sym) do
@@ -206,7 +208,7 @@ module ActiveRecord::Turntable
206
208
  end
207
209
  end
208
210
 
209
- %w(table_exists?).each do |name|
211
+ %w(data_source_exists?).each do |name|
210
212
  define_method(name.to_sym) do |*args|
211
213
  master.connection_pool.with_connection do |c|
212
214
  c.schema_cache.send(name.to_sym, *args)
@@ -52,7 +52,6 @@ module ActiveRecord::Turntable::Migration
52
52
  options = options.merge(id: false)
53
53
 
54
54
  # TODO: pkname should be pulled from table definitions
55
- pkname = "id"
56
55
  sequence_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
57
56
  create_table(sequence_table_name, options) do |t|
58
57
  t.integer :id, limit: 8
@@ -62,7 +61,6 @@ module ActiveRecord::Turntable::Migration
62
61
 
63
62
  def drop_sequence_for(table_name, options = {})
64
63
  # TODO: pkname should be pulled from table definitions
65
- pkname = "id"
66
64
  sequence_table_name = ActiveRecord::Turntable::Sequencer.sequence_name(table_name, "id")
67
65
  drop_table(sequence_table_name)
68
66
  end
@@ -108,7 +106,7 @@ module ActiveRecord::Turntable::Migration
108
106
  def up(migrations_paths, target_version = nil)
109
107
  super
110
108
 
111
- ActiveRecord::Tasks::DatabaseTasks.each_current_turntable_cluster_connected do |name, configuration|
109
+ ActiveRecord::Tasks::DatabaseTasks.each_current_turntable_cluster_connected(current_environment) do |name, configuration|
112
110
  puts "[turntable] *** Migrating database: #{configuration['database']}(Shard: #{name})"
113
111
  super(migrations_paths, target_version)
114
112
  end
@@ -117,7 +115,7 @@ module ActiveRecord::Turntable::Migration
117
115
  def down(migrations_paths, target_version = nil, &block)
118
116
  super
119
117
 
120
- ActiveRecord::Tasks::DatabaseTasks.each_current_turntable_cluster_connected do |name, configuration|
118
+ ActiveRecord::Tasks::DatabaseTasks.each_current_turntable_cluster_connected(current_environment) do |name, configuration|
121
119
  puts "[turntable] *** Migrating database: #{configuration['database']}(Shard: #{name})"
122
120
  super(migrations_paths, target_version, &block)
123
121
  end
@@ -126,7 +124,7 @@ module ActiveRecord::Turntable::Migration
126
124
  def run(*args)
127
125
  super
128
126
 
129
- ActiveRecord::Tasks::DatabaseTasks.each_current_turntable_cluster_connected do |name, configuration|
127
+ ActiveRecord::Tasks::DatabaseTasks.each_current_turntable_cluster_connected(current_environment) do |name, configuration|
130
128
  puts "[turntable] *** Migrating database: #{configuration['database']}(Shard: #{name})"
131
129
  super(*args)
132
130
  end
@@ -1,3 +1,4 @@
1
+ # rubocop:disable Style/CaseEquality
1
2
  require "active_support/core_ext/object/try"
2
3
  require "active_record/turntable/sql_tree_patch"
3
4
 
@@ -11,7 +12,7 @@ module ActiveRecord::Turntable
11
12
 
12
13
  delegate :logger, to: ActiveRecord::Base
13
14
 
14
- NOT_USED_FOR_SHARDING_OPERATORS_REGEXP = /\A(NOT IN|IS|IS NOT|BETWEEN|LIKE|\!\=|<<|>>|<>|>\=|<=|[\*\+\-\/\%\|\&><])\z/
15
+ NOT_USED_FOR_SHARDING_OPERATORS_REGEXP = /\A(NOT IN|IS|IS NOT|BETWEEN|LIKE|!=|<<|>>|<>|>=|<=|[*+%|&><-])\z/
15
16
 
16
17
  def initialize(proxy)
17
18
  @proxy = proxy
@@ -60,7 +61,7 @@ module ActiveRecord::Turntable
60
61
  lkeys = find_shard_keys(tree.lhs, table_name, shard_key)
61
62
  rkeys = find_shard_keys(tree.rhs, table_name, shard_key)
62
63
  if lkeys.present? && rkeys.present?
63
- lkeys + rkeys
64
+ (lkeys + rkeys).uniq
64
65
  else
65
66
  []
66
67
  end
@@ -68,22 +69,22 @@ module ActiveRecord::Turntable
68
69
  lkeys = find_shard_keys(tree.lhs, table_name, shard_key)
69
70
  rkeys = find_shard_keys(tree.rhs, table_name, shard_key)
70
71
  if lkeys.present? || rkeys.present?
71
- lkeys + rkeys
72
+ (lkeys + rkeys).uniq
72
73
  else
73
74
  []
74
75
  end
75
76
  when "IN", "=", "=="
76
77
  field = tree.lhs.respond_to?(:table) ? tree.lhs : nil
77
78
  if tree.rhs.is_a?(SQLTree::Node::SubQuery)
78
- if field.try(:table) == table_name and field.name == shard_key
79
+ if (field.try(:table) == table_name) && (field.name == shard_key)
79
80
  find_shard_keys(tree.rhs.where, table_name, shard_key)
80
81
  else
81
82
  []
82
83
  end
83
84
  else
84
85
  values = Array(tree.rhs)
85
- if field.try(:table) == table_name and field.name == shard_key and
86
- !tree.rhs.is_a?(SQLTree::Node::SubQuery)
86
+ if (field.try(:table) == table_name) && (field.name == shard_key) &&
87
+ !tree.rhs.is_a?(SQLTree::Node::SubQuery)
87
88
  values.map(&:value).compact
88
89
  else
89
90
  []
@@ -113,14 +114,15 @@ module ActiveRecord::Turntable
113
114
  end
114
115
 
115
116
  def bind_sql(sql, binds)
117
+ binds = binds ? binds.dup : []
116
118
  # TODO: substitution value should be determined by adapter
117
- query = sql.is_a?(String) ? sql : @proxy.to_sql(sql, binds ? binds.dup : [])
118
- query = if query.include?("\0") && binds.is_a?(Array) && binds[0].is_a?(Array) && binds[0][0].is_a?(ActiveRecord::ConnectionAdapters::Column)
119
- binds = binds.dup
120
- query.gsub("\0") { @proxy.master.connection.quote(*binds.shift.reverse) }
121
- else
122
- query
123
- end
119
+ query = sql.is_a?(String) ? sql : @proxy.to_sql(sql, binds)
120
+ if query.include?("\0") && binds.is_a?(Array) && binds[0].is_a?(Array) && binds[0][0].is_a?(ActiveRecord::ConnectionAdapters::Column)
121
+ binds = binds.dup
122
+ query.gsub("\0") { @proxy.master.connection.quote(*binds.shift.reverse) }
123
+ else
124
+ query
125
+ end
124
126
  end
125
127
 
126
128
  def build_select_fader(tree, method, query, *args, &block)
@@ -133,6 +135,7 @@ module ActiveRecord::Turntable
133
135
  @proxy.klass.table_name,
134
136
  @proxy.klass.turntable_shard_key.to_s)
135
137
  end
138
+ shard_keys.uniq!
136
139
 
137
140
  if shard_keys.size == 1 # shard
138
141
  return Fader::SpecifiedShard.new(@proxy,
@@ -142,8 +145,7 @@ module ActiveRecord::Turntable
142
145
  tree.select.first.to_sql == '1 AS "one"' # for `SELECT 1 AS one` (AR::Base.exists?)
143
146
  return Fader::SelectShardsMergeResult.new(@proxy,
144
147
  build_shards_with_same_query(@proxy.shards.values, query),
145
- method, query, *args, &block
146
- )
148
+ method, query, *args, &block)
147
149
  elsif tree.group_by || tree.order_by || tree.limit.try(:value).to_i > 0
148
150
  raise CannotSpecifyShardError, "cannot specify shard for query: #{tree.to_sql}"
149
151
  elsif shard_keys.present?
@@ -155,8 +157,7 @@ module ActiveRecord::Turntable
155
157
  else
156
158
  return Fader::SelectShardsMergeResult.new(@proxy,
157
159
  Hash[shard_keys.map { |k| [@proxy.cluster.shard_for(k), query] }],
158
- method, query, *args, &block
159
- )
160
+ method, query, *args, &block)
160
161
  end
161
162
  else # scan all shards
162
163
  if SQLTree::Node::SelectDeclaration === tree.select.first &&
@@ -177,8 +178,7 @@ module ActiveRecord::Turntable
177
178
  end
178
179
  return Fader::SelectShardsMergeResult.new(@proxy,
179
180
  build_shards_with_same_query(@proxy.shards.values, query),
180
- method, query, *args, &block
181
- )
181
+ method, query, *args, &block)
182
182
  else
183
183
  raise CannotSpecifyShardError, "cannot specify shard for query: #{tree.to_sql}"
184
184
  end
@@ -231,3 +231,4 @@ module ActiveRecord::Turntable
231
231
  end
232
232
  end
233
233
  end
234
+ # rubocop:enable Style/CaseEquality