Skip to content

Commit 1a64cdc

Browse files
committed
Limit and Offset can take SqlLiteral objects now. Always compose correct SQL. Use Arel.sql vs long winded constructor.
1 parent 7d43e59 commit 1a64cdc

File tree

2 files changed

+24
-17
lines changed

2 files changed

+24
-17
lines changed

lib/arel/visitors/sqlserver.rb

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,18 @@ def order(*exprs)
3434
@ast.orders.concat(exprs.map{ |x|
3535
case x
3636
when Arel::Attributes::Attribute
37-
c = engine.connection
3837
table = Arel::Table.new(x.relation.table_alias || x.relation.name)
3938
expr = table[x.name]
4039
Arel::Nodes::Ordering.new expr
4140
when String
4241
x.split(',').map do |s|
4342
expr, direction = s.split
44-
expr = Arel::Nodes::SqlLiteral.new(expr)
43+
expr = Arel.sql(expr)
4544
direction = direction =~ /desc/i ? :desc : :asc
4645
Arel::Nodes::Ordering.new expr, direction
4746
end
4847
else
49-
expr = Arel::Nodes::SqlLiteral.new x.to_s
48+
expr = Arel.sql(x.to_s)
5049
Arel::Nodes::Ordering.new expr
5150
end
5251
}.flatten)
@@ -84,7 +83,7 @@ def visit_Arel_Nodes_SelectStatement(o)
8483
end
8584

8685
def visit_Arel_Nodes_Offset(o)
87-
"WHERE [__rnt].[__rn] > #{visit o.expr}"
86+
"WHERE [__rnt].[__rn] > (#{visit o.expr})"
8887
end
8988

9089
def visit_Arel_Nodes_Limit(o)
@@ -121,7 +120,7 @@ def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
121120
groups = projections.map { |x| projection_without_expression(x) }
122121
projections = projections.map { |x| projection_without_expression(x) }
123122
orders = orders.map do |x|
124-
expr = Arel::Nodes::SqlLiteral.new projection_without_expression(x.expr)
123+
expr = Arel.sql projection_without_expression(x.expr)
125124
x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
126125
end
127126
end
@@ -151,7 +150,7 @@ def visit_Arel_Nodes_SelectStatementWithOffset(o)
151150

152151
def visit_Arel_Nodes_SelectStatementForComplexCount(o)
153152
core = o.cores.first
154-
o.limit.expr = o.limit.expr.to_i + (o.offset ? o.offset.expr.to_i : 0) if o.limit
153+
o.limit.expr = Arel.sql("#{o.limit.expr} + #{o.offset ? o.offset.expr : 0}") if o.limit
155154
orders = rowtable_orders(o)
156155
[ "SELECT COUNT([count]) AS [count_id]",
157156
"FROM (",
@@ -286,11 +285,11 @@ def rowtable_projections(o)
286285
end
287286
elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
288287
core.projections.map do |x|
289-
Arel::Nodes::SqlLiteral.new x.split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
288+
Arel.sql x.split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
290289
end
291290
elsif function_select_statement?(o)
292291
# TODO: [ARel 2.2] Use Arel.star
293-
[Arel::Nodes::SqlLiteral.new('*')]
292+
[Arel.sql('*')]
294293
else
295294
tn = table_from_select_statement(o).name
296295
core.projections.map { |x| x.gsub /\[#{tn}\]\./, '[__rnt].' }
@@ -310,7 +309,7 @@ def rowtable_orders(o)
310309

311310
# TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
312311
def projection_without_expression(projection)
313-
Arel::Nodes::SqlLiteral.new(projection.split(',').map do |x|
312+
Arel.sql(projection.split(',').map do |x|
314313
x.strip!
315314
x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
316315
x.sub!(/^DISTINCT\s*/,'')

test/cases/offset_and_limit_test_sqlserver.rb

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,40 @@ class OffsetAndLimitTestSqlserver < ActiveRecord::TestCase
1616
assert_sql(/SELECT TOP \(10\)/) { Book.limit(10).all }
1717
end
1818

19-
should 'only allow integers for limit' do
20-
assert_sql(/SELECT TOP \(20\)/) { Book.limit('20-twenty').all }
19+
should 'allow sql literal for limit' do
20+
assert_sql(/SELECT TOP \(3-2\)/) { Book.limit(Arel.sql('3-2')).all }
21+
assert_sql(/SELECT TOP \(SELECT 2 AS \[count\]\)/) do
22+
books = Book.all :limit => Arel.sql('SELECT 2 AS [count]')
23+
assert_equal 2, books.size
24+
end
2125
end
2226

2327
end
2428

2529
context 'When selecting with offset' do
2630

2731
should 'have no limit (top) if only offset is passed' do
28-
assert_sql(/SELECT \[__rnt\]\.\* FROM.*WHERE \[__rnt\]\.\[__rn\] > 1/) { Book.all(:offset=>1) }
32+
assert_sql(/SELECT \[__rnt\]\.\* FROM.*WHERE \[__rnt\]\.\[__rn\] > \(1\)/) { Book.all(:offset=>1) }
2933
end
3034

3135
end
3236

3337
context 'When selecting with limit and offset' do
3438

35-
should 'only allow integers for offset' do
36-
assert_sql(/WHERE \[__rnt\]\.\[__rn\] > 0/) { Book.limit(10).offset('five').all }
39+
should 'allow sql literal for offset' do
40+
assert_sql(/WHERE \[__rnt\]\.\[__rn\] > \(3-2\)/) { Book.limit(10).offset(Arel.sql('3-2')).all }
41+
assert_sql(/WHERE \[__rnt\]\.\[__rn\] > \(SELECT 8 AS \[count\]\)/) do
42+
books = Book.all :limit => 3, :offset => Arel.sql('SELECT 8 AS [count]')
43+
assert_equal 2, books.size, 'remember there are only 10 books and offset is 8'
44+
end
3745
end
3846

39-
should 'convert strings which look like integers to integers' do
40-
assert_sql(/WHERE \[__rnt\]\.\[__rn\] > 5/) { Book.limit(10).offset('5').all }
47+
should 'not convert strings which look like integers to integers' do
48+
assert_sql(/WHERE \[__rnt\]\.\[__rn\] > \(N'5'\)/) { Book.limit(10).offset('5').all }
4149
end
4250

4351
should 'alter SQL to limit number of records returned offset by specified amount' do
44-
sql = %|SELECT TOP (3) [__rnt].* FROM ( SELECT ROW_NUMBER() OVER (ORDER BY [books].[id] ASC) AS [__rn], [books].* FROM [books] ) AS [__rnt] WHERE [__rnt].[__rn] > 5|
52+
sql = %|SELECT TOP (3) [__rnt].* FROM ( SELECT ROW_NUMBER() OVER (ORDER BY [books].[id] ASC) AS [__rn], [books].* FROM [books] ) AS [__rnt] WHERE [__rnt].[__rn] > (5)|
4553
assert_sql(sql) { Book.limit(3).offset(5).all }
4654
end
4755

0 commit comments

Comments
 (0)