-
Notifications
You must be signed in to change notification settings - Fork 388
/
limit_helpers.rb
149 lines (121 loc) · 5.49 KB
/
limit_helpers.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
module ArJdbc
module MSSQL
module LimitHelpers
FIND_SELECT = /\b(SELECT(?:\s+DISTINCT)?)\b(.*)/im # :nodoc:
module SqlServerReplaceLimitOffset
module_function
def replace_limit_offset!(sql, limit, offset, order)
if limit
offset ||= 0
start_row = offset + 1
end_row = offset + limit.to_i
_, select, rest_of_query = FIND_SELECT.match(sql).to_a
rest_of_query.strip!
if rest_of_query[0...1] == "1" && rest_of_query !~ /1 AS/i
rest_of_query[0] = "*"
end
if rest_of_query[0...1] == "*"
from_table = Utils.get_table_name(rest_of_query, true)
rest_of_query = from_table + '.' + rest_of_query
end
new_sql = "#{select} t.* FROM (SELECT ROW_NUMBER() OVER(#{order}) AS _row_num, #{rest_of_query}"
new_sql << ") AS t WHERE t._row_num BETWEEN #{start_row.to_s} AND #{end_row.to_s}"
sql.replace(new_sql)
end
sql
end
end
module SqlServerAddLimitOffset
def add_limit_offset!(sql, options)
if options[:limit]
order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
sql.sub!(/ ORDER BY.*$/i, '')
SqlServerReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
end
end
end
module SqlServer2000ReplaceLimitOffset
module_function
def replace_limit_offset!(sql, limit, offset, order)
if limit
offset ||= 0
start_row = offset + 1
end_row = offset + limit.to_i
_, select, rest_of_query = FIND_SELECT.match(sql).to_a
#need the table name for avoiding amiguity
table_name = Utils.get_table_name(sql, true)
primary_key = get_primary_key(order, table_name)
#I am not sure this will cover all bases. but all the tests pass
if order[/ORDER/].nil?
new_order = "ORDER BY #{order}, [#{table_name}].[#{primary_key}]" if order.index("#{table_name}.#{primary_key}").nil?
else
new_order ||= order
end
if (start_row == 1) && (end_row ==1)
new_sql = "#{select} TOP 1 #{rest_of_query} #{new_order}"
sql.replace(new_sql)
else
# We are in deep trouble here. SQL Server does not have any kind of OFFSET build in.
# Only remaining solution is adding a where condition to be sure that the ID is not in SELECT TOP OFFSET FROM SAME_QUERY.
# To do so we need to extract each part of the query to insert our additional condition in the right place.
query_without_select = rest_of_query[/FROM/i=~ rest_of_query.. -1]
additional_condition = "#{table_name}.#{primary_key} NOT IN (#{select} TOP #{offset} #{table_name}.#{primary_key} #{query_without_select} #{new_order})"
# Extract the different parts of the query
having, group_by, where, from, selection = split_sql(rest_of_query, /having/i, /group by/i, /where/i, /from/i)
# Update the where part to add our additional condition
if where.blank?
where = "WHERE #{additional_condition}"
else
where = "#{where} AND #{additional_condition}"
end
# Replace the query to be our new customized query
sql.replace("#{select} TOP #{limit} #{selection} #{from} #{where} #{group_by} #{having} #{new_order}")
end
end
sql
end
# Split the rest_of_query into chunks based on regexs (applied from end of string to the beginning)
# The result is an array of regexs.size+1 elements (the last one being the remaining once everything was chopped away)
def split_sql rest_of_query, *regexs
results = Array.new
regexs.each do |regex|
if position = (regex =~ rest_of_query)
# Extract the matched string and chop the rest_of_query
matched = rest_of_query[position..-1]
rest_of_query = rest_of_query[0...position]
else
matched = nil
end
results << matched
end
results << rest_of_query
results
end
def get_primary_key(order, table_name) # table_name might be quoted
if order =~ /(\w*id\w*)/i
$1
else
unquoted_name = unquote_table_name(table_name)
model = descendants.find { |m| m.table_name == table_name || m.table_name == unquoted_name }
model ? model.primary_key : 'id'
end
end
private
if ActiveRecord::VERSION::MAJOR >= 3
def descendants; ::ActiveRecord::Base.descendants; end
else
def descendants; ::ActiveRecord::Base.send(:subclasses) end
end
end
module SqlServer2000AddLimitOffset
def add_limit_offset!(sql, options)
if options[:limit]
order = "ORDER BY #{options[:order] || determine_order_clause(sql)}"
sql.sub!(/ ORDER BY.*$/i, '')
SqlServer2000ReplaceLimitOffset.replace_limit_offset!(sql, options[:limit], options[:offset], order)
end
end
end
end
end
end