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.

Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. 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