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.
- data/CHANGELOG.md +6749 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +177 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +147 -0
- data/lib/active_record/aggregations.rb +255 -0
- data/lib/active_record/associations.rb +1604 -0
- data/lib/active_record/associations/alias_tracker.rb +79 -0
- data/lib/active_record/associations/association.rb +239 -0
- data/lib/active_record/associations/association_scope.rb +119 -0
- data/lib/active_record/associations/belongs_to_association.rb +79 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +574 -0
- data/lib/active_record/associations/collection_proxy.rb +132 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
- data/lib/active_record/associations/has_many_association.rb +108 -0
- data/lib/active_record/associations/has_many_through_association.rb +180 -0
- data/lib/active_record/associations/has_one_association.rb +73 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +55 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +127 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +83 -0
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +272 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +101 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +135 -0
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
- data/lib/active_record/attribute_methods/write.rb +69 -0
- data/lib/active_record/autosave_association.rb +422 -0
- data/lib/active_record/base.rb +716 -0
- data/lib/active_record/callbacks.rb +275 -0
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +270 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +119 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +906 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +156 -0
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +183 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +68 -0
- data/lib/active_record/migration.rb +765 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +469 -0
- data/lib/active_record/observer.rb +121 -0
- data/lib/active_record/persistence.rb +372 -0
- data/lib/active_record/query_cache.rb +74 -0
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +119 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +620 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +534 -0
- data/lib/active_record/relation.rb +534 -0
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +354 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +398 -0
- data/lib/active_record/relation/predicate_builder.rb +58 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +148 -0
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +18 -0
- data/lib/active_record/serializers/xml_serializer.rb +202 -0
- data/lib/active_record/session_store.rb +358 -0
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +73 -0
- data/lib/active_record/timestamp.rb +113 -0
- data/lib/active_record/transactions.rb +360 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +83 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +25 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- metadata +242 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
require 'date'
|
|
2
|
+
require 'bigdecimal'
|
|
3
|
+
require 'bigdecimal/util'
|
|
4
|
+
require 'active_support/core_ext/benchmark'
|
|
5
|
+
require 'active_support/deprecation'
|
|
6
|
+
require 'active_record/connection_adapters/schema_cache'
|
|
7
|
+
require 'monitor'
|
|
8
|
+
|
|
9
|
+
module ActiveRecord
|
|
10
|
+
module ConnectionAdapters # :nodoc:
|
|
11
|
+
extend ActiveSupport::Autoload
|
|
12
|
+
|
|
13
|
+
autoload :Column
|
|
14
|
+
|
|
15
|
+
autoload_under 'abstract' do
|
|
16
|
+
autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
|
|
17
|
+
autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
|
|
18
|
+
autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions'
|
|
19
|
+
autoload :Table, 'active_record/connection_adapters/abstract/schema_definitions'
|
|
20
|
+
|
|
21
|
+
autoload :SchemaStatements
|
|
22
|
+
autoload :DatabaseStatements
|
|
23
|
+
autoload :DatabaseLimits
|
|
24
|
+
autoload :Quoting
|
|
25
|
+
|
|
26
|
+
autoload :ConnectionPool
|
|
27
|
+
autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool'
|
|
28
|
+
autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool'
|
|
29
|
+
autoload :ConnectionSpecification
|
|
30
|
+
|
|
31
|
+
autoload :QueryCache
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Active Record supports multiple database systems. AbstractAdapter and
|
|
35
|
+
# related classes form the abstraction layer which makes this possible.
|
|
36
|
+
# An AbstractAdapter represents a connection to a database, and provides an
|
|
37
|
+
# abstract interface for database-specific functionality such as establishing
|
|
38
|
+
# a connection, escaping values, building the right SQL fragments for ':offset'
|
|
39
|
+
# and ':limit' options, etc.
|
|
40
|
+
#
|
|
41
|
+
# All the concrete database adapters follow the interface laid down in this class.
|
|
42
|
+
# ActiveRecord::Base.connection returns an AbstractAdapter object, which
|
|
43
|
+
# you can use.
|
|
44
|
+
#
|
|
45
|
+
# Most of the methods in the adapter are useful during migrations. Most
|
|
46
|
+
# notably, the instance methods provided by SchemaStatement are very useful.
|
|
47
|
+
class AbstractAdapter
|
|
48
|
+
include Quoting, DatabaseStatements, SchemaStatements
|
|
49
|
+
include DatabaseLimits
|
|
50
|
+
include QueryCache
|
|
51
|
+
include ActiveSupport::Callbacks
|
|
52
|
+
include MonitorMixin
|
|
53
|
+
|
|
54
|
+
define_callbacks :checkout, :checkin
|
|
55
|
+
|
|
56
|
+
attr_accessor :visitor, :pool
|
|
57
|
+
attr_reader :schema_cache, :last_use, :in_use
|
|
58
|
+
alias :in_use? :in_use
|
|
59
|
+
|
|
60
|
+
def initialize(connection, logger = nil, pool = nil) #:nodoc:
|
|
61
|
+
super()
|
|
62
|
+
|
|
63
|
+
@active = nil
|
|
64
|
+
@connection = connection
|
|
65
|
+
@in_use = false
|
|
66
|
+
@instrumenter = ActiveSupport::Notifications.instrumenter
|
|
67
|
+
@last_use = false
|
|
68
|
+
@logger = logger
|
|
69
|
+
@open_transactions = 0
|
|
70
|
+
@pool = pool
|
|
71
|
+
@query_cache = Hash.new { |h,sql| h[sql] = {} }
|
|
72
|
+
@query_cache_enabled = false
|
|
73
|
+
@schema_cache = SchemaCache.new self
|
|
74
|
+
@visitor = nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def lease
|
|
78
|
+
synchronize do
|
|
79
|
+
unless in_use
|
|
80
|
+
@in_use = true
|
|
81
|
+
@last_use = Time.now
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def expire
|
|
87
|
+
@in_use = false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns the human-readable name of the adapter. Use mixed case - one
|
|
91
|
+
# can always use downcase if needed.
|
|
92
|
+
def adapter_name
|
|
93
|
+
'Abstract'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Does this adapter support migrations? Backend specific, as the
|
|
97
|
+
# abstract adapter always returns +false+.
|
|
98
|
+
def supports_migrations?
|
|
99
|
+
false
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Can this adapter determine the primary key for tables not attached
|
|
103
|
+
# to an Active Record class, such as join tables? Backend specific, as
|
|
104
|
+
# the abstract adapter always returns +false+.
|
|
105
|
+
def supports_primary_key?
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Does this adapter support using DISTINCT within COUNT? This is +true+
|
|
110
|
+
# for all adapters except sqlite.
|
|
111
|
+
def supports_count_distinct?
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Does this adapter support DDL rollbacks in transactions? That is, would
|
|
116
|
+
# CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
|
|
117
|
+
# SQL Server, and others support this. MySQL and others do not.
|
|
118
|
+
def supports_ddl_transactions?
|
|
119
|
+
false
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def supports_bulk_alter?
|
|
123
|
+
false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Does this adapter support savepoints? PostgreSQL and MySQL do,
|
|
127
|
+
# SQLite < 3.6.8 does not.
|
|
128
|
+
def supports_savepoints?
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Should primary key values be selected from their corresponding
|
|
133
|
+
# sequence before the insert statement? If true, next_sequence_value
|
|
134
|
+
# is called before each insert to set the record's primary key.
|
|
135
|
+
# This is false for all adapters but Firebird.
|
|
136
|
+
def prefetch_primary_key?(table_name = nil)
|
|
137
|
+
false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Does this adapter support index sort order?
|
|
141
|
+
def supports_index_sort_order?
|
|
142
|
+
false
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Does this adapter support explain? As of this writing sqlite3,
|
|
146
|
+
# mysql2, and postgresql are the only ones that do.
|
|
147
|
+
def supports_explain?
|
|
148
|
+
false
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# QUOTING ==================================================
|
|
152
|
+
|
|
153
|
+
# Override to return the quoted table name. Defaults to column quoting.
|
|
154
|
+
def quote_table_name(name)
|
|
155
|
+
quote_column_name(name)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Returns a bind substitution value given a +column+ and list of current
|
|
159
|
+
# +binds+
|
|
160
|
+
def substitute_at(column, index)
|
|
161
|
+
Arel.sql '?'
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# REFERENTIAL INTEGRITY ====================================
|
|
165
|
+
|
|
166
|
+
# Override to turn off referential integrity while executing <tt>&block</tt>.
|
|
167
|
+
def disable_referential_integrity
|
|
168
|
+
yield
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# CONNECTION MANAGEMENT ====================================
|
|
172
|
+
|
|
173
|
+
# Checks whether the connection to the database is still active. This includes
|
|
174
|
+
# checking whether the database is actually capable of responding, i.e. whether
|
|
175
|
+
# the connection isn't stale.
|
|
176
|
+
def active?
|
|
177
|
+
@active != false
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Disconnects from the database if already connected, and establishes a
|
|
181
|
+
# new connection with the database.
|
|
182
|
+
def reconnect!
|
|
183
|
+
@active = true
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Disconnects from the database if already connected. Otherwise, this
|
|
187
|
+
# method does nothing.
|
|
188
|
+
def disconnect!
|
|
189
|
+
@active = false
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Reset the state of this connection, directing the DBMS to clear
|
|
193
|
+
# transactions and other connection-related server-side state. Usually a
|
|
194
|
+
# database-dependent operation.
|
|
195
|
+
#
|
|
196
|
+
# The default implementation does nothing; the implementation should be
|
|
197
|
+
# overridden by concrete adapters.
|
|
198
|
+
def reset!
|
|
199
|
+
# this should be overridden by concrete adapters
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
###
|
|
203
|
+
# Clear any caching the database adapter may be doing, for example
|
|
204
|
+
# clearing the prepared statement cache. This is database specific.
|
|
205
|
+
def clear_cache!
|
|
206
|
+
# this should be overridden by concrete adapters
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Returns true if its required to reload the connection between requests for development mode.
|
|
210
|
+
# This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
|
|
211
|
+
def requires_reloading?
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Checks whether the connection to the database is still active (i.e. not stale).
|
|
216
|
+
# This is done under the hood by calling <tt>active?</tt>. If the connection
|
|
217
|
+
# is no longer active, then this method will reconnect to the database.
|
|
218
|
+
def verify!(*ignored)
|
|
219
|
+
reconnect! unless active?
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Provides access to the underlying database driver for this adapter. For
|
|
223
|
+
# example, this method returns a Mysql object in case of MysqlAdapter,
|
|
224
|
+
# and a PGconn object in case of PostgreSQLAdapter.
|
|
225
|
+
#
|
|
226
|
+
# This is useful for when you need to call a proprietary method such as
|
|
227
|
+
# PostgreSQL's lo_* methods.
|
|
228
|
+
def raw_connection
|
|
229
|
+
@connection
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
attr_reader :open_transactions
|
|
233
|
+
|
|
234
|
+
def increment_open_transactions
|
|
235
|
+
@open_transactions += 1
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def decrement_open_transactions
|
|
239
|
+
@open_transactions -= 1
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def transaction_joinable=(joinable)
|
|
243
|
+
@transaction_joinable = joinable
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def create_savepoint
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def rollback_to_savepoint
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def release_savepoint
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def case_sensitive_modifier(node)
|
|
256
|
+
node
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def case_insensitive_comparison(table, attribute, column, value)
|
|
260
|
+
table[attribute].lower.eq(table.lower(value))
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def current_savepoint_name
|
|
264
|
+
"active_record_#{open_transactions}"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Check the connection back in to the connection pool
|
|
268
|
+
def close
|
|
269
|
+
pool.checkin self
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
protected
|
|
273
|
+
|
|
274
|
+
def log(sql, name = "SQL", binds = [])
|
|
275
|
+
@instrumenter.instrument(
|
|
276
|
+
"sql.active_record",
|
|
277
|
+
:sql => sql,
|
|
278
|
+
:name => name,
|
|
279
|
+
:connection_id => object_id,
|
|
280
|
+
:binds => binds) { yield }
|
|
281
|
+
rescue Exception => e
|
|
282
|
+
message = "#{e.class.name}: #{e.message}: #{sql}"
|
|
283
|
+
@logger.debug message if @logger
|
|
284
|
+
exception = translate_exception(e, message)
|
|
285
|
+
exception.set_backtrace e.backtrace
|
|
286
|
+
raise exception
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def translate_exception(e, message)
|
|
290
|
+
# override in derived class
|
|
291
|
+
ActiveRecord::StatementInvalid.new(message)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
class AbstractMysqlAdapter < AbstractAdapter
|
|
6
|
+
class Column < ConnectionAdapters::Column # :nodoc:
|
|
7
|
+
attr_reader :collation
|
|
8
|
+
|
|
9
|
+
def initialize(name, default, sql_type = nil, null = true, collation = nil)
|
|
10
|
+
super(name, default, sql_type, null)
|
|
11
|
+
@collation = collation
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def extract_default(default)
|
|
15
|
+
if sql_type =~ /blob/i || type == :text
|
|
16
|
+
if default.blank?
|
|
17
|
+
return null ? nil : ''
|
|
18
|
+
else
|
|
19
|
+
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
|
20
|
+
end
|
|
21
|
+
elsif missing_default_forged_as_empty_string?(default)
|
|
22
|
+
nil
|
|
23
|
+
else
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_default?
|
|
29
|
+
return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Must return the relevant concrete adapter
|
|
34
|
+
def adapter
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def case_sensitive?
|
|
39
|
+
collation && !collation.match(/_ci$/)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def simplified_type(field_type)
|
|
45
|
+
return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
|
|
46
|
+
|
|
47
|
+
case field_type
|
|
48
|
+
when /enum/i, /set/i then :string
|
|
49
|
+
when /year/i then :integer
|
|
50
|
+
when /bit/i then :binary
|
|
51
|
+
else
|
|
52
|
+
super
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def extract_limit(sql_type)
|
|
57
|
+
case sql_type
|
|
58
|
+
when /blob|text/i
|
|
59
|
+
case sql_type
|
|
60
|
+
when /tiny/i
|
|
61
|
+
255
|
|
62
|
+
when /medium/i
|
|
63
|
+
16777215
|
|
64
|
+
when /long/i
|
|
65
|
+
2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
|
|
66
|
+
else
|
|
67
|
+
super # we could return 65535 here, but we leave it undecorated by default
|
|
68
|
+
end
|
|
69
|
+
when /^bigint/i; 8
|
|
70
|
+
when /^int/i; 4
|
|
71
|
+
when /^mediumint/i; 3
|
|
72
|
+
when /^smallint/i; 2
|
|
73
|
+
when /^tinyint/i; 1
|
|
74
|
+
else
|
|
75
|
+
super
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# MySQL misreports NOT NULL column default when none is given.
|
|
80
|
+
# We can't detect this for columns which may have a legitimate ''
|
|
81
|
+
# default (string) but we can for others (integer, datetime, boolean,
|
|
82
|
+
# and the rest).
|
|
83
|
+
#
|
|
84
|
+
# Test whether the column has default '', is not null, and is not
|
|
85
|
+
# a type allowing default ''.
|
|
86
|
+
def missing_default_forged_as_empty_string?(default)
|
|
87
|
+
type != :string && !null && default == ''
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# :singleton-method:
|
|
93
|
+
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
|
|
94
|
+
# as boolean. If you wish to disable this emulation (which was the default
|
|
95
|
+
# behavior in versions 0.13.1 and earlier) you can add the following line
|
|
96
|
+
# to your application.rb file:
|
|
97
|
+
#
|
|
98
|
+
# ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
|
|
99
|
+
class_attribute :emulate_booleans
|
|
100
|
+
self.emulate_booleans = true
|
|
101
|
+
|
|
102
|
+
LOST_CONNECTION_ERROR_MESSAGES = [
|
|
103
|
+
"Server shutdown in progress",
|
|
104
|
+
"Broken pipe",
|
|
105
|
+
"Lost connection to MySQL server during query",
|
|
106
|
+
"MySQL server has gone away" ]
|
|
107
|
+
|
|
108
|
+
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
|
109
|
+
|
|
110
|
+
NATIVE_DATABASE_TYPES = {
|
|
111
|
+
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
|
|
112
|
+
:string => { :name => "varchar", :limit => 255 },
|
|
113
|
+
:text => { :name => "text" },
|
|
114
|
+
:integer => { :name => "int", :limit => 4 },
|
|
115
|
+
:float => { :name => "float" },
|
|
116
|
+
:decimal => { :name => "decimal" },
|
|
117
|
+
:datetime => { :name => "datetime" },
|
|
118
|
+
:timestamp => { :name => "datetime" },
|
|
119
|
+
:time => { :name => "time" },
|
|
120
|
+
:date => { :name => "date" },
|
|
121
|
+
:binary => { :name => "blob" },
|
|
122
|
+
:boolean => { :name => "tinyint", :limit => 1 }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# FIXME: Make the first parameter more similar for the two adapters
|
|
126
|
+
def initialize(connection, logger, connection_options, config)
|
|
127
|
+
super(connection, logger)
|
|
128
|
+
@connection_options, @config = connection_options, config
|
|
129
|
+
@quoted_column_names, @quoted_table_names = {}, {}
|
|
130
|
+
@visitor = Arel::Visitors::MySQL.new self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def adapter_name #:nodoc:
|
|
134
|
+
self.class::ADAPTER_NAME
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Returns true, since this connection adapter supports migrations.
|
|
138
|
+
def supports_migrations?
|
|
139
|
+
true
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def supports_primary_key?
|
|
143
|
+
true
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Returns true, since this connection adapter supports savepoints.
|
|
147
|
+
def supports_savepoints?
|
|
148
|
+
true
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def supports_bulk_alter? #:nodoc:
|
|
152
|
+
true
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Technically MySQL allows to create indexes with the sort order syntax
|
|
156
|
+
# but at the moment (5.5) it doesn't yet implement them
|
|
157
|
+
def supports_index_sort_order?
|
|
158
|
+
true
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def native_database_types
|
|
162
|
+
NATIVE_DATABASE_TYPES
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# HELPER METHODS ===========================================
|
|
166
|
+
|
|
167
|
+
# The two drivers have slightly different ways of yielding hashes of results, so
|
|
168
|
+
# this method must be implemented to provide a uniform interface.
|
|
169
|
+
def each_hash(result) # :nodoc:
|
|
170
|
+
raise NotImplementedError
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Overridden by the adapters to instantiate their specific Column type.
|
|
174
|
+
def new_column(field, default, type, null, collation) # :nodoc:
|
|
175
|
+
Column.new(field, default, type, null, collation)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Must return the Mysql error number from the exception, if the exception has an
|
|
179
|
+
# error number.
|
|
180
|
+
def error_number(exception) # :nodoc:
|
|
181
|
+
raise NotImplementedError
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# QUOTING ==================================================
|
|
185
|
+
|
|
186
|
+
def quote(value, column = nil)
|
|
187
|
+
if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
|
|
188
|
+
s = column.class.string_to_binary(value).unpack("H*")[0]
|
|
189
|
+
"x'#{s}'"
|
|
190
|
+
elsif value.kind_of?(BigDecimal)
|
|
191
|
+
value.to_s("F")
|
|
192
|
+
else
|
|
193
|
+
super
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def quote_column_name(name) #:nodoc:
|
|
198
|
+
@quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def quote_table_name(name) #:nodoc:
|
|
202
|
+
@quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def quoted_true
|
|
206
|
+
QUOTED_TRUE
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def quoted_false
|
|
210
|
+
QUOTED_FALSE
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# REFERENTIAL INTEGRITY ====================================
|
|
214
|
+
|
|
215
|
+
def disable_referential_integrity(&block) #:nodoc:
|
|
216
|
+
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
|
|
217
|
+
|
|
218
|
+
begin
|
|
219
|
+
update("SET FOREIGN_KEY_CHECKS = 0")
|
|
220
|
+
yield
|
|
221
|
+
ensure
|
|
222
|
+
update("SET FOREIGN_KEY_CHECKS = #{old}")
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# DATABASE STATEMENTS ======================================
|
|
227
|
+
|
|
228
|
+
# Executes the SQL statement in the context of this connection.
|
|
229
|
+
def execute(sql, name = nil)
|
|
230
|
+
if name == :skip_logging
|
|
231
|
+
@connection.query(sql)
|
|
232
|
+
else
|
|
233
|
+
log(sql, name) { @connection.query(sql) }
|
|
234
|
+
end
|
|
235
|
+
rescue ActiveRecord::StatementInvalid => exception
|
|
236
|
+
if exception.message.split(":").first =~ /Packets out of order/
|
|
237
|
+
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."
|
|
238
|
+
else
|
|
239
|
+
raise
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# MysqlAdapter has to free a result after using it, so we use this method to write
|
|
244
|
+
# stuff in a abstract way without concerning ourselves about whether it needs to be
|
|
245
|
+
# explicitly freed or not.
|
|
246
|
+
def execute_and_free(sql, name = nil) #:nodoc:
|
|
247
|
+
yield execute(sql, name)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def update_sql(sql, name = nil) #:nodoc:
|
|
251
|
+
super
|
|
252
|
+
@connection.affected_rows
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def begin_db_transaction
|
|
256
|
+
execute "BEGIN"
|
|
257
|
+
rescue Exception
|
|
258
|
+
# Transactions aren't supported
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def commit_db_transaction #:nodoc:
|
|
262
|
+
execute "COMMIT"
|
|
263
|
+
rescue Exception
|
|
264
|
+
# Transactions aren't supported
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def rollback_db_transaction #:nodoc:
|
|
268
|
+
execute "ROLLBACK"
|
|
269
|
+
rescue Exception
|
|
270
|
+
# Transactions aren't supported
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def create_savepoint
|
|
274
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def rollback_to_savepoint
|
|
278
|
+
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def release_savepoint
|
|
282
|
+
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
|
286
|
+
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
|
287
|
+
# these, we must use a subquery. However, MySQL is too stupid to create a
|
|
288
|
+
# temporary table for this automatically, so we have to give it some prompting
|
|
289
|
+
# in the form of a subsubquery. Ugh!
|
|
290
|
+
def join_to_update(update, select) #:nodoc:
|
|
291
|
+
if select.limit || select.offset || select.orders.any?
|
|
292
|
+
subsubselect = select.clone
|
|
293
|
+
subsubselect.projections = [update.key]
|
|
294
|
+
|
|
295
|
+
subselect = Arel::SelectManager.new(select.engine)
|
|
296
|
+
subselect.project Arel.sql(update.key.name)
|
|
297
|
+
subselect.from subsubselect.as('__active_record_temp')
|
|
298
|
+
|
|
299
|
+
update.where update.key.in(subselect)
|
|
300
|
+
else
|
|
301
|
+
update.table select.source
|
|
302
|
+
update.wheres = select.constraints
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# SCHEMA STATEMENTS ========================================
|
|
307
|
+
|
|
308
|
+
def structure_dump #:nodoc:
|
|
309
|
+
if supports_views?
|
|
310
|
+
sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
|
|
311
|
+
else
|
|
312
|
+
sql = "SHOW TABLES"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
select_all(sql).map { |table|
|
|
316
|
+
table.delete('Table_type')
|
|
317
|
+
sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
|
|
318
|
+
exec_without_stmt(sql).first['Create Table'] + ";\n\n"
|
|
319
|
+
}.join
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Drops the database specified on the +name+ attribute
|
|
323
|
+
# and creates it again using the provided +options+.
|
|
324
|
+
def recreate_database(name, options = {})
|
|
325
|
+
drop_database(name)
|
|
326
|
+
create_database(name, options)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
|
|
330
|
+
# Charset defaults to utf8.
|
|
331
|
+
#
|
|
332
|
+
# Example:
|
|
333
|
+
# create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
|
|
334
|
+
# create_database 'matt_development'
|
|
335
|
+
# create_database 'matt_development', :charset => :big5
|
|
336
|
+
def create_database(name, options = {})
|
|
337
|
+
if options[:collation]
|
|
338
|
+
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
|
|
339
|
+
else
|
|
340
|
+
execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Drops a MySQL database.
|
|
345
|
+
#
|
|
346
|
+
# Example:
|
|
347
|
+
# drop_database('sebastian_development')
|
|
348
|
+
def drop_database(name) #:nodoc:
|
|
349
|
+
execute "DROP DATABASE IF EXISTS `#{name}`"
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def current_database
|
|
353
|
+
select_value 'SELECT DATABASE() as db'
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Returns the database character set.
|
|
357
|
+
def charset
|
|
358
|
+
show_variable 'character_set_database'
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Returns the database collation strategy.
|
|
362
|
+
def collation
|
|
363
|
+
show_variable 'collation_database'
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def tables(name = nil, database = nil, like = nil) #:nodoc:
|
|
367
|
+
sql = "SHOW TABLES "
|
|
368
|
+
sql << "IN #{database} " if database
|
|
369
|
+
sql << "LIKE #{quote(like)}" if like
|
|
370
|
+
|
|
371
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
|
372
|
+
result.collect { |field| field.first }
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def table_exists?(name)
|
|
377
|
+
return false unless name
|
|
378
|
+
return true if tables(nil, nil, name).any?
|
|
379
|
+
|
|
380
|
+
name = name.to_s
|
|
381
|
+
schema, table = name.split('.', 2)
|
|
382
|
+
|
|
383
|
+
unless table # A table was provided without a schema
|
|
384
|
+
table = schema
|
|
385
|
+
schema = nil
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
tables(nil, schema, table).any?
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Returns an array of indexes for the given table.
|
|
392
|
+
def indexes(table_name, name = nil) #:nodoc:
|
|
393
|
+
indexes = []
|
|
394
|
+
current_index = nil
|
|
395
|
+
execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
|
|
396
|
+
each_hash(result) do |row|
|
|
397
|
+
if current_index != row[:Key_name]
|
|
398
|
+
next if row[:Key_name] == 'PRIMARY' # skip the primary key
|
|
399
|
+
current_index = row[:Key_name]
|
|
400
|
+
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
indexes.last.columns << row[:Column_name]
|
|
404
|
+
indexes.last.lengths << row[:Sub_part]
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
indexes
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
|
412
|
+
def columns(table_name, name = nil)#:nodoc:
|
|
413
|
+
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
|
414
|
+
execute_and_free(sql, 'SCHEMA') do |result|
|
|
415
|
+
each_hash(result).map do |field|
|
|
416
|
+
new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation])
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def create_table(table_name, options = {}) #:nodoc:
|
|
422
|
+
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def bulk_change_table(table_name, operations) #:nodoc:
|
|
426
|
+
sqls = operations.map do |command, args|
|
|
427
|
+
table, arguments = args.shift, args
|
|
428
|
+
method = :"#{command}_sql"
|
|
429
|
+
|
|
430
|
+
if respond_to?(method)
|
|
431
|
+
send(method, table, *arguments)
|
|
432
|
+
else
|
|
433
|
+
raise "Unknown method called : #{method}(#{arguments.inspect})"
|
|
434
|
+
end
|
|
435
|
+
end.flatten.join(", ")
|
|
436
|
+
|
|
437
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# Renames a table.
|
|
441
|
+
#
|
|
442
|
+
# Example:
|
|
443
|
+
# rename_table('octopuses', 'octopi')
|
|
444
|
+
def rename_table(table_name, new_name)
|
|
445
|
+
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def add_column(table_name, column_name, type, options = {})
|
|
449
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def change_column_default(table_name, column_name, default)
|
|
453
|
+
column = column_for(table_name, column_name)
|
|
454
|
+
change_column table_name, column_name, column.sql_type, :default => default
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
|
458
|
+
column = column_for(table_name, column_name)
|
|
459
|
+
|
|
460
|
+
unless null || default.nil?
|
|
461
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
change_column table_name, column_name, column.sql_type, :null => null
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
468
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
|
472
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
# Maps logical Rails types to MySQL-specific data types.
|
|
476
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
|
477
|
+
return super unless type.to_s == 'integer'
|
|
478
|
+
|
|
479
|
+
case limit
|
|
480
|
+
when 1; 'tinyint'
|
|
481
|
+
when 2; 'smallint'
|
|
482
|
+
when 3; 'mediumint'
|
|
483
|
+
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
|
484
|
+
when 5..8; 'bigint'
|
|
485
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def add_column_position!(sql, options)
|
|
490
|
+
if options[:first]
|
|
491
|
+
sql << " FIRST"
|
|
492
|
+
elsif options[:after]
|
|
493
|
+
sql << " AFTER #{quote_column_name(options[:after])}"
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# SHOW VARIABLES LIKE 'name'
|
|
498
|
+
def show_variable(name)
|
|
499
|
+
variables = select_all("SHOW VARIABLES LIKE '#{name}'")
|
|
500
|
+
variables.first['Value'] unless variables.empty?
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Returns a table's primary key and belonging sequence.
|
|
504
|
+
def pk_and_sequence_for(table)
|
|
505
|
+
execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result|
|
|
506
|
+
create_table = each_hash(result).first[:"Create Table"]
|
|
507
|
+
if create_table.to_s =~ /PRIMARY KEY\s+\((.+)\)/
|
|
508
|
+
keys = $1.split(",").map { |key| key.gsub(/[`"]/, "") }
|
|
509
|
+
keys.length == 1 ? [keys.first, nil] : nil
|
|
510
|
+
else
|
|
511
|
+
nil
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Returns just a table's primary key
|
|
517
|
+
def primary_key(table)
|
|
518
|
+
pk_and_sequence = pk_and_sequence_for(table)
|
|
519
|
+
pk_and_sequence && pk_and_sequence.first
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def case_sensitive_modifier(node)
|
|
523
|
+
Arel::Nodes::Bin.new(node)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def case_insensitive_comparison(table, attribute, column, value)
|
|
527
|
+
if column.case_sensitive?
|
|
528
|
+
super
|
|
529
|
+
else
|
|
530
|
+
table[attribute].eq(value)
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
|
|
535
|
+
where_sql
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
protected
|
|
539
|
+
|
|
540
|
+
def add_index_length(option_strings, column_names, options = {})
|
|
541
|
+
if options.is_a?(Hash) && length = options[:length]
|
|
542
|
+
case length
|
|
543
|
+
when Hash
|
|
544
|
+
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)}
|
|
545
|
+
when Fixnum
|
|
546
|
+
column_names.each {|name| option_strings[name] += "(#{length})"}
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
return option_strings
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def quoted_columns_for_index(column_names, options = {})
|
|
554
|
+
option_strings = Hash[column_names.map {|name| [name, '']}]
|
|
555
|
+
|
|
556
|
+
# add index length
|
|
557
|
+
option_strings = add_index_length(option_strings, column_names, options)
|
|
558
|
+
|
|
559
|
+
# add index sort order
|
|
560
|
+
option_strings = add_index_sort_order(option_strings, column_names, options)
|
|
561
|
+
|
|
562
|
+
column_names.map {|name| quote_column_name(name) + option_strings[name]}
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def translate_exception(exception, message)
|
|
566
|
+
case error_number(exception)
|
|
567
|
+
when 1062
|
|
568
|
+
RecordNotUnique.new(message, exception)
|
|
569
|
+
when 1452
|
|
570
|
+
InvalidForeignKey.new(message, exception)
|
|
571
|
+
else
|
|
572
|
+
super
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def add_column_sql(table_name, column_name, type, options = {})
|
|
577
|
+
add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
578
|
+
add_column_options!(add_column_sql, options)
|
|
579
|
+
add_column_position!(add_column_sql, options)
|
|
580
|
+
add_column_sql
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def change_column_sql(table_name, column_name, type, options = {})
|
|
584
|
+
column = column_for(table_name, column_name)
|
|
585
|
+
|
|
586
|
+
unless options_include_default?(options)
|
|
587
|
+
options[:default] = column.default
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
unless options.has_key?(:null)
|
|
591
|
+
options[:null] = column.null
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
|
595
|
+
add_column_options!(change_column_sql, options)
|
|
596
|
+
add_column_position!(change_column_sql, options)
|
|
597
|
+
change_column_sql
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def rename_column_sql(table_name, column_name, new_column_name)
|
|
601
|
+
options = {}
|
|
602
|
+
|
|
603
|
+
if column = columns(table_name).find { |c| c.name == column_name.to_s }
|
|
604
|
+
options[:default] = column.default
|
|
605
|
+
options[:null] = column.null
|
|
606
|
+
else
|
|
607
|
+
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
|
|
611
|
+
rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
|
|
612
|
+
add_column_options!(rename_column_sql, options)
|
|
613
|
+
rename_column_sql
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def remove_column_sql(table_name, *column_names)
|
|
617
|
+
columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
|
|
618
|
+
end
|
|
619
|
+
alias :remove_columns_sql :remove_column
|
|
620
|
+
|
|
621
|
+
def add_index_sql(table_name, column_name, options = {})
|
|
622
|
+
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
|
623
|
+
"ADD #{index_type} INDEX #{index_name} (#{index_columns})"
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
def remove_index_sql(table_name, options = {})
|
|
627
|
+
index_name = index_name_for_remove(table_name, options)
|
|
628
|
+
"DROP INDEX #{index_name}"
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def add_timestamps_sql(table_name)
|
|
632
|
+
[add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def remove_timestamps_sql(table_name)
|
|
636
|
+
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
private
|
|
640
|
+
|
|
641
|
+
def supports_views?
|
|
642
|
+
version[0] >= 5
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def column_for(table_name, column_name)
|
|
646
|
+
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
|
647
|
+
raise "No such column: #{table_name}.#{column_name}"
|
|
648
|
+
end
|
|
649
|
+
column
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
end
|