activerecord-sqlserver-adapter 4.1.8 → 4.2.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|