activerecord 4.2.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 +1372 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +218 -0
- data/examples/performance.rb +184 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +173 -0
- data/lib/active_record/aggregations.rb +266 -0
- data/lib/active_record/association_relation.rb +22 -0
- data/lib/active_record/associations.rb +1724 -0
- data/lib/active_record/associations/alias_tracker.rb +87 -0
- data/lib/active_record/associations/association.rb +253 -0
- data/lib/active_record/associations/association_scope.rb +194 -0
- data/lib/active_record/associations/belongs_to_association.rb +111 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
- data/lib/active_record/associations/builder/association.rb +149 -0
- data/lib/active_record/associations/builder/belongs_to.rb +116 -0
- data/lib/active_record/associations/builder/collection_association.rb +91 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +124 -0
- data/lib/active_record/associations/builder/has_many.rb +15 -0
- data/lib/active_record/associations/builder/has_one.rb +23 -0
- data/lib/active_record/associations/builder/singular_association.rb +38 -0
- data/lib/active_record/associations/collection_association.rb +634 -0
- data/lib/active_record/associations/collection_proxy.rb +1027 -0
- data/lib/active_record/associations/has_many_association.rb +184 -0
- data/lib/active_record/associations/has_many_through_association.rb +238 -0
- data/lib/active_record/associations/has_one_association.rb +105 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +282 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +122 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +22 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
- data/lib/active_record/associations/preloader.rb +203 -0
- data/lib/active_record/associations/preloader/association.rb +162 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +96 -0
- data/lib/active_record/associations/singular_association.rb +86 -0
- data/lib/active_record/associations/through_association.rb +96 -0
- data/lib/active_record/attribute.rb +149 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods.rb +439 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +71 -0
- data/lib/active_record/attribute_methods/dirty.rb +181 -0
- data/lib/active_record/attribute_methods/primary_key.rb +128 -0
- data/lib/active_record/attribute_methods/query.rb +40 -0
- data/lib/active_record/attribute_methods/read.rb +103 -0
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -0
- data/lib/active_record/attribute_methods/write.rb +83 -0
- data/lib/active_record/attribute_set.rb +77 -0
- data/lib/active_record/attribute_set/builder.rb +86 -0
- data/lib/active_record/attributes.rb +139 -0
- data/lib/active_record/autosave_association.rb +439 -0
- data/lib/active_record/base.rb +317 -0
- data/lib/active_record/callbacks.rb +313 -0
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +38 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +659 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +373 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +133 -0
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +574 -0
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +991 -0
- data/lib/active_record/connection_adapters/abstract/transaction.rb +219 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +487 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +883 -0
- data/lib/active_record/connection_adapters/column.rb +82 -0
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +282 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +491 -0
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -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 +14 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -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/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -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/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +588 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +754 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +628 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +566 -0
- data/lib/active_record/counter_cache.rb +175 -0
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +198 -0
- data/lib/active_record/errors.rb +252 -0
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +1007 -0
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- data/lib/active_record/locale/en.yml +47 -0
- data/lib/active_record/locking/optimistic.rb +204 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +75 -0
- data/lib/active_record/migration.rb +1051 -0
- data/lib/active_record/migration/command_recorder.rb +197 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +340 -0
- data/lib/active_record/nested_attributes.rb +548 -0
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +532 -0
- data/lib/active_record/query_cache.rb +56 -0
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +162 -0
- data/lib/active_record/railties/console_sandbox.rb +5 -0
- data/lib/active_record/railties/controller_runtime.rb +50 -0
- data/lib/active_record/railties/databases.rake +391 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +881 -0
- data/lib/active_record/relation.rb +681 -0
- data/lib/active_record/relation/batches.rb +138 -0
- data/lib/active_record/relation/calculations.rb +403 -0
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +528 -0
- data/lib/active_record/relation/merger.rb +170 -0
- data/lib/active_record/relation/predicate_builder.rb +126 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +47 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +1176 -0
- data/lib/active_record/relation/spawn_methods.rb +75 -0
- data/lib/active_record/result.rb +131 -0
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +64 -0
- data/lib/active_record/schema_dumper.rb +251 -0
- data/lib/active_record/schema_migration.rb +56 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/scoping/default.rb +134 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/serialization.rb +22 -0
- data/lib/active_record/serializers/xml_serializer.rb +193 -0
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- data/lib/active_record/tasks/database_tasks.rb +296 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +145 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +121 -0
- data/lib/active_record/transactions.rb +417 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +30 -0
- data/lib/active_record/type/date.rb +46 -0
- data/lib/active_record/type/date_time.rb +43 -0
- data/lib/active_record/type/decimal.rb +40 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
- data/lib/active_record/type/integer.rb +55 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +56 -0
- data/lib/active_record/type/string.rb +36 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +101 -0
- data/lib/active_record/validations.rb +90 -0
- data/lib/active_record/validations/associated.rb +51 -0
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +229 -0
- data/lib/active_record/version.rb +8 -0
- data/lib/rails/generators/active_record.rb +17 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +70 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +22 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +45 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +52 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- metadata +309 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class SchemaCache
|
4
|
+
attr_reader :version
|
5
|
+
attr_accessor :connection
|
6
|
+
|
7
|
+
def initialize(conn)
|
8
|
+
@connection = conn
|
9
|
+
|
10
|
+
@columns = {}
|
11
|
+
@columns_hash = {}
|
12
|
+
@primary_keys = {}
|
13
|
+
@tables = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def primary_keys(table_name)
|
17
|
+
@primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# A cached lookup for table existence.
|
21
|
+
def table_exists?(name)
|
22
|
+
prepare_tables if @tables.empty?
|
23
|
+
return @tables[name] if @tables.key? name
|
24
|
+
|
25
|
+
@tables[name] = connection.table_exists?(name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add internal cache for table with +table_name+.
|
29
|
+
def add(table_name)
|
30
|
+
if table_exists?(table_name)
|
31
|
+
primary_keys(table_name)
|
32
|
+
columns(table_name)
|
33
|
+
columns_hash(table_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def tables(name)
|
38
|
+
@tables[name]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get the columns for a table
|
42
|
+
def columns(table_name)
|
43
|
+
@columns[table_name] ||= connection.columns(table_name)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get the columns for a table as a hash, key is the column name
|
47
|
+
# value is the column object.
|
48
|
+
def columns_hash(table_name)
|
49
|
+
@columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
|
50
|
+
[col.name, col]
|
51
|
+
}]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Clears out internal caches
|
55
|
+
def clear!
|
56
|
+
@columns.clear
|
57
|
+
@columns_hash.clear
|
58
|
+
@primary_keys.clear
|
59
|
+
@tables.clear
|
60
|
+
@version = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def size
|
64
|
+
[@columns, @columns_hash, @primary_keys, @tables].map { |x|
|
65
|
+
x.size
|
66
|
+
}.inject :+
|
67
|
+
end
|
68
|
+
|
69
|
+
# Clear out internal caches for table with +table_name+.
|
70
|
+
def clear_table_cache!(table_name)
|
71
|
+
@columns.delete table_name
|
72
|
+
@columns_hash.delete table_name
|
73
|
+
@primary_keys.delete table_name
|
74
|
+
@tables.delete table_name
|
75
|
+
end
|
76
|
+
|
77
|
+
def marshal_dump
|
78
|
+
# if we get current version during initialization, it happens stack over flow.
|
79
|
+
@version = ActiveRecord::Migrator.current_version
|
80
|
+
[@version, @columns, @columns_hash, @primary_keys, @tables]
|
81
|
+
end
|
82
|
+
|
83
|
+
def marshal_load(array)
|
84
|
+
@version, @columns, @columns_hash, @primary_keys, @tables = array
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def prepare_tables
|
90
|
+
connection.tables.each { |table| @tables[table] = true }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,628 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
+
require 'active_record/connection_adapters/statement_pool'
|
3
|
+
require 'arel/visitors/bind_visitor'
|
4
|
+
|
5
|
+
gem 'sqlite3', '~> 1.3.6'
|
6
|
+
require 'sqlite3'
|
7
|
+
|
8
|
+
module ActiveRecord
|
9
|
+
module ConnectionHandling # :nodoc:
|
10
|
+
# sqlite3 adapter reuses sqlite_connection.
|
11
|
+
def sqlite3_connection(config)
|
12
|
+
# Require database.
|
13
|
+
unless config[:database]
|
14
|
+
raise ArgumentError, "No database file specified. Missing argument: database"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Allow database path relative to Rails.root, but only if the database
|
18
|
+
# path is not the special path that tells sqlite to build a database only
|
19
|
+
# in memory.
|
20
|
+
if ':memory:' != config[:database]
|
21
|
+
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
|
22
|
+
dirname = File.dirname(config[:database])
|
23
|
+
Dir.mkdir(dirname) unless File.directory?(dirname)
|
24
|
+
end
|
25
|
+
|
26
|
+
db = SQLite3::Database.new(
|
27
|
+
config[:database].to_s,
|
28
|
+
:results_as_hash => true
|
29
|
+
)
|
30
|
+
|
31
|
+
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
|
32
|
+
|
33
|
+
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
|
34
|
+
rescue Errno::ENOENT => error
|
35
|
+
if error.message.include?("No such file or directory")
|
36
|
+
raise ActiveRecord::NoDatabaseError.new(error.message, error)
|
37
|
+
else
|
38
|
+
raise
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
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
|
+
class SQLite3String < Type::String # :nodoc:
|
54
|
+
def type_cast_for_database(value)
|
55
|
+
if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT
|
56
|
+
value.encode(Encoding::UTF_8)
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# The SQLite3 adapter works SQLite 3.6.16 or newer
|
64
|
+
# with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3).
|
65
|
+
#
|
66
|
+
# Options:
|
67
|
+
#
|
68
|
+
# * <tt>:database</tt> - Path to the database file.
|
69
|
+
class SQLite3Adapter < AbstractAdapter
|
70
|
+
ADAPTER_NAME = 'SQLite'.freeze
|
71
|
+
include Savepoints
|
72
|
+
|
73
|
+
NATIVE_DATABASE_TYPES = {
|
74
|
+
primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
|
75
|
+
string: { name: "varchar" },
|
76
|
+
text: { name: "text" },
|
77
|
+
integer: { name: "integer" },
|
78
|
+
float: { name: "float" },
|
79
|
+
decimal: { name: "decimal" },
|
80
|
+
datetime: { name: "datetime" },
|
81
|
+
time: { name: "time" },
|
82
|
+
date: { name: "date" },
|
83
|
+
binary: { name: "blob" },
|
84
|
+
boolean: { name: "boolean" }
|
85
|
+
}
|
86
|
+
|
87
|
+
class Version
|
88
|
+
include Comparable
|
89
|
+
|
90
|
+
def initialize(version_string)
|
91
|
+
@version = version_string.split('.').map { |v| v.to_i }
|
92
|
+
end
|
93
|
+
|
94
|
+
def <=>(version_string)
|
95
|
+
@version <=> version_string.split('.').map { |v| v.to_i }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class StatementPool < ConnectionAdapters::StatementPool
|
100
|
+
def initialize(connection, max)
|
101
|
+
super
|
102
|
+
@cache = Hash.new { |h,pid| h[pid] = {} }
|
103
|
+
end
|
104
|
+
|
105
|
+
def each(&block); cache.each(&block); end
|
106
|
+
def key?(key); cache.key?(key); end
|
107
|
+
def [](key); cache[key]; end
|
108
|
+
def length; cache.length; end
|
109
|
+
|
110
|
+
def []=(sql, key)
|
111
|
+
while @max <= cache.size
|
112
|
+
dealloc(cache.shift.last[:stmt])
|
113
|
+
end
|
114
|
+
cache[sql] = key
|
115
|
+
end
|
116
|
+
|
117
|
+
def clear
|
118
|
+
cache.each_value do |hash|
|
119
|
+
dealloc hash[:stmt]
|
120
|
+
end
|
121
|
+
cache.clear
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def cache
|
126
|
+
@cache[$$]
|
127
|
+
end
|
128
|
+
|
129
|
+
def dealloc(stmt)
|
130
|
+
stmt.close unless stmt.closed?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def initialize(connection, logger, connection_options, config)
|
135
|
+
super(connection, logger)
|
136
|
+
|
137
|
+
@active = nil
|
138
|
+
@statements = StatementPool.new(@connection,
|
139
|
+
self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
|
140
|
+
@config = config
|
141
|
+
|
142
|
+
@visitor = Arel::Visitors::SQLite.new self
|
143
|
+
|
144
|
+
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
|
145
|
+
@prepared_statements = true
|
146
|
+
else
|
147
|
+
@prepared_statements = false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def supports_ddl_transactions?
|
152
|
+
true
|
153
|
+
end
|
154
|
+
|
155
|
+
def supports_savepoints?
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
def supports_partial_index?
|
160
|
+
sqlite_version >= '3.8.0'
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns true, since this connection adapter supports prepared statement
|
164
|
+
# caching.
|
165
|
+
def supports_statement_cache?
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns true, since this connection adapter supports migrations.
|
170
|
+
def supports_migrations? #:nodoc:
|
171
|
+
true
|
172
|
+
end
|
173
|
+
|
174
|
+
def supports_primary_key? #:nodoc:
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
def requires_reloading?
|
179
|
+
true
|
180
|
+
end
|
181
|
+
|
182
|
+
def supports_views?
|
183
|
+
true
|
184
|
+
end
|
185
|
+
|
186
|
+
def active?
|
187
|
+
@active != false
|
188
|
+
end
|
189
|
+
|
190
|
+
# Disconnects from the database if already connected. Otherwise, this
|
191
|
+
# method does nothing.
|
192
|
+
def disconnect!
|
193
|
+
super
|
194
|
+
@active = false
|
195
|
+
@connection.close rescue nil
|
196
|
+
end
|
197
|
+
|
198
|
+
# Clears the prepared statements cache.
|
199
|
+
def clear_cache!
|
200
|
+
@statements.clear
|
201
|
+
end
|
202
|
+
|
203
|
+
def supports_index_sort_order?
|
204
|
+
true
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns 62. SQLite supports index names up to 64
|
208
|
+
# characters. The rest is used by rails internally to perform
|
209
|
+
# temporary rename operations
|
210
|
+
def allowed_index_name_length
|
211
|
+
index_name_length - 2
|
212
|
+
end
|
213
|
+
|
214
|
+
def native_database_types #:nodoc:
|
215
|
+
NATIVE_DATABASE_TYPES
|
216
|
+
end
|
217
|
+
|
218
|
+
# Returns the current database encoding format as a string, eg: 'UTF-8'
|
219
|
+
def encoding
|
220
|
+
@connection.encoding.to_s
|
221
|
+
end
|
222
|
+
|
223
|
+
def supports_explain?
|
224
|
+
true
|
225
|
+
end
|
226
|
+
|
227
|
+
# QUOTING ==================================================
|
228
|
+
|
229
|
+
def _quote(value) # :nodoc:
|
230
|
+
case value
|
231
|
+
when Type::Binary::Data
|
232
|
+
"x'#{value.hex}'"
|
233
|
+
else
|
234
|
+
super
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def _type_cast(value) # :nodoc:
|
239
|
+
case value
|
240
|
+
when BigDecimal
|
241
|
+
value.to_f
|
242
|
+
else
|
243
|
+
super
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def quote_string(s) #:nodoc:
|
248
|
+
@connection.class.quote(s)
|
249
|
+
end
|
250
|
+
|
251
|
+
def quote_table_name_for_assignment(table, attr)
|
252
|
+
quote_column_name(attr)
|
253
|
+
end
|
254
|
+
|
255
|
+
def quote_column_name(name) #:nodoc:
|
256
|
+
%Q("#{name.to_s.gsub('"', '""')}")
|
257
|
+
end
|
258
|
+
|
259
|
+
# Quote date/time values for use in SQL input. Includes microseconds
|
260
|
+
# if the value is a Time responding to usec.
|
261
|
+
def quoted_date(value) #:nodoc:
|
262
|
+
if value.respond_to?(:usec)
|
263
|
+
"#{super}.#{sprintf("%06d", value.usec)}"
|
264
|
+
else
|
265
|
+
super
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
#--
|
270
|
+
# DATABASE STATEMENTS ======================================
|
271
|
+
#++
|
272
|
+
|
273
|
+
def explain(arel, binds = [])
|
274
|
+
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
275
|
+
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
|
276
|
+
end
|
277
|
+
|
278
|
+
class ExplainPrettyPrinter
|
279
|
+
# Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
|
280
|
+
# the output of the SQLite shell:
|
281
|
+
#
|
282
|
+
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
|
283
|
+
# 0|1|1|SCAN TABLE posts (~100000 rows)
|
284
|
+
#
|
285
|
+
def pp(result) # :nodoc:
|
286
|
+
result.rows.map do |row|
|
287
|
+
row.join('|')
|
288
|
+
end.join("\n") + "\n"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def exec_query(sql, name = nil, binds = [])
|
293
|
+
type_casted_binds = binds.map { |col, val|
|
294
|
+
[col, type_cast(val, col)]
|
295
|
+
}
|
296
|
+
|
297
|
+
log(sql, name, type_casted_binds) do
|
298
|
+
# Don't cache statements if they are not prepared
|
299
|
+
if without_prepared_statement?(binds)
|
300
|
+
stmt = @connection.prepare(sql)
|
301
|
+
begin
|
302
|
+
cols = stmt.columns
|
303
|
+
records = stmt.to_a
|
304
|
+
ensure
|
305
|
+
stmt.close
|
306
|
+
end
|
307
|
+
stmt = records
|
308
|
+
else
|
309
|
+
cache = @statements[sql] ||= {
|
310
|
+
:stmt => @connection.prepare(sql)
|
311
|
+
}
|
312
|
+
stmt = cache[:stmt]
|
313
|
+
cols = cache[:cols] ||= stmt.columns
|
314
|
+
stmt.reset!
|
315
|
+
stmt.bind_params type_casted_binds.map { |_, val| val }
|
316
|
+
end
|
317
|
+
|
318
|
+
ActiveRecord::Result.new(cols, stmt.to_a)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def exec_delete(sql, name = 'SQL', binds = [])
|
323
|
+
exec_query(sql, name, binds)
|
324
|
+
@connection.changes
|
325
|
+
end
|
326
|
+
alias :exec_update :exec_delete
|
327
|
+
|
328
|
+
def last_inserted_id(result)
|
329
|
+
@connection.last_insert_row_id
|
330
|
+
end
|
331
|
+
|
332
|
+
def execute(sql, name = nil) #:nodoc:
|
333
|
+
log(sql, name) { @connection.execute(sql) }
|
334
|
+
end
|
335
|
+
|
336
|
+
def update_sql(sql, name = nil) #:nodoc:
|
337
|
+
super
|
338
|
+
@connection.changes
|
339
|
+
end
|
340
|
+
|
341
|
+
def delete_sql(sql, name = nil) #:nodoc:
|
342
|
+
sql += " WHERE 1=1" unless sql =~ /WHERE/i
|
343
|
+
super sql, name
|
344
|
+
end
|
345
|
+
|
346
|
+
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
347
|
+
super
|
348
|
+
id_value || @connection.last_insert_row_id
|
349
|
+
end
|
350
|
+
alias :create :insert_sql
|
351
|
+
|
352
|
+
def select_rows(sql, name = nil, binds = [])
|
353
|
+
exec_query(sql, name, binds).rows
|
354
|
+
end
|
355
|
+
|
356
|
+
def begin_db_transaction #:nodoc:
|
357
|
+
log('begin transaction',nil) { @connection.transaction }
|
358
|
+
end
|
359
|
+
|
360
|
+
def commit_db_transaction #:nodoc:
|
361
|
+
log('commit transaction',nil) { @connection.commit }
|
362
|
+
end
|
363
|
+
|
364
|
+
def rollback_db_transaction #:nodoc:
|
365
|
+
log('rollback transaction',nil) { @connection.rollback }
|
366
|
+
end
|
367
|
+
|
368
|
+
# SCHEMA STATEMENTS ========================================
|
369
|
+
|
370
|
+
def tables(name = nil, table_name = nil) #:nodoc:
|
371
|
+
sql = <<-SQL
|
372
|
+
SELECT name
|
373
|
+
FROM sqlite_master
|
374
|
+
WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence'
|
375
|
+
SQL
|
376
|
+
sql << " AND name = #{quote_table_name(table_name)}" if table_name
|
377
|
+
|
378
|
+
exec_query(sql, 'SCHEMA').map do |row|
|
379
|
+
row['name']
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def table_exists?(table_name)
|
384
|
+
table_name && tables(nil, table_name).any?
|
385
|
+
end
|
386
|
+
|
387
|
+
# Returns an array of +Column+ objects for the table specified by +table_name+.
|
388
|
+
def columns(table_name) #:nodoc:
|
389
|
+
table_structure(table_name).map do |field|
|
390
|
+
case field["dflt_value"]
|
391
|
+
when /^null$/i
|
392
|
+
field["dflt_value"] = nil
|
393
|
+
when /^'(.*)'$/m
|
394
|
+
field["dflt_value"] = $1.gsub("''", "'")
|
395
|
+
when /^"(.*)"$/m
|
396
|
+
field["dflt_value"] = $1.gsub('""', '"')
|
397
|
+
end
|
398
|
+
|
399
|
+
sql_type = field['type']
|
400
|
+
cast_type = lookup_cast_type(sql_type)
|
401
|
+
new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# Returns an array of indexes for the given table.
|
406
|
+
def indexes(table_name, name = nil) #:nodoc:
|
407
|
+
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
|
408
|
+
sql = <<-SQL
|
409
|
+
SELECT sql
|
410
|
+
FROM sqlite_master
|
411
|
+
WHERE name=#{quote(row['name'])} AND type='index'
|
412
|
+
UNION ALL
|
413
|
+
SELECT sql
|
414
|
+
FROM sqlite_temp_master
|
415
|
+
WHERE name=#{quote(row['name'])} AND type='index'
|
416
|
+
SQL
|
417
|
+
index_sql = exec_query(sql).first['sql']
|
418
|
+
match = /\sWHERE\s+(.+)$/i.match(index_sql)
|
419
|
+
where = match[1] if match
|
420
|
+
IndexDefinition.new(
|
421
|
+
table_name,
|
422
|
+
row['name'],
|
423
|
+
row['unique'] != 0,
|
424
|
+
exec_query("PRAGMA index_info('#{row['name']}')", "SCHEMA").map { |col|
|
425
|
+
col['name']
|
426
|
+
}, nil, nil, where)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def primary_key(table_name) #:nodoc:
|
431
|
+
column = table_structure(table_name).find { |field|
|
432
|
+
field['pk'] == 1
|
433
|
+
}
|
434
|
+
column && column['name']
|
435
|
+
end
|
436
|
+
|
437
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
438
|
+
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
439
|
+
end
|
440
|
+
|
441
|
+
# Renames a table.
|
442
|
+
#
|
443
|
+
# Example:
|
444
|
+
# rename_table('octopuses', 'octopi')
|
445
|
+
def rename_table(table_name, new_name)
|
446
|
+
exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}"
|
447
|
+
rename_table_indexes(table_name, new_name)
|
448
|
+
end
|
449
|
+
|
450
|
+
# See: http://www.sqlite.org/lang_altertable.html
|
451
|
+
# SQLite has an additional restriction on the ALTER TABLE statement
|
452
|
+
def valid_alter_table_type?(type)
|
453
|
+
type.to_sym != :primary_key
|
454
|
+
end
|
455
|
+
|
456
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
457
|
+
if valid_alter_table_type?(type)
|
458
|
+
super(table_name, column_name, type, options)
|
459
|
+
else
|
460
|
+
alter_table(table_name) do |definition|
|
461
|
+
definition.column(column_name, type, options)
|
462
|
+
end
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
467
|
+
alter_table(table_name) do |definition|
|
468
|
+
definition.remove_column column_name
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
473
|
+
alter_table(table_name) do |definition|
|
474
|
+
definition[column_name].default = default
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def change_column_null(table_name, column_name, null, default = nil)
|
479
|
+
unless null || default.nil?
|
480
|
+
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
481
|
+
end
|
482
|
+
alter_table(table_name) do |definition|
|
483
|
+
definition[column_name].null = null
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
488
|
+
alter_table(table_name) do |definition|
|
489
|
+
include_default = options_include_default?(options)
|
490
|
+
definition[column_name].instance_eval do
|
491
|
+
self.type = type
|
492
|
+
self.limit = options[:limit] if options.include?(:limit)
|
493
|
+
self.default = options[:default] if include_default
|
494
|
+
self.null = options[:null] if options.include?(:null)
|
495
|
+
self.precision = options[:precision] if options.include?(:precision)
|
496
|
+
self.scale = options[:scale] if options.include?(:scale)
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
502
|
+
column = column_for(table_name, column_name)
|
503
|
+
alter_table(table_name, rename: {column.name => new_column_name.to_s})
|
504
|
+
rename_column_indexes(table_name, column.name, new_column_name)
|
505
|
+
end
|
506
|
+
|
507
|
+
protected
|
508
|
+
|
509
|
+
def initialize_type_map(m)
|
510
|
+
super
|
511
|
+
m.register_type(/binary/i, SQLite3Binary.new)
|
512
|
+
register_class_with_limit m, %r(char)i, SQLite3String
|
513
|
+
end
|
514
|
+
|
515
|
+
def table_structure(table_name)
|
516
|
+
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
|
517
|
+
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
518
|
+
structure
|
519
|
+
end
|
520
|
+
|
521
|
+
def alter_table(table_name, options = {}) #:nodoc:
|
522
|
+
altered_table_name = "a#{table_name}"
|
523
|
+
caller = lambda {|definition| yield definition if block_given?}
|
524
|
+
|
525
|
+
transaction do
|
526
|
+
move_table(table_name, altered_table_name,
|
527
|
+
options.merge(:temporary => true))
|
528
|
+
move_table(altered_table_name, table_name, &caller)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def move_table(from, to, options = {}, &block) #:nodoc:
|
533
|
+
copy_table(from, to, options, &block)
|
534
|
+
drop_table(from)
|
535
|
+
end
|
536
|
+
|
537
|
+
def copy_table(from, to, options = {}) #:nodoc:
|
538
|
+
from_primary_key = primary_key(from)
|
539
|
+
options[:id] = false
|
540
|
+
create_table(to, options) do |definition|
|
541
|
+
@definition = definition
|
542
|
+
@definition.primary_key(from_primary_key) if from_primary_key.present?
|
543
|
+
columns(from).each do |column|
|
544
|
+
column_name = options[:rename] ?
|
545
|
+
(options[:rename][column.name] ||
|
546
|
+
options[:rename][column.name.to_sym] ||
|
547
|
+
column.name) : column.name
|
548
|
+
next if column_name == from_primary_key
|
549
|
+
|
550
|
+
@definition.column(column_name, column.type,
|
551
|
+
:limit => column.limit, :default => column.default,
|
552
|
+
:precision => column.precision, :scale => column.scale,
|
553
|
+
:null => column.null)
|
554
|
+
end
|
555
|
+
yield @definition if block_given?
|
556
|
+
end
|
557
|
+
copy_table_indexes(from, to, options[:rename] || {})
|
558
|
+
copy_table_contents(from, to,
|
559
|
+
@definition.columns.map {|column| column.name},
|
560
|
+
options[:rename] || {})
|
561
|
+
end
|
562
|
+
|
563
|
+
def copy_table_indexes(from, to, rename = {}) #:nodoc:
|
564
|
+
indexes(from).each do |index|
|
565
|
+
name = index.name
|
566
|
+
if to == "a#{from}"
|
567
|
+
name = "t#{name}"
|
568
|
+
elsif from == "a#{to}"
|
569
|
+
name = name[1..-1]
|
570
|
+
end
|
571
|
+
|
572
|
+
to_column_names = columns(to).map { |c| c.name }
|
573
|
+
columns = index.columns.map {|c| rename[c] || c }.select do |column|
|
574
|
+
to_column_names.include?(column)
|
575
|
+
end
|
576
|
+
|
577
|
+
unless columns.empty?
|
578
|
+
# index name can't be the same
|
579
|
+
opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true }
|
580
|
+
opts[:unique] = true if index.unique
|
581
|
+
add_index(to, columns, opts)
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
587
|
+
column_mappings = Hash[columns.map {|name| [name, name]}]
|
588
|
+
rename.each { |a| column_mappings[a.last] = a.first }
|
589
|
+
from_columns = columns(from).collect {|col| col.name}
|
590
|
+
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
591
|
+
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
592
|
+
|
593
|
+
quoted_to = quote_table_name(to)
|
594
|
+
|
595
|
+
raw_column_mappings = Hash[columns(from).map { |c| [c.name, c] }]
|
596
|
+
|
597
|
+
exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
|
598
|
+
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
|
599
|
+
|
600
|
+
column_values = columns.map do |col|
|
601
|
+
quote(row[column_mappings[col]], raw_column_mappings[col])
|
602
|
+
end
|
603
|
+
|
604
|
+
sql << column_values * ', '
|
605
|
+
sql << ')'
|
606
|
+
exec_query sql
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def sqlite_version
|
611
|
+
@sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
|
612
|
+
end
|
613
|
+
|
614
|
+
def translate_exception(exception, message)
|
615
|
+
case exception.message
|
616
|
+
# SQLite 3.8.2 returns a newly formatted error message:
|
617
|
+
# UNIQUE constraint failed: *table_name*.*column_name*
|
618
|
+
# Older versions of SQLite return:
|
619
|
+
# column *column_name* is not unique
|
620
|
+
when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/
|
621
|
+
RecordNotUnique.new(message, exception)
|
622
|
+
else
|
623
|
+
super
|
624
|
+
end
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|