activerecord-jdbc-adapter 1.2.8 → 1.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -1
- data/Gemfile +2 -1
- data/Gemfile.lock +5 -6
- data/History.txt +11 -0
- data/gemfiles/rails23.gemfile +2 -1
- data/gemfiles/rails23.gemfile.lock +5 -2
- data/gemfiles/rails30.gemfile +2 -1
- data/gemfiles/rails30.gemfile.lock +5 -2
- data/gemfiles/rails31.gemfile +2 -1
- data/gemfiles/rails31.gemfile.lock +5 -2
- data/gemfiles/rails32.gemfile +2 -1
- data/gemfiles/rails32.gemfile.lock +5 -2
- data/lib/arel/engines/sql/compilers/mssql_compiler.rb +1 -1
- data/lib/arel/visitors/sql_server.rb +4 -4
- data/lib/arjdbc/db2/adapter.rb +14 -3
- data/lib/arjdbc/discover.rb +1 -1
- data/lib/arjdbc/jdbc/adapter.rb +1 -0
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/connection.rb +73 -63
- data/lib/arjdbc/jdbc/extension.rb +1 -1
- data/lib/arjdbc/mssql.rb +3 -0
- data/lib/arjdbc/mssql/adapter.rb +132 -115
- data/lib/arjdbc/mssql/connection_methods.rb +1 -1
- data/lib/arjdbc/mssql/limit_helpers.rb +62 -66
- data/lib/arjdbc/mssql/lock_helpers.rb +2 -2
- data/lib/arjdbc/mssql/tsql_methods.rb +58 -0
- data/lib/arjdbc/mssql/utils.rb +53 -0
- data/lib/arjdbc/oracle/adapter.rb +61 -39
- data/lib/arjdbc/sqlite3/adapter.rb +3 -6
- data/lib/arjdbc/version.rb +1 -1
- data/src/java/arjdbc/jdbc/AdapterJavaService.java +4 -2
- data/src/java/arjdbc/mssql/MSSQLModule.java +70 -0
- data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +128 -0
- data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +25 -112
- data/test/db/mssql.rb +8 -8
- data/test/db2_simple_test.rb +7 -0
- data/test/models/entry.rb +2 -1
- data/test/mssql_binary_test.rb +6 -0
- data/test/mssql_db_create_test.rb +5 -2
- data/test/mssql_identity_insert_test.rb +1 -2
- data/test/mssql_ignore_system_views_test.rb +5 -5
- data/test/mssql_limit_offset_test.rb +51 -55
- data/test/mssql_multibyte_test.rb +1 -2
- data/test/mssql_row_locking_test.rb +1 -1
- data/test/mssql_simple_test.rb +6 -10
- data/test/{mssql_row_locking_sql_test.rb → mssql_test.rb} +110 -18
- data/test/mysql_db_create_test.rb +13 -7
- data/test/oracle_simple_test.rb +18 -0
- data/test/postgres_db_create_test.rb +26 -13
- data/test/simple.rb +45 -15
- data/test/sqlite3_schema_dump_test.rb +6 -0
- data/test/sqlite3_type_conversion_test.rb +20 -17
- data/test/test_helper.rb +44 -2
- metadata +9 -4
- data/lib/arjdbc/mssql/tsql_helper.rb +0 -53
data/lib/arjdbc/mssql.rb
CHANGED
data/lib/arjdbc/mssql/adapter.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
require 'strscan'
|
2
|
-
require 'arjdbc/mssql/
|
2
|
+
require 'arjdbc/mssql/utils'
|
3
|
+
require 'arjdbc/mssql/tsql_methods'
|
3
4
|
require 'arjdbc/mssql/limit_helpers'
|
4
5
|
require 'arjdbc/mssql/lock_helpers'
|
5
6
|
require 'arjdbc/jdbc/serialized_attributes_helper'
|
6
7
|
|
7
8
|
module ArJdbc
|
8
|
-
module
|
9
|
+
module MSSQL
|
10
|
+
include Utils
|
9
11
|
include TSqlMethods
|
10
|
-
include LimitHelpers
|
11
12
|
|
12
13
|
@@_lob_callback_added = nil
|
13
14
|
|
14
|
-
def self.extended(
|
15
|
+
def self.extended(base)
|
15
16
|
unless @@_lob_callback_added
|
16
17
|
ActiveRecord::Base.class_eval do
|
17
18
|
def after_save_with_mssql_lob
|
@@ -31,51 +32,51 @@ module ArJdbc
|
|
31
32
|
ActiveRecord::Base.after_save :after_save_with_mssql_lob
|
32
33
|
@@_lob_callback_added = true
|
33
34
|
end
|
34
|
-
|
35
|
+
|
36
|
+
if ( version = base.sqlserver_version ) == '2000'
|
37
|
+
extend LimitHelpers::SqlServer2000AddLimitOffset
|
38
|
+
else
|
39
|
+
extend LimitHelpers::SqlServerAddLimitOffset
|
40
|
+
end
|
41
|
+
base.config[:sqlserver_version] ||= version
|
35
42
|
end
|
36
43
|
|
37
44
|
def self.column_selector
|
38
|
-
[/sqlserver|tds|Microsoft SQL/i, lambda {|cfg,
|
45
|
+
[ /sqlserver|tds|Microsoft SQL/i, lambda { |cfg, column| column.extend(::ArJdbc::MSSQL::Column) } ]
|
39
46
|
end
|
40
47
|
|
41
48
|
def self.jdbc_connection_class
|
42
|
-
::ActiveRecord::ConnectionAdapters::
|
49
|
+
::ActiveRecord::ConnectionAdapters::MSSQLJdbcConnection
|
43
50
|
end
|
44
51
|
|
45
52
|
def self.arel2_visitors(config)
|
46
53
|
require 'arel/visitors/sql_server'
|
47
|
-
|
48
|
-
|
54
|
+
visitors = config[:sqlserver_version] == '2000' ?
|
55
|
+
::Arel::Visitors::SQLServer2000 : ::Arel::Visitors::SQLServer
|
56
|
+
{ 'mssql' => visitors, 'jdbcmssql' => visitors, 'sqlserver' => visitors }
|
49
57
|
end
|
50
58
|
|
51
59
|
def sqlserver_version
|
52
|
-
@sqlserver_version ||=
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
config[:sqlserver_version] = version = sqlserver_version
|
57
|
-
if version == "2000"
|
58
|
-
extend LimitHelpers::SqlServer2000AddLimitOffset
|
59
|
-
else
|
60
|
-
extend LimitHelpers::SqlServerAddLimitOffset
|
60
|
+
@sqlserver_version ||= begin
|
61
|
+
config_version = config[:sqlserver_version]
|
62
|
+
config_version ? config_version.to_s :
|
63
|
+
select_value("SELECT @@version")[/Microsoft SQL Server\s+(\d{4})/, 1]
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
64
|
-
def modify_types(
|
65
|
-
super(
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
tp[:text] = {:name => "NTEXT"}
|
67
|
+
def modify_types(types) #:nodoc:
|
68
|
+
super(types)
|
69
|
+
types[:string] = { :name => "NVARCHAR", :limit => 255 }
|
70
|
+
if sqlserver_2000?
|
71
|
+
types[:text] = { :name => "NTEXT" }
|
70
72
|
else
|
71
|
-
|
73
|
+
types[:text] = { :name => "NVARCHAR(MAX)" }
|
72
74
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
tp
|
75
|
+
types[:primary_key] = "int NOT NULL IDENTITY(1, 1) PRIMARY KEY"
|
76
|
+
types[:integer][:limit] = nil
|
77
|
+
types[:boolean] = { :name => "bit" }
|
78
|
+
types[:binary] = { :name => "image" }
|
79
|
+
types
|
79
80
|
end
|
80
81
|
|
81
82
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
@@ -88,12 +89,12 @@ module ArJdbc
|
|
88
89
|
# MSSQL Server 2000 is skipped here because I don't know how it will behave.
|
89
90
|
#
|
90
91
|
# See: http://msdn.microsoft.com/en-us/library/ms186939.aspx
|
91
|
-
if type.to_s == 'string'
|
92
|
+
if type.to_s == 'string' && limit == 1073741823 && ! sqlserver_2000?
|
92
93
|
'NVARCHAR(MAX)'
|
93
94
|
elsif %w( boolean date datetime ).include?(type.to_s)
|
94
|
-
super(type)
|
95
|
+
super(type) # cannot specify limit/precision/scale with these types
|
95
96
|
else
|
96
|
-
super
|
97
|
+
super # TSqlMethods#type_to_sql
|
97
98
|
end
|
98
99
|
end
|
99
100
|
|
@@ -207,52 +208,47 @@ module ArJdbc
|
|
207
208
|
# column, so we include Integer here.
|
208
209
|
when String, ActiveSupport::Multibyte::Chars, Integer
|
209
210
|
value = value.to_s
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
value.to_s
|
215
|
-
elsif
|
211
|
+
column_type = column && column.type
|
212
|
+
if column_type == :binary
|
213
|
+
"'#{quote_string(ArJdbc::MSSQL::Column.string_to_binary(value))}'" # ' (for ruby-mode)
|
214
|
+
elsif column_type == :integer
|
215
|
+
value.to_i.to_s
|
216
|
+
elsif column_type == :float
|
217
|
+
value.to_f.to_s
|
218
|
+
elsif ! column.respond_to?(:is_utf8?) || column.is_utf8?
|
216
219
|
"N'#{quote_string(value)}'" # ' (for ruby-mode)
|
217
220
|
else
|
218
221
|
super
|
219
222
|
end
|
220
223
|
when TrueClass then '1'
|
221
224
|
when FalseClass then '0'
|
222
|
-
else
|
225
|
+
else super
|
223
226
|
end
|
224
227
|
end
|
225
228
|
|
226
|
-
def quote_string(string)
|
227
|
-
string.gsub(/\'/, "''")
|
228
|
-
end
|
229
|
-
|
230
229
|
def quote_table_name(name)
|
231
230
|
quote_column_name(name)
|
232
231
|
end
|
233
232
|
|
234
233
|
def quote_column_name(name)
|
235
|
-
"[#{name}]"
|
236
|
-
|
237
|
-
|
238
|
-
def quoted_true
|
239
|
-
quote true
|
240
|
-
end
|
241
|
-
|
242
|
-
def quoted_false
|
243
|
-
quote false
|
234
|
+
name.to_s.split('.').map do |n| # "[#{name}]"
|
235
|
+
n =~ /^\[.*\]$/ ? n : "[#{n.gsub(']', ']]')}]"
|
236
|
+
end.join('.')
|
244
237
|
end
|
245
238
|
|
246
|
-
|
247
|
-
|
239
|
+
ADAPTER_NAME = 'MSSQL'
|
240
|
+
|
241
|
+
def adapter_name # :nodoc:
|
242
|
+
ADAPTER_NAME
|
248
243
|
end
|
249
244
|
|
250
245
|
def change_order_direction(order)
|
246
|
+
asc, desc = /\bASC\b/i, /\bDESC\b/i
|
251
247
|
order.split(",").collect do |fragment|
|
252
248
|
case fragment
|
253
|
-
when
|
254
|
-
when
|
255
|
-
else
|
249
|
+
when desc then fragment.gsub(desc, "ASC")
|
250
|
+
when asc then fragment.gsub(asc, "DESC")
|
251
|
+
else "#{fragment.split(',').join(' DESC,')} DESC"
|
256
252
|
end
|
257
253
|
end.join(",")
|
258
254
|
end
|
@@ -271,7 +267,7 @@ module ArJdbc
|
|
271
267
|
execute "DROP DATABASE #{name}"
|
272
268
|
end
|
273
269
|
|
274
|
-
def create_database(name, options={})
|
270
|
+
def create_database(name, options = {})
|
275
271
|
execute "CREATE DATABASE #{name}"
|
276
272
|
execute "USE #{name}"
|
277
273
|
end
|
@@ -331,18 +327,31 @@ module ArJdbc
|
|
331
327
|
|
332
328
|
def remove_default_constraint(table_name, column_name)
|
333
329
|
clear_cached_table(table_name)
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
330
|
+
if sqlserver_2000?
|
331
|
+
# NOTE: since SQLServer 2005 these are provided as sys.sysobjects etc.
|
332
|
+
# but only due backwards-compatibility views and should be avoided ...
|
333
|
+
defaults = select_values "SELECT d.name" <<
|
334
|
+
" FROM sysobjects d, syscolumns c, sysobjects t" <<
|
335
|
+
" WHERE c.cdefault = d.id AND c.name = '#{column_name}'" <<
|
336
|
+
" AND t.name = '#{table_name}' AND c.id = t.id"
|
337
|
+
else
|
338
|
+
defaults = select_values "SELECT d.name FROM sys.tables t" <<
|
339
|
+
" JOIN sys.default_constraints d ON d.parent_object_id = t.object_id" <<
|
340
|
+
" JOIN sys.columns c ON c.object_id = t.object_id AND c.column_id = d.parent_column_id" <<
|
341
|
+
" WHERE t.name = '#{table_name}' AND c.name = '#{column_name}'"
|
342
|
+
end
|
343
|
+
defaults.each do |def_name|
|
344
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{def_name}"
|
345
|
+
end
|
338
346
|
end
|
339
347
|
|
340
348
|
def remove_check_constraints(table_name, column_name)
|
341
349
|
clear_cached_table(table_name)
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
350
|
+
constraints = select_values "SELECT constraint_name" <<
|
351
|
+
" FROM information_schema.constraint_column_usage" <<
|
352
|
+
" WHERE table_name = '#{table_name}' AND column_name = '#{column_name}'"
|
353
|
+
constraints.each do |constraint_name|
|
354
|
+
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint_name}"
|
346
355
|
end
|
347
356
|
end
|
348
357
|
|
@@ -350,22 +359,27 @@ module ArJdbc
|
|
350
359
|
execute "DROP INDEX #{table_name}.#{index_name(table_name, options)}"
|
351
360
|
end
|
352
361
|
|
362
|
+
def table_exists?(name)
|
363
|
+
!! ( jdbc_columns(name) rescue nil )
|
364
|
+
end
|
365
|
+
|
366
|
+
SKIP_COLUMNS_TABLE_NAMES_RE = /^information_schema\./i # :nodoc:
|
367
|
+
|
353
368
|
def columns(table_name, name = nil)
|
354
|
-
# It's possible for table_name to be an empty string, or nil, if something
|
355
|
-
# which doesn't involve a table.
|
369
|
+
# It's possible for table_name to be an empty string, or nil, if something
|
370
|
+
# attempts to issue SQL which doesn't involve a table.
|
371
|
+
# IE. "SELECT 1" or "SELECT * FROM someFunction()".
|
356
372
|
return [] if table_name.blank?
|
357
|
-
|
358
|
-
|
359
|
-
# Remove []'s from around the table name, valid in a select statement, but not when matching metadata.
|
360
|
-
table_name = table_name.gsub(/[\[\]]/, '')
|
373
|
+
|
374
|
+
table_name = unquote_table_name(table_name)
|
361
375
|
|
362
|
-
return [] if table_name =~
|
363
|
-
|
364
|
-
unless @table_columns[table_name]
|
376
|
+
return [] if table_name =~ SKIP_COLUMNS_TABLE_NAMES_RE
|
377
|
+
|
378
|
+
unless (@table_columns ||= {})[table_name]
|
365
379
|
@table_columns[table_name] = super
|
366
|
-
@table_columns[table_name].each do |
|
367
|
-
|
368
|
-
|
380
|
+
@table_columns[table_name].each do |column|
|
381
|
+
column.identity = true if column.sql_type =~ /identity/i
|
382
|
+
column.is_special = true if column.sql_type =~ /text|ntext|image|xml/i
|
369
383
|
end
|
370
384
|
end
|
371
385
|
@table_columns[table_name]
|
@@ -374,7 +388,7 @@ module ArJdbc
|
|
374
388
|
# Turns IDENTITY_INSERT ON for table during execution of the block
|
375
389
|
# N.B. This sets the state of IDENTITY_INSERT to OFF after the
|
376
390
|
# block has been executed without regard to its previous state
|
377
|
-
def with_identity_insert_enabled(table_name
|
391
|
+
def with_identity_insert_enabled(table_name)
|
378
392
|
set_identity_insert(table_name, true)
|
379
393
|
yield
|
380
394
|
ensure
|
@@ -384,16 +398,17 @@ module ArJdbc
|
|
384
398
|
def set_identity_insert(table_name, enable = true)
|
385
399
|
execute "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
|
386
400
|
rescue Exception => e
|
387
|
-
raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned
|
401
|
+
raise ActiveRecord::ActiveRecordError, "IDENTITY_INSERT could not be turned" +
|
402
|
+
" #{enable ? 'ON' : 'OFF'} for table #{table_name} due : #{e.inspect}"
|
388
403
|
end
|
389
404
|
|
390
405
|
def identity_column(table_name)
|
391
|
-
columns(table_name)
|
392
|
-
return
|
406
|
+
for column in columns(table_name)
|
407
|
+
return column.name if column.identity
|
393
408
|
end
|
394
|
-
|
409
|
+
nil
|
395
410
|
end
|
396
|
-
|
411
|
+
|
397
412
|
def query_requires_identity_insert?(sql)
|
398
413
|
table_name = get_table_name(sql)
|
399
414
|
id_column = identity_column(table_name)
|
@@ -402,32 +417,7 @@ module ArJdbc
|
|
402
417
|
return table_name if insert_columns.include?(id_column)
|
403
418
|
end
|
404
419
|
end
|
405
|
-
|
406
|
-
def unquote_column_name(name)
|
407
|
-
if name =~ /^\[.*\]$/
|
408
|
-
name[1..-2]
|
409
|
-
else
|
410
|
-
name
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
def get_special_columns(table_name)
|
415
|
-
special = []
|
416
|
-
columns(table_name).each do |col|
|
417
|
-
special << col.name if col.is_special
|
418
|
-
end
|
419
|
-
special
|
420
|
-
end
|
421
|
-
|
422
|
-
def repair_special_columns(sql)
|
423
|
-
special_cols = get_special_columns(get_table_name(sql))
|
424
|
-
for col in special_cols.to_a
|
425
|
-
sql.gsub!(Regexp.new(" #{col.to_s} = "), " #{col.to_s} LIKE ")
|
426
|
-
sql.gsub!(/ORDER BY #{col.to_s}/i, '')
|
427
|
-
end
|
428
|
-
sql
|
429
|
-
end
|
430
|
-
|
420
|
+
|
431
421
|
def determine_order_clause(sql)
|
432
422
|
return $1 if sql =~ /ORDER BY (.*)$/
|
433
423
|
table_name = get_table_name(sql)
|
@@ -437,7 +427,8 @@ module ArJdbc
|
|
437
427
|
def determine_primary_key(table_name)
|
438
428
|
primary_key = columns(table_name).detect { |column| column.primary || column.identity }
|
439
429
|
return primary_key.name if primary_key
|
440
|
-
# Look for an id column
|
430
|
+
# Look for an id column and return it,
|
431
|
+
# without changing case, to cover DBs with a case-sensitive collation :
|
441
432
|
columns(table_name).each { |column| return column.name if column.name =~ /^id$/i }
|
442
433
|
# Give up and provide something which is going to crash almost certainly
|
443
434
|
columns(table_name)[0].name
|
@@ -448,11 +439,15 @@ module ArJdbc
|
|
448
439
|
end
|
449
440
|
|
450
441
|
def reset_column_information
|
451
|
-
@table_columns = nil
|
442
|
+
@table_columns = nil if defined? @table_columns
|
452
443
|
end
|
453
444
|
|
454
445
|
private
|
455
446
|
|
447
|
+
def sqlserver_2000?
|
448
|
+
sqlserver_version <= '2000'
|
449
|
+
end
|
450
|
+
|
456
451
|
def _execute(sql, name = nil)
|
457
452
|
# Match the start of the SQL to determine appropriate behavior.
|
458
453
|
# Be aware of multi-line SQL which might begin with 'create stored_proc'
|
@@ -469,13 +464,35 @@ module ArJdbc
|
|
469
464
|
@connection.execute_insert(sql)
|
470
465
|
end
|
471
466
|
elsif sql.lstrip =~ /\A\(?\s*(select|show)/i # self.class.select?(sql)
|
472
|
-
repair_special_columns(sql)
|
467
|
+
sql = repair_special_columns(sql)
|
473
468
|
@connection.execute_query(sql)
|
474
469
|
else # sql.lstrip =~ /\A(create|exec)/i
|
475
470
|
@connection.execute_update(sql)
|
476
471
|
end
|
477
472
|
end
|
478
473
|
|
474
|
+
def repair_special_columns(sql)
|
475
|
+
qualified_table_name = get_table_name(sql, true)
|
476
|
+
special_columns = get_special_columns(qualified_table_name)
|
477
|
+
for column in special_columns.to_a
|
478
|
+
sql.gsub!(Regexp.new(" #{column} = "), " #{column} LIKE ")
|
479
|
+
sql.gsub!(/ORDER BY #{column.to_s}/i, '')
|
480
|
+
end if special_columns
|
481
|
+
sql
|
482
|
+
end
|
483
|
+
|
484
|
+
def get_special_columns(qualified_table_name)
|
485
|
+
special = []
|
486
|
+
columns(qualified_table_name).each do |column|
|
487
|
+
special << column.name if column.is_special
|
488
|
+
end
|
489
|
+
special
|
490
|
+
end
|
491
|
+
|
492
|
+
def sqlserver_2000?
|
493
|
+
sqlserver_version <= '2000'
|
494
|
+
end
|
495
|
+
|
479
496
|
end
|
480
497
|
end
|
481
498
|
|
@@ -13,7 +13,7 @@ class ActiveRecord::Base
|
|
13
13
|
config[:host] ||= "localhost"
|
14
14
|
config[:port] ||= 1433
|
15
15
|
config[:driver] ||= defined?(::Jdbc::JTDS.driver_name) ? ::Jdbc::JTDS.driver_name : 'net.sourceforge.jtds.jdbc.Driver'
|
16
|
-
config[:adapter_spec] = ::ArJdbc::
|
16
|
+
config[:adapter_spec] = ::ArJdbc::MSSQL
|
17
17
|
|
18
18
|
config[:url] ||= begin
|
19
19
|
url = "jdbc:jtds:sqlserver://#{config[:host]}:#{config[:port]}/#{config[:database]}"
|
@@ -1,48 +1,57 @@
|
|
1
|
-
module
|
2
|
-
module
|
1
|
+
module ArJdbc
|
2
|
+
module MSSQL
|
3
3
|
module LimitHelpers
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
|
5
|
+
FIND_SELECT = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im # :nodoc:
|
6
|
+
|
7
|
+
module SqlServerReplaceLimitOffset
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def replace_limit_offset!(sql, limit, offset, order)
|
12
|
+
if limit
|
13
|
+
offset ||= 0
|
14
|
+
start_row = offset + 1
|
15
|
+
end_row = offset + limit.to_i
|
16
|
+
_, select, rest_of_query = FIND_SELECT.match(sql).to_a
|
17
|
+
rest_of_query.strip!
|
18
|
+
if rest_of_query[0...1] == "1" && rest_of_query !~ /1 AS/i
|
19
|
+
rest_of_query[0] = "*"
|
20
|
+
end
|
21
|
+
if rest_of_query[0...1] == "*"
|
22
|
+
from_table = Utils.get_table_name(rest_of_query, true)
|
23
|
+
rest_of_query = from_table + '.' + rest_of_query
|
24
|
+
end
|
25
|
+
new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query}"
|
26
|
+
new_sql << ") AS t WHERE t._row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
|
27
|
+
sql.replace(new_sql)
|
28
|
+
end
|
29
|
+
sql
|
12
30
|
end
|
13
31
|
end
|
14
32
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
if ActiveRecord::VERSION::MAJOR >= 3
|
23
|
-
models = ActiveRecord::Base.descendants
|
24
|
-
else
|
25
|
-
models = ActiveRecord::Base.send(:subclasses)
|
26
|
-
end
|
27
|
-
|
28
|
-
model = models.select{|model| model.table_name == $1}.first
|
29
|
-
if model then
|
30
|
-
model.primary_key
|
31
|
-
else
|
32
|
-
'id'
|
33
|
+
module SqlServerAddLimitOffset
|
34
|
+
|
35
|
+
def add_limit_offset!(sql, options)
|
36
|
+
if options[:limit]
|
37
|
+
order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
|
38
|
+
sql.sub!(/ ORDER BY.*$/i, '')
|
39
|
+
SqlServerReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
|
33
40
|
end
|
34
41
|
end
|
42
|
+
|
35
43
|
end
|
36
|
-
|
44
|
+
|
37
45
|
module SqlServer2000ReplaceLimitOffset
|
46
|
+
|
38
47
|
module_function
|
48
|
+
|
39
49
|
def replace_limit_offset!(sql, limit, offset, order)
|
40
50
|
if limit
|
41
51
|
offset ||= 0
|
42
52
|
start_row = offset + 1
|
43
53
|
end_row = offset + limit.to_i
|
44
|
-
|
45
|
-
whole, select, rest_of_query = find_select.match(sql).to_a
|
54
|
+
_, select, rest_of_query = FIND_SELECT.match(sql).to_a
|
46
55
|
if (start_row == 1) && (end_row ==1)
|
47
56
|
new_sql = "#{select} TOP 1 #{rest_of_query}"
|
48
57
|
sql.replace(new_sql)
|
@@ -52,8 +61,8 @@ module ::ArJdbc
|
|
52
61
|
#removing out stuff before the FROM...
|
53
62
|
rest = rest_of_query[/FROM/i=~ rest_of_query.. -1]
|
54
63
|
#need the table name for avoiding amiguity
|
55
|
-
table_name =
|
56
|
-
primary_key =
|
64
|
+
table_name = Utils.get_table_name(sql, true)
|
65
|
+
primary_key = get_primary_key(order, table_name)
|
57
66
|
#I am not sure this will cover all bases. but all the tests pass
|
58
67
|
if order[/ORDER/].nil?
|
59
68
|
new_order = "ORDER BY #{order}, #{table_name}.#{primary_key}" if order.index("#{table_name}.#{primary_key}").nil?
|
@@ -72,52 +81,39 @@ module ::ArJdbc
|
|
72
81
|
end
|
73
82
|
sql
|
74
83
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
84
|
+
|
85
|
+
def get_primary_key(order, table_name) # table_name might be quoted
|
86
|
+
if order =~ /(\w*id\w*)/i
|
87
|
+
$1
|
88
|
+
else
|
89
|
+
unquoted_name = unquote_table_name(table_name)
|
90
|
+
model = descendants.find { |m| m.table_name == table_name || m.table_name == unquoted_name }
|
91
|
+
model ? model.primary_key : 'id'
|
83
92
|
end
|
84
93
|
end
|
85
|
-
end
|
86
94
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end_row = offset + limit.to_i
|
94
|
-
find_select = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im
|
95
|
-
whole, select, rest_of_query = find_select.match(sql).to_a
|
96
|
-
rest_of_query.strip!
|
97
|
-
if rest_of_query[0...1] == "1" && rest_of_query !~ /1 AS/i
|
98
|
-
rest_of_query[0] = "*"
|
99
|
-
end
|
100
|
-
if rest_of_query[0] == "*"
|
101
|
-
from_table = LimitHelpers.get_table_name(rest_of_query)
|
102
|
-
rest_of_query = from_table + '.' + rest_of_query
|
103
|
-
end
|
104
|
-
new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query}"
|
105
|
-
new_sql << ") AS t WHERE t._row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
|
106
|
-
sql.replace(new_sql)
|
107
|
-
end
|
108
|
-
sql
|
95
|
+
private
|
96
|
+
|
97
|
+
if ActiveRecord::VERSION::MAJOR >= 3
|
98
|
+
def descendants; ::ActiveRecord::Base.descendants; end
|
99
|
+
else
|
100
|
+
def descendants; ::ActiveRecord::Base.send(:subclasses) end
|
109
101
|
end
|
102
|
+
|
110
103
|
end
|
111
104
|
|
112
|
-
module
|
105
|
+
module SqlServer2000AddLimitOffset
|
106
|
+
|
113
107
|
def add_limit_offset!(sql, options)
|
114
108
|
if options[:limit]
|
115
109
|
order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
|
116
110
|
sql.sub!(/ ORDER BY.*$/i, '')
|
117
|
-
|
111
|
+
SqlServer2000ReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
|
118
112
|
end
|
119
113
|
end
|
114
|
+
|
120
115
|
end
|
116
|
+
|
121
117
|
end
|
122
118
|
end
|
123
119
|
end
|