activerecord_authorails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. data/CHANGELOG +3043 -0
  2. data/README +360 -0
  3. data/RUNNING_UNIT_TESTS +64 -0
  4. data/Rakefile +226 -0
  5. data/examples/associations.png +0 -0
  6. data/examples/associations.rb +87 -0
  7. data/examples/shared_setup.rb +15 -0
  8. data/examples/validation.rb +85 -0
  9. data/install.rb +30 -0
  10. data/lib/active_record.rb +85 -0
  11. data/lib/active_record/acts/list.rb +244 -0
  12. data/lib/active_record/acts/nested_set.rb +211 -0
  13. data/lib/active_record/acts/tree.rb +89 -0
  14. data/lib/active_record/aggregations.rb +191 -0
  15. data/lib/active_record/associations.rb +1637 -0
  16. data/lib/active_record/associations/association_collection.rb +190 -0
  17. data/lib/active_record/associations/association_proxy.rb +158 -0
  18. data/lib/active_record/associations/belongs_to_association.rb +56 -0
  19. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  20. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +169 -0
  21. data/lib/active_record/associations/has_many_association.rb +210 -0
  22. data/lib/active_record/associations/has_many_through_association.rb +247 -0
  23. data/lib/active_record/associations/has_one_association.rb +80 -0
  24. data/lib/active_record/attribute_methods.rb +75 -0
  25. data/lib/active_record/base.rb +2164 -0
  26. data/lib/active_record/calculations.rb +270 -0
  27. data/lib/active_record/callbacks.rb +367 -0
  28. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +279 -0
  29. data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -0
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +58 -0
  31. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +343 -0
  32. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +310 -0
  33. data/lib/active_record/connection_adapters/abstract_adapter.rb +161 -0
  34. data/lib/active_record/connection_adapters/db2_adapter.rb +228 -0
  35. data/lib/active_record/connection_adapters/firebird_adapter.rb +728 -0
  36. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  37. data/lib/active_record/connection_adapters/mysql_adapter.rb +414 -0
  38. data/lib/active_record/connection_adapters/openbase_adapter.rb +350 -0
  39. data/lib/active_record/connection_adapters/oracle_adapter.rb +689 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +584 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +407 -0
  42. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +591 -0
  43. data/lib/active_record/connection_adapters/sybase_adapter.rb +662 -0
  44. data/lib/active_record/deprecated_associations.rb +104 -0
  45. data/lib/active_record/deprecated_finders.rb +44 -0
  46. data/lib/active_record/fixtures.rb +628 -0
  47. data/lib/active_record/locking/optimistic.rb +106 -0
  48. data/lib/active_record/locking/pessimistic.rb +77 -0
  49. data/lib/active_record/migration.rb +394 -0
  50. data/lib/active_record/observer.rb +178 -0
  51. data/lib/active_record/query_cache.rb +64 -0
  52. data/lib/active_record/reflection.rb +222 -0
  53. data/lib/active_record/schema.rb +58 -0
  54. data/lib/active_record/schema_dumper.rb +149 -0
  55. data/lib/active_record/timestamp.rb +51 -0
  56. data/lib/active_record/transactions.rb +136 -0
  57. data/lib/active_record/validations.rb +843 -0
  58. data/lib/active_record/vendor/db2.rb +362 -0
  59. data/lib/active_record/vendor/mysql.rb +1214 -0
  60. data/lib/active_record/vendor/simple.rb +693 -0
  61. data/lib/active_record/version.rb +9 -0
  62. data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
  63. data/lib/active_record/wrappings.rb +58 -0
  64. data/lib/active_record/xml_serialization.rb +308 -0
  65. data/test/aaa_create_tables_test.rb +59 -0
  66. data/test/abstract_unit.rb +77 -0
  67. data/test/active_schema_test_mysql.rb +31 -0
  68. data/test/adapter_test.rb +87 -0
  69. data/test/adapter_test_sqlserver.rb +81 -0
  70. data/test/aggregations_test.rb +95 -0
  71. data/test/all.sh +8 -0
  72. data/test/ar_schema_test.rb +33 -0
  73. data/test/association_inheritance_reload.rb +14 -0
  74. data/test/associations/callbacks_test.rb +126 -0
  75. data/test/associations/cascaded_eager_loading_test.rb +138 -0
  76. data/test/associations/eager_test.rb +393 -0
  77. data/test/associations/extension_test.rb +42 -0
  78. data/test/associations/join_model_test.rb +497 -0
  79. data/test/associations_test.rb +1809 -0
  80. data/test/attribute_methods_test.rb +49 -0
  81. data/test/base_test.rb +1586 -0
  82. data/test/binary_test.rb +37 -0
  83. data/test/calculations_test.rb +219 -0
  84. data/test/callbacks_test.rb +377 -0
  85. data/test/class_inheritable_attributes_test.rb +32 -0
  86. data/test/column_alias_test.rb +17 -0
  87. data/test/connection_test_firebird.rb +8 -0
  88. data/test/connections/native_db2/connection.rb +25 -0
  89. data/test/connections/native_firebird/connection.rb +26 -0
  90. data/test/connections/native_frontbase/connection.rb +27 -0
  91. data/test/connections/native_mysql/connection.rb +24 -0
  92. data/test/connections/native_openbase/connection.rb +21 -0
  93. data/test/connections/native_oracle/connection.rb +27 -0
  94. data/test/connections/native_postgresql/connection.rb +23 -0
  95. data/test/connections/native_sqlite/connection.rb +34 -0
  96. data/test/connections/native_sqlite3/connection.rb +34 -0
  97. data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
  98. data/test/connections/native_sqlserver/connection.rb +23 -0
  99. data/test/connections/native_sqlserver_odbc/connection.rb +25 -0
  100. data/test/connections/native_sybase/connection.rb +23 -0
  101. data/test/copy_table_sqlite.rb +64 -0
  102. data/test/datatype_test_postgresql.rb +52 -0
  103. data/test/default_test_firebird.rb +16 -0
  104. data/test/defaults_test.rb +60 -0
  105. data/test/deprecated_associations_test.rb +396 -0
  106. data/test/deprecated_finder_test.rb +151 -0
  107. data/test/empty_date_time_test.rb +25 -0
  108. data/test/finder_test.rb +504 -0
  109. data/test/fixtures/accounts.yml +28 -0
  110. data/test/fixtures/author.rb +99 -0
  111. data/test/fixtures/author_favorites.yml +4 -0
  112. data/test/fixtures/authors.yml +7 -0
  113. data/test/fixtures/auto_id.rb +4 -0
  114. data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +1 -0
  115. data/test/fixtures/bad_fixtures/attr_with_spaces +1 -0
  116. data/test/fixtures/bad_fixtures/blank_line +3 -0
  117. data/test/fixtures/bad_fixtures/duplicate_attributes +3 -0
  118. data/test/fixtures/bad_fixtures/missing_value +1 -0
  119. data/test/fixtures/binary.rb +2 -0
  120. data/test/fixtures/categories.yml +14 -0
  121. data/test/fixtures/categories/special_categories.yml +9 -0
  122. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  123. data/test/fixtures/categories_ordered.yml +7 -0
  124. data/test/fixtures/categories_posts.yml +23 -0
  125. data/test/fixtures/categorization.rb +5 -0
  126. data/test/fixtures/categorizations.yml +17 -0
  127. data/test/fixtures/category.rb +20 -0
  128. data/test/fixtures/column_name.rb +3 -0
  129. data/test/fixtures/comment.rb +23 -0
  130. data/test/fixtures/comments.yml +59 -0
  131. data/test/fixtures/companies.yml +55 -0
  132. data/test/fixtures/company.rb +107 -0
  133. data/test/fixtures/company_in_module.rb +59 -0
  134. data/test/fixtures/computer.rb +3 -0
  135. data/test/fixtures/computers.yml +4 -0
  136. data/test/fixtures/course.rb +3 -0
  137. data/test/fixtures/courses.yml +7 -0
  138. data/test/fixtures/customer.rb +55 -0
  139. data/test/fixtures/customers.yml +17 -0
  140. data/test/fixtures/db_definitions/db2.drop.sql +32 -0
  141. data/test/fixtures/db_definitions/db2.sql +231 -0
  142. data/test/fixtures/db_definitions/db22.drop.sql +2 -0
  143. data/test/fixtures/db_definitions/db22.sql +5 -0
  144. data/test/fixtures/db_definitions/firebird.drop.sql +63 -0
  145. data/test/fixtures/db_definitions/firebird.sql +304 -0
  146. data/test/fixtures/db_definitions/firebird2.drop.sql +2 -0
  147. data/test/fixtures/db_definitions/firebird2.sql +6 -0
  148. data/test/fixtures/db_definitions/frontbase.drop.sql +32 -0
  149. data/test/fixtures/db_definitions/frontbase.sql +268 -0
  150. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  151. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  152. data/test/fixtures/db_definitions/mysql.drop.sql +32 -0
  153. data/test/fixtures/db_definitions/mysql.sql +234 -0
  154. data/test/fixtures/db_definitions/mysql2.drop.sql +2 -0
  155. data/test/fixtures/db_definitions/mysql2.sql +5 -0
  156. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  157. data/test/fixtures/db_definitions/openbase.sql +302 -0
  158. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  159. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  160. data/test/fixtures/db_definitions/oracle.drop.sql +65 -0
  161. data/test/fixtures/db_definitions/oracle.sql +325 -0
  162. data/test/fixtures/db_definitions/oracle2.drop.sql +2 -0
  163. data/test/fixtures/db_definitions/oracle2.sql +6 -0
  164. data/test/fixtures/db_definitions/postgresql.drop.sql +37 -0
  165. data/test/fixtures/db_definitions/postgresql.sql +263 -0
  166. data/test/fixtures/db_definitions/postgresql2.drop.sql +2 -0
  167. data/test/fixtures/db_definitions/postgresql2.sql +5 -0
  168. data/test/fixtures/db_definitions/schema.rb +60 -0
  169. data/test/fixtures/db_definitions/sqlite.drop.sql +32 -0
  170. data/test/fixtures/db_definitions/sqlite.sql +215 -0
  171. data/test/fixtures/db_definitions/sqlite2.drop.sql +2 -0
  172. data/test/fixtures/db_definitions/sqlite2.sql +5 -0
  173. data/test/fixtures/db_definitions/sqlserver.drop.sql +34 -0
  174. data/test/fixtures/db_definitions/sqlserver.sql +243 -0
  175. data/test/fixtures/db_definitions/sqlserver2.drop.sql +2 -0
  176. data/test/fixtures/db_definitions/sqlserver2.sql +5 -0
  177. data/test/fixtures/db_definitions/sybase.drop.sql +34 -0
  178. data/test/fixtures/db_definitions/sybase.sql +218 -0
  179. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  180. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  181. data/test/fixtures/default.rb +2 -0
  182. data/test/fixtures/developer.rb +52 -0
  183. data/test/fixtures/developers.yml +21 -0
  184. data/test/fixtures/developers_projects.yml +17 -0
  185. data/test/fixtures/developers_projects/david_action_controller +3 -0
  186. data/test/fixtures/developers_projects/david_active_record +3 -0
  187. data/test/fixtures/developers_projects/jamis_active_record +2 -0
  188. data/test/fixtures/edge.rb +5 -0
  189. data/test/fixtures/edges.yml +6 -0
  190. data/test/fixtures/entrant.rb +3 -0
  191. data/test/fixtures/entrants.yml +14 -0
  192. data/test/fixtures/fk_test_has_fk.yml +3 -0
  193. data/test/fixtures/fk_test_has_pk.yml +2 -0
  194. data/test/fixtures/flowers.jpg +0 -0
  195. data/test/fixtures/funny_jokes.yml +10 -0
  196. data/test/fixtures/joke.rb +6 -0
  197. data/test/fixtures/keyboard.rb +3 -0
  198. data/test/fixtures/legacy_thing.rb +3 -0
  199. data/test/fixtures/legacy_things.yml +3 -0
  200. data/test/fixtures/migrations/1_people_have_last_names.rb +9 -0
  201. data/test/fixtures/migrations/2_we_need_reminders.rb +12 -0
  202. data/test/fixtures/migrations/3_innocent_jointable.rb +12 -0
  203. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  204. data/test/fixtures/migrations_with_duplicate/1_people_have_last_names.rb +9 -0
  205. data/test/fixtures/migrations_with_duplicate/2_we_need_reminders.rb +12 -0
  206. data/test/fixtures/migrations_with_duplicate/3_foo.rb +7 -0
  207. data/test/fixtures/migrations_with_duplicate/3_innocent_jointable.rb +12 -0
  208. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  209. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  210. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  211. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  212. data/test/fixtures/mixed_case_monkey.rb +3 -0
  213. data/test/fixtures/mixed_case_monkeys.yml +6 -0
  214. data/test/fixtures/mixin.rb +63 -0
  215. data/test/fixtures/mixins.yml +127 -0
  216. data/test/fixtures/movie.rb +5 -0
  217. data/test/fixtures/movies.yml +7 -0
  218. data/test/fixtures/naked/csv/accounts.csv +1 -0
  219. data/test/fixtures/naked/yml/accounts.yml +1 -0
  220. data/test/fixtures/naked/yml/companies.yml +1 -0
  221. data/test/fixtures/naked/yml/courses.yml +1 -0
  222. data/test/fixtures/order.rb +4 -0
  223. data/test/fixtures/people.yml +3 -0
  224. data/test/fixtures/person.rb +4 -0
  225. data/test/fixtures/post.rb +58 -0
  226. data/test/fixtures/posts.yml +48 -0
  227. data/test/fixtures/project.rb +27 -0
  228. data/test/fixtures/projects.yml +7 -0
  229. data/test/fixtures/reader.rb +4 -0
  230. data/test/fixtures/readers.yml +4 -0
  231. data/test/fixtures/reply.rb +37 -0
  232. data/test/fixtures/subject.rb +4 -0
  233. data/test/fixtures/subscriber.rb +6 -0
  234. data/test/fixtures/subscribers/first +2 -0
  235. data/test/fixtures/subscribers/second +2 -0
  236. data/test/fixtures/tag.rb +7 -0
  237. data/test/fixtures/tagging.rb +6 -0
  238. data/test/fixtures/taggings.yml +18 -0
  239. data/test/fixtures/tags.yml +7 -0
  240. data/test/fixtures/task.rb +3 -0
  241. data/test/fixtures/tasks.yml +7 -0
  242. data/test/fixtures/topic.rb +25 -0
  243. data/test/fixtures/topics.yml +22 -0
  244. data/test/fixtures/vertex.rb +9 -0
  245. data/test/fixtures/vertices.yml +4 -0
  246. data/test/fixtures_test.rb +401 -0
  247. data/test/inheritance_test.rb +205 -0
  248. data/test/lifecycle_test.rb +137 -0
  249. data/test/locking_test.rb +190 -0
  250. data/test/method_scoping_test.rb +416 -0
  251. data/test/migration_test.rb +768 -0
  252. data/test/migration_test_firebird.rb +124 -0
  253. data/test/mixin_nested_set_test.rb +196 -0
  254. data/test/mixin_test.rb +550 -0
  255. data/test/modules_test.rb +34 -0
  256. data/test/multiple_db_test.rb +60 -0
  257. data/test/pk_test.rb +104 -0
  258. data/test/readonly_test.rb +107 -0
  259. data/test/reflection_test.rb +159 -0
  260. data/test/schema_authorization_test_postgresql.rb +75 -0
  261. data/test/schema_dumper_test.rb +96 -0
  262. data/test/schema_test_postgresql.rb +64 -0
  263. data/test/synonym_test_oracle.rb +17 -0
  264. data/test/table_name_test_sqlserver.rb +23 -0
  265. data/test/threaded_connections_test.rb +48 -0
  266. data/test/transactions_test.rb +230 -0
  267. data/test/unconnected_test.rb +32 -0
  268. data/test/validations_test.rb +1097 -0
  269. data/test/xml_serialization_test.rb +125 -0
  270. metadata +365 -0
@@ -0,0 +1,407 @@
1
+ # Author: Luke Holden <lholden@cablelan.net>
2
+ # Updated for SQLite3: Jamis Buck <jamis@37signals.com>
3
+
4
+ require 'active_record/connection_adapters/abstract_adapter'
5
+
6
+ module ActiveRecord
7
+ class Base
8
+ class << self
9
+ # sqlite3 adapter reuses sqlite_connection.
10
+ def sqlite3_connection(config) # :nodoc:
11
+ parse_config!(config)
12
+
13
+ unless self.class.const_defined?(:SQLite3)
14
+ require_library_or_gem(config[:adapter])
15
+ end
16
+
17
+ db = SQLite3::Database.new(
18
+ config[:database],
19
+ :results_as_hash => true,
20
+ :type_translation => false
21
+ )
22
+
23
+ db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
24
+
25
+ ConnectionAdapters::SQLite3Adapter.new(db, logger)
26
+ end
27
+
28
+ # Establishes a connection to the database that's used by all Active Record objects
29
+ def sqlite_connection(config) # :nodoc:
30
+ parse_config!(config)
31
+
32
+ unless self.class.const_defined?(:SQLite)
33
+ require_library_or_gem(config[:adapter])
34
+
35
+ db = SQLite::Database.new(config[:database], 0)
36
+ db.show_datatypes = "ON" if !defined? SQLite::Version
37
+ db.results_as_hash = true if defined? SQLite::Version
38
+ db.type_translation = false
39
+
40
+ # "Downgrade" deprecated sqlite API
41
+ if SQLite.const_defined?(:Version)
42
+ ConnectionAdapters::SQLite2Adapter.new(db, logger)
43
+ else
44
+ ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+ def parse_config!(config)
51
+ config[:database] ||= config[:dbfile]
52
+ # Require database.
53
+ unless config[:database]
54
+ raise ArgumentError, "No database file specified. Missing argument: database"
55
+ end
56
+
57
+ # Allow database path relative to RAILS_ROOT, but only if
58
+ # the database path is not the special path that tells
59
+ # Sqlite build a database only in memory.
60
+ if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
61
+ config[:database] = File.expand_path(config[:database], RAILS_ROOT)
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ module ConnectionAdapters #:nodoc:
68
+ class SQLiteColumn < Column #:nodoc:
69
+ class << self
70
+ def string_to_binary(value)
71
+ value.gsub(/\0|\%/) do |b|
72
+ case b
73
+ when "\0" then "%00"
74
+ when "%" then "%25"
75
+ end
76
+ end
77
+ end
78
+
79
+ def binary_to_string(value)
80
+ value.gsub(/%00|%25/) do |b|
81
+ case b
82
+ when "%00" then "\0"
83
+ when "%25" then "%"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ # 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
91
+ # from http://rubyforge.org/projects/sqlite-ruby/).
92
+ #
93
+ # Options:
94
+ #
95
+ # * <tt>:database</tt> -- Path to the database file.
96
+ class SQLiteAdapter < AbstractAdapter
97
+ def adapter_name #:nodoc:
98
+ 'SQLite'
99
+ end
100
+
101
+ def supports_migrations? #:nodoc:
102
+ true
103
+ end
104
+
105
+ def requires_reloading?
106
+ true
107
+ end
108
+
109
+ def supports_count_distinct? #:nodoc:
110
+ false
111
+ end
112
+
113
+ def native_database_types #:nodoc:
114
+ {
115
+ :primary_key => "INTEGER PRIMARY KEY NOT NULL",
116
+ :string => { :name => "varchar", :limit => 255 },
117
+ :text => { :name => "text" },
118
+ :integer => { :name => "integer" },
119
+ :float => { :name => "float" },
120
+ :decimal => { :name => "decimal" },
121
+ :datetime => { :name => "datetime" },
122
+ :timestamp => { :name => "datetime" },
123
+ :time => { :name => "datetime" },
124
+ :date => { :name => "date" },
125
+ :binary => { :name => "blob" },
126
+ :boolean => { :name => "boolean" }
127
+ }
128
+ end
129
+
130
+
131
+ # QUOTING ==================================================
132
+
133
+ def quote_string(s) #:nodoc:
134
+ @connection.class.quote(s)
135
+ end
136
+
137
+ def quote_column_name(name) #:nodoc:
138
+ %Q("#{name}")
139
+ end
140
+
141
+
142
+ # DATABASE STATEMENTS ======================================
143
+
144
+ def execute(sql, name = nil) #:nodoc:
145
+ catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
146
+ end
147
+
148
+ def update(sql, name = nil) #:nodoc:
149
+ execute(sql, name)
150
+ @connection.changes
151
+ end
152
+
153
+ def delete(sql, name = nil) #:nodoc:
154
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
155
+ execute(sql, name)
156
+ @connection.changes
157
+ end
158
+
159
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
160
+ execute(sql, name = nil)
161
+ id_value || @connection.last_insert_row_id
162
+ end
163
+
164
+ def select_all(sql, name = nil) #:nodoc:
165
+ execute(sql, name).map do |row|
166
+ record = {}
167
+ row.each_key do |key|
168
+ if key.is_a?(String)
169
+ record[key.sub(/^\w+\./, '')] = row[key]
170
+ end
171
+ end
172
+ record
173
+ end
174
+ end
175
+
176
+ def select_one(sql, name = nil) #:nodoc:
177
+ result = select_all(sql, name)
178
+ result.nil? ? nil : result.first
179
+ end
180
+
181
+
182
+ def begin_db_transaction #:nodoc:
183
+ catch_schema_changes { @connection.transaction }
184
+ end
185
+
186
+ def commit_db_transaction #:nodoc:
187
+ catch_schema_changes { @connection.commit }
188
+ end
189
+
190
+ def rollback_db_transaction #:nodoc:
191
+ catch_schema_changes { @connection.rollback }
192
+ end
193
+
194
+
195
+ # SELECT ... FOR UPDATE is redundant since the table is locked.
196
+ def add_lock!(sql, options) #:nodoc:
197
+ sql
198
+ end
199
+
200
+
201
+ # SCHEMA STATEMENTS ========================================
202
+
203
+ def tables(name = nil) #:nodoc:
204
+ execute("SELECT name FROM sqlite_master WHERE type = 'table'", name).map do |row|
205
+ row[0]
206
+ end
207
+ end
208
+
209
+ def columns(table_name, name = nil) #:nodoc:
210
+ table_structure(table_name).map do |field|
211
+ SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
212
+ end
213
+ end
214
+
215
+ def indexes(table_name, name = nil) #:nodoc:
216
+ execute("PRAGMA index_list(#{table_name})", name).map do |row|
217
+ index = IndexDefinition.new(table_name, row['name'])
218
+ index.unique = row['unique'] != '0'
219
+ index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
220
+ index
221
+ end
222
+ end
223
+
224
+ def primary_key(table_name) #:nodoc:
225
+ column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
226
+ column ? column['name'] : nil
227
+ end
228
+
229
+ def remove_index(table_name, options={}) #:nodoc:
230
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
231
+ end
232
+
233
+ def rename_table(name, new_name)
234
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
235
+ end
236
+
237
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
238
+ super(table_name, column_name, type, options)
239
+ # See last paragraph on http://www.sqlite.org/lang_altertable.html
240
+ execute "VACUUM"
241
+ end
242
+
243
+ def remove_column(table_name, column_name) #:nodoc:
244
+ alter_table(table_name) do |definition|
245
+ definition.columns.delete(definition[column_name])
246
+ end
247
+ end
248
+
249
+ def change_column_default(table_name, column_name, default) #:nodoc:
250
+ alter_table(table_name) do |definition|
251
+ definition[column_name].default = default
252
+ end
253
+ end
254
+
255
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
256
+ alter_table(table_name) do |definition|
257
+ include_default = options_include_default?(options)
258
+ definition[column_name].instance_eval do
259
+ self.type = type
260
+ self.limit = options[:limit] if options.include?(:limit)
261
+ self.default = options[:default] if include_default
262
+ end
263
+ end
264
+ end
265
+
266
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
267
+ alter_table(table_name, :rename => {column_name => new_column_name})
268
+ end
269
+
270
+
271
+ protected
272
+ def table_structure(table_name)
273
+ returning structure = execute("PRAGMA table_info(#{table_name})") do
274
+ raise ActiveRecord::StatementInvalid if structure.empty?
275
+ end
276
+ end
277
+
278
+ def alter_table(table_name, options = {}) #:nodoc:
279
+ altered_table_name = "altered_#{table_name}"
280
+ caller = lambda {|definition| yield definition if block_given?}
281
+
282
+ transaction do
283
+ move_table(table_name, altered_table_name,
284
+ options.merge(:temporary => true))
285
+ move_table(altered_table_name, table_name, &caller)
286
+ end
287
+ end
288
+
289
+ def move_table(from, to, options = {}, &block) #:nodoc:
290
+ copy_table(from, to, options, &block)
291
+ drop_table(from)
292
+ end
293
+
294
+ def copy_table(from, to, options = {}) #:nodoc:
295
+ create_table(to, options) do |@definition|
296
+ columns(from).each do |column|
297
+ column_name = options[:rename] ?
298
+ (options[:rename][column.name] ||
299
+ options[:rename][column.name.to_sym] ||
300
+ column.name) : column.name
301
+
302
+ @definition.column(column_name, column.type,
303
+ :limit => column.limit, :default => column.default,
304
+ :null => column.null)
305
+ end
306
+ @definition.primary_key(primary_key(from))
307
+ yield @definition if block_given?
308
+ end
309
+
310
+ copy_table_indexes(from, to)
311
+ copy_table_contents(from, to,
312
+ @definition.columns.map {|column| column.name},
313
+ options[:rename] || {})
314
+ end
315
+
316
+ def copy_table_indexes(from, to) #:nodoc:
317
+ indexes(from).each do |index|
318
+ name = index.name
319
+ if to == "altered_#{from}"
320
+ name = "temp_#{name}"
321
+ elsif from == "altered_#{to}"
322
+ name = name[5..-1]
323
+ end
324
+
325
+ # index name can't be the same
326
+ opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
327
+ opts[:unique] = true if index.unique
328
+ add_index(to, index.columns, opts)
329
+ end
330
+ end
331
+
332
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
333
+ column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
334
+ rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
335
+ from_columns = columns(from).collect {|col| col.name}
336
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
337
+ @connection.execute "SELECT * FROM #{from}" do |row|
338
+ sql = "INSERT INTO #{to} ("+columns*','+") VALUES ("
339
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
340
+ sql << ')'
341
+ @connection.execute sql
342
+ end
343
+ end
344
+
345
+ def catch_schema_changes
346
+ return yield
347
+ rescue ActiveRecord::StatementInvalid => exception
348
+ if exception.message =~ /database schema has changed/
349
+ reconnect!
350
+ retry
351
+ else
352
+ raise
353
+ end
354
+ end
355
+ end
356
+
357
+ class SQLite3Adapter < SQLiteAdapter # :nodoc:
358
+ def table_structure(table_name)
359
+ returning structure = @connection.table_info(table_name) do
360
+ raise ActiveRecord::StatementInvalid if structure.empty?
361
+ end
362
+ end
363
+ end
364
+
365
+ class SQLite2Adapter < SQLiteAdapter # :nodoc:
366
+ # SQLite 2 does not support COUNT(DISTINCT) queries:
367
+ #
368
+ # select COUNT(DISTINCT ArtistID) from CDs;
369
+ #
370
+ # In order to get the number of artists we execute the following statement
371
+ #
372
+ # SELECT COUNT(ArtistID) FROM (SELECT DISTINCT ArtistID FROM CDs);
373
+ def execute(sql, name = nil) #:nodoc:
374
+ super(rewrite_count_distinct_queries(sql), name)
375
+ end
376
+
377
+ def rewrite_count_distinct_queries(sql)
378
+ if sql =~ /count\(distinct ([^\)]+)\)( AS \w+)? (.*)/i
379
+ distinct_column = $1
380
+ distinct_query = $3
381
+ column_name = distinct_column.split('.').last
382
+ "SELECT COUNT(#{column_name}) FROM (SELECT DISTINCT #{distinct_column} #{distinct_query})"
383
+ else
384
+ sql
385
+ end
386
+ end
387
+
388
+ def rename_table(name, new_name)
389
+ move_table(name, new_name)
390
+ end
391
+
392
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
393
+ alter_table(table_name) do |definition|
394
+ definition.column(column_name, type, options)
395
+ end
396
+ end
397
+
398
+ end
399
+
400
+ class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
401
+ def insert(sql, name = nil, pk = nil, id_value = nil)
402
+ execute(sql, name = nil)
403
+ id_value || @connection.last_insert_rowid
404
+ end
405
+ end
406
+ end
407
+ end
@@ -0,0 +1,591 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+
6
+ # sqlserver_adapter.rb -- ActiveRecord adapter for Microsoft SQL Server
7
+ #
8
+ # Author: Joey Gibson <joey@joeygibson.com>
9
+ # Date: 10/14/2004
10
+ #
11
+ # Modifications: DeLynn Berry <delynnb@megastarfinancial.com>
12
+ # Date: 3/22/2005
13
+ #
14
+ # Modifications (ODBC): Mark Imbriaco <mark.imbriaco@pobox.com>
15
+ # Date: 6/26/2005
16
+
17
+ # Modifications (Migrations): Tom Ward <tom@popdog.net>
18
+ # Date: 27/10/2005
19
+ #
20
+ # Modifications (Numerous fixes as maintainer): Ryan Tomayko <rtomayko@gmail.com>
21
+ # Date: Up to July 2006
22
+
23
+ # Current maintainer: Tom Ward <tom@popdog.net>
24
+
25
+ module ActiveRecord
26
+ class Base
27
+ def self.sqlserver_connection(config) #:nodoc:
28
+ require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
29
+
30
+ config = config.symbolize_keys
31
+
32
+ mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
33
+ username = config[:username] ? config[:username].to_s : 'sa'
34
+ password = config[:password] ? config[:password].to_s : ''
35
+ autocommit = config.key?(:autocommit) ? config[:autocommit] : true
36
+ if mode == "ODBC"
37
+ raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn)
38
+ dsn = config[:dsn]
39
+ driver_url = "DBI:ODBC:#{dsn}"
40
+ else
41
+ raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database)
42
+ database = config[:database]
43
+ host = config[:host] ? config[:host].to_s : 'localhost'
44
+ driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};"
45
+ end
46
+ conn = DBI.connect(driver_url, username, password)
47
+ conn["AutoCommit"] = autocommit
48
+ ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
49
+ end
50
+ end # class Base
51
+
52
+ module ConnectionAdapters
53
+ class SQLServerColumn < Column# :nodoc:
54
+ attr_reader :identity, :is_special
55
+
56
+ def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0
57
+ super(name, default, sql_type, null)
58
+ @identity = identity
59
+ @is_special = sql_type =~ /text|ntext|image/i
60
+ # TODO: check ok to remove @scale = scale_value
61
+ # SQL Server only supports limits on *char and float types
62
+ @limit = nil unless @type == :float or @type == :string
63
+ end
64
+
65
+ def simplified_type(field_type)
66
+ case field_type
67
+ when /money/i then :decimal
68
+ when /image/i then :binary
69
+ when /bit/i then :boolean
70
+ when /uniqueidentifier/i then :string
71
+ else super
72
+ end
73
+ end
74
+
75
+ def type_cast(value)
76
+ return nil if value.nil?
77
+ case type
78
+ when :datetime then cast_to_datetime(value)
79
+ when :timestamp then cast_to_time(value)
80
+ when :time then cast_to_time(value)
81
+ when :date then cast_to_datetime(value)
82
+ when :boolean then value == true or (value =~ /^t(rue)?$/i) == 0 or value.to_s == '1'
83
+ else super
84
+ end
85
+ end
86
+
87
+ def cast_to_time(value)
88
+ return value if value.is_a?(Time)
89
+ time_array = ParseDate.parsedate(value)
90
+ Time.send(Base.default_timezone, *time_array) rescue nil
91
+ end
92
+
93
+ def cast_to_datetime(value)
94
+ return value.to_time if value.is_a?(DBI::Timestamp)
95
+
96
+ if value.is_a?(Time)
97
+ if value.year != 0 and value.month != 0 and value.day != 0
98
+ return value
99
+ else
100
+ return Time.mktime(2000, 1, 1, value.hour, value.min, value.sec) rescue nil
101
+ end
102
+ end
103
+
104
+ if value.is_a?(DateTime)
105
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
106
+ end
107
+
108
+ return cast_to_time(value) if value.is_a?(Date) or value.is_a?(String) rescue nil
109
+ value
110
+ end
111
+
112
+ # TODO: Find less hack way to convert DateTime objects into Times
113
+
114
+ def self.string_to_time(value)
115
+ if value.is_a?(DateTime)
116
+ return Time.mktime(value.year, value.mon, value.day, value.hour, value.min, value.sec)
117
+ else
118
+ super
119
+ end
120
+ end
121
+
122
+ # These methods will only allow the adapter to insert binary data with a length of 7K or less
123
+ # because of a SQL Server statement length policy.
124
+ def self.string_to_binary(value)
125
+ value.gsub(/(\r|\n|\0|\x1a)/) do
126
+ case $1
127
+ when "\r" then "%00"
128
+ when "\n" then "%01"
129
+ when "\0" then "%02"
130
+ when "\x1a" then "%03"
131
+ end
132
+ end
133
+ end
134
+
135
+ def self.binary_to_string(value)
136
+ value.gsub(/(%00|%01|%02|%03)/) do
137
+ case $1
138
+ when "%00" then "\r"
139
+ when "%01" then "\n"
140
+ when "%02\0" then "\0"
141
+ when "%03" then "\x1a"
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ # In ADO mode, this adapter will ONLY work on Windows systems,
148
+ # since it relies on Win32OLE, which, to my knowledge, is only
149
+ # available on Windows.
150
+ #
151
+ # This mode also relies on the ADO support in the DBI module. If you are using the
152
+ # one-click installer of Ruby, then you already have DBI installed, but
153
+ # the ADO module is *NOT* installed. You will need to get the latest
154
+ # source distribution of Ruby-DBI from http://ruby-dbi.sourceforge.net/
155
+ # unzip it, and copy the file
156
+ # <tt>src/lib/dbd_ado/ADO.rb</tt>
157
+ # to
158
+ # <tt>X:/Ruby/lib/ruby/site_ruby/1.8/DBD/ADO/ADO.rb</tt>
159
+ # (you will more than likely need to create the ADO directory).
160
+ # Once you've installed that file, you are ready to go.
161
+ #
162
+ # In ODBC mode, the adapter requires the ODBC support in the DBI module which requires
163
+ # the Ruby ODBC module. Ruby ODBC 0.996 was used in development and testing,
164
+ # and it is available at http://www.ch-werner.de/rubyodbc/
165
+ #
166
+ # Options:
167
+ #
168
+ # * <tt>:mode</tt> -- ADO or ODBC. Defaults to ADO.
169
+ # * <tt>:username</tt> -- Defaults to sa.
170
+ # * <tt>:password</tt> -- Defaults to empty string.
171
+ #
172
+ # ADO specific options:
173
+ #
174
+ # * <tt>:host</tt> -- Defaults to localhost.
175
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
176
+ #
177
+ # ODBC specific options:
178
+ #
179
+ # * <tt>:dsn</tt> -- Defaults to nothing.
180
+ #
181
+ # ADO code tested on Windows 2000 and higher systems,
182
+ # running ruby 1.8.2 (2004-07-29) [i386-mswin32], and SQL Server 2000 SP3.
183
+ #
184
+ # ODBC code tested on a Fedora Core 4 system, running FreeTDS 0.63,
185
+ # unixODBC 2.2.11, Ruby ODBC 0.996, Ruby DBI 0.0.23 and Ruby 1.8.2.
186
+ # [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux]
187
+ class SQLServerAdapter < AbstractAdapter
188
+
189
+ def initialize(connection, logger, connection_options=nil)
190
+ super(connection, logger)
191
+ @connection_options = connection_options
192
+ end
193
+
194
+ def native_database_types
195
+ {
196
+ :primary_key => "int NOT NULL IDENTITY(1, 1) PRIMARY KEY",
197
+ :string => { :name => "varchar", :limit => 255 },
198
+ :text => { :name => "text" },
199
+ :integer => { :name => "int" },
200
+ :float => { :name => "float", :limit => 8 },
201
+ :decimal => { :name => "decimal" },
202
+ :datetime => { :name => "datetime" },
203
+ :timestamp => { :name => "datetime" },
204
+ :time => { :name => "datetime" },
205
+ :date => { :name => "datetime" },
206
+ :binary => { :name => "image"},
207
+ :boolean => { :name => "bit"}
208
+ }
209
+ end
210
+
211
+ def adapter_name
212
+ 'SQLServer'
213
+ end
214
+
215
+ def supports_migrations? #:nodoc:
216
+ true
217
+ end
218
+
219
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
220
+ return super unless type.to_s == 'integer'
221
+
222
+ if limit.nil? || limit == 4
223
+ 'integer'
224
+ elsif limit < 4
225
+ 'smallint'
226
+ else
227
+ 'bigint'
228
+ end
229
+ end
230
+
231
+ # CONNECTION MANAGEMENT ====================================#
232
+
233
+ # Returns true if the connection is active.
234
+ def active?
235
+ @connection.execute("SELECT 1").finish
236
+ true
237
+ rescue DBI::DatabaseError, DBI::InterfaceError
238
+ false
239
+ end
240
+
241
+ # Reconnects to the database, returns false if no connection could be made.
242
+ def reconnect!
243
+ disconnect!
244
+ @connection = DBI.connect(*@connection_options)
245
+ rescue DBI::DatabaseError => e
246
+ @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger
247
+ false
248
+ end
249
+
250
+ # Disconnects from the database
251
+
252
+ def disconnect!
253
+ @connection.disconnect rescue nil
254
+ end
255
+
256
+ def columns(table_name, name = nil)
257
+ return [] if table_name.blank?
258
+ table_name = table_name.to_s if table_name.is_a?(Symbol)
259
+ table_name = table_name.split('.')[-1] unless table_name.nil?
260
+ table_name = table_name.gsub(/[\[\]]/, '')
261
+ sql = %Q{
262
+ SELECT
263
+ cols.COLUMN_NAME as ColName,
264
+ cols.COLUMN_DEFAULT as DefaultValue,
265
+ cols.NUMERIC_SCALE as numeric_scale,
266
+ cols.NUMERIC_PRECISION as numeric_precision,
267
+ cols.DATA_TYPE as ColType,
268
+ cols.IS_NULLABLE As IsNullable,
269
+ COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,
270
+ COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,
271
+ cols.NUMERIC_SCALE as Scale
272
+ FROM INFORMATION_SCHEMA.COLUMNS cols
273
+ WHERE cols.TABLE_NAME = '#{table_name}'
274
+ }
275
+ # Comment out if you want to have the Columns select statment logged.
276
+ # Personally, I think it adds unnecessary bloat to the log.
277
+ # If you do comment it out, make sure to un-comment the "result" line that follows
278
+ result = log(sql, name) { @connection.select_all(sql) }
279
+ #result = @connection.select_all(sql)
280
+ columns = []
281
+ result.each do |field|
282
+ default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/ ? nil : field[:DefaultValue]
283
+ if field[:ColType] =~ /numeric|decimal/i
284
+ type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
285
+ else
286
+ type = "#{field[:ColType]}(#{field[:Length]})"
287
+ end
288
+ is_identity = field[:IsIdentity] == 1
289
+ is_nullable = field[:IsNullable] == 'YES'
290
+ columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
291
+ end
292
+ columns
293
+ end
294
+
295
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
296
+ execute(sql, name)
297
+ id_value || select_one("SELECT @@IDENTITY AS Ident")["Ident"]
298
+ end
299
+
300
+ def update(sql, name = nil)
301
+ execute(sql, name) do |handle|
302
+ handle.rows
303
+ end || select_one("SELECT @@ROWCOUNT AS AffectedRows")["AffectedRows"]
304
+ end
305
+
306
+ alias_method :delete, :update
307
+
308
+ def execute(sql, name = nil)
309
+ if sql =~ /^\s*INSERT/i && (table_name = query_requires_identity_insert?(sql))
310
+ log(sql, name) do
311
+ with_identity_insert_enabled(table_name) do
312
+ @connection.execute(sql) do |handle|
313
+ yield(handle) if block_given?
314
+ end
315
+ end
316
+ end
317
+ else
318
+ log(sql, name) do
319
+ @connection.execute(sql) do |handle|
320
+ yield(handle) if block_given?
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ def begin_db_transaction
327
+ @connection["AutoCommit"] = false
328
+ rescue Exception => e
329
+ @connection["AutoCommit"] = true
330
+ end
331
+
332
+ def commit_db_transaction
333
+ @connection.commit
334
+ ensure
335
+ @connection["AutoCommit"] = true
336
+ end
337
+
338
+ def rollback_db_transaction
339
+ @connection.rollback
340
+ ensure
341
+ @connection["AutoCommit"] = true
342
+ end
343
+
344
+ def quote(value, column = nil)
345
+ return value.quoted_id if value.respond_to?(:quoted_id)
346
+
347
+ case value
348
+ when TrueClass then '1'
349
+ when FalseClass then '0'
350
+ when Time, DateTime then "'#{value.strftime("%Y%m%d %H:%M:%S")}'"
351
+ when Date then "'#{value.strftime("%Y%m%d")}'"
352
+ else super
353
+ end
354
+ end
355
+
356
+ def quote_string(string)
357
+ string.gsub(/\'/, "''")
358
+ end
359
+
360
+ def quote_column_name(name)
361
+ "[#{name}]"
362
+ end
363
+
364
+ def add_limit_offset!(sql, options)
365
+ if options[:limit] and options[:offset]
366
+ total_rows = @connection.select_all("SELECT count(*) as TotalRows from (#{sql.gsub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally")[0][:TotalRows].to_i
367
+ if (options[:limit] + options[:offset]) >= total_rows
368
+ options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
369
+ end
370
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i, "SELECT * FROM (SELECT TOP #{options[:limit]} * FROM (SELECT#{$1} TOP #{options[:limit] + options[:offset]} ")
371
+ sql << ") AS tmp1"
372
+ if options[:order]
373
+ options[:order] = options[:order].split(',').map do |field|
374
+ parts = field.split(" ")
375
+ tc = parts[0]
376
+ if sql =~ /\.\[/ and tc =~ /\./ # if column quoting used in query
377
+ tc.gsub!(/\./, '\\.\\[')
378
+ tc << '\\]'
379
+ end
380
+ if sql =~ /#{tc} AS (t\d_r\d\d?)/
381
+ parts[0] = $1
382
+ elsif parts[0] =~ /\w+\.(\w+)/
383
+ parts[0] = $1
384
+ end
385
+ parts.join(' ')
386
+ end.join(', ')
387
+ sql << " ORDER BY #{change_order_direction(options[:order])}) AS tmp2 ORDER BY #{options[:order]}"
388
+ else
389
+ sql << " ) AS tmp2"
390
+ end
391
+ elsif sql !~ /^\s*SELECT (@@|COUNT\()/i
392
+ sql.sub!(/^\s*SELECT(\s+DISTINCT)?/i) do
393
+ "SELECT#{$1} TOP #{options[:limit]}"
394
+ end unless options[:limit].nil?
395
+ end
396
+ end
397
+
398
+ def recreate_database(name)
399
+ drop_database(name)
400
+ create_database(name)
401
+ end
402
+
403
+ def drop_database(name)
404
+ execute "DROP DATABASE #{name}"
405
+ end
406
+
407
+ def create_database(name)
408
+ execute "CREATE DATABASE #{name}"
409
+ end
410
+
411
+ def current_database
412
+ @connection.select_one("select DB_NAME()")[0]
413
+ end
414
+
415
+ def tables(name = nil)
416
+ execute("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'", name) do |sth|
417
+ sth.inject([]) do |tables, field|
418
+ table_name = field[0]
419
+ tables << table_name unless table_name == 'dtproperties'
420
+ tables
421
+ end
422
+ end
423
+ end
424
+
425
+ def indexes(table_name, name = nil)
426
+ ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = false
427
+ indexes = []
428
+ execute("EXEC sp_helpindex '#{table_name}'", name) do |sth|
429
+ sth.each do |index|
430
+ unique = index[1] =~ /unique/
431
+ primary = index[1] =~ /primary key/
432
+ if !primary
433
+ indexes << IndexDefinition.new(table_name, index[0], unique, index[2].split(", "))
434
+ end
435
+ end
436
+ end
437
+ indexes
438
+ ensure
439
+ ActiveRecord::Base.connection.instance_variable_get("@connection")["AutoCommit"] = true
440
+ end
441
+
442
+ def rename_table(name, new_name)
443
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
444
+ end
445
+
446
+ # Adds a new column to the named table.
447
+ # See TableDefinition#column for details of the options you can use.
448
+ def add_column(table_name, column_name, type, options = {})
449
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
450
+ add_column_options!(add_column_sql, options)
451
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
452
+ # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
453
+ execute(add_column_sql)
454
+ end
455
+
456
+ def rename_column(table, column, new_column_name)
457
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
458
+ end
459
+
460
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
461
+ sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
462
+ if options_include_default?(options)
463
+ remove_default_constraint(table_name, column_name)
464
+ sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
465
+ end
466
+ sql_commands.each {|c|
467
+ execute(c)
468
+ }
469
+ end
470
+
471
+ def remove_column(table_name, column_name)
472
+ remove_check_constraints(table_name, column_name)
473
+ remove_default_constraint(table_name, column_name)
474
+ execute "ALTER TABLE [#{table_name}] DROP COLUMN [#{column_name}]"
475
+ end
476
+
477
+ def remove_default_constraint(table_name, column_name)
478
+ constraints = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
479
+
480
+ constraints.each do |constraint|
481
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
482
+ end
483
+ end
484
+
485
+ def remove_check_constraints(table_name, column_name)
486
+ # TODO remove all constraints in single method
487
+ constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
488
+ constraints.each do |constraint|
489
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
490
+ end
491
+ end
492
+
493
+ def remove_index(table_name, options = {})
494
+ execute "DROP INDEX #{table_name}.#{quote_column_name(index_name(table_name, options))}"
495
+ end
496
+
497
+ private
498
+ def select(sql, name = nil)
499
+ repair_special_columns(sql)
500
+
501
+ result = []
502
+ execute(sql) do |handle|
503
+ handle.each do |row|
504
+ row_hash = {}
505
+ row.each_with_index do |value, i|
506
+ if value.is_a? DBI::Timestamp
507
+ value = DateTime.new(value.year, value.month, value.day, value.hour, value.minute, value.sec)
508
+ end
509
+ row_hash[handle.column_names[i]] = value
510
+ end
511
+ result << row_hash
512
+ end
513
+ end
514
+ result
515
+ end
516
+
517
+ # Turns IDENTITY_INSERT ON for table during execution of the block
518
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
519
+ # block has been executed without regard to its previous state
520
+
521
+ def with_identity_insert_enabled(table_name, &block)
522
+ set_identity_insert(table_name, true)
523
+ yield
524
+ ensure
525
+ set_identity_insert(table_name, false)
526
+ end
527
+
528
+ def set_identity_insert(table_name, enable = true)
529
+ execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
530
+ rescue Exception => e
531
+ raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
532
+ end
533
+
534
+ def get_table_name(sql)
535
+ if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
536
+ $1
537
+ elsif sql =~ /from\s+([^\(\s]+)\s*/i
538
+ $1
539
+ else
540
+ nil
541
+ end
542
+ end
543
+
544
+ def identity_column(table_name)
545
+ @table_columns = {} unless @table_columns
546
+ @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
547
+ @table_columns[table_name].each do |col|
548
+ return col.name if col.identity
549
+ end
550
+
551
+ return nil
552
+ end
553
+
554
+ def query_requires_identity_insert?(sql)
555
+ table_name = get_table_name(sql)
556
+ id_column = identity_column(table_name)
557
+ sql =~ /\[#{id_column}\]/ ? table_name : nil
558
+ end
559
+
560
+ def change_order_direction(order)
561
+ order.split(",").collect {|fragment|
562
+ case fragment
563
+ when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
564
+ when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
565
+ else String.new(fragment).split(',').join(' DESC,') + ' DESC'
566
+ end
567
+ }.join(",")
568
+ end
569
+
570
+ def get_special_columns(table_name)
571
+ special = []
572
+ @table_columns ||= {}
573
+ @table_columns[table_name] ||= columns(table_name)
574
+ @table_columns[table_name].each do |col|
575
+ special << col.name if col.is_special
576
+ end
577
+ special
578
+ end
579
+
580
+ def repair_special_columns(sql)
581
+ special_cols = get_special_columns(get_table_name(sql))
582
+ for col in special_cols.to_a
583
+ sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
584
+ sql.gsub!(/ORDER BY #{col.to_s}/i, '')
585
+ end
586
+ sql
587
+ end
588
+
589
+ end #class SQLServerAdapter < AbstractAdapter
590
+ end #module ConnectionAdapters
591
+ end #module ActiveRecord