activerecord 3.2.19 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,55 +1,569 @@
|
|
1
|
-
require 'active_record/connection_adapters/
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/statement_pool'
|
3
|
+
require 'active_record/connection_adapters/sqlite3/explain_pretty_printer'
|
4
|
+
require 'active_record/connection_adapters/sqlite3/quoting'
|
5
|
+
require 'active_record/connection_adapters/sqlite3/schema_creation'
|
2
6
|
|
3
|
-
gem 'sqlite3', '~> 1.3.
|
7
|
+
gem 'sqlite3', '~> 1.3.6'
|
4
8
|
require 'sqlite3'
|
5
9
|
|
6
10
|
module ActiveRecord
|
7
|
-
|
8
|
-
|
9
|
-
def self.sqlite3_connection(config) # :nodoc:
|
11
|
+
module ConnectionHandling # :nodoc:
|
12
|
+
def sqlite3_connection(config)
|
10
13
|
# Require database.
|
11
14
|
unless config[:database]
|
12
15
|
raise ArgumentError, "No database file specified. Missing argument: database"
|
13
16
|
end
|
14
17
|
|
15
|
-
# Allow database path relative to Rails.root, but only if
|
16
|
-
#
|
17
|
-
#
|
18
|
-
if
|
19
|
-
config[:database] = File.expand_path(config[:database], Rails.root)
|
20
|
-
|
21
|
-
|
22
|
-
unless 'sqlite3' == config[:adapter]
|
23
|
-
raise ArgumentError, 'adapter name should be "sqlite3"'
|
18
|
+
# Allow database path relative to Rails.root, but only if the database
|
19
|
+
# path is not the special path that tells sqlite to build a database only
|
20
|
+
# in memory.
|
21
|
+
if ':memory:' != config[:database]
|
22
|
+
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
|
23
|
+
dirname = File.dirname(config[:database])
|
24
|
+
Dir.mkdir(dirname) unless File.directory?(dirname)
|
24
25
|
end
|
25
26
|
|
26
27
|
db = SQLite3::Database.new(
|
27
|
-
config[:database],
|
28
|
+
config[:database].to_s,
|
28
29
|
:results_as_hash => true
|
29
30
|
)
|
30
31
|
|
31
|
-
db.busy_timeout(config[:timeout]) if config[:timeout]
|
32
|
+
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
|
32
33
|
|
33
|
-
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
|
34
|
+
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
|
35
|
+
rescue Errno::ENOENT => error
|
36
|
+
if error.message.include?("No such file or directory")
|
37
|
+
raise ActiveRecord::NoDatabaseError
|
38
|
+
else
|
39
|
+
raise
|
40
|
+
end
|
34
41
|
end
|
35
42
|
end
|
36
43
|
|
37
44
|
module ConnectionAdapters #:nodoc:
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
# The SQLite3 adapter works SQLite 3.6.16 or newer
|
46
|
+
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
|
47
|
+
#
|
48
|
+
# Options:
|
49
|
+
#
|
50
|
+
# * <tt>:database</tt> - Path to the database file.
|
51
|
+
class SQLite3Adapter < AbstractAdapter
|
52
|
+
ADAPTER_NAME = 'SQLite'.freeze
|
53
|
+
|
54
|
+
include SQLite3::Quoting
|
55
|
+
|
56
|
+
NATIVE_DATABASE_TYPES = {
|
57
|
+
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
|
58
|
+
string: { name: "varchar" },
|
59
|
+
text: { name: "text" },
|
60
|
+
integer: { name: "integer" },
|
61
|
+
float: { name: "float" },
|
62
|
+
decimal: { name: "decimal" },
|
63
|
+
datetime: { name: "datetime" },
|
64
|
+
time: { name: "time" },
|
65
|
+
date: { name: "date" },
|
66
|
+
binary: { name: "blob" },
|
67
|
+
boolean: { name: "boolean" }
|
68
|
+
}
|
69
|
+
|
70
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
71
|
+
private
|
72
|
+
|
73
|
+
def dealloc(stmt)
|
74
|
+
stmt[:stmt].close unless stmt[:stmt].closed?
|
45
75
|
end
|
46
76
|
end
|
47
77
|
|
78
|
+
def schema_creation # :nodoc:
|
79
|
+
SQLite3::SchemaCreation.new self
|
80
|
+
end
|
81
|
+
|
82
|
+
def arel_visitor # :nodoc:
|
83
|
+
Arel::Visitors::SQLite.new(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize(connection, logger, connection_options, config)
|
87
|
+
super(connection, logger, config)
|
88
|
+
|
89
|
+
@active = nil
|
90
|
+
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
91
|
+
end
|
92
|
+
|
93
|
+
def supports_ddl_transactions?
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
def supports_savepoints?
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def supports_partial_index?
|
102
|
+
sqlite_version >= '3.8.0'
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns true, since this connection adapter supports prepared statement
|
106
|
+
# caching.
|
107
|
+
def supports_statement_cache?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns true, since this connection adapter supports migrations.
|
112
|
+
def supports_migrations? #:nodoc:
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def supports_primary_key? #:nodoc:
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
def requires_reloading?
|
121
|
+
true
|
122
|
+
end
|
123
|
+
|
124
|
+
def supports_views?
|
125
|
+
true
|
126
|
+
end
|
127
|
+
|
128
|
+
def supports_datetime_with_precision?
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
132
|
+
def supports_multi_insert?
|
133
|
+
sqlite_version >= '3.7.11'
|
134
|
+
end
|
135
|
+
|
136
|
+
def active?
|
137
|
+
@active != false
|
138
|
+
end
|
139
|
+
|
140
|
+
# Disconnects from the database if already connected. Otherwise, this
|
141
|
+
# method does nothing.
|
142
|
+
def disconnect!
|
143
|
+
super
|
144
|
+
@active = false
|
145
|
+
@connection.close rescue nil
|
146
|
+
end
|
147
|
+
|
148
|
+
# Clears the prepared statements cache.
|
149
|
+
def clear_cache!
|
150
|
+
@statements.clear
|
151
|
+
end
|
152
|
+
|
153
|
+
def supports_index_sort_order?
|
154
|
+
true
|
155
|
+
end
|
156
|
+
|
157
|
+
def valid_type?(type)
|
158
|
+
true
|
159
|
+
end
|
160
|
+
|
161
|
+
# Returns 62. SQLite supports index names up to 64
|
162
|
+
# characters. The rest is used by rails internally to perform
|
163
|
+
# temporary rename operations
|
164
|
+
def allowed_index_name_length
|
165
|
+
index_name_length - 2
|
166
|
+
end
|
167
|
+
|
168
|
+
def native_database_types #:nodoc:
|
169
|
+
NATIVE_DATABASE_TYPES
|
170
|
+
end
|
171
|
+
|
48
172
|
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
49
173
|
def encoding
|
50
174
|
@connection.encoding.to_s
|
51
175
|
end
|
52
176
|
|
177
|
+
def supports_explain?
|
178
|
+
true
|
179
|
+
end
|
180
|
+
|
181
|
+
#--
|
182
|
+
# DATABASE STATEMENTS ======================================
|
183
|
+
#++
|
184
|
+
|
185
|
+
def explain(arel, binds = [])
|
186
|
+
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
187
|
+
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
|
188
|
+
end
|
189
|
+
|
190
|
+
def exec_query(sql, name = nil, binds = [], prepare: false)
|
191
|
+
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
|
192
|
+
|
193
|
+
log(sql, name, binds) do
|
194
|
+
# Don't cache statements if they are not prepared
|
195
|
+
unless prepare
|
196
|
+
stmt = @connection.prepare(sql)
|
197
|
+
begin
|
198
|
+
cols = stmt.columns
|
199
|
+
unless without_prepared_statement?(binds)
|
200
|
+
stmt.bind_params(type_casted_binds)
|
201
|
+
end
|
202
|
+
records = stmt.to_a
|
203
|
+
ensure
|
204
|
+
stmt.close
|
205
|
+
end
|
206
|
+
stmt = records
|
207
|
+
else
|
208
|
+
cache = @statements[sql] ||= {
|
209
|
+
:stmt => @connection.prepare(sql)
|
210
|
+
}
|
211
|
+
stmt = cache[:stmt]
|
212
|
+
cols = cache[:cols] ||= stmt.columns
|
213
|
+
stmt.reset!
|
214
|
+
stmt.bind_params(type_casted_binds)
|
215
|
+
end
|
216
|
+
|
217
|
+
ActiveRecord::Result.new(cols, stmt.to_a)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def exec_delete(sql, name = 'SQL', binds = [])
|
222
|
+
exec_query(sql, name, binds)
|
223
|
+
@connection.changes
|
224
|
+
end
|
225
|
+
alias :exec_update :exec_delete
|
226
|
+
|
227
|
+
def last_inserted_id(result)
|
228
|
+
@connection.last_insert_row_id
|
229
|
+
end
|
230
|
+
|
231
|
+
def execute(sql, name = nil) #:nodoc:
|
232
|
+
log(sql, name) { @connection.execute(sql) }
|
233
|
+
end
|
234
|
+
|
235
|
+
def begin_db_transaction #:nodoc:
|
236
|
+
log('begin transaction',nil) { @connection.transaction }
|
237
|
+
end
|
238
|
+
|
239
|
+
def commit_db_transaction #:nodoc:
|
240
|
+
log('commit transaction',nil) { @connection.commit }
|
241
|
+
end
|
242
|
+
|
243
|
+
def exec_rollback_db_transaction #:nodoc:
|
244
|
+
log('rollback transaction',nil) { @connection.rollback }
|
245
|
+
end
|
246
|
+
|
247
|
+
# SCHEMA STATEMENTS ========================================
|
248
|
+
|
249
|
+
def tables(name = nil) # :nodoc:
|
250
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
251
|
+
#tables currently returns both tables and views.
|
252
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only return tables.
|
253
|
+
Use #data_sources instead.
|
254
|
+
MSG
|
255
|
+
|
256
|
+
if name
|
257
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
258
|
+
Passing arguments to #tables is deprecated without replacement.
|
259
|
+
MSG
|
260
|
+
end
|
261
|
+
|
262
|
+
data_sources
|
263
|
+
end
|
264
|
+
|
265
|
+
def data_sources
|
266
|
+
select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA')
|
267
|
+
end
|
268
|
+
|
269
|
+
def table_exists?(table_name)
|
270
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
271
|
+
#table_exists? currently checks both tables and views.
|
272
|
+
This behavior is deprecated and will be changed with Rails 5.1 to only check tables.
|
273
|
+
Use #data_source_exists? instead.
|
274
|
+
MSG
|
275
|
+
|
276
|
+
data_source_exists?(table_name)
|
277
|
+
end
|
278
|
+
|
279
|
+
def data_source_exists?(table_name)
|
280
|
+
return false unless table_name.present?
|
281
|
+
|
282
|
+
sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
|
283
|
+
sql << " AND name = #{quote(table_name)}"
|
284
|
+
|
285
|
+
select_values(sql, 'SCHEMA').any?
|
286
|
+
end
|
287
|
+
|
288
|
+
def views # :nodoc:
|
289
|
+
select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA')
|
290
|
+
end
|
291
|
+
|
292
|
+
def view_exists?(view_name) # :nodoc:
|
293
|
+
return false unless view_name.present?
|
294
|
+
|
295
|
+
sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'"
|
296
|
+
sql << " AND name = #{quote(view_name)}"
|
297
|
+
|
298
|
+
select_values(sql, 'SCHEMA').any?
|
299
|
+
end
|
300
|
+
|
301
|
+
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
302
|
+
def columns(table_name) # :nodoc:
|
303
|
+
table_name = table_name.to_s
|
304
|
+
table_structure(table_name).map do |field|
|
305
|
+
case field["dflt_value"]
|
306
|
+
when /^null$/i
|
307
|
+
field["dflt_value"] = nil
|
308
|
+
when /^'(.*)'$/m
|
309
|
+
field["dflt_value"] = $1.gsub("''", "'")
|
310
|
+
when /^"(.*)"$/m
|
311
|
+
field["dflt_value"] = $1.gsub('""', '"')
|
312
|
+
end
|
313
|
+
|
314
|
+
collation = field['collation']
|
315
|
+
sql_type = field['type']
|
316
|
+
type_metadata = fetch_type_metadata(sql_type)
|
317
|
+
new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, table_name, nil, collation)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# Returns an array of indexes for the given table.
|
322
|
+
def indexes(table_name, name = nil) #:nodoc:
|
323
|
+
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
|
324
|
+
sql = <<-SQL
|
325
|
+
SELECT sql
|
326
|
+
FROM sqlite_master
|
327
|
+
WHERE name=#{quote(row['name'])} AND type='index'
|
328
|
+
UNION ALL
|
329
|
+
SELECT sql
|
330
|
+
FROM sqlite_temp_master
|
331
|
+
WHERE name=#{quote(row['name'])} AND type='index'
|
332
|
+
SQL
|
333
|
+
index_sql = exec_query(sql).first['sql']
|
334
|
+
match = /\sWHERE\s+(.+)$/i.match(index_sql)
|
335
|
+
where = match[1] if match
|
336
|
+
IndexDefinition.new(
|
337
|
+
table_name,
|
338
|
+
row['name'],
|
339
|
+
row['unique'] != 0,
|
340
|
+
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
|
341
|
+
col['name']
|
342
|
+
}, nil, nil, where)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def primary_keys(table_name) # :nodoc:
|
347
|
+
pks = table_structure(table_name).select { |f| f['pk'] > 0 }
|
348
|
+
pks.sort_by { |f| f['pk'] }.map { |f| f['name'] }
|
349
|
+
end
|
350
|
+
|
351
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
352
|
+
index_name = index_name_for_remove(table_name, options)
|
353
|
+
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
354
|
+
end
|
355
|
+
|
356
|
+
# Renames a table.
|
357
|
+
#
|
358
|
+
# Example:
|
359
|
+
# rename_table('octopuses', 'octopi')
|
360
|
+
def rename_table(table_name, new_name)
|
361
|
+
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
362
|
+
rename_table_indexes(table_name, new_name)
|
363
|
+
end
|
364
|
+
|
365
|
+
# See: http://www.sqlite.org/lang_altertable.html
|
366
|
+
# SQLite has an additional restriction on the ALTER TABLE statement
|
367
|
+
def valid_alter_table_type?(type)
|
368
|
+
type.to_sym != :primary_key
|
369
|
+
end
|
370
|
+
|
371
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
372
|
+
if valid_alter_table_type?(type)
|
373
|
+
super(table_name, column_name, type, options)
|
374
|
+
else
|
375
|
+
alter_table(table_name) do |definition|
|
376
|
+
definition.column(column_name, type, options)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
382
|
+
alter_table(table_name) do |definition|
|
383
|
+
definition.remove_column column_name
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
388
|
+
default = extract_new_default_value(default_or_changes)
|
389
|
+
|
390
|
+
alter_table(table_name) do |definition|
|
391
|
+
definition[column_name].default = default
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
396
|
+
unless null || default.nil?
|
397
|
+
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
398
|
+
end
|
399
|
+
alter_table(table_name) do |definition|
|
400
|
+
definition[column_name].null = null
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
405
|
+
alter_table(table_name) do |definition|
|
406
|
+
include_default = options_include_default?(options)
|
407
|
+
definition[column_name].instance_eval do
|
408
|
+
self.type = type
|
409
|
+
self.limit = options[:limit] if options.include?(:limit)
|
410
|
+
self.default = options[:default] if include_default
|
411
|
+
self.null = options[:null] if options.include?(:null)
|
412
|
+
self.precision = options[:precision] if options.include?(:precision)
|
413
|
+
self.scale = options[:scale] if options.include?(:scale)
|
414
|
+
self.collation = options[:collation] if options.include?(:collation)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
420
|
+
column = column_for(table_name, column_name)
|
421
|
+
alter_table(table_name, rename: {column.name => new_column_name.to_s})
|
422
|
+
rename_column_indexes(table_name, column.name, new_column_name)
|
423
|
+
end
|
424
|
+
|
425
|
+
protected
|
426
|
+
|
427
|
+
def table_structure(table_name)
|
428
|
+
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA')
|
429
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
430
|
+
table_structure_with_collation(table_name, structure)
|
431
|
+
end
|
432
|
+
|
433
|
+
def alter_table(table_name, options = {}) #:nodoc:
|
434
|
+
altered_table_name = "a#{table_name}"
|
435
|
+
caller = lambda {|definition| yield definition if block_given?}
|
436
|
+
|
437
|
+
transaction do
|
438
|
+
move_table(table_name, altered_table_name,
|
439
|
+
options.merge(:temporary => true))
|
440
|
+
move_table(altered_table_name, table_name, &caller)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
def move_table(from, to, options = {}, &block) #:nodoc:
|
445
|
+
copy_table(from, to, options, &block)
|
446
|
+
drop_table(from)
|
447
|
+
end
|
448
|
+
|
449
|
+
def copy_table(from, to, options = {}) #:nodoc:
|
450
|
+
from_primary_key = primary_key(from)
|
451
|
+
options[:id] = false
|
452
|
+
create_table(to, options) do |definition|
|
453
|
+
@definition = definition
|
454
|
+
@definition.primary_key(from_primary_key) if from_primary_key.present?
|
455
|
+
columns(from).each do |column|
|
456
|
+
column_name = options[:rename] ?
|
457
|
+
(options[:rename][column.name] ||
|
458
|
+
options[:rename][column.name.to_sym] ||
|
459
|
+
column.name) : column.name
|
460
|
+
next if column_name == from_primary_key
|
461
|
+
|
462
|
+
@definition.column(column_name, column.type,
|
463
|
+
:limit => column.limit, :default => column.default,
|
464
|
+
:precision => column.precision, :scale => column.scale,
|
465
|
+
:null => column.null, collation: column.collation)
|
466
|
+
end
|
467
|
+
yield @definition if block_given?
|
468
|
+
end
|
469
|
+
copy_table_indexes(from, to, options[:rename] || {})
|
470
|
+
copy_table_contents(from, to,
|
471
|
+
@definition.columns.map(&:name),
|
472
|
+
options[:rename] || {})
|
473
|
+
end
|
474
|
+
|
475
|
+
def copy_table_indexes(from, to, rename = {}) #:nodoc:
|
476
|
+
indexes(from).each do |index|
|
477
|
+
name = index.name
|
478
|
+
if to == "a#{from}"
|
479
|
+
name = "t#{name}"
|
480
|
+
elsif from == "a#{to}"
|
481
|
+
name = name[1..-1]
|
482
|
+
end
|
483
|
+
|
484
|
+
to_column_names = columns(to).map(&:name)
|
485
|
+
columns = index.columns.map {|c| rename[c] || c }.select do |column|
|
486
|
+
to_column_names.include?(column)
|
487
|
+
end
|
488
|
+
|
489
|
+
unless columns.empty?
|
490
|
+
# index name can't be the same
|
491
|
+
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
|
492
|
+
opts[:unique] = true if index.unique
|
493
|
+
add_index(to, columns, opts)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
499
|
+
column_mappings = Hash[columns.map {|name| [name, name]}]
|
500
|
+
rename.each { |a| column_mappings[a.last] = a.first }
|
501
|
+
from_columns = columns(from).collect(&:name)
|
502
|
+
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
503
|
+
from_columns_to_copy = columns.map { |col| column_mappings[col] }
|
504
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
505
|
+
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ','
|
506
|
+
|
507
|
+
exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
508
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
509
|
+
end
|
510
|
+
|
511
|
+
def sqlite_version
|
512
|
+
@sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
|
513
|
+
end
|
514
|
+
|
515
|
+
def translate_exception(exception, message)
|
516
|
+
case exception.message
|
517
|
+
# SQLite 3.8.2 returns a newly formatted error message:
|
518
|
+
# UNIQUE constraint failed: *table_name*.*column_name*
|
519
|
+
# Older versions of SQLite return:
|
520
|
+
# column *column_name* is not unique
|
521
|
+
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
|
522
|
+
RecordNotUnique.new(message)
|
523
|
+
else
|
524
|
+
super
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
private
|
529
|
+
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
|
530
|
+
|
531
|
+
def table_structure_with_collation(table_name, basic_structure)
|
532
|
+
collation_hash = {}
|
533
|
+
sql = "SELECT sql FROM
|
534
|
+
(SELECT * FROM sqlite_master UNION ALL
|
535
|
+
SELECT * FROM sqlite_temp_master)
|
536
|
+
WHERE type='table' and name='#{ table_name }' \;"
|
537
|
+
|
538
|
+
# Result will have following sample string
|
539
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
540
|
+
# "password_digest" varchar COLLATE "NOCASE");
|
541
|
+
result = exec_query(sql, 'SCHEMA').first
|
542
|
+
|
543
|
+
if result
|
544
|
+
# Splitting with left parentheses and picking up last will return all
|
545
|
+
# columns separated with comma(,).
|
546
|
+
columns_string = result["sql"].split('(').last
|
547
|
+
|
548
|
+
columns_string.split(',').each do |column_string|
|
549
|
+
# This regex will match the column name and collation type and will save
|
550
|
+
# the value in $1 and $2 respectively.
|
551
|
+
collation_hash[$1] = $2 if (COLLATE_REGEX =~ column_string)
|
552
|
+
end
|
553
|
+
|
554
|
+
basic_structure.map! do |column|
|
555
|
+
column_name = column['name']
|
556
|
+
|
557
|
+
if collation_hash.has_key? column_name
|
558
|
+
column['collation'] = collation_hash[column_name]
|
559
|
+
end
|
560
|
+
|
561
|
+
column
|
562
|
+
end
|
563
|
+
else
|
564
|
+
basic_structure.to_hash
|
565
|
+
end
|
566
|
+
end
|
53
567
|
end
|
54
568
|
end
|
55
569
|
end
|
@@ -1,38 +1,57 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
|
-
class StatementPool
|
3
|
+
class StatementPool # :nodoc:
|
4
4
|
include Enumerable
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
DEFAULT_STATEMENT_LIMIT = 1000
|
7
|
+
|
8
|
+
def initialize(statement_limit = nil)
|
9
|
+
@cache = Hash.new { |h,pid| h[pid] = {} }
|
10
|
+
@statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT
|
9
11
|
end
|
10
12
|
|
11
|
-
def each
|
12
|
-
|
13
|
+
def each(&block)
|
14
|
+
cache.each(&block)
|
13
15
|
end
|
14
16
|
|
15
17
|
def key?(key)
|
16
|
-
|
18
|
+
cache.key?(key)
|
17
19
|
end
|
18
20
|
|
19
21
|
def [](key)
|
20
|
-
|
22
|
+
cache[key]
|
21
23
|
end
|
22
24
|
|
23
25
|
def length
|
24
|
-
|
26
|
+
cache.length
|
25
27
|
end
|
26
28
|
|
27
|
-
def []=(sql,
|
28
|
-
|
29
|
+
def []=(sql, stmt)
|
30
|
+
while @statement_limit <= cache.size
|
31
|
+
dealloc(cache.shift.last)
|
32
|
+
end
|
33
|
+
cache[sql] = stmt
|
29
34
|
end
|
30
35
|
|
31
36
|
def clear
|
32
|
-
|
37
|
+
cache.each_value do |stmt|
|
38
|
+
dealloc stmt
|
39
|
+
end
|
40
|
+
cache.clear
|
33
41
|
end
|
34
42
|
|
35
43
|
def delete(key)
|
44
|
+
dealloc cache[key]
|
45
|
+
cache.delete(key)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def cache
|
51
|
+
@cache[Process.pid]
|
52
|
+
end
|
53
|
+
|
54
|
+
def dealloc(stmt)
|
36
55
|
raise NotImplementedError
|
37
56
|
end
|
38
57
|
end
|