activerecord 7.0.8.7 → 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 +1339 -1572
- 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 +17 -9
- data/lib/active_record/associations/collection_proxy.rb +16 -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 -3
- 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 +1 -1
- 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 +55 -9
- 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 +502 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
- 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 +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- 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 +9 -6
- 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 +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
- 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 +131 -5
- 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 +213 -109
- 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 +183 -33
- 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 +10 -5
- 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 +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +351 -62
- 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 +10 -1
- 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 +36 -10
- 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/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/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
@@ -8,7 +8,7 @@ module ActiveRecord
|
|
8
8
|
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
9
9
|
|
10
10
|
def quote_string(s)
|
11
|
-
|
11
|
+
::SQLite3::Database.quote(s)
|
12
12
|
end
|
13
13
|
|
14
14
|
def quote_table_name_for_assignment(table, attr)
|
@@ -89,7 +89,7 @@ module ActiveRecord
|
|
89
89
|
(
|
90
90
|
(?:
|
91
91
|
# "table_name"."column_name" | function(one or no argument)
|
92
|
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")
|
92
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
93
93
|
)
|
94
94
|
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
95
95
|
)
|
@@ -102,8 +102,9 @@ module ActiveRecord
|
|
102
102
|
(
|
103
103
|
(?:
|
104
104
|
# "table_name"."column_name" | function(one or no argument)
|
105
|
-
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")
|
105
|
+
((?:\w+\.|"\w+"\.)?(?:\w+|"\w+") | \w+\((?:|\g<2>)\))
|
106
106
|
)
|
107
|
+
(?:\s+COLLATE\s+(?:\w+|"\w+"))?
|
107
108
|
(?:\s+ASC|\s+DESC)?
|
108
109
|
)
|
109
110
|
(?:\s*,\s*\g<1>)*
|
@@ -3,6 +3,7 @@
|
|
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
|
7
8
|
def change_column(column_name, type, **options)
|
8
9
|
name = column_name.to_s
|
@@ -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
|
|
@@ -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
|
@@ -297,20 +338,42 @@ module ActiveRecord
|
|
297
338
|
rename_column_indexes(table_name, column.name, new_column_name)
|
298
339
|
end
|
299
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
|
+
|
300
354
|
def add_reference(table_name, ref_name, **options) # :nodoc:
|
301
355
|
super(table_name, ref_name, type: :integer, **options)
|
302
356
|
end
|
303
357
|
alias :add_belongs_to :add_reference
|
304
358
|
|
305
359
|
def foreign_keys(table_name)
|
306
|
-
|
307
|
-
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
|
308
365
|
options = {
|
309
|
-
column: row["from"],
|
310
|
-
primary_key: row["to"],
|
311
366
|
on_delete: extract_foreign_key_action(row["on_delete"]),
|
312
367
|
on_update: extract_foreign_key_action(row["on_update"])
|
313
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
|
314
377
|
ForeignKeyDefinition.new(table_name, row["table"], options)
|
315
378
|
end
|
316
379
|
end
|
@@ -367,12 +430,9 @@ module ActiveRecord
|
|
367
430
|
end
|
368
431
|
|
369
432
|
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
433
|
+
EXTENDED_TYPE_MAPS = Concurrent::Map.new
|
370
434
|
|
371
435
|
private
|
372
|
-
def type_map
|
373
|
-
TYPE_MAP
|
374
|
-
end
|
375
|
-
|
376
436
|
# See https://www.sqlite.org/limits.html,
|
377
437
|
# the default value is 999 when not configured.
|
378
438
|
def bind_params_length
|
@@ -380,7 +440,7 @@ module ActiveRecord
|
|
380
440
|
end
|
381
441
|
|
382
442
|
def table_structure(table_name)
|
383
|
-
structure =
|
443
|
+
structure = internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
384
444
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
385
445
|
table_structure_with_collation(table_name, structure)
|
386
446
|
end
|
@@ -399,6 +459,9 @@ module ActiveRecord
|
|
399
459
|
# Numeric types
|
400
460
|
when /\A-?\d+(\.\d*)?\z/
|
401
461
|
$&
|
462
|
+
# Binary columns
|
463
|
+
when /x'(.*)'/
|
464
|
+
[ $1 ].pack("H*")
|
402
465
|
else
|
403
466
|
# Anything else is blank or some function
|
404
467
|
# and we can't know the value of that, so return nil.
|
@@ -480,12 +543,21 @@ module ActiveRecord
|
|
480
543
|
default = -> { column.default_function } if default.nil?
|
481
544
|
end
|
482
545
|
|
483
|
-
|
484
|
-
limit: column.limit,
|
485
|
-
precision: column.precision,
|
486
|
-
|
546
|
+
column_options = {
|
547
|
+
limit: column.limit,
|
548
|
+
precision: column.precision,
|
549
|
+
scale: column.scale,
|
550
|
+
null: column.null,
|
551
|
+
collation: column.collation,
|
487
552
|
primary_key: column_name == from_primary_key
|
488
|
-
|
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)
|
489
561
|
end
|
490
562
|
|
491
563
|
yield @definition if block_given?
|
@@ -533,7 +605,7 @@ module ActiveRecord
|
|
533
605
|
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
534
606
|
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
535
607
|
|
536
|
-
|
608
|
+
internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
537
609
|
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
538
610
|
end
|
539
611
|
|
@@ -543,22 +615,24 @@ module ActiveRecord
|
|
543
615
|
# Older versions of SQLite return:
|
544
616
|
# column *column_name* is not unique
|
545
617
|
if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
|
546
|
-
RecordNotUnique.new(message, sql: sql, binds: binds)
|
618
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
547
619
|
elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
|
548
|
-
NotNullViolation.new(message, sql: sql, binds: binds)
|
620
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
549
621
|
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
550
|
-
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
622
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
551
623
|
elsif exception.message.match?(/called on a closed database/i)
|
552
|
-
ConnectionNotEstablished.new(exception)
|
624
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
553
625
|
else
|
554
626
|
super
|
555
627
|
end
|
556
628
|
end
|
557
629
|
|
558
|
-
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
|
559
632
|
|
560
633
|
def table_structure_with_collation(table_name, basic_structure)
|
561
634
|
collation_hash = {}
|
635
|
+
auto_increments = {}
|
562
636
|
sql = <<~SQL
|
563
637
|
SELECT sql FROM
|
564
638
|
(SELECT * FROM sqlite_master UNION ALL
|
@@ -580,6 +654,7 @@ module ActiveRecord
|
|
580
654
|
# This regex will match the column name and collation type and will save
|
581
655
|
# the value in $1 and $2 respectively.
|
582
656
|
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
657
|
+
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
583
658
|
end
|
584
659
|
|
585
660
|
basic_structure.map do |column|
|
@@ -589,6 +664,10 @@ module ActiveRecord
|
|
589
664
|
column["collation"] = collation_hash[column_name]
|
590
665
|
end
|
591
666
|
|
667
|
+
if auto_increments.has_key?(column_name)
|
668
|
+
column["auto_increment"] = true
|
669
|
+
end
|
670
|
+
|
592
671
|
column
|
593
672
|
end
|
594
673
|
else
|
@@ -605,17 +684,23 @@ module ActiveRecord
|
|
605
684
|
end
|
606
685
|
|
607
686
|
def connect
|
608
|
-
@
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
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
|
613
698
|
end
|
614
699
|
|
615
700
|
def configure_connection
|
616
|
-
@
|
701
|
+
@raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
|
617
702
|
|
618
|
-
|
703
|
+
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
619
704
|
end
|
620
705
|
end
|
621
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)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module Trilogy
|
6
|
+
module DatabaseStatements
|
7
|
+
def select_all(*, **) # :nodoc:
|
8
|
+
result = super
|
9
|
+
with_raw_connection do |conn|
|
10
|
+
conn.next_result while conn.more_results_exist?
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
|
15
|
+
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
|
16
|
+
sql = transform_query(sql)
|
17
|
+
check_if_write_query(sql)
|
18
|
+
mark_transaction_written_if_write(sql)
|
19
|
+
|
20
|
+
result = raw_execute(sql, name, async: async)
|
21
|
+
ActiveRecord::Result.new(result.fields, result.to_a)
|
22
|
+
end
|
23
|
+
|
24
|
+
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
25
|
+
sql = transform_query(sql)
|
26
|
+
check_if_write_query(sql)
|
27
|
+
mark_transaction_written_if_write(sql)
|
28
|
+
|
29
|
+
raw_execute(to_sql(sql, binds), name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
33
|
+
sql = transform_query(sql)
|
34
|
+
check_if_write_query(sql)
|
35
|
+
mark_transaction_written_if_write(sql)
|
36
|
+
|
37
|
+
result = raw_execute(to_sql(sql, binds), name)
|
38
|
+
result.affected_rows
|
39
|
+
end
|
40
|
+
|
41
|
+
alias :exec_update :exec_delete # :nodoc:
|
42
|
+
|
43
|
+
private
|
44
|
+
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
45
|
+
log(sql, name, async: async) do
|
46
|
+
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
47
|
+
sync_timezone_changes(conn)
|
48
|
+
result = conn.query(sql)
|
49
|
+
handle_warnings(sql)
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def last_inserted_id(result)
|
56
|
+
result.last_insert_id
|
57
|
+
end
|
58
|
+
|
59
|
+
def sync_timezone_changes(conn)
|
60
|
+
# Sync any changes since connection last established.
|
61
|
+
if default_timezone == :local
|
62
|
+
conn.query_flags |= ::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
63
|
+
else
|
64
|
+
conn.query_flags &= ~::Trilogy::QUERY_FLAGS_LOCAL_TIMEZONE
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def execute_batch(statements, name = nil)
|
69
|
+
statements = statements.map { |sql| transform_query(sql) }
|
70
|
+
combine_multi_statements(statements).each do |statement|
|
71
|
+
with_raw_connection do |conn|
|
72
|
+
raw_execute(statement, name)
|
73
|
+
conn.next_result while conn.more_results_exist?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def multi_statements_enabled?
|
79
|
+
!!@config[:multi_statement]
|
80
|
+
end
|
81
|
+
|
82
|
+
def with_multi_statements
|
83
|
+
if multi_statements_enabled?
|
84
|
+
return yield
|
85
|
+
end
|
86
|
+
|
87
|
+
with_raw_connection do |conn|
|
88
|
+
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_ON)
|
89
|
+
|
90
|
+
yield
|
91
|
+
ensure
|
92
|
+
conn.set_server_option(::Trilogy::SET_SERVER_MULTI_STATEMENTS_OFF)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|