activerecord 4.2.11.2 → 6.0.2.2
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.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +675 -1582
- data/MIT-LICENSE +4 -2
- data/README.rdoc +13 -12
- data/examples/performance.rb +33 -32
- data/examples/simple.rb +5 -4
- data/lib/active_record.rb +41 -22
- data/lib/active_record/aggregations.rb +267 -251
- data/lib/active_record/association_relation.rb +26 -12
- data/lib/active_record/associations.rb +1737 -1597
- data/lib/active_record/associations/alias_tracker.rb +29 -35
- data/lib/active_record/associations/association.rb +133 -58
- data/lib/active_record/associations/association_scope.rb +103 -132
- data/lib/active_record/associations/belongs_to_association.rb +65 -60
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -12
- data/lib/active_record/associations/builder/association.rb +27 -40
- data/lib/active_record/associations/builder/belongs_to.rb +69 -55
- data/lib/active_record/associations/builder/collection_association.rb +10 -33
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +52 -66
- data/lib/active_record/associations/builder/has_many.rb +8 -4
- data/lib/active_record/associations/builder/has_one.rb +46 -5
- data/lib/active_record/associations/builder/singular_association.rb +16 -10
- data/lib/active_record/associations/collection_association.rb +136 -288
- data/lib/active_record/associations/collection_proxy.rb +241 -146
- data/lib/active_record/associations/foreign_association.rb +10 -1
- data/lib/active_record/associations/has_many_association.rb +34 -97
- data/lib/active_record/associations/has_many_through_association.rb +60 -87
- data/lib/active_record/associations/has_one_association.rb +61 -49
- data/lib/active_record/associations/has_one_through_association.rb +20 -11
- data/lib/active_record/associations/join_dependency.rb +141 -167
- data/lib/active_record/associations/join_dependency/join_association.rb +38 -86
- data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +14 -14
- data/lib/active_record/associations/preloader.rb +90 -92
- data/lib/active_record/associations/preloader/association.rb +90 -123
- data/lib/active_record/associations/preloader/through_association.rb +85 -65
- data/lib/active_record/associations/singular_association.rb +18 -39
- data/lib/active_record/associations/through_association.rb +38 -18
- data/lib/active_record/attribute_assignment.rb +56 -183
- data/lib/active_record/attribute_decorators.rb +39 -15
- data/lib/active_record/attribute_methods.rb +120 -135
- data/lib/active_record/attribute_methods/before_type_cast.rb +13 -8
- data/lib/active_record/attribute_methods/dirty.rb +174 -144
- data/lib/active_record/attribute_methods/primary_key.rb +91 -83
- data/lib/active_record/attribute_methods/query.rb +6 -5
- data/lib/active_record/attribute_methods/read.rb +20 -76
- data/lib/active_record/attribute_methods/serialization.rb +40 -20
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
- data/lib/active_record/attribute_methods/write.rb +32 -54
- data/lib/active_record/attributes.rb +214 -82
- data/lib/active_record/autosave_association.rb +96 -38
- data/lib/active_record/base.rb +57 -45
- data/lib/active_record/callbacks.rb +100 -74
- data/lib/active_record/coders/json.rb +3 -1
- data/lib/active_record/coders/yaml_column.rb +24 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +806 -296
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +26 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +238 -115
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +83 -23
- data/lib/active_record/connection_adapters/abstract/quoting.rb +170 -53
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +74 -46
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +356 -227
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +79 -36
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +664 -243
- data/lib/active_record/connection_adapters/abstract/transaction.rb +191 -83
- data/lib/active_record/connection_adapters/abstract_adapter.rb +469 -202
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +517 -633
- data/lib/active_record/connection_adapters/column.rb +56 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +174 -152
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +29 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +202 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +95 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +88 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +264 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +31 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +58 -180
- data/lib/active_record/connection_adapters/postgresql/column.rb +21 -11
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +66 -114
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +9 -8
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
- data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +58 -54
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +10 -5
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +144 -47
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +178 -108
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +470 -290
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +555 -356
- data/lib/active_record/connection_adapters/schema_cache.rb +72 -25
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +37 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +103 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +137 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -345
- data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
- data/lib/active_record/connection_handling.rb +183 -41
- data/lib/active_record/core.rb +253 -229
- data/lib/active_record/counter_cache.rb +67 -49
- data/lib/active_record/database_configurations.rb +233 -0
- data/lib/active_record/database_configurations/database_config.rb +37 -0
- data/lib/active_record/database_configurations/hash_config.rb +50 -0
- data/lib/active_record/database_configurations/url_config.rb +79 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +87 -105
- data/lib/active_record/enum.rb +163 -86
- data/lib/active_record/errors.rb +188 -53
- data/lib/active_record/explain.rb +23 -11
- data/lib/active_record/explain_registry.rb +4 -2
- data/lib/active_record/explain_subscriber.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +35 -9
- data/lib/active_record/fixture_set/model_metadata.rb +33 -0
- data/lib/active_record/fixture_set/render_context.rb +17 -0
- data/lib/active_record/fixture_set/table_row.rb +153 -0
- data/lib/active_record/fixture_set/table_rows.rb +47 -0
- data/lib/active_record/fixtures.rb +228 -499
- data/lib/active_record/gem_version.rb +5 -3
- data/lib/active_record/inheritance.rb +158 -112
- data/lib/active_record/insert_all.rb +179 -0
- data/lib/active_record/integration.rb +123 -29
- data/lib/active_record/internal_metadata.rb +53 -0
- data/lib/active_record/legacy_yaml_adapter.rb +21 -3
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +87 -96
- data/lib/active_record/locking/pessimistic.rb +18 -6
- data/lib/active_record/log_subscriber.rb +76 -33
- data/lib/active_record/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +621 -303
- data/lib/active_record/migration/command_recorder.rb +177 -90
- data/lib/active_record/migration/compatibility.rb +244 -0
- data/lib/active_record/migration/join_table.rb +8 -6
- data/lib/active_record/model_schema.rb +315 -112
- data/lib/active_record/nested_attributes.rb +264 -222
- data/lib/active_record/no_touching.rb +14 -1
- data/lib/active_record/null_relation.rb +24 -37
- data/lib/active_record/persistence.rb +557 -125
- data/lib/active_record/query_cache.rb +19 -23
- data/lib/active_record/querying.rb +43 -29
- data/lib/active_record/railtie.rb +143 -44
- data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
- data/lib/active_record/railties/console_sandbox.rb +2 -0
- data/lib/active_record/railties/controller_runtime.rb +34 -33
- data/lib/active_record/railties/databases.rake +331 -185
- data/lib/active_record/readonly_attributes.rb +5 -4
- data/lib/active_record/reflection.rb +428 -279
- data/lib/active_record/relation.rb +519 -341
- data/lib/active_record/relation/batches.rb +207 -55
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/calculations.rb +267 -253
- data/lib/active_record/relation/delegation.rb +70 -80
- data/lib/active_record/relation/finder_methods.rb +286 -241
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +78 -87
- data/lib/active_record/relation/predicate_builder.rb +114 -119
- data/lib/active_record/relation/predicate_builder/array_handler.rb +27 -26
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +43 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +18 -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 +53 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +22 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
- data/lib/active_record/relation/query_attribute.rb +50 -0
- data/lib/active_record/relation/query_methods.rb +597 -393
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -13
- data/lib/active_record/relation/where_clause.rb +190 -0
- data/lib/active_record/relation/where_clause_factory.rb +33 -0
- data/lib/active_record/result.rb +79 -42
- data/lib/active_record/runtime_registry.rb +6 -4
- data/lib/active_record/sanitization.rb +144 -121
- data/lib/active_record/schema.rb +21 -24
- data/lib/active_record/schema_dumper.rb +112 -93
- data/lib/active_record/schema_migration.rb +24 -17
- data/lib/active_record/scoping.rb +45 -26
- data/lib/active_record/scoping/default.rb +101 -85
- data/lib/active_record/scoping/named.rb +87 -33
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/statement_cache.rb +73 -36
- data/lib/active_record/store.rb +127 -42
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +75 -0
- data/lib/active_record/tasks/database_tasks.rb +307 -100
- data/lib/active_record/tasks/mysql_database_tasks.rb +55 -99
- data/lib/active_record/tasks/postgresql_database_tasks.rb +81 -41
- data/lib/active_record/tasks/sqlite_database_tasks.rb +38 -16
- data/lib/active_record/test_databases.rb +23 -0
- data/lib/active_record/test_fixtures.rb +225 -0
- data/lib/active_record/timestamp.rb +86 -40
- data/lib/active_record/touch_later.rb +66 -0
- data/lib/active_record/transactions.rb +217 -151
- data/lib/active_record/translation.rb +3 -1
- data/lib/active_record/type.rb +78 -23
- data/lib/active_record/type/adapter_specific_registry.rb +129 -0
- data/lib/active_record/type/date.rb +4 -45
- data/lib/active_record/type/date_time.rb +4 -49
- data/lib/active_record/type/decimal_without_scale.rb +6 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
- data/lib/active_record/type/internal/timezone.rb +17 -0
- data/lib/active_record/type/json.rb +30 -0
- data/lib/active_record/type/serialized.rb +24 -15
- data/lib/active_record/type/text.rb +2 -2
- data/lib/active_record/type/time.rb +11 -16
- data/lib/active_record/type/type_map.rb +15 -17
- data/lib/active_record/type/unsigned_integer.rb +9 -7
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/type_caster/connection.rb +34 -0
- data/lib/active_record/type_caster/map.rb +20 -0
- data/lib/active_record/validations.rb +39 -35
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +13 -4
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +14 -13
- data/lib/active_record/validations/uniqueness.rb +42 -55
- data/lib/active_record/version.rb +3 -1
- data/lib/arel.rb +58 -0
- data/lib/arel/alias_predication.rb +9 -0
- data/lib/arel/attributes.rb +22 -0
- data/lib/arel/attributes/attribute.rb +37 -0
- data/lib/arel/collectors/bind.rb +24 -0
- data/lib/arel/collectors/composite.rb +31 -0
- data/lib/arel/collectors/plain_string.rb +20 -0
- data/lib/arel/collectors/sql_string.rb +20 -0
- data/lib/arel/collectors/substitute_binds.rb +28 -0
- data/lib/arel/crud.rb +42 -0
- data/lib/arel/delete_manager.rb +18 -0
- data/lib/arel/errors.rb +9 -0
- data/lib/arel/expressions.rb +29 -0
- data/lib/arel/factory_methods.rb +49 -0
- data/lib/arel/insert_manager.rb +49 -0
- data/lib/arel/math.rb +45 -0
- data/lib/arel/nodes.rb +68 -0
- data/lib/arel/nodes/and.rb +32 -0
- data/lib/arel/nodes/ascending.rb +23 -0
- data/lib/arel/nodes/binary.rb +52 -0
- data/lib/arel/nodes/bind_param.rb +36 -0
- data/lib/arel/nodes/case.rb +55 -0
- data/lib/arel/nodes/casted.rb +50 -0
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/count.rb +12 -0
- data/lib/arel/nodes/delete_statement.rb +45 -0
- data/lib/arel/nodes/descending.rb +23 -0
- data/lib/arel/nodes/equality.rb +18 -0
- data/lib/arel/nodes/extract.rb +24 -0
- data/lib/arel/nodes/false.rb +16 -0
- data/lib/arel/nodes/full_outer_join.rb +8 -0
- data/lib/arel/nodes/function.rb +44 -0
- data/lib/arel/nodes/grouping.rb +8 -0
- data/lib/arel/nodes/in.rb +8 -0
- data/lib/arel/nodes/infix_operation.rb +80 -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/matches.rb +18 -0
- data/lib/arel/nodes/named_function.rb +23 -0
- data/lib/arel/nodes/node.rb +50 -0
- data/lib/arel/nodes/node_expression.rb +13 -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 +16 -0
- data/lib/arel/nodes/string_join.rb +11 -0
- data/lib/arel/nodes/table_alias.rb +27 -0
- data/lib/arel/nodes/terminal.rb +16 -0
- data/lib/arel/nodes/true.rb +16 -0
- data/lib/arel/nodes/unary.rb +45 -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 +41 -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/order_predications.rb +13 -0
- data/lib/arel/predications.rb +257 -0
- data/lib/arel/select_manager.rb +271 -0
- data/lib/arel/table.rb +110 -0
- data/lib/arel/tree_manager.rb +72 -0
- data/lib/arel/update_manager.rb +34 -0
- data/lib/arel/visitors.rb +20 -0
- data/lib/arel/visitors/depth_first.rb +204 -0
- data/lib/arel/visitors/dot.rb +297 -0
- data/lib/arel/visitors/ibm_db.rb +34 -0
- data/lib/arel/visitors/informix.rb +62 -0
- data/lib/arel/visitors/mssql.rb +157 -0
- data/lib/arel/visitors/mysql.rb +83 -0
- data/lib/arel/visitors/oracle.rb +159 -0
- data/lib/arel/visitors/oracle12.rb +66 -0
- data/lib/arel/visitors/postgresql.rb +110 -0
- data/lib/arel/visitors/sqlite.rb +39 -0
- data/lib/arel/visitors/to_sql.rb +889 -0
- data/lib/arel/visitors/visitor.rb +46 -0
- data/lib/arel/visitors/where_sql.rb +23 -0
- data/lib/arel/window_predications.rb +9 -0
- data/lib/rails/generators/active_record.rb +7 -5
- data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
- data/lib/rails/generators/active_record/migration.rb +31 -1
- data/lib/rails/generators/active_record/migration/migration_generator.rb +42 -37
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
- data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +11 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +19 -22
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +22 -0
- data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
- metadata +166 -58
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
- data/lib/active_record/associations/preloader/collection_association.rb +0 -24
- data/lib/active_record/associations/preloader/has_many.rb +0 -17
- data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
- data/lib/active_record/associations/preloader/has_one.rb +0 -23
- data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
- data/lib/active_record/associations/preloader/singular_association.rb +0 -21
- data/lib/active_record/attribute.rb +0 -163
- data/lib/active_record/attribute_set.rb +0 -81
- data/lib/active_record/attribute_set/builder.rb +0 -106
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -31
- data/lib/active_record/type/decimal.rb +0 -64
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -59
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -40
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/value.rb +0 -110
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +0 -19
- data/lib/rails/generators/active_record/model/templates/model.rb +0 -10
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
7
|
+
def references(*args, **options)
|
8
|
+
super(*args, type: :integer, **options)
|
9
|
+
end
|
10
|
+
alias :belongs_to :references
|
11
|
+
|
12
|
+
private
|
13
|
+
def integer_like_primary_key_type(type, options)
|
14
|
+
:primary_key
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc:
|
7
|
+
private
|
8
|
+
def default_primary_key?(column)
|
9
|
+
schema_type(column) == :integer
|
10
|
+
end
|
11
|
+
|
12
|
+
def explicit_primary_key_default?(column)
|
13
|
+
column.bigint?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
module SchemaStatements # :nodoc:
|
7
|
+
# Returns an array of indexes for the given table.
|
8
|
+
def indexes(table_name)
|
9
|
+
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|
|
10
|
+
# Indexes SQLite creates implicitly for internal use start with "sqlite_".
|
11
|
+
# See https://www.sqlite.org/fileformat2.html#intschema
|
12
|
+
next if row["name"].starts_with?("sqlite_")
|
13
|
+
|
14
|
+
index_sql = query_value(<<~SQL, "SCHEMA")
|
15
|
+
SELECT sql
|
16
|
+
FROM sqlite_master
|
17
|
+
WHERE name = #{quote(row['name'])} AND type = 'index'
|
18
|
+
UNION ALL
|
19
|
+
SELECT sql
|
20
|
+
FROM sqlite_temp_master
|
21
|
+
WHERE name = #{quote(row['name'])} AND type = 'index'
|
22
|
+
SQL
|
23
|
+
|
24
|
+
/\bON\b\s*"?(\w+?)"?\s*\((?<expressions>.+?)\)(?:\s*WHERE\b\s*(?<where>.+))?\z/i =~ index_sql
|
25
|
+
|
26
|
+
columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col|
|
27
|
+
col["name"]
|
28
|
+
end
|
29
|
+
|
30
|
+
orders = {}
|
31
|
+
|
32
|
+
if columns.any?(&:nil?) # index created with an expression
|
33
|
+
columns = expressions
|
34
|
+
else
|
35
|
+
# Add info on sort order for columns (only desc order is explicitly specified,
|
36
|
+
# asc is the default)
|
37
|
+
if index_sql # index_sql can be null in case of primary key indexes
|
38
|
+
index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
|
39
|
+
orders[order_column] = :desc
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
IndexDefinition.new(
|
45
|
+
table_name,
|
46
|
+
row["name"],
|
47
|
+
row["unique"] != 0,
|
48
|
+
columns,
|
49
|
+
where: where,
|
50
|
+
orders: orders
|
51
|
+
)
|
52
|
+
end.compact
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_foreign_key(from_table, to_table, **options)
|
56
|
+
alter_table(from_table) do |definition|
|
57
|
+
to_table = strip_table_name_prefix_and_suffix(to_table)
|
58
|
+
definition.foreign_key(to_table, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_foreign_key(from_table, to_table = nil, **options)
|
63
|
+
to_table ||= options[:to_table]
|
64
|
+
options = options.except(:name, :to_table)
|
65
|
+
foreign_keys = foreign_keys(from_table)
|
66
|
+
|
67
|
+
fkey = foreign_keys.detect do |fk|
|
68
|
+
table = to_table || begin
|
69
|
+
table = options[:column].to_s.delete_suffix("_id")
|
70
|
+
Base.pluralize_table_names ? table.pluralize : table
|
71
|
+
end
|
72
|
+
table = strip_table_name_prefix_and_suffix(table)
|
73
|
+
fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
74
|
+
fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
|
75
|
+
end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
|
76
|
+
|
77
|
+
foreign_keys.delete(fkey)
|
78
|
+
alter_table(from_table, foreign_keys)
|
79
|
+
end
|
80
|
+
|
81
|
+
def create_schema_dumper(options)
|
82
|
+
SQLite3::SchemaDumper.create(self, options)
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def schema_creation
|
87
|
+
SQLite3::SchemaCreation.new(self)
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_table_definition(*args)
|
91
|
+
SQLite3::TableDefinition.new(self, *args)
|
92
|
+
end
|
93
|
+
|
94
|
+
def new_column_from_field(table_name, field)
|
95
|
+
default = \
|
96
|
+
case field["dflt_value"]
|
97
|
+
when /^null$/i
|
98
|
+
nil
|
99
|
+
when /^'(.*)'$/m
|
100
|
+
$1.gsub("''", "'")
|
101
|
+
when /^"(.*)"$/m
|
102
|
+
$1.gsub('""', '"')
|
103
|
+
else
|
104
|
+
field["dflt_value"]
|
105
|
+
end
|
106
|
+
|
107
|
+
type_metadata = fetch_type_metadata(field["type"])
|
108
|
+
Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, collation: field["collation"])
|
109
|
+
end
|
110
|
+
|
111
|
+
def data_source_sql(name = nil, type: nil)
|
112
|
+
scope = quoted_scope(name, type: type)
|
113
|
+
scope[:type] ||= "'table','view'"
|
114
|
+
|
115
|
+
sql = +"SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'"
|
116
|
+
sql << " AND name = #{scope[:name]}" if scope[:name]
|
117
|
+
sql << " AND type IN (#{scope[:type]})"
|
118
|
+
sql
|
119
|
+
end
|
120
|
+
|
121
|
+
def quoted_scope(name = nil, type: nil)
|
122
|
+
type = \
|
123
|
+
case type
|
124
|
+
when "BASE TABLE"
|
125
|
+
"'table'"
|
126
|
+
when "VIEW"
|
127
|
+
"'view'"
|
128
|
+
end
|
129
|
+
scope = {}
|
130
|
+
scope[:name] = quote(name) if name
|
131
|
+
scope[:type] = type if type
|
132
|
+
scope
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -1,14 +1,23 @@
|
|
1
|
-
|
2
|
-
require 'active_record/connection_adapters/statement_pool'
|
3
|
-
require 'arel/visitors/bind_visitor'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
require
|
3
|
+
require "active_record/connection_adapters/abstract_adapter"
|
4
|
+
require "active_record/connection_adapters/statement_pool"
|
5
|
+
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
|
6
|
+
require "active_record/connection_adapters/sqlite3/quoting"
|
7
|
+
require "active_record/connection_adapters/sqlite3/database_statements"
|
8
|
+
require "active_record/connection_adapters/sqlite3/schema_creation"
|
9
|
+
require "active_record/connection_adapters/sqlite3/schema_definitions"
|
10
|
+
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
11
|
+
require "active_record/connection_adapters/sqlite3/schema_statements"
|
12
|
+
|
13
|
+
gem "sqlite3", "~> 1.4"
|
14
|
+
require "sqlite3"
|
7
15
|
|
8
16
|
module ActiveRecord
|
9
17
|
module ConnectionHandling # :nodoc:
|
10
|
-
# sqlite3 adapter reuses sqlite_connection.
|
11
18
|
def sqlite3_connection(config)
|
19
|
+
config = config.symbolize_keys
|
20
|
+
|
12
21
|
# Require database.
|
13
22
|
unless config[:database]
|
14
23
|
raise ArgumentError, "No database file specified. Missing argument: database"
|
@@ -17,7 +26,7 @@ module ActiveRecord
|
|
17
26
|
# Allow database path relative to Rails.root, but only if the database
|
18
27
|
# path is not the special path that tells sqlite to build a database only
|
19
28
|
# in memory.
|
20
|
-
if
|
29
|
+
if ":memory:" != config[:database]
|
21
30
|
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
|
22
31
|
dirname = File.dirname(config[:database])
|
23
32
|
Dir.mkdir(dirname) unless File.directory?(dirname)
|
@@ -25,15 +34,13 @@ module ActiveRecord
|
|
25
34
|
|
26
35
|
db = SQLite3::Database.new(
|
27
36
|
config[:database].to_s,
|
28
|
-
:
|
37
|
+
config.merge(results_as_hash: true)
|
29
38
|
)
|
30
39
|
|
31
|
-
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
|
32
|
-
|
33
40
|
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
|
34
41
|
rescue Errno::ENOENT => error
|
35
42
|
if error.message.include?("No such file or directory")
|
36
|
-
raise ActiveRecord::NoDatabaseError
|
43
|
+
raise ActiveRecord::NoDatabaseError
|
37
44
|
else
|
38
45
|
raise
|
39
46
|
end
|
@@ -41,15 +48,6 @@ module ActiveRecord
|
|
41
48
|
end
|
42
49
|
|
43
50
|
module ConnectionAdapters #:nodoc:
|
44
|
-
class SQLite3Binary < Type::Binary # :nodoc:
|
45
|
-
def cast_value(value)
|
46
|
-
if value.encoding != Encoding::ASCII_8BIT
|
47
|
-
value = value.force_encoding(Encoding::ASCII_8BIT)
|
48
|
-
end
|
49
|
-
value
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
51
|
# The SQLite3 adapter works SQLite 3.6.16 or newer
|
54
52
|
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
|
55
53
|
#
|
@@ -57,11 +55,14 @@ module ActiveRecord
|
|
57
55
|
#
|
58
56
|
# * <tt>:database</tt> - Path to the database file.
|
59
57
|
class SQLite3Adapter < AbstractAdapter
|
60
|
-
ADAPTER_NAME =
|
61
|
-
|
58
|
+
ADAPTER_NAME = "SQLite"
|
59
|
+
|
60
|
+
include SQLite3::Quoting
|
61
|
+
include SQLite3::SchemaStatements
|
62
|
+
include SQLite3::DatabaseStatements
|
62
63
|
|
63
64
|
NATIVE_DATABASE_TYPES = {
|
64
|
-
primary_key:
|
65
|
+
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
|
65
66
|
string: { name: "varchar" },
|
66
67
|
text: { name: "text" },
|
67
68
|
integer: { name: "integer" },
|
@@ -71,58 +72,39 @@ module ActiveRecord
|
|
71
72
|
time: { name: "time" },
|
72
73
|
date: { name: "date" },
|
73
74
|
binary: { name: "blob" },
|
74
|
-
boolean: { name: "boolean" }
|
75
|
+
boolean: { name: "boolean" },
|
76
|
+
json: { name: "json" },
|
75
77
|
}
|
76
78
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
@cache = Hash.new { |h,pid| h[pid] = {} }
|
81
|
-
end
|
82
|
-
|
83
|
-
def each(&block); cache.each(&block); end
|
84
|
-
def key?(key); cache.key?(key); end
|
85
|
-
def [](key); cache[key]; end
|
86
|
-
def length; cache.length; end
|
87
|
-
|
88
|
-
def []=(sql, key)
|
89
|
-
while @max <= cache.size
|
90
|
-
dealloc(cache.shift.last[:stmt])
|
91
|
-
end
|
92
|
-
cache[sql] = key
|
79
|
+
def self.represent_boolean_as_integer=(value) # :nodoc:
|
80
|
+
if value == false
|
81
|
+
raise "`.represent_boolean_as_integer=` is now always true, so make sure your application can work with it and remove this settings."
|
93
82
|
end
|
94
83
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
cache.clear
|
100
|
-
end
|
84
|
+
ActiveSupport::Deprecation.warn(
|
85
|
+
"`.represent_boolean_as_integer=` is now always true, so setting this is deprecated and will be removed in Rails 6.1."
|
86
|
+
)
|
87
|
+
end
|
101
88
|
|
89
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
102
90
|
private
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
def dealloc(stmt)
|
108
|
-
stmt.close unless stmt.closed?
|
109
|
-
end
|
91
|
+
def dealloc(stmt)
|
92
|
+
stmt.close unless stmt.closed?
|
93
|
+
end
|
110
94
|
end
|
111
95
|
|
112
96
|
def initialize(connection, logger, connection_options, config)
|
113
|
-
super(connection, logger)
|
114
|
-
|
115
|
-
|
116
|
-
@statements = StatementPool.new(@connection,
|
117
|
-
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
|
118
|
-
@config = config
|
119
|
-
|
120
|
-
@visitor = Arel::Visitors::SQLite.new self
|
97
|
+
super(connection, logger, config)
|
98
|
+
configure_connection
|
99
|
+
end
|
121
100
|
|
122
|
-
|
123
|
-
|
101
|
+
def self.database_exists?(config)
|
102
|
+
config = config.symbolize_keys
|
103
|
+
if config[:database] == ":memory:"
|
104
|
+
return true
|
124
105
|
else
|
125
|
-
|
106
|
+
database_file = defined?(Rails.root) ? File.expand_path(config[:database], Rails.root) : config[:database]
|
107
|
+
File.exist?(database_file)
|
126
108
|
end
|
127
109
|
end
|
128
110
|
|
@@ -135,55 +117,66 @@ module ActiveRecord
|
|
135
117
|
end
|
136
118
|
|
137
119
|
def supports_partial_index?
|
138
|
-
|
120
|
+
true
|
121
|
+
end
|
122
|
+
|
123
|
+
def supports_expression_index?
|
124
|
+
database_version >= "3.9.0"
|
139
125
|
end
|
140
126
|
|
141
|
-
|
142
|
-
# caching.
|
143
|
-
def supports_statement_cache?
|
127
|
+
def requires_reloading?
|
144
128
|
true
|
145
129
|
end
|
146
130
|
|
147
|
-
|
148
|
-
def supports_migrations? #:nodoc:
|
131
|
+
def supports_foreign_keys?
|
149
132
|
true
|
150
133
|
end
|
151
134
|
|
152
|
-
def
|
135
|
+
def supports_views?
|
153
136
|
true
|
154
137
|
end
|
155
138
|
|
156
|
-
def
|
139
|
+
def supports_datetime_with_precision?
|
157
140
|
true
|
158
141
|
end
|
159
142
|
|
160
|
-
def
|
143
|
+
def supports_json?
|
161
144
|
true
|
162
145
|
end
|
163
146
|
|
147
|
+
def supports_common_table_expressions?
|
148
|
+
database_version >= "3.8.3"
|
149
|
+
end
|
150
|
+
|
151
|
+
def supports_insert_on_conflict?
|
152
|
+
database_version >= "3.24.0"
|
153
|
+
end
|
154
|
+
alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
|
155
|
+
alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
|
156
|
+
alias supports_insert_conflict_target? supports_insert_on_conflict?
|
157
|
+
|
164
158
|
def active?
|
165
|
-
|
159
|
+
!@connection.closed?
|
160
|
+
end
|
161
|
+
|
162
|
+
def reconnect!
|
163
|
+
super
|
164
|
+
connect if @connection.closed?
|
166
165
|
end
|
167
166
|
|
168
167
|
# Disconnects from the database if already connected. Otherwise, this
|
169
168
|
# method does nothing.
|
170
169
|
def disconnect!
|
171
170
|
super
|
172
|
-
@active = false
|
173
171
|
@connection.close rescue nil
|
174
172
|
end
|
175
173
|
|
176
|
-
# Clears the prepared statements cache.
|
177
|
-
def clear_cache!
|
178
|
-
@statements.clear
|
179
|
-
end
|
180
|
-
|
181
174
|
def supports_index_sort_order?
|
182
175
|
true
|
183
176
|
end
|
184
177
|
|
185
178
|
# Returns 62. SQLite supports index names up to 64
|
186
|
-
# characters. The rest is used by
|
179
|
+
# characters. The rest is used by Rails internally to perform
|
187
180
|
# temporary rename operations
|
188
181
|
def allowed_index_name_length
|
189
182
|
index_name_length - 2
|
@@ -202,224 +195,43 @@ module ActiveRecord
|
|
202
195
|
true
|
203
196
|
end
|
204
197
|
|
205
|
-
|
206
|
-
|
207
|
-
def _quote(value) # :nodoc:
|
208
|
-
case value
|
209
|
-
when Type::Binary::Data
|
210
|
-
"x'#{value.hex}'"
|
211
|
-
else
|
212
|
-
super
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
def _type_cast(value) # :nodoc:
|
217
|
-
case value
|
218
|
-
when BigDecimal
|
219
|
-
value.to_f
|
220
|
-
when String
|
221
|
-
if value.encoding == Encoding::ASCII_8BIT
|
222
|
-
super(value.encode(Encoding::UTF_8))
|
223
|
-
else
|
224
|
-
super
|
225
|
-
end
|
226
|
-
else
|
227
|
-
super
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def quote_string(s) #:nodoc:
|
232
|
-
@connection.class.quote(s)
|
198
|
+
def supports_lazy_transactions?
|
199
|
+
true
|
233
200
|
end
|
234
201
|
|
235
|
-
|
236
|
-
quote_column_name(attr)
|
237
|
-
end
|
202
|
+
# REFERENTIAL INTEGRITY ====================================
|
238
203
|
|
239
|
-
def
|
240
|
-
|
241
|
-
|
204
|
+
def disable_referential_integrity # :nodoc:
|
205
|
+
old_foreign_keys = query_value("PRAGMA foreign_keys")
|
206
|
+
old_defer_foreign_keys = query_value("PRAGMA defer_foreign_keys")
|
242
207
|
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
208
|
+
begin
|
209
|
+
execute("PRAGMA defer_foreign_keys = ON")
|
210
|
+
execute("PRAGMA foreign_keys = OFF")
|
211
|
+
yield
|
212
|
+
ensure
|
213
|
+
execute("PRAGMA defer_foreign_keys = #{old_defer_foreign_keys}")
|
214
|
+
execute("PRAGMA foreign_keys = #{old_foreign_keys}")
|
250
215
|
end
|
251
216
|
end
|
252
217
|
|
253
218
|
#--
|
254
219
|
# DATABASE STATEMENTS ======================================
|
255
220
|
#++
|
256
|
-
|
257
221
|
def explain(arel, binds = [])
|
258
222
|
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
259
|
-
ExplainPrettyPrinter.new.pp(exec_query(sql,
|
260
|
-
end
|
261
|
-
|
262
|
-
class ExplainPrettyPrinter
|
263
|
-
# Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
|
264
|
-
# the output of the SQLite shell:
|
265
|
-
#
|
266
|
-
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
|
267
|
-
# 0|1|1|SCAN TABLE posts (~100000 rows)
|
268
|
-
#
|
269
|
-
def pp(result) # :nodoc:
|
270
|
-
result.rows.map do |row|
|
271
|
-
row.join('|')
|
272
|
-
end.join("\n") + "\n"
|
273
|
-
end
|
274
|
-
end
|
275
|
-
|
276
|
-
def exec_query(sql, name = nil, binds = [])
|
277
|
-
type_casted_binds = binds.map { |col, val|
|
278
|
-
[col, type_cast(val, col)]
|
279
|
-
}
|
280
|
-
|
281
|
-
log(sql, name, type_casted_binds) do
|
282
|
-
# Don't cache statements if they are not prepared
|
283
|
-
if without_prepared_statement?(binds)
|
284
|
-
stmt = @connection.prepare(sql)
|
285
|
-
begin
|
286
|
-
cols = stmt.columns
|
287
|
-
records = stmt.to_a
|
288
|
-
ensure
|
289
|
-
stmt.close
|
290
|
-
end
|
291
|
-
stmt = records
|
292
|
-
else
|
293
|
-
cache = @statements[sql] ||= {
|
294
|
-
:stmt => @connection.prepare(sql)
|
295
|
-
}
|
296
|
-
stmt = cache[:stmt]
|
297
|
-
cols = cache[:cols] ||= stmt.columns
|
298
|
-
stmt.reset!
|
299
|
-
stmt.bind_params type_casted_binds.map { |_, val| val }
|
300
|
-
end
|
301
|
-
|
302
|
-
ActiveRecord::Result.new(cols, stmt.to_a)
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
def exec_delete(sql, name = 'SQL', binds = [])
|
307
|
-
exec_query(sql, name, binds)
|
308
|
-
@connection.changes
|
309
|
-
end
|
310
|
-
alias :exec_update :exec_delete
|
311
|
-
|
312
|
-
def last_inserted_id(result)
|
313
|
-
@connection.last_insert_row_id
|
314
|
-
end
|
315
|
-
|
316
|
-
def execute(sql, name = nil) #:nodoc:
|
317
|
-
log(sql, name) { @connection.execute(sql) }
|
318
|
-
end
|
319
|
-
|
320
|
-
def update_sql(sql, name = nil) #:nodoc:
|
321
|
-
super
|
322
|
-
@connection.changes
|
323
|
-
end
|
324
|
-
|
325
|
-
def delete_sql(sql, name = nil) #:nodoc:
|
326
|
-
sql += " WHERE 1=1" unless sql =~ /WHERE/i
|
327
|
-
super sql, name
|
328
|
-
end
|
329
|
-
|
330
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
331
|
-
super
|
332
|
-
id_value || @connection.last_insert_row_id
|
333
|
-
end
|
334
|
-
alias :create :insert_sql
|
335
|
-
|
336
|
-
def select_rows(sql, name = nil, binds = [])
|
337
|
-
exec_query(sql, name, binds).rows
|
338
|
-
end
|
339
|
-
|
340
|
-
def begin_db_transaction #:nodoc:
|
341
|
-
log('begin transaction',nil) { @connection.transaction }
|
342
|
-
end
|
343
|
-
|
344
|
-
def commit_db_transaction #:nodoc:
|
345
|
-
log('commit transaction',nil) { @connection.commit }
|
346
|
-
end
|
347
|
-
|
348
|
-
def exec_rollback_db_transaction #:nodoc:
|
349
|
-
log('rollback transaction',nil) { @connection.rollback }
|
223
|
+
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
|
350
224
|
end
|
351
225
|
|
352
226
|
# SCHEMA STATEMENTS ========================================
|
353
227
|
|
354
|
-
def
|
355
|
-
|
356
|
-
|
357
|
-
FROM sqlite_master
|
358
|
-
WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence'
|
359
|
-
SQL
|
360
|
-
sql << " AND name = #{quote_table_name(table_name)}" if table_name
|
361
|
-
|
362
|
-
exec_query(sql, 'SCHEMA').map do |row|
|
363
|
-
row['name']
|
364
|
-
end
|
365
|
-
end
|
366
|
-
alias data_sources tables
|
367
|
-
|
368
|
-
def table_exists?(table_name)
|
369
|
-
table_name && tables(nil, table_name).any?
|
370
|
-
end
|
371
|
-
alias data_source_exists? table_exists?
|
372
|
-
|
373
|
-
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
374
|
-
def columns(table_name) #:nodoc:
|
375
|
-
table_structure(table_name).map do |field|
|
376
|
-
case field["dflt_value"]
|
377
|
-
when /^null$/i
|
378
|
-
field["dflt_value"] = nil
|
379
|
-
when /^'(.*)'$/m
|
380
|
-
field["dflt_value"] = $1.gsub("''", "'")
|
381
|
-
when /^"(.*)"$/m
|
382
|
-
field["dflt_value"] = $1.gsub('""', '"')
|
383
|
-
end
|
384
|
-
|
385
|
-
sql_type = field['type']
|
386
|
-
cast_type = lookup_cast_type(sql_type)
|
387
|
-
new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
# Returns an array of indexes for the given table.
|
392
|
-
def indexes(table_name, name = nil) #:nodoc:
|
393
|
-
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
|
394
|
-
sql = <<-SQL
|
395
|
-
SELECT sql
|
396
|
-
FROM sqlite_master
|
397
|
-
WHERE name=#{quote(row['name'])} AND type='index'
|
398
|
-
UNION ALL
|
399
|
-
SELECT sql
|
400
|
-
FROM sqlite_temp_master
|
401
|
-
WHERE name=#{quote(row['name'])} AND type='index'
|
402
|
-
SQL
|
403
|
-
index_sql = exec_query(sql).first['sql']
|
404
|
-
match = /\sWHERE\s+(.+)$/i.match(index_sql)
|
405
|
-
where = match[1] if match
|
406
|
-
IndexDefinition.new(
|
407
|
-
table_name,
|
408
|
-
row['name'],
|
409
|
-
row['unique'] != 0,
|
410
|
-
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
|
411
|
-
col['name']
|
412
|
-
}, nil, nil, where)
|
413
|
-
end
|
228
|
+
def primary_keys(table_name) # :nodoc:
|
229
|
+
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
|
230
|
+
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
|
414
231
|
end
|
415
232
|
|
416
|
-
def
|
417
|
-
|
418
|
-
return nil unless pks.count == 1
|
419
|
-
pks[0]['name']
|
420
|
-
end
|
421
|
-
|
422
|
-
def remove_index!(table_name, index_name) #:nodoc:
|
233
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
234
|
+
index_name = index_name_for_remove(table_name, options)
|
423
235
|
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
424
236
|
end
|
425
237
|
|
@@ -432,35 +244,34 @@ module ActiveRecord
|
|
432
244
|
rename_table_indexes(table_name, new_name)
|
433
245
|
end
|
434
246
|
|
435
|
-
# See: http://www.sqlite.org/lang_altertable.html
|
436
|
-
# SQLite has an additional restriction on the ALTER TABLE statement
|
437
|
-
def valid_alter_table_type?(type)
|
438
|
-
type.to_sym != :primary_key
|
439
|
-
end
|
440
|
-
|
441
247
|
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
442
|
-
if
|
443
|
-
super(table_name, column_name, type, options)
|
444
|
-
else
|
248
|
+
if invalid_alter_table_type?(type, options)
|
445
249
|
alter_table(table_name) do |definition|
|
446
250
|
definition.column(column_name, type, options)
|
447
251
|
end
|
252
|
+
else
|
253
|
+
super
|
448
254
|
end
|
449
255
|
end
|
450
256
|
|
451
257
|
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
452
258
|
alter_table(table_name) do |definition|
|
453
259
|
definition.remove_column column_name
|
260
|
+
definition.foreign_keys.delete_if do |_, fk_options|
|
261
|
+
fk_options[:column] == column_name.to_s
|
262
|
+
end
|
454
263
|
end
|
455
264
|
end
|
456
265
|
|
457
|
-
def change_column_default(table_name, column_name,
|
266
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
267
|
+
default = extract_new_default_value(default_or_changes)
|
268
|
+
|
458
269
|
alter_table(table_name) do |definition|
|
459
270
|
definition[column_name].default = default
|
460
271
|
end
|
461
272
|
end
|
462
273
|
|
463
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
274
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
464
275
|
unless null || default.nil?
|
465
276
|
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
466
277
|
end
|
@@ -471,80 +282,150 @@ module ActiveRecord
|
|
471
282
|
|
472
283
|
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
473
284
|
alter_table(table_name) do |definition|
|
474
|
-
include_default = options_include_default?(options)
|
475
285
|
definition[column_name].instance_eval do
|
476
286
|
self.type = type
|
477
287
|
self.limit = options[:limit] if options.include?(:limit)
|
478
|
-
self.default = options[:default] if
|
288
|
+
self.default = options[:default] if options.include?(:default)
|
479
289
|
self.null = options[:null] if options.include?(:null)
|
480
290
|
self.precision = options[:precision] if options.include?(:precision)
|
481
|
-
self.scale
|
291
|
+
self.scale = options[:scale] if options.include?(:scale)
|
292
|
+
self.collation = options[:collation] if options.include?(:collation)
|
482
293
|
end
|
483
294
|
end
|
484
295
|
end
|
485
296
|
|
486
297
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
487
298
|
column = column_for(table_name, column_name)
|
488
|
-
alter_table(table_name, rename: {column.name => new_column_name.to_s})
|
299
|
+
alter_table(table_name, rename: { column.name => new_column_name.to_s })
|
489
300
|
rename_column_indexes(table_name, column.name, new_column_name)
|
490
301
|
end
|
491
302
|
|
492
|
-
|
303
|
+
def add_reference(table_name, ref_name, **options) # :nodoc:
|
304
|
+
super(table_name, ref_name, type: :integer, **options)
|
305
|
+
end
|
306
|
+
alias :add_belongs_to :add_reference
|
493
307
|
|
494
|
-
|
308
|
+
def foreign_keys(table_name)
|
309
|
+
fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
310
|
+
fk_info.map do |row|
|
311
|
+
options = {
|
312
|
+
column: row["from"],
|
313
|
+
primary_key: row["to"],
|
314
|
+
on_delete: extract_foreign_key_action(row["on_delete"]),
|
315
|
+
on_update: extract_foreign_key_action(row["on_update"])
|
316
|
+
}
|
317
|
+
ForeignKeyDefinition.new(table_name, row["table"], options)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def build_insert_sql(insert) # :nodoc:
|
322
|
+
sql = +"INSERT #{insert.into} #{insert.values_list}"
|
323
|
+
|
324
|
+
if insert.skip_duplicates?
|
325
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
|
326
|
+
elsif insert.update_duplicates?
|
327
|
+
sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
|
328
|
+
sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
|
329
|
+
end
|
330
|
+
|
331
|
+
sql
|
332
|
+
end
|
333
|
+
|
334
|
+
def get_database_version # :nodoc:
|
335
|
+
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
336
|
+
end
|
337
|
+
|
338
|
+
def check_version # :nodoc:
|
339
|
+
if database_version < "3.8.0"
|
340
|
+
raise "Your version of SQLite (#{database_version}) is too old. Active Record supports SQLite >= 3.8."
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
private
|
345
|
+
# See https://www.sqlite.org/limits.html,
|
346
|
+
# the default value is 999 when not configured.
|
347
|
+
def bind_params_length
|
348
|
+
999
|
349
|
+
end
|
350
|
+
|
351
|
+
def initialize_type_map(m = type_map)
|
495
352
|
super
|
496
|
-
m
|
353
|
+
register_class_with_limit m, %r(int)i, SQLite3Integer
|
497
354
|
end
|
498
355
|
|
499
356
|
def table_structure(table_name)
|
500
|
-
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})",
|
357
|
+
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
501
358
|
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
502
|
-
structure
|
359
|
+
table_structure_with_collation(table_name, structure)
|
360
|
+
end
|
361
|
+
alias column_definitions table_structure
|
362
|
+
|
363
|
+
# See: https://www.sqlite.org/lang_altertable.html
|
364
|
+
# SQLite has an additional restriction on the ALTER TABLE statement
|
365
|
+
def invalid_alter_table_type?(type, options)
|
366
|
+
type.to_sym == :primary_key || options[:primary_key]
|
503
367
|
end
|
504
368
|
|
505
|
-
def alter_table(table_name,
|
369
|
+
def alter_table(table_name, foreign_keys = foreign_keys(table_name), **options)
|
506
370
|
altered_table_name = "a#{table_name}"
|
507
|
-
|
371
|
+
|
372
|
+
caller = lambda do |definition|
|
373
|
+
rename = options[:rename] || {}
|
374
|
+
foreign_keys.each do |fk|
|
375
|
+
if column = rename[fk.options[:column]]
|
376
|
+
fk.options[:column] = column
|
377
|
+
end
|
378
|
+
to_table = strip_table_name_prefix_and_suffix(fk.to_table)
|
379
|
+
definition.foreign_key(to_table, fk.options)
|
380
|
+
end
|
381
|
+
|
382
|
+
yield definition if block_given?
|
383
|
+
end
|
508
384
|
|
509
385
|
transaction do
|
510
|
-
|
511
|
-
options.merge(:
|
512
|
-
|
386
|
+
disable_referential_integrity do
|
387
|
+
move_table(table_name, altered_table_name, options.merge(temporary: true))
|
388
|
+
move_table(altered_table_name, table_name, &caller)
|
389
|
+
end
|
513
390
|
end
|
514
391
|
end
|
515
392
|
|
516
|
-
def move_table(from, to, options = {}, &block)
|
393
|
+
def move_table(from, to, options = {}, &block)
|
517
394
|
copy_table(from, to, options, &block)
|
518
395
|
drop_table(from)
|
519
396
|
end
|
520
397
|
|
521
|
-
def copy_table(from, to, options = {})
|
398
|
+
def copy_table(from, to, options = {})
|
522
399
|
from_primary_key = primary_key(from)
|
523
400
|
options[:id] = false
|
524
401
|
create_table(to, options) do |definition|
|
525
402
|
@definition = definition
|
526
|
-
|
403
|
+
if from_primary_key.is_a?(Array)
|
404
|
+
@definition.primary_keys from_primary_key
|
405
|
+
end
|
527
406
|
columns(from).each do |column|
|
528
407
|
column_name = options[:rename] ?
|
529
408
|
(options[:rename][column.name] ||
|
530
409
|
options[:rename][column.name.to_sym] ||
|
531
410
|
column.name) : column.name
|
532
|
-
next if column_name == from_primary_key
|
533
411
|
|
534
412
|
@definition.column(column_name, column.type,
|
535
|
-
:
|
536
|
-
:
|
537
|
-
:null
|
413
|
+
limit: column.limit, default: column.default,
|
414
|
+
precision: column.precision, scale: column.scale,
|
415
|
+
null: column.null, collation: column.collation,
|
416
|
+
primary_key: column_name == from_primary_key
|
417
|
+
)
|
538
418
|
end
|
419
|
+
|
539
420
|
yield @definition if block_given?
|
540
421
|
end
|
541
422
|
copy_table_indexes(from, to, options[:rename] || {})
|
542
423
|
copy_table_contents(from, to,
|
543
|
-
@definition.columns.map
|
424
|
+
@definition.columns.map(&:name),
|
544
425
|
options[:rename] || {})
|
545
426
|
end
|
546
427
|
|
547
|
-
def copy_table_indexes(from, to, rename = {})
|
428
|
+
def copy_table_indexes(from, to, rename = {})
|
548
429
|
indexes(from).each do |index|
|
549
430
|
name = index.name
|
550
431
|
if to == "a#{from}"
|
@@ -553,60 +434,128 @@ module ActiveRecord
|
|
553
434
|
name = name[1..-1]
|
554
435
|
end
|
555
436
|
|
556
|
-
|
557
|
-
|
558
|
-
to_column_names.
|
437
|
+
columns = index.columns
|
438
|
+
if columns.is_a?(Array)
|
439
|
+
to_column_names = columns(to).map(&:name)
|
440
|
+
columns = columns.map { |c| rename[c] || c }.select do |column|
|
441
|
+
to_column_names.include?(column)
|
442
|
+
end
|
559
443
|
end
|
560
444
|
|
561
445
|
unless columns.empty?
|
562
446
|
# index name can't be the same
|
563
447
|
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
|
564
448
|
opts[:unique] = true if index.unique
|
449
|
+
opts[:where] = index.where if index.where
|
565
450
|
add_index(to, columns, opts)
|
566
451
|
end
|
567
452
|
end
|
568
453
|
end
|
569
454
|
|
570
|
-
def copy_table_contents(from, to, columns, rename = {})
|
571
|
-
column_mappings = Hash[columns.map {|name| [name, name]}]
|
455
|
+
def copy_table_contents(from, to, columns, rename = {})
|
456
|
+
column_mappings = Hash[columns.map { |name| [name, name] }]
|
572
457
|
rename.each { |a| column_mappings[a.last] = a.first }
|
573
|
-
from_columns = columns(from).collect
|
574
|
-
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
|
582
|
-
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
|
583
|
-
|
584
|
-
column_values = columns.map do |col|
|
585
|
-
quote(row[column_mappings[col]], raw_column_mappings[col])
|
586
|
-
end
|
587
|
-
|
588
|
-
sql << column_values * ', '
|
589
|
-
sql << ')'
|
590
|
-
exec_query sql
|
591
|
-
end
|
458
|
+
from_columns = columns(from).collect(&:name)
|
459
|
+
columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
|
460
|
+
from_columns_to_copy = columns.map { |col| column_mappings[col] }
|
461
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
462
|
+
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
463
|
+
|
464
|
+
exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
465
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
592
466
|
end
|
593
467
|
|
594
|
-
def
|
595
|
-
@sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
|
596
|
-
end
|
597
|
-
|
598
|
-
def translate_exception(exception, message)
|
468
|
+
def translate_exception(exception, message:, sql:, binds:)
|
599
469
|
case exception.message
|
600
470
|
# SQLite 3.8.2 returns a newly formatted error message:
|
601
471
|
# UNIQUE constraint failed: *table_name*.*column_name*
|
602
472
|
# Older versions of SQLite return:
|
603
473
|
# column *column_name* is not unique
|
604
474
|
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
|
605
|
-
RecordNotUnique.new(message,
|
475
|
+
RecordNotUnique.new(message, sql: sql, binds: binds)
|
476
|
+
when /.* may not be NULL/, /NOT NULL constraint failed: .*/
|
477
|
+
NotNullViolation.new(message, sql: sql, binds: binds)
|
478
|
+
when /FOREIGN KEY constraint failed/i
|
479
|
+
InvalidForeignKey.new(message, sql: sql, binds: binds)
|
606
480
|
else
|
607
481
|
super
|
608
482
|
end
|
609
483
|
end
|
484
|
+
|
485
|
+
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
|
486
|
+
|
487
|
+
def table_structure_with_collation(table_name, basic_structure)
|
488
|
+
collation_hash = {}
|
489
|
+
sql = <<~SQL
|
490
|
+
SELECT sql FROM
|
491
|
+
(SELECT * FROM sqlite_master UNION ALL
|
492
|
+
SELECT * FROM sqlite_temp_master)
|
493
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
494
|
+
SQL
|
495
|
+
|
496
|
+
# Result will have following sample string
|
497
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
498
|
+
# "password_digest" varchar COLLATE "NOCASE");
|
499
|
+
result = exec_query(sql, "SCHEMA").first
|
500
|
+
|
501
|
+
if result
|
502
|
+
# Splitting with left parentheses and discarding the first part will return all
|
503
|
+
# columns separated with comma(,).
|
504
|
+
columns_string = result["sql"].split("(", 2).last
|
505
|
+
|
506
|
+
columns_string.split(",").each do |column_string|
|
507
|
+
# This regex will match the column name and collation type and will save
|
508
|
+
# the value in $1 and $2 respectively.
|
509
|
+
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
510
|
+
end
|
511
|
+
|
512
|
+
basic_structure.map! do |column|
|
513
|
+
column_name = column["name"]
|
514
|
+
|
515
|
+
if collation_hash.has_key? column_name
|
516
|
+
column["collation"] = collation_hash[column_name]
|
517
|
+
end
|
518
|
+
|
519
|
+
column
|
520
|
+
end
|
521
|
+
else
|
522
|
+
basic_structure.to_a
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
def arel_visitor
|
527
|
+
Arel::Visitors::SQLite.new(self)
|
528
|
+
end
|
529
|
+
|
530
|
+
def build_statement_pool
|
531
|
+
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
|
532
|
+
end
|
533
|
+
|
534
|
+
def connect
|
535
|
+
@connection = ::SQLite3::Database.new(
|
536
|
+
@config[:database].to_s,
|
537
|
+
@config.merge(results_as_hash: true)
|
538
|
+
)
|
539
|
+
configure_connection
|
540
|
+
end
|
541
|
+
|
542
|
+
def configure_connection
|
543
|
+
@connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
|
544
|
+
|
545
|
+
execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
546
|
+
end
|
547
|
+
|
548
|
+
class SQLite3Integer < Type::Integer # :nodoc:
|
549
|
+
private
|
550
|
+
def _limit
|
551
|
+
# INTEGER storage class can be stored 8 bytes value.
|
552
|
+
# See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
|
553
|
+
limit || 8
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
|
610
558
|
end
|
559
|
+
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
611
560
|
end
|
612
561
|
end
|