activerecord 1.14.4 → 1.15.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 (159) hide show
  1. data/CHANGELOG +400 -1
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +21 -3
  4. data/Rakefile +55 -10
  5. data/lib/active_record.rb +10 -4
  6. data/lib/active_record/acts/list.rb +15 -4
  7. data/lib/active_record/acts/nested_set.rb +11 -12
  8. data/lib/active_record/acts/tree.rb +13 -14
  9. data/lib/active_record/aggregations.rb +46 -22
  10. data/lib/active_record/associations.rb +213 -162
  11. data/lib/active_record/associations/association_collection.rb +45 -15
  12. data/lib/active_record/associations/association_proxy.rb +32 -13
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
  14. data/lib/active_record/associations/has_many_association.rb +37 -17
  15. data/lib/active_record/associations/has_many_through_association.rb +120 -30
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +75 -0
  18. data/lib/active_record/base.rb +282 -203
  19. data/lib/active_record/calculations.rb +95 -54
  20. data/lib/active_record/callbacks.rb +13 -24
  21. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  28. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
  29. data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
  30. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  31. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
  32. data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
  33. data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
  35. data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
  36. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
  37. data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
  38. data/lib/active_record/deprecated_associations.rb +24 -10
  39. data/lib/active_record/deprecated_finders.rb +4 -1
  40. data/lib/active_record/fixtures.rb +37 -23
  41. data/lib/active_record/locking/optimistic.rb +106 -0
  42. data/lib/active_record/locking/pessimistic.rb +77 -0
  43. data/lib/active_record/migration.rb +8 -5
  44. data/lib/active_record/observer.rb +73 -34
  45. data/lib/active_record/reflection.rb +21 -7
  46. data/lib/active_record/schema_dumper.rb +33 -5
  47. data/lib/active_record/timestamp.rb +23 -34
  48. data/lib/active_record/transactions.rb +37 -30
  49. data/lib/active_record/validations.rb +46 -30
  50. data/lib/active_record/vendor/mysql.rb +20 -5
  51. data/lib/active_record/version.rb +2 -2
  52. data/lib/active_record/wrappings.rb +1 -2
  53. data/lib/active_record/xml_serialization.rb +308 -0
  54. data/test/aaa_create_tables_test.rb +5 -1
  55. data/test/abstract_unit.rb +18 -8
  56. data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
  57. data/test/adapter_test.rb +9 -7
  58. data/test/adapter_test_sqlserver.rb +81 -0
  59. data/test/aggregations_test.rb +29 -0
  60. data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
  61. data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
  62. data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
  63. data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
  64. data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
  65. data/test/associations_test.rb +339 -45
  66. data/test/attribute_methods_test.rb +49 -0
  67. data/test/base_test.rb +321 -67
  68. data/test/calculations_test.rb +48 -10
  69. data/test/callbacks_test.rb +13 -0
  70. data/test/connection_test_firebird.rb +8 -0
  71. data/test/connections/native_db2/connection.rb +18 -17
  72. data/test/connections/native_firebird/connection.rb +19 -17
  73. data/test/connections/native_frontbase/connection.rb +27 -0
  74. data/test/connections/native_mysql/connection.rb +18 -15
  75. data/test/connections/native_openbase/connection.rb +14 -15
  76. data/test/connections/native_oracle/connection.rb +16 -12
  77. data/test/connections/native_postgresql/connection.rb +16 -17
  78. data/test/connections/native_sqlite/connection.rb +3 -6
  79. data/test/connections/native_sqlite3/connection.rb +3 -6
  80. data/test/connections/native_sqlserver/connection.rb +16 -17
  81. data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
  82. data/test/connections/native_sybase/connection.rb +16 -17
  83. data/test/datatype_test_postgresql.rb +52 -0
  84. data/test/defaults_test.rb +52 -10
  85. data/test/deprecated_associations_test.rb +151 -107
  86. data/test/deprecated_finder_test.rb +83 -66
  87. data/test/empty_date_time_test.rb +25 -0
  88. data/test/finder_test.rb +118 -11
  89. data/test/fixtures/accounts.yml +6 -1
  90. data/test/fixtures/author.rb +27 -4
  91. data/test/fixtures/categorizations.yml +8 -2
  92. data/test/fixtures/category.rb +1 -2
  93. data/test/fixtures/comments.yml +0 -6
  94. data/test/fixtures/companies.yml +6 -1
  95. data/test/fixtures/company.rb +23 -1
  96. data/test/fixtures/company_in_module.rb +8 -10
  97. data/test/fixtures/customer.rb +2 -2
  98. data/test/fixtures/customers.yml +9 -0
  99. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  100. data/test/fixtures/db_definitions/db2.sql +9 -0
  101. data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
  102. data/test/fixtures/db_definitions/firebird.sql +13 -1
  103. data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
  104. data/test/fixtures/db_definitions/frontbase.sql +262 -0
  105. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  107. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  108. data/test/fixtures/db_definitions/mysql.sql +23 -14
  109. data/test/fixtures/db_definitions/openbase.sql +13 -1
  110. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  111. data/test/fixtures/db_definitions/oracle.sql +29 -2
  112. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  113. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  114. data/test/fixtures/db_definitions/schema.rb +29 -1
  115. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  116. data/test/fixtures/db_definitions/sqlite.sql +12 -3
  117. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  118. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  119. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  120. data/test/fixtures/db_definitions/sybase.sql +13 -4
  121. data/test/fixtures/developer.rb +12 -0
  122. data/test/fixtures/edge.rb +5 -0
  123. data/test/fixtures/edges.yml +6 -0
  124. data/test/fixtures/funny_jokes.yml +3 -7
  125. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  126. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  127. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  128. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  129. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  130. data/test/fixtures/mixin.rb +15 -0
  131. data/test/fixtures/mixins.yml +38 -0
  132. data/test/fixtures/post.rb +3 -2
  133. data/test/fixtures/project.rb +3 -1
  134. data/test/fixtures/topic.rb +6 -1
  135. data/test/fixtures/topics.yml +4 -4
  136. data/test/fixtures/vertex.rb +9 -0
  137. data/test/fixtures/vertices.yml +4 -0
  138. data/test/fixtures_test.rb +45 -0
  139. data/test/inheritance_test.rb +67 -6
  140. data/test/lifecycle_test.rb +40 -19
  141. data/test/locking_test.rb +170 -26
  142. data/test/method_scoping_test.rb +2 -2
  143. data/test/migration_test.rb +387 -110
  144. data/test/migration_test_firebird.rb +124 -0
  145. data/test/mixin_nested_set_test.rb +14 -2
  146. data/test/mixin_test.rb +56 -18
  147. data/test/modules_test.rb +8 -2
  148. data/test/multiple_db_test.rb +2 -2
  149. data/test/pk_test.rb +1 -0
  150. data/test/reflection_test.rb +8 -2
  151. data/test/schema_authorization_test_postgresql.rb +75 -0
  152. data/test/schema_dumper_test.rb +40 -4
  153. data/test/table_name_test_sqlserver.rb +23 -0
  154. data/test/threaded_connections_test.rb +19 -16
  155. data/test/transactions_test.rb +86 -72
  156. data/test/validations_test.rb +126 -56
  157. data/test/xml_serialization_test.rb +125 -0
  158. metadata +45 -11
  159. data/lib/active_record/locking.rb +0 -79
@@ -1,11 +1,20 @@
1
1
  # sybase_adaptor.rb
2
- # Author: John Sheets <dev@metacasa.net>
3
- # Date: 01 Mar 2006
4
- #
5
- # Based on code from Will Sobel (http://dev.rubyonrails.org/ticket/2030)
6
- #
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
7
  # 17 Mar 2006: Added support for migrations; fixed issues with :boolean columns.
8
- #
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().
9
18
 
10
19
  require 'active_record/connection_adapters/abstract_adapter'
11
20
 
@@ -35,7 +44,7 @@ module ActiveRecord
35
44
 
36
45
  ConnectionAdapters::SybaseAdapter.new(
37
46
  SybSQL.new({'S' => host, 'U' => username, 'P' => password},
38
- ConnectionAdapters::SybaseAdapterContext), database, logger)
47
+ ConnectionAdapters::SybaseAdapterContext), database, config, logger)
39
48
  end
40
49
  end # class Base
41
50
 
@@ -48,7 +57,7 @@ module ActiveRecord
48
57
  #
49
58
  # * <tt>:host</tt> -- The name of the database server. No default, must be provided.
50
59
  # * <tt>:database</tt> -- The name of the database. No default, must be provided.
51
- # * <tt>:username</tt> -- Defaults to sa.
60
+ # * <tt>:username</tt> -- Defaults to "sa".
52
61
  # * <tt>:password</tt> -- Defaults to empty string.
53
62
  #
54
63
  # Usage Notes:
@@ -75,7 +84,7 @@ module ActiveRecord
75
84
  # 2> go
76
85
  class SybaseAdapter < AbstractAdapter # :nodoc:
77
86
  class ColumnWithIdentity < Column
78
- attr_reader :identity, :primary
87
+ attr_reader :identity
79
88
 
80
89
  def initialize(name, default, sql_type = nil, nullable = nil, identity = nil, primary = nil)
81
90
  super(name, default, sql_type, nullable)
@@ -84,14 +93,15 @@ module ActiveRecord
84
93
 
85
94
  def simplified_type(field_type)
86
95
  case field_type
87
- when /int|bigint|smallint|tinyint/i then :integer
88
- when /float|double|decimal|money|numeric|real|smallmoney/i then :float
89
- when /text|ntext/i then :text
90
- when /binary|image|varbinary/i then :binary
91
- when /char|nchar|nvarchar|string|varchar/i then :string
92
- when /bit/i then :boolean
93
- when /datetime|smalldatetime/i then :datetime
94
- else super
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
95
105
  end
96
106
  end
97
107
 
@@ -106,10 +116,12 @@ module ActiveRecord
106
116
  end # class ColumnWithIdentity
107
117
 
108
118
  # Sybase adapter
109
- def initialize(connection, database, logger = nil)
119
+ def initialize(connection, database, config = {}, logger = nil)
110
120
  super(connection, logger)
111
121
  context = connection.context
112
122
  context.init(logger)
123
+ @config = config
124
+ @numconvert = config.has_key?(:numconvert) ? config[:numconvert] : true
113
125
  @limit = @offset = 0
114
126
  unless connection.sql_norow("USE #{database}")
115
127
  raise "Cannot USE #{database}"
@@ -123,6 +135,7 @@ module ActiveRecord
123
135
  :text => { :name => "text" },
124
136
  :integer => { :name => "int" },
125
137
  :float => { :name => "float", :limit => 8 },
138
+ :decimal => { :name => "decimal" },
126
139
  :datetime => { :name => "datetime" },
127
140
  :timestamp => { :name => "timestamp" },
128
141
  :time => { :name => "time" },
@@ -132,6 +145,15 @@ module ActiveRecord
132
145
  }
133
146
  end
134
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
+
135
157
  def adapter_name
136
158
  'Sybase'
137
159
  end
@@ -154,29 +176,6 @@ module ActiveRecord
154
176
  30
155
177
  end
156
178
 
157
- # Check for a limit statement and parse out the limit and
158
- # offset if specified. Remove the limit from the sql statement
159
- # and call select.
160
- def select_all(sql, name = nil)
161
- select(sql, name)
162
- end
163
-
164
- # Remove limit clause from statement. This will almost always
165
- # contain LIMIT 1 from the caller. set the rowcount to 1 before
166
- # calling select.
167
- def select_one(sql, name = nil)
168
- result = select(sql, name)
169
- result.nil? ? nil : result.first
170
- end
171
-
172
- def columns(table_name, name = nil)
173
- table_structure(table_name).inject([]) do |columns, column|
174
- name, default, type, nullable, identity, primary = column
175
- columns << ColumnWithIdentity.new(name, default, type, nullable, identity, primary)
176
- columns
177
- end
178
- end
179
-
180
179
  def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
181
180
  begin
182
181
  table_name = get_table_name(sql)
@@ -186,7 +185,7 @@ module ActiveRecord
186
185
  if col != nil
187
186
  if query_contains_identity_column(sql, col)
188
187
  begin
189
- execute enable_identity_insert(table_name, true)
188
+ enable_identity_insert(table_name, true)
190
189
  ii_enabled = true
191
190
  rescue Exception => e
192
191
  raise ActiveRecordError, "IDENTITY_INSERT could not be turned ON"
@@ -202,7 +201,7 @@ module ActiveRecord
202
201
  ensure
203
202
  if ii_enabled
204
203
  begin
205
- execute enable_identity_insert(table_name, false)
204
+ enable_identity_insert(table_name, false)
206
205
  rescue Exception => e
207
206
  raise ActiveRecordError, "IDENTITY_INSERT could not be turned OFF"
208
207
  end
@@ -211,45 +210,62 @@ module ActiveRecord
211
210
  end
212
211
 
213
212
  def execute(sql, name = nil)
214
- log(sql, name) do
215
- @connection.context.reset
216
- @connection.set_rowcount(@limit || 0)
217
- @limit = @offset = nil
218
- @connection.sql_norow(sql)
219
- if @connection.cmd_fail? or @connection.context.failed?
220
- raise "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
221
- end
222
- end
223
- # Return rows affected
213
+ raw_execute(sql, name)
224
214
  @connection.results[0].row_count
225
215
  end
226
216
 
227
- alias_method :update, :execute
228
- alias_method :delete, :execute
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
229
220
 
230
- def begin_db_transaction() execute "BEGIN TRAN" end
231
- def commit_db_transaction() execute "COMMIT TRAN" end
232
- def rollback_db_transaction() execute "ROLLBACK TRAN" end
221
+ def current_database
222
+ select_one("select DB_NAME() as name")["name"]
223
+ end
233
224
 
234
225
  def tables(name = nil)
235
- tables = []
236
- select("select name from sysobjects where type='U'", name).each do |row|
237
- tables << row['name']
238
- end
239
- tables
226
+ select("select name from sysobjects where type='U'", name).map { |row| row['name'] }
240
227
  end
241
228
 
242
229
  def indexes(table_name, name = nil)
243
- indexes = []
244
- select("exec sp_helpindex #{table_name}", name).each do |index|
230
+ select("exec sp_helpindex #{table_name}", name).map do |index|
245
231
  unique = index["index_description"] =~ /unique/
246
232
  primary = index["index_description"] =~ /^clustered/
247
233
  if !primary
248
234
  cols = index["index_keys"].split(", ").each { |col| col.strip! }
249
- indexes << IndexDefinition.new(table_name, index["index_name"], unique, cols)
235
+ IndexDefinition.new(table_name, index["index_name"], unique, cols)
250
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)
251
268
  end
252
- indexes
253
269
  end
254
270
 
255
271
  def quoted_true
@@ -261,11 +277,13 @@ module ActiveRecord
261
277
  end
262
278
 
263
279
  def quote(value, column = nil)
280
+ return value.quoted_id if value.respond_to?(:quoted_id)
281
+
264
282
  case value
265
283
  when String
266
284
  if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
267
285
  "#{quote_string(column.class.string_to_binary(value))}"
268
- elsif value =~ /^[+-]?[0-9]+$/o
286
+ elsif @numconvert && force_numeric?(column) && value =~ /^[+-]?[0-9]+$/o
269
287
  value
270
288
  else
271
289
  "'#{quote_string(value)}'"
@@ -273,39 +291,16 @@ module ActiveRecord
273
291
  when NilClass then (column && column.type == :boolean) ? '0' : "NULL"
274
292
  when TrueClass then '1'
275
293
  when FalseClass then '0'
276
- when Float, Fixnum, Bignum then value.to_s
277
- when Date then "'#{value.to_s}'"
294
+ when Float, Fixnum, Bignum then force_numeric?(column) ? value.to_s : "'#{value.to_s}'"
278
295
  when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
279
- else "'#{quote_string(value.to_yaml)}'"
296
+ else super
280
297
  end
281
298
  end
282
299
 
283
- def quote_column(type, value)
284
- case type
285
- when :boolean
286
- case value
287
- when String then value =~ /^[ty]/o ? 1 : 0
288
- when true then 1
289
- when false then 0
290
- else value.to_i
291
- end
292
- when :integer then value.to_i
293
- when :float then value.to_f
294
- when :text, :string, :enum
295
- case value
296
- when String, Symbol, Fixnum, Float, Bignum, TrueClass, FalseClass
297
- "'#{quote_string(value.to_s)}'"
298
- else
299
- "'#{quote_string(value.to_yaml)}'"
300
- end
301
- when :date, :datetime, :time
302
- case value
303
- when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
304
- when Date then "'#{value.to_s}'"
305
- else "'#{quote_string(value)}'"
306
- end
307
- else "'#{quote_string(value.to_yaml)}'"
308
- end
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))
309
304
  end
310
305
 
311
306
  def quote_string(s)
@@ -313,13 +308,15 @@ module ActiveRecord
313
308
  end
314
309
 
315
310
  def quote_column_name(name)
316
- "[#{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
317
314
  end
318
315
 
319
316
  def add_limit_offset!(sql, options) # :nodoc:
320
317
  @limit = options[:limit]
321
318
  @offset = options[:offset]
322
- if !normal_select?
319
+ if use_temp_table?
323
320
  # Use temp table to hack offset with Sybase
324
321
  sql.sub!(/ FROM /i, ' INTO #artemp FROM ')
325
322
  elsif zero_limit?
@@ -335,6 +332,11 @@ module ActiveRecord
335
332
  end
336
333
  end
337
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
+
338
340
  def supports_migrations? #:nodoc:
339
341
  true
340
342
  end
@@ -348,12 +350,18 @@ module ActiveRecord
348
350
  end
349
351
 
350
352
  def change_column(table_name, column_name, type, options = {}) #:nodoc:
351
- sql_commands = ["ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit])}"]
352
- if options[:default]
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)
353
362
  remove_default_constraint(table_name, column_name)
354
- sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{options[:default]} FOR #{column_name}"
363
+ execute "ALTER TABLE #{table_name} REPLACE #{column_name} DEFAULT #{quote options[:default]}"
355
364
  end
356
- sql_commands.each { |c| execute(c) }
357
365
  end
358
366
 
359
367
  def remove_column(table_name, column_name)
@@ -362,10 +370,10 @@ module ActiveRecord
362
370
  end
363
371
 
364
372
  def remove_default_constraint(table_name, column_name)
365
- defaults = select "select def.name from sysobjects def, syscolumns col, sysobjects tab where col.cdefault = def.id and col.name = '#{column_name}' and tab.name = '#{table_name}' and col.id = tab.id"
366
- defaults.each {|constraint|
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|
367
375
  execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
368
- }
376
+ end
369
377
  end
370
378
 
371
379
  def remove_index(table_name, options = {})
@@ -373,7 +381,7 @@ module ActiveRecord
373
381
  end
374
382
 
375
383
  def add_column_options!(sql, options) #:nodoc:
376
- sql << " DEFAULT #{quote(options[:default], options[:column])}" unless options[:default].nil?
384
+ sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
377
385
 
378
386
  if check_null_for_column?(options[:column], sql)
379
387
  sql << (options[:null] == false ? " NOT NULL" : " NULL")
@@ -381,6 +389,12 @@ module ActiveRecord
381
389
  sql
382
390
  end
383
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
+
384
398
  private
385
399
  def check_null_for_column?(col, sql)
386
400
  # Sybase columns are NOT NULL by default, so explicitly set NULL
@@ -424,34 +438,44 @@ module ActiveRecord
424
438
  end
425
439
  end
426
440
 
427
- def normal_select?
428
- # If limit is not set at all, we can ignore offset;
429
- # If limit *is* set but offset is zero, use normal select
430
- # with simple SET ROWCOUNT. Thus, only use the temp table
431
- # if limit is set and offset > 0.
432
- has_limit = !@limit.nil?
433
- has_offset = !@offset.nil? && @offset > 0
434
- !has_limit || !has_offset
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
435
447
  end
436
448
 
437
449
  def zero_limit?
438
450
  !@limit.nil? && @limit == 0
439
451
  end
440
452
 
441
- # Select limit number of rows starting at optional offset.
442
- def select(sql, name = nil)
443
- @connection.context.reset
453
+ def raw_execute(sql, name = nil)
444
454
  log(sql, name) do
445
- if normal_select?
446
- # If limit is not explicitly set, return all results.
447
- @logger.debug "Setting row count to (#{@limit || 'off'})" if @logger
448
-
449
- # Run a normal select
450
- @connection.set_rowcount(@limit || 0)
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
451
459
  @connection.sql(sql)
452
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
453
476
  # Select into a temp table and prune results
454
477
  @logger.debug "Selecting #{@limit + (@offset || 0)} or fewer rows into #artemp" if @logger
478
+ @connection.context.reset
455
479
  @connection.set_rowcount(@limit + (@offset || 0))
456
480
  @connection.sql_norow(sql) # Select into temp table
457
481
  @logger.debug "Deleting #{@offset || 0} or fewer rows from #artemp" if @logger
@@ -462,29 +486,21 @@ module ActiveRecord
462
486
  end
463
487
  end
464
488
 
489
+ raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}" if @connection.context.failed? or @connection.cmd_fail?
490
+
465
491
  rows = []
466
- if @connection.context.failed? or @connection.cmd_fail?
467
- raise StatementInvalid, "SQL Command Failed for #{name}: #{sql}\nMessage: #{@connection.context.message}"
468
- else
469
- results = @connection.top_row_result
470
- if results && results.rows.length > 0
471
- fields = fixup_column_names(results.columns)
472
- results.rows.each do |row|
473
- hashed_row = {}
474
- row.zip(fields) { |cell, column| hashed_row[column] = cell }
475
- rows << hashed_row
476
- end
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
477
499
  end
478
500
  end
479
- @connection.sql_norow("drop table #artemp") if !normal_select?
501
+ @connection.sql_norow("drop table #artemp") if use_temp_table?
480
502
  @limit = @offset = nil
481
- return rows
482
- end
483
-
484
- def enable_identity_insert(table_name, enable = true)
485
- if has_identity_column(table_name)
486
- "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
487
- end
503
+ rows
488
504
  end
489
505
 
490
506
  def get_table_name(sql)
@@ -502,80 +518,42 @@ module ActiveRecord
502
518
  end
503
519
 
504
520
  def get_identity_column(table_name)
505
- @table_columns = {} unless @table_columns
506
- @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
507
- @table_columns[table_name].each do |col|
508
- return col.name if col.identity
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
509
526
  end
510
-
511
- return nil
527
+ @id_columns[table_name]
512
528
  end
513
529
 
514
530
  def query_contains_identity_column(sql, col)
515
531
  sql =~ /\[#{col}\]/
516
532
  end
517
533
 
518
- # Remove trailing _ from names.
519
- def fixup_column_names(columns)
520
- columns.map { |column| column.sub(/_$/, '') }
521
- end
522
-
523
- def table_structure(table_name)
524
- sql = <<SQLTEXT
525
- SELECT col.name AS name, type.name AS type, col.prec, col.scale, col.length,
526
- col.status, obj.sysstat2, def.text
527
- FROM sysobjects obj, syscolumns col, systypes type, syscomments def
528
- WHERE obj.id = col.id AND col.usertype = type.usertype AND col.cdefault *= def.id
529
- AND obj.type = 'U' AND obj.name = '#{table_name}' ORDER BY col.colid
530
- SQLTEXT
531
- log(sql, "Get Column Info ") do
532
- @connection.set_rowcount(0)
533
- @connection.sql(sql)
534
- end
535
- if @connection.context.failed?
536
- raise "SQL Command for table_structure for #{table_name} failed\nMessage: #{@connection.context.message}"
537
- elsif !@connection.cmd_fail?
538
- columns = []
539
- results = @connection.top_row_result
540
- results.rows.each do |row|
541
- name, type, prec, scale, length, status, sysstat2, default = row
542
- type = normalize_type(type, prec, scale, length)
543
- default_value = nil
544
- name.sub!(/_$/o, '')
545
- if default =~ /DEFAULT\s+(.+)/o
546
- default_value = $1.strip
547
- default_value = default_value[1...-1] if default_value =~ /^['"]/o
548
- end
549
- nullable = (status & 8) == 8
550
- identity = status >= 128
551
- primary = (sysstat2 & 8) == 8
552
-
553
- columns << [name, default_value, type, nullable, identity, primary]
554
- end
555
- columns
556
- else
557
- nil
558
- end
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
559
537
  end
560
538
 
561
539
  def normalize_type(field_type, prec, scale, length)
562
- if field_type =~ /numeric/i and (scale.nil? or scale == 0)
563
- type = 'int'
540
+ has_scale = (!scale.nil? && scale > 0)
541
+ type = if field_type =~ /numeric/i and !has_scale
542
+ 'int'
564
543
  elsif field_type =~ /money/i
565
- type = 'numeric'
544
+ 'numeric'
566
545
  else
567
- type = field_type
568
- end
569
- size = ''
570
- if prec
571
- size = "(#{prec})"
572
- elsif length
573
- size = "(#{length})"
546
+ resolve_type(field_type.strip)
574
547
  end
575
- return type + size
576
- end
577
548
 
578
- def default_value(value)
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}"
579
557
  end
580
558
  end # class SybaseAdapter
581
559
 
@@ -667,18 +645,18 @@ class Fixtures
667
645
  alias :original_insert_fixtures :insert_fixtures
668
646
 
669
647
  def insert_fixtures
670
- values.each do |fixture|
671
- allow_identity_inserts table_name, true
672
- @connection.execute "INSERT INTO #{@table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
673
- allow_identity_inserts table_name, false
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
674
656
  end
675
657
  end
676
-
677
- def allow_identity_inserts(table_name, enable)
678
- @connection.execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}" rescue nil
679
- end
680
658
  end
681
659
 
682
660
  rescue LoadError => cannot_require_sybase
683
661
  # Couldn't load sybase adapter
684
- end
662
+ end