activerecord 3.2.22.5 → 4.0.0.beta1
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 +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- 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/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/session_store.rb +0 -360
- data/lib/rails/generators/active_record/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,32 +0,0 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
require 'active_support/deprecation'
|
3
|
-
|
4
|
-
module ActiveRecord
|
5
|
-
module AttributeMethods
|
6
|
-
module DeprecatedUnderscoreRead
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
included do
|
10
|
-
attribute_method_prefix "_"
|
11
|
-
end
|
12
|
-
|
13
|
-
module ClassMethods
|
14
|
-
protected
|
15
|
-
|
16
|
-
def define_method__attribute(attr_name)
|
17
|
-
# Do nothing, let it hit method missing instead.
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
protected
|
22
|
-
|
23
|
-
def _attribute(attr_name)
|
24
|
-
ActiveSupport::Deprecation.warn(
|
25
|
-
"You have called '_#{attr_name}'. This is deprecated. Please use " \
|
26
|
-
"either '#{attr_name}' or read_attribute('#{attr_name}')."
|
27
|
-
)
|
28
|
-
read_attribute(attr_name)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,191 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
|
3
|
-
module ActiveRecord
|
4
|
-
class Base
|
5
|
-
class ConnectionSpecification #:nodoc:
|
6
|
-
attr_reader :config, :adapter_method
|
7
|
-
def initialize (config, adapter_method)
|
8
|
-
@config, @adapter_method = config, adapter_method
|
9
|
-
end
|
10
|
-
|
11
|
-
##
|
12
|
-
# Builds a ConnectionSpecification from user input
|
13
|
-
class Resolver # :nodoc:
|
14
|
-
attr_reader :config, :klass, :configurations
|
15
|
-
|
16
|
-
def initialize(config, configurations)
|
17
|
-
@config = config
|
18
|
-
@configurations = configurations
|
19
|
-
end
|
20
|
-
|
21
|
-
def spec
|
22
|
-
case config
|
23
|
-
when nil
|
24
|
-
raise AdapterNotSpecified unless defined?(Rails.env)
|
25
|
-
resolve_string_connection Rails.env
|
26
|
-
when Symbol, String
|
27
|
-
resolve_string_connection config.to_s
|
28
|
-
when Hash
|
29
|
-
resolve_hash_connection config
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
def resolve_string_connection(spec) # :nodoc:
|
35
|
-
hash = configurations.fetch(spec) do |k|
|
36
|
-
connection_url_to_hash(k)
|
37
|
-
end
|
38
|
-
|
39
|
-
raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
|
40
|
-
|
41
|
-
resolve_hash_connection hash
|
42
|
-
end
|
43
|
-
|
44
|
-
def resolve_hash_connection(spec) # :nodoc:
|
45
|
-
spec = spec.symbolize_keys
|
46
|
-
|
47
|
-
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
|
48
|
-
|
49
|
-
begin
|
50
|
-
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
|
51
|
-
rescue LoadError => e
|
52
|
-
raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace
|
53
|
-
end
|
54
|
-
|
55
|
-
adapter_method = "#{spec[:adapter]}_connection"
|
56
|
-
|
57
|
-
ConnectionSpecification.new(spec, adapter_method)
|
58
|
-
end
|
59
|
-
|
60
|
-
def connection_url_to_hash(url) # :nodoc:
|
61
|
-
config = URI.parse url
|
62
|
-
adapter = config.scheme
|
63
|
-
adapter = "postgresql" if adapter == "postgres"
|
64
|
-
spec = { :adapter => adapter,
|
65
|
-
:username => config.user,
|
66
|
-
:password => config.password,
|
67
|
-
:port => config.port,
|
68
|
-
:database => config.path.sub(%r{^/},""),
|
69
|
-
:host => config.host }
|
70
|
-
spec.reject!{ |_,value| value.blank? }
|
71
|
-
spec.map { |key,value| spec[key] = URI.unescape(value) if value.is_a?(String) }
|
72
|
-
if config.query
|
73
|
-
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
|
74
|
-
spec.merge!(options)
|
75
|
-
end
|
76
|
-
spec
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
##
|
82
|
-
# :singleton-method:
|
83
|
-
# The connection handler
|
84
|
-
class_attribute :connection_handler, :instance_writer => false
|
85
|
-
self.connection_handler = ConnectionAdapters::ConnectionHandler.new
|
86
|
-
|
87
|
-
# Returns the connection currently associated with the class. This can
|
88
|
-
# also be used to "borrow" the connection to do database work that isn't
|
89
|
-
# easily done without going straight to SQL.
|
90
|
-
def connection
|
91
|
-
self.class.connection
|
92
|
-
end
|
93
|
-
|
94
|
-
# Establishes the connection to the database. Accepts a hash as input where
|
95
|
-
# the <tt>:adapter</tt> key must be specified with the name of a database adapter (in lower-case)
|
96
|
-
# example for regular databases (MySQL, Postgresql, etc):
|
97
|
-
#
|
98
|
-
# ActiveRecord::Base.establish_connection(
|
99
|
-
# :adapter => "mysql",
|
100
|
-
# :host => "localhost",
|
101
|
-
# :username => "myuser",
|
102
|
-
# :password => "mypass",
|
103
|
-
# :database => "somedatabase"
|
104
|
-
# )
|
105
|
-
#
|
106
|
-
# Example for SQLite database:
|
107
|
-
#
|
108
|
-
# ActiveRecord::Base.establish_connection(
|
109
|
-
# :adapter => "sqlite",
|
110
|
-
# :database => "path/to/dbfile"
|
111
|
-
# )
|
112
|
-
#
|
113
|
-
# Also accepts keys as strings (for parsing from YAML for example):
|
114
|
-
#
|
115
|
-
# ActiveRecord::Base.establish_connection(
|
116
|
-
# "adapter" => "sqlite",
|
117
|
-
# "database" => "path/to/dbfile"
|
118
|
-
# )
|
119
|
-
#
|
120
|
-
# Or a URL:
|
121
|
-
#
|
122
|
-
# ActiveRecord::Base.establish_connection(
|
123
|
-
# "postgres://myuser:mypass@localhost/somedatabase"
|
124
|
-
# )
|
125
|
-
#
|
126
|
-
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
127
|
-
# may be returned on an error.
|
128
|
-
def self.establish_connection(spec = ENV["DATABASE_URL"])
|
129
|
-
resolver = ConnectionSpecification::Resolver.new spec, configurations
|
130
|
-
spec = resolver.spec
|
131
|
-
|
132
|
-
unless respond_to?(spec.adapter_method)
|
133
|
-
raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
|
134
|
-
end
|
135
|
-
|
136
|
-
remove_connection
|
137
|
-
connection_handler.establish_connection name, spec
|
138
|
-
end
|
139
|
-
|
140
|
-
class << self
|
141
|
-
# Returns the connection currently associated with the class. This can
|
142
|
-
# also be used to "borrow" the connection to do database work unrelated
|
143
|
-
# to any of the specific Active Records.
|
144
|
-
def connection
|
145
|
-
retrieve_connection
|
146
|
-
end
|
147
|
-
|
148
|
-
def connection_id
|
149
|
-
Thread.current['ActiveRecord::Base.connection_id']
|
150
|
-
end
|
151
|
-
|
152
|
-
def connection_id=(connection_id)
|
153
|
-
Thread.current['ActiveRecord::Base.connection_id'] = connection_id
|
154
|
-
end
|
155
|
-
|
156
|
-
# Returns the configuration of the associated connection as a hash:
|
157
|
-
#
|
158
|
-
# ActiveRecord::Base.connection_config
|
159
|
-
# # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
|
160
|
-
#
|
161
|
-
# Please use only for reading.
|
162
|
-
def connection_config
|
163
|
-
connection_pool.spec.config
|
164
|
-
end
|
165
|
-
|
166
|
-
def connection_pool
|
167
|
-
connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
|
168
|
-
end
|
169
|
-
|
170
|
-
def retrieve_connection
|
171
|
-
connection_handler.retrieve_connection(self)
|
172
|
-
end
|
173
|
-
|
174
|
-
# Returns true if Active Record is connected.
|
175
|
-
def connected?
|
176
|
-
connection_handler.connected?(self)
|
177
|
-
end
|
178
|
-
|
179
|
-
def remove_connection(klass = self)
|
180
|
-
connection_handler.remove_connection(klass)
|
181
|
-
end
|
182
|
-
|
183
|
-
def clear_active_connections!
|
184
|
-
connection_handler.clear_active_connections!
|
185
|
-
end
|
186
|
-
|
187
|
-
delegate :clear_reloadable_connections!,
|
188
|
-
:clear_all_connections!,:verify_active_connections!, :to => :connection_handler
|
189
|
-
end
|
190
|
-
end
|
191
|
-
end
|
@@ -1,583 +0,0 @@
|
|
1
|
-
require 'active_record/connection_adapters/abstract_adapter'
|
2
|
-
require 'active_record/connection_adapters/statement_pool'
|
3
|
-
require 'active_support/core_ext/string/encoding'
|
4
|
-
require 'arel/visitors/bind_visitor'
|
5
|
-
|
6
|
-
module ActiveRecord
|
7
|
-
module ConnectionAdapters #:nodoc:
|
8
|
-
class SQLiteColumn < Column #:nodoc:
|
9
|
-
class << self
|
10
|
-
def binary_to_string(value)
|
11
|
-
if value.respond_to?(:force_encoding) && value.encoding != Encoding::ASCII_8BIT
|
12
|
-
value = value.force_encoding(Encoding::ASCII_8BIT)
|
13
|
-
end
|
14
|
-
value
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
# The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby
|
20
|
-
# drivers (available both as gems and from http://rubyforge.org/projects/sqlite-ruby/).
|
21
|
-
#
|
22
|
-
# Options:
|
23
|
-
#
|
24
|
-
# * <tt>:database</tt> - Path to the database file.
|
25
|
-
class SQLiteAdapter < AbstractAdapter
|
26
|
-
class Version
|
27
|
-
include Comparable
|
28
|
-
|
29
|
-
def initialize(version_string)
|
30
|
-
@version = version_string.split('.').map { |v| v.to_i }
|
31
|
-
end
|
32
|
-
|
33
|
-
def <=>(version_string)
|
34
|
-
@version <=> version_string.split('.').map { |v| v.to_i }
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
class StatementPool < ConnectionAdapters::StatementPool
|
39
|
-
def initialize(connection, max)
|
40
|
-
super
|
41
|
-
@cache = Hash.new { |h,pid| h[pid] = {} }
|
42
|
-
end
|
43
|
-
|
44
|
-
def each(&block); cache.each(&block); end
|
45
|
-
def key?(key); cache.key?(key); end
|
46
|
-
def [](key); cache[key]; end
|
47
|
-
def length; cache.length; end
|
48
|
-
|
49
|
-
def []=(sql, key)
|
50
|
-
while @max <= cache.size
|
51
|
-
dealloc(cache.shift.last[:stmt])
|
52
|
-
end
|
53
|
-
cache[sql] = key
|
54
|
-
end
|
55
|
-
|
56
|
-
def clear
|
57
|
-
cache.values.each do |hash|
|
58
|
-
dealloc hash[:stmt]
|
59
|
-
end
|
60
|
-
cache.clear
|
61
|
-
end
|
62
|
-
|
63
|
-
private
|
64
|
-
def cache
|
65
|
-
@cache[$$]
|
66
|
-
end
|
67
|
-
|
68
|
-
def dealloc(stmt)
|
69
|
-
stmt.close unless stmt.closed?
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
|
74
|
-
include Arel::Visitors::BindVisitor
|
75
|
-
end
|
76
|
-
|
77
|
-
def initialize(connection, logger, config)
|
78
|
-
super(connection, logger)
|
79
|
-
@statements = StatementPool.new(@connection,
|
80
|
-
config.fetch(:statement_limit) { 1000 })
|
81
|
-
@config = config
|
82
|
-
|
83
|
-
if config.fetch(:prepared_statements) { true }
|
84
|
-
@visitor = Arel::Visitors::SQLite.new self
|
85
|
-
else
|
86
|
-
@visitor = BindSubstitution.new self
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def adapter_name #:nodoc:
|
91
|
-
'SQLite'
|
92
|
-
end
|
93
|
-
|
94
|
-
# Returns true if SQLite version is '2.0.0' or greater, false otherwise.
|
95
|
-
def supports_ddl_transactions?
|
96
|
-
sqlite_version >= '2.0.0'
|
97
|
-
end
|
98
|
-
|
99
|
-
# Returns true if SQLite version is '3.6.8' or greater, false otherwise.
|
100
|
-
def supports_savepoints?
|
101
|
-
sqlite_version >= '3.6.8'
|
102
|
-
end
|
103
|
-
|
104
|
-
# Returns true, since this connection adapter supports prepared statement
|
105
|
-
# caching.
|
106
|
-
def supports_statement_cache?
|
107
|
-
true
|
108
|
-
end
|
109
|
-
|
110
|
-
# Returns true, since this connection adapter supports migrations.
|
111
|
-
def supports_migrations? #:nodoc:
|
112
|
-
true
|
113
|
-
end
|
114
|
-
|
115
|
-
# Returns true.
|
116
|
-
def supports_primary_key? #:nodoc:
|
117
|
-
true
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns true.
|
121
|
-
def supports_explain?
|
122
|
-
true
|
123
|
-
end
|
124
|
-
|
125
|
-
def requires_reloading?
|
126
|
-
true
|
127
|
-
end
|
128
|
-
|
129
|
-
# Returns true if SQLite version is '3.1.6' or greater, false otherwise.
|
130
|
-
def supports_add_column?
|
131
|
-
sqlite_version >= '3.1.6'
|
132
|
-
end
|
133
|
-
|
134
|
-
# Disconnects from the database if already connected. Otherwise, this
|
135
|
-
# method does nothing.
|
136
|
-
def disconnect!
|
137
|
-
super
|
138
|
-
clear_cache!
|
139
|
-
@connection.close rescue nil
|
140
|
-
end
|
141
|
-
|
142
|
-
# Clears the prepared statements cache.
|
143
|
-
def clear_cache!
|
144
|
-
@statements.clear
|
145
|
-
end
|
146
|
-
|
147
|
-
# Returns true if SQLite version is '3.2.6' or greater, false otherwise.
|
148
|
-
def supports_count_distinct? #:nodoc:
|
149
|
-
sqlite_version >= '3.2.6'
|
150
|
-
end
|
151
|
-
|
152
|
-
# Returns true if SQLite version is '3.1.0' or greater, false otherwise.
|
153
|
-
def supports_autoincrement? #:nodoc:
|
154
|
-
sqlite_version >= '3.1.0'
|
155
|
-
end
|
156
|
-
|
157
|
-
def supports_index_sort_order?
|
158
|
-
sqlite_version >= '3.3.0'
|
159
|
-
end
|
160
|
-
|
161
|
-
def native_database_types #:nodoc:
|
162
|
-
{
|
163
|
-
:primary_key => default_primary_key_type,
|
164
|
-
:string => { :name => "varchar", :limit => 255 },
|
165
|
-
:text => { :name => "text" },
|
166
|
-
:integer => { :name => "integer" },
|
167
|
-
:float => { :name => "float" },
|
168
|
-
:decimal => { :name => "decimal" },
|
169
|
-
:datetime => { :name => "datetime" },
|
170
|
-
:timestamp => { :name => "datetime" },
|
171
|
-
:time => { :name => "time" },
|
172
|
-
:date => { :name => "date" },
|
173
|
-
:binary => { :name => "blob" },
|
174
|
-
:boolean => { :name => "boolean" }
|
175
|
-
}
|
176
|
-
end
|
177
|
-
|
178
|
-
|
179
|
-
# QUOTING ==================================================
|
180
|
-
|
181
|
-
def quote_string(s) #:nodoc:
|
182
|
-
@connection.class.quote(s)
|
183
|
-
end
|
184
|
-
|
185
|
-
def quote_column_name(name) #:nodoc:
|
186
|
-
%Q("#{name.to_s.gsub('"', '""')}")
|
187
|
-
end
|
188
|
-
|
189
|
-
# Quote date/time values for use in SQL input. Includes microseconds
|
190
|
-
# if the value is a Time responding to usec.
|
191
|
-
def quoted_date(value) #:nodoc:
|
192
|
-
if value.respond_to?(:usec)
|
193
|
-
"#{super}.#{sprintf("%06d", value.usec)}"
|
194
|
-
else
|
195
|
-
super
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
if "<3".encoding_aware?
|
200
|
-
def type_cast(value, column) # :nodoc:
|
201
|
-
return value.to_f if BigDecimal === value
|
202
|
-
return super unless String === value
|
203
|
-
return super unless column && value
|
204
|
-
|
205
|
-
value = super
|
206
|
-
if column.type == :string && value.encoding == Encoding::ASCII_8BIT
|
207
|
-
logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
|
208
|
-
value = value.encode Encoding::UTF_8
|
209
|
-
end
|
210
|
-
value
|
211
|
-
end
|
212
|
-
else
|
213
|
-
def type_cast(value, column) # :nodoc:
|
214
|
-
return super unless BigDecimal === value
|
215
|
-
|
216
|
-
value.to_f
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
# DATABASE STATEMENTS ======================================
|
221
|
-
|
222
|
-
def explain(arel, binds = [])
|
223
|
-
sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
|
224
|
-
ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
|
225
|
-
end
|
226
|
-
|
227
|
-
class ExplainPrettyPrinter
|
228
|
-
# Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles
|
229
|
-
# the output of the SQLite shell:
|
230
|
-
#
|
231
|
-
# 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
|
232
|
-
# 0|1|1|SCAN TABLE posts (~100000 rows)
|
233
|
-
#
|
234
|
-
def pp(result) # :nodoc:
|
235
|
-
result.rows.map do |row|
|
236
|
-
row.join('|')
|
237
|
-
end.join("\n") + "\n"
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def exec_query(sql, name = nil, binds = [])
|
242
|
-
log(sql, name, binds) do
|
243
|
-
|
244
|
-
# Don't cache statements without bind values
|
245
|
-
if binds.empty?
|
246
|
-
stmt = @connection.prepare(sql)
|
247
|
-
cols = stmt.columns
|
248
|
-
records = stmt.to_a
|
249
|
-
stmt.close
|
250
|
-
stmt = records
|
251
|
-
else
|
252
|
-
cache = @statements[sql] ||= {
|
253
|
-
:stmt => @connection.prepare(sql)
|
254
|
-
}
|
255
|
-
stmt = cache[:stmt]
|
256
|
-
cols = cache[:cols] ||= stmt.columns
|
257
|
-
stmt.reset!
|
258
|
-
stmt.bind_params binds.map { |col, val|
|
259
|
-
type_cast(val, col)
|
260
|
-
}
|
261
|
-
end
|
262
|
-
|
263
|
-
ActiveRecord::Result.new(cols, stmt.to_a)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
def exec_delete(sql, name = 'SQL', binds = [])
|
268
|
-
exec_query(sql, name, binds)
|
269
|
-
@connection.changes
|
270
|
-
end
|
271
|
-
alias :exec_update :exec_delete
|
272
|
-
|
273
|
-
def last_inserted_id(result)
|
274
|
-
@connection.last_insert_row_id
|
275
|
-
end
|
276
|
-
|
277
|
-
def execute(sql, name = nil) #:nodoc:
|
278
|
-
log(sql, name) { @connection.execute(sql) }
|
279
|
-
end
|
280
|
-
|
281
|
-
def update_sql(sql, name = nil) #:nodoc:
|
282
|
-
super
|
283
|
-
@connection.changes
|
284
|
-
end
|
285
|
-
|
286
|
-
def delete_sql(sql, name = nil) #:nodoc:
|
287
|
-
sql += " WHERE 1=1" unless sql =~ /WHERE/i
|
288
|
-
super sql, name
|
289
|
-
end
|
290
|
-
|
291
|
-
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
|
292
|
-
super
|
293
|
-
id_value || @connection.last_insert_row_id
|
294
|
-
end
|
295
|
-
alias :create :insert_sql
|
296
|
-
|
297
|
-
def select_rows(sql, name = nil)
|
298
|
-
exec_query(sql, name).rows
|
299
|
-
end
|
300
|
-
|
301
|
-
def create_savepoint
|
302
|
-
execute("SAVEPOINT #{current_savepoint_name}")
|
303
|
-
end
|
304
|
-
|
305
|
-
def rollback_to_savepoint
|
306
|
-
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
307
|
-
end
|
308
|
-
|
309
|
-
def release_savepoint
|
310
|
-
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
311
|
-
end
|
312
|
-
|
313
|
-
def begin_db_transaction #:nodoc:
|
314
|
-
log('begin transaction',nil) { @connection.transaction }
|
315
|
-
end
|
316
|
-
|
317
|
-
def commit_db_transaction #:nodoc:
|
318
|
-
log('commit transaction',nil) { @connection.commit }
|
319
|
-
end
|
320
|
-
|
321
|
-
def rollback_db_transaction #:nodoc:
|
322
|
-
log('rollback transaction',nil) { @connection.rollback }
|
323
|
-
end
|
324
|
-
|
325
|
-
# SCHEMA STATEMENTS ========================================
|
326
|
-
|
327
|
-
def tables(name = 'SCHEMA', table_name = nil) #:nodoc:
|
328
|
-
sql = <<-SQL
|
329
|
-
SELECT name
|
330
|
-
FROM sqlite_master
|
331
|
-
WHERE type = 'table' AND NOT name = 'sqlite_sequence'
|
332
|
-
SQL
|
333
|
-
sql << " AND name = #{quote_table_name(table_name)}" if table_name
|
334
|
-
|
335
|
-
exec_query(sql, name).map do |row|
|
336
|
-
row['name']
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
def table_exists?(name)
|
341
|
-
name && tables('SCHEMA', name).any?
|
342
|
-
end
|
343
|
-
|
344
|
-
# Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
|
345
|
-
def columns(table_name, name = nil) #:nodoc:
|
346
|
-
table_structure(table_name).map do |field|
|
347
|
-
case field["dflt_value"]
|
348
|
-
when /^null$/i
|
349
|
-
field["dflt_value"] = nil
|
350
|
-
when /^'(.*)'$/
|
351
|
-
field["dflt_value"] = $1.gsub(/''/, "'")
|
352
|
-
when /^"(.*)"$/
|
353
|
-
field["dflt_value"] = $1.gsub(/""/, '"')
|
354
|
-
end
|
355
|
-
|
356
|
-
SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
# Returns an array of indexes for the given table.
|
361
|
-
def indexes(table_name, name = nil) #:nodoc:
|
362
|
-
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", 'SCHEMA').map do |row|
|
363
|
-
IndexDefinition.new(
|
364
|
-
table_name,
|
365
|
-
row['name'],
|
366
|
-
row['unique'] != 0,
|
367
|
-
exec_query("PRAGMA index_info('#{row['name']}')", 'SCHEMA').map { |col|
|
368
|
-
col['name']
|
369
|
-
})
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
def primary_key(table_name) #:nodoc:
|
374
|
-
column = table_structure(table_name).find { |field|
|
375
|
-
field['pk'] == 1
|
376
|
-
}
|
377
|
-
column && column['name']
|
378
|
-
end
|
379
|
-
|
380
|
-
def remove_index!(table_name, index_name) #:nodoc:
|
381
|
-
exec_query "DROP INDEX #{quote_column_name(index_name)}"
|
382
|
-
end
|
383
|
-
|
384
|
-
# Renames a table.
|
385
|
-
#
|
386
|
-
# Example:
|
387
|
-
# rename_table('octopuses', 'octopi')
|
388
|
-
def rename_table(name, new_name)
|
389
|
-
exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
|
390
|
-
end
|
391
|
-
|
392
|
-
# See: http://www.sqlite.org/lang_altertable.html
|
393
|
-
# SQLite has an additional restriction on the ALTER TABLE statement
|
394
|
-
def valid_alter_table_options( type, options)
|
395
|
-
type.to_sym != :primary_key
|
396
|
-
end
|
397
|
-
|
398
|
-
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
399
|
-
if supports_add_column? && valid_alter_table_options( type, options )
|
400
|
-
super(table_name, column_name, type, options)
|
401
|
-
else
|
402
|
-
alter_table(table_name) do |definition|
|
403
|
-
definition.column(column_name, type, options)
|
404
|
-
end
|
405
|
-
end
|
406
|
-
end
|
407
|
-
|
408
|
-
def remove_column(table_name, *column_names) #:nodoc:
|
409
|
-
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
410
|
-
|
411
|
-
if column_names.flatten!
|
412
|
-
message = 'Passing array to remove_columns is deprecated, please use ' +
|
413
|
-
'multiple arguments, like: `remove_columns(:posts, :foo, :bar)`'
|
414
|
-
ActiveSupport::Deprecation.warn message, caller
|
415
|
-
end
|
416
|
-
|
417
|
-
column_names.each do |column_name|
|
418
|
-
alter_table(table_name) do |definition|
|
419
|
-
definition.columns.delete(definition[column_name])
|
420
|
-
end
|
421
|
-
end
|
422
|
-
end
|
423
|
-
alias :remove_columns :remove_column
|
424
|
-
|
425
|
-
def change_column_default(table_name, column_name, default) #:nodoc:
|
426
|
-
alter_table(table_name) do |definition|
|
427
|
-
definition[column_name].default = default
|
428
|
-
end
|
429
|
-
end
|
430
|
-
|
431
|
-
def change_column_null(table_name, column_name, null, default = nil)
|
432
|
-
unless null || default.nil?
|
433
|
-
exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
434
|
-
end
|
435
|
-
alter_table(table_name) do |definition|
|
436
|
-
definition[column_name].null = null
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
441
|
-
alter_table(table_name) do |definition|
|
442
|
-
include_default = options_include_default?(options)
|
443
|
-
definition[column_name].instance_eval do
|
444
|
-
self.type = type
|
445
|
-
self.limit = options[:limit] if options.include?(:limit)
|
446
|
-
self.default = options[:default] if include_default
|
447
|
-
self.null = options[:null] if options.include?(:null)
|
448
|
-
self.precision = options[:precision] if options.include?(:precision)
|
449
|
-
self.scale = options[:scale] if options.include?(:scale)
|
450
|
-
end
|
451
|
-
end
|
452
|
-
end
|
453
|
-
|
454
|
-
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
455
|
-
unless columns(table_name).detect{|c| c.name == column_name.to_s }
|
456
|
-
raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
|
457
|
-
end
|
458
|
-
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
|
459
|
-
end
|
460
|
-
|
461
|
-
def empty_insert_statement_value
|
462
|
-
"VALUES(NULL)"
|
463
|
-
end
|
464
|
-
|
465
|
-
protected
|
466
|
-
def select(sql, name = nil, binds = []) #:nodoc:
|
467
|
-
exec_query(sql, name, binds).to_a
|
468
|
-
end
|
469
|
-
|
470
|
-
def table_structure(table_name)
|
471
|
-
structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
|
472
|
-
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
|
473
|
-
structure
|
474
|
-
end
|
475
|
-
|
476
|
-
def alter_table(table_name, options = {}) #:nodoc:
|
477
|
-
altered_table_name = "altered_#{table_name}"
|
478
|
-
caller = lambda {|definition| yield definition if block_given?}
|
479
|
-
|
480
|
-
transaction do
|
481
|
-
move_table(table_name, altered_table_name,
|
482
|
-
options.merge(:temporary => true))
|
483
|
-
move_table(altered_table_name, table_name, &caller)
|
484
|
-
end
|
485
|
-
end
|
486
|
-
|
487
|
-
def move_table(from, to, options = {}, &block) #:nodoc:
|
488
|
-
copy_table(from, to, options, &block)
|
489
|
-
drop_table(from)
|
490
|
-
end
|
491
|
-
|
492
|
-
def copy_table(from, to, options = {}) #:nodoc:
|
493
|
-
from_primary_key = primary_key(from)
|
494
|
-
options[:primary_key] = from_primary_key if from_primary_key != 'id'
|
495
|
-
unless options[:primary_key]
|
496
|
-
options[:id] = !columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == from_primary_key
|
497
|
-
end
|
498
|
-
create_table(to, options) do |definition|
|
499
|
-
@definition = definition
|
500
|
-
columns(from).each do |column|
|
501
|
-
column_name = options[:rename] ?
|
502
|
-
(options[:rename][column.name] ||
|
503
|
-
options[:rename][column.name.to_sym] ||
|
504
|
-
column.name) : column.name
|
505
|
-
|
506
|
-
@definition.column(column_name, column.type,
|
507
|
-
:limit => column.limit, :default => column.default,
|
508
|
-
:precision => column.precision, :scale => column.scale,
|
509
|
-
:null => column.null)
|
510
|
-
end
|
511
|
-
@definition.primary_key(from_primary_key) if from_primary_key
|
512
|
-
yield @definition if block_given?
|
513
|
-
end
|
514
|
-
|
515
|
-
copy_table_indexes(from, to, options[:rename] || {})
|
516
|
-
copy_table_contents(from, to,
|
517
|
-
@definition.columns.map {|column| column.name},
|
518
|
-
options[:rename] || {})
|
519
|
-
end
|
520
|
-
|
521
|
-
def copy_table_indexes(from, to, rename = {}) #:nodoc:
|
522
|
-
indexes(from).each do |index|
|
523
|
-
name = index.name
|
524
|
-
if to == "altered_#{from}"
|
525
|
-
name = "temp_#{name}"
|
526
|
-
elsif from == "altered_#{to}"
|
527
|
-
name = name[5..-1]
|
528
|
-
end
|
529
|
-
|
530
|
-
to_column_names = columns(to).map { |c| c.name }
|
531
|
-
columns = index.columns.map {|c| rename[c] || c }.select do |column|
|
532
|
-
to_column_names.include?(column)
|
533
|
-
end
|
534
|
-
|
535
|
-
unless columns.empty?
|
536
|
-
# index name can't be the same
|
537
|
-
opts = { :name => name.gsub(/(^|_)(#{from})_/, "\\1#{to}_") }
|
538
|
-
opts[:unique] = true if index.unique
|
539
|
-
add_index(to, columns, opts)
|
540
|
-
end
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
|
545
|
-
column_mappings = Hash[columns.map {|name| [name, name]}]
|
546
|
-
rename.each { |a| column_mappings[a.last] = a.first }
|
547
|
-
from_columns = columns(from).collect {|col| col.name}
|
548
|
-
columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
|
549
|
-
quoted_columns = columns.map { |col| quote_column_name(col) } * ','
|
550
|
-
|
551
|
-
quoted_to = quote_table_name(to)
|
552
|
-
exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
|
553
|
-
sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
|
554
|
-
sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
|
555
|
-
sql << ')'
|
556
|
-
exec_query sql
|
557
|
-
end
|
558
|
-
end
|
559
|
-
|
560
|
-
def sqlite_version
|
561
|
-
@sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
|
562
|
-
end
|
563
|
-
|
564
|
-
def default_primary_key_type
|
565
|
-
if supports_autoincrement?
|
566
|
-
'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
|
567
|
-
else
|
568
|
-
'INTEGER PRIMARY KEY NOT NULL'
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
def translate_exception(exception, message)
|
573
|
-
case exception.message
|
574
|
-
when /column(s)? .* (is|are) not unique/
|
575
|
-
RecordNotUnique.new(message, exception)
|
576
|
-
else
|
577
|
-
super
|
578
|
-
end
|
579
|
-
end
|
580
|
-
|
581
|
-
end
|
582
|
-
end
|
583
|
-
end
|