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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +15 -0
  3. data/CHANGELOG.md +60 -0
  4. data/Gemfile +45 -0
  5. data/Guardfile +29 -0
  6. data/MIT-LICENSE +5 -5
  7. data/README.md +193 -0
  8. data/RUNNING_UNIT_TESTS.md +95 -0
  9. data/Rakefile +48 -0
  10. data/activerecord-sqlserver-adapter.gemspec +28 -0
  11. data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +5 -15
  12. data/lib/active_record/connection_adapters/sqlserver/core_ext/attribute_methods.rb +25 -0
  13. data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -4
  14. data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +9 -3
  15. data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +3 -1
  16. data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +130 -151
  17. data/lib/active_record/connection_adapters/sqlserver/errors.rb +0 -25
  18. data/lib/active_record/connection_adapters/sqlserver/quoting.rb +39 -78
  19. data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +71 -47
  20. data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +14 -30
  21. data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +112 -108
  22. data/lib/active_record/connection_adapters/sqlserver/showplan.rb +4 -2
  23. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +1 -1
  24. data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +1 -1
  25. data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +52 -7
  26. data/lib/active_record/connection_adapters/sqlserver/transaction.rb +52 -0
  27. data/lib/active_record/connection_adapters/sqlserver/type.rb +46 -0
  28. data/lib/active_record/connection_adapters/sqlserver/type/big_integer.rb +15 -0
  29. data/lib/active_record/connection_adapters/sqlserver/type/binary.rb +15 -0
  30. data/lib/active_record/connection_adapters/sqlserver/type/boolean.rb +13 -0
  31. data/lib/active_record/connection_adapters/sqlserver/type/castable.rb +15 -0
  32. data/lib/active_record/connection_adapters/sqlserver/type/char.rb +15 -0
  33. data/lib/active_record/connection_adapters/sqlserver/type/core_ext/value.rb +39 -0
  34. data/lib/active_record/connection_adapters/sqlserver/type/date.rb +14 -0
  35. data/lib/active_record/connection_adapters/sqlserver/type/datetime.rb +37 -0
  36. data/lib/active_record/connection_adapters/sqlserver/type/decimal.rb +13 -0
  37. data/lib/active_record/connection_adapters/sqlserver/type/float.rb +17 -0
  38. data/lib/active_record/connection_adapters/sqlserver/type/integer.rb +13 -0
  39. data/lib/active_record/connection_adapters/sqlserver/type/money.rb +21 -0
  40. data/lib/active_record/connection_adapters/sqlserver/type/quoter.rb +32 -0
  41. data/lib/active_record/connection_adapters/sqlserver/type/real.rb +17 -0
  42. data/lib/active_record/connection_adapters/sqlserver/type/small_integer.rb +13 -0
  43. data/lib/active_record/connection_adapters/sqlserver/type/small_money.rb +21 -0
  44. data/lib/active_record/connection_adapters/sqlserver/type/smalldatetime.rb +24 -0
  45. data/lib/active_record/connection_adapters/sqlserver/type/string.rb +12 -0
  46. data/lib/active_record/connection_adapters/sqlserver/type/text.rb +15 -0
  47. data/lib/active_record/connection_adapters/sqlserver/type/time.rb +59 -0
  48. data/lib/active_record/connection_adapters/sqlserver/type/timestamp.rb +15 -0
  49. data/lib/active_record/connection_adapters/sqlserver/type/tiny_integer.rb +22 -0
  50. data/lib/active_record/connection_adapters/sqlserver/type/unicode_char.rb +15 -0
  51. data/lib/active_record/connection_adapters/sqlserver/type/unicode_string.rb +12 -0
  52. data/lib/active_record/connection_adapters/sqlserver/type/unicode_text.rb +15 -0
  53. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar.rb +20 -0
  54. data/lib/active_record/connection_adapters/sqlserver/type/unicode_varchar_max.rb +20 -0
  55. data/lib/active_record/connection_adapters/sqlserver/type/uuid.rb +23 -0
  56. data/lib/active_record/connection_adapters/sqlserver/type/varbinary.rb +20 -0
  57. data/lib/active_record/connection_adapters/sqlserver/type/varbinary_max.rb +20 -0
  58. data/lib/active_record/connection_adapters/sqlserver/type/varchar.rb +20 -0
  59. data/lib/active_record/connection_adapters/sqlserver/type/varchar_max.rb +20 -0
  60. data/lib/active_record/connection_adapters/sqlserver/utils.rb +118 -12
  61. data/lib/active_record/connection_adapters/sqlserver/version.rb +11 -0
  62. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +133 -198
  63. data/lib/active_record/connection_adapters/sqlserver_column.rb +15 -86
  64. data/lib/active_record/sqlserver_base.rb +2 -0
  65. data/lib/arel/visitors/sqlserver.rb +120 -393
  66. data/lib/{arel/arel_sqlserver.rb → arel_sqlserver.rb} +1 -3
  67. data/test/cases/adapter_test_sqlserver.rb +420 -0
  68. data/test/cases/coerced_tests.rb +642 -0
  69. data/test/cases/column_test_sqlserver.rb +703 -0
  70. data/test/cases/connection_test_sqlserver.rb +216 -0
  71. data/test/cases/database_statements_test_sqlserver.rb +57 -0
  72. data/test/cases/execute_procedure_test_sqlserver.rb +38 -0
  73. data/test/cases/helper_sqlserver.rb +36 -0
  74. data/test/cases/migration_test_sqlserver.rb +66 -0
  75. data/test/cases/order_test_sqlserver.rb +147 -0
  76. data/test/cases/pessimistic_locking_test_sqlserver.rb +90 -0
  77. data/test/cases/schema_dumper_test_sqlserver.rb +175 -0
  78. data/test/cases/schema_test_sqlserver.rb +54 -0
  79. data/test/cases/scratchpad_test_sqlserver.rb +9 -0
  80. data/test/cases/showplan_test_sqlserver.rb +65 -0
  81. data/test/cases/specific_schema_test_sqlserver.rb +118 -0
  82. data/test/cases/transaction_test_sqlserver.rb +61 -0
  83. data/test/cases/utils_test_sqlserver.rb +91 -0
  84. data/test/cases/uuid_test_sqlserver.rb +41 -0
  85. data/test/config.yml +35 -0
  86. data/test/fixtures/1px.gif +0 -0
  87. data/test/migrations/transaction_table/1_table_will_never_be_created.rb +11 -0
  88. data/test/models/sqlserver/customers_view.rb +3 -0
  89. data/test/models/sqlserver/datatype.rb +3 -0
  90. data/test/models/sqlserver/datatype_migration.rb +3 -0
  91. data/test/models/sqlserver/dollar_table_name.rb +3 -0
  92. data/test/models/sqlserver/edge_schema.rb +13 -0
  93. data/test/models/sqlserver/fk_has_fk.rb +3 -0
  94. data/test/models/sqlserver/fk_has_pk.rb +3 -0
  95. data/test/models/sqlserver/natural_pk_data.rb +4 -0
  96. data/test/models/sqlserver/natural_pk_int_data.rb +3 -0
  97. data/test/models/sqlserver/no_pk_data.rb +3 -0
  98. data/test/models/sqlserver/quoted_table.rb +7 -0
  99. data/test/models/sqlserver/quoted_view_1.rb +3 -0
  100. data/test/models/sqlserver/quoted_view_2.rb +3 -0
  101. data/test/models/sqlserver/string_default.rb +3 -0
  102. data/test/models/sqlserver/string_defaults_big_view.rb +3 -0
  103. data/test/models/sqlserver/string_defaults_view.rb +3 -0
  104. data/test/models/sqlserver/tinyint_pk.rb +3 -0
  105. data/test/models/sqlserver/upper.rb +3 -0
  106. data/test/models/sqlserver/uppered.rb +3 -0
  107. data/test/models/sqlserver/uuid.rb +3 -0
  108. data/test/schema/datatypes/2012.sql +64 -0
  109. data/test/schema/sqlserver_specific_schema.rb +181 -0
  110. data/test/support/coerceable_test_sqlserver.rb +45 -0
  111. data/test/support/load_schema_sqlserver.rb +29 -0
  112. data/test/support/minitest_sqlserver.rb +1 -0
  113. data/test/support/paths_sqlserver.rb +48 -0
  114. data/test/support/rake_helpers.rb +41 -0
  115. data/test/support/sql_counter_sqlserver.rb +32 -0
  116. metadata +271 -21
  117. data/CHANGELOG +0 -39
  118. data/VERSION +0 -1
  119. data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +0 -17
  120. data/lib/active_record/sqlserver_test_case.rb +0 -17
  121. data/lib/arel/nodes_sqlserver.rb +0 -14
  122. 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
- def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
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
- super(name, default, sql_type, null)
7
- @primary = @sqlserver_options[:is_identity] || @sqlserver_options[:is_primary]
8
+ @default_function = @sqlserver_options[:default_function]
8
9
  end
9
10
 
10
- class << self
11
- def string_to_binary(value)
12
- "0x#{value.unpack("H*")[0]}"
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
- 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
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
- 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
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
- def visit_Arel_Nodes_SelectStatement(o, a)
8
- if complex_count_sql?(o)
9
- visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
10
- elsif distinct_non_present_orders?(o, a)
11
- visit_Arel_Nodes_SelectStatementDistinctNonPresentOrders(o, a)
12
- elsif o.offset
13
- visit_Arel_Nodes_SelectStatementWithOffset(o, a)
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
- visit_Arel_Nodes_SelectStatementWithOutOffset(o, a)
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 visit_Arel_Nodes_Offset(o, a)
27
- "WHERE [__rnt].[__rn] > (#{visit o.expr, a})"
28
- end
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 visit_Arel_Nodes_Lock(o, a)
35
- visit o.expr, a
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 visit_Arel_Nodes_Ordering(o, a)
39
- if o.respond_to?(:direction)
40
- "#{visit o.expr, a} #{o.ascending? ? 'ASC' : 'DESC'}"
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
- visit o.expr, a
53
+ collector << FETCH
54
+ visit o.expr, collector
55
+ collector << ROWS_ONLY
43
56
  end
44
57
  end
45
58
 
46
- def visit_Arel_Nodes_Bin(o, a)
47
- "#{visit o.expr, a} #{@connection.cs_equality_operator}"
48
- end
49
-
50
- # SQLServer ToSql/Visitor (Additions)
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
- if o.offset
72
- select_frags << 'ROW_NUMBER() OVER (ORDER BY __order) AS __offset'
73
- else
74
- select_frags << '__order'
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
- 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(' ')
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 visit_Arel_Nodes_SelectStatementWithOutOffset(o, a, windowed = false)
123
- find_and_fix_uncorrelated_joins_in_select_statement(o)
124
- core = o.cores.first
125
- projections = core.projections
126
- groups = core.groups
127
- orders = o.orders.uniq
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
- def visit_Arel_Nodes_SelectStatementForComplexCount(o, a)
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 source_with_lock_for_select_statement(o, a)
211
- core = o.cores.first
212
- source = "FROM #{visit(core.source, a).strip}" if core.source
213
- if source && o.lock
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
- end
221
-
222
- def table_from_select_statement(o)
223
- core = o.cores.first
224
- # TODO: [ARel 2.2] Use #from/#source vs. #froms
225
- # if Arel::Table === core.from
226
- # core.from
227
- # elsif Arel::Nodes::SqlLiteral === core.from
228
- # Arel::Table.new(core.from, @engine)
229
- # elsif Arel::Nodes::JoinSource === core.source
230
- # Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
231
- # end
232
- table_finder = lambda do |x|
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
- table_finder.call(core.froms)
116
+ collector
243
117
  end
244
118
 
245
- def single_distinct_select_statement?(o)
246
- projections = o.cores.first.projections
247
- p1 = projections.first
248
- projections.size == 1 &&
249
- ((p1.respond_to?(:distinct) && p1.distinct) ||
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
- # 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)
126
+ # SQLServer Helpers
305
127
 
306
- return true
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 windowed_single_distinct_select_statement?(o)
314
- o.limit &&
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 single_distinct_select_everything_statement?(o, a)
320
- single_distinct_select_statement?(o) &&
321
- visit(o.cores.first.projections.first, a).ends_with?('.*')
322
- end
323
-
324
- def top_one_everything_for_through_join?(o, a)
325
- single_distinct_select_everything_statement?(o, a) &&
326
- (o.limit && !o.offset) &&
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 function_select_statement?(o)
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
- single_distinct_select_statement?(o) &&
345
- (o.limit && !o.offset) &&
346
- core.groups.empty? &&
347
- !single_distinct_select_everything_statement?(o, a)
348
- end
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 rowtable_orders(o)
429
- if !o.orders.empty?
430
- o.orders
431
- else
432
- t = table_from_select_statement(o)
433
- c = t.primary_key || t.columns.first
434
- [c.asc]
435
- end.uniq
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