activerecord 3.1.10 → 4.2.11

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.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,248 +1,179 @@
1
- # encoding: utf-8
2
- require 'arel/visitors/bind_visitor'
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
3
2
 
4
- gem 'mysql2', '~> 0.3.10'
3
+ gem 'mysql2', '>= 0.3.13', '< 0.6.0'
5
4
  require 'mysql2'
6
5
 
7
6
  module ActiveRecord
8
- class Base
7
+ module ConnectionHandling # :nodoc:
9
8
  # Establishes a connection to the database that's used by all Active Record objects.
10
- def self.mysql2_connection(config)
9
+ def mysql2_connection(config)
10
+ config = config.symbolize_keys
11
+
11
12
  config[:username] = 'root' if config[:username].nil?
12
13
 
13
14
  if Mysql2::Client.const_defined? :FOUND_ROWS
14
15
  config[:flags] = Mysql2::Client::FOUND_ROWS
15
16
  end
16
17
 
17
- client = Mysql2::Client.new(config.symbolize_keys)
18
+ client = Mysql2::Client.new(config)
18
19
  options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
19
20
  ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
21
+ rescue Mysql2::Error => error
22
+ if error.message.include?("Unknown database")
23
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
24
+ else
25
+ raise
26
+ end
20
27
  end
21
28
  end
22
29
 
23
30
  module ConnectionAdapters
24
- class Mysql2IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
25
- end
31
+ class Mysql2Adapter < AbstractMysqlAdapter
32
+ ADAPTER_NAME = 'Mysql2'.freeze
26
33
 
27
- class Mysql2Column < Column
28
- BOOL = "tinyint(1)"
29
- def extract_default(default)
30
- if sql_type =~ /blob/i || type == :text
31
- if default.blank?
32
- return null ? nil : ''
33
- else
34
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
35
- end
36
- elsif missing_default_forged_as_empty_string?(default)
37
- nil
34
+ def initialize(connection, logger, connection_options, config)
35
+ super
36
+ @prepared_statements = false
37
+ configure_connection
38
+ end
39
+
40
+ MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
41
+ def initialize_schema_migrations_table
42
+ if charset == 'utf8mb4'
43
+ ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4)
38
44
  else
39
- super
45
+ ActiveRecord::SchemaMigration.create_table
40
46
  end
41
47
  end
42
48
 
43
- def has_default?
44
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
45
- super
49
+ def supports_explain?
50
+ true
46
51
  end
47
52
 
48
- private
49
- def simplified_type(field_type)
50
- return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
51
-
52
- case field_type
53
- when /enum/i, /set/i then :string
54
- when /year/i then :integer
55
- when /bit/i then :binary
56
- else
57
- super
58
- end
59
- end
53
+ # HELPER METHODS ===========================================
60
54
 
61
- def extract_limit(sql_type)
62
- case sql_type
63
- when /blob|text/i
64
- case sql_type
65
- when /tiny/i
66
- 255
67
- when /medium/i
68
- 16777215
69
- when /long/i
70
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
71
- else
72
- super # we could return 65535 here, but we leave it undecorated by default
73
- end
74
- when /^bigint/i; 8
75
- when /^int/i; 4
76
- when /^mediumint/i; 3
77
- when /^smallint/i; 2
78
- when /^tinyint/i; 1
79
- else
80
- super
55
+ def each_hash(result) # :nodoc:
56
+ if block_given?
57
+ result.each(:as => :hash, :symbolize_keys => true) do |row|
58
+ yield row
81
59
  end
60
+ else
61
+ to_enum(:each_hash, result)
82
62
  end
83
-
84
- # MySQL misreports NOT NULL column default when none is given.
85
- # We can't detect this for columns which may have a legitimate ''
86
- # default (string) but we can for others (integer, datetime, boolean,
87
- # and the rest).
88
- #
89
- # Test whether the column has default '', is not null, and is not
90
- # a type allowing default ''.
91
- def missing_default_forged_as_empty_string?(default)
92
- type != :string && !null && default == ''
93
- end
94
- end
95
-
96
- class Mysql2Adapter < AbstractAdapter
97
- cattr_accessor :emulate_booleans
98
- self.emulate_booleans = true
99
-
100
- ADAPTER_NAME = 'Mysql2'
101
- PRIMARY = "PRIMARY"
102
-
103
- LOST_CONNECTION_ERROR_MESSAGES = [
104
- "Server shutdown in progress",
105
- "Broken pipe",
106
- "Lost connection to MySQL server during query",
107
- "MySQL server has gone away" ]
108
-
109
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
110
-
111
- NATIVE_DATABASE_TYPES = {
112
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
113
- :string => { :name => "varchar", :limit => 255 },
114
- :text => { :name => "text" },
115
- :integer => { :name => "int", :limit => 4 },
116
- :float => { :name => "float" },
117
- :decimal => { :name => "decimal" },
118
- :datetime => { :name => "datetime" },
119
- :timestamp => { :name => "datetime" },
120
- :time => { :name => "time" },
121
- :date => { :name => "date" },
122
- :binary => { :name => "blob" },
123
- :boolean => { :name => "tinyint", :limit => 1 }
124
- }
125
-
126
- def initialize(connection, logger, connection_options, config)
127
- super(connection, logger)
128
- @connection_options, @config = connection_options, config
129
- @quoted_column_names, @quoted_table_names = {}, {}
130
- configure_connection
131
- end
132
-
133
- class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
134
- include Arel::Visitors::BindVisitor
135
63
  end
136
64
 
137
- def self.visitor_for(pool) # :nodoc:
138
- BindSubstitution.new pool
65
+ def error_number(exception)
66
+ exception.error_number if exception.respond_to?(:error_number)
139
67
  end
140
68
 
141
- def adapter_name
142
- ADAPTER_NAME
143
- end
69
+ #--
70
+ # QUOTING ==================================================
71
+ #++
144
72
 
145
- # Returns true, since this connection adapter supports migrations.
146
- def supports_migrations?
147
- true
73
+ def quote_string(string)
74
+ @connection.escape(string)
148
75
  end
149
76
 
150
- def supports_primary_key?
151
- true
152
- end
77
+ #--
78
+ # CONNECTION MANAGEMENT ====================================
79
+ #++
153
80
 
154
- # Returns true, since this connection adapter supports savepoints.
155
- def supports_savepoints?
156
- true
81
+ def active?
82
+ @connection.ping
157
83
  end
158
84
 
159
- def native_database_types
160
- NATIVE_DATABASE_TYPES
85
+ def reconnect!
86
+ super
87
+ disconnect!
88
+ connect
161
89
  end
90
+ alias :reset! :reconnect!
162
91
 
163
- # QUOTING ==================================================
164
-
165
- def quote(value, column = nil)
166
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
167
- s = column.class.string_to_binary(value).unpack("H*")[0]
168
- "x'#{s}'"
169
- elsif value.kind_of?(BigDecimal)
170
- value.to_s("F")
171
- else
172
- super
173
- end
92
+ # Disconnects from the database if already connected.
93
+ # Otherwise, this method does nothing.
94
+ def disconnect!
95
+ super
96
+ @connection.close
174
97
  end
175
98
 
176
- def quote_column_name(name) #:nodoc:
177
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
178
- end
99
+ #--
100
+ # DATABASE STATEMENTS ======================================
101
+ #++
179
102
 
180
- def quote_table_name(name) #:nodoc:
181
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
182
- end
103
+ def explain(arel, binds = [])
104
+ sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
105
+ start = Time.now
106
+ result = exec_query(sql, 'EXPLAIN', binds)
107
+ elapsed = Time.now - start
183
108
 
184
- def quote_string(string)
185
- @connection.escape(string)
109
+ ExplainPrettyPrinter.new.pp(result, elapsed)
186
110
  end
187
111
 
188
- def quoted_true
189
- QUOTED_TRUE
190
- end
112
+ class ExplainPrettyPrinter # :nodoc:
113
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
114
+ # MySQL shell:
115
+ #
116
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
117
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
118
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
119
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
120
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
121
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
122
+ # 2 rows in set (0.00 sec)
123
+ #
124
+ # This is an exercise in Ruby hyperrealism :).
125
+ def pp(result, elapsed)
126
+ widths = compute_column_widths(result)
127
+ separator = build_separator(widths)
191
128
 
192
- def quoted_false
193
- QUOTED_FALSE
194
- end
129
+ pp = []
195
130
 
196
- def substitute_at(column, index)
197
- Arel::Nodes::BindParam.new "\0"
198
- end
131
+ pp << separator
132
+ pp << build_cells(result.columns, widths)
133
+ pp << separator
199
134
 
200
- # REFERENTIAL INTEGRITY ====================================
135
+ result.rows.each do |row|
136
+ pp << build_cells(row, widths)
137
+ end
201
138
 
202
- def disable_referential_integrity(&block) #:nodoc:
203
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
139
+ pp << separator
140
+ pp << build_footer(result.rows.length, elapsed)
204
141
 
205
- begin
206
- update("SET FOREIGN_KEY_CHECKS = 0")
207
- yield
208
- ensure
209
- update("SET FOREIGN_KEY_CHECKS = #{old}")
142
+ pp.join("\n") + "\n"
210
143
  end
211
- end
212
-
213
- # CONNECTION MANAGEMENT ====================================
214
144
 
215
- def active?
216
- return false unless @connection
217
- @connection.ping
218
- end
145
+ private
219
146
 
220
- def reconnect!
221
- disconnect!
222
- connect
223
- end
147
+ def compute_column_widths(result)
148
+ [].tap do |widths|
149
+ result.columns.each_with_index do |column, i|
150
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
151
+ widths << cells_in_column.map(&:length).max
152
+ end
153
+ end
154
+ end
224
155
 
225
- # this is set to true in 2.3, but we don't want it to be
226
- def requires_reloading?
227
- false
228
- end
156
+ def build_separator(widths)
157
+ padding = 1
158
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
159
+ end
229
160
 
230
- # Disconnects from the database if already connected.
231
- # Otherwise, this method does nothing.
232
- def disconnect!
233
- unless @connection.nil?
234
- @connection.close
235
- @connection = nil
161
+ def build_cells(items, widths)
162
+ cells = []
163
+ items.each_with_index do |item, i|
164
+ item = 'NULL' if item.nil?
165
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
166
+ cells << item.to_s.send(justifier, widths[i])
167
+ end
168
+ '| ' + cells.join(' | ') + ' |'
236
169
  end
237
- end
238
170
 
239
- def reset!
240
- disconnect!
241
- connect
171
+ def build_footer(nrows, elapsed)
172
+ rows_label = nrows == 1 ? 'row' : 'rows'
173
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
174
+ end
242
175
  end
243
176
 
244
- # DATABASE STATEMENTS ======================================
245
-
246
177
  # FIXME: re-enable the following once a "better" query_cache solution is in core
247
178
  #
248
179
  # The overrides below perform much better than the originals in AbstractAdapter
@@ -252,7 +183,7 @@ module ActiveRecord
252
183
  # # as values.
253
184
  # def select_one(sql, name = nil)
254
185
  # result = execute(sql, name)
255
- # result.each(:as => :hash) do |r|
186
+ # result.each(as: :hash) do |r|
256
187
  # return r
257
188
  # end
258
189
  # end
@@ -273,7 +204,7 @@ module ActiveRecord
273
204
 
274
205
  # Returns an array of arrays containing the field values.
275
206
  # Order is the same as that returned by +columns+.
276
- def select_rows(sql, name = nil)
207
+ def select_rows(sql, name = nil, binds = [])
277
208
  execute(sql, name).to_a
278
209
  end
279
210
 
@@ -282,26 +213,24 @@ module ActiveRecord
282
213
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
283
214
  # made since we established the connection
284
215
  @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
285
- if name == :skip_logging
286
- @connection.query(sql)
287
- else
288
- log(sql, name) { @connection.query(sql) }
289
- end
290
- rescue ActiveRecord::StatementInvalid => exception
291
- if exception.message.split(":").first =~ /Packets out of order/
292
- 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."
293
- else
294
- raise
295
- end
216
+
217
+ super
296
218
  end
297
219
 
220
+ def exec_query(sql, name = 'SQL', binds = [])
221
+ result = execute(sql, name)
222
+ ActiveRecord::Result.new(result.fields, result.to_a)
223
+ end
224
+
225
+ alias exec_without_stmt exec_query
226
+
298
227
  def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
299
228
  super
300
229
  id_value || @connection.last_id
301
230
  end
302
231
  alias :create :insert_sql
303
232
 
304
- def exec_insert(sql, name, binds)
233
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
305
234
  execute to_sql(sql, binds), name
306
235
  end
307
236
 
@@ -315,416 +244,25 @@ module ActiveRecord
315
244
  @connection.last_id
316
245
  end
317
246
 
318
- def update_sql(sql, name = nil)
319
- super
320
- @connection.affected_rows
321
- end
322
-
323
- def begin_db_transaction
324
- execute "BEGIN"
325
- rescue Exception
326
- # Transactions aren't supported
327
- end
328
-
329
- def commit_db_transaction
330
- execute "COMMIT"
331
- rescue Exception
332
- # Transactions aren't supported
333
- end
334
-
335
- def rollback_db_transaction
336
- execute "ROLLBACK"
337
- rescue Exception
338
- # Transactions aren't supported
339
- end
340
-
341
- def create_savepoint
342
- execute("SAVEPOINT #{current_savepoint_name}")
343
- end
344
-
345
- def rollback_to_savepoint
346
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
347
- end
348
-
349
- def release_savepoint
350
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
351
- end
352
-
353
- def add_limit_offset!(sql, options)
354
- limit, offset = options[:limit], options[:offset]
355
- if limit && offset
356
- sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
357
- elsif limit
358
- sql << " LIMIT #{sanitize_limit(limit)}"
359
- elsif offset
360
- sql << " OFFSET #{offset.to_i}"
361
- end
362
- sql
363
- end
364
- deprecate :add_limit_offset!
365
-
366
- # SCHEMA STATEMENTS ========================================
367
-
368
- def structure_dump
369
- if supports_views?
370
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
371
- else
372
- sql = "SHOW TABLES"
373
- end
374
-
375
- select_all(sql).inject("") do |structure, table|
376
- table.delete('Table_type')
377
- structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
378
- end
379
- end
380
-
381
- # Drops the database specified on the +name+ attribute
382
- # and creates it again using the provided +options+.
383
- def recreate_database(name, options = {})
384
- drop_database(name)
385
- create_database(name, options)
386
- end
387
-
388
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
389
- # Charset defaults to utf8.
390
- #
391
- # Example:
392
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
393
- # create_database 'matt_development'
394
- # create_database 'matt_development', :charset => :big5
395
- def create_database(name, options = {})
396
- if options[:collation]
397
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
398
- else
399
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
400
- end
401
- end
402
-
403
- # Drops a MySQL database.
404
- #
405
- # Example:
406
- # drop_database('sebastian_development')
407
- def drop_database(name) #:nodoc:
408
- execute "DROP DATABASE IF EXISTS `#{name}`"
409
- end
410
-
411
- def current_database
412
- select_value 'SELECT DATABASE() as db'
413
- end
414
-
415
- # Returns the database character set.
416
- def charset
417
- show_variable 'character_set_database'
418
- end
419
-
420
- # Returns the database collation strategy.
421
- def collation
422
- show_variable 'collation_database'
423
- end
424
-
425
- def tables(name = nil, database = nil) #:nodoc:
426
- sql = "SHOW TABLES "
427
- sql << "IN #{quote_table_name(database)} " if database
428
-
429
- execute(sql, 'SCHEMA').collect do |field|
430
- field.first
431
- end
432
- end
433
-
434
- def table_exists?(name)
435
- return true if super
436
-
437
- name = name.to_s
438
- schema, table = name.split('.', 2)
439
-
440
- unless table # A table was provided without a schema
441
- table = schema
442
- schema = nil
443
- end
444
-
445
- tables(nil, schema).include? table
446
- end
447
-
448
- def drop_table(table_name, options = {})
449
- super(table_name, options)
450
- end
451
-
452
- # Returns an array of indexes for the given table.
453
- def indexes(table_name, name = nil)
454
- indexes = []
455
- current_index = nil
456
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
457
- result.each(:symbolize_keys => true, :as => :hash) do |row|
458
- if current_index != row[:Key_name]
459
- next if row[:Key_name] == PRIMARY # skip the primary key
460
- current_index = row[:Key_name]
461
- indexes << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
462
- end
463
-
464
- indexes.last.columns << row[:Column_name]
465
- indexes.last.lengths << row[:Sub_part]
466
- end
467
- indexes
468
- end
469
-
470
- # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+.
471
- def columns(table_name, name = nil)
472
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
473
- columns = []
474
- result = execute(sql, 'SCHEMA')
475
- result.each(:symbolize_keys => true, :as => :hash) { |field|
476
- columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
477
- }
478
- columns
479
- end
480
-
481
- def create_table(table_name, options = {})
482
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
483
- end
484
-
485
- # Renames a table.
486
- #
487
- # Example:
488
- # rename_table('octopuses', 'octopi')
489
- def rename_table(table_name, new_name)
490
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
491
- end
492
-
493
- def add_column(table_name, column_name, type, options = {})
494
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
495
- add_column_options!(add_column_sql, options)
496
- add_column_position!(add_column_sql, options)
497
- execute(add_column_sql)
498
- end
499
-
500
- def change_column_default(table_name, column_name, default)
501
- column = column_for(table_name, column_name)
502
- change_column table_name, column_name, column.sql_type, :default => default
503
- end
504
-
505
- def change_column_null(table_name, column_name, null, default = nil)
506
- column = column_for(table_name, column_name)
507
-
508
- unless null || default.nil?
509
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
510
- end
511
-
512
- change_column table_name, column_name, column.sql_type, :null => null
513
- end
514
-
515
- def change_column(table_name, column_name, type, options = {})
516
- column = column_for(table_name, column_name)
517
-
518
- unless options_include_default?(options)
519
- options[:default] = column.default
520
- end
521
-
522
- unless options.has_key?(:null)
523
- options[:null] = column.null
524
- end
525
-
526
- 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])}"
527
- add_column_options!(change_column_sql, options)
528
- add_column_position!(change_column_sql, options)
529
- execute(change_column_sql)
530
- end
531
-
532
- def rename_column(table_name, column_name, new_column_name)
533
- options = {}
534
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
535
- options[:default] = column.default
536
- options[:null] = column.null
537
- else
538
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
539
- end
540
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
541
- rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
542
- add_column_options!(rename_column_sql, options)
543
- execute(rename_column_sql)
544
- end
545
-
546
- # Maps logical Rails types to MySQL-specific data types.
547
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
548
- case type.to_s
549
- when 'integer'
550
- case limit
551
- when 1; 'tinyint'
552
- when 2; 'smallint'
553
- when 3; 'mediumint'
554
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
555
- when 5..8; 'bigint'
556
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
557
- end
558
- when 'text'
559
- case limit
560
- when 0..0xff; 'tinytext'
561
- when nil, 0x100..0xffff; 'text'
562
- when 0x10000..0xffffff; 'mediumtext'
563
- when 0x1000000..0xffffffff; 'longtext'
564
- else raise(ActiveRecordError, "No text type has character length #{limit}")
565
- end
566
- else
567
- super
568
- end
569
- end
570
-
571
- def add_column_position!(sql, options)
572
- if options[:first]
573
- sql << " FIRST"
574
- elsif options[:after]
575
- sql << " AFTER #{quote_column_name(options[:after])}"
576
- end
577
- end
578
-
579
- # SHOW VARIABLES LIKE 'name'.
580
- def show_variable(name)
581
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
582
- variables.first['Value'] unless variables.empty?
583
- end
584
-
585
- # Returns a table's primary key and belonging sequence.
586
- def pk_and_sequence_for(table)
587
- result = execute("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA')
588
- create_table = result.first[1]
589
-
590
- if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
591
- keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
592
- keys.length == 1 ? [keys.first, nil] : nil
593
- else
594
- nil
595
- end
596
- end
597
-
598
- # Returns just a table's primary key
599
- def primary_key(table)
600
- pk_and_sequence = pk_and_sequence_for(table)
601
- pk_and_sequence && pk_and_sequence.first
602
- end
247
+ private
603
248
 
604
- def case_sensitive_equality_operator
605
- "= BINARY"
249
+ def connect
250
+ @connection = Mysql2::Client.new(@config)
251
+ configure_connection
606
252
  end
607
- deprecate :case_sensitive_equality_operator
608
253
 
609
- def case_sensitive_modifier(node)
610
- Arel::Nodes::Bin.new(node)
254
+ def configure_connection
255
+ @connection.query_options.merge!(:as => :array)
256
+ super
611
257
  end
612
258
 
613
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
614
- where_sql
259
+ def full_version
260
+ @full_version ||= @connection.server_info[:version]
615
261
  end
616
262
 
617
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
618
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
619
- # these, we must use a subquery. However, MySQL is too stupid to create a
620
- # temporary table for this automatically, so we have to give it some prompting
621
- # in the form of a subsubquery. Ugh!
622
- def join_to_update(update, select) #:nodoc:
623
- if select.limit || select.offset || select.orders.any?
624
- subsubselect = select.clone
625
- subsubselect.projections = [update.key]
626
-
627
- subselect = Arel::SelectManager.new(select.engine)
628
- subselect.project Arel.sql(update.key.name)
629
- subselect.from subsubselect.as('__active_record_temp')
630
-
631
- update.where update.key.in(subselect)
632
- else
633
- update.table select.source
634
- update.wheres = select.constraints
635
- end
263
+ def set_field_encoding field_name
264
+ field_name
636
265
  end
637
-
638
- protected
639
- def quoted_columns_for_index(column_names, options = {})
640
- length = options[:length] if options.is_a?(Hash)
641
-
642
- quoted_column_names = case length
643
- when Hash
644
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
645
- when Fixnum
646
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
647
- else
648
- column_names.map {|name| quote_column_name(name) }
649
- end
650
- end
651
-
652
- def translate_exception(exception, message)
653
- return super unless exception.respond_to?(:error_number)
654
-
655
- case exception.error_number
656
- when 1062
657
- RecordNotUnique.new(message, exception)
658
- when 1452
659
- InvalidForeignKey.new(message, exception)
660
- else
661
- super
662
- end
663
- end
664
-
665
- private
666
- def connect
667
- @connection = Mysql2::Client.new(@config)
668
- configure_connection
669
- end
670
-
671
- def configure_connection
672
- @connection.query_options.merge!(:as => :array)
673
-
674
- # By default, MySQL 'where id is null' selects the last inserted id.
675
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
676
- variable_assignments = ['SQL_AUTO_IS_NULL=0']
677
- encoding = @config[:encoding]
678
-
679
- # make sure we set the encoding
680
- variable_assignments << "NAMES '#{encoding}'" if encoding
681
-
682
- # increase timeout so mysql server doesn't disconnect us
683
- wait_timeout = @config[:wait_timeout]
684
- wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
685
- variable_assignments << "@@wait_timeout = #{wait_timeout}"
686
-
687
- execute("SET #{variable_assignments.join(', ')}", :skip_logging)
688
- end
689
-
690
- # Returns an array of record hashes with the column names as keys and
691
- # column values as values.
692
- def select(sql, name = nil, binds = [])
693
- exec_query(sql, name).to_a
694
- end
695
-
696
- def exec_query(sql, name = 'SQL', binds = [])
697
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
698
-
699
- log(sql, name, binds) do
700
- begin
701
- result = @connection.query(sql)
702
- rescue ActiveRecord::StatementInvalid => exception
703
- if exception.message.split(":").first =~ /Packets out of order/
704
- 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."
705
- else
706
- raise
707
- end
708
- end
709
-
710
- ActiveRecord::Result.new(result.fields, result.to_a)
711
- end
712
- end
713
-
714
- def supports_views?
715
- version[0] >= 5
716
- end
717
-
718
- def version
719
- @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
720
- end
721
-
722
- def column_for(table_name, column_name)
723
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
724
- raise "No such column: #{table_name}.#{column_name}"
725
- end
726
- column
727
- end
728
266
  end
729
267
  end
730
268
  end