activerecord 1.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 (255) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +213 -0
  5. data/examples/performance.rb +172 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record/aggregations.rb +180 -84
  8. data/lib/active_record/associations/alias_tracker.rb +76 -0
  9. data/lib/active_record/associations/association.rb +248 -0
  10. data/lib/active_record/associations/association_scope.rb +135 -0
  11. data/lib/active_record/associations/belongs_to_association.rb +92 -0
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +35 -0
  13. data/lib/active_record/associations/builder/association.rb +108 -0
  14. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  15. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  17. data/lib/active_record/associations/builder/has_many.rb +15 -0
  18. data/lib/active_record/associations/builder/has_one.rb +25 -0
  19. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  20. data/lib/active_record/associations/collection_association.rb +608 -0
  21. data/lib/active_record/associations/collection_proxy.rb +986 -0
  22. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +58 -39
  23. data/lib/active_record/associations/has_many_association.rb +116 -85
  24. data/lib/active_record/associations/has_many_through_association.rb +197 -0
  25. data/lib/active_record/associations/has_one_association.rb +102 -0
  26. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  27. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  28. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  29. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  30. data/lib/active_record/associations/join_dependency.rb +235 -0
  31. data/lib/active_record/associations/join_helper.rb +45 -0
  32. data/lib/active_record/associations/preloader/association.rb +121 -0
  33. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  34. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  35. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  36. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  37. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  38. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  39. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  40. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  41. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  42. data/lib/active_record/associations/preloader.rb +178 -0
  43. data/lib/active_record/associations/singular_association.rb +64 -0
  44. data/lib/active_record/associations/through_association.rb +87 -0
  45. data/lib/active_record/associations.rb +1437 -431
  46. data/lib/active_record/attribute_assignment.rb +201 -0
  47. data/lib/active_record/attribute_methods/before_type_cast.rb +70 -0
  48. data/lib/active_record/attribute_methods/dirty.rb +118 -0
  49. data/lib/active_record/attribute_methods/primary_key.rb +122 -0
  50. data/lib/active_record/attribute_methods/query.rb +40 -0
  51. data/lib/active_record/attribute_methods/read.rb +107 -0
  52. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  53. data/lib/active_record/attribute_methods/time_zone_conversion.rb +59 -0
  54. data/lib/active_record/attribute_methods/write.rb +63 -0
  55. data/lib/active_record/attribute_methods.rb +393 -0
  56. data/lib/active_record/autosave_association.rb +426 -0
  57. data/lib/active_record/base.rb +268 -930
  58. data/lib/active_record/callbacks.rb +203 -230
  59. data/lib/active_record/coders/yaml_column.rb +38 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +638 -0
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +67 -0
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +390 -0
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +95 -0
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +129 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +501 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +873 -0
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +389 -275
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  71. data/lib/active_record/connection_adapters/column.rb +318 -0
  72. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +517 -90
  75. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  76. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  77. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  81. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  82. data/lib/active_record/connection_adapters/postgresql_adapter.rb +911 -138
  83. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  84. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +624 -0
  85. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  86. data/lib/active_record/connection_handling.rb +98 -0
  87. data/lib/active_record/core.rb +463 -0
  88. data/lib/active_record/counter_cache.rb +122 -0
  89. data/lib/active_record/dynamic_matchers.rb +131 -0
  90. data/lib/active_record/errors.rb +213 -0
  91. data/lib/active_record/explain.rb +38 -0
  92. data/lib/active_record/explain_registry.rb +30 -0
  93. data/lib/active_record/explain_subscriber.rb +29 -0
  94. data/lib/active_record/fixture_set/file.rb +55 -0
  95. data/lib/active_record/fixtures.rb +892 -138
  96. data/lib/active_record/inheritance.rb +200 -0
  97. data/lib/active_record/integration.rb +60 -0
  98. data/lib/active_record/locale/en.yml +47 -0
  99. data/lib/active_record/locking/optimistic.rb +181 -0
  100. data/lib/active_record/locking/pessimistic.rb +77 -0
  101. data/lib/active_record/log_subscriber.rb +82 -0
  102. data/lib/active_record/migration/command_recorder.rb +164 -0
  103. data/lib/active_record/migration/join_table.rb +15 -0
  104. data/lib/active_record/migration.rb +1015 -0
  105. data/lib/active_record/model_schema.rb +345 -0
  106. data/lib/active_record/nested_attributes.rb +546 -0
  107. data/lib/active_record/null_relation.rb +65 -0
  108. data/lib/active_record/persistence.rb +509 -0
  109. data/lib/active_record/query_cache.rb +56 -0
  110. data/lib/active_record/querying.rb +62 -0
  111. data/lib/active_record/railtie.rb +205 -0
  112. data/lib/active_record/railties/console_sandbox.rb +5 -0
  113. data/lib/active_record/railties/controller_runtime.rb +50 -0
  114. data/lib/active_record/railties/databases.rake +402 -0
  115. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  116. data/lib/active_record/readonly_attributes.rb +30 -0
  117. data/lib/active_record/reflection.rb +544 -87
  118. data/lib/active_record/relation/batches.rb +93 -0
  119. data/lib/active_record/relation/calculations.rb +399 -0
  120. data/lib/active_record/relation/delegation.rb +125 -0
  121. data/lib/active_record/relation/finder_methods.rb +349 -0
  122. data/lib/active_record/relation/merger.rb +161 -0
  123. data/lib/active_record/relation/predicate_builder.rb +106 -0
  124. data/lib/active_record/relation/query_methods.rb +1044 -0
  125. data/lib/active_record/relation/spawn_methods.rb +73 -0
  126. data/lib/active_record/relation.rb +655 -0
  127. data/lib/active_record/result.rb +67 -0
  128. data/lib/active_record/runtime_registry.rb +17 -0
  129. data/lib/active_record/sanitization.rb +168 -0
  130. data/lib/active_record/schema.rb +65 -0
  131. data/lib/active_record/schema_dumper.rb +204 -0
  132. data/lib/active_record/schema_migration.rb +39 -0
  133. data/lib/active_record/scoping/default.rb +146 -0
  134. data/lib/active_record/scoping/named.rb +175 -0
  135. data/lib/active_record/scoping.rb +82 -0
  136. data/lib/active_record/serialization.rb +22 -0
  137. data/lib/active_record/serializers/xml_serializer.rb +197 -0
  138. data/lib/active_record/statement_cache.rb +26 -0
  139. data/lib/active_record/store.rb +156 -0
  140. data/lib/active_record/tasks/database_tasks.rb +203 -0
  141. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  142. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  143. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  144. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  145. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  146. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  147. data/lib/active_record/test_case.rb +96 -0
  148. data/lib/active_record/timestamp.rb +119 -0
  149. data/lib/active_record/transactions.rb +366 -69
  150. data/lib/active_record/translation.rb +22 -0
  151. data/lib/active_record/validations/associated.rb +49 -0
  152. data/lib/active_record/validations/presence.rb +65 -0
  153. data/lib/active_record/validations/uniqueness.rb +225 -0
  154. data/lib/active_record/validations.rb +64 -185
  155. data/lib/active_record/version.rb +11 -0
  156. data/lib/active_record.rb +149 -24
  157. data/lib/rails/generators/active_record/migration/migration_generator.rb +62 -0
  158. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  159. data/lib/rails/generators/active_record/migration/templates/migration.rb +39 -0
  160. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  161. data/lib/rails/generators/active_record/model/templates/model.rb +10 -0
  162. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  163. data/lib/rails/generators/active_record.rb +23 -0
  164. metadata +261 -161
  165. data/CHANGELOG +0 -581
  166. data/README +0 -361
  167. data/RUNNING_UNIT_TESTS +0 -36
  168. data/dev-utils/eval_debugger.rb +0 -9
  169. data/examples/associations.png +0 -0
  170. data/examples/associations.rb +0 -87
  171. data/examples/shared_setup.rb +0 -15
  172. data/examples/validation.rb +0 -88
  173. data/install.rb +0 -60
  174. data/lib/active_record/associations/association_collection.rb +0 -70
  175. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -107
  176. data/lib/active_record/deprecated_associations.rb +0 -70
  177. data/lib/active_record/observer.rb +0 -71
  178. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  179. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  180. data/lib/active_record/support/clean_logger.rb +0 -10
  181. data/lib/active_record/support/inflector.rb +0 -70
  182. data/lib/active_record/vendor/mysql.rb +0 -1117
  183. data/lib/active_record/vendor/simple.rb +0 -702
  184. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  185. data/lib/active_record/wrappings.rb +0 -59
  186. data/rakefile +0 -122
  187. data/test/abstract_unit.rb +0 -16
  188. data/test/aggregations_test.rb +0 -34
  189. data/test/all.sh +0 -8
  190. data/test/associations_test.rb +0 -477
  191. data/test/base_test.rb +0 -513
  192. data/test/class_inheritable_attributes_test.rb +0 -33
  193. data/test/connections/native_mysql/connection.rb +0 -24
  194. data/test/connections/native_postgresql/connection.rb +0 -24
  195. data/test/connections/native_sqlite/connection.rb +0 -24
  196. data/test/deprecated_associations_test.rb +0 -336
  197. data/test/finder_test.rb +0 -67
  198. data/test/fixtures/accounts/signals37 +0 -3
  199. data/test/fixtures/accounts/unknown +0 -2
  200. data/test/fixtures/auto_id.rb +0 -4
  201. data/test/fixtures/column_name.rb +0 -3
  202. data/test/fixtures/companies/first_client +0 -6
  203. data/test/fixtures/companies/first_firm +0 -4
  204. data/test/fixtures/companies/second_client +0 -6
  205. data/test/fixtures/company.rb +0 -37
  206. data/test/fixtures/company_in_module.rb +0 -33
  207. data/test/fixtures/course.rb +0 -3
  208. data/test/fixtures/courses/java +0 -2
  209. data/test/fixtures/courses/ruby +0 -2
  210. data/test/fixtures/customer.rb +0 -30
  211. data/test/fixtures/customers/david +0 -6
  212. data/test/fixtures/db_definitions/mysql.sql +0 -96
  213. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  214. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  215. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  216. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  217. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  218. data/test/fixtures/default.rb +0 -2
  219. data/test/fixtures/developer.rb +0 -8
  220. data/test/fixtures/developers/david +0 -2
  221. data/test/fixtures/developers/jamis +0 -2
  222. data/test/fixtures/developers_projects/david_action_controller +0 -2
  223. data/test/fixtures/developers_projects/david_active_record +0 -2
  224. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  225. data/test/fixtures/entrant.rb +0 -3
  226. data/test/fixtures/entrants/first +0 -3
  227. data/test/fixtures/entrants/second +0 -3
  228. data/test/fixtures/entrants/third +0 -3
  229. data/test/fixtures/fixture_database.sqlite +0 -0
  230. data/test/fixtures/fixture_database_2.sqlite +0 -0
  231. data/test/fixtures/movie.rb +0 -5
  232. data/test/fixtures/movies/first +0 -2
  233. data/test/fixtures/movies/second +0 -2
  234. data/test/fixtures/project.rb +0 -3
  235. data/test/fixtures/projects/action_controller +0 -2
  236. data/test/fixtures/projects/active_record +0 -2
  237. data/test/fixtures/reply.rb +0 -21
  238. data/test/fixtures/subscriber.rb +0 -5
  239. data/test/fixtures/subscribers/first +0 -2
  240. data/test/fixtures/subscribers/second +0 -2
  241. data/test/fixtures/topic.rb +0 -20
  242. data/test/fixtures/topics/first +0 -9
  243. data/test/fixtures/topics/second +0 -8
  244. data/test/fixtures_test.rb +0 -20
  245. data/test/inflector_test.rb +0 -104
  246. data/test/inheritance_test.rb +0 -125
  247. data/test/lifecycle_test.rb +0 -110
  248. data/test/modules_test.rb +0 -21
  249. data/test/multiple_db_test.rb +0 -46
  250. data/test/pk_test.rb +0 -57
  251. data/test/reflection_test.rb +0 -78
  252. data/test/thread_safety_test.rb +0 -33
  253. data/test/transactions_test.rb +0 -83
  254. data/test/unconnected_test.rb +0 -24
  255. data/test/validations_test.rb +0 -126
@@ -0,0 +1,624 @@
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'
7
+
8
+ module ActiveRecord
9
+ module ConnectionHandling # :nodoc:
10
+ # sqlite3 adapter reuses sqlite_connection.
11
+ def sqlite3_connection(config)
12
+ # Require database.
13
+ unless config[:database]
14
+ raise ArgumentError, "No database file specified. Missing argument: database"
15
+ end
16
+
17
+ # Allow database path relative to Rails.root, but only if
18
+ # the database path is not the special path that tells
19
+ # Sqlite to build a database only in memory.
20
+ if defined?(Rails.root) && ':memory:' != config[:database]
21
+ config[:database] = File.expand_path(config[:database], Rails.root)
22
+ end
23
+
24
+ db = SQLite3::Database.new(
25
+ config[:database],
26
+ :results_as_hash => true
27
+ )
28
+
29
+ db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
30
+
31
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
32
+ end
33
+ end
34
+
35
+ module ConnectionAdapters #: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
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
+
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)
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.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}"
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
+
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
622
+ end
623
+ end
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