activerecord 6.0.3 → 6.1.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 +4 -4
- data/CHANGELOG.md +968 -682
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_record.rb +7 -14
- data/lib/active_record/aggregations.rb +5 -5
- data/lib/active_record/association_relation.rb +30 -12
- data/lib/active_record/associations.rb +118 -11
- data/lib/active_record/associations/alias_tracker.rb +19 -15
- data/lib/active_record/associations/association.rb +44 -28
- data/lib/active_record/associations/association_scope.rb +19 -15
- data/lib/active_record/associations/belongs_to_association.rb +22 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
- data/lib/active_record/associations/builder/association.rb +32 -5
- data/lib/active_record/associations/builder/belongs_to.rb +10 -7
- data/lib/active_record/associations/builder/collection_association.rb +5 -4
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
- data/lib/active_record/associations/builder/has_many.rb +6 -2
- data/lib/active_record/associations/builder/has_one.rb +11 -14
- data/lib/active_record/associations/builder/singular_association.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +19 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -5
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +24 -2
- data/lib/active_record/associations/has_many_through_association.rb +10 -4
- data/lib/active_record/associations/has_one_association.rb +15 -1
- data/lib/active_record/associations/join_dependency.rb +72 -50
- data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
- data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
- data/lib/active_record/associations/preloader.rb +11 -5
- data/lib/active_record/associations/preloader/association.rb +51 -25
- data/lib/active_record/associations/preloader/through_association.rb +2 -2
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/attribute_assignment.rb +10 -8
- data/lib/active_record/attribute_methods.rb +64 -54
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
- data/lib/active_record/attribute_methods/dirty.rb +1 -11
- data/lib/active_record/attribute_methods/primary_key.rb +6 -2
- data/lib/active_record/attribute_methods/query.rb +3 -6
- data/lib/active_record/attribute_methods/read.rb +8 -11
- data/lib/active_record/attribute_methods/serialization.rb +11 -5
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
- data/lib/active_record/attribute_methods/write.rb +12 -20
- data/lib/active_record/attributes.rb +33 -8
- data/lib/active_record/autosave_association.rb +57 -40
- data/lib/active_record/base.rb +2 -14
- data/lib/active_record/callbacks.rb +152 -22
- data/lib/active_record/coders/yaml_column.rb +1 -1
- data/lib/active_record/connection_adapters.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -23
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
- data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
- data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +129 -88
- data/lib/active_record/connection_adapters/column.rb +15 -1
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
- data/lib/active_record/connection_adapters/pool_config.rb +73 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +74 -63
- data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
- data/lib/active_record/connection_handling.rb +218 -71
- data/lib/active_record/core.rb +245 -61
- data/lib/active_record/database_configurations.rb +124 -85
- data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
- data/lib/active_record/database_configurations/database_config.rb +52 -9
- data/lib/active_record/database_configurations/hash_config.rb +54 -8
- data/lib/active_record/database_configurations/url_config.rb +15 -40
- data/lib/active_record/delegated_type.rb +209 -0
- data/lib/active_record/destroy_association_async_job.rb +36 -0
- data/lib/active_record/enum.rb +82 -38
- data/lib/active_record/errors.rb +47 -12
- data/lib/active_record/explain.rb +9 -4
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +10 -17
- data/lib/active_record/fixture_set/model_metadata.rb +1 -2
- data/lib/active_record/fixture_set/render_context.rb +1 -1
- data/lib/active_record/fixture_set/table_row.rb +2 -2
- data/lib/active_record/fixtures.rb +58 -9
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/inheritance.rb +40 -18
- data/lib/active_record/insert_all.rb +35 -6
- data/lib/active_record/integration.rb +3 -5
- data/lib/active_record/internal_metadata.rb +16 -7
- data/lib/active_record/legacy_yaml_adapter.rb +7 -3
- data/lib/active_record/locking/optimistic.rb +33 -17
- data/lib/active_record/locking/pessimistic.rb +6 -2
- data/lib/active_record/log_subscriber.rb +27 -8
- data/lib/active_record/middleware/database_selector.rb +4 -1
- data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
- data/lib/active_record/migration.rb +114 -84
- data/lib/active_record/migration/command_recorder.rb +47 -27
- data/lib/active_record/migration/compatibility.rb +68 -17
- data/lib/active_record/model_schema.rb +117 -13
- data/lib/active_record/nested_attributes.rb +2 -3
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/persistence.rb +50 -45
- data/lib/active_record/query_cache.rb +15 -5
- data/lib/active_record/querying.rb +11 -6
- data/lib/active_record/railtie.rb +64 -44
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/databases.rake +276 -99
- data/lib/active_record/readonly_attributes.rb +4 -0
- data/lib/active_record/reflection.rb +71 -57
- data/lib/active_record/relation.rb +95 -67
- data/lib/active_record/relation/batches.rb +38 -31
- data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
- data/lib/active_record/relation/calculations.rb +101 -44
- data/lib/active_record/relation/delegation.rb +2 -1
- data/lib/active_record/relation/finder_methods.rb +45 -15
- data/lib/active_record/relation/from_clause.rb +1 -1
- data/lib/active_record/relation/merger.rb +27 -25
- data/lib/active_record/relation/predicate_builder.rb +61 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +333 -195
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +8 -7
- data/lib/active_record/relation/where_clause.rb +107 -60
- data/lib/active_record/result.rb +41 -33
- data/lib/active_record/runtime_registry.rb +2 -2
- data/lib/active_record/sanitization.rb +6 -17
- data/lib/active_record/schema_dumper.rb +34 -4
- data/lib/active_record/schema_migration.rb +2 -8
- data/lib/active_record/scoping/named.rb +6 -17
- data/lib/active_record/secure_token.rb +16 -8
- data/lib/active_record/serialization.rb +5 -3
- data/lib/active_record/signed_id.rb +116 -0
- data/lib/active_record/statement_cache.rb +20 -4
- data/lib/active_record/store.rb +2 -2
- data/lib/active_record/suppressor.rb +2 -2
- data/lib/active_record/table_metadata.rb +42 -51
- data/lib/active_record/tasks/database_tasks.rb +140 -113
- data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
- data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
- data/lib/active_record/test_databases.rb +5 -4
- data/lib/active_record/test_fixtures.rb +37 -16
- data/lib/active_record/timestamp.rb +4 -6
- data/lib/active_record/touch_later.rb +21 -21
- data/lib/active_record/transactions.rb +19 -66
- data/lib/active_record/type.rb +8 -1
- data/lib/active_record/type/serialized.rb +6 -2
- data/lib/active_record/type/time.rb +10 -0
- data/lib/active_record/type_caster/connection.rb +0 -1
- data/lib/active_record/type_caster/map.rb +8 -5
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/numericality.rb +35 -0
- data/lib/active_record/validations/uniqueness.rb +24 -4
- data/lib/arel.rb +5 -13
- data/lib/arel/attributes/attribute.rb +4 -0
- data/lib/arel/collectors/bind.rb +5 -0
- data/lib/arel/collectors/composite.rb +8 -0
- data/lib/arel/collectors/sql_string.rb +7 -0
- data/lib/arel/collectors/substitute_binds.rb +7 -0
- data/lib/arel/nodes.rb +3 -1
- data/lib/arel/nodes/binary.rb +82 -8
- data/lib/arel/nodes/bind_param.rb +8 -0
- data/lib/arel/nodes/casted.rb +21 -9
- data/lib/arel/nodes/equality.rb +6 -9
- data/lib/arel/nodes/grouping.rb +3 -0
- data/lib/arel/nodes/homogeneous_in.rb +72 -0
- data/lib/arel/nodes/in.rb +8 -1
- data/lib/arel/nodes/infix_operation.rb +13 -1
- data/lib/arel/nodes/join_source.rb +1 -1
- data/lib/arel/nodes/node.rb +7 -6
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/sql_literal.rb +3 -0
- data/lib/arel/nodes/table_alias.rb +7 -3
- data/lib/arel/nodes/unary.rb +0 -1
- data/lib/arel/predications.rb +12 -18
- data/lib/arel/select_manager.rb +1 -2
- data/lib/arel/table.rb +13 -5
- data/lib/arel/visitors.rb +0 -7
- data/lib/arel/visitors/dot.rb +14 -2
- data/lib/arel/visitors/mysql.rb +11 -1
- data/lib/arel/visitors/postgresql.rb +15 -4
- data/lib/arel/visitors/to_sql.rb +89 -78
- data/lib/rails/generators/active_record/migration.rb +6 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- metadata +28 -29
- data/lib/active_record/advisory_lock_base.rb +0 -18
- data/lib/active_record/attribute_decorators.rb +0 -88
- data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
- data/lib/active_record/define_callbacks.rb +0 -22
- data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
- data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
- data/lib/active_record/relation/where_clause_factory.rb +0 -33
- data/lib/arel/attributes.rb +0 -22
- data/lib/arel/visitors/depth_first.rb +0 -203
- data/lib/arel/visitors/ibm_db.rb +0 -34
- data/lib/arel/visitors/informix.rb +0 -62
- data/lib/arel/visitors/mssql.rb +0 -156
- data/lib/arel/visitors/oracle.rb +0 -158
- data/lib/arel/visitors/oracle12.rb +0 -65
- data/lib/arel/visitors/where_sql.rb +0 -22
@@ -1,8 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/file/atomic"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module ConnectionAdapters
|
5
7
|
class SchemaCache
|
8
|
+
def self.load_from(filename)
|
9
|
+
return unless File.file?(filename)
|
10
|
+
|
11
|
+
read(filename) do |file|
|
12
|
+
filename.include?(".dump") ? Marshal.load(file) : YAML.load(file)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.read(filename, &block)
|
17
|
+
if File.extname(filename) == ".gz"
|
18
|
+
Zlib::GzipReader.open(filename) { |gz|
|
19
|
+
yield gz.read
|
20
|
+
}
|
21
|
+
else
|
22
|
+
yield File.read(filename)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
private_class_method :read
|
26
|
+
|
6
27
|
attr_reader :version
|
7
28
|
attr_accessor :connection
|
8
29
|
|
@@ -26,27 +47,33 @@ module ActiveRecord
|
|
26
47
|
end
|
27
48
|
|
28
49
|
def encode_with(coder)
|
50
|
+
reset_version!
|
51
|
+
|
29
52
|
coder["columns"] = @columns
|
30
|
-
coder["columns_hash"] = @columns_hash
|
31
53
|
coder["primary_keys"] = @primary_keys
|
32
54
|
coder["data_sources"] = @data_sources
|
33
55
|
coder["indexes"] = @indexes
|
34
|
-
coder["version"] =
|
56
|
+
coder["version"] = @version
|
35
57
|
coder["database_version"] = database_version
|
36
58
|
end
|
37
59
|
|
38
60
|
def init_with(coder)
|
39
61
|
@columns = coder["columns"]
|
40
|
-
@columns_hash = coder["columns_hash"]
|
41
62
|
@primary_keys = coder["primary_keys"]
|
42
63
|
@data_sources = coder["data_sources"]
|
43
64
|
@indexes = coder["indexes"] || {}
|
44
65
|
@version = coder["version"]
|
45
66
|
@database_version = coder["database_version"]
|
67
|
+
|
68
|
+
derive_columns_hash_and_deduplicate_values
|
46
69
|
end
|
47
70
|
|
48
71
|
def primary_keys(table_name)
|
49
|
-
@primary_keys
|
72
|
+
@primary_keys.fetch(table_name) do
|
73
|
+
if data_source_exists?(table_name)
|
74
|
+
@primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
|
75
|
+
end
|
76
|
+
end
|
50
77
|
end
|
51
78
|
|
52
79
|
# A cached lookup for table existence.
|
@@ -54,7 +81,7 @@ module ActiveRecord
|
|
54
81
|
prepare_data_sources if @data_sources.empty?
|
55
82
|
return @data_sources[name] if @data_sources.key? name
|
56
83
|
|
57
|
-
@data_sources[name] = connection.data_source_exists?(name)
|
84
|
+
@data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
|
58
85
|
end
|
59
86
|
|
60
87
|
# Add internal cache for table with +table_name+.
|
@@ -73,15 +100,17 @@ module ActiveRecord
|
|
73
100
|
|
74
101
|
# Get the columns for a table
|
75
102
|
def columns(table_name)
|
76
|
-
@columns
|
103
|
+
@columns.fetch(table_name) do
|
104
|
+
@columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
|
105
|
+
end
|
77
106
|
end
|
78
107
|
|
79
108
|
# Get the columns for a table as a hash, key is the column name
|
80
109
|
# value is the column object.
|
81
110
|
def columns_hash(table_name)
|
82
|
-
@columns_hash
|
83
|
-
[
|
84
|
-
|
111
|
+
@columns_hash.fetch(table_name) do
|
112
|
+
@columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name).freeze
|
113
|
+
end
|
85
114
|
end
|
86
115
|
|
87
116
|
# Checks whether the columns hash is already cached for a table.
|
@@ -90,7 +119,9 @@ module ActiveRecord
|
|
90
119
|
end
|
91
120
|
|
92
121
|
def indexes(table_name)
|
93
|
-
@indexes
|
122
|
+
@indexes.fetch(table_name) do
|
123
|
+
@indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
|
124
|
+
end
|
94
125
|
end
|
95
126
|
|
96
127
|
def database_version # :nodoc:
|
@@ -121,21 +152,73 @@ module ActiveRecord
|
|
121
152
|
@indexes.delete name
|
122
153
|
end
|
123
154
|
|
155
|
+
def dump_to(filename)
|
156
|
+
clear!
|
157
|
+
connection.data_sources.each { |table| add(table) }
|
158
|
+
open(filename) { |f|
|
159
|
+
if filename.include?(".dump")
|
160
|
+
f.write(Marshal.dump(self))
|
161
|
+
else
|
162
|
+
f.write(YAML.dump(self))
|
163
|
+
end
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
124
167
|
def marshal_dump
|
125
|
-
|
126
|
-
|
127
|
-
[@version, @columns,
|
168
|
+
reset_version!
|
169
|
+
|
170
|
+
[@version, @columns, {}, @primary_keys, @data_sources, @indexes, database_version]
|
128
171
|
end
|
129
172
|
|
130
173
|
def marshal_load(array)
|
131
|
-
@version, @columns,
|
132
|
-
@indexes
|
174
|
+
@version, @columns, _columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
|
175
|
+
@indexes ||= {}
|
176
|
+
|
177
|
+
derive_columns_hash_and_deduplicate_values
|
133
178
|
end
|
134
179
|
|
135
180
|
private
|
181
|
+
def reset_version!
|
182
|
+
@version = connection.migration_context.current_version
|
183
|
+
end
|
184
|
+
|
185
|
+
def derive_columns_hash_and_deduplicate_values
|
186
|
+
@columns = deep_deduplicate(@columns)
|
187
|
+
@columns_hash = @columns.transform_values { |columns| columns.index_by(&:name) }
|
188
|
+
@primary_keys = deep_deduplicate(@primary_keys)
|
189
|
+
@data_sources = deep_deduplicate(@data_sources)
|
190
|
+
@indexes = deep_deduplicate(@indexes)
|
191
|
+
end
|
192
|
+
|
193
|
+
def deep_deduplicate(value)
|
194
|
+
case value
|
195
|
+
when Hash
|
196
|
+
value.transform_keys { |k| deep_deduplicate(k) }.transform_values { |v| deep_deduplicate(v) }
|
197
|
+
when Array
|
198
|
+
value.map { |i| deep_deduplicate(i) }
|
199
|
+
when String, Deduplicable
|
200
|
+
-value
|
201
|
+
else
|
202
|
+
value
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
136
206
|
def prepare_data_sources
|
137
207
|
connection.data_sources.each { |source| @data_sources[source] = true }
|
138
208
|
end
|
209
|
+
|
210
|
+
def open(filename)
|
211
|
+
File.atomic_write(filename) do |file|
|
212
|
+
if File.extname(filename) == ".gz"
|
213
|
+
zipper = Zlib::GzipWriter.new file
|
214
|
+
yield zipper
|
215
|
+
zipper.flush
|
216
|
+
zipper.close
|
217
|
+
else
|
218
|
+
yield file
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
139
222
|
end
|
140
223
|
end
|
141
224
|
end
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_record/connection_adapters/deduplicable"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
# :stopdoc:
|
5
7
|
module ConnectionAdapters
|
6
8
|
class SqlTypeMetadata
|
9
|
+
include Deduplicable
|
10
|
+
|
7
11
|
attr_reader :sql_type, :type, :limit, :precision, :scale
|
8
12
|
|
9
13
|
def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
|
@@ -32,6 +36,12 @@ module ActiveRecord
|
|
32
36
|
precision.hash >> 1 ^
|
33
37
|
scale.hash >> 2
|
34
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def deduplicated
|
42
|
+
@sql_type = -sql_type
|
43
|
+
super
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
end
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
module SQLite3
|
6
6
|
module DatabaseStatements
|
7
7
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
8
|
-
:
|
8
|
+
:pragma
|
9
9
|
) # :nodoc:
|
10
10
|
private_constant :READ_QUERY
|
11
11
|
|
@@ -13,12 +13,18 @@ module ActiveRecord
|
|
13
13
|
!READ_QUERY.match?(sql)
|
14
14
|
end
|
15
15
|
|
16
|
+
def explain(arel, binds = [])
|
17
|
+
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
18
|
+
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
|
19
|
+
end
|
20
|
+
|
16
21
|
def execute(sql, name = nil) #:nodoc:
|
17
22
|
if preventing_writes? && write_query?(sql)
|
18
23
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
19
24
|
end
|
20
25
|
|
21
26
|
materialize_transactions
|
27
|
+
mark_transaction_written_if_write(sql)
|
22
28
|
|
23
29
|
log(sql, name) do
|
24
30
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -33,6 +39,7 @@ module ActiveRecord
|
|
33
39
|
end
|
34
40
|
|
35
41
|
materialize_transactions
|
42
|
+
mark_transaction_written_if_write(sql)
|
36
43
|
|
37
44
|
type_casted_binds = type_casted_binds(binds)
|
38
45
|
|
@@ -58,7 +65,7 @@ module ActiveRecord
|
|
58
65
|
records = stmt.to_a
|
59
66
|
end
|
60
67
|
|
61
|
-
|
68
|
+
build_result(columns: cols, rows: records)
|
62
69
|
end
|
63
70
|
end
|
64
71
|
end
|
@@ -69,20 +76,37 @@ module ActiveRecord
|
|
69
76
|
end
|
70
77
|
alias :exec_update :exec_delete
|
71
78
|
|
79
|
+
def begin_isolated_db_transaction(isolation) #:nodoc
|
80
|
+
raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
|
81
|
+
raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
|
82
|
+
|
83
|
+
Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
|
84
|
+
@connection.read_uncommitted = true
|
85
|
+
begin_db_transaction
|
86
|
+
end
|
87
|
+
|
72
88
|
def begin_db_transaction #:nodoc:
|
73
|
-
log("begin transaction",
|
89
|
+
log("begin transaction", "TRANSACTION") { @connection.transaction }
|
74
90
|
end
|
75
91
|
|
76
92
|
def commit_db_transaction #:nodoc:
|
77
|
-
log("commit transaction",
|
93
|
+
log("commit transaction", "TRANSACTION") { @connection.commit }
|
94
|
+
reset_read_uncommitted
|
78
95
|
end
|
79
96
|
|
80
97
|
def exec_rollback_db_transaction #:nodoc:
|
81
|
-
log("rollback transaction",
|
98
|
+
log("rollback transaction", "TRANSACTION") { @connection.rollback }
|
99
|
+
reset_read_uncommitted
|
82
100
|
end
|
83
101
|
|
84
|
-
|
85
102
|
private
|
103
|
+
def reset_read_uncommitted
|
104
|
+
read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
|
105
|
+
return unless read_uncommitted
|
106
|
+
|
107
|
+
@connection.read_uncommitted = read_uncommitted
|
108
|
+
end
|
109
|
+
|
86
110
|
def execute_batch(statements, name = nil)
|
87
111
|
sql = combine_multi_statements(statements)
|
88
112
|
|
@@ -91,6 +115,7 @@ module ActiveRecord
|
|
91
115
|
end
|
92
116
|
|
93
117
|
materialize_transactions
|
118
|
+
mark_transaction_written_if_write(sql)
|
94
119
|
|
95
120
|
log(sql, name) do
|
96
121
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -3,8 +3,12 @@
|
|
3
3
|
module ActiveRecord
|
4
4
|
module ConnectionAdapters
|
5
5
|
module SQLite3
|
6
|
-
class SchemaCreation <
|
6
|
+
class SchemaCreation < SchemaCreation # :nodoc:
|
7
7
|
private
|
8
|
+
def supports_index_using?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
8
12
|
def add_column_options!(sql, options)
|
9
13
|
if options[:collation]
|
10
14
|
sql << " COLLATE \"#{options[:collation]}\""
|
@@ -9,7 +9,7 @@ module ActiveRecord
|
|
9
9
|
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
|
10
10
|
# Indexes SQLite creates implicitly for internal use start with "sqlite_".
|
11
11
|
# See https://www.sqlite.org/fileformat2.html#intschema
|
12
|
-
next if row["name"].
|
12
|
+
next if row["name"].start_with?("sqlite_")
|
13
13
|
|
14
14
|
index_sql = query_value(<<~SQL, "SCHEMA")
|
15
15
|
SELECT sql
|
@@ -61,7 +61,7 @@ module ActiveRecord
|
|
61
61
|
|
62
62
|
def remove_foreign_key(from_table, to_table = nil, **options)
|
63
63
|
to_table ||= options[:to_table]
|
64
|
-
options = options.except(:name, :to_table)
|
64
|
+
options = options.except(:name, :to_table, :validate)
|
65
65
|
foreign_keys = foreign_keys(from_table)
|
66
66
|
|
67
67
|
fkey = foreign_keys.detect do |fk|
|
@@ -78,6 +78,35 @@ module ActiveRecord
|
|
78
78
|
alter_table(from_table, foreign_keys)
|
79
79
|
end
|
80
80
|
|
81
|
+
def check_constraints(table_name)
|
82
|
+
table_sql = query_value(<<-SQL, "SCHEMA")
|
83
|
+
SELECT sql
|
84
|
+
FROM sqlite_master
|
85
|
+
WHERE name = #{quote_table_name(table_name)} AND type = 'table'
|
86
|
+
UNION ALL
|
87
|
+
SELECT sql
|
88
|
+
FROM sqlite_temp_master
|
89
|
+
WHERE name = #{quote_table_name(table_name)} AND type = 'table'
|
90
|
+
SQL
|
91
|
+
|
92
|
+
table_sql.to_s.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
|
93
|
+
CheckConstraintDefinition.new(table_name, expression, name: name)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_check_constraint(table_name, expression, **options)
|
98
|
+
alter_table(table_name) do |definition|
|
99
|
+
definition.check_constraint(expression, **options)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def remove_check_constraint(table_name, expression = nil, **options)
|
104
|
+
check_constraints = check_constraints(table_name)
|
105
|
+
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
|
106
|
+
check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
|
107
|
+
alter_table(table_name, foreign_keys(table_name), check_constraints)
|
108
|
+
end
|
109
|
+
|
81
110
|
def create_schema_dumper(options)
|
82
111
|
SQLite3::SchemaDumper.create(self, options)
|
83
112
|
end
|
@@ -87,8 +116,12 @@ module ActiveRecord
|
|
87
116
|
SQLite3::SchemaCreation.new(self)
|
88
117
|
end
|
89
118
|
|
90
|
-
def create_table_definition(
|
91
|
-
SQLite3::TableDefinition.new(self,
|
119
|
+
def create_table_definition(name, **options)
|
120
|
+
SQLite3::TableDefinition.new(self, name, **options)
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_index_length!(table_name, new_name, internal = false)
|
124
|
+
super unless internal
|
92
125
|
end
|
93
126
|
|
94
127
|
def new_column_from_field(table_name, field)
|
@@ -26,7 +26,7 @@ module ActiveRecord
|
|
26
26
|
# Allow database path relative to Rails.root, but only if the database
|
27
27
|
# path is not the special path that tells sqlite to build a database only
|
28
28
|
# in memory.
|
29
|
-
if ":memory:" != config[:database]
|
29
|
+
if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:")
|
30
30
|
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
|
31
31
|
dirname = File.dirname(config[:database])
|
32
32
|
Dir.mkdir(dirname) unless File.directory?(dirname)
|
@@ -48,8 +48,8 @@ module ActiveRecord
|
|
48
48
|
end
|
49
49
|
|
50
50
|
module ConnectionAdapters #:nodoc:
|
51
|
-
# The SQLite3 adapter works
|
52
|
-
#
|
51
|
+
# The SQLite3 adapter works with the sqlite3-ruby drivers
|
52
|
+
# (available as gem from https://rubygems.org/gems/sqlite3).
|
53
53
|
#
|
54
54
|
# Options:
|
55
55
|
#
|
@@ -76,16 +76,6 @@ module ActiveRecord
|
|
76
76
|
json: { name: "json" },
|
77
77
|
}
|
78
78
|
|
79
|
-
def self.represent_boolean_as_integer=(value) # :nodoc:
|
80
|
-
if value == false
|
81
|
-
raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
|
82
|
-
end
|
83
|
-
|
84
|
-
ActiveSupport::Deprecation.warn(
|
85
|
-
"`.represent_boolean_as_integer=` is now always true, so setting this is deprecated and will be removed in Rails 6.1."
|
86
|
-
)
|
87
|
-
end
|
88
|
-
|
89
79
|
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
90
80
|
private
|
91
81
|
def dealloc(stmt)
|
@@ -116,6 +106,10 @@ module ActiveRecord
|
|
116
106
|
true
|
117
107
|
end
|
118
108
|
|
109
|
+
def supports_transaction_isolation?
|
110
|
+
true
|
111
|
+
end
|
112
|
+
|
119
113
|
def supports_partial_index?
|
120
114
|
true
|
121
115
|
end
|
@@ -132,6 +126,10 @@ module ActiveRecord
|
|
132
126
|
true
|
133
127
|
end
|
134
128
|
|
129
|
+
def supports_check_constraints?
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
135
133
|
def supports_views?
|
136
134
|
true
|
137
135
|
end
|
@@ -175,13 +173,6 @@ module ActiveRecord
|
|
175
173
|
true
|
176
174
|
end
|
177
175
|
|
178
|
-
# Returns 62. SQLite supports index names up to 64
|
179
|
-
# characters. The rest is used by Rails internally to perform
|
180
|
-
# temporary rename operations
|
181
|
-
def allowed_index_name_length
|
182
|
-
index_name_length - 2
|
183
|
-
end
|
184
|
-
|
185
176
|
def native_database_types #:nodoc:
|
186
177
|
NATIVE_DATABASE_TYPES
|
187
178
|
end
|
@@ -215,14 +206,6 @@ module ActiveRecord
|
|
215
206
|
end
|
216
207
|
end
|
217
208
|
|
218
|
-
#--
|
219
|
-
# DATABASE STATEMENTS ======================================
|
220
|
-
#++
|
221
|
-
def explain(arel, binds = [])
|
222
|
-
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
223
|
-
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
|
224
|
-
end
|
225
|
-
|
226
209
|
# SCHEMA STATEMENTS ========================================
|
227
210
|
|
228
211
|
def primary_keys(table_name) # :nodoc:
|
@@ -230,8 +213,11 @@ module ActiveRecord
|
|
230
213
|
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
|
231
214
|
end
|
232
215
|
|
233
|
-
def remove_index(table_name,
|
234
|
-
|
216
|
+
def remove_index(table_name, column_name = nil, **options) # :nodoc:
|
217
|
+
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
|
218
|
+
|
219
|
+
index_name = index_name_for_remove(table_name, column_name, options)
|
220
|
+
|
235
221
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
236
222
|
end
|
237
223
|
|
@@ -240,6 +226,8 @@ module ActiveRecord
|
|
240
226
|
# Example:
|
241
227
|
# rename_table('octopuses', 'octopi')
|
242
228
|
def rename_table(table_name, new_name)
|
229
|
+
schema_cache.clear_data_source_cache!(table_name.to_s)
|
230
|
+
schema_cache.clear_data_source_cache!(new_name.to_s)
|
243
231
|
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
244
232
|
rename_table_indexes(table_name, new_name)
|
245
233
|
end
|
@@ -280,16 +268,11 @@ module ActiveRecord
|
|
280
268
|
end
|
281
269
|
end
|
282
270
|
|
283
|
-
def change_column(table_name, column_name, type, options
|
271
|
+
def change_column(table_name, column_name, type, **options) #:nodoc:
|
284
272
|
alter_table(table_name) do |definition|
|
285
273
|
definition[column_name].instance_eval do
|
286
|
-
self.type
|
287
|
-
self.
|
288
|
-
self.default = options[:default] if options.include?(:default)
|
289
|
-
self.null = options[:null] if options.include?(:null)
|
290
|
-
self.precision = options[:precision] if options.include?(:precision)
|
291
|
-
self.scale = options[:scale] if options.include?(:scale)
|
292
|
-
self.collation = options[:collation] if options.include?(:collation)
|
274
|
+
self.type = aliased_types(type.to_s, type)
|
275
|
+
self.options.merge!(options)
|
293
276
|
end
|
294
277
|
end
|
295
278
|
end
|
@@ -325,12 +308,17 @@ module ActiveRecord
|
|
325
308
|
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
326
309
|
elsif insert.update_duplicates?
|
327
310
|
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
311
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
|
328
312
|
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
329
313
|
end
|
330
314
|
|
331
315
|
sql
|
332
316
|
end
|
333
317
|
|
318
|
+
def shared_cache? # :nodoc:
|
319
|
+
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
320
|
+
end
|
321
|
+
|
334
322
|
def get_database_version # :nodoc:
|
335
323
|
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
336
324
|
end
|
@@ -367,7 +355,12 @@ module ActiveRecord
|
|
367
355
|
options[:null] == false && options[:default].nil?
|
368
356
|
end
|
369
357
|
|
370
|
-
def alter_table(
|
358
|
+
def alter_table(
|
359
|
+
table_name,
|
360
|
+
foreign_keys = foreign_keys(table_name),
|
361
|
+
check_constraints = check_constraints(table_name),
|
362
|
+
**options
|
363
|
+
)
|
371
364
|
altered_table_name = "a#{table_name}"
|
372
365
|
|
373
366
|
caller = lambda do |definition|
|
@@ -380,6 +373,10 @@ module ActiveRecord
|
|
380
373
|
definition.foreign_key(to_table, **fk.options)
|
381
374
|
end
|
382
375
|
|
376
|
+
check_constraints.each do |chk|
|
377
|
+
definition.check_constraint(chk.expression, **chk.options)
|
378
|
+
end
|
379
|
+
|
383
380
|
yield definition if block_given?
|
384
381
|
end
|
385
382
|
|
@@ -404,6 +401,7 @@ module ActiveRecord
|
|
404
401
|
if from_primary_key.is_a?(Array)
|
405
402
|
@definition.primary_keys from_primary_key
|
406
403
|
end
|
404
|
+
|
407
405
|
columns(from).each do |column|
|
408
406
|
column_name = options[:rename] ?
|
409
407
|
(options[:rename][column.name] ||
|
@@ -445,10 +443,10 @@ module ActiveRecord
|
|
445
443
|
|
446
444
|
unless columns.empty?
|
447
445
|
# index name can't be the same
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
add_index(to, columns,
|
446
|
+
options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
|
447
|
+
options[:unique] = true if index.unique
|
448
|
+
options[:where] = index.where if index.where
|
449
|
+
add_index(to, columns, **options)
|
452
450
|
end
|
453
451
|
end
|
454
452
|
end
|
@@ -467,17 +465,18 @@ module ActiveRecord
|
|
467
465
|
end
|
468
466
|
|
469
467
|
def translate_exception(exception, message:, sql:, binds:)
|
470
|
-
case exception.message
|
471
468
|
# SQLite 3.8.2 returns a newly formatted error message:
|
472
469
|
# UNIQUE constraint failed: *table_name*.*column_name*
|
473
470
|
# Older versions of SQLite return:
|
474
471
|
# column *column_name* is not unique
|
475
|
-
|
472
|
+
if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
|
476
473
|
RecordNotUnique.new(message, sql: sql, binds: binds)
|
477
|
-
|
474
|
+
elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
|
478
475
|
NotNullViolation.new(message, sql: sql, binds: binds)
|
479
|
-
|
476
|
+
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
480
477
|
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
478
|
+
elsif exception.message.match?(/called on a closed database/i)
|
479
|
+
ConnectionNotEstablished.new(exception)
|
481
480
|
else
|
482
481
|
super
|
483
482
|
end
|
@@ -497,12 +496,12 @@ module ActiveRecord
|
|
497
496
|
# Result will have following sample string
|
498
497
|
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
499
498
|
# "password_digest" varchar COLLATE "NOCASE");
|
500
|
-
result =
|
499
|
+
result = query_value(sql, "SCHEMA")
|
501
500
|
|
502
501
|
if result
|
503
502
|
# Splitting with left parentheses and discarding the first part will return all
|
504
503
|
# columns separated with comma(,).
|
505
|
-
columns_string = result
|
504
|
+
columns_string = result.split("(", 2).last
|
506
505
|
|
507
506
|
columns_string.split(",").each do |column_string|
|
508
507
|
# This regex will match the column name and collation type and will save
|
@@ -510,7 +509,7 @@ module ActiveRecord
|
|
510
509
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
511
510
|
end
|
512
511
|
|
513
|
-
basic_structure.map
|
512
|
+
basic_structure.map do |column|
|
514
513
|
column_name = column["name"]
|
515
514
|
|
516
515
|
if collation_hash.has_key? column_name
|