activerecord 5.2.3
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 +937 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +217 -0
- data/examples/performance.rb +185 -0
- data/examples/simple.rb +15 -0
- data/lib/active_record.rb +188 -0
- data/lib/active_record/aggregations.rb +283 -0
- data/lib/active_record/association_relation.rb +40 -0
- data/lib/active_record/associations.rb +1860 -0
- data/lib/active_record/associations/alias_tracker.rb +81 -0
- data/lib/active_record/associations/association.rb +299 -0
- data/lib/active_record/associations/association_scope.rb +168 -0
- data/lib/active_record/associations/belongs_to_association.rb +130 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +140 -0
- data/lib/active_record/associations/builder/belongs_to.rb +163 -0
- data/lib/active_record/associations/builder/collection_association.rb +82 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
- data/lib/active_record/associations/builder/has_many.rb +17 -0
- data/lib/active_record/associations/builder/has_one.rb +30 -0
- data/lib/active_record/associations/builder/singular_association.rb +42 -0
- data/lib/active_record/associations/collection_association.rb +513 -0
- data/lib/active_record/associations/collection_proxy.rb +1131 -0
- data/lib/active_record/associations/foreign_association.rb +13 -0
- data/lib/active_record/associations/has_many_association.rb +144 -0
- data/lib/active_record/associations/has_many_through_association.rb +227 -0
- data/lib/active_record/associations/has_one_association.rb +120 -0
- data/lib/active_record/associations/has_one_through_association.rb +45 -0
- data/lib/active_record/associations/join_dependency.rb +262 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +193 -0
- data/lib/active_record/associations/preloader/association.rb +131 -0
- data/lib/active_record/associations/preloader/through_association.rb +107 -0
- data/lib/active_record/associations/singular_association.rb +73 -0
- data/lib/active_record/associations/through_association.rb +121 -0
- data/lib/active_record/attribute_assignment.rb +88 -0
- data/lib/active_record/attribute_decorators.rb +90 -0
- data/lib/active_record/attribute_methods.rb +492 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
- data/lib/active_record/attribute_methods/dirty.rb +150 -0
- data/lib/active_record/attribute_methods/primary_key.rb +143 -0
- data/lib/active_record/attribute_methods/query.rb +42 -0
- data/lib/active_record/attribute_methods/read.rb +85 -0
- data/lib/active_record/attribute_methods/serialization.rb +90 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
- data/lib/active_record/attribute_methods/write.rb +68 -0
- data/lib/active_record/attributes.rb +266 -0
- data/lib/active_record/autosave_association.rb +498 -0
- data/lib/active_record/base.rb +329 -0
- data/lib/active_record/callbacks.rb +353 -0
- data/lib/active_record/coders/json.rb +15 -0
- data/lib/active_record/coders/yaml_column.rb +50 -0
- data/lib/active_record/collection_cache_key.rb +53 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
- data/lib/active_record/connection_adapters/column.rb +91 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -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 +106 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
- data/lib/active_record/connection_handling.rb +145 -0
- data/lib/active_record/core.rb +559 -0
- data/lib/active_record/counter_cache.rb +218 -0
- data/lib/active_record/define_callbacks.rb +22 -0
- data/lib/active_record/dynamic_matchers.rb +122 -0
- data/lib/active_record/enum.rb +244 -0
- data/lib/active_record/errors.rb +380 -0
- data/lib/active_record/explain.rb +50 -0
- data/lib/active_record/explain_registry.rb +32 -0
- data/lib/active_record/explain_subscriber.rb +34 -0
- data/lib/active_record/fixture_set/file.rb +82 -0
- data/lib/active_record/fixtures.rb +1065 -0
- data/lib/active_record/gem_version.rb +17 -0
- data/lib/active_record/inheritance.rb +283 -0
- data/lib/active_record/integration.rb +155 -0
- data/lib/active_record/internal_metadata.rb +45 -0
- data/lib/active_record/legacy_yaml_adapter.rb +48 -0
- data/lib/active_record/locale/en.yml +48 -0
- data/lib/active_record/locking/optimistic.rb +198 -0
- data/lib/active_record/locking/pessimistic.rb +89 -0
- data/lib/active_record/log_subscriber.rb +137 -0
- data/lib/active_record/migration.rb +1378 -0
- data/lib/active_record/migration/command_recorder.rb +240 -0
- data/lib/active_record/migration/compatibility.rb +217 -0
- data/lib/active_record/migration/join_table.rb +17 -0
- data/lib/active_record/model_schema.rb +521 -0
- data/lib/active_record/nested_attributes.rb +600 -0
- data/lib/active_record/no_touching.rb +58 -0
- data/lib/active_record/null_relation.rb +68 -0
- data/lib/active_record/persistence.rb +763 -0
- data/lib/active_record/query_cache.rb +45 -0
- data/lib/active_record/querying.rb +70 -0
- data/lib/active_record/railtie.rb +226 -0
- data/lib/active_record/railties/console_sandbox.rb +7 -0
- data/lib/active_record/railties/controller_runtime.rb +56 -0
- data/lib/active_record/railties/databases.rake +377 -0
- data/lib/active_record/readonly_attributes.rb +24 -0
- data/lib/active_record/reflection.rb +1044 -0
- data/lib/active_record/relation.rb +629 -0
- data/lib/active_record/relation/batches.rb +287 -0
- data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
- data/lib/active_record/relation/calculations.rb +417 -0
- data/lib/active_record/relation/delegation.rb +147 -0
- data/lib/active_record/relation/finder_methods.rb +565 -0
- data/lib/active_record/relation/from_clause.rb +26 -0
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder.rb +152 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
- data/lib/active_record/relation/query_attribute.rb +45 -0
- data/lib/active_record/relation/query_methods.rb +1231 -0
- data/lib/active_record/relation/record_fetch_warning.rb +51 -0
- data/lib/active_record/relation/spawn_methods.rb +77 -0
- data/lib/active_record/relation/where_clause.rb +186 -0
- data/lib/active_record/relation/where_clause_factory.rb +34 -0
- data/lib/active_record/result.rb +149 -0
- data/lib/active_record/runtime_registry.rb +24 -0
- data/lib/active_record/sanitization.rb +222 -0
- data/lib/active_record/schema.rb +70 -0
- data/lib/active_record/schema_dumper.rb +255 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +106 -0
- data/lib/active_record/scoping/default.rb +152 -0
- data/lib/active_record/scoping/named.rb +213 -0
- data/lib/active_record/secure_token.rb +40 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/statement_cache.rb +121 -0
- data/lib/active_record/store.rb +211 -0
- data/lib/active_record/suppressor.rb +61 -0
- data/lib/active_record/table_metadata.rb +82 -0
- data/lib/active_record/tasks/database_tasks.rb +337 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
- data/lib/active_record/timestamp.rb +153 -0
- data/lib/active_record/touch_later.rb +64 -0
- data/lib/active_record/transactions.rb +502 -0
- data/lib/active_record/translation.rb +24 -0
- data/lib/active_record/type.rb +79 -0
- data/lib/active_record/type/adapter_specific_registry.rb +136 -0
- data/lib/active_record/type/date.rb +9 -0
- data/lib/active_record/type/date_time.rb +9 -0
- data/lib/active_record/type/decimal_without_scale.rb +15 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
- 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 +71 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +21 -0
- data/lib/active_record/type/type_map.rb +62 -0
- data/lib/active_record/type/unsigned_integer.rb +17 -0
- data/lib/active_record/type_caster.rb +9 -0
- data/lib/active_record/type_caster/connection.rb +33 -0
- data/lib/active_record/type_caster/map.rb +23 -0
- data/lib/active_record/validations.rb +93 -0
- data/lib/active_record/validations/absence.rb +25 -0
- data/lib/active_record/validations/associated.rb +60 -0
- data/lib/active_record/validations/length.rb +26 -0
- data/lib/active_record/validations/presence.rb +68 -0
- data/lib/active_record/validations/uniqueness.rb +238 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +19 -0
- 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 +35 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
- 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.tt +46 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
- metadata +333 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
class SchemaCache
|
6
|
+
attr_reader :version
|
7
|
+
attr_accessor :connection
|
8
|
+
|
9
|
+
def initialize(conn)
|
10
|
+
@connection = conn
|
11
|
+
|
12
|
+
@columns = {}
|
13
|
+
@columns_hash = {}
|
14
|
+
@primary_keys = {}
|
15
|
+
@data_sources = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize_dup(other)
|
19
|
+
super
|
20
|
+
@columns = @columns.dup
|
21
|
+
@columns_hash = @columns_hash.dup
|
22
|
+
@primary_keys = @primary_keys.dup
|
23
|
+
@data_sources = @data_sources.dup
|
24
|
+
end
|
25
|
+
|
26
|
+
def encode_with(coder)
|
27
|
+
coder["columns"] = @columns
|
28
|
+
coder["columns_hash"] = @columns_hash
|
29
|
+
coder["primary_keys"] = @primary_keys
|
30
|
+
coder["data_sources"] = @data_sources
|
31
|
+
coder["version"] = connection.migration_context.current_version
|
32
|
+
end
|
33
|
+
|
34
|
+
def init_with(coder)
|
35
|
+
@columns = coder["columns"]
|
36
|
+
@columns_hash = coder["columns_hash"]
|
37
|
+
@primary_keys = coder["primary_keys"]
|
38
|
+
@data_sources = coder["data_sources"]
|
39
|
+
@version = coder["version"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def primary_keys(table_name)
|
43
|
+
@primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
|
44
|
+
end
|
45
|
+
|
46
|
+
# A cached lookup for table existence.
|
47
|
+
def data_source_exists?(name)
|
48
|
+
prepare_data_sources if @data_sources.empty?
|
49
|
+
return @data_sources[name] if @data_sources.key? name
|
50
|
+
|
51
|
+
@data_sources[name] = connection.data_source_exists?(name)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add internal cache for table with +table_name+.
|
55
|
+
def add(table_name)
|
56
|
+
if data_source_exists?(table_name)
|
57
|
+
primary_keys(table_name)
|
58
|
+
columns(table_name)
|
59
|
+
columns_hash(table_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def data_sources(name)
|
64
|
+
@data_sources[name]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get the columns for a table
|
68
|
+
def columns(table_name)
|
69
|
+
@columns[table_name] ||= connection.columns(table_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get the columns for a table as a hash, key is the column name
|
73
|
+
# value is the column object.
|
74
|
+
def columns_hash(table_name)
|
75
|
+
@columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
|
76
|
+
[col.name, col]
|
77
|
+
}]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Clears out internal caches
|
81
|
+
def clear!
|
82
|
+
@columns.clear
|
83
|
+
@columns_hash.clear
|
84
|
+
@primary_keys.clear
|
85
|
+
@data_sources.clear
|
86
|
+
@version = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
def size
|
90
|
+
[@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+
|
91
|
+
end
|
92
|
+
|
93
|
+
# Clear out internal caches for the data source +name+.
|
94
|
+
def clear_data_source_cache!(name)
|
95
|
+
@columns.delete name
|
96
|
+
@columns_hash.delete name
|
97
|
+
@primary_keys.delete name
|
98
|
+
@data_sources.delete name
|
99
|
+
end
|
100
|
+
|
101
|
+
def marshal_dump
|
102
|
+
# if we get current version during initialization, it happens stack over flow.
|
103
|
+
@version = connection.migration_context.current_version
|
104
|
+
[@version, @columns, @columns_hash, @primary_keys, @data_sources]
|
105
|
+
end
|
106
|
+
|
107
|
+
def marshal_load(array)
|
108
|
+
@version, @columns, @columns_hash, @primary_keys, @data_sources = array
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def prepare_data_sources
|
114
|
+
connection.data_sources.each { |source| @data_sources[source] = true }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# :stopdoc:
|
5
|
+
module ConnectionAdapters
|
6
|
+
class SqlTypeMetadata
|
7
|
+
attr_reader :sql_type, :type, :limit, :precision, :scale
|
8
|
+
|
9
|
+
def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil)
|
10
|
+
@sql_type = sql_type
|
11
|
+
@type = type
|
12
|
+
@limit = limit
|
13
|
+
@precision = precision
|
14
|
+
@scale = scale
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
other.is_a?(SqlTypeMetadata) &&
|
19
|
+
attributes_for_hash == other.attributes_for_hash
|
20
|
+
end
|
21
|
+
alias eql? ==
|
22
|
+
|
23
|
+
def hash
|
24
|
+
attributes_for_hash.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def attributes_for_hash
|
30
|
+
[self.class, sql_type, type, limit, precision, scale]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
class ExplainPrettyPrinter # :nodoc:
|
7
|
+
# Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles
|
8
|
+
# the output of the SQLite shell:
|
9
|
+
#
|
10
|
+
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
|
11
|
+
# 0|1|1|SCAN TABLE posts (~100000 rows)
|
12
|
+
#
|
13
|
+
def pp(result)
|
14
|
+
result.rows.map do |row|
|
15
|
+
row.join("|")
|
16
|
+
end.join("\n") + "\n"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
module Quoting # :nodoc:
|
7
|
+
def quote_string(s)
|
8
|
+
@connection.class.quote(s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def quote_table_name_for_assignment(table, attr)
|
12
|
+
quote_column_name(attr)
|
13
|
+
end
|
14
|
+
|
15
|
+
def quote_table_name(name)
|
16
|
+
@quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
def quote_column_name(name)
|
20
|
+
@quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze
|
21
|
+
end
|
22
|
+
|
23
|
+
def quoted_time(value)
|
24
|
+
value = value.change(year: 2000, month: 1, day: 1)
|
25
|
+
quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ")
|
26
|
+
end
|
27
|
+
|
28
|
+
def quoted_binary(value)
|
29
|
+
"x'#{value.hex}'"
|
30
|
+
end
|
31
|
+
|
32
|
+
def quoted_true
|
33
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1".freeze : "'t'".freeze
|
34
|
+
end
|
35
|
+
|
36
|
+
def unquoted_true
|
37
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t".freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def quoted_false
|
41
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0".freeze : "'f'".freeze
|
42
|
+
end
|
43
|
+
|
44
|
+
def unquoted_false
|
45
|
+
ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f".freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def _type_cast(value)
|
51
|
+
case value
|
52
|
+
when BigDecimal
|
53
|
+
value.to_f
|
54
|
+
when String
|
55
|
+
if value.encoding == Encoding::ASCII_8BIT
|
56
|
+
super(value.encode(Encoding::UTF_8))
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module SQLite3
|
6
|
+
class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
|
7
|
+
private
|
8
|
+
def add_column_options!(sql, options)
|
9
|
+
if options[:collation]
|
10
|
+
sql << " COLLATE \"#{options[:collation]}\""
|
11
|
+
end
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -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,106 @@
|
|
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
|
+
/\sWHERE\s+(?<where>.+)$/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
|
+
# Add info on sort order for columns (only desc order is explicitly specified, asc is
|
31
|
+
# the default)
|
32
|
+
orders = {}
|
33
|
+
if index_sql # index_sql can be null in case of primary key indexes
|
34
|
+
index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column|
|
35
|
+
orders[order_column] = :desc
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
IndexDefinition.new(
|
40
|
+
table_name,
|
41
|
+
row["name"],
|
42
|
+
row["unique"] != 0,
|
43
|
+
columns,
|
44
|
+
where: where,
|
45
|
+
orders: orders
|
46
|
+
)
|
47
|
+
end.compact
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_schema_dumper(options)
|
51
|
+
SQLite3::SchemaDumper.create(self, options)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def schema_creation
|
56
|
+
SQLite3::SchemaCreation.new(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_table_definition(*args)
|
60
|
+
SQLite3::TableDefinition.new(*args)
|
61
|
+
end
|
62
|
+
|
63
|
+
def new_column_from_field(table_name, field)
|
64
|
+
default = \
|
65
|
+
case field["dflt_value"]
|
66
|
+
when /^null$/i
|
67
|
+
nil
|
68
|
+
when /^'(.*)'$/m
|
69
|
+
$1.gsub("''", "'")
|
70
|
+
when /^"(.*)"$/m
|
71
|
+
$1.gsub('""', '"')
|
72
|
+
else
|
73
|
+
field["dflt_value"]
|
74
|
+
end
|
75
|
+
|
76
|
+
type_metadata = fetch_type_metadata(field["type"])
|
77
|
+
Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"])
|
78
|
+
end
|
79
|
+
|
80
|
+
def data_source_sql(name = nil, type: nil)
|
81
|
+
scope = quoted_scope(name, type: type)
|
82
|
+
scope[:type] ||= "'table','view'"
|
83
|
+
|
84
|
+
sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup
|
85
|
+
sql << " AND name = #{scope[:name]}" if scope[:name]
|
86
|
+
sql << " AND type IN (#{scope[:type]})"
|
87
|
+
sql
|
88
|
+
end
|
89
|
+
|
90
|
+
def quoted_scope(name = nil, type: nil)
|
91
|
+
type = \
|
92
|
+
case type
|
93
|
+
when "BASE TABLE"
|
94
|
+
"'table'"
|
95
|
+
when "VIEW"
|
96
|
+
"'view'"
|
97
|
+
end
|
98
|
+
scope = {}
|
99
|
+
scope[:name] = quote(name) if name
|
100
|
+
scope[:type] = type if type
|
101
|
+
scope
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,573 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record/connection_adapters/abstract_adapter"
|
4
|
+
require "active_record/connection_adapters/statement_pool"
|
5
|
+
require "active_record/connection_adapters/sqlite3/explain_pretty_printer"
|
6
|
+
require "active_record/connection_adapters/sqlite3/quoting"
|
7
|
+
require "active_record/connection_adapters/sqlite3/schema_creation"
|
8
|
+
require "active_record/connection_adapters/sqlite3/schema_definitions"
|
9
|
+
require "active_record/connection_adapters/sqlite3/schema_dumper"
|
10
|
+
require "active_record/connection_adapters/sqlite3/schema_statements"
|
11
|
+
|
12
|
+
gem "sqlite3", "~> 1.3", ">= 1.3.6"
|
13
|
+
require "sqlite3"
|
14
|
+
|
15
|
+
module ActiveRecord
|
16
|
+
module ConnectionHandling # :nodoc:
|
17
|
+
def sqlite3_connection(config)
|
18
|
+
# Require database.
|
19
|
+
unless config[:database]
|
20
|
+
raise ArgumentError, "No database file specified. Missing argument: database"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Allow database path relative to Rails.root, but only if the database
|
24
|
+
# path is not the special path that tells sqlite to build a database only
|
25
|
+
# in memory.
|
26
|
+
if ":memory:" != config[:database]
|
27
|
+
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
|
28
|
+
dirname = File.dirname(config[:database])
|
29
|
+
Dir.mkdir(dirname) unless File.directory?(dirname)
|
30
|
+
end
|
31
|
+
|
32
|
+
db = SQLite3::Database.new(
|
33
|
+
config[:database].to_s,
|
34
|
+
results_as_hash: true
|
35
|
+
)
|
36
|
+
|
37
|
+
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
|
38
|
+
|
39
|
+
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
|
40
|
+
rescue Errno::ENOENT => error
|
41
|
+
if error.message.include?("No such file or directory")
|
42
|
+
raise ActiveRecord::NoDatabaseError
|
43
|
+
else
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module ConnectionAdapters #:nodoc:
|
50
|
+
# The SQLite3 adapter works SQLite 3.6.16 or newer
|
51
|
+
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
|
52
|
+
#
|
53
|
+
# Options:
|
54
|
+
#
|
55
|
+
# * <tt>:database</tt> - Path to the database file.
|
56
|
+
class SQLite3Adapter < AbstractAdapter
|
57
|
+
ADAPTER_NAME = "SQLite".freeze
|
58
|
+
|
59
|
+
include SQLite3::Quoting
|
60
|
+
include SQLite3::SchemaStatements
|
61
|
+
|
62
|
+
NATIVE_DATABASE_TYPES = {
|
63
|
+
primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL",
|
64
|
+
string: { name: "varchar" },
|
65
|
+
text: { name: "text" },
|
66
|
+
integer: { name: "integer" },
|
67
|
+
float: { name: "float" },
|
68
|
+
decimal: { name: "decimal" },
|
69
|
+
datetime: { name: "datetime" },
|
70
|
+
time: { name: "time" },
|
71
|
+
date: { name: "date" },
|
72
|
+
binary: { name: "blob" },
|
73
|
+
boolean: { name: "boolean" },
|
74
|
+
json: { name: "json" },
|
75
|
+
}
|
76
|
+
|
77
|
+
##
|
78
|
+
# :singleton-method:
|
79
|
+
# Indicates whether boolean values are stored in sqlite3 databases as 1
|
80
|
+
# and 0 or 't' and 'f'. Leaving <tt>ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer</tt>
|
81
|
+
# set to false is deprecated. SQLite databases have used 't' and 'f' to
|
82
|
+
# serialize boolean values and must have old data converted to 1 and 0
|
83
|
+
# (its native boolean serialization) before setting this flag to true.
|
84
|
+
# Conversion can be accomplished by setting up a rake task which runs
|
85
|
+
#
|
86
|
+
# ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1)
|
87
|
+
# ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0)
|
88
|
+
# for all models and all boolean columns, after which the flag must be set
|
89
|
+
# to true by adding the following to your <tt>application.rb</tt> file:
|
90
|
+
#
|
91
|
+
# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
|
92
|
+
class_attribute :represent_boolean_as_integer, default: false
|
93
|
+
|
94
|
+
class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
|
95
|
+
private
|
96
|
+
def dealloc(stmt)
|
97
|
+
stmt[:stmt].close unless stmt[:stmt].closed?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(connection, logger, connection_options, config)
|
102
|
+
super(connection, logger, config)
|
103
|
+
|
104
|
+
@active = true
|
105
|
+
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
|
106
|
+
|
107
|
+
configure_connection
|
108
|
+
end
|
109
|
+
|
110
|
+
def supports_ddl_transactions?
|
111
|
+
true
|
112
|
+
end
|
113
|
+
|
114
|
+
def supports_savepoints?
|
115
|
+
true
|
116
|
+
end
|
117
|
+
|
118
|
+
def supports_partial_index?
|
119
|
+
sqlite_version >= "3.8.0"
|
120
|
+
end
|
121
|
+
|
122
|
+
def requires_reloading?
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def supports_foreign_keys_in_create?
|
127
|
+
sqlite_version >= "3.6.19"
|
128
|
+
end
|
129
|
+
|
130
|
+
def supports_views?
|
131
|
+
true
|
132
|
+
end
|
133
|
+
|
134
|
+
def supports_datetime_with_precision?
|
135
|
+
true
|
136
|
+
end
|
137
|
+
|
138
|
+
def supports_json?
|
139
|
+
true
|
140
|
+
end
|
141
|
+
|
142
|
+
def supports_multi_insert?
|
143
|
+
sqlite_version >= "3.7.11"
|
144
|
+
end
|
145
|
+
|
146
|
+
def active?
|
147
|
+
@active
|
148
|
+
end
|
149
|
+
|
150
|
+
# Disconnects from the database if already connected. Otherwise, this
|
151
|
+
# method does nothing.
|
152
|
+
def disconnect!
|
153
|
+
super
|
154
|
+
@active = false
|
155
|
+
@connection.close rescue nil
|
156
|
+
end
|
157
|
+
|
158
|
+
# Clears the prepared statements cache.
|
159
|
+
def clear_cache!
|
160
|
+
@statements.clear
|
161
|
+
end
|
162
|
+
|
163
|
+
def supports_index_sort_order?
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns 62. SQLite supports index names up to 64
|
168
|
+
# characters. The rest is used by Rails internally to perform
|
169
|
+
# temporary rename operations
|
170
|
+
def allowed_index_name_length
|
171
|
+
index_name_length - 2
|
172
|
+
end
|
173
|
+
|
174
|
+
def native_database_types #:nodoc:
|
175
|
+
NATIVE_DATABASE_TYPES
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
179
|
+
def encoding
|
180
|
+
@connection.encoding.to_s
|
181
|
+
end
|
182
|
+
|
183
|
+
def supports_explain?
|
184
|
+
true
|
185
|
+
end
|
186
|
+
|
187
|
+
# REFERENTIAL INTEGRITY ====================================
|
188
|
+
|
189
|
+
def disable_referential_integrity # :nodoc:
|
190
|
+
old = query_value("PRAGMA foreign_keys")
|
191
|
+
|
192
|
+
begin
|
193
|
+
execute("PRAGMA foreign_keys = OFF")
|
194
|
+
yield
|
195
|
+
ensure
|
196
|
+
execute("PRAGMA foreign_keys = #{old}")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#--
|
201
|
+
# DATABASE STATEMENTS ======================================
|
202
|
+
#++
|
203
|
+
|
204
|
+
def explain(arel, binds = [])
|
205
|
+
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
206
|
+
SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", []))
|
207
|
+
end
|
208
|
+
|
209
|
+
def exec_query(sql, name = nil, binds = [], prepare: false)
|
210
|
+
type_casted_binds = type_casted_binds(binds)
|
211
|
+
|
212
|
+
log(sql, name, binds, type_casted_binds) do
|
213
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
214
|
+
# Don't cache statements if they are not prepared
|
215
|
+
unless prepare
|
216
|
+
stmt = @connection.prepare(sql)
|
217
|
+
begin
|
218
|
+
cols = stmt.columns
|
219
|
+
unless without_prepared_statement?(binds)
|
220
|
+
stmt.bind_params(type_casted_binds)
|
221
|
+
end
|
222
|
+
records = stmt.to_a
|
223
|
+
ensure
|
224
|
+
stmt.close
|
225
|
+
end
|
226
|
+
else
|
227
|
+
cache = @statements[sql] ||= {
|
228
|
+
stmt: @connection.prepare(sql)
|
229
|
+
}
|
230
|
+
stmt = cache[:stmt]
|
231
|
+
cols = cache[:cols] ||= stmt.columns
|
232
|
+
stmt.reset!
|
233
|
+
stmt.bind_params(type_casted_binds)
|
234
|
+
records = stmt.to_a
|
235
|
+
end
|
236
|
+
|
237
|
+
ActiveRecord::Result.new(cols, records)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def exec_delete(sql, name = "SQL", binds = [])
|
243
|
+
exec_query(sql, name, binds)
|
244
|
+
@connection.changes
|
245
|
+
end
|
246
|
+
alias :exec_update :exec_delete
|
247
|
+
|
248
|
+
def last_inserted_id(result)
|
249
|
+
@connection.last_insert_row_id
|
250
|
+
end
|
251
|
+
|
252
|
+
def execute(sql, name = nil) #:nodoc:
|
253
|
+
log(sql, name) do
|
254
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
255
|
+
@connection.execute(sql)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def begin_db_transaction #:nodoc:
|
261
|
+
log("begin transaction", nil) { @connection.transaction }
|
262
|
+
end
|
263
|
+
|
264
|
+
def commit_db_transaction #:nodoc:
|
265
|
+
log("commit transaction", nil) { @connection.commit }
|
266
|
+
end
|
267
|
+
|
268
|
+
def exec_rollback_db_transaction #:nodoc:
|
269
|
+
log("rollback transaction", nil) { @connection.rollback }
|
270
|
+
end
|
271
|
+
|
272
|
+
# SCHEMA STATEMENTS ========================================
|
273
|
+
|
274
|
+
def primary_keys(table_name) # :nodoc:
|
275
|
+
pks = table_structure(table_name).select { |f| f["pk"] > 0 }
|
276
|
+
pks.sort_by { |f| f["pk"] }.map { |f| f["name"] }
|
277
|
+
end
|
278
|
+
|
279
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
280
|
+
index_name = index_name_for_remove(table_name, options)
|
281
|
+
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
282
|
+
end
|
283
|
+
|
284
|
+
# Renames a table.
|
285
|
+
#
|
286
|
+
# Example:
|
287
|
+
# rename_table('octopuses', 'octopi')
|
288
|
+
def rename_table(table_name, new_name)
|
289
|
+
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
290
|
+
rename_table_indexes(table_name, new_name)
|
291
|
+
end
|
292
|
+
|
293
|
+
def valid_alter_table_type?(type, options = {})
|
294
|
+
!invalid_alter_table_type?(type, options)
|
295
|
+
end
|
296
|
+
deprecate :valid_alter_table_type?
|
297
|
+
|
298
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
299
|
+
if invalid_alter_table_type?(type, options)
|
300
|
+
alter_table(table_name) do |definition|
|
301
|
+
definition.column(column_name, type, options)
|
302
|
+
end
|
303
|
+
else
|
304
|
+
super
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
309
|
+
alter_table(table_name) do |definition|
|
310
|
+
definition.remove_column column_name
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
315
|
+
default = extract_new_default_value(default_or_changes)
|
316
|
+
|
317
|
+
alter_table(table_name) do |definition|
|
318
|
+
definition[column_name].default = default
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
323
|
+
unless null || default.nil?
|
324
|
+
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
325
|
+
end
|
326
|
+
alter_table(table_name) do |definition|
|
327
|
+
definition[column_name].null = null
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
332
|
+
alter_table(table_name) do |definition|
|
333
|
+
definition[column_name].instance_eval do
|
334
|
+
self.type = type
|
335
|
+
self.limit = options[:limit] if options.include?(:limit)
|
336
|
+
self.default = options[:default] if options.include?(:default)
|
337
|
+
self.null = options[:null] if options.include?(:null)
|
338
|
+
self.precision = options[:precision] if options.include?(:precision)
|
339
|
+
self.scale = options[:scale] if options.include?(:scale)
|
340
|
+
self.collation = options[:collation] if options.include?(:collation)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
346
|
+
column = column_for(table_name, column_name)
|
347
|
+
alter_table(table_name, rename: { column.name => new_column_name.to_s })
|
348
|
+
rename_column_indexes(table_name, column.name, new_column_name)
|
349
|
+
end
|
350
|
+
|
351
|
+
def add_reference(table_name, ref_name, **options) # :nodoc:
|
352
|
+
super(table_name, ref_name, type: :integer, **options)
|
353
|
+
end
|
354
|
+
alias :add_belongs_to :add_reference
|
355
|
+
|
356
|
+
def foreign_keys(table_name)
|
357
|
+
fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA")
|
358
|
+
fk_info.map do |row|
|
359
|
+
options = {
|
360
|
+
column: row["from"],
|
361
|
+
primary_key: row["to"],
|
362
|
+
on_delete: extract_foreign_key_action(row["on_delete"]),
|
363
|
+
on_update: extract_foreign_key_action(row["on_update"])
|
364
|
+
}
|
365
|
+
ForeignKeyDefinition.new(table_name, row["table"], options)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def insert_fixtures(rows, table_name)
|
370
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
371
|
+
`insert_fixtures` is deprecated and will be removed in the next version of Rails.
|
372
|
+
Consider using `insert_fixtures_set` for performance improvement.
|
373
|
+
MSG
|
374
|
+
insert_fixtures_set(table_name => rows)
|
375
|
+
end
|
376
|
+
|
377
|
+
def insert_fixtures_set(fixture_set, tables_to_delete = [])
|
378
|
+
disable_referential_integrity do
|
379
|
+
transaction(requires_new: true) do
|
380
|
+
tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" }
|
381
|
+
|
382
|
+
fixture_set.each do |table_name, rows|
|
383
|
+
rows.each { |row| insert_fixture(row, table_name) }
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
private
|
390
|
+
def initialize_type_map(m = type_map)
|
391
|
+
super
|
392
|
+
register_class_with_limit m, %r(int)i, SQLite3Integer
|
393
|
+
end
|
394
|
+
|
395
|
+
def table_structure(table_name)
|
396
|
+
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA")
|
397
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
398
|
+
table_structure_with_collation(table_name, structure)
|
399
|
+
end
|
400
|
+
alias column_definitions table_structure
|
401
|
+
|
402
|
+
# See: https://www.sqlite.org/lang_altertable.html
|
403
|
+
# SQLite has an additional restriction on the ALTER TABLE statement
|
404
|
+
def invalid_alter_table_type?(type, options)
|
405
|
+
type.to_sym == :primary_key || options[:primary_key]
|
406
|
+
end
|
407
|
+
|
408
|
+
def alter_table(table_name, options = {})
|
409
|
+
altered_table_name = "a#{table_name}"
|
410
|
+
caller = lambda { |definition| yield definition if block_given? }
|
411
|
+
|
412
|
+
transaction do
|
413
|
+
move_table(table_name, altered_table_name,
|
414
|
+
options.merge(temporary: true))
|
415
|
+
move_table(altered_table_name, table_name, &caller)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def move_table(from, to, options = {}, &block)
|
420
|
+
copy_table(from, to, options, &block)
|
421
|
+
drop_table(from)
|
422
|
+
end
|
423
|
+
|
424
|
+
def copy_table(from, to, options = {})
|
425
|
+
from_primary_key = primary_key(from)
|
426
|
+
options[:id] = false
|
427
|
+
create_table(to, options) do |definition|
|
428
|
+
@definition = definition
|
429
|
+
if from_primary_key.is_a?(Array)
|
430
|
+
@definition.primary_keys from_primary_key
|
431
|
+
end
|
432
|
+
columns(from).each do |column|
|
433
|
+
column_name = options[:rename] ?
|
434
|
+
(options[:rename][column.name] ||
|
435
|
+
options[:rename][column.name.to_sym] ||
|
436
|
+
column.name) : column.name
|
437
|
+
|
438
|
+
@definition.column(column_name, column.type,
|
439
|
+
limit: column.limit, default: column.default,
|
440
|
+
precision: column.precision, scale: column.scale,
|
441
|
+
null: column.null, collation: column.collation,
|
442
|
+
primary_key: column_name == from_primary_key
|
443
|
+
)
|
444
|
+
end
|
445
|
+
yield @definition if block_given?
|
446
|
+
end
|
447
|
+
copy_table_indexes(from, to, options[:rename] || {})
|
448
|
+
copy_table_contents(from, to,
|
449
|
+
@definition.columns.map(&:name),
|
450
|
+
options[:rename] || {})
|
451
|
+
end
|
452
|
+
|
453
|
+
def copy_table_indexes(from, to, rename = {})
|
454
|
+
indexes(from).each do |index|
|
455
|
+
name = index.name
|
456
|
+
if to == "a#{from}"
|
457
|
+
name = "t#{name}"
|
458
|
+
elsif from == "a#{to}"
|
459
|
+
name = name[1..-1]
|
460
|
+
end
|
461
|
+
|
462
|
+
to_column_names = columns(to).map(&:name)
|
463
|
+
columns = index.columns.map { |c| rename[c] || c }.select do |column|
|
464
|
+
to_column_names.include?(column)
|
465
|
+
end
|
466
|
+
|
467
|
+
unless columns.empty?
|
468
|
+
# index name can't be the same
|
469
|
+
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
|
470
|
+
opts[:unique] = true if index.unique
|
471
|
+
opts[:where] = index.where if index.where
|
472
|
+
add_index(to, columns, opts)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def copy_table_contents(from, to, columns, rename = {})
|
478
|
+
column_mappings = Hash[columns.map { |name| [name, name] }]
|
479
|
+
rename.each { |a| column_mappings[a.last] = a.first }
|
480
|
+
from_columns = columns(from).collect(&:name)
|
481
|
+
columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) }
|
482
|
+
from_columns_to_copy = columns.map { |col| column_mappings[col] }
|
483
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ","
|
484
|
+
quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * ","
|
485
|
+
|
486
|
+
exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns})
|
487
|
+
SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")
|
488
|
+
end
|
489
|
+
|
490
|
+
def sqlite_version
|
491
|
+
@sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
|
492
|
+
end
|
493
|
+
|
494
|
+
def translate_exception(exception, message)
|
495
|
+
case exception.message
|
496
|
+
# SQLite 3.8.2 returns a newly formatted error message:
|
497
|
+
# UNIQUE constraint failed: *table_name*.*column_name*
|
498
|
+
# Older versions of SQLite return:
|
499
|
+
# column *column_name* is not unique
|
500
|
+
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
|
501
|
+
RecordNotUnique.new(message)
|
502
|
+
when /.* may not be NULL/, /NOT NULL constraint failed: .*/
|
503
|
+
NotNullViolation.new(message)
|
504
|
+
when /FOREIGN KEY constraint failed/i
|
505
|
+
InvalidForeignKey.new(message)
|
506
|
+
else
|
507
|
+
super
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze
|
512
|
+
|
513
|
+
def table_structure_with_collation(table_name, basic_structure)
|
514
|
+
collation_hash = {}
|
515
|
+
sql = <<-SQL
|
516
|
+
SELECT sql FROM
|
517
|
+
(SELECT * FROM sqlite_master UNION ALL
|
518
|
+
SELECT * FROM sqlite_temp_master)
|
519
|
+
WHERE type = 'table' AND name = #{quote(table_name)}
|
520
|
+
SQL
|
521
|
+
|
522
|
+
# Result will have following sample string
|
523
|
+
# CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
524
|
+
# "password_digest" varchar COLLATE "NOCASE");
|
525
|
+
result = exec_query(sql, "SCHEMA").first
|
526
|
+
|
527
|
+
if result
|
528
|
+
# Splitting with left parentheses and picking up last will return all
|
529
|
+
# columns separated with comma(,).
|
530
|
+
columns_string = result["sql"].split("(").last
|
531
|
+
|
532
|
+
columns_string.split(",").each do |column_string|
|
533
|
+
# This regex will match the column name and collation type and will save
|
534
|
+
# the value in $1 and $2 respectively.
|
535
|
+
collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string
|
536
|
+
end
|
537
|
+
|
538
|
+
basic_structure.map! do |column|
|
539
|
+
column_name = column["name"]
|
540
|
+
|
541
|
+
if collation_hash.has_key? column_name
|
542
|
+
column["collation"] = collation_hash[column_name]
|
543
|
+
end
|
544
|
+
|
545
|
+
column
|
546
|
+
end
|
547
|
+
else
|
548
|
+
basic_structure.to_hash
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
def arel_visitor
|
553
|
+
Arel::Visitors::SQLite.new(self)
|
554
|
+
end
|
555
|
+
|
556
|
+
def configure_connection
|
557
|
+
execute("PRAGMA foreign_keys = ON", "SCHEMA")
|
558
|
+
end
|
559
|
+
|
560
|
+
class SQLite3Integer < Type::Integer # :nodoc:
|
561
|
+
private
|
562
|
+
def _limit
|
563
|
+
# INTEGER storage class can be stored 8 bytes value.
|
564
|
+
# See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes
|
565
|
+
limit || 8
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3)
|
570
|
+
end
|
571
|
+
ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter)
|
572
|
+
end
|
573
|
+
end
|