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