activerecord 1.15.6 → 2.0.0

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

Potentially problematic release.


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

Files changed (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -1,861 +0,0 @@
1
- # Requires FrontBase Ruby bindings (gem install ruby-frontbase)
2
-
3
- require 'active_record/connection_adapters/abstract_adapter'
4
-
5
- FB_TRACE = false
6
-
7
- module ActiveRecord
8
-
9
- class Base
10
- class << self
11
- # Establishes a connection to the database that's used by all Active Record objects.
12
- def frontbase_connection(config) # :nodoc:
13
- # FrontBase only supports one unnamed sequence per table
14
- define_attr_method(:set_sequence_name, :sequence_name, &Proc.new {|*args| nil})
15
-
16
- config = config.symbolize_keys
17
- database = config[:database]
18
- port = config[:port]
19
- host = config[:host]
20
- username = config[:username]
21
- password = config[:password]
22
- dbpassword = config[:dbpassword]
23
- session_name = config[:session_name]
24
-
25
- dbpassword = '' if dbpassword.nil?
26
-
27
- # Turn off colorization since it makes tail/less output difficult
28
- self.colorize_logging = false
29
-
30
- require_library_or_gem 'frontbase' unless self.class.const_defined? :FBSQL_Connect
31
-
32
- # Check bindings version
33
- version = "0.0.0"
34
- version = FBSQL_Connect::FB_BINDINGS_VERSION if defined? FBSQL_Connect::FB_BINDINGS_VERSION
35
-
36
- if ActiveRecord::ConnectionAdapters::FrontBaseAdapter.compare_versions(version,"1.0.0") == -1
37
- raise AdapterNotFound,
38
- 'The FrontBase adapter requires ruby-frontbase version 1.0.0 or greater; you appear ' <<
39
- "to be running an older version (#{version}) -- please update ruby-frontbase (gem install ruby-frontbase)."
40
- end
41
- connection = FBSQL_Connect.connect(host, port, database, username, password, dbpassword, session_name)
42
- ConnectionAdapters::FrontBaseAdapter.new(connection, logger, [host, port, database, username, password, dbpassword, session_name], config)
43
- end
44
- end
45
- end
46
-
47
- module ConnectionAdapters
48
-
49
- # From EOF Documentation....
50
- # buffer should have space for EOUniqueBinaryKeyLength (12) bytes.
51
- # Assigns a world-wide unique ID made up of:
52
- # < Sequence [2], ProcessID [2] , Time [4], IP Addr [4] >
53
-
54
- class TwelveByteKey < String #:nodoc:
55
- @@mutex = Mutex.new
56
- @@sequence_number = rand(65536)
57
- @@key_cached_pid_component = nil
58
- @@key_cached_ip_component = nil
59
-
60
- def initialize(string = nil)
61
- # Generate a unique key
62
- if string.nil?
63
- new_key = replace('_' * 12)
64
-
65
- new_key[0..1] = self.class.key_sequence_component
66
- new_key[2..3] = self.class.key_pid_component
67
- new_key[4..7] = self.class.key_time_component
68
- new_key[8..11] = self.class.key_ip_component
69
- new_key
70
- else
71
- if string.size == 24
72
- string.gsub!(/[[:xdigit:]]{2}/) { |x| x.hex.chr }
73
- end
74
- raise "string is not 12 bytes long" unless string.size == 12
75
- super(string)
76
- end
77
- end
78
-
79
- def inspect
80
- unpack("H*").first.upcase
81
- end
82
-
83
- alias_method :to_s, :inspect
84
-
85
- private
86
-
87
- class << self
88
- def key_sequence_component
89
- seq = nil
90
- @@mutex.synchronize do
91
- seq = @@sequence_number
92
- @@sequence_number = (@@sequence_number + 1) % 65536
93
- end
94
-
95
- sequence_component = "__"
96
- sequence_component[0] = seq >> 8
97
- sequence_component[1] = seq
98
- sequence_component
99
- end
100
-
101
- def key_pid_component
102
- if @@key_cached_pid_component.nil?
103
- @@mutex.synchronize do
104
- pid = $$
105
- pid_component = "__"
106
- pid_component[0] = pid >> 8
107
- pid_component[1] = pid
108
- @@key_cached_pid_component = pid_component
109
- end
110
- end
111
- @@key_cached_pid_component
112
- end
113
-
114
- def key_time_component
115
- time = Time.new.to_i
116
- time_component = "____"
117
- time_component[0] = (time & 0xFF000000) >> 24
118
- time_component[1] = (time & 0x00FF0000) >> 16
119
- time_component[2] = (time & 0x0000FF00) >> 8
120
- time_component[3] = (time & 0x000000FF)
121
- time_component
122
- end
123
-
124
- def key_ip_component
125
- if @@key_cached_ip_component.nil?
126
- @@mutex.synchronize do
127
- old_lookup_flag = BasicSocket.do_not_reverse_lookup
128
- BasicSocket.do_not_reverse_lookup = true
129
- udpsocket = UDPSocket.new
130
- udpsocket.connect("17.112.152.32",1)
131
- ip_string = udpsocket.addr[3]
132
- BasicSocket.do_not_reverse_lookup = old_lookup_flag
133
- packed = Socket.pack_sockaddr_in(0,ip_string)
134
- addr_subset = packed[4..7]
135
- ip = addr_subset[0] << 24 | addr_subset[1] << 16 | addr_subset[2] << 8 | addr_subset[3]
136
- ip_component = "____"
137
- ip_component[0] = (ip & 0xFF000000) >> 24
138
- ip_component[1] = (ip & 0x00FF0000) >> 16
139
- ip_component[2] = (ip & 0x0000FF00) >> 8
140
- ip_component[3] = (ip & 0x000000FF)
141
- @@key_cached_ip_component = ip_component
142
- end
143
- end
144
- @@key_cached_ip_component
145
- end
146
- end
147
- end
148
-
149
- class FrontBaseColumn < Column #:nodoc:
150
- attr_reader :fb_autogen
151
-
152
- def initialize(base, name, type, typename, limit, precision, scale, default, nullable)
153
-
154
- @base = base
155
- @name = name
156
- @type = simplified_type(type,typename,limit)
157
- @limit = limit
158
- @precision = precision
159
- @scale = scale
160
- @default = default
161
- @null = nullable == "YES"
162
- @text = [:string, :text].include? @type
163
- @number = [:float, :integer, :decimal].include? @type
164
- @fb_autogen = false
165
-
166
- if @default
167
- @default.gsub!(/^'(.*)'$/,'\1') if @text
168
- @fb_autogen = @default.include?("SELECT UNIQUE FROM")
169
- case @type
170
- when :boolean
171
- @default = @default == "TRUE"
172
- when :binary
173
- if @default != "X''"
174
- buffer = ""
175
- @default.scan(/../) { |h| buffer << h.hex.chr }
176
- @default = buffer
177
- else
178
- @default = ""
179
- end
180
- else
181
- @default = type_cast(@default)
182
- end
183
- end
184
- end
185
-
186
- # Casts value (which is a String) to an appropriate instance.
187
- def type_cast(value)
188
- if type == :twelvebytekey
189
- ActiveRecord::ConnectionAdapters::TwelveByteKey.new(value)
190
- else
191
- super(value)
192
- end
193
- end
194
-
195
- def type_cast_code(var_name)
196
- if type == :twelvebytekey
197
- "ActiveRecord::ConnectionAdapters::TwelveByteKey.new(#{var_name})"
198
- else
199
- super(var_name)
200
- end
201
- end
202
-
203
- private
204
- def simplified_type(field_type, type_name,limit)
205
- ret_type = :string
206
- puts "typecode: [#{field_type}] [#{type_name}]" if FB_TRACE
207
-
208
- # 12 byte primary keys are a special case that Apple's EOF
209
- # used heavily. Optimize for this case
210
- if field_type == 11 && limit == 96
211
- ret_type = :twelvebytekey # BIT(96)
212
- else
213
- ret_type = case field_type
214
- when 1 then :boolean # BOOLEAN
215
- when 2 then :integer # INTEGER
216
- when 4 then :float # FLOAT
217
- when 10 then :string # CHARACTER VARYING
218
- when 11 then :bitfield # BIT
219
- when 13 then :date # DATE
220
- when 14 then :time # TIME
221
- when 16 then :timestamp # TIMESTAMP
222
- when 20 then :text # CLOB
223
- when 21 then :binary # BLOB
224
- when 22 then :integer # TINYINT
225
- else
226
- puts "ERROR: Unknown typecode: [#{field_type}] [#{type_name}]"
227
- end
228
- end
229
- puts "ret_type: #{ret_type.inspect}" if FB_TRACE
230
- ret_type
231
- end
232
- end
233
-
234
- class FrontBaseAdapter < AbstractAdapter
235
-
236
- class << self
237
- def compare_versions(v1, v2)
238
- v1_seg = v1.split(".")
239
- v2_seg = v2.split(".")
240
- 0.upto([v1_seg.length,v2_seg.length].min) do |i|
241
- step = (v1_seg[i].to_i <=> v2_seg[i].to_i)
242
- return step unless step == 0
243
- end
244
- return v1_seg.length <=> v2_seg.length
245
- end
246
- end
247
-
248
- def initialize(connection, logger, connection_options, config)
249
- super(connection, logger)
250
- @connection_options, @config = connection_options, config
251
- @transaction_mode = :pessimistic
252
-
253
- # Start out in auto-commit mode
254
- self.rollback_db_transaction
255
-
256
- # threaded_connections_test.rb will fail unless we set the session
257
- # to optimistic locking mode
258
- # set_pessimistic_transactions
259
- # execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC"
260
- end
261
-
262
- # Returns the human-readable name of the adapter. Use mixed case - one
263
- # can always use downcase if needed.
264
- def adapter_name #:nodoc:
265
- 'FrontBase'
266
- end
267
-
268
- # Does this adapter support migrations? Backend specific, as the
269
- # abstract adapter always returns +false+.
270
- def supports_migrations? #:nodoc:
271
- true
272
- end
273
-
274
- def native_database_types #:nodoc:
275
- {
276
- :primary_key => "INTEGER DEFAULT UNIQUE PRIMARY KEY",
277
- :string => { :name => "VARCHAR", :limit => 255 },
278
- :text => { :name => "CLOB" },
279
- :integer => { :name => "INTEGER" },
280
- :float => { :name => "FLOAT" },
281
- :decimal => { :name => "DECIMAL" },
282
- :datetime => { :name => "TIMESTAMP" },
283
- :timestamp => { :name => "TIMESTAMP" },
284
- :time => { :name => "TIME" },
285
- :date => { :name => "DATE" },
286
- :binary => { :name => "BLOB" },
287
- :boolean => { :name => "BOOLEAN" },
288
- :twelvebytekey => { :name => "BYTE", :limit => 12}
289
- }
290
- end
291
-
292
-
293
- # QUOTING ==================================================
294
-
295
- # Quotes the column value to help prevent
296
- # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
297
- def quote(value, column = nil)
298
- return value.quoted_id if value.respond_to?(:quoted_id)
299
-
300
- retvalue = "<INVALID>"
301
-
302
- puts "quote(#{value.inspect}(#{value.class}),#{column.type.inspect})" if FB_TRACE
303
- # If a column was passed in, use column type information
304
- unless value.nil?
305
- if column
306
- retvalue = case column.type
307
- when :string
308
- if value.kind_of?(String)
309
- "'#{quote_string(value.to_s)}'" # ' (for ruby-mode)
310
- else
311
- "'#{quote_string(value.to_yaml)}'"
312
- end
313
- when :integer
314
- if value.kind_of?(TrueClass)
315
- '1'
316
- elsif value.kind_of?(FalseClass)
317
- '0'
318
- else
319
- value.to_i.to_s
320
- end
321
- when :float
322
- value.to_f.to_s
323
- when :decimal
324
- value.to_d.to_s("F")
325
- when :datetime, :timestamp
326
- "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
327
- when :time
328
- "TIME '#{value.strftime("%H:%M:%S")}'"
329
- when :date
330
- "DATE '#{value.strftime("%Y-%m-%d")}'"
331
- when :twelvebytekey
332
- value = value.to_s.unpack("H*").first unless value.kind_of?(TwelveByteKey)
333
- "X'#{value.to_s}'"
334
- when :boolean
335
- value = quoted_true if value.kind_of?(TrueClass)
336
- value = quoted_false if value.kind_of?(FalseClass)
337
- value
338
- when :binary
339
- blob_handle = @connection.create_blob(value.to_s)
340
- puts "SQL -> Insert #{value.to_s.length} byte blob as #{retvalue}" if FB_TRACE
341
- blob_handle.handle
342
- when :text
343
- if value.kind_of?(String)
344
- clobdata = value.to_s # ' (for ruby-mode)
345
- else
346
- clobdata = value.to_yaml
347
- end
348
- clob_handle = @connection.create_clob(clobdata)
349
- puts "SQL -> Insert #{value.to_s.length} byte clob as #{retvalue}" if FB_TRACE
350
- clob_handle.handle
351
- else
352
- raise "*** UNKNOWN TYPE: #{column.type.inspect}"
353
- end # case
354
- # Since we don't have column type info, make a best guess based
355
- # on the Ruby class of the value
356
- else
357
- retvalue = case value
358
- when ActiveRecord::ConnectionAdapters::TwelveByteKey
359
- s = value.unpack("H*").first
360
- "X'#{s}'"
361
- when String
362
- if column && column.type == :binary
363
- s = value.unpack("H*").first
364
- "X'#{s}'"
365
- elsif column && [:integer, :float, :decimal].include?(column.type)
366
- value.to_s
367
- else
368
- "'#{quote_string(value)}'" # ' (for ruby-mode)
369
- end
370
- when NilClass
371
- "NULL"
372
- when TrueClass
373
- (column && column.type == :integer ? '1' : quoted_true)
374
- when FalseClass
375
- (column && column.type == :integer ? '0' : quoted_false)
376
- when Float, Fixnum, Bignum, BigDecimal
377
- value.to_s
378
- when Time, Date, DateTime
379
- if column
380
- case column.type
381
- when :date
382
- "DATE '#{value.strftime("%Y-%m-%d")}'"
383
- when :time
384
- "TIME '#{value.strftime("%H:%M:%S")}'"
385
- when :timestamp
386
- "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
387
- else
388
- raise NotImplementedError, "Unknown column type!"
389
- end # case
390
- else # Column wasn't passed in, so try to guess the right type
391
- if value.kind_of? Date
392
- "DATE '#{value.strftime("%Y-%m-%d")}'"
393
- else
394
- if [:hour, :min, :sec].all? {|part| value.send(:part).zero? }
395
- "TIME '#{value.strftime("%H:%M:%S")}'"
396
- else
397
- "TIMESTAMP '#{quoted_date(value)}'"
398
- end
399
- end
400
- end #if column
401
- else
402
- "'#{quote_string(value.to_yaml)}'"
403
- end #case
404
- end
405
- else
406
- retvalue = "NULL"
407
- end
408
-
409
- retvalue
410
- end # def
411
-
412
- # Quotes a string, escaping any ' (single quote) characters.
413
- def quote_string(s)
414
- s.gsub(/'/, "''") # ' (for ruby-mode)
415
- end
416
-
417
- def quote_column_name(name) #:nodoc:
418
- %( "#{name}" )
419
- end
420
-
421
- def quoted_true
422
- "true"
423
- end
424
-
425
- def quoted_false
426
- "false"
427
- end
428
-
429
-
430
- # CONNECTION MANAGEMENT ====================================
431
-
432
- def active?
433
- true if @connection.status == 1
434
- rescue => e
435
- false
436
- end
437
-
438
- def reconnect!
439
- @connection.close rescue nil
440
- @connection = FBSQL_Connect.connect(*@connection_options.first(7))
441
- end
442
-
443
- # Close this connection
444
- def disconnect!
445
- @connection.close rescue nil
446
- @active = false
447
- end
448
-
449
- # DATABASE STATEMENTS ======================================
450
-
451
- # Returns an array of record hashes with the column names as keys and
452
- # column values as values.
453
- def select_all(sql, name = nil) #:nodoc:
454
- fbsql = cleanup_fb_sql(sql)
455
- return_value = []
456
- fbresult = execute(sql, name)
457
- puts "select_all SQL -> #{fbsql}" if FB_TRACE
458
- columns = fbresult.columns
459
-
460
- fbresult.each do |row|
461
- puts "SQL <- #{row.inspect}" if FB_TRACE
462
- hashed_row = {}
463
- colnum = 0
464
- row.each do |col|
465
- hashed_row[columns[colnum]] = col
466
- if col.kind_of?(FBSQL_LOB)
467
- hashed_row[columns[colnum]] = col.read
468
- end
469
- colnum += 1
470
- end
471
- puts "raw row: #{hashed_row.inspect}" if FB_TRACE
472
- return_value << hashed_row
473
- end
474
- return_value
475
- end
476
-
477
- def select_one(sql, name = nil) #:nodoc:
478
- fbsql = cleanup_fb_sql(sql)
479
- return_value = []
480
- fbresult = execute(fbsql, name)
481
- puts "SQL -> #{fbsql}" if FB_TRACE
482
- columns = fbresult.columns
483
-
484
- fbresult.each do |row|
485
- puts "SQL <- #{row.inspect}" if FB_TRACE
486
- hashed_row = {}
487
- colnum = 0
488
- row.each do |col|
489
- hashed_row[columns[colnum]] = col
490
- if col.kind_of?(FBSQL_LOB)
491
- hashed_row[columns[colnum]] = col.read
492
- end
493
- colnum += 1
494
- end
495
- return_value << hashed_row
496
- break
497
- end
498
- fbresult.clear
499
- return_value.first
500
- end
501
-
502
- def query(sql, name = nil) #:nodoc:
503
- fbsql = cleanup_fb_sql(sql)
504
- puts "SQL(query) -> #{fbsql}" if FB_TRACE
505
- log(fbsql, name) { @connection.query(fbsql) }
506
- rescue => e
507
- puts "FB Exception: #{e.inspect}" if FB_TRACE
508
- raise e
509
- end
510
-
511
- def execute(sql, name = nil) #:nodoc:
512
- fbsql = cleanup_fb_sql(sql)
513
- puts "SQL(execute) -> #{fbsql}" if FB_TRACE
514
- log(fbsql, name) { @connection.query(fbsql) }
515
- rescue ActiveRecord::StatementInvalid => e
516
- if e.message.scan(/Table name - \w* - exists/).empty?
517
- puts "FB Exception: #{e.inspect}" if FB_TRACE
518
- raise e
519
- end
520
- end
521
-
522
- # Returns the last auto-generated ID from the affected table.
523
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
524
- puts "SQL -> #{sql.inspect}" if FB_TRACE
525
- execute(sql, name)
526
- id_value || pk
527
- end
528
-
529
- # Executes the update statement and returns the number of rows affected.
530
- def update(sql, name = nil) #:nodoc:
531
- puts "SQL -> #{sql.inspect}" if FB_TRACE
532
- execute(sql, name).num_rows
533
- end
534
-
535
- alias_method :delete, :update #:nodoc:
536
-
537
- def set_pessimistic_transactions
538
- if @transaction_mode == :optimistic
539
- execute "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC, READ WRITE"
540
- @transaction_mode = :pessimistic
541
- end
542
- end
543
-
544
- def set_optimistic_transactions
545
- if @transaction_mode == :pessimistic
546
- execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC"
547
- @transaction_mode = :optimistic
548
- end
549
- end
550
-
551
- def begin_db_transaction #:nodoc:
552
- execute "SET COMMIT FALSE" rescue nil
553
- end
554
-
555
- def commit_db_transaction #:nodoc:
556
- execute "COMMIT"
557
- ensure
558
- execute "SET COMMIT TRUE"
559
- end
560
-
561
- def rollback_db_transaction #:nodoc:
562
- execute "ROLLBACK"
563
- ensure
564
- execute "SET COMMIT TRUE"
565
- end
566
-
567
- def add_limit_offset!(sql, options) #:nodoc:
568
- if limit = options[:limit]
569
- offset = options[:offset] || 0
570
-
571
- # Here is the full syntax FrontBase supports:
572
- # (from gclem@frontbase.com)
573
- #
574
- # TOP <limit - unsigned integer>
575
- # TOP ( <offset expr>, <limit expr>)
576
-
577
- # "TOP 0" is not allowed, so we have
578
- # to use a cheap trick.
579
- if limit.zero?
580
- case sql
581
- when /WHERE/i
582
- sql.sub!(/WHERE/i, 'WHERE 0 = 1 AND ')
583
- when /ORDER\s+BY/i
584
- sql.sub!(/ORDER\s+BY/i, 'WHERE 0 = 1 ORDER BY')
585
- else
586
- sql << 'WHERE 0 = 1'
587
- end
588
- else
589
- if offset.zero?
590
- sql.replace sql.gsub("SELECT ","SELECT TOP #{limit} ")
591
- else
592
- sql.replace sql.gsub("SELECT ","SELECT TOP(#{offset},#{limit}) ")
593
- end
594
- end
595
- end
596
- end
597
-
598
- def prefetch_primary_key?(table_name = nil)
599
- true
600
- end
601
-
602
- # Returns the next sequence value from a sequence generator. Not generally
603
- # called directly; used by ActiveRecord to get the next primary key value
604
- # when inserting a new database record (see #prefetch_primary_key?).
605
- def next_sequence_value(sequence_name)
606
- unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value")
607
- # The test cases cannot handle a zero primary key
608
- unique.zero? ? select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") : unique
609
- end
610
-
611
- def default_sequence_name(table, column)
612
- table
613
- end
614
-
615
- # Set the sequence to the max value of the table's column.
616
- def reset_sequence!(table, column, sequence = nil)
617
- klasses = classes_for_table_name(table)
618
- klass = klasses.nil? ? nil : klasses.first
619
- pk = klass.primary_key unless klass.nil?
620
- if pk && klass.columns_hash[pk].type == :integer
621
- execute("SET UNIQUE FOR #{klass.table_name}(#{pk})")
622
- end
623
- end
624
-
625
- def classes_for_table_name(table)
626
- ActiveRecord::Base.send(:subclasses).select {|klass| klass.table_name == table}
627
- end
628
-
629
- def reset_pk_sequence!(table, pk = nil, sequence = nil)
630
- klasses = classes_for_table_name(table)
631
- klass = klasses.nil? ? nil : klasses.first
632
- pk = klass.primary_key unless klass.nil?
633
- if pk && klass.columns_hash[pk].type == :integer
634
- mpk = select_value("SELECT MAX(#{pk}) FROM #{table}")
635
- execute("SET UNIQUE FOR #{klass.table_name}(#{pk})")
636
- end
637
- end
638
-
639
- # SCHEMA STATEMENTS ========================================
640
-
641
- def structure_dump #:nodoc:
642
- select_all("SHOW TABLES").inject('') do |structure, table|
643
- structure << select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] << ";\n\n"
644
- end
645
- end
646
-
647
- def recreate_database(name) #:nodoc:
648
- drop_database(name)
649
- create_database(name)
650
- end
651
-
652
- def create_database(name) #:nodoc:
653
- execute "CREATE DATABASE #{name}"
654
- end
655
-
656
- def drop_database(name) #:nodoc:
657
- execute "DROP DATABASE #{name}"
658
- end
659
-
660
- def current_database
661
- select_value('SELECT "CATALOG_NAME" FROM INFORMATION_SCHEMA.CATALOGS').downcase
662
- end
663
-
664
- def tables(name = nil) #:nodoc:
665
- select_values(<<-SQL, nil)
666
- SELECT "TABLE_NAME"
667
- FROM INFORMATION_SCHEMA.TABLES AS T0,
668
- INFORMATION_SCHEMA.SCHEMATA AS T1
669
- WHERE T0.SCHEMA_PK = T1.SCHEMA_PK
670
- AND "SCHEMA_NAME" = CURRENT_SCHEMA
671
- SQL
672
- end
673
-
674
- def indexes(table_name, name = nil)#:nodoc:
675
- indexes = []
676
- current_index = nil
677
- sql = <<-SQL
678
- SELECT INDEX_NAME, T2.ORDINAL_POSITION, INDEX_COLUMN_COUNT, INDEX_TYPE,
679
- "COLUMN_NAME", IS_NULLABLE
680
- FROM INFORMATION_SCHEMA.TABLES AS T0,
681
- INFORMATION_SCHEMA.INDEXES AS T1,
682
- INFORMATION_SCHEMA.INDEX_COLUMN_USAGE AS T2,
683
- INFORMATION_SCHEMA.COLUMNS AS T3
684
- WHERE T0."TABLE_NAME" = '#{table_name}'
685
- AND INDEX_TYPE <> 0
686
- AND T0.TABLE_PK = T1.TABLE_PK
687
- AND T0.TABLE_PK = T2.TABLE_PK
688
- AND T0.TABLE_PK = T3.TABLE_PK
689
- AND T1.INDEXES_PK = T2.INDEX_PK
690
- AND T2.COLUMN_PK = T3.COLUMN_PK
691
- ORDER BY INDEX_NAME, T2.ORDINAL_POSITION
692
- SQL
693
-
694
- columns = []
695
- query(sql).each do |row|
696
- index_name = row[0]
697
- ord_position = row[1]
698
- ndx_colcount = row[2]
699
- index_type = row[3]
700
- column_name = row[4]
701
-
702
- is_unique = index_type == 1
703
-
704
- columns << column_name
705
- if ord_position == ndx_colcount
706
- indexes << IndexDefinition.new(table_name, index_name, is_unique , columns)
707
- columns = []
708
- end
709
- end
710
- indexes
711
- end
712
-
713
- def columns(table_name, name = nil)#:nodoc:
714
- sql = <<-SQL
715
- SELECT "TABLE_NAME", "COLUMN_NAME", ORDINAL_POSITION, IS_NULLABLE, COLUMN_DEFAULT,
716
- DATA_TYPE, DATA_TYPE_CODE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION,
717
- NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, DATETIME_PRECISION_LEADING
718
- FROM INFORMATION_SCHEMA.TABLES T0,
719
- INFORMATION_SCHEMA.COLUMNS T1,
720
- INFORMATION_SCHEMA.DATA_TYPE_DESCRIPTOR T3
721
- WHERE "TABLE_NAME" = '#{table_name}'
722
- AND T0.TABLE_PK = T1.TABLE_PK
723
- AND T0.TABLE_PK = T3.TABLE_OR_DOMAIN_PK
724
- AND T1.COLUMN_PK = T3.COLUMN_NAME_PK
725
- ORDER BY T1.ORDINAL_POSITION
726
- SQL
727
-
728
- rawresults = query(sql,name)
729
- columns = []
730
- rawresults.each do |field|
731
- args = [base = field[0],
732
- name = field[1],
733
- typecode = field[6],
734
- typestring = field[5],
735
- limit = field[7],
736
- precision = field[8],
737
- scale = field[9],
738
- default = field[4],
739
- nullable = field[3]]
740
- columns << FrontBaseColumn.new(*args)
741
- end
742
- columns
743
- end
744
-
745
- def create_table(name, options = {})
746
- table_definition = TableDefinition.new(self)
747
- table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
748
-
749
- yield table_definition
750
-
751
- if options[:force]
752
- drop_table(name) rescue nil
753
- end
754
-
755
- create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
756
- create_sql << "#{name} ("
757
- create_sql << table_definition.to_sql
758
- create_sql << ") #{options[:options]}"
759
- begin_db_transaction
760
- execute create_sql
761
- commit_db_transaction
762
- rescue ActiveRecord::StatementInvalid => e
763
- raise e unless e.message.match(/Table name - \w* - exists/)
764
- end
765
-
766
- def rename_table(name, new_name)
767
- columns = columns(name)
768
- pkcol = columns.find {|c| c.fb_autogen}
769
- execute "ALTER TABLE NAME #{name} TO #{new_name}"
770
- if pkcol
771
- change_column_default(new_name,pkcol.name,"UNIQUE")
772
- begin_db_transaction
773
- mpk = select_value("SELECT MAX(#{pkcol.name}) FROM #{new_name}")
774
- mpk = 0 if mpk.nil?
775
- execute "SET UNIQUE=#{mpk} FOR #{new_name}"
776
- commit_db_transaction
777
- end
778
- end
779
-
780
- # Drops a table from the database.
781
- def drop_table(name, options = {})
782
- execute "DROP TABLE #{name} RESTRICT"
783
- rescue ActiveRecord::StatementInvalid => e
784
- raise e unless e.message.match(/Referenced TABLE - \w* - does not exist/)
785
- end
786
-
787
- # Adds a new column to the named table.
788
- # See TableDefinition#column for details of the options you can use.
789
- def add_column(table_name, column_name, type, options = {})
790
- add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
791
- options[:type] = type
792
- add_column_options!(add_column_sql, options)
793
- execute(add_column_sql)
794
- end
795
-
796
- def add_column_options!(sql, options) #:nodoc:
797
- default_value = quote(options[:default], options[:column])
798
- if options_include_default?(options)
799
- if options[:type] == :boolean
800
- default_value = options[:default] == 0 ? quoted_false : quoted_true
801
- end
802
- sql << " DEFAULT #{default_value}"
803
- end
804
- sql << " NOT NULL" if options[:null] == false
805
- end
806
-
807
- # Removes the column from the table definition.
808
- # ===== Examples
809
- # remove_column(:suppliers, :qualification)
810
- def remove_column(table_name, column_name)
811
- execute "ALTER TABLE #{table_name} DROP #{column_name} RESTRICT"
812
- end
813
-
814
- def remove_index(table_name, options = {}) #:nodoc:
815
- if options[:unique]
816
- execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{quote_column_name(index_name(table_name, options))} RESTRICT"
817
- else
818
- execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
819
- end
820
- end
821
-
822
- def change_column_default(table_name, column_name, default) #:nodoc:
823
- execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{default}" if default != "NULL"
824
- end
825
-
826
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
827
- change_column_sql = %( ALTER COLUMN "#{table_name}"."#{column_name}" TO #{type_to_sql(type, options[:limit])} )
828
- execute(change_column_sql)
829
- change_column_sql = %( ALTER TABLE "#{table_name}" ALTER COLUMN "#{column_name}" )
830
-
831
- if options_include_default?(options)
832
- default_value = quote(options[:default], options[:column])
833
- if type == :boolean
834
- default_value = options[:default] == 0 ? quoted_false : quoted_true
835
- end
836
- change_column_sql << " SET DEFAULT #{default_value}"
837
- end
838
-
839
- execute(change_column_sql)
840
-
841
- # change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}"
842
- # add_column_options!(change_column_sql, options)
843
- # execute(change_column_sql)
844
- end
845
-
846
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
847
- execute %( ALTER COLUMN NAME "#{table_name}"."#{column_name}" TO "#{new_column_name}" )
848
- end
849
-
850
- private
851
-
852
- # Clean up sql to make it something FrontBase can digest
853
- def cleanup_fb_sql(sql) #:nodoc:
854
- # Turn non-standard != into standard <>
855
- cleansql = sql.gsub("!=", "<>")
856
- # Strip blank lines and comments
857
- cleansql.split("\n").reject { |line| line.match(/^(?:\s*|--.*)$/) } * "\n"
858
- end
859
- end
860
- end
861
- end