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,662 +0,0 @@
1
- # sybase_adaptor.rb
2
- # Author: John R. Sheets
3
- #
4
- # 01 Mar 2006: Initial version. Based on code from Will Sobel
5
- # (http://dev.rubyonrails.org/ticket/2030)
6
- #
7
- # 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns.
8
- #
9
- # 13 Apr 2006: Improved column type support to properly handle dates and user-defined
10
- # types; fixed quoting of integer columns.
11
- #
12
- # 05 Jan 2007: Updated for Rails 1.2 release:
13
- # restricted Fixtures#insert_fixtures monkeypatch to Sybase adapter;
14
- # removed SQL type precision from TEXT type to fix broken
15
- # ActiveRecordStore (jburks, #6878); refactored select() to use execute();
16
- # fixed leaked exception for no-op change_column(); removed verbose SQL dump
17
- # from columns(); added missing scale parameter in normalize_type().
18
-
19
- require 'active_record/connection_adapters/abstract_adapter'
20
-
21
- begin
22
- require 'sybsql'
23
-
24
- module ActiveRecord
25
- class Base
26
- # Establishes a connection to the database that's used by all Active Record objects
27
- def self.sybase_connection(config) # :nodoc:
28
- config = config.symbolize_keys
29
-
30
- username = config[:username] ? config[:username].to_s : 'sa'
31
- password = config[:password] ? config[:password].to_s : ''
32
-
33
- if config.has_key?(:host)
34
- host = config[:host]
35
- else
36
- raise ArgumentError, "No database server name specified. Missing argument: host."
37
- end
38
-
39
- if config.has_key?(:database)
40
- database = config[:database]
41
- else
42
- raise ArgumentError, "No database specified. Missing argument: database."
43
- end
44
-
45
- ConnectionAdapters::SybaseAdapter.new(
46
- SybSQL.new({'S' => host, 'U' => username, 'P' => password},
47
- ConnectionAdapters::SybaseAdapterContext), database, config, logger)
48
- end
49
- end # class Base
50
-
51
- module ConnectionAdapters
52
-
53
- # ActiveRecord connection adapter for Sybase Open Client bindings
54
- # (see http://raa.ruby-lang.org/project/sybase-ctlib).
55
- #
56
- # Options:
57
- #
58
- # * <tt>:host</tt> -- The name of the database server. No default, must be provided.
59
- # * <tt>:database</tt> -- The name of the database. No default, must be provided.
60
- # * <tt>:username</tt> -- Defaults to "sa".
61
- # * <tt>:password</tt> -- Defaults to empty string.
62
- #
63
- # Usage Notes:
64
- #
65
- # * The sybase-ctlib bindings do not support the DATE SQL column type; use DATETIME instead.
66
- # * Table and column names are limited to 30 chars in Sybase 12.5
67
- # * :binary columns not yet supported
68
- # * :boolean columns use the BIT SQL type, which does not allow nulls or
69
- # indexes. If a DEFAULT is not specified for ALTER TABLE commands, the
70
- # column will be declared with DEFAULT 0 (false).
71
- #
72
- # Migrations:
73
- #
74
- # The Sybase adapter supports migrations, but for ALTER TABLE commands to
75
- # work, the database must have the database option 'select into' set to
76
- # 'true' with sp_dboption (see below). The sp_helpdb command lists the current
77
- # options for all databases.
78
- #
79
- # 1> use mydb
80
- # 2> go
81
- # 1> master..sp_dboption mydb, "select into", true
82
- # 2> go
83
- # 1> checkpoint
84
- # 2> go
85
- class SybaseAdapter < AbstractAdapter # :nodoc:
86
- class ColumnWithIdentity < Column
87
- attr_reader :identity
88
-
89
- def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil)
90
- super(name, default, sql_type, nullable)
91
- @default, @identity, @primary = type_cast(default), identity, primary
92
- end
93
-
94
- def simplified_type(field_type)
95
- case field_type
96
- when /int|bigint|smallint|tinyint/i then :integer
97
- when /float|double|real/i then :float
98
- when /decimal|money|numeric|smallmoney/i then :decimal
99
- when /text|ntext/i then :text
100
- when /binary|image|varbinary/i then :binary
101
- when /char|nchar|nvarchar|string|varchar/i then :string
102
- when /bit/i then :boolean
103
- when /datetime|smalldatetime/i then :datetime
104
- else super
105
- end
106
- end
107
-
108
- def self.string_to_binary(value)
109
- "0x#{value.unpack("H*")[0]}"
110
- end
111
-
112
- def self.binary_to_string(value)
113
- # FIXME: sybase-ctlib uses separate sql method for binary columns.
114
- value
115
- end
116
- end # class ColumnWithIdentity
117
-
118
- # Sybase adapter
119
- def initialize(connection, database, config = {}, logger = nil)
120
- super(connection, logger)
121
- context = connection.context
122
- context.init(logger)
123
- @config = config
124
- @numconvert = config.has_key?(:numconvert) ? config[:numconvert] : true
125
- @limit = @offset = 0
126
- unless connection.sql_norow("USE #{database}")
127
- raise "Cannot USE #{database}"
128
- end
129
- end
130
-
131
- def native_database_types
132
- {
133
- :primary_key => "numeric(9,0) IDENTITY PRIMARY KEY",
134
- :string => { :name => "varchar", :limit => 255 },
135
- :text => { :name => "text" },
136
- :integer => { :name => "int" },
137
- :float => { :name => "float", :limit => 8 },
138
- :decimal => { :name => "decimal" },
139
- :datetime => { :name => "datetime" },
140
- :timestamp => { :name => "timestamp" },
141
- :time => { :name => "time" },
142
- :date => { :name => "datetime" },
143
- :binary => { :name => "image"},
144
- :boolean => { :name => "bit" }
145
- }
146
- end
147
-
148
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
149
- return super unless type.to_s == 'integer'
150
- if !limit.nil? && limit < 4
151
- 'smallint'
152
- else
153
- 'integer'
154
- end
155
- end
156
-
157
- def adapter_name
158
- 'Sybase'
159
- end
160
-
161
- def active?
162
- !(@connection.connection.nil? || @connection.connection_dead?)
163
- end
164
-
165
- def disconnect!
166
- @connection.close rescue nil
167
- end
168
-
169
- def reconnect!
170
- raise "Sybase Connection Adapter does not yet support reconnect!"
171
- # disconnect!
172
- # connect! # Not yet implemented
173
- end
174
-
175
- def table_alias_length
176
- 30
177
- end
178
-
179
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
180
- begin
181
- table_name = get_table_name(sql)
182
- col = get_identity_column(table_name)
183
- ii_enabled = false
184
-
185
- if col != nil
186
- if query_contains_identity_column(sql, col)
187
- begin
188
- enable_identity_insert(table_name, true)
189
- ii_enabled = true
190
- rescue Exception => e
191
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
192
- end
193
- end
194
- end
195
-
196
- log(sql, name) do
197
- execute(sql, name)
198
- ident = select_one("SELECT @@IDENTITY AS last_id")["last_id"]
199
- id_value || ident
200
- end
201
- ensure
202
- if ii_enabled
203
- begin
204
- enable_identity_insert(table_name, false)
205
- rescue Exception => e
206
- raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
207
- end
208
- end
209
- end
210
- end
211
-
212
- def execute(sql, name = nil)
213
- raw_execute(sql, name)
214
- @connection.results[0].row_count
215
- end
216
-
217
- def begin_db_transaction() raw_execute "BEGIN TRAN" end
218
- def commit_db_transaction() raw_execute "COMMIT TRAN" end
219
- def rollback_db_transaction() raw_execute "ROLLBACK TRAN" end
220
-
221
- def current_database
222
- select_one("select DB_NAME() as name")["name"]
223
- end
224
-
225
- def tables(name = nil)
226
- select("select name from sysobjects where type='U'", name).map { |row| row['name'] }
227
- end
228
-
229
- def indexes(table_name, name = nil)
230
- select("exec sp_helpindex #{table_name}", name).map do |index|
231
- unique = index["index_description"] =~ /unique/
232
- primary = index["index_description"] =~ /^clustered/
233
- if !primary
234
- cols = index["index_keys"].split(", ").each { |col| col.strip! }
235
- IndexDefinition.new(table_name, index["index_name"], unique, cols)
236
- end
237
- end.compact
238
- end
239
-
240
- def columns(table_name, name = nil)
241
- sql = <<SQLTEXT
242
- SELECT col.name AS name, type.name AS type, col.prec, col.scale,
243
- col.length, col.status, obj.sysstat2, def.text
244
- FROM sysobjects obj, syscolumns col, systypes type, syscomments def
245
- WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
246
- AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
247
- SQLTEXT
248
- @logger.debug "Get Column Info for table '#{table_name}'" if @logger
249
- @connection.set_rowcount(0)
250
- @connection.sql(sql)
251
-
252
- raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}" if @connection.context.failed?
253
- return nil if @connection.cmd_fail?
254
-
255
- @connection.top_row_result.rows.map do |row|
256
- name, type, prec, scale, length, status, sysstat2, default = row
257
- name.sub!(/_$/o, '')
258
- type = normalize_type(type, prec, scale, length)
259
- default_value = nil
260
- if default =~ /DEFAULT\s+(.+)/o
261
- default_value = $1.strip
262
- default_value = default_value[1...-1] if default_value =~ /^['"]/o
263
- end
264
- nullable = (status & 8) == 8
265
- identity = status >= 128
266
- primary = (sysstat2 & 8) == 8
267
- ColumnWithIdentity.new(name, default_value, type, nullable, identity, primary)
268
- end
269
- end
270
-
271
- def quoted_true
272
- "1"
273
- end
274
-
275
- def quoted_false
276
- "0"
277
- end
278
-
279
- def quote(value, column = nil)
280
- return value.quoted_id if value.respond_to?(:quoted_id)
281
-
282
- case value
283
- when String
284
- if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
285
- "#{quote_string(column.class.string_to_binary(value))}"
286
- elsif @numconvert && force_numeric?(column) && value =~ /^[+-]?[0-9]+$/o
287
- value
288
- else
289
- "'#{quote_string(value)}'"
290
- end
291
- when NilClass then (column && column.type == :boolean) ? '0' : "NULL"
292
- when TrueClass then '1'
293
- when FalseClass then '0'
294
- when Float, Fixnum, Bignum then force_numeric?(column) ? value.to_s : "'#{value.to_s}'"
295
- when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
296
- else super
297
- end
298
- end
299
-
300
- # True if column is explicitly declared non-numeric, or
301
- # if column is nil (not specified).
302
- def force_numeric?(column)
303
- (column.nil? || [:integer, :float, :decimal].include?(column.type))
304
- end
305
-
306
- def quote_string(s)
307
- s.gsub(/'/, "''") # ' (for ruby-mode)
308
- end
309
-
310
- def quote_column_name(name)
311
- # If column name is close to max length, skip the quotes, since they
312
- # seem to count as part of the length.
313
- ((name.to_s.length + 2) <= table_alias_length) ? "[#{name}]" : name.to_s
314
- end
315
-
316
- def add_limit_offset!(sql, options) # :nodoc:
317
- @limit = options[:limit]
318
- @offset = options[:offset]
319
- if use_temp_table?
320
- # Use temp table to hack offset with Sybase
321
- sql.sub!(/ FROM /i, ' INTO #artemp FROM ')
322
- elsif zero_limit?
323
- # "SET ROWCOUNT 0" turns off limits, so we have
324
- # to use a cheap trick.
325
- if sql =~ /WHERE/i
326
- sql.sub!(/WHERE/i, 'WHERE 1 = 2 AND ')
327
- elsif sql =~ /ORDER\s+BY/i
328
- sql.sub!(/ORDER\s+BY/i, 'WHERE 1 = 2 ORDER BY')
329
- else
330
- sql << 'WHERE 1 = 2'
331
- end
332
- end
333
- end
334
-
335
- def add_lock!(sql, options) #:nodoc:
336
- @logger.info "Warning: Sybase :lock option '#{options[:lock].inspect}' not supported" if @logger && options.has_key?(:lock)
337
- sql
338
- end
339
-
340
- def supports_migrations? #:nodoc:
341
- true
342
- end
343
-
344
- def rename_table(name, new_name)
345
- execute "EXEC sp_rename '#{name}', '#{new_name}'"
346
- end
347
-
348
- def rename_column(table, column, new_column_name)
349
- execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
350
- end
351
-
352
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
353
- begin
354
- execute "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"
355
- rescue StatementInvalid => e
356
- # Swallow exception and reset context if no-op.
357
- raise e unless e.message =~ /no columns to drop, add or modify/
358
- @connection.context.reset
359
- end
360
-
361
- if options.has_key?(:default)
362
- remove_default_constraint(table_name, column_name)
363
- execute "ALTER TABLE #{table_name} REPLACE #{column_name} DEFAULT #{quote options[:default]}"
364
- end
365
- end
366
-
367
- def remove_column(table_name, column_name)
368
- remove_default_constraint(table_name, column_name)
369
- execute "ALTER TABLE #{table_name} DROP #{column_name}"
370
- end
371
-
372
- def remove_default_constraint(table_name, column_name)
373
- sql = "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"
374
- select(sql).each do |constraint|
375
- execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
376
- end
377
- end
378
-
379
- def remove_index(table_name, options = {})
380
- execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
381
- end
382
-
383
- def add_column_options!(sql, options) #:nodoc:
384
- sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
385
-
386
- if check_null_for_column?(options[:column], sql)
387
- sql << (options[:null] == false ? " NOT NULL" : " NULL")
388
- end
389
- sql
390
- end
391
-
392
- def enable_identity_insert(table_name, enable = true)
393
- if has_identity_column(table_name)
394
- execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
395
- end
396
- end
397
-
398
- private
399
- def check_null_for_column?(col, sql)
400
- # Sybase columns are NOT NULL by default, so explicitly set NULL
401
- # if :null option is omitted. Disallow NULLs for boolean.
402
- type = col.nil? ? "" : col[:type]
403
-
404
- # Ignore :null if a primary key
405
- return false if type =~ /PRIMARY KEY/i
406
-
407
- # Ignore :null if a :boolean or BIT column
408
- if (sql =~ /\s+bit(\s+DEFAULT)?/i) || type == :boolean
409
- # If no default clause found on a boolean column, add one.
410
- sql << " DEFAULT 0" if $1.nil?
411
- return false
412
- end
413
- true
414
- end
415
-
416
- # Return the last value of the identity global value.
417
- def last_insert_id
418
- @connection.sql("SELECT @@IDENTITY")
419
- unless @connection.cmd_fail?
420
- id = @connection.top_row_result.rows.first.first
421
- if id
422
- id = id.to_i
423
- id = nil if id == 0
424
- end
425
- else
426
- id = nil
427
- end
428
- id
429
- end
430
-
431
- def affected_rows(name = nil)
432
- @connection.sql("SELECT @@ROWCOUNT")
433
- unless @connection.cmd_fail?
434
- count = @connection.top_row_result.rows.first.first
435
- count = count.to_i if count
436
- else
437
- 0
438
- end
439
- end
440
-
441
- # If limit is not set at all, we can ignore offset;
442
- # if limit *is* set but offset is zero, use normal select
443
- # with simple SET ROWCOUNT. Thus, only use the temp table
444
- # if limit is set and offset > 0.
445
- def use_temp_table?
446
- !@limit.nil? && !@offset.nil? && @offset > 0
447
- end
448
-
449
- def zero_limit?
450
- !@limit.nil? && @limit == 0
451
- end
452
-
453
- def raw_execute(sql, name = nil)
454
- log(sql, name) do
455
- @connection.context.reset
456
- @logger.debug "Setting row count to (#{@limit})" if @logger && @limit
457
- @connection.set_rowcount(@limit || 0)
458
- if sql =~ /^\s*SELECT/i
459
- @connection.sql(sql)
460
- else
461
- @connection.sql_norow(sql)
462
- end
463
- @limit = @offset = nil
464
- if @connection.cmd_fail? or @connection.context.failed?
465
- raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
466
- end
467
- end
468
- end
469
-
470
- # Select limit number of rows starting at optional offset.
471
- def select(sql, name = nil)
472
- if !use_temp_table?
473
- execute(sql, name)
474
- else
475
- log(sql, name) do
476
- # Select into a temp table and prune results
477
- @logger.debug "Selecting #{@limit + (@offset || 0)} or fewer rows into #artemp" if @logger
478
- @connection.context.reset
479
- @connection.set_rowcount(@limit + (@offset || 0))
480
- @connection.sql_norow(sql) # Select into temp table
481
- @logger.debug "Deleting #{@offset || 0} or fewer rows from #artemp" if @logger
482
- @connection.set_rowcount(@offset || 0)
483
- @connection.sql_norow("delete from #artemp") # Delete leading rows
484
- @connection.set_rowcount(0)
485
- @connection.sql("select * from #artemp") # Return the rest
486
- end
487
- end
488
-
489
- raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}" if @connection.context.failed? or @connection.cmd_fail?
490
-
491
- rows = []
492
- results = @connection.top_row_result
493
- if results && results.rows.length > 0
494
- fields = results.columns.map { |column| column.sub(/_$/, '') }
495
- results.rows.each do |row|
496
- hashed_row = {}
497
- row.zip(fields) { |cell, column| hashed_row[column] = cell }
498
- rows << hashed_row
499
- end
500
- end
501
- @connection.sql_norow("drop table #artemp") if use_temp_table?
502
- @limit = @offset = nil
503
- rows
504
- end
505
-
506
- def get_table_name(sql)
507
- if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
508
- $1
509
- elsif sql =~ /from\s+([^\(\s]+)\s*/i
510
- $1
511
- else
512
- nil
513
- end
514
- end
515
-
516
- def has_identity_column(table_name)
517
- !get_identity_column(table_name).nil?
518
- end
519
-
520
- def get_identity_column(table_name)
521
- @id_columns ||= {}
522
- if !@id_columns.has_key?(table_name)
523
- @logger.debug "Looking up identity column for table '#{table_name}'" if @logger
524
- col = columns(table_name).detect { |col| col.identity }
525
- @id_columns[table_name] = col.nil? ? nil : col.name
526
- end
527
- @id_columns[table_name]
528
- end
529
-
530
- def query_contains_identity_column(sql, col)
531
- sql =~ /\[#{col}\]/
532
- end
533
-
534
- # Resolve all user-defined types (udt) to their fundamental types.
535
- def resolve_type(field_type)
536
- (@udts ||= {})[field_type] ||= select_one("sp_help #{field_type}")["Storage_type"].strip
537
- end
538
-
539
- def normalize_type(field_type, prec, scale, length)
540
- has_scale = (!scale.nil? && scale > 0)
541
- type = if field_type =~ /numeric/i and !has_scale
542
- 'int'
543
- elsif field_type =~ /money/i
544
- 'numeric'
545
- else
546
- resolve_type(field_type.strip)
547
- end
548
-
549
- spec = if prec
550
- has_scale ? "(#{prec},#{scale})" : "(#{prec})"
551
- elsif length && !(type =~ /date|time|text/)
552
- "(#{length})"
553
- else
554
- ''
555
- end
556
- "#{type}#{spec}"
557
- end
558
- end # class SybaseAdapter
559
-
560
- class SybaseAdapterContext < SybSQLContext
561
- DEADLOCK = 1205
562
- attr_reader :message
563
-
564
- def init(logger = nil)
565
- @deadlocked = false
566
- @failed = false
567
- @logger = logger
568
- @message = nil
569
- end
570
-
571
- def srvmsgCB(con, msg)
572
- # Do not log change of context messages.
573
- if msg['severity'] == 10 or msg['severity'] == 0
574
- return true
575
- end
576
-
577
- if msg['msgnumber'] == DEADLOCK
578
- @deadlocked = true
579
- else
580
- @logger.info "SQL Command failed!" if @logger
581
- @failed = true
582
- end
583
-
584
- if @logger
585
- @logger.error "** SybSQLContext Server Message: **"
586
- @logger.error " Message number #{msg['msgnumber']} Severity #{msg['severity']} State #{msg['state']} Line #{msg['line']}"
587
- @logger.error " Server #{msg['srvname']}"
588
- @logger.error " Procedure #{msg['proc']}"
589
- @logger.error " Message String: #{msg['text']}"
590
- end
591
-
592
- @message = msg['text']
593
-
594
- true
595
- end
596
-
597
- def deadlocked?
598
- @deadlocked
599
- end
600
-
601
- def failed?
602
- @failed
603
- end
604
-
605
- def reset
606
- @deadlocked = false
607
- @failed = false
608
- @message = nil
609
- end
610
-
611
- def cltmsgCB(con, msg)
612
- return true unless ( msg.kind_of?(Hash) )
613
- unless ( msg[ "severity" ] ) then
614
- return true
615
- end
616
-
617
- if @logger
618
- @logger.error "** SybSQLContext Client-Message: **"
619
- @logger.error " Message number: LAYER=#{msg[ 'layer' ]} ORIGIN=#{msg[ 'origin' ]} SEVERITY=#{msg[ 'severity' ]} NUMBER=#{msg[ 'number' ]}"
620
- @logger.error " Message String: #{msg['msgstring']}"
621
- @logger.error " OS Error: #{msg['osstring']}"
622
-
623
- @message = msg['msgstring']
624
- end
625
-
626
- @failed = true
627
-
628
- # Not retry , CS_CV_RETRY_FAIL( probability TimeOut )
629
- if( msg[ 'severity' ] == "RETRY_FAIL" ) then
630
- @timeout_p = true
631
- return false
632
- end
633
-
634
- return true
635
- end
636
- end # class SybaseAdapterContext
637
-
638
- end # module ConnectionAdapters
639
- end # module ActiveRecord
640
-
641
-
642
- # Allow identity inserts for fixtures.
643
- require "active_record/fixtures"
644
- class Fixtures
645
- alias :original_insert_fixtures :insert_fixtures
646
-
647
- def insert_fixtures
648
- if @connection.instance_of?(ActiveRecord::ConnectionAdapters::SybaseAdapter)
649
- values.each do |fixture|
650
- @connection.enable_identity_insert(table_name, true)
651
- @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
652
- @connection.enable_identity_insert(table_name, false)
653
- end
654
- else
655
- original_insert_fixtures
656
- end
657
- end
658
- end
659
-
660
- rescue LoadError => cannot_require_sybase
661
- # Couldn't load sybase adapter
662
- end