sskirby-activerecord 3.2.1

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