activerecord 3.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 (93) hide show
  1. data/CHANGELOG +6023 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +162 -0
  5. data/examples/simple.rb +14 -0
  6. data/lib/active_record.rb +124 -0
  7. data/lib/active_record/aggregations.rb +277 -0
  8. data/lib/active_record/association_preload.rb +403 -0
  9. data/lib/active_record/associations.rb +2254 -0
  10. data/lib/active_record/associations/association_collection.rb +562 -0
  11. data/lib/active_record/associations/association_proxy.rb +295 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +137 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  17. data/lib/active_record/associations/has_one_association.rb +143 -0
  18. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  19. data/lib/active_record/associations/through_association_scope.rb +154 -0
  20. data/lib/active_record/attribute_methods.rb +60 -0
  21. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +116 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  27. data/lib/active_record/attribute_methods/write.rb +37 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1867 -0
  30. data/lib/active_record/callbacks.rb +288 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  32. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  33. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  34. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  37. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  38. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +212 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +643 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1030 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  43. data/lib/active_record/connection_adapters/sqlite_adapter.rb +401 -0
  44. data/lib/active_record/counter_cache.rb +115 -0
  45. data/lib/active_record/dynamic_finder_match.rb +53 -0
  46. data/lib/active_record/dynamic_scope_match.rb +32 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1008 -0
  49. data/lib/active_record/locale/en.yml +40 -0
  50. data/lib/active_record/locking/optimistic.rb +172 -0
  51. data/lib/active_record/locking/pessimistic.rb +55 -0
  52. data/lib/active_record/log_subscriber.rb +48 -0
  53. data/lib/active_record/migration.rb +617 -0
  54. data/lib/active_record/named_scope.rb +138 -0
  55. data/lib/active_record/nested_attributes.rb +417 -0
  56. data/lib/active_record/observer.rb +140 -0
  57. data/lib/active_record/persistence.rb +291 -0
  58. data/lib/active_record/query_cache.rb +36 -0
  59. data/lib/active_record/railtie.rb +91 -0
  60. data/lib/active_record/railties/controller_runtime.rb +38 -0
  61. data/lib/active_record/railties/databases.rake +512 -0
  62. data/lib/active_record/reflection.rb +403 -0
  63. data/lib/active_record/relation.rb +393 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +286 -0
  66. data/lib/active_record/relation/finder_methods.rb +355 -0
  67. data/lib/active_record/relation/predicate_builder.rb +41 -0
  68. data/lib/active_record/relation/query_methods.rb +261 -0
  69. data/lib/active_record/relation/spawn_methods.rb +112 -0
  70. data/lib/active_record/schema.rb +59 -0
  71. data/lib/active_record/schema_dumper.rb +195 -0
  72. data/lib/active_record/serialization.rb +60 -0
  73. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  74. data/lib/active_record/session_store.rb +340 -0
  75. data/lib/active_record/test_case.rb +67 -0
  76. data/lib/active_record/timestamp.rb +88 -0
  77. data/lib/active_record/transactions.rb +356 -0
  78. data/lib/active_record/validations.rb +84 -0
  79. data/lib/active_record/validations/associated.rb +48 -0
  80. data/lib/active_record/validations/uniqueness.rb +185 -0
  81. data/lib/active_record/version.rb +9 -0
  82. data/lib/rails/generators/active_record.rb +27 -0
  83. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  84. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  85. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  86. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  87. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  88. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  89. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  90. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  91. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  92. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  93. metadata +224 -0
@@ -0,0 +1,212 @@
1
+ require 'date'
2
+ require 'bigdecimal'
3
+ require 'bigdecimal/util'
4
+ require 'active_support/core_ext/benchmark'
5
+
6
+ # TODO: Autoload these files
7
+ require 'active_record/connection_adapters/abstract/schema_definitions'
8
+ require 'active_record/connection_adapters/abstract/schema_statements'
9
+ require 'active_record/connection_adapters/abstract/database_statements'
10
+ require 'active_record/connection_adapters/abstract/quoting'
11
+ require 'active_record/connection_adapters/abstract/connection_pool'
12
+ require 'active_record/connection_adapters/abstract/connection_specification'
13
+ require 'active_record/connection_adapters/abstract/query_cache'
14
+ require 'active_record/connection_adapters/abstract/database_limits'
15
+
16
+ module ActiveRecord
17
+ module ConnectionAdapters # :nodoc:
18
+ # Active Record supports multiple database systems. AbstractAdapter and
19
+ # related classes form the abstraction layer which makes this possible.
20
+ # An AbstractAdapter represents a connection to a database, and provides an
21
+ # abstract interface for database-specific functionality such as establishing
22
+ # a connection, escaping values, building the right SQL fragments for ':offset'
23
+ # and ':limit' options, etc.
24
+ #
25
+ # All the concrete database adapters follow the interface laid down in this class.
26
+ # ActiveRecord::Base.connection returns an AbstractAdapter object, which
27
+ # you can use.
28
+ #
29
+ # Most of the methods in the adapter are useful during migrations. Most
30
+ # notably, the instance methods provided by SchemaStatement are very useful.
31
+ class AbstractAdapter
32
+ include Quoting, DatabaseStatements, SchemaStatements
33
+ include DatabaseLimits
34
+ include QueryCache
35
+ include ActiveSupport::Callbacks
36
+
37
+ define_callbacks :checkout, :checkin
38
+
39
+ def initialize(connection, logger = nil) #:nodoc:
40
+ @active = nil
41
+ @connection, @logger = connection, logger
42
+ @query_cache_enabled = false
43
+ @query_cache = {}
44
+ @instrumenter = ActiveSupport::Notifications.instrumenter
45
+ end
46
+
47
+ # Returns the human-readable name of the adapter. Use mixed case - one
48
+ # can always use downcase if needed.
49
+ def adapter_name
50
+ 'Abstract'
51
+ end
52
+
53
+ # Does this adapter support migrations? Backend specific, as the
54
+ # abstract adapter always returns +false+.
55
+ def supports_migrations?
56
+ false
57
+ end
58
+
59
+ # Can this adapter determine the primary key for tables not attached
60
+ # to an Active Record class, such as join tables? Backend specific, as
61
+ # the abstract adapter always returns +false+.
62
+ def supports_primary_key?
63
+ false
64
+ end
65
+
66
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
67
+ # for all adapters except sqlite.
68
+ def supports_count_distinct?
69
+ true
70
+ end
71
+
72
+ # Does this adapter support DDL rollbacks in transactions? That is, would
73
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
74
+ # SQL Server, and others support this. MySQL and others do not.
75
+ def supports_ddl_transactions?
76
+ false
77
+ end
78
+
79
+ # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
80
+ # does not.
81
+ def supports_savepoints?
82
+ false
83
+ end
84
+
85
+ # Should primary key values be selected from their corresponding
86
+ # sequence before the insert statement? If true, next_sequence_value
87
+ # is called before each insert to set the record's primary key.
88
+ # This is false for all adapters but Firebird.
89
+ def prefetch_primary_key?(table_name = nil)
90
+ false
91
+ end
92
+
93
+ # QUOTING ==================================================
94
+
95
+ # Override to return the quoted table name. Defaults to column quoting.
96
+ def quote_table_name(name)
97
+ quote_column_name(name)
98
+ end
99
+
100
+ # REFERENTIAL INTEGRITY ====================================
101
+
102
+ # Override to turn off referential integrity while executing <tt>&block</tt>.
103
+ def disable_referential_integrity
104
+ yield
105
+ end
106
+
107
+ # CONNECTION MANAGEMENT ====================================
108
+
109
+ # Checks whether the connection to the database is still active. This includes
110
+ # checking whether the database is actually capable of responding, i.e. whether
111
+ # the connection isn't stale.
112
+ def active?
113
+ @active != false
114
+ end
115
+
116
+ # Disconnects from the database if already connected, and establishes a
117
+ # new connection with the database.
118
+ def reconnect!
119
+ @active = true
120
+ end
121
+
122
+ # Disconnects from the database if already connected. Otherwise, this
123
+ # method does nothing.
124
+ def disconnect!
125
+ @active = false
126
+ end
127
+
128
+ # Reset the state of this connection, directing the DBMS to clear
129
+ # transactions and other connection-related server-side state. Usually a
130
+ # database-dependent operation.
131
+ #
132
+ # The default implementation does nothing; the implementation should be
133
+ # overridden by concrete adapters.
134
+ def reset!
135
+ # this should be overridden by concrete adapters
136
+ end
137
+
138
+ # Returns true if its required to reload the connection between requests for development mode.
139
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
140
+ def requires_reloading?
141
+ false
142
+ end
143
+
144
+ # Checks whether the connection to the database is still active (i.e. not stale).
145
+ # This is done under the hood by calling <tt>active?</tt>. If the connection
146
+ # is no longer active, then this method will reconnect to the database.
147
+ def verify!(*ignored)
148
+ reconnect! unless active?
149
+ end
150
+
151
+ # Provides access to the underlying database driver for this adapter. For
152
+ # example, this method returns a Mysql object in case of MysqlAdapter,
153
+ # and a PGconn object in case of PostgreSQLAdapter.
154
+ #
155
+ # This is useful for when you need to call a proprietary method such as
156
+ # PostgreSQL's lo_* methods.
157
+ def raw_connection
158
+ @connection
159
+ end
160
+
161
+ def open_transactions
162
+ @open_transactions ||= 0
163
+ end
164
+
165
+ def increment_open_transactions
166
+ @open_transactions ||= 0
167
+ @open_transactions += 1
168
+ end
169
+
170
+ def decrement_open_transactions
171
+ @open_transactions -= 1
172
+ end
173
+
174
+ def transaction_joinable=(joinable)
175
+ @transaction_joinable = joinable
176
+ end
177
+
178
+ def create_savepoint
179
+ end
180
+
181
+ def rollback_to_savepoint
182
+ end
183
+
184
+ def release_savepoint
185
+ end
186
+
187
+ def current_savepoint_name
188
+ "active_record_#{open_transactions}"
189
+ end
190
+
191
+ protected
192
+
193
+ def log(sql, name)
194
+ name ||= "SQL"
195
+ @instrumenter.instrument("sql.active_record",
196
+ :sql => sql, :name => name, :connection_id => object_id) do
197
+ yield
198
+ end
199
+ rescue Exception => e
200
+ message = "#{e.class.name}: #{e.message}: #{sql}"
201
+ @logger.debug message if @logger
202
+ raise translate_exception(e, message)
203
+ end
204
+
205
+ def translate_exception(e, message)
206
+ # override in derived class
207
+ ActiveRecord::StatementInvalid.new(message)
208
+ end
209
+
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,643 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/kernel/requires'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'set'
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.mysql_connection(config) # :nodoc:
10
+ config = config.symbolize_keys
11
+ host = config[:host]
12
+ port = config[:port]
13
+ socket = config[:socket]
14
+ username = config[:username] ? config[:username].to_s : 'root'
15
+ password = config[:password].to_s
16
+ database = config[:database]
17
+
18
+ unless defined? Mysql
19
+ begin
20
+ require 'mysql'
21
+ rescue LoadError
22
+ raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql', '2.8.1'"
23
+ end
24
+
25
+ unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash)
26
+ raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'"
27
+ end
28
+ end
29
+
30
+ mysql = Mysql.init
31
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
32
+
33
+ default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0
34
+ default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
35
+ options = [host, username, password, database, port, socket, default_flags]
36
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
37
+ end
38
+ end
39
+
40
+ module ConnectionAdapters
41
+ class MysqlColumn < Column #:nodoc:
42
+ def extract_default(default)
43
+ if sql_type =~ /blob/i || type == :text
44
+ if default.blank?
45
+ return null ? nil : ''
46
+ else
47
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
48
+ end
49
+ elsif missing_default_forged_as_empty_string?(default)
50
+ nil
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def has_default?
57
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
58
+ super
59
+ end
60
+
61
+ private
62
+ def simplified_type(field_type)
63
+ return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
64
+ return :string if field_type =~ /enum/i
65
+ super
66
+ end
67
+
68
+ def extract_limit(sql_type)
69
+ case sql_type
70
+ when /blob|text/i
71
+ case sql_type
72
+ when /tiny/i
73
+ 255
74
+ when /medium/i
75
+ 16777215
76
+ when /long/i
77
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
78
+ else
79
+ super # we could return 65535 here, but we leave it undecorated by default
80
+ end
81
+ when /^bigint/i; 8
82
+ when /^int/i; 4
83
+ when /^mediumint/i; 3
84
+ when /^smallint/i; 2
85
+ when /^tinyint/i; 1
86
+ else
87
+ super
88
+ end
89
+ end
90
+
91
+ # MySQL misreports NOT NULL column default when none is given.
92
+ # We can't detect this for columns which may have a legitimate ''
93
+ # default (string) but we can for others (integer, datetime, boolean,
94
+ # and the rest).
95
+ #
96
+ # Test whether the column has default '', is not null, and is not
97
+ # a type allowing default ''.
98
+ def missing_default_forged_as_empty_string?(default)
99
+ type != :string && !null && default == ''
100
+ end
101
+ end
102
+
103
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
104
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
105
+ #
106
+ # Options:
107
+ #
108
+ # * <tt>:host</tt> - Defaults to "localhost".
109
+ # * <tt>:port</tt> - Defaults to 3306.
110
+ # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
111
+ # * <tt>:username</tt> - Defaults to "root"
112
+ # * <tt>:password</tt> - Defaults to nothing.
113
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
114
+ # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
115
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
116
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
117
+ # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
118
+ # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
119
+ # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
120
+ # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
121
+ #
122
+ class MysqlAdapter < AbstractAdapter
123
+
124
+ ##
125
+ # :singleton-method:
126
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
127
+ # as boolean. If you wish to disable this emulation (which was the default
128
+ # behavior in versions 0.13.1 and earlier) you can add the following line
129
+ # to your application.rb file:
130
+ #
131
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
132
+ cattr_accessor :emulate_booleans
133
+ self.emulate_booleans = true
134
+
135
+ ADAPTER_NAME = 'MySQL'.freeze
136
+
137
+ LOST_CONNECTION_ERROR_MESSAGES = [
138
+ "Server shutdown in progress",
139
+ "Broken pipe",
140
+ "Lost connection to MySQL server during query",
141
+ "MySQL server has gone away" ]
142
+
143
+ QUOTED_TRUE, QUOTED_FALSE = '1'.freeze, '0'.freeze
144
+
145
+ NATIVE_DATABASE_TYPES = {
146
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
147
+ :string => { :name => "varchar", :limit => 255 },
148
+ :text => { :name => "text" },
149
+ :integer => { :name => "int", :limit => 4 },
150
+ :float => { :name => "float" },
151
+ :decimal => { :name => "decimal" },
152
+ :datetime => { :name => "datetime" },
153
+ :timestamp => { :name => "datetime" },
154
+ :time => { :name => "time" },
155
+ :date => { :name => "date" },
156
+ :binary => { :name => "blob" },
157
+ :boolean => { :name => "tinyint", :limit => 1 }
158
+ }
159
+
160
+ def initialize(connection, logger, connection_options, config)
161
+ super(connection, logger)
162
+ @connection_options, @config = connection_options, config
163
+ @quoted_column_names, @quoted_table_names = {}, {}
164
+ connect
165
+ end
166
+
167
+ def adapter_name #:nodoc:
168
+ ADAPTER_NAME
169
+ end
170
+
171
+ def supports_migrations? #:nodoc:
172
+ true
173
+ end
174
+
175
+ def supports_primary_key? #:nodoc:
176
+ true
177
+ end
178
+
179
+ def supports_savepoints? #:nodoc:
180
+ true
181
+ end
182
+
183
+ def native_database_types #:nodoc:
184
+ NATIVE_DATABASE_TYPES
185
+ end
186
+
187
+
188
+ # QUOTING ==================================================
189
+
190
+ def quote(value, column = nil)
191
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
192
+ s = column.class.string_to_binary(value).unpack("H*")[0]
193
+ "x'#{s}'"
194
+ elsif value.kind_of?(BigDecimal)
195
+ value.to_s("F")
196
+ else
197
+ super
198
+ end
199
+ end
200
+
201
+ def quote_column_name(name) #:nodoc:
202
+ @quoted_column_names[name] ||= "`#{name}`"
203
+ end
204
+
205
+ def quote_table_name(name) #:nodoc:
206
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
207
+ end
208
+
209
+ def quote_string(string) #:nodoc:
210
+ @connection.quote(string)
211
+ end
212
+
213
+ def quoted_true
214
+ QUOTED_TRUE
215
+ end
216
+
217
+ def quoted_false
218
+ QUOTED_FALSE
219
+ end
220
+
221
+ # REFERENTIAL INTEGRITY ====================================
222
+
223
+ def disable_referential_integrity #:nodoc:
224
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
225
+
226
+ begin
227
+ update("SET FOREIGN_KEY_CHECKS = 0")
228
+ yield
229
+ ensure
230
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
231
+ end
232
+ end
233
+
234
+ # CONNECTION MANAGEMENT ====================================
235
+
236
+ def active?
237
+ if @connection.respond_to?(:stat)
238
+ @connection.stat
239
+ else
240
+ @connection.query 'select 1'
241
+ end
242
+
243
+ # mysql-ruby doesn't raise an exception when stat fails.
244
+ if @connection.respond_to?(:errno)
245
+ @connection.errno.zero?
246
+ else
247
+ true
248
+ end
249
+ rescue Mysql::Error
250
+ false
251
+ end
252
+
253
+ def reconnect!
254
+ disconnect!
255
+ connect
256
+ end
257
+
258
+ def disconnect!
259
+ @connection.close rescue nil
260
+ end
261
+
262
+ def reset!
263
+ if @connection.respond_to?(:change_user)
264
+ # See http://bugs.mysql.com/bug.php?id=33540 -- the workaround way to
265
+ # reset the connection is to change the user to the same user.
266
+ @connection.change_user(@config[:username], @config[:password], @config[:database])
267
+ configure_connection
268
+ end
269
+ end
270
+
271
+ # DATABASE STATEMENTS ======================================
272
+
273
+ def select_rows(sql, name = nil)
274
+ @connection.query_with_result = true
275
+ result = execute(sql, name)
276
+ rows = []
277
+ result.each { |row| rows << row }
278
+ result.free
279
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
280
+ rows
281
+ end
282
+
283
+ # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
284
+ # the Result object after you're done using it.
285
+ def execute(sql, name = nil) #:nodoc:
286
+ if name == :skip_logging
287
+ @connection.query(sql)
288
+ else
289
+ log(sql, name) { @connection.query(sql) }
290
+ end
291
+ rescue ActiveRecord::StatementInvalid => exception
292
+ if exception.message.split(":").first =~ /Packets out of order/
293
+ 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."
294
+ else
295
+ raise
296
+ end
297
+ end
298
+
299
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
300
+ super sql, name
301
+ id_value || @connection.insert_id
302
+ end
303
+ alias :create :insert_sql
304
+
305
+ def update_sql(sql, name = nil) #:nodoc:
306
+ super
307
+ @connection.affected_rows
308
+ end
309
+
310
+ def begin_db_transaction #:nodoc:
311
+ execute "BEGIN"
312
+ rescue Exception
313
+ # Transactions aren't supported
314
+ end
315
+
316
+ def commit_db_transaction #:nodoc:
317
+ execute "COMMIT"
318
+ rescue Exception
319
+ # Transactions aren't supported
320
+ end
321
+
322
+ def rollback_db_transaction #:nodoc:
323
+ execute "ROLLBACK"
324
+ rescue Exception
325
+ # Transactions aren't supported
326
+ end
327
+
328
+ def create_savepoint
329
+ execute("SAVEPOINT #{current_savepoint_name}")
330
+ end
331
+
332
+ def rollback_to_savepoint
333
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
334
+ end
335
+
336
+ def release_savepoint
337
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
338
+ end
339
+
340
+ def add_limit_offset!(sql, options) #:nodoc:
341
+ limit, offset = options[:limit], options[:offset]
342
+ if limit && offset
343
+ sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
344
+ elsif limit
345
+ sql << " LIMIT #{sanitize_limit(limit)}"
346
+ elsif offset
347
+ sql << " OFFSET #{offset.to_i}"
348
+ end
349
+ sql
350
+ end
351
+
352
+ # SCHEMA STATEMENTS ========================================
353
+
354
+ def structure_dump #:nodoc:
355
+ if supports_views?
356
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
357
+ else
358
+ sql = "SHOW TABLES"
359
+ end
360
+
361
+ select_all(sql).inject("") do |structure, table|
362
+ table.delete('Table_type')
363
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
364
+ end
365
+ end
366
+
367
+ def recreate_database(name, options = {}) #:nodoc:
368
+ drop_database(name)
369
+ create_database(name, options)
370
+ end
371
+
372
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
373
+ # Charset defaults to utf8.
374
+ #
375
+ # Example:
376
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
377
+ # create_database 'matt_development'
378
+ # create_database 'matt_development', :charset => :big5
379
+ def create_database(name, options = {})
380
+ if options[:collation]
381
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
382
+ else
383
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
384
+ end
385
+ end
386
+
387
+ def drop_database(name) #:nodoc:
388
+ execute "DROP DATABASE IF EXISTS `#{name}`"
389
+ end
390
+
391
+ def current_database
392
+ select_value 'SELECT DATABASE() as db'
393
+ end
394
+
395
+ # Returns the database character set.
396
+ def charset
397
+ show_variable 'character_set_database'
398
+ end
399
+
400
+ # Returns the database collation strategy.
401
+ def collation
402
+ show_variable 'collation_database'
403
+ end
404
+
405
+ def tables(name = nil) #:nodoc:
406
+ tables = []
407
+ result = execute("SHOW TABLES", name)
408
+ result.each { |field| tables << field[0] }
409
+ result.free
410
+ tables
411
+ end
412
+
413
+ def drop_table(table_name, options = {})
414
+ super(table_name, options)
415
+ end
416
+
417
+ def indexes(table_name, name = nil)#:nodoc:
418
+ indexes = []
419
+ current_index = nil
420
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
421
+ result.each do |row|
422
+ if current_index != row[2]
423
+ next if row[2] == "PRIMARY" # skip the primary key
424
+ current_index = row[2]
425
+ indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
426
+ end
427
+
428
+ indexes.last.columns << row[4]
429
+ indexes.last.lengths << row[7]
430
+ end
431
+ result.free
432
+ indexes
433
+ end
434
+
435
+ def columns(table_name, name = nil)#:nodoc:
436
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
437
+ columns = []
438
+ result = execute(sql, :skip_logging)
439
+ result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
440
+ result.free
441
+ columns
442
+ end
443
+
444
+ def create_table(table_name, options = {}) #:nodoc:
445
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
446
+ end
447
+
448
+ def rename_table(table_name, new_name)
449
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
450
+ end
451
+
452
+ def add_column(table_name, column_name, type, options = {})
453
+ 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])}"
454
+ add_column_options!(add_column_sql, options)
455
+ add_column_position!(add_column_sql, options)
456
+ execute(add_column_sql)
457
+ end
458
+
459
+ def change_column_default(table_name, column_name, default) #:nodoc:
460
+ column = column_for(table_name, column_name)
461
+ change_column table_name, column_name, column.sql_type, :default => default
462
+ end
463
+
464
+ def change_column_null(table_name, column_name, null, default = nil)
465
+ column = column_for(table_name, column_name)
466
+
467
+ unless null || default.nil?
468
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
469
+ end
470
+
471
+ change_column table_name, column_name, column.sql_type, :null => null
472
+ end
473
+
474
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
475
+ column = column_for(table_name, column_name)
476
+
477
+ unless options_include_default?(options)
478
+ options[:default] = column.default
479
+ end
480
+
481
+ unless options.has_key?(:null)
482
+ options[:null] = column.null
483
+ end
484
+
485
+ 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])}"
486
+ add_column_options!(change_column_sql, options)
487
+ add_column_position!(change_column_sql, options)
488
+ execute(change_column_sql)
489
+ end
490
+
491
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
492
+ options = {}
493
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
494
+ options[:default] = column.default
495
+ options[:null] = column.null
496
+ else
497
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
498
+ end
499
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
500
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
501
+ add_column_options!(rename_column_sql, options)
502
+ execute(rename_column_sql)
503
+ end
504
+
505
+ # Maps logical Rails types to MySQL-specific data types.
506
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
507
+ return super unless type.to_s == 'integer'
508
+
509
+ case limit
510
+ when 1; 'tinyint'
511
+ when 2; 'smallint'
512
+ when 3; 'mediumint'
513
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
514
+ when 5..8; 'bigint'
515
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
516
+ end
517
+ end
518
+
519
+ def add_column_position!(sql, options)
520
+ if options[:first]
521
+ sql << " FIRST"
522
+ elsif options[:after]
523
+ sql << " AFTER #{quote_column_name(options[:after])}"
524
+ end
525
+ end
526
+
527
+ # SHOW VARIABLES LIKE 'name'
528
+ def show_variable(name)
529
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
530
+ variables.first['Value'] unless variables.empty?
531
+ end
532
+
533
+ # Returns a table's primary key and belonging sequence.
534
+ def pk_and_sequence_for(table) #:nodoc:
535
+ keys = []
536
+ result = execute("describe #{quote_table_name(table)}")
537
+ result.each_hash do |h|
538
+ keys << h["Field"]if h["Key"] == "PRI"
539
+ end
540
+ result.free
541
+ keys.length == 1 ? [keys.first, nil] : nil
542
+ end
543
+
544
+ # Returns just a table's primary key
545
+ def primary_key(table)
546
+ pk_and_sequence = pk_and_sequence_for(table)
547
+ pk_and_sequence && pk_and_sequence.first
548
+ end
549
+
550
+ def case_sensitive_equality_operator
551
+ "= BINARY"
552
+ end
553
+
554
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
555
+ where_sql
556
+ end
557
+
558
+ protected
559
+ def quoted_columns_for_index(column_names, options = {})
560
+ length = options[:length] if options.is_a?(Hash)
561
+
562
+ quoted_column_names = case length
563
+ when Hash
564
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
565
+ when Fixnum
566
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
567
+ else
568
+ column_names.map {|name| quote_column_name(name) }
569
+ end
570
+ end
571
+
572
+ def translate_exception(exception, message)
573
+ return super unless exception.respond_to?(:errno)
574
+
575
+ case exception.errno
576
+ when 1062
577
+ RecordNotUnique.new(message, exception)
578
+ when 1452
579
+ InvalidForeignKey.new(message, exception)
580
+ else
581
+ super
582
+ end
583
+ end
584
+
585
+ private
586
+ def connect
587
+ encoding = @config[:encoding]
588
+ if encoding
589
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
590
+ end
591
+
592
+ if @config[:sslca] || @config[:sslkey]
593
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
594
+ end
595
+
596
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
597
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
598
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
599
+
600
+ @connection.real_connect(*@connection_options)
601
+
602
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
603
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
604
+
605
+ configure_connection
606
+ end
607
+
608
+ def configure_connection
609
+ encoding = @config[:encoding]
610
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
611
+
612
+ # By default, MySQL 'where id is null' selects the last inserted id.
613
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
614
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
615
+ end
616
+
617
+ def select(sql, name = nil)
618
+ @connection.query_with_result = true
619
+ result = execute(sql, name)
620
+ rows = []
621
+ result.each_hash { |row| rows << row }
622
+ result.free
623
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
624
+ rows
625
+ end
626
+
627
+ def supports_views?
628
+ version[0] >= 5
629
+ end
630
+
631
+ def version
632
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
633
+ end
634
+
635
+ def column_for(table_name, column_name)
636
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
637
+ raise "No such column: #{table_name}.#{column_name}"
638
+ end
639
+ column
640
+ end
641
+ end
642
+ end
643
+ end