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.
- data/History.txt +11 -0
- data/Manifest.txt +6 -0
- data/README.txt +2 -1
- data/lib/active_record/connection_adapters/mssql_adapter.rb +13 -0
- data/lib/jdbc_adapter/jdbc.rake +2 -1
- data/lib/jdbc_adapter/jdbc_adapter_internal.jar +0 -0
- data/lib/jdbc_adapter/jdbc_mssql.rb +228 -146
- data/lib/jdbc_adapter/version.rb +1 -1
- data/rakelib/test.rake +16 -9
- data/test/db/mssql.rb +5 -5
- data/test/mssql_db_create_test.rb +24 -0
- data/test/mssql_identity_insert_test.rb +19 -0
- data/test/mssql_legacy_types_test.rb +58 -0
- data/test/mssql_limit_offset_test.rb +108 -0
- data/test/mssql_multibyte_test.rb +18 -0
- data/test/mssql_simple_test.rb +43 -0
- data/test/simple.rb +2 -2
- metadata +9 -3
data/History.txt
CHANGED
@@ -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
|
data/Manifest.txt
CHANGED
@@ -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
|
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'
|
data/lib/jdbc_adapter/jdbc.rake
CHANGED
@@ -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
|
Binary file
|
@@ -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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
232
|
+
def rename_table(name, new_name)
|
233
|
+
execute "EXEC sp_rename '#{name}', '#{new_name}'"
|
234
|
+
end
|
185
235
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
197
|
-
|
198
|
-
|
246
|
+
def rename_column(table, column, new_column_name)
|
247
|
+
execute "EXEC sp_rename '#{table}.#{column}', '#{new_column_name}'"
|
248
|
+
end
|
199
249
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
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
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
236
|
-
|
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
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
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
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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.
|
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
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
325
|
+
#SELECT .. FOR UPDATE is not supported on Microsoft SQL Server
|
326
|
+
def add_lock!(sql, options)
|
327
|
+
sql
|
328
|
+
end
|
272
329
|
|
273
|
-
|
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
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
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
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
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
|
-
|
302
|
-
|
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
|
-
|
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
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
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
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
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
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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
|
|
data/lib/jdbc_adapter/version.rb
CHANGED
data/rakelib/test.rake
CHANGED
@@ -9,26 +9,33 @@ else
|
|
9
9
|
task :test => [:test_mysql]
|
10
10
|
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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" << "
|
25
|
-
t.libs.push *FileList["adapters/#{
|
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
|
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 = []
|
data/test/db/mssql.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
MSSQL_CONFIG = {
|
2
2
|
:username => 'blog',
|
3
3
|
:password => '',
|
4
|
-
:adapter => '
|
5
|
-
:
|
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(
|
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
|
data/test/mssql_simple_test.rb
CHANGED
@@ -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
|
data/test/simple.rb
CHANGED
@@ -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] =~ /
|
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
|
-
-
|
9
|
-
version: 0.9.
|
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-
|
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
|