activerecord 7.0.6 → 7.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1356 -1425
- data/MIT-LICENSE +1 -1
- data/README.rdoc +15 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +18 -10
- data/lib/active_record/associations/collection_proxy.rb +21 -11
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -7
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader.rb +12 -9
- data/lib/active_record/associations/singular_association.rb +6 -8
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +193 -97
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +40 -26
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +60 -18
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
- data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +496 -102
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -113
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +14 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +163 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +71 -94
- data/lib/active_record/core.rb +128 -138
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +8 -3
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +36 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -26
- data/lib/active_record/errors.rb +89 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +119 -71
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +5 -7
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +100 -4
- data/lib/active_record/migration/compatibility.rb +142 -58
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration.rb +265 -112
- data/lib/active_record/model_schema.rb +47 -27
- data/lib/active_record/nested_attributes.rb +28 -3
- data/lib/active_record/normalization.rb +158 -0
- data/lib/active_record/persistence.rb +186 -34
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +139 -145
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +169 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +152 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- data/lib/active_record/relation/finder_methods.rb +85 -15
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +27 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +377 -69
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +76 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +41 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +11 -2
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +4 -0
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +52 -17
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
|
@@ -4,8 +4,11 @@ module ActiveRecord
|
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module SQLite3
|
|
6
6
|
module Quoting # :nodoc:
|
|
7
|
+
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
|
8
|
+
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
|
9
|
+
|
|
7
10
|
def quote_string(s)
|
|
8
|
-
|
|
11
|
+
::SQLite3::Database.quote(s)
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
def quote_table_name_for_assignment(table, attr)
|
|
@@ -13,11 +16,11 @@ module ActiveRecord
|
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
def quote_table_name(name)
|
|
16
|
-
|
|
19
|
+
QUOTED_TABLE_NAMES[name] ||= super.gsub(".", "\".\"").freeze
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def quote_column_name(name)
|
|
20
|
-
|
|
23
|
+
QUOTED_COLUMN_NAMES[name] ||= %Q("#{super.gsub('"', '""')}")
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
def quoted_time(value)
|
|
@@ -86,7 +89,7 @@ module ActiveRecord
|
|
|
86
89
|
(
|
|
87
90
|
(?:
|
|
88
91
|
# "table_name"."column_name" | function(one or no argument)
|
|
89
|
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")
|
|
92
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
|
90
93
|
)
|
|
91
94
|
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
|
92
95
|
)
|
|
@@ -99,8 +102,9 @@ module ActiveRecord
|
|
|
99
102
|
(
|
|
100
103
|
(?:
|
|
101
104
|
# "table_name"."column_name" | function(one or no argument)
|
|
102
|
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")
|
|
105
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
|
103
106
|
)
|
|
107
|
+
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
|
|
104
108
|
(?:\s+ASC|\s+DESC)?
|
|
105
109
|
)
|
|
106
110
|
(?:\s*,\s*\g<1>)*
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
|
5
5
|
module SQLite3
|
|
6
|
+
# = Active Record SQLite3 Adapter \Table Definition
|
|
6
7
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
|
8
|
+
def change_column(column_name, type, **options)
|
|
9
|
+
name = column_name.to_s
|
|
10
|
+
@columns_hash[name] = nil
|
|
11
|
+
column(name, type, **options)
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
def references(*args, **options)
|
|
8
15
|
super(*args, type: :integer, **options)
|
|
9
16
|
end
|
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
|
6
6
|
module SchemaStatements # :nodoc:
|
|
7
7
|
# Returns an array of indexes for the given table.
|
|
8
8
|
def indexes(table_name)
|
|
9
|
-
|
|
9
|
+
internal_exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").filter_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
12
|
next if row["name"].start_with?("sqlite_")
|
|
@@ -23,7 +23,7 @@ module ActiveRecord
|
|
|
23
23
|
|
|
24
24
|
/\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?(?:\s*\/\*.*\*\/)?\z/i =~ index_sql
|
|
25
25
|
|
|
26
|
-
columns =
|
|
26
|
+
columns = internal_exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
|
|
27
27
|
col["name"]
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -84,11 +84,11 @@ module ActiveRecord
|
|
|
84
84
|
table_sql = query_value(<<-SQL, "SCHEMA")
|
|
85
85
|
SELECT sql
|
|
86
86
|
FROM sqlite_master
|
|
87
|
-
WHERE name = #{
|
|
87
|
+
WHERE name = #{quote(table_name)} AND type = 'table'
|
|
88
88
|
UNION ALL
|
|
89
89
|
SELECT sql
|
|
90
90
|
FROM sqlite_temp_master
|
|
91
|
-
WHERE name = #{
|
|
91
|
+
WHERE name = #{quote(table_name)} AND type = 'table'
|
|
92
92
|
SQL
|
|
93
93
|
|
|
94
94
|
table_sql.to_s.scan(/CONSTRAINT\s+(?<name>\w+)\s+CHECK\s+\((?<expression>(:?[^()]|\(\g<expression>\))+)\)/i).map do |name, expression|
|
|
@@ -102,7 +102,9 @@ module ActiveRecord
|
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
-
def remove_check_constraint(table_name, expression = nil, **options)
|
|
105
|
+
def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
|
|
106
|
+
return if if_exists && !check_constraint_exists?(table_name, **options)
|
|
107
|
+
|
|
106
108
|
check_constraints = check_constraints(table_name)
|
|
107
109
|
chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
|
|
108
110
|
check_constraints.delete_if { |chk| chk.name == chk_name_to_delete }
|
|
@@ -113,9 +115,13 @@ module ActiveRecord
|
|
|
113
115
|
SQLite3::SchemaDumper.create(self, options)
|
|
114
116
|
end
|
|
115
117
|
|
|
118
|
+
def schema_creation # :nodoc
|
|
119
|
+
SQLite3::SchemaCreation.new(self)
|
|
120
|
+
end
|
|
121
|
+
|
|
116
122
|
private
|
|
117
|
-
def
|
|
118
|
-
|
|
123
|
+
def valid_table_definition_options
|
|
124
|
+
super + [:rename]
|
|
119
125
|
end
|
|
120
126
|
|
|
121
127
|
def create_table_definition(name, **options)
|
|
@@ -126,12 +132,13 @@ module ActiveRecord
|
|
|
126
132
|
super unless internal
|
|
127
133
|
end
|
|
128
134
|
|
|
129
|
-
def new_column_from_field(table_name, field)
|
|
135
|
+
def new_column_from_field(table_name, field, definitions)
|
|
130
136
|
default = field["dflt_value"]
|
|
131
137
|
|
|
132
138
|
type_metadata = fetch_type_metadata(field["type"])
|
|
133
139
|
default_value = extract_value_from_default(default)
|
|
134
140
|
default_function = extract_default_function(default_value, default)
|
|
141
|
+
rowid = is_column_the_rowid?(field, definitions)
|
|
135
142
|
|
|
136
143
|
Column.new(
|
|
137
144
|
field["name"],
|
|
@@ -139,10 +146,22 @@ module ActiveRecord
|
|
|
139
146
|
type_metadata,
|
|
140
147
|
field["notnull"].to_i == 0,
|
|
141
148
|
default_function,
|
|
142
|
-
collation: field["collation"]
|
|
149
|
+
collation: field["collation"],
|
|
150
|
+
auto_increment: field["auto_increment"],
|
|
151
|
+
rowid: rowid
|
|
143
152
|
)
|
|
144
153
|
end
|
|
145
154
|
|
|
155
|
+
INTEGER_REGEX = /integer/i
|
|
156
|
+
# if a rowid table has a primary key that consists of a single column
|
|
157
|
+
# and the declared type of that column is "INTEGER" in any mixture of upper and lower case,
|
|
158
|
+
# then the column becomes an alias for the rowid.
|
|
159
|
+
def is_column_the_rowid?(field, column_definitions)
|
|
160
|
+
return false unless INTEGER_REGEX.match?(field["type"]) && field["pk"] == 1
|
|
161
|
+
# is the primary key a single column?
|
|
162
|
+
column_definitions.one? { |c| c["pk"] > 0 }
|
|
163
|
+
end
|
|
164
|
+
|
|
146
165
|
def data_source_sql(name = nil, type: nil)
|
|
147
166
|
scope = quoted_scope(name, type: type)
|
|
148
167
|
scope[:type] ||= "'table','view'"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "active_record/connection_adapters/abstract_adapter"
|
|
4
4
|
require "active_record/connection_adapters/statement_pool"
|
|
5
|
+
require "active_record/connection_adapters/sqlite3/column"
|
|
5
6
|
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
|
|
6
7
|
require "active_record/connection_adapters/sqlite3/quoting"
|
|
7
8
|
require "active_record/connection_adapters/sqlite3/database_statements"
|
|
@@ -15,39 +16,18 @@ require "sqlite3"
|
|
|
15
16
|
|
|
16
17
|
module ActiveRecord
|
|
17
18
|
module ConnectionHandling # :nodoc:
|
|
18
|
-
def
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# Require database.
|
|
22
|
-
unless config[:database]
|
|
23
|
-
raise ArgumentError, "No database file specified. Missing argument: database"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Allow database path relative to Rails.root, but only if the database
|
|
27
|
-
# path is not the special path that tells sqlite to build a database only
|
|
28
|
-
# in memory.
|
|
29
|
-
if ":memory:" != config[:database] && !config[:database].to_s.start_with?("file:")
|
|
30
|
-
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
|
|
31
|
-
dirname = File.dirname(config[:database])
|
|
32
|
-
Dir.mkdir(dirname) unless File.directory?(dirname)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
db = SQLite3::Database.new(
|
|
36
|
-
config[:database].to_s,
|
|
37
|
-
config.merge(results_as_hash: true)
|
|
38
|
-
)
|
|
19
|
+
def sqlite3_adapter_class
|
|
20
|
+
ConnectionAdapters::SQLite3Adapter
|
|
21
|
+
end
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if error.message.include?("No such file or directory")
|
|
43
|
-
raise ActiveRecord::NoDatabaseError
|
|
44
|
-
else
|
|
45
|
-
raise
|
|
46
|
-
end
|
|
23
|
+
def sqlite3_connection(config)
|
|
24
|
+
sqlite3_adapter_class.new(config)
|
|
47
25
|
end
|
|
48
26
|
end
|
|
49
27
|
|
|
50
28
|
module ConnectionAdapters # :nodoc:
|
|
29
|
+
# = Active Record SQLite3 Adapter
|
|
30
|
+
#
|
|
51
31
|
# The SQLite3 adapter works with the sqlite3-ruby drivers
|
|
52
32
|
# (available as gem from https://rubygems.org/gems/sqlite3).
|
|
53
33
|
#
|
|
@@ -57,10 +37,42 @@ module ActiveRecord
|
|
|
57
37
|
class SQLite3Adapter < AbstractAdapter
|
|
58
38
|
ADAPTER_NAME = "SQLite"
|
|
59
39
|
|
|
40
|
+
class << self
|
|
41
|
+
def new_client(config)
|
|
42
|
+
::SQLite3::Database.new(config[:database].to_s, config)
|
|
43
|
+
rescue Errno::ENOENT => error
|
|
44
|
+
if error.message.include?("No such file or directory")
|
|
45
|
+
raise ActiveRecord::NoDatabaseError
|
|
46
|
+
else
|
|
47
|
+
raise
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def dbconsole(config, options = {})
|
|
52
|
+
args = []
|
|
53
|
+
|
|
54
|
+
args << "-#{options[:mode]}" if options[:mode]
|
|
55
|
+
args << "-header" if options[:header]
|
|
56
|
+
args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
|
|
57
|
+
|
|
58
|
+
find_cmd_and_exec("sqlite3", *args)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
60
62
|
include SQLite3::Quoting
|
|
61
63
|
include SQLite3::SchemaStatements
|
|
62
64
|
include SQLite3::DatabaseStatements
|
|
63
65
|
|
|
66
|
+
##
|
|
67
|
+
# :singleton-method:
|
|
68
|
+
# Configure the SQLite3Adapter to be used in a strict strings mode.
|
|
69
|
+
# This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
|
|
70
|
+
# For example, it is possible to create an index for a non existing column.
|
|
71
|
+
# If you wish to enable this mode you can add the following line to your application.rb file:
|
|
72
|
+
#
|
|
73
|
+
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
|
74
|
+
class_attribute :strict_strings_by_default, default: false
|
|
75
|
+
|
|
64
76
|
NATIVE_DATABASE_TYPES = {
|
|
65
77
|
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
|
|
66
78
|
string: { name: "varchar" },
|
|
@@ -77,26 +89,47 @@ module ActiveRecord
|
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
|
92
|
+
alias reset clear
|
|
93
|
+
|
|
80
94
|
private
|
|
81
95
|
def dealloc(stmt)
|
|
82
96
|
stmt.close unless stmt.closed?
|
|
83
97
|
end
|
|
84
98
|
end
|
|
85
99
|
|
|
86
|
-
def initialize(
|
|
87
|
-
|
|
88
|
-
super(connection, logger, config)
|
|
89
|
-
configure_connection
|
|
90
|
-
end
|
|
100
|
+
def initialize(...)
|
|
101
|
+
super
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
@memory_database = false
|
|
104
|
+
case @config[:database].to_s
|
|
105
|
+
when ""
|
|
106
|
+
raise ArgumentError, "No database file specified. Missing argument: database"
|
|
107
|
+
when ":memory:"
|
|
108
|
+
@memory_database = true
|
|
109
|
+
when /\Afile:/
|
|
96
110
|
else
|
|
97
|
-
|
|
98
|
-
File.
|
|
111
|
+
# Otherwise we have a path relative to Rails.root
|
|
112
|
+
@config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root)
|
|
113
|
+
dirname = File.dirname(@config[:database])
|
|
114
|
+
unless File.directory?(dirname)
|
|
115
|
+
begin
|
|
116
|
+
Dir.mkdir(dirname)
|
|
117
|
+
rescue Errno::ENOENT => error
|
|
118
|
+
if error.message.include?("No such file or directory")
|
|
119
|
+
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
|
120
|
+
else
|
|
121
|
+
raise
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
99
125
|
end
|
|
126
|
+
|
|
127
|
+
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
|
128
|
+
@connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def database_exists?
|
|
132
|
+
@config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
|
|
100
133
|
end
|
|
101
134
|
|
|
102
135
|
def supports_ddl_transactions?
|
|
@@ -159,19 +192,18 @@ module ActiveRecord
|
|
|
159
192
|
end
|
|
160
193
|
|
|
161
194
|
def active?
|
|
162
|
-
!@
|
|
195
|
+
@raw_connection && !@raw_connection.closed?
|
|
163
196
|
end
|
|
164
197
|
|
|
165
|
-
|
|
166
|
-
super
|
|
167
|
-
connect if @connection.closed?
|
|
168
|
-
end
|
|
198
|
+
alias :reset! :reconnect!
|
|
169
199
|
|
|
170
200
|
# Disconnects from the database if already connected. Otherwise, this
|
|
171
201
|
# method does nothing.
|
|
172
202
|
def disconnect!
|
|
173
203
|
super
|
|
174
|
-
|
|
204
|
+
|
|
205
|
+
@raw_connection&.close rescue nil
|
|
206
|
+
@raw_connection = nil
|
|
175
207
|
end
|
|
176
208
|
|
|
177
209
|
def supports_index_sort_order?
|
|
@@ -184,7 +216,7 @@ module ActiveRecord
|
|
|
184
216
|
|
|
185
217
|
# Returns the current database encoding format as a string, e.g. 'UTF-8'
|
|
186
218
|
def encoding
|
|
187
|
-
|
|
219
|
+
any_raw_connection.encoding.to_s
|
|
188
220
|
end
|
|
189
221
|
|
|
190
222
|
def supports_explain?
|
|
@@ -211,8 +243,14 @@ module ActiveRecord
|
|
|
211
243
|
end
|
|
212
244
|
end
|
|
213
245
|
|
|
214
|
-
def
|
|
215
|
-
|
|
246
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
|
247
|
+
sql = "PRAGMA foreign_key_check"
|
|
248
|
+
result = execute(sql)
|
|
249
|
+
|
|
250
|
+
unless result.blank?
|
|
251
|
+
tables = result.map { |row| row["table"] }
|
|
252
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql)
|
|
253
|
+
end
|
|
216
254
|
end
|
|
217
255
|
|
|
218
256
|
# SCHEMA STATEMENTS ========================================
|
|
@@ -234,7 +272,8 @@ module ActiveRecord
|
|
|
234
272
|
#
|
|
235
273
|
# Example:
|
|
236
274
|
# rename_table('octopuses', 'octopi')
|
|
237
|
-
def rename_table(table_name, new_name)
|
|
275
|
+
def rename_table(table_name, new_name, **options)
|
|
276
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
|
238
277
|
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
239
278
|
schema_cache.clear_data_source_cache!(new_name.to_s)
|
|
240
279
|
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
|
@@ -277,8 +316,10 @@ module ActiveRecord
|
|
|
277
316
|
end
|
|
278
317
|
|
|
279
318
|
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
|
319
|
+
validate_change_column_null_argument!(null)
|
|
320
|
+
|
|
280
321
|
unless null || default.nil?
|
|
281
|
-
|
|
322
|
+
internal_exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
282
323
|
end
|
|
283
324
|
alter_table(table_name) do |definition|
|
|
284
325
|
definition[column_name].null = null
|
|
@@ -287,10 +328,7 @@ module ActiveRecord
|
|
|
287
328
|
|
|
288
329
|
def change_column(table_name, column_name, type, **options) # :nodoc:
|
|
289
330
|
alter_table(table_name) do |definition|
|
|
290
|
-
definition
|
|
291
|
-
self.type = aliased_types(type.to_s, type)
|
|
292
|
-
self.options.merge!(options)
|
|
293
|
-
end
|
|
331
|
+
definition.change_column(column_name, type, **options)
|
|
294
332
|
end
|
|
295
333
|
end
|
|
296
334
|
|
|
@@ -300,20 +338,42 @@ module ActiveRecord
|
|
|
300
338
|
rename_column_indexes(table_name, column.name, new_column_name)
|
|
301
339
|
end
|
|
302
340
|
|
|
341
|
+
def add_timestamps(table_name, **options)
|
|
342
|
+
options[:null] = false if options[:null].nil?
|
|
343
|
+
|
|
344
|
+
if !options.key?(:precision)
|
|
345
|
+
options[:precision] = 6
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
alter_table(table_name) do |definition|
|
|
349
|
+
definition.column :created_at, :datetime, **options
|
|
350
|
+
definition.column :updated_at, :datetime, **options
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
303
354
|
def add_reference(table_name, ref_name, **options) # :nodoc:
|
|
304
355
|
super(table_name, ref_name, type: :integer, **options)
|
|
305
356
|
end
|
|
306
357
|
alias :add_belongs_to :add_reference
|
|
307
358
|
|
|
308
359
|
def foreign_keys(table_name)
|
|
309
|
-
|
|
310
|
-
fk_info
|
|
360
|
+
# SQLite returns 1 row for each column of composite foreign keys.
|
|
361
|
+
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
|
362
|
+
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
|
363
|
+
grouped_fk.map do |group|
|
|
364
|
+
row = group.first
|
|
311
365
|
options = {
|
|
312
|
-
column: row["from"],
|
|
313
|
-
primary_key: row["to"],
|
|
314
366
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
|
315
367
|
on_update: extract_foreign_key_action(row["on_update"])
|
|
316
368
|
}
|
|
369
|
+
|
|
370
|
+
if group.one?
|
|
371
|
+
options[:column] = row["from"]
|
|
372
|
+
options[:primary_key] = row["to"]
|
|
373
|
+
else
|
|
374
|
+
options[:column] = group.map { |row| row["from"] }
|
|
375
|
+
options[:primary_key] = group.map { |row| row["to"] }
|
|
376
|
+
end
|
|
317
377
|
ForeignKeyDefinition.new(table_name, row["table"], options)
|
|
318
378
|
end
|
|
319
379
|
end
|
|
@@ -370,12 +430,9 @@ module ActiveRecord
|
|
|
370
430
|
end
|
|
371
431
|
|
|
372
432
|
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
|
433
|
+
EXTENDED_TYPE_MAPS = Concurrent::Map.new
|
|
373
434
|
|
|
374
435
|
private
|
|
375
|
-
def type_map
|
|
376
|
-
TYPE_MAP
|
|
377
|
-
end
|
|
378
|
-
|
|
379
436
|
# See https://www.sqlite.org/limits.html,
|
|
380
437
|
# the default value is 999 when not configured.
|
|
381
438
|
def bind_params_length
|
|
@@ -383,7 +440,7 @@ module ActiveRecord
|
|
|
383
440
|
end
|
|
384
441
|
|
|
385
442
|
def table_structure(table_name)
|
|
386
|
-
structure =
|
|
443
|
+
structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
|
387
444
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
|
388
445
|
table_structure_with_collation(table_name, structure)
|
|
389
446
|
end
|
|
@@ -402,6 +459,9 @@ module ActiveRecord
|
|
|
402
459
|
# Numeric types
|
|
403
460
|
when /\A-?\d+(\.\d*)?\z/
|
|
404
461
|
$&
|
|
462
|
+
# Binary columns
|
|
463
|
+
when /x'(.*)'/
|
|
464
|
+
[ $1 ].pack("H*")
|
|
405
465
|
else
|
|
406
466
|
# Anything else is blank or some function
|
|
407
467
|
# and we can't know the value of that, so return nil.
|
|
@@ -483,12 +543,21 @@ module ActiveRecord
|
|
|
483
543
|
default = -> { column.default_function } if default.nil?
|
|
484
544
|
end
|
|
485
545
|
|
|
486
|
-
|
|
487
|
-
limit: column.limit,
|
|
488
|
-
precision: column.precision,
|
|
489
|
-
|
|
546
|
+
column_options = {
|
|
547
|
+
limit: column.limit,
|
|
548
|
+
precision: column.precision,
|
|
549
|
+
scale: column.scale,
|
|
550
|
+
null: column.null,
|
|
551
|
+
collation: column.collation,
|
|
490
552
|
primary_key: column_name == from_primary_key
|
|
491
|
-
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
unless column.auto_increment?
|
|
556
|
+
column_options[:default] = default
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
column_type = column.bigint? ? :bigint : column.type
|
|
560
|
+
@definition.column(column_name, column_type, **column_options)
|
|
492
561
|
end
|
|
493
562
|
|
|
494
563
|
yield @definition if block_given?
|
|
@@ -536,7 +605,7 @@ module ActiveRecord
|
|
|
536
605
|
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
|
537
606
|
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
|
538
607
|
|
|
539
|
-
|
|
608
|
+
internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
|
540
609
|
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
|
541
610
|
end
|
|
542
611
|
|
|
@@ -546,22 +615,24 @@ module ActiveRecord
|
|
|
546
615
|
# Older versions of SQLite return:
|
|
547
616
|
# column *column_name* is not unique
|
|
548
617
|
if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
|
|
549
|
-
RecordNotUnique.new(message, sql: sql, binds: binds)
|
|
618
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
550
619
|
elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
|
|
551
|
-
NotNullViolation.new(message, sql: sql, binds: binds)
|
|
620
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
552
621
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
|
553
|
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
|
622
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
554
623
|
elsif exception.message.match?(/called on a closed database/i)
|
|
555
|
-
ConnectionNotEstablished.new(exception)
|
|
624
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
556
625
|
else
|
|
557
626
|
super
|
|
558
627
|
end
|
|
559
628
|
end
|
|
560
629
|
|
|
561
|
-
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
|
630
|
+
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
|
631
|
+
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
|
562
632
|
|
|
563
633
|
def table_structure_with_collation(table_name, basic_structure)
|
|
564
634
|
collation_hash = {}
|
|
635
|
+
auto_increments = {}
|
|
565
636
|
sql = <<~SQL
|
|
566
637
|
SELECT sql FROM
|
|
567
638
|
(SELECT * FROM sqlite_master UNION ALL
|
|
@@ -583,6 +654,7 @@ module ActiveRecord
|
|
|
583
654
|
# This regex will match the column name and collation type and will save
|
|
584
655
|
# the value in $1 and $2 respectively.
|
|
585
656
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
|
657
|
+
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
|
586
658
|
end
|
|
587
659
|
|
|
588
660
|
basic_structure.map do |column|
|
|
@@ -592,6 +664,10 @@ module ActiveRecord
|
|
|
592
664
|
column["collation"] = collation_hash[column_name]
|
|
593
665
|
end
|
|
594
666
|
|
|
667
|
+
if auto_increments.has_key?(column_name)
|
|
668
|
+
column["auto_increment"] = true
|
|
669
|
+
end
|
|
670
|
+
|
|
595
671
|
column
|
|
596
672
|
end
|
|
597
673
|
else
|
|
@@ -608,17 +684,23 @@ module ActiveRecord
|
|
|
608
684
|
end
|
|
609
685
|
|
|
610
686
|
def connect
|
|
611
|
-
@
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
687
|
+
@raw_connection = self.class.new_client(@connection_parameters)
|
|
688
|
+
rescue ConnectionNotEstablished => ex
|
|
689
|
+
raise ex.set_pool(@pool)
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def reconnect
|
|
693
|
+
if active?
|
|
694
|
+
@raw_connection.rollback rescue nil
|
|
695
|
+
else
|
|
696
|
+
connect
|
|
697
|
+
end
|
|
616
698
|
end
|
|
617
699
|
|
|
618
700
|
def configure_connection
|
|
619
|
-
@
|
|
701
|
+
@raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
|
|
620
702
|
|
|
621
|
-
|
|
703
|
+
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
|
622
704
|
end
|
|
623
705
|
end
|
|
624
706
|
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
|
@@ -42,6 +42,13 @@ module ActiveRecord
|
|
|
42
42
|
cache.clear
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Clear the pool without deallocating; this is only safe when we
|
|
46
|
+
# know the server has independently deallocated all statements
|
|
47
|
+
# (e.g. due to a reconnect, or a DISCARD ALL)
|
|
48
|
+
def reset
|
|
49
|
+
cache.clear
|
|
50
|
+
end
|
|
51
|
+
|
|
45
52
|
def delete(key)
|
|
46
53
|
dealloc cache[key]
|
|
47
54
|
cache.delete(key)
|