activerecord 3.2.22.4 → 4.0.13

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