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.
Files changed (55) hide show
  1. data/.travis.yml +1 -1
  2. data/Gemfile +2 -1
  3. data/Gemfile.lock +5 -6
  4. data/History.txt +11 -0
  5. data/gemfiles/rails23.gemfile +2 -1
  6. data/gemfiles/rails23.gemfile.lock +5 -2
  7. data/gemfiles/rails30.gemfile +2 -1
  8. data/gemfiles/rails30.gemfile.lock +5 -2
  9. data/gemfiles/rails31.gemfile +2 -1
  10. data/gemfiles/rails31.gemfile.lock +5 -2
  11. data/gemfiles/rails32.gemfile +2 -1
  12. data/gemfiles/rails32.gemfile.lock +5 -2
  13. data/lib/arel/engines/sql/compilers/mssql_compiler.rb +1 -1
  14. data/lib/arel/visitors/sql_server.rb +4 -4
  15. data/lib/arjdbc/db2/adapter.rb +14 -3
  16. data/lib/arjdbc/discover.rb +1 -1
  17. data/lib/arjdbc/jdbc/adapter.rb +1 -0
  18. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  19. data/lib/arjdbc/jdbc/connection.rb +73 -63
  20. data/lib/arjdbc/jdbc/extension.rb +1 -1
  21. data/lib/arjdbc/mssql.rb +3 -0
  22. data/lib/arjdbc/mssql/adapter.rb +132 -115
  23. data/lib/arjdbc/mssql/connection_methods.rb +1 -1
  24. data/lib/arjdbc/mssql/limit_helpers.rb +62 -66
  25. data/lib/arjdbc/mssql/lock_helpers.rb +2 -2
  26. data/lib/arjdbc/mssql/tsql_methods.rb +58 -0
  27. data/lib/arjdbc/mssql/utils.rb +53 -0
  28. data/lib/arjdbc/oracle/adapter.rb +61 -39
  29. data/lib/arjdbc/sqlite3/adapter.rb +3 -6
  30. data/lib/arjdbc/version.rb +1 -1
  31. data/src/java/arjdbc/jdbc/AdapterJavaService.java +4 -2
  32. data/src/java/arjdbc/mssql/MSSQLModule.java +70 -0
  33. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +128 -0
  34. data/src/java/arjdbc/mssql/MssqlRubyJdbcConnection.java +25 -112
  35. data/test/db/mssql.rb +8 -8
  36. data/test/db2_simple_test.rb +7 -0
  37. data/test/models/entry.rb +2 -1
  38. data/test/mssql_binary_test.rb +6 -0
  39. data/test/mssql_db_create_test.rb +5 -2
  40. data/test/mssql_identity_insert_test.rb +1 -2
  41. data/test/mssql_ignore_system_views_test.rb +5 -5
  42. data/test/mssql_limit_offset_test.rb +51 -55
  43. data/test/mssql_multibyte_test.rb +1 -2
  44. data/test/mssql_row_locking_test.rb +1 -1
  45. data/test/mssql_simple_test.rb +6 -10
  46. data/test/{mssql_row_locking_sql_test.rb → mssql_test.rb} +110 -18
  47. data/test/mysql_db_create_test.rb +13 -7
  48. data/test/oracle_simple_test.rb +18 -0
  49. data/test/postgres_db_create_test.rb +26 -13
  50. data/test/simple.rb +45 -15
  51. data/test/sqlite3_schema_dump_test.rb +6 -0
  52. data/test/sqlite3_type_conversion_test.rb +20 -17
  53. data/test/test_helper.rb +44 -2
  54. metadata +9 -4
  55. data/lib/arjdbc/mssql/tsql_helper.rb +0 -53
@@ -26,7 +26,7 @@ module ArJdbc
26
26
  # true
27
27
  # end
28
28
  # end
29
- def self.extension(name,&block)
29
+ def self.extension(name, &block)
30
30
  if const_defined?(name)
31
31
  mod = const_get(name)
32
32
  else
@@ -1,3 +1,6 @@
1
1
  require 'arjdbc/jdbc'
2
2
  require 'arjdbc/mssql/adapter'
3
3
  require 'arjdbc/mssql/connection_methods'
4
+ module ArJdbc
5
+ MsSQL = MSSQL # compatibility with 1.2
6
+ end
@@ -1,17 +1,18 @@
1
1
  require 'strscan'
2
- require 'arjdbc/mssql/tsql_helper'
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 MsSQL
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(mod)
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
- mod.add_version_specific_add_limit_offset
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,col| col.extend(::ArJdbc::MsSQL::Column)}]
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::MssqlJdbcConnection
49
+ ::ActiveRecord::ConnectionAdapters::MSSQLJdbcConnection
43
50
  end
44
51
 
45
52
  def self.arel2_visitors(config)
46
53
  require 'arel/visitors/sql_server'
47
- visitor_class = config[:sqlserver_version] == "2000" ? ::Arel::Visitors::SQLServer2000 : ::Arel::Visitors::SQLServer
48
- {}.tap {|v| %w(mssql sqlserver jdbcmssql).each {|x| v[x] = visitor_class } }
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 ||= select_value("select @@version")[/Microsoft SQL Server\s+(\d{4})/, 1]
53
- end
54
-
55
- def add_version_specific_add_limit_offset
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(tp) #:nodoc:
65
- super(tp)
66
- tp[:string] = {:name => "NVARCHAR", :limit => 255}
67
-
68
- if sqlserver_version == "2000"
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
- tp[:text] = {:name => "NVARCHAR(MAX)"}
73
+ types[:text] = { :name => "NVARCHAR(MAX)" }
72
74
  end
73
-
74
- tp[:primary_key] = "int NOT NULL IDENTITY(1, 1) PRIMARY KEY"
75
- tp[:integer][:limit] = nil
76
- tp[:boolean] = {:name => "bit"}
77
- tp[:binary] = {:name => "image"}
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' and limit == 1073741823 and sqlserver_version != "2000"
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) # cannot specify limit/precision/scale with these types
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
- if column && column.type == :binary
211
- "'#{quote_string(ArJdbc::MsSQL::Column.string_to_binary(value))}'" # ' (for ruby-mode)
212
- elsif column && [:integer, :float].include?(column.type)
213
- value = column.type == :integer ? value.to_i : value.to_f
214
- value.to_s
215
- elsif !column.respond_to?(:is_utf8?) || column.is_utf8?
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 super
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
- end
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
- def adapter_name #:nodoc:
247
- 'MsSQL'
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 /\bDESC\b/i then fragment.gsub(/\bDESC\b/i, "ASC")
254
- when /\bASC\b/i then fragment.gsub(/\bASC\b/i, "DESC")
255
- else String.new(fragment).split(',').join(' DESC,') + ' DESC'
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
- 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"
335
- defaults.each {|constraint|
336
- execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["name"]}"
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
- # TODO remove all constraints in single method
343
- constraints = select "SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE where TABLE_NAME = '#{table_name}' and COLUMN_NAME = '#{column_name}'"
344
- constraints.each do |constraint|
345
- execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint["CONSTRAINT_NAME"]}"
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 attempts to issue SQL
355
- # which doesn't involve a table. IE. "SELECT 1" or "SELECT * from someFunction()".
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
- table_name = table_name.to_s if table_name.is_a?(Symbol)
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 =~ /^information_schema\./i
363
- @table_columns ||= {}
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 |col|
367
- col.identity = true if col.sql_type =~ /identity/i
368
- col.is_special = true if col.sql_type =~ /text|ntext|image|xml/i
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, &block)
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 #{enable ? 'ON' : 'OFF'} for table #{table_name}"
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).each do |col|
392
- return col.name if col.identity
406
+ for column in columns(table_name)
407
+ return column.name if column.identity
393
408
  end
394
- return nil
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. Return it, without changing case, to cover dbs with a case-sensitive collation.
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::MsSQL
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 ::ArJdbc
2
- module MsSQL
1
+ module ArJdbc
2
+ module MSSQL
3
3
  module LimitHelpers
4
- module_function
5
- def get_table_name(sql)
6
- if sql =~ /^\s*insert\s+into\s+([^\(\s,]+)\s*|^\s*update\s+([^\(\s,]+)\s*/i
7
- $1
8
- elsif sql =~ /\bfrom\s+([^\(\s,]+)\s*/i
9
- $1
10
- else
11
- nil
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
- def get_primary_key(order, table_name)
16
- if order =~ /(\w*id\w*)/i
17
- $1
18
- else
19
- table_name[/\[+(.*)\]+/i]
20
-
21
- # rails 2 vs rails 3
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
- find_select = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im
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 = LimitHelpers.get_table_name(sql)
56
- primary_key = LimitHelpers.get_primary_key(order, table_name)
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
- end
76
-
77
- module SqlServer2000AddLimitOffset
78
- def add_limit_offset!(sql, options)
79
- if options[:limit]
80
- order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
81
- sql.sub!(/ ORDER BY.*$/i, '')
82
- SqlServer2000ReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
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
- module SqlServerReplaceLimitOffset
88
- module_function
89
- def replace_limit_offset!(sql, limit, offset, order)
90
- if limit
91
- offset ||= 0
92
- start_row = offset + 1
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 SqlServerAddLimitOffset
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
- SqlServerReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
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