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,161 @@
1
+ require 'benchmark'
2
+ require 'date'
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+
6
+ require 'active_record/connection_adapters/abstract/schema_definitions'
7
+ require 'active_record/connection_adapters/abstract/schema_statements'
8
+ require 'active_record/connection_adapters/abstract/database_statements'
9
+ require 'active_record/connection_adapters/abstract/quoting'
10
+ require 'active_record/connection_adapters/abstract/connection_specification'
11
+
12
+ module ActiveRecord
13
+ module ConnectionAdapters # :nodoc:
14
+ # All the concrete database adapters follow the interface laid down in this class.
15
+ # You can use this interface directly by borrowing the database connection from the Base with
16
+ # Base.connection.
17
+ #
18
+ # Most of the methods in the adapter are useful during migrations. Most
19
+ # notably, SchemaStatements#create_table, SchemaStatements#drop_table,
20
+ # SchemaStatements#add_index, SchemaStatements#remove_index,
21
+ # SchemaStatements#add_column, SchemaStatements#change_column and
22
+ # SchemaStatements#remove_column are very useful.
23
+ class AbstractAdapter
24
+ include Quoting, DatabaseStatements, SchemaStatements
25
+ @@row_even = true
26
+
27
+ def initialize(connection, logger = nil) #:nodoc:
28
+ @connection, @logger = connection, logger
29
+ @runtime = 0
30
+ @last_verification = 0
31
+ end
32
+
33
+ # Returns the human-readable name of the adapter. Use mixed case - one
34
+ # can always use downcase if needed.
35
+ def adapter_name
36
+ 'Abstract'
37
+ end
38
+
39
+ # Does this adapter support migrations? Backend specific, as the
40
+ # abstract adapter always returns +false+.
41
+ def supports_migrations?
42
+ false
43
+ end
44
+
45
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
46
+ # for all adapters except sqlite.
47
+ def supports_count_distinct?
48
+ true
49
+ end
50
+
51
+ # Should primary key values be selected from their corresponding
52
+ # sequence before the insert statement? If true, next_sequence_value
53
+ # is called before each insert to set the record's primary key.
54
+ # This is false for all adapters but Firebird.
55
+ def prefetch_primary_key?(table_name = nil)
56
+ false
57
+ end
58
+
59
+ def reset_runtime #:nodoc:
60
+ rt, @runtime = @runtime, 0
61
+ rt
62
+ end
63
+
64
+
65
+ # CONNECTION MANAGEMENT ====================================
66
+
67
+ # Is this connection active and ready to perform queries?
68
+ def active?
69
+ @active != false
70
+ end
71
+
72
+ # Close this connection and open a new one in its place.
73
+ def reconnect!
74
+ @active = true
75
+ end
76
+
77
+ # Close this connection
78
+ def disconnect!
79
+ @active = false
80
+ end
81
+
82
+ # Returns true if its safe to reload the connection between requests for development mode.
83
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
84
+ def requires_reloading?
85
+ false
86
+ end
87
+
88
+ # Lazily verify this connection, calling +active?+ only if it hasn't
89
+ # been called for +timeout+ seconds.
90
+ def verify!(timeout)
91
+ now = Time.now.to_i
92
+ if (now - @last_verification) > timeout
93
+ reconnect! unless active?
94
+ @last_verification = now
95
+ end
96
+ end
97
+
98
+ # Provides access to the underlying database connection. Useful for
99
+ # when you need to call a proprietary method such as postgresql's lo_*
100
+ # methods
101
+ def raw_connection
102
+ @connection
103
+ end
104
+
105
+ protected
106
+ def log(sql, name)
107
+ if block_given?
108
+ if @logger and @logger.level <= Logger::INFO
109
+ result = nil
110
+ seconds = Benchmark.realtime { result = yield }
111
+ @runtime += seconds
112
+ log_info(sql, name, seconds)
113
+ result
114
+ else
115
+ yield
116
+ end
117
+ else
118
+ log_info(sql, name, 0)
119
+ nil
120
+ end
121
+ rescue Exception => e
122
+ # Log message and raise exception.
123
+ # Set last_verfication to 0, so that connection gets verified
124
+ # upon reentering the request loop
125
+ @last_verification = 0
126
+ message = "#{e.class.name}: #{e.message}: #{sql}"
127
+ log_info(message, name, 0)
128
+ raise ActiveRecord::StatementInvalid, message
129
+ end
130
+
131
+ def log_info(sql, name, runtime)
132
+ return unless @logger
133
+
134
+ @logger.debug(
135
+ format_log_entry(
136
+ "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
137
+ sql.gsub(/ +/, " ")
138
+ )
139
+ )
140
+ end
141
+
142
+ def format_log_entry(message, dump = nil)
143
+ if ActiveRecord::Base.colorize_logging
144
+ if @@row_even
145
+ @@row_even = false
146
+ message_color, dump_color = "4;36;1", "0;1"
147
+ else
148
+ @@row_even = true
149
+ message_color, dump_color = "4;35;1", "0"
150
+ end
151
+
152
+ log_entry = " \e[#{message_color}m#{message}\e[0m "
153
+ log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
154
+ log_entry
155
+ else
156
+ "%s %s" % [message, dump]
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,228 @@
1
+ # Author/Maintainer: Maik Schmidt <contact@maik-schmidt.de>
2
+
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+
5
+ begin
6
+ require 'db2/db2cli' unless self.class.const_defined?(:DB2CLI)
7
+ require 'active_record/vendor/db2'
8
+
9
+ module ActiveRecord
10
+ class Base
11
+ # Establishes a connection to the database that's used by
12
+ # all Active Record objects
13
+ def self.db2_connection(config) # :nodoc:
14
+ config = config.symbolize_keys
15
+ usr = config[:username]
16
+ pwd = config[:password]
17
+ schema = config[:schema]
18
+
19
+ if config.has_key?(:database)
20
+ database = config[:database]
21
+ else
22
+ raise ArgumentError, 'No database specified. Missing argument: database.'
23
+ end
24
+
25
+ connection = DB2::Connection.new(DB2::Environment.new)
26
+ connection.connect(database, usr, pwd)
27
+ ConnectionAdapters::DB2Adapter.new(connection, logger, :schema => schema)
28
+ end
29
+ end
30
+
31
+ module ConnectionAdapters
32
+ # The DB2 adapter works with the C-based CLI driver (http://rubyforge.org/projects/ruby-dbi/)
33
+ #
34
+ # Options:
35
+ #
36
+ # * <tt>:username</tt> -- Defaults to nothing
37
+ # * <tt>:password</tt> -- Defaults to nothing
38
+ # * <tt>:database</tt> -- The name of the database. No default, must be provided.
39
+ # * <tt>:schema</tt> -- Database schema to be set initially.
40
+ class DB2Adapter < AbstractAdapter
41
+ def initialize(connection, logger, connection_options)
42
+ super(connection, logger)
43
+ @connection_options = connection_options
44
+ if schema = @connection_options[:schema]
45
+ with_statement do |stmt|
46
+ stmt.exec_direct("SET SCHEMA=#{schema}")
47
+ end
48
+ end
49
+ end
50
+
51
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
52
+ execute(sql, name = nil)
53
+ id_value || last_insert_id
54
+ end
55
+
56
+ def execute(sql, name = nil)
57
+ rows_affected = 0
58
+ with_statement do |stmt|
59
+ log(sql, name) do
60
+ stmt.exec_direct(sql)
61
+ rows_affected = stmt.row_count
62
+ end
63
+ end
64
+ rows_affected
65
+ end
66
+
67
+ def begin_db_transaction
68
+ @connection.set_auto_commit_off
69
+ end
70
+
71
+ def commit_db_transaction
72
+ @connection.commit
73
+ @connection.set_auto_commit_on
74
+ end
75
+
76
+ def rollback_db_transaction
77
+ @connection.rollback
78
+ @connection.set_auto_commit_on
79
+ end
80
+
81
+ def quote_column_name(column_name)
82
+ column_name
83
+ end
84
+
85
+ def adapter_name()
86
+ 'DB2'
87
+ end
88
+
89
+ def quote_string(string)
90
+ string.gsub(/'/, "''") # ' (for ruby-mode)
91
+ end
92
+
93
+ def add_limit_offset!(sql, options)
94
+ if limit = options[:limit]
95
+ offset = options[:offset] || 0
96
+ # The following trick was added by andrea+rails@webcom.it.
97
+ sql.gsub!(/SELECT/i, 'SELECT B.* FROM (SELECT A.*, row_number() over () AS internal$rownum FROM (SELECT')
98
+ sql << ") A ) B WHERE B.internal$rownum > #{offset} AND B.internal$rownum <= #{limit + offset}"
99
+ end
100
+ end
101
+
102
+ def tables(name = nil)
103
+ result = []
104
+ schema = @connection_options[:schema] || '%'
105
+ with_statement do |stmt|
106
+ stmt.tables(schema).each { |t| result << t[2].downcase }
107
+ end
108
+ result
109
+ end
110
+
111
+ def indexes(table_name, name = nil)
112
+ tmp = {}
113
+ schema = @connection_options[:schema] || ''
114
+ with_statement do |stmt|
115
+ stmt.indexes(table_name, schema).each do |t|
116
+ next unless t[5]
117
+ next if t[4] == 'SYSIBM' # Skip system indexes.
118
+ idx_name = t[5].downcase
119
+ col_name = t[8].downcase
120
+ if tmp.has_key?(idx_name)
121
+ tmp[idx_name].columns << col_name
122
+ else
123
+ is_unique = t[3] == 0
124
+ tmp[idx_name] = IndexDefinition.new(table_name, idx_name, is_unique, [col_name])
125
+ end
126
+ end
127
+ end
128
+ tmp.values
129
+ end
130
+
131
+ def columns(table_name, name = nil)
132
+ result = []
133
+ schema = @connection_options[:schema] || '%'
134
+ with_statement do |stmt|
135
+ stmt.columns(table_name, schema).each do |c|
136
+ c_name = c[3].downcase
137
+ c_default = c[12] == 'NULL' ? nil : c[12]
138
+ c_default.gsub!(/^'(.*)'$/, '\1') if !c_default.nil?
139
+ c_type = c[5].downcase
140
+ c_type += "(#{c[6]})" if !c[6].nil? && c[6] != ''
141
+ result << Column.new(c_name, c_default, c_type, c[17] == 'YES')
142
+ end
143
+ end
144
+ result
145
+ end
146
+
147
+ def native_database_types
148
+ {
149
+ :primary_key => 'int generated by default as identity (start with 42) primary key',
150
+ :string => { :name => 'varchar', :limit => 255 },
151
+ :text => { :name => 'clob', :limit => 32768 },
152
+ :integer => { :name => 'int' },
153
+ :float => { :name => 'float' },
154
+ :decimal => { :name => 'decimal' },
155
+ :datetime => { :name => 'timestamp' },
156
+ :timestamp => { :name => 'timestamp' },
157
+ :time => { :name => 'time' },
158
+ :date => { :name => 'date' },
159
+ :binary => { :name => 'blob', :limit => 32768 },
160
+ :boolean => { :name => 'decimal', :limit => 1 }
161
+ }
162
+ end
163
+
164
+ def quoted_true
165
+ '1'
166
+ end
167
+
168
+ def quoted_false
169
+ '0'
170
+ end
171
+
172
+ def active?
173
+ @connection.select_one 'select 1 from ibm.sysdummy1'
174
+ true
175
+ rescue Exception
176
+ false
177
+ end
178
+
179
+ def reconnect!
180
+ end
181
+
182
+ def table_alias_length
183
+ 128
184
+ end
185
+
186
+ private
187
+
188
+ def with_statement
189
+ stmt = DB2::Statement.new(@connection)
190
+ yield stmt
191
+ stmt.free
192
+ end
193
+
194
+ def last_insert_id
195
+ row = select_one(<<-GETID.strip)
196
+ with temp(id) as (values (identity_val_local())) select * from temp
197
+ GETID
198
+ row['id'].to_i
199
+ end
200
+
201
+ def select(sql, name = nil)
202
+ rows = []
203
+ with_statement do |stmt|
204
+ log(sql, name) do
205
+ stmt.exec_direct("#{sql.gsub(/=\s*null/i, 'IS NULL')} with ur")
206
+ end
207
+
208
+ while row = stmt.fetch_as_hash
209
+ row.delete('internal$rownum')
210
+ rows << row
211
+ end
212
+ end
213
+ rows
214
+ end
215
+ end
216
+ end
217
+ end
218
+ rescue LoadError
219
+ # DB2 driver is unavailable.
220
+ module ActiveRecord # :nodoc:
221
+ class Base
222
+ def self.db2_connection(config) # :nodoc:
223
+ # Set up a reasonable error message
224
+ raise LoadError, "DB2 Libraries could not be loaded."
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,728 @@
1
+ # Author: Ken Kunz <kennethkunz@gmail.com>
2
+
3
+ require 'active_record/connection_adapters/abstract_adapter'
4
+
5
+ module FireRuby # :nodoc: all
6
+ NON_EXISTENT_DOMAIN_ERROR = "335544569"
7
+ class Database
8
+ def self.db_string_for(config)
9
+ unless config.has_key?(:database)
10
+ raise ArgumentError, "No database specified. Missing argument: database."
11
+ end
12
+ host_string = config.values_at(:host, :service, :port).compact.first(2).join("/") if config[:host]
13
+ [host_string, config[:database]].join(":")
14
+ end
15
+
16
+ def self.new_from_config(config)
17
+ db = new db_string_for(config)
18
+ db.character_set = config[:charset]
19
+ return db
20
+ end
21
+ end
22
+ end
23
+
24
+ module ActiveRecord
25
+ class << Base
26
+ def firebird_connection(config) # :nodoc:
27
+ require_library_or_gem 'fireruby'
28
+ unless defined? FireRuby::SQLType
29
+ raise AdapterNotFound,
30
+ 'The Firebird adapter requires FireRuby version 0.4.0 or greater; you appear ' <<
31
+ 'to be running an older version -- please update FireRuby (gem install fireruby).'
32
+ end
33
+ config.symbolize_keys!
34
+ db = FireRuby::Database.new_from_config(config)
35
+ connection_params = config.values_at(:username, :password)
36
+ connection = db.connect(*connection_params)
37
+ ConnectionAdapters::FirebirdAdapter.new(connection, logger, connection_params)
38
+ end
39
+ end
40
+
41
+ module ConnectionAdapters
42
+ class FirebirdColumn < Column # :nodoc:
43
+ VARCHAR_MAX_LENGTH = 32_765
44
+ BLOB_MAX_LENGTH = 32_767
45
+
46
+ def initialize(name, domain, type, sub_type, length, precision, scale, default_source, null_flag)
47
+ @firebird_type = FireRuby::SQLType.to_base_type(type, sub_type).to_s
48
+
49
+ super(name.downcase, nil, @firebird_type, !null_flag)
50
+
51
+ @default = parse_default(default_source) if default_source
52
+ @limit = decide_limit(length)
53
+ @domain, @sub_type, @precision, @scale = domain, sub_type, precision, scale.abs
54
+ end
55
+
56
+ def type
57
+ if @domain =~ /BOOLEAN/
58
+ :boolean
59
+ elsif @type == :binary and @sub_type == 1
60
+ :text
61
+ else
62
+ @type
63
+ end
64
+ end
65
+
66
+ def default
67
+ type_cast(decide_default) if @default
68
+ end
69
+
70
+ def self.value_to_boolean(value)
71
+ %W(#{FirebirdAdapter.boolean_domain[:true]} true t 1).include? value.to_s.downcase
72
+ end
73
+
74
+ private
75
+ def parse_default(default_source)
76
+ default_source =~ /^\s*DEFAULT\s+(.*)\s*$/i
77
+ return $1 unless $1.upcase == "NULL"
78
+ end
79
+
80
+ def decide_default
81
+ if @default =~ /^'?(\d*\.?\d+)'?$/ or
82
+ @default =~ /^'(.*)'$/ && [:text, :string, :binary, :boolean].include?(type)
83
+ $1
84
+ else
85
+ firebird_cast_default
86
+ end
87
+ end
88
+
89
+ # Submits a _CAST_ query to the database, casting the default value to the specified SQL type.
90
+ # This enables Firebird to provide an actual value when context variables are used as column
91
+ # defaults (such as CURRENT_TIMESTAMP).
92
+ def firebird_cast_default
93
+ sql = "SELECT CAST(#{@default} AS #{column_def}) FROM RDB$DATABASE"
94
+ if connection = Base.active_connections.values.detect { |conn| conn && conn.adapter_name == 'Firebird' }
95
+ connection.execute(sql).to_a.first['CAST']
96
+ else
97
+ raise ConnectionNotEstablished, "No Firebird connections established."
98
+ end
99
+ end
100
+
101
+ def decide_limit(length)
102
+ if text? or number?
103
+ length
104
+ elsif @firebird_type == 'BLOB'
105
+ BLOB_MAX_LENGTH
106
+ end
107
+ end
108
+
109
+ def column_def
110
+ case @firebird_type
111
+ when 'BLOB' then "VARCHAR(#{VARCHAR_MAX_LENGTH})"
112
+ when 'CHAR', 'VARCHAR' then "#{@firebird_type}(#{@limit})"
113
+ when 'NUMERIC', 'DECIMAL' then "#{@firebird_type}(#{@precision},#{@scale.abs})"
114
+ when 'DOUBLE' then "DOUBLE PRECISION"
115
+ else @firebird_type
116
+ end
117
+ end
118
+
119
+ def simplified_type(field_type)
120
+ if field_type == 'TIMESTAMP'
121
+ :datetime
122
+ else
123
+ super
124
+ end
125
+ end
126
+ end
127
+
128
+ # The Firebird adapter relies on the FireRuby[http://rubyforge.org/projects/fireruby/]
129
+ # extension, version 0.4.0 or later (available as a gem or from
130
+ # RubyForge[http://rubyforge.org/projects/fireruby/]). FireRuby works with
131
+ # Firebird 1.5.x on Linux, OS X and Win32 platforms.
132
+ #
133
+ # == Usage Notes
134
+ #
135
+ # === Sequence (Generator) Names
136
+ # The Firebird adapter supports the same approach adopted for the Oracle
137
+ # adapter. See ActiveRecord::Base#set_sequence_name for more details.
138
+ #
139
+ # Note that in general there is no need to create a <tt>BEFORE INSERT</tt>
140
+ # trigger corresponding to a Firebird sequence generator when using
141
+ # ActiveRecord. In other words, you don't have to try to make Firebird
142
+ # simulate an <tt>AUTO_INCREMENT</tt> or +IDENTITY+ column. When saving a
143
+ # new record, ActiveRecord pre-fetches the next sequence value for the table
144
+ # and explicitly includes it in the +INSERT+ statement. (Pre-fetching the
145
+ # next primary key value is the only reliable method for the Firebird
146
+ # adapter to report back the +id+ after a successful insert.)
147
+ #
148
+ # === BOOLEAN Domain
149
+ # Firebird 1.5 does not provide a native +BOOLEAN+ type. But you can easily
150
+ # define a +BOOLEAN+ _domain_ for this purpose, e.g.:
151
+ #
152
+ # CREATE DOMAIN D_BOOLEAN AS SMALLINT CHECK (VALUE IN (0, 1) OR VALUE IS NULL);
153
+ #
154
+ # When the Firebird adapter encounters a column that is based on a domain
155
+ # that includes "BOOLEAN" in the domain name, it will attempt to treat
156
+ # the column as a +BOOLEAN+.
157
+ #
158
+ # By default, the Firebird adapter will assume that the BOOLEAN domain is
159
+ # defined as above. This can be modified if needed. For example, if you
160
+ # have a legacy schema with the following +BOOLEAN+ domain defined:
161
+ #
162
+ # CREATE DOMAIN BOOLEAN AS CHAR(1) CHECK (VALUE IN ('T', 'F'));
163
+ #
164
+ # ...you can add the following line to your <tt>environment.rb</tt> file:
165
+ #
166
+ # ActiveRecord::ConnectionAdapters::FirebirdAdapter.boolean_domain = { :true => 'T', :false => 'F' }
167
+ #
168
+ # === BLOB Elements
169
+ # The Firebird adapter currently provides only limited support for +BLOB+
170
+ # columns. You cannot currently retrieve or insert a +BLOB+ as an IO stream.
171
+ # When selecting a +BLOB+, the entire element is converted into a String.
172
+ # When inserting or updating a +BLOB+, the entire value is included in-line
173
+ # in the SQL statement, limiting you to values <= 32KB in size.
174
+ #
175
+ # === Column Name Case Semantics
176
+ # Firebird and ActiveRecord have somewhat conflicting case semantics for
177
+ # column names.
178
+ #
179
+ # [*Firebird*]
180
+ # The standard practice is to use unquoted column names, which can be
181
+ # thought of as case-insensitive. (In fact, Firebird converts them to
182
+ # uppercase.) Quoted column names (not typically used) are case-sensitive.
183
+ # [*ActiveRecord*]
184
+ # Attribute accessors corresponding to column names are case-sensitive.
185
+ # The defaults for primary key and inheritance columns are lowercase, and
186
+ # in general, people use lowercase attribute names.
187
+ #
188
+ # In order to map between the differing semantics in a way that conforms
189
+ # to common usage for both Firebird and ActiveRecord, uppercase column names
190
+ # in Firebird are converted to lowercase attribute names in ActiveRecord,
191
+ # and vice-versa. Mixed-case column names retain their case in both
192
+ # directions. Lowercase (quoted) Firebird column names are not supported.
193
+ # This is similar to the solutions adopted by other adapters.
194
+ #
195
+ # In general, the best approach is to use unqouted (case-insensitive) column
196
+ # names in your Firebird DDL (or if you must quote, use uppercase column
197
+ # names). These will correspond to lowercase attributes in ActiveRecord.
198
+ #
199
+ # For example, a Firebird table based on the following DDL:
200
+ #
201
+ # CREATE TABLE products (
202
+ # id BIGINT NOT NULL PRIMARY KEY,
203
+ # "TYPE" VARCHAR(50),
204
+ # name VARCHAR(255) );
205
+ #
206
+ # ...will correspond to an ActiveRecord model class called +Product+ with
207
+ # the following attributes: +id+, +type+, +name+.
208
+ #
209
+ # ==== Quoting <tt>"TYPE"</tt> and other Firebird reserved words:
210
+ # In ActiveRecord, the default inheritance column name is +type+. The word
211
+ # _type_ is a Firebird reserved word, so it must be quoted in any Firebird
212
+ # SQL statements. Because of the case mapping described above, you should
213
+ # always reference this column using quoted-uppercase syntax
214
+ # (<tt>"TYPE"</tt>) within Firebird DDL or other SQL statements (as in the
215
+ # example above). This holds true for any other Firebird reserved words used
216
+ # as column names as well.
217
+ #
218
+ # === Migrations
219
+ # The Firebird Adapter now supports Migrations.
220
+ #
221
+ # ==== Create/Drop Table and Sequence Generators
222
+ # Creating or dropping a table will automatically create/drop a
223
+ # correpsonding sequence generator, using the default naming convension.
224
+ # You can specify a different name using the <tt>:sequence</tt> option; no
225
+ # generator is created if <tt>:sequence</tt> is set to +false+.
226
+ #
227
+ # ==== Rename Table
228
+ # The Firebird #rename_table Migration should be used with caution.
229
+ # Firebird 1.5 lacks built-in support for this feature, so it is
230
+ # implemented by making a copy of the original table (including column
231
+ # definitions, indexes and data records), and then dropping the original
232
+ # table. Constraints and Triggers are _not_ properly copied, so avoid
233
+ # this method if your original table includes constraints (other than
234
+ # the primary key) or triggers. (Consider manually copying your table
235
+ # or using a view instead.)
236
+ #
237
+ # == Connection Options
238
+ # The following options are supported by the Firebird adapter. None of the
239
+ # options have default values.
240
+ #
241
+ # <tt>:database</tt>::
242
+ # <i>Required option.</i> Specifies one of: (i) a Firebird database alias;
243
+ # (ii) the full path of a database file; _or_ (iii) a full Firebird
244
+ # connection string. <i>Do not specify <tt>:host</tt>, <tt>:service</tt>
245
+ # or <tt>:port</tt> as separate options when using a full connection
246
+ # string.</i>
247
+ # <tt>:host</tt>::
248
+ # Set to <tt>"remote.host.name"</tt> for remote database connections.
249
+ # May be omitted for local connections if a full database path is
250
+ # specified for <tt>:database</tt>. Some platforms require a value of
251
+ # <tt>"localhost"</tt> for local connections when using a Firebird
252
+ # database _alias_.
253
+ # <tt>:service</tt>::
254
+ # Specifies a service name for the connection. Only used if <tt>:host</tt>
255
+ # is provided. Required when connecting to a non-standard service.
256
+ # <tt>:port</tt>::
257
+ # Specifies the connection port. Only used if <tt>:host</tt> is provided
258
+ # and <tt>:service</tt> is not. Required when connecting to a non-standard
259
+ # port and <tt>:service</tt> is not defined.
260
+ # <tt>:username</tt>::
261
+ # Specifies the database user. May be omitted or set to +nil+ (together
262
+ # with <tt>:password</tt>) to use the underlying operating system user
263
+ # credentials on supported platforms.
264
+ # <tt>:password</tt>::
265
+ # Specifies the database password. Must be provided if <tt>:username</tt>
266
+ # is explicitly specified; should be omitted if OS user credentials are
267
+ # are being used.
268
+ # <tt>:charset</tt>::
269
+ # Specifies the character set to be used by the connection. Refer to
270
+ # Firebird documentation for valid options.
271
+ class FirebirdAdapter < AbstractAdapter
272
+ TEMP_COLUMN_NAME = 'AR$TEMP_COLUMN'
273
+
274
+ @@boolean_domain = { :name => "d_boolean", :type => "smallint", :true => 1, :false => 0 }
275
+ cattr_accessor :boolean_domain
276
+
277
+ def initialize(connection, logger, connection_params = nil)
278
+ super(connection, logger)
279
+ @connection_params = connection_params
280
+ end
281
+
282
+ def adapter_name # :nodoc:
283
+ 'Firebird'
284
+ end
285
+
286
+ def supports_migrations? # :nodoc:
287
+ true
288
+ end
289
+
290
+ def native_database_types # :nodoc:
291
+ {
292
+ :primary_key => "BIGINT NOT NULL PRIMARY KEY",
293
+ :string => { :name => "varchar", :limit => 255 },
294
+ :text => { :name => "blob sub_type text" },
295
+ :integer => { :name => "bigint" },
296
+ :decimal => { :name => "decimal" },
297
+ :numeric => { :name => "numeric" },
298
+ :float => { :name => "float" },
299
+ :datetime => { :name => "timestamp" },
300
+ :timestamp => { :name => "timestamp" },
301
+ :time => { :name => "time" },
302
+ :date => { :name => "date" },
303
+ :binary => { :name => "blob sub_type 0" },
304
+ :boolean => boolean_domain
305
+ }
306
+ end
307
+
308
+ # Returns true for Firebird adapter (since Firebird requires primary key
309
+ # values to be pre-fetched before insert). See also #next_sequence_value.
310
+ def prefetch_primary_key?(table_name = nil)
311
+ true
312
+ end
313
+
314
+ def default_sequence_name(table_name, primary_key = nil) # :nodoc:
315
+ "#{table_name}_seq"
316
+ end
317
+
318
+
319
+ # QUOTING ==================================================
320
+
321
+ def quote(value, column = nil) # :nodoc:
322
+ if [Time, DateTime].include?(value.class)
323
+ "CAST('#{value.strftime("%Y-%m-%d %H:%M:%S")}' AS TIMESTAMP)"
324
+ else
325
+ super
326
+ end
327
+ end
328
+
329
+ def quote_string(string) # :nodoc:
330
+ string.gsub(/'/, "''")
331
+ end
332
+
333
+ def quote_column_name(column_name) # :nodoc:
334
+ %Q("#{ar_to_fb_case(column_name.to_s)}")
335
+ end
336
+
337
+ def quoted_true # :nodoc:
338
+ quote(boolean_domain[:true])
339
+ end
340
+
341
+ def quoted_false # :nodoc:
342
+ quote(boolean_domain[:false])
343
+ end
344
+
345
+
346
+ # CONNECTION MANAGEMENT ====================================
347
+
348
+ def active? # :nodoc:
349
+ not @connection.closed?
350
+ end
351
+
352
+ def disconnect! # :nodoc:
353
+ @connection.close rescue nil
354
+ end
355
+
356
+ def reconnect! # :nodoc:
357
+ disconnect!
358
+ @connection = @connection.database.connect(*@connection_params)
359
+ end
360
+
361
+
362
+ # DATABASE STATEMENTS ======================================
363
+
364
+ def select_all(sql, name = nil) # :nodoc:
365
+ select(sql, name)
366
+ end
367
+
368
+ def select_one(sql, name = nil) # :nodoc:
369
+ select(sql, name).first
370
+ end
371
+
372
+ def execute(sql, name = nil, &block) # :nodoc:
373
+ log(sql, name) do
374
+ if @transaction
375
+ @connection.execute(sql, @transaction, &block)
376
+ else
377
+ @connection.execute_immediate(sql, &block)
378
+ end
379
+ end
380
+ end
381
+
382
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) # :nodoc:
383
+ execute(sql, name)
384
+ id_value
385
+ end
386
+
387
+ alias_method :update, :execute
388
+ alias_method :delete, :execute
389
+
390
+ def begin_db_transaction() # :nodoc:
391
+ @transaction = @connection.start_transaction
392
+ end
393
+
394
+ def commit_db_transaction() # :nodoc:
395
+ @transaction.commit
396
+ ensure
397
+ @transaction = nil
398
+ end
399
+
400
+ def rollback_db_transaction() # :nodoc:
401
+ @transaction.rollback
402
+ ensure
403
+ @transaction = nil
404
+ end
405
+
406
+ def add_limit_offset!(sql, options) # :nodoc:
407
+ if options[:limit]
408
+ limit_string = "FIRST #{options[:limit]}"
409
+ limit_string << " SKIP #{options[:offset]}" if options[:offset]
410
+ sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ')
411
+ end
412
+ end
413
+
414
+ # Returns the next sequence value from a sequence generator. Not generally
415
+ # called directly; used by ActiveRecord to get the next primary key value
416
+ # when inserting a new database record (see #prefetch_primary_key?).
417
+ def next_sequence_value(sequence_name)
418
+ FireRuby::Generator.new(sequence_name, @connection).next(1)
419
+ end
420
+
421
+
422
+ # SCHEMA STATEMENTS ========================================
423
+
424
+ def current_database # :nodoc:
425
+ file = @connection.database.file.split(':').last
426
+ File.basename(file, '.*')
427
+ end
428
+
429
+ def recreate_database! # :nodoc:
430
+ sql = "SELECT rdb$character_set_name FROM rdb$database"
431
+ charset = execute(sql).to_a.first[0].rstrip
432
+ disconnect!
433
+ @connection.database.drop(*@connection_params)
434
+ FireRuby::Database.create(@connection.database.file,
435
+ @connection_params[0], @connection_params[1], 4096, charset)
436
+ end
437
+
438
+ def tables(name = nil) # :nodoc:
439
+ sql = "SELECT rdb$relation_name FROM rdb$relations WHERE rdb$system_flag = 0"
440
+ execute(sql, name).collect { |row| row[0].rstrip.downcase }
441
+ end
442
+
443
+ def indexes(table_name, name = nil) # :nodoc:
444
+ index_metadata(table_name, false, name).inject([]) do |indexes, row|
445
+ if indexes.empty? or indexes.last.name != row[0]
446
+ indexes << IndexDefinition.new(table_name, row[0].rstrip.downcase, row[1] == 1, [])
447
+ end
448
+ indexes.last.columns << row[2].rstrip.downcase
449
+ indexes
450
+ end
451
+ end
452
+
453
+ def columns(table_name, name = nil) # :nodoc:
454
+ sql = <<-end_sql
455
+ SELECT r.rdb$field_name, r.rdb$field_source, f.rdb$field_type, f.rdb$field_sub_type,
456
+ f.rdb$field_length, f.rdb$field_precision, f.rdb$field_scale,
457
+ COALESCE(r.rdb$default_source, f.rdb$default_source) rdb$default_source,
458
+ COALESCE(r.rdb$null_flag, f.rdb$null_flag) rdb$null_flag
459
+ FROM rdb$relation_fields r
460
+ JOIN rdb$fields f ON r.rdb$field_source = f.rdb$field_name
461
+ WHERE r.rdb$relation_name = '#{table_name.to_s.upcase}'
462
+ ORDER BY r.rdb$field_position
463
+ end_sql
464
+ execute(sql, name).collect do |field|
465
+ field_values = field.values.collect do |value|
466
+ case value
467
+ when String then value.rstrip
468
+ when FireRuby::Blob then value.to_s
469
+ else value
470
+ end
471
+ end
472
+ FirebirdColumn.new(*field_values)
473
+ end
474
+ end
475
+
476
+ def create_table(name, options = {}) # :nodoc:
477
+ begin
478
+ super
479
+ rescue StatementInvalid
480
+ raise unless non_existent_domain_error?
481
+ create_boolean_domain
482
+ super
483
+ end
484
+ unless options[:id] == false or options[:sequence] == false
485
+ sequence_name = options[:sequence] || default_sequence_name(name)
486
+ create_sequence(sequence_name)
487
+ end
488
+ end
489
+
490
+ def drop_table(name, options = {}) # :nodoc:
491
+ super(name)
492
+ unless options[:sequence] == false
493
+ sequence_name = options[:sequence] || default_sequence_name(name)
494
+ drop_sequence(sequence_name) if sequence_exists?(sequence_name)
495
+ end
496
+ end
497
+
498
+ def add_column(table_name, column_name, type, options = {}) # :nodoc:
499
+ super
500
+ rescue StatementInvalid
501
+ raise unless non_existent_domain_error?
502
+ create_boolean_domain
503
+ super
504
+ end
505
+
506
+ def change_column(table_name, column_name, type, options = {}) # :nodoc:
507
+ change_column_type(table_name, column_name, type, options)
508
+ change_column_position(table_name, column_name, options[:position]) if options.include?(:position)
509
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
510
+ end
511
+
512
+ def change_column_default(table_name, column_name, default) # :nodoc:
513
+ table_name = table_name.to_s.upcase
514
+ sql = <<-end_sql
515
+ UPDATE rdb$relation_fields f1
516
+ SET f1.rdb$default_source =
517
+ (SELECT f2.rdb$default_source FROM rdb$relation_fields f2
518
+ WHERE f2.rdb$relation_name = '#{table_name}'
519
+ AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}'),
520
+ f1.rdb$default_value =
521
+ (SELECT f2.rdb$default_value FROM rdb$relation_fields f2
522
+ WHERE f2.rdb$relation_name = '#{table_name}'
523
+ AND f2.rdb$field_name = '#{TEMP_COLUMN_NAME}')
524
+ WHERE f1.rdb$relation_name = '#{table_name}'
525
+ AND f1.rdb$field_name = '#{ar_to_fb_case(column_name.to_s)}'
526
+ end_sql
527
+ transaction do
528
+ add_column(table_name, TEMP_COLUMN_NAME, :string, :default => default)
529
+ execute sql
530
+ remove_column(table_name, TEMP_COLUMN_NAME)
531
+ end
532
+ end
533
+
534
+ def rename_column(table_name, column_name, new_column_name) # :nodoc:
535
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TO #{new_column_name}"
536
+ end
537
+
538
+ def remove_index(table_name, options) #:nodoc:
539
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
540
+ end
541
+
542
+ def rename_table(name, new_name) # :nodoc:
543
+ if table_has_constraints_or_dependencies?(name)
544
+ raise ActiveRecordError,
545
+ "Table #{name} includes constraints or dependencies that are not supported by " <<
546
+ "the Firebird rename_table migration. Try explicitly removing the constraints/" <<
547
+ "dependencies first, or manually renaming the table."
548
+ end
549
+
550
+ transaction do
551
+ copy_table(name, new_name)
552
+ copy_table_indexes(name, new_name)
553
+ end
554
+ begin
555
+ copy_table_data(name, new_name)
556
+ copy_sequence_value(name, new_name)
557
+ rescue
558
+ drop_table(new_name)
559
+ raise
560
+ end
561
+ drop_table(name)
562
+ end
563
+
564
+ def dump_schema_information # :nodoc:
565
+ super << ";\n"
566
+ end
567
+
568
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) # :nodoc:
569
+ case type
570
+ when :integer then integer_sql_type(limit)
571
+ when :float then float_sql_type(limit)
572
+ when :string then super(type, limit, precision, scale)
573
+ else super(type, limit, precision, scale)
574
+ end
575
+ end
576
+
577
+ private
578
+ def integer_sql_type(limit)
579
+ case limit
580
+ when (1..2) then 'smallint'
581
+ when (3..4) then 'integer'
582
+ else 'bigint'
583
+ end
584
+ end
585
+
586
+ def float_sql_type(limit)
587
+ limit.to_i <= 4 ? 'float' : 'double precision'
588
+ end
589
+
590
+ def select(sql, name = nil)
591
+ execute(sql, name).collect do |row|
592
+ hashed_row = {}
593
+ row.each do |column, value|
594
+ value = value.to_s if FireRuby::Blob === value
595
+ hashed_row[fb_to_ar_case(column)] = value
596
+ end
597
+ hashed_row
598
+ end
599
+ end
600
+
601
+ def primary_key(table_name)
602
+ if pk_row = index_metadata(table_name, true).to_a.first
603
+ pk_row[2].rstrip.downcase
604
+ end
605
+ end
606
+
607
+ def index_metadata(table_name, pk, name = nil)
608
+ sql = <<-end_sql
609
+ SELECT i.rdb$index_name, i.rdb$unique_flag, s.rdb$field_name
610
+ FROM rdb$indices i
611
+ JOIN rdb$index_segments s ON i.rdb$index_name = s.rdb$index_name
612
+ LEFT JOIN rdb$relation_constraints c ON i.rdb$index_name = c.rdb$index_name
613
+ WHERE i.rdb$relation_name = '#{table_name.to_s.upcase}'
614
+ end_sql
615
+ if pk
616
+ sql << "AND c.rdb$constraint_type = 'PRIMARY KEY'\n"
617
+ else
618
+ sql << "AND (c.rdb$constraint_type IS NULL OR c.rdb$constraint_type != 'PRIMARY KEY')\n"
619
+ end
620
+ sql << "ORDER BY i.rdb$index_name, s.rdb$field_position\n"
621
+ execute sql, name
622
+ end
623
+
624
+ def change_column_type(table_name, column_name, type, options = {})
625
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type_to_sql(type, options[:limit])}"
626
+ execute sql
627
+ rescue StatementInvalid
628
+ raise unless non_existent_domain_error?
629
+ create_boolean_domain
630
+ execute sql
631
+ end
632
+
633
+ def change_column_position(table_name, column_name, position)
634
+ execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} POSITION #{position}"
635
+ end
636
+
637
+ def copy_table(from, to)
638
+ table_opts = {}
639
+ if pk = primary_key(from)
640
+ table_opts[:primary_key] = pk
641
+ else
642
+ table_opts[:id] = false
643
+ end
644
+ create_table(to, table_opts) do |table|
645
+ from_columns = columns(from).reject { |col| col.name == table_opts[:primary_key] }
646
+ from_columns.each do |column|
647
+ col_opts = [:limit, :default, :null].inject({}) { |opts, opt| opts.merge(opt => column.send(opt)) }
648
+ table.column column.name, column.type, col_opts
649
+ end
650
+ end
651
+ end
652
+
653
+ def copy_table_indexes(from, to)
654
+ indexes(from).each do |index|
655
+ unless index.name[from.to_s]
656
+ raise ActiveRecordError,
657
+ "Cannot rename index #{index.name}, because the index name does not include " <<
658
+ "the original table name (#{from}). Try explicitly removing the index on the " <<
659
+ "original table and re-adding it on the new (renamed) table."
660
+ end
661
+ options = {}
662
+ options[:name] = index.name.gsub(from.to_s, to.to_s)
663
+ options[:unique] = index.unique
664
+ add_index(to, index.columns, options)
665
+ end
666
+ end
667
+
668
+ def copy_table_data(from, to)
669
+ execute "INSERT INTO #{to} SELECT * FROM #{from}", "Copy #{from} data to #{to}"
670
+ end
671
+
672
+ def copy_sequence_value(from, to)
673
+ sequence_value = FireRuby::Generator.new(default_sequence_name(from), @connection).last
674
+ execute "SET GENERATOR #{default_sequence_name(to)} TO #{sequence_value}"
675
+ end
676
+
677
+ def sequence_exists?(sequence_name)
678
+ FireRuby::Generator.exists?(sequence_name, @connection)
679
+ end
680
+
681
+ def create_sequence(sequence_name)
682
+ FireRuby::Generator.create(sequence_name.to_s, @connection)
683
+ end
684
+
685
+ def drop_sequence(sequence_name)
686
+ FireRuby::Generator.new(sequence_name.to_s, @connection).drop
687
+ end
688
+
689
+ def create_boolean_domain
690
+ sql = <<-end_sql
691
+ CREATE DOMAIN #{boolean_domain[:name]} AS #{boolean_domain[:type]}
692
+ CHECK (VALUE IN (#{quoted_true}, #{quoted_false}) OR VALUE IS NULL)
693
+ end_sql
694
+ execute sql rescue nil
695
+ end
696
+
697
+ def table_has_constraints_or_dependencies?(table_name)
698
+ table_name = table_name.to_s.upcase
699
+ sql = <<-end_sql
700
+ SELECT 1 FROM rdb$relation_constraints
701
+ WHERE rdb$relation_name = '#{table_name}'
702
+ AND rdb$constraint_type IN ('UNIQUE', 'FOREIGN KEY', 'CHECK')
703
+ UNION
704
+ SELECT 1 FROM rdb$dependencies
705
+ WHERE rdb$depended_on_name = '#{table_name}'
706
+ AND rdb$depended_on_type = 0
707
+ end_sql
708
+ !select(sql).empty?
709
+ end
710
+
711
+ def non_existent_domain_error?
712
+ $!.message.include? FireRuby::NON_EXISTENT_DOMAIN_ERROR
713
+ end
714
+
715
+ # Maps uppercase Firebird column names to lowercase for ActiveRecord;
716
+ # mixed-case columns retain their original case.
717
+ def fb_to_ar_case(column_name)
718
+ column_name =~ /[[:lower:]]/ ? column_name : column_name.downcase
719
+ end
720
+
721
+ # Maps lowercase ActiveRecord column names to uppercase for Fierbird;
722
+ # mixed-case columns retain their original case.
723
+ def ar_to_fb_case(column_name)
724
+ column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase
725
+ end
726
+ end
727
+ end
728
+ end