activerecord 7.0.8.7 → 7.2.2.1
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 +631 -1944
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- 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 +123 -131
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- 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 +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- 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 +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- 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 +25 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- 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 +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +167 -97
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- data/lib/active_record/migration/default_strategy.rb +22 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- 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 +267 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +3 -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 +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- 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 +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +106 -24
- data/lib/active_record/translation.rb +0 -2
- 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 +1 -3
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +9 -3
- 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 +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/sqlite.rb +25 -0
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +54 -12
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -33,7 +33,7 @@ module ActiveRecord
|
|
33
33
|
when ::Numeric
|
34
34
|
# Sometimes operations on Times returns just float number of seconds so we need to handle that.
|
35
35
|
# Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
|
36
|
-
value.
|
36
|
+
ActiveSupport::Duration.build(value).iso8601(precision: self.precision)
|
37
37
|
else
|
38
38
|
super
|
39
39
|
end
|
@@ -27,9 +27,10 @@ module ActiveRecord
|
|
27
27
|
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
|
28
28
|
case value
|
29
29
|
when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
|
30
|
-
value.
|
30
|
+
value.delete!("^-0-9.")
|
31
31
|
when /^-?\D*+[\d.]+,\d{2}$/ # (2)
|
32
|
-
value.
|
32
|
+
value.delete!("^-0-9,")
|
33
|
+
value.tr!(",", ".")
|
33
34
|
end
|
34
35
|
|
35
36
|
super(value)
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def cast_value(value)
|
21
|
-
return if
|
21
|
+
return if ["empty", ""].include? value
|
22
22
|
return value unless value.is_a?(::String)
|
23
23
|
|
24
24
|
extracted = extract_bounds(value)
|
@@ -28,7 +28,7 @@ module ActiveRecord
|
|
28
28
|
if !infinity?(from) && extracted[:exclude_start]
|
29
29
|
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
|
30
30
|
end
|
31
|
-
::Range.new(from, to, extracted[:exclude_end])
|
31
|
+
::Range.new(*sanitize_bounds(from, to), extracted[:exclude_end])
|
32
32
|
end
|
33
33
|
|
34
34
|
def serialize(value)
|
@@ -76,6 +76,15 @@ module ActiveRecord
|
|
76
76
|
}
|
77
77
|
end
|
78
78
|
|
79
|
+
INFINITE_FLOAT_RANGE = (-::Float::INFINITY)..(::Float::INFINITY) # :nodoc:
|
80
|
+
|
81
|
+
def sanitize_bounds(from, to)
|
82
|
+
[
|
83
|
+
(from == -::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(to)) ? nil : from,
|
84
|
+
(to == ::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(from)) ? nil : to
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
79
88
|
# When formatting the bound values of range types, PostgreSQL quotes
|
80
89
|
# the bound value using double-quotes in certain conditions. Within
|
81
90
|
# a double-quoted string, literal " and \ characters are themselves
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
time = super
|
16
16
|
return time if time.is_a?(ActiveSupport::TimeWithZone) || !time.acts_like?(:time)
|
17
17
|
|
18
|
-
# While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to
|
18
|
+
# While in UTC mode, the PG gem may not return times back in "UTC" even if they were provided to PostgreSQL in UTC.
|
19
19
|
# We prefer times always in UTC, so here we convert back.
|
20
20
|
if is_utc?
|
21
21
|
time.getutc
|
@@ -6,6 +6,7 @@ module ActiveRecord
|
|
6
6
|
module OID # :nodoc:
|
7
7
|
class Uuid < Type::Value # :nodoc:
|
8
8
|
ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z}
|
9
|
+
CANONICAL_UUID = %r{\A[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}\z}
|
9
10
|
|
10
11
|
alias :serialize :deserialize
|
11
12
|
|
@@ -15,18 +16,27 @@ module ActiveRecord
|
|
15
16
|
|
16
17
|
def changed?(old_value, new_value, _new_value_before_type_cast)
|
17
18
|
old_value.class != new_value.class ||
|
18
|
-
new_value
|
19
|
+
new_value != old_value
|
19
20
|
end
|
20
21
|
|
21
22
|
def changed_in_place?(raw_old_value, new_value)
|
22
23
|
raw_old_value.class != new_value.class ||
|
23
|
-
new_value
|
24
|
+
new_value != raw_old_value
|
24
25
|
end
|
25
26
|
|
26
27
|
private
|
27
28
|
def cast_value(value)
|
28
|
-
|
29
|
-
|
29
|
+
value = value.to_s
|
30
|
+
format_uuid(value) if value.match?(ACCEPTABLE_UUID)
|
31
|
+
end
|
32
|
+
|
33
|
+
def format_uuid(uuid)
|
34
|
+
if uuid.match?(CANONICAL_UUID)
|
35
|
+
uuid
|
36
|
+
else
|
37
|
+
uuid = uuid.delete("{}-").downcase
|
38
|
+
"#{uuid[..7]}-#{uuid[8..11]}-#{uuid[12..15]}-#{uuid[16..19]}-#{uuid[20..]}"
|
39
|
+
end
|
30
40
|
end
|
31
41
|
end
|
32
42
|
end
|
@@ -4,9 +4,62 @@ module ActiveRecord
|
|
4
4
|
module ConnectionAdapters
|
5
5
|
module PostgreSQL
|
6
6
|
module Quoting
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
7
9
|
QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
|
8
10
|
QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
|
9
11
|
|
12
|
+
module ClassMethods # :nodoc:
|
13
|
+
def column_name_matcher
|
14
|
+
/
|
15
|
+
\A
|
16
|
+
(
|
17
|
+
(?:
|
18
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
19
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
20
|
+
)
|
21
|
+
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
22
|
+
)
|
23
|
+
(?:\s*,\s*\g<1>)*
|
24
|
+
\z
|
25
|
+
/ix
|
26
|
+
end
|
27
|
+
|
28
|
+
def column_name_with_order_matcher
|
29
|
+
/
|
30
|
+
\A
|
31
|
+
(
|
32
|
+
(?:
|
33
|
+
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
34
|
+
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
|
35
|
+
)
|
36
|
+
(?:\s+COLLATE\s+"\w+")?
|
37
|
+
(?:\s+ASC|\s+DESC)?
|
38
|
+
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
39
|
+
)
|
40
|
+
(?:\s*,\s*\g<1>)*
|
41
|
+
\z
|
42
|
+
/ix
|
43
|
+
end
|
44
|
+
|
45
|
+
# Quotes column names for use in SQL queries.
|
46
|
+
def quote_column_name(name) # :nodoc:
|
47
|
+
QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
# Checks the following cases:
|
51
|
+
#
|
52
|
+
# - table_name
|
53
|
+
# - "table.name"
|
54
|
+
# - schema_name.table_name
|
55
|
+
# - schema_name."table.name"
|
56
|
+
# - "schema.name".table_name
|
57
|
+
# - "schema.name"."table.name"
|
58
|
+
def quote_table_name(name) # :nodoc:
|
59
|
+
QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
10
63
|
class IntegerOutOf64BitRange < StandardError
|
11
64
|
def initialize(msg)
|
12
65
|
super(msg)
|
@@ -15,14 +68,14 @@ module ActiveRecord
|
|
15
68
|
|
16
69
|
# Escapes binary strings for bytea input to the database.
|
17
70
|
def escape_bytea(value)
|
18
|
-
|
71
|
+
valid_raw_connection.escape_bytea(value) if value
|
19
72
|
end
|
20
73
|
|
21
74
|
# Unescapes bytea output from a database to the binary string it represents.
|
22
75
|
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
23
76
|
# on escaped binary output from database drive.
|
24
77
|
def unescape_bytea(value)
|
25
|
-
|
78
|
+
valid_raw_connection.unescape_bytea(value) if value
|
26
79
|
end
|
27
80
|
|
28
81
|
def check_int_in_range(value)
|
@@ -72,33 +125,18 @@ module ActiveRecord
|
|
72
125
|
|
73
126
|
# Quotes strings for use in SQL input.
|
74
127
|
def quote_string(s) # :nodoc:
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
# Checks the following cases:
|
79
|
-
#
|
80
|
-
# - table_name
|
81
|
-
# - "table.name"
|
82
|
-
# - schema_name.table_name
|
83
|
-
# - schema_name."table.name"
|
84
|
-
# - "schema.name".table_name
|
85
|
-
# - "schema.name"."table.name"
|
86
|
-
def quote_table_name(name) # :nodoc:
|
87
|
-
QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
|
88
|
-
end
|
89
|
-
|
90
|
-
# Quotes schema names for use in SQL queries.
|
91
|
-
def quote_schema_name(name)
|
92
|
-
PG::Connection.quote_ident(name)
|
128
|
+
with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
|
129
|
+
connection.escape(s)
|
130
|
+
end
|
93
131
|
end
|
94
132
|
|
95
133
|
def quote_table_name_for_assignment(table, attr)
|
96
134
|
quote_column_name(attr)
|
97
135
|
end
|
98
136
|
|
99
|
-
# Quotes
|
100
|
-
def
|
101
|
-
|
137
|
+
# Quotes schema names for use in SQL queries.
|
138
|
+
def quote_schema_name(schema_name)
|
139
|
+
quote_column_name(schema_name)
|
102
140
|
end
|
103
141
|
|
104
142
|
# Quote date/time values for use in SQL input.
|
@@ -118,7 +156,7 @@ module ActiveRecord
|
|
118
156
|
def quote_default_expression(value, column) # :nodoc:
|
119
157
|
if value.is_a?(Proc)
|
120
158
|
value.call
|
121
|
-
elsif column.type == :uuid && value.is_a?(String) &&
|
159
|
+
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
|
122
160
|
value # Does not quote function default values for UUID columns
|
123
161
|
elsif column.respond_to?(:array?)
|
124
162
|
type = lookup_cast_type_from_column(column)
|
@@ -141,52 +179,18 @@ module ActiveRecord
|
|
141
179
|
encode_array(value)
|
142
180
|
when Range
|
143
181
|
encode_range(value)
|
182
|
+
when Rational
|
183
|
+
value.to_f
|
144
184
|
else
|
145
185
|
super
|
146
186
|
end
|
147
187
|
end
|
148
188
|
|
149
189
|
def lookup_cast_type_from_column(column) # :nodoc:
|
190
|
+
verify! if type_map.nil?
|
150
191
|
type_map.lookup(column.oid, column.fmod, column.sql_type)
|
151
192
|
end
|
152
193
|
|
153
|
-
def column_name_matcher
|
154
|
-
COLUMN_NAME
|
155
|
-
end
|
156
|
-
|
157
|
-
def column_name_with_order_matcher
|
158
|
-
COLUMN_NAME_WITH_ORDER
|
159
|
-
end
|
160
|
-
|
161
|
-
COLUMN_NAME = /
|
162
|
-
\A
|
163
|
-
(
|
164
|
-
(?:
|
165
|
-
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
166
|
-
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
|
167
|
-
)
|
168
|
-
(?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
|
169
|
-
)
|
170
|
-
(?:\s*,\s*\g<1>)*
|
171
|
-
\z
|
172
|
-
/ix
|
173
|
-
|
174
|
-
COLUMN_NAME_WITH_ORDER = /
|
175
|
-
\A
|
176
|
-
(
|
177
|
-
(?:
|
178
|
-
# "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
|
179
|
-
((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
|
180
|
-
)
|
181
|
-
(?:\s+ASC|\s+DESC)?
|
182
|
-
(?:\s+NULLS\s+(?:FIRST|LAST))?
|
183
|
-
)
|
184
|
-
(?:\s*,\s*\g<1>)*
|
185
|
-
\z
|
186
|
-
/ix
|
187
|
-
|
188
|
-
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
189
|
-
|
190
194
|
private
|
191
195
|
def lookup_cast_type(sql_type)
|
192
196
|
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
@@ -38,7 +38,7 @@ Rails needs superuser privileges to disable referential integrity.
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
41
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
42
42
|
sql = <<~SQL
|
43
43
|
do $$
|
44
44
|
declare r record;
|
@@ -61,14 +61,8 @@ Rails needs superuser privileges to disable referential integrity.
|
|
61
61
|
$$;
|
62
62
|
SQL
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
execute(sql)
|
67
|
-
end
|
68
|
-
|
69
|
-
true
|
70
|
-
rescue ActiveRecord::StatementInvalid
|
71
|
-
false
|
64
|
+
transaction(requires_new: true) do
|
65
|
+
execute(sql)
|
72
66
|
end
|
73
67
|
end
|
74
68
|
end
|
@@ -5,21 +5,30 @@ module ActiveRecord
|
|
5
5
|
module PostgreSQL
|
6
6
|
class SchemaCreation < SchemaCreation # :nodoc:
|
7
7
|
private
|
8
|
+
delegate :quoted_include_columns_for_index, to: :@conn
|
9
|
+
|
8
10
|
def visit_AlterTable(o)
|
9
|
-
|
11
|
+
sql = super
|
12
|
+
sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
13
|
+
sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
|
14
|
+
sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
|
15
|
+
sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
|
16
|
+
sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
|
10
17
|
end
|
11
18
|
|
12
19
|
def visit_AddForeignKey(o)
|
13
20
|
super.dup.tap do |sql|
|
14
|
-
if o.deferrable
|
15
|
-
sql << " DEFERRABLE"
|
16
|
-
sql << " INITIALLY #{o.deferrable.to_s.upcase}" unless o.deferrable == true
|
17
|
-
end
|
18
|
-
|
21
|
+
sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
|
19
22
|
sql << " NOT VALID" unless o.validate?
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
26
|
+
def visit_ForeignKeyDefinition(o)
|
27
|
+
super.dup.tap do |sql|
|
28
|
+
sql << " DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
23
32
|
def visit_CheckConstraintDefinition(o)
|
24
33
|
super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? }
|
25
34
|
end
|
@@ -28,6 +37,54 @@ module ActiveRecord
|
|
28
37
|
"VALIDATE CONSTRAINT #{quote_column_name(name)}"
|
29
38
|
end
|
30
39
|
|
40
|
+
def visit_ExclusionConstraintDefinition(o)
|
41
|
+
sql = ["CONSTRAINT"]
|
42
|
+
sql << quote_column_name(o.name)
|
43
|
+
sql << "EXCLUDE"
|
44
|
+
sql << "USING #{o.using}" if o.using
|
45
|
+
sql << "(#{o.expression})"
|
46
|
+
sql << "WHERE (#{o.where})" if o.where
|
47
|
+
sql << "DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}" if o.deferrable
|
48
|
+
|
49
|
+
sql.join(" ")
|
50
|
+
end
|
51
|
+
|
52
|
+
def visit_UniqueConstraintDefinition(o)
|
53
|
+
column_name = Array(o.column).map { |column| quote_column_name(column) }.join(", ")
|
54
|
+
|
55
|
+
sql = ["CONSTRAINT"]
|
56
|
+
sql << quote_column_name(o.name)
|
57
|
+
sql << "UNIQUE"
|
58
|
+
|
59
|
+
if o.using_index
|
60
|
+
sql << "USING INDEX #{quote_column_name(o.using_index)}"
|
61
|
+
else
|
62
|
+
sql << "(#{column_name})"
|
63
|
+
end
|
64
|
+
|
65
|
+
if o.deferrable
|
66
|
+
sql << "DEFERRABLE INITIALLY #{o.deferrable.to_s.upcase}"
|
67
|
+
end
|
68
|
+
|
69
|
+
sql.join(" ")
|
70
|
+
end
|
71
|
+
|
72
|
+
def visit_AddExclusionConstraint(o)
|
73
|
+
"ADD #{accept(o)}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def visit_DropExclusionConstraint(name)
|
77
|
+
"DROP CONSTRAINT #{quote_column_name(name)}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_AddUniqueConstraint(o)
|
81
|
+
"ADD #{accept(o)}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def visit_DropUniqueConstraint(name)
|
85
|
+
"DROP CONSTRAINT #{quote_column_name(name)}"
|
86
|
+
end
|
87
|
+
|
31
88
|
def visit_ChangeColumnDefinition(o)
|
32
89
|
column = o.column
|
33
90
|
column.sql_type = type_to_sql(column.type, **column.options)
|
@@ -64,6 +121,15 @@ module ActiveRecord
|
|
64
121
|
change_column_sql
|
65
122
|
end
|
66
123
|
|
124
|
+
def visit_ChangeColumnDefaultDefinition(o)
|
125
|
+
sql = +"ALTER COLUMN #{quote_column_name(o.column.name)} "
|
126
|
+
if o.default.nil?
|
127
|
+
sql << "DROP DEFAULT"
|
128
|
+
else
|
129
|
+
sql << "SET DEFAULT #{quote_default_expression(o.default, o.column)}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
67
133
|
def add_column_options!(sql, options)
|
68
134
|
if options[:collation]
|
69
135
|
sql << " COLLATE \"#{options[:collation]}\""
|
@@ -84,6 +150,10 @@ module ActiveRecord
|
|
84
150
|
super
|
85
151
|
end
|
86
152
|
|
153
|
+
def quoted_include_columns(o)
|
154
|
+
String === o ? o : quoted_include_columns_for_index(o)
|
155
|
+
end
|
156
|
+
|
87
157
|
# Returns any SQL string to go between CREATE and TABLE. May be nil.
|
88
158
|
def table_modifier_in_create(o)
|
89
159
|
# A table cannot be both TEMPORARY and UNLOGGED, since all TEMPORARY
|
@@ -189,16 +189,83 @@ module ActiveRecord
|
|
189
189
|
end
|
190
190
|
end
|
191
191
|
|
192
|
+
ExclusionConstraintDefinition = Struct.new(:table_name, :expression, :options) do
|
193
|
+
def name
|
194
|
+
options[:name]
|
195
|
+
end
|
196
|
+
|
197
|
+
def using
|
198
|
+
options[:using]
|
199
|
+
end
|
200
|
+
|
201
|
+
def where
|
202
|
+
options[:where]
|
203
|
+
end
|
204
|
+
|
205
|
+
def deferrable
|
206
|
+
options[:deferrable]
|
207
|
+
end
|
208
|
+
|
209
|
+
def export_name_on_schema_dump?
|
210
|
+
!ActiveRecord::SchemaDumper.excl_ignore_pattern.match?(name) if name
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
UniqueConstraintDefinition = Struct.new(:table_name, :column, :options) do
|
215
|
+
def name
|
216
|
+
options[:name]
|
217
|
+
end
|
218
|
+
|
219
|
+
def deferrable
|
220
|
+
options[:deferrable]
|
221
|
+
end
|
222
|
+
|
223
|
+
def using_index
|
224
|
+
options[:using_index]
|
225
|
+
end
|
226
|
+
|
227
|
+
def export_name_on_schema_dump?
|
228
|
+
!ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name
|
229
|
+
end
|
230
|
+
|
231
|
+
def defined_for?(name: nil, column: nil, **options)
|
232
|
+
(name.nil? || self.name == name.to_s) &&
|
233
|
+
(column.nil? || Array(self.column) == Array(column).map(&:to_s)) &&
|
234
|
+
options.all? { |k, v| self.options[k].to_s == v.to_s }
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# = Active Record PostgreSQL Adapter \Table Definition
|
192
239
|
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
193
240
|
include ColumnMethods
|
194
241
|
|
195
|
-
attr_reader :unlogged
|
242
|
+
attr_reader :exclusion_constraints, :unique_constraints, :unlogged
|
196
243
|
|
197
244
|
def initialize(*, **)
|
198
245
|
super
|
246
|
+
@exclusion_constraints = []
|
247
|
+
@unique_constraints = []
|
199
248
|
@unlogged = ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables
|
200
249
|
end
|
201
250
|
|
251
|
+
def exclusion_constraint(expression, **options)
|
252
|
+
exclusion_constraints << new_exclusion_constraint_definition(expression, options)
|
253
|
+
end
|
254
|
+
|
255
|
+
def unique_constraint(column_name, **options)
|
256
|
+
unique_constraints << new_unique_constraint_definition(column_name, options)
|
257
|
+
end
|
258
|
+
|
259
|
+
def new_exclusion_constraint_definition(expression, options) # :nodoc:
|
260
|
+
options = @conn.exclusion_constraint_options(name, expression, options)
|
261
|
+
ExclusionConstraintDefinition.new(name, expression, options)
|
262
|
+
end
|
263
|
+
|
264
|
+
def new_unique_constraint_definition(column_name, options) # :nodoc:
|
265
|
+
options = @conn.unique_constraint_options(name, column_name, options)
|
266
|
+
UniqueConstraintDefinition.new(name, column_name, options)
|
267
|
+
end
|
268
|
+
|
202
269
|
def new_column_definition(name, type, **options) # :nodoc:
|
203
270
|
case type
|
204
271
|
when :virtual
|
@@ -209,6 +276,10 @@ module ActiveRecord
|
|
209
276
|
end
|
210
277
|
|
211
278
|
private
|
279
|
+
def valid_column_definition_options
|
280
|
+
super + [:array, :using, :cast_as, :as, :type, :enum_type, :stored]
|
281
|
+
end
|
282
|
+
|
212
283
|
def aliased_types(name, fallback)
|
213
284
|
fallback
|
214
285
|
end
|
@@ -222,21 +293,99 @@ module ActiveRecord
|
|
222
293
|
end
|
223
294
|
end
|
224
295
|
|
296
|
+
# = Active Record PostgreSQL Adapter \Table
|
225
297
|
class Table < ActiveRecord::ConnectionAdapters::Table
|
226
298
|
include ColumnMethods
|
299
|
+
|
300
|
+
# Adds an exclusion constraint.
|
301
|
+
#
|
302
|
+
# t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check")
|
303
|
+
#
|
304
|
+
# See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint]
|
305
|
+
def exclusion_constraint(*args)
|
306
|
+
@base.add_exclusion_constraint(name, *args)
|
307
|
+
end
|
308
|
+
|
309
|
+
# Removes the given exclusion constraint from the table.
|
310
|
+
#
|
311
|
+
# t.remove_exclusion_constraint(name: "price_check")
|
312
|
+
#
|
313
|
+
# See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint]
|
314
|
+
def remove_exclusion_constraint(*args)
|
315
|
+
@base.remove_exclusion_constraint(name, *args)
|
316
|
+
end
|
317
|
+
|
318
|
+
# Adds a unique constraint.
|
319
|
+
#
|
320
|
+
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
|
321
|
+
#
|
322
|
+
# See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
|
323
|
+
def unique_constraint(*args)
|
324
|
+
@base.add_unique_constraint(name, *args)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Removes the given unique constraint from the table.
|
328
|
+
#
|
329
|
+
# t.remove_unique_constraint(name: "unique_position")
|
330
|
+
#
|
331
|
+
# See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
|
332
|
+
def remove_unique_constraint(*args)
|
333
|
+
@base.remove_unique_constraint(name, *args)
|
334
|
+
end
|
335
|
+
|
336
|
+
# Validates the given constraint on the table.
|
337
|
+
#
|
338
|
+
# t.check_constraint("price > 0", name: "price_check", validate: false)
|
339
|
+
# t.validate_constraint "price_check"
|
340
|
+
#
|
341
|
+
# See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint]
|
342
|
+
def validate_constraint(*args)
|
343
|
+
@base.validate_constraint(name, *args)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Validates the given check constraint on the table
|
347
|
+
#
|
348
|
+
# t.check_constraint("price > 0", name: "price_check", validate: false)
|
349
|
+
# t.validate_check_constraint name: "price_check"
|
350
|
+
#
|
351
|
+
# See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint]
|
352
|
+
def validate_check_constraint(*args)
|
353
|
+
@base.validate_check_constraint(name, *args)
|
354
|
+
end
|
227
355
|
end
|
228
356
|
|
357
|
+
# = Active Record PostgreSQL Adapter Alter \Table
|
229
358
|
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
230
|
-
attr_reader :constraint_validations
|
359
|
+
attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_constraint_adds, :unique_constraint_drops
|
231
360
|
|
232
361
|
def initialize(td)
|
233
362
|
super
|
234
363
|
@constraint_validations = []
|
364
|
+
@exclusion_constraint_adds = []
|
365
|
+
@exclusion_constraint_drops = []
|
366
|
+
@unique_constraint_adds = []
|
367
|
+
@unique_constraint_drops = []
|
235
368
|
end
|
236
369
|
|
237
370
|
def validate_constraint(name)
|
238
371
|
@constraint_validations << name
|
239
372
|
end
|
373
|
+
|
374
|
+
def add_exclusion_constraint(expression, options)
|
375
|
+
@exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options)
|
376
|
+
end
|
377
|
+
|
378
|
+
def drop_exclusion_constraint(constraint_name)
|
379
|
+
@exclusion_constraint_drops << constraint_name
|
380
|
+
end
|
381
|
+
|
382
|
+
def add_unique_constraint(column_name, options)
|
383
|
+
@unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
|
384
|
+
end
|
385
|
+
|
386
|
+
def drop_unique_constraint(unique_constraint_name)
|
387
|
+
@unique_constraint_drops << unique_constraint_name
|
388
|
+
end
|
240
389
|
end
|
241
390
|
end
|
242
391
|
end
|