omg-activerecord 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +355 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +219 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record/aggregations.rb +287 -0
- data/lib/active_record/association_relation.rb +50 -0
- data/lib/active_record/associations/alias_tracker.rb +90 -0
- data/lib/active_record/associations/association.rb +417 -0
- data/lib/active_record/associations/association_scope.rb +175 -0
- data/lib/active_record/associations/belongs_to_association.rb +163 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/builder/association.rb +170 -0
- data/lib/active_record/associations/builder/belongs_to.rb +160 -0
- data/lib/active_record/associations/builder/collection_association.rb +80 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -0
- data/lib/active_record/associations/builder/has_many.rb +23 -0
- data/lib/active_record/associations/builder/has_one.rb +61 -0
- data/lib/active_record/associations/builder/singular_association.rb +48 -0
- data/lib/active_record/associations/collection_association.rb +535 -0
- data/lib/active_record/associations/collection_proxy.rb +1163 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +40 -0
- data/lib/active_record/associations/has_many_association.rb +167 -0
- data/lib/active_record/associations/has_many_through_association.rb +232 -0
- data/lib/active_record/associations/has_one_association.rb +142 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +106 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/join_dependency.rb +301 -0
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +316 -0
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +153 -0
- data/lib/active_record/associations/preloader/through_association.rb +150 -0
- data/lib/active_record/associations/preloader.rb +135 -0
- data/lib/active_record/associations/singular_association.rb +76 -0
- data/lib/active_record/associations/through_association.rb +132 -0
- data/lib/active_record/associations.rb +1897 -0
- data/lib/active_record/asynchronous_queries_tracker.rb +64 -0
- data/lib/active_record/attribute_assignment.rb +82 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +106 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +262 -0
- data/lib/active_record/attribute_methods/primary_key.rb +158 -0
- data/lib/active_record/attribute_methods/query.rb +50 -0
- data/lib/active_record/attribute_methods/read.rb +46 -0
- data/lib/active_record/attribute_methods/serialization.rb +232 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +94 -0
- data/lib/active_record/attribute_methods/write.rb +49 -0
- data/lib/active_record/attribute_methods.rb +542 -0
- data/lib/active_record/attributes.rb +307 -0
- data/lib/active_record/autosave_association.rb +586 -0
- data/lib/active_record/base.rb +338 -0
- data/lib/active_record/callbacks.rb +452 -0
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +290 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +210 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +923 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +31 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +747 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +319 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +239 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +24 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +190 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +961 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +106 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1883 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +676 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +1218 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +1016 -0
- data/lib/active_record/connection_adapters/column.rb +122 -0
- data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +28 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +71 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +114 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +106 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +97 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +300 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +40 -0
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +196 -0
- data/lib/active_record/connection_adapters/pool_config.rb +83 -0
- data/lib/active_record/connection_adapters/pool_manager.rb +57 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +82 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +231 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +91 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +54 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +74 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +124 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +125 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +238 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +169 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +392 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +127 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1162 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1182 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +478 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +45 -0
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +145 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +39 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +221 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +843 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +67 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +69 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +212 -0
- data/lib/active_record/connection_adapters.rb +176 -0
- data/lib/active_record/connection_handling.rb +413 -0
- data/lib/active_record/core.rb +836 -0
- data/lib/active_record/counter_cache.rb +230 -0
- data/lib/active_record/database_configurations/connection_url_resolver.rb +105 -0
- data/lib/active_record/database_configurations/database_config.rb +104 -0
- data/lib/active_record/database_configurations/hash_config.rb +172 -0
- data/lib/active_record/database_configurations/url_config.rb +78 -0
- data/lib/active_record/database_configurations.rb +309 -0
- data/lib/active_record/delegated_type.rb +289 -0
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +38 -0
- data/lib/active_record/disable_joins_association_relation.rb +39 -0
- data/lib/active_record/dynamic_matchers.rb +121 -0
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +70 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +230 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +184 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +177 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +159 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +96 -0
- data/lib/active_record/encryption/null_encryptor.rb +25 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
- data/lib/active_record/encryption/scheme.rb +107 -0
- data/lib/active_record/encryption.rb +58 -0
- data/lib/active_record/enum.rb +424 -0
- data/lib/active_record/errors.rb +614 -0
- data/lib/active_record/explain.rb +63 -0
- data/lib/active_record/explain_registry.rb +37 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +89 -0
- data/lib/active_record/fixture_set/model_metadata.rb +42 -0
- data/lib/active_record/fixture_set/render_context.rb +19 -0
- data/lib/active_record/fixture_set/table_row.rb +208 -0
- data/lib/active_record/fixture_set/table_rows.rb +46 -0
- data/lib/active_record/fixtures.rb +850 -0
- data/lib/active_record/future_result.rb +182 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +366 -0
- data/lib/active_record/insert_all.rb +328 -0
- data/lib/active_record/integration.rb +209 -0
- data/lib/active_record/internal_metadata.rb +164 -0
- data/lib/active_record/legacy_yaml_adapter.rb +15 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +228 -0
- data/lib/active_record/locking/pessimistic.rb +102 -0
- data/lib/active_record/log_subscriber.rb +149 -0
- 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/session.rb +48 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
- data/lib/active_record/middleware/database_selector.rb +87 -0
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +406 -0
- data/lib/active_record/migration/compatibility.rb +490 -0
- 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/join_table.rb +16 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +1626 -0
- data/lib/active_record/model_schema.rb +635 -0
- data/lib/active_record/nested_attributes.rb +633 -0
- data/lib/active_record/no_touching.rb +65 -0
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +968 -0
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/query_logs.rb +247 -0
- data/lib/active_record/query_logs_formatter.rb +30 -0
- data/lib/active_record/querying.rb +122 -0
- data/lib/active_record/railtie.rb +440 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +65 -0
- data/lib/active_record/railties/databases.rake +641 -0
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +66 -0
- data/lib/active_record/reflection.rb +1287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +115 -0
- data/lib/active_record/relation/batches.rb +491 -0
- data/lib/active_record/relation/calculations.rb +679 -0
- data/lib/active_record/relation/delegation.rb +154 -0
- data/lib/active_record/relation/finder_methods.rb +661 -0
- data/lib/active_record/relation/from_clause.rb +30 -0
- data/lib/active_record/relation/merger.rb +192 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +76 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +60 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +24 -0
- data/lib/active_record/relation/predicate_builder.rb +181 -0
- data/lib/active_record/relation/query_attribute.rb +68 -0
- data/lib/active_record/relation/query_methods.rb +2235 -0
- data/lib/active_record/relation/record_fetch_warning.rb +52 -0
- data/lib/active_record/relation/spawn_methods.rb +78 -0
- data/lib/active_record/relation/where_clause.rb +218 -0
- data/lib/active_record/relation.rb +1495 -0
- data/lib/active_record/result.rb +249 -0
- data/lib/active_record/runtime_registry.rb +82 -0
- data/lib/active_record/sanitization.rb +254 -0
- data/lib/active_record/schema.rb +77 -0
- data/lib/active_record/schema_dumper.rb +364 -0
- data/lib/active_record/schema_migration.rb +106 -0
- data/lib/active_record/scoping/default.rb +205 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/scoping.rb +136 -0
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +66 -0
- data/lib/active_record/serialization.rb +29 -0
- data/lib/active_record/signed_id.rb +137 -0
- data/lib/active_record/statement_cache.rb +164 -0
- data/lib/active_record/store.rb +299 -0
- data/lib/active_record/suppressor.rb +59 -0
- data/lib/active_record/table_metadata.rb +85 -0
- data/lib/active_record/tasks/database_tasks.rb +681 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +120 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +147 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +89 -0
- data/lib/active_record/test_databases.rb +24 -0
- data/lib/active_record/test_fixtures.rb +321 -0
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +177 -0
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +70 -0
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +523 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type/adapter_specific_registry.rb +144 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +57 -0
- data/lib/active_record/type/internal/timezone.rb +22 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +76 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +35 -0
- data/lib/active_record/type/type_map.rb +58 -0
- data/lib/active_record/type/unsigned_integer.rb +16 -0
- data/lib/active_record/type.rb +83 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +65 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/numericality.rb +36 -0
- data/lib/active_record/validations/presence.rb +45 -0
- data/lib/active_record/validations/uniqueness.rb +295 -0
- data/lib/active_record/validations.rb +101 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/active_record.rb +616 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes/attribute.rb +33 -0
- data/lib/arel/collectors/bind.rb +31 -0
- data/lib/arel/collectors/composite.rb +46 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +27 -0
- data/lib/arel/collectors/substitute_binds.rb +35 -0
- data/lib/arel/crud.rb +48 -0
- data/lib/arel/delete_manager.rb +32 -0
- data/lib/arel/errors.rb +19 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +53 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +48 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +125 -0
- data/lib/arel/nodes/bind_param.rb +44 -0
- data/lib/arel/nodes/bound_sql_literal.rb +65 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +62 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +44 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +15 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +45 -0
- data/lib/arel/nodes/grouping.rb +11 -0
- data/lib/arel/nodes/homogeneous_in.rb +68 -0
- data/lib/arel/nodes/in.rb +15 -0
- data/lib/arel/nodes/infix_operation.rb +92 -0
- data/lib/arel/nodes/inner_join.rb +8 -0
- data/lib/arel/nodes/insert_statement.rb +37 -0
- data/lib/arel/nodes/join_source.rb +20 -0
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/nary.rb +39 -0
- data/lib/arel/nodes/node.rb +161 -0
- data/lib/arel/nodes/node_expression.rb +13 -0
- data/lib/arel/nodes/ordering.rb +27 -0
- data/lib/arel/nodes/outer_join.rb +8 -0
- data/lib/arel/nodes/over.rb +15 -0
- data/lib/arel/nodes/regexp.rb +16 -0
- data/lib/arel/nodes/right_outer_join.rb +8 -0
- data/lib/arel/nodes/select_core.rb +67 -0
- data/lib/arel/nodes/select_statement.rb +41 -0
- data/lib/arel/nodes/sql_literal.rb +32 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +35 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +44 -0
- data/lib/arel/nodes/unary_operation.rb +20 -0
- data/lib/arel/nodes/unqualified_column.rb +22 -0
- data/lib/arel/nodes/update_statement.rb +46 -0
- data/lib/arel/nodes/values_list.rb +9 -0
- data/lib/arel/nodes/window.rb +126 -0
- data/lib/arel/nodes/with.rb +11 -0
- data/lib/arel/nodes.rb +75 -0
- data/lib/arel/order_predications.rb +13 -0
- data/lib/arel/predications.rb +260 -0
- data/lib/arel/select_manager.rb +276 -0
- data/lib/arel/table.rb +121 -0
- data/lib/arel/tree_manager.rb +65 -0
- data/lib/arel/update_manager.rb +49 -0
- data/lib/arel/visitors/dot.rb +299 -0
- data/lib/arel/visitors/mysql.rb +111 -0
- data/lib/arel/visitors/postgresql.rb +99 -0
- data/lib/arel/visitors/sqlite.rb +38 -0
- data/lib/arel/visitors/to_sql.rb +1033 -0
- data/lib/arel/visitors/visitor.rb +45 -0
- data/lib/arel/visitors.rb +13 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/arel.rb +73 -0
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +26 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +76 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +29 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +48 -0
- data/lib/rails/generators/active_record/migration.rb +54 -0
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +94 -0
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- data/lib/rails/generators/active_record.rb +19 -0
- metadata +505 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/connection_adapters/abstract_adapter"
|
|
4
|
+
require "active_record/connection_adapters/statement_pool"
|
|
5
|
+
require "active_record/connection_adapters/sqlite3/column"
|
|
6
|
+
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
|
|
7
|
+
require "active_record/connection_adapters/sqlite3/quoting"
|
|
8
|
+
require "active_record/connection_adapters/sqlite3/database_statements"
|
|
9
|
+
require "active_record/connection_adapters/sqlite3/schema_creation"
|
|
10
|
+
require "active_record/connection_adapters/sqlite3/schema_definitions"
|
|
11
|
+
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
|
12
|
+
require "active_record/connection_adapters/sqlite3/schema_statements"
|
|
13
|
+
|
|
14
|
+
gem "sqlite3", ">= 2.0"
|
|
15
|
+
require "sqlite3"
|
|
16
|
+
|
|
17
|
+
module ActiveRecord
|
|
18
|
+
module ConnectionAdapters # :nodoc:
|
|
19
|
+
# = Active Record SQLite3 Adapter
|
|
20
|
+
#
|
|
21
|
+
# The SQLite3 adapter works with the sqlite3-ruby drivers
|
|
22
|
+
# (available as gem from https://rubygems.org/gems/sqlite3).
|
|
23
|
+
#
|
|
24
|
+
# Options:
|
|
25
|
+
#
|
|
26
|
+
# * <tt>:database</tt> - Path to the database file.
|
|
27
|
+
class SQLite3Adapter < AbstractAdapter
|
|
28
|
+
ADAPTER_NAME = "SQLite"
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def new_client(config)
|
|
32
|
+
::SQLite3::Database.new(config[:database].to_s, config)
|
|
33
|
+
rescue Errno::ENOENT => error
|
|
34
|
+
if error.message.include?("No such file or directory")
|
|
35
|
+
raise ActiveRecord::NoDatabaseError
|
|
36
|
+
else
|
|
37
|
+
raise
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def dbconsole(config, options = {})
|
|
42
|
+
args = []
|
|
43
|
+
|
|
44
|
+
args << "-#{options[:mode]}" if options[:mode]
|
|
45
|
+
args << "-header" if options[:header]
|
|
46
|
+
args << File.expand_path(config.database, Rails.respond_to?(:root) ? Rails.root : nil)
|
|
47
|
+
|
|
48
|
+
find_cmd_and_exec(ActiveRecord.database_cli[:sqlite], *args)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
include SQLite3::Quoting
|
|
53
|
+
include SQLite3::SchemaStatements
|
|
54
|
+
include SQLite3::DatabaseStatements
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# :singleton-method:
|
|
58
|
+
# Configure the SQLite3Adapter to be used in a strict strings mode.
|
|
59
|
+
# This will disable double-quoted string literals, because otherwise typos can silently go unnoticed.
|
|
60
|
+
# For example, it is possible to create an index for a non existing column.
|
|
61
|
+
# If you wish to enable this mode you can add the following line to your application.rb file:
|
|
62
|
+
#
|
|
63
|
+
# config.active_record.sqlite3_adapter_strict_strings_by_default = true
|
|
64
|
+
class_attribute :strict_strings_by_default, default: false
|
|
65
|
+
|
|
66
|
+
NATIVE_DATABASE_TYPES = {
|
|
67
|
+
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
|
|
68
|
+
string: { name: "varchar" },
|
|
69
|
+
text: { name: "text" },
|
|
70
|
+
integer: { name: "integer" },
|
|
71
|
+
float: { name: "float" },
|
|
72
|
+
decimal: { name: "decimal" },
|
|
73
|
+
datetime: { name: "datetime" },
|
|
74
|
+
time: { name: "time" },
|
|
75
|
+
date: { name: "date" },
|
|
76
|
+
binary: { name: "blob" },
|
|
77
|
+
boolean: { name: "boolean" },
|
|
78
|
+
json: { name: "json" },
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
DEFAULT_PRAGMAS = {
|
|
82
|
+
"foreign_keys" => true,
|
|
83
|
+
"journal_mode" => :wal,
|
|
84
|
+
"synchronous" => :normal,
|
|
85
|
+
"mmap_size" => 134217728, # 128 megabytes
|
|
86
|
+
"journal_size_limit" => 67108864, # 64 megabytes
|
|
87
|
+
"cache_size" => 2000
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
|
91
|
+
alias reset clear
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
def dealloc(stmt)
|
|
95
|
+
stmt.close unless stmt.closed?
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def initialize(...)
|
|
100
|
+
super
|
|
101
|
+
|
|
102
|
+
@memory_database = false
|
|
103
|
+
case @config[:database].to_s
|
|
104
|
+
when ""
|
|
105
|
+
raise ArgumentError, "No database file specified. Missing argument: database"
|
|
106
|
+
when ":memory:"
|
|
107
|
+
@memory_database = true
|
|
108
|
+
when /\Afile:/
|
|
109
|
+
else
|
|
110
|
+
# Otherwise we have a path relative to Rails.root
|
|
111
|
+
@config[:database] = File.expand_path(@config[:database], Rails.root) if defined?(Rails.root)
|
|
112
|
+
dirname = File.dirname(@config[:database])
|
|
113
|
+
unless File.directory?(dirname)
|
|
114
|
+
begin
|
|
115
|
+
FileUtils.mkdir_p(dirname)
|
|
116
|
+
rescue SystemCallError
|
|
117
|
+
raise ActiveRecord::NoDatabaseError.new(connection_pool: @pool)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@last_affected_rows = nil
|
|
123
|
+
@previous_read_uncommitted = nil
|
|
124
|
+
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
|
|
125
|
+
@connection_parameters = @config.merge(
|
|
126
|
+
database: @config[:database].to_s,
|
|
127
|
+
results_as_hash: true,
|
|
128
|
+
default_transaction_mode: :immediate,
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def database_exists?
|
|
133
|
+
@config[:database] == ":memory:" || File.exist?(@config[:database].to_s)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def supports_ddl_transactions?
|
|
137
|
+
true
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def supports_savepoints?
|
|
141
|
+
true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def supports_transaction_isolation?
|
|
145
|
+
true
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def supports_partial_index?
|
|
149
|
+
true
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def supports_expression_index?
|
|
153
|
+
database_version >= "3.9.0"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def requires_reloading?
|
|
157
|
+
true
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def supports_foreign_keys?
|
|
161
|
+
true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def supports_check_constraints?
|
|
165
|
+
true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def supports_views?
|
|
169
|
+
true
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def supports_datetime_with_precision?
|
|
173
|
+
true
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def supports_json?
|
|
177
|
+
true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def supports_common_table_expressions?
|
|
181
|
+
database_version >= "3.8.3"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def supports_insert_returning?
|
|
185
|
+
database_version >= "3.35.0"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def supports_insert_on_conflict?
|
|
189
|
+
database_version >= "3.24.0"
|
|
190
|
+
end
|
|
191
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
|
192
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
|
193
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
|
194
|
+
|
|
195
|
+
def supports_concurrent_connections?
|
|
196
|
+
!@memory_database
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def supports_virtual_columns?
|
|
200
|
+
database_version >= "3.31.0"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def connected?
|
|
204
|
+
!(@raw_connection.nil? || @raw_connection.closed?)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
alias_method :active?, :connected?
|
|
208
|
+
|
|
209
|
+
alias :reset! :reconnect!
|
|
210
|
+
|
|
211
|
+
# Disconnects from the database if already connected. Otherwise, this
|
|
212
|
+
# method does nothing.
|
|
213
|
+
def disconnect!
|
|
214
|
+
super
|
|
215
|
+
|
|
216
|
+
@raw_connection&.close rescue nil
|
|
217
|
+
@raw_connection = nil
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def supports_index_sort_order?
|
|
221
|
+
true
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def native_database_types # :nodoc:
|
|
225
|
+
NATIVE_DATABASE_TYPES
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Returns the current database encoding format as a string, e.g. 'UTF-8'
|
|
229
|
+
def encoding
|
|
230
|
+
any_raw_connection.encoding.to_s
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def supports_explain?
|
|
234
|
+
true
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def supports_lazy_transactions?
|
|
238
|
+
true
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def supports_deferrable_constraints?
|
|
242
|
+
true
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# REFERENTIAL INTEGRITY ====================================
|
|
246
|
+
|
|
247
|
+
def disable_referential_integrity # :nodoc:
|
|
248
|
+
old_foreign_keys = query_value("PRAGMA foreign_keys")
|
|
249
|
+
old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
|
|
250
|
+
|
|
251
|
+
begin
|
|
252
|
+
execute("PRAGMA defer_foreign_keys = ON")
|
|
253
|
+
execute("PRAGMA foreign_keys = OFF")
|
|
254
|
+
yield
|
|
255
|
+
ensure
|
|
256
|
+
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
|
|
257
|
+
execute("PRAGMA foreign_keys = #{old_foreign_keys}")
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def check_all_foreign_keys_valid! # :nodoc:
|
|
262
|
+
sql = "PRAGMA foreign_key_check"
|
|
263
|
+
result = execute(sql)
|
|
264
|
+
|
|
265
|
+
unless result.blank?
|
|
266
|
+
tables = result.map { |row| row["table"] }
|
|
267
|
+
raise ActiveRecord::StatementInvalid.new("Foreign key violations found: #{tables.join(", ")}", sql: sql, connection_pool: @pool)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# SCHEMA STATEMENTS ========================================
|
|
272
|
+
|
|
273
|
+
def primary_keys(table_name) # :nodoc:
|
|
274
|
+
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
|
|
275
|
+
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def remove_index(table_name, column_name = nil, **options) # :nodoc:
|
|
279
|
+
return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
|
|
280
|
+
|
|
281
|
+
index_name = index_name_for_remove(table_name, column_name, options)
|
|
282
|
+
|
|
283
|
+
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
VIRTUAL_TABLE_REGEX = /USING\s+(\w+)\s*\((.+)\)/i
|
|
287
|
+
|
|
288
|
+
# Returns a list of defined virtual tables
|
|
289
|
+
def virtual_tables
|
|
290
|
+
query = <<~SQL
|
|
291
|
+
SELECT name, sql FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL %';
|
|
292
|
+
SQL
|
|
293
|
+
|
|
294
|
+
exec_query(query, "SCHEMA").cast_values.each_with_object({}) do |row, memo|
|
|
295
|
+
table_name, sql = row[0], row[1]
|
|
296
|
+
_, module_name, arguments = sql.match(VIRTUAL_TABLE_REGEX).to_a
|
|
297
|
+
memo[table_name] = [module_name, arguments]
|
|
298
|
+
end.to_a
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Creates a virtual table
|
|
302
|
+
#
|
|
303
|
+
# Example:
|
|
304
|
+
# create_virtual_table :emails, :fts5, ['sender', 'title',' body']
|
|
305
|
+
def create_virtual_table(table_name, module_name, values)
|
|
306
|
+
exec_query "CREATE VIRTUAL TABLE IF NOT EXISTS #{table_name} USING #{module_name} (#{values.join(", ")})"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Drops a virtual table
|
|
310
|
+
#
|
|
311
|
+
# Although this command ignores +module_name+ and +values+,
|
|
312
|
+
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
|
|
313
|
+
# In that case, +module_name+, +values+ and +options+ will be used by #create_virtual_table.
|
|
314
|
+
def drop_virtual_table(table_name, module_name, values, **options)
|
|
315
|
+
drop_table(table_name)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Renames a table.
|
|
319
|
+
#
|
|
320
|
+
# Example:
|
|
321
|
+
# rename_table('octopuses', 'octopi')
|
|
322
|
+
def rename_table(table_name, new_name, **options)
|
|
323
|
+
validate_table_length!(new_name) unless options[:_uses_legacy_table_name]
|
|
324
|
+
schema_cache.clear_data_source_cache!(table_name.to_s)
|
|
325
|
+
schema_cache.clear_data_source_cache!(new_name.to_s)
|
|
326
|
+
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
|
327
|
+
rename_table_indexes(table_name, new_name, **options)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def add_column(table_name, column_name, type, **options) # :nodoc:
|
|
331
|
+
type = type.to_sym
|
|
332
|
+
if invalid_alter_table_type?(type, options)
|
|
333
|
+
alter_table(table_name) do |definition|
|
|
334
|
+
definition.column(column_name, type, **options)
|
|
335
|
+
end
|
|
336
|
+
else
|
|
337
|
+
super
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def remove_column(table_name, column_name, type = nil, **options) # :nodoc:
|
|
342
|
+
alter_table(table_name) do |definition|
|
|
343
|
+
definition.remove_column column_name
|
|
344
|
+
definition.foreign_keys.delete_if { |fk| fk.column == column_name.to_s }
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def remove_columns(table_name, *column_names, type: nil, **options) # :nodoc:
|
|
349
|
+
alter_table(table_name) do |definition|
|
|
350
|
+
column_names.each do |column_name|
|
|
351
|
+
definition.remove_column column_name
|
|
352
|
+
end
|
|
353
|
+
column_names = column_names.map(&:to_s)
|
|
354
|
+
definition.foreign_keys.delete_if { |fk| column_names.include?(fk.column) }
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def change_column_default(table_name, column_name, default_or_changes) # :nodoc:
|
|
359
|
+
default = extract_new_default_value(default_or_changes)
|
|
360
|
+
|
|
361
|
+
alter_table(table_name) do |definition|
|
|
362
|
+
definition[column_name].default = default
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def change_column_null(table_name, column_name, null, default = nil) # :nodoc:
|
|
367
|
+
validate_change_column_null_argument!(null)
|
|
368
|
+
|
|
369
|
+
unless null || default.nil?
|
|
370
|
+
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")
|
|
371
|
+
end
|
|
372
|
+
alter_table(table_name) do |definition|
|
|
373
|
+
definition[column_name].null = null
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def change_column(table_name, column_name, type, **options) # :nodoc:
|
|
378
|
+
alter_table(table_name) do |definition|
|
|
379
|
+
definition.change_column(column_name, type, **options)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def rename_column(table_name, column_name, new_column_name) # :nodoc:
|
|
384
|
+
column = column_for(table_name, column_name)
|
|
385
|
+
alter_table(table_name, rename: { column.name => new_column_name.to_s })
|
|
386
|
+
rename_column_indexes(table_name, column.name, new_column_name)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def add_timestamps(table_name, **options)
|
|
390
|
+
options[:null] = false if options[:null].nil?
|
|
391
|
+
|
|
392
|
+
if !options.key?(:precision)
|
|
393
|
+
options[:precision] = 6
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
alter_table(table_name) do |definition|
|
|
397
|
+
definition.column :created_at, :datetime, **options
|
|
398
|
+
definition.column :updated_at, :datetime, **options
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def add_reference(table_name, ref_name, **options) # :nodoc:
|
|
403
|
+
super(table_name, ref_name, type: :integer, **options)
|
|
404
|
+
end
|
|
405
|
+
alias :add_belongs_to :add_reference
|
|
406
|
+
|
|
407
|
+
FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
|
|
408
|
+
DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
|
|
409
|
+
def foreign_keys(table_name)
|
|
410
|
+
# SQLite returns 1 row for each column of composite foreign keys.
|
|
411
|
+
fk_info = internal_exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
|
412
|
+
# Deferred or immediate foreign keys can only be seen in the CREATE TABLE sql
|
|
413
|
+
fk_defs = table_structure_sql(table_name)
|
|
414
|
+
.select do |column_string|
|
|
415
|
+
column_string.start_with?("CONSTRAINT") &&
|
|
416
|
+
column_string.include?("FOREIGN KEY")
|
|
417
|
+
end
|
|
418
|
+
.to_h do |fk_string|
|
|
419
|
+
_, from, table, to = fk_string.match(FK_REGEX).to_a
|
|
420
|
+
_, mode = fk_string.match(DEFERRABLE_REGEX).to_a
|
|
421
|
+
deferred = mode&.downcase&.to_sym || false
|
|
422
|
+
[[table, from, to], deferred]
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
grouped_fk = fk_info.group_by { |row| row["id"] }.values.each { |group| group.sort_by! { |row| row["seq"] } }
|
|
426
|
+
grouped_fk.map do |group|
|
|
427
|
+
row = group.first
|
|
428
|
+
options = {
|
|
429
|
+
on_delete: extract_foreign_key_action(row["on_delete"]),
|
|
430
|
+
on_update: extract_foreign_key_action(row["on_update"]),
|
|
431
|
+
deferrable: fk_defs[[row["table"], row["from"], row["to"]]]
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if group.one?
|
|
435
|
+
options[:column] = row["from"]
|
|
436
|
+
options[:primary_key] = row["to"]
|
|
437
|
+
else
|
|
438
|
+
options[:column] = group.map { |row| row["from"] }
|
|
439
|
+
options[:primary_key] = group.map { |row| row["to"] }
|
|
440
|
+
end
|
|
441
|
+
ForeignKeyDefinition.new(table_name, row["table"], options)
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def build_insert_sql(insert) # :nodoc:
|
|
446
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
|
447
|
+
|
|
448
|
+
if insert.skip_duplicates?
|
|
449
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
|
450
|
+
elsif insert.update_duplicates?
|
|
451
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
|
452
|
+
if insert.raw_update_sql?
|
|
453
|
+
sql << insert.raw_update_sql
|
|
454
|
+
else
|
|
455
|
+
sql << insert.touch_model_timestamps_unless { |column| "#{column} IS excluded.#{column}" }
|
|
456
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
sql << " RETURNING #{insert.returning}" if insert.returning
|
|
461
|
+
sql
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def shared_cache? # :nodoc:
|
|
465
|
+
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def get_database_version # :nodoc:
|
|
469
|
+
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)", "SCHEMA"))
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def check_version # :nodoc:
|
|
473
|
+
if database_version < "3.8.0"
|
|
474
|
+
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
class SQLite3Integer < Type::Integer # :nodoc:
|
|
479
|
+
private
|
|
480
|
+
def _limit
|
|
481
|
+
# INTEGER storage class can be stored 8 bytes value.
|
|
482
|
+
# See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
|
|
483
|
+
limit || 8
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
|
|
488
|
+
|
|
489
|
+
class << self
|
|
490
|
+
private
|
|
491
|
+
def initialize_type_map(m)
|
|
492
|
+
super
|
|
493
|
+
register_class_with_limit m, %r(int)i, SQLite3Integer
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
|
|
498
|
+
EXTENDED_TYPE_MAPS = Concurrent::Map.new
|
|
499
|
+
|
|
500
|
+
private
|
|
501
|
+
# See https://www.sqlite.org/limits.html,
|
|
502
|
+
# the default value is 999 when not configured.
|
|
503
|
+
def bind_params_length
|
|
504
|
+
999
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
def table_structure(table_name)
|
|
508
|
+
structure = table_info(table_name)
|
|
509
|
+
raise ActiveRecord::StatementInvalid.new("Could not find table '#{table_name}'", connection_pool: @pool) if structure.empty?
|
|
510
|
+
table_structure_with_collation(table_name, structure)
|
|
511
|
+
end
|
|
512
|
+
alias column_definitions table_structure
|
|
513
|
+
|
|
514
|
+
def extract_value_from_default(default)
|
|
515
|
+
case default
|
|
516
|
+
when /^null$/i
|
|
517
|
+
nil
|
|
518
|
+
# Quoted types
|
|
519
|
+
when /^'([^|]*)'$/m
|
|
520
|
+
$1.gsub("''", "'")
|
|
521
|
+
# Quoted types
|
|
522
|
+
when /^"([^|]*)"$/m
|
|
523
|
+
$1.gsub('""', '"')
|
|
524
|
+
# Numeric types
|
|
525
|
+
when /\A-?\d+(\.\d*)?\z/
|
|
526
|
+
$&
|
|
527
|
+
# Binary columns
|
|
528
|
+
when /x'(.*)'/
|
|
529
|
+
[ $1 ].pack("H*")
|
|
530
|
+
else
|
|
531
|
+
# Anything else is blank or some function
|
|
532
|
+
# and we can't know the value of that, so return nil.
|
|
533
|
+
nil
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def extract_default_function(default_value, default)
|
|
538
|
+
default if has_default_function?(default_value, default)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
def has_default_function?(default_value, default)
|
|
542
|
+
!default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# See: https://www.sqlite.org/lang_altertable.html
|
|
546
|
+
# SQLite has an additional restriction on the ALTER TABLE statement
|
|
547
|
+
def invalid_alter_table_type?(type, options)
|
|
548
|
+
type == :primary_key || options[:primary_key] ||
|
|
549
|
+
options[:null] == false && options[:default].nil? ||
|
|
550
|
+
(type == :virtual && options[:stored])
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def alter_table(
|
|
554
|
+
table_name,
|
|
555
|
+
foreign_keys = foreign_keys(table_name),
|
|
556
|
+
check_constraints = check_constraints(table_name),
|
|
557
|
+
**options
|
|
558
|
+
)
|
|
559
|
+
altered_table_name = "a#{table_name}"
|
|
560
|
+
|
|
561
|
+
caller = lambda do |definition|
|
|
562
|
+
rename = options[:rename] || {}
|
|
563
|
+
foreign_keys.each do |fk|
|
|
564
|
+
if column = rename[fk.options[:column]]
|
|
565
|
+
fk.options[:column] = column
|
|
566
|
+
end
|
|
567
|
+
to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
|
568
|
+
definition.foreign_key(to_table, **fk.options)
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
check_constraints.each do |chk|
|
|
572
|
+
definition.check_constraint(chk.expression, **chk.options)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
yield definition if block_given?
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
transaction do
|
|
579
|
+
disable_referential_integrity do
|
|
580
|
+
move_table(table_name, altered_table_name, options.merge(temporary: true))
|
|
581
|
+
move_table(altered_table_name, table_name, &caller)
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def move_table(from, to, options = {}, &block)
|
|
587
|
+
copy_table(from, to, options, &block)
|
|
588
|
+
drop_table(from)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def copy_table(from, to, options = {})
|
|
592
|
+
from_primary_key = primary_key(from)
|
|
593
|
+
options[:id] = false
|
|
594
|
+
create_table(to, **options) do |definition|
|
|
595
|
+
@definition = definition
|
|
596
|
+
if from_primary_key.is_a?(Array)
|
|
597
|
+
@definition.primary_keys from_primary_key
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
columns(from).each do |column|
|
|
601
|
+
column_name = options[:rename] ?
|
|
602
|
+
(options[:rename][column.name] ||
|
|
603
|
+
options[:rename][column.name.to_sym] ||
|
|
604
|
+
column.name) : column.name
|
|
605
|
+
|
|
606
|
+
column_options = {
|
|
607
|
+
limit: column.limit,
|
|
608
|
+
precision: column.precision,
|
|
609
|
+
scale: column.scale,
|
|
610
|
+
null: column.null,
|
|
611
|
+
collation: column.collation,
|
|
612
|
+
primary_key: column_name == from_primary_key
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if column.virtual?
|
|
616
|
+
column_options[:as] = column.default_function
|
|
617
|
+
column_options[:stored] = column.virtual_stored?
|
|
618
|
+
column_options[:type] = column.type
|
|
619
|
+
elsif column.has_default?
|
|
620
|
+
type = lookup_cast_type_from_column(column)
|
|
621
|
+
default = type.deserialize(column.default)
|
|
622
|
+
default = -> { column.default_function } if default.nil?
|
|
623
|
+
|
|
624
|
+
unless column.auto_increment?
|
|
625
|
+
column_options[:default] = default
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
column_type = column.virtual? ? :virtual : (column.bigint? ? :bigint : column.type)
|
|
630
|
+
@definition.column(column_name, column_type, **column_options)
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
yield @definition if block_given?
|
|
634
|
+
end
|
|
635
|
+
copy_table_indexes(from, to, options[:rename] || {})
|
|
636
|
+
|
|
637
|
+
columns_to_copy = @definition.columns.reject { |col| col.options.key?(:as) }.map(&:name)
|
|
638
|
+
copy_table_contents(from, to,
|
|
639
|
+
columns_to_copy,
|
|
640
|
+
options[:rename] || {})
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
def copy_table_indexes(from, to, rename = {})
|
|
644
|
+
indexes(from).each do |index|
|
|
645
|
+
name = index.name
|
|
646
|
+
if to == "a#{from}"
|
|
647
|
+
name = "t#{name}"
|
|
648
|
+
elsif from == "a#{to}"
|
|
649
|
+
name = name[1..-1]
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
columns = index.columns
|
|
653
|
+
if columns.is_a?(Array)
|
|
654
|
+
to_column_names = columns(to).map(&:name)
|
|
655
|
+
columns = columns.map { |c| rename[c] || c }.select do |column|
|
|
656
|
+
to_column_names.include?(column)
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
unless columns.empty?
|
|
661
|
+
# index name can't be the same
|
|
662
|
+
options = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
|
|
663
|
+
options[:unique] = true if index.unique
|
|
664
|
+
options[:where] = index.where if index.where
|
|
665
|
+
options[:order] = index.orders if index.orders
|
|
666
|
+
add_index(to, columns, **options)
|
|
667
|
+
end
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def copy_table_contents(from, to, columns, rename = {})
|
|
672
|
+
column_mappings = Hash[columns.map { |name| [name, name] }]
|
|
673
|
+
rename.each { |a| column_mappings[a.last] = a.first }
|
|
674
|
+
from_columns = columns(from).collect(&:name)
|
|
675
|
+
columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
|
|
676
|
+
from_columns_to_copy = columns.map { |col| column_mappings[col] }
|
|
677
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
|
678
|
+
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
|
679
|
+
|
|
680
|
+
internal_exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
|
681
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def translate_exception(exception, message:, sql:, binds:)
|
|
685
|
+
# SQLite 3.8.2 returns a newly formatted error message:
|
|
686
|
+
# UNIQUE constraint failed: *table_name*.*column_name*
|
|
687
|
+
# Older versions of SQLite return:
|
|
688
|
+
# column *column_name* is not unique
|
|
689
|
+
if exception.message.match?(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/i)
|
|
690
|
+
RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
691
|
+
elsif exception.message.match?(/(.* may not be NULL|NOT NULL constraint failed: .*)/i)
|
|
692
|
+
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
693
|
+
elsif exception.message.match?(/FOREIGN KEY constraint failed/i)
|
|
694
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
695
|
+
elsif exception.message.match?(/called on a closed database/i)
|
|
696
|
+
ConnectionNotEstablished.new(exception, connection_pool: @pool)
|
|
697
|
+
elsif exception.is_a?(::SQLite3::BusyException)
|
|
698
|
+
StatementTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
699
|
+
else
|
|
700
|
+
super
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
COLLATE_REGEX = /.*"(\w+)".*collate\s+"(\w+)".*/i
|
|
705
|
+
PRIMARY_KEY_AUTOINCREMENT_REGEX = /.*"(\w+)".+PRIMARY KEY AUTOINCREMENT/i
|
|
706
|
+
GENERATED_ALWAYS_AS_REGEX = /.*"(\w+)".+GENERATED ALWAYS AS \((.+)\) (?:STORED|VIRTUAL)/i
|
|
707
|
+
|
|
708
|
+
def table_structure_with_collation(table_name, basic_structure)
|
|
709
|
+
collation_hash = {}
|
|
710
|
+
auto_increments = {}
|
|
711
|
+
generated_columns = {}
|
|
712
|
+
|
|
713
|
+
column_strings = table_structure_sql(table_name, basic_structure.map { |column| column["name"] })
|
|
714
|
+
|
|
715
|
+
if column_strings.any?
|
|
716
|
+
column_strings.each do |column_string|
|
|
717
|
+
# This regex will match the column name and collation type and will save
|
|
718
|
+
# the value in $1 and $2 respectively.
|
|
719
|
+
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
|
720
|
+
auto_increments[$1] = true if PRIMARY_KEY_AUTOINCREMENT_REGEX =~ column_string
|
|
721
|
+
generated_columns[$1] = $2 if GENERATED_ALWAYS_AS_REGEX =~ column_string
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
basic_structure.map do |column|
|
|
725
|
+
column = column.to_h
|
|
726
|
+
|
|
727
|
+
column_name = column["name"]
|
|
728
|
+
|
|
729
|
+
if collation_hash.has_key? column_name
|
|
730
|
+
column["collation"] = collation_hash[column_name]
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
if auto_increments.has_key?(column_name)
|
|
734
|
+
column["auto_increment"] = true
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
if generated_columns.has_key?(column_name)
|
|
738
|
+
column["dflt_value"] = generated_columns[column_name]
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
column
|
|
742
|
+
end
|
|
743
|
+
else
|
|
744
|
+
basic_structure.to_a
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
UNQUOTED_OPEN_PARENS_REGEX = /\((?![^'"]*['"][^'"]*$)/
|
|
749
|
+
FINAL_CLOSE_PARENS_REGEX = /\);*\z/
|
|
750
|
+
|
|
751
|
+
def table_structure_sql(table_name, column_names = nil)
|
|
752
|
+
unless column_names
|
|
753
|
+
column_info = table_info(table_name)
|
|
754
|
+
column_names = column_info.map { |column| column["name"] }
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
sql = <<~SQL
|
|
758
|
+
SELECT sql FROM
|
|
759
|
+
(SELECT * FROM sqlite_master UNION ALL
|
|
760
|
+
SELECT * FROM sqlite_temp_master)
|
|
761
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
|
762
|
+
SQL
|
|
763
|
+
|
|
764
|
+
# Result will have following sample string
|
|
765
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
|
766
|
+
# "password_digest" varchar COLLATE "NOCASE",
|
|
767
|
+
# "o_id" integer,
|
|
768
|
+
# CONSTRAINT "fk_rails_78146ddd2e" FOREIGN KEY ("o_id") REFERENCES "os" ("id"));
|
|
769
|
+
result = query_value(sql, "SCHEMA")
|
|
770
|
+
|
|
771
|
+
return [] unless result
|
|
772
|
+
|
|
773
|
+
# Splitting with left parentheses and discarding the first part will return all
|
|
774
|
+
# columns separated with comma(,).
|
|
775
|
+
result.partition(UNQUOTED_OPEN_PARENS_REGEX)
|
|
776
|
+
.last
|
|
777
|
+
.sub(FINAL_CLOSE_PARENS_REGEX, "")
|
|
778
|
+
# column definitions can have a comma in them, so split on commas followed
|
|
779
|
+
# by a space and a column name in quotes or followed by the keyword CONSTRAINT
|
|
780
|
+
.split(/,(?=\s(?:CONSTRAINT|"(?:#{Regexp.union(column_names).source})"))/i)
|
|
781
|
+
.map(&:strip)
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
def table_info(table_name)
|
|
785
|
+
if supports_virtual_columns?
|
|
786
|
+
internal_exec_query("PRAGMA table_xinfo(#{quote_table_name(table_name)})", "SCHEMA")
|
|
787
|
+
else
|
|
788
|
+
internal_exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
def arel_visitor
|
|
793
|
+
Arel::Visitors::SQLite.new(self)
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
def build_statement_pool
|
|
797
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def connect
|
|
801
|
+
@raw_connection = self.class.new_client(@connection_parameters)
|
|
802
|
+
rescue ConnectionNotEstablished => ex
|
|
803
|
+
raise ex.set_pool(@pool)
|
|
804
|
+
end
|
|
805
|
+
|
|
806
|
+
def reconnect
|
|
807
|
+
if active?
|
|
808
|
+
@raw_connection.rollback rescue nil
|
|
809
|
+
else
|
|
810
|
+
connect
|
|
811
|
+
end
|
|
812
|
+
end
|
|
813
|
+
|
|
814
|
+
def configure_connection
|
|
815
|
+
if @config[:timeout] && @config[:retries]
|
|
816
|
+
raise ArgumentError, "Cannot specify both timeout and retries arguments"
|
|
817
|
+
elsif @config[:timeout]
|
|
818
|
+
timeout = self.class.type_cast_config_to_integer(@config[:timeout])
|
|
819
|
+
raise TypeError, "timeout must be integer, not #{timeout}" unless timeout.is_a?(Integer)
|
|
820
|
+
@raw_connection.busy_handler_timeout = timeout
|
|
821
|
+
elsif @config[:retries]
|
|
822
|
+
ActiveRecord.deprecator.warn(<<~MSG)
|
|
823
|
+
The retries option is deprecated and will be removed in Rails 8.1. Use timeout instead.
|
|
824
|
+
MSG
|
|
825
|
+
retries = self.class.type_cast_config_to_integer(@config[:retries])
|
|
826
|
+
raw_connection.busy_handler { |count| count <= retries }
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
super
|
|
830
|
+
|
|
831
|
+
pragmas = @config.fetch(:pragmas, {}).stringify_keys
|
|
832
|
+
DEFAULT_PRAGMAS.merge(pragmas).each do |pragma, value|
|
|
833
|
+
if ::SQLite3::Pragmas.method_defined?("#{pragma}=")
|
|
834
|
+
@raw_connection.public_send("#{pragma}=", value)
|
|
835
|
+
else
|
|
836
|
+
warn "Unknown SQLite pragma: #{pragma}"
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
end
|
|
840
|
+
end
|
|
841
|
+
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
|
842
|
+
end
|
|
843
|
+
end
|