activerecord 2.2.3 → 2.3.2

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 (120) hide show
  1. data/CHANGELOG +438 -396
  2. data/Rakefile +4 -2
  3. data/lib/active_record.rb +46 -43
  4. data/lib/active_record/association_preload.rb +34 -19
  5. data/lib/active_record/associations.rb +193 -251
  6. data/lib/active_record/associations/association_collection.rb +38 -21
  7. data/lib/active_record/associations/association_proxy.rb +11 -4
  8. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +2 -2
  9. data/lib/active_record/associations/has_many_association.rb +2 -2
  10. data/lib/active_record/associations/has_many_through_association.rb +8 -8
  11. data/lib/active_record/associations/has_one_association.rb +11 -2
  12. data/lib/active_record/attribute_methods.rb +1 -0
  13. data/lib/active_record/autosave_association.rb +349 -0
  14. data/lib/active_record/base.rb +292 -106
  15. data/lib/active_record/batches.rb +73 -0
  16. data/lib/active_record/calculations.rb +34 -16
  17. data/lib/active_record/callbacks.rb +37 -8
  18. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +16 -0
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -0
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +103 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -6
  22. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +28 -25
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +29 -5
  24. data/lib/active_record/connection_adapters/mysql_adapter.rb +50 -21
  25. data/lib/active_record/connection_adapters/postgresql_adapter.rb +26 -41
  26. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  27. data/lib/active_record/connection_adapters/sqlite_adapter.rb +41 -21
  28. data/lib/active_record/dirty.rb +1 -1
  29. data/lib/active_record/dynamic_scope_match.rb +25 -0
  30. data/lib/active_record/fixtures.rb +193 -198
  31. data/lib/active_record/locale/en.yml +1 -1
  32. data/lib/active_record/locking/optimistic.rb +33 -0
  33. data/lib/active_record/migration.rb +8 -2
  34. data/lib/active_record/named_scope.rb +13 -6
  35. data/lib/active_record/nested_attributes.rb +329 -0
  36. data/lib/active_record/query_cache.rb +25 -13
  37. data/lib/active_record/reflection.rb +6 -1
  38. data/lib/active_record/schema_dumper.rb +2 -0
  39. data/lib/active_record/serialization.rb +3 -1
  40. data/lib/active_record/serializers/json_serializer.rb +19 -0
  41. data/lib/active_record/serializers/xml_serializer.rb +28 -13
  42. data/lib/active_record/session_store.rb +318 -0
  43. data/lib/active_record/test_case.rb +15 -9
  44. data/lib/active_record/timestamp.rb +2 -2
  45. data/lib/active_record/transactions.rb +58 -8
  46. data/lib/active_record/validations.rb +29 -24
  47. data/lib/active_record/version.rb +2 -2
  48. data/test/cases/ar_schema_test.rb +0 -1
  49. data/test/cases/associations/belongs_to_associations_test.rb +35 -131
  50. data/test/cases/associations/cascaded_eager_loading_test.rb +8 -0
  51. data/test/cases/associations/eager_load_nested_include_test.rb +29 -0
  52. data/test/cases/associations/eager_test.rb +137 -7
  53. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +45 -7
  54. data/test/cases/associations/has_many_associations_test.rb +110 -149
  55. data/test/cases/associations/has_many_through_associations_test.rb +39 -7
  56. data/test/cases/associations/has_one_associations_test.rb +39 -92
  57. data/test/cases/associations/has_one_through_associations_test.rb +34 -3
  58. data/test/cases/associations/inner_join_association_test.rb +0 -5
  59. data/test/cases/associations/join_model_test.rb +5 -7
  60. data/test/cases/attribute_methods_test.rb +13 -1
  61. data/test/cases/autosave_association_test.rb +901 -0
  62. data/test/cases/base_test.rb +41 -21
  63. data/test/cases/batches_test.rb +61 -0
  64. data/test/cases/calculations_test.rb +37 -17
  65. data/test/cases/callbacks_test.rb +43 -5
  66. data/test/cases/connection_pool_test.rb +25 -0
  67. data/test/cases/copy_table_test_sqlite.rb +11 -0
  68. data/test/cases/datatype_test_postgresql.rb +1 -0
  69. data/test/cases/defaults_test.rb +37 -26
  70. data/test/cases/dirty_test.rb +26 -2
  71. data/test/cases/finder_test.rb +79 -44
  72. data/test/cases/fixtures_test.rb +15 -19
  73. data/test/cases/helper.rb +26 -19
  74. data/test/cases/inheritance_test.rb +2 -2
  75. data/test/cases/json_serialization_test.rb +1 -1
  76. data/test/cases/locking_test.rb +23 -5
  77. data/test/cases/method_scoping_test.rb +126 -3
  78. data/test/cases/migration_test.rb +253 -237
  79. data/test/cases/named_scope_test.rb +73 -3
  80. data/test/cases/nested_attributes_test.rb +509 -0
  81. data/test/cases/query_cache_test.rb +0 -4
  82. data/test/cases/reflection_test.rb +13 -3
  83. data/test/cases/reload_models_test.rb +3 -1
  84. data/test/cases/repair_helper.rb +50 -0
  85. data/test/cases/schema_dumper_test.rb +0 -1
  86. data/test/cases/transactions_test.rb +177 -12
  87. data/test/cases/validations_i18n_test.rb +288 -294
  88. data/test/cases/validations_test.rb +230 -180
  89. data/test/cases/xml_serialization_test.rb +19 -1
  90. data/test/fixtures/fixture_database.sqlite3 +0 -0
  91. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  92. data/test/fixtures/member_types.yml +6 -0
  93. data/test/fixtures/members.yml +3 -1
  94. data/test/fixtures/people.yml +10 -1
  95. data/test/fixtures/toys.yml +4 -0
  96. data/test/models/author.rb +1 -2
  97. data/test/models/bird.rb +3 -0
  98. data/test/models/category.rb +1 -0
  99. data/test/models/company.rb +3 -0
  100. data/test/models/developer.rb +12 -0
  101. data/test/models/event.rb +3 -0
  102. data/test/models/member.rb +1 -0
  103. data/test/models/member_detail.rb +1 -0
  104. data/test/models/member_type.rb +3 -0
  105. data/test/models/owner.rb +2 -1
  106. data/test/models/parrot.rb +2 -0
  107. data/test/models/person.rb +6 -0
  108. data/test/models/pet.rb +2 -1
  109. data/test/models/pirate.rb +55 -1
  110. data/test/models/post.rb +6 -0
  111. data/test/models/project.rb +1 -0
  112. data/test/models/reply.rb +6 -0
  113. data/test/models/ship.rb +8 -1
  114. data/test/models/ship_part.rb +5 -0
  115. data/test/models/topic.rb +13 -1
  116. data/test/models/toy.rb +4 -0
  117. data/test/schema/schema.rb +35 -2
  118. metadata +70 -9
  119. data/test/fixtures/fixture_database.sqlite +0 -0
  120. data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -14,12 +14,12 @@ module ActiveRecord
14
14
  def dirties_query_cache(base, *method_names)
15
15
  method_names.each do |method_name|
16
16
  base.class_eval <<-end_code, __FILE__, __LINE__
17
- def #{method_name}_with_query_dirty(*args)
18
- clear_query_cache if @query_cache_enabled
19
- #{method_name}_without_query_dirty(*args)
20
- end
21
-
22
- alias_method_chain :#{method_name}, :query_dirty
17
+ def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
18
+ clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
19
+ #{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
20
+ end # end
21
+ #
22
+ alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
23
23
  end_code
24
24
  end
25
25
  end
@@ -8,6 +8,7 @@ module ActiveRecord
8
8
  # An abstract definition of a column in a table.
9
9
  class Column
10
10
  TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
11
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
11
12
 
12
13
  module Format
13
14
  ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
@@ -32,10 +33,12 @@ module ActiveRecord
32
33
  @primary = nil
33
34
  end
34
35
 
36
+ # Returns +true+ if the column is either of type string or text.
35
37
  def text?
36
38
  type == :string || type == :text
37
39
  end
38
40
 
41
+ # Returns +true+ if the column is either of type integer, float or decimal.
39
42
  def number?
40
43
  type == :integer || type == :float || type == :decimal
41
44
  end
@@ -295,7 +298,7 @@ module ActiveRecord
295
298
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
296
299
  # end
297
300
  # end
298
- #
301
+ #
299
302
  # def self.down
300
303
  # ...
301
304
  # end
@@ -474,12 +477,12 @@ module ActiveRecord
474
477
 
475
478
  %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
476
479
  class_eval <<-EOV
477
- def #{column_type}(*args)
478
- options = args.extract_options!
479
- column_names = args
480
-
481
- column_names.each { |name| column(name, '#{column_type}', options) }
482
- end
480
+ def #{column_type}(*args) # def string(*args)
481
+ options = args.extract_options! # options = args.extract_options!
482
+ column_names = args # column_names = args
483
+ #
484
+ column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) }
485
+ end # end
483
486
  EOV
484
487
  end
485
488
 
@@ -674,24 +677,24 @@ module ActiveRecord
674
677
  # t.string(:goat, :sheep)
675
678
  %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
676
679
  class_eval <<-EOV
677
- def #{column_type}(*args)
678
- options = args.extract_options!
679
- column_names = args
680
-
681
- column_names.each do |name|
682
- column = ColumnDefinition.new(@base, name, '#{column_type}')
683
- if options[:limit]
684
- column.limit = options[:limit]
685
- elsif native['#{column_type}'.to_sym].is_a?(Hash)
686
- column.limit = native['#{column_type}'.to_sym][:limit]
687
- end
688
- column.precision = options[:precision]
689
- column.scale = options[:scale]
690
- column.default = options[:default]
691
- column.null = options[:null]
692
- @base.add_column(@table_name, name, column.sql_type, options)
693
- end
694
- end
680
+ def #{column_type}(*args) # def string(*args)
681
+ options = args.extract_options! # options = args.extract_options!
682
+ column_names = args # column_names = args
683
+ #
684
+ column_names.each do |name| # column_names.each do |name|
685
+ column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
686
+ if options[:limit] # if options[:limit]
687
+ column.limit = options[:limit] # column.limit = options[:limit]
688
+ elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
689
+ column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
690
+ end # end
691
+ column.precision = options[:precision] # column.precision = options[:precision]
692
+ column.scale = options[:scale] # column.scale = options[:scale]
693
+ column.default = options[:default] # column.default = options[:default]
694
+ column.null = options[:null] # column.null = options[:null]
695
+ @base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
696
+ end # end
697
+ end # end
695
698
  EOV
696
699
  end
697
700
 
@@ -3,6 +3,7 @@ require 'date'
3
3
  require 'bigdecimal'
4
4
  require 'bigdecimal/util'
5
5
 
6
+ # TODO: Autoload these files
6
7
  require 'active_record/connection_adapters/abstract/schema_definitions'
7
8
  require 'active_record/connection_adapters/abstract/schema_statements'
8
9
  require 'active_record/connection_adapters/abstract/database_statements'
@@ -65,6 +66,12 @@ module ActiveRecord
65
66
  def supports_ddl_transactions?
66
67
  false
67
68
  end
69
+
70
+ # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
71
+ # does not.
72
+ def supports_savepoints?
73
+ false
74
+ end
68
75
 
69
76
  # Should primary key values be selected from their corresponding
70
77
  # sequence before the insert statement? If true, next_sequence_value
@@ -159,9 +166,26 @@ module ActiveRecord
159
166
  @open_transactions -= 1
160
167
  end
161
168
 
162
- def log_info(sql, name, seconds)
169
+ def transaction_joinable=(joinable)
170
+ @transaction_joinable = joinable
171
+ end
172
+
173
+ def create_savepoint
174
+ end
175
+
176
+ def rollback_to_savepoint
177
+ end
178
+
179
+ def release_savepoint
180
+ end
181
+
182
+ def current_savepoint_name
183
+ "active_record_#{open_transactions}"
184
+ end
185
+
186
+ def log_info(sql, name, ms)
163
187
  if @logger && @logger.debug?
164
- name = "#{name.nil? ? "SQL" : name} (#{sprintf("%.1f", seconds * 1000)}ms)"
188
+ name = '%s (%.1fms)' % [name || 'SQL', ms]
165
189
  @logger.debug(format_log_entry(name, sql.squeeze(' ')))
166
190
  end
167
191
  end
@@ -170,9 +194,9 @@ module ActiveRecord
170
194
  def log(sql, name)
171
195
  if block_given?
172
196
  result = nil
173
- seconds = Benchmark.realtime { result = yield }
174
- @runtime += seconds
175
- log_info(sql, name, seconds)
197
+ ms = Benchmark.ms { result = yield }
198
+ @runtime += ms
199
+ log_info(sql, name, ms)
176
200
  result
177
201
  else
178
202
  log_info(sql, name, 0)
@@ -13,23 +13,25 @@ module MysqlCompat #:nodoc:
13
13
  # C driver >= 2.7 returns null values in each_hash
14
14
  if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
15
15
  target.class_eval <<-'end_eval'
16
- def all_hashes
17
- rows = []
18
- each_hash { |row| rows << row }
19
- rows
20
- end
16
+ def all_hashes # def all_hashes
17
+ rows = [] # rows = []
18
+ each_hash { |row| rows << row } # each_hash { |row| rows << row }
19
+ rows # rows
20
+ end # end
21
21
  end_eval
22
22
 
23
23
  # adapters before 2.7 don't have a version constant
24
24
  # and don't return null values in each_hash
25
25
  else
26
26
  target.class_eval <<-'end_eval'
27
- def all_hashes
28
- rows = []
29
- all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
30
- each_hash { |row| rows << all_fields.dup.update(row) }
31
- rows
32
- end
27
+ def all_hashes # def all_hashes
28
+ rows = [] # rows = []
29
+ all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
30
+ fields[f.name] = nil; fields # fields[f.name] = nil; fields
31
+ } # }
32
+ each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
33
+ rows # rows
34
+ end # end
33
35
  end_eval
34
36
  end
35
37
 
@@ -157,13 +159,16 @@ module ActiveRecord
157
159
  # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
158
160
  # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
159
161
  #
160
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
161
- # as boolean. If you wish to disable this emulation (which was the default
162
- # behavior in versions 0.13.1 and earlier) you can add the following line
163
- # to your environment.rb file:
164
- #
165
- # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
166
162
  class MysqlAdapter < AbstractAdapter
163
+
164
+ ##
165
+ # :singleton-method:
166
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
167
+ # as boolean. If you wish to disable this emulation (which was the default
168
+ # behavior in versions 0.13.1 and earlier) you can add the following line
169
+ # to your environment.rb file:
170
+ #
171
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
167
172
  cattr_accessor :emulate_booleans
168
173
  self.emulate_booleans = true
169
174
 
@@ -206,6 +211,10 @@ module ActiveRecord
206
211
  def supports_migrations? #:nodoc:
207
212
  true
208
213
  end
214
+
215
+ def supports_savepoints? #:nodoc:
216
+ true
217
+ end
209
218
 
210
219
  def native_database_types #:nodoc:
211
220
  NATIVE_DATABASE_TYPES
@@ -306,6 +315,7 @@ module ActiveRecord
306
315
  rows
307
316
  end
308
317
 
318
+ # Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
309
319
  def execute(sql, name = nil) #:nodoc:
310
320
  log(sql, name) { @connection.query(sql) }
311
321
  rescue ActiveRecord::StatementInvalid => exception
@@ -344,6 +354,17 @@ module ActiveRecord
344
354
  # Transactions aren't supported
345
355
  end
346
356
 
357
+ def create_savepoint
358
+ execute("SAVEPOINT #{current_savepoint_name}")
359
+ end
360
+
361
+ def rollback_to_savepoint
362
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
363
+ end
364
+
365
+ def release_savepoint
366
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
367
+ end
347
368
 
348
369
  def add_limit_offset!(sql, options) #:nodoc:
349
370
  if limit = options[:limit]
@@ -412,7 +433,9 @@ module ActiveRecord
412
433
 
413
434
  def tables(name = nil) #:nodoc:
414
435
  tables = []
415
- execute("SHOW TABLES", name).each { |field| tables << field[0] }
436
+ result = execute("SHOW TABLES", name)
437
+ result.each { |field| tables << field[0] }
438
+ result.free
416
439
  tables
417
440
  end
418
441
 
@@ -423,7 +446,8 @@ module ActiveRecord
423
446
  def indexes(table_name, name = nil)#:nodoc:
424
447
  indexes = []
425
448
  current_index = nil
426
- execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
449
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
450
+ result.each do |row|
427
451
  if current_index != row[2]
428
452
  next if row[2] == "PRIMARY" # skip the primary key
429
453
  current_index = row[2]
@@ -432,13 +456,16 @@ module ActiveRecord
432
456
 
433
457
  indexes.last.columns << row[4]
434
458
  end
459
+ result.free
435
460
  indexes
436
461
  end
437
462
 
438
463
  def columns(table_name, name = nil)#:nodoc:
439
464
  sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
440
465
  columns = []
441
- execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
466
+ result = execute(sql, name)
467
+ result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
468
+ result.free
442
469
  columns
443
470
  end
444
471
 
@@ -519,9 +546,11 @@ module ActiveRecord
519
546
  # Returns a table's primary key and belonging sequence.
520
547
  def pk_and_sequence_for(table) #:nodoc:
521
548
  keys = []
522
- execute("describe #{quote_table_name(table)}").each_hash do |h|
549
+ result = execute("describe #{quote_table_name(table)}")
550
+ result.each_hash do |h|
523
551
  keys << h["Field"]if h["Key"] == "PRI"
524
552
  end
553
+ result.free
525
554
  keys.length == 1 ? [keys.first, nil] : nil
526
555
  end
527
556
 
@@ -272,6 +272,10 @@ module ActiveRecord
272
272
  def supports_ddl_transactions?
273
273
  true
274
274
  end
275
+
276
+ def supports_savepoints?
277
+ true
278
+ end
275
279
 
276
280
  # Returns the configured supported identifier length supported by PostgreSQL,
277
281
  # or report the default of 63 on PostgreSQL 7.x.
@@ -528,45 +532,26 @@ module ActiveRecord
528
532
  def rollback_db_transaction
529
533
  execute "ROLLBACK"
530
534
  end
535
+
536
+ if defined?(PGconn::PQTRANS_IDLE)
537
+ # The ruby-pg driver supports inspecting the transaction status,
538
+ # while the ruby-postgres driver does not.
539
+ def outside_transaction?
540
+ @connection.transaction_status == PGconn::PQTRANS_IDLE
541
+ end
542
+ end
531
543
 
532
- # ruby-pg defines Ruby constants for transaction status,
533
- # ruby-postgres does not.
534
- PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0
535
-
536
- # Check whether a transaction is active.
537
- def transaction_active?
538
- @connection.transaction_status != PQTRANS_IDLE
544
+ def create_savepoint
545
+ execute("SAVEPOINT #{current_savepoint_name}")
539
546
  end
540
547
 
541
- # Wrap a block in a transaction. Returns result of block.
542
- def transaction(start_db_transaction = true)
543
- transaction_open = false
544
- begin
545
- if block_given?
546
- if start_db_transaction
547
- begin_db_transaction
548
- transaction_open = true
549
- end
550
- yield
551
- end
552
- rescue Exception => database_transaction_rollback
553
- if transaction_open && transaction_active?
554
- transaction_open = false
555
- rollback_db_transaction
556
- end
557
- raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
558
- end
559
- ensure
560
- if transaction_open && transaction_active?
561
- begin
562
- commit_db_transaction
563
- rescue Exception => database_transaction_rollback
564
- rollback_db_transaction
565
- raise
566
- end
567
- end
548
+ def rollback_to_savepoint
549
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
568
550
  end
569
551
 
552
+ def release_savepoint
553
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
554
+ end
570
555
 
571
556
  # SCHEMA STATEMENTS ========================================
572
557
 
@@ -950,13 +935,13 @@ module ActiveRecord
950
935
  # should know about this but can't detect it there, so deal with it here.
951
936
  money_precision = (postgresql_version >= 80300) ? 19 : 10
952
937
  PostgreSQLColumn.module_eval(<<-end_eval)
953
- def extract_precision(sql_type)
954
- if sql_type =~ /^money$/
955
- #{money_precision}
956
- else
957
- super
958
- end
959
- end
938
+ def extract_precision(sql_type) # def extract_precision(sql_type)
939
+ if sql_type =~ /^money$/ # if sql_type =~ /^money$/
940
+ #{money_precision} # 19
941
+ else # else
942
+ super # super
943
+ end # end
944
+ end # end
960
945
  end_eval
961
946
 
962
947
  configure_connection
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
 
19
19
  db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
20
20
 
21
- ConnectionAdapters::SQLite3Adapter.new(db, logger)
21
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
22
22
  end
23
23
  end
24
24
 
@@ -17,9 +17,9 @@ module ActiveRecord
17
17
 
18
18
  # "Downgrade" deprecated sqlite API
19
19
  if SQLite.const_defined?(:Version)
20
- ConnectionAdapters::SQLite2Adapter.new(db, logger)
20
+ ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
21
21
  else
22
- ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
22
+ ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger, config)
23
23
  end
24
24
  end
25
25
  end
@@ -72,10 +72,31 @@ module ActiveRecord
72
72
  #
73
73
  # * <tt>:database</tt> - Path to the database file.
74
74
  class SQLiteAdapter < AbstractAdapter
75
+ class Version
76
+ include Comparable
77
+
78
+ def initialize(version_string)
79
+ @version = version_string.split('.').map(&:to_i)
80
+ end
81
+
82
+ def <=>(version_string)
83
+ @version <=> version_string.split('.').map(&:to_i)
84
+ end
85
+ end
86
+
87
+ def initialize(connection, logger, config)
88
+ super(connection, logger)
89
+ @config = config
90
+ end
91
+
75
92
  def adapter_name #:nodoc:
76
93
  'SQLite'
77
94
  end
78
95
 
96
+ def supports_ddl_transactions?
97
+ sqlite_version >= '2.0.0'
98
+ end
99
+
79
100
  def supports_migrations? #:nodoc:
80
101
  true
81
102
  end
@@ -83,6 +104,10 @@ module ActiveRecord
83
104
  def requires_reloading?
84
105
  true
85
106
  end
107
+
108
+ def supports_add_column?
109
+ sqlite_version >= '3.1.6'
110
+ end
86
111
 
87
112
  def disconnect!
88
113
  super
@@ -164,7 +189,6 @@ module ActiveRecord
164
189
  catch_schema_changes { @connection.rollback }
165
190
  end
166
191
 
167
-
168
192
  # SELECT ... FOR UPDATE is redundant since the table is locked.
169
193
  def add_lock!(sql, options) #:nodoc:
170
194
  sql
@@ -213,14 +237,20 @@ module ActiveRecord
213
237
  execute "ALTER TABLE #{name} RENAME TO #{new_name}"
214
238
  end
215
239
 
240
+ # See: http://www.sqlite.org/lang_altertable.html
241
+ # SQLite has an additional restriction on the ALTER TABLE statement
242
+ def valid_alter_table_options( type, options)
243
+ type.to_sym != :primary_key
244
+ end
245
+
216
246
  def add_column(table_name, column_name, type, options = {}) #:nodoc:
217
- if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
218
- raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
247
+ if supports_add_column? && valid_alter_table_options( type, options )
248
+ super(table_name, column_name, type, options)
249
+ else
250
+ alter_table(table_name) do |definition|
251
+ definition.column(column_name, type, options)
252
+ end
219
253
  end
220
-
221
- super(table_name, column_name, type, options)
222
- # See last paragraph on http://www.sqlite.org/lang_altertable.html
223
- execute "VACUUM"
224
254
  end
225
255
 
226
256
  def remove_column(table_name, *column_names) #:nodoc:
@@ -306,7 +336,7 @@ module ActiveRecord
306
336
  end
307
337
 
308
338
  def copy_table(from, to, options = {}) #:nodoc:
309
- options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
339
+ options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
310
340
  create_table(to, options) do |definition|
311
341
  @definition = definition
312
342
  columns(from).each do |column|
@@ -380,7 +410,7 @@ module ActiveRecord
380
410
  end
381
411
 
382
412
  def sqlite_version
383
- @sqlite_version ||= select_value('select sqlite_version(*)')
413
+ @sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
384
414
  end
385
415
 
386
416
  def default_primary_key_type
@@ -393,19 +423,9 @@ module ActiveRecord
393
423
  end
394
424
 
395
425
  class SQLite2Adapter < SQLiteAdapter # :nodoc:
396
- def supports_count_distinct? #:nodoc:
397
- false
398
- end
399
-
400
426
  def rename_table(name, new_name)
401
427
  move_table(name, new_name)
402
428
  end
403
-
404
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
405
- alter_table(table_name) do |definition|
406
- definition.column(column_name, type, options)
407
- end
408
- end
409
429
  end
410
430
 
411
431
  class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc: