activerecord 1.0.0 → 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 (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
@@ -1,25 +1,79 @@
1
1
  require 'active_record/connection_adapters/abstract_adapter'
2
- require 'parsedate'
3
-
4
- begin
5
- begin
6
- # Only include the MySQL driver if one hasn't already been loaded
7
- require 'mysql' unless self.class.const_defined?(:Mysql)
8
- rescue LoadError
9
- # Only use the supplied backup Ruby/MySQL driver if no driver is already in place
10
- require 'active_record/vendor/mysql'
2
+ require 'set'
3
+
4
+ module MysqlCompat #:nodoc:
5
+ # add all_hashes method to standard mysql-c bindings or pure ruby version
6
+ def self.define_all_hashes_method!
7
+ raise 'Mysql not loaded' unless defined?(::Mysql)
8
+
9
+ target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
10
+ return if target.instance_methods.include?('all_hashes')
11
+
12
+ # Ruby driver has a version string and returns null values in each_hash
13
+ # C driver >= 2.7 returns null values in each_hash
14
+ if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
15
+ target.class_eval <<-'end_eval'
16
+ def all_hashes
17
+ rows = []
18
+ each_hash { |row| rows << row }
19
+ rows
20
+ end
21
+ end_eval
22
+
23
+ # adapters before 2.7 don't have a version constant
24
+ # and don't return null values in each_hash
25
+ else
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
33
+ end_eval
34
+ end
35
+
36
+ unless target.instance_methods.include?('all_hashes') ||
37
+ target.instance_methods.include?(:all_hashes)
38
+ raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
39
+ end
11
40
  end
41
+ end
12
42
 
13
43
  module ActiveRecord
14
44
  class Base
15
- # Establishes a connection to the database that's used by all Active Record objects
45
+ def self.require_mysql
46
+ # Include the MySQL driver if one hasn't already been loaded
47
+ unless defined? Mysql
48
+ begin
49
+ require_library_or_gem 'mysql'
50
+ rescue LoadError => cannot_require_mysql
51
+ # Use the bundled Ruby/MySQL driver if no driver is already in place
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
+
58
+ require 'active_record/vendor/mysql'
59
+ rescue LoadError
60
+ raise cannot_require_mysql
61
+ end
62
+ end
63
+ end
64
+
65
+ # Define Mysql::Result.all_hashes
66
+ MysqlCompat.define_all_hashes_method!
67
+ end
68
+
69
+ # Establishes a connection to the database that's used by all Active Record objects.
16
70
  def self.mysql_connection(config) # :nodoc:
17
- symbolize_strings_in_hash(config)
71
+ config = config.symbolize_keys
18
72
  host = config[:host]
19
73
  port = config[:port]
20
74
  socket = config[:socket]
21
- username = config[:username] || "root"
22
- password = config[:password]
75
+ username = config[:username] ? config[:username].to_s : 'root'
76
+ password = config[:password].to_s
23
77
 
24
78
  if config.has_key?(:database)
25
79
  database = config[:database]
@@ -27,105 +81,416 @@ module ActiveRecord
27
81
  raise ArgumentError, "No database specified. Missing argument: database."
28
82
  end
29
83
 
30
- ConnectionAdapters::MysqlAdapter.new(
31
- Mysql::real_connect(host, username, password, database, port, socket), logger
32
- )
84
+ require_mysql
85
+ mysql = Mysql.init
86
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
87
+
88
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
33
89
  end
34
90
  end
35
91
 
36
92
  module ConnectionAdapters
37
- class MysqlAdapter < AbstractAdapter # :nodoc:
38
- def select_all(sql, name = nil)
39
- select(sql, name)
93
+ class MysqlColumn < Column #:nodoc:
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
40
106
  end
41
107
 
42
- def select_one(sql, name = nil)
43
- result = select(sql, name)
44
- result.nil? ? nil : result.first
108
+ private
109
+ def simplified_type(field_type)
110
+ return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
111
+ return :string if field_type =~ /enum/i
112
+ super
113
+ end
114
+
115
+ # MySQL misreports NOT NULL column default when none is given.
116
+ # We can't detect this for columns which may have a legitimate ''
117
+ # default (string) but we can for others (integer, datetime, boolean,
118
+ # and the rest).
119
+ #
120
+ # Test whether the column has default '', is not null, and is not
121
+ # a type allowing default ''.
122
+ def missing_default_forged_as_empty_string?(default)
123
+ type != :string && !null && default == ''
124
+ end
125
+ end
126
+
127
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
128
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
129
+ #
130
+ # Options:
131
+ #
132
+ # * <tt>:host</tt> -- Defaults to localhost
133
+ # * <tt>:port</tt> -- Defaults to 3306
134
+ # * <tt>:socket</tt> -- Defaults to /tmp/mysql.sock
135
+ # * <tt>:username</tt> -- Defaults to root
136
+ # * <tt>:password</tt> -- Defaults to nothing
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
139
+ # * <tt>:sslkey</tt> -- Necessary to use MySQL with an SSL connection
140
+ # * <tt>:sslcert</tt> -- Necessary to use MySQL with an SSL connection
141
+ # * <tt>:sslcapath</tt> -- Necessary to use MySQL with an SSL connection
142
+ # * <tt>:sslcipher</tt> -- Necessary to use MySQL with an SSL connection
143
+ #
144
+ # By default, the MysqlAdapter will consider all columns of type tinyint(1)
145
+ # as boolean. If you wish to disable this emulation (which was the default
146
+ # behavior in versions 0.13.1 and earlier) you can add the following line
147
+ # to your environment.rb file:
148
+ #
149
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
150
+ class MysqlAdapter < AbstractAdapter
151
+ @@emulate_booleans = true
152
+ cattr_accessor :emulate_booleans
153
+
154
+ LOST_CONNECTION_ERROR_MESSAGES = [
155
+ "Server shutdown in progress",
156
+ "Broken pipe",
157
+ "Lost connection to MySQL server during query",
158
+ "MySQL server has gone away"
159
+ ]
160
+
161
+ def initialize(connection, logger, connection_options, config)
162
+ super(connection, logger)
163
+ @connection_options, @config = connection_options, config
164
+
165
+ connect
45
166
  end
46
167
 
47
- def columns(table_name, name = nil)
48
- sql = "SHOW FIELDS FROM #{table_name}"
49
- result = nil
50
- log(sql, name, @connection) { |connection| result = connection.query(sql) }
168
+ def adapter_name #:nodoc:
169
+ 'MySQL'
170
+ end
51
171
 
52
- columns = []
53
- result.each { |field| columns << Column.new(field[0], field[4], field[1]) }
54
- columns
172
+ def supports_migrations? #:nodoc:
173
+ true
55
174
  end
56
175
 
57
- def insert(sql, name = nil, pk = nil, id_value = nil)
58
- execute(sql, name = nil)
59
- return id_value || @connection.insert_id
176
+ def native_database_types #:nodoc:
177
+ {
178
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
179
+ :string => { :name => "varchar", :limit => 255 },
180
+ :text => { :name => "text" },
181
+ :integer => { :name => "int", :limit => 11 },
182
+ :float => { :name => "float" },
183
+ :decimal => { :name => "decimal" },
184
+ :datetime => { :name => "datetime" },
185
+ :timestamp => { :name => "datetime" },
186
+ :time => { :name => "time" },
187
+ :date => { :name => "date" },
188
+ :binary => { :name => "blob" },
189
+ :boolean => { :name => "tinyint", :limit => 1 }
190
+ }
60
191
  end
61
192
 
62
- def execute(sql, name = nil)
63
- log(sql, name, @connection) { |connection| connection.query(sql) }
193
+
194
+ # QUOTING ==================================================
195
+
196
+ def quote(value, column = nil)
197
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
198
+ s = column.class.string_to_binary(value).unpack("H*")[0]
199
+ "x'#{s}'"
200
+ elsif value.kind_of?(BigDecimal)
201
+ "'#{value.to_s("F")}'"
202
+ else
203
+ super
204
+ end
205
+ end
206
+
207
+ def quote_column_name(name) #:nodoc:
208
+ "`#{name}`"
209
+ end
210
+
211
+ def quote_table_name(name) #:nodoc:
212
+ quote_column_name(name).gsub('.', '`.`')
213
+ end
214
+
215
+ def quote_string(string) #:nodoc:
216
+ @connection.quote(string)
217
+ end
218
+
219
+ def quoted_true
220
+ "1"
221
+ end
222
+
223
+ def quoted_false
224
+ "0"
64
225
  end
65
226
 
66
- alias_method :update, :execute
67
- alias_method :delete, :execute
68
-
69
- def begin_db_transaction
227
+ # REFERENTIAL INTEGRITY ====================================
228
+
229
+ def disable_referential_integrity(&block) #:nodoc:
230
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
231
+
70
232
  begin
71
- execute "BEGIN"
72
- rescue Exception
73
- # Transactions aren't supported
233
+ update("SET FOREIGN_KEY_CHECKS = 0")
234
+ yield
235
+ ensure
236
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
74
237
  end
75
238
  end
76
-
77
- def commit_db_transaction
78
- begin
79
- execute "COMMIT"
80
- rescue Exception
81
- # Transactions aren't supported
239
+
240
+ # CONNECTION MANAGEMENT ====================================
241
+
242
+ def active?
243
+ if @connection.respond_to?(:stat)
244
+ @connection.stat
245
+ else
246
+ @connection.query 'select 1'
82
247
  end
248
+
249
+ # mysql-ruby doesn't raise an exception when stat fails.
250
+ if @connection.respond_to?(:errno)
251
+ @connection.errno.zero?
252
+ else
253
+ true
254
+ end
255
+ rescue Mysql::Error
256
+ false
83
257
  end
84
-
85
- def rollback_db_transaction
86
- begin
87
- execute "ROLLBACK"
88
- rescue Exception
89
- # Transactions aren't supported
258
+
259
+ def reconnect!
260
+ disconnect!
261
+ connect
262
+ end
263
+
264
+ def disconnect!
265
+ @connection.close rescue nil
266
+ end
267
+
268
+
269
+ # DATABASE STATEMENTS ======================================
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
+
280
+ def execute(sql, name = nil) #:nodoc:
281
+ log(sql, name) { @connection.query(sql) }
282
+ rescue ActiveRecord::StatementInvalid => exception
283
+ if exception.message.split(":").first =~ /Packets out of order/
284
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
285
+ else
286
+ raise
90
287
  end
91
288
  end
92
289
 
93
- def quote_column_name(name)
94
- return "`#{name}`"
290
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
291
+ super sql, name
292
+ id_value || @connection.insert_id
293
+ end
294
+
295
+ def update_sql(sql, name = nil) #:nodoc:
296
+ super
297
+ @connection.affected_rows
298
+ end
299
+
300
+ def begin_db_transaction #:nodoc:
301
+ execute "BEGIN"
302
+ rescue Exception
303
+ # Transactions aren't supported
95
304
  end
96
-
97
- def structure_dump
98
- select_all("SHOW TABLES").inject("") do |structure, table|
99
- structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n"
305
+
306
+ def commit_db_transaction #:nodoc:
307
+ execute "COMMIT"
308
+ rescue Exception
309
+ # Transactions aren't supported
310
+ end
311
+
312
+ def rollback_db_transaction #:nodoc:
313
+ execute "ROLLBACK"
314
+ rescue Exception
315
+ # Transactions aren't supported
316
+ end
317
+
318
+
319
+ def add_limit_offset!(sql, options) #:nodoc:
320
+ if limit = options[:limit]
321
+ unless offset = options[:offset]
322
+ sql << " LIMIT #{limit}"
323
+ else
324
+ sql << " LIMIT #{offset}, #{limit}"
325
+ end
326
+ end
327
+ end
328
+
329
+
330
+ # SCHEMA STATEMENTS ========================================
331
+
332
+ def structure_dump #:nodoc:
333
+ if supports_views?
334
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
335
+ else
336
+ sql = "SHOW TABLES"
337
+ end
338
+
339
+ select_all(sql).inject("") do |structure, table|
340
+ table.delete('Table_type')
341
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
100
342
  end
101
343
  end
102
-
103
- def recreate_database(name)
344
+
345
+ def recreate_database(name) #:nodoc:
104
346
  drop_database(name)
105
347
  create_database(name)
106
348
  end
107
-
108
- def drop_database(name)
109
- execute "DROP DATABASE IF EXISTS #{name}"
349
+
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
363
+ end
364
+
365
+ def drop_database(name) #:nodoc:
366
+ execute "DROP DATABASE IF EXISTS `#{name}`"
367
+ end
368
+
369
+ def current_database
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'
381
+ end
382
+
383
+ def tables(name = nil) #:nodoc:
384
+ tables = []
385
+ execute("SHOW TABLES", name).each { |field| tables << field[0] }
386
+ tables
387
+ end
388
+
389
+ def drop_table(table_name, options = {})
390
+ super(table_name, options)
391
+ end
392
+
393
+ def indexes(table_name, name = nil)#:nodoc:
394
+ indexes = []
395
+ current_index = nil
396
+ execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
397
+ if current_index != row[2]
398
+ next if row[2] == "PRIMARY" # skip the primary key
399
+ current_index = row[2]
400
+ indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
401
+ end
402
+
403
+ indexes.last.columns << row[4]
404
+ end
405
+ indexes
406
+ end
407
+
408
+ def columns(table_name, name = nil)#:nodoc:
409
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
410
+ columns = []
411
+ execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
412
+ columns
413
+ end
414
+
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)}"
421
+ end
422
+
423
+ def change_column_default(table_name, column_name, default) #:nodoc:
424
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
425
+
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)}")
427
+ end
428
+
429
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
430
+ unless options_include_default?(options)
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
436
+ end
437
+
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])}"
439
+ add_column_options!(change_column_sql, options)
440
+ execute(change_column_sql)
441
+ end
442
+
443
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
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}"
446
+ end
447
+
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?
110
453
  end
111
-
112
- def create_database(name)
113
- execute "CREATE DATABASE #{name}"
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
114
462
  end
115
-
463
+
116
464
  private
465
+ def connect
466
+ encoding = @config[:encoding]
467
+ if encoding
468
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
469
+ end
470
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
471
+ @connection.real_connect(*@connection_options)
472
+ execute("SET NAMES '#{encoding}'") if encoding
473
+
474
+ # By default, MySQL 'where id is null' selects the last inserted id.
475
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
476
+ execute("SET SQL_AUTO_IS_NULL=0")
477
+ end
478
+
117
479
  def select(sql, name = nil)
118
- result = nil
119
- log(sql, name, @connection) { |connection| connection.query_with_result = true; result = connection.query(sql) }
120
- rows = []
121
- all_fields_initialized = result.fetch_fields.inject({}) { |all_fields, f| all_fields[f.name] = nil; all_fields }
122
- result.each_hash { |row| rows << all_fields_initialized.dup.update(row) }
480
+ @connection.query_with_result = true
481
+ result = execute(sql, name)
482
+ rows = result.all_hashes
483
+ result.free
123
484
  rows
124
485
  end
486
+
487
+ def supports_views?
488
+ version[0] >= 5
489
+ end
490
+
491
+ def version
492
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
493
+ end
125
494
  end
126
495
  end
127
496
  end
128
-
129
- rescue LoadError
130
- # MySQL is not available, so neither should the adapter be
131
- end