Skip to content

Commit aac9d92

Browse files
committed
WIP
1 parent ca7a519 commit aac9d92

File tree

4 files changed

+168
-9
lines changed

4 files changed

+168
-9
lines changed

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require 'arel/visitors/sqlserver'
12
require 'active_record'
23
require 'active_record/connection_adapters/abstract_adapter'
34
require 'active_record/connection_adapters/sqlserver/core_ext/active_record'

lib/arel/visitors/sqlserver.rb

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,140 @@ module Visitors
33
class SQLServer < Arel::Visitors::ToSql
44

55

6+
private
7+
8+
# SQLServer ToSql/Visitor (Overides)
9+
10+
def visit_Arel_Nodes_SelectStatement(o)
11+
if complex_count_sql?(o)
12+
visit_Arel_Nodes_SelectStatementForComplexCount(o)
13+
elsif o.offset
14+
visit_Arel_Nodes_SelectStatementWithOffset(o)
15+
else
16+
visit_Arel_Nodes_SelectStatementWithOutOffset(o)
17+
end
18+
end
19+
20+
def visit_Arel_Nodes_Offset(o)
21+
"WHERE [__rnt].[__rn] > #{visit o.expr}"
22+
end
23+
24+
def visit_Arel_Nodes_Limit(o)
25+
"TOP (#{visit o.expr})"
26+
end
27+
28+
29+
# SQLServer ToSql/Visitor (Additions)
30+
31+
def visit_Arel_Nodes_SelectStatementWithOutOffset(o, windowed=false)
32+
core = o.cores.first
33+
projections = core.projections
34+
if windowed && !function_select_statement?(o)
35+
projections = projections.map { |x| projection_without_expression(x) }
36+
elsif eager_limiting_select?(o)
37+
38+
end
39+
[ ("SELECT" if !windowed),
40+
(visit(o.limit) if o.limit && !windowed),
41+
(projections.map{ |x| visit(x) }.join(', ')),
42+
("FROM #{visit core.froms}" if core.froms),
43+
(visit(o.lock) if o.lock),
44+
# (joins unless joins.blank?),
45+
("WHERE #{core.wheres.map{ |x| visit(x) }.join ' AND ' }" unless core.wheres.empty?),
46+
("GROUP BY #{core.groups.map { |x| visit x }.join ', ' }" unless core.groups.empty?),
47+
(visit(core.having) if core.having),
48+
("ORDER BY #{o.orders.map{ |x| visit(x) }.join(', ')}" if !o.orders.empty? && !windowed)
49+
].compact.join ' '
50+
end
51+
52+
def visit_Arel_Nodes_SelectStatementWithOffset(o)
53+
orders = rowtable_orders(o)
54+
[ "SELECT",
55+
(visit(o.limit) if o.limit && !single_distinct_select?(o)),
56+
(rowtable_projections(o).map{ |x| visit(x) }.join(', ')),
57+
"FROM (",
58+
"SELECT ROW_NUMBER() OVER (ORDER BY #{orders.map{ |x| visit(x) }.uniq.join(', ')}) AS [__rn],",
59+
visit_Arel_Nodes_SelectStatementWithOutOffset(o,true),
60+
") AS [__rnt]",
61+
(visit(o.offset) if o.offset),
62+
].compact.join ' '
63+
end
64+
65+
def visit_Arel_Nodes_SelectStatementForComplexCount(o)
66+
67+
end
68+
69+
70+
# SQLServer Helpers
71+
72+
def table_name_from_select_statement(o)
73+
o.cores.first.source.left.name
74+
end
75+
76+
def single_distinct_select?(o)
77+
projections = o.cores.first.projections
78+
projections.size == 1 && projections.first.include?('DISTINCT')
79+
end
80+
81+
def function_select_statement?(o)
82+
core = o.cores.first
83+
core.projections.any? { |x| Arel::Nodes::Function === x }
84+
end
85+
86+
def eager_limiting_select?(o)
87+
false
88+
# single_distinct_select?(o) && taken_only? && relation.group_clauses.blank?
89+
end
90+
91+
def complex_count_sql?(o)
92+
false
93+
# projections = relation.projections
94+
# projections.first.is_a?(Arel::Count) && projections.size == 1 &&
95+
# (relation.taken.present? || relation.wheres.present?) && relation.joins(self).blank?
96+
end
97+
98+
def rowtable_projections(o)
99+
core = o.cores.first
100+
if single_distinct_select?(o)
101+
raise 'TODO: single_distinct_select'
102+
# ::Array.wrap(relation.select_clauses.first.dup.tap do |sc|
103+
# sc.sub! 'DISTINCT', "DISTINCT #{taken_clause if relation.taken.present?}".strip
104+
# sc.sub! table_name_from_select_clause(sc), '__rnt'
105+
# sc.strip!
106+
# end)
107+
elsif false # relation.join? && all_select_clauses_aliased?
108+
raise 'TODO: relation.join? && all_select_clauses_aliased?'
109+
# relation.select_clauses.map do |sc|
110+
# sc.split(',').map { |c| c.split(' AS ').last.strip }.join(', ')
111+
# end
112+
elsif function_select_statement?(o)
113+
[Arel.star]
114+
else
115+
tn = table_name_from_select_statement(o)
116+
core.projections.map { |x| x.gsub /\[#{tn}\]\./, '[__rnt].' }
117+
end
118+
end
119+
120+
def rowtable_orders(o)
121+
if !o.orders.empty?
122+
o.orders
123+
elsif false # TODO relation.join?
124+
# table_names_from_select_clauses.map { |tn| quote("#{tn}.#{pk_for_table(tn)}") }
125+
else
126+
tn = table_name_from_select_statement(o)
127+
[Arel::Table.new(tn, @engine).primary_key.asc]
128+
end
129+
end
130+
131+
def projection_without_expression(projection)
132+
projection.to_s.split(',').map do |x|
133+
x.strip!
134+
x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
135+
x.sub!(/^DISTINCT\s*/,'')
136+
x.sub!(/TOP\s*\(\d+\)\s*/i,'')
137+
x.strip
138+
end.join(', ')
139+
end
6140

7141
end
8142
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require 'cases/sqlserver_helper'
2+
require 'models/task'
3+
require 'models/book'
4+
require 'models/post'
5+
6+
class BBBBasicTestSqlserver < ActiveRecord::TestCase
7+
8+
fixtures :tasks, :posts
9+
10+
setup :create_10_books
11+
12+
13+
should 'pass limit' do
14+
Book.count :limit => 3, :offset => 5, :lock => 'WITH (NOLOCK)'
15+
end
16+
17+
18+
protected
19+
20+
def create_10_books
21+
Book.delete_all
22+
@books = (1..10).map{ |i| Book.create! }
23+
end
24+
25+
26+
end
27+

test/cases/sqlserver_helper.rb

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,14 @@ def method_added(method)
8080
# Our changes/additions to ActiveRecord test helpers specific for SQL Server.
8181

8282
ActiveRecord::Base.connection.class.class_eval do
83-
IGNORED_SQL << %r|SELECT SCOPE_IDENTITY| << %r{INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS)}
84-
IGNORED_SQL << %r|SELECT @@IDENTITY| << %r|SELECT @@ROWCOUNT| << %r|SELECT @@version| << %r|SELECT @@TRANCOUNT|
85-
end
86-
87-
ActiveRecord::ConnectionAdapters::SQLServerAdapter.class_eval do
88-
def raw_select_with_query_record(sql, name=nil, options={})
83+
IGNORED_SQL << %r|SELECT SCOPE_IDENTITY| << %r{INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS)} <<
84+
%r|SELECT @@IDENTITY| << %r|SELECT @@ROWCOUNT| << %r|SELECT @@version| << %r|SELECT @@TRANCOUNT|
85+
def select_with_query_record(sql, name=nil)
8986
$queries_executed ||= []
9087
$queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
91-
raw_select_without_query_record(sql,name,options)
88+
select_without_query_record(sql, name)
9289
end
93-
alias_method_chain :raw_select, :query_record
90+
alias_method_chain :select, :query_record
9491
end
9592

9693
module ActiveRecord
@@ -111,7 +108,7 @@ def assert_sql(*patterns_to_match)
111108
patterns_to_match.each do |pattern|
112109
failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
113110
end
114-
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found in:\n#{$queries_executed.inspect}"
111+
assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
115112
end
116113
def connection_mode_dblib? ; self.class.connection_mode_dblib? ; end
117114
def connection_mode_odbc? ; self.class.connection_mode_odbc? ; end

0 commit comments

Comments
 (0)