activerecord 3.1.0 → 3.1.1.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

data/CHANGELOG CHANGED
@@ -1,4 +1,39 @@
1
- *Rails 3.1.0 (unreleased)*
1
+ *Rails 3.1.1 (unreleased)*
2
+
3
+ * LRU cache in mysql and sqlite are now per-process caches.
4
+
5
+ * lib/active_record/connection_adapters/mysql_adapter.rb: LRU cache
6
+ keys are per process id.
7
+ * lib/active_record/connection_adapters/sqlite_adapter.rb: ditto
8
+
9
+ [Aaron Patterson]
10
+
11
+ * Database adapters use a statement pool for limiting the number of open
12
+ prepared statments on the database. The limit defaults to 1000, but can
13
+ be adjusted in your database config by changing 'statement_limit'.
14
+
15
+ * Fix clash between using 'preload', 'joins' or 'eager_load' in a default scope and including the
16
+ default scoped model in a nested through association. (GH #2834.) [Jon Leighton]
17
+
18
+ * Ensure we are not comparing a string with a symbol in HasManyAssociation#inverse_updates_counter_cache?.
19
+ Fixes GH #2755, where a counter cache could be decremented twice as far as it was supposed to be.
20
+
21
+ [Jon Leighton]
22
+
23
+ * Don't send any queries to the database when the foreign key of a belongs_to is nil. Fixes
24
+ GH #2828. [Georg Friedrich]
25
+
26
+ * Fixed find_in_batches method to not include order from default_scope. See GH #2832 [Arun Agrawal]
27
+
28
+ * Don't compute table name for abstract classes. Fixes problem with setting the primary key
29
+ in an abstract class. See GH #2791. [Akira Matsuda]
30
+
31
+ * Psych errors with poor yaml formatting are proxied. Fixes GH #2645 and
32
+ GH #2731
33
+
34
+ * Use the LIMIT word with the methods #last and #first. Fixes GH #2783 [Damien Mathieu]
35
+
36
+ *Rails 3.1.0 (August 30, 2011)*
2
37
 
3
38
  * Add a proxy_association method to association proxies, which can be called by association
4
39
  extensions to access information about the association. This replaces proxy_owner etc with
@@ -21,13 +21,6 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
-
25
- activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
26
- $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
27
-
28
- activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
29
- $:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
30
-
31
24
  require 'active_support'
32
25
  require 'active_support/i18n'
33
26
  require 'active_model'
@@ -53,12 +53,18 @@ module ActiveRecord
53
53
  # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
54
54
  quoted_name = connection.quote_table_name(name).downcase
55
55
 
56
- table_joins.map { |join|
57
- # Table names + table aliases
58
- join.left.downcase.scan(
59
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
60
- ).size
61
- }.sum
56
+ counts = table_joins.map do |join|
57
+ if join.is_a?(Arel::Nodes::StringJoin)
58
+ # Table names + table aliases
59
+ join.left.downcase.scan(
60
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
61
+ ).size
62
+ else
63
+ join.left.table_name == name ? 1 : 0
64
+ end
65
+ end
66
+
67
+ counts.sum
62
68
  end
63
69
 
64
70
  def truncate(name)
@@ -20,6 +20,10 @@ module ActiveRecord
20
20
 
21
21
  private
22
22
 
23
+ def find_target?
24
+ !loaded? && foreign_key_present? && klass
25
+ end
26
+
23
27
  def update_counters(record)
24
28
  counter_cache_name = reflection.counter_cache_column
25
29
 
@@ -78,10 +78,14 @@ module ActiveRecord
78
78
  end
79
79
 
80
80
  def find(*args)
81
- if options[:finder_sql]
82
- find_by_scan(*args)
81
+ if block_given?
82
+ load_target.find(*args) { |*block_args| yield(*block_args) }
83
83
  else
84
- scoped.find(*args)
84
+ if options[:finder_sql]
85
+ find_by_scan(*args)
86
+ else
87
+ scoped.find(*args)
88
+ end
85
89
  end
86
90
  end
87
91
 
@@ -78,7 +78,7 @@ module ActiveRecord
78
78
  def method_missing(method, *args, &block)
79
79
  match = DynamicFinderMatch.match(method)
80
80
  if match && match.instantiator?
81
- record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
81
+ send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
82
82
  proxy_association.send :set_owner_attributes, r
83
83
  proxy_association.send :add_to_target, r
84
84
  yield(r) if block_given?
@@ -16,7 +16,7 @@ module ActiveRecord
16
16
  chain[1..-1].each do |reflection|
17
17
  scope = scope.merge(
18
18
  reflection.klass.scoped.with_default_scope.
19
- except(:select, :create_with, :includes)
19
+ except(:select, :create_with, :includes, :preload, :joins, :eager_load)
20
20
  )
21
21
  end
22
22
  scope
@@ -622,6 +622,8 @@ module ActiveRecord #:nodoc:
622
622
 
623
623
  # Computes the table name, (re)sets it internally, and returns it.
624
624
  def reset_table_name #:nodoc:
625
+ return if abstract_class?
626
+
625
627
  self.table_name = compute_table_name
626
628
  end
627
629
 
@@ -421,7 +421,7 @@ module ActiveRecord
421
421
  # can be used as an argument for establish_connection, for easily
422
422
  # re-establishing the connection.
423
423
  def remove_connection(klass)
424
- pool = @connection_pools[klass.name]
424
+ pool = @connection_pools.delete(klass.name)
425
425
  return nil unless pool
426
426
 
427
427
  pool.automatic_reconnect = false
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  end
101
101
 
102
102
  def connection_pool
103
- connection_handler.retrieve_connection_pool(self)
103
+ connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
104
104
  end
105
105
 
106
106
  def retrieve_connection
@@ -2,6 +2,7 @@ require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'active_support/core_ext/kernel/requires'
3
3
  require 'active_support/core_ext/object/blank'
4
4
  require 'set'
5
+ require 'active_record/connection_adapters/statement_pool'
5
6
 
6
7
  gem 'mysql', '~> 2.8.1'
7
8
  require 'mysql'
@@ -184,11 +185,45 @@ module ActiveRecord
184
185
  :boolean => { :name => "tinyint", :limit => 1 }
185
186
  }
186
187
 
188
+ class StatementPool < ConnectionAdapters::StatementPool
189
+ def initialize(connection, max = 1000)
190
+ super
191
+ @cache = Hash.new { |h,pid| h[pid] = {} }
192
+ end
193
+
194
+ def each(&block); cache.each(&block); end
195
+ def key?(key); cache.key?(key); end
196
+ def [](key); cache[key]; end
197
+ def length; cache.length; end
198
+ def delete(key); cache.delete(key); end
199
+
200
+ def []=(sql, key)
201
+ while @max <= cache.size
202
+ cache.shift.last[:stmt].close
203
+ end
204
+ cache[sql] = key
205
+ end
206
+
207
+ def clear
208
+ cache.values.each do |hash|
209
+ hash[:stmt].close
210
+ end
211
+ cache.clear
212
+ end
213
+
214
+ private
215
+ def cache
216
+ @cache[$$]
217
+ end
218
+ end
219
+
187
220
  def initialize(connection, logger, connection_options, config)
188
221
  super(connection, logger)
189
222
  @connection_options, @config = connection_options, config
190
223
  @quoted_column_names, @quoted_table_names = {}, {}
191
224
  @statements = {}
225
+ @statements = StatementPool.new(@connection,
226
+ config.fetch(:statement_limit) { 1000 })
192
227
  @client_encoding = nil
193
228
  connect
194
229
  end
@@ -334,9 +369,6 @@ module ActiveRecord
334
369
 
335
370
  # Clears the prepared statements cache.
336
371
  def clear_cache!
337
- @statements.values.each do |cache|
338
- cache[:stmt].close
339
- end
340
372
  @statements.clear
341
373
  end
342
374
 
@@ -755,7 +787,7 @@ module ActiveRecord
755
787
  def quoted_columns_for_index(column_names, options = {})
756
788
  length = options[:length] if options.is_a?(Hash)
757
789
 
758
- quoted_column_names = case length
790
+ case length
759
791
  when Hash
760
792
  column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
761
793
  when Fixnum
@@ -1,6 +1,7 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'active_support/core_ext/kernel/requires'
3
3
  require 'active_support/core_ext/object/blank'
4
+ require 'active_record/connection_adapters/statement_pool'
4
5
 
5
6
  # Make sure we're using pg high enough for PGResult#values
6
7
  gem 'pg', '~> 0.11'
@@ -247,6 +248,47 @@ module ActiveRecord
247
248
  true
248
249
  end
249
250
 
251
+ class StatementPool < ConnectionAdapters::StatementPool
252
+ def initialize(connection, max)
253
+ super
254
+ @counter = 0
255
+ @cache = Hash.new { |h,pid| h[pid] = {} }
256
+ end
257
+
258
+ def each(&block); cache.each(&block); end
259
+ def key?(key); cache.key?(key); end
260
+ def [](key); cache[key]; end
261
+ def length; cache.length; end
262
+
263
+ def next_key
264
+ "a#{@counter + 1}"
265
+ end
266
+
267
+ def []=(sql, key)
268
+ while @max <= cache.size
269
+ dealloc(cache.shift.last)
270
+ end
271
+ @counter += 1
272
+ cache[sql] = key
273
+ end
274
+
275
+ def clear
276
+ cache.each_value do |stmt_key|
277
+ dealloc stmt_key
278
+ end
279
+ cache.clear
280
+ end
281
+
282
+ private
283
+ def cache
284
+ @cache[$$]
285
+ end
286
+
287
+ def dealloc(key)
288
+ @connection.query "DEALLOCATE #{key}"
289
+ end
290
+ end
291
+
250
292
  # Initializes and connects a PostgreSQL adapter.
251
293
  def initialize(connection, logger, connection_parameters, config)
252
294
  super(connection, logger)
@@ -255,9 +297,10 @@ module ActiveRecord
255
297
  # @local_tz is initialized as nil to avoid warnings when connect tries to use it
256
298
  @local_tz = nil
257
299
  @table_alias_length = nil
258
- @statements = {}
259
300
 
260
301
  connect
302
+ @statements = StatementPool.new @connection,
303
+ config.fetch(:statement_limit) { 1000 }
261
304
 
262
305
  if postgresql_version < 80200
263
306
  raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!"
@@ -272,9 +315,6 @@ module ActiveRecord
272
315
 
273
316
  # Clears the prepared statements cache.
274
317
  def clear_cache!
275
- @statements.each_value do |value|
276
- @connection.query "DEALLOCATE #{value}"
277
- end
278
318
  @statements.clear
279
319
  end
280
320
 
@@ -672,6 +712,7 @@ module ActiveRecord
672
712
 
673
713
  def table_exists?(name)
674
714
  schema, table = extract_schema_and_table(name.to_s)
715
+ return false unless table # Abstract classes is having nil table name
675
716
 
676
717
  binds = [[nil, table.gsub(/(^"|"$)/,'')]]
677
718
  binds << [nil, schema] if schema
@@ -680,7 +721,7 @@ module ActiveRecord
680
721
  SELECT COUNT(*)
681
722
  FROM pg_tables
682
723
  WHERE tablename = $1
683
- #{schema ? "AND schemaname = $2" : ''}
724
+ AND schemaname = #{schema ? "$2" : "ANY (current_schemas(false))"}
684
725
  SQL
685
726
  end
686
727
 
@@ -964,7 +1005,7 @@ module ActiveRecord
964
1005
 
965
1006
  def exec_cache(sql, binds)
966
1007
  unless @statements.key? sql
967
- nextkey = "a#{@statements.length + 1}"
1008
+ nextkey = @statements.next_key
968
1009
  @connection.prepare nextkey, sql
969
1010
  @statements[sql] = nextkey
970
1011
  end
@@ -1,5 +1,7 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
2
  require 'active_support/core_ext/kernel/requires'
3
+ require 'active_record/connection_adapters/statement_pool'
4
+ require 'active_support/core_ext/string/encoding'
3
5
 
4
6
  module ActiveRecord
5
7
  module ConnectionAdapters #:nodoc:
@@ -48,9 +50,45 @@ module ActiveRecord
48
50
  end
49
51
  end
50
52
 
53
+ class StatementPool < ConnectionAdapters::StatementPool
54
+ def initialize(connection, max)
55
+ super
56
+ @cache = Hash.new { |h,pid| h[pid] = {} }
57
+ end
58
+
59
+ def each(&block); cache.each(&block); end
60
+ def key?(key); cache.key?(key); end
61
+ def [](key); cache[key]; end
62
+ def length; cache.length; end
63
+
64
+ def []=(sql, key)
65
+ while @max <= cache.size
66
+ dealloc(cache.shift.last[:stmt])
67
+ end
68
+ cache[sql] = key
69
+ end
70
+
71
+ def clear
72
+ cache.values.each do |hash|
73
+ dealloc hash[:stmt]
74
+ end
75
+ cache.clear
76
+ end
77
+
78
+ private
79
+ def cache
80
+ @cache[$$]
81
+ end
82
+
83
+ def dealloc(stmt)
84
+ stmt.close unless stmt.closed?
85
+ end
86
+ end
87
+
51
88
  def initialize(connection, logger, config)
52
89
  super(connection, logger)
53
- @statements = {}
90
+ @statements = StatementPool.new(@connection,
91
+ config.fetch(:statement_limit) { 1000 })
54
92
  @config = config
55
93
  end
56
94
 
@@ -107,7 +145,6 @@ module ActiveRecord
107
145
 
108
146
  # Clears the prepared statements cache.
109
147
  def clear_cache!
110
- @statements.values.each { |hash| hash[:stmt].close }
111
148
  @statements.clear
112
149
  end
113
150
 
@@ -159,10 +196,25 @@ module ActiveRecord
159
196
  end
160
197
  end
161
198
 
162
- def type_cast(value, column) # :nodoc:
163
- return super unless BigDecimal === value
199
+ if "<3".encoding_aware?
200
+ def type_cast(value, column) # :nodoc:
201
+ return value.to_f if BigDecimal === value
202
+ return super unless String === value
203
+ return super unless column && value
164
204
 
165
- value.to_f
205
+ value = super
206
+ if column.type == :string && value.encoding == Encoding::ASCII_8BIT
207
+ @logger.error "Binary data inserted for `string` type on column `#{column.name}`"
208
+ value.encode! 'utf-8'
209
+ end
210
+ value
211
+ end
212
+ else
213
+ def type_cast(value, column) # :nodoc:
214
+ return super unless BigDecimal === value
215
+
216
+ value.to_f
217
+ end
166
218
  end
167
219
 
168
220
  # DATABASE STATEMENTS ======================================
@@ -0,0 +1,40 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class StatementPool
4
+ include Enumerable
5
+
6
+ def initialize(connection, max = 1000)
7
+ @connection = connection
8
+ @max = max
9
+ end
10
+
11
+ def each
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def key?(key)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ def [](key)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def length
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def []=(sql, key)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def clear
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def delete(key)
36
+ raise NotImplementedError
37
+ end
38
+ end
39
+ end
40
+ end