activerecord 3.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 (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -0,0 +1,318 @@
1
+ require 'set'
2
+
3
+ module ActiveRecord
4
+ # :stopdoc:
5
+ module ConnectionAdapters
6
+ # An abstract definition of a column in a table.
7
+ class Column
8
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set
9
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
10
+
11
+ module Format
12
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
13
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
14
+ end
15
+
16
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
17
+ attr_accessor :primary, :coder
18
+
19
+ alias :encoded? :coder
20
+
21
+ # Instantiates a new column in the table.
22
+ #
23
+ # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
24
+ # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
25
+ # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
26
+ # <tt>company_name varchar(60)</tt>.
27
+ # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
28
+ # +null+ determines if this column allows +NULL+ values.
29
+ def initialize(name, default, sql_type = nil, null = true)
30
+ @name = name
31
+ @sql_type = sql_type
32
+ @null = null
33
+ @limit = extract_limit(sql_type)
34
+ @precision = extract_precision(sql_type)
35
+ @scale = extract_scale(sql_type)
36
+ @type = simplified_type(sql_type)
37
+ @default = extract_default(default)
38
+ @primary = nil
39
+ @coder = nil
40
+ end
41
+
42
+ # Returns +true+ if the column is either of type string or text.
43
+ def text?
44
+ type == :string || type == :text
45
+ end
46
+
47
+ # Returns +true+ if the column is either of type integer, float or decimal.
48
+ def number?
49
+ type == :integer || type == :float || type == :decimal
50
+ end
51
+
52
+ def has_default?
53
+ !default.nil?
54
+ end
55
+
56
+ # Returns the Ruby class that corresponds to the abstract data type.
57
+ def klass
58
+ case type
59
+ when :integer then Fixnum
60
+ when :float then Float
61
+ when :decimal then BigDecimal
62
+ when :datetime, :timestamp, :time then Time
63
+ when :date then Date
64
+ when :text, :string, :binary then String
65
+ when :boolean then Object
66
+ end
67
+ end
68
+
69
+ def binary?
70
+ type == :binary
71
+ end
72
+
73
+ # Casts a Ruby value to something appropriate for writing to the database.
74
+ def type_cast_for_write(value)
75
+ return value unless number?
76
+
77
+ case value
78
+ when FalseClass
79
+ 0
80
+ when TrueClass
81
+ 1
82
+ when String
83
+ value.presence
84
+ else
85
+ value
86
+ end
87
+ end
88
+
89
+ # Casts value (which is a String) to an appropriate instance.
90
+ def type_cast(value)
91
+ return nil if value.nil?
92
+ return coder.load(value) if encoded?
93
+
94
+ klass = self.class
95
+
96
+ case type
97
+ when :string, :text then value
98
+ when :integer then klass.value_to_integer(value)
99
+ when :float then value.to_f
100
+ when :decimal then klass.value_to_decimal(value)
101
+ when :datetime, :timestamp then klass.string_to_time(value)
102
+ when :time then klass.string_to_dummy_time(value)
103
+ when :date then klass.value_to_date(value)
104
+ when :binary then klass.binary_to_string(value)
105
+ when :boolean then klass.value_to_boolean(value)
106
+ else value
107
+ end
108
+ end
109
+
110
+ def type_cast_code(var_name)
111
+ message = "Column#type_cast_code is deprecated in favor of using Column#type_cast only, " \
112
+ "and it is going to be removed in future Rails versions."
113
+ ActiveSupport::Deprecation.warn message
114
+
115
+ klass = self.class.name
116
+
117
+ case type
118
+ when :string, :text then var_name
119
+ when :integer then "#{klass}.value_to_integer(#{var_name})"
120
+ when :float then "#{var_name}.to_f"
121
+ when :decimal then "#{klass}.value_to_decimal(#{var_name})"
122
+ when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
123
+ when :time then "#{klass}.string_to_dummy_time(#{var_name})"
124
+ when :date then "#{klass}.value_to_date(#{var_name})"
125
+ when :binary then "#{klass}.binary_to_string(#{var_name})"
126
+ when :boolean then "#{klass}.value_to_boolean(#{var_name})"
127
+ when :hstore then "#{klass}.string_to_hstore(#{var_name})"
128
+ when :inet, :cidr then "#{klass}.string_to_cidr(#{var_name})"
129
+ when :json then "#{klass}.string_to_json(#{var_name})"
130
+ else var_name
131
+ end
132
+ end
133
+
134
+ # Returns the human name of the column name.
135
+ #
136
+ # ===== Examples
137
+ # Column.new('sales_stage', ...).human_name # => 'Sales stage'
138
+ def human_name
139
+ Base.human_attribute_name(@name)
140
+ end
141
+
142
+ def extract_default(default)
143
+ type_cast(default)
144
+ end
145
+
146
+ # Used to convert from Strings to BLOBs
147
+ def string_to_binary(value)
148
+ self.class.string_to_binary(value)
149
+ end
150
+
151
+ class << self
152
+ # Used to convert from Strings to BLOBs
153
+ def string_to_binary(value)
154
+ value
155
+ end
156
+
157
+ # Used to convert from BLOBs to Strings
158
+ def binary_to_string(value)
159
+ value
160
+ end
161
+
162
+ def value_to_date(value)
163
+ if value.is_a?(String)
164
+ return nil if value.empty?
165
+ fast_string_to_date(value) || fallback_string_to_date(value)
166
+ elsif value.respond_to?(:to_date)
167
+ value.to_date
168
+ else
169
+ value
170
+ end
171
+ end
172
+
173
+ def string_to_time(string)
174
+ return string unless string.is_a?(String)
175
+ return nil if string.empty?
176
+
177
+ fast_string_to_time(string) || fallback_string_to_time(string)
178
+ end
179
+
180
+ def string_to_dummy_time(string)
181
+ return string unless string.is_a?(String)
182
+ return nil if string.empty?
183
+
184
+ dummy_time_string = "2000-01-01 #{string}"
185
+
186
+ fast_string_to_time(dummy_time_string) || begin
187
+ time_hash = Date._parse(dummy_time_string)
188
+ return nil if time_hash[:hour].nil?
189
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
190
+ end
191
+ end
192
+
193
+ # convert something to a boolean
194
+ def value_to_boolean(value)
195
+ if value.is_a?(String) && value.empty?
196
+ nil
197
+ else
198
+ TRUE_VALUES.include?(value)
199
+ end
200
+ end
201
+
202
+ # Used to convert values to integer.
203
+ # handle the case when an integer column is used to store boolean values
204
+ def value_to_integer(value)
205
+ case value
206
+ when TrueClass, FalseClass
207
+ value ? 1 : 0
208
+ else
209
+ value.to_i rescue nil
210
+ end
211
+ end
212
+
213
+ # convert something to a BigDecimal
214
+ def value_to_decimal(value)
215
+ # Using .class is faster than .is_a? and
216
+ # subclasses of BigDecimal will be handled
217
+ # in the else clause
218
+ if value.class == BigDecimal
219
+ value
220
+ elsif value.respond_to?(:to_d)
221
+ value.to_d
222
+ else
223
+ value.to_s.to_d
224
+ end
225
+ end
226
+
227
+ protected
228
+ # '0.123456' -> 123456
229
+ # '1.123456' -> 123456
230
+ def microseconds(time)
231
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
232
+ end
233
+
234
+ def new_date(year, mon, mday)
235
+ if year && year != 0
236
+ Date.new(year, mon, mday) rescue nil
237
+ end
238
+ end
239
+
240
+ def new_time(year, mon, mday, hour, min, sec, microsec)
241
+ # Treat 0000-00-00 00:00:00 as nil.
242
+ return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
243
+
244
+ Time.send(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
245
+ end
246
+
247
+ def fast_string_to_date(string)
248
+ if string =~ Format::ISO_DATE
249
+ new_date $1.to_i, $2.to_i, $3.to_i
250
+ end
251
+ end
252
+
253
+ # Doesn't handle time zones.
254
+ def fast_string_to_time(string)
255
+ if string =~ Format::ISO_DATETIME
256
+ microsec = ($7.to_r * 1_000_000).to_i
257
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
258
+ end
259
+ end
260
+
261
+ def fallback_string_to_date(string)
262
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
263
+ end
264
+
265
+ def fallback_string_to_time(string)
266
+ time_hash = Date._parse(string)
267
+ time_hash[:sec_fraction] = microseconds(time_hash)
268
+
269
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
270
+ end
271
+ end
272
+
273
+ private
274
+ def extract_limit(sql_type)
275
+ $1.to_i if sql_type =~ /\((.*)\)/
276
+ end
277
+
278
+ def extract_precision(sql_type)
279
+ $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
280
+ end
281
+
282
+ def extract_scale(sql_type)
283
+ case sql_type
284
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
285
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
286
+ end
287
+ end
288
+
289
+ def simplified_type(field_type)
290
+ case field_type
291
+ when /int/i
292
+ :integer
293
+ when /float|double/i
294
+ :float
295
+ when /decimal|numeric|number/i
296
+ extract_scale(field_type) == 0 ? :integer : :decimal
297
+ when /datetime/i
298
+ :datetime
299
+ when /timestamp/i
300
+ :timestamp
301
+ when /time/i
302
+ :time
303
+ when /date/i
304
+ :date
305
+ when /clob/i, /text/i
306
+ :text
307
+ when /blob/i, /binary/i
308
+ :binary
309
+ when /char/i, /string/i
310
+ :string
311
+ when /boolean/i
312
+ :boolean
313
+ end
314
+ end
315
+ end
316
+ end
317
+ # :startdoc:
318
+ end
@@ -0,0 +1,96 @@
1
+ require 'uri'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class ConnectionSpecification #:nodoc:
6
+ attr_reader :config, :adapter_method
7
+
8
+ def initialize(config, adapter_method)
9
+ @config, @adapter_method = config, adapter_method
10
+ end
11
+
12
+ def initialize_dup(original)
13
+ @config = original.config.dup
14
+ end
15
+
16
+ ##
17
+ # Builds a ConnectionSpecification from user input
18
+ class Resolver # :nodoc:
19
+ attr_reader :config, :klass, :configurations
20
+
21
+ def initialize(config, configurations)
22
+ @config = config
23
+ @configurations = configurations
24
+ end
25
+
26
+ def spec
27
+ case config
28
+ when nil
29
+ raise AdapterNotSpecified unless defined?(Rails.env)
30
+ resolve_string_connection Rails.env
31
+ when Symbol, String
32
+ resolve_string_connection config.to_s
33
+ when Hash
34
+ resolve_hash_connection config
35
+ end
36
+ end
37
+
38
+ private
39
+ def resolve_string_connection(spec) # :nodoc:
40
+ hash = configurations.fetch(spec) do |k|
41
+ connection_url_to_hash(k)
42
+ end
43
+
44
+ raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
45
+
46
+ resolve_hash_connection hash
47
+ end
48
+
49
+ def resolve_hash_connection(spec) # :nodoc:
50
+ spec = spec.symbolize_keys
51
+
52
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
53
+
54
+ path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter"
55
+ begin
56
+ require path_to_adapter
57
+ rescue Gem::LoadError => e
58
+ raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile."
59
+ rescue LoadError => e
60
+ raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace
61
+ end
62
+
63
+ adapter_method = "#{spec[:adapter]}_connection"
64
+
65
+ ConnectionSpecification.new(spec, adapter_method)
66
+ end
67
+
68
+ def connection_url_to_hash(url) # :nodoc:
69
+ config = URI.parse url
70
+ adapter = config.scheme
71
+ adapter = "postgresql" if adapter == "postgres"
72
+ spec = { :adapter => adapter,
73
+ :username => config.user,
74
+ :password => config.password,
75
+ :port => config.port,
76
+ :database => config.path.sub(%r{^/},""),
77
+ :host => config.host }
78
+
79
+ spec.reject!{ |_,value| value.blank? }
80
+
81
+ uri_parser = URI::Parser.new
82
+
83
+ spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
84
+
85
+ if config.query
86
+ options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
87
+
88
+ spec.merge!(options)
89
+ end
90
+
91
+ spec
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,273 @@
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+
3
+ gem 'mysql2', '~> 0.3.10'
4
+ require 'mysql2'
5
+
6
+ module ActiveRecord
7
+ module ConnectionHandling # :nodoc:
8
+ # Establishes a connection to the database that's used by all Active Record objects.
9
+ def mysql2_connection(config)
10
+ config = config.symbolize_keys
11
+
12
+ config[:username] = 'root' if config[:username].nil?
13
+
14
+ if Mysql2::Client.const_defined? :FOUND_ROWS
15
+ config[:flags] = Mysql2::Client::FOUND_ROWS
16
+ end
17
+
18
+ client = Mysql2::Client.new(config)
19
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
20
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
21
+ end
22
+ end
23
+
24
+ module ConnectionAdapters
25
+ class Mysql2Adapter < AbstractMysqlAdapter
26
+
27
+ class Column < AbstractMysqlAdapter::Column # :nodoc:
28
+ def adapter
29
+ Mysql2Adapter
30
+ end
31
+ end
32
+
33
+ ADAPTER_NAME = 'Mysql2'
34
+
35
+ def initialize(connection, logger, connection_options, config)
36
+ super
37
+ @visitor = BindSubstitution.new self
38
+ configure_connection
39
+ end
40
+
41
+ MAX_INDEX_LENGTH_FOR_UTF8MB4 = 191
42
+ def initialize_schema_migrations_table
43
+ if @config[:encoding] == 'utf8mb4'
44
+ ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_UTF8MB4)
45
+ else
46
+ ActiveRecord::SchemaMigration.create_table
47
+ end
48
+ end
49
+
50
+ def supports_explain?
51
+ true
52
+ end
53
+
54
+ # HELPER METHODS ===========================================
55
+
56
+ def each_hash(result) # :nodoc:
57
+ if block_given?
58
+ result.each(:as => :hash, :symbolize_keys => true) do |row|
59
+ yield row
60
+ end
61
+ else
62
+ to_enum(:each_hash, result)
63
+ end
64
+ end
65
+
66
+ def new_column(field, default, type, null, collation, extra = "") # :nodoc:
67
+ Column.new(field, default, type, null, collation, strict_mode?, extra)
68
+ end
69
+
70
+ def error_number(exception)
71
+ exception.error_number if exception.respond_to?(:error_number)
72
+ end
73
+
74
+ # QUOTING ==================================================
75
+
76
+ def quote_string(string)
77
+ @connection.escape(string)
78
+ end
79
+
80
+ # CONNECTION MANAGEMENT ====================================
81
+
82
+ def active?
83
+ return false unless @connection
84
+ @connection.ping
85
+ end
86
+
87
+ def reconnect!
88
+ super
89
+ disconnect!
90
+ connect
91
+ end
92
+ alias :reset! :reconnect!
93
+
94
+ # Disconnects from the database if already connected.
95
+ # Otherwise, this method does nothing.
96
+ def disconnect!
97
+ super
98
+ unless @connection.nil?
99
+ @connection.close
100
+ @connection = nil
101
+ end
102
+ end
103
+
104
+ # DATABASE STATEMENTS ======================================
105
+
106
+ def explain(arel, binds = [])
107
+ sql = "EXPLAIN #{to_sql(arel, binds.dup)}"
108
+ start = Time.now
109
+ result = exec_query(sql, 'EXPLAIN', binds)
110
+ elapsed = Time.now - start
111
+
112
+ ExplainPrettyPrinter.new.pp(result, elapsed)
113
+ end
114
+
115
+ class ExplainPrettyPrinter # :nodoc:
116
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
117
+ # MySQL shell:
118
+ #
119
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
120
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
121
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
122
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
123
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
124
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
125
+ # 2 rows in set (0.00 sec)
126
+ #
127
+ # This is an exercise in Ruby hyperrealism :).
128
+ def pp(result, elapsed)
129
+ widths = compute_column_widths(result)
130
+ separator = build_separator(widths)
131
+
132
+ pp = []
133
+
134
+ pp << separator
135
+ pp << build_cells(result.columns, widths)
136
+ pp << separator
137
+
138
+ result.rows.each do |row|
139
+ pp << build_cells(row, widths)
140
+ end
141
+
142
+ pp << separator
143
+ pp << build_footer(result.rows.length, elapsed)
144
+
145
+ pp.join("\n") + "\n"
146
+ end
147
+
148
+ private
149
+
150
+ def compute_column_widths(result)
151
+ [].tap do |widths|
152
+ result.columns.each_with_index do |column, i|
153
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
154
+ widths << cells_in_column.map(&:length).max
155
+ end
156
+ end
157
+ end
158
+
159
+ def build_separator(widths)
160
+ padding = 1
161
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
162
+ end
163
+
164
+ def build_cells(items, widths)
165
+ cells = []
166
+ items.each_with_index do |item, i|
167
+ item = 'NULL' if item.nil?
168
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
169
+ cells << item.to_s.send(justifier, widths[i])
170
+ end
171
+ '| ' + cells.join(' | ') + ' |'
172
+ end
173
+
174
+ def build_footer(nrows, elapsed)
175
+ rows_label = nrows == 1 ? 'row' : 'rows'
176
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
177
+ end
178
+ end
179
+
180
+ # FIXME: re-enable the following once a "better" query_cache solution is in core
181
+ #
182
+ # The overrides below perform much better than the originals in AbstractAdapter
183
+ # because we're able to take advantage of mysql2's lazy-loading capabilities
184
+ #
185
+ # # Returns a record hash with the column names as keys and column values
186
+ # # as values.
187
+ # def select_one(sql, name = nil)
188
+ # result = execute(sql, name)
189
+ # result.each(as: :hash) do |r|
190
+ # return r
191
+ # end
192
+ # end
193
+ #
194
+ # # Returns a single value from a record
195
+ # def select_value(sql, name = nil)
196
+ # result = execute(sql, name)
197
+ # if first = result.first
198
+ # first.first
199
+ # end
200
+ # end
201
+ #
202
+ # # Returns an array of the values of the first column in a select:
203
+ # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
204
+ # def select_values(sql, name = nil)
205
+ # execute(sql, name).map { |row| row.first }
206
+ # end
207
+
208
+ # Returns an array of arrays containing the field values.
209
+ # Order is the same as that returned by +columns+.
210
+ def select_rows(sql, name = nil)
211
+ execute(sql, name).to_a
212
+ end
213
+
214
+ # Executes the SQL statement in the context of this connection.
215
+ def execute(sql, name = nil)
216
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
217
+ # made since we established the connection
218
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
219
+
220
+ super
221
+ end
222
+
223
+ def exec_query(sql, name = 'SQL', binds = [])
224
+ result = execute(sql, name)
225
+ ActiveRecord::Result.new(result.fields, result.to_a)
226
+ end
227
+
228
+ alias exec_without_stmt exec_query
229
+
230
+ # Returns an array of record hashes with the column names as keys and
231
+ # column values as values.
232
+ def select(sql, name = nil, binds = [])
233
+ exec_query(sql, name)
234
+ end
235
+
236
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
237
+ super
238
+ id_value || @connection.last_id
239
+ end
240
+ alias :create :insert_sql
241
+
242
+ def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
243
+ execute to_sql(sql, binds), name
244
+ end
245
+
246
+ def exec_delete(sql, name, binds)
247
+ execute to_sql(sql, binds), name
248
+ @connection.affected_rows
249
+ end
250
+ alias :exec_update :exec_delete
251
+
252
+ def last_inserted_id(result)
253
+ @connection.last_id
254
+ end
255
+
256
+ private
257
+
258
+ def connect
259
+ @connection = Mysql2::Client.new(@config)
260
+ configure_connection
261
+ end
262
+
263
+ def configure_connection
264
+ @connection.query_options.merge!(:as => :array)
265
+ super
266
+ end
267
+
268
+ def version
269
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
270
+ end
271
+ end
272
+ end
273
+ end