activerecord-jdbc-adapter 1.3.17 → 1.3.18

Sign up to get free protection for your applications and to get access to all the features.
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