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.
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