switchman 1.13.3 → 2.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/switchman/shard.rb +712 -11
  3. data/db/migrate/20130328212039_create_switchman_shards.rb +2 -0
  4. data/db/migrate/20130328224244_create_default_shard.rb +3 -1
  5. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +2 -0
  6. data/db/migrate/20180828183945_add_default_shard_index.rb +3 -1
  7. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +2 -0
  8. data/db/migrate/20190114212900_add_unique_name_indexes.rb +2 -0
  9. data/lib/switchman/action_controller/caching.rb +2 -0
  10. data/lib/switchman/active_record/abstract_adapter.rb +6 -4
  11. data/lib/switchman/active_record/association.rb +45 -16
  12. data/lib/switchman/active_record/attribute_methods.rb +43 -17
  13. data/lib/switchman/active_record/base.rb +60 -20
  14. data/lib/switchman/active_record/batches.rb +2 -0
  15. data/lib/switchman/active_record/calculations.rb +5 -2
  16. data/lib/switchman/active_record/connection_handler.rb +48 -25
  17. data/lib/switchman/active_record/connection_pool.rb +19 -15
  18. data/lib/switchman/active_record/finder_methods.rb +2 -0
  19. data/lib/switchman/active_record/log_subscriber.rb +10 -12
  20. data/lib/switchman/active_record/migration.rb +48 -2
  21. data/lib/switchman/active_record/model_schema.rb +3 -1
  22. data/lib/switchman/active_record/persistence.rb +13 -2
  23. data/lib/switchman/active_record/postgresql_adapter.rb +149 -139
  24. data/lib/switchman/active_record/predicate_builder.rb +3 -1
  25. data/lib/switchman/active_record/query_cache.rb +19 -107
  26. data/lib/switchman/active_record/query_methods.rb +25 -3
  27. data/lib/switchman/active_record/reflection.rb +21 -8
  28. data/lib/switchman/active_record/relation.rb +66 -10
  29. data/lib/switchman/active_record/spawn_methods.rb +2 -0
  30. data/lib/switchman/active_record/statement_cache.rb +6 -25
  31. data/lib/switchman/active_record/table_definition.rb +4 -2
  32. data/lib/switchman/active_record/type_caster.rb +2 -0
  33. data/lib/switchman/active_record/where_clause_factory.rb +2 -0
  34. data/lib/switchman/active_support/cache.rb +18 -0
  35. data/lib/switchman/arel.rb +2 -0
  36. data/lib/switchman/call_super.rb +2 -0
  37. data/lib/switchman/connection_pool_proxy.rb +44 -22
  38. data/lib/switchman/database_server.rb +34 -19
  39. data/lib/switchman/default_shard.rb +3 -0
  40. data/lib/switchman/engine.rb +20 -15
  41. data/lib/switchman/environment.rb +2 -0
  42. data/lib/switchman/errors.rb +2 -0
  43. data/lib/switchman/{shackles → guard_rail}/relation.rb +7 -5
  44. data/lib/switchman/{shackles.rb → guard_rail.rb} +6 -4
  45. data/lib/switchman/open4.rb +2 -0
  46. data/lib/switchman/r_spec_helper.rb +9 -7
  47. data/lib/switchman/rails.rb +2 -0
  48. data/lib/switchman/schema_cache.rb +11 -1
  49. data/lib/switchman/sharded_instrumenter.rb +3 -1
  50. data/lib/switchman/standard_error.rb +3 -1
  51. data/lib/switchman/test_helper.rb +7 -11
  52. data/lib/switchman/version.rb +3 -1
  53. data/lib/switchman.rb +5 -1
  54. data/lib/tasks/switchman.rake +24 -17
  55. metadata +53 -26
  56. data/app/models/switchman/shard_internal.rb +0 -714
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module FinderMethods
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module LogSubscriber
@@ -18,18 +20,14 @@ module Switchman
18
20
  shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
19
21
 
20
22
  unless (payload[:binds] || []).empty?
21
- if ::Rails.version < '5.0.3'
22
- binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect
23
- else
24
- use_old_format = (::Rails.version < '5.1') ? (::Rails.version < '5.0.7') : (::Rails.version < '5.1.5')
25
- args = use_old_format ?
26
- [payload[:binds], payload[:type_casted_binds]] :
27
- [payload[:type_casted_binds]]
28
- casted_params = type_casted_binds(*args)
29
- binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
30
- render_bind(attr, value)
31
- }.inspect
32
- end
23
+ use_old_format = (::Rails.version < '5.1.5')
24
+ args = use_old_format ?
25
+ [payload[:binds], payload[:type_casted_binds]] :
26
+ [payload[:type_casted_binds]]
27
+ casted_params = type_casted_binds(*args)
28
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
29
+ render_bind(attr, value)
30
+ }.inspect
33
31
  end
34
32
 
35
33
  name = colorize_payload_name(name, payload[:name])
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module Migration
@@ -28,9 +30,53 @@ module Switchman
28
30
  end
29
31
 
30
32
  module Migrator
33
+ # significant change: just return MIGRATOR_SALT directly
34
+ # especially if you're going through pgbouncer, the database
35
+ # name you're accessing may not be consistent. it is NOT allowed
36
+ # to run migrations against multiple shards in the same database
37
+ # concurrently
31
38
  def generate_migrator_advisory_lock_id
32
- shard_name_hash = Zlib.crc32(Shard.current.name)
33
- ::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
39
+ ::ActiveRecord::Migrator::MIGRATOR_SALT
40
+ end
41
+
42
+ if ::Rails.version >= '6.0'
43
+ # copy/paste from Rails 6.1
44
+ def with_advisory_lock
45
+ lock_id = generate_migrator_advisory_lock_id
46
+
47
+ with_advisory_lock_connection do |connection|
48
+ got_lock = connection.get_advisory_lock(lock_id)
49
+ raise ::ActiveRecord::ConcurrentMigrationError unless got_lock
50
+ load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
51
+ yield
52
+ ensure
53
+ if got_lock && !connection.release_advisory_lock(lock_id)
54
+ raise ::ActiveRecord::ConcurrentMigrationError.new(
55
+ ::ActiveRecord::ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
56
+ )
57
+ end
58
+ end
59
+ end
60
+
61
+ # significant change: strip out prefer_secondary from config
62
+ def with_advisory_lock_connection
63
+ pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
64
+ ::ActiveRecord::Base.connection_config.except(:prefer_secondary)
65
+ )
66
+
67
+ pool.with_connection { |connection| yield(connection) }
68
+ ensure
69
+ pool&.disconnect!
70
+ end
71
+ end
72
+ end
73
+
74
+ module MigrationContext
75
+ def migrations
76
+ return @migrations if instance_variable_defined?(:@migrations)
77
+ migrations_cache = Thread.current[:migrations_cache] ||= {}
78
+ key = Digest::MD5.hexdigest(migration_files.sort.join(','))
79
+ @migrations = migrations_cache[key] ||= super
34
80
  end
35
81
  end
36
82
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module ModelSchema
4
6
  module ClassMethods
5
7
  def quoted_table_name
6
8
  @quoted_table_name ||= {}
7
- @quoted_table_name[Shard.current.id] ||= connection.quote_table_name(table_name)
9
+ @quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
8
10
  end
9
11
  end
10
12
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module Persistence
4
6
  # touch reads the id attribute directly, so it's not relative to the current shard
5
- def touch(*)
7
+ def touch(*, **)
6
8
  shard.activate(self.class.shard_category) { super }
7
9
  end
8
10
 
@@ -11,6 +13,15 @@ module Switchman
11
13
  shard.activate(self.class.shard_category) { super }
12
14
  end
13
15
  end
16
+
17
+ def delete
18
+ db = shard.database_server
19
+ if ::GuardRail.environment != db.guard_rail_environment
20
+ return db.unguard { super }
21
+ else
22
+ super
23
+ end
24
+ end
14
25
  end
15
26
  end
16
- end
27
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module PostgreSQLAdapter
@@ -38,125 +40,163 @@ module Switchman
38
40
  select_values("SELECT * FROM unnest(current_schemas(false))")
39
41
  end
40
42
 
41
- def use_qualified_names?
42
- @config[:use_qualified_names]
43
+ def extract_schema_qualified_name(string)
44
+ name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
45
+ if string && !name.schema
46
+ name.instance_variable_set(:@schema, shard.name)
47
+ end
48
+ [name.schema, name.identifier]
43
49
  end
44
50
 
45
- def tables(name = nil)
46
- schema = shard.name if use_qualified_names?
47
-
48
- query(<<-SQL, 'SCHEMA').map { |row| row[0] }
49
- SELECT tablename
50
- FROM pg_tables
51
- WHERE schemaname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
52
- SQL
51
+ # significant change: use the shard name if no explicit schema
52
+ def quoted_scope(name = nil, type: nil)
53
+ schema, name = extract_schema_qualified_name(name)
54
+ type = \
55
+ case type
56
+ when "BASE TABLE"
57
+ "'r','p'"
58
+ when "VIEW"
59
+ "'v','m'"
60
+ when "FOREIGN TABLE"
61
+ "'f'"
62
+ end
63
+ scope = {}
64
+ scope[:schema] = quote(schema || shard.name)
65
+ scope[:name] = quote(name) if name
66
+ scope[:type] = type if type
67
+ scope
53
68
  end
54
69
 
55
- if ::Rails.version >= '5.1'
56
- def extract_schema_qualified_name(string)
57
- name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(string.to_s)
58
- if string && !name.schema && use_qualified_names?
59
- name.instance_variable_set(:@schema, shard.name)
60
- end
61
- [name.schema, name.identifier]
70
+ if ::Rails.version < '6.0'
71
+ def tables(name = nil)
72
+ query(<<-SQL, 'SCHEMA').map { |row| row[0] }
73
+ SELECT tablename
74
+ FROM pg_tables
75
+ WHERE schemaname = '#{shard.name}'
76
+ SQL
62
77
  end
63
- else
64
- def data_source_exists?(name)
78
+
79
+ def view_exists?(name)
65
80
  name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
66
81
  return false unless name.identifier
67
- if !name.schema && use_qualified_names?
82
+ if !name.schema
68
83
  name.instance_variable_set(:@schema, shard.name)
69
84
  end
70
85
 
71
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
72
- SELECT COUNT(*)
86
+ select_values(<<-SQL, 'SCHEMA').any?
87
+ SELECT c.relname
73
88
  FROM pg_class c
74
89
  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
75
- WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view
90
+ WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
76
91
  AND c.relname = '#{name.identifier}'
77
- AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
92
+ AND n.nspname = '#{shard.name}'
78
93
  SQL
79
94
  end
80
- end
81
95
 
82
- def view_exists?(name)
83
- name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
84
- return false unless name.identifier
85
- if !name.schema && use_qualified_names?
86
- name.instance_variable_set(:@schema, shard.name)
87
- end
96
+ def indexes(table_name)
97
+ result = query(<<-SQL, 'SCHEMA')
98
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
99
+ FROM pg_class t
100
+ INNER JOIN pg_index d ON t.oid = d.indrelid
101
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
102
+ WHERE i.relkind = 'i'
103
+ AND d.indisprimary = 'f'
104
+ AND t.relname = '#{table_name}'
105
+ AND t.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
106
+ ORDER BY i.relname
107
+ SQL
88
108
 
89
- select_values(<<-SQL, 'SCHEMA').any?
90
- SELECT c.relname
91
- FROM pg_class c
92
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
93
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
94
- AND c.relname = '#{name.identifier}'
95
- AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'}
96
- SQL
97
- end
98
109
 
99
- def indexes(table_name)
100
- schema = shard.name if use_qualified_names?
101
-
102
- result = query(<<-SQL, 'SCHEMA')
103
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
104
- FROM pg_class t
105
- INNER JOIN pg_index d ON t.oid = d.indrelid
106
- INNER JOIN pg_class i ON d.indexrelid = i.oid
107
- WHERE i.relkind = 'i'
108
- AND d.indisprimary = 'f'
109
- AND t.relname = '#{table_name}'
110
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'} )
111
- ORDER BY i.relname
112
- SQL
113
-
114
-
115
- result.map do |row|
116
- index_name = row[0]
117
- unique = row[1] == true || row[1] == 't'
118
- indkey = row[2].split(" ")
119
- inddef = row[3]
120
- oid = row[4]
121
-
122
- columns = Hash[query(<<-SQL, "SCHEMA")]
123
- SELECT a.attnum, a.attname
124
- FROM pg_attribute a
125
- WHERE a.attrelid = #{oid}
126
- AND a.attnum IN (#{indkey.join(",")})
110
+ result.map do |row|
111
+ index_name = row[0]
112
+ unique = row[1] == true || row[1] == 't'
113
+ indkey = row[2].split(" ")
114
+ inddef = row[3]
115
+ oid = row[4]
116
+
117
+ columns = Hash[query(<<-SQL, "SCHEMA")]
118
+ SELECT a.attnum, a.attname
119
+ FROM pg_attribute a
120
+ WHERE a.attrelid = #{oid}
121
+ AND a.attnum IN (#{indkey.join(",")})
122
+ SQL
123
+
124
+ column_names = columns.stringify_keys.values_at(*indkey).compact
125
+
126
+ unless column_names.empty?
127
+ # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
128
+ desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
129
+ orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
130
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
131
+ using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
132
+
133
+ if ::Rails.version >= "5.2"
134
+ ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, orders: orders, where: where, using: using)
135
+ else
136
+ ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
137
+ end
138
+ end
139
+ end.compact
140
+ end
141
+
142
+ def index_name_exists?(table_name, index_name, _default = nil)
143
+ exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
144
+ SELECT COUNT(*)
145
+ FROM pg_class t
146
+ INNER JOIN pg_index d ON t.oid = d.indrelid
147
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
148
+ WHERE i.relkind = 'i'
149
+ AND i.relname = '#{index_name}'
150
+ AND t.relname = '#{table_name}'
151
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
127
152
  SQL
153
+ end
128
154
 
129
- column_names = columns.stringify_keys.values_at(*indkey).compact
155
+ def foreign_keys(table_name)
156
+ # mostly copy-pasted from AR - only change is to the nspname condition for qualified names support
157
+ fk_info = select_all <<-SQL.strip_heredoc
158
+ SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
159
+ FROM pg_constraint c
160
+ JOIN pg_class t1 ON c.conrelid = t1.oid
161
+ JOIN pg_class t2 ON c.confrelid = t2.oid
162
+ JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
163
+ JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
164
+ JOIN pg_namespace t3 ON c.connamespace = t3.oid
165
+ WHERE c.contype = 'f'
166
+ AND t1.relname = #{quote(table_name)}
167
+ AND t3.nspname = '#{shard.name}'
168
+ ORDER BY c.conname
169
+ SQL
130
170
 
131
- unless column_names.empty?
132
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
133
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
134
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
135
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
136
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
171
+ fk_info.map do |row|
172
+ options = {
173
+ column: row['column'],
174
+ name: row['name'],
175
+ primary_key: row['primary_key']
176
+ }
177
+
178
+ options[:on_delete] = extract_foreign_key_action(row['on_delete'])
179
+ options[:on_update] = extract_foreign_key_action(row['on_update'])
180
+
181
+ # strip the schema name from to_table if it matches
182
+ to_table = row['to_table']
183
+ to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(to_table)
184
+ if to_table_qualified_name.schema == shard.name
185
+ to_table = to_table_qualified_name.identifier
186
+ end
137
187
 
138
- if ::Rails.version >= "5.2"
139
- ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, orders: orders, where: where, using: using)
140
- else
141
- ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
188
+ ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, to_table, options)
189
+ end
190
+ end
191
+ else
192
+ def foreign_keys(table_name)
193
+ super.each do |fk|
194
+ to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(fk.to_table)
195
+ if to_table_qualified_name.schema == shard.name
196
+ fk.to_table = to_table_qualified_name.identifier
142
197
  end
143
198
  end
144
- end.compact
145
- end
146
-
147
- def index_name_exists?(table_name, index_name, _default = nil)
148
- schema = shard.name if use_qualified_names?
149
-
150
- exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0
151
- SELECT COUNT(*)
152
- FROM pg_class t
153
- INNER JOIN pg_index d ON t.oid = d.indrelid
154
- INNER JOIN pg_class i ON d.indexrelid = i.oid
155
- WHERE i.relkind = 'i'
156
- AND i.relname = '#{index_name}'
157
- AND t.relname = '#{table_name}'
158
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'} )
159
- SQL
199
+ end
160
200
  end
161
201
 
162
202
  def quote_local_table_name(name)
@@ -169,59 +209,25 @@ module Switchman
169
209
  def quote_table_name(name)
170
210
  return quote_local_table_name(name) if @use_local_table_name
171
211
  name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
172
- if !name.schema && use_qualified_names?
212
+ if !name.schema
173
213
  name.instance_variable_set(:@schema, shard.name)
174
214
  end
175
215
  name.quoted
176
216
  end
177
217
 
178
- def with_local_table_name
179
- @use_local_table_name = true
180
- yield
181
- ensure
182
- @use_local_table_name = false
218
+ def with_global_table_name(&block)
219
+ with_local_table_name(false, &block)
183
220
  end
184
221
 
185
- def foreign_keys(table_name)
186
- schema = shard.name if use_qualified_names?
187
-
188
- # mostly copy-pasted from AR - only change is to the nspname condition for qualified names support
189
- fk_info = select_all <<-SQL.strip_heredoc
190
- SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete
191
- FROM pg_constraint c
192
- JOIN pg_class t1 ON c.conrelid = t1.oid
193
- JOIN pg_class t2 ON c.confrelid = t2.oid
194
- JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
195
- JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
196
- JOIN pg_namespace t3 ON c.connamespace = t3.oid
197
- WHERE c.contype = 'f'
198
- AND t1.relname = #{quote(table_name)}
199
- AND t3.nspname = #{schema ? "'#{schema}'" : 'ANY (current_schemas(false))'}
200
- ORDER BY c.conname
201
- SQL
202
-
203
- fk_info.map do |row|
204
- options = {
205
- column: row['column'],
206
- name: row['name'],
207
- primary_key: row['primary_key']
208
- }
209
-
210
- options[:on_delete] = extract_foreign_key_action(row['on_delete'])
211
- options[:on_update] = extract_foreign_key_action(row['on_update'])
212
-
213
- # strip the schema name from to_table if it matches
214
- to_table = row['to_table']
215
- to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(to_table)
216
- if use_qualified_names? && to_table_qualified_name.schema == shard.name
217
- to_table = to_table_qualified_name.identifier
218
- end
219
-
220
- ::ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(table_name, to_table, options)
221
- end
222
+ def with_local_table_name(enable = true)
223
+ old_value = @use_local_table_name
224
+ @use_local_table_name = enable
225
+ yield
226
+ ensure
227
+ @use_local_table_name = old_value
222
228
  end
223
229
 
224
- def add_index_options(_table_name, _column_name, _options = {})
230
+ def add_index_options(_table_name, _column_name, **)
225
231
  index_name, index_type, index_columns, index_options, algorithm, using = super
226
232
  algorithm = nil if DatabaseServer.creating_new_shard && algorithm == "CONCURRENTLY"
227
233
  [index_name, index_type, index_columns, index_options, algorithm, using]
@@ -248,6 +254,10 @@ module Switchman
248
254
 
249
255
  execute "ALTER INDEX #{quote_table_name(old_name)} RENAME TO #{quote_local_table_name(new_name)}"
250
256
  end
257
+
258
+ def columns(*)
259
+ with_local_table_name(false) { super }
260
+ end
251
261
  end
252
262
  end
253
263
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module PredicateBuilder
@@ -13,7 +15,7 @@ module Switchman
13
15
  def convert_to_id(value)
14
16
  case value
15
17
  when ::ActiveRecord::Base
16
- value.send(primary_key)
18
+ value.id
17
19
  else
18
20
  super
19
21
  end
@@ -1,122 +1,34 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Switchman
2
4
  module ActiveRecord
3
5
  module QueryCache
4
- if ::Rails.version < '5.0.1'
5
- # thread local accessors to replace @query_cache_enabled
6
- def query_cache
7
- thread_cache = Thread.current[:query_cache] ||= {}
8
- thread_cache[self.object_id] ||= Hash.new { |h,sql| h[sql] = {} }
9
- end
10
-
11
- def query_cache_enabled
12
- Thread.current[:query_cache_enabled]
13
- end
14
-
15
- def query_cache_enabled=(value)
16
- Thread.current[:query_cache_enabled] = value
17
- end
18
-
19
- # basically wholesale repeat of the methods from the original (see
20
- # https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb),
21
- # but with self.query_cache_enabled and self.query_cache_enabled= instead
22
- # of @query_cache_enabled.
23
-
24
- def enable_query_cache!
25
- self.query_cache_enabled = true
26
- end
27
-
28
- def disable_query_cache!
29
- self.query_cache_enabled = false
30
- end
31
-
32
- def cache
33
- old, self.query_cache_enabled = query_cache_enabled, true
34
- yield
35
- ensure
36
- self.query_cache_enabled = old
37
- clear_query_cache unless self.query_cache_enabled
38
- end
39
-
40
- def uncached
41
- old, self.query_cache_enabled = query_cache_enabled, false
42
- yield
43
- ensure
44
- self.query_cache_enabled = old
45
- end
46
-
47
- def clear_query_cache
48
- Thread.current[:query_cache]&.clear
49
- end
50
-
51
- def select_all(arel, name = nil, binds = [], preparable: nil)
52
- if self.query_cache_enabled && !locked?(arel)
53
- arel, binds = binds_from_relation(arel, binds)
54
- sql = to_sql(arel, binds)
55
- cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
56
- else
57
- super
58
- end
59
- end
60
-
61
- # no reason to define these on the including class directly. the super
62
- # works just as well from a method on the included module
63
- [:insert, :update, :delete].each do |method_name|
64
- class_eval <<-end_code, __FILE__, __LINE__ + 1
65
- def #{method_name}(*args)
66
- clear_query_cache if self.query_cache_enabled
67
- super
68
- end
69
- end_code
70
- end
71
- end
72
6
 
73
7
  private
74
8
 
75
- if ::Rails.version < '5.1'
76
- def cache_sql(sql, binds)
77
- # have to include the shard id in the cache key because of switching dbs on the same connection
78
- sql = "#{self.shard.id}::#{sql}"
9
+ def cache_sql(sql, name, binds)
10
+ # have to include the shard id in the cache key because of switching dbs on the same connection
11
+ sql = "#{self.shard.id}::#{sql}"
12
+ @lock.synchronize do
79
13
  result =
80
14
  if query_cache[sql].key?(binds)
81
- args = {:sql => sql, :binds => binds, :name => "CACHE", :connection_id => object_id}
82
- args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.0.7'
83
- ::ActiveSupport::Notifications.instrument("sql.active_record", args)
15
+ args = {
16
+ sql: sql,
17
+ binds: binds,
18
+ name: name,
19
+ connection_id: object_id,
20
+ cached: true
21
+ }
22
+ args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
23
+ ::ActiveSupport::Notifications.instrument(
24
+ "sql.active_record",
25
+ args
26
+ )
84
27
  query_cache[sql][binds]
85
28
  else
86
29
  query_cache[sql][binds] = yield
87
30
  end
88
-
89
- if ::ActiveRecord::Result === result
90
- result.dup
91
- else
92
- result.collect { |row| row.dup }
93
- end
94
- end
95
- else
96
- def cache_sql(sql, name, binds)
97
- # have to include the shard id in the cache key because of switching dbs on the same connection
98
- sql = "#{self.shard.id}::#{sql}"
99
- @lock.synchronize do
100
- result =
101
- if query_cache[sql].key?(binds)
102
- args = {
103
- sql: sql,
104
- binds: binds,
105
- name: name,
106
- connection_id: object_id,
107
- cached: true
108
- }
109
- args[:type_casted_binds] = -> { type_casted_binds(binds) } if ::Rails.version >= '5.1.5'
110
- ::ActiveSupport::Notifications.instrument(
111
- "sql.active_record",
112
- args
113
- )
114
- query_cache[sql][binds]
115
- else
116
- query_cache[sql][binds] = yield
117
- end
118
- result.dup
119
- end
31
+ result.dup
120
32
  end
121
33
  end
122
34
  end