activerecord-jdbc-adapter 0.9.4-java → 0.9.5-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,14 @@
1
+ == 0.9.5
2
+
3
+ - The MSSQL release, courtesy of Mike Williams and Lonely
4
+ Planet.
5
+ - JRuby + AR-JDBC is now seen as the hassle-free way of using Rails
6
+ with SQLServer!
7
+ - Many fixes for MSSQL, including ACTIVERECORD_JDBC-18,
8
+ ACTIVERECORD_JDBC-41, ACTIVERECORD_JDBC-56, ACTIVERECORD_JDBC-94,
9
+ ACTIVERECORD_JDBC-99, JRUBY-3805, JRUBY-3793, JRUBY-4221
10
+ - All tests pass on Rails 3.0.0.beta3!
11
+
1
12
  == 0.9.4
2
13
 
3
14
  - ACTIVERECORD_JDBC-96: DB2 JdbcSpec cannot dump schema correctly
@@ -14,6 +14,7 @@ lib/active_record/connection_adapters/informix_adapter.rb
14
14
  lib/active_record/connection_adapters/jdbc_adapter.rb
15
15
  lib/active_record/connection_adapters/jdbc_adapter_spec.rb
16
16
  lib/active_record/connection_adapters/jndi_adapter.rb
17
+ lib/active_record/connection_adapters/mssql_adapter.rb
17
18
  lib/active_record/connection_adapters/mysql_adapter.rb
18
19
  lib/active_record/connection_adapters/oracle_adapter.rb
19
20
  lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -60,6 +61,11 @@ test/jndi_callbacks_test.rb
60
61
  test/jndi_test.rb
61
62
  test/manualTestDatabase.rb
62
63
  test/minirunit.rb
64
+ test/mssql_db_create_test.rb
65
+ test/mssql_identity_insert_test.rb
66
+ test/mssql_legacy_types_test.rb
67
+ test/mssql_limit_offset_test.rb
68
+ test/mssql_multibyte_test.rb
63
69
  test/mssql_simple_test.rb
64
70
  test/mysql_db_create_test.rb
65
71
  test/mysql_info_test.rb
data/README.txt CHANGED
@@ -16,7 +16,7 @@ What's there, and what is not there:
16
16
  * MySQL - Complete support
17
17
  * PostgreSQL - Complete support
18
18
  * Oracle - Complete support
19
- * Microsoft SQL Server - Complete support except for change_column_default
19
+ * Microsoft SQL Server - Complete support
20
20
  * DB2 - Complete, except for the migrations:
21
21
  * change_column
22
22
  * change_column_default
@@ -57,6 +57,7 @@ To use activerecord-jdbc-adapter with JRuby on Rails:
57
57
  * derby (<tt>activerecord-jdbcderby-adapter</tt>)
58
58
  * hsqldb (<tt>activerecord-jdbchsqldb-adapter</tt>)
59
59
  * h2 (<tt>activerecord-jdbch2-adapter</tt>)
60
+ * mssql (<tt>activerecord-jdbcmssql-adapter</tt>)
60
61
 
61
62
  2a. For Rails 3, if you're generating a new application, use the
62
63
  following command to generate your application:
@@ -0,0 +1,13 @@
1
+ tried_gem = false
2
+ begin
3
+ require "jdbc/jtds"
4
+ rescue LoadError
5
+ unless tried_gem
6
+ require 'rubygems'
7
+ gem "jdbc-mssql"
8
+ tried_gem = true
9
+ retry
10
+ end
11
+ # trust that the jtds jar is already present
12
+ end
13
+ require 'active_record/connection_adapters/jdbc_adapter'
@@ -63,7 +63,8 @@ namespace :db do
63
63
  ActiveRecord::Base.establish_connection(config.merge({'database' => nil, 'url' => url}))
64
64
  ActiveRecord::Base.connection.create_database(config['database'])
65
65
  ActiveRecord::Base.establish_connection(config)
66
- rescue
66
+ rescue => e
67
+ raise e unless config['adapter'] =~ /mysql|postgresql|sqlite/
67
68
  previous_create_database(config.merge('adapter' => config['adapter'].sub(/^jdbc/, '')))
68
69
  end
69
70
  end
@@ -1,20 +1,35 @@
1
1
  require 'jdbc_adapter/tsql_helper'
2
2
 
3
3
  module ::JdbcSpec
4
+
5
+ module ActiveRecordExtensions
6
+
7
+ def mssql_connection(config)
8
+ require "active_record/connection_adapters/mssql_adapter"
9
+ config[:host] ||= "localhost"
10
+ config[:port] ||= 1433
11
+ config[:url] ||= "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
12
+ config[:driver] ||= "net.sourceforge.jtds.jdbc.Driver"
13
+ embedded_driver(config)
14
+ end
15
+
16
+ end
17
+
4
18
  module MsSQL
19
+
5
20
  include TSqlMethods
6
21
 
7
22
  def self.extended(mod)
8
23
  unless @lob_callback_added
9
24
  ActiveRecord::Base.class_eval do
10
25
  def after_save_with_mssql_lob
11
- self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
12
- value = self[c.name]
13
- value = value.to_yaml if unserializable_attribute?(c.name, c)
14
- next if value.nil? || (value == '')
26
+ self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
27
+ value = self[c.name]
28
+ value = value.to_yaml if unserializable_attribute?(c.name, c)
29
+ next if value.nil? || (value == '')
15
30
 
16
- connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
17
- end
31
+ connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
32
+ end
18
33
  end
19
34
  end
20
35
 
@@ -35,30 +50,42 @@ module ::JdbcSpec
35
50
  ::ActiveRecord::ConnectionAdapters::MssqlJdbcConnection
36
51
  end
37
52
 
53
+ def modify_types(tp) #:nodoc:
54
+ super(tp)
55
+ tp[:string] = {:name => "NVARCHAR", :limit => 255}
56
+ tp[:text] = {:name => "NVARCHAR(MAX)"}
57
+ tp
58
+ end
59
+
38
60
  module Column
61
+
39
62
  attr_accessor :identity, :is_special
40
63
 
41
64
  def simplified_type(field_type)
42
65
  case field_type
43
- when /int|bigint|smallint|tinyint/i then :integer
44
- when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
45
- when /float|double|decimal|money|real|smallmoney/i then :decimal
46
- when /datetime|smalldatetime/i then :datetime
47
- when /timestamp/i then :timestamp
48
- when /time/i then :time
49
- when /date/i then :date
50
- when /text|ntext/i then :text
51
- when /binary|image|varbinary/i then :binary
52
- when /char|nchar|nvarchar|string|varchar/i then :string
53
- when /bit/i then :boolean
54
- when /uniqueidentifier/i then :string
66
+ when /int|bigint|smallint|tinyint/i then :integer
67
+ when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
68
+ when /float|double|decimal|money|real|smallmoney/i then :decimal
69
+ when /datetime|smalldatetime/i then :datetime
70
+ when /timestamp/i then :timestamp
71
+ when /time/i then :time
72
+ when /date/i then :date
73
+ when /text|ntext/i then :text
74
+ when /binary|image|varbinary/i then :binary
75
+ when /char|nchar|nvarchar|string|varchar/i then :string
76
+ when /bit/i then :boolean
77
+ when /uniqueidentifier/i then :string
55
78
  end
56
79
  end
57
80
 
81
+ def default_value(value)
82
+ return $1 if value =~ /^\(N?'(.*)'\)$/
83
+ value
84
+ end
85
+
58
86
  def type_cast(value)
59
87
  return nil if value.nil? || value == "(null)" || value == "(NULL)"
60
88
  case type
61
- when :string then unquote_string value
62
89
  when :integer then unquote(value).to_i rescue value ? 1 : 0
63
90
  when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
64
91
  when :decimal then self.class.value_to_decimal(unquote(value))
@@ -70,11 +97,11 @@ module ::JdbcSpec
70
97
  when :binary then unquote value
71
98
  else value
72
99
  end
100
+
73
101
  end
74
102
 
75
- # JRUBY-2011: Match balanced quotes and parenthesis - 'text',('text') or (text)
76
- def unquote_string(value)
77
- value.to_s.sub(/^\((.*)\)$/,'\1').sub(/^'(.*)'$/,'\1')
103
+ def is_utf8?
104
+ sql_type =~ /nvarchar|ntext|nchar/i
78
105
  end
79
106
 
80
107
  def unquote(value)
@@ -112,6 +139,7 @@ module ::JdbcSpec
112
139
  def self.string_to_binary(value)
113
140
  ''
114
141
  end
142
+
115
143
  end
116
144
 
117
145
  def quote(value, column = nil)
@@ -125,8 +153,10 @@ module ::JdbcSpec
125
153
  elsif column && [:integer, :float].include?(column.type)
126
154
  value = column.type == :integer ? value.to_i : value.to_f
127
155
  value.to_s
128
- else
156
+ elsif !column.respond_to?(:is_utf8?) || column.is_utf8?
129
157
  "N'#{quote_string(value)}'" # ' (for ruby-mode)
158
+ else
159
+ super
130
160
  end
131
161
  when TrueClass then '1'
132
162
  when FalseClass then '0'
@@ -154,14 +184,34 @@ module ::JdbcSpec
154
184
  quote false
155
185
  end
156
186
 
187
+ def add_limit_offset!(sql, options)
188
+ limit = options[:limit]
189
+ if limit
190
+ offset = (options[:offset] || 0).to_i
191
+ start_row = offset + 1
192
+ end_row = offset + limit.to_i
193
+ order = (options[:order] || determine_order_clause(sql))
194
+ sql.sub!(/ ORDER BY.*$/i, '')
195
+ find_select = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/i
196
+ whole, select, rest_of_query = find_select.match(sql).to_a
197
+ new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(ORDER BY #{order}) AS row_num, #{rest_of_query}"
198
+ new_sql << ") AS t WHERE t.row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
199
+ sql.replace(new_sql)
200
+ end
201
+ end
202
+
157
203
  def change_order_direction(order)
158
- order.split(",").collect {|fragment|
204
+ order.split(",").collect do |fragment|
159
205
  case fragment
160
206
  when /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
161
207
  when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
162
208
  else String.new(fragment).split(',').join(' DESC,') + ' DESC'
163
209
  end
164
- }.join(",")
210
+ end.join(",")
211
+ end
212
+
213
+ def supports_ddl_transactions?
214
+ true
165
215
  end
166
216
 
167
217
  def recreate_database(name)
@@ -179,159 +229,191 @@ module ::JdbcSpec
179
229
  execute "USE #{name}"
180
230
  end
181
231
 
182
- def rename_table(name, new_name)
183
- execute "EXEC sp_rename '#{name}', '#{new_name}'"
184
- end
232
+ def rename_table(name, new_name)
233
+ execute "EXEC sp_rename '#{name}', '#{new_name}'"
234
+ end
185
235
 
186
- # Adds a new column to the named table.
187
- # See TableDefinition#column for details of the options you can use.
188
- def add_column(table_name, column_name, type, options = {})
189
- add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
190
- add_column_options!(add_column_sql, options)
191
- # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
192
- # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
193
- execute(add_column_sql)
194
- end
236
+ # Adds a new column to the named table.
237
+ # See TableDefinition#column for details of the options you can use.
238
+ def add_column(table_name, column_name, type, options = {})
239
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
240
+ add_column_options!(add_column_sql, options)
241
+ # TODO: Add support to mimic date columns, using constraints to mark them as such in the database
242
+ # add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
243
+ execute(add_column_sql)
244
+ end
195
245
 
196
- def rename_column(table, column, new_column_name)
197
- execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
198
- end
246
+ def rename_column(table, column, new_column_name)
247
+ execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
248
+ end
199
249
 
200
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
201
- sql_commands = ["ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"]
202
- if options_include_default?(options)
203
- remove_default_constraint(table_name, column_name)
204
- sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{column_name}"
205
- end
206
- sql_commands.each {|c|
207
- execute(c)
208
- }
209
- end
210
- def change_column_default(table_name, column_name, default) #:nodoc:
211
- remove_default_constraint(table_name, column_name)
212
- execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default, column_name)} FOR #{column_name}"
213
- end
214
- def remove_column(table_name, column_name)
215
- remove_check_constraints(table_name, column_name)
216
- remove_default_constraint(table_name, column_name)
217
- execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
250
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
251
+ change_column_type(table_name, column_name, type, options)
252
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
253
+ end
254
+
255
+ def change_column_type(table_name, column_name, type, options = {}) #:nodoc:
256
+ sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
257
+ if options.has_key?(:null)
258
+ sql += (options[:null] ? " NULL" : " NOT NULL")
218
259
  end
219
-
220
- def remove_default_constraint(table_name, column_name)
221
- 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"
222
- defaults.each {|constraint|
223
- execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
224
- }
260
+ execute(sql)
261
+ end
262
+
263
+ def change_column_default(table_name, column_name, default) #:nodoc:
264
+ remove_default_constraint(table_name, column_name)
265
+ unless default.nil?
266
+ execute "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
225
267
  end
268
+ end
269
+
270
+ def remove_column(table_name, column_name)
271
+ remove_check_constraints(table_name, column_name)
272
+ remove_default_constraint(table_name, column_name)
273
+ execute "ALTER TABLE #{table_name} DROP COLUMN [#{column_name}]"
274
+ end
226
275
 
227
- def remove_check_constraints(table_name, column_name)
228
- # TODO remove all constraints in single method
229
- constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
230
- constraints.each do |constraint|
231
- execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
232
- end
233
- end
276
+ def remove_default_constraint(table_name, column_name)
277
+ 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"
278
+ defaults.each {|constraint|
279
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
280
+ }
281
+ end
234
282
 
235
- def remove_index(table_name, options = {})
236
- execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
283
+ def remove_check_constraints(table_name, column_name)
284
+ # TODO remove all constraints in single method
285
+ constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
286
+ constraints.each do |constraint|
287
+ execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
237
288
  end
289
+ end
238
290
 
291
+ def remove_index(table_name, options = {})
292
+ execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
293
+ end
239
294
 
240
- def columns(table_name, name = nil)
241
- return [] if table_name =~ /^information_schema\./i
242
- cc = super
243
- cc.each do |col|
244
- col.identity = true if col.sql_type =~ /identity/i
245
- col.is_special = true if col.sql_type =~ /text|ntext|image/i
246
- end
247
- cc
295
+ def columns(table_name, name = nil)
296
+ return [] if table_name =~ /^information_schema\./i
297
+ cc = super
298
+ cc.each do |col|
299
+ col.identity = true if col.sql_type =~ /identity/i
300
+ col.is_special = true if col.sql_type =~ /text|ntext|image/i
248
301
  end
302
+ cc
303
+ end
249
304
 
250
- def _execute(sql, name = nil)
251
- if sql.lstrip =~ /^insert/i
252
- if query_requires_identity_insert?(sql)
253
- table_name = get_table_name(sql)
254
- with_identity_insert_enabled(table_name) do
255
- id = @connection.execute_insert(sql)
256
- end
257
- else
258
- @connection.execute_insert(sql)
305
+ def _execute(sql, name = nil)
306
+ if sql.lstrip =~ /^insert/i
307
+ if query_requires_identity_insert?(sql)
308
+ table_name = get_table_name(sql)
309
+ with_identity_insert_enabled(table_name) do
310
+ id = @connection.execute_insert(sql)
259
311
  end
260
- elsif sql.lstrip =~ /^\(?\s*(select|show)/i
261
- repair_special_columns(sql)
262
- @connection.execute_query(sql)
263
312
  else
264
- @connection.execute_update(sql)
313
+ @connection.execute_insert(sql)
265
314
  end
315
+ elsif sql.lstrip =~ /^(create|exec)/i
316
+ @connection.execute_update(sql)
317
+ elsif sql.lstrip =~ /^\(?\s*(select|show)/i
318
+ repair_special_columns(sql)
319
+ @connection.execute_query(sql)
320
+ else
321
+ @connection.execute_update(sql)
266
322
  end
323
+ end
267
324
 
268
- #SELECT .. FOR UPDATE is not supported on Microsoft SQL Server
269
- def add_lock!(sql, options)
270
- sql
271
- end
325
+ #SELECT .. FOR UPDATE is not supported on Microsoft SQL Server
326
+ def add_lock!(sql, options)
327
+ sql
328
+ end
272
329
 
273
- private
274
- # Turns IDENTITY_INSERT ON for table during execution of the block
275
- # N.B. This sets the state of IDENTITY_INSERT to OFF after the
276
- # block has been executed without regard to its previous state
330
+ private
277
331
 
278
- def with_identity_insert_enabled(table_name, &block)
279
- set_identity_insert(table_name, true)
280
- yield
281
- ensure
282
- set_identity_insert(table_name, false)
283
- end
332
+ # Turns IDENTITY_INSERT ON for table during execution of the block
333
+ # N.B. This sets the state of IDENTITY_INSERT to OFF after the
334
+ # block has been executed without regard to its previous state
335
+ def with_identity_insert_enabled(table_name, &block)
336
+ set_identity_insert(table_name, true)
337
+ yield
338
+ ensure
339
+ set_identity_insert(table_name, false)
340
+ end
284
341
 
285
- def set_identity_insert(table_name, enable = true)
286
- execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
287
- rescue Exception => e
288
- raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
342
+ def set_identity_insert(table_name, enable = true)
343
+ execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
344
+ rescue Exception => e
345
+ raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
346
+ end
347
+
348
+ def get_table_name(sql)
349
+ if sql =~ /^\s*insert\s+into\s+([^\(\s,]+)\s*|^\s*update\s+([^\(\s,]+)\s*/i
350
+ $1
351
+ elsif sql =~ /from\s+([^\(\s,]+)\s*/i
352
+ $1
353
+ else
354
+ nil
289
355
  end
356
+ end
290
357
 
291
- def get_table_name(sql)
292
- if sql =~ /^\s*insert\s+into\s+([^\(\s,]+)\s*|^\s*update\s+([^\(\s,]+)\s*/i
293
- $1
294
- elsif sql =~ /from\s+([^\(\s,]+)\s*/i
295
- $1
296
- else
297
- nil
298
- end
358
+ def identity_column(table_name)
359
+ @table_columns = {} unless @table_columns
360
+ @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
361
+ @table_columns[table_name].each do |col|
362
+ return col.name if col.identity
299
363
  end
300
364
 
301
- def identity_column(table_name)
302
- @table_columns = {} unless @table_columns
303
- @table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
304
- @table_columns[table_name].each do |col|
305
- return col.name if col.identity
306
- end
365
+ return nil
366
+ end
307
367
 
308
- return nil
368
+ def query_requires_identity_insert?(sql)
369
+ table_name = get_table_name(sql)
370
+ id_column = identity_column(table_name)
371
+ if sql.strip =~ /insert into [^ ]+ ?\((.+?)\)/i
372
+ insert_columns = $1.split(/, */).map(&method(:unquote_column_name))
373
+ return table_name if insert_columns.include?(id_column)
309
374
  end
375
+ end
310
376
 
311
- def query_requires_identity_insert?(sql)
312
- table_name = get_table_name(sql)
313
- id_column = identity_column(table_name)
314
- sql =~ /\[#{id_column}\]/ ? table_name : nil
377
+ def unquote_column_name(name)
378
+ if name =~ /^\[.*\]$/
379
+ name[1..-2]
380
+ else
381
+ name
315
382
  end
383
+ end
316
384
 
317
- def get_special_columns(table_name)
318
- special = []
319
- @table_columns ||= {}
320
- @table_columns[table_name] ||= columns(table_name)
321
- @table_columns[table_name].each do |col|
322
- special << col.name if col.is_special
323
- end
324
- special
385
+ def get_special_columns(table_name)
386
+ special = []
387
+ @table_columns ||= {}
388
+ @table_columns[table_name] ||= columns(table_name)
389
+ @table_columns[table_name].each do |col|
390
+ special << col.name if col.is_special
325
391
  end
392
+ special
393
+ end
326
394
 
327
- def repair_special_columns(sql)
328
- special_cols = get_special_columns(get_table_name(sql))
329
- for col in special_cols.to_a
330
- sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
331
- sql.gsub!(/ORDER BY #{col.to_s}/i, '')
332
- end
333
- sql
395
+ def repair_special_columns(sql)
396
+ special_cols = get_special_columns(get_table_name(sql))
397
+ for col in special_cols.to_a
398
+ sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
399
+ sql.gsub!(/ORDER BY #{col.to_s}/i, '')
334
400
  end
401
+ sql
402
+ end
403
+
404
+ def determine_order_clause(sql)
405
+ return $1 if sql =~ /ORDER BY (.*)$/
406
+ sql =~ /FROM +(\w+?)\b/ || raise("can't determine table name")
407
+ table_name = $1
408
+ "#{table_name}.#{determine_primary_key(table_name)}"
409
+ end
410
+
411
+ def determine_primary_key(table_name)
412
+ primary_key = columns(table_name).detect { |column| column.primary || column.identity }
413
+ primary_key ? primary_key.name : "id"
335
414
  end
415
+
336
416
  end
417
+
418
+ end
337
419
 
@@ -1,5 +1,5 @@
1
1
  module JdbcAdapter
2
2
  module Version
3
- VERSION = "0.9.4"
3
+ VERSION = "0.9.5"
4
4
  end
5
5
  end
@@ -9,26 +9,33 @@ else
9
9
  task :test => [:test_mysql]
10
10
  end
11
11
 
12
- FileList['drivers/*'].each do |d|
13
- next unless File.directory?(d)
14
- driver = File.basename(d)
15
- Rake::TestTask.new("test_#{driver}") do |t|
16
- files = FileList["test/#{driver}*test.rb"]
17
- if driver == "derby"
12
+ def declare_test_task_for(adapter, options = {})
13
+ driver = options[:driver] || adapter
14
+ Rake::TestTask.new("test_#{adapter}") do |t|
15
+ files = FileList["test/#{adapter}*test.rb"]
16
+ if adapter == "derby"
18
17
  files << 'test/activerecord/connection_adapters/type_conversion_test.rb'
19
18
  end
20
19
  t.test_files = files
21
20
  t.libs = []
22
21
  if defined?(JRUBY_VERSION)
23
22
  t.ruby_opts << "-rjdbc/#{driver}"
24
- t.libs << "lib" << "#{d}/lib"
25
- t.libs.push *FileList["adapters/#{driver}*/lib"]
23
+ t.libs << "lib" << "drivers/#{driver}/lib"
24
+ t.libs.push *FileList["adapters/#{adapter}*/lib"]
26
25
  end
27
26
  t.libs << "test"
28
27
  t.verbose = true
29
28
  end
30
29
  end
31
30
 
31
+ declare_test_task_for :derby
32
+ declare_test_task_for :h2
33
+ declare_test_task_for :hsqldb
34
+ declare_test_task_for :mssql, :driver => :jtds
35
+ declare_test_task_for :mysql
36
+ declare_test_task_for :postgres
37
+ declare_test_task_for :sqlite3
38
+
32
39
  Rake::TestTask.new(:test_jdbc) do |t|
33
40
  t.test_files = FileList['test/generic_jdbc_connection_test.rb', 'test/jndi_callbacks_test.rb']
34
41
  t.libs << 'test' << 'drivers/mysql/lib'
@@ -43,7 +50,7 @@ task :test_postgresql => [:test_postgres]
43
50
  task :test_pgsql => [:test_postgres]
44
51
 
45
52
  # Ensure driver for these DBs is on your classpath
46
- %w(oracle db2 cachedb mssql informix).each do |d|
53
+ %w(oracle db2 cachedb informix).each do |d|
47
54
  Rake::TestTask.new("test_#{d}") do |t|
48
55
  t.test_files = FileList["test/#{d}_simple_test.rb"]
49
56
  t.libs = []
@@ -1,9 +1,9 @@
1
- config = {
1
+ MSSQL_CONFIG = {
2
2
  :username => 'blog',
3
3
  :password => '',
4
- :adapter => 'jdbc',
5
- :url => "jdbc:jtds:sqlserver://localhost:1433/weblog_development",
6
- :driver => 'net.sourceforge.jtds.jdbc.Driver'
4
+ :adapter => 'mssql',
5
+ :database => 'weblog_development'
7
6
  }
7
+ MSSQL_CONFIG[:host] = ENV['SQLHOST'] if ENV['SQLHOST']
8
8
 
9
- ActiveRecord::Base.establish_connection( config )
9
+ ActiveRecord::Base.establish_connection(MSSQL_CONFIG)
@@ -0,0 +1,24 @@
1
+ require 'abstract_db_create'
2
+ require 'db/mssql'
3
+
4
+ class MysqlDbCreateTest < Test::Unit::TestCase
5
+ include AbstractDbCreate
6
+
7
+ def db_config
8
+ MSSQL_CONFIG
9
+ end
10
+
11
+ def test_rake_db_create
12
+ begin
13
+ Rake::Task["db:create"].invoke
14
+ rescue => e
15
+ if e.message =~ /CREATE DATABASE permission denied/
16
+ puts "\nwarning: db:create test skipped; add 'dbcreator' role to user '#{db_config[:username]}' to run"
17
+ return
18
+ end
19
+ end
20
+ ActiveRecord::Base.establish_connection(db_config.merge(:database => "master"))
21
+ databases = ActiveRecord::Base.connection.select_rows("SELECT NAME FROM sys.sysdatabases").flatten
22
+ assert databases.include?(@db_name)
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ require 'jdbc_common'
2
+ require 'db/mssql'
3
+
4
+ class MsSQLIdentityInsertTest < Test::Unit::TestCase
5
+
6
+ include MigrationSetup
7
+
8
+ def test_enable_identity_insert_when_necessary
9
+ Entry.connection.execute("INSERT INTO entries([id], [title]) VALUES (333, 'Title')")
10
+ Entry.connection.execute("INSERT INTO entries([title], [id]) VALUES ('Title', 344)")
11
+ Entry.connection.execute("INSERT INTO entries(id, title) VALUES (666, 'Title')")
12
+ Entry.connection.execute("INSERT INTO entries(id, title) (SELECT id+123, title FROM entries)")
13
+ end
14
+
15
+ def test_dont_enable_identity_insert_when_unnecessary
16
+ Entry.connection.execute("INSERT INTO entries([title]) VALUES ('[id]')")
17
+ end
18
+
19
+ end
@@ -0,0 +1,58 @@
1
+ require 'jdbc_common'
2
+ require 'db/mssql'
3
+
4
+ ActiveRecord::Schema.verbose = false
5
+
6
+ class CreateArticles < ActiveRecord::Migration
7
+
8
+ def self.up
9
+ execute <<-SQL
10
+ CREATE TABLE articles (
11
+ [id] int NOT NULL IDENTITY(1, 1) PRIMARY KEY,
12
+ [title] VARCHAR(100),
13
+ [author] VARCHAR(60) DEFAULT 'anonymous',
14
+ [body] TEXT
15
+ )
16
+ SQL
17
+ end
18
+
19
+ def self.down
20
+ drop_table "articles"
21
+ end
22
+
23
+ end
24
+
25
+ class Article < ActiveRecord::Base
26
+ end
27
+
28
+ class MsSQLLegacyTypesTest < Test::Unit::TestCase
29
+
30
+ def setup
31
+ CreateArticles.up
32
+ @connection = ActiveRecord::Base.connection
33
+ end
34
+
35
+ def teardown
36
+ CreateArticles.down
37
+ ActiveRecord::Base.clear_active_connections!
38
+ end
39
+
40
+ def test_varchar_column
41
+ Article.create!(:title => "Blah blah")
42
+ article = Article.first
43
+ assert_equal("Blah blah", article.title)
44
+ end
45
+
46
+ SAMPLE_TEXT = "Lorem ipsum dolor sit amet ..."
47
+
48
+ def test_text_column
49
+ Article.create!(:body => SAMPLE_TEXT)
50
+ article = Article.first
51
+ assert_equal(SAMPLE_TEXT, article.body)
52
+ end
53
+
54
+ def test_varchar_default_value
55
+ assert_equal("anonymous", Article.new.author)
56
+ end
57
+
58
+ end
@@ -0,0 +1,108 @@
1
+ require 'jdbc_common'
2
+ require 'db/mssql'
3
+
4
+ ActiveRecord::Schema.verbose = false
5
+
6
+ class CreateLongShips < ActiveRecord::Migration
7
+
8
+ def self.up
9
+ create_table "long_ships", :force => true do |t|
10
+ t.string "name", :limit => 50, :null => false
11
+ t.integer "width", :default => 123
12
+ t.integer "length", :default => 456
13
+ end
14
+ end
15
+
16
+ def self.down
17
+ drop_table "long_ships"
18
+ end
19
+
20
+ end
21
+
22
+ class LongShip < ActiveRecord::Base
23
+ has_many :vikings
24
+ end
25
+
26
+ class CreateVikings < ActiveRecord::Migration
27
+
28
+ def self.up
29
+ create_table "vikings", :force => true do |t|
30
+ t.integer "long_ship_id", :null => false
31
+ t.string "name", :limit => 50, :default => "Sven"
32
+ end
33
+ end
34
+
35
+ def self.down
36
+ drop_table "vikings"
37
+ end
38
+
39
+ end
40
+
41
+ class Viking < ActiveRecord::Base
42
+ belongs_to :long_ship
43
+ end
44
+
45
+ class MsSQLLimitOffsetTest < Test::Unit::TestCase
46
+
47
+ def setup
48
+ CreateLongShips.up
49
+ CreateVikings.up
50
+ @connection = ActiveRecord::Base.connection
51
+ end
52
+
53
+ def teardown
54
+ CreateVikings.down
55
+ CreateLongShips.down
56
+ ActiveRecord::Base.clear_active_connections!
57
+ end
58
+
59
+ def test_limit_and_offset
60
+ %w(one two three four five six seven eight).each do |name|
61
+ LongShip.create!(:name => name)
62
+ end
63
+ ship_names = LongShip.find(:all, :offset => 2, :limit => 3).map(&:name)
64
+ assert_equal(%w(three four five), ship_names)
65
+ end
66
+
67
+ def test_limit_and_offset_with_order
68
+ %w(one two three four five six seven eight).each do |name|
69
+ LongShip.create!(:name => name)
70
+ end
71
+ ship_names = LongShip.find(:all, :order => "name", :offset => 4, :limit => 2).map(&:name)
72
+ assert_equal(%w(seven six), ship_names)
73
+ end
74
+
75
+ # TODO: work out how to fix DISTINCT support without breaking :include
76
+ # def test_limit_and_offset_with_distinct
77
+ # %w(c a b a b a c d c d).each do |name|
78
+ # LongShip.create!(:name => name)
79
+ # end
80
+ # ship_names = LongShip.find(:all, :select => "DISTINCT name", :order => "name", :offset => 1, :limit => 2).map(&:name)
81
+ # assert_equal(%w(b c), ship_names)
82
+ # end
83
+
84
+ def test_limit_and_offset_with_include
85
+ skei = LongShip.create!(:name => "Skei")
86
+ skei.vikings.create!(:name => "Bob")
87
+ skei.vikings.create!(:name => "Ben")
88
+ skei.vikings.create!(:name => "Basil")
89
+ ships = Viking.find(:all, :include => :long_ship, :offset => 1, :limit => 2)
90
+ assert_equal(2, ships.size)
91
+ end
92
+
93
+ def test_limit_and_offset_with_include_and_order
94
+
95
+ boat1 = LongShip.create!(:name => "1-Skei")
96
+ boat2 = LongShip.create!(:name => "2-Skei")
97
+
98
+ boat1.vikings.create!(:name => "Adam")
99
+ boat2.vikings.create!(:name => "Ben")
100
+ boat1.vikings.create!(:name => "Carl")
101
+ boat2.vikings.create!(:name => "Donald")
102
+
103
+ vikings = Viking.find(:all, :include => :long_ship, :order => "long_ships.name, vikings.name", :offset => 0, :limit => 3)
104
+ assert_equal(["Adam", "Carl", "Ben"], vikings.map(&:name))
105
+
106
+ end
107
+
108
+ end
@@ -0,0 +1,18 @@
1
+ #! /usr/bin/env jruby
2
+
3
+ require 'jdbc_common'
4
+ require 'db/mssql'
5
+
6
+ class MsSQLMultibyteTest < Test::Unit::TestCase
7
+
8
+ include MultibyteTestMethods
9
+
10
+ def test_select_multibyte_string
11
+ Entry.create!(:title => 'テスト', :content => '本文')
12
+ entry = Entry.find(:last)
13
+ assert_equal "テスト", entry.title
14
+ assert_equal "本文", entry.content
15
+ assert_equal entry, Entry.find_by_title("テスト")
16
+ end
17
+
18
+ end
@@ -2,5 +2,48 @@ require 'jdbc_common'
2
2
  require 'db/mssql'
3
3
 
4
4
  class MsSQLSimpleTest < Test::Unit::TestCase
5
+
5
6
  include SimpleTestMethods
7
+
8
+ # MS SQL 2005 doesn't have a DATE class, only TIMESTAMP.
9
+ undef_method :test_save_date
10
+
11
+ # String comparisons are insensitive by default
12
+ undef_method :test_validates_uniqueness_of_strings_case_sensitive
13
+
14
+ def test_does_not_munge_quoted_strings
15
+ example_quoted_values = [%{'quoted'}, %{D'oh!}]
16
+ example_quoted_values.each do |value|
17
+ entry = Entry.create!(:title => value)
18
+ entry.reload
19
+ assert_equal(value, entry.title)
20
+ end
21
+ end
22
+
23
+ def test_change_column_default
24
+
25
+ Entry.connection.change_column "entries", "title", :string, :default => "new default"
26
+ Entry.reset_column_information
27
+ assert_equal("new default", Entry.new.title)
28
+
29
+ Entry.connection.change_column "entries", "title", :string, :default => nil
30
+ Entry.reset_column_information
31
+ assert_equal(nil, Entry.new.title)
32
+
33
+ end
34
+
35
+ def test_change_column_nullability
36
+
37
+ Entry.connection.change_column "entries", "title", :string, :null => true
38
+ Entry.reset_column_information
39
+ title_column = Entry.columns.find { |c| c.name == "title" }
40
+ assert(title_column.null)
41
+
42
+ Entry.connection.change_column "entries", "title", :string, :null => false
43
+ Entry.reset_column_information
44
+ title_column = Entry.columns.find { |c| c.name == "title" }
45
+ assert(!title_column.null)
46
+
47
+ end
48
+
6
49
  end
@@ -305,8 +305,8 @@ module SimpleTestMethods
305
305
  end
306
306
 
307
307
  def test_validates_uniqueness_of_strings_case_sensitive
308
- # MySQL string cmps are case-insensitive by default, so skip this test
309
- return if ActiveRecord::Base.connection.config[:adapter] =~ /mysql/
308
+ # In MySQL and MsSQL, string cmps are case-insensitive by default, so skip this test
309
+ return if ActiveRecord::Base.connection.config[:adapter] =~ /m[sy]sql/
310
310
 
311
311
  name_lower = ValidatesUniquenessOfString.new(:cs_string => "name", :ci_string => '1')
312
312
  name_lower.save!
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 9
8
- - 4
9
- version: 0.9.4
8
+ - 5
9
+ version: 0.9.5
10
10
  platform: java
11
11
  authors:
12
12
  - Nick Sieger, Ola Bini and JRuby contributors
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-05 00:00:00 -05:00
17
+ date: 2010-04-22 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -49,6 +49,7 @@ files:
49
49
  - lib/active_record/connection_adapters/jdbc_adapter.rb
50
50
  - lib/active_record/connection_adapters/jdbc_adapter_spec.rb
51
51
  - lib/active_record/connection_adapters/jndi_adapter.rb
52
+ - lib/active_record/connection_adapters/mssql_adapter.rb
52
53
  - lib/active_record/connection_adapters/mysql_adapter.rb
53
54
  - lib/active_record/connection_adapters/oracle_adapter.rb
54
55
  - lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -95,6 +96,11 @@ files:
95
96
  - test/jndi_test.rb
96
97
  - test/manualTestDatabase.rb
97
98
  - test/minirunit.rb
99
+ - test/mssql_db_create_test.rb
100
+ - test/mssql_identity_insert_test.rb
101
+ - test/mssql_legacy_types_test.rb
102
+ - test/mssql_limit_offset_test.rb
103
+ - test/mssql_multibyte_test.rb
98
104
  - test/mssql_simple_test.rb
99
105
  - test/mysql_db_create_test.rb
100
106
  - test/mysql_info_test.rb