activerecord-sqlserver-adapter 3.2.18 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +4 -28
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -9
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +3 -25
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +4 -14
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +74 -80
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +10 -14
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +24 -15
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +24 -19
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +28 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +118 -77
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +10 -13
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +8 -11
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -5
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +4 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +121 -247
- data/lib/active_record/connection_adapters/sqlserver_column.rb +116 -0
- data/lib/active_record/sqlserver_base.rb +28 -0
- data/lib/active_record/sqlserver_test_case.rb +17 -0
- data/lib/arel/arel_sqlserver.rb +5 -0
- data/lib/arel/nodes_sqlserver.rb +14 -0
- data/lib/arel/select_manager_sqlserver.rb +62 -0
- data/lib/arel/visitors/sqlserver.rb +251 -188
- metadata +32 -10
- data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +0 -97
@@ -0,0 +1,116 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class SQLServerColumn < Column
|
4
|
+
def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
|
5
|
+
@sqlserver_options = sqlserver_options.symbolize_keys
|
6
|
+
super(name, default, sql_type, null)
|
7
|
+
@primary = @sqlserver_options[:is_identity] || @sqlserver_options[:is_primary]
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def string_to_binary(value)
|
12
|
+
"0x#{value.unpack("H*")[0]}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def binary_to_string(value)
|
16
|
+
if value.encoding != Encoding::ASCII_8BIT
|
17
|
+
value = value.force_encoding(Encoding::ASCII_8BIT)
|
18
|
+
end
|
19
|
+
value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_identity?
|
24
|
+
@sqlserver_options[:is_identity]
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_primary?
|
28
|
+
@sqlserver_options[:is_primary]
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_utf8?
|
32
|
+
@sql_type =~ /nvarchar|ntext|nchar/i
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_integer?
|
36
|
+
@sql_type =~ /int/i
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_real?
|
40
|
+
@sql_type =~ /real/i
|
41
|
+
end
|
42
|
+
|
43
|
+
def sql_type_for_statement
|
44
|
+
if is_integer? || is_real?
|
45
|
+
sql_type.sub(/\((\d+)?\)/, '')
|
46
|
+
else
|
47
|
+
sql_type
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_function
|
52
|
+
@sqlserver_options[:default_function]
|
53
|
+
end
|
54
|
+
|
55
|
+
def table_name
|
56
|
+
@sqlserver_options[:table_name]
|
57
|
+
end
|
58
|
+
|
59
|
+
def table_klass
|
60
|
+
@table_klass ||= begin
|
61
|
+
table_name.classify.constantize
|
62
|
+
rescue StandardError, NameError, LoadError
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
(@table_klass && @table_klass < ActiveRecord::Base) ? @table_klass : nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def database_year
|
69
|
+
@sqlserver_options[:database_year]
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def extract_limit(sql_type)
|
75
|
+
case sql_type
|
76
|
+
when /^smallint/i
|
77
|
+
2
|
78
|
+
when /^int/i
|
79
|
+
4
|
80
|
+
when /^bigint/i
|
81
|
+
8
|
82
|
+
when /\(max\)/, /decimal/, /numeric/
|
83
|
+
nil
|
84
|
+
else
|
85
|
+
super
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def simplified_type(field_type)
|
90
|
+
case field_type
|
91
|
+
when /real/i then :float
|
92
|
+
when /money/i then :decimal
|
93
|
+
when /image/i then :binary
|
94
|
+
when /bit/i then :boolean
|
95
|
+
when /uniqueidentifier/i then :uuid
|
96
|
+
when /datetime/i then simplified_datetime
|
97
|
+
when /varchar\(max\)/ then :text
|
98
|
+
when /timestamp/ then :binary
|
99
|
+
else super
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def simplified_datetime
|
104
|
+
if database_year >= 2008
|
105
|
+
:datetime
|
106
|
+
elsif table_klass && table_klass.coerced_sqlserver_date_columns.include?(name)
|
107
|
+
:date
|
108
|
+
elsif table_klass && table_klass.coerced_sqlserver_time_columns.include?(name)
|
109
|
+
:time
|
110
|
+
else
|
111
|
+
:datetime
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end # class SQLServerColumn
|
115
|
+
end # module ConnectionAdapters
|
116
|
+
end # module ActiveRecord
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Base
|
3
|
+
def self.sqlserver_connection(config) #:nodoc:
|
4
|
+
config = config.symbolize_keys
|
5
|
+
config.reverse_merge! mode: :dblib
|
6
|
+
mode = config[:mode].to_s.downcase.underscore.to_sym
|
7
|
+
case mode
|
8
|
+
when :dblib
|
9
|
+
require 'tiny_tds'
|
10
|
+
when :odbc
|
11
|
+
raise ArgumentError, 'Missing :dsn configuration.' unless config.key?(:dsn)
|
12
|
+
require 'odbc'
|
13
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
|
14
|
+
else
|
15
|
+
raise ArgumentError, "Unknown connection mode in #{config.inspect}."
|
16
|
+
end
|
17
|
+
ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(mode: mode))
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.did_retry_sqlserver_connection(connection, count)
|
21
|
+
logger.info "CONNECTION RETRY: #{connection.class.name} retry ##{count}."
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.did_lose_sqlserver_connection(connection)
|
25
|
+
logger.info "CONNECTION LOST: #{connection.class.name}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'active_record/test_case.rb'
|
2
|
+
|
3
|
+
# TODO: I'm struggling to figure out how to unsubscribe from only one 'sql.active_record'
|
4
|
+
# This is a temporary hack until we can just get the sqlserver_ignored regex in rails
|
5
|
+
ActiveSupport::Notifications.notifier.listeners_for('sql.active_record').each do |listener|
|
6
|
+
if listener.inspect =~ /ActiveRecord::SQLCounter/
|
7
|
+
ActiveSupport::Notifications.unsubscribe(listener)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ActiveRecord
|
12
|
+
class SQLCounter
|
13
|
+
sqlserver_ignored = [/SELECT SCOPE_IDENTITY/, /INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS)/, /SELECT @@version/, /SELECT @@TRANCOUNT/, /(BEGIN|COMMIT|ROLLBACK|SAVE) TRANSACTION/]
|
14
|
+
ignored_sql.concat sqlserver_ignored
|
15
|
+
end
|
16
|
+
ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Arel
|
2
|
+
module Nodes
|
3
|
+
# Extending the Ordering class to be comparison friendly which allows us to call #uniq on a
|
4
|
+
# collection of them. See SelectManager#order for more details.
|
5
|
+
class Ordering < Arel::Nodes::Unary
|
6
|
+
def eql?(other)
|
7
|
+
# Arel::Nodes::Ascending or Arel::Nodes::Desecnding
|
8
|
+
other.is_a?(Arel::Nodes::Ordering) &&
|
9
|
+
expr == other.expr
|
10
|
+
end
|
11
|
+
alias_method :==, :eql?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Arel
|
2
|
+
class SelectManager < Arel::TreeManager
|
3
|
+
AR_CA_SQLSA_NAME = 'ActiveRecord::ConnectionAdapters::SQLServerAdapter'.freeze
|
4
|
+
|
5
|
+
# Getting real Ordering objects is very important for us. We need to be able to call #uniq on
|
6
|
+
# a colleciton of them reliably as well as using their true object attributes to mutate them
|
7
|
+
# to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
|
8
|
+
# is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
|
9
|
+
alias_method :order_without_sqlserver, :order
|
10
|
+
def order(*expr)
|
11
|
+
return order_without_sqlserver(*expr) unless engine_activerecord_sqlserver_adapter?
|
12
|
+
@ast.orders.concat(expr.map do |x|
|
13
|
+
case x
|
14
|
+
when Arel::Attributes::Attribute
|
15
|
+
table = Arel::Table.new(x.relation.table_alias || x.relation.name)
|
16
|
+
e = table[x.name]
|
17
|
+
Arel::Nodes::Ascending.new e
|
18
|
+
when Arel::Nodes::Ordering
|
19
|
+
x
|
20
|
+
when String
|
21
|
+
x.split(',').map do |s|
|
22
|
+
s = x if x.strip =~ /\A\b\w+\b\(.*,.*\)(\s+(ASC|DESC))?\Z/i # Allow functions with comma(s) to pass thru.
|
23
|
+
s.strip!
|
24
|
+
d = s =~ /(ASC|DESC)\Z/i ? Regexp.last_match[1].upcase : nil
|
25
|
+
e = d.nil? ? s : s.mb_chars[0...-d.length].strip
|
26
|
+
e = Arel.sql(e)
|
27
|
+
d && d == 'DESC' ? Arel::Nodes::Descending.new(e) : Arel::Nodes::Ascending.new(e)
|
28
|
+
end
|
29
|
+
else
|
30
|
+
e = Arel.sql(x.to_s)
|
31
|
+
Arel::Nodes::Ascending.new e
|
32
|
+
end
|
33
|
+
end.flatten)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# A friendly over ride that allows us to put a special lock object that can have a default or pass
|
38
|
+
# custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
|
39
|
+
alias_method :lock_without_sqlserver, :lock
|
40
|
+
def lock(locking = true)
|
41
|
+
if engine_activerecord_sqlserver_adapter?
|
42
|
+
case locking
|
43
|
+
when true
|
44
|
+
locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
|
45
|
+
when Arel::Nodes::SqlLiteral
|
46
|
+
when String
|
47
|
+
locking = Arel.sql locking
|
48
|
+
end
|
49
|
+
@ast.lock = Arel::Nodes::Lock.new(locking)
|
50
|
+
self
|
51
|
+
else
|
52
|
+
lock_without_sqlserver(locking)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def engine_activerecord_sqlserver_adapter?
|
59
|
+
@engine.connection && @engine.connection.class.name == AR_CA_SQLSA_NAME
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -1,214 +1,217 @@
|
|
1
|
-
require 'arel'
|
2
|
-
|
3
1
|
module Arel
|
4
|
-
|
5
|
-
module Nodes
|
6
|
-
|
7
|
-
# Extending the Ordering class to be comparrison friendly which allows us to call #uniq on a
|
8
|
-
# collection of them. See SelectManager#order for more details.
|
9
|
-
class Ordering < Arel::Nodes::Unary
|
10
|
-
def hash
|
11
|
-
expr.hash
|
12
|
-
end
|
13
|
-
def ==(other)
|
14
|
-
other.is_a?(Arel::Nodes::Ordering) && self.expr == other.expr
|
15
|
-
end
|
16
|
-
def eql?(other)
|
17
|
-
self == other
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
class SelectManager < Arel::TreeManager
|
24
|
-
|
25
|
-
AR_CA_SQLSA_NAME = 'ActiveRecord::ConnectionAdapters::SQLServerAdapter'.freeze
|
26
|
-
|
27
|
-
# Getting real Ordering objects is very important for us. We need to be able to call #uniq on
|
28
|
-
# a colleciton of them reliably as well as using their true object attributes to mutate them
|
29
|
-
# to grouping objects for the inner sql during a select statment with an offset/rownumber. So this
|
30
|
-
# is here till ActiveRecord & ARel does this for us instead of using SqlLiteral objects.
|
31
|
-
alias :order_without_sqlserver :order
|
32
|
-
def order(*expr)
|
33
|
-
return order_without_sqlserver(*expr) unless engine_activerecord_sqlserver_adapter?
|
34
|
-
@ast.orders.concat(expr.map{ |x|
|
35
|
-
case x
|
36
|
-
when Arel::Attributes::Attribute
|
37
|
-
table = Arel::Table.new(x.relation.table_alias || x.relation.name)
|
38
|
-
e = table[x.name]
|
39
|
-
Arel::Nodes::Ascending.new e
|
40
|
-
when Arel::Nodes::Ordering
|
41
|
-
x
|
42
|
-
when String
|
43
|
-
x.split(',').map do |s|
|
44
|
-
s = x if x.strip =~ /\A\b\w+\b\(.*,.*\)(\s+(ASC|DESC))?\Z/i # Allow functions with comma(s) to pass thru.
|
45
|
-
s.strip!
|
46
|
-
d = s =~ /(ASC|DESC)\Z/i ? $1.upcase : nil
|
47
|
-
e = d.nil? ? s : s.mb_chars[0...-d.length].strip
|
48
|
-
e = Arel.sql(e)
|
49
|
-
d && d == "DESC" ? Arel::Nodes::Descending.new(e) : Arel::Nodes::Ascending.new(e)
|
50
|
-
end
|
51
|
-
else
|
52
|
-
e = Arel.sql(x.to_s)
|
53
|
-
Arel::Nodes::Ascending.new e
|
54
|
-
end
|
55
|
-
}.flatten)
|
56
|
-
self
|
57
|
-
end
|
58
|
-
|
59
|
-
# A friendly over ride that allows us to put a special lock object that can have a default or pass
|
60
|
-
# custom string hints down. See the visit_Arel_Nodes_LockWithSQLServer delegation method.
|
61
|
-
alias :lock_without_sqlserver :lock
|
62
|
-
def lock(locking=true)
|
63
|
-
if engine_activerecord_sqlserver_adapter?
|
64
|
-
case locking
|
65
|
-
when true
|
66
|
-
locking = Arel.sql('WITH(HOLDLOCK, ROWLOCK)')
|
67
|
-
when Arel::Nodes::SqlLiteral
|
68
|
-
when String
|
69
|
-
locking = Arel.sql locking
|
70
|
-
end
|
71
|
-
@ast.lock = Arel::Nodes::Lock.new(locking)
|
72
|
-
self
|
73
|
-
else
|
74
|
-
lock_without_sqlserver(locking)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
def engine_activerecord_sqlserver_adapter?
|
81
|
-
@engine.connection && @engine.connection.class.name == AR_CA_SQLSA_NAME
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
2
|
module Visitors
|
87
3
|
class SQLServer < Arel::Visitors::ToSql
|
88
|
-
|
89
4
|
private
|
90
5
|
|
91
6
|
# SQLServer ToSql/Visitor (Overides)
|
92
|
-
|
93
|
-
def visit_Arel_Nodes_SelectStatement(o)
|
7
|
+
def visit_Arel_Nodes_SelectStatement(o, a)
|
94
8
|
if complex_count_sql?(o)
|
95
|
-
visit_Arel_Nodes_SelectStatementForComplexCount(o)
|
9
|
+
visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
|
10
|
+
elsif distinct_non_present_orders?(o, a)
|
11
|
+
visit_Arel_Nodes_SelectStatementDistinctNonPresentOrders(o, a)
|
96
12
|
elsif o.offset
|
97
|
-
visit_Arel_Nodes_SelectStatementWithOffset(o)
|
13
|
+
visit_Arel_Nodes_SelectStatementWithOffset(o, a)
|
98
14
|
else
|
99
|
-
visit_Arel_Nodes_SelectStatementWithOutOffset(o)
|
15
|
+
visit_Arel_Nodes_SelectStatementWithOutOffset(o, a)
|
100
16
|
end
|
101
17
|
end
|
102
|
-
|
103
|
-
def visit_Arel_Nodes_UpdateStatement(o)
|
18
|
+
|
19
|
+
def visit_Arel_Nodes_UpdateStatement(o, a)
|
104
20
|
if o.orders.any? && o.limit.nil?
|
105
|
-
o.limit = Nodes::Limit.new(
|
21
|
+
o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
|
106
22
|
end
|
107
23
|
super
|
108
24
|
end
|
109
25
|
|
110
|
-
def visit_Arel_Nodes_Offset(o)
|
111
|
-
"WHERE [__rnt].[__rn] > (#{visit o.expr})"
|
26
|
+
def visit_Arel_Nodes_Offset(o, a)
|
27
|
+
"WHERE [__rnt].[__rn] > (#{visit o.expr, a})"
|
112
28
|
end
|
113
29
|
|
114
|
-
def visit_Arel_Nodes_Limit(o)
|
115
|
-
"TOP (#{visit o.expr})"
|
30
|
+
def visit_Arel_Nodes_Limit(o, a)
|
31
|
+
"TOP (#{visit o.expr, a})"
|
116
32
|
end
|
117
33
|
|
118
|
-
def visit_Arel_Nodes_Lock(o)
|
119
|
-
visit o.expr
|
34
|
+
def visit_Arel_Nodes_Lock(o, a)
|
35
|
+
visit o.expr, a
|
120
36
|
end
|
121
|
-
|
122
|
-
def visit_Arel_Nodes_Ordering(o)
|
37
|
+
|
38
|
+
def visit_Arel_Nodes_Ordering(o, a)
|
123
39
|
if o.respond_to?(:direction)
|
124
|
-
"#{visit o.expr} #{o.ascending? ? 'ASC' : 'DESC'}"
|
40
|
+
"#{visit o.expr, a} #{o.ascending? ? 'ASC' : 'DESC'}"
|
125
41
|
else
|
126
|
-
visit o.expr
|
42
|
+
visit o.expr, a
|
127
43
|
end
|
128
44
|
end
|
129
|
-
|
130
|
-
def visit_Arel_Nodes_Bin(o)
|
131
|
-
"#{visit o.expr} #{@connection.cs_equality_operator}"
|
45
|
+
|
46
|
+
def visit_Arel_Nodes_Bin(o, a)
|
47
|
+
"#{visit o.expr, a} #{@connection.cs_equality_operator}"
|
132
48
|
end
|
133
49
|
|
134
50
|
# SQLServer ToSql/Visitor (Additions)
|
135
51
|
|
136
|
-
|
52
|
+
# This constructs a query using DENSE_RANK() and ROW_NUMBER() to allow
|
53
|
+
# ordering a DISTINCT set of data by columns in another table that are
|
54
|
+
# not part of what we actually want to be DISTINCT. Without this, it is
|
55
|
+
# possible for the DISTINCT qualifier combined with TOP to return fewer
|
56
|
+
# rows than were requested.
|
57
|
+
def visit_Arel_Nodes_SelectStatementDistinctNonPresentOrders(o, a)
|
58
|
+
core = o.cores.first
|
59
|
+
projections = core.projections
|
60
|
+
groups = core.groups
|
61
|
+
orders = o.orders.uniq
|
62
|
+
|
63
|
+
select_frags = projections.map do |x|
|
64
|
+
frag = projection_to_sql_remove_distinct(x, core, a)
|
65
|
+
# Remove the table specifier
|
66
|
+
frag.gsub!(/^[^\.]*\./, '')
|
67
|
+
# If there is an alias, remove everything but
|
68
|
+
frag.gsub(/^.*\sAS\s+/i, '')
|
69
|
+
end
|
70
|
+
|
71
|
+
if o.offset
|
72
|
+
select_frags << 'ROW_NUMBER() OVER (ORDER BY __order) AS __offset'
|
73
|
+
else
|
74
|
+
select_frags << '__order'
|
75
|
+
end
|
76
|
+
|
77
|
+
projection_list = projections.map { |x| projection_to_sql_remove_distinct(x, core, a) }.join(', ')
|
78
|
+
|
79
|
+
sql = [
|
80
|
+
('SELECT'),
|
81
|
+
(visit(core.set_quantifier, a) if core.set_quantifier && !o.offset),
|
82
|
+
(visit(o.limit, a) if o.limit && !o.offset),
|
83
|
+
(select_frags.join(', ')),
|
84
|
+
('FROM ('),
|
85
|
+
('SELECT'),
|
86
|
+
(
|
87
|
+
[
|
88
|
+
(projection_list),
|
89
|
+
(', DENSE_RANK() OVER ('),
|
90
|
+
("ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}" unless orders.empty?),
|
91
|
+
(') AS __order'),
|
92
|
+
(', ROW_NUMBER() OVER ('),
|
93
|
+
("PARTITION BY #{projection_list}" if !orders.empty?),
|
94
|
+
(" ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}" unless orders.empty?),
|
95
|
+
(') AS __joined_row_num')
|
96
|
+
].join('')
|
97
|
+
),
|
98
|
+
(source_with_lock_for_select_statement(o, a)),
|
99
|
+
("WHERE #{core.wheres.map { |x| visit(x, a) }.join ' AND ' }" unless core.wheres.empty?),
|
100
|
+
("GROUP BY #{groups.map { |x| visit(x, a) }.join ', ' }" unless groups.empty?),
|
101
|
+
(visit(core.having, a) if core.having),
|
102
|
+
(') AS __sq'),
|
103
|
+
('WHERE __joined_row_num = 1'),
|
104
|
+
('ORDER BY __order' unless o.offset)
|
105
|
+
].compact.join(' ')
|
106
|
+
|
107
|
+
if o.offset
|
108
|
+
sql = [
|
109
|
+
('SELECT'),
|
110
|
+
(visit(core.set_quantifier, a) if core.set_quantifier),
|
111
|
+
(visit(o.limit, a) if o.limit),
|
112
|
+
('*'),
|
113
|
+
('FROM (' + sql + ') AS __osq'),
|
114
|
+
("WHERE __offset > #{visit(o.offset.expr, a)}"),
|
115
|
+
('ORDER BY __offset')
|
116
|
+
].join(' ')
|
117
|
+
end
|
118
|
+
|
119
|
+
sql
|
120
|
+
end
|
121
|
+
|
122
|
+
def visit_Arel_Nodes_SelectStatementWithOutOffset(o, a, windowed = false)
|
137
123
|
find_and_fix_uncorrelated_joins_in_select_statement(o)
|
138
124
|
core = o.cores.first
|
139
125
|
projections = core.projections
|
140
126
|
groups = core.groups
|
141
127
|
orders = o.orders.uniq
|
142
128
|
if windowed
|
143
|
-
projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x) }
|
144
|
-
groups = projections.map { |x| projection_without_expression(x) } if windowed_single_distinct_select_statement?(o) && groups.empty?
|
129
|
+
projections = function_select_statement?(o) ? projections : projections.map { |x| projection_without_expression(x, a) }
|
130
|
+
groups = projections.map { |x| projection_without_expression(x, a) } if windowed_single_distinct_select_statement?(o) && groups.empty?
|
145
131
|
groups += orders.map { |x| Arel.sql(x.expr) } if windowed_single_distinct_select_statement?(o)
|
146
|
-
elsif eager_limiting_select_statement?(o)
|
147
|
-
projections = projections.map { |x| projection_without_expression(x) }
|
148
|
-
groups = projections.map { |x| projection_without_expression(x) }
|
132
|
+
elsif eager_limiting_select_statement?(o, a)
|
133
|
+
projections = projections.map { |x| projection_without_expression(x, a) }
|
134
|
+
groups = projections.map { |x| projection_without_expression(x, a) }
|
149
135
|
orders = orders.map do |x|
|
150
|
-
expr = Arel.sql projection_without_expression(x.expr)
|
136
|
+
expr = Arel.sql projection_without_expression(x.expr, a)
|
151
137
|
x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
|
152
138
|
end
|
153
|
-
elsif top_one_everything_for_through_join?(o)
|
154
|
-
projections = projections.map { |x| projection_without_expression(x) }
|
139
|
+
elsif top_one_everything_for_through_join?(o, a)
|
140
|
+
projections = projections.map { |x| projection_without_expression(x, a) }
|
155
141
|
end
|
156
|
-
[
|
157
|
-
(
|
158
|
-
(visit(
|
159
|
-
(
|
160
|
-
(
|
161
|
-
|
162
|
-
|
163
|
-
(
|
164
|
-
(
|
142
|
+
[
|
143
|
+
('SELECT' unless windowed),
|
144
|
+
(visit(core.set_quantifier, a) if core.set_quantifier && !windowed),
|
145
|
+
(visit(o.limit, a) if o.limit && !windowed),
|
146
|
+
(projections.map do |x|
|
147
|
+
v = visit(x, a)
|
148
|
+
v == '1' ? '1 AS [__wrp]' : v
|
149
|
+
end.join(', ')),
|
150
|
+
(source_with_lock_for_select_statement(o, a)),
|
151
|
+
("WHERE #{core.wheres.map { |x| visit(x, a) }.join ' AND ' }" unless core.wheres.empty?),
|
152
|
+
("GROUP BY #{groups.map { |x| visit(x, a) }.join ', ' }" unless groups.empty?),
|
153
|
+
(visit(core.having, a) if core.having),
|
154
|
+
("ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}" if !orders.empty? && !windowed)
|
165
155
|
].compact.join ' '
|
166
156
|
end
|
167
157
|
|
168
|
-
def visit_Arel_Nodes_SelectStatementWithOffset(o)
|
158
|
+
def visit_Arel_Nodes_SelectStatementWithOffset(o, a)
|
169
159
|
core = o.cores.first
|
170
|
-
o.limit ||= Arel::Nodes::Limit.new(
|
160
|
+
o.limit ||= Arel::Nodes::Limit.new(9_223_372_036_854_775_807)
|
171
161
|
orders = rowtable_orders(o)
|
172
|
-
[
|
173
|
-
|
174
|
-
(
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
162
|
+
[
|
163
|
+
'SELECT',
|
164
|
+
(visit(o.limit, a) if o.limit && !windowed_single_distinct_select_statement?(o)),
|
165
|
+
(rowtable_projections(o, a).map { |x| visit(x, a) }.join(', ')),
|
166
|
+
'FROM (',
|
167
|
+
"SELECT #{core.set_quantifier ? 'DISTINCT DENSE_RANK()' : 'ROW_NUMBER()'} OVER (ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}) AS [__rn],",
|
168
|
+
visit_Arel_Nodes_SelectStatementWithOutOffset(o, a, true),
|
169
|
+
') AS [__rnt]',
|
170
|
+
(visit(o.offset, a) if o.offset),
|
171
|
+
'ORDER BY [__rnt].[__rn] ASC'
|
181
172
|
].compact.join ' '
|
182
173
|
end
|
183
174
|
|
184
|
-
def visit_Arel_Nodes_SelectStatementForComplexCount(o)
|
175
|
+
def visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
|
185
176
|
core = o.cores.first
|
186
177
|
o.limit.expr = Arel.sql("#{o.limit.expr} + #{o.offset ? o.offset.expr : 0}") if o.limit
|
187
178
|
orders = rowtable_orders(o)
|
188
|
-
[
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
")
|
200
|
-
|
179
|
+
[
|
180
|
+
'SELECT COUNT([count]) AS [count_id]',
|
181
|
+
'FROM (',
|
182
|
+
'SELECT',
|
183
|
+
(visit(o.limit, a) if o.limit),
|
184
|
+
"ROW_NUMBER() OVER (ORDER BY #{orders.map { |x| visit(x, a) }.join(', ')}) AS [__rn],",
|
185
|
+
'1 AS [count]',
|
186
|
+
(source_with_lock_for_select_statement(o, a)),
|
187
|
+
("WHERE #{core.wheres.map { |x| visit(x, a) }.join ' AND ' }" unless core.wheres.empty?),
|
188
|
+
("GROUP BY #{core.groups.map { |x| visit(x, a) }.join ', ' }" unless core.groups.empty?),
|
189
|
+
(visit(core.having, a) if core.having),
|
190
|
+
("ORDER BY #{o.orders.map { |x| visit(x, a) }.join(', ')}" unless o.orders.empty?),
|
191
|
+
') AS [__rnt]',
|
192
|
+
(visit(o.offset, a) if o.offset)
|
201
193
|
].compact.join ' '
|
202
194
|
end
|
203
195
|
|
204
|
-
|
205
196
|
# SQLServer Helpers
|
206
197
|
|
207
|
-
def
|
198
|
+
def projection_to_sql_remove_distinct(x, core, a)
|
199
|
+
frag = Arel.sql(visit(x, a))
|
200
|
+
# In Rails 4.0.0, DISTINCT was in a projection, whereas with 4.0.1
|
201
|
+
# it is now stored in the set_quantifier. This moves it to the correct
|
202
|
+
# place so the code works on both 4.0.0 and 4.0.1.
|
203
|
+
if frag =~ /^\s*DISTINCT\s+/i
|
204
|
+
core.set_quantifier = Arel::Nodes::Distinct.new
|
205
|
+
frag.gsub!(/\s*DISTINCT\s+/, '')
|
206
|
+
end
|
207
|
+
frag
|
208
|
+
end
|
209
|
+
|
210
|
+
def source_with_lock_for_select_statement(o, a)
|
208
211
|
core = o.cores.first
|
209
|
-
source = "FROM #{visit(core.source).strip}" if core.source
|
212
|
+
source = "FROM #{visit(core.source, a).strip}" if core.source
|
210
213
|
if source && o.lock
|
211
|
-
lock = visit o.lock
|
214
|
+
lock = visit o.lock, a
|
212
215
|
index = source.match(/FROM [\w\[\]\.]+/)[0].mb_chars.length
|
213
216
|
source.insert index, " #{lock}"
|
214
217
|
else
|
@@ -226,7 +229,7 @@ module Arel
|
|
226
229
|
# elsif Arel::Nodes::JoinSource === core.source
|
227
230
|
# Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
|
228
231
|
# end
|
229
|
-
table_finder = lambda
|
232
|
+
table_finder = lambda do |x|
|
230
233
|
case x
|
231
234
|
when Arel::Table
|
232
235
|
x
|
@@ -235,7 +238,7 @@ module Arel
|
|
235
238
|
when Arel::Nodes::Join
|
236
239
|
table_finder.call(x.left)
|
237
240
|
end
|
238
|
-
|
241
|
+
end
|
239
242
|
table_finder.call(core.froms)
|
240
243
|
end
|
241
244
|
|
@@ -246,25 +249,88 @@ module Arel
|
|
246
249
|
((p1.respond_to?(:distinct) && p1.distinct) ||
|
247
250
|
p1.respond_to?(:include?) && p1.include?('DISTINCT'))
|
248
251
|
end
|
249
|
-
|
252
|
+
|
253
|
+
# Determine if the SELECT statement is asking for DISTINCT results,
|
254
|
+
# but is using columns not part of the SELECT list in the ORDER BY.
|
255
|
+
# This is necessary because SQL Server requires all ORDER BY entries
|
256
|
+
# be in the SELECT list with DISTINCT. However, these ordering columns
|
257
|
+
# can cause duplicate rows, which affect when using a limit.
|
258
|
+
def distinct_non_present_orders?(o, a)
|
259
|
+
projections = o.cores.first.projections
|
260
|
+
|
261
|
+
sq = o.cores.first.set_quantifier
|
262
|
+
p1 = projections.first
|
263
|
+
|
264
|
+
found_distinct = sq && sq.class.to_s =~ /Distinct/
|
265
|
+
if (p1.respond_to?(:distinct) && p1.distinct) || (p1.respond_to?(:include?) && p1.include?('DISTINCT'))
|
266
|
+
found_distinct = true
|
267
|
+
end
|
268
|
+
|
269
|
+
return false if !found_distinct || o.orders.uniq.empty?
|
270
|
+
|
271
|
+
tables_all_columns = []
|
272
|
+
expressions = projections.map do |p|
|
273
|
+
visit(p, a).split(',').map do |x|
|
274
|
+
x.strip!
|
275
|
+
# Rails 4.0.0 included DISTINCT in the first projection
|
276
|
+
x.gsub!(/\s*DISTINCT\s+/, '')
|
277
|
+
# Aliased column names
|
278
|
+
x.gsub!(/\s+AS\s+\w+/i, '')
|
279
|
+
# Identifier quoting
|
280
|
+
x.gsub!(/\[|\]/, '')
|
281
|
+
star_match = /^(\w+)\.\*$/.match(x)
|
282
|
+
tables_all_columns << star_match[1] if star_match
|
283
|
+
x.strip.downcase
|
284
|
+
end.join(', ')
|
285
|
+
end
|
286
|
+
|
287
|
+
# Make sure each order by is in the select list, otherwise there needs
|
288
|
+
# to be a subquery with row_numbe()
|
289
|
+
o.orders.uniq.each do |order|
|
290
|
+
order = visit(order, a)
|
291
|
+
order.strip!
|
292
|
+
|
293
|
+
order.gsub!(/\s+(asc|desc)/i, '')
|
294
|
+
# Identifier quoting
|
295
|
+
order.gsub!(/\[|\]/, '')
|
296
|
+
|
297
|
+
order.strip!
|
298
|
+
order.downcase!
|
299
|
+
|
300
|
+
# If we selected all columns from a table, the order is ok
|
301
|
+
table_match = /^(\w+)\.\w+$/.match(order)
|
302
|
+
next if table_match && tables_all_columns.include?(table_match[1])
|
303
|
+
|
304
|
+
next if expressions.include?(order)
|
305
|
+
|
306
|
+
return true
|
307
|
+
end
|
308
|
+
|
309
|
+
# We didn't find anything in the order by no being selected
|
310
|
+
false
|
311
|
+
end
|
312
|
+
|
250
313
|
def windowed_single_distinct_select_statement?(o)
|
251
|
-
o.limit &&
|
314
|
+
o.limit &&
|
315
|
+
o.offset &&
|
316
|
+
single_distinct_select_statement?(o)
|
252
317
|
end
|
253
|
-
|
254
|
-
def single_distinct_select_everything_statement?(o)
|
255
|
-
single_distinct_select_statement?(o) &&
|
318
|
+
|
319
|
+
def single_distinct_select_everything_statement?(o, a)
|
320
|
+
single_distinct_select_statement?(o) &&
|
321
|
+
visit(o.cores.first.projections.first, a).ends_with?('.*')
|
256
322
|
end
|
257
|
-
|
258
|
-
def top_one_everything_for_through_join?(o)
|
259
|
-
single_distinct_select_everything_statement?(o) &&
|
260
|
-
(o.limit && !o.offset) &&
|
323
|
+
|
324
|
+
def top_one_everything_for_through_join?(o, a)
|
325
|
+
single_distinct_select_everything_statement?(o, a) &&
|
326
|
+
(o.limit && !o.offset) &&
|
261
327
|
join_in_select_statement?(o)
|
262
328
|
end
|
263
329
|
|
264
|
-
def all_projections_aliased_in_select_statement?(o)
|
330
|
+
def all_projections_aliased_in_select_statement?(o, a)
|
265
331
|
projections = o.cores.first.projections
|
266
332
|
projections.all? do |x|
|
267
|
-
visit(x).split(',').all? { |y| y.include?(' AS ') }
|
333
|
+
visit(x, a).split(',').all? { |y| y.include?(' AS ') }
|
268
334
|
end
|
269
335
|
end
|
270
336
|
|
@@ -273,12 +339,12 @@ module Arel
|
|
273
339
|
core.projections.any? { |x| Arel::Nodes::Function === x }
|
274
340
|
end
|
275
341
|
|
276
|
-
def eager_limiting_select_statement?(o)
|
342
|
+
def eager_limiting_select_statement?(o, a)
|
277
343
|
core = o.cores.first
|
278
|
-
single_distinct_select_statement?(o) &&
|
279
|
-
(o.limit && !o.offset) &&
|
280
|
-
core.groups.empty? &&
|
281
|
-
!single_distinct_select_everything_statement?(o)
|
344
|
+
single_distinct_select_statement?(o) &&
|
345
|
+
(o.limit && !o.offset) &&
|
346
|
+
core.groups.empty? &&
|
347
|
+
!single_distinct_select_everything_statement?(o, a)
|
282
348
|
end
|
283
349
|
|
284
350
|
def join_in_select_statement?(o)
|
@@ -293,7 +359,7 @@ module Arel
|
|
293
359
|
o.limit &&
|
294
360
|
!join_in_select_statement?(o)
|
295
361
|
end
|
296
|
-
|
362
|
+
|
297
363
|
def select_primary_key_sql?(o)
|
298
364
|
core = o.cores.first
|
299
365
|
return false if core.projections.size != 1
|
@@ -320,22 +386,22 @@ module Arel
|
|
320
386
|
j2 = core.froms.right
|
321
387
|
return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::SqlLiteral === j2 && j2.include?('JOIN ')
|
322
388
|
j1_tn = j1.right.name
|
323
|
-
j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[],1)
|
389
|
+
j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[], 1)
|
324
390
|
return unless j1_tn == j2_tn
|
325
391
|
on_index = j2.index(' ON ')
|
326
392
|
j2.insert on_index, " AS [#{j2_tn}_crltd]"
|
327
393
|
j2.sub! "[#{j2_tn}].", "[#{j2_tn}_crltd]."
|
328
394
|
end
|
329
395
|
|
330
|
-
def rowtable_projections(o)
|
396
|
+
def rowtable_projections(o, a)
|
331
397
|
core = o.cores.first
|
332
398
|
if windowed_single_distinct_select_statement?(o) && core.groups.blank?
|
333
399
|
tn = table_from_select_statement(o).name
|
334
400
|
core.projections.map do |x|
|
335
401
|
x.dup.tap do |p|
|
336
402
|
p.sub! 'DISTINCT', ''
|
337
|
-
p.insert 0, visit(o.limit) if o.limit
|
338
|
-
p.gsub!
|
403
|
+
p.insert 0, visit(o.limit, a) if o.limit
|
404
|
+
p.gsub!(/\[?#{tn}\]?\./, '[__rnt].')
|
339
405
|
p.strip!
|
340
406
|
end
|
341
407
|
end
|
@@ -343,14 +409,14 @@ module Arel
|
|
343
409
|
tn = table_from_select_statement(o).name
|
344
410
|
core.projections.map do |x|
|
345
411
|
x.dup.tap do |p|
|
346
|
-
p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit)}".strip if o.limit
|
347
|
-
p.gsub!
|
412
|
+
p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit, a)}".strip if o.limit
|
413
|
+
p.gsub!(/\[?#{tn}\]?\./, '[__rnt].')
|
348
414
|
p.strip!
|
349
415
|
end
|
350
416
|
end
|
351
|
-
elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o)
|
417
|
+
elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o, a)
|
352
418
|
core.projections.map do |x|
|
353
|
-
Arel.sql visit(x).split(',').map{ |y| y.split(' AS ').last.strip }.join(', ')
|
419
|
+
Arel.sql visit(x, a).split(',').map { |y| y.split(' AS ').last.strip }.join(', ')
|
354
420
|
end
|
355
421
|
elsif select_primary_key_sql?(o)
|
356
422
|
[Arel.sql("[__rnt].#{quote_column_name(core.projections.first.name)}")]
|
@@ -360,7 +426,6 @@ module Arel
|
|
360
426
|
end
|
361
427
|
|
362
428
|
def rowtable_orders(o)
|
363
|
-
core = o.cores.first
|
364
429
|
if !o.orders.empty?
|
365
430
|
o.orders
|
366
431
|
else
|
@@ -371,19 +436,17 @@ module Arel
|
|
371
436
|
end
|
372
437
|
|
373
438
|
# TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
|
374
|
-
def projection_without_expression(projection)
|
375
|
-
Arel.sql(visit(projection).split(',').map do |x|
|
439
|
+
def projection_without_expression(projection, a)
|
440
|
+
Arel.sql(visit(projection, a).split(',').map do |x|
|
376
441
|
x.strip!
|
377
|
-
x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/,'\3')
|
378
|
-
x.sub!(/^DISTINCT\s*/,'')
|
379
|
-
x.sub!(/TOP\s*\(\d+\)\s*/i,'')
|
442
|
+
x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/, '\3')
|
443
|
+
x.sub!(/^DISTINCT\s*/, '')
|
444
|
+
x.sub!(/TOP\s*\(\d+\)\s*/i, '')
|
380
445
|
x.strip
|
381
446
|
end.join(', '))
|
382
447
|
end
|
383
|
-
|
384
448
|
end
|
385
449
|
end
|
386
|
-
|
387
450
|
end
|
388
451
|
|
389
452
|
Arel::Visitors::VISITORS['sqlserver'] = Arel::Visitors::SQLServer
|