activerecord 6.0.0.beta1 → 6.0.1.rc1
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 +529 -10
- data/README.rdoc +3 -1
- data/lib/active_record.rb +7 -1
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +4 -3
- data/lib/active_record/associations/association.rb +27 -2
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency.rb +14 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/preloader.rb +12 -7
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +21 -7
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
- data/lib/active_record/connection_handling.rb +40 -17
- data/lib/active_record/core.rb +35 -24
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/model_schema.rb +3 -0
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation.rb +185 -35
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +23 -14
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +230 -69
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +56 -46
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/arel.rb +18 -4
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +7 -2
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +19 -12
- data/lib/active_record/collection_cache_key.rb +0 -53
- data/lib/arel/nodes/values.rb +0 -16
@@ -13,6 +13,7 @@ module ActiveRecord
|
|
13
13
|
@columns_hash = {}
|
14
14
|
@primary_keys = {}
|
15
15
|
@data_sources = {}
|
16
|
+
@indexes = {}
|
16
17
|
end
|
17
18
|
|
18
19
|
def initialize_dup(other)
|
@@ -21,22 +22,27 @@ module ActiveRecord
|
|
21
22
|
@columns_hash = @columns_hash.dup
|
22
23
|
@primary_keys = @primary_keys.dup
|
23
24
|
@data_sources = @data_sources.dup
|
25
|
+
@indexes = @indexes.dup
|
24
26
|
end
|
25
27
|
|
26
28
|
def encode_with(coder)
|
27
|
-
coder["columns"]
|
28
|
-
coder["columns_hash"]
|
29
|
-
coder["primary_keys"]
|
30
|
-
coder["data_sources"]
|
31
|
-
coder["
|
29
|
+
coder["columns"] = @columns
|
30
|
+
coder["columns_hash"] = @columns_hash
|
31
|
+
coder["primary_keys"] = @primary_keys
|
32
|
+
coder["data_sources"] = @data_sources
|
33
|
+
coder["indexes"] = @indexes
|
34
|
+
coder["version"] = connection.migration_context.current_version
|
35
|
+
coder["database_version"] = database_version
|
32
36
|
end
|
33
37
|
|
34
38
|
def init_with(coder)
|
35
|
-
@columns
|
36
|
-
@columns_hash
|
37
|
-
@primary_keys
|
38
|
-
@data_sources
|
39
|
-
@
|
39
|
+
@columns = coder["columns"]
|
40
|
+
@columns_hash = coder["columns_hash"]
|
41
|
+
@primary_keys = coder["primary_keys"]
|
42
|
+
@data_sources = coder["data_sources"]
|
43
|
+
@indexes = coder["indexes"] || {}
|
44
|
+
@version = coder["version"]
|
45
|
+
@database_version = coder["database_version"]
|
40
46
|
end
|
41
47
|
|
42
48
|
def primary_keys(table_name)
|
@@ -57,6 +63,7 @@ module ActiveRecord
|
|
57
63
|
primary_keys(table_name)
|
58
64
|
columns(table_name)
|
59
65
|
columns_hash(table_name)
|
66
|
+
indexes(table_name)
|
60
67
|
end
|
61
68
|
end
|
62
69
|
|
@@ -82,17 +89,27 @@ module ActiveRecord
|
|
82
89
|
@columns_hash.key?(table_name)
|
83
90
|
end
|
84
91
|
|
92
|
+
def indexes(table_name)
|
93
|
+
@indexes[table_name] ||= connection.indexes(table_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
def database_version # :nodoc:
|
97
|
+
@database_version ||= connection.get_database_version
|
98
|
+
end
|
99
|
+
|
85
100
|
# Clears out internal caches
|
86
101
|
def clear!
|
87
102
|
@columns.clear
|
88
103
|
@columns_hash.clear
|
89
104
|
@primary_keys.clear
|
90
105
|
@data_sources.clear
|
106
|
+
@indexes.clear
|
91
107
|
@version = nil
|
108
|
+
@database_version = nil
|
92
109
|
end
|
93
110
|
|
94
111
|
def size
|
95
|
-
[@columns, @columns_hash, @primary_keys, @data_sources].
|
112
|
+
[@columns, @columns_hash, @primary_keys, @data_sources].sum(&:size)
|
96
113
|
end
|
97
114
|
|
98
115
|
# Clear out internal caches for the data source +name+.
|
@@ -101,20 +118,21 @@ module ActiveRecord
|
|
101
118
|
@columns_hash.delete name
|
102
119
|
@primary_keys.delete name
|
103
120
|
@data_sources.delete name
|
121
|
+
@indexes.delete name
|
104
122
|
end
|
105
123
|
|
106
124
|
def marshal_dump
|
107
125
|
# if we get current version during initialization, it happens stack over flow.
|
108
126
|
@version = connection.migration_context.current_version
|
109
|
-
[@version, @columns, @columns_hash, @primary_keys, @data_sources]
|
127
|
+
[@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, database_version]
|
110
128
|
end
|
111
129
|
|
112
130
|
def marshal_load(array)
|
113
|
-
@version, @columns, @columns_hash, @primary_keys, @data_sources = array
|
131
|
+
@version, @columns, @columns_hash, @primary_keys, @data_sources, @indexes, @database_version = array
|
132
|
+
@indexes = @indexes || {}
|
114
133
|
end
|
115
134
|
|
116
135
|
private
|
117
|
-
|
118
136
|
def prepare_data_sources
|
119
137
|
connection.data_sources.each { |source| @data_sources[source] = true }
|
120
138
|
end
|
@@ -16,19 +16,22 @@ module ActiveRecord
|
|
16
16
|
|
17
17
|
def ==(other)
|
18
18
|
other.is_a?(SqlTypeMetadata) &&
|
19
|
-
|
19
|
+
sql_type == other.sql_type &&
|
20
|
+
type == other.type &&
|
21
|
+
limit == other.limit &&
|
22
|
+
precision == other.precision &&
|
23
|
+
scale == other.scale
|
20
24
|
end
|
21
25
|
alias eql? ==
|
22
26
|
|
23
27
|
def hash
|
24
|
-
|
28
|
+
SqlTypeMetadata.hash ^
|
29
|
+
sql_type.hash ^
|
30
|
+
type.hash ^
|
31
|
+
limit.hash ^
|
32
|
+
precision.hash >> 1 ^
|
33
|
+
scale.hash >> 2
|
25
34
|
end
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
def attributes_for_hash
|
30
|
-
[self.class, sql_type, type, limit, precision, scale]
|
31
|
-
end
|
32
35
|
end
|
33
36
|
end
|
34
37
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
module DatabaseStatements
|
7
|
+
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
8
|
+
:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with
|
9
|
+
) # :nodoc:
|
10
|
+
private_constant :READ_QUERY
|
11
|
+
|
12
|
+
def write_query?(sql) # :nodoc:
|
13
|
+
!READ_QUERY.match?(sql)
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(sql, name = nil) #:nodoc:
|
17
|
+
if preventing_writes? && write_query?(sql)
|
18
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
19
|
+
end
|
20
|
+
|
21
|
+
materialize_transactions
|
22
|
+
|
23
|
+
log(sql, name) do
|
24
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
25
|
+
@connection.execute(sql)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def exec_query(sql, name = nil, binds = [], prepare: false)
|
31
|
+
if preventing_writes? && write_query?(sql)
|
32
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
33
|
+
end
|
34
|
+
|
35
|
+
materialize_transactions
|
36
|
+
|
37
|
+
type_casted_binds = type_casted_binds(binds)
|
38
|
+
|
39
|
+
log(sql, name, binds, type_casted_binds) do
|
40
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
41
|
+
# Don't cache statements if they are not prepared
|
42
|
+
unless prepare
|
43
|
+
stmt = @connection.prepare(sql)
|
44
|
+
begin
|
45
|
+
cols = stmt.columns
|
46
|
+
unless without_prepared_statement?(binds)
|
47
|
+
stmt.bind_params(type_casted_binds)
|
48
|
+
end
|
49
|
+
records = stmt.to_a
|
50
|
+
ensure
|
51
|
+
stmt.close
|
52
|
+
end
|
53
|
+
else
|
54
|
+
stmt = @statements[sql] ||= @connection.prepare(sql)
|
55
|
+
cols = stmt.columns
|
56
|
+
stmt.reset!
|
57
|
+
stmt.bind_params(type_casted_binds)
|
58
|
+
records = stmt.to_a
|
59
|
+
end
|
60
|
+
|
61
|
+
ActiveRecord::Result.new(cols, records)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def exec_delete(sql, name = "SQL", binds = [])
|
67
|
+
exec_query(sql, name, binds)
|
68
|
+
@connection.changes
|
69
|
+
end
|
70
|
+
alias :exec_update :exec_delete
|
71
|
+
|
72
|
+
def begin_db_transaction #:nodoc:
|
73
|
+
log("begin transaction", nil) { @connection.transaction }
|
74
|
+
end
|
75
|
+
|
76
|
+
def commit_db_transaction #:nodoc:
|
77
|
+
log("commit transaction", nil) { @connection.commit }
|
78
|
+
end
|
79
|
+
|
80
|
+
def exec_rollback_db_transaction #:nodoc:
|
81
|
+
log("rollback transaction", nil) { @connection.rollback }
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
private
|
86
|
+
def execute_batch(sql, name = nil)
|
87
|
+
if preventing_writes? && write_query?(sql)
|
88
|
+
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
89
|
+
end
|
90
|
+
|
91
|
+
materialize_transactions
|
92
|
+
|
93
|
+
log(sql, name) do
|
94
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
95
|
+
@connection.execute_batch2(sql)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def last_inserted_id(result)
|
101
|
+
@connection.last_insert_row_id
|
102
|
+
end
|
103
|
+
|
104
|
+
def build_fixture_statements(fixture_set)
|
105
|
+
fixture_set.flat_map do |table_name, fixtures|
|
106
|
+
next if fixtures.empty?
|
107
|
+
fixtures.map { |fixture| build_fixture_sql([fixture], table_name) }
|
108
|
+
end.compact
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_truncate_statements(*table_names)
|
112
|
+
truncate_tables = table_names.map do |table_name|
|
113
|
+
"DELETE FROM #{quote_table_name(table_name)}"
|
114
|
+
end
|
115
|
+
combine_multi_statements(truncate_tables)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -13,11 +13,11 @@ module ActiveRecord
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def quote_table_name(name)
|
16
|
-
|
16
|
+
self.class.quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
|
17
17
|
end
|
18
18
|
|
19
19
|
def quote_column_name(name)
|
20
|
-
|
20
|
+
self.class.quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
|
21
21
|
end
|
22
22
|
|
23
23
|
def quoted_time(value)
|
@@ -45,6 +45,42 @@ module ActiveRecord
|
|
45
45
|
0
|
46
46
|
end
|
47
47
|
|
48
|
+
def column_name_matcher
|
49
|
+
COLUMN_NAME
|
50
|
+
end
|
51
|
+
|
52
|
+
def column_name_with_order_matcher
|
53
|
+
COLUMN_NAME_WITH_ORDER
|
54
|
+
end
|
55
|
+
|
56
|
+
COLUMN_NAME = /
|
57
|
+
\A
|
58
|
+
(
|
59
|
+
(?:
|
60
|
+
# "table_name"."column_name" | function(one or no argument)
|
61
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
|
62
|
+
)
|
63
|
+
(?:\s+AS\s+(?:\w+|"\w+"))?
|
64
|
+
)
|
65
|
+
(?:\s*,\s*\g<1>)*
|
66
|
+
\z
|
67
|
+
/ix
|
68
|
+
|
69
|
+
COLUMN_NAME_WITH_ORDER = /
|
70
|
+
\A
|
71
|
+
(
|
72
|
+
(?:
|
73
|
+
# "table_name"."column_name" | function(one or no argument)
|
74
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")) | \w+\((?:|\g<2>)\)
|
75
|
+
)
|
76
|
+
(?:\s+ASC|\s+DESC)?
|
77
|
+
)
|
78
|
+
(?:\s*,\s*\g<1>)*
|
79
|
+
\z
|
80
|
+
/ix
|
81
|
+
|
82
|
+
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
83
|
+
|
48
84
|
private
|
49
85
|
|
50
86
|
def _type_cast(value)
|
@@ -52,6 +52,32 @@ module ActiveRecord
|
|
52
52
|
end.compact
|
53
53
|
end
|
54
54
|
|
55
|
+
def add_foreign_key(from_table, to_table, **options)
|
56
|
+
alter_table(from_table) do |definition|
|
57
|
+
to_table = strip_table_name_prefix_and_suffix(to_table)
|
58
|
+
definition.foreign_key(to_table, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_foreign_key(from_table, to_table = nil, **options)
|
63
|
+
to_table ||= options[:to_table]
|
64
|
+
options = options.except(:name, :to_table)
|
65
|
+
foreign_keys = foreign_keys(from_table)
|
66
|
+
|
67
|
+
fkey = foreign_keys.detect do |fk|
|
68
|
+
table = to_table || begin
|
69
|
+
table = options[:column].to_s.delete_suffix("_id")
|
70
|
+
Base.pluralize_table_names ? table.pluralize : table
|
71
|
+
end
|
72
|
+
table = strip_table_name_prefix_and_suffix(table)
|
73
|
+
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
74
|
+
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
|
75
|
+
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
|
76
|
+
|
77
|
+
foreign_keys.delete(fkey)
|
78
|
+
alter_table(from_table, foreign_keys)
|
79
|
+
end
|
80
|
+
|
55
81
|
def create_schema_dumper(options)
|
56
82
|
SQLite3::SchemaDumper.create(self, options)
|
57
83
|
end
|
@@ -62,7 +88,7 @@ module ActiveRecord
|
|
62
88
|
end
|
63
89
|
|
64
90
|
def create_table_definition(*args)
|
65
|
-
SQLite3::TableDefinition.new(*args)
|
91
|
+
SQLite3::TableDefinition.new(self, *args)
|
66
92
|
end
|
67
93
|
|
68
94
|
def new_column_from_field(table_name, field)
|
@@ -79,7 +105,7 @@ module ActiveRecord
|
|
79
105
|
end
|
80
106
|
|
81
107
|
type_metadata = fetch_type_metadata(field["type"])
|
82
|
-
Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0,
|
108
|
+
Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
|
83
109
|
end
|
84
110
|
|
85
111
|
def data_source_sql(name = nil, type: nil)
|
@@ -4,12 +4,13 @@ require "active_record/connection_adapters/abstract_adapter"
|
|
4
4
|
require "active_record/connection_adapters/statement_pool"
|
5
5
|
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
|
6
6
|
require "active_record/connection_adapters/sqlite3/quoting"
|
7
|
+
require "active_record/connection_adapters/sqlite3/database_statements"
|
7
8
|
require "active_record/connection_adapters/sqlite3/schema_creation"
|
8
9
|
require "active_record/connection_adapters/sqlite3/schema_definitions"
|
9
10
|
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
10
11
|
require "active_record/connection_adapters/sqlite3/schema_statements"
|
11
12
|
|
12
|
-
gem "sqlite3", "~> 1.
|
13
|
+
gem "sqlite3", "~> 1.4"
|
13
14
|
require "sqlite3"
|
14
15
|
|
15
16
|
module ActiveRecord
|
@@ -36,8 +37,6 @@ module ActiveRecord
|
|
36
37
|
config.merge(results_as_hash: true)
|
37
38
|
)
|
38
39
|
|
39
|
-
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
|
40
|
-
|
41
40
|
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
|
42
41
|
rescue Errno::ENOENT => error
|
43
42
|
if error.message.include?("No such file or directory")
|
@@ -60,6 +59,7 @@ module ActiveRecord
|
|
60
59
|
|
61
60
|
include SQLite3::Quoting
|
62
61
|
include SQLite3::SchemaStatements
|
62
|
+
include SQLite3::DatabaseStatements
|
63
63
|
|
64
64
|
NATIVE_DATABASE_TYPES = {
|
65
65
|
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
|
@@ -95,12 +95,19 @@ module ActiveRecord
|
|
95
95
|
|
96
96
|
def initialize(connection, logger, connection_options, config)
|
97
97
|
super(connection, logger, config)
|
98
|
-
|
99
|
-
@active = true
|
100
|
-
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
101
98
|
configure_connection
|
102
99
|
end
|
103
100
|
|
101
|
+
def self.database_exists?(config)
|
102
|
+
config = config.symbolize_keys
|
103
|
+
if config[:database] == ":memory:"
|
104
|
+
return true
|
105
|
+
else
|
106
|
+
database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
|
107
|
+
File.exist?(database_file)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
104
111
|
def supports_ddl_transactions?
|
105
112
|
true
|
106
113
|
end
|
@@ -114,14 +121,14 @@ module ActiveRecord
|
|
114
121
|
end
|
115
122
|
|
116
123
|
def supports_expression_index?
|
117
|
-
|
124
|
+
database_version >= "3.9.0"
|
118
125
|
end
|
119
126
|
|
120
127
|
def requires_reloading?
|
121
128
|
true
|
122
129
|
end
|
123
130
|
|
124
|
-
def
|
131
|
+
def supports_foreign_keys?
|
125
132
|
true
|
126
133
|
end
|
127
134
|
|
@@ -137,23 +144,33 @@ module ActiveRecord
|
|
137
144
|
true
|
138
145
|
end
|
139
146
|
|
147
|
+
def supports_common_table_expressions?
|
148
|
+
database_version >= "3.8.3"
|
149
|
+
end
|
150
|
+
|
151
|
+
def supports_insert_on_conflict?
|
152
|
+
database_version >= "3.24.0"
|
153
|
+
end
|
154
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
155
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
156
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
157
|
+
|
140
158
|
def active?
|
141
|
-
|
159
|
+
!@connection.closed?
|
160
|
+
end
|
161
|
+
|
162
|
+
def reconnect!
|
163
|
+
super
|
164
|
+
connect if @connection.closed?
|
142
165
|
end
|
143
166
|
|
144
167
|
# Disconnects from the database if already connected. Otherwise, this
|
145
168
|
# method does nothing.
|
146
169
|
def disconnect!
|
147
170
|
super
|
148
|
-
@active = false
|
149
171
|
@connection.close rescue nil
|
150
172
|
end
|
151
173
|
|
152
|
-
# Clears the prepared statements cache.
|
153
|
-
def clear_cache!
|
154
|
-
@statements.clear
|
155
|
-
end
|
156
|
-
|
157
174
|
def supports_index_sort_order?
|
158
175
|
true
|
159
176
|
end
|
@@ -201,91 +218,11 @@ module ActiveRecord
|
|
201
218
|
#--
|
202
219
|
# DATABASE STATEMENTS ======================================
|
203
220
|
#++
|
204
|
-
|
205
|
-
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc:
|
206
|
-
private_constant :READ_QUERY
|
207
|
-
|
208
|
-
def write_query?(sql) # :nodoc:
|
209
|
-
!READ_QUERY.match?(sql)
|
210
|
-
end
|
211
|
-
|
212
221
|
def explain(arel, binds = [])
|
213
222
|
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
214
223
|
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
|
215
224
|
end
|
216
225
|
|
217
|
-
def exec_query(sql, name = nil, binds = [], prepare: false)
|
218
|
-
if preventing_writes? && write_query?(sql)
|
219
|
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
220
|
-
end
|
221
|
-
|
222
|
-
materialize_transactions
|
223
|
-
|
224
|
-
type_casted_binds = type_casted_binds(binds)
|
225
|
-
|
226
|
-
log(sql, name, binds, type_casted_binds) do
|
227
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
228
|
-
# Don't cache statements if they are not prepared
|
229
|
-
unless prepare
|
230
|
-
stmt = @connection.prepare(sql)
|
231
|
-
begin
|
232
|
-
cols = stmt.columns
|
233
|
-
unless without_prepared_statement?(binds)
|
234
|
-
stmt.bind_params(type_casted_binds)
|
235
|
-
end
|
236
|
-
records = stmt.to_a
|
237
|
-
ensure
|
238
|
-
stmt.close
|
239
|
-
end
|
240
|
-
else
|
241
|
-
stmt = @statements[sql] ||= @connection.prepare(sql)
|
242
|
-
cols = stmt.columns
|
243
|
-
stmt.reset!
|
244
|
-
stmt.bind_params(type_casted_binds)
|
245
|
-
records = stmt.to_a
|
246
|
-
end
|
247
|
-
|
248
|
-
ActiveRecord::Result.new(cols, records)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
def exec_delete(sql, name = "SQL", binds = [])
|
254
|
-
exec_query(sql, name, binds)
|
255
|
-
@connection.changes
|
256
|
-
end
|
257
|
-
alias :exec_update :exec_delete
|
258
|
-
|
259
|
-
def last_inserted_id(result)
|
260
|
-
@connection.last_insert_row_id
|
261
|
-
end
|
262
|
-
|
263
|
-
def execute(sql, name = nil) #:nodoc:
|
264
|
-
if preventing_writes? && write_query?(sql)
|
265
|
-
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
266
|
-
end
|
267
|
-
|
268
|
-
materialize_transactions
|
269
|
-
|
270
|
-
log(sql, name) do
|
271
|
-
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
272
|
-
@connection.execute(sql)
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
def begin_db_transaction #:nodoc:
|
278
|
-
log("begin transaction", nil) { @connection.transaction }
|
279
|
-
end
|
280
|
-
|
281
|
-
def commit_db_transaction #:nodoc:
|
282
|
-
log("commit transaction", nil) { @connection.commit }
|
283
|
-
end
|
284
|
-
|
285
|
-
def exec_rollback_db_transaction #:nodoc:
|
286
|
-
log("rollback transaction", nil) { @connection.rollback }
|
287
|
-
end
|
288
|
-
|
289
226
|
# SCHEMA STATEMENTS ========================================
|
290
227
|
|
291
228
|
def primary_keys(table_name) # :nodoc:
|
@@ -320,6 +257,9 @@ module ActiveRecord
|
|
320
257
|
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
321
258
|
alter_table(table_name) do |definition|
|
322
259
|
definition.remove_column column_name
|
260
|
+
definition.foreign_keys.delete_if do |_, fk_options|
|
261
|
+
fk_options[:column] == column_name.to_s
|
262
|
+
end
|
323
263
|
end
|
324
264
|
end
|
325
265
|
|
@@ -378,15 +318,26 @@ module ActiveRecord
|
|
378
318
|
end
|
379
319
|
end
|
380
320
|
|
381
|
-
def
|
382
|
-
|
383
|
-
transaction(requires_new: true) do
|
384
|
-
tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
|
321
|
+
def build_insert_sql(insert) # :nodoc:
|
322
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
385
323
|
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
324
|
+
if insert.skip_duplicates?
|
325
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
326
|
+
elsif insert.update_duplicates?
|
327
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
328
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
329
|
+
end
|
330
|
+
|
331
|
+
sql
|
332
|
+
end
|
333
|
+
|
334
|
+
def get_database_version # :nodoc:
|
335
|
+
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
336
|
+
end
|
337
|
+
|
338
|
+
def check_version # :nodoc:
|
339
|
+
if database_version < "3.8.0"
|
340
|
+
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
|
390
341
|
end
|
391
342
|
end
|
392
343
|
|
@@ -397,12 +348,6 @@ module ActiveRecord
|
|
397
348
|
999
|
398
349
|
end
|
399
350
|
|
400
|
-
def check_version
|
401
|
-
if sqlite_version < "3.8.0"
|
402
|
-
raise "Your version of SQLite (#{sqlite_version}) is too old. Active Record supports SQLite >= 3.8."
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
351
|
def initialize_type_map(m = type_map)
|
407
352
|
super
|
408
353
|
register_class_with_limit m, %r(int)i, SQLite3Integer
|
@@ -421,9 +366,8 @@ module ActiveRecord
|
|
421
366
|
type.to_sym == :primary_key || options[:primary_key]
|
422
367
|
end
|
423
368
|
|
424
|
-
def alter_table(table_name,
|
369
|
+
def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
|
425
370
|
altered_table_name = "a#{table_name}"
|
426
|
-
foreign_keys = foreign_keys(table_name)
|
427
371
|
|
428
372
|
caller = lambda do |definition|
|
429
373
|
rename = options[:rename] || {}
|
@@ -431,7 +375,8 @@ module ActiveRecord
|
|
431
375
|
if column = rename[fk.options[:column]]
|
432
376
|
fk.options[:column] = column
|
433
377
|
end
|
434
|
-
|
378
|
+
to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
379
|
+
definition.foreign_key(to_table, fk.options)
|
435
380
|
end
|
436
381
|
|
437
382
|
yield definition if block_given?
|
@@ -520,10 +465,6 @@ module ActiveRecord
|
|
520
465
|
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
521
466
|
end
|
522
467
|
|
523
|
-
def sqlite_version
|
524
|
-
@sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
525
|
-
end
|
526
|
-
|
527
468
|
def translate_exception(exception, message:, sql:, binds:)
|
528
469
|
case exception.message
|
529
470
|
# SQLite 3.8.2 returns a newly formatted error message:
|
@@ -558,9 +499,9 @@ module ActiveRecord
|
|
558
499
|
result = exec_query(sql, "SCHEMA").first
|
559
500
|
|
560
501
|
if result
|
561
|
-
# Splitting with left parentheses and
|
502
|
+
# Splitting with left parentheses and discarding the first part will return all
|
562
503
|
# columns separated with comma(,).
|
563
|
-
columns_string = result["sql"].split("(").last
|
504
|
+
columns_string = result["sql"].split("(", 2).last
|
564
505
|
|
565
506
|
columns_string.split(",").each do |column_string|
|
566
507
|
# This regex will match the column name and collation type and will save
|
@@ -586,7 +527,21 @@ module ActiveRecord
|
|
586
527
|
Arel::Visitors::SQLite.new(self)
|
587
528
|
end
|
588
529
|
|
530
|
+
def build_statement_pool
|
531
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
532
|
+
end
|
533
|
+
|
534
|
+
def connect
|
535
|
+
@connection = ::SQLite3::Database.new(
|
536
|
+
@config[:database].to_s,
|
537
|
+
@config.merge(results_as_hash: true)
|
538
|
+
)
|
539
|
+
configure_connection
|
540
|
+
end
|
541
|
+
|
589
542
|
def configure_connection
|
543
|
+
@connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
|
544
|
+
|
590
545
|
execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
591
546
|
end
|
592
547
|
|