activerecord 1.0.0 → 2.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 (311) hide show
  1. data/CHANGELOG +4928 -3
  2. data/README +45 -46
  3. data/RUNNING_UNIT_TESTS +8 -11
  4. data/Rakefile +247 -0
  5. data/install.rb +8 -38
  6. data/lib/active_record/aggregations.rb +64 -49
  7. data/lib/active_record/associations/association_collection.rb +217 -47
  8. data/lib/active_record/associations/association_proxy.rb +159 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +155 -37
  12. data/lib/active_record/associations/has_many_association.rb +145 -75
  13. data/lib/active_record/associations/has_many_through_association.rb +283 -0
  14. data/lib/active_record/associations/has_one_association.rb +96 -0
  15. data/lib/active_record/associations.rb +1537 -304
  16. data/lib/active_record/attribute_methods.rb +328 -0
  17. data/lib/active_record/base.rb +2001 -588
  18. data/lib/active_record/calculations.rb +269 -0
  19. data/lib/active_record/callbacks.rb +169 -165
  20. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +308 -0
  21. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -0
  22. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  23. data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
  24. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +472 -0
  25. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +306 -0
  26. data/lib/active_record/connection_adapters/abstract_adapter.rb +125 -279
  27. data/lib/active_record/connection_adapters/mysql_adapter.rb +442 -77
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +805 -135
  29. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  30. data/lib/active_record/connection_adapters/sqlite_adapter.rb +353 -69
  31. data/lib/active_record/fixtures.rb +946 -100
  32. data/lib/active_record/locking/optimistic.rb +144 -0
  33. data/lib/active_record/locking/pessimistic.rb +77 -0
  34. data/lib/active_record/migration.rb +417 -0
  35. data/lib/active_record/observer.rb +142 -32
  36. data/lib/active_record/query_cache.rb +23 -0
  37. data/lib/active_record/reflection.rb +163 -70
  38. data/lib/active_record/schema.rb +58 -0
  39. data/lib/active_record/schema_dumper.rb +171 -0
  40. data/lib/active_record/serialization.rb +98 -0
  41. data/lib/active_record/serializers/json_serializer.rb +71 -0
  42. data/lib/active_record/serializers/xml_serializer.rb +315 -0
  43. data/lib/active_record/timestamp.rb +41 -0
  44. data/lib/active_record/transactions.rb +87 -57
  45. data/lib/active_record/validations.rb +909 -122
  46. data/lib/active_record/vendor/db2.rb +362 -0
  47. data/lib/active_record/vendor/mysql.rb +126 -29
  48. data/lib/active_record/version.rb +9 -0
  49. data/lib/active_record.rb +35 -7
  50. data/lib/activerecord.rb +1 -0
  51. data/test/aaa_create_tables_test.rb +72 -0
  52. data/test/abstract_unit.rb +73 -5
  53. data/test/active_schema_test_mysql.rb +43 -0
  54. data/test/adapter_test.rb +105 -0
  55. data/test/adapter_test_sqlserver.rb +95 -0
  56. data/test/aggregations_test.rb +110 -16
  57. data/test/all.sh +2 -2
  58. data/test/ar_schema_test.rb +33 -0
  59. data/test/association_inheritance_reload.rb +14 -0
  60. data/test/associations/ar_joins_test.rb +0 -0
  61. data/test/associations/callbacks_test.rb +147 -0
  62. data/test/associations/cascaded_eager_loading_test.rb +110 -0
  63. data/test/associations/eager_singularization_test.rb +145 -0
  64. data/test/associations/eager_test.rb +442 -0
  65. data/test/associations/extension_test.rb +47 -0
  66. data/test/associations/inner_join_association_test.rb +88 -0
  67. data/test/associations/join_model_test.rb +553 -0
  68. data/test/associations_test.rb +1930 -267
  69. data/test/attribute_methods_test.rb +146 -0
  70. data/test/base_test.rb +1316 -84
  71. data/test/binary_test.rb +32 -0
  72. data/test/calculations_test.rb +251 -0
  73. data/test/callbacks_test.rb +400 -0
  74. data/test/class_inheritable_attributes_test.rb +3 -4
  75. data/test/column_alias_test.rb +17 -0
  76. data/test/connection_test_firebird.rb +8 -0
  77. data/test/connection_test_mysql.rb +30 -0
  78. data/test/connections/native_db2/connection.rb +25 -0
  79. data/test/connections/native_firebird/connection.rb +26 -0
  80. data/test/connections/native_frontbase/connection.rb +27 -0
  81. data/test/connections/native_mysql/connection.rb +21 -18
  82. data/test/connections/native_openbase/connection.rb +21 -0
  83. data/test/connections/native_oracle/connection.rb +27 -0
  84. data/test/connections/native_postgresql/connection.rb +17 -18
  85. data/test/connections/native_sqlite/connection.rb +17 -16
  86. data/test/connections/native_sqlite3/connection.rb +25 -0
  87. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  88. data/test/connections/native_sybase/connection.rb +23 -0
  89. data/test/copy_table_test_sqlite.rb +69 -0
  90. data/test/datatype_test_postgresql.rb +203 -0
  91. data/test/date_time_test.rb +37 -0
  92. data/test/default_test_firebird.rb +16 -0
  93. data/test/defaults_test.rb +67 -0
  94. data/test/deprecated_finder_test.rb +30 -0
  95. data/test/finder_test.rb +607 -32
  96. data/test/fixtures/accounts.yml +28 -0
  97. data/test/fixtures/all/developers.yml +0 -0
  98. data/test/fixtures/all/people.csv +0 -0
  99. data/test/fixtures/all/tasks.yml +0 -0
  100. data/test/fixtures/author.rb +107 -0
  101. data/test/fixtures/author_favorites.yml +4 -0
  102. data/test/fixtures/authors.yml +7 -0
  103. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  104. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  105. data/test/fixtures/bad_fixtures/blank_line +3 -0
  106. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  107. data/test/fixtures/bad_fixtures/missing_value +1 -0
  108. data/test/fixtures/binaries.yml +132 -0
  109. data/test/fixtures/binary.rb +2 -0
  110. data/test/fixtures/book.rb +4 -0
  111. data/test/fixtures/books.yml +7 -0
  112. data/test/fixtures/categories/special_categories.yml +9 -0
  113. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  114. data/test/fixtures/categories.yml +14 -0
  115. data/test/fixtures/categories_ordered.yml +7 -0
  116. data/test/fixtures/categories_posts.yml +23 -0
  117. data/test/fixtures/categorization.rb +5 -0
  118. data/test/fixtures/categorizations.yml +17 -0
  119. data/test/fixtures/category.rb +26 -0
  120. data/test/fixtures/citation.rb +6 -0
  121. data/test/fixtures/comment.rb +23 -0
  122. data/test/fixtures/comments.yml +59 -0
  123. data/test/fixtures/companies.yml +55 -0
  124. data/test/fixtures/company.rb +81 -4
  125. data/test/fixtures/company_in_module.rb +32 -6
  126. data/test/fixtures/computer.rb +4 -0
  127. data/test/fixtures/computers.yml +4 -0
  128. data/test/fixtures/contact.rb +16 -0
  129. data/test/fixtures/courses.yml +7 -0
  130. data/test/fixtures/customer.rb +28 -3
  131. data/test/fixtures/customers.yml +17 -0
  132. data/test/fixtures/db_definitions/db2.drop.sql +33 -0
  133. data/test/fixtures/db_definitions/db2.sql +235 -0
  134. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  135. data/test/fixtures/db_definitions/db22.sql +5 -0
  136. data/test/fixtures/db_definitions/firebird.drop.sql +65 -0
  137. data/test/fixtures/db_definitions/firebird.sql +310 -0
  138. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  139. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  140. data/test/fixtures/db_definitions/frontbase.drop.sql +33 -0
  141. data/test/fixtures/db_definitions/frontbase.sql +273 -0
  142. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  143. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  144. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  145. data/test/fixtures/db_definitions/openbase.sql +318 -0
  146. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  148. data/test/fixtures/db_definitions/oracle.drop.sql +67 -0
  149. data/test/fixtures/db_definitions/oracle.sql +330 -0
  150. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  151. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  152. data/test/fixtures/db_definitions/postgresql.drop.sql +44 -0
  153. data/test/fixtures/db_definitions/postgresql.sql +217 -38
  154. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/postgresql2.sql +2 -2
  156. data/test/fixtures/db_definitions/schema.rb +354 -0
  157. data/test/fixtures/db_definitions/schema2.rb +11 -0
  158. data/test/fixtures/db_definitions/sqlite.drop.sql +33 -0
  159. data/test/fixtures/db_definitions/sqlite.sql +139 -5
  160. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  161. data/test/fixtures/db_definitions/sqlite2.sql +1 -0
  162. data/test/fixtures/db_definitions/sybase.drop.sql +35 -0
  163. data/test/fixtures/db_definitions/sybase.sql +222 -0
  164. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  165. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  166. data/test/fixtures/developer.rb +70 -6
  167. data/test/fixtures/developers.yml +21 -0
  168. data/test/fixtures/developers_projects/david_action_controller +2 -1
  169. data/test/fixtures/developers_projects/david_active_record +2 -1
  170. data/test/fixtures/developers_projects.yml +17 -0
  171. data/test/fixtures/edge.rb +5 -0
  172. data/test/fixtures/edges.yml +6 -0
  173. data/test/fixtures/entrants.yml +14 -0
  174. data/test/fixtures/example.log +1 -0
  175. data/test/fixtures/fk_test_has_fk.yml +3 -0
  176. data/test/fixtures/fk_test_has_pk.yml +2 -0
  177. data/test/fixtures/flowers.jpg +0 -0
  178. data/test/fixtures/funny_jokes.yml +10 -0
  179. data/test/fixtures/item.rb +7 -0
  180. data/test/fixtures/items.yml +4 -0
  181. data/test/fixtures/joke.rb +3 -0
  182. data/test/fixtures/keyboard.rb +3 -0
  183. data/test/fixtures/legacy_thing.rb +3 -0
  184. data/test/fixtures/legacy_things.yml +3 -0
  185. data/test/fixtures/matey.rb +4 -0
  186. data/test/fixtures/mateys.yml +4 -0
  187. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  188. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  189. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  190. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  191. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  192. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  193. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  194. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  195. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  196. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  197. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  198. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  199. data/test/fixtures/minimalistic.rb +2 -0
  200. data/test/fixtures/minimalistics.yml +2 -0
  201. data/test/fixtures/mixed_case_monkey.rb +3 -0
  202. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  203. data/test/fixtures/mixins.yml +29 -0
  204. data/test/fixtures/movies.yml +7 -0
  205. data/test/fixtures/naked/csv/accounts.csv +1 -0
  206. data/test/fixtures/naked/yml/accounts.yml +1 -0
  207. data/test/fixtures/naked/yml/companies.yml +1 -0
  208. data/test/fixtures/naked/yml/courses.yml +1 -0
  209. data/test/fixtures/order.rb +4 -0
  210. data/test/fixtures/parrot.rb +13 -0
  211. data/test/fixtures/parrots.yml +27 -0
  212. data/test/fixtures/parrots_pirates.yml +7 -0
  213. data/test/fixtures/people.yml +3 -0
  214. data/test/fixtures/person.rb +4 -0
  215. data/test/fixtures/pirate.rb +5 -0
  216. data/test/fixtures/pirates.yml +9 -0
  217. data/test/fixtures/post.rb +59 -0
  218. data/test/fixtures/posts.yml +48 -0
  219. data/test/fixtures/project.rb +27 -2
  220. data/test/fixtures/projects.yml +7 -0
  221. data/test/fixtures/reader.rb +4 -0
  222. data/test/fixtures/readers.yml +4 -0
  223. data/test/fixtures/reply.rb +18 -2
  224. data/test/fixtures/reserved_words/distinct.yml +5 -0
  225. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  226. data/test/fixtures/reserved_words/group.yml +14 -0
  227. data/test/fixtures/reserved_words/select.yml +8 -0
  228. data/test/fixtures/reserved_words/values.yml +7 -0
  229. data/test/fixtures/ship.rb +3 -0
  230. data/test/fixtures/ships.yml +5 -0
  231. data/test/fixtures/subject.rb +4 -0
  232. data/test/fixtures/subscriber.rb +4 -3
  233. data/test/fixtures/tag.rb +7 -0
  234. data/test/fixtures/tagging.rb +10 -0
  235. data/test/fixtures/taggings.yml +25 -0
  236. data/test/fixtures/tags.yml +7 -0
  237. data/test/fixtures/task.rb +3 -0
  238. data/test/fixtures/tasks.yml +7 -0
  239. data/test/fixtures/topic.rb +20 -3
  240. data/test/fixtures/topics.yml +22 -0
  241. data/test/fixtures/treasure.rb +4 -0
  242. data/test/fixtures/treasures.yml +10 -0
  243. data/test/fixtures/vertex.rb +9 -0
  244. data/test/fixtures/vertices.yml +4 -0
  245. data/test/fixtures_test.rb +574 -8
  246. data/test/inheritance_test.rb +113 -27
  247. data/test/json_serialization_test.rb +180 -0
  248. data/test/lifecycle_test.rb +56 -29
  249. data/test/locking_test.rb +273 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +933 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_test.rb +95 -0
  254. data/test/modules_test.rb +23 -10
  255. data/test/multiple_db_test.rb +17 -3
  256. data/test/pk_test.rb +59 -15
  257. data/test/query_cache_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +124 -27
  260. data/test/reserved_word_test_mysql.rb +177 -0
  261. data/test/schema_authorization_test_postgresql.rb +75 -0
  262. data/test/schema_dumper_test.rb +131 -0
  263. data/test/schema_test_postgresql.rb +64 -0
  264. data/test/serialization_test.rb +47 -0
  265. data/test/synonym_test_oracle.rb +17 -0
  266. data/test/table_name_test_sqlserver.rb +23 -0
  267. data/test/threaded_connections_test.rb +48 -0
  268. data/test/transactions_test.rb +227 -29
  269. data/test/unconnected_test.rb +14 -6
  270. data/test/validations_test.rb +1293 -32
  271. data/test/xml_serialization_test.rb +202 -0
  272. metadata +347 -143
  273. data/dev-utils/eval_debugger.rb +0 -9
  274. data/examples/associations.rb +0 -87
  275. data/examples/shared_setup.rb +0 -15
  276. data/examples/validation.rb +0 -88
  277. data/lib/active_record/deprecated_associations.rb +0 -70
  278. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  279. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  280. data/lib/active_record/support/clean_logger.rb +0 -10
  281. data/lib/active_record/support/inflector.rb +0 -70
  282. data/lib/active_record/vendor/simple.rb +0 -702
  283. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  284. data/lib/active_record/wrappings.rb +0 -59
  285. data/rakefile +0 -122
  286. data/test/deprecated_associations_test.rb +0 -336
  287. data/test/fixtures/accounts/signals37 +0 -3
  288. data/test/fixtures/accounts/unknown +0 -2
  289. data/test/fixtures/companies/first_client +0 -6
  290. data/test/fixtures/companies/first_firm +0 -4
  291. data/test/fixtures/companies/second_client +0 -6
  292. data/test/fixtures/courses/java +0 -2
  293. data/test/fixtures/courses/ruby +0 -2
  294. data/test/fixtures/customers/david +0 -6
  295. data/test/fixtures/db_definitions/mysql.sql +0 -96
  296. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  297. data/test/fixtures/developers/david +0 -2
  298. data/test/fixtures/developers/jamis +0 -2
  299. data/test/fixtures/entrants/first +0 -3
  300. data/test/fixtures/entrants/second +0 -3
  301. data/test/fixtures/entrants/third +0 -3
  302. data/test/fixtures/fixture_database.sqlite +0 -0
  303. data/test/fixtures/fixture_database_2.sqlite +0 -0
  304. data/test/fixtures/movies/first +0 -2
  305. data/test/fixtures/movies/second +0 -2
  306. data/test/fixtures/projects/action_controller +0 -2
  307. data/test/fixtures/projects/active_record +0 -2
  308. data/test/fixtures/topics/first +0 -9
  309. data/test/fixtures/topics/second +0 -8
  310. data/test/inflector_test.rb +0 -104
  311. data/test/thread_safety_test.rb +0 -33
@@ -0,0 +1,34 @@
1
+ require 'active_record/connection_adapters/sqlite_adapter'
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ # sqlite3 adapter reuses sqlite_connection.
6
+ def self.sqlite3_connection(config) # :nodoc:
7
+ parse_sqlite_config!(config)
8
+
9
+ unless self.class.const_defined?(:SQLite3)
10
+ require_library_or_gem(config[:adapter])
11
+ end
12
+
13
+ db = SQLite3::Database.new(
14
+ config[:database],
15
+ :results_as_hash => true,
16
+ :type_translation => false
17
+ )
18
+
19
+ db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
20
+
21
+ ConnectionAdapters::SQLite3Adapter.new(db, logger)
22
+ end
23
+ end
24
+
25
+ module ConnectionAdapters #:nodoc:
26
+ class SQLite3Adapter < SQLiteAdapter # :nodoc:
27
+ def table_structure(table_name)
28
+ returning structure = @connection.table_info(table_name) do
29
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,107 +1,391 @@
1
- # sqlite_adapter.rb
2
- # author: Luke Holden <lholden@cablelan.net>
3
-
4
1
  require 'active_record/connection_adapters/abstract_adapter'
5
2
 
6
- begin
7
- require 'sqlite' unless self.class.const_defined?(:SQLite)
8
-
9
- module ActiveRecord
10
- class Base
3
+ module ActiveRecord
4
+ class Base
5
+ class << self
11
6
  # Establishes a connection to the database that's used by all Active Record objects
12
- def self.sqlite_connection(config) # :nodoc:
13
- symbolize_strings_in_hash(config)
14
- unless config.has_key?(:dbfile)
15
- raise ArgumentError, "No database file specified. Missing argument: dbfile"
7
+ def sqlite_connection(config) # :nodoc:
8
+ parse_sqlite_config!(config)
9
+
10
+ unless self.class.const_defined?(:SQLite)
11
+ require_library_or_gem(config[:adapter])
12
+
13
+ db = SQLite::Database.new(config[:database], 0)
14
+ db.show_datatypes = "ON" if !defined? SQLite::Version
15
+ db.results_as_hash = true if defined? SQLite::Version
16
+ db.type_translation = false
17
+
18
+ # "Downgrade" deprecated sqlite API
19
+ if SQLite.const_defined?(:Version)
20
+ ConnectionAdapters::SQLite2Adapter.new(db, logger)
21
+ else
22
+ ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
23
+ end
16
24
  end
25
+ end
17
26
 
18
- db = SQLite::Database.new(config[:dbfile], 0)
27
+ private
28
+ def parse_sqlite_config!(config)
29
+ config[:database] ||= config[:dbfile]
30
+ # Require database.
31
+ unless config[:database]
32
+ raise ArgumentError, "No database file specified. Missing argument: database"
33
+ end
19
34
 
20
- db.show_datatypes = "ON" if !defined? SQLite::Version
21
- db.results_as_hash = true if defined? SQLite::Version
22
- db.type_translation = false
35
+ # Allow database path relative to RAILS_ROOT, but only if
36
+ # the database path is not the special path that tells
37
+ # Sqlite to build a database only in memory.
38
+ if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
39
+ config[:database] = File.expand_path(config[:database], RAILS_ROOT)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ module ConnectionAdapters #:nodoc:
46
+ class SQLiteColumn < Column #:nodoc:
47
+ class << self
48
+ def string_to_binary(value)
49
+ value.gsub(/\0|\%/n) do |b|
50
+ case b
51
+ when "\0" then "%00"
52
+ when "%" then "%25"
53
+ end
54
+ end
55
+ end
23
56
 
24
- ConnectionAdapters::SQLiteAdapter.new(db, logger)
57
+ def binary_to_string(value)
58
+ value.gsub(/%00|%25/n) do |b|
59
+ case b
60
+ when "%00" then "\0"
61
+ when "%25" then "%"
62
+ end
63
+ end
64
+ end
25
65
  end
26
66
  end
27
67
 
28
- module ConnectionAdapters
29
- class SQLiteAdapter < AbstractAdapter # :nodoc:
30
- def select_all(sql, name = nil)
31
- select(sql, name)
68
+ # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
69
+ # from http://rubyforge.org/projects/sqlite-ruby/).
70
+ #
71
+ # Options:
72
+ #
73
+ # * <tt>:database</tt> -- Path to the database file.
74
+ class SQLiteAdapter < AbstractAdapter
75
+ def adapter_name #:nodoc:
76
+ 'SQLite'
77
+ end
78
+
79
+ def supports_migrations? #:nodoc:
80
+ true
81
+ end
82
+
83
+ def requires_reloading?
84
+ true
85
+ end
86
+
87
+ def disconnect!
88
+ super
89
+ @connection.close rescue nil
90
+ end
91
+
92
+ def supports_count_distinct? #:nodoc:
93
+ sqlite_version >= '3.2.6'
94
+ end
95
+
96
+ def supports_autoincrement? #:nodoc:
97
+ sqlite_version >= '3.1.0'
98
+ end
99
+
100
+ def native_database_types #:nodoc:
101
+ {
102
+ :primary_key => default_primary_key_type,
103
+ :string => { :name => "varchar", :limit => 255 },
104
+ :text => { :name => "text" },
105
+ :integer => { :name => "integer" },
106
+ :float => { :name => "float" },
107
+ :decimal => { :name => "decimal" },
108
+ :datetime => { :name => "datetime" },
109
+ :timestamp => { :name => "datetime" },
110
+ :time => { :name => "datetime" },
111
+ :date => { :name => "date" },
112
+ :binary => { :name => "blob" },
113
+ :boolean => { :name => "boolean" }
114
+ }
115
+ end
116
+
117
+
118
+ # QUOTING ==================================================
119
+
120
+ def quote_string(s) #:nodoc:
121
+ @connection.class.quote(s)
122
+ end
123
+
124
+ def quote_column_name(name) #:nodoc:
125
+ %Q("#{name}")
126
+ end
127
+
128
+
129
+ # DATABASE STATEMENTS ======================================
130
+
131
+ def execute(sql, name = nil) #:nodoc:
132
+ catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
133
+ end
134
+
135
+ def update_sql(sql, name = nil) #:nodoc:
136
+ super
137
+ @connection.changes
138
+ end
139
+
140
+ def delete_sql(sql, name = nil) #:nodoc:
141
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
142
+ super sql, name
143
+ end
144
+
145
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
146
+ super || @connection.last_insert_row_id
147
+ end
148
+
149
+ def select_rows(sql, name = nil)
150
+ execute(sql, name).map do |row|
151
+ (0...(row.size / 2)).map { |i| row[i] }
32
152
  end
153
+ end
154
+
155
+ def begin_db_transaction #:nodoc:
156
+ catch_schema_changes { @connection.transaction }
157
+ end
158
+
159
+ def commit_db_transaction #:nodoc:
160
+ catch_schema_changes { @connection.commit }
161
+ end
162
+
163
+ def rollback_db_transaction #:nodoc:
164
+ catch_schema_changes { @connection.rollback }
165
+ end
166
+
167
+
168
+ # SELECT ... FOR UPDATE is redundant since the table is locked.
169
+ def add_lock!(sql, options) #:nodoc:
170
+ sql
171
+ end
172
+
33
173
 
34
- def select_one(sql, name = nil)
35
- result = select(sql, name)
36
- result.nil? ? nil : result.first
174
+ # SCHEMA STATEMENTS ========================================
175
+
176
+ def tables(name = nil) #:nodoc:
177
+ sql = <<-SQL
178
+ SELECT name
179
+ FROM sqlite_master
180
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
181
+ SQL
182
+
183
+ execute(sql, name).map do |row|
184
+ row[0]
37
185
  end
186
+ end
38
187
 
39
- def columns(table_name, name = nil)
40
- table_structure(table_name).inject([]) do |columns, field|
41
- columns << Column.new(field['name'], field['dflt_value'], field['type'])
42
- columns
43
- end
188
+ def columns(table_name, name = nil) #:nodoc:
189
+ table_structure(table_name).map do |field|
190
+ SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
44
191
  end
192
+ end
45
193
 
46
- def insert(sql, name = nil, pk = nil, id_value = nil)
47
- execute(sql, name = nil)
48
- id_value || @connection.send( defined?( SQLite::Version ) ? :last_insert_row_id : :last_insert_rowid )
194
+ def indexes(table_name, name = nil) #:nodoc:
195
+ execute("PRAGMA index_list(#{table_name})", name).map do |row|
196
+ index = IndexDefinition.new(table_name, row['name'])
197
+ index.unique = row['unique'] != '0'
198
+ index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
199
+ index
49
200
  end
201
+ end
202
+
203
+ def primary_key(table_name) #:nodoc:
204
+ column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
205
+ column ? column['name'] : nil
206
+ end
50
207
 
51
- def execute(sql, name = nil)
52
- log(sql, name, @connection) do |connection|
53
- if defined?( SQLite::Version )
54
- case sql
55
- when "BEGIN" then connection.transaction
56
- when "COMMIT" then connection.commit
57
- when "ROLLBACK" then connection.rollback
58
- else connection.execute(sql)
208
+ def remove_index(table_name, options={}) #:nodoc:
209
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
210
+ end
211
+
212
+ def rename_table(name, new_name)
213
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
214
+ end
215
+
216
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
217
+ super(table_name, column_name, type, options)
218
+ # See last paragraph on http://www.sqlite.org/lang_altertable.html
219
+ execute "VACUUM"
220
+ end
221
+
222
+ def remove_column(table_name, column_name) #:nodoc:
223
+ alter_table(table_name) do |definition|
224
+ definition.columns.delete(definition[column_name])
225
+ end
226
+ end
227
+
228
+ def change_column_default(table_name, column_name, default) #:nodoc:
229
+ alter_table(table_name) do |definition|
230
+ definition[column_name].default = default
231
+ end
232
+ end
233
+
234
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
235
+ alter_table(table_name) do |definition|
236
+ include_default = options_include_default?(options)
237
+ definition[column_name].instance_eval do
238
+ self.type = type
239
+ self.limit = options[:limit] if options.include?(:limit)
240
+ self.default = options[:default] if include_default
241
+ self.null = options[:null] if options.include?(:null)
242
+ end
243
+ end
244
+ end
245
+
246
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
247
+ alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
248
+ end
249
+
250
+ def empty_insert_statement(table_name)
251
+ "INSERT INTO #{table_name} VALUES(NULL)"
252
+ end
253
+
254
+ protected
255
+ def select(sql, name = nil) #:nodoc:
256
+ execute(sql, name).map do |row|
257
+ record = {}
258
+ row.each_key do |key|
259
+ if key.is_a?(String)
260
+ record[key.sub(/^\w+\./, '')] = row[key]
59
261
  end
60
- else
61
- connection.execute( sql )
62
262
  end
263
+ record
264
+ end
265
+ end
266
+
267
+ def table_structure(table_name)
268
+ returning structure = execute("PRAGMA table_info(#{table_name})") do
269
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
63
270
  end
64
271
  end
65
272
 
66
- alias_method :update, :execute
67
- alias_method :delete, :execute
273
+ def alter_table(table_name, options = {}) #:nodoc:
274
+ altered_table_name = "altered_#{table_name}"
275
+ caller = lambda {|definition| yield definition if block_given?}
68
276
 
69
- def begin_db_transaction() execute "BEGIN" end
70
- def commit_db_transaction() execute "COMMIT" end
71
- def rollback_db_transaction() execute "ROLLBACK" end
277
+ transaction do
278
+ move_table(table_name, altered_table_name,
279
+ options.merge(:temporary => true))
280
+ move_table(altered_table_name, table_name, &caller)
281
+ end
282
+ end
72
283
 
73
- def quote_column_name(name)
74
- return "'#{name}'"
284
+ def move_table(from, to, options = {}, &block) #:nodoc:
285
+ copy_table(from, to, options, &block)
286
+ drop_table(from)
75
287
  end
76
288
 
77
- private
78
- def select(sql, name = nil)
79
- results = nil
80
- log(sql, name, @connection) { |connection| results = connection.execute(sql) }
289
+ def copy_table(from, to, options = {}) #:nodoc:
290
+ options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
291
+ create_table(to, options) do |definition|
292
+ @definition = definition
293
+ columns(from).each do |column|
294
+ column_name = options[:rename] ?
295
+ (options[:rename][column.name] ||
296
+ options[:rename][column.name.to_sym] ||
297
+ column.name) : column.name
298
+
299
+ @definition.column(column_name, column.type,
300
+ :limit => column.limit, :default => column.default,
301
+ :null => column.null)
302
+ end
303
+ @definition.primary_key(primary_key(from)) if primary_key(from)
304
+ yield @definition if block_given?
305
+ end
81
306
 
82
- rows = []
307
+ copy_table_indexes(from, to)
308
+ copy_table_contents(from, to,
309
+ @definition.columns.map {|column| column.name},
310
+ options[:rename] || {})
311
+ end
83
312
 
84
- results.each do |row|
85
- hash_only_row = {}
86
- row.each_key do |key|
87
- hash_only_row[key.gsub(/\w\./, "")] = row[key] unless key.class == Fixnum
88
- end
89
- rows << hash_only_row
313
+ def copy_table_indexes(from, to) #:nodoc:
314
+ indexes(from).each do |index|
315
+ name = index.name
316
+ if to == "altered_#{from}"
317
+ name = "temp_#{name}"
318
+ elsif from == "altered_#{to}"
319
+ name = name[5..-1]
90
320
  end
91
321
 
92
- return rows
322
+ # index name can't be the same
323
+ opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
324
+ opts[:unique] = true if index.unique
325
+ add_index(to, index.columns, opts)
93
326
  end
327
+ end
328
+
329
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
330
+ column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
331
+ rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
332
+ from_columns = columns(from).collect {|col| col.name}
333
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
334
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
94
335
 
95
- def table_structure(table_name)
96
- sql = "PRAGMA table_info(#{table_name});"
97
- results = nil
98
- log(sql, nil, @connection) { |connection| results = connection.execute(sql) }
99
- return results
336
+ @connection.execute "SELECT * FROM #{from}" do |row|
337
+ sql = "INSERT INTO #{to} (#{quoted_columns}) VALUES ("
338
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
339
+ sql << ')'
340
+ @connection.execute sql
100
341
  end
342
+ end
343
+
344
+ def catch_schema_changes
345
+ return yield
346
+ rescue ActiveRecord::StatementInvalid => exception
347
+ if exception.message =~ /database schema has changed/
348
+ reconnect!
349
+ retry
350
+ else
351
+ raise
352
+ end
353
+ end
354
+
355
+ def sqlite_version
356
+ @sqlite_version ||= select_value('select sqlite_version(*)')
357
+ end
358
+
359
+ def default_primary_key_type
360
+ if supports_autoincrement?
361
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
362
+ else
363
+ 'INTEGER PRIMARY KEY NOT NULL'.freeze
364
+ end
365
+ end
366
+ end
367
+
368
+ class SQLite2Adapter < SQLiteAdapter # :nodoc:
369
+ def supports_count_distinct? #:nodoc:
370
+ false
371
+ end
372
+
373
+ def rename_table(name, new_name)
374
+ move_table(name, new_name)
375
+ end
376
+
377
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
378
+ alter_table(table_name) do |definition|
379
+ definition.column(column_name, type, options)
380
+ end
381
+ end
382
+ end
383
+
384
+ class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
385
+ def insert(sql, name = nil, pk = nil, id_value = nil)
386
+ execute(sql, name = nil)
387
+ id_value || @connection.last_insert_rowid
101
388
  end
102
389
  end
103
390
  end
104
- rescue LoadError
105
- retry if require('rubygems') rescue LoadError
106
- # SQLite driver is not availible
107
391
  end