activerecord 4.1.16 → 4.2.11.3
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 +5 -5
- data/CHANGELOG.md +1162 -1801
- data/README.rdoc +15 -10
- data/lib/active_record/aggregations.rb +15 -8
- data/lib/active_record/association_relation.rb +13 -0
- data/lib/active_record/associations/alias_tracker.rb +3 -12
- data/lib/active_record/associations/association.rb +16 -4
- data/lib/active_record/associations/association_scope.rb +83 -38
- data/lib/active_record/associations/belongs_to_association.rb +28 -10
- data/lib/active_record/associations/builder/association.rb +15 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -29
- data/lib/active_record/associations/builder/collection_association.rb +5 -1
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +8 -13
- data/lib/active_record/associations/builder/has_many.rb +1 -1
- data/lib/active_record/associations/builder/has_one.rb +2 -2
- data/lib/active_record/associations/builder/singular_association.rb +8 -1
- data/lib/active_record/associations/collection_association.rb +63 -27
- data/lib/active_record/associations/collection_proxy.rb +29 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +83 -22
- data/lib/active_record/associations/has_many_through_association.rb +49 -26
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
- data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
- data/lib/active_record/associations/join_dependency.rb +26 -13
- data/lib/active_record/associations/preloader/association.rb +14 -11
- data/lib/active_record/associations/preloader/through_association.rb +4 -3
- data/lib/active_record/associations/preloader.rb +36 -26
- data/lib/active_record/associations/singular_association.rb +17 -2
- data/lib/active_record/associations/through_association.rb +5 -12
- data/lib/active_record/associations.rb +158 -49
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +19 -11
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
- data/lib/active_record/attribute_methods/dirty.rb +107 -43
- data/lib/active_record/attribute_methods/primary_key.rb +7 -8
- data/lib/active_record/attribute_methods/query.rb +1 -1
- data/lib/active_record/attribute_methods/read.rb +22 -59
- data/lib/active_record/attribute_methods/serialization.rb +16 -150
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -40
- data/lib/active_record/attribute_methods/write.rb +9 -24
- data/lib/active_record/attribute_methods.rb +56 -94
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +19 -12
- data/lib/active_record/base.rb +13 -24
- data/lib/active_record/callbacks.rb +6 -6
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +84 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +138 -56
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +268 -71
- data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
- data/lib/active_record/connection_adapters/abstract_adapter.rb +171 -59
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +293 -139
- data/lib/active_record/connection_adapters/column.rb +29 -240
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +67 -144
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +131 -43
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
- data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +163 -39
- data/lib/active_record/counter_cache.rb +60 -6
- data/lib/active_record/enum.rb +9 -11
- data/lib/active_record/errors.rb +53 -30
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixtures.rb +55 -69
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +35 -10
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locking/optimistic.rb +46 -26
- data/lib/active_record/migration/command_recorder.rb +19 -2
- data/lib/active_record/migration/join_table.rb +1 -1
- data/lib/active_record/migration.rb +71 -46
- data/lib/active_record/model_schema.rb +52 -58
- data/lib/active_record/nested_attributes.rb +5 -5
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +46 -26
- data/lib/active_record/query_cache.rb +3 -3
- data/lib/active_record/querying.rb +10 -7
- data/lib/active_record/railtie.rb +18 -11
- data/lib/active_record/railties/databases.rake +50 -51
- data/lib/active_record/readonly_attributes.rb +0 -1
- data/lib/active_record/reflection.rb +273 -114
- data/lib/active_record/relation/batches.rb +0 -2
- data/lib/active_record/relation/calculations.rb +41 -37
- data/lib/active_record/relation/finder_methods.rb +70 -47
- data/lib/active_record/relation/merger.rb +39 -29
- data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -5
- data/lib/active_record/relation/predicate_builder.rb +16 -8
- data/lib/active_record/relation/query_methods.rb +114 -65
- data/lib/active_record/relation/spawn_methods.rb +3 -0
- data/lib/active_record/relation.rb +57 -25
- data/lib/active_record/result.rb +18 -7
- data/lib/active_record/sanitization.rb +12 -2
- data/lib/active_record/schema.rb +0 -1
- data/lib/active_record/schema_dumper.rb +59 -28
- data/lib/active_record/schema_migration.rb +5 -4
- data/lib/active_record/scoping/default.rb +6 -4
- data/lib/active_record/scoping/named.rb +4 -0
- data/lib/active_record/serializers/xml_serializer.rb +3 -7
- data/lib/active_record/statement_cache.rb +95 -10
- data/lib/active_record/store.rb +5 -5
- data/lib/active_record/tasks/database_tasks.rb +61 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +20 -11
- data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
- data/lib/active_record/timestamp.rb +9 -7
- data/lib/active_record/transactions.rb +53 -27
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +5 -3
- data/lib/active_record/validations/presence.rb +5 -3
- data/lib/active_record/validations/uniqueness.rb +25 -29
- data/lib/active_record/validations.rb +25 -19
- data/lib/active_record.rb +4 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
- metadata +66 -11
- data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'arel/visitors/bind_visitor'
|
2
|
+
require 'active_support/core_ext/string/strip'
|
2
3
|
|
3
4
|
module ActiveRecord
|
4
5
|
module ConnectionAdapters
|
@@ -6,12 +7,29 @@ module ActiveRecord
|
|
6
7
|
include Savepoints
|
7
8
|
|
8
9
|
class SchemaCreation < AbstractAdapter::SchemaCreation
|
9
|
-
|
10
10
|
def visit_AddColumn(o)
|
11
11
|
add_column_position!(super, column_options(o))
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
15
|
+
|
16
|
+
def visit_DropForeignKey(name)
|
17
|
+
"DROP FOREIGN KEY #{name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_TableDefinition(o)
|
21
|
+
name = o.name
|
22
|
+
create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} "
|
23
|
+
|
24
|
+
statements = o.columns.map { |c| accept c }
|
25
|
+
statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) })
|
26
|
+
|
27
|
+
create_sql << "(#{statements.join(', ')}) " if statements.present?
|
28
|
+
create_sql << "#{o.options}"
|
29
|
+
create_sql << " AS #{@conn.to_sql(o.as)}" if o.as
|
30
|
+
create_sql
|
31
|
+
end
|
32
|
+
|
15
33
|
def visit_ChangeColumnDefinition(o)
|
16
34
|
column = o.column
|
17
35
|
options = o.options
|
@@ -29,38 +47,45 @@ module ActiveRecord
|
|
29
47
|
end
|
30
48
|
sql
|
31
49
|
end
|
50
|
+
|
51
|
+
def index_in_create(table_name, column_name, options)
|
52
|
+
index_name, index_type, index_columns, index_options, index_algorithm, index_using = @conn.add_index_options(table_name, column_name, options)
|
53
|
+
"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_options} #{index_algorithm}"
|
54
|
+
end
|
32
55
|
end
|
33
56
|
|
34
57
|
def schema_creation
|
35
58
|
SchemaCreation.new self
|
36
59
|
end
|
37
60
|
|
61
|
+
def prepare_column_options(column, types) # :nodoc:
|
62
|
+
spec = super
|
63
|
+
spec.delete(:limit) if :boolean === column.type
|
64
|
+
spec
|
65
|
+
end
|
66
|
+
|
38
67
|
class Column < ConnectionAdapters::Column # :nodoc:
|
39
68
|
attr_reader :collation, :strict, :extra
|
40
69
|
|
41
|
-
def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
|
70
|
+
def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
|
42
71
|
@strict = strict
|
43
72
|
@collation = collation
|
44
73
|
@extra = extra
|
45
|
-
super(name, default, sql_type, null)
|
74
|
+
super(name, default, cast_type, sql_type, null)
|
75
|
+
assert_valid_default(default)
|
76
|
+
extract_default
|
46
77
|
end
|
47
78
|
|
48
|
-
def extract_default
|
79
|
+
def extract_default
|
49
80
|
if blob_or_text_column?
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
54
|
-
end
|
55
|
-
elsif missing_default_forged_as_empty_string?(default)
|
56
|
-
nil
|
57
|
-
else
|
58
|
-
super
|
81
|
+
@default = null || strict ? nil : ''
|
82
|
+
elsif missing_default_forged_as_empty_string?(@default)
|
83
|
+
@default = nil
|
59
84
|
end
|
60
85
|
end
|
61
86
|
|
62
87
|
def has_default?
|
63
|
-
return false if blob_or_text_column? #
|
88
|
+
return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
|
64
89
|
super
|
65
90
|
end
|
66
91
|
|
@@ -68,55 +93,18 @@ module ActiveRecord
|
|
68
93
|
sql_type =~ /blob/i || type == :text
|
69
94
|
end
|
70
95
|
|
71
|
-
# Must return the relevant concrete adapter
|
72
|
-
def adapter
|
73
|
-
raise NotImplementedError
|
74
|
-
end
|
75
|
-
|
76
96
|
def case_sensitive?
|
77
97
|
collation && !collation.match(/_ci$/)
|
78
98
|
end
|
79
99
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
case field_type
|
86
|
-
when /enum/i, /set/i then :string
|
87
|
-
when /year/i then :integer
|
88
|
-
when /bit/i then :binary
|
89
|
-
else
|
90
|
-
super
|
91
|
-
end
|
100
|
+
def ==(other)
|
101
|
+
super &&
|
102
|
+
collation == other.collation &&
|
103
|
+
strict == other.strict &&
|
104
|
+
extra == other.extra
|
92
105
|
end
|
93
106
|
|
94
|
-
|
95
|
-
case sql_type
|
96
|
-
when /^enum\((.+)\)/i
|
97
|
-
$1.split(',').map{|enum| enum.strip.length - 2}.max
|
98
|
-
when /blob|text/i
|
99
|
-
case sql_type
|
100
|
-
when /tiny/i
|
101
|
-
255
|
102
|
-
when /medium/i
|
103
|
-
16777215
|
104
|
-
when /long/i
|
105
|
-
2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
|
106
|
-
else
|
107
|
-
super # we could return 65535 here, but we leave it undecorated by default
|
108
|
-
end
|
109
|
-
when /^bigint/i; 8
|
110
|
-
when /^int/i; 4
|
111
|
-
when /^mediumint/i; 3
|
112
|
-
when /^smallint/i; 2
|
113
|
-
when /^tinyint/i; 1
|
114
|
-
when /^float/i; 24
|
115
|
-
when /^double/i; 53
|
116
|
-
else
|
117
|
-
super
|
118
|
-
end
|
119
|
-
end
|
107
|
+
private
|
120
108
|
|
121
109
|
# MySQL misreports NOT NULL column default when none is given.
|
122
110
|
# We can't detect this for columns which may have a legitimate ''
|
@@ -128,6 +116,16 @@ module ActiveRecord
|
|
128
116
|
def missing_default_forged_as_empty_string?(default)
|
129
117
|
type != :string && !null && default == ''
|
130
118
|
end
|
119
|
+
|
120
|
+
def assert_valid_default(default)
|
121
|
+
if blob_or_text_column? && default.present?
|
122
|
+
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def attributes_for_hash
|
127
|
+
super + [collation, strict, extra]
|
128
|
+
end
|
131
129
|
end
|
132
130
|
|
133
131
|
##
|
@@ -157,7 +155,6 @@ module ActiveRecord
|
|
157
155
|
:float => { :name => "float" },
|
158
156
|
:decimal => { :name => "decimal" },
|
159
157
|
:datetime => { :name => "datetime" },
|
160
|
-
:timestamp => { :name => "datetime" },
|
161
158
|
:time => { :name => "time" },
|
162
159
|
:date => { :name => "date" },
|
163
160
|
:binary => { :name => "blob" },
|
@@ -167,28 +164,21 @@ module ActiveRecord
|
|
167
164
|
INDEX_TYPES = [:fulltext, :spatial]
|
168
165
|
INDEX_USINGS = [:btree, :hash]
|
169
166
|
|
170
|
-
class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
|
171
|
-
include Arel::Visitors::BindVisitor
|
172
|
-
end
|
173
|
-
|
174
167
|
# FIXME: Make the first parameter more similar for the two adapters
|
175
168
|
def initialize(connection, logger, connection_options, config)
|
176
169
|
super(connection, logger)
|
177
170
|
@connection_options, @config = connection_options, config
|
178
171
|
@quoted_column_names, @quoted_table_names = {}, {}
|
179
172
|
|
173
|
+
@visitor = Arel::Visitors::MySQL.new self
|
174
|
+
|
180
175
|
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
181
176
|
@prepared_statements = true
|
182
|
-
@visitor = Arel::Visitors::MySQL.new self
|
183
177
|
else
|
184
|
-
@
|
178
|
+
@prepared_statements = false
|
185
179
|
end
|
186
180
|
end
|
187
181
|
|
188
|
-
def adapter_name #:nodoc:
|
189
|
-
self.class::ADAPTER_NAME
|
190
|
-
end
|
191
|
-
|
192
182
|
# Returns true, since this connection adapter supports migrations.
|
193
183
|
def supports_migrations?
|
194
184
|
true
|
@@ -208,23 +198,28 @@ module ActiveRecord
|
|
208
198
|
true
|
209
199
|
end
|
210
200
|
|
211
|
-
def type_cast(value, column)
|
212
|
-
case value
|
213
|
-
when TrueClass
|
214
|
-
1
|
215
|
-
when FalseClass
|
216
|
-
0
|
217
|
-
else
|
218
|
-
super
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
201
|
# MySQL 4 technically support transaction isolation, but it is affected by a bug
|
223
202
|
# where the transaction level gets persisted for the whole session:
|
224
203
|
#
|
225
204
|
# http://bugs.mysql.com/bug.php?id=39170
|
226
205
|
def supports_transaction_isolation?
|
227
|
-
version
|
206
|
+
version >= '5.0.0'
|
207
|
+
end
|
208
|
+
|
209
|
+
def supports_indexes_in_create?
|
210
|
+
true
|
211
|
+
end
|
212
|
+
|
213
|
+
def supports_foreign_keys?
|
214
|
+
true
|
215
|
+
end
|
216
|
+
|
217
|
+
def supports_views?
|
218
|
+
version >= '5.0.0'
|
219
|
+
end
|
220
|
+
|
221
|
+
def supports_datetime_with_precision?
|
222
|
+
version >= '5.6.4'
|
228
223
|
end
|
229
224
|
|
230
225
|
def native_database_types
|
@@ -243,12 +238,11 @@ module ActiveRecord
|
|
243
238
|
raise NotImplementedError
|
244
239
|
end
|
245
240
|
|
246
|
-
|
247
|
-
|
248
|
-
Column.new(field, default, type, null, collation, extra)
|
241
|
+
def new_column(field, default, cast_type, sql_type = nil, null = true, collation = "", extra = "") # :nodoc:
|
242
|
+
Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
|
249
243
|
end
|
250
244
|
|
251
|
-
# Must return the
|
245
|
+
# Must return the MySQL error number from the exception, if the exception has an
|
252
246
|
# error number.
|
253
247
|
def error_number(exception) # :nodoc:
|
254
248
|
raise NotImplementedError
|
@@ -256,12 +250,9 @@ module ActiveRecord
|
|
256
250
|
|
257
251
|
# QUOTING ==================================================
|
258
252
|
|
259
|
-
def
|
260
|
-
if value.
|
261
|
-
|
262
|
-
"x'#{s}'"
|
263
|
-
elsif value.kind_of?(BigDecimal)
|
264
|
-
value.to_s("F")
|
253
|
+
def _quote(value) # :nodoc:
|
254
|
+
if value.is_a?(Type::Binary::Data)
|
255
|
+
"x'#{value.hex}'"
|
265
256
|
else
|
266
257
|
super
|
267
258
|
end
|
@@ -279,10 +270,26 @@ module ActiveRecord
|
|
279
270
|
QUOTED_TRUE
|
280
271
|
end
|
281
272
|
|
273
|
+
def unquoted_true
|
274
|
+
1
|
275
|
+
end
|
276
|
+
|
282
277
|
def quoted_false
|
283
278
|
QUOTED_FALSE
|
284
279
|
end
|
285
280
|
|
281
|
+
def unquoted_false
|
282
|
+
0
|
283
|
+
end
|
284
|
+
|
285
|
+
def quoted_date(value)
|
286
|
+
if supports_datetime_with_precision? && value.acts_like?(:time) && value.respond_to?(:usec)
|
287
|
+
"#{super}.#{sprintf("%06d", value.usec)}"
|
288
|
+
else
|
289
|
+
super
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
286
293
|
# REFERENTIAL INTEGRITY ====================================
|
287
294
|
|
288
295
|
def disable_referential_integrity #:nodoc:
|
@@ -296,7 +303,14 @@ module ActiveRecord
|
|
296
303
|
end
|
297
304
|
end
|
298
305
|
|
306
|
+
#--
|
299
307
|
# DATABASE STATEMENTS ======================================
|
308
|
+
#++
|
309
|
+
|
310
|
+
def clear_cache!
|
311
|
+
super
|
312
|
+
reload_type_map
|
313
|
+
end
|
300
314
|
|
301
315
|
# Executes the SQL statement in the context of this connection.
|
302
316
|
def execute(sql, name = nil)
|
@@ -328,7 +342,7 @@ module ActiveRecord
|
|
328
342
|
execute "COMMIT"
|
329
343
|
end
|
330
344
|
|
331
|
-
def
|
345
|
+
def exec_rollback_db_transaction #:nodoc:
|
332
346
|
execute "ROLLBACK"
|
333
347
|
end
|
334
348
|
|
@@ -405,9 +419,14 @@ module ActiveRecord
|
|
405
419
|
result.collect { |field| field.first }
|
406
420
|
end
|
407
421
|
end
|
422
|
+
alias data_sources tables
|
423
|
+
|
424
|
+
def truncate(table_name, name = nil)
|
425
|
+
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
426
|
+
end
|
408
427
|
|
409
428
|
def table_exists?(name)
|
410
|
-
return false unless name
|
429
|
+
return false unless name.present?
|
411
430
|
return true if tables(nil, nil, name).any?
|
412
431
|
|
413
432
|
name = name.to_s
|
@@ -420,6 +439,7 @@ module ActiveRecord
|
|
420
439
|
|
421
440
|
tables(nil, schema, table).any?
|
422
441
|
end
|
442
|
+
alias data_source_exists? table_exists?
|
423
443
|
|
424
444
|
# Returns an array of indexes for the given table.
|
425
445
|
def indexes(table_name, name = nil) #:nodoc:
|
@@ -451,7 +471,9 @@ module ActiveRecord
|
|
451
471
|
execute_and_free(sql, 'SCHEMA') do |result|
|
452
472
|
each_hash(result).map do |field|
|
453
473
|
field_name = set_field_encoding(field[:Field])
|
454
|
-
|
474
|
+
sql_type = field[:Type]
|
475
|
+
cast_type = lookup_cast_type(sql_type)
|
476
|
+
new_column(field_name, field[:Default], cast_type, sql_type, field[:Null] == "YES", field[:Collation], field[:Extra])
|
455
477
|
end
|
456
478
|
end
|
457
479
|
end
|
@@ -461,7 +483,7 @@ module ActiveRecord
|
|
461
483
|
end
|
462
484
|
|
463
485
|
def bulk_change_table(table_name, operations) #:nodoc:
|
464
|
-
sqls = operations.
|
486
|
+
sqls = operations.flat_map do |command, args|
|
465
487
|
table, arguments = args.shift, args
|
466
488
|
method = :"#{command}_sql"
|
467
489
|
|
@@ -470,7 +492,7 @@ module ActiveRecord
|
|
470
492
|
else
|
471
493
|
raise "Unknown method called : #{method}(#{arguments.inspect})"
|
472
494
|
end
|
473
|
-
end.
|
495
|
+
end.join(", ")
|
474
496
|
|
475
497
|
execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
|
476
498
|
end
|
@@ -485,18 +507,20 @@ module ActiveRecord
|
|
485
507
|
end
|
486
508
|
|
487
509
|
def drop_table(table_name, options = {})
|
488
|
-
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}"
|
510
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
489
511
|
end
|
490
512
|
|
491
513
|
def rename_index(table_name, old_name, new_name)
|
492
514
|
if supports_rename_index?
|
515
|
+
validate_index_length!(table_name, new_name)
|
516
|
+
|
493
517
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}"
|
494
518
|
else
|
495
519
|
super
|
496
520
|
end
|
497
521
|
end
|
498
522
|
|
499
|
-
def change_column_default(table_name, column_name, default)
|
523
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
500
524
|
column = column_for(table_name, column_name)
|
501
525
|
change_column table_name, column_name, column.sql_type, :default => default
|
502
526
|
end
|
@@ -525,6 +549,34 @@ module ActiveRecord
|
|
525
549
|
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
|
526
550
|
end
|
527
551
|
|
552
|
+
def foreign_keys(table_name)
|
553
|
+
fk_info = select_all <<-SQL.strip_heredoc
|
554
|
+
SELECT fk.referenced_table_name as 'to_table'
|
555
|
+
,fk.referenced_column_name as 'primary_key'
|
556
|
+
,fk.column_name as 'column'
|
557
|
+
,fk.constraint_name as 'name'
|
558
|
+
FROM information_schema.key_column_usage fk
|
559
|
+
WHERE fk.referenced_column_name is not null
|
560
|
+
AND fk.table_schema = '#{@config[:database]}'
|
561
|
+
AND fk.table_name = '#{table_name}'
|
562
|
+
SQL
|
563
|
+
|
564
|
+
create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
|
565
|
+
|
566
|
+
fk_info.map do |row|
|
567
|
+
options = {
|
568
|
+
column: row['column'],
|
569
|
+
name: row['name'],
|
570
|
+
primary_key: row['primary_key']
|
571
|
+
}
|
572
|
+
|
573
|
+
options[:on_update] = extract_foreign_key_action(create_table_info, row['name'], "UPDATE")
|
574
|
+
options[:on_delete] = extract_foreign_key_action(create_table_info, row['name'], "DELETE")
|
575
|
+
|
576
|
+
ForeignKeyDefinition.new(table_name, row['to_table'], options)
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
528
580
|
# Maps logical Rails types to MySQL-specific data types.
|
529
581
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
530
582
|
case type.to_s
|
@@ -552,23 +604,24 @@ module ActiveRecord
|
|
552
604
|
when 0x1000000..0xffffffff; 'longtext'
|
553
605
|
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
554
606
|
end
|
607
|
+
when 'datetime'
|
608
|
+
return super unless precision
|
609
|
+
|
610
|
+
case precision
|
611
|
+
when 0..6; "datetime(#{precision})"
|
612
|
+
else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
|
613
|
+
end
|
555
614
|
else
|
556
615
|
super
|
557
616
|
end
|
558
617
|
end
|
559
618
|
|
560
|
-
def add_column_position!(sql, options)
|
561
|
-
if options[:first]
|
562
|
-
sql << " FIRST"
|
563
|
-
elsif options[:after]
|
564
|
-
sql << " AFTER #{quote_column_name(options[:after])}"
|
565
|
-
end
|
566
|
-
end
|
567
|
-
|
568
619
|
# SHOW VARIABLES LIKE 'name'
|
569
620
|
def show_variable(name)
|
570
|
-
variables = select_all("
|
621
|
+
variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
|
571
622
|
variables.first['Value'] unless variables.empty?
|
623
|
+
rescue ActiveRecord::StatementInvalid
|
624
|
+
nil
|
572
625
|
end
|
573
626
|
|
574
627
|
# Returns a table's primary key and belonging sequence.
|
@@ -590,10 +643,19 @@ module ActiveRecord
|
|
590
643
|
pk_and_sequence && pk_and_sequence.first
|
591
644
|
end
|
592
645
|
|
593
|
-
def case_sensitive_modifier(node)
|
646
|
+
def case_sensitive_modifier(node, table_attribute)
|
647
|
+
node = Arel::Nodes.build_quoted node, table_attribute
|
594
648
|
Arel::Nodes::Bin.new(node)
|
595
649
|
end
|
596
650
|
|
651
|
+
def case_sensitive_comparison(table, attribute, column, value)
|
652
|
+
if column.case_sensitive?
|
653
|
+
table[attribute].eq(value)
|
654
|
+
else
|
655
|
+
super
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
597
659
|
def case_insensitive_comparison(table, attribute, column, value)
|
598
660
|
if column.case_sensitive?
|
599
661
|
super
|
@@ -602,8 +664,19 @@ module ActiveRecord
|
|
602
664
|
end
|
603
665
|
end
|
604
666
|
|
605
|
-
|
606
|
-
|
667
|
+
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
|
668
|
+
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
|
669
|
+
# distinct queries, and requires that the ORDER BY include the distinct column.
|
670
|
+
# See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
|
671
|
+
def columns_for_distinct(columns, orders) # :nodoc:
|
672
|
+
order_columns = orders.reject(&:blank?).map { |s|
|
673
|
+
# Convert Arel node to string
|
674
|
+
s = s.to_sql unless s.is_a?(String)
|
675
|
+
# Remove any ASC/DESC modifiers
|
676
|
+
s.gsub(/\s+(?:ASC|DESC)\b/i, '')
|
677
|
+
}.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" }
|
678
|
+
|
679
|
+
[super, *order_columns].join(', ')
|
607
680
|
end
|
608
681
|
|
609
682
|
def strict_mode?
|
@@ -616,12 +689,65 @@ module ActiveRecord
|
|
616
689
|
|
617
690
|
protected
|
618
691
|
|
692
|
+
def initialize_type_map(m) # :nodoc:
|
693
|
+
super
|
694
|
+
|
695
|
+
register_class_with_limit m, %r(char)i, MysqlString
|
696
|
+
|
697
|
+
m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1)
|
698
|
+
m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1)
|
699
|
+
m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1)
|
700
|
+
m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1)
|
701
|
+
m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1)
|
702
|
+
m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1)
|
703
|
+
m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1)
|
704
|
+
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
705
|
+
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
706
|
+
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
707
|
+
|
708
|
+
register_integer_type m, %r(^bigint)i, limit: 8
|
709
|
+
register_integer_type m, %r(^int)i, limit: 4
|
710
|
+
register_integer_type m, %r(^mediumint)i, limit: 3
|
711
|
+
register_integer_type m, %r(^smallint)i, limit: 2
|
712
|
+
register_integer_type m, %r(^tinyint)i, limit: 1
|
713
|
+
|
714
|
+
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
|
715
|
+
m.alias_type %r(set)i, 'varchar'
|
716
|
+
m.alias_type %r(year)i, 'integer'
|
717
|
+
m.alias_type %r(bit)i, 'binary'
|
718
|
+
|
719
|
+
m.register_type(%r(datetime)i) do |sql_type|
|
720
|
+
precision = extract_precision(sql_type)
|
721
|
+
MysqlDateTime.new(precision: precision)
|
722
|
+
end
|
723
|
+
|
724
|
+
m.register_type(%r(enum)i) do |sql_type|
|
725
|
+
limit = sql_type[/^enum\((.+)\)/i, 1]
|
726
|
+
.split(',').map{|enum| enum.strip.length - 2}.max
|
727
|
+
MysqlString.new(limit: limit)
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
def register_integer_type(mapping, key, options) # :nodoc:
|
732
|
+
mapping.register_type(key) do |sql_type|
|
733
|
+
if /unsigned/i =~ sql_type
|
734
|
+
Type::UnsignedInteger.new(options)
|
735
|
+
else
|
736
|
+
Type::Integer.new(options)
|
737
|
+
end
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
619
741
|
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
620
742
|
# to give it some prompting in the form of a subsubquery. Ugh!
|
621
743
|
def subquery_for(key, select)
|
622
744
|
subsubselect = select.clone
|
623
745
|
subsubselect.projections = [key]
|
624
746
|
|
747
|
+
# Materialize subquery by adding distinct
|
748
|
+
# to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
|
749
|
+
subsubselect.distinct unless select.limit || select.offset || select.orders.any?
|
750
|
+
|
625
751
|
subselect = Arel::SelectManager.new(select.engine)
|
626
752
|
subselect.project Arel.sql(key.name)
|
627
753
|
subselect.from subsubselect.as('__active_record_temp')
|
@@ -632,7 +758,7 @@ module ActiveRecord
|
|
632
758
|
case length
|
633
759
|
when Hash
|
634
760
|
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
635
|
-
when
|
761
|
+
when Integer
|
636
762
|
column_names.each {|name| option_strings[name] += "(#{length})"}
|
637
763
|
end
|
638
764
|
end
|
@@ -685,15 +811,13 @@ module ActiveRecord
|
|
685
811
|
end
|
686
812
|
|
687
813
|
def rename_column_sql(table_name, column_name, new_column_name)
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
|
696
|
-
end
|
814
|
+
column = column_for(table_name, column_name)
|
815
|
+
options = {
|
816
|
+
name: new_column_name,
|
817
|
+
default: column.default,
|
818
|
+
null: column.null,
|
819
|
+
auto_increment: column.extra == "auto_increment"
|
820
|
+
}
|
697
821
|
|
698
822
|
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
699
823
|
schema_creation.accept ChangeColumnDefinition.new column, current_type, options
|
@@ -721,33 +845,22 @@ module ActiveRecord
|
|
721
845
|
[add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]
|
722
846
|
end
|
723
847
|
|
724
|
-
def remove_timestamps_sql(table_name)
|
848
|
+
def remove_timestamps_sql(table_name, options = {})
|
725
849
|
[remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
|
726
850
|
end
|
727
851
|
|
728
852
|
private
|
729
853
|
|
730
854
|
def version
|
731
|
-
@version ||= full_version.
|
855
|
+
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
|
732
856
|
end
|
733
857
|
|
734
858
|
def mariadb?
|
735
859
|
full_version =~ /mariadb/i
|
736
860
|
end
|
737
861
|
|
738
|
-
def supports_views?
|
739
|
-
version[0] >= 5
|
740
|
-
end
|
741
|
-
|
742
862
|
def supports_rename_index?
|
743
|
-
mariadb? ? false :
|
744
|
-
end
|
745
|
-
|
746
|
-
def column_for(table_name, column_name)
|
747
|
-
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
748
|
-
raise "No such column: #{table_name}.#{column_name}"
|
749
|
-
end
|
750
|
-
column
|
863
|
+
mariadb? ? false : version >= '5.7.6'
|
751
864
|
end
|
752
865
|
|
753
866
|
def configure_connection
|
@@ -758,9 +871,9 @@ module ActiveRecord
|
|
758
871
|
variables['sql_auto_is_null'] = 0
|
759
872
|
|
760
873
|
# Increase timeout so the server doesn't disconnect us.
|
761
|
-
wait_timeout = @config[:wait_timeout]
|
762
|
-
wait_timeout = 2147483 unless wait_timeout.is_a?(
|
763
|
-
variables[
|
874
|
+
wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout])
|
875
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Integer)
|
876
|
+
variables["wait_timeout"] = wait_timeout
|
764
877
|
|
765
878
|
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
766
879
|
# http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html#sqlmode_strict_all_tables
|
@@ -772,14 +885,18 @@ module ActiveRecord
|
|
772
885
|
# NAMES does not have an equals sign, see
|
773
886
|
# http://dev.mysql.com/doc/refman/5.0/en/set-statement.html#id944430
|
774
887
|
# (trailing comma because variable_assignments will always have content)
|
775
|
-
|
888
|
+
if @config[:encoding]
|
889
|
+
encoding = "NAMES #{@config[:encoding]}"
|
890
|
+
encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
|
891
|
+
encoding << ", "
|
892
|
+
end
|
776
893
|
|
777
894
|
# Gather up all of the SET variables...
|
778
895
|
variable_assignments = variables.map do |k, v|
|
779
896
|
if v == ':default' || v == :default
|
780
|
-
"@@SESSION.#{k
|
897
|
+
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
781
898
|
elsif !v.nil?
|
782
|
-
"@@SESSION.#{k
|
899
|
+
"@@SESSION.#{k} = #{quote(v)}"
|
783
900
|
end
|
784
901
|
# or else nil; compact to clear nils out
|
785
902
|
end.compact.join(', ')
|
@@ -787,6 +904,43 @@ module ActiveRecord
|
|
787
904
|
# ...and send them all in one query
|
788
905
|
@connection.query "SET #{encoding} #{variable_assignments}"
|
789
906
|
end
|
907
|
+
|
908
|
+
def extract_foreign_key_action(structure, name, action) # :nodoc:
|
909
|
+
if structure =~ /CONSTRAINT #{quote_column_name(name)} FOREIGN KEY .* REFERENCES .* ON #{action} (CASCADE|SET NULL|RESTRICT)/
|
910
|
+
case $1
|
911
|
+
when 'CASCADE'; :cascade
|
912
|
+
when 'SET NULL'; :nullify
|
913
|
+
end
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
class MysqlDateTime < Type::DateTime # :nodoc:
|
918
|
+
private
|
919
|
+
|
920
|
+
def has_precision?
|
921
|
+
precision || 0
|
922
|
+
end
|
923
|
+
end
|
924
|
+
|
925
|
+
class MysqlString < Type::String # :nodoc:
|
926
|
+
def type_cast_for_database(value)
|
927
|
+
case value
|
928
|
+
when true then "1"
|
929
|
+
when false then "0"
|
930
|
+
else super
|
931
|
+
end
|
932
|
+
end
|
933
|
+
|
934
|
+
private
|
935
|
+
|
936
|
+
def cast_value(value)
|
937
|
+
case value
|
938
|
+
when true then "1"
|
939
|
+
when false then "0"
|
940
|
+
else super
|
941
|
+
end
|
942
|
+
end
|
943
|
+
end
|
790
944
|
end
|
791
945
|
end
|
792
946
|
end
|