activerecord 4.2.11.3 → 5.0.0.beta1
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 +1029 -1349
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -7
- data/examples/performance.rb +2 -2
- data/lib/active_record.rb +7 -3
- data/lib/active_record/aggregations.rb +35 -25
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations.rb +305 -204
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +10 -8
- data/lib/active_record/associations/association_scope.rb +73 -102
- data/lib/active_record/associations/belongs_to_association.rb +20 -32
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +41 -18
- data/lib/active_record/associations/builder/collection_association.rb +8 -24
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +11 -11
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +10 -5
- data/lib/active_record/associations/builder/singular_association.rb +2 -9
- data/lib/active_record/associations/collection_association.rb +40 -43
- data/lib/active_record/associations/collection_proxy.rb +55 -29
- data/lib/active_record/associations/foreign_association.rb +1 -1
- data/lib/active_record/associations/has_many_association.rb +20 -71
- data/lib/active_record/associations/has_many_through_association.rb +8 -52
- data/lib/active_record/associations/has_one_association.rb +12 -5
- data/lib/active_record/associations/join_dependency.rb +28 -18
- data/lib/active_record/associations/join_dependency/join_association.rb +13 -12
- data/lib/active_record/associations/preloader.rb +13 -4
- data/lib/active_record/associations/preloader/association.rb +45 -51
- data/lib/active_record/associations/preloader/collection_association.rb +0 -6
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/through_association.rb +5 -4
- data/lib/active_record/associations/singular_association.rb +6 -0
- data/lib/active_record/associations/through_association.rb +11 -3
- data/lib/active_record/attribute.rb +61 -17
- data/lib/active_record/attribute/user_provided_default.rb +23 -0
- data/lib/active_record/attribute_assignment.rb +27 -140
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods.rb +79 -26
- data/lib/active_record/attribute_methods/before_type_cast.rb +1 -1
- data/lib/active_record/attribute_methods/dirty.rb +46 -86
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +26 -42
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +42 -9
- data/lib/active_record/attribute_methods/write.rb +13 -24
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set.rb +30 -3
- data/lib/active_record/attribute_set/builder.rb +6 -4
- data/lib/active_record/attributes.rb +194 -81
- data/lib/active_record/autosave_association.rb +33 -15
- data/lib/active_record/base.rb +30 -18
- data/lib/active_record/callbacks.rb +36 -40
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +431 -122
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +40 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -8
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -38
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +229 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +275 -115
- data/lib/active_record/connection_adapters/abstract/transaction.rb +32 -33
- data/lib/active_record/connection_adapters/abstract_adapter.rb +83 -32
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +384 -221
- data/lib/active_record/connection_adapters/column.rb +27 -41
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -21
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +57 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +69 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +22 -101
- data/lib/active_record/connection_adapters/postgresql/column.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +23 -57
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -22
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +23 -16
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -11
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +174 -128
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +184 -112
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +15 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +134 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +28 -11
- data/lib/active_record/connection_handling.rb +5 -5
- data/lib/active_record/core.rb +72 -104
- data/lib/active_record/counter_cache.rb +9 -20
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +110 -76
- data/lib/active_record/errors.rb +72 -47
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +19 -4
- data/lib/active_record/fixtures.rb +76 -40
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +27 -40
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/legacy_yaml_adapter.rb +18 -2
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +10 -14
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +40 -22
- data/lib/active_record/migration.rb +304 -133
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +90 -0
- data/lib/active_record/model_schema.rb +92 -40
- data/lib/active_record/nested_attributes.rb +45 -34
- data/lib/active_record/null_relation.rb +15 -7
- data/lib/active_record/persistence.rb +112 -72
- data/lib/active_record/querying.rb +6 -5
- data/lib/active_record/railtie.rb +20 -13
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +47 -38
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +182 -57
- data/lib/active_record/relation.rb +152 -100
- data/lib/active_record/relation/batches.rb +133 -33
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/calculations.rb +80 -101
- data/lib/active_record/relation/delegation.rb +6 -19
- data/lib/active_record/relation/finder_methods.rb +58 -46
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +13 -42
- data/lib/active_record/relation/predicate_builder.rb +99 -105
- data/lib/active_record/relation/predicate_builder/array_handler.rb +11 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +78 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +17 -0
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +274 -238
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +3 -6
- data/lib/active_record/relation/where_clause.rb +173 -0
- data/lib/active_record/relation/where_clause_factory.rb +37 -0
- data/lib/active_record/result.rb +4 -3
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +94 -65
- data/lib/active_record/schema.rb +23 -22
- data/lib/active_record/schema_dumper.rb +33 -22
- data/lib/active_record/schema_migration.rb +10 -4
- data/lib/active_record/scoping.rb +17 -6
- data/lib/active_record/scoping/default.rb +19 -6
- data/lib/active_record/scoping/named.rb +39 -28
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +2 -4
- data/lib/active_record/statement_cache.rb +15 -13
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +54 -0
- data/lib/active_record/table_metadata.rb +64 -0
- data/lib/active_record/tasks/database_tasks.rb +30 -40
- data/lib/active_record/tasks/mysql_database_tasks.rb +7 -15
- data/lib/active_record/tasks/postgresql_database_tasks.rb +11 -2
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +16 -9
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +138 -56
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -45
- data/lib/active_record/type/date_time.rb +2 -49
- data/lib/active_record/type/internal/abstract_json.rb +33 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +9 -14
- data/lib/active_record/type/time.rb +3 -21
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record/validations/absence.rb +24 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +36 -0
- data/lib/active_record/validations/presence.rb +12 -12
- data/lib/active_record/validations/uniqueness.rb +24 -21
- data/lib/rails/generators/active_record/migration.rb +7 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -3
- data/lib/rails/generators/active_record/migration/templates/migration.rb +4 -1
- data/lib/rails/generators/active_record/model/model_generator.rb +21 -15
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +50 -35
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decimal_without_scale.rb +0 -11
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -110
@@ -1,13 +1,10 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
3
|
class TransactionState
|
4
|
-
attr_reader :parent
|
5
|
-
|
6
4
|
VALID_STATES = Set.new([:committed, :rolledback, nil])
|
7
5
|
|
8
6
|
def initialize(state = nil)
|
9
7
|
@state = state
|
10
|
-
@parent = nil
|
11
8
|
end
|
12
9
|
|
13
10
|
def finalized?
|
@@ -27,7 +24,7 @@ module ActiveRecord
|
|
27
24
|
end
|
28
25
|
|
29
26
|
def set_state(state)
|
30
|
-
|
27
|
+
unless VALID_STATES.include?(state)
|
31
28
|
raise ArgumentError, "Invalid transaction state: #{state}"
|
32
29
|
end
|
33
30
|
@state = state
|
@@ -47,11 +44,12 @@ module ActiveRecord
|
|
47
44
|
attr_reader :connection, :state, :records, :savepoint_name
|
48
45
|
attr_writer :joinable
|
49
46
|
|
50
|
-
def initialize(connection, options)
|
47
|
+
def initialize(connection, options, run_commit_callbacks: false)
|
51
48
|
@connection = connection
|
52
49
|
@state = TransactionState.new
|
53
50
|
@records = []
|
54
51
|
@joinable = options.fetch(:joinable, true)
|
52
|
+
@run_commit_callbacks = run_commit_callbacks
|
55
53
|
end
|
56
54
|
|
57
55
|
def add_record(record)
|
@@ -65,16 +63,11 @@ module ActiveRecord
|
|
65
63
|
def rollback_records
|
66
64
|
ite = records.uniq
|
67
65
|
while record = ite.shift
|
68
|
-
|
69
|
-
record.rolledback! full_rollback?
|
70
|
-
rescue => e
|
71
|
-
raise if ActiveRecord::Base.raise_in_transactional_callbacks
|
72
|
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
|
73
|
-
end
|
66
|
+
record.rolledback!(force_restore_state: full_rollback?)
|
74
67
|
end
|
75
68
|
ensure
|
76
69
|
ite.each do |i|
|
77
|
-
i.rolledback!(full_rollback?, false)
|
70
|
+
i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false)
|
78
71
|
end
|
79
72
|
end
|
80
73
|
|
@@ -82,20 +75,22 @@ module ActiveRecord
|
|
82
75
|
@state.set_state(:committed)
|
83
76
|
end
|
84
77
|
|
78
|
+
def before_commit_records
|
79
|
+
records.uniq.each(&:before_committed!) if @run_commit_callbacks
|
80
|
+
end
|
81
|
+
|
85
82
|
def commit_records
|
86
83
|
ite = records.uniq
|
87
84
|
while record = ite.shift
|
88
|
-
|
85
|
+
if @run_commit_callbacks
|
89
86
|
record.committed!
|
90
|
-
|
91
|
-
|
92
|
-
record.
|
87
|
+
else
|
88
|
+
# if not running callbacks, only adds the record to the parent transaction
|
89
|
+
record.add_to_transaction
|
93
90
|
end
|
94
91
|
end
|
95
92
|
ensure
|
96
|
-
ite.each
|
97
|
-
i.committed!(false)
|
98
|
-
end
|
93
|
+
ite.each { |i| i.committed!(should_run_callbacks: false) }
|
99
94
|
end
|
100
95
|
|
101
96
|
def full_rollback?; true; end
|
@@ -106,8 +101,8 @@ module ActiveRecord
|
|
106
101
|
|
107
102
|
class SavepointTransaction < Transaction
|
108
103
|
|
109
|
-
def initialize(connection, savepoint_name, options)
|
110
|
-
super(connection, options)
|
104
|
+
def initialize(connection, savepoint_name, options, *args)
|
105
|
+
super(connection, options, *args)
|
111
106
|
if options[:isolation]
|
112
107
|
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
|
113
108
|
end
|
@@ -117,14 +112,11 @@ module ActiveRecord
|
|
117
112
|
def rollback
|
118
113
|
connection.rollback_to_savepoint(savepoint_name)
|
119
114
|
super
|
120
|
-
rollback_records
|
121
115
|
end
|
122
116
|
|
123
117
|
def commit
|
124
118
|
connection.release_savepoint(savepoint_name)
|
125
119
|
super
|
126
|
-
parent = connection.transaction_manager.current_transaction
|
127
|
-
records.each { |r| parent.add_record(r) }
|
128
120
|
end
|
129
121
|
|
130
122
|
def full_rollback?; false; end
|
@@ -132,7 +124,7 @@ module ActiveRecord
|
|
132
124
|
|
133
125
|
class RealTransaction < Transaction
|
134
126
|
|
135
|
-
def initialize(connection, options)
|
127
|
+
def initialize(connection, options, *args)
|
136
128
|
super
|
137
129
|
if options[:isolation]
|
138
130
|
connection.begin_isolated_db_transaction(options[:isolation])
|
@@ -144,13 +136,11 @@ module ActiveRecord
|
|
144
136
|
def rollback
|
145
137
|
connection.rollback_db_transaction
|
146
138
|
super
|
147
|
-
rollback_records
|
148
139
|
end
|
149
140
|
|
150
141
|
def commit
|
151
142
|
connection.commit_db_transaction
|
152
143
|
super
|
153
|
-
commit_records
|
154
144
|
end
|
155
145
|
end
|
156
146
|
|
@@ -161,22 +151,31 @@ module ActiveRecord
|
|
161
151
|
end
|
162
152
|
|
163
153
|
def begin_transaction(options = {})
|
154
|
+
run_commit_callbacks = !current_transaction.joinable?
|
164
155
|
transaction =
|
165
156
|
if @stack.empty?
|
166
|
-
RealTransaction.new(@connection, options)
|
157
|
+
RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks)
|
167
158
|
else
|
168
|
-
SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options
|
159
|
+
SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options,
|
160
|
+
run_commit_callbacks: run_commit_callbacks)
|
169
161
|
end
|
162
|
+
|
170
163
|
@stack.push(transaction)
|
171
164
|
transaction
|
172
165
|
end
|
173
166
|
|
174
167
|
def commit_transaction
|
175
|
-
@stack.
|
168
|
+
transaction = @stack.last
|
169
|
+
transaction.before_commit_records
|
170
|
+
@stack.pop
|
171
|
+
transaction.commit
|
172
|
+
transaction.commit_records
|
176
173
|
end
|
177
174
|
|
178
|
-
def rollback_transaction
|
179
|
-
@stack.pop
|
175
|
+
def rollback_transaction(transaction = nil)
|
176
|
+
transaction ||= @stack.pop
|
177
|
+
transaction.rollback
|
178
|
+
transaction.rollback_records
|
180
179
|
end
|
181
180
|
|
182
181
|
def within_new_transaction(options = {})
|
@@ -193,7 +192,7 @@ module ActiveRecord
|
|
193
192
|
begin
|
194
193
|
commit_transaction
|
195
194
|
rescue Exception
|
196
|
-
transaction
|
195
|
+
rollback_transaction(transaction) unless transaction.state.completed?
|
197
196
|
raise
|
198
197
|
end
|
199
198
|
end
|
@@ -1,12 +1,10 @@
|
|
1
|
-
require 'date'
|
2
|
-
require 'bigdecimal'
|
3
|
-
require 'bigdecimal/util'
|
4
1
|
require 'active_record/type'
|
5
2
|
require 'active_support/core_ext/benchmark'
|
3
|
+
require 'active_record/connection_adapters/determine_if_preparable_visitor'
|
6
4
|
require 'active_record/connection_adapters/schema_cache'
|
5
|
+
require 'active_record/connection_adapters/sql_type_metadata'
|
7
6
|
require 'active_record/connection_adapters/abstract/schema_dumper'
|
8
7
|
require 'active_record/connection_adapters/abstract/schema_creation'
|
9
|
-
require 'monitor'
|
10
8
|
require 'arel/collectors/bind'
|
11
9
|
require 'arel/collectors/sql_string'
|
12
10
|
|
@@ -21,10 +19,10 @@ module ActiveRecord
|
|
21
19
|
autoload :IndexDefinition
|
22
20
|
autoload :ColumnDefinition
|
23
21
|
autoload :ChangeColumnDefinition
|
22
|
+
autoload :ForeignKeyDefinition
|
24
23
|
autoload :TableDefinition
|
25
24
|
autoload :Table
|
26
25
|
autoload :AlterTable
|
27
|
-
autoload :TimestampDefaultDeprecation
|
28
26
|
end
|
29
27
|
|
30
28
|
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
|
@@ -54,22 +52,21 @@ module ActiveRecord
|
|
54
52
|
# related classes form the abstraction layer which makes this possible.
|
55
53
|
# An AbstractAdapter represents a connection to a database, and provides an
|
56
54
|
# abstract interface for database-specific functionality such as establishing
|
57
|
-
# a connection, escaping values, building the right SQL fragments for
|
58
|
-
# and
|
55
|
+
# a connection, escaping values, building the right SQL fragments for +:offset+
|
56
|
+
# and +:limit+ options, etc.
|
59
57
|
#
|
60
58
|
# All the concrete database adapters follow the interface laid down in this class.
|
61
|
-
# ActiveRecord::Base.connection returns an AbstractAdapter object, which
|
59
|
+
# {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
|
62
60
|
# you can use.
|
63
61
|
#
|
64
62
|
# Most of the methods in the adapter are useful during migrations. Most
|
65
|
-
# notably, the instance methods provided by
|
63
|
+
# notably, the instance methods provided by SchemaStatements are very useful.
|
66
64
|
class AbstractAdapter
|
67
65
|
ADAPTER_NAME = 'Abstract'.freeze
|
68
66
|
include Quoting, DatabaseStatements, SchemaStatements
|
69
67
|
include DatabaseLimits
|
70
68
|
include QueryCache
|
71
69
|
include ActiveSupport::Callbacks
|
72
|
-
include MonitorMixin
|
73
70
|
include ColumnDumper
|
74
71
|
|
75
72
|
SIMPLE_INT = /\A\d+\z/
|
@@ -98,14 +95,15 @@ module ActiveRecord
|
|
98
95
|
|
99
96
|
attr_reader :prepared_statements
|
100
97
|
|
101
|
-
def initialize(connection, logger = nil,
|
98
|
+
def initialize(connection, logger = nil, config = {}) # :nodoc:
|
102
99
|
super()
|
103
100
|
|
104
101
|
@connection = connection
|
105
102
|
@owner = nil
|
106
103
|
@instrumenter = ActiveSupport::Notifications.instrumenter
|
107
104
|
@logger = logger
|
108
|
-
@
|
105
|
+
@config = config
|
106
|
+
@pool = nil
|
109
107
|
@schema_cache = SchemaCache.new self
|
110
108
|
@visitor = nil
|
111
109
|
@prepared_statements = false
|
@@ -125,7 +123,8 @@ module ActiveRecord
|
|
125
123
|
|
126
124
|
class BindCollector < Arel::Collectors::Bind
|
127
125
|
def compile(bvs, conn)
|
128
|
-
|
126
|
+
casted_binds = conn.prepare_binds_for_database(bvs)
|
127
|
+
super(casted_binds.map { |value| conn.quote(value) })
|
129
128
|
end
|
130
129
|
end
|
131
130
|
|
@@ -151,12 +150,20 @@ module ActiveRecord
|
|
151
150
|
SchemaCreation.new self
|
152
151
|
end
|
153
152
|
|
153
|
+
# this method must only be called while holding connection pool's mutex
|
154
154
|
def lease
|
155
|
-
|
156
|
-
|
157
|
-
|
155
|
+
if in_use?
|
156
|
+
msg = 'Cannot lease connection, '
|
157
|
+
if @owner == Thread.current
|
158
|
+
msg << 'it is already leased by the current thread.'
|
159
|
+
else
|
160
|
+
msg << "it is already in use by a different thread: #{@owner}. " <<
|
161
|
+
"Current thread: #{Thread.current}."
|
158
162
|
end
|
163
|
+
raise ActiveRecordError, msg
|
159
164
|
end
|
165
|
+
|
166
|
+
@owner = Thread.current
|
160
167
|
end
|
161
168
|
|
162
169
|
def schema_cache=(cache)
|
@@ -164,6 +171,7 @@ module ActiveRecord
|
|
164
171
|
@schema_cache = cache
|
165
172
|
end
|
166
173
|
|
174
|
+
# this method must only be called while holding connection pool's mutex
|
167
175
|
def expire
|
168
176
|
@owner = nil
|
169
177
|
end
|
@@ -207,6 +215,11 @@ module ActiveRecord
|
|
207
215
|
false
|
208
216
|
end
|
209
217
|
|
218
|
+
# Does this adapter support application-enforced advisory locking?
|
219
|
+
def supports_advisory_locks?
|
220
|
+
false
|
221
|
+
end
|
222
|
+
|
210
223
|
# Should primary key values be selected from their corresponding
|
211
224
|
# sequence before the insert statement? If true, next_sequence_value
|
212
225
|
# is called before each insert to set the record's primary key.
|
@@ -255,6 +268,16 @@ module ActiveRecord
|
|
255
268
|
false
|
256
269
|
end
|
257
270
|
|
271
|
+
# Does this adapter support datetime with precision?
|
272
|
+
def supports_datetime_with_precision?
|
273
|
+
false
|
274
|
+
end
|
275
|
+
|
276
|
+
# Does this adapter support json data type?
|
277
|
+
def supports_json?
|
278
|
+
false
|
279
|
+
end
|
280
|
+
|
258
281
|
# This is meant to be implemented by the adapters that support extensions
|
259
282
|
def disable_extension(name)
|
260
283
|
end
|
@@ -263,6 +286,20 @@ module ActiveRecord
|
|
263
286
|
def enable_extension(name)
|
264
287
|
end
|
265
288
|
|
289
|
+
# This is meant to be implemented by the adapters that support advisory
|
290
|
+
# locks
|
291
|
+
#
|
292
|
+
# Return true if we got the lock, otherwise false
|
293
|
+
def get_advisory_lock(lock_id) # :nodoc:
|
294
|
+
end
|
295
|
+
|
296
|
+
# This is meant to be implemented by the adapters that support advisory
|
297
|
+
# locks.
|
298
|
+
#
|
299
|
+
# Return true if we released the lock, otherwise false
|
300
|
+
def release_advisory_lock(lock_id) # :nodoc:
|
301
|
+
end
|
302
|
+
|
266
303
|
# A list of extensions, to be filled in by adapters that support them.
|
267
304
|
def extensions
|
268
305
|
[]
|
@@ -273,8 +310,6 @@ module ActiveRecord
|
|
273
310
|
{}
|
274
311
|
end
|
275
312
|
|
276
|
-
# QUOTING ==================================================
|
277
|
-
|
278
313
|
# Returns a bind substitution value given a bind +column+
|
279
314
|
# NOTE: The column param is currently being used by the sqlserver-adapter
|
280
315
|
def substitute_at(column, _unused = 0)
|
@@ -334,7 +369,7 @@ module ActiveRecord
|
|
334
369
|
end
|
335
370
|
|
336
371
|
# Checks whether the connection to the database is still active (i.e. not stale).
|
337
|
-
# This is done under the hood by calling
|
372
|
+
# This is done under the hood by calling #active?. If the connection
|
338
373
|
# is no longer active, then this method will reconnect to the database.
|
339
374
|
def verify!(*ignored)
|
340
375
|
reconnect! unless active?
|
@@ -367,9 +402,18 @@ module ActiveRecord
|
|
367
402
|
end
|
368
403
|
|
369
404
|
def case_insensitive_comparison(table, attribute, column, value)
|
370
|
-
|
405
|
+
if can_perform_case_insensitive_comparison_for?(column)
|
406
|
+
table[attribute].lower.eq(table.lower(value))
|
407
|
+
else
|
408
|
+
case_sensitive_comparison(table, attribute, column, value)
|
409
|
+
end
|
371
410
|
end
|
372
411
|
|
412
|
+
def can_perform_case_insensitive_comparison_for?(column)
|
413
|
+
true
|
414
|
+
end
|
415
|
+
private :can_perform_case_insensitive_comparison_for?
|
416
|
+
|
373
417
|
def current_savepoint_name
|
374
418
|
current_transaction.savepoint_name
|
375
419
|
end
|
@@ -385,8 +429,8 @@ module ActiveRecord
|
|
385
429
|
end
|
386
430
|
end
|
387
431
|
|
388
|
-
def new_column(name, default,
|
389
|
-
Column.new(name, default,
|
432
|
+
def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
|
433
|
+
Column.new(name, default, sql_type_metadata, null, default_function, collation)
|
390
434
|
end
|
391
435
|
|
392
436
|
def lookup_cast_type(sql_type) # :nodoc:
|
@@ -400,15 +444,15 @@ module ActiveRecord
|
|
400
444
|
protected
|
401
445
|
|
402
446
|
def initialize_type_map(m) # :nodoc:
|
403
|
-
register_class_with_limit m, %r(boolean)i,
|
404
|
-
register_class_with_limit m, %r(char)i,
|
405
|
-
register_class_with_limit m, %r(binary)i,
|
406
|
-
register_class_with_limit m, %r(text)i,
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
register_class_with_limit m, %r(float)i,
|
411
|
-
register_class_with_limit m, %r(int)i,
|
447
|
+
register_class_with_limit m, %r(boolean)i, Type::Boolean
|
448
|
+
register_class_with_limit m, %r(char)i, Type::String
|
449
|
+
register_class_with_limit m, %r(binary)i, Type::Binary
|
450
|
+
register_class_with_limit m, %r(text)i, Type::Text
|
451
|
+
register_class_with_precision m, %r(date)i, Type::Date
|
452
|
+
register_class_with_precision m, %r(time)i, Type::Time
|
453
|
+
register_class_with_precision m, %r(datetime)i, Type::DateTime
|
454
|
+
register_class_with_limit m, %r(float)i, Type::Float
|
455
|
+
register_class_with_limit m, %r(int)i, Type::Integer
|
412
456
|
|
413
457
|
m.alias_type %r(blob)i, 'binary'
|
414
458
|
m.alias_type %r(clob)i, 'text'
|
@@ -442,6 +486,13 @@ module ActiveRecord
|
|
442
486
|
end
|
443
487
|
end
|
444
488
|
|
489
|
+
def register_class_with_precision(mapping, key, klass) # :nodoc:
|
490
|
+
mapping.register_type(key) do |*args|
|
491
|
+
precision = extract_precision(args.last)
|
492
|
+
klass.new(precision: precision)
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
445
496
|
def extract_scale(sql_type) # :nodoc:
|
446
497
|
case sql_type
|
447
498
|
when /\((\d+)\)/ then 0
|
@@ -488,7 +539,7 @@ module ActiveRecord
|
|
488
539
|
|
489
540
|
def translate_exception(exception, message)
|
490
541
|
# override in derived class
|
491
|
-
ActiveRecord::StatementInvalid.new(message
|
542
|
+
ActiveRecord::StatementInvalid.new(message)
|
492
543
|
end
|
493
544
|
|
494
545
|
def without_prepared_statement?(binds)
|
@@ -1,77 +1,29 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/mysql/schema_creation'
|
3
|
+
require 'active_record/connection_adapters/mysql/schema_definitions'
|
4
|
+
require 'active_record/connection_adapters/mysql/schema_dumper'
|
5
|
+
|
2
6
|
require 'active_support/core_ext/string/strip'
|
3
7
|
|
4
8
|
module ActiveRecord
|
5
9
|
module ConnectionAdapters
|
6
10
|
class AbstractMysqlAdapter < AbstractAdapter
|
11
|
+
include MySQL::ColumnDumper
|
7
12
|
include Savepoints
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
add_column_position!(super, column_options(o))
|
12
|
-
end
|
13
|
-
|
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
|
-
|
33
|
-
def visit_ChangeColumnDefinition(o)
|
34
|
-
column = o.column
|
35
|
-
options = o.options
|
36
|
-
sql_type = type_to_sql(o.type, options[:limit], options[:precision], options[:scale])
|
37
|
-
change_column_sql = "CHANGE #{quote_column_name(column.name)} #{quote_column_name(options[:name])} #{sql_type}"
|
38
|
-
add_column_options!(change_column_sql, options.merge(column: column))
|
39
|
-
add_column_position!(change_column_sql, options)
|
40
|
-
end
|
41
|
-
|
42
|
-
def add_column_position!(sql, options)
|
43
|
-
if options[:first]
|
44
|
-
sql << " FIRST"
|
45
|
-
elsif options[:after]
|
46
|
-
sql << " AFTER #{quote_column_name(options[:after])}"
|
47
|
-
end
|
48
|
-
sql
|
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
|
14
|
+
def update_table_definition(table_name, base) # :nodoc:
|
15
|
+
MySQL::Table.new(table_name, base)
|
55
16
|
end
|
56
17
|
|
57
18
|
def schema_creation
|
58
|
-
SchemaCreation.new
|
59
|
-
end
|
60
|
-
|
61
|
-
def prepare_column_options(column, types) # :nodoc:
|
62
|
-
spec = super
|
63
|
-
spec.delete(:limit) if :boolean === column.type
|
64
|
-
spec
|
19
|
+
MySQL::SchemaCreation.new(self)
|
65
20
|
end
|
66
21
|
|
67
22
|
class Column < ConnectionAdapters::Column # :nodoc:
|
68
|
-
|
23
|
+
delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
|
69
24
|
|
70
|
-
def initialize(
|
71
|
-
|
72
|
-
@collation = collation
|
73
|
-
@extra = extra
|
74
|
-
super(name, default, cast_type, sql_type, null)
|
25
|
+
def initialize(*)
|
26
|
+
super
|
75
27
|
assert_valid_default(default)
|
76
28
|
extract_default
|
77
29
|
end
|
@@ -79,7 +31,7 @@ module ActiveRecord
|
|
79
31
|
def extract_default
|
80
32
|
if blob_or_text_column?
|
81
33
|
@default = null || strict ? nil : ''
|
82
|
-
elsif missing_default_forged_as_empty_string?(
|
34
|
+
elsif missing_default_forged_as_empty_string?(default)
|
83
35
|
@default = nil
|
84
36
|
end
|
85
37
|
end
|
@@ -93,15 +45,16 @@ module ActiveRecord
|
|
93
45
|
sql_type =~ /blob/i || type == :text
|
94
46
|
end
|
95
47
|
|
48
|
+
def unsigned?
|
49
|
+
/unsigned/ === sql_type
|
50
|
+
end
|
51
|
+
|
96
52
|
def case_sensitive?
|
97
53
|
collation && !collation.match(/_ci$/)
|
98
54
|
end
|
99
55
|
|
100
|
-
def
|
101
|
-
|
102
|
-
collation == other.collation &&
|
103
|
-
strict == other.strict &&
|
104
|
-
extra == other.extra
|
56
|
+
def auto_increment?
|
57
|
+
extra == 'auto_increment'
|
105
58
|
end
|
106
59
|
|
107
60
|
private
|
@@ -122,9 +75,32 @@ module ActiveRecord
|
|
122
75
|
raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
|
123
76
|
end
|
124
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
|
125
101
|
|
126
102
|
def attributes_for_hash
|
127
|
-
|
103
|
+
[self.class, @type_metadata, extra, strict]
|
128
104
|
end
|
129
105
|
end
|
130
106
|
|
@@ -148,17 +124,18 @@ module ActiveRecord
|
|
148
124
|
QUOTED_TRUE, QUOTED_FALSE = '1', '0'
|
149
125
|
|
150
126
|
NATIVE_DATABASE_TYPES = {
|
151
|
-
:
|
152
|
-
:
|
153
|
-
:
|
154
|
-
:
|
155
|
-
:
|
156
|
-
:
|
157
|
-
:
|
158
|
-
:
|
159
|
-
:
|
160
|
-
:
|
161
|
-
:
|
127
|
+
primary_key: "int auto_increment PRIMARY KEY",
|
128
|
+
string: { name: "varchar", limit: 255 },
|
129
|
+
text: { name: "text" },
|
130
|
+
integer: { name: "int", limit: 4 },
|
131
|
+
float: { name: "float" },
|
132
|
+
decimal: { name: "decimal" },
|
133
|
+
datetime: { name: "datetime" },
|
134
|
+
time: { name: "time" },
|
135
|
+
date: { name: "date" },
|
136
|
+
binary: { name: "blob" },
|
137
|
+
boolean: { name: "tinyint", limit: 1 },
|
138
|
+
json: { name: "json" },
|
162
139
|
}
|
163
140
|
|
164
141
|
INDEX_TYPES = [:fulltext, :spatial]
|
@@ -166,19 +143,33 @@ module ActiveRecord
|
|
166
143
|
|
167
144
|
# FIXME: Make the first parameter more similar for the two adapters
|
168
145
|
def initialize(connection, logger, connection_options, config)
|
169
|
-
super(connection, logger)
|
170
|
-
@connection_options, @config = connection_options, config
|
146
|
+
super(connection, logger, config)
|
171
147
|
@quoted_column_names, @quoted_table_names = {}, {}
|
172
148
|
|
173
149
|
@visitor = Arel::Visitors::MySQL.new self
|
174
150
|
|
175
151
|
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
176
152
|
@prepared_statements = true
|
153
|
+
@visitor.extend(DetermineIfPreparableVisitor)
|
177
154
|
else
|
178
155
|
@prepared_statements = false
|
179
156
|
end
|
180
157
|
end
|
181
158
|
|
159
|
+
MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191
|
160
|
+
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
|
167
|
+
end
|
168
|
+
|
169
|
+
def version
|
170
|
+
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
|
171
|
+
end
|
172
|
+
|
182
173
|
# Returns true, since this connection adapter supports migrations.
|
183
174
|
def supports_migrations?
|
184
175
|
true
|
@@ -206,6 +197,10 @@ module ActiveRecord
|
|
206
197
|
version >= '5.0.0'
|
207
198
|
end
|
208
199
|
|
200
|
+
def supports_explain?
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
209
204
|
def supports_indexes_in_create?
|
210
205
|
true
|
211
206
|
end
|
@@ -222,6 +217,20 @@ module ActiveRecord
|
|
222
217
|
version >= '5.6.4'
|
223
218
|
end
|
224
219
|
|
220
|
+
# 5.0.0 definitely supports it, possibly supported by earlier versions but
|
221
|
+
# not sure
|
222
|
+
def supports_advisory_locks?
|
223
|
+
version >= '5.0.0'
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
227
|
+
select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1'
|
228
|
+
end
|
229
|
+
|
230
|
+
def release_advisory_lock(lock_name) # :nodoc:
|
231
|
+
select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1'
|
232
|
+
end
|
233
|
+
|
225
234
|
def native_database_types
|
226
235
|
NATIVE_DATABASE_TYPES
|
227
236
|
end
|
@@ -238,8 +247,8 @@ module ActiveRecord
|
|
238
247
|
raise NotImplementedError
|
239
248
|
end
|
240
249
|
|
241
|
-
def new_column(field, default,
|
242
|
-
Column.new(field, default,
|
250
|
+
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)
|
243
252
|
end
|
244
253
|
|
245
254
|
# Must return the MySQL error number from the exception, if the exception has an
|
@@ -283,10 +292,10 @@ module ActiveRecord
|
|
283
292
|
end
|
284
293
|
|
285
294
|
def quoted_date(value)
|
286
|
-
if supports_datetime_with_precision?
|
287
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
288
|
-
else
|
295
|
+
if supports_datetime_with_precision?
|
289
296
|
super
|
297
|
+
else
|
298
|
+
super.sub(/\.\d{6}\z/, '')
|
290
299
|
end
|
291
300
|
end
|
292
301
|
|
@@ -307,6 +316,80 @@ module ActiveRecord
|
|
307
316
|
# DATABASE STATEMENTS ======================================
|
308
317
|
#++
|
309
318
|
|
319
|
+
def explain(arel, binds = [])
|
320
|
+
sql = "EXPLAIN #{to_sql(arel, binds)}"
|
321
|
+
start = Time.now
|
322
|
+
result = exec_query(sql, 'EXPLAIN', binds)
|
323
|
+
elapsed = Time.now - start
|
324
|
+
|
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
|
391
|
+
end
|
392
|
+
|
310
393
|
def clear_cache!
|
311
394
|
super
|
312
395
|
reload_type_map
|
@@ -410,36 +493,70 @@ module ActiveRecord
|
|
410
493
|
show_variable 'collation_database'
|
411
494
|
end
|
412
495
|
|
413
|
-
def tables(name = nil
|
414
|
-
|
415
|
-
|
416
|
-
|
496
|
+
def tables(name = nil) # :nodoc:
|
497
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
498
|
+
#tables currently returns both tables and views.
|
499
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
|
500
|
+
Use #data_sources instead.
|
501
|
+
MSG
|
417
502
|
|
418
|
-
|
419
|
-
|
503
|
+
if name
|
504
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
505
|
+
Passing arguments to #tables is deprecated without replacement.
|
506
|
+
MSG
|
420
507
|
end
|
508
|
+
|
509
|
+
data_sources
|
510
|
+
end
|
511
|
+
|
512
|
+
def data_sources
|
513
|
+
sql = "SELECT table_name FROM information_schema.tables "
|
514
|
+
sql << "WHERE table_schema = #{quote(@config[:database])}"
|
515
|
+
|
516
|
+
select_values(sql, 'SCHEMA')
|
421
517
|
end
|
422
|
-
alias data_sources tables
|
423
518
|
|
424
519
|
def truncate(table_name, name = nil)
|
425
520
|
execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name
|
426
521
|
end
|
427
522
|
|
428
|
-
def table_exists?(
|
429
|
-
|
430
|
-
|
523
|
+
def table_exists?(table_name)
|
524
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
525
|
+
#table_exists? currently checks both tables and views.
|
526
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
527
|
+
Use #data_source_exists? instead.
|
528
|
+
MSG
|
529
|
+
|
530
|
+
data_source_exists?(table_name)
|
531
|
+
end
|
532
|
+
|
533
|
+
def data_source_exists?(table_name)
|
534
|
+
return false unless table_name.present?
|
431
535
|
|
432
|
-
name
|
433
|
-
schema,
|
536
|
+
schema, name = table_name.to_s.split('.', 2)
|
537
|
+
schema, name = @config[:database], schema unless name # A table was provided without a schema
|
434
538
|
|
435
|
-
|
436
|
-
|
437
|
-
schema = nil
|
438
|
-
end
|
539
|
+
sql = "SELECT table_name FROM information_schema.tables "
|
540
|
+
sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
|
439
541
|
|
440
|
-
|
542
|
+
select_values(sql, 'SCHEMA').any?
|
543
|
+
end
|
544
|
+
|
545
|
+
def views # :nodoc:
|
546
|
+
select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA')
|
547
|
+
end
|
548
|
+
|
549
|
+
def view_exists?(view_name) # :nodoc:
|
550
|
+
return false unless view_name.present?
|
551
|
+
|
552
|
+
schema, name = view_name.to_s.split('.', 2)
|
553
|
+
schema, name = @config[:database], schema unless name # A view was provided without a schema
|
554
|
+
|
555
|
+
sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'"
|
556
|
+
sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}"
|
557
|
+
|
558
|
+
select_values(sql, 'SCHEMA').any?
|
441
559
|
end
|
442
|
-
alias data_source_exists? table_exists?
|
443
560
|
|
444
561
|
# Returns an array of indexes for the given table.
|
445
562
|
def indexes(table_name, name = nil) #:nodoc:
|
@@ -470,10 +587,8 @@ module ActiveRecord
|
|
470
587
|
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
|
471
588
|
execute_and_free(sql, 'SCHEMA') do |result|
|
472
589
|
each_hash(result).map do |field|
|
473
|
-
|
474
|
-
|
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])
|
590
|
+
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
|
591
|
+
new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
|
477
592
|
end
|
478
593
|
end
|
479
594
|
end
|
@@ -506,8 +621,23 @@ module ActiveRecord
|
|
506
621
|
rename_table_indexes(table_name, new_name)
|
507
622
|
end
|
508
623
|
|
624
|
+
# Drops a table from the database.
|
625
|
+
#
|
626
|
+
# [<tt>:force</tt>]
|
627
|
+
# Set to +:cascade+ to drop dependent objects as well.
|
628
|
+
# Defaults to false.
|
629
|
+
# [<tt>:if_exists</tt>]
|
630
|
+
# Set to +true+ to only drop the table if it exists.
|
631
|
+
# Defaults to false.
|
632
|
+
# [<tt>:temporary</tt>]
|
633
|
+
# Set to +true+ to drop temporary table.
|
634
|
+
# Defaults to false.
|
635
|
+
#
|
636
|
+
# Although this command ignores most +options+ and the block if one is given,
|
637
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
638
|
+
# In that case, +options+ and the block will be used by create_table.
|
509
639
|
def drop_table(table_name, options = {})
|
510
|
-
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
640
|
+
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
|
511
641
|
end
|
512
642
|
|
513
643
|
def rename_index(table_name, old_name, new_name)
|
@@ -520,12 +650,13 @@ module ActiveRecord
|
|
520
650
|
end
|
521
651
|
end
|
522
652
|
|
523
|
-
def change_column_default(table_name, column_name,
|
653
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
654
|
+
default = extract_new_default_value(default_or_changes)
|
524
655
|
column = column_for(table_name, column_name)
|
525
656
|
change_column table_name, column_name, column.sql_type, :default => default
|
526
657
|
end
|
527
658
|
|
528
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
659
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
529
660
|
column = column_for(table_name, column_name)
|
530
661
|
|
531
662
|
unless null || default.nil?
|
@@ -545,8 +676,8 @@ module ActiveRecord
|
|
545
676
|
end
|
546
677
|
|
547
678
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
548
|
-
index_name, index_type, index_columns,
|
549
|
-
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})
|
679
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
680
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
|
550
681
|
end
|
551
682
|
|
552
683
|
def foreign_keys(table_name)
|
@@ -561,7 +692,7 @@ module ActiveRecord
|
|
561
692
|
AND fk.table_name = '#{table_name}'
|
562
693
|
SQL
|
563
694
|
|
564
|
-
create_table_info =
|
695
|
+
create_table_info = create_table_info(table_name)
|
565
696
|
|
566
697
|
fk_info.map do |row|
|
567
698
|
options = {
|
@@ -577,43 +708,37 @@ module ActiveRecord
|
|
577
708
|
end
|
578
709
|
end
|
579
710
|
|
711
|
+
def table_options(table_name)
|
712
|
+
create_table_info = create_table_info(table_name)
|
713
|
+
|
714
|
+
# strip create_definitions and partition_options
|
715
|
+
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
|
716
|
+
|
717
|
+
# strip AUTO_INCREMENT
|
718
|
+
raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
|
719
|
+
end
|
720
|
+
|
580
721
|
# Maps logical Rails types to MySQL-specific data types.
|
581
|
-
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
582
|
-
case type.to_s
|
583
|
-
when 'binary'
|
584
|
-
case limit
|
585
|
-
when 0..0xfff; "varbinary(#{limit})"
|
586
|
-
when nil; "blob"
|
587
|
-
when 0x1000..0xffffffff; "blob(#{limit})"
|
588
|
-
else raise(ActiveRecordError, "No binary type has character length #{limit}")
|
589
|
-
end
|
722
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
|
723
|
+
sql = case type.to_s
|
590
724
|
when 'integer'
|
591
|
-
|
592
|
-
when 1; 'tinyint'
|
593
|
-
when 2; 'smallint'
|
594
|
-
when 3; 'mediumint'
|
595
|
-
when nil, 4, 11; 'int(11)' # compatibility with MySQL default
|
596
|
-
when 5..8; 'bigint'
|
597
|
-
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
598
|
-
end
|
725
|
+
integer_to_sql(limit)
|
599
726
|
when 'text'
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
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.")
|
727
|
+
text_to_sql(limit)
|
728
|
+
when 'blob'
|
729
|
+
binary_to_sql(limit)
|
730
|
+
when 'binary'
|
731
|
+
if (0..0xfff) === limit
|
732
|
+
"varbinary(#{limit})"
|
733
|
+
else
|
734
|
+
binary_to_sql(limit)
|
613
735
|
end
|
614
736
|
else
|
615
|
-
super
|
737
|
+
super(type, limit, precision, scale)
|
616
738
|
end
|
739
|
+
|
740
|
+
sql << ' unsigned' if unsigned && type != :primary_key
|
741
|
+
sql
|
617
742
|
end
|
618
743
|
|
619
744
|
# SHOW VARIABLES LIKE 'name'
|
@@ -624,23 +749,20 @@ module ActiveRecord
|
|
624
749
|
nil
|
625
750
|
end
|
626
751
|
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
keys = $1.split(",").map { |key| key.delete('`"') }
|
633
|
-
keys.length == 1 ? [keys.first, nil] : nil
|
634
|
-
else
|
635
|
-
nil
|
636
|
-
end
|
637
|
-
end
|
638
|
-
end
|
752
|
+
def primary_keys(table_name) # :nodoc:
|
753
|
+
raise ArgumentError unless table_name.present?
|
754
|
+
|
755
|
+
schema, name = table_name.to_s.split('.', 2)
|
756
|
+
schema, name = @config[:database], schema unless name # A table was provided without a schema
|
639
757
|
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
758
|
+
select_values(<<-SQL.strip_heredoc, 'SCHEMA')
|
759
|
+
SELECT column_name
|
760
|
+
FROM information_schema.key_column_usage
|
761
|
+
WHERE constraint_name = 'PRIMARY'
|
762
|
+
AND table_schema = #{quote(schema)}
|
763
|
+
AND table_name = #{quote(name)}
|
764
|
+
ORDER BY ordinal_position
|
765
|
+
SQL
|
644
766
|
end
|
645
767
|
|
646
768
|
def case_sensitive_modifier(node, table_attribute)
|
@@ -664,21 +786,6 @@ module ActiveRecord
|
|
664
786
|
end
|
665
787
|
end
|
666
788
|
|
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(', ')
|
680
|
-
end
|
681
|
-
|
682
789
|
def strict_mode?
|
683
790
|
self.class.type_cast_config_to_boolean(@config.fetch(:strict, true))
|
684
791
|
end
|
@@ -704,6 +811,7 @@ module ActiveRecord
|
|
704
811
|
m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1)
|
705
812
|
m.register_type %r(^float)i, Type::Float.new(limit: 24)
|
706
813
|
m.register_type %r(^double)i, Type::Float.new(limit: 53)
|
814
|
+
m.register_type %r(^json)i, MysqlJson.new
|
707
815
|
|
708
816
|
register_integer_type m, %r(^bigint)i, limit: 8
|
709
817
|
register_integer_type m, %r(^int)i, limit: 4
|
@@ -712,20 +820,20 @@ module ActiveRecord
|
|
712
820
|
register_integer_type m, %r(^tinyint)i, limit: 1
|
713
821
|
|
714
822
|
m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
|
715
|
-
m.alias_type %r(set)i, 'varchar'
|
716
823
|
m.alias_type %r(year)i, 'integer'
|
717
824
|
m.alias_type %r(bit)i, 'binary'
|
718
825
|
|
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
826
|
m.register_type(%r(enum)i) do |sql_type|
|
725
827
|
limit = sql_type[/^enum\((.+)\)/i, 1]
|
726
828
|
.split(',').map{|enum| enum.strip.length - 2}.max
|
727
829
|
MysqlString.new(limit: limit)
|
728
830
|
end
|
831
|
+
|
832
|
+
m.register_type(%r(^set)i) do |sql_type|
|
833
|
+
limit = sql_type[/^set\((.+)\)/i, 1]
|
834
|
+
.split(',').map{|set| set.strip.length - 1}.sum - 1
|
835
|
+
MysqlString.new(limit: limit)
|
836
|
+
end
|
729
837
|
end
|
730
838
|
|
731
839
|
def register_integer_type(mapping, key, options) # :nodoc:
|
@@ -738,19 +846,16 @@ module ActiveRecord
|
|
738
846
|
end
|
739
847
|
end
|
740
848
|
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
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?
|
849
|
+
def extract_precision(sql_type)
|
850
|
+
if /time/ === sql_type
|
851
|
+
super || 0
|
852
|
+
else
|
853
|
+
super
|
854
|
+
end
|
855
|
+
end
|
750
856
|
|
751
|
-
|
752
|
-
|
753
|
-
subselect.from subsubselect.as('__active_record_temp')
|
857
|
+
def fetch_type_metadata(sql_type, extra = "")
|
858
|
+
MysqlTypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
|
754
859
|
end
|
755
860
|
|
756
861
|
def add_index_length(option_strings, column_names, options = {})
|
@@ -758,7 +863,7 @@ module ActiveRecord
|
|
758
863
|
case length
|
759
864
|
when Hash
|
760
865
|
column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name) && length[name].present?}
|
761
|
-
when
|
866
|
+
when Fixnum
|
762
867
|
column_names.each {|name| option_strings[name] += "(#{length})"}
|
763
868
|
end
|
764
869
|
end
|
@@ -781,18 +886,18 @@ module ActiveRecord
|
|
781
886
|
def translate_exception(exception, message)
|
782
887
|
case error_number(exception)
|
783
888
|
when 1062
|
784
|
-
RecordNotUnique.new(message
|
889
|
+
RecordNotUnique.new(message)
|
785
890
|
when 1452
|
786
|
-
InvalidForeignKey.new(message
|
891
|
+
InvalidForeignKey.new(message)
|
787
892
|
else
|
788
893
|
super
|
789
894
|
end
|
790
895
|
end
|
791
896
|
|
792
897
|
def add_column_sql(table_name, column_name, type, options = {})
|
793
|
-
td = create_table_definition
|
898
|
+
td = create_table_definition(table_name)
|
794
899
|
cd = td.new_column_definition(column_name, type, options)
|
795
|
-
schema_creation.
|
900
|
+
schema_creation.accept(AddColumnDefinition.new(cd))
|
796
901
|
end
|
797
902
|
|
798
903
|
def change_column_sql(table_name, column_name, type, options = {})
|
@@ -806,21 +911,23 @@ module ActiveRecord
|
|
806
911
|
options[:null] = column.null
|
807
912
|
end
|
808
913
|
|
809
|
-
|
810
|
-
|
914
|
+
td = create_table_definition(table_name)
|
915
|
+
cd = td.new_column_definition(column.name, type, options)
|
916
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
811
917
|
end
|
812
918
|
|
813
919
|
def rename_column_sql(table_name, column_name, new_column_name)
|
814
920
|
column = column_for(table_name, column_name)
|
815
921
|
options = {
|
816
|
-
name: new_column_name,
|
817
922
|
default: column.default,
|
818
923
|
null: column.null,
|
819
|
-
auto_increment: column.
|
924
|
+
auto_increment: column.auto_increment?
|
820
925
|
}
|
821
926
|
|
822
927
|
current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", 'SCHEMA')["Type"]
|
823
|
-
|
928
|
+
td = create_table_definition(table_name)
|
929
|
+
cd = td.new_column_definition(new_column_name, current_type, options)
|
930
|
+
schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
|
824
931
|
end
|
825
932
|
|
826
933
|
def remove_column_sql(table_name, column_name, type = nil, options = {})
|
@@ -832,8 +939,9 @@ module ActiveRecord
|
|
832
939
|
end
|
833
940
|
|
834
941
|
def add_index_sql(table_name, column_name, options = {})
|
835
|
-
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
836
|
-
|
942
|
+
index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
|
943
|
+
index_algorithm[0, 0] = ", " if index_algorithm.present?
|
944
|
+
"ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"
|
837
945
|
end
|
838
946
|
|
839
947
|
def remove_index_sql(table_name, options = {})
|
@@ -851,8 +959,17 @@ module ActiveRecord
|
|
851
959
|
|
852
960
|
private
|
853
961
|
|
854
|
-
|
855
|
-
|
962
|
+
# MySQL is too stupid to create a temporary table for use subquery, so we have
|
963
|
+
# to give it some prompting in the form of a subsubquery. Ugh!
|
964
|
+
def subquery_for(key, select)
|
965
|
+
subsubselect = select.clone
|
966
|
+
subsubselect.projections = [key]
|
967
|
+
|
968
|
+
subselect = Arel::SelectManager.new(select.engine)
|
969
|
+
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')
|
856
973
|
end
|
857
974
|
|
858
975
|
def mariadb?
|
@@ -866,24 +983,25 @@ module ActiveRecord
|
|
866
983
|
def configure_connection
|
867
984
|
variables = @config.fetch(:variables, {}).stringify_keys
|
868
985
|
|
869
|
-
# By default, MySQL 'where id is null' selects the last inserted id.
|
870
|
-
# Turn this off. http://dev.rubyonrails.org/ticket/6778
|
986
|
+
# By default, MySQL 'where id is null' selects the last inserted id; Turn this off.
|
871
987
|
variables['sql_auto_is_null'] = 0
|
872
988
|
|
873
989
|
# Increase timeout so the server doesn't disconnect us.
|
874
|
-
wait_timeout =
|
875
|
-
wait_timeout = 2147483 unless wait_timeout.is_a?(
|
876
|
-
variables[
|
990
|
+
wait_timeout = @config[:wait_timeout]
|
991
|
+
wait_timeout = 2147483 unless wait_timeout.is_a?(Fixnum)
|
992
|
+
variables['wait_timeout'] = self.class.type_cast_config_to_integer(wait_timeout)
|
993
|
+
|
994
|
+
defaults = [':default', :default].to_set
|
877
995
|
|
878
996
|
# Make MySQL reject illegal values rather than truncating or blanking them, see
|
879
|
-
# http://dev.mysql.com/doc/refman/5.
|
997
|
+
# http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
|
880
998
|
# If the user has provided another value for sql_mode, don't replace it.
|
881
|
-
unless variables.has_key?('sql_mode')
|
999
|
+
unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
|
882
1000
|
variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
|
883
1001
|
end
|
884
1002
|
|
885
1003
|
# NAMES does not have an equals sign, see
|
886
|
-
# http://dev.mysql.com/doc/refman/5.
|
1004
|
+
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
|
887
1005
|
# (trailing comma because variable_assignments will always have content)
|
888
1006
|
if @config[:encoding]
|
889
1007
|
encoding = "NAMES #{@config[:encoding]}"
|
@@ -893,7 +1011,7 @@ module ActiveRecord
|
|
893
1011
|
|
894
1012
|
# Gather up all of the SET variables...
|
895
1013
|
variable_assignments = variables.map do |k, v|
|
896
|
-
if v
|
1014
|
+
if defaults.include?(v)
|
897
1015
|
"@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default
|
898
1016
|
elsif !v.nil?
|
899
1017
|
"@@SESSION.#{k} = #{quote(v)}"
|
@@ -914,16 +1032,57 @@ module ActiveRecord
|
|
914
1032
|
end
|
915
1033
|
end
|
916
1034
|
|
917
|
-
|
918
|
-
|
1035
|
+
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"]
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
|
1041
|
+
MySQL::TableDefinition.new(name, temporary, options, as)
|
1042
|
+
end
|
919
1043
|
|
920
|
-
|
921
|
-
|
1044
|
+
def integer_to_sql(limit) # :nodoc:
|
1045
|
+
case limit
|
1046
|
+
when 1; 'tinyint'
|
1047
|
+
when 2; 'smallint'
|
1048
|
+
when 3; 'mediumint'
|
1049
|
+
when nil, 4; 'int'
|
1050
|
+
when 5..8; 'bigint'
|
1051
|
+
when 11; 'int(11)' # backward compatibility with Rails 2.0
|
1052
|
+
else raise(ActiveRecordError, "No integer type has byte size #{limit}")
|
1053
|
+
end
|
1054
|
+
end
|
1055
|
+
|
1056
|
+
def text_to_sql(limit) # :nodoc:
|
1057
|
+
case limit
|
1058
|
+
when 0..0xff; 'tinytext'
|
1059
|
+
when nil, 0x100..0xffff; 'text'
|
1060
|
+
when 0x10000..0xffffff; 'mediumtext'
|
1061
|
+
when 0x1000000..0xffffffff; 'longtext'
|
1062
|
+
else raise(ActiveRecordError, "No text type has byte length #{limit}")
|
1063
|
+
end
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
def binary_to_sql(limit) # :nodoc:
|
1067
|
+
case limit
|
1068
|
+
when 0..0xff; 'tinyblob'
|
1069
|
+
when nil, 0x100..0xffff; 'blob'
|
1070
|
+
when 0x10000..0xffffff; 'mediumblob'
|
1071
|
+
when 0x1000000..0xffffffff; 'longblob'
|
1072
|
+
else raise(ActiveRecordError, "No binary type has byte length #{limit}")
|
1073
|
+
end
|
1074
|
+
end
|
1075
|
+
|
1076
|
+
class MysqlJson < Type::Internal::AbstractJson # :nodoc:
|
1077
|
+
def changed_in_place?(raw_old_value, new_value)
|
1078
|
+
# Normalization is required because MySQL JSON data format includes
|
1079
|
+
# the space between the elements.
|
1080
|
+
super(serialize(deserialize(raw_old_value)), new_value)
|
922
1081
|
end
|
923
1082
|
end
|
924
1083
|
|
925
1084
|
class MysqlString < Type::String # :nodoc:
|
926
|
-
def
|
1085
|
+
def serialize(value)
|
927
1086
|
case value
|
928
1087
|
when true then "1"
|
929
1088
|
when false then "0"
|
@@ -941,6 +1100,10 @@ module ActiveRecord
|
|
941
1100
|
end
|
942
1101
|
end
|
943
1102
|
end
|
1103
|
+
|
1104
|
+
ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2)
|
1105
|
+
ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2)
|
1106
|
+
ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
|
944
1107
|
end
|
945
1108
|
end
|
946
1109
|
end
|