@@ -9,6 +9,8 @@ class SQLServer < Arel::Visitors::ToSql
9
9
FETCH0 = " FETCH FIRST (SELECT 0) "
10
10
ROWS_ONLY = " ROWS ONLY"
11
11
12
+ ONE_AS_ONE = ActiveRecord ::FinderMethods ::ONE_AS_ONE
13
+
12
14
private
13
15
14
16
# SQLServer ToSql/Visitor (Overrides)
@@ -251,12 +253,8 @@ def visit_Arel_Nodes_SelectStatement_SQLServer_Lock(collector, options = {})
251
253
end
252
254
collector
253
255
end
254
-
255
- # AIDO
256
+
256
257
def visit_Orders_And_Let_Fetch_Happen ( o , collector )
257
-
258
- # binding.pry if $DEBUG
259
-
260
258
make_Fetch_Possible_And_Deterministic o
261
259
if o . orders . any?
262
260
collector << " ORDER BY "
@@ -304,45 +302,54 @@ def select_statement_lock?
304
302
@select_statement && @select_statement . lock
305
303
end
306
304
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:
309
306
# 1. If the query has projections, use the first projection as the ORDER BY clause.
310
307
# 2. If the query has SQL literal projection, use the first part of the SQL literal as the ORDER BY clause.
311
308
# 3. If the query has a table with a primary key, use the primary key as the ORDER BY clause.
312
309
def make_Fetch_Possible_And_Deterministic ( o )
313
310
return if o . limit . nil? && o . offset . nil?
314
311
return if o . orders . any?
315
312
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 ) )
325
314
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 ( /\s AS\s /i ) . first )
331
- o . orders = [ first_projection . asc ]
332
315
else
333
-
334
316
pk = primary_Key_From_Table ( table_From_Statement ( o ) )
335
317
o . orders = [ pk . asc ] if pk
336
318
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
337
337
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 ( /\s AS\s /i )
345
+ parts . pop if parts . length > 1
346
+ projection . join ( " AS " )
340
347
end
341
348
342
349
def distinct_One_As_One_Is_So_Not_Fetch ( o )
343
350
core = o . cores . first
344
351
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 }
346
353
limit_one = [ nil , 0 , 1 ] . include? node_value ( o . limit )
347
354
348
355
if distinct && one_as_one && limit_one && !o . offset
0 commit comments