Skip to content

Commit f739a7b

Browse files
committed
Fix
1 parent 38a943e commit f739a7b

File tree

1 file changed

+33
-26
lines changed

1 file changed

+33
-26
lines changed

lib/arel/visitors/sqlserver.rb

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class SQLServer < Arel::Visitors::ToSql
99
FETCH0 = " FETCH FIRST (SELECT 0) "
1010
ROWS_ONLY = " ROWS ONLY"
1111

12+
ONE_AS_ONE = ActiveRecord::FinderMethods::ONE_AS_ONE
13+
1214
private
1315

1416
# SQLServer ToSql/Visitor (Overrides)
@@ -251,12 +253,8 @@ def visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {})
251253
end
252254
collector
253255
end
254-
255-
# AIDO
256+
256257
def visit_Orders_And_Let_Fetch_Happen(o, collector)
257-
258-
# binding.pry if $DEBUG
259-
260258
make_Fetch_Possible_And_Deterministic o
261259
if o.orders.any?
262260
collector << " ORDER BY "
@@ -304,45 +302,54 @@ def select_statement_lock?
304302
@select_statement && @select_statement.lock
305303
end
306304

307-
# If LIMIT/OFFSET is used without ORDER BY, SQLServer will return an error.
308-
# This method will add a deterministic ORDER BY clause to the query using following rules:
305+
# LIMIT/OFFSET cannot be used without an ORDER. This method adds a deterministic ORDER using following rules:
309306
# 1. If the query has projections, use the first projection as the ORDER BY clause.
310307
# 2. If the query has SQL literal projection, use the first part of the SQL literal as the ORDER BY clause.
311308
# 3. If the query has a table with a primary key, use the primary key as the ORDER BY clause.
312309
def make_Fetch_Possible_And_Deterministic(o)
313310
return if o.limit.nil? && o.offset.nil?
314311
return if o.orders.any?
315312

316-
# TODO: Refactor to list all projections and then find the first one that looks good.
317-
318-
projection = o.cores.first.projections.first
319-
320-
321-
binding.pry if $DEBUG
322-
323-
324-
if projection.is_a?(Arel::Attributes::Attribute) && !projection.name.include?("*")
313+
if (projection = projection_to_order_by_for_fetch(o))
325314
o.orders = [projection.asc]
326-
327-
# TODO: Use better logic to find first projection that is usable for ordering.
328-
elsif projection.is_a?(Arel::Nodes::SqlLiteral) && !projection.match?(/^\s*(1 as ONE|\*)(\s|,)*/i)
329-
330-
first_projection = Arel::Nodes::SqlLiteral.new(projection.split(",").first.split(/\sAS\s/i).first)
331-
o.orders = [first_projection.asc]
332315
else
333-
334316
pk = primary_Key_From_Table(table_From_Statement(o))
335317
o.orders = [pk.asc] if pk
336318
end
319+
end
320+
321+
def projection_to_order_by_for_fetch(o)
322+
o.cores.first.projections.each do |projection|
323+
case projection
324+
when Arel::Attributes::Attribute
325+
return projection unless projection.name.include?("*")
326+
when Arel::Nodes::SqlLiteral
327+
projection.split(",").each do |p|
328+
next if p.match?(/#{Regexp.escape(ONE_AS_ONE)}/i) || p.include?("*")
329+
330+
return Arel::Nodes::SqlLiteral.new(remove_last_AS_from_projection(p))
331+
end
332+
end
333+
end
334+
335+
nil
336+
end
337337

338-
# rescue => e
339-
# binding.pry
338+
# Remove last AS from projection that could contain multiple AS clauses.
339+
# Examples:
340+
# - 'name'
341+
# - 'name AS first_name'
342+
# - 'AVG(accounts.credit_limit AS DECIMAL) AS avg_credit_limit)'
343+
def remove_last_AS_from_projection(projection)
344+
parts = projection.split(/\sAS\s/i)
345+
parts.pop if parts.length > 1
346+
projection.join(" AS ")
340347
end
341348

342349
def distinct_One_As_One_Is_So_Not_Fetch(o)
343350
core = o.cores.first
344351
distinct = Nodes::Distinct === core.set_quantifier
345-
one_as_one = core.projections.all? { |x| x == ActiveRecord::FinderMethods::ONE_AS_ONE }
352+
one_as_one = core.projections.all? { |x| x == ONE_AS_ONE }
346353
limit_one = [nil, 0, 1].include? node_value(o.limit)
347354

348355
if distinct && one_as_one && limit_one && !o.offset

0 commit comments

Comments
 (0)