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.

Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -15
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +1 -1
  5. data/lib/active_record.rb +2 -1
  6. data/lib/active_record/aggregations.rb +1 -1
  7. data/lib/active_record/associations.rb +3 -0
  8. data/lib/active_record/associations/builder/belongs_to.rb +1 -1
  9. data/lib/active_record/associations/builder/has_one.rb +1 -1
  10. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  11. data/lib/active_record/associations/has_many_through_association.rb +5 -0
  12. data/lib/active_record/associations/join_dependency/join_association.rb +1 -2
  13. data/lib/active_record/associations/preloader/through_association.rb +7 -2
  14. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -7
  15. data/lib/active_record/autosave_association.rb +18 -3
  16. data/lib/active_record/base.rb +0 -3
  17. data/lib/active_record/collection_cache_key.rb +12 -3
  18. data/lib/active_record/connection_adapters/abstract/database_statements.rb +21 -34
  19. data/lib/active_record/connection_adapters/abstract/quoting.rb +8 -4
  20. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +7 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +36 -20
  23. data/lib/active_record/connection_adapters/abstract/transaction.rb +8 -2
  24. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -15
  25. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +57 -198
  26. data/lib/active_record/connection_adapters/column.rb +1 -1
  27. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  28. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  29. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +24 -0
  30. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  31. data/lib/active_record/connection_adapters/mysql2_adapter.rb +15 -34
  32. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +7 -69
  33. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  34. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +4 -0
  35. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
  36. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +8 -1
  37. data/lib/active_record/connection_adapters/postgresql/quoting.rb +5 -4
  38. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -7
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +15 -23
  40. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  41. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -31
  42. data/lib/active_record/connection_handling.rb +1 -1
  43. data/lib/active_record/core.rb +1 -1
  44. data/lib/active_record/counter_cache.rb +4 -4
  45. data/lib/active_record/enum.rb +8 -5
  46. data/lib/active_record/errors.rb +6 -1
  47. data/lib/active_record/gem_version.rb +1 -1
  48. data/lib/active_record/inheritance.rb +6 -1
  49. data/lib/active_record/internal_metadata.rb +56 -0
  50. data/lib/active_record/migration.rb +85 -20
  51. data/lib/active_record/migration/compatibility.rb +28 -2
  52. data/lib/active_record/model_schema.rb +25 -1
  53. data/lib/active_record/persistence.rb +11 -10
  54. data/lib/active_record/railtie.rb +6 -3
  55. data/lib/active_record/railties/databases.rake +20 -6
  56. data/lib/active_record/reflection.rb +39 -31
  57. data/lib/active_record/relation.rb +4 -4
  58. data/lib/active_record/relation/batches.rb +26 -41
  59. data/lib/active_record/relation/batches/batch_enumerator.rb +6 -6
  60. data/lib/active_record/relation/finder_methods.rb +35 -13
  61. data/lib/active_record/relation/from_clause.rb +1 -1
  62. data/lib/active_record/relation/merger.rb +3 -0
  63. data/lib/active_record/relation/predicate_builder.rb +19 -1
  64. data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -1
  65. data/lib/active_record/relation/query_methods.rb +37 -19
  66. data/lib/active_record/relation/record_fetch_warning.rb +4 -6
  67. data/lib/active_record/relation/where_clause.rb +1 -1
  68. data/lib/active_record/relation/where_clause_factory.rb +1 -0
  69. data/lib/active_record/sanitization.rb +1 -1
  70. data/lib/active_record/schema.rb +3 -0
  71. data/lib/active_record/schema_dumper.rb +1 -1
  72. data/lib/active_record/schema_migration.rb +5 -14
  73. data/lib/active_record/scoping.rb +17 -11
  74. data/lib/active_record/scoping/default.rb +2 -2
  75. data/lib/active_record/tasks/database_tasks.rb +18 -0
  76. data/lib/active_record/timestamp.rb +5 -1
  77. data/lib/active_record/transactions.rb +3 -3
  78. data/lib/active_record/validations/uniqueness.rb +6 -3
  79. data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -0
  80. data/lib/rails/generators/active_record/model/model_generator.rb +7 -1
  81. metadata +14 -7
@@ -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
- timestamp = collection.max_by(&timestamp_column).public_send(timestamp_column)
10
+ if size > 0
11
+ timestamp = collection.max_by(&timestamp_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
- size = result["size"]
21
- timestamp = column_type.deserialize(result["timestamp"])
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
- # Returns the last auto-generated ID from the affected table.
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 = exec_insert(sql, name, binds, pk, sequence_name)
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 mysql, mysql2 and postgresql adapters support setting the transaction
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
- columns = schema_cache.columns_hash(table_name)
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 "#{name}".)
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) #:nodoc:
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 mysql and mysql2 where table.column can be used to
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) #:nodoc:
106
- value = lookup_cast_type(column.sql_type).serialize(value)
107
- quote(value)
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
@@ -69,7 +69,7 @@ module ActiveRecord
69
69
  def initialize(
70
70
  name,
71
71
  polymorphic: false,
72
- index: false,
72
+ index: true,
73
73
  foreign_key: false,
74
74
  type: :integer,
75
75
  **options
@@ -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
- unless default.nil?
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) end
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
- columns(table_name).any?{ |c| c.name == column_name &&
114
- (!type || c.type == type) &&
115
- (!options.key?(:limit) || c.limit == options[:limit]) &&
116
- (!options.key?(:precision) || c.precision == options[:precision]) &&
117
- (!options.key?(:scale) || c.scale == options[:scale]) &&
118
- (!options.key?(:default) || c.default == options[:default]) &&
119
- (!options.key?(:null) || c.null == options[:null]) }
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
- ActiveRecord::SchemaMigration.order('version').map { |sm|
961
- "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
962
- }.join "\n\n"
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
- inserted = Set.new
987
- (versions - migrated).each do |v|
988
- if inserted.include?(v)
989
- raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
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
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
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
- def columns_for_distinct(columns, orders) #:nodoc:
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
- transaction.before_commit_records
170
- @stack.pop
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 Mysql object in case of MysqlAdapter,
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
- table_attr = table[attribute]
400
- value = case_sensitive_modifier(value, table_attr) unless value.nil?
401
- table_attr.eq(value)
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(value))
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 MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
110
- # as boolean. If you wish to disable this emulation (which was the default
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::Mysql[2]Adapter.emulate_booleans = false
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
- def initialize_schema_migrations_table
162
- if CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
163
- ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN)
164
- else
165
- ActiveRecord::SchemaMigration.create_table
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
- # MysqlAdapter has to free a result after using it, so we use this method to write
404
- # stuff in an abstract way without concerning ourselves about whether it needs to be
405
- # explicitly freed or not.
406
- def execute_and_free(sql, name = nil) #:nodoc:
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) #:nodoc:
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)#:nodoc:
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
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
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(value)
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 /unsigned/i =~ sql_type
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
- MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
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
- # Materialized subquery by adding distinct
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
- @create_table_info_cache = {}
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