switchman 2.0.10 → 3.0.1

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/Rakefile +10 -2
  3. data/app/models/switchman/shard.rb +234 -270
  4. data/app/models/switchman/unsharded_record.rb +7 -0
  5. data/db/migrate/20130328212039_create_switchman_shards.rb +1 -1
  6. data/db/migrate/20130328224244_create_default_shard.rb +5 -5
  7. data/db/migrate/20161206323434_add_back_default_string_limits_switchman.rb +1 -0
  8. data/db/migrate/20180828183945_add_default_shard_index.rb +1 -1
  9. data/db/migrate/20180828192111_add_timestamps_to_shards.rb +7 -5
  10. data/db/migrate/20190114212900_add_unique_name_indexes.rb +5 -3
  11. data/lib/switchman/action_controller/caching.rb +2 -2
  12. data/lib/switchman/active_record/abstract_adapter.rb +1 -0
  13. data/lib/switchman/active_record/association.rb +78 -89
  14. data/lib/switchman/active_record/attribute_methods.rb +106 -52
  15. data/lib/switchman/active_record/base.rb +58 -59
  16. data/lib/switchman/active_record/calculations.rb +74 -67
  17. data/lib/switchman/active_record/connection_pool.rb +14 -41
  18. data/lib/switchman/active_record/database_configurations/database_config.rb +13 -0
  19. data/lib/switchman/active_record/database_configurations.rb +34 -0
  20. data/lib/switchman/active_record/finder_methods.rb +11 -16
  21. data/lib/switchman/active_record/log_subscriber.rb +4 -8
  22. data/lib/switchman/active_record/migration.rb +14 -43
  23. data/lib/switchman/active_record/model_schema.rb +1 -1
  24. data/lib/switchman/active_record/persistence.rb +4 -6
  25. data/lib/switchman/active_record/postgresql_adapter.rb +32 -160
  26. data/lib/switchman/active_record/predicate_builder.rb +1 -1
  27. data/lib/switchman/active_record/query_cache.rb +18 -19
  28. data/lib/switchman/active_record/query_methods.rb +173 -182
  29. data/lib/switchman/active_record/reflection.rb +6 -10
  30. data/lib/switchman/active_record/relation.rb +28 -74
  31. data/lib/switchman/active_record/spawn_methods.rb +27 -29
  32. data/lib/switchman/active_record/statement_cache.rb +18 -35
  33. data/lib/switchman/active_record/tasks/database_tasks.rb +16 -0
  34. data/lib/switchman/active_support/cache.rb +3 -5
  35. data/lib/switchman/arel.rb +13 -8
  36. data/lib/switchman/database_server.rb +122 -144
  37. data/lib/switchman/default_shard.rb +52 -16
  38. data/lib/switchman/engine.rb +61 -57
  39. data/lib/switchman/environment.rb +4 -8
  40. data/lib/switchman/errors.rb +1 -0
  41. data/lib/switchman/guard_rail/relation.rb +5 -7
  42. data/lib/switchman/guard_rail.rb +6 -19
  43. data/lib/switchman/r_spec_helper.rb +29 -37
  44. data/lib/switchman/rails.rb +14 -12
  45. data/lib/switchman/sharded_instrumenter.rb +1 -1
  46. data/lib/switchman/standard_error.rb +15 -3
  47. data/lib/switchman/test_helper.rb +6 -10
  48. data/lib/switchman/version.rb +1 -1
  49. data/lib/switchman.rb +3 -5
  50. data/lib/tasks/switchman.rake +54 -69
  51. metadata +86 -45
  52. data/lib/switchman/active_record/batches.rb +0 -11
  53. data/lib/switchman/active_record/connection_handler.rb +0 -172
  54. data/lib/switchman/active_record/where_clause_factory.rb +0 -36
  55. data/lib/switchman/connection_pool_proxy.rb +0 -173
  56. data/lib/switchman/schema_cache.rb +0 -28
@@ -7,19 +7,18 @@ module Switchman
7
7
  return super(id) unless klass.integral_id?
8
8
 
9
9
  if shard_source_value != :implicit
10
- current_shard = Shard.current(klass.shard_category)
11
- result = self.activate do |relation, shard|
10
+ current_shard = Shard.current(klass.connection_classes)
11
+ result = activate do |relation, shard|
12
12
  current_id = Shard.relative_id_for(id, current_shard, shard)
13
13
  # current_id will be nil for non-integral id
14
14
  next unless current_id
15
15
  # skip the shard if the object can't be on it. unless we're only looking at one shard;
16
16
  # we might be expecting a shadow object
17
- next if current_id > Shard::IDS_PER_SHARD && self.all_shards.length > 1
17
+ next if current_id > Shard::IDS_PER_SHARD && all_shards.length > 1
18
+
18
19
  relation.call_super(:find_one, FinderMethods, current_id)
19
20
  end
20
- if result.is_a?(Array)
21
- result = result.first
22
- end
21
+ result = result.first if result.is_a?(Array)
23
22
  # we may have skipped all shards
24
23
  raise_record_not_found_exception!(id, 0, 1) unless result
25
24
  return result
@@ -34,8 +33,8 @@ module Switchman
34
33
  end
35
34
 
36
35
  def find_some_ordered(ids)
37
- current_shard = Shard.current(klass.shard_category)
38
- ids = ids.map{|id| Shard.relative_id_for(id, current_shard, current_shard)}
36
+ current_shard = Shard.current(klass.connection_classes)
37
+ ids = ids.map { |id| Shard.relative_id_for(id, current_shard, current_shard) }
39
38
  super(ids)
40
39
  end
41
40
 
@@ -45,14 +44,12 @@ module Switchman
45
44
 
46
45
  def exists?(conditions = :none)
47
46
  conditions = conditions.id if ::ActiveRecord::Base === conditions
48
- return false if !conditions
47
+ return false unless conditions
49
48
 
50
- relation = ::Rails.version >= "5.2" ?
51
- apply_join_dependency(eager_loading: false) :
52
- apply_join_dependency(self, construct_join_dependency)
49
+ relation = apply_join_dependency(eager_loading: false)
53
50
  return false if ::ActiveRecord::NullRelation === relation
54
51
 
55
- relation = relation.except(:select, :order).select("1 AS one").limit(1)
52
+ relation = relation.except(:select, :order).select('1 AS one').limit(1)
56
53
 
57
54
  case conditions
58
55
  when Array, Hash
@@ -62,9 +59,7 @@ module Switchman
62
59
  end
63
60
 
64
61
  relation.activate do |shard_rel|
65
- return true if ::Rails.version >= "5.2" ?
66
- connection.select_value(shard_rel.arel, "#{name} Exists") :
67
- connection.select_value(shard_rel, "#{name} Exists", shard_rel.bound_attributes)
62
+ return true if connection.select_value(shard_rel.arel, "#{name} Exists")
68
63
  end
69
64
  false
70
65
  end
@@ -14,20 +14,16 @@ module Switchman
14
14
 
15
15
  name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
16
16
  name = "CACHE #{name}" if payload[:cached]
17
- sql = payload[:sql].squeeze(' '.freeze)
17
+ sql = payload[:sql].squeeze(' ')
18
18
  binds = nil
19
19
  shard = payload[:shard]
20
20
  shard = " [#{shard[:database_server_id]}:#{shard[:id]} #{shard[:env]}]" if shard
21
21
 
22
22
  unless (payload[:binds] || []).empty?
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|
23
+ casted_params = type_casted_binds(payload[:type_casted_binds])
24
+ binds = ' ' + payload[:binds].zip(casted_params).map do |attr, value|
29
25
  render_bind(attr, value)
30
- }.inspect
26
+ end.inspect
31
27
  end
32
28
 
33
29
  name = colorize_payload_name(name, payload[:name])
@@ -4,73 +4,44 @@ module Switchman
4
4
  module ActiveRecord
5
5
  module Migration
6
6
  module Compatibility
7
- module V5_0
7
+ module V5_0 # rubocop:disable Naming/ClassAndModuleCamelCase
8
8
  def create_table(*args, **options)
9
- unless options.key?(:id)
10
- options[:id] = :bigserial
11
- end
12
- if block_given?
13
- super do |td|
14
- yield td
15
- end
16
- else
17
- super
18
- end
9
+ options[:id] = :bigserial unless options.key?(:id)
10
+ super
19
11
  end
20
12
  end
21
13
  end
22
14
 
23
15
  def connection
24
16
  conn = super
25
- if conn.shard != ::ActiveRecord::Base.connection_pool.current_pool.shard
26
- ::ActiveRecord::Base.connection_pool.current_pool.switch_database(conn)
27
- end
17
+ ::ActiveRecord::Base.connection_pool.switch_database(conn) if conn.shard != ::ActiveRecord::Base.connection_pool.shard
28
18
  conn
29
19
  end
30
20
  end
31
21
 
32
22
  module Migrator
33
- # significant change: hash shard id, not database name
23
+ # significant change: hash shard name, not database name
34
24
  def generate_migrator_advisory_lock_id
35
25
  shard_name_hash = Zlib.crc32(Shard.current.name)
36
26
  ::ActiveRecord::Migrator::MIGRATOR_SALT * shard_name_hash
37
27
  end
38
28
 
39
- if ::Rails.version >= '6.0'
40
- # copy/paste from Rails 6.1
41
- def with_advisory_lock
42
- lock_id = generate_migrator_advisory_lock_id
43
-
44
- with_advisory_lock_connection do |connection|
45
- got_lock = connection.get_advisory_lock(lock_id)
46
- raise ::ActiveRecord::ConcurrentMigrationError unless got_lock
47
- load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock
48
- yield
49
- ensure
50
- if got_lock && !connection.release_advisory_lock(lock_id)
51
- raise ::ActiveRecord::ConcurrentMigrationError.new(
52
- ::ActiveRecord::ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE
53
- )
54
- end
55
- end
56
- end
29
+ # significant change: strip out prefer_secondary from config
30
+ def with_advisory_lock_connection
31
+ pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
32
+ ::ActiveRecord::Base.connection_db_config.configuration_hash.except(:prefer_secondary)
33
+ )
57
34
 
58
- # significant change: strip out prefer_secondary from config
59
- def with_advisory_lock_connection
60
- pool = ::ActiveRecord::ConnectionAdapters::ConnectionHandler.new.establish_connection(
61
- ::ActiveRecord::Base.connection_config.except(:prefer_secondary)
62
- )
63
-
64
- pool.with_connection { |connection| yield(connection) }
65
- ensure
66
- pool&.disconnect!
67
- end
35
+ pool.with_connection { |connection| yield(connection) } # rubocop:disable Style/ExplicitBlockArgument
36
+ ensure
37
+ pool&.disconnect!
68
38
  end
69
39
  end
70
40
 
71
41
  module MigrationContext
72
42
  def migrations
73
43
  return @migrations if instance_variable_defined?(:@migrations)
44
+
74
45
  migrations_cache = Thread.current[:migrations_cache] ||= {}
75
46
  key = Digest::MD5.hexdigest(migration_files.sort.join(','))
76
47
  @migrations = migrations_cache[key] ||= super
@@ -6,7 +6,7 @@ module Switchman
6
6
  module ClassMethods
7
7
  def quoted_table_name
8
8
  @quoted_table_name ||= {}
9
- @quoted_table_name[Shard.current(shard_category).id] ||= connection.quote_table_name(table_name)
9
+ @quoted_table_name[Shard.current(connection_classes).id] ||= connection.quote_table_name(table_name)
10
10
  end
11
11
  end
12
12
  end
@@ -5,14 +5,12 @@ module Switchman
5
5
  module Persistence
6
6
  # touch reads the id attribute directly, so it's not relative to the current shard
7
7
  def touch(*, **)
8
- shard.activate(self.class.shard_category) { super }
8
+ shard.activate(self.class.connection_classes) { super }
9
9
  end
10
10
 
11
- if ::Rails.version >= '5.2'
12
- def update_columns(*)
13
- shard.activate(self.class.shard_category) { super }
14
- end
11
+ def update_columns(*)
12
+ shard.activate(self.class.connection_classes) { super }
15
13
  end
16
14
  end
17
15
  end
18
- end
16
+ end
@@ -9,22 +9,22 @@ module Switchman
9
9
 
10
10
  option_string = options.sum do |key, value|
11
11
  case key
12
- when :owner
13
- " OWNER = \"#{value}\""
14
- when :template
15
- " TEMPLATE = \"#{value}\""
16
- when :encoding
17
- " ENCODING = '#{value}'"
18
- when :collation
19
- " LC_COLLATE = '#{value}'"
20
- when :ctype
21
- " LC_CTYPE = '#{value}'"
22
- when :tablespace
23
- " TABLESPACE = \"#{value}\""
24
- when :connection_limit
25
- " CONNECTION LIMIT = #{value}"
26
- else
27
- ""
12
+ when :owner
13
+ " OWNER = \"#{value}\""
14
+ when :template
15
+ " TEMPLATE = \"#{value}\""
16
+ when :encoding
17
+ " ENCODING = '#{value}'"
18
+ when :collation
19
+ " LC_COLLATE = '#{value}'"
20
+ when :ctype
21
+ " LC_CTYPE = '#{value}'"
22
+ when :tablespace
23
+ " TABLESPACE = \"#{value}\""
24
+ when :connection_limit
25
+ " CONNECTION LIMIT = #{value}"
26
+ else
27
+ ''
28
28
  end
29
29
  end
30
30
 
@@ -37,14 +37,12 @@ module Switchman
37
37
  end
38
38
 
39
39
  def current_schemas
40
- select_values("SELECT * FROM unnest(current_schemas(false))")
40
+ select_values('SELECT * FROM unnest(current_schemas(false))')
41
41
  end
42
42
 
43
43
  def extract_schema_qualified_name(string)
44
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
45
+ name.instance_variable_set(:@schema, shard.name) if string && !name.schema
48
46
  [name.schema, name.identifier]
49
47
  end
50
48
 
@@ -52,12 +50,12 @@ module Switchman
52
50
  def quoted_scope(name = nil, type: nil)
53
51
  schema, name = extract_schema_qualified_name(name)
54
52
  type = \
55
- case type
56
- when "BASE TABLE"
53
+ case type # rubocop:disable Style/HashLikeCase
54
+ when 'BASE TABLE'
57
55
  "'r','p'"
58
- when "VIEW"
56
+ when 'VIEW'
59
57
  "'v','m'"
60
- when "FOREIGN TABLE"
58
+ when 'FOREIGN TABLE'
61
59
  "'f'"
62
60
  end
63
61
  scope = {}
@@ -67,135 +65,10 @@ module Switchman
67
65
  scope
68
66
  end
69
67
 
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
77
- end
78
-
79
- def view_exists?(name)
80
- name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
81
- return false unless name.identifier
82
- if !name.schema
83
- name.instance_variable_set(:@schema, shard.name)
84
- end
85
-
86
- select_values(<<-SQL, 'SCHEMA').any?
87
- SELECT c.relname
88
- FROM pg_class c
89
- LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
90
- WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view
91
- AND c.relname = '#{name.identifier}'
92
- AND n.nspname = '#{shard.name}'
93
- SQL
94
- end
95
-
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 i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = '#{shard.name}' )
106
- ORDER BY i.relname
107
- SQL
108
-
109
-
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}' )
152
- SQL
153
- end
154
-
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
170
-
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
187
-
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
197
- end
198
- end
68
+ def foreign_keys(table_name)
69
+ super.each do |fk|
70
+ to_table_qualified_name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(fk.to_table)
71
+ fk.to_table = to_table_qualified_name.identifier if to_table_qualified_name.schema == shard.name
199
72
  end
200
73
  end
201
74
 
@@ -208,14 +81,13 @@ module Switchman
208
81
 
209
82
  def quote_table_name(name)
210
83
  return quote_local_table_name(name) if @use_local_table_name
84
+
211
85
  name = ::ActiveRecord::ConnectionAdapters::PostgreSQL::Utils.extract_schema_qualified_name(name.to_s)
212
- if !name.schema
213
- name.instance_variable_set(:@schema, shard.name)
214
- end
86
+ name.instance_variable_set(:@schema, shard.name) unless name.schema
215
87
  name.quoted
216
88
  end
217
89
 
218
- def with_local_table_name(enable = true)
90
+ def with_local_table_name(enable = true) # rubocop:disable Style/OptionalBooleanParameter
219
91
  old_value = @use_local_table_name
220
92
  @use_local_table_name = enable
221
93
  yield
@@ -224,9 +96,9 @@ module Switchman
224
96
  end
225
97
 
226
98
  def add_index_options(_table_name, _column_name, **)
227
- index_name, index_type, index_columns, index_options, algorithm, using = super
228
- algorithm = nil if DatabaseServer.creating_new_shard && algorithm == "CONCURRENTLY"
229
- [index_name, index_type, index_columns, index_options, algorithm, using]
99
+ index, algorithm, if_not_exists = super
100
+ algorithm = nil if DatabaseServer.creating_new_shard && algorithm == 'CONCURRENTLY'
101
+ [index, algorithm, if_not_exists]
230
102
  end
231
103
 
232
104
  def rename_table(table_name, new_name)
@@ -23,4 +23,4 @@ module Switchman
23
23
  end
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -3,31 +3,30 @@
3
3
  module Switchman
4
4
  module ActiveRecord
5
5
  module QueryCache
6
-
7
6
  private
8
7
 
9
8
  def cache_sql(sql, name, binds)
10
9
  # have to include the shard id in the cache key because of switching dbs on the same connection
11
- sql = "#{self.shard.id}::#{sql}"
10
+ sql = "#{shard.id}::#{sql}"
12
11
  @lock.synchronize do
13
12
  result =
14
- if query_cache[sql].key?(binds)
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
- )
27
- query_cache[sql][binds]
28
- else
29
- query_cache[sql][binds] = yield
30
- end
13
+ if query_cache[sql].key?(binds)
14
+ args = {
15
+ sql: sql,
16
+ binds: binds,
17
+ name: name,
18
+ connection_id: object_id,
19
+ cached: true,
20
+ type_casted_binds: -> { type_casted_binds(binds) }
21
+ }
22
+ ::ActiveSupport::Notifications.instrument(
23
+ 'sql.active_record',
24
+ args
25
+ )
26
+ query_cache[sql][binds]
27
+ else
28
+ query_cache[sql][binds] = yield
29
+ end
31
30
  result.dup
32
31
  end
33
32
  end