square-activerecord 3.0.7

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 (94) hide show
  1. data/CHANGELOG +6140 -0
  2. data/README.rdoc +222 -0
  3. data/examples/associations.png +0 -0
  4. data/examples/performance.rb +179 -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 +430 -0
  9. data/lib/active_record/associations.rb +2307 -0
  10. data/lib/active_record/associations/association_collection.rb +572 -0
  11. data/lib/active_record/associations/association_proxy.rb +299 -0
  12. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +82 -0
  14. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
  15. data/lib/active_record/associations/has_many_association.rb +128 -0
  16. data/lib/active_record/associations/has_many_through_association.rb +115 -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 +30 -0
  22. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  23. data/lib/active_record/attribute_methods/primary_key.rb +56 -0
  24. data/lib/active_record/attribute_methods/query.rb +39 -0
  25. data/lib/active_record/attribute_methods/read.rb +145 -0
  26. data/lib/active_record/attribute_methods/time_zone_conversion.rb +64 -0
  27. data/lib/active_record/attribute_methods/write.rb +43 -0
  28. data/lib/active_record/autosave_association.rb +369 -0
  29. data/lib/active_record/base.rb +1904 -0
  30. data/lib/active_record/callbacks.rb +284 -0
  31. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +364 -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 +333 -0
  35. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  36. data/lib/active_record/connection_adapters/abstract/quoting.rb +73 -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 +539 -0
  39. data/lib/active_record/connection_adapters/abstract_adapter.rb +217 -0
  40. data/lib/active_record/connection_adapters/mysql_adapter.rb +657 -0
  41. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1031 -0
  42. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -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 +56 -0
  46. data/lib/active_record/dynamic_scope_match.rb +23 -0
  47. data/lib/active_record/errors.rb +172 -0
  48. data/lib/active_record/fixtures.rb +1006 -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 +419 -0
  56. data/lib/active_record/observer.rb +125 -0
  57. data/lib/active_record/persistence.rb +290 -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 +411 -0
  63. data/lib/active_record/relation.rb +394 -0
  64. data/lib/active_record/relation/batches.rb +89 -0
  65. data/lib/active_record/relation/calculations.rb +295 -0
  66. data/lib/active_record/relation/finder_methods.rb +363 -0
  67. data/lib/active_record/relation/predicate_builder.rb +48 -0
  68. data/lib/active_record/relation/query_methods.rb +303 -0
  69. data/lib/active_record/relation/spawn_methods.rb +132 -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 +359 -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 +190 -0
  81. data/lib/active_record/version.rb +10 -0
  82. data/lib/rails/generators/active_record.rb +19 -0
  83. data/lib/rails/generators/active_record/migration.rb +15 -0
  84. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  85. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  86. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  87. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  88. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  89. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  90. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  91. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  92. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  93. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  94. metadata +223 -0
@@ -0,0 +1,217 @@
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
+ # Does this adapter restrict the number of ids you can use in a list. Oracle has a limit of 1000.
94
+ def ids_in_list_limit
95
+ nil
96
+ end
97
+
98
+ # QUOTING ==================================================
99
+
100
+ # Override to return the quoted table name. Defaults to column quoting.
101
+ def quote_table_name(name)
102
+ quote_column_name(name)
103
+ end
104
+
105
+ # REFERENTIAL INTEGRITY ====================================
106
+
107
+ # Override to turn off referential integrity while executing <tt>&block</tt>.
108
+ def disable_referential_integrity
109
+ yield
110
+ end
111
+
112
+ # CONNECTION MANAGEMENT ====================================
113
+
114
+ # Checks whether the connection to the database is still active. This includes
115
+ # checking whether the database is actually capable of responding, i.e. whether
116
+ # the connection isn't stale.
117
+ def active?
118
+ @active != false
119
+ end
120
+
121
+ # Disconnects from the database if already connected, and establishes a
122
+ # new connection with the database.
123
+ def reconnect!
124
+ @active = true
125
+ end
126
+
127
+ # Disconnects from the database if already connected. Otherwise, this
128
+ # method does nothing.
129
+ def disconnect!
130
+ @active = false
131
+ end
132
+
133
+ # Reset the state of this connection, directing the DBMS to clear
134
+ # transactions and other connection-related server-side state. Usually a
135
+ # database-dependent operation.
136
+ #
137
+ # The default implementation does nothing; the implementation should be
138
+ # overridden by concrete adapters.
139
+ def reset!
140
+ # this should be overridden by concrete adapters
141
+ end
142
+
143
+ # Returns true if its required to reload the connection between requests for development mode.
144
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
145
+ def requires_reloading?
146
+ false
147
+ end
148
+
149
+ # Checks whether the connection to the database is still active (i.e. not stale).
150
+ # This is done under the hood by calling <tt>active?</tt>. If the connection
151
+ # is no longer active, then this method will reconnect to the database.
152
+ def verify!(*ignored)
153
+ reconnect! unless active?
154
+ end
155
+
156
+ # Provides access to the underlying database driver for this adapter. For
157
+ # example, this method returns a Mysql object in case of MysqlAdapter,
158
+ # and a PGconn object in case of PostgreSQLAdapter.
159
+ #
160
+ # This is useful for when you need to call a proprietary method such as
161
+ # PostgreSQL's lo_* methods.
162
+ def raw_connection
163
+ @connection
164
+ end
165
+
166
+ def open_transactions
167
+ @open_transactions ||= 0
168
+ end
169
+
170
+ def increment_open_transactions
171
+ @open_transactions ||= 0
172
+ @open_transactions += 1
173
+ end
174
+
175
+ def decrement_open_transactions
176
+ @open_transactions -= 1
177
+ end
178
+
179
+ def transaction_joinable=(joinable)
180
+ @transaction_joinable = joinable
181
+ end
182
+
183
+ def create_savepoint
184
+ end
185
+
186
+ def rollback_to_savepoint
187
+ end
188
+
189
+ def release_savepoint
190
+ end
191
+
192
+ def current_savepoint_name
193
+ "active_record_#{open_transactions}"
194
+ end
195
+
196
+ protected
197
+
198
+ def log(sql, name)
199
+ name ||= "SQL"
200
+ @instrumenter.instrument("sql.active_record",
201
+ :sql => sql, :name => name, :connection_id => object_id) do
202
+ yield
203
+ end
204
+ rescue Exception => e
205
+ message = "#{e.class.name}: #{e.message}: #{sql}"
206
+ @logger.debug message if @logger
207
+ raise translate_exception(e, message)
208
+ end
209
+
210
+ def translate_exception(e, message)
211
+ # override in derived class
212
+ ActiveRecord::StatementInvalid.new(message)
213
+ end
214
+
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,657 @@
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 mysql2 gem. Add it to your Gemfile: gem 'mysql2'"
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'. Or use gem 'mysql2'"
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).map do |table|
362
+ table.delete('Table_type')
363
+ select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
364
+ end.join("")
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, database = nil) #:nodoc:
406
+ tables = []
407
+ result = execute(["SHOW TABLES", database].compact.join(' IN '), name)
408
+ result.each { |field| tables << field[0] }
409
+ result.free
410
+ tables
411
+ end
412
+
413
+ def table_exists?(name)
414
+ return true if super
415
+
416
+ name = name.to_s
417
+ schema, table = name.split('.', 2)
418
+
419
+ unless table # A table was provided without a schema
420
+ table = schema
421
+ schema = nil
422
+ end
423
+
424
+ tables(nil, schema).include? table
425
+ end
426
+
427
+ def drop_table(table_name, options = {})
428
+ super(table_name, options)
429
+ end
430
+
431
+ def indexes(table_name, name = nil)#:nodoc:
432
+ indexes = []
433
+ current_index = nil
434
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
435
+ result.each do |row|
436
+ if current_index != row[2]
437
+ next if row[2] == "PRIMARY" # skip the primary key
438
+ current_index = row[2]
439
+ indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
440
+ end
441
+
442
+ indexes.last.columns << row[4]
443
+ indexes.last.lengths << row[7]
444
+ end
445
+ result.free
446
+ indexes
447
+ end
448
+
449
+ def columns(table_name, name = nil)#:nodoc:
450
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
451
+ columns = []
452
+ result = execute(sql, :skip_logging)
453
+ result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
454
+ result.free
455
+ columns
456
+ end
457
+
458
+ def create_table(table_name, options = {}) #:nodoc:
459
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
460
+ end
461
+
462
+ def rename_table(table_name, new_name)
463
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
464
+ end
465
+
466
+ def add_column(table_name, column_name, type, options = {})
467
+ 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])}"
468
+ add_column_options!(add_column_sql, options)
469
+ add_column_position!(add_column_sql, options)
470
+ execute(add_column_sql)
471
+ end
472
+
473
+ def change_column_default(table_name, column_name, default) #:nodoc:
474
+ column = column_for(table_name, column_name)
475
+ change_column table_name, column_name, column.sql_type, :default => default
476
+ end
477
+
478
+ def change_column_null(table_name, column_name, null, default = nil)
479
+ column = column_for(table_name, column_name)
480
+
481
+ unless null || default.nil?
482
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
483
+ end
484
+
485
+ change_column table_name, column_name, column.sql_type, :null => null
486
+ end
487
+
488
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
489
+ column = column_for(table_name, column_name)
490
+
491
+ unless options_include_default?(options)
492
+ options[:default] = column.default
493
+ end
494
+
495
+ unless options.has_key?(:null)
496
+ options[:null] = column.null
497
+ end
498
+
499
+ 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])}"
500
+ add_column_options!(change_column_sql, options)
501
+ add_column_position!(change_column_sql, options)
502
+ execute(change_column_sql)
503
+ end
504
+
505
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
506
+ options = {}
507
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
508
+ options[:default] = column.default
509
+ options[:null] = column.null
510
+ else
511
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
512
+ end
513
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
514
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
515
+ add_column_options!(rename_column_sql, options)
516
+ execute(rename_column_sql)
517
+ end
518
+
519
+ # Maps logical Rails types to MySQL-specific data types.
520
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
521
+ return super unless type.to_s == 'integer'
522
+
523
+ case limit
524
+ when 1; 'tinyint'
525
+ when 2; 'smallint'
526
+ when 3; 'mediumint'
527
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
528
+ when 5..8; 'bigint'
529
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
530
+ end
531
+ end
532
+
533
+ def add_column_position!(sql, options)
534
+ if options[:first]
535
+ sql << " FIRST"
536
+ elsif options[:after]
537
+ sql << " AFTER #{quote_column_name(options[:after])}"
538
+ end
539
+ end
540
+
541
+ # SHOW VARIABLES LIKE 'name'
542
+ def show_variable(name)
543
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
544
+ variables.first['Value'] unless variables.empty?
545
+ end
546
+
547
+ # Returns a table's primary key and belonging sequence.
548
+ def pk_and_sequence_for(table) #:nodoc:
549
+ keys = []
550
+ result = execute("describe #{quote_table_name(table)}")
551
+ result.each_hash do |h|
552
+ keys << h["Field"]if h["Key"] == "PRI"
553
+ end
554
+ result.free
555
+ keys.length == 1 ? [keys.first, nil] : nil
556
+ end
557
+
558
+ # Returns just a table's primary key
559
+ def primary_key(table)
560
+ pk_and_sequence = pk_and_sequence_for(table)
561
+ pk_and_sequence && pk_and_sequence.first
562
+ end
563
+
564
+ def case_sensitive_equality_operator
565
+ "= BINARY"
566
+ end
567
+
568
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
569
+ where_sql
570
+ end
571
+
572
+ protected
573
+ def quoted_columns_for_index(column_names, options = {})
574
+ length = options[:length] if options.is_a?(Hash)
575
+
576
+ quoted_column_names = case length
577
+ when Hash
578
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
579
+ when Fixnum
580
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
581
+ else
582
+ column_names.map {|name| quote_column_name(name) }
583
+ end
584
+ end
585
+
586
+ def translate_exception(exception, message)
587
+ return super unless exception.respond_to?(:errno)
588
+
589
+ case exception.errno
590
+ when 1062
591
+ RecordNotUnique.new(message, exception)
592
+ when 1452
593
+ InvalidForeignKey.new(message, exception)
594
+ else
595
+ super
596
+ end
597
+ end
598
+
599
+ private
600
+ def connect
601
+ encoding = @config[:encoding]
602
+ if encoding
603
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
604
+ end
605
+
606
+ if @config[:sslca] || @config[:sslkey]
607
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
608
+ end
609
+
610
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
611
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
612
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
613
+
614
+ @connection.real_connect(*@connection_options)
615
+
616
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
617
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
618
+
619
+ configure_connection
620
+ end
621
+
622
+ def configure_connection
623
+ encoding = @config[:encoding]
624
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
625
+
626
+ # By default, MySQL 'where id is null' selects the last inserted id.
627
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
628
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
629
+ end
630
+
631
+ def select(sql, name = nil)
632
+ @connection.query_with_result = true
633
+ result = execute(sql, name)
634
+ rows = []
635
+ result.each_hash { |row| rows << row }
636
+ result.free
637
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
638
+ rows
639
+ end
640
+
641
+ def supports_views?
642
+ version[0] >= 5
643
+ end
644
+
645
+ def version
646
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
647
+ end
648
+
649
+ def column_for(table_name, column_name)
650
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
651
+ raise "No such column: #{table_name}.#{column_name}"
652
+ end
653
+ column
654
+ end
655
+ end
656
+ end
657
+ end