activerecord 3.0.20 → 3.1.0.beta1

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.

Files changed (122) hide show
  1. data/CHANGELOG +220 -91
  2. data/README.rdoc +3 -3
  3. data/examples/performance.rb +88 -109
  4. data/lib/active_record.rb +6 -2
  5. data/lib/active_record/aggregations.rb +22 -45
  6. data/lib/active_record/associations.rb +264 -991
  7. data/lib/active_record/associations/alias_tracker.rb +85 -0
  8. data/lib/active_record/associations/association.rb +231 -0
  9. data/lib/active_record/associations/association_scope.rb +120 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +40 -60
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +15 -63
  12. data/lib/active_record/associations/builder/association.rb +53 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +63 -0
  16. data/lib/active_record/associations/builder/has_many.rb +65 -0
  17. data/lib/active_record/associations/builder/has_one.rb +63 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +524 -0
  20. data/lib/active_record/associations/collection_proxy.rb +125 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +27 -118
  22. data/lib/active_record/associations/has_many_association.rb +50 -79
  23. data/lib/active_record/associations/has_many_through_association.rb +98 -67
  24. data/lib/active_record/associations/has_one_association.rb +45 -115
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency.rb +215 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +150 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_helper.rb +56 -0
  31. data/lib/active_record/associations/preloader.rb +177 -0
  32. data/lib/active_record/associations/preloader/association.rb +126 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  42. data/lib/active_record/associations/singular_association.rb +55 -0
  43. data/lib/active_record/associations/through_association.rb +80 -0
  44. data/lib/active_record/attribute_methods.rb +19 -5
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +9 -8
  46. data/lib/active_record/attribute_methods/dirty.rb +8 -2
  47. data/lib/active_record/attribute_methods/primary_key.rb +33 -13
  48. data/lib/active_record/attribute_methods/read.rb +17 -17
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -4
  50. data/lib/active_record/attribute_methods/write.rb +2 -1
  51. data/lib/active_record/autosave_association.rb +66 -45
  52. data/lib/active_record/base.rb +445 -273
  53. data/lib/active_record/callbacks.rb +24 -33
  54. data/lib/active_record/coders/yaml_column.rb +41 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +106 -13
  56. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +16 -2
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +12 -11
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -12
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +16 -16
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +61 -22
  61. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +16 -273
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +80 -42
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +44 -25
  64. data/lib/active_record/connection_adapters/column.rb +268 -0
  65. data/lib/active_record/connection_adapters/mysql2_adapter.rb +686 -0
  66. data/lib/active_record/connection_adapters/mysql_adapter.rb +331 -88
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +295 -267
  68. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -7
  69. data/lib/active_record/connection_adapters/sqlite_adapter.rb +108 -26
  70. data/lib/active_record/counter_cache.rb +7 -4
  71. data/lib/active_record/fixtures.rb +174 -192
  72. data/lib/active_record/identity_map.rb +131 -0
  73. data/lib/active_record/locking/optimistic.rb +20 -14
  74. data/lib/active_record/locking/pessimistic.rb +4 -4
  75. data/lib/active_record/log_subscriber.rb +24 -4
  76. data/lib/active_record/migration.rb +265 -144
  77. data/lib/active_record/migration/command_recorder.rb +103 -0
  78. data/lib/active_record/named_scope.rb +68 -25
  79. data/lib/active_record/nested_attributes.rb +58 -15
  80. data/lib/active_record/observer.rb +3 -7
  81. data/lib/active_record/persistence.rb +58 -38
  82. data/lib/active_record/query_cache.rb +25 -3
  83. data/lib/active_record/railtie.rb +21 -12
  84. data/lib/active_record/railties/console_sandbox.rb +6 -0
  85. data/lib/active_record/railties/databases.rake +147 -116
  86. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  87. data/lib/active_record/reflection.rb +176 -44
  88. data/lib/active_record/relation.rb +125 -49
  89. data/lib/active_record/relation/batches.rb +7 -5
  90. data/lib/active_record/relation/calculations.rb +50 -18
  91. data/lib/active_record/relation/finder_methods.rb +47 -26
  92. data/lib/active_record/relation/predicate_builder.rb +24 -21
  93. data/lib/active_record/relation/query_methods.rb +117 -101
  94. data/lib/active_record/relation/spawn_methods.rb +27 -20
  95. data/lib/active_record/result.rb +34 -0
  96. data/lib/active_record/schema.rb +5 -6
  97. data/lib/active_record/schema_dumper.rb +11 -13
  98. data/lib/active_record/serialization.rb +2 -2
  99. data/lib/active_record/serializers/xml_serializer.rb +10 -10
  100. data/lib/active_record/session_store.rb +8 -2
  101. data/lib/active_record/test_case.rb +9 -20
  102. data/lib/active_record/timestamp.rb +21 -9
  103. data/lib/active_record/transactions.rb +16 -15
  104. data/lib/active_record/validations.rb +21 -22
  105. data/lib/active_record/validations/associated.rb +3 -1
  106. data/lib/active_record/validations/uniqueness.rb +48 -58
  107. data/lib/active_record/version.rb +3 -3
  108. data/lib/rails/generators/active_record.rb +6 -0
  109. data/lib/rails/generators/active_record/migration/templates/migration.rb +10 -2
  110. data/lib/rails/generators/active_record/model/model_generator.rb +2 -1
  111. data/lib/rails/generators/active_record/model/templates/migration.rb +6 -5
  112. data/lib/rails/generators/active_record/model/templates/model.rb +2 -0
  113. data/lib/rails/generators/active_record/model/templates/module.rb +2 -0
  114. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  115. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +2 -1
  116. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +2 -2
  117. metadata +106 -77
  118. checksums.yaml +0 -7
  119. data/lib/active_record/association_preload.rb +0 -431
  120. data/lib/active_record/associations/association_collection.rb +0 -572
  121. data/lib/active_record/associations/association_proxy.rb +0 -304
  122. data/lib/active_record/associations/through_association_scope.rb +0 -160
@@ -12,11 +12,15 @@ module ActiveRecord
12
12
 
13
13
  # Truncates a table alias according to the limits of the current adapter.
14
14
  def table_alias_for(table_name)
15
- table_name[0..table_alias_length-1].gsub(/\./, '_')
15
+ table_name[0...table_alias_length].gsub(/\./, '_')
16
16
  end
17
17
 
18
18
  # def tables(name = nil) end
19
19
 
20
+ # Checks to see if the table +table_name+ exists on the database.
21
+ #
22
+ # === Example
23
+ # table_exists?(:developers)
20
24
  def table_exists?(table_name)
21
25
  tables.include?(table_name.to_s)
22
26
  end
@@ -24,7 +28,7 @@ module ActiveRecord
24
28
  # Returns an array of indexes for the given table.
25
29
  # def indexes(table_name, name = nil) end
26
30
 
27
- # Checks to see if an index exists on a table for a given index definition
31
+ # Checks to see if an index exists on a table for a given index definition.
28
32
  #
29
33
  # === Examples
30
34
  # # Check an index exists
@@ -151,10 +155,10 @@ module ActiveRecord
151
155
  #
152
156
  # See also TableDefinition#column for details on how to create columns.
153
157
  def create_table(table_name, options = {})
154
- table_definition = TableDefinition.new(self)
155
- table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
158
+ td = table_definition
159
+ td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
156
160
 
157
- yield table_definition if block_given?
161
+ yield td if block_given?
158
162
 
159
163
  if options[:force] && table_exists?(table_name)
160
164
  drop_table(table_name, options)
@@ -162,7 +166,7 @@ module ActiveRecord
162
166
 
163
167
  create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
164
168
  create_sql << "#{quote_table_name(table_name)} ("
165
- create_sql << table_definition.to_sql
169
+ create_sql << td.to_sql
166
170
  create_sql << ") #{options[:options]}"
167
171
  execute create_sql
168
172
  end
@@ -176,6 +180,13 @@ module ActiveRecord
176
180
  # # Other column alterations here
177
181
  # end
178
182
  #
183
+ # The +options+ hash can include the following keys:
184
+ # [<tt>:bulk</tt>]
185
+ # Set this to true to make this a bulk alter query, such as
186
+ # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
187
+ #
188
+ # Defaults to false.
189
+ #
179
190
  # ===== Examples
180
191
  # ====== Add a column
181
192
  # change_table(:suppliers) do |t|
@@ -224,8 +235,14 @@ module ActiveRecord
224
235
  #
225
236
  # See also Table for details on
226
237
  # all of the various column transformation
227
- def change_table(table_name)
228
- yield Table.new(table_name, self)
238
+ def change_table(table_name, options = {})
239
+ if supports_bulk_alter? && options[:bulk]
240
+ recorder = ActiveRecord::Migration::CommandRecorder.new(self)
241
+ yield Table.new(table_name, recorder)
242
+ bulk_change_table(table_name, recorder.commands)
243
+ else
244
+ yield Table.new(table_name, self)
245
+ end
229
246
  end
230
247
 
231
248
  # Renames a table.
@@ -253,10 +270,7 @@ module ActiveRecord
253
270
  # remove_column(:suppliers, :qualification)
254
271
  # remove_columns(:suppliers, :qualification, :experience)
255
272
  def remove_column(table_name, *column_names)
256
- raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
257
- column_names.flatten.each do |column_name|
258
- execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
259
- end
273
+ columns_for_remove(table_name, *column_names).each {|column_name| execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}" }
260
274
  end
261
275
  alias :remove_columns :remove_column
262
276
 
@@ -269,12 +283,11 @@ module ActiveRecord
269
283
  raise NotImplementedError, "change_column is not implemented"
270
284
  end
271
285
 
272
- # Sets a new default value for a column. If you want to set the default
273
- # value to +NULL+, you are out of luck. You need to
274
- # DatabaseStatements#execute the appropriate SQL statement yourself.
286
+ # Sets a new default value for a column.
275
287
  # ===== Examples
276
288
  # change_column_default(:suppliers, :qualification, 'new')
277
289
  # change_column_default(:accounts, :authorized, 1)
290
+ # change_column_default(:users, :email, nil)
278
291
  def change_column_default(table_name, column_name, default)
279
292
  raise NotImplementedError, "change_column_default is not implemented"
280
293
  end
@@ -327,25 +340,8 @@ module ActiveRecord
327
340
  #
328
341
  # Note: SQLite doesn't support index length
329
342
  def add_index(table_name, column_name, options = {})
330
- column_names = Array.wrap(column_name)
331
- index_name = index_name(table_name, :column => column_names)
332
-
333
- if Hash === options # legacy support, since this param was a string
334
- index_type = options[:unique] ? "UNIQUE" : ""
335
- index_name = options[:name].to_s if options.key?(:name)
336
- else
337
- index_type = options
338
- end
339
-
340
- if index_name.length > index_name_length
341
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
342
- end
343
- if index_name_exists?(table_name, index_name, false)
344
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
345
- end
346
- quoted_column_names = quoted_columns_for_index(column_names, options).join(", ")
347
-
348
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
343
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
344
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})"
349
345
  end
350
346
 
351
347
  # Remove the given index from the table.
@@ -359,11 +355,7 @@ module ActiveRecord
359
355
  # Remove the index named by_branch_party in the accounts table.
360
356
  # remove_index :accounts, :name => :by_branch_party
361
357
  def remove_index(table_name, options = {})
362
- index_name = index_name(table_name, options)
363
- unless index_name_exists?(table_name, index_name, true)
364
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
365
- end
366
- remove_index!(table_name, index_name)
358
+ remove_index!(table_name, index_name_for_remove(table_name, options))
367
359
  end
368
360
 
369
361
  def remove_index!(table_name, index_name) #:nodoc:
@@ -442,12 +434,14 @@ module ActiveRecord
442
434
  end
443
435
  end
444
436
 
445
- def assume_migrated_upto_version(version, migrations_path = ActiveRecord::Migrator.migrations_path)
437
+ def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
438
+ migrations_paths = Array.wrap(migrations_paths)
446
439
  version = version.to_i
447
440
  sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
448
441
 
449
442
  migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
450
- versions = Dir["#{migrations_path}/[0-9]*_*.rb"].map do |filename|
443
+ paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
444
+ versions = Dir[*paths].map do |filename|
451
445
  filename.split('/').last.split('_').first.to_i
452
446
  end
453
447
 
@@ -467,7 +461,7 @@ module ActiveRecord
467
461
  end
468
462
 
469
463
  def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
470
- if native = native_database_types[type]
464
+ if native = native_database_types[type.to_sym]
471
465
  column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
472
466
 
473
467
  if type == :decimal # ignore limit, use precision and scale
@@ -534,6 +528,50 @@ module ActiveRecord
534
528
  def options_include_default?(options)
535
529
  options.include?(:default) && !(options[:null] == false && options[:default].nil?)
536
530
  end
531
+
532
+ def add_index_options(table_name, column_name, options = {})
533
+ column_names = Array.wrap(column_name)
534
+ index_name = index_name(table_name, :column => column_names)
535
+
536
+ if Hash === options # legacy support, since this param was a string
537
+ index_type = options[:unique] ? "UNIQUE" : ""
538
+ index_name = options[:name].to_s if options.key?(:name)
539
+ else
540
+ index_type = options
541
+ end
542
+
543
+ if index_name.length > index_name_length
544
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
545
+ end
546
+ if index_name_exists?(table_name, index_name, false)
547
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
548
+ end
549
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
550
+
551
+ [index_name, index_type, index_columns]
552
+ end
553
+
554
+ def index_name_for_remove(table_name, options = {})
555
+ index_name = index_name(table_name, options)
556
+
557
+ unless index_name_exists?(table_name, index_name, true)
558
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
559
+ end
560
+
561
+ index_name
562
+ end
563
+
564
+ def columns_for_remove(table_name, *column_names)
565
+ column_names = column_names.flatten
566
+
567
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
568
+ column_names.map {|column_name| quote_column_name(column_name) }
569
+ end
570
+
571
+ private
572
+ def table_definition
573
+ TableDefinition.new(self)
574
+ end
537
575
  end
538
576
  end
539
577
  end
@@ -4,6 +4,7 @@ require 'bigdecimal/util'
4
4
  require 'active_support/core_ext/benchmark'
5
5
 
6
6
  # TODO: Autoload these files
7
+ require 'active_record/connection_adapters/column'
7
8
  require 'active_record/connection_adapters/abstract/schema_definitions'
8
9
  require 'active_record/connection_adapters/abstract/schema_statements'
9
10
  require 'active_record/connection_adapters/abstract/database_statements'
@@ -12,6 +13,7 @@ require 'active_record/connection_adapters/abstract/connection_pool'
12
13
  require 'active_record/connection_adapters/abstract/connection_specification'
13
14
  require 'active_record/connection_adapters/abstract/query_cache'
14
15
  require 'active_record/connection_adapters/abstract/database_limits'
16
+ require 'active_record/result'
15
17
 
16
18
  module ActiveRecord
17
19
  module ConnectionAdapters # :nodoc:
@@ -40,61 +42,60 @@ module ActiveRecord
40
42
  @active = nil
41
43
  @connection, @logger = connection, logger
42
44
  @query_cache_enabled = false
43
- @query_cache = {}
45
+ @query_cache = Hash.new { |h,sql| h[sql] = {} }
44
46
  @instrumenter = ActiveSupport::Notifications.instrumenter
45
47
  end
46
48
 
47
- # Returns the human-readable name of the adapter. Use mixed case - one
49
+ # Returns the human-readable name of the adapter. Use mixed case - one
48
50
  # can always use downcase if needed.
49
51
  def adapter_name
50
52
  'Abstract'
51
53
  end
52
54
 
53
- # Does this adapter support migrations? Backend specific, as the
55
+ # Does this adapter support migrations? Backend specific, as the
54
56
  # abstract adapter always returns +false+.
55
57
  def supports_migrations?
56
58
  false
57
59
  end
58
60
 
59
61
  # Can this adapter determine the primary key for tables not attached
60
- # to an Active Record class, such as join tables? Backend specific, as
62
+ # to an Active Record class, such as join tables? Backend specific, as
61
63
  # the abstract adapter always returns +false+.
62
64
  def supports_primary_key?
63
65
  false
64
66
  end
65
67
 
66
- # Does this adapter support using DISTINCT within COUNT? This is +true+
68
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
67
69
  # for all adapters except sqlite.
68
70
  def supports_count_distinct?
69
71
  true
70
72
  end
71
73
 
72
- # Does this adapter support DDL rollbacks in transactions? That is, would
73
- # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
74
- # SQL Server, and others support this. MySQL and others do not.
74
+ # Does this adapter support DDL rollbacks in transactions? That is, would
75
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
76
+ # SQL Server, and others support this. MySQL and others do not.
75
77
  def supports_ddl_transactions?
76
78
  false
77
79
  end
78
80
 
79
- # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
80
- # does not.
81
+ def supports_bulk_alter?
82
+ false
83
+ end
84
+
85
+ # Does this adapter support savepoints? PostgreSQL and MySQL do,
86
+ # SQLite < 3.6.8 does not.
81
87
  def supports_savepoints?
82
88
  false
83
89
  end
84
90
 
85
91
  # Should primary key values be selected from their corresponding
86
- # sequence before the insert statement? If true, next_sequence_value
92
+ # sequence before the insert statement? If true, next_sequence_value
87
93
  # is called before each insert to set the record's primary key.
88
94
  # This is false for all adapters but Firebird.
89
95
  def prefetch_primary_key?(table_name = nil)
90
96
  false
91
97
  end
92
98
 
93
- # Does this adapter restrict the number of ids you can use in a list. Oracle has a limit of 1000.
94
- def ids_in_list_limit
95
- nil
96
- end
97
-
98
99
  # QUOTING ==================================================
99
100
 
100
101
  # Override to return the quoted table name. Defaults to column quoting.
@@ -102,6 +103,12 @@ module ActiveRecord
102
103
  quote_column_name(name)
103
104
  end
104
105
 
106
+ # Returns a bind substitution value given a +column+ and list of current
107
+ # +binds+
108
+ def substitute_at(column, index)
109
+ Arel.sql '?'
110
+ end
111
+
105
112
  # REFERENTIAL INTEGRITY ====================================
106
113
 
107
114
  # Override to turn off referential integrity while executing <tt>&block</tt>.
@@ -140,6 +147,13 @@ module ActiveRecord
140
147
  # this should be overridden by concrete adapters
141
148
  end
142
149
 
150
+ ###
151
+ # Clear any caching the database adapter may be doing, for example
152
+ # clearing the prepared statement cache. This is database specific.
153
+ def clear_cache!
154
+ # this should be overridden by concrete adapters
155
+ end
156
+
143
157
  # Returns true if its required to reload the connection between requests for development mode.
144
158
  # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
145
159
  def requires_reloading?
@@ -189,24 +203,29 @@ module ActiveRecord
189
203
  def release_savepoint
190
204
  end
191
205
 
206
+ def case_sensitive_modifier(node)
207
+ node
208
+ end
209
+
192
210
  def current_savepoint_name
193
211
  "active_record_#{open_transactions}"
194
212
  end
195
213
 
196
214
  protected
197
215
 
198
- def log(sql, name)
199
- name ||= "SQL"
200
- @instrumenter.instrument("sql.active_record",
201
- :sql => sql, :name => name, :connection_id => object_id) do
202
- yield
203
- end
216
+ def log(sql, name = "SQL", binds = [])
217
+ @instrumenter.instrument(
218
+ "sql.active_record",
219
+ :sql => sql,
220
+ :name => name,
221
+ :connection_id => object_id,
222
+ :binds => binds) { yield }
204
223
  rescue Exception => e
205
224
  message = "#{e.class.name}: #{e.message}: #{sql}"
206
225
  @logger.debug message if @logger
207
- ex = translate_exception(e, message)
208
- ex.set_backtrace e.backtrace
209
- raise ex
226
+ exception = translate_exception(e, message)
227
+ exception.set_backtrace e.backtrace
228
+ raise exception
210
229
  end
211
230
 
212
231
  def translate_exception(e, message)
@@ -0,0 +1,268 @@
1
+ module ActiveRecord
2
+ # :stopdoc:
3
+ module ConnectionAdapters
4
+ # An abstract definition of a column in a table.
5
+ class Column
6
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
7
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
8
+
9
+ module Format
10
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
11
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
12
+ end
13
+
14
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
15
+ attr_accessor :primary, :coder
16
+
17
+ alias :encoded? :coder
18
+
19
+ # Instantiates a new column in the table.
20
+ #
21
+ # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
22
+ # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
23
+ # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
24
+ # <tt>company_name varchar(60)</tt>.
25
+ # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
26
+ # +null+ determines if this column allows +NULL+ values.
27
+ def initialize(name, default, sql_type = nil, null = true)
28
+ @name = name
29
+ @sql_type = sql_type
30
+ @null = null
31
+ @limit = extract_limit(sql_type)
32
+ @precision = extract_precision(sql_type)
33
+ @scale = extract_scale(sql_type)
34
+ @type = simplified_type(sql_type)
35
+ @default = extract_default(default)
36
+ @primary = nil
37
+ @coder = nil
38
+ end
39
+
40
+ # Returns +true+ if the column is either of type string or text.
41
+ def text?
42
+ type == :string || type == :text
43
+ end
44
+
45
+ # Returns +true+ if the column is either of type integer, float or decimal.
46
+ def number?
47
+ type == :integer || type == :float || type == :decimal
48
+ end
49
+
50
+ def has_default?
51
+ !default.nil?
52
+ end
53
+
54
+ # Returns the Ruby class that corresponds to the abstract data type.
55
+ def klass
56
+ case type
57
+ when :integer then Fixnum
58
+ when :float then Float
59
+ when :decimal then BigDecimal
60
+ when :datetime, :timestamp, :time then Time
61
+ when :date then Date
62
+ when :text, :string, :binary then String
63
+ when :boolean then Object
64
+ end
65
+ end
66
+
67
+ # Casts value (which is a String) to an appropriate instance.
68
+ def type_cast(value)
69
+ return nil if value.nil?
70
+ return coder.load(value) if encoded?
71
+
72
+ klass = self.class
73
+
74
+ case type
75
+ when :string, :text then value
76
+ when :integer then value.to_i rescue value ? 1 : 0
77
+ when :float then value.to_f
78
+ when :decimal then klass.value_to_decimal(value)
79
+ when :datetime, :timestamp then klass.string_to_time(value)
80
+ when :time then klass.string_to_dummy_time(value)
81
+ when :date then klass.string_to_date(value)
82
+ when :binary then klass.binary_to_string(value)
83
+ when :boolean then klass.value_to_boolean(value)
84
+ else value
85
+ end
86
+ end
87
+
88
+ def type_cast_code(var_name)
89
+ klass = self.class.name
90
+
91
+ case type
92
+ when :string, :text then var_name
93
+ when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
94
+ when :float then "#{var_name}.to_f"
95
+ when :decimal then "#{klass}.value_to_decimal(#{var_name})"
96
+ when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
97
+ when :time then "#{klass}.string_to_dummy_time(#{var_name})"
98
+ when :date then "#{klass}.string_to_date(#{var_name})"
99
+ when :binary then "#{klass}.binary_to_string(#{var_name})"
100
+ when :boolean then "#{klass}.value_to_boolean(#{var_name})"
101
+ else var_name
102
+ end
103
+ end
104
+
105
+ # Returns the human name of the column name.
106
+ #
107
+ # ===== Examples
108
+ # Column.new('sales_stage', ...).human_name # => 'Sales stage'
109
+ def human_name
110
+ Base.human_attribute_name(@name)
111
+ end
112
+
113
+ def extract_default(default)
114
+ type_cast(default)
115
+ end
116
+
117
+ # Used to convert from Strings to BLOBs
118
+ def string_to_binary(value)
119
+ self.class.string_to_binary(value)
120
+ end
121
+
122
+ class << self
123
+ # Used to convert from Strings to BLOBs
124
+ def string_to_binary(value)
125
+ value
126
+ end
127
+
128
+ # Used to convert from BLOBs to Strings
129
+ def binary_to_string(value)
130
+ value
131
+ end
132
+
133
+ def string_to_date(string)
134
+ return string unless string.is_a?(String)
135
+ return nil if string.empty?
136
+
137
+ fast_string_to_date(string) || fallback_string_to_date(string)
138
+ end
139
+
140
+ def string_to_time(string)
141
+ return string unless string.is_a?(String)
142
+ return nil if string.empty?
143
+
144
+ fast_string_to_time(string) || fallback_string_to_time(string)
145
+ end
146
+
147
+ def string_to_dummy_time(string)
148
+ return string unless string.is_a?(String)
149
+ return nil if string.empty?
150
+
151
+ string_to_time "2000-01-01 #{string}"
152
+ end
153
+
154
+ # convert something to a boolean
155
+ def value_to_boolean(value)
156
+ if value.is_a?(String) && value.blank?
157
+ nil
158
+ else
159
+ TRUE_VALUES.include?(value)
160
+ end
161
+ end
162
+
163
+ # convert something to a BigDecimal
164
+ def value_to_decimal(value)
165
+ # Using .class is faster than .is_a? and
166
+ # subclasses of BigDecimal will be handled
167
+ # in the else clause
168
+ if value.class == BigDecimal
169
+ value
170
+ elsif value.respond_to?(:to_d)
171
+ value.to_d
172
+ else
173
+ value.to_s.to_d
174
+ end
175
+ end
176
+
177
+ protected
178
+ # '0.123456' -> 123456
179
+ # '1.123456' -> 123456
180
+ def microseconds(time)
181
+ ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
182
+ end
183
+
184
+ def new_date(year, mon, mday)
185
+ if year && year != 0
186
+ Date.new(year, mon, mday) rescue nil
187
+ end
188
+ end
189
+
190
+ def new_time(year, mon, mday, hour, min, sec, microsec)
191
+ # Treat 0000-00-00 00:00:00 as nil.
192
+ return nil if year.nil? || year == 0
193
+
194
+ Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
195
+ end
196
+
197
+ def fast_string_to_date(string)
198
+ if string =~ Format::ISO_DATE
199
+ new_date $1.to_i, $2.to_i, $3.to_i
200
+ end
201
+ end
202
+
203
+ # Doesn't handle time zones.
204
+ def fast_string_to_time(string)
205
+ if string =~ Format::ISO_DATETIME
206
+ microsec = ($7.to_f * 1_000_000).to_i
207
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
208
+ end
209
+ end
210
+
211
+ def fallback_string_to_date(string)
212
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
213
+ end
214
+
215
+ def fallback_string_to_time(string)
216
+ time_hash = Date._parse(string)
217
+ time_hash[:sec_fraction] = microseconds(time_hash)
218
+
219
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
220
+ end
221
+ end
222
+
223
+ private
224
+ def extract_limit(sql_type)
225
+ $1.to_i if sql_type =~ /\((.*)\)/
226
+ end
227
+
228
+ def extract_precision(sql_type)
229
+ $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
230
+ end
231
+
232
+ def extract_scale(sql_type)
233
+ case sql_type
234
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
235
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
236
+ end
237
+ end
238
+
239
+ def simplified_type(field_type)
240
+ case field_type
241
+ when /int/i
242
+ :integer
243
+ when /float|double/i
244
+ :float
245
+ when /decimal|numeric|number/i
246
+ extract_scale(field_type) == 0 ? :integer : :decimal
247
+ when /datetime/i
248
+ :datetime
249
+ when /timestamp/i
250
+ :timestamp
251
+ when /time/i
252
+ :time
253
+ when /date/i
254
+ :date
255
+ when /clob/i, /text/i
256
+ :text
257
+ when /blob/i, /binary/i
258
+ :binary
259
+ when /char/i, /string/i
260
+ :string
261
+ when /boolean/i
262
+ :boolean
263
+ end
264
+ end
265
+ end
266
+ end
267
+ # :startdoc:
268
+ end