activerecord 3.2.22.5 → 4.0.0.beta1

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

Potentially problematic release.


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

Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1024 -543
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +20 -29
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +55 -44
  7. data/lib/active_record/aggregations.rb +40 -34
  8. data/lib/active_record/associations.rb +204 -276
  9. data/lib/active_record/associations/alias_tracker.rb +1 -1
  10. data/lib/active_record/associations/association.rb +30 -35
  11. data/lib/active_record/associations/association_scope.rb +40 -40
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -2
  13. data/lib/active_record/associations/builder/association.rb +81 -28
  14. data/lib/active_record/associations/builder/belongs_to.rb +35 -57
  15. data/lib/active_record/associations/builder/collection_association.rb +54 -40
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -64
  18. data/lib/active_record/associations/builder/has_one.rb +13 -50
  19. data/lib/active_record/associations/builder/singular_association.rb +13 -13
  20. data/lib/active_record/associations/collection_association.rb +92 -88
  21. data/lib/active_record/associations/collection_proxy.rb +913 -63
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
  23. data/lib/active_record/associations/has_many_association.rb +35 -9
  24. data/lib/active_record/associations/has_many_through_association.rb +24 -14
  25. data/lib/active_record/associations/has_one_association.rb +33 -13
  26. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +2 -2
  28. data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  30. data/lib/active_record/associations/join_helper.rb +1 -11
  31. data/lib/active_record/associations/preloader.rb +14 -17
  32. data/lib/active_record/associations/preloader/association.rb +29 -33
  33. data/lib/active_record/associations/preloader/collection_association.rb +1 -1
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
  35. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  36. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  37. data/lib/active_record/associations/preloader/through_association.rb +13 -17
  38. data/lib/active_record/associations/singular_association.rb +11 -11
  39. data/lib/active_record/associations/through_association.rb +2 -2
  40. data/lib/active_record/attribute_assignment.rb +133 -153
  41. data/lib/active_record/attribute_methods.rb +196 -93
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
  43. data/lib/active_record/attribute_methods/dirty.rb +31 -28
  44. data/lib/active_record/attribute_methods/primary_key.rb +38 -30
  45. data/lib/active_record/attribute_methods/query.rb +5 -4
  46. data/lib/active_record/attribute_methods/read.rb +62 -91
  47. data/lib/active_record/attribute_methods/serialization.rb +97 -66
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
  49. data/lib/active_record/attribute_methods/write.rb +32 -39
  50. data/lib/active_record/autosave_association.rb +56 -70
  51. data/lib/active_record/base.rb +53 -450
  52. data/lib/active_record/callbacks.rb +53 -18
  53. data/lib/active_record/coders/yaml_column.rb +11 -9
  54. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
  55. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  56. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
  57. data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
  58. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
  65. data/lib/active_record/connection_adapters/column.rb +46 -24
  66. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  67. data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
  68. data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
  69. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  70. data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
  73. data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
  74. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  75. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
  76. data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
  77. data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
  79. data/lib/active_record/connection_handling.rb +98 -0
  80. data/lib/active_record/core.rb +428 -0
  81. data/lib/active_record/counter_cache.rb +106 -108
  82. data/lib/active_record/dynamic_matchers.rb +110 -63
  83. data/lib/active_record/errors.rb +25 -8
  84. data/lib/active_record/explain.rb +8 -58
  85. data/lib/active_record/explain_subscriber.rb +6 -3
  86. data/lib/active_record/fixture_set/file.rb +56 -0
  87. data/lib/active_record/fixtures.rb +146 -148
  88. data/lib/active_record/inheritance.rb +77 -59
  89. data/lib/active_record/integration.rb +5 -5
  90. data/lib/active_record/locale/en.yml +8 -1
  91. data/lib/active_record/locking/optimistic.rb +38 -42
  92. data/lib/active_record/locking/pessimistic.rb +4 -4
  93. data/lib/active_record/log_subscriber.rb +19 -9
  94. data/lib/active_record/migration.rb +318 -153
  95. data/lib/active_record/migration/command_recorder.rb +90 -31
  96. data/lib/active_record/migration/join_table.rb +15 -0
  97. data/lib/active_record/model_schema.rb +69 -92
  98. data/lib/active_record/nested_attributes.rb +113 -148
  99. data/lib/active_record/null_relation.rb +65 -0
  100. data/lib/active_record/persistence.rb +188 -97
  101. data/lib/active_record/query_cache.rb +18 -36
  102. data/lib/active_record/querying.rb +19 -15
  103. data/lib/active_record/railtie.rb +91 -36
  104. data/lib/active_record/railties/console_sandbox.rb +0 -2
  105. data/lib/active_record/railties/controller_runtime.rb +2 -2
  106. data/lib/active_record/railties/databases.rake +90 -309
  107. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  108. data/lib/active_record/readonly_attributes.rb +7 -3
  109. data/lib/active_record/reflection.rb +72 -56
  110. data/lib/active_record/relation.rb +241 -157
  111. data/lib/active_record/relation/batches.rb +25 -22
  112. data/lib/active_record/relation/calculations.rb +143 -121
  113. data/lib/active_record/relation/delegation.rb +96 -18
  114. data/lib/active_record/relation/finder_methods.rb +117 -183
  115. data/lib/active_record/relation/merger.rb +133 -0
  116. data/lib/active_record/relation/predicate_builder.rb +90 -42
  117. data/lib/active_record/relation/query_methods.rb +666 -136
  118. data/lib/active_record/relation/spawn_methods.rb +43 -150
  119. data/lib/active_record/result.rb +33 -6
  120. data/lib/active_record/sanitization.rb +24 -50
  121. data/lib/active_record/schema.rb +19 -12
  122. data/lib/active_record/schema_dumper.rb +31 -39
  123. data/lib/active_record/schema_migration.rb +36 -0
  124. data/lib/active_record/scoping.rb +0 -124
  125. data/lib/active_record/scoping/default.rb +48 -45
  126. data/lib/active_record/scoping/named.rb +74 -103
  127. data/lib/active_record/serialization.rb +6 -2
  128. data/lib/active_record/serializers/xml_serializer.rb +9 -15
  129. data/lib/active_record/store.rb +119 -15
  130. data/lib/active_record/tasks/database_tasks.rb +158 -0
  131. data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
  132. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  133. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  134. data/lib/active_record/test_case.rb +61 -38
  135. data/lib/active_record/timestamp.rb +8 -9
  136. data/lib/active_record/transactions.rb +65 -51
  137. data/lib/active_record/validations.rb +17 -15
  138. data/lib/active_record/validations/associated.rb +20 -14
  139. data/lib/active_record/validations/presence.rb +65 -0
  140. data/lib/active_record/validations/uniqueness.rb +93 -52
  141. data/lib/active_record/version.rb +4 -4
  142. data/lib/rails/generators/active_record.rb +3 -5
  143. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
  144. data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
  145. data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
  146. data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
  147. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  148. metadata +53 -46
  149. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  150. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  151. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  152. data/lib/active_record/dynamic_finder_match.rb +0 -68
  153. data/lib/active_record/dynamic_scope_match.rb +0 -23
  154. data/lib/active_record/fixtures/file.rb +0 -65
  155. data/lib/active_record/identity_map.rb +0 -162
  156. data/lib/active_record/observer.rb +0 -121
  157. data/lib/active_record/session_store.rb +0 -360
  158. data/lib/rails/generators/active_record/migration.rb +0 -15
  159. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  160. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  161. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  162. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,26 +1,17 @@
1
1
  module ActiveRecord
2
2
  module ConnectionAdapters
3
3
  class SchemaCache
4
- attr_reader :primary_keys, :tables
5
- attr_reader :connection
4
+ attr_reader :primary_keys, :tables, :version
5
+ attr_accessor :connection
6
6
 
7
7
  def initialize(conn)
8
8
  @connection = conn
9
- @tables = {}
10
9
 
11
- @columns = Hash.new do |h, table_name|
12
- h[table_name] = connection.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) ? connection.primary_key(table_name) : nil
23
- end
10
+ @columns = {}
11
+ @columns_hash = {}
12
+ @primary_keys = {}
13
+ @tables = {}
14
+ prepare_default_proc
24
15
  end
25
16
 
26
17
  # A cached lookup for table existence.
@@ -30,6 +21,15 @@ module ActiveRecord
30
21
  @tables[name] = connection.table_exists?(name)
31
22
  end
32
23
 
24
+ # Add internal cache for table with +table_name+.
25
+ def add(table_name)
26
+ if table_exists?(table_name)
27
+ @primary_keys[table_name]
28
+ @columns[table_name]
29
+ @columns_hash[table_name]
30
+ end
31
+ end
32
+
33
33
  # Get the columns for a table
34
34
  def columns(table = nil)
35
35
  if table
@@ -55,6 +55,7 @@ module ActiveRecord
55
55
  @columns_hash.clear
56
56
  @primary_keys.clear
57
57
  @tables.clear
58
+ @version = nil
58
59
  end
59
60
 
60
61
  # Clear out internal caches for table with +table_name+.
@@ -64,6 +65,37 @@ module ActiveRecord
64
65
  @primary_keys.delete table_name
65
66
  @tables.delete table_name
66
67
  end
68
+
69
+ def marshal_dump
70
+ # if we get current version during initialization, it happens stack over flow.
71
+ @version = ActiveRecord::Migrator.current_version
72
+ [@version] + [:@columns, :@columns_hash, :@primary_keys, :@tables].map do |val|
73
+ self.instance_variable_get(val).inject({}) { |h, v| h[v[0]] = v[1]; h }
74
+ end
75
+ end
76
+
77
+ def marshal_load(array)
78
+ @version, @columns, @columns_hash, @primary_keys, @tables = array
79
+ prepare_default_proc
80
+ end
81
+
82
+ private
83
+
84
+ def prepare_default_proc
85
+ @columns.default_proc = Proc.new do |h, table_name|
86
+ h[table_name] = connection.columns(table_name)
87
+ end
88
+
89
+ @columns_hash.default_proc = Proc.new do |h, table_name|
90
+ h[table_name] = Hash[columns[table_name].map { |col|
91
+ [col.name, col]
92
+ }]
93
+ end
94
+
95
+ @primary_keys.default_proc = Proc.new do |h, table_name|
96
+ h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
97
+ end
98
+ end
67
99
  end
68
100
  end
69
101
  end
@@ -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
8
10
  # sqlite3 adapter reuses sqlite_connection.
9
- def self.sqlite3_connection(config) # :nodoc:
11
+ def sqlite3_connection(config) # :nodoc:
10
12
  # Require database.
11
13
  unless config[:database]
12
14
  raise ArgumentError, "No database file specified. Missing argument: database"
@@ -19,23 +21,208 @@ module ActiveRecord
19
21
  config[:database] = File.expand_path(config[:database], Rails.root)
20
22
  end
21
23
 
22
- unless 'sqlite3' == config[:adapter]
23
- raise ArgumentError, 'adapter name should be "sqlite3"'
24
- end
25
-
26
24
  db = SQLite3::Database.new(
27
25
  config[:database],
28
26
  :results_as_hash => true
29
27
  )
30
28
 
31
- db.busy_timeout(config[:timeout]) if config[:timeout]
29
+ db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
32
30
 
33
31
  ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
34
32
  end
35
33
  end
36
34
 
37
35
  module ConnectionAdapters #:nodoc:
38
- 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 = BindSubstitution.new self
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
213
+
214
+ # Returns the current database encoding format as a string, eg: 'UTF-8'
215
+ def encoding
216
+ @connection.encoding.to_s
217
+ end
218
+
219
+ # Returns true.
220
+ def supports_explain?
221
+ true
222
+ end
223
+
224
+ # QUOTING ==================================================
225
+
39
226
  def quote(value, column = nil)
40
227
  if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
41
228
  s = column.class.string_to_binary(value).unpack("H*")[0]
@@ -45,11 +232,385 @@ module ActiveRecord
45
232
  end
46
233
  end
47
234
 
48
- # Returns the current database encoding format as a string, eg: 'UTF-8'
49
- def encoding
50
- @connection.encoding.to_s
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)}"
51
432
  end
52
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)
452
+ else
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.columns.delete(definition[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}"
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)
500
+ end
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
+ exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row|
587
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
588
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
589
+ sql << ')'
590
+ exec_query sql
591
+ end
592
+ end
593
+
594
+ def sqlite_version
595
+ @sqlite_version ||= SQLite3Adapter::Version.new(select_value('select sqlite_version(*)'))
596
+ end
597
+
598
+ def default_primary_key_type
599
+ if supports_autoincrement?
600
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
601
+ else
602
+ 'INTEGER PRIMARY KEY NOT NULL'
603
+ end
604
+ end
605
+
606
+ def translate_exception(exception, message)
607
+ case exception.message
608
+ when /column(s)? .* (is|are) not unique/
609
+ RecordNotUnique.new(message, exception)
610
+ else
611
+ super
612
+ end
613
+ end
53
614
  end
54
615
  end
55
616
  end