activerecord_csi 2.3.5.p6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. data/CHANGELOG +5858 -0
  2. data/README +351 -0
  3. data/RUNNING_UNIT_TESTS +36 -0
  4. data/Rakefile +270 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/performance.rb +162 -0
  7. data/install.rb +30 -0
  8. data/lib/active_record/aggregations.rb +261 -0
  9. data/lib/active_record/association_preload.rb +389 -0
  10. data/lib/active_record/associations/association_collection.rb +475 -0
  11. data/lib/active_record/associations/association_proxy.rb +278 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +76 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +122 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +266 -0
  17. data/lib/active_record/associations/has_one_association.rb +133 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +37 -0
  19. data/lib/active_record/associations.rb +2241 -0
  20. data/lib/active_record/attribute_methods.rb +388 -0
  21. data/lib/active_record/autosave_association.rb +364 -0
  22. data/lib/active_record/base.rb +3171 -0
  23. data/lib/active_record/batches.rb +81 -0
  24. data/lib/active_record/calculations.rb +311 -0
  25. data/lib/active_record/callbacks.rb +360 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
  27. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
  35. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
  36. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  37. data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
  38. data/lib/active_record/dirty.rb +183 -0
  39. data/lib/active_record/dynamic_finder_match.rb +41 -0
  40. data/lib/active_record/dynamic_scope_match.rb +25 -0
  41. data/lib/active_record/fixtures.rb +996 -0
  42. data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
  43. data/lib/active_record/locale/en.yml +58 -0
  44. data/lib/active_record/locking/optimistic.rb +148 -0
  45. data/lib/active_record/locking/pessimistic.rb +55 -0
  46. data/lib/active_record/migration.rb +566 -0
  47. data/lib/active_record/named_scope.rb +192 -0
  48. data/lib/active_record/nested_attributes.rb +392 -0
  49. data/lib/active_record/observer.rb +197 -0
  50. data/lib/active_record/query_cache.rb +33 -0
  51. data/lib/active_record/reflection.rb +320 -0
  52. data/lib/active_record/schema.rb +51 -0
  53. data/lib/active_record/schema_dumper.rb +182 -0
  54. data/lib/active_record/serialization.rb +101 -0
  55. data/lib/active_record/serializers/json_serializer.rb +91 -0
  56. data/lib/active_record/serializers/xml_serializer.rb +357 -0
  57. data/lib/active_record/session_store.rb +326 -0
  58. data/lib/active_record/test_case.rb +66 -0
  59. data/lib/active_record/timestamp.rb +71 -0
  60. data/lib/active_record/transactions.rb +235 -0
  61. data/lib/active_record/validations.rb +1135 -0
  62. data/lib/active_record/version.rb +9 -0
  63. data/lib/active_record.rb +84 -0
  64. data/lib/activerecord.rb +2 -0
  65. data/test/assets/example.log +1 -0
  66. data/test/assets/flowers.jpg +0 -0
  67. data/test/cases/aaa_create_tables_test.rb +24 -0
  68. data/test/cases/active_schema_test_mysql.rb +100 -0
  69. data/test/cases/active_schema_test_postgresql.rb +24 -0
  70. data/test/cases/adapter_test.rb +145 -0
  71. data/test/cases/aggregations_test.rb +167 -0
  72. data/test/cases/ar_schema_test.rb +32 -0
  73. data/test/cases/associations/belongs_to_associations_test.rb +425 -0
  74. data/test/cases/associations/callbacks_test.rb +161 -0
  75. data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
  76. data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
  77. data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
  78. data/test/cases/associations/eager_singularization_test.rb +145 -0
  79. data/test/cases/associations/eager_test.rb +834 -0
  80. data/test/cases/associations/extension_test.rb +62 -0
  81. data/test/cases/associations/habtm_join_table_test.rb +56 -0
  82. data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
  83. data/test/cases/associations/has_many_associations_test.rb +1134 -0
  84. data/test/cases/associations/has_many_through_associations_test.rb +346 -0
  85. data/test/cases/associations/has_one_associations_test.rb +330 -0
  86. data/test/cases/associations/has_one_through_associations_test.rb +209 -0
  87. data/test/cases/associations/inner_join_association_test.rb +93 -0
  88. data/test/cases/associations/join_model_test.rb +712 -0
  89. data/test/cases/associations_test.rb +262 -0
  90. data/test/cases/attribute_methods_test.rb +305 -0
  91. data/test/cases/autosave_association_test.rb +1142 -0
  92. data/test/cases/base_test.rb +2154 -0
  93. data/test/cases/batches_test.rb +61 -0
  94. data/test/cases/binary_test.rb +30 -0
  95. data/test/cases/calculations_test.rb +348 -0
  96. data/test/cases/callbacks_observers_test.rb +38 -0
  97. data/test/cases/callbacks_test.rb +438 -0
  98. data/test/cases/class_inheritable_attributes_test.rb +32 -0
  99. data/test/cases/column_alias_test.rb +17 -0
  100. data/test/cases/column_definition_test.rb +70 -0
  101. data/test/cases/connection_pool_test.rb +25 -0
  102. data/test/cases/connection_test_firebird.rb +8 -0
  103. data/test/cases/connection_test_mysql.rb +64 -0
  104. data/test/cases/copy_table_test_sqlite.rb +80 -0
  105. data/test/cases/database_statements_test.rb +12 -0
  106. data/test/cases/datatype_test_postgresql.rb +204 -0
  107. data/test/cases/date_time_test.rb +37 -0
  108. data/test/cases/default_test_firebird.rb +16 -0
  109. data/test/cases/defaults_test.rb +111 -0
  110. data/test/cases/deprecated_finder_test.rb +30 -0
  111. data/test/cases/dirty_test.rb +316 -0
  112. data/test/cases/finder_respond_to_test.rb +76 -0
  113. data/test/cases/finder_test.rb +1066 -0
  114. data/test/cases/fixtures_test.rb +656 -0
  115. data/test/cases/helper.rb +68 -0
  116. data/test/cases/i18n_test.rb +46 -0
  117. data/test/cases/inheritance_test.rb +262 -0
  118. data/test/cases/invalid_date_test.rb +24 -0
  119. data/test/cases/json_serialization_test.rb +205 -0
  120. data/test/cases/lifecycle_test.rb +193 -0
  121. data/test/cases/locking_test.rb +304 -0
  122. data/test/cases/method_scoping_test.rb +704 -0
  123. data/test/cases/migration_test.rb +1523 -0
  124. data/test/cases/migration_test_firebird.rb +124 -0
  125. data/test/cases/mixin_test.rb +96 -0
  126. data/test/cases/modules_test.rb +81 -0
  127. data/test/cases/multiple_db_test.rb +85 -0
  128. data/test/cases/named_scope_test.rb +361 -0
  129. data/test/cases/nested_attributes_test.rb +581 -0
  130. data/test/cases/pk_test.rb +119 -0
  131. data/test/cases/pooled_connections_test.rb +103 -0
  132. data/test/cases/query_cache_test.rb +123 -0
  133. data/test/cases/readonly_test.rb +107 -0
  134. data/test/cases/reflection_test.rb +194 -0
  135. data/test/cases/reload_models_test.rb +22 -0
  136. data/test/cases/repair_helper.rb +50 -0
  137. data/test/cases/reserved_word_test_mysql.rb +176 -0
  138. data/test/cases/sanitize_test.rb +25 -0
  139. data/test/cases/schema_authorization_test_postgresql.rb +75 -0
  140. data/test/cases/schema_dumper_test.rb +211 -0
  141. data/test/cases/schema_test_postgresql.rb +178 -0
  142. data/test/cases/serialization_test.rb +47 -0
  143. data/test/cases/synonym_test_oracle.rb +17 -0
  144. data/test/cases/timestamp_test.rb +75 -0
  145. data/test/cases/transactions_test.rb +522 -0
  146. data/test/cases/unconnected_test.rb +32 -0
  147. data/test/cases/validations_i18n_test.rb +955 -0
  148. data/test/cases/validations_test.rb +1640 -0
  149. data/test/cases/xml_serialization_test.rb +240 -0
  150. data/test/config.rb +5 -0
  151. data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
  152. data/test/connections/jdbc_jdbch2/connection.rb +18 -0
  153. data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
  154. data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
  155. data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
  156. data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
  157. data/test/connections/native_db2/connection.rb +25 -0
  158. data/test/connections/native_firebird/connection.rb +26 -0
  159. data/test/connections/native_frontbase/connection.rb +27 -0
  160. data/test/connections/native_mysql/connection.rb +25 -0
  161. data/test/connections/native_openbase/connection.rb +21 -0
  162. data/test/connections/native_oracle/connection.rb +27 -0
  163. data/test/connections/native_postgresql/connection.rb +25 -0
  164. data/test/connections/native_sqlite/connection.rb +25 -0
  165. data/test/connections/native_sqlite3/connection.rb +25 -0
  166. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  167. data/test/connections/native_sybase/connection.rb +23 -0
  168. data/test/fixtures/accounts.yml +29 -0
  169. data/test/fixtures/all/developers.yml +0 -0
  170. data/test/fixtures/all/people.csv +0 -0
  171. data/test/fixtures/all/tasks.yml +0 -0
  172. data/test/fixtures/author_addresses.yml +5 -0
  173. data/test/fixtures/author_favorites.yml +4 -0
  174. data/test/fixtures/authors.yml +9 -0
  175. data/test/fixtures/binaries.yml +132 -0
  176. data/test/fixtures/books.yml +7 -0
  177. data/test/fixtures/categories/special_categories.yml +9 -0
  178. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  179. data/test/fixtures/categories.yml +14 -0
  180. data/test/fixtures/categories_ordered.yml +7 -0
  181. data/test/fixtures/categories_posts.yml +23 -0
  182. data/test/fixtures/categorizations.yml +17 -0
  183. data/test/fixtures/clubs.yml +6 -0
  184. data/test/fixtures/comments.yml +59 -0
  185. data/test/fixtures/companies.yml +56 -0
  186. data/test/fixtures/computers.yml +4 -0
  187. data/test/fixtures/courses.yml +7 -0
  188. data/test/fixtures/customers.yml +26 -0
  189. data/test/fixtures/developers.yml +21 -0
  190. data/test/fixtures/developers_projects.yml +17 -0
  191. data/test/fixtures/edges.yml +6 -0
  192. data/test/fixtures/entrants.yml +14 -0
  193. data/test/fixtures/fixture_database.sqlite3 +0 -0
  194. data/test/fixtures/fixture_database_2.sqlite3 +0 -0
  195. data/test/fixtures/fk_test_has_fk.yml +3 -0
  196. data/test/fixtures/fk_test_has_pk.yml +2 -0
  197. data/test/fixtures/funny_jokes.yml +10 -0
  198. data/test/fixtures/items.yml +4 -0
  199. data/test/fixtures/jobs.yml +7 -0
  200. data/test/fixtures/legacy_things.yml +3 -0
  201. data/test/fixtures/mateys.yml +4 -0
  202. data/test/fixtures/member_types.yml +6 -0
  203. data/test/fixtures/members.yml +6 -0
  204. data/test/fixtures/memberships.yml +20 -0
  205. data/test/fixtures/minimalistics.yml +2 -0
  206. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  207. data/test/fixtures/mixins.yml +29 -0
  208. data/test/fixtures/movies.yml +7 -0
  209. data/test/fixtures/naked/csv/accounts.csv +1 -0
  210. data/test/fixtures/naked/yml/accounts.yml +1 -0
  211. data/test/fixtures/naked/yml/companies.yml +1 -0
  212. data/test/fixtures/naked/yml/courses.yml +1 -0
  213. data/test/fixtures/organizations.yml +5 -0
  214. data/test/fixtures/owners.yml +7 -0
  215. data/test/fixtures/parrots.yml +27 -0
  216. data/test/fixtures/parrots_pirates.yml +7 -0
  217. data/test/fixtures/people.yml +15 -0
  218. data/test/fixtures/pets.yml +14 -0
  219. data/test/fixtures/pirates.yml +9 -0
  220. data/test/fixtures/posts.yml +52 -0
  221. data/test/fixtures/price_estimates.yml +7 -0
  222. data/test/fixtures/projects.yml +7 -0
  223. data/test/fixtures/readers.yml +9 -0
  224. data/test/fixtures/references.yml +17 -0
  225. data/test/fixtures/reserved_words/distinct.yml +5 -0
  226. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  227. data/test/fixtures/reserved_words/group.yml +14 -0
  228. data/test/fixtures/reserved_words/select.yml +8 -0
  229. data/test/fixtures/reserved_words/values.yml +7 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/sponsors.yml +9 -0
  232. data/test/fixtures/subscribers.yml +7 -0
  233. data/test/fixtures/subscriptions.yml +12 -0
  234. data/test/fixtures/taggings.yml +28 -0
  235. data/test/fixtures/tags.yml +7 -0
  236. data/test/fixtures/tasks.yml +7 -0
  237. data/test/fixtures/topics.yml +42 -0
  238. data/test/fixtures/toys.yml +4 -0
  239. data/test/fixtures/treasures.yml +10 -0
  240. data/test/fixtures/vertices.yml +4 -0
  241. data/test/fixtures/warehouse-things.yml +3 -0
  242. data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
  243. data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
  244. data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
  245. data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
  246. data/test/migrations/duplicate/3_foo.rb +7 -0
  247. data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
  248. data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
  249. data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
  250. data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
  251. data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
  252. data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
  253. data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
  254. data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
  255. data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
  256. data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
  257. data/test/migrations/missing/1_people_have_last_names.rb +9 -0
  258. data/test/migrations/missing/3_we_need_reminders.rb +12 -0
  259. data/test/migrations/missing/4_innocent_jointable.rb +12 -0
  260. data/test/migrations/valid/1_people_have_last_names.rb +9 -0
  261. data/test/migrations/valid/2_we_need_reminders.rb +12 -0
  262. data/test/migrations/valid/3_innocent_jointable.rb +12 -0
  263. data/test/models/author.rb +146 -0
  264. data/test/models/auto_id.rb +4 -0
  265. data/test/models/binary.rb +2 -0
  266. data/test/models/bird.rb +3 -0
  267. data/test/models/book.rb +4 -0
  268. data/test/models/categorization.rb +5 -0
  269. data/test/models/category.rb +34 -0
  270. data/test/models/citation.rb +6 -0
  271. data/test/models/club.rb +13 -0
  272. data/test/models/column_name.rb +3 -0
  273. data/test/models/comment.rb +29 -0
  274. data/test/models/company.rb +171 -0
  275. data/test/models/company_in_module.rb +61 -0
  276. data/test/models/computer.rb +3 -0
  277. data/test/models/contact.rb +16 -0
  278. data/test/models/contract.rb +5 -0
  279. data/test/models/course.rb +3 -0
  280. data/test/models/customer.rb +73 -0
  281. data/test/models/default.rb +2 -0
  282. data/test/models/developer.rb +101 -0
  283. data/test/models/edge.rb +5 -0
  284. data/test/models/entrant.rb +3 -0
  285. data/test/models/essay.rb +3 -0
  286. data/test/models/event.rb +3 -0
  287. data/test/models/guid.rb +2 -0
  288. data/test/models/item.rb +7 -0
  289. data/test/models/job.rb +5 -0
  290. data/test/models/joke.rb +3 -0
  291. data/test/models/keyboard.rb +3 -0
  292. data/test/models/legacy_thing.rb +3 -0
  293. data/test/models/matey.rb +4 -0
  294. data/test/models/member.rb +12 -0
  295. data/test/models/member_detail.rb +5 -0
  296. data/test/models/member_type.rb +3 -0
  297. data/test/models/membership.rb +9 -0
  298. data/test/models/minimalistic.rb +2 -0
  299. data/test/models/mixed_case_monkey.rb +3 -0
  300. data/test/models/movie.rb +5 -0
  301. data/test/models/order.rb +4 -0
  302. data/test/models/organization.rb +6 -0
  303. data/test/models/owner.rb +5 -0
  304. data/test/models/parrot.rb +16 -0
  305. data/test/models/person.rb +16 -0
  306. data/test/models/pet.rb +5 -0
  307. data/test/models/pirate.rb +70 -0
  308. data/test/models/post.rb +100 -0
  309. data/test/models/price_estimate.rb +3 -0
  310. data/test/models/project.rb +30 -0
  311. data/test/models/reader.rb +4 -0
  312. data/test/models/reference.rb +4 -0
  313. data/test/models/reply.rb +46 -0
  314. data/test/models/ship.rb +10 -0
  315. data/test/models/ship_part.rb +5 -0
  316. data/test/models/sponsor.rb +4 -0
  317. data/test/models/subject.rb +4 -0
  318. data/test/models/subscriber.rb +8 -0
  319. data/test/models/subscription.rb +4 -0
  320. data/test/models/tag.rb +7 -0
  321. data/test/models/tagging.rb +10 -0
  322. data/test/models/task.rb +3 -0
  323. data/test/models/topic.rb +80 -0
  324. data/test/models/toy.rb +6 -0
  325. data/test/models/treasure.rb +8 -0
  326. data/test/models/vertex.rb +9 -0
  327. data/test/models/warehouse_thing.rb +5 -0
  328. data/test/schema/mysql_specific_schema.rb +24 -0
  329. data/test/schema/postgresql_specific_schema.rb +114 -0
  330. data/test/schema/schema.rb +493 -0
  331. data/test/schema/schema2.rb +6 -0
  332. data/test/schema/sqlite_specific_schema.rb +25 -0
  333. metadata +420 -0
@@ -0,0 +1,630 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
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
+ target.instance_methods.include?(:all_hashes)
12
+
13
+ # Ruby driver has a version string and returns null values in each_hash
14
+ # C driver >= 2.7 returns null values in each_hash
15
+ if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
16
+ target.class_eval <<-'end_eval'
17
+ def all_hashes # def all_hashes
18
+ rows = [] # rows = []
19
+ each_hash { |row| rows << row } # each_hash { |row| rows << row }
20
+ rows # rows
21
+ end # end
22
+ end_eval
23
+
24
+ # adapters before 2.7 don't have a version constant
25
+ # and don't return null values in each_hash
26
+ else
27
+ target.class_eval <<-'end_eval'
28
+ def all_hashes # def all_hashes
29
+ rows = [] # rows = []
30
+ all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
31
+ fields[f.name] = nil; fields # fields[f.name] = nil; fields
32
+ } # }
33
+ each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
34
+ rows # rows
35
+ end # end
36
+ end_eval
37
+ end
38
+
39
+ unless target.instance_methods.include?('all_hashes') ||
40
+ target.instance_methods.include?(:all_hashes)
41
+ raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
42
+ end
43
+ end
44
+ end
45
+
46
+ module ActiveRecord
47
+ class Base
48
+ # Establishes a connection to the database that's used by all Active Record objects.
49
+ def self.mysql_connection(config) # :nodoc:
50
+ config = config.symbolize_keys
51
+ host = config[:host]
52
+ port = config[:port]
53
+ socket = config[:socket]
54
+ username = config[:username] ? config[:username].to_s : 'root'
55
+ password = config[:password].to_s
56
+ database = config[:database]
57
+
58
+ # Require the MySQL driver and define Mysql::Result.all_hashes
59
+ unless defined? Mysql
60
+ begin
61
+ require_library_or_gem('mysql')
62
+ rescue LoadError
63
+ $stderr.puts '!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.'
64
+ raise
65
+ end
66
+ end
67
+
68
+ MysqlCompat.define_all_hashes_method!
69
+
70
+ mysql = Mysql.init
71
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
72
+
73
+ default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
74
+ options = [host, username, password, database, port, socket, default_flags]
75
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
76
+ end
77
+ end
78
+
79
+ module ConnectionAdapters
80
+ class MysqlColumn < Column #:nodoc:
81
+ def extract_default(default)
82
+ if sql_type =~ /blob/i || type == :text
83
+ if default.blank?
84
+ return null ? nil : ''
85
+ else
86
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
87
+ end
88
+ elsif missing_default_forged_as_empty_string?(default)
89
+ nil
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ def has_default?
96
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
97
+ super
98
+ end
99
+
100
+ private
101
+ def simplified_type(field_type)
102
+ return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
103
+ return :string if field_type =~ /enum/i
104
+ super
105
+ end
106
+
107
+ def extract_limit(sql_type)
108
+ case sql_type
109
+ when /blob|text/i
110
+ case sql_type
111
+ when /tiny/i
112
+ 255
113
+ when /medium/i
114
+ 16777215
115
+ when /long/i
116
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
117
+ else
118
+ super # we could return 65535 here, but we leave it undecorated by default
119
+ end
120
+ when /^bigint/i; 8
121
+ when /^int/i; 4
122
+ when /^mediumint/i; 3
123
+ when /^smallint/i; 2
124
+ when /^tinyint/i; 1
125
+ else
126
+ super
127
+ end
128
+ end
129
+
130
+ # MySQL misreports NOT NULL column default when none is given.
131
+ # We can't detect this for columns which may have a legitimate ''
132
+ # default (string) but we can for others (integer, datetime, boolean,
133
+ # and the rest).
134
+ #
135
+ # Test whether the column has default '', is not null, and is not
136
+ # a type allowing default ''.
137
+ def missing_default_forged_as_empty_string?(default)
138
+ type != :string && !null && default == ''
139
+ end
140
+ end
141
+
142
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
143
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
144
+ #
145
+ # Options:
146
+ #
147
+ # * <tt>:host</tt> - Defaults to "localhost".
148
+ # * <tt>:port</tt> - Defaults to 3306.
149
+ # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
150
+ # * <tt>:username</tt> - Defaults to "root"
151
+ # * <tt>:password</tt> - Defaults to nothing.
152
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
153
+ # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
154
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
155
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
156
+ # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
157
+ # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
158
+ # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
159
+ # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
160
+ #
161
+ class MysqlAdapter < AbstractAdapter
162
+
163
+ ##
164
+ # :singleton-method:
165
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
166
+ # as boolean. If you wish to disable this emulation (which was the default
167
+ # behavior in versions 0.13.1 and earlier) you can add the following line
168
+ # to your environment.rb file:
169
+ #
170
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
171
+ cattr_accessor :emulate_booleans
172
+ self.emulate_booleans = true
173
+
174
+ ADAPTER_NAME = 'MySQL'.freeze
175
+
176
+ LOST_CONNECTION_ERROR_MESSAGES = [
177
+ "Server shutdown in progress",
178
+ "Broken pipe",
179
+ "Lost connection to MySQL server during query",
180
+ "MySQL server has gone away" ]
181
+
182
+ QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
183
+
184
+ NATIVE_DATABASE_TYPES = {
185
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
186
+ :string => { :name => "varchar", :limit => 255 },
187
+ :text => { :name => "text" },
188
+ :integer => { :name => "int", :limit => 4 },
189
+ :float => { :name => "float" },
190
+ :decimal => { :name => "decimal" },
191
+ :datetime => { :name => "datetime" },
192
+ :timestamp => { :name => "datetime" },
193
+ :time => { :name => "time" },
194
+ :date => { :name => "date" },
195
+ :binary => { :name => "blob" },
196
+ :boolean => { :name => "tinyint", :limit => 1 }
197
+ }
198
+
199
+ def initialize(connection, logger, connection_options, config)
200
+ super(connection, logger)
201
+ @connection_options, @config = connection_options, config
202
+ @quoted_column_names, @quoted_table_names = {}, {}
203
+ connect
204
+ end
205
+
206
+ def adapter_name #:nodoc:
207
+ ADAPTER_NAME
208
+ end
209
+
210
+ def supports_migrations? #:nodoc:
211
+ true
212
+ end
213
+
214
+ def supports_primary_key? #:nodoc:
215
+ true
216
+ end
217
+
218
+ def supports_savepoints? #:nodoc:
219
+ true
220
+ end
221
+
222
+ def native_database_types #:nodoc:
223
+ NATIVE_DATABASE_TYPES
224
+ end
225
+
226
+
227
+ # QUOTING ==================================================
228
+
229
+ def quote(value, column = nil)
230
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
231
+ s = column.class.string_to_binary(value).unpack("H*")[0]
232
+ "x'#{s}'"
233
+ elsif value.kind_of?(BigDecimal)
234
+ value.to_s("F")
235
+ else
236
+ super
237
+ end
238
+ end
239
+
240
+ def quote_column_name(name) #:nodoc:
241
+ @quoted_column_names[name] ||= "`#{name}`"
242
+ end
243
+
244
+ def quote_table_name(name) #:nodoc:
245
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
246
+ end
247
+
248
+ def quote_string(string) #:nodoc:
249
+ @connection.quote(string)
250
+ end
251
+
252
+ def quoted_true
253
+ QUOTED_TRUE
254
+ end
255
+
256
+ def quoted_false
257
+ QUOTED_FALSE
258
+ end
259
+
260
+ # REFERENTIAL INTEGRITY ====================================
261
+
262
+ def disable_referential_integrity(&block) #:nodoc:
263
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
264
+
265
+ begin
266
+ update("SET FOREIGN_KEY_CHECKS = 0")
267
+ yield
268
+ ensure
269
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
270
+ end
271
+ end
272
+
273
+ # CONNECTION MANAGEMENT ====================================
274
+
275
+ def active?
276
+ if @connection.respond_to?(:stat)
277
+ @connection.stat
278
+ else
279
+ @connection.query 'select 1'
280
+ end
281
+
282
+ # mysql-ruby doesn't raise an exception when stat fails.
283
+ if @connection.respond_to?(:errno)
284
+ @connection.errno.zero?
285
+ else
286
+ true
287
+ end
288
+ rescue Mysql::Error
289
+ false
290
+ end
291
+
292
+ def reconnect!
293
+ disconnect!
294
+ connect
295
+ end
296
+
297
+ def disconnect!
298
+ @connection.close rescue nil
299
+ end
300
+
301
+ def reset!
302
+ if @connection.respond_to?(:change_user)
303
+ # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
304
+ # reset the connection is to change the user to the same user.
305
+ @connection.change_user(@config[:username], @config[:password], @config[:database])
306
+ configure_connection
307
+ end
308
+ end
309
+
310
+ # DATABASE STATEMENTS ======================================
311
+
312
+ def select_rows(sql, name = nil)
313
+ @connection.query_with_result = true
314
+ result = execute(sql, name)
315
+ rows = []
316
+ result.each { |row| rows << row }
317
+ result.free
318
+ rows
319
+ end
320
+
321
+ # 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.
322
+ def execute(sql, name = nil) #:nodoc:
323
+ log(sql, name) { @connection.query(sql) }
324
+ rescue ActiveRecord::StatementInvalid => exception
325
+ if exception.message.split(":").first =~ /Packets out of order/
326
+ 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."
327
+ else
328
+ raise
329
+ end
330
+ end
331
+
332
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
333
+ super sql, name
334
+ id_value || @connection.insert_id
335
+ end
336
+
337
+ def update_sql(sql, name = nil) #:nodoc:
338
+ super
339
+ @connection.affected_rows
340
+ end
341
+
342
+ def begin_db_transaction #:nodoc:
343
+ execute "BEGIN"
344
+ rescue Exception
345
+ # Transactions aren't supported
346
+ end
347
+
348
+ def commit_db_transaction #:nodoc:
349
+ execute "COMMIT"
350
+ rescue Exception
351
+ # Transactions aren't supported
352
+ end
353
+
354
+ def rollback_db_transaction #:nodoc:
355
+ execute "ROLLBACK"
356
+ rescue Exception
357
+ # Transactions aren't supported
358
+ end
359
+
360
+ def create_savepoint
361
+ execute("SAVEPOINT #{current_savepoint_name}")
362
+ end
363
+
364
+ def rollback_to_savepoint
365
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
366
+ end
367
+
368
+ def release_savepoint
369
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
370
+ end
371
+
372
+ def add_limit_offset!(sql, options) #:nodoc:
373
+ if limit = options[:limit]
374
+ limit = sanitize_limit(limit)
375
+ unless offset = options[:offset]
376
+ sql << " LIMIT #{limit}"
377
+ else
378
+ sql << " LIMIT #{offset.to_i}, #{limit}"
379
+ end
380
+ end
381
+ end
382
+
383
+
384
+ # SCHEMA STATEMENTS ========================================
385
+
386
+ def structure_dump #:nodoc:
387
+ if supports_views?
388
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
389
+ else
390
+ sql = "SHOW TABLES"
391
+ end
392
+
393
+ select_all(sql).inject("") do |structure, table|
394
+ table.delete('Table_type')
395
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
396
+ end
397
+ end
398
+
399
+ def recreate_database(name, options = {}) #:nodoc:
400
+ drop_database(name)
401
+ create_database(name, options)
402
+ end
403
+
404
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
405
+ # Charset defaults to utf8.
406
+ #
407
+ # Example:
408
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
409
+ # create_database 'matt_development'
410
+ # create_database 'matt_development', :charset => :big5
411
+ def create_database(name, options = {})
412
+ if options[:collation]
413
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
414
+ else
415
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
416
+ end
417
+ end
418
+
419
+ def drop_database(name) #:nodoc:
420
+ execute "DROP DATABASE IF EXISTS `#{name}`"
421
+ end
422
+
423
+ def current_database
424
+ select_value 'SELECT DATABASE() as db'
425
+ end
426
+
427
+ # Returns the database character set.
428
+ def charset
429
+ show_variable 'character_set_database'
430
+ end
431
+
432
+ # Returns the database collation strategy.
433
+ def collation
434
+ show_variable 'collation_database'
435
+ end
436
+
437
+ def tables(name = nil) #:nodoc:
438
+ tables = []
439
+ result = execute("SHOW TABLES", name)
440
+ result.each { |field| tables << field[0] }
441
+ result.free
442
+ tables
443
+ end
444
+
445
+ def drop_table(table_name, options = {})
446
+ super(table_name, options)
447
+ end
448
+
449
+ def indexes(table_name, name = nil)#:nodoc:
450
+ indexes = []
451
+ current_index = nil
452
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
453
+ result.each do |row|
454
+ if current_index != row[2]
455
+ next if row[2] == "PRIMARY" # skip the primary key
456
+ current_index = row[2]
457
+ indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
458
+ end
459
+
460
+ indexes.last.columns << row[4]
461
+ end
462
+ result.free
463
+ indexes
464
+ end
465
+
466
+ def columns(table_name, name = nil)#:nodoc:
467
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
468
+ columns = []
469
+ result = execute(sql, name)
470
+ result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
471
+ result.free
472
+ columns
473
+ end
474
+
475
+ def create_table(table_name, options = {}) #:nodoc:
476
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
477
+ end
478
+
479
+ def rename_table(table_name, new_name)
480
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
481
+ end
482
+
483
+ def change_column_default(table_name, column_name, default) #:nodoc:
484
+ column = column_for(table_name, column_name)
485
+ change_column table_name, column_name, column.sql_type, :default => default
486
+ end
487
+
488
+ def change_column_null(table_name, column_name, null, default = nil)
489
+ column = column_for(table_name, column_name)
490
+
491
+ unless null || default.nil?
492
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
493
+ end
494
+
495
+ change_column table_name, column_name, column.sql_type, :null => null
496
+ end
497
+
498
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
499
+ column = column_for(table_name, column_name)
500
+
501
+ unless options_include_default?(options)
502
+ options[:default] = column.default
503
+ end
504
+
505
+ unless options.has_key?(:null)
506
+ options[:null] = column.null
507
+ end
508
+
509
+ 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])}"
510
+ add_column_options!(change_column_sql, options)
511
+ execute(change_column_sql)
512
+ end
513
+
514
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
515
+ options = {}
516
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
517
+ options[:default] = column.default
518
+ options[:null] = column.null
519
+ else
520
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
521
+ end
522
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
523
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
524
+ add_column_options!(rename_column_sql, options)
525
+ execute(rename_column_sql)
526
+ end
527
+
528
+ # Maps logical Rails types to MySQL-specific data types.
529
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
530
+ return super unless type.to_s == 'integer'
531
+
532
+ case limit
533
+ when 1; 'tinyint'
534
+ when 2; 'smallint'
535
+ when 3; 'mediumint'
536
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
537
+ when 5..8; 'bigint'
538
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
539
+ end
540
+ end
541
+
542
+
543
+ # SHOW VARIABLES LIKE 'name'
544
+ def show_variable(name)
545
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
546
+ variables.first['Value'] unless variables.empty?
547
+ end
548
+
549
+ # Returns a table's primary key and belonging sequence.
550
+ def pk_and_sequence_for(table) #:nodoc:
551
+ keys = []
552
+ result = execute("describe #{quote_table_name(table)}")
553
+ result.each_hash do |h|
554
+ keys << h["Field"]if h["Key"] == "PRI"
555
+ end
556
+ result.free
557
+ keys.length == 1 ? [keys.first, nil] : nil
558
+ end
559
+
560
+ # Returns just a table's primary key
561
+ def primary_key(table)
562
+ pk_and_sequence = pk_and_sequence_for(table)
563
+ pk_and_sequence && pk_and_sequence.first
564
+ end
565
+
566
+ def case_sensitive_equality_operator
567
+ "= BINARY"
568
+ end
569
+
570
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
571
+ where_sql
572
+ end
573
+
574
+ private
575
+ def connect
576
+ encoding = @config[:encoding]
577
+ if encoding
578
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
579
+ end
580
+
581
+ if @config[:sslca] || @config[:sslkey]
582
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
583
+ end
584
+
585
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
586
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
587
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
588
+
589
+ @connection.real_connect(*@connection_options)
590
+
591
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
592
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
593
+
594
+ configure_connection
595
+ end
596
+
597
+ def configure_connection
598
+ encoding = @config[:encoding]
599
+ execute("SET NAMES '#{encoding}'") if encoding
600
+
601
+ # By default, MySQL 'where id is null' selects the last inserted id.
602
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
603
+ execute("SET SQL_AUTO_IS_NULL=0")
604
+ end
605
+
606
+ def select(sql, name = nil)
607
+ @connection.query_with_result = true
608
+ result = execute(sql, name)
609
+ rows = result.all_hashes
610
+ result.free
611
+ rows
612
+ end
613
+
614
+ def supports_views?
615
+ version[0] >= 5
616
+ end
617
+
618
+ def version
619
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
620
+ end
621
+
622
+ def column_for(table_name, column_name)
623
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
624
+ raise "No such column: #{table_name}.#{column_name}"
625
+ end
626
+ column
627
+ end
628
+ end
629
+ end
630
+ end