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.
- 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
|