activerecord 5.0.0.beta1.1 → 5.0.0.beta2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +123 -15
- data/MIT-LICENSE +2 -2
- data/README.rdoc +1 -1
- data/lib/active_record.rb +2 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations.rb +3 -0
- data/lib/active_record/associations/builder/belongs_to.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +5 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -2
- data/lib/active_record/associations/preloader/through_association.rb +7 -2
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -7
- data/lib/active_record/autosave_association.rb +18 -3
- data/lib/active_record/base.rb +0 -3
- data/lib/active_record/collection_cache_key.rb +12 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +21 -34
- data/lib/active_record/connection_adapters/abstract/quoting.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +7 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +36 -20
- data/lib/active_record/connection_adapters/abstract/transaction.rb +8 -2
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -15
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -198
- data/lib/active_record/connection_adapters/column.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +24 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +15 -34
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +7 -69
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +8 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +5 -4
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -7
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -23
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -31
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +1 -1
- data/lib/active_record/counter_cache.rb +4 -4
- data/lib/active_record/enum.rb +8 -5
- data/lib/active_record/errors.rb +6 -1
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +6 -1
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/migration.rb +85 -20
- data/lib/active_record/migration/compatibility.rb +28 -2
- data/lib/active_record/model_schema.rb +25 -1
- data/lib/active_record/persistence.rb +11 -10
- data/lib/active_record/railtie.rb +6 -3
- data/lib/active_record/railties/databases.rake +20 -6
- data/lib/active_record/reflection.rb +39 -31
- data/lib/active_record/relation.rb +4 -4
- data/lib/active_record/relation/batches.rb +26 -41
- data/lib/active_record/relation/batches/batch_enumerator.rb +6 -6
- data/lib/active_record/relation/finder_methods.rb +35 -13
- data/lib/active_record/relation/from_clause.rb +1 -1
- data/lib/active_record/relation/merger.rb +3 -0
- data/lib/active_record/relation/predicate_builder.rb +19 -1
- data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -1
- data/lib/active_record/relation/query_methods.rb +37 -19
- data/lib/active_record/relation/record_fetch_warning.rb +4 -6
- data/lib/active_record/relation/where_clause.rb +1 -1
- data/lib/active_record/relation/where_clause_factory.rb +1 -0
- data/lib/active_record/sanitization.rb +1 -1
- data/lib/active_record/schema.rb +3 -0
- data/lib/active_record/schema_dumper.rb +1 -1
- data/lib/active_record/schema_migration.rb +5 -14
- data/lib/active_record/scoping.rb +17 -11
- data/lib/active_record/scoping/default.rb +2 -2
- data/lib/active_record/tasks/database_tasks.rb +18 -0
- data/lib/active_record/timestamp.rb +5 -1
- data/lib/active_record/transactions.rb +3 -3
- data/lib/active_record/validations/uniqueness.rb +6 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +7 -1
- metadata +14 -7
data/lib/active_record/base.rb
CHANGED
@@ -132,9 +132,6 @@ module ActiveRecord #:nodoc:
|
|
132
132
|
# end
|
133
133
|
# end
|
134
134
|
#
|
135
|
-
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
|
136
|
-
# or <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
|
137
|
-
#
|
138
135
|
# == Attribute query methods
|
139
136
|
#
|
140
137
|
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
|
@@ -7,18 +7,27 @@ module ActiveRecord
|
|
7
7
|
|
8
8
|
if collection.loaded?
|
9
9
|
size = collection.size
|
10
|
-
|
10
|
+
if size > 0
|
11
|
+
timestamp = collection.max_by(×tamp_column).public_send(timestamp_column)
|
12
|
+
end
|
11
13
|
else
|
12
14
|
column_type = type_for_attribute(timestamp_column.to_s)
|
13
15
|
column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
|
14
16
|
|
15
17
|
query = collection
|
18
|
+
.unscope(:select)
|
16
19
|
.select("COUNT(*) AS size", "MAX(#{column}) AS timestamp")
|
17
20
|
.unscope(:order)
|
18
21
|
result = connection.select_one(query)
|
19
22
|
|
20
|
-
|
21
|
-
|
23
|
+
if result.blank?
|
24
|
+
size = 0
|
25
|
+
timestamp = nil
|
26
|
+
else
|
27
|
+
size = result["size"]
|
28
|
+
timestamp = column_type.deserialize(result["timestamp"])
|
29
|
+
end
|
30
|
+
|
22
31
|
end
|
23
32
|
|
24
33
|
if timestamp
|
@@ -58,8 +58,8 @@ module ActiveRecord
|
|
58
58
|
|
59
59
|
# Returns an array of the values of the first column in a select:
|
60
60
|
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
61
|
-
def select_values(arel, name = nil)
|
62
|
-
arel, binds = binds_from_relation arel,
|
61
|
+
def select_values(arel, name = nil, binds = [])
|
62
|
+
arel, binds = binds_from_relation arel, binds
|
63
63
|
select_rows(to_sql(arel, binds), name, binds).map(&:first)
|
64
64
|
end
|
65
65
|
|
@@ -69,7 +69,11 @@ module ActiveRecord
|
|
69
69
|
end
|
70
70
|
undef_method :select_rows
|
71
71
|
|
72
|
-
# Executes the SQL statement in the context of this connection
|
72
|
+
# Executes the SQL statement in the context of this connection and returns
|
73
|
+
# the raw result from the connection adapter.
|
74
|
+
# Note: depending on your database connector, the result returned by this
|
75
|
+
# method may be manually memory managed. Consider using the exec_query
|
76
|
+
# wrapper instead.
|
73
77
|
def execute(sql, name = nil)
|
74
78
|
end
|
75
79
|
undef_method :execute
|
@@ -106,7 +110,7 @@ module ActiveRecord
|
|
106
110
|
exec_query(sql, name, binds)
|
107
111
|
end
|
108
112
|
|
109
|
-
#
|
113
|
+
# Executes an INSERT query and returns the new record's ID
|
110
114
|
#
|
111
115
|
# +id_value+ will be returned unless the value is nil, in
|
112
116
|
# which case the database will attempt to calculate the last inserted
|
@@ -115,20 +119,24 @@ module ActiveRecord
|
|
115
119
|
# If the next id was calculated in advance (as in Oracle), it should be
|
116
120
|
# passed in as +id_value+.
|
117
121
|
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
|
118
|
-
sql, binds = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
|
119
|
-
value
|
122
|
+
sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
|
123
|
+
value = exec_insert(sql, name, binds, pk, sequence_name)
|
120
124
|
id_value || last_inserted_id(value)
|
121
125
|
end
|
126
|
+
alias create insert
|
127
|
+
alias insert_sql insert
|
122
128
|
|
123
129
|
# Executes the update statement and returns the number of rows affected.
|
124
130
|
def update(arel, name = nil, binds = [])
|
125
131
|
exec_update(to_sql(arel, binds), name, binds)
|
126
132
|
end
|
133
|
+
alias update_sql update
|
127
134
|
|
128
135
|
# Executes the delete statement and returns the number of rows affected.
|
129
136
|
def delete(arel, name = nil, binds = [])
|
130
137
|
exec_delete(to_sql(arel, binds), name, binds)
|
131
138
|
end
|
139
|
+
alias delete_sql delete
|
132
140
|
|
133
141
|
# Returns +true+ when the connection adapter supports prepared statement
|
134
142
|
# caching, otherwise returns +false+
|
@@ -208,7 +216,7 @@ module ActiveRecord
|
|
208
216
|
# * You are joining an existing open transaction
|
209
217
|
# * You are creating a nested (savepoint) transaction
|
210
218
|
#
|
211
|
-
# The
|
219
|
+
# The mysql2 and postgresql adapters support setting the transaction
|
212
220
|
# isolation level. However, support is disabled for MySQL versions below 5,
|
213
221
|
# because they are affected by a bug[http://bugs.mysql.com/bug.php?id=39170]
|
214
222
|
# which means the isolation level gets persisted outside the transaction.
|
@@ -296,14 +304,15 @@ module ActiveRecord
|
|
296
304
|
# Inserts the given fixture into the table. Overridden in adapters that require
|
297
305
|
# something beyond a simple insert (eg. Oracle).
|
298
306
|
def insert_fixture(fixture, table_name)
|
299
|
-
|
307
|
+
fixture = fixture.stringify_keys
|
300
308
|
|
309
|
+
columns = schema_cache.columns_hash(table_name)
|
301
310
|
binds = fixture.map do |name, value|
|
302
311
|
if column = columns[name]
|
303
312
|
type = lookup_cast_type_from_column(column)
|
304
313
|
Relation::QueryAttribute.new(name, value, type)
|
305
314
|
else
|
306
|
-
raise Fixture::FixtureError, %(table "#{table_name}" has no column named
|
315
|
+
raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
|
307
316
|
end
|
308
317
|
end
|
309
318
|
key_list = fixture.keys.map { |name| quote_column_name(name) }
|
@@ -344,18 +353,12 @@ module ActiveRecord
|
|
344
353
|
# The default strategy for an UPDATE with joins is to use a subquery. This doesn't work
|
345
354
|
# on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in
|
346
355
|
# an UPDATE statement, so in the MySQL adapters we redefine this to do that.
|
347
|
-
def join_to_update(update, select)
|
348
|
-
key = update.key
|
356
|
+
def join_to_update(update, select, key) # :nodoc:
|
349
357
|
subselect = subquery_for(key, select)
|
350
358
|
|
351
359
|
update.where key.in(subselect)
|
352
360
|
end
|
353
|
-
|
354
|
-
def join_to_delete(delete, select, key) #:nodoc:
|
355
|
-
subselect = subquery_for(key, select)
|
356
|
-
|
357
|
-
delete.where key.in(subselect)
|
358
|
-
end
|
361
|
+
alias join_to_delete join_to_update
|
359
362
|
|
360
363
|
protected
|
361
364
|
|
@@ -375,24 +378,8 @@ module ActiveRecord
|
|
375
378
|
exec_query(sql, name, binds, prepare: true)
|
376
379
|
end
|
377
380
|
|
378
|
-
# Returns the last auto-generated ID from the affected table.
|
379
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
380
|
-
execute(sql, name)
|
381
|
-
id_value
|
382
|
-
end
|
383
|
-
|
384
|
-
# Executes the update statement and returns the number of rows affected.
|
385
|
-
def update_sql(sql, name = nil)
|
386
|
-
execute(sql, name)
|
387
|
-
end
|
388
|
-
|
389
|
-
# Executes the delete statement and returns the number of rows affected.
|
390
|
-
def delete_sql(sql, name = nil)
|
391
|
-
update_sql(sql, name)
|
392
|
-
end
|
393
|
-
|
394
381
|
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
|
395
|
-
[sql, binds]
|
382
|
+
[sql, binds, pk, sequence_name]
|
396
383
|
end
|
397
384
|
|
398
385
|
def last_inserted_id(result)
|
@@ -93,7 +93,7 @@ module ActiveRecord
|
|
93
93
|
# Override to return the quoted table name for assignment. Defaults to
|
94
94
|
# table quoting.
|
95
95
|
#
|
96
|
-
# This works for
|
96
|
+
# This works for mysql2 where table.column can be used to
|
97
97
|
# resolve ambiguity.
|
98
98
|
#
|
99
99
|
# We override this in the sqlite3 and postgresql adapters to use only
|
@@ -102,9 +102,13 @@ module ActiveRecord
|
|
102
102
|
quote_table_name("#{table}.#{attr}")
|
103
103
|
end
|
104
104
|
|
105
|
-
def quote_default_expression(value, column)
|
106
|
-
value
|
107
|
-
|
105
|
+
def quote_default_expression(value, column) # :nodoc:
|
106
|
+
if value.is_a?(Proc)
|
107
|
+
value.call
|
108
|
+
else
|
109
|
+
value = lookup_cast_type(column.sql_type).serialize(value)
|
110
|
+
quote(value)
|
111
|
+
end
|
108
112
|
end
|
109
113
|
|
110
114
|
def quoted_true
|
@@ -76,11 +76,17 @@ module ActiveRecord
|
|
76
76
|
def schema_default(column)
|
77
77
|
type = lookup_cast_type_from_column(column)
|
78
78
|
default = type.deserialize(column.default)
|
79
|
-
|
79
|
+
if default.nil?
|
80
|
+
schema_expression(column)
|
81
|
+
else
|
80
82
|
type.type_cast_for_schema(default)
|
81
83
|
end
|
82
84
|
end
|
83
85
|
|
86
|
+
def schema_expression(column)
|
87
|
+
"-> { #{column.default_function.inspect} }" if column.default_function
|
88
|
+
end
|
89
|
+
|
84
90
|
def schema_collation(column)
|
85
91
|
column.collation.inspect if column.collation
|
86
92
|
end
|
@@ -92,7 +92,9 @@ module ActiveRecord
|
|
92
92
|
|
93
93
|
# Returns an array of Column objects for the table specified by +table_name+.
|
94
94
|
# See the concrete implementation for details on the expected parameter values.
|
95
|
-
def columns(table_name)
|
95
|
+
def columns(table_name)
|
96
|
+
raise NotImplementedError, "#columns is not implemented"
|
97
|
+
end
|
96
98
|
|
97
99
|
# Checks to see if a column exists in a given table.
|
98
100
|
#
|
@@ -110,18 +112,25 @@ module ActiveRecord
|
|
110
112
|
#
|
111
113
|
def column_exists?(table_name, column_name, type = nil, options = {})
|
112
114
|
column_name = column_name.to_s
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
115
|
+
checks = []
|
116
|
+
checks << lambda { |c| c.name == column_name }
|
117
|
+
checks << lambda { |c| c.type == type } if type
|
118
|
+
(migration_keys - [:name]).each do |attr|
|
119
|
+
checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
|
120
|
+
end
|
121
|
+
|
122
|
+
columns(table_name).any? { |c| checks.all? { |check| check[c] } }
|
120
123
|
end
|
121
124
|
|
122
125
|
# Returns just a table's primary key
|
123
126
|
def primary_key(table_name)
|
124
127
|
pks = primary_keys(table_name)
|
128
|
+
warn <<-WARNING.strip_heredoc if pks.count > 1
|
129
|
+
WARNING: Rails does not support composite primary key.
|
130
|
+
|
131
|
+
#{table_name} has composite primary key. Composite primary key is ignored.
|
132
|
+
WARNING
|
133
|
+
|
125
134
|
pks.first if pks.one?
|
126
135
|
end
|
127
136
|
|
@@ -957,9 +966,9 @@ module ActiveRecord
|
|
957
966
|
def dump_schema_information #:nodoc:
|
958
967
|
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
|
959
968
|
|
960
|
-
|
961
|
-
|
962
|
-
|
969
|
+
sql = "INSERT INTO #{sm_table} (version) VALUES "
|
970
|
+
sql << ActiveRecord::SchemaMigration.order('version').pluck(:version).map {|v| "('#{v}')" }.join(', ')
|
971
|
+
sql << ";\n\n"
|
963
972
|
end
|
964
973
|
|
965
974
|
# Should not be called normally, but this operation is non-destructive.
|
@@ -968,6 +977,14 @@ module ActiveRecord
|
|
968
977
|
ActiveRecord::SchemaMigration.create_table
|
969
978
|
end
|
970
979
|
|
980
|
+
def initialize_internal_metadata_table
|
981
|
+
ActiveRecord::InternalMetadata.create_table
|
982
|
+
end
|
983
|
+
|
984
|
+
def internal_string_options_for_primary_key # :nodoc:
|
985
|
+
{ primary_key: true }
|
986
|
+
end
|
987
|
+
|
971
988
|
def assume_migrated_upto_version(version, migrations_paths)
|
972
989
|
migrations_paths = Array(migrations_paths)
|
973
990
|
version = version.to_i
|
@@ -983,14 +1000,12 @@ module ActiveRecord
|
|
983
1000
|
execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
|
984
1001
|
end
|
985
1002
|
|
986
|
-
|
987
|
-
|
988
|
-
if
|
989
|
-
raise "Duplicate migration #{
|
990
|
-
elsif v < version
|
991
|
-
execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
|
992
|
-
inserted << v
|
1003
|
+
inserting = (versions - migrated).select {|v| v < version}
|
1004
|
+
if inserting.any?
|
1005
|
+
if (duplicate = inserting.detect {|v| inserting.count(v) > 1})
|
1006
|
+
raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
|
993
1007
|
end
|
1008
|
+
execute "INSERT INTO #{sm_table} (version) VALUES #{inserting.map {|v| "('#{v}')"}.join(', ') }"
|
994
1009
|
end
|
995
1010
|
end
|
996
1011
|
|
@@ -1028,11 +1043,12 @@ module ActiveRecord
|
|
1028
1043
|
end
|
1029
1044
|
|
1030
1045
|
# Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
|
1031
|
-
#
|
1046
|
+
# PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
|
1032
1047
|
# require the order columns appear in the SELECT.
|
1033
1048
|
#
|
1034
1049
|
# columns_for_distinct("posts.id", ["posts.created_at desc"])
|
1035
|
-
|
1050
|
+
#
|
1051
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
1036
1052
|
columns
|
1037
1053
|
end
|
1038
1054
|
|
@@ -33,6 +33,7 @@ module ActiveRecord
|
|
33
33
|
|
34
34
|
class NullTransaction #:nodoc:
|
35
35
|
def initialize; end
|
36
|
+
def state; end
|
36
37
|
def closed?; true; end
|
37
38
|
def open?; false; end
|
38
39
|
def joinable?; false; end
|
@@ -166,8 +167,13 @@ module ActiveRecord
|
|
166
167
|
|
167
168
|
def commit_transaction
|
168
169
|
transaction = @stack.last
|
169
|
-
|
170
|
-
|
170
|
+
|
171
|
+
begin
|
172
|
+
transaction.before_commit_records
|
173
|
+
ensure
|
174
|
+
@stack.pop
|
175
|
+
end
|
176
|
+
|
171
177
|
transaction.commit
|
172
178
|
transaction.commit_records
|
173
179
|
end
|
@@ -23,6 +23,7 @@ module ActiveRecord
|
|
23
23
|
autoload :TableDefinition
|
24
24
|
autoload :Table
|
25
25
|
autoload :AlterTable
|
26
|
+
autoload :ReferenceDefinition
|
26
27
|
end
|
27
28
|
|
28
29
|
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
|
@@ -310,12 +311,6 @@ module ActiveRecord
|
|
310
311
|
{}
|
311
312
|
end
|
312
313
|
|
313
|
-
# Returns a bind substitution value given a bind +column+
|
314
|
-
# NOTE: The column param is currently being used by the sqlserver-adapter
|
315
|
-
def substitute_at(column, _unused = 0)
|
316
|
-
Arel::Nodes::BindParam.new
|
317
|
-
end
|
318
|
-
|
319
314
|
# REFERENTIAL INTEGRITY ====================================
|
320
315
|
|
321
316
|
# Override to turn off referential integrity while executing <tt>&block</tt>.
|
@@ -376,7 +371,7 @@ module ActiveRecord
|
|
376
371
|
end
|
377
372
|
|
378
373
|
# Provides access to the underlying database driver for this adapter. For
|
379
|
-
# example, this method returns a
|
374
|
+
# example, this method returns a Mysql2::Client object in case of Mysql2Adapter,
|
380
375
|
# and a PGconn object in case of PostgreSQLAdapter.
|
381
376
|
#
|
382
377
|
# This is useful for when you need to call a proprietary method such as
|
@@ -391,19 +386,17 @@ module ActiveRecord
|
|
391
386
|
def release_savepoint(name = nil)
|
392
387
|
end
|
393
388
|
|
394
|
-
def case_sensitive_modifier(node, table_attribute)
|
395
|
-
node
|
396
|
-
end
|
397
|
-
|
398
389
|
def case_sensitive_comparison(table, attribute, column, value)
|
399
|
-
|
400
|
-
|
401
|
-
|
390
|
+
if value.nil?
|
391
|
+
table[attribute].eq(value)
|
392
|
+
else
|
393
|
+
table[attribute].eq(Arel::Nodes::BindParam.new)
|
394
|
+
end
|
402
395
|
end
|
403
396
|
|
404
397
|
def case_insensitive_comparison(table, attribute, column, value)
|
405
398
|
if can_perform_case_insensitive_comparison_for?(column)
|
406
|
-
table[attribute].lower.eq(table.lower(
|
399
|
+
table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
|
407
400
|
else
|
408
401
|
case_sensitive_comparison(table, attribute, column, value)
|
409
402
|
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/mysql/column'
|
3
|
+
require 'active_record/connection_adapters/mysql/explain_pretty_printer'
|
2
4
|
require 'active_record/connection_adapters/mysql/schema_creation'
|
3
5
|
require 'active_record/connection_adapters/mysql/schema_definitions'
|
4
6
|
require 'active_record/connection_adapters/mysql/schema_dumper'
|
7
|
+
require 'active_record/connection_adapters/mysql/type_metadata'
|
5
8
|
|
6
9
|
require 'active_support/core_ext/string/strip'
|
7
10
|
|
@@ -19,108 +22,16 @@ module ActiveRecord
|
|
19
22
|
MySQL::SchemaCreation.new(self)
|
20
23
|
end
|
21
24
|
|
22
|
-
class Column < ConnectionAdapters::Column # :nodoc:
|
23
|
-
delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
|
24
|
-
|
25
|
-
def initialize(*)
|
26
|
-
super
|
27
|
-
assert_valid_default(default)
|
28
|
-
extract_default
|
29
|
-
end
|
30
|
-
|
31
|
-
def extract_default
|
32
|
-
if blob_or_text_column?
|
33
|
-
@default = null || strict ? nil : ''
|
34
|
-
elsif missing_default_forged_as_empty_string?(default)
|
35
|
-
@default = nil
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def has_default?
|
40
|
-
return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
|
41
|
-
super
|
42
|
-
end
|
43
|
-
|
44
|
-
def blob_or_text_column?
|
45
|
-
sql_type =~ /blob/i || type == :text
|
46
|
-
end
|
47
|
-
|
48
|
-
def unsigned?
|
49
|
-
/unsigned/ === sql_type
|
50
|
-
end
|
51
|
-
|
52
|
-
def case_sensitive?
|
53
|
-
collation && !collation.match(/_ci$/)
|
54
|
-
end
|
55
|
-
|
56
|
-
def auto_increment?
|
57
|
-
extra == 'auto_increment'
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
# MySQL misreports NOT NULL column default when none is given.
|
63
|
-
# We can't detect this for columns which may have a legitimate ''
|
64
|
-
# default (string) but we can for others (integer, datetime, boolean,
|
65
|
-
# and the rest).
|
66
|
-
#
|
67
|
-
# Test whether the column has default '', is not null, and is not
|
68
|
-
# a type allowing default ''.
|
69
|
-
def missing_default_forged_as_empty_string?(default)
|
70
|
-
type != :string && !null && default == ''
|
71
|
-
end
|
72
|
-
|
73
|
-
def assert_valid_default(default)
|
74
|
-
if blob_or_text_column? && default.present?
|
75
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class MysqlTypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
|
81
|
-
attr_reader :extra, :strict
|
82
|
-
|
83
|
-
def initialize(type_metadata, extra: "", strict: false)
|
84
|
-
super(type_metadata)
|
85
|
-
@type_metadata = type_metadata
|
86
|
-
@extra = extra
|
87
|
-
@strict = strict
|
88
|
-
end
|
89
|
-
|
90
|
-
def ==(other)
|
91
|
-
other.is_a?(MysqlTypeMetadata) &&
|
92
|
-
attributes_for_hash == other.attributes_for_hash
|
93
|
-
end
|
94
|
-
alias eql? ==
|
95
|
-
|
96
|
-
def hash
|
97
|
-
attributes_for_hash.hash
|
98
|
-
end
|
99
|
-
|
100
|
-
protected
|
101
|
-
|
102
|
-
def attributes_for_hash
|
103
|
-
[self.class, @type_metadata, extra, strict]
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
25
|
##
|
108
26
|
# :singleton-method:
|
109
|
-
# By default, the
|
110
|
-
# as boolean. If you wish to disable this emulation
|
111
|
-
# behavior in versions 0.13.1 and earlier) you can add the following line
|
27
|
+
# By default, the Mysql2Adapter will consider all columns of type <tt>tinyint(1)</tt>
|
28
|
+
# as boolean. If you wish to disable this emulation you can add the following line
|
112
29
|
# to your application.rb file:
|
113
30
|
#
|
114
|
-
# ActiveRecord::ConnectionAdapters::
|
31
|
+
# ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false
|
115
32
|
class_attribute :emulate_booleans
|
116
33
|
self.emulate_booleans = true
|
117
34
|
|
118
|
-
LOST_CONNECTION_ERROR_MESSAGES = [
|
119
|
-
"Server shutdown in progress",
|
120
|
-
"Broken pipe",
|
121
|
-
"Lost connection to MySQL server during query",
|
122
|
-
"MySQL server has gone away" ]
|
123
|
-
|
124
35
|
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
125
36
|
|
126
37
|
NATIVE_DATABASE_TYPES = {
|
@@ -156,14 +67,12 @@ module ActiveRecord
|
|
156
67
|
end
|
157
68
|
end
|
158
69
|
|
159
|
-
MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191
|
160
70
|
CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32']
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
71
|
+
|
72
|
+
def internal_string_options_for_primary_key # :nodoc:
|
73
|
+
super.tap { |options|
|
74
|
+
options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
|
75
|
+
}
|
167
76
|
end
|
168
77
|
|
169
78
|
def version
|
@@ -248,7 +157,7 @@ module ActiveRecord
|
|
248
157
|
end
|
249
158
|
|
250
159
|
def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
|
251
|
-
Column.new(field, default, sql_type_metadata, null, default_function, collation)
|
160
|
+
MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation)
|
252
161
|
end
|
253
162
|
|
254
163
|
# Must return the MySQL error number from the exception, if the exception has an
|
@@ -322,72 +231,7 @@ module ActiveRecord
|
|
322
231
|
result = exec_query(sql, 'EXPLAIN', binds)
|
323
232
|
elapsed = Time.now - start
|
324
233
|
|
325
|
-
ExplainPrettyPrinter.new.pp(result, elapsed)
|
326
|
-
end
|
327
|
-
|
328
|
-
class ExplainPrettyPrinter # :nodoc:
|
329
|
-
# Pretty prints the result of an EXPLAIN in a way that resembles the output of the
|
330
|
-
# MySQL shell:
|
331
|
-
#
|
332
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
333
|
-
# | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
|
334
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
335
|
-
# | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | |
|
336
|
-
# | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where |
|
337
|
-
# +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+
|
338
|
-
# 2 rows in set (0.00 sec)
|
339
|
-
#
|
340
|
-
# This is an exercise in Ruby hyperrealism :).
|
341
|
-
def pp(result, elapsed)
|
342
|
-
widths = compute_column_widths(result)
|
343
|
-
separator = build_separator(widths)
|
344
|
-
|
345
|
-
pp = []
|
346
|
-
|
347
|
-
pp << separator
|
348
|
-
pp << build_cells(result.columns, widths)
|
349
|
-
pp << separator
|
350
|
-
|
351
|
-
result.rows.each do |row|
|
352
|
-
pp << build_cells(row, widths)
|
353
|
-
end
|
354
|
-
|
355
|
-
pp << separator
|
356
|
-
pp << build_footer(result.rows.length, elapsed)
|
357
|
-
|
358
|
-
pp.join("\n") + "\n"
|
359
|
-
end
|
360
|
-
|
361
|
-
private
|
362
|
-
|
363
|
-
def compute_column_widths(result)
|
364
|
-
[].tap do |widths|
|
365
|
-
result.columns.each_with_index do |column, i|
|
366
|
-
cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s}
|
367
|
-
widths << cells_in_column.map(&:length).max
|
368
|
-
end
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
def build_separator(widths)
|
373
|
-
padding = 1
|
374
|
-
'+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+'
|
375
|
-
end
|
376
|
-
|
377
|
-
def build_cells(items, widths)
|
378
|
-
cells = []
|
379
|
-
items.each_with_index do |item, i|
|
380
|
-
item = 'NULL' if item.nil?
|
381
|
-
justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust'
|
382
|
-
cells << item.to_s.send(justifier, widths[i])
|
383
|
-
end
|
384
|
-
'| ' + cells.join(' | ') + ' |'
|
385
|
-
end
|
386
|
-
|
387
|
-
def build_footer(nrows, elapsed)
|
388
|
-
rows_label = nrows == 1 ? 'row' : 'rows'
|
389
|
-
"#{nrows} #{rows_label} in set (%.2f sec)" % elapsed
|
390
|
-
end
|
234
|
+
MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
|
391
235
|
end
|
392
236
|
|
393
237
|
def clear_cache!
|
@@ -400,18 +244,13 @@ module ActiveRecord
|
|
400
244
|
log(sql, name) { @connection.query(sql) }
|
401
245
|
end
|
402
246
|
|
403
|
-
#
|
404
|
-
# stuff in an abstract way without concerning ourselves about whether it
|
405
|
-
# explicitly freed or not.
|
406
|
-
def execute_and_free(sql, name = nil)
|
247
|
+
# Mysql2Adapter doesn't have to free a result after using it, but we use this method
|
248
|
+
# to write stuff in an abstract way without concerning ourselves about whether it
|
249
|
+
# needs to be explicitly freed or not.
|
250
|
+
def execute_and_free(sql, name = nil) # :nodoc:
|
407
251
|
yield execute(sql, name)
|
408
252
|
end
|
409
253
|
|
410
|
-
def update_sql(sql, name = nil) #:nodoc:
|
411
|
-
super
|
412
|
-
@connection.affected_rows
|
413
|
-
end
|
414
|
-
|
415
254
|
def begin_db_transaction
|
416
255
|
execute "BEGIN"
|
417
256
|
end
|
@@ -432,7 +271,7 @@ module ActiveRecord
|
|
432
271
|
# In the simple case, MySQL allows us to place JOINs directly into the UPDATE
|
433
272
|
# query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
|
434
273
|
# these, we must use a subquery.
|
435
|
-
def join_to_update(update, select)
|
274
|
+
def join_to_update(update, select, key) # :nodoc:
|
436
275
|
if select.limit || select.offset || select.orders.any?
|
437
276
|
super
|
438
277
|
else
|
@@ -521,6 +360,7 @@ module ActiveRecord
|
|
521
360
|
end
|
522
361
|
|
523
362
|
def table_exists?(table_name)
|
363
|
+
# Update lib/active_record/internal_metadata.rb when this gets removed
|
524
364
|
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
525
365
|
#table_exists? currently checks both tables and views.
|
526
366
|
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
@@ -583,12 +423,16 @@ module ActiveRecord
|
|
583
423
|
end
|
584
424
|
|
585
425
|
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
586
|
-
def columns(table_name)
|
426
|
+
def columns(table_name) # :nodoc:
|
587
427
|
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
588
428
|
execute_and_free(sql, 'SCHEMA') do |result|
|
589
429
|
each_hash(result).map do |field|
|
590
430
|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
|
591
|
-
|
431
|
+
if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
|
432
|
+
new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
|
433
|
+
else
|
434
|
+
new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
|
435
|
+
end
|
592
436
|
end
|
593
437
|
end
|
594
438
|
end
|
@@ -637,6 +481,7 @@ module ActiveRecord
|
|
637
481
|
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
638
482
|
# In that case, +options+ and the block will be used by create_table.
|
639
483
|
def drop_table(table_name, options = {})
|
484
|
+
create_table_info_cache.delete(table_name) if create_table_info_cache.key?(table_name)
|
640
485
|
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
641
486
|
end
|
642
487
|
|
@@ -765,16 +610,11 @@ module ActiveRecord
|
|
765
610
|
SQL
|
766
611
|
end
|
767
612
|
|
768
|
-
def case_sensitive_modifier(node, table_attribute)
|
769
|
-
node = Arel::Nodes.build_quoted node, table_attribute
|
770
|
-
Arel::Nodes::Bin.new(node)
|
771
|
-
end
|
772
|
-
|
773
613
|
def case_sensitive_comparison(table, attribute, column, value)
|
774
|
-
if column.case_sensitive?
|
775
|
-
table[attribute].eq(value)
|
776
|
-
else
|
614
|
+
if value.nil? || column.case_sensitive?
|
777
615
|
super
|
616
|
+
else
|
617
|
+
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
|
778
618
|
end
|
779
619
|
end
|
780
620
|
|
@@ -782,10 +622,25 @@ module ActiveRecord
|
|
782
622
|
if column.case_sensitive?
|
783
623
|
super
|
784
624
|
else
|
785
|
-
table[attribute].eq(
|
625
|
+
table[attribute].eq(Arel::Nodes::BindParam.new)
|
786
626
|
end
|
787
627
|
end
|
788
628
|
|
629
|
+
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
630
|
+
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
631
|
+
# distinct queries, and requires that the ORDER BY include the distinct column.
|
632
|
+
# See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
|
633
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
634
|
+
order_columns = orders.reject(&:blank?).map { |s|
|
635
|
+
# Convert Arel node to string
|
636
|
+
s = s.to_sql unless s.is_a?(String)
|
637
|
+
# Remove any ASC/DESC modifiers
|
638
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
|
639
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
640
|
+
|
641
|
+
[super, *order_columns].join(', ')
|
642
|
+
end
|
643
|
+
|
789
644
|
def strict_mode?
|
790
645
|
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
791
646
|
end
|
@@ -838,7 +693,7 @@ module ActiveRecord
|
|
838
693
|
|
839
694
|
def register_integer_type(mapping, key, options) # :nodoc:
|
840
695
|
mapping.register_type(key) do |sql_type|
|
841
|
-
if /
|
696
|
+
if /\bunsigned\z/ === sql_type
|
842
697
|
Type::UnsignedInteger.new(options)
|
843
698
|
else
|
844
699
|
Type::Integer.new(options)
|
@@ -855,7 +710,7 @@ module ActiveRecord
|
|
855
710
|
end
|
856
711
|
|
857
712
|
def fetch_type_metadata(sql_type, extra = "")
|
858
|
-
|
713
|
+
MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
|
859
714
|
end
|
860
715
|
|
861
716
|
def add_index_length(option_strings, column_names, options = {})
|
@@ -965,11 +820,13 @@ module ActiveRecord
|
|
965
820
|
subsubselect = select.clone
|
966
821
|
subsubselect.projections = [key]
|
967
822
|
|
823
|
+
# Materialize subquery by adding distinct
|
824
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
825
|
+
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
826
|
+
|
968
827
|
subselect = Arel::SelectManager.new(select.engine)
|
969
828
|
subselect.project Arel.sql(key.name)
|
970
|
-
|
971
|
-
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
972
|
-
subselect.from subsubselect.distinct.as('__active_record_temp')
|
829
|
+
subselect.from subsubselect.as('__active_record_temp')
|
973
830
|
end
|
974
831
|
|
975
832
|
def mariadb?
|
@@ -1032,9 +889,12 @@ module ActiveRecord
|
|
1032
889
|
end
|
1033
890
|
end
|
1034
891
|
|
892
|
+
def create_table_info_cache # :nodoc:
|
893
|
+
@create_table_info_cache ||= {}
|
894
|
+
end
|
895
|
+
|
1035
896
|
def create_table_info(table_name) # :nodoc:
|
1036
|
-
|
1037
|
-
@create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
897
|
+
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
1038
898
|
end
|
1039
899
|
|
1040
900
|
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
|
@@ -1048,7 +908,6 @@ module ActiveRecord
|
|
1048
908
|
when 3; 'mediumint'
|
1049
909
|
when nil, 4; 'int'
|
1050
910
|
when 5..8; 'bigint'
|
1051
|
-
when 11; 'int(11)' # backward compatibility with Rails 2.0
|
1052
911
|
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
1053
912
|
end
|
1054
913
|
end
|