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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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