activerecord 1.0.0 → 4.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -1,131 +1,558 @@
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'
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+ require 'active_record/connection_adapters/statement_pool'
3
+ require 'active_support/core_ext/hash/keys'
4
+
5
+ gem 'mysql', '~> 2.9'
6
+ require 'mysql'
7
+
8
+ class Mysql
9
+ class Time
10
+ def to_date
11
+ Date.new(year, month, day)
12
+ end
11
13
  end
14
+ class Stmt; include Enumerable end
15
+ class Result; include Enumerable end
16
+ end
12
17
 
13
18
  module ActiveRecord
14
- class Base
15
- # Establishes a connection to the database that's used by all Active Record objects
16
- def self.mysql_connection(config) # :nodoc:
17
- symbolize_strings_in_hash(config)
19
+ module ConnectionHandling # :nodoc:
20
+ # Establishes a connection to the database that's used by all Active Record objects.
21
+ def mysql_connection(config)
22
+ config = config.symbolize_keys
18
23
  host = config[:host]
19
24
  port = config[:port]
20
25
  socket = config[:socket]
21
- username = config[:username] || "root"
22
- password = config[:password]
26
+ username = config[:username] ? config[:username].to_s : 'root'
27
+ password = config[:password].to_s
28
+ database = config[:database]
23
29
 
24
- if config.has_key?(:database)
25
- database = config[:database]
26
- else
27
- raise ArgumentError, "No database specified. Missing argument: database."
28
- end
30
+ mysql = Mysql.init
31
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
29
32
 
30
- ConnectionAdapters::MysqlAdapter.new(
31
- Mysql::real_connect(host, username, password, database, port, socket), logger
32
- )
33
+ default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
34
+ default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
35
+ options = [host, username, password, database, port, socket, default_flags]
36
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
33
37
  end
34
38
  end
35
39
 
36
40
  module ConnectionAdapters
37
- class MysqlAdapter < AbstractAdapter # :nodoc:
38
- def select_all(sql, name = nil)
39
- select(sql, name)
41
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
42
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
43
+ #
44
+ # Options:
45
+ #
46
+ # * <tt>:host</tt> - Defaults to "localhost".
47
+ # * <tt>:port</tt> - Defaults to 3306.
48
+ # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
49
+ # * <tt>:username</tt> - Defaults to "root"
50
+ # * <tt>:password</tt> - Defaults to nothing.
51
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
52
+ # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
53
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
54
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html)
55
+ # * <tt>:variables</tt> - (Optional) A hash session variables to send as `SET @@SESSION.key = value` on each database connection. Use the value `:default` to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/set-statement.html).
56
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
57
+ # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
58
+ # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
59
+ # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
60
+ # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
61
+ #
62
+ class MysqlAdapter < AbstractMysqlAdapter
63
+
64
+ class Column < AbstractMysqlAdapter::Column #:nodoc:
65
+ def self.string_to_time(value)
66
+ return super unless Mysql::Time === value
67
+ new_time(
68
+ value.year,
69
+ value.month,
70
+ value.day,
71
+ value.hour,
72
+ value.minute,
73
+ value.second,
74
+ value.second_part)
75
+ end
76
+
77
+ def self.string_to_dummy_time(v)
78
+ return super unless Mysql::Time === v
79
+ new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
80
+ end
81
+
82
+ def self.string_to_date(v)
83
+ return super unless Mysql::Time === v
84
+ new_date(v.year, v.month, v.day)
85
+ end
86
+
87
+ def adapter
88
+ MysqlAdapter
89
+ end
40
90
  end
41
91
 
42
- def select_one(sql, name = nil)
43
- result = select(sql, name)
44
- result.nil? ? nil : result.first
92
+ ADAPTER_NAME = 'MySQL'
93
+
94
+ class StatementPool < ConnectionAdapters::StatementPool
95
+ def initialize(connection, max = 1000)
96
+ super
97
+ @cache = Hash.new { |h,pid| h[pid] = {} }
98
+ end
99
+
100
+ def each(&block); cache.each(&block); end
101
+ def key?(key); cache.key?(key); end
102
+ def [](key); cache[key]; end
103
+ def length; cache.length; end
104
+ def delete(key); cache.delete(key); end
105
+
106
+ def []=(sql, key)
107
+ while @max <= cache.size
108
+ cache.shift.last[:stmt].close
109
+ end
110
+ cache[sql] = key
111
+ end
112
+
113
+ def clear
114
+ cache.values.each do |hash|
115
+ hash[:stmt].close
116
+ end
117
+ cache.clear
118
+ end
119
+
120
+ private
121
+ def cache
122
+ @cache[Process.pid]
123
+ end
124
+ end
125
+
126
+ def initialize(connection, logger, connection_options, config)
127
+ super
128
+ @statements = StatementPool.new(@connection,
129
+ self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
130
+ @client_encoding = nil
131
+ connect
132
+ end
133
+
134
+ # Returns true, since this connection adapter supports prepared statement
135
+ # caching.
136
+ def supports_statement_cache?
137
+ true
138
+ end
139
+
140
+ # HELPER METHODS ===========================================
141
+
142
+ def each_hash(result) # :nodoc:
143
+ if block_given?
144
+ result.each_hash do |row|
145
+ row.symbolize_keys!
146
+ yield row
147
+ end
148
+ else
149
+ to_enum(:each_hash, result)
150
+ end
45
151
  end
46
152
 
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) }
153
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
154
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
155
+ end
51
156
 
52
- columns = []
53
- result.each { |field| columns << Column.new(field[0], field[4], field[1]) }
54
- columns
157
+ def error_number(exception) # :nodoc:
158
+ exception.errno if exception.respond_to?(:errno)
55
159
  end
56
160
 
57
- def insert(sql, name = nil, pk = nil, id_value = nil)
58
- execute(sql, name = nil)
59
- return id_value || @connection.insert_id
161
+ # QUOTING ==================================================
162
+
163
+ def type_cast(value, column)
164
+ return super unless value == true || value == false
165
+
166
+ value ? 1 : 0
60
167
  end
61
168
 
62
- def execute(sql, name = nil)
63
- log(sql, name, @connection) { |connection| connection.query(sql) }
169
+ def quote_string(string) #:nodoc:
170
+ @connection.quote(string)
64
171
  end
65
172
 
66
- alias_method :update, :execute
67
- alias_method :delete, :execute
68
-
69
- def begin_db_transaction
70
- begin
71
- execute "BEGIN"
72
- rescue Exception
73
- # Transactions aren't supported
173
+ # CONNECTION MANAGEMENT ====================================
174
+
175
+ def active?
176
+ if @connection.respond_to?(:stat)
177
+ @connection.stat
178
+ else
179
+ @connection.query 'select 1'
180
+ end
181
+
182
+ # mysql-ruby doesn't raise an exception when stat fails.
183
+ if @connection.respond_to?(:errno)
184
+ @connection.errno.zero?
185
+ else
186
+ true
74
187
  end
188
+ rescue Mysql::Error
189
+ false
190
+ end
191
+
192
+ def reconnect!
193
+ super
194
+ disconnect!
195
+ connect
196
+ end
197
+
198
+ # Disconnects from the database if already connected. Otherwise, this
199
+ # method does nothing.
200
+ def disconnect!
201
+ super
202
+ @connection.close rescue nil
75
203
  end
76
-
77
- def commit_db_transaction
78
- begin
79
- execute "COMMIT"
80
- rescue Exception
81
- # Transactions aren't supported
204
+
205
+ def reset!
206
+ if @connection.respond_to?(:change_user)
207
+ # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
208
+ # reset the connection is to change the user to the same user.
209
+ @connection.change_user(@config[:username], @config[:password], @config[:database])
210
+ configure_connection
82
211
  end
83
212
  end
84
-
85
- def rollback_db_transaction
86
- begin
87
- execute "ROLLBACK"
88
- rescue Exception
89
- # Transactions aren't supported
213
+
214
+ # DATABASE STATEMENTS ======================================
215
+
216
+ def select_rows(sql, name = nil)
217
+ @connection.query_with_result = true
218
+ rows = exec_query(sql, name).rows
219
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
220
+ rows
221
+ end
222
+
223
+ # Clears the prepared statements cache.
224
+ def clear_cache!
225
+ @statements.clear
226
+ end
227
+
228
+ # Taken from here:
229
+ # https://github.com/tmtm/ruby-mysql/blob/master/lib/mysql/charset.rb
230
+ # Author: TOMITA Masahiro <tommy@tmtm.org>
231
+ ENCODINGS = {
232
+ "armscii8" => nil,
233
+ "ascii" => Encoding::US_ASCII,
234
+ "big5" => Encoding::Big5,
235
+ "binary" => Encoding::ASCII_8BIT,
236
+ "cp1250" => Encoding::Windows_1250,
237
+ "cp1251" => Encoding::Windows_1251,
238
+ "cp1256" => Encoding::Windows_1256,
239
+ "cp1257" => Encoding::Windows_1257,
240
+ "cp850" => Encoding::CP850,
241
+ "cp852" => Encoding::CP852,
242
+ "cp866" => Encoding::IBM866,
243
+ "cp932" => Encoding::Windows_31J,
244
+ "dec8" => nil,
245
+ "eucjpms" => Encoding::EucJP_ms,
246
+ "euckr" => Encoding::EUC_KR,
247
+ "gb2312" => Encoding::EUC_CN,
248
+ "gbk" => Encoding::GBK,
249
+ "geostd8" => nil,
250
+ "greek" => Encoding::ISO_8859_7,
251
+ "hebrew" => Encoding::ISO_8859_8,
252
+ "hp8" => nil,
253
+ "keybcs2" => nil,
254
+ "koi8r" => Encoding::KOI8_R,
255
+ "koi8u" => Encoding::KOI8_U,
256
+ "latin1" => Encoding::ISO_8859_1,
257
+ "latin2" => Encoding::ISO_8859_2,
258
+ "latin5" => Encoding::ISO_8859_9,
259
+ "latin7" => Encoding::ISO_8859_13,
260
+ "macce" => Encoding::MacCentEuro,
261
+ "macroman" => Encoding::MacRoman,
262
+ "sjis" => Encoding::SHIFT_JIS,
263
+ "swe7" => nil,
264
+ "tis620" => Encoding::TIS_620,
265
+ "ucs2" => Encoding::UTF_16BE,
266
+ "ujis" => Encoding::EucJP_ms,
267
+ "utf8" => Encoding::UTF_8,
268
+ "utf8mb4" => Encoding::UTF_8,
269
+ }
270
+
271
+ # Get the client encoding for this database
272
+ def client_encoding
273
+ return @client_encoding if @client_encoding
274
+
275
+ result = exec_query(
276
+ "SHOW VARIABLES WHERE Variable_name = 'character_set_client'",
277
+ 'SCHEMA')
278
+ @client_encoding = ENCODINGS[result.rows.last.last]
279
+ end
280
+
281
+ def exec_query(sql, name = 'SQL', binds = [])
282
+ # If the configuration sets prepared_statements:false, binds will
283
+ # always be empty, since the bind variables will have been already
284
+ # substituted and removed from binds by BindVisitor, so this will
285
+ # effectively disable prepared statement usage completely.
286
+ if binds.empty?
287
+ result_set, affected_rows = exec_without_stmt(sql, name)
288
+ else
289
+ result_set, affected_rows = exec_stmt(sql, name, binds)
90
290
  end
291
+
292
+ yield affected_rows if block_given?
293
+
294
+ result_set
91
295
  end
92
296
 
93
- def quote_column_name(name)
94
- return "`#{name}`"
297
+ def last_inserted_id(result)
298
+ @connection.insert_id
95
299
  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"
300
+
301
+ module Fields
302
+ class Type
303
+ def type; end
304
+
305
+ def type_cast_for_write(value)
306
+ value
307
+ end
308
+ end
309
+
310
+ class Identity < Type
311
+ def type_cast(value); value; end
100
312
  end
313
+
314
+ class Integer < Type
315
+ def type_cast(value)
316
+ return if value.nil?
317
+
318
+ value.to_i rescue value ? 1 : 0
319
+ end
320
+ end
321
+
322
+ class Date < Type
323
+ def type; :date; end
324
+
325
+ def type_cast(value)
326
+ return if value.nil?
327
+
328
+ # FIXME: probably we can improve this since we know it is mysql
329
+ # specific
330
+ ConnectionAdapters::Column.value_to_date value
331
+ end
332
+ end
333
+
334
+ class DateTime < Type
335
+ def type; :datetime; end
336
+
337
+ def type_cast(value)
338
+ return if value.nil?
339
+
340
+ # FIXME: probably we can improve this since we know it is mysql
341
+ # specific
342
+ ConnectionAdapters::Column.string_to_time value
343
+ end
344
+ end
345
+
346
+ class Time < Type
347
+ def type; :time; end
348
+
349
+ def type_cast(value)
350
+ return if value.nil?
351
+
352
+ # FIXME: probably we can improve this since we know it is mysql
353
+ # specific
354
+ ConnectionAdapters::Column.string_to_dummy_time value
355
+ end
356
+ end
357
+
358
+ class Float < Type
359
+ def type; :float; end
360
+
361
+ def type_cast(value)
362
+ return if value.nil?
363
+
364
+ value.to_f
365
+ end
366
+ end
367
+
368
+ class Decimal < Type
369
+ def type_cast(value)
370
+ return if value.nil?
371
+
372
+ ConnectionAdapters::Column.value_to_decimal value
373
+ end
374
+ end
375
+
376
+ class Boolean < Type
377
+ def type_cast(value)
378
+ return if value.nil?
379
+
380
+ ConnectionAdapters::Column.value_to_boolean value
381
+ end
382
+ end
383
+
384
+ TYPES = {}
385
+
386
+ # Register an MySQL +type_id+ with a typecasting object in
387
+ # +type+.
388
+ def self.register_type(type_id, type)
389
+ TYPES[type_id] = type
390
+ end
391
+
392
+ def self.alias_type(new, old)
393
+ TYPES[new] = TYPES[old]
394
+ end
395
+
396
+ register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
397
+ register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
398
+ alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
399
+ alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
400
+
401
+ register_type Mysql::Field::TYPE_VAR_STRING, Fields::Identity.new
402
+ register_type Mysql::Field::TYPE_BLOB, Fields::Identity.new
403
+ register_type Mysql::Field::TYPE_DATE, Fields::Date.new
404
+ register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
405
+ register_type Mysql::Field::TYPE_TIME, Fields::Time.new
406
+ register_type Mysql::Field::TYPE_FLOAT, Fields::Float.new
407
+
408
+ Mysql::Field.constants.grep(/TYPE/).map { |class_name|
409
+ Mysql::Field.const_get class_name
410
+ }.reject { |const| TYPES.key? const }.each do |const|
411
+ register_type const, Fields::Identity.new
412
+ end
413
+ end
414
+
415
+ def exec_without_stmt(sql, name = 'SQL') # :nodoc:
416
+ # Some queries, like SHOW CREATE TABLE don't work through the prepared
417
+ # statement API. For those queries, we need to use this method. :'(
418
+ log(sql, name) do
419
+ result = @connection.query(sql)
420
+ affected_rows = @connection.affected_rows
421
+
422
+ if result
423
+ types = {}
424
+ result.fetch_fields.each { |field|
425
+ if field.decimals > 0
426
+ types[field.name] = Fields::Decimal.new
427
+ else
428
+ types[field.name] = Fields::TYPES.fetch(field.type) {
429
+ Fields::Identity.new
430
+ }
431
+ end
432
+ }
433
+ result_set = ActiveRecord::Result.new(types.keys, result.to_a, types)
434
+ result.free
435
+ else
436
+ result_set = ActiveRecord::Result.new([], [])
437
+ end
438
+
439
+ [result_set, affected_rows]
440
+ end
441
+ end
442
+
443
+ def execute_and_free(sql, name = nil)
444
+ result = execute(sql, name)
445
+ ret = yield result
446
+ result.free
447
+ ret
101
448
  end
102
-
103
- def recreate_database(name)
104
- drop_database(name)
105
- create_database(name)
449
+
450
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
451
+ super sql, name
452
+ id_value || @connection.insert_id
106
453
  end
107
-
108
- def drop_database(name)
109
- execute "DROP DATABASE IF EXISTS #{name}"
454
+ alias :create :insert_sql
455
+
456
+ def exec_delete(sql, name, binds)
457
+ affected_rows = 0
458
+
459
+ exec_query(sql, name, binds) do |n|
460
+ affected_rows = n
461
+ end
462
+
463
+ affected_rows
110
464
  end
111
-
112
- def create_database(name)
113
- execute "CREATE DATABASE #{name}"
465
+ alias :exec_update :exec_delete
466
+
467
+ def begin_db_transaction #:nodoc:
468
+ exec_query "BEGIN"
469
+ rescue Mysql::Error
470
+ # Transactions aren't supported
114
471
  end
115
-
472
+
116
473
  private
117
- 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) }
123
- rows
474
+
475
+ def exec_stmt(sql, name, binds)
476
+ cache = {}
477
+ log(sql, name, binds) do
478
+ if binds.empty?
479
+ stmt = @connection.prepare(sql)
480
+ else
481
+ cache = @statements[sql] ||= {
482
+ :stmt => @connection.prepare(sql)
483
+ }
484
+ stmt = cache[:stmt]
485
+ end
486
+
487
+ begin
488
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
489
+ rescue Mysql::Error => e
490
+ # Older versions of MySQL leave the prepared statement in a bad
491
+ # place when an error occurs. To support older mysql versions, we
492
+ # need to close the statement and delete the statement from the
493
+ # cache.
494
+ stmt.close
495
+ @statements.delete sql
496
+ raise e
497
+ end
498
+
499
+ cols = nil
500
+ if metadata = stmt.result_metadata
501
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
502
+ field.name
503
+ }
504
+ end
505
+
506
+ result_set = ActiveRecord::Result.new(cols, stmt.to_a) if cols
507
+ affected_rows = stmt.affected_rows
508
+
509
+ stmt.result_metadata.free if cols
510
+ stmt.free_result
511
+ stmt.close if binds.empty?
512
+
513
+ [result_set, affected_rows]
514
+ end
515
+ end
516
+
517
+ def connect
518
+ encoding = @config[:encoding]
519
+ if encoding
520
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
521
+ end
522
+
523
+ if @config[:sslca] || @config[:sslkey]
524
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
124
525
  end
526
+
527
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
528
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
529
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
530
+
531
+ @connection.real_connect(*@connection_options)
532
+
533
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
534
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
535
+
536
+ configure_connection
537
+ end
538
+
539
+ # Many Rails applications monkey-patch a replacement of the configure_connection method
540
+ # and don't call 'super', so leave this here even though it looks superfluous.
541
+ def configure_connection
542
+ super
543
+ end
544
+
545
+ def select(sql, name = nil, binds = [])
546
+ @connection.query_with_result = true
547
+ rows = exec_query(sql, name, binds)
548
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
549
+ rows
550
+ end
551
+
552
+ # Returns the version of the connected MySQL server.
553
+ def version
554
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
555
+ end
125
556
  end
126
557
  end
127
558
  end
128
-
129
- rescue LoadError
130
- # MySQL is not available, so neither should the adapter be
131
- end