activerecord 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

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