activerecord-jdbc-adapter 1.3.17 → 1.3.18

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +24 -5
  3. data/History.md +54 -0
  4. data/lib/arel/visitors/compat.rb +30 -2
  5. data/lib/arel/visitors/db2.rb +118 -29
  6. data/lib/arel/visitors/derby.rb +84 -29
  7. data/lib/arel/visitors/firebird.rb +66 -9
  8. data/lib/arel/visitors/h2.rb +16 -0
  9. data/lib/arel/visitors/hsqldb.rb +6 -3
  10. data/lib/arel/visitors/postgresql_jdbc.rb +6 -0
  11. data/lib/arel/visitors/sql_server.rb +121 -40
  12. data/lib/arel/visitors/sql_server/ng42.rb +293 -0
  13. data/lib/arjdbc.rb +1 -7
  14. data/lib/arjdbc/db2.rb +1 -0
  15. data/lib/arjdbc/db2/adapter.rb +118 -18
  16. data/lib/arjdbc/derby/adapter.rb +29 -8
  17. data/lib/arjdbc/firebird.rb +1 -0
  18. data/lib/arjdbc/firebird/adapter.rb +126 -11
  19. data/lib/arjdbc/hsqldb/adapter.rb +3 -0
  20. data/lib/arjdbc/informix.rb +1 -0
  21. data/lib/arjdbc/jdbc.rb +17 -0
  22. data/lib/arjdbc/jdbc/adapter.rb +28 -3
  23. data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
  24. data/lib/arjdbc/jdbc/column.rb +7 -3
  25. data/lib/arjdbc/jdbc/type_cast.rb +2 -0
  26. data/lib/arjdbc/jdbc/type_converter.rb +28 -15
  27. data/lib/arjdbc/mimer.rb +1 -0
  28. data/lib/arjdbc/mssql.rb +2 -1
  29. data/lib/arjdbc/mssql/adapter.rb +105 -30
  30. data/lib/arjdbc/mssql/column.rb +30 -7
  31. data/lib/arjdbc/mssql/limit_helpers.rb +22 -9
  32. data/lib/arjdbc/mssql/types.rb +343 -0
  33. data/lib/arjdbc/mssql/utils.rb +25 -2
  34. data/lib/arjdbc/mysql/adapter.rb +22 -21
  35. data/lib/arjdbc/oracle.rb +1 -0
  36. data/lib/arjdbc/oracle/adapter.rb +291 -19
  37. data/lib/arjdbc/oracle/column.rb +9 -5
  38. data/lib/arjdbc/oracle/connection_methods.rb +4 -1
  39. data/lib/arjdbc/postgresql/_bc_time_cast_patch.rb +21 -0
  40. data/lib/arjdbc/postgresql/adapter.rb +7 -1
  41. data/lib/arjdbc/postgresql/oid/bytea.rb +3 -0
  42. data/lib/arjdbc/postgresql/oid_types.rb +2 -1
  43. data/lib/arjdbc/tasks/database_tasks.rb +3 -0
  44. data/lib/arjdbc/util/quoted_cache.rb +2 -2
  45. data/lib/arjdbc/util/serialized_attributes.rb +11 -0
  46. data/lib/arjdbc/version.rb +1 -1
  47. data/rakelib/02-test.rake +1 -1
  48. data/rakelib/db.rake +3 -1
  49. data/src/java/arjdbc/firebird/FirebirdRubyJdbcConnection.java +190 -0
  50. data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +259 -61
  51. data/src/java/arjdbc/mssql/MSSQLRubyJdbcConnection.java +13 -2
  52. data/src/java/arjdbc/oracle/OracleRubyJdbcConnection.java +192 -15
  53. data/src/java/arjdbc/postgresql/PostgreSQLRubyJdbcConnection.java +10 -2
  54. metadata +9 -4
@@ -4,19 +4,76 @@ module Arel
4
4
  module Visitors
5
5
  class Firebird < Arel::Visitors::ToSql
6
6
 
7
- def visit_Arel_Nodes_SelectStatement o, a = nil
8
- lim_off = ''
9
- lim_off << "FIRST #{do_visit o.limit.expr, a} " if o.limit
10
- lim_off << " SKIP #{do_visit o.offset.expr, a}" if o.offset
11
- lim_off.strip!
7
+ if ArJdbc::AR42
8
+ def visit_Arel_Nodes_SelectStatement(o, a)
9
+ a = o.cores.inject(a) { |c, x| visit_Arel_Nodes_SelectCore(x, c) }
12
10
 
13
- sql = o.cores.map { |x| do_visit_select_core x, a }.join
14
- sql << " ORDER BY #{o.orders.map { |x| do_visit x, a }.join(', ')}" unless o.orders.empty?
11
+ limit, offset = o.limit, o.offset
12
+ if limit || offset
13
+ select = a.parts[0]
15
14
 
16
- sql.sub!(/\A(\s*SELECT\s)/i, '\&' + lim_off + ' ') unless lim_off.empty?
17
- sql
15
+ sql = Arel::Collectors::SQLString.new
16
+ visit(limit, sql) if limit
17
+ if offset
18
+ sql << ' ' if limit
19
+ visit(offset, sql)
20
+ end
21
+
22
+ a.parts[0] = "#{select} #{sql.value}"
23
+ end
24
+
25
+ unless o.orders.empty?
26
+ a << ' ORDER BY '
27
+ last = o.orders.length - 1
28
+ o.orders.each_with_index do |x, i|
29
+ visit(x, a); a << ', ' unless last == i
30
+ end
31
+ end
32
+
33
+ a
34
+ end
35
+
36
+ def visit_Arel_Nodes_Limit(o, a)
37
+ a << "FIRST #{limit_for(o)}"
38
+ end
39
+
40
+ def visit_Arel_Nodes_Offset(o, a)
41
+ a << 'SKIP '; visit(o.value, a)
42
+ end
43
+
44
+ else
45
+
46
+ def visit_Arel_Nodes_SelectStatement o, a = nil
47
+ if o.limit
48
+ limit = do_visit o.limit.expr, a
49
+ else
50
+ limit = nil
51
+ end
52
+ if o.offset
53
+ offset = do_visit o.offset.expr, a
54
+ else
55
+ offset = nil
56
+ end
57
+
58
+ sql = o.cores.map { |x| do_visit_select_core x, a }.join
59
+ @connection.insert_limit_offset!(sql, limit, offset) if limit || offset
60
+
61
+ unless o.orders.empty?
62
+ sql << ' ORDER BY '
63
+ last = o.orders.length - 1
64
+ o.orders.each_with_index do |x, i|
65
+ sql << do_visit(x, a); sql << ', ' unless last == i
66
+ end
67
+ end
68
+
69
+ sql
70
+ end
18
71
  end
19
72
 
20
73
  end
21
74
  end
22
75
  end
76
+
77
+ Arel::Collectors::Bind.class_eval do
78
+ attr_reader :parts
79
+ end if defined? Arel::Collectors::Bind
@@ -4,6 +4,22 @@ require 'arel/visitors/hsqldb'
4
4
  module Arel
5
5
  module Visitors
6
6
  class H2 < Arel::Visitors::HSQLDB
7
+ def visit_Arel_Nodes_SelectStatement(o, *)
8
+ o.limit ||= Arel::Nodes::Limit.new(-1) if o.offset
9
+ super
10
+ end if ArJdbc::AR42
11
+
12
+ def limit_offset sql, o
13
+ offset = o.offset || 0
14
+ offset = offset.expr unless (offset.nil? || offset == 0)
15
+ if limit = o.limit
16
+ "SELECT LIMIT #{offset} #{limit_for(limit)} #{sql[7..-1]}"
17
+ elsif offset > 0
18
+ "SELECT LIMIT #{offset} -1 #{sql[7..-1]}" # removes "SELECT "
19
+ else
20
+ sql
21
+ end
22
+ end unless ArJdbc::AR42
7
23
  end
8
24
  end
9
25
  end
@@ -3,12 +3,16 @@ require 'arel/visitors/compat'
3
3
  module Arel
4
4
  module Visitors
5
5
  class HSQLDB < Arel::Visitors::ToSql
6
+ def visit_Arel_Nodes_SelectStatement(o, *)
7
+ o.limit ||= Arel::Nodes::Limit.new(0) if o.offset
8
+ super
9
+ end if ArJdbc::AR42
6
10
 
7
11
  def visit_Arel_Nodes_SelectStatement o, a = nil
8
12
  sql = limit_offset(o.cores.map { |x| do_visit_select_core x, a }.join, o)
9
13
  sql << " ORDER BY #{o.orders.map { |x| do_visit x, a }.join(', ')}" unless o.orders.empty?
10
14
  sql
11
- end
15
+ end unless ArJdbc::AR42
12
16
 
13
17
  private
14
18
 
@@ -22,8 +26,7 @@ module Arel
22
26
  else
23
27
  sql
24
28
  end
25
- end
26
-
29
+ end unless ArJdbc::AR42
27
30
  end
28
31
  end
29
32
  end
@@ -0,0 +1,6 @@
1
+ require 'arel/visitors/compat'
2
+
3
+ class Arel::Visitors::PostgreSQL
4
+ # AREL converts bind argument markers "?" to "$n" for PG, but JDBC wants "?".
5
+ remove_method :visit_Arel_Nodes_BindParam if ArJdbc::AR42
6
+ end
@@ -2,34 +2,41 @@ require 'arel/visitors/compat'
2
2
 
3
3
  module Arel
4
4
  module Visitors
5
- # @note AREL set's up `Arel::Visitors::MSSQL` but we should not use that one !
5
+ ToSql.class_eval do
6
+ alias_method :_visit_Arel_Nodes_SelectStatement, :visit_Arel_Nodes_SelectStatement
7
+ end
8
+ # @note AREL set's up `Arel::Visitors::MSSQL` but its not usable as is ...
9
+ # @private
6
10
  class SQLServer < const_defined?(:MSSQL) ? MSSQL : ToSql
7
11
 
12
+ private
13
+
8
14
  def visit_Arel_Nodes_SelectStatement(*args) # [o] AR <= 4.0 [o, a] on 4.1
9
15
  o, a = args.first, args.last
10
16
 
11
- return super if ! o.limit && ! o.offset # NOTE: really?
17
+ return _visit_Arel_Nodes_SelectStatement(*args) if ! o.limit && ! o.offset
12
18
 
13
19
  unless o.orders.empty?
14
- select_order_by = "ORDER BY #{do_visit_columns(o.orders, a).join(', ')}"
20
+ select_order_by = do_visit_columns o.orders, a, 'ORDER BY '
15
21
  end
16
22
 
17
- select_count = false
18
- sql = o.cores.map do |x|
23
+ select_count = false; sql = ''
24
+ o.cores.each do |x|
19
25
  x = x.dup
20
- order_by = select_order_by || determine_order_by(x, a)
26
+ core_order_by = select_order_by || determine_order_by(x, a)
21
27
  if select_count? x
22
- p = order_by ? row_num_literal(order_by) : Arel::Nodes::SqlLiteral.new("*")
23
- x.projections = [p]
28
+ x.projections = [
29
+ Arel::Nodes::SqlLiteral.new(core_order_by ? over_row_num(core_order_by) : '*')
30
+ ]
24
31
  select_count = true
25
32
  else
26
33
  # NOTE: this should really be added here and we should built the
27
34
  # wrapping SQL but than #replace_limit_offset! assumes it does that
28
35
  # ... MS-SQL adapter code seems to be 'hacked' by a lot of people
29
- #x.projections << row_num_literal(order_by)
36
+ #x.projections << Arel::Nodes::SqlLiteral.new over_row_num(order_by)
30
37
  end
31
- do_visit_select_core x, a
32
- end.join
38
+ sql << do_visit_select_core(x, a)
39
+ end
33
40
 
34
41
  #sql = "SELECT _t.* FROM (#{sql}) as _t WHERE #{get_offset_limit_clause(o)}"
35
42
  select_order_by ||= "ORDER BY #{@connection.determine_order_clause(sql)}"
@@ -40,7 +47,7 @@ module Arel
40
47
  add_lock!(sql, :lock => o.lock && true)
41
48
 
42
49
  sql
43
- end
50
+ end unless ArJdbc::AR42
44
51
 
45
52
  # @private
46
53
  MAX_LIMIT_VALUE = 9_223_372_036_854_775_807
@@ -59,19 +66,19 @@ module Arel
59
66
  #
60
67
  # we return nothing here and add the appropriate stuff with #add_lock!
61
68
  #do_visit o.expr, a
62
- end
69
+ end unless ArJdbc::AR42
63
70
 
64
71
  def visit_Arel_Nodes_Top o, a = nil
65
72
  # `top` wouldn't really work here:
66
73
  # User.select("distinct first_name").limit(10)
67
74
  # would generate "select top 10 distinct first_name from users",
68
75
  # which is invalid should be "select distinct top 10 first_name ..."
69
- ''
76
+ a || ''
70
77
  end
71
78
 
72
79
  def visit_Arel_Nodes_Limit o, a = nil
73
80
  "TOP (#{do_visit o.expr, a})"
74
- end
81
+ end unless ArJdbc::AR42
75
82
 
76
83
  def visit_Arel_Nodes_Ordering o, a = nil
77
84
  expr = do_visit o.expr, a
@@ -80,55 +87,126 @@ module Arel
80
87
  else
81
88
  expr
82
89
  end
83
- end
90
+ end unless ArJdbc::AR42
84
91
 
85
92
  def visit_Arel_Nodes_Bin o, a = nil
86
- "#{do_visit o.expr, a} COLLATE Latin1_General_CS_AS_WS"
87
- end
93
+ expr = o.expr; sql = do_visit expr, a
94
+ if expr.respond_to?(:val) && expr.val.is_a?(Numeric)
95
+ sql
96
+ else
97
+ sql << " #{::ArJdbc::MSSQL.cs_equality_operator} "
98
+ sql
99
+ end
100
+ end unless ArJdbc::AR42
88
101
 
89
102
  private
90
103
 
104
+ def self.possibly_private_method_defined?(name)
105
+ private_method_defined?(name) || method_defined?(name)
106
+ end
107
+
91
108
  def select_count? x
92
109
  x.projections.length == 1 && Arel::Nodes::Count === x.projections.first
93
- end
110
+ end unless possibly_private_method_defined? :select_count?
94
111
 
95
112
  def determine_order_by x, a
96
113
  unless x.groups.empty?
97
- "ORDER BY #{do_visit_columns(x.groups, a).join(', ')}"
114
+ do_visit_columns x.groups, a, 'ORDER BY '
98
115
  else
99
- table_pk = find_left_table_pk(x.froms, a)
100
- table_pk == 'NULL' ? nil : "ORDER BY #{table_pk}"
116
+ table_pk = find_left_table_pk(x)
117
+ table_pk && "ORDER BY #{table_pk}"
101
118
  end
102
119
  end
103
120
 
104
- def do_visit_columns(colls, a)
105
- colls.map { |x| do_visit x, a }
121
+ def find_left_table_pk o
122
+ primary_key_from_table table_from_select_core(o)
106
123
  end
107
124
 
108
- # @private
109
- NON_SIMPLE_ORDER_COLUMN = /\sASC|\sDESC|\sCASE|\sCOLLATE|[\.,\[\(]/i # MIN(width)
125
+ def do_visit_columns(colls, a, sql)
126
+ last = colls.size - 1
127
+ colls.each_with_index do |x, i|
128
+ sql << do_visit(x, a); sql << ', ' unless i == last
129
+ end
130
+ sql
131
+ end
132
+
133
+ def do_visit_columns(colls, a, sql)
134
+ prefix = sql
135
+ sql = Arel::Collectors::PlainString.new
136
+ sql << prefix if prefix
110
137
 
111
- def do_visit_columns(colls, a)
112
- colls = colls.map { |x| do_visit x, a }
113
- colls.map! do |x|
114
- if x !~ NON_SIMPLE_ORDER_COLUMN && x.to_i == 0
115
- @connection.quote_column_name(x)
138
+ last = colls.size - 1
139
+ colls.each_with_index do |x, i|
140
+ visit(x, sql); sql << ', ' unless i == last
141
+ end
142
+ sql.value
143
+ end if ArJdbc::AR42
144
+
145
+ def do_visit_columns(colls, a, sql)
146
+ non_simple_order = /\sASC|\sDESC|\sCASE|\sCOLLATE|[\.,\[\(]/i # MIN(width)
147
+
148
+ last = colls.size - 1
149
+ colls.each_with_index do |x, i|
150
+ coll = do_visit(x, a)
151
+
152
+ if coll !~ non_simple_order && coll.to_i == 0
153
+ sql << @connection.quote_column_name(coll)
116
154
  else
117
- x
155
+ sql << coll
118
156
  end
157
+
158
+ sql << ', ' unless i == last
119
159
  end
120
- colls
160
+ sql
121
161
  end if Arel::VERSION < '4.0.0'
122
162
 
123
- def row_num_literal order_by
124
- Arel::Nodes::SqlLiteral.new("ROW_NUMBER() OVER (#{order_by}) as _row_num")
163
+ def over_row_num order_by
164
+ "ROW_NUMBER() OVER (#{order_by}) as _row_num"
165
+ end # unless possibly_private_method_defined? :row_num_literal
166
+
167
+ def table_from_select_core core
168
+ if Arel::Table === core.from
169
+ core.from
170
+ elsif Arel::Nodes::SqlLiteral === core.from
171
+ Arel::Table.new(core.from, @engine)
172
+ elsif Arel::Nodes::JoinSource === core.source
173
+ Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
174
+ end
125
175
  end
126
176
 
127
- # @fixme raise exception of there is no pk?
128
- # @fixme Table.primary_key will be deprecated. What is the replacement?
129
- def find_left_table_pk o, a
130
- return do_visit o.primary_key, a if o.instance_of? Arel::Table
131
- find_left_table_pk o.left, a if o.kind_of? Arel::Nodes::Join
177
+ def table_from_select_core core
178
+ table_finder = lambda do |x|
179
+ case x
180
+ when Arel::Table
181
+ x
182
+ when Arel::Nodes::SqlLiteral
183
+ Arel::Table.new(x, @engine)
184
+ when Arel::Nodes::Join
185
+ table_finder.call(x.left)
186
+ end
187
+ end
188
+ table_finder.call(core.froms)
189
+ end if ActiveRecord::VERSION::STRING < '3.2'
190
+
191
+ def primary_key_from_table t
192
+ return unless t
193
+ return t.primary_key if t.primary_key
194
+
195
+ engine = t.engine
196
+ if engine_pk = engine.primary_key
197
+ pk = engine.arel_table[engine_pk]
198
+ return pk if pk
199
+ end
200
+
201
+ pk = (@primary_keys ||= {}).fetch(table_name = engine.table_name) do
202
+ pk_name = @connection.primary_key(table_name)
203
+ # some tables might be without primary key
204
+ @primary_keys[table_name] = pk_name && t[pk_name]
205
+ end
206
+ return pk if pk
207
+
208
+ column_name = engine.columns.first.try(:name)
209
+ column_name && t[column_name]
132
210
  end
133
211
 
134
212
  include ArJdbc::MSSQL::LockMethods
@@ -140,5 +218,8 @@ module Arel
140
218
  class SQLServer2000 < SQLServer
141
219
  include ArJdbc::MSSQL::LimitHelpers::SqlServer2000ReplaceLimitOffset
142
220
  end
221
+
222
+ load 'arel/visitors/sql_server/ng42.rb' if ArJdbc::AR42
223
+
143
224
  end
144
225
  end
@@ -0,0 +1,293 @@
1
+ module Arel
2
+ module Visitors
3
+ class SQLServerNG < 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
+ private
12
+
13
+ # SQLServer ToSql/Visitor (Overides)
14
+
15
+ #def visit_Arel_Nodes_BindParam o, collector
16
+ # collector.add_bind(o) { |i| "@#{i-1}" }
17
+ #end
18
+
19
+ def visit_Arel_Nodes_Bin o, collector
20
+ visit o.expr, collector
21
+ if o.expr.val.is_a? Numeric
22
+ collector
23
+ else
24
+ collector << " #{::ArJdbc::MSSQL.cs_equality_operator} "
25
+ end
26
+ end
27
+
28
+ def visit_Arel_Nodes_UpdateStatement(o, a)
29
+ if o.orders.any? && o.limit.nil?
30
+ o.limit = Nodes::Limit.new(9_223_372_036_854_775_807)
31
+ end
32
+ super
33
+ end
34
+
35
+ def visit_Arel_Nodes_Lock o, collector
36
+ o.expr = Arel.sql('WITH(UPDLOCK)') if o.expr.to_s =~ /FOR UPDATE/
37
+ collector << SPACE
38
+ visit o.expr, collector
39
+ end
40
+
41
+ def visit_Arel_Nodes_Offset o, collector
42
+ collector << OFFSET
43
+ visit o.expr, collector
44
+ collector << ROWS
45
+ end
46
+
47
+ def visit_Arel_Nodes_Limit o, collector
48
+ if node_value(o) == 0
49
+ collector << FETCH0
50
+ collector << ROWS_ONLY
51
+ else
52
+ collector << FETCH
53
+ visit o.expr, collector
54
+ collector << ROWS_ONLY
55
+ end
56
+ end
57
+
58
+ def visit_Arel_Nodes_SelectStatement o, collector
59
+ distinct_One_As_One_Is_So_Not_Fetch o
60
+
61
+ set_select_statement_lock o.lock
62
+
63
+ if o.with
64
+ collector = visit o.with, collector
65
+ collector << SPACE
66
+ end
67
+
68
+ return _visit_Arel_Nodes_SelectStatement(o, collector) if ! o.limit && ! o.offset
69
+
70
+ # collector = o.cores.inject(collector) { |c,x|
71
+ # visit_Arel_Nodes_SelectCore(x, c)
72
+ # }
73
+
74
+ unless o.orders.empty?
75
+ select_order_by = do_visit_columns o.orders, collector, 'ORDER BY '
76
+ end
77
+
78
+ select_count = false
79
+ collector = o.cores.inject(collector) do |c, x|
80
+ unless core_order_by = select_order_by
81
+ core_order_by = generate_order_by determine_order_by(o, x)
82
+ end
83
+
84
+ if select_count? x
85
+ x.projections = [ Arel::Nodes::SqlLiteral.new(over_row_num(core_order_by)) ]
86
+ select_count = true
87
+ else
88
+ # NOTE: this should really be added here and we should built the
89
+ # wrapping SQL but than #replace_limit_offset! assumes it does that
90
+ # ... MS-SQL adapter code seems to be 'hacked' by a lot of people
91
+ #x.projections << Arel::Nodes::SqlLiteral.new(over_row_num(select_order_by))
92
+ end if core_order_by
93
+ visit_Arel_Nodes_SelectCore(x, c)
94
+ end
95
+ # END collector = o.cores.inject(collector) { |c,x|
96
+
97
+ # collector = visit_Orders_And_Let_Fetch_Happen o, collector
98
+ # collector = visit_Make_Fetch_Happen o, collector
99
+ # collector # __method__ END
100
+
101
+ self.class.collector_proxy(collector) do |sql|
102
+ select_order_by ||= "ORDER BY #{@connection.determine_order_clause(sql)}"
103
+ replace_limit_offset!(sql, limit_for(o.limit), o.offset && o.offset.value.to_i, select_order_by)
104
+ sql = "SELECT COUNT(*) AS count_id FROM (#{sql}) AS subquery" if select_count
105
+ sql
106
+ end
107
+
108
+ ensure
109
+ set_select_statement_lock nil
110
+ end
111
+
112
+ def visit_Arel_Nodes_JoinSource o, collector
113
+ if o.left
114
+ collector = visit o.left, collector
115
+ collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector
116
+ end
117
+ if o.right.any?
118
+ collector << " " if o.left
119
+ collector = inject_join o.right, collector, ' '
120
+ end
121
+ collector
122
+ end
123
+
124
+ def visit_Arel_Nodes_OuterJoin o, collector
125
+ collector << "LEFT OUTER JOIN "
126
+ collector = visit o.left, collector
127
+ collector = visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, space: true
128
+ collector << " "
129
+ visit o.right, collector
130
+ end
131
+
132
+ # SQLServer ToSql/Visitor (Additions)
133
+
134
+ def visit_Arel_Nodes_SelectStatement_SQLServer_Lock collector, options = {}
135
+ if lock = select_statement_lock
136
+ collector = visit lock, collector
137
+ collector << SPACE if options[:space]
138
+ end
139
+ collector
140
+ end
141
+
142
+ def visit_Orders_And_Let_Fetch_Happen o, collector
143
+ make_Fetch_Possible_And_Deterministic o
144
+ unless o.orders.empty?
145
+ collector << SPACE
146
+ collector << ORDER_BY
147
+ len = o.orders.length - 1
148
+ o.orders.each_with_index { |x, i|
149
+ collector = visit(x, collector)
150
+ collector << COMMA unless len == i
151
+ }
152
+ end
153
+ collector
154
+ end
155
+
156
+ def visit_Make_Fetch_Happen o, collector
157
+ o.offset = Nodes::Offset.new(0) if o.limit && !o.offset
158
+ collector = visit o.offset, collector if o.offset
159
+ collector = visit o.limit, collector if o.limit
160
+ collector
161
+ end
162
+
163
+ # SQLServer Helpers
164
+
165
+ # attr_reader :select_statement_lock
166
+ def select_statement_lock
167
+ Thread.current[:'Arel::Visitors::SQLServerNG.select_statement_lock']
168
+ end
169
+
170
+ def set_select_statement_lock(lock) # @select_statement_lock = lock
171
+ Thread.current[:'Arel::Visitors::SQLServerNG.select_statement_lock'] = lock
172
+ end
173
+
174
+ def make_Fetch_Possible_And_Deterministic o
175
+ return if o.limit.nil? && o.offset.nil?
176
+ if o.orders.empty? # ORDER BY mandatory with OFFSET FETCH clause
177
+ t = table_From_Statement o
178
+ pk = primary_Key_From_Table t
179
+ return unless pk
180
+ # Prefer deterministic vs a simple `(SELECT NULL)` expr.
181
+ o.orders = [ pk.asc ]
182
+ end
183
+ end
184
+
185
+ def distinct_One_As_One_Is_So_Not_Fetch o
186
+ core = o.cores.first
187
+ distinct = Nodes::Distinct === core.set_quantifier
188
+ oneasone = core.projections.all? { |x| x == ActiveRecord::FinderMethods::ONE_AS_ONE }
189
+ limitone = node_value(o.limit) == 1
190
+ if distinct && oneasone && limitone && !o.offset
191
+ core.projections = [Arel.sql("TOP(1) 1 AS [one]")]
192
+ o.limit = nil
193
+ end
194
+ end
195
+
196
+ def table_From_Statement o
197
+ core = o.cores.first
198
+ if Arel::Table === core.from
199
+ core.from
200
+ elsif Arel::Nodes::SqlLiteral === core.from
201
+ Arel::Table.new(core.from)
202
+ elsif Arel::Nodes::JoinSource === core.source
203
+ Arel::Nodes::SqlLiteral === core.source.left ? Arel::Table.new(core.source.left, @engine) : core.source.left
204
+ end
205
+ end
206
+
207
+ def primary_Key_From_Table t
208
+ return unless t
209
+ return t.primary_key if t.primary_key
210
+ if engine_pk = t.engine.primary_key
211
+ pk = t.engine.arel_table[engine_pk]
212
+ return pk if pk
213
+ end
214
+ pk = t.engine.connection.schema_cache.primary_keys(t.engine.table_name)
215
+ return pk if pk
216
+ column_name = t.engine.columns.first.try(:name)
217
+ column_name ? t[column_name] : nil
218
+ end
219
+
220
+ def determine_order_by o, x
221
+ if o.orders.any?
222
+ o.orders
223
+ elsif x.groups.any?
224
+ x.groups
225
+ else
226
+ pk = find_left_table_pk(x)
227
+ pk ? [ pk ] : nil # []
228
+ end
229
+ end
230
+
231
+ def generate_order_by orders
232
+ do_visit_columns orders, nil, 'ORDER BY '
233
+ end
234
+
235
+ SQLString = ActiveRecord::ConnectionAdapters::AbstractAdapter::SQLString
236
+ # BindCollector = ActiveRecord::ConnectionAdapters::AbstractAdapter::BindCollector
237
+
238
+ def self.collector_proxy(collector, &block)
239
+ if collector.is_a?(SQLString)
240
+ return SQLStringProxy.new(collector, block)
241
+ end
242
+ BindCollectorProxy.new(collector, block)
243
+ end
244
+
245
+ class BindCollectorProxy < ActiveRecord::ConnectionAdapters::AbstractAdapter::BindCollector
246
+
247
+ def initialize(collector, block); @delegate = collector; @block = block end
248
+
249
+ def << str; @delegate << str; self end
250
+
251
+ def add_bind bind; @delegate.add_bind bind; self end
252
+
253
+ def value; @delegate.value; end
254
+
255
+ #def substitute_binds bvs; @delegate.substitute_binds(bvs); self end
256
+
257
+ def compile(bvs, conn)
258
+ _yield_str @delegate.compile(bvs, conn)
259
+ end
260
+
261
+ private
262
+
263
+ def method_missing(name, *args, &block); @delegate.send(name, args, &block) end
264
+
265
+ def _yield_str(str); @block ? @block.call(str) : str end
266
+
267
+ end
268
+
269
+ class SQLStringProxy < ActiveRecord::ConnectionAdapters::AbstractAdapter::SQLString
270
+
271
+ def initialize(collector, block); @delegate = collector; @block = block end
272
+
273
+ def << str; @delegate << str; self end
274
+
275
+ def add_bind bind; @delegate.add_bind bind; self end
276
+
277
+ def compile(bvs, conn)
278
+ _yield_str @delegate.compile(bvs, conn)
279
+ end
280
+
281
+ private
282
+
283
+ def method_missing(name, *args, &block); @delegate.send(name, args, &block) end
284
+
285
+ def _yield_str(str); @block ? @block.call(str) : str end
286
+
287
+ end
288
+
289
+ end
290
+ end
291
+ end
292
+
293
+ Arel::Visitors::VISITORS['mssql'] = Arel::Visitors::VISITORS['sqlserver'] = Arel::Visitors::SQLServerNG