activerecord 1.15.6 → 2.0.0

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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -54,7 +54,7 @@ module ActiveRecord
54
54
  # [<tt>:temporary</tt>]
55
55
  # Make a temporary table.
56
56
  # [<tt>:force</tt>]
57
- # Set to true or false to drop the table before creating it.
57
+ # Set to true to drop the table before creating it.
58
58
  # Defaults to false.
59
59
  #
60
60
  # ===== Examples
@@ -81,45 +81,45 @@ module ActiveRecord
81
81
  # t.column :supplier_id, :integer
82
82
  # end
83
83
  # generates:
84
- # CREATE TABLE categories_suppliers_join (
84
+ # CREATE TABLE categories_suppliers (
85
85
  # category_id int,
86
86
  # supplier_id int
87
87
  # )
88
88
  #
89
89
  # See also TableDefinition#column for details on how to create columns.
90
- def create_table(name, options = {})
90
+ def create_table(table_name, options = {})
91
91
  table_definition = TableDefinition.new(self)
92
92
  table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
93
93
 
94
94
  yield table_definition
95
95
 
96
96
  if options[:force]
97
- drop_table(name, options) rescue nil
97
+ drop_table(table_name, options) rescue nil
98
98
  end
99
99
 
100
100
  create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
101
- create_sql << "#{name} ("
101
+ create_sql << "#{quote_table_name(table_name)} ("
102
102
  create_sql << table_definition.to_sql
103
103
  create_sql << ") #{options[:options]}"
104
104
  execute create_sql
105
105
  end
106
-
106
+
107
107
  # Renames a table.
108
108
  # ===== Example
109
109
  # rename_table('octopuses', 'octopi')
110
- def rename_table(name, new_name)
110
+ def rename_table(table_name, new_name)
111
111
  raise NotImplementedError, "rename_table is not implemented"
112
112
  end
113
113
 
114
114
  # Drops a table from the database.
115
- def drop_table(name, options = {})
116
- execute "DROP TABLE #{name}"
115
+ def drop_table(table_name, options = {})
116
+ execute "DROP TABLE #{quote_table_name(table_name)}"
117
117
  end
118
118
 
119
119
  # Adds a new column to the named table.
120
120
  # See TableDefinition#column for details of the options you can use.
121
121
  def add_column(table_name, column_name, type, options = {})
122
- add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
122
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
123
123
  add_column_options!(add_column_sql, options)
124
124
  execute(add_column_sql)
125
125
  end
@@ -128,7 +128,7 @@ module ActiveRecord
128
128
  # ===== Examples
129
129
  # remove_column(:suppliers, :qualification)
130
130
  def remove_column(table_name, column_name)
131
- execute "ALTER TABLE #{table_name} DROP #{quote_column_name(column_name)}"
131
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
132
132
  end
133
133
 
134
134
  # Changes the column's definition according to the new options.
@@ -142,7 +142,7 @@ module ActiveRecord
142
142
 
143
143
  # Sets a new default value for a column. If you want to set the default
144
144
  # value to +NULL+, you are out of luck. You need to
145
- # DatabaseStatements#execute the apppropriate SQL statement yourself.
145
+ # DatabaseStatements#execute the appropriate SQL statement yourself.
146
146
  # ===== Examples
147
147
  # change_column_default(:suppliers, :qualification, 'new')
148
148
  # change_column_default(:accounts, :authorized, 1)
@@ -160,13 +160,13 @@ module ActiveRecord
160
160
  # Adds a new index to the table. +column_name+ can be a single Symbol, or
161
161
  # an Array of Symbols.
162
162
  #
163
- # The index will be named after the table and the first column names,
163
+ # The index will be named after the table and the first column name,
164
164
  # unless you pass +:name+ as an option.
165
165
  #
166
166
  # When creating an index on multiple columns, the first column is used as a name
167
167
  # for the index. For example, when you specify an index on two columns
168
168
  # [+:first+, +:last+], the DBMS creates an index for both columns as well as an
169
- # index for the first colum +:first+. Using just the first name for this index
169
+ # index for the first column +:first+. Using just the first name for this index
170
170
  # makes sense, because you will never have to create a singular index with this
171
171
  # name.
172
172
  #
@@ -194,7 +194,7 @@ module ActiveRecord
194
194
  index_type = options
195
195
  end
196
196
  quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
197
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{table_name} (#{quoted_column_names})"
197
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
198
198
  end
199
199
 
200
200
  # Remove the given index from the table.
@@ -234,17 +234,17 @@ module ActiveRecord
234
234
  # The migrations module handles this automatically.
235
235
  def initialize_schema_information
236
236
  begin
237
- execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"
238
- execute "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES(0)"
237
+ execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:integer)})"
238
+ execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(0)"
239
239
  rescue ActiveRecord::StatementInvalid
240
- # Schema has been intialized
240
+ # Schema has been initialized
241
241
  end
242
242
  end
243
243
 
244
244
  def dump_schema_information #:nodoc:
245
245
  begin
246
246
  if (current_schema = ActiveRecord::Migrator.current_version) > 0
247
- return "INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} (version) VALUES (#{current_schema})"
247
+ return "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})"
248
248
  end
249
249
  rescue ActiveRecord::StatementInvalid
250
250
  # No Schema Info
@@ -253,25 +253,28 @@ module ActiveRecord
253
253
 
254
254
 
255
255
  def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
256
- native = native_database_types[type]
257
- column_type_sql = native.is_a?(Hash) ? native[:name] : native
258
- if type == :decimal # ignore limit, use precison and scale
259
- precision ||= native[:precision]
260
- scale ||= native[:scale]
261
- if precision
262
- if scale
263
- column_type_sql << "(#{precision},#{scale})"
256
+ if native = native_database_types[type]
257
+ column_type_sql = native.is_a?(Hash) ? native[:name] : native
258
+ if type == :decimal # ignore limit, use precision and scale
259
+ precision ||= native[:precision]
260
+ scale ||= native[:scale]
261
+ if precision
262
+ if scale
263
+ column_type_sql << "(#{precision},#{scale})"
264
+ else
265
+ column_type_sql << "(#{precision})"
266
+ end
264
267
  else
265
- column_type_sql << "(#{precision})"
268
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" if scale
266
269
  end
270
+ column_type_sql
267
271
  else
268
- raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified" if scale
272
+ limit ||= native[:limit]
273
+ column_type_sql << "(#{limit})" if limit
274
+ column_type_sql
269
275
  end
270
- column_type_sql
271
276
  else
272
- limit ||= native[:limit]
273
- column_type_sql << "(#{limit})" if limit
274
- column_type_sql
277
+ column_type_sql = type
275
278
  end
276
279
  end
277
280
 
@@ -291,7 +294,7 @@ module ActiveRecord
291
294
  # ORDER BY clause for the passed order option.
292
295
  # PostgreSQL overrides this due to its stricter standards compliance.
293
296
  def add_order_by_for_association_limiting!(sql, options)
294
- sql << "ORDER BY #{options[:order]}"
297
+ sql << " ORDER BY #{options[:order]}"
295
298
  end
296
299
 
297
300
  protected
@@ -8,6 +8,7 @@ require 'active_record/connection_adapters/abstract/schema_statements'
8
8
  require 'active_record/connection_adapters/abstract/database_statements'
9
9
  require 'active_record/connection_adapters/abstract/quoting'
10
10
  require 'active_record/connection_adapters/abstract/connection_specification'
11
+ require 'active_record/connection_adapters/abstract/query_cache'
11
12
 
12
13
  module ActiveRecord
13
14
  module ConnectionAdapters # :nodoc:
@@ -22,8 +23,9 @@ module ActiveRecord
22
23
  # SchemaStatements#remove_column are very useful.
23
24
  class AbstractAdapter
24
25
  include Quoting, DatabaseStatements, SchemaStatements
26
+ include QueryCache
25
27
  @@row_even = true
26
-
28
+
27
29
  def initialize(connection, logger = nil) #:nodoc:
28
30
  @connection, @logger = connection, logger
29
31
  @runtime = 0
@@ -41,7 +43,7 @@ module ActiveRecord
41
43
  def supports_migrations?
42
44
  false
43
45
  end
44
-
46
+
45
47
  # Does this adapter support using DISTINCT within COUNT? This is +true+
46
48
  # for all adapters except sqlite.
47
49
  def supports_count_distinct?
@@ -61,6 +63,19 @@ module ActiveRecord
61
63
  rt
62
64
  end
63
65
 
66
+ # QUOTING ==================================================
67
+
68
+ # Override to return the quoted table name if the database needs it
69
+ def quote_table_name(name)
70
+ name
71
+ end
72
+
73
+ # REFERENTIAL INTEGRITY ====================================
74
+
75
+ # Override to turn off referential integrity while executing +&block+
76
+ def disable_referential_integrity(&block)
77
+ yield
78
+ end
64
79
 
65
80
  # CONNECTION MANAGEMENT ====================================
66
81
 
@@ -86,7 +101,7 @@ module ActiveRecord
86
101
  end
87
102
 
88
103
  # Lazily verify this connection, calling +active?+ only if it hasn't
89
- # been called for +timeout+ seconds.
104
+ # been called for +timeout+ seconds.
90
105
  def verify!(timeout)
91
106
  now = Time.now.to_i
92
107
  if (now - @last_verification) > timeout
@@ -94,7 +109,7 @@ module ActiveRecord
94
109
  @last_verification = now
95
110
  end
96
111
  end
97
-
112
+
98
113
  # Provides access to the underlying database connection. Useful for
99
114
  # when you need to call a proprietary method such as postgresql's lo_*
100
115
  # methods
@@ -102,10 +117,17 @@ module ActiveRecord
102
117
  @connection
103
118
  end
104
119
 
120
+ def log_info(sql, name, runtime)
121
+ if @logger && @logger.debug?
122
+ name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
123
+ @logger.debug format_log_entry(name, sql.squeeze(' '))
124
+ end
125
+ end
126
+
105
127
  protected
106
128
  def log(sql, name)
107
129
  if block_given?
108
- if @logger and @logger.level <= Logger::INFO
130
+ if @logger and @logger.debug?
109
131
  result = nil
110
132
  seconds = Benchmark.realtime { result = yield }
111
133
  @runtime += seconds
@@ -120,7 +142,7 @@ module ActiveRecord
120
142
  end
121
143
  rescue Exception => e
122
144
  # Log message and raise exception.
123
- # Set last_verfication to 0, so that connection gets verified
145
+ # Set last_verification to 0, so that connection gets verified
124
146
  # upon reentering the request loop
125
147
  @last_verification = 0
126
148
  message = "#{e.class.name}: #{e.message}: #{sql}"
@@ -128,17 +150,6 @@ module ActiveRecord
128
150
  raise ActiveRecord::StatementInvalid, message
129
151
  end
130
152
 
131
- def log_info(sql, name, runtime)
132
- return unless @logger
133
-
134
- @logger.debug(
135
- format_log_entry(
136
- "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
137
- sql.gsub(/ +/, " ")
138
- )
139
- )
140
- end
141
-
142
153
  def format_log_entry(message, dump = nil)
143
154
  if ActiveRecord::Base.colorize_logging
144
155
  if @@row_even
@@ -33,7 +33,8 @@ module MysqlCompat #:nodoc:
33
33
  end_eval
34
34
  end
35
35
 
36
- unless target.instance_methods.include?('all_hashes')
36
+ unless target.instance_methods.include?('all_hashes') ||
37
+ target.instance_methods.include?(:all_hashes)
37
38
  raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
38
39
  end
39
40
  end
@@ -49,6 +50,11 @@ module ActiveRecord
49
50
  rescue LoadError => cannot_require_mysql
50
51
  # Use the bundled Ruby/MySQL driver if no driver is already in place
51
52
  begin
53
+ ActiveRecord::Base.logger.info(
54
+ "WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. " +
55
+ "Please install the C-based MySQL library instead (gem install mysql)."
56
+ ) if ActiveRecord::Base.logger
57
+
52
58
  require 'active_record/vendor/mysql'
53
59
  rescue LoadError
54
60
  raise cannot_require_mysql
@@ -85,12 +91,18 @@ module ActiveRecord
85
91
 
86
92
  module ConnectionAdapters
87
93
  class MysqlColumn < Column #:nodoc:
88
- TYPES_ALLOWING_EMPTY_STRING_DEFAULT = Set.new([:binary, :string, :text])
89
-
90
- def initialize(name, default, sql_type = nil, null = true)
91
- @original_default = default
92
- super
93
- @default = nil if missing_default_forged_as_empty_string?
94
+ def extract_default(default)
95
+ if type == :binary || type == :text
96
+ if default.blank?
97
+ default
98
+ else
99
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
100
+ end
101
+ elsif missing_default_forged_as_empty_string?(default)
102
+ nil
103
+ else
104
+ super
105
+ end
94
106
  end
95
107
 
96
108
  private
@@ -102,13 +114,13 @@ module ActiveRecord
102
114
 
103
115
  # MySQL misreports NOT NULL column default when none is given.
104
116
  # We can't detect this for columns which may have a legitimate ''
105
- # default (string, text, binary) but we can for others (integer,
106
- # datetime, boolean, and the rest).
117
+ # default (string) but we can for others (integer, datetime, boolean,
118
+ # and the rest).
107
119
  #
108
120
  # Test whether the column has default '', is not null, and is not
109
121
  # a type allowing default ''.
110
- def missing_default_forged_as_empty_string?
111
- !null && @original_default == '' && !TYPES_ALLOWING_EMPTY_STRING_DEFAULT.include?(type)
122
+ def missing_default_forged_as_empty_string?(default)
123
+ type != :string && !null && default == ''
112
124
  end
113
125
  end
114
126
 
@@ -123,6 +135,7 @@ module ActiveRecord
123
135
  # * <tt>:username</tt> -- Defaults to root
124
136
  # * <tt>:password</tt> -- Defaults to nothing
125
137
  # * <tt>:database</tt> -- The name of the database. No default, must be provided.
138
+ # * <tt>:encoding</tt> -- (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection
126
139
  # * <tt>:sslkey</tt> -- Necessary to use MySQL with an SSL connection
127
140
  # * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
128
141
  # * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
@@ -195,6 +208,10 @@ module ActiveRecord
195
208
  "`#{name}`"
196
209
  end
197
210
 
211
+ def quote_table_name(name) #:nodoc:
212
+ quote_column_name(name).gsub('.', '`.`')
213
+ end
214
+
198
215
  def quote_string(string) #:nodoc:
199
216
  @connection.quote(string)
200
217
  end
@@ -202,11 +219,23 @@ module ActiveRecord
202
219
  def quoted_true
203
220
  "1"
204
221
  end
205
-
222
+
206
223
  def quoted_false
207
224
  "0"
208
225
  end
209
226
 
227
+ # REFERENTIAL INTEGRITY ====================================
228
+
229
+ def disable_referential_integrity(&block) #:nodoc:
230
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
231
+
232
+ begin
233
+ update("SET FOREIGN_KEY_CHECKS = 0")
234
+ yield
235
+ ensure
236
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
237
+ end
238
+ end
210
239
 
211
240
  # CONNECTION MANAGEMENT ====================================
212
241
 
@@ -231,7 +260,7 @@ module ActiveRecord
231
260
  disconnect!
232
261
  connect
233
262
  end
234
-
263
+
235
264
  def disconnect!
236
265
  @connection.close rescue nil
237
266
  end
@@ -239,6 +268,15 @@ module ActiveRecord
239
268
 
240
269
  # DATABASE STATEMENTS ======================================
241
270
 
271
+ def select_rows(sql, name = nil)
272
+ @connection.query_with_result = true
273
+ result = execute(sql, name)
274
+ rows = []
275
+ result.each { |row| rows << row }
276
+ result.free
277
+ rows
278
+ end
279
+
242
280
  def execute(sql, name = nil) #:nodoc:
243
281
  log(sql, name) { @connection.query(sql) }
244
282
  rescue ActiveRecord::StatementInvalid => exception
@@ -249,13 +287,13 @@ module ActiveRecord
249
287
  end
250
288
  end
251
289
 
252
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
253
- execute(sql, name = nil)
290
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
291
+ super sql, name
254
292
  id_value || @connection.insert_id
255
293
  end
256
294
 
257
- def update(sql, name = nil) #:nodoc:
258
- execute(sql, name)
295
+ def update_sql(sql, name = nil) #:nodoc:
296
+ super
259
297
  @connection.affected_rows
260
298
  end
261
299
 
@@ -297,10 +335,10 @@ module ActiveRecord
297
335
  else
298
336
  sql = "SHOW TABLES"
299
337
  end
300
-
338
+
301
339
  select_all(sql).inject("") do |structure, table|
302
340
  table.delete('Table_type')
303
- structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
341
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
304
342
  end
305
343
  end
306
344
 
@@ -309,16 +347,37 @@ module ActiveRecord
309
347
  create_database(name)
310
348
  end
311
349
 
312
- def create_database(name) #:nodoc:
313
- execute "CREATE DATABASE `#{name}`"
350
+ # Create a new MySQL database with optional :charset and :collation.
351
+ # Charset defaults to utf8.
352
+ #
353
+ # Example:
354
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
355
+ # create_database 'matt_development'
356
+ # create_database 'matt_development', :charset => :big5
357
+ def create_database(name, options = {})
358
+ if options[:collation]
359
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
360
+ else
361
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
362
+ end
314
363
  end
315
-
364
+
316
365
  def drop_database(name) #:nodoc:
317
366
  execute "DROP DATABASE IF EXISTS `#{name}`"
318
367
  end
319
368
 
320
369
  def current_database
321
- select_one("SELECT DATABASE() as db")["db"]
370
+ select_value 'SELECT DATABASE() as db'
371
+ end
372
+
373
+ # Returns the database character set.
374
+ def charset
375
+ show_variable 'character_set_database'
376
+ end
377
+
378
+ # Returns the database collation strategy.
379
+ def collation
380
+ show_variable 'collation_database'
322
381
  end
323
382
 
324
383
  def tables(name = nil) #:nodoc:
@@ -327,10 +386,14 @@ module ActiveRecord
327
386
  tables
328
387
  end
329
388
 
389
+ def drop_table(table_name, options = {})
390
+ super(table_name, options)
391
+ end
392
+
330
393
  def indexes(table_name, name = nil)#:nodoc:
331
394
  indexes = []
332
395
  current_index = nil
333
- execute("SHOW KEYS FROM #{table_name}", name).each do |row|
396
+ execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
334
397
  if current_index != row[2]
335
398
  next if row[2] == "PRIMARY" # skip the primary key
336
399
  current_index = row[2]
@@ -343,42 +406,61 @@ module ActiveRecord
343
406
  end
344
407
 
345
408
  def columns(table_name, name = nil)#:nodoc:
346
- sql = "SHOW FIELDS FROM #{table_name}"
409
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
347
410
  columns = []
348
411
  execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
349
412
  columns
350
413
  end
351
414
 
352
- def create_table(name, options = {}) #:nodoc:
353
- super(name, {:options => "ENGINE=InnoDB"}.merge(options))
415
+ def create_table(table_name, options = {}) #:nodoc:
416
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
417
+ end
418
+
419
+ def rename_table(table_name, new_name)
420
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
354
421
  end
355
-
356
- def rename_table(name, new_name)
357
- execute "RENAME TABLE #{name} TO #{new_name}"
358
- end
359
422
 
360
423
  def change_column_default(table_name, column_name, default) #:nodoc:
361
- current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
424
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
362
425
 
363
- execute("ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{current_type} DEFAULT #{quote(default)}")
426
+ execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
364
427
  end
365
428
 
366
429
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
367
430
  unless options_include_default?(options)
368
- options[:default] = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Default"]
431
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
432
+ options[:default] = column.default
433
+ else
434
+ raise "No such column: #{table_name}.#{column_name}"
435
+ end
369
436
  end
370
437
 
371
- change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
438
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
372
439
  add_column_options!(change_column_sql, options)
373
440
  execute(change_column_sql)
374
441
  end
375
442
 
376
443
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
377
- current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
378
- execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
444
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
445
+ execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
379
446
  end
380
447
 
381
448
 
449
+ # SHOW VARIABLES LIKE 'name'
450
+ def show_variable(name)
451
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
452
+ variables.first['Value'] unless variables.empty?
453
+ end
454
+
455
+ # Returns a table's primary key and belonging sequence.
456
+ def pk_and_sequence_for(table) #:nodoc:
457
+ keys = []
458
+ execute("describe #{quote_table_name(table)}").each_hash do |h|
459
+ keys << h["Field"]if h["Key"] == "PRI"
460
+ end
461
+ keys.length == 1 ? [keys.first, nil] : nil
462
+ end
463
+
382
464
  private
383
465
  def connect
384
466
  encoding = @config[:encoding]