activerecord-sqlserver-adapter 4.1.8 → 4.2.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +15 -0
- data/CHANGELOG.md +60 -0
- data/Gemfile +45 -0
- data/Guardfile +29 -0
- data/MIT-LICENSE +5 -5
- data/README.md +193 -0
- data/RUNNING_UNIT_TESTS.md +95 -0
- data/Rakefile +48 -0
- data/activerecord-sqlserver-adapter.gemspec +28 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +5 -15
- data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -4
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +9 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +3 -1
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +130 -151
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -25
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +39 -78
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +71 -47
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -30
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +112 -108
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +4 -2
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +52 -7
- data/lib/active_record/connection_adapters/sqlserver/transaction.rb +52 -0
- data/lib/active_record/connection_adapters/sqlserver/type.rb +46 -0
- data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/castable.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/char.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/core_ext/value.rb +39 -0
- data/lib/active_record/connection_adapters/sqlserver/type/date.rb +14 -0
- data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +37 -0
- data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/float.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver/type/real.rb +17 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +13 -0
- data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +21 -0
- data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +24 -0
- data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/time.rb +59 -0
- data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +22 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +15 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +20 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +118 -12
- data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +133 -198
- data/lib/active_record/connection_adapters/sqlserver_column.rb +15 -86
- data/lib/active_record/sqlserver_base.rb +2 -0
- data/lib/arel/visitors/sqlserver.rb +120 -393
- data/lib/{arel/arel_sqlserver.rb → arel_sqlserver.rb} +1 -3
- data/test/cases/adapter_test_sqlserver.rb +420 -0
- data/test/cases/coerced_tests.rb +642 -0
- data/test/cases/column_test_sqlserver.rb +703 -0
- data/test/cases/connection_test_sqlserver.rb +216 -0
- data/test/cases/database_statements_test_sqlserver.rb +57 -0
- data/test/cases/execute_procedure_test_sqlserver.rb +38 -0
- data/test/cases/helper_sqlserver.rb +36 -0
- data/test/cases/migration_test_sqlserver.rb +66 -0
- data/test/cases/order_test_sqlserver.rb +147 -0
- data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
- data/test/cases/schema_dumper_test_sqlserver.rb +175 -0
- data/test/cases/schema_test_sqlserver.rb +54 -0
- data/test/cases/scratchpad_test_sqlserver.rb +9 -0
- data/test/cases/showplan_test_sqlserver.rb +65 -0
- data/test/cases/specific_schema_test_sqlserver.rb +118 -0
- data/test/cases/transaction_test_sqlserver.rb +61 -0
- data/test/cases/utils_test_sqlserver.rb +91 -0
- data/test/cases/uuid_test_sqlserver.rb +41 -0
- data/test/config.yml +35 -0
- data/test/fixtures/1px.gif +0 -0
- data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
- data/test/models/sqlserver/customers_view.rb +3 -0
- data/test/models/sqlserver/datatype.rb +3 -0
- data/test/models/sqlserver/datatype_migration.rb +3 -0
- data/test/models/sqlserver/dollar_table_name.rb +3 -0
- data/test/models/sqlserver/edge_schema.rb +13 -0
- data/test/models/sqlserver/fk_has_fk.rb +3 -0
- data/test/models/sqlserver/fk_has_pk.rb +3 -0
- data/test/models/sqlserver/natural_pk_data.rb +4 -0
- data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
- data/test/models/sqlserver/no_pk_data.rb +3 -0
- data/test/models/sqlserver/quoted_table.rb +7 -0
- data/test/models/sqlserver/quoted_view_1.rb +3 -0
- data/test/models/sqlserver/quoted_view_2.rb +3 -0
- data/test/models/sqlserver/string_default.rb +3 -0
- data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
- data/test/models/sqlserver/string_defaults_view.rb +3 -0
- data/test/models/sqlserver/tinyint_pk.rb +3 -0
- data/test/models/sqlserver/upper.rb +3 -0
- data/test/models/sqlserver/uppered.rb +3 -0
- data/test/models/sqlserver/uuid.rb +3 -0
- data/test/schema/datatypes/2012.sql +64 -0
- data/test/schema/sqlserver_specific_schema.rb +181 -0
- data/test/support/coerceable_test_sqlserver.rb +45 -0
- data/test/support/load_schema_sqlserver.rb +29 -0
- data/test/support/minitest_sqlserver.rb +1 -0
- data/test/support/paths_sqlserver.rb +48 -0
- data/test/support/rake_helpers.rb +41 -0
- data/test/support/sql_counter_sqlserver.rb +32 -0
- metadata +271 -21
- data/CHANGELOG +0 -39
- data/VERSION +0 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +0 -17
- data/lib/active_record/sqlserver_test_case.rb +0 -17
- data/lib/arel/nodes_sqlserver.rb +0 -14
- data/lib/arel/select_manager_sqlserver.rb +0 -62
@@ -1,23 +1,23 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
3
|
class SQLServerColumn < Column
|
4
|
-
|
4
|
+
|
5
|
+
def initialize(name, default, cast_type, sql_type = nil, null = true, sqlserver_options = {})
|
6
|
+
super(name, default, cast_type, sql_type, null)
|
5
7
|
@sqlserver_options = sqlserver_options.symbolize_keys
|
6
|
-
|
7
|
-
@primary = @sqlserver_options[:is_identity] || @sqlserver_options[:is_primary]
|
8
|
+
@default_function = @sqlserver_options[:default_function]
|
8
9
|
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def sql_type_for_statement
|
12
|
+
if is_integer? || is_real?
|
13
|
+
sql_type.sub(/\((\d+)?\)/, '')
|
14
|
+
else
|
15
|
+
sql_type
|
13
16
|
end
|
17
|
+
end
|
14
18
|
|
15
|
-
|
16
|
-
|
17
|
-
value = value.force_encoding(Encoding::ASCII_8BIT)
|
18
|
-
end
|
19
|
-
value
|
20
|
-
end
|
19
|
+
def table_name
|
20
|
+
@sqlserver_options[:table_name]
|
21
21
|
end
|
22
22
|
|
23
23
|
def is_identity?
|
@@ -40,77 +40,6 @@ module ActiveRecord
|
|
40
40
|
@sql_type =~ /real/i
|
41
41
|
end
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class Base
|
3
|
+
|
3
4
|
def self.sqlserver_connection(config) #:nodoc:
|
4
5
|
config = config.symbolize_keys
|
5
6
|
config.reverse_merge! mode: :dblib
|
@@ -24,5 +25,6 @@ module ActiveRecord
|
|
24
25
|
def self.did_lose_sqlserver_connection(connection)
|
25
26
|
logger.info "CONNECTION LOST: #{connection.class.name}"
|
26
27
|
end
|
28
|
+
|
27
29
|
end
|
28
30
|
end
|
@@ -1,18 +1,28 @@
|
|
1
1
|
module Arel
|
2
2
|
module Visitors
|
3
3
|
class SQLServer < Arel::Visitors::ToSql
|
4
|
+
|
5
|
+
OFFSET = " OFFSET "
|
6
|
+
ROWS = " ROWS"
|
7
|
+
FETCH = " FETCH NEXT "
|
8
|
+
FETCH0 = " FETCH FIRST (SELECT 0) "
|
9
|
+
ROWS_ONLY = " ROWS ONLY"
|
10
|
+
|
11
|
+
|
4
12
|
private
|
5
13
|
|
6
14
|
# SQLServer ToSql/Visitor (Overides)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
15
|
+
|
16
|
+
def visit_Arel_Nodes_BindParam o, collector
|
17
|
+
collector.add_bind(o) { |i| "@#{i-1}" }
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_Arel_Nodes_Bin o, collector
|
21
|
+
visit o.expr, collector
|
22
|
+
if o.expr.val.is_a? Numeric
|
23
|
+
collector
|
14
24
|
else
|
15
|
-
|
25
|
+
collector << " #{ActiveRecord::ConnectionAdapters::SQLServerAdapter.cs_equality_operator} "
|
16
26
|
end
|
17
27
|
end
|
18
28
|
|
@@ -23,428 +33,145 @@ module Arel
|
|
23
33
|
super
|
24
34
|
end
|
25
35
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def visit_Arel_Nodes_Limit(o, a)
|
31
|
-
"TOP (#{visit o.expr, a})"
|
36
|
+
def visit_Arel_Nodes_Lock o, collector
|
37
|
+
o.expr = Arel.sql('WITH(UPDLOCK)') if o.expr.to_s =~ /FOR UPDATE/
|
38
|
+
collector << SPACE
|
39
|
+
visit o.expr, collector
|
32
40
|
end
|
33
41
|
|
34
|
-
def
|
35
|
-
|
42
|
+
def visit_Arel_Nodes_Offset o, collector
|
43
|
+
collector << OFFSET
|
44
|
+
visit o.expr, collector
|
45
|
+
collector << ROWS
|
36
46
|
end
|
37
47
|
|
38
|
-
def
|
39
|
-
if o
|
40
|
-
|
48
|
+
def visit_Arel_Nodes_Limit o, collector
|
49
|
+
if node_value(o) == 0
|
50
|
+
collector << FETCH0
|
51
|
+
collector << ROWS_ONLY
|
41
52
|
else
|
42
|
-
|
53
|
+
collector << FETCH
|
54
|
+
visit o.expr, collector
|
55
|
+
collector << ROWS_ONLY
|
43
56
|
end
|
44
57
|
end
|
45
58
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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, '')
|
59
|
+
def visit_Arel_Nodes_SelectStatement o, collector
|
60
|
+
@select_statement = o
|
61
|
+
if o.with
|
62
|
+
collector = visit o.with, collector
|
63
|
+
collector << SPACE
|
69
64
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
65
|
+
collector = o.cores.inject(collector) { |c,x|
|
66
|
+
visit_Arel_Nodes_SelectCore(x, c)
|
67
|
+
}
|
68
|
+
collector = visit_Orders_And_Let_Fetch_Happen o, collector
|
69
|
+
collector = visit_Make_Fetch_Happen o, collector
|
70
|
+
collector
|
71
|
+
ensure
|
72
|
+
@select_statement = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def visit_Arel_Nodes_JoinSource o, collector
|
76
|
+
if o.left
|
77
|
+
collector = visit o.left, collector
|
78
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
|
75
79
|
end
|
76
|
-
|
77
|
-
|
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(' ')
|
80
|
+
if o.right.any?
|
81
|
+
collector << " " if o.left
|
82
|
+
collector = inject_join o.right, collector, ' '
|
117
83
|
end
|
118
|
-
|
119
|
-
sql
|
84
|
+
collector
|
120
85
|
end
|
121
86
|
|
122
|
-
def
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
if windowed
|
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?
|
131
|
-
groups += orders.map { |x| Arel.sql(x.expr) } if windowed_single_distinct_select_statement?(o)
|
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) }
|
135
|
-
orders = orders.map do |x|
|
136
|
-
expr = Arel.sql projection_without_expression(x.expr, a)
|
137
|
-
x.descending? ? Arel::Nodes::Max.new([expr]) : Arel::Nodes::Min.new([expr])
|
138
|
-
end
|
139
|
-
elsif top_one_everything_for_through_join?(o, a)
|
140
|
-
projections = projections.map { |x| projection_without_expression(x, a) }
|
141
|
-
end
|
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)
|
155
|
-
].compact.join ' '
|
156
|
-
end
|
157
|
-
|
158
|
-
def visit_Arel_Nodes_SelectStatementWithOffset(o, a)
|
159
|
-
core = o.cores.first
|
160
|
-
o.limit ||= Arel::Nodes::Limit.new(9_223_372_036_854_775_807)
|
161
|
-
orders = rowtable_orders(o)
|
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'
|
172
|
-
].compact.join ' '
|
87
|
+
def visit_Arel_Nodes_OuterJoin o, collector
|
88
|
+
collector << "LEFT OUTER JOIN "
|
89
|
+
collector = visit o.left, collector
|
90
|
+
collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
|
91
|
+
collector << " "
|
92
|
+
visit o.right, collector
|
173
93
|
end
|
174
94
|
|
175
|
-
|
176
|
-
core = o.cores.first
|
177
|
-
o.limit.expr = Arel.sql("#{o.limit.expr} + #{o.offset ? o.offset.expr : 0}") if o.limit
|
178
|
-
orders = rowtable_orders(o)
|
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)
|
193
|
-
].compact.join ' '
|
194
|
-
end
|
195
|
-
|
196
|
-
# SQLServer Helpers
|
197
|
-
|
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
|
95
|
+
# SQLServer ToSql/Visitor (Additions)
|
209
96
|
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
lock = visit o.lock, a
|
215
|
-
index = source.match(/FROM [\w\[\]\.]+/)[0].mb_chars.length
|
216
|
-
source.insert index, " #{lock}"
|
217
|
-
else
|
218
|
-
source
|
97
|
+
def visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, options = {}
|
98
|
+
if select_statement_lock?
|
99
|
+
collector = visit @select_statement.lock, collector
|
100
|
+
collector << SPACE if options[:space]
|
219
101
|
end
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
case x
|
234
|
-
when Arel::Table
|
235
|
-
x
|
236
|
-
when Arel::Nodes::SqlLiteral
|
237
|
-
Arel::Table.new(x, @engine)
|
238
|
-
when Arel::Nodes::Join
|
239
|
-
table_finder.call(x.left)
|
240
|
-
end
|
102
|
+
collector
|
103
|
+
end
|
104
|
+
|
105
|
+
def visit_Orders_And_Let_Fetch_Happen o, collector
|
106
|
+
make_Fetch_Possible_And_Deterministic o
|
107
|
+
unless o.orders.empty?
|
108
|
+
collector << SPACE
|
109
|
+
collector << ORDER_BY
|
110
|
+
len = o.orders.length - 1
|
111
|
+
o.orders.each_with_index { |x, i|
|
112
|
+
collector = visit(x, collector)
|
113
|
+
collector << COMMA unless len == i
|
114
|
+
}
|
241
115
|
end
|
242
|
-
|
116
|
+
collector
|
243
117
|
end
|
244
118
|
|
245
|
-
def
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
p1.respond_to?(:include?) && p1.include?('DISTINCT'))
|
119
|
+
def visit_Make_Fetch_Happen o, collector
|
120
|
+
o.offset = Nodes::Offset.new(0) if o.limit && !o.offset
|
121
|
+
collector = visit o.offset, collector if o.offset
|
122
|
+
collector = visit o.limit, collector if o.limit
|
123
|
+
collector
|
251
124
|
end
|
252
125
|
|
253
|
-
#
|
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)
|
126
|
+
# SQLServer Helpers
|
305
127
|
|
306
|
-
|
128
|
+
def node_value(node)
|
129
|
+
case node.expr
|
130
|
+
when NilClass then nil
|
131
|
+
when Numeric then node.expr
|
132
|
+
when Arel::Nodes::Unary then node.expr.expr
|
307
133
|
end
|
308
|
-
|
309
|
-
# We didn't find anything in the order by no being selected
|
310
|
-
false
|
311
134
|
end
|
312
135
|
|
313
|
-
def
|
314
|
-
|
315
|
-
o.offset &&
|
316
|
-
single_distinct_select_statement?(o)
|
136
|
+
def select_statement_lock?
|
137
|
+
@select_statement && @select_statement.lock
|
317
138
|
end
|
318
139
|
|
319
|
-
def
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
join_in_select_statement?(o)
|
328
|
-
end
|
329
|
-
|
330
|
-
def all_projections_aliased_in_select_statement?(o, a)
|
331
|
-
projections = o.cores.first.projections
|
332
|
-
projections.all? do |x|
|
333
|
-
visit(x, a).split(',').all? { |y| y.include?(' AS ') }
|
140
|
+
def make_Fetch_Possible_And_Deterministic o
|
141
|
+
return if o.limit.nil? && o.offset.nil?
|
142
|
+
t = table_From_Statement o
|
143
|
+
pk = primary_Key_From_Table t
|
144
|
+
return unless pk
|
145
|
+
if o.orders.empty?
|
146
|
+
# Prefer deterministic vs a simple `(SELECT NULL)` expr.
|
147
|
+
o.orders = [pk.asc]
|
334
148
|
end
|
335
149
|
end
|
336
150
|
|
337
|
-
def
|
338
|
-
core = o.cores.first
|
339
|
-
core.projections.any? { |x| Arel::Nodes::Function === x }
|
340
|
-
end
|
341
|
-
|
342
|
-
def eager_limiting_select_statement?(o, a)
|
151
|
+
def table_From_Statement o
|
343
152
|
core = o.cores.first
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
def join_in_select_statement?(o)
|
351
|
-
core = o.cores.first
|
352
|
-
core.source.right.any? { |x| Arel::Nodes::Join === x }
|
353
|
-
end
|
354
|
-
|
355
|
-
def complex_count_sql?(o)
|
356
|
-
core = o.cores.first
|
357
|
-
core.projections.size == 1 &&
|
358
|
-
Arel::Nodes::Count === core.projections.first &&
|
359
|
-
o.limit &&
|
360
|
-
!join_in_select_statement?(o)
|
361
|
-
end
|
362
|
-
|
363
|
-
def select_primary_key_sql?(o)
|
364
|
-
core = o.cores.first
|
365
|
-
return false if core.projections.size != 1
|
366
|
-
p = core.projections.first
|
367
|
-
t = table_from_select_statement(o)
|
368
|
-
Arel::Attributes::Attribute === p && t.primary_key && t.primary_key.name == p.name
|
369
|
-
end
|
370
|
-
|
371
|
-
def find_and_fix_uncorrelated_joins_in_select_statement(o)
|
372
|
-
core = o.cores.first
|
373
|
-
# TODO: [ARel 2.2] Use #from/#source vs. #froms
|
374
|
-
# return if !join_in_select_statement?(o) || core.source.right.size != 2
|
375
|
-
# j1 = core.source.right.first
|
376
|
-
# j2 = core.source.right.second
|
377
|
-
# return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::StringJoin === j2
|
378
|
-
# j1_tn = j1.left.name
|
379
|
-
# j2_tn = j2.left.match(/JOIN \[(.*)\].*ON/).try(:[],1)
|
380
|
-
# return unless j1_tn == j2_tn
|
381
|
-
# crltd_tn = "#{j1_tn}_crltd"
|
382
|
-
# j1.left.table_alias = crltd_tn
|
383
|
-
# j1.right.expr.left.relation.table_alias = crltd_tn
|
384
|
-
return if !join_in_select_statement?(o) || !(Arel::Nodes::StringJoin === core.froms)
|
385
|
-
j1 = core.froms.left
|
386
|
-
j2 = core.froms.right
|
387
|
-
return unless Arel::Nodes::OuterJoin === j1 && Arel::Nodes::SqlLiteral === j2 && j2.include?('JOIN ')
|
388
|
-
j1_tn = j1.right.name
|
389
|
-
j2_tn = j2.match(/JOIN \[(.*)\].*ON/).try(:[], 1)
|
390
|
-
return unless j1_tn == j2_tn
|
391
|
-
on_index = j2.index(' ON ')
|
392
|
-
j2.insert on_index, " AS [#{j2_tn}_crltd]"
|
393
|
-
j2.sub! "[#{j2_tn}].", "[#{j2_tn}_crltd]."
|
394
|
-
end
|
395
|
-
|
396
|
-
def rowtable_projections(o, a)
|
397
|
-
core = o.cores.first
|
398
|
-
if windowed_single_distinct_select_statement?(o) && core.groups.blank?
|
399
|
-
tn = table_from_select_statement(o).name
|
400
|
-
core.projections.map do |x|
|
401
|
-
x.dup.tap do |p|
|
402
|
-
p.sub! 'DISTINCT', ''
|
403
|
-
p.insert 0, visit(o.limit, a) if o.limit
|
404
|
-
p.gsub!(/\[?#{tn}\]?\./, '[__rnt].')
|
405
|
-
p.strip!
|
406
|
-
end
|
407
|
-
end
|
408
|
-
elsif single_distinct_select_statement?(o)
|
409
|
-
tn = table_from_select_statement(o).name
|
410
|
-
core.projections.map do |x|
|
411
|
-
x.dup.tap do |p|
|
412
|
-
p.sub! 'DISTINCT', "DISTINCT #{visit(o.limit, a)}".strip if o.limit
|
413
|
-
p.gsub!(/\[?#{tn}\]?\./, '[__rnt].')
|
414
|
-
p.strip!
|
415
|
-
end
|
416
|
-
end
|
417
|
-
elsif join_in_select_statement?(o) && all_projections_aliased_in_select_statement?(o, a)
|
418
|
-
core.projections.map do |x|
|
419
|
-
Arel.sql visit(x, a).split(',').map { |y| y.split(' AS ').last.strip }.join(', ')
|
420
|
-
end
|
421
|
-
elsif select_primary_key_sql?(o)
|
422
|
-
[Arel.sql("[__rnt].#{quote_column_name(core.projections.first.name)}")]
|
423
|
-
else
|
424
|
-
[Arel.sql('[__rnt].*')]
|
153
|
+
if Arel::Table === core.from
|
154
|
+
core.from
|
155
|
+
elsif Arel::Nodes::SqlLiteral === core.from
|
156
|
+
Arel::Table.new(core.from)
|
157
|
+
elsif Arel::Nodes::JoinSource === core.source
|
158
|
+
Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
|
425
159
|
end
|
426
160
|
end
|
427
161
|
|
428
|
-
def
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
162
|
+
def primary_Key_From_Table t
|
163
|
+
return unless t
|
164
|
+
return t.primary_key if t.primary_key
|
165
|
+
if engine_pk = t.engine.primary_key
|
166
|
+
pk = t.engine.arel_table[engine_pk]
|
167
|
+
return pk if pk
|
168
|
+
end
|
169
|
+
pk = t.engine.connection.schema_cache.primary_keys(t.engine.table_name)
|
170
|
+
return pk if pk
|
171
|
+
column_name = t.engine.columns.first.try(:name)
|
172
|
+
column_name ? t[column_name] : nil
|
436
173
|
end
|
437
174
|
|
438
|
-
# TODO: We use this for grouping too, maybe make Grouping objects vs SqlLiteral.
|
439
|
-
def projection_without_expression(projection, a)
|
440
|
-
Arel.sql(visit(projection, a).split(',').map do |x|
|
441
|
-
x.strip!
|
442
|
-
x.sub!(/^(COUNT|SUM|MAX|MIN|AVG)\s*(\((.*)\))?/, '\3')
|
443
|
-
x.sub!(/^DISTINCT\s*/, '')
|
444
|
-
x.sub!(/TOP\s*\(\d+\)\s*/i, '')
|
445
|
-
x.strip
|
446
|
-
end.join(', '))
|
447
|
-
end
|
448
175
|
end
|
449
176
|
end
|
450
177
|
end
|