sskirby-activerecord 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,270 @@
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
+ # Casts value (which is a String) to an appropriate instance.
70
+ def type_cast(value)
71
+ return nil if value.nil?
72
+ return coder.load(value) if encoded?
73
+
74
+ klass = self.class
75
+
76
+ case type
77
+ when :string, :text then value
78
+ when :integer then value.to_i rescue value ? 1 : 0
79
+ when :float then value.to_f
80
+ when :decimal then klass.value_to_decimal(value)
81
+ when :datetime, :timestamp then klass.string_to_time(value)
82
+ when :time then klass.string_to_dummy_time(value)
83
+ when :date then klass.string_to_date(value)
84
+ when :binary then klass.binary_to_string(value)
85
+ when :boolean then klass.value_to_boolean(value)
86
+ else value
87
+ end
88
+ end
89
+
90
+ def type_cast_code(var_name)
91
+ klass = self.class.name
92
+
93
+ case type
94
+ when :string, :text then var_name
95
+ when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
96
+ when :float then "#{var_name}.to_f"
97
+ when :decimal then "#{klass}.value_to_decimal(#{var_name})"
98
+ when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
99
+ when :time then "#{klass}.string_to_dummy_time(#{var_name})"
100
+ when :date then "#{klass}.string_to_date(#{var_name})"
101
+ when :binary then "#{klass}.binary_to_string(#{var_name})"
102
+ when :boolean then "#{klass}.value_to_boolean(#{var_name})"
103
+ else var_name
104
+ end
105
+ end
106
+
107
+ # Returns the human name of the column name.
108
+ #
109
+ # ===== Examples
110
+ # Column.new('sales_stage', ...).human_name # => 'Sales stage'
111
+ def human_name
112
+ Base.human_attribute_name(@name)
113
+ end
114
+
115
+ def extract_default(default)
116
+ type_cast(default)
117
+ end
118
+
119
+ # Used to convert from Strings to BLOBs
120
+ def string_to_binary(value)
121
+ self.class.string_to_binary(value)
122
+ end
123
+
124
+ class << self
125
+ # Used to convert from Strings to BLOBs
126
+ def string_to_binary(value)
127
+ value
128
+ end
129
+
130
+ # Used to convert from BLOBs to Strings
131
+ def binary_to_string(value)
132
+ value
133
+ end
134
+
135
+ def string_to_date(string)
136
+ return string unless string.is_a?(String)
137
+ return nil if string.empty?
138
+
139
+ fast_string_to_date(string) || fallback_string_to_date(string)
140
+ end
141
+
142
+ def string_to_time(string)
143
+ return string unless string.is_a?(String)
144
+ return nil if string.empty?
145
+
146
+ fast_string_to_time(string) || fallback_string_to_time(string)
147
+ end
148
+
149
+ def string_to_dummy_time(string)
150
+ return string unless string.is_a?(String)
151
+ return nil if string.empty?
152
+
153
+ string_to_time "2000-01-01 #{string}"
154
+ end
155
+
156
+ # convert something to a boolean
157
+ def value_to_boolean(value)
158
+ if value.is_a?(String) && value.blank?
159
+ nil
160
+ else
161
+ TRUE_VALUES.include?(value)
162
+ end
163
+ end
164
+
165
+ # convert something to a BigDecimal
166
+ def value_to_decimal(value)
167
+ # Using .class is faster than .is_a? and
168
+ # subclasses of BigDecimal will be handled
169
+ # in the else clause
170
+ if value.class == BigDecimal
171
+ value
172
+ elsif value.respond_to?(:to_d)
173
+ value.to_d
174
+ else
175
+ value.to_s.to_d
176
+ end
177
+ end
178
+
179
+ protected
180
+ # '0.123456' -> 123456
181
+ # '1.123456' -> 123456
182
+ def microseconds(time)
183
+ ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
184
+ end
185
+
186
+ def new_date(year, mon, mday)
187
+ if year && year != 0
188
+ Date.new(year, mon, mday) rescue nil
189
+ end
190
+ end
191
+
192
+ def new_time(year, mon, mday, hour, min, sec, microsec)
193
+ # Treat 0000-00-00 00:00:00 as nil.
194
+ return nil if year.nil? || (year == 0 && mon == 0 && mday == 0)
195
+
196
+ Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
197
+ end
198
+
199
+ def fast_string_to_date(string)
200
+ if string =~ Format::ISO_DATE
201
+ new_date $1.to_i, $2.to_i, $3.to_i
202
+ end
203
+ end
204
+
205
+ # Doesn't handle time zones.
206
+ def fast_string_to_time(string)
207
+ if string =~ Format::ISO_DATETIME
208
+ microsec = ($7.to_f * 1_000_000).to_i
209
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
210
+ end
211
+ end
212
+
213
+ def fallback_string_to_date(string)
214
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
215
+ end
216
+
217
+ def fallback_string_to_time(string)
218
+ time_hash = Date._parse(string)
219
+ time_hash[:sec_fraction] = microseconds(time_hash)
220
+
221
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
222
+ end
223
+ end
224
+
225
+ private
226
+ def extract_limit(sql_type)
227
+ $1.to_i if sql_type =~ /\((.*)\)/
228
+ end
229
+
230
+ def extract_precision(sql_type)
231
+ $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
232
+ end
233
+
234
+ def extract_scale(sql_type)
235
+ case sql_type
236
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
237
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
238
+ end
239
+ end
240
+
241
+ def simplified_type(field_type)
242
+ case field_type
243
+ when /int/i
244
+ :integer
245
+ when /float|double/i
246
+ :float
247
+ when /decimal|numeric|number/i
248
+ extract_scale(field_type) == 0 ? :integer : :decimal
249
+ when /datetime/i
250
+ :datetime
251
+ when /timestamp/i
252
+ :timestamp
253
+ when /time/i
254
+ :time
255
+ when /date/i
256
+ :date
257
+ when /clob/i, /text/i
258
+ :text
259
+ when /blob/i, /binary/i
260
+ :binary
261
+ when /char/i, /string/i
262
+ :string
263
+ when /boolean/i
264
+ :boolean
265
+ end
266
+ end
267
+ end
268
+ end
269
+ # :startdoc:
270
+ end
@@ -0,0 +1,288 @@
1
+ require 'active_record/connection_adapters/abstract_mysql_adapter'
2
+
3
+ gem 'mysql2', '~> 0.3.10'
4
+ require 'mysql2'
5
+
6
+ module ActiveRecord
7
+ class Base
8
+ # Establishes a connection to the database that's used by all Active Record objects.
9
+ def self.mysql2_connection(config)
10
+ config[:username] = 'root' if config[:username].nil?
11
+
12
+ if Mysql2::Client.const_defined? :FOUND_ROWS
13
+ config[:flags] = Mysql2::Client::FOUND_ROWS
14
+ end
15
+
16
+ client = Mysql2::Client.new(config.symbolize_keys)
17
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
18
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
19
+ end
20
+ end
21
+
22
+ module ConnectionAdapters
23
+ class Mysql2Adapter < AbstractMysqlAdapter
24
+
25
+ class Column < AbstractMysqlAdapter::Column # :nodoc:
26
+ def adapter
27
+ Mysql2Adapter
28
+ end
29
+ end
30
+
31
+ ADAPTER_NAME = 'Mysql2'
32
+
33
+ def initialize(connection, logger, connection_options, config)
34
+ super
35
+ configure_connection
36
+ end
37
+
38
+ def supports_explain?
39
+ true
40
+ end
41
+
42
+ # HELPER METHODS ===========================================
43
+
44
+ def each_hash(result) # :nodoc:
45
+ if block_given?
46
+ result.each(:as => :hash, :symbolize_keys => true) do |row|
47
+ yield row
48
+ end
49
+ else
50
+ to_enum(:each_hash, result)
51
+ end
52
+ end
53
+
54
+ def new_column(field, default, type, null, collation) # :nodoc:
55
+ Column.new(field, default, type, null, collation)
56
+ end
57
+
58
+ def error_number(exception)
59
+ exception.error_number if exception.respond_to?(:error_number)
60
+ end
61
+
62
+ # QUOTING ==================================================
63
+
64
+ def quote_string(string)
65
+ @connection.escape(string)
66
+ end
67
+
68
+ def substitute_at(column, index)
69
+ Arel.sql "\0"
70
+ end
71
+
72
+ # CONNECTION MANAGEMENT ====================================
73
+
74
+ def active?
75
+ return false unless @connection
76
+ @connection.ping
77
+ end
78
+
79
+ def reconnect!
80
+ disconnect!
81
+ connect
82
+ end
83
+
84
+ # Disconnects from the database if already connected.
85
+ # Otherwise, this method does nothing.
86
+ def disconnect!
87
+ unless @connection.nil?
88
+ @connection.close
89
+ @connection = nil
90
+ end
91
+ end
92
+
93
+ def reset!
94
+ disconnect!
95
+ connect
96
+ end
97
+
98
+ # DATABASE STATEMENTS ======================================
99
+
100
+ def explain(arel, binds = [])
101
+ sql = "EXPLAIN #{to_sql(arel)}"
102
+ start = Time.now
103
+ result = exec_query(sql, 'EXPLAIN', binds)
104
+ elapsed = Time.now - start
105
+
106
+ ExplainPrettyPrinter.new.pp(result, elapsed)
107
+ end
108
+
109
+ class ExplainPrettyPrinter # :nodoc:
110
+ # Pretty prints the result of a EXPLAIN in a way that resembles the output of the
111
+ # MySQL shell:
112
+ #
113
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
114
+ # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
115
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
116
+ # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
117
+ # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
118
+ # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
119
+ # 2 rows in set (0.00 sec)
120
+ #
121
+ # This is an exercise in Ruby hyperrealism :).
122
+ def pp(result, elapsed)
123
+ widths = compute_column_widths(result)
124
+ separator = build_separator(widths)
125
+
126
+ pp = []
127
+
128
+ pp << separator
129
+ pp << build_cells(result.columns, widths)
130
+ pp << separator
131
+
132
+ result.rows.each do |row|
133
+ pp << build_cells(row, widths)
134
+ end
135
+
136
+ pp << separator
137
+ pp << build_footer(result.rows.length, elapsed)
138
+
139
+ pp.join("\n") + "\n"
140
+ end
141
+
142
+ private
143
+
144
+ def compute_column_widths(result)
145
+ [].tap do |widths|
146
+ result.columns.each_with_index do |column, i|
147
+ cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
148
+ widths << cells_in_column.map(&:length).max
149
+ end
150
+ end
151
+ end
152
+
153
+ def build_separator(widths)
154
+ padding = 1
155
+ '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
156
+ end
157
+
158
+ def build_cells(items, widths)
159
+ cells = []
160
+ items.each_with_index do |item, i|
161
+ item = 'NULL' if item.nil?
162
+ justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
163
+ cells << item.to_s.send(justifier, widths[i])
164
+ end
165
+ '| ' + cells.join(' | ') + ' |'
166
+ end
167
+
168
+ def build_footer(nrows, elapsed)
169
+ rows_label = nrows == 1 ? 'row' : 'rows'
170
+ "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
171
+ end
172
+ end
173
+
174
+ # FIXME: re-enable the following once a "better" query_cache solution is in core
175
+ #
176
+ # The overrides below perform much better than the originals in AbstractAdapter
177
+ # because we're able to take advantage of mysql2's lazy-loading capabilities
178
+ #
179
+ # # Returns a record hash with the column names as keys and column values
180
+ # # as values.
181
+ # def select_one(sql, name = nil)
182
+ # result = execute(sql, name)
183
+ # result.each(:as => :hash) do |r|
184
+ # return r
185
+ # end
186
+ # end
187
+ #
188
+ # # Returns a single value from a record
189
+ # def select_value(sql, name = nil)
190
+ # result = execute(sql, name)
191
+ # if first = result.first
192
+ # first.first
193
+ # end
194
+ # end
195
+ #
196
+ # # Returns an array of the values of the first column in a select:
197
+ # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
198
+ # def select_values(sql, name = nil)
199
+ # execute(sql, name).map { |row| row.first }
200
+ # end
201
+
202
+ # Returns an array of arrays containing the field values.
203
+ # Order is the same as that returned by +columns+.
204
+ def select_rows(sql, name = nil)
205
+ execute(sql, name).to_a
206
+ end
207
+
208
+ # Executes the SQL statement in the context of this connection.
209
+ def execute(sql, name = nil)
210
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
211
+ # made since we established the connection
212
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
213
+
214
+ super
215
+ end
216
+
217
+ def exec_query(sql, name = 'SQL', binds = [])
218
+ result = execute(sql, name)
219
+ ActiveRecord::Result.new(result.fields, result.to_a)
220
+ end
221
+
222
+ alias exec_without_stmt exec_query
223
+
224
+ # Returns an array of record hashes with the column names as keys and
225
+ # column values as values.
226
+ def select(sql, name = nil, binds = [])
227
+ binds = binds.dup
228
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
229
+ end
230
+
231
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
232
+ super
233
+ id_value || @connection.last_id
234
+ end
235
+ alias :create :insert_sql
236
+
237
+ def exec_insert(sql, name, binds)
238
+ binds = binds.dup
239
+
240
+ # Pretend to support bind parameters
241
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
242
+ end
243
+
244
+ def exec_delete(sql, name, binds)
245
+ binds = binds.dup
246
+
247
+ # Pretend to support bind parameters
248
+ execute sql.gsub("\0") { quote(*binds.shift.reverse) }, name
249
+ @connection.affected_rows
250
+ end
251
+ alias :exec_update :exec_delete
252
+
253
+ def last_inserted_id(result)
254
+ @connection.last_id
255
+ end
256
+
257
+ private
258
+
259
+ def connect
260
+ @connection = Mysql2::Client.new(@config)
261
+ configure_connection
262
+ end
263
+
264
+ def configure_connection
265
+ @connection.query_options.merge!(:as => :array)
266
+
267
+ # By default, MySQL 'where id is null' selects the last inserted id.
268
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
269
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
270
+ encoding = @config[:encoding]
271
+
272
+ # make sure we set the encoding
273
+ variable_assignments << "NAMES '#{encoding}'" if encoding
274
+
275
+ # increase timeout so mysql server doesn't disconnect us
276
+ wait_timeout = @config[:wait_timeout]
277
+ wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
278
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
279
+
280
+ execute("SET #{variable_assignments.join(', ')}", :skip_logging)
281
+ end
282
+
283
+ def version
284
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
285
+ end
286
+ end
287
+ end
288
+ end