activerecord-jdbc-adapter 1.2.8 → 1.2.9
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/.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
|