arel 5.0.1.20140414130214 → 6.0.0.beta1

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -2
  3. data/History.txt +9 -4
  4. data/Manifest.txt +9 -7
  5. data/README.markdown +85 -8
  6. data/Rakefile +1 -1
  7. data/arel.gemspec +15 -16
  8. data/lib/arel.rb +1 -12
  9. data/lib/arel/collectors/bind.rb +36 -0
  10. data/lib/arel/collectors/plain_string.rb +18 -0
  11. data/lib/arel/collectors/sql_string.rb +18 -0
  12. data/lib/arel/factory_methods.rb +1 -1
  13. data/lib/arel/insert_manager.rb +5 -1
  14. data/lib/arel/nodes.rb +41 -0
  15. data/lib/arel/nodes/and.rb +1 -5
  16. data/lib/arel/nodes/binary.rb +2 -0
  17. data/lib/arel/nodes/extract.rb +0 -2
  18. data/lib/arel/nodes/full_outer_join.rb +6 -0
  19. data/lib/arel/nodes/function.rb +0 -1
  20. data/lib/arel/nodes/insert_statement.rb +5 -2
  21. data/lib/arel/nodes/node.rb +5 -1
  22. data/lib/arel/nodes/right_outer_join.rb +6 -0
  23. data/lib/arel/nodes/window.rb +23 -5
  24. data/lib/arel/predications.rb +41 -33
  25. data/lib/arel/select_manager.rb +13 -37
  26. data/lib/arel/table.rb +13 -9
  27. data/lib/arel/tree_manager.rb +8 -2
  28. data/lib/arel/update_manager.rb +2 -2
  29. data/lib/arel/visitors.rb +0 -2
  30. data/lib/arel/visitors/bind_substitute.rb +9 -0
  31. data/lib/arel/visitors/bind_visitor.rb +10 -5
  32. data/lib/arel/visitors/depth_first.rb +60 -57
  33. data/lib/arel/visitors/dot.rb +84 -80
  34. data/lib/arel/visitors/ibm_db.rb +4 -2
  35. data/lib/arel/visitors/informix.rb +39 -21
  36. data/lib/arel/visitors/mssql.rb +41 -23
  37. data/lib/arel/visitors/mysql.rb +48 -22
  38. data/lib/arel/visitors/oracle.rb +33 -24
  39. data/lib/arel/visitors/postgresql.rb +15 -8
  40. data/lib/arel/visitors/reduce.rb +25 -0
  41. data/lib/arel/visitors/sqlite.rb +3 -2
  42. data/lib/arel/visitors/to_sql.rb +455 -248
  43. data/lib/arel/visitors/visitor.rb +2 -2
  44. data/lib/arel/visitors/where_sql.rb +3 -2
  45. data/test/attributes/test_attribute.rb +12 -3
  46. data/test/collectors/test_bind_collector.rb +70 -0
  47. data/test/collectors/test_sql_string.rb +38 -0
  48. data/test/helper.rb +10 -1
  49. data/test/nodes/test_bin.rb +2 -2
  50. data/test/nodes/test_count.rb +0 -6
  51. data/test/nodes/test_equality.rb +1 -1
  52. data/test/nodes/test_grouping.rb +1 -1
  53. data/test/nodes/test_infix_operation.rb +1 -1
  54. data/test/nodes/test_select_core.rb +7 -7
  55. data/test/nodes/test_sql_literal.rb +10 -6
  56. data/test/nodes/test_window.rb +9 -3
  57. data/test/support/fake_record.rb +16 -4
  58. data/test/test_factory_methods.rb +1 -1
  59. data/test/test_insert_manager.rb +33 -4
  60. data/test/test_select_manager.rb +164 -92
  61. data/test/test_table.rb +49 -4
  62. data/test/visitors/test_bind_visitor.rb +18 -10
  63. data/test/visitors/test_depth_first.rb +12 -0
  64. data/test/visitors/test_dot.rb +4 -4
  65. data/test/visitors/test_ibm_db.rb +11 -5
  66. data/test/visitors/test_informix.rb +14 -8
  67. data/test/visitors/test_mssql.rb +12 -8
  68. data/test/visitors/test_mysql.rb +17 -12
  69. data/test/visitors/test_oracle.rb +25 -21
  70. data/test/visitors/test_postgres.rb +50 -12
  71. data/test/visitors/test_sqlite.rb +2 -2
  72. data/test/visitors/test_to_sql.rb +177 -81
  73. metadata +24 -19
  74. data/lib/arel/deprecated.rb +0 -4
  75. data/lib/arel/expression.rb +0 -5
  76. data/lib/arel/sql/engine.rb +0 -10
  77. data/lib/arel/sql_literal.rb +0 -4
  78. data/lib/arel/visitors/join_sql.rb +0 -19
  79. data/lib/arel/visitors/order_clauses.rb +0 -11
  80. data/test/visitors/test_join_sql.rb +0 -42
@@ -3,8 +3,10 @@ module Arel
3
3
  class IBM_DB < Arel::Visitors::ToSql
4
4
  private
5
5
 
6
- def visit_Arel_Nodes_Limit o, a
7
- "FETCH FIRST #{visit o.expr, a} ROWS ONLY"
6
+ def visit_Arel_Nodes_Limit o, collector
7
+ collector << "FETCH FIRST "
8
+ collector = visit o.expr, collector
9
+ collector << " ROWS ONLY"
8
10
  end
9
11
 
10
12
  end
@@ -2,30 +2,48 @@ module Arel
2
2
  module Visitors
3
3
  class Informix < Arel::Visitors::ToSql
4
4
  private
5
- def visit_Arel_Nodes_SelectStatement o, a
6
- [
7
- "SELECT",
8
- (visit(o.offset, a) if o.offset),
9
- (visit(o.limit, a) if o.limit),
10
- o.cores.map { |x| visit_Arel_Nodes_SelectCore x, a }.join,
11
- ("ORDER BY #{o.orders.map { |x| visit x, a }.join(', ')}" unless o.orders.empty?),
12
- (visit(o.lock, a) if o.lock),
13
- ].compact.join ' '
5
+ def visit_Arel_Nodes_SelectStatement o, collector
6
+ collector << "SELECT "
7
+ collector = maybe_visit o.offset, collector
8
+ collector = maybe_visit o.limit, collector
9
+ collector = o.cores.inject(collector) { |c,x|
10
+ visit_Arel_Nodes_SelectCore x, c
11
+ }
12
+ if o.orders.any?
13
+ collector << "ORDER BY "
14
+ collector = inject_join o.orders, collector, ", "
15
+ end
16
+ collector = maybe_visit o.lock, collector
14
17
  end
15
- def visit_Arel_Nodes_SelectCore o, a
16
- [
17
- "#{o.projections.map { |x| visit x, a }.join ', '}",
18
- ("FROM #{visit(o.source, a)}" if o.source && !o.source.empty?),
19
- ("WHERE #{o.wheres.map { |x| visit x, a }.join ' AND ' }" unless o.wheres.empty?),
20
- ("GROUP BY #{o.groups.map { |x| visit x, a }.join ', ' }" unless o.groups.empty?),
21
- (visit(o.having, a) if o.having),
22
- ].compact.join ' '
18
+ def visit_Arel_Nodes_SelectCore o, collector
19
+ collector = inject_join o.projections, collector, ", "
20
+ froms = false
21
+ if o.source && !o.source.empty?
22
+ froms = true
23
+ collector << " FROM "
24
+ collector = visit o.source, collector
25
+ end
26
+
27
+ if o.wheres.any?
28
+ collector << " WHERE "
29
+ collector = inject_join o.wheres, collector, " AND "
30
+ end
31
+
32
+ if o.groups.any?
33
+ collector << "GROUP BY "
34
+ collector = inject_join o.groups, collector, ", "
35
+ end
36
+
37
+ maybe_visit o.having, collector
23
38
  end
24
- def visit_Arel_Nodes_Offset o, a
25
- "SKIP #{visit o.expr, a}"
39
+ def visit_Arel_Nodes_Offset o, collector
40
+ collector << "SKIP "
41
+ visit o.expr, collector
26
42
  end
27
- def visit_Arel_Nodes_Limit o, a
28
- "LIMIT #{visit o.expr, a}"
43
+ def visit_Arel_Nodes_Limit o, collector
44
+ collector << "LIMIT "
45
+ visit o.expr, collector
46
+ collector << " "
29
47
  end
30
48
  end
31
49
  end
@@ -1,39 +1,54 @@
1
1
  module Arel
2
2
  module Visitors
3
3
  class MSSQL < Arel::Visitors::ToSql
4
+ RowNumber = Struct.new :children
5
+
4
6
  private
5
7
 
6
8
  # `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate
7
9
  # "select top 10 distinct first_name from users", which is invalid query! it should be
8
10
  # "select distinct top 10 first_name from users"
9
- def visit_Arel_Nodes_Top o, a
11
+ def visit_Arel_Nodes_Top o
10
12
  ""
11
13
  end
12
14
 
13
- def visit_Arel_Nodes_SelectStatement o, a
15
+ def visit_Arel_Visitors_MSSQL_RowNumber o, collector
16
+ collector << "ROW_NUMBER() OVER (ORDER BY "
17
+ inject_join(o.children, collector, ', ') << ") as _row_num"
18
+ end
19
+
20
+ def visit_Arel_Nodes_SelectStatement o, collector
14
21
  if !o.limit && !o.offset
15
- return super o, a
22
+ return super
16
23
  end
17
24
 
18
- select_order_by = "ORDER BY #{o.orders.map { |x| visit x, a }.join(', ')}" unless o.orders.empty?
19
-
20
25
  is_select_count = false
21
- sql = o.cores.map { |x|
22
- core_order_by = select_order_by || determine_order_by(x, a)
26
+ o.cores.each { |x|
27
+ core_order_by = row_num_literal determine_order_by(o.orders, x)
23
28
  if select_count? x
24
- x.projections = [row_num_literal(core_order_by)]
29
+ x.projections = [core_order_by]
25
30
  is_select_count = true
26
31
  else
27
- x.projections << row_num_literal(core_order_by)
32
+ x.projections << core_order_by
28
33
  end
34
+ }
29
35
 
30
- visit_Arel_Nodes_SelectCore x, a
31
- }.join
36
+ if is_select_count
37
+ # fixme count distinct wouldn't work with limit or offset
38
+ collector << "SELECT COUNT(1) as count_id FROM ("
39
+ end
40
+
41
+ collector << "SELECT _t.* FROM ("
42
+ collector = o.cores.inject(collector) { |c,x|
43
+ visit_Arel_Nodes_SelectCore x, c
44
+ }
45
+ collector << ") as _t WHERE #{get_offset_limit_clause(o)}"
32
46
 
33
- sql = "SELECT _t.* FROM (#{sql}) as _t WHERE #{get_offset_limit_clause(o)}"
34
- # fixme count distinct wouldn't work with limit or offset
35
- sql = "SELECT COUNT(1) as count_id FROM (#{sql}) AS subquery" if is_select_count
36
- sql
47
+ if is_select_count
48
+ collector << ") AS subquery"
49
+ else
50
+ collector
51
+ end
37
52
  end
38
53
 
39
54
  def get_offset_limit_clause o
@@ -46,16 +61,19 @@ module Arel
46
61
  end
47
62
  end
48
63
 
49
- def determine_order_by x, a
50
- unless x.groups.empty?
51
- "ORDER BY #{x.groups.map { |g| visit g, a }.join ', ' }"
64
+ def determine_order_by orders, x
65
+ if orders.any?
66
+ orders
67
+ elsif x.groups.any?
68
+ x.groups
52
69
  else
53
- "ORDER BY #{find_left_table_pk(x.froms, a)}"
70
+ pk = find_left_table_pk(x.froms)
71
+ pk ? [pk] : []
54
72
  end
55
73
  end
56
74
 
57
75
  def row_num_literal order_by
58
- Nodes::SqlLiteral.new("ROW_NUMBER() OVER (#{order_by}) as _row_num")
76
+ RowNumber.new order_by
59
77
  end
60
78
 
61
79
  def select_count? x
@@ -64,9 +82,9 @@ module Arel
64
82
 
65
83
  # FIXME raise exception of there is no pk?
66
84
  # FIXME!! Table.primary_key will be deprecated. What is the replacement??
67
- def find_left_table_pk o, a
68
- return visit o.primary_key, a if o.instance_of? Arel::Table
69
- find_left_table_pk o.left, a if o.kind_of? Arel::Nodes::Join
85
+ def find_left_table_pk o
86
+ return o.primary_key if o.instance_of? Arel::Table
87
+ find_left_table_pk o.left if o.kind_of? Arel::Nodes::Join
70
88
  end
71
89
  end
72
90
  end
@@ -2,53 +2,79 @@ module Arel
2
2
  module Visitors
3
3
  class MySQL < Arel::Visitors::ToSql
4
4
  private
5
- def visit_Arel_Nodes_Union o, a, suppress_parens = false
6
- left_result = case o.left
5
+ def visit_Arel_Nodes_Union o, collector, suppress_parens = false
6
+ unless suppress_parens
7
+ collector << "( "
8
+ end
9
+
10
+ collector = case o.left
7
11
  when Arel::Nodes::Union
8
- visit_Arel_Nodes_Union o.left, a, true
12
+ visit_Arel_Nodes_Union o.left, collector, true
9
13
  else
10
- visit o.left, a
14
+ visit o.left, collector
11
15
  end
12
16
 
13
- right_result = case o.right
17
+ collector << " UNION "
18
+
19
+ collector = case o.right
14
20
  when Arel::Nodes::Union
15
- visit_Arel_Nodes_Union o.right, a, true
21
+ visit_Arel_Nodes_Union o.right, collector, true
16
22
  else
17
- visit o.right, a
23
+ visit o.right, collector
18
24
  end
19
25
 
20
26
  if suppress_parens
21
- "#{left_result} UNION #{right_result}"
27
+ collector
22
28
  else
23
- "( #{left_result} UNION #{right_result} )"
29
+ collector << " )"
24
30
  end
25
31
  end
26
32
 
27
- def visit_Arel_Nodes_Bin o, a
28
- "BINARY #{visit o.expr, a}"
33
+ def visit_Arel_Nodes_Bin o, collector
34
+ collector << "BINARY "
35
+ visit o.expr, collector
29
36
  end
30
37
 
31
38
  ###
32
39
  # :'(
33
40
  # http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214
34
- def visit_Arel_Nodes_SelectStatement o, a
35
- o.limit = Arel::Nodes::Limit.new(18446744073709551615) if o.offset && !o.limit
41
+ def visit_Arel_Nodes_SelectStatement o, collector
42
+ if o.offset && !o.limit
43
+ o.limit = Arel::Nodes::Limit.new(Nodes.build_quoted(18446744073709551615))
44
+ end
36
45
  super
37
46
  end
38
47
 
39
- def visit_Arel_Nodes_SelectCore o, a
48
+ def visit_Arel_Nodes_SelectCore o, collector
40
49
  o.froms ||= Arel.sql('DUAL')
41
50
  super
42
51
  end
43
52
 
44
- def visit_Arel_Nodes_UpdateStatement o, a
45
- [
46
- "UPDATE #{visit o.relation, a}",
47
- ("SET #{o.values.map { |value| visit value, a }.join ', '}" unless o.values.empty?),
48
- ("WHERE #{o.wheres.map { |x| visit x, a }.join ' AND '}" unless o.wheres.empty?),
49
- ("ORDER BY #{o.orders.map { |x| visit x, a }.join(', ')}" unless o.orders.empty?),
50
- (visit(o.limit, a) if o.limit),
51
- ].compact.join ' '
53
+ def visit_Arel_Nodes_UpdateStatement o, collector
54
+ collector << "UPDATE "
55
+ collector = visit o.relation, collector
56
+
57
+ unless o.values.empty?
58
+ collector << " SET "
59
+ collector = inject_join o.values, collector, ', '
60
+ end
61
+
62
+ unless o.wheres.empty?
63
+ collector << " WHERE "
64
+ collector = inject_join o.wheres, collector, ' AND '
65
+ end
66
+
67
+ unless o.orders.empty?
68
+ collector << " ORDER BY "
69
+ collector = inject_join o.orders, collector, ', '
70
+ end
71
+
72
+ if o.limit
73
+ collector << " "
74
+ visit(o.limit, collector)
75
+ else
76
+ collector
77
+ end
52
78
  end
53
79
 
54
80
  end
@@ -3,8 +3,8 @@ module Arel
3
3
  class Oracle < Arel::Visitors::ToSql
4
4
  private
5
5
 
6
- def visit_Arel_Nodes_SelectStatement o, a
7
- o = order_hacks(o, a)
6
+ def visit_Arel_Nodes_SelectStatement o, collector
7
+ o = order_hacks(o)
8
8
 
9
9
  # if need to select first records without ORDER BY and GROUP BY and without DISTINCT
10
10
  # then can use simple ROWNUM in WHERE clause
@@ -17,55 +17,64 @@ module Arel
17
17
 
18
18
  if o.limit && o.offset
19
19
  o = o.dup
20
- limit = o.limit.expr.to_i
20
+ limit = o.limit.expr.expr
21
21
  offset = o.offset
22
22
  o.offset = nil
23
- sql = super(o, a)
24
- return <<-eosql
23
+ collector << "
25
24
  SELECT * FROM (
26
25
  SELECT raw_sql_.*, rownum raw_rnum_
27
- FROM (#{sql}) raw_sql_
26
+ FROM ("
27
+
28
+ collector = super(o, collector)
29
+ collector << ") raw_sql_
28
30
  WHERE rownum <= #{offset.expr.to_i + limit}
29
31
  )
30
- WHERE #{visit offset, a}
31
- eosql
32
+ WHERE "
33
+ return visit(offset, collector)
32
34
  end
33
35
 
34
36
  if o.limit
35
37
  o = o.dup
36
38
  limit = o.limit.expr
37
- return "SELECT * FROM (#{super(o, a)}) WHERE ROWNUM <= #{visit limit, a}"
39
+ collector << "SELECT * FROM ("
40
+ collector = super(o, collector)
41
+ collector << ") WHERE ROWNUM <= "
42
+ return visit limit, collector
38
43
  end
39
44
 
40
45
  if o.offset
41
46
  o = o.dup
42
47
  offset = o.offset
43
48
  o.offset = nil
44
- sql = super(o, a)
45
- return <<-eosql
46
- SELECT * FROM (
49
+ collector << "SELECT * FROM (
47
50
  SELECT raw_sql_.*, rownum raw_rnum_
48
- FROM (#{sql}) raw_sql_
51
+ FROM ("
52
+ collector = super(o, collector)
53
+ collector << ") raw_sql_
49
54
  )
50
- WHERE #{visit offset, a}
51
- eosql
55
+ WHERE "
56
+ return visit offset, collector
52
57
  end
53
58
 
54
59
  super
55
60
  end
56
61
 
57
- def visit_Arel_Nodes_Limit o, a
62
+ def visit_Arel_Nodes_Limit o, collector
63
+ collector
58
64
  end
59
65
 
60
- def visit_Arel_Nodes_Offset o, a
61
- "raw_rnum_ > #{visit o.expr, a}"
66
+ def visit_Arel_Nodes_Offset o, collector
67
+ collector << "raw_rnum_ > "
68
+ visit o.expr, collector
62
69
  end
63
70
 
64
- def visit_Arel_Nodes_Except o, a
65
- "( #{visit o.left, a} MINUS #{visit o.right, a} )"
71
+ def visit_Arel_Nodes_Except o, collector
72
+ collector << "( "
73
+ collector = infix_value o, collector, " MINUS "
74
+ collector << " )"
66
75
  end
67
76
 
68
- def visit_Arel_Nodes_UpdateStatement o, a
77
+ def visit_Arel_Nodes_UpdateStatement o, collector
69
78
  # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
70
79
  if o.orders.any? && o.limit.nil?
71
80
  # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
@@ -79,7 +88,7 @@ module Arel
79
88
 
80
89
  ###
81
90
  # Hacks for the order clauses specific to Oracle
82
- def order_hacks o, a
91
+ def order_hacks o
83
92
  return o if o.orders.empty?
84
93
  return o unless o.cores.any? do |core|
85
94
  core.projections.any? do |projection|
@@ -89,9 +98,9 @@ module Arel
89
98
  # Previous version with join and split broke ORDER BY clause
90
99
  # if it contained functions with several arguments (separated by ',').
91
100
  #
92
- # orders = o.orders.map { |x| visit x, a }.join(', ').split(',')
101
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
93
102
  orders = o.orders.map do |x|
94
- string = visit x, a
103
+ string = visit(x, Arel::Collectors::SQLString.new).value
95
104
  if string.include?(',')
96
105
  split_order_string(string)
97
106
  else
@@ -3,18 +3,25 @@ module Arel
3
3
  class PostgreSQL < Arel::Visitors::ToSql
4
4
  private
5
5
 
6
- def visit_Arel_Nodes_Matches o, a
7
- a = o.left if Arel::Attributes::Attribute === o.left
8
- "#{visit o.left, a} ILIKE #{visit o.right, a}"
6
+ def visit_Arel_Nodes_Matches o, collector
7
+ infix_value o, collector, ' ILIKE '
9
8
  end
10
9
 
11
- def visit_Arel_Nodes_DoesNotMatch o, a
12
- a = o.left if Arel::Attributes::Attribute === o.left
13
- "#{visit o.left, a} NOT ILIKE #{visit o.right, a}"
10
+ def visit_Arel_Nodes_DoesNotMatch o, collector
11
+ infix_value o, collector, ' NOT ILIKE '
14
12
  end
15
13
 
16
- def visit_Arel_Nodes_DistinctOn o, a
17
- "DISTINCT ON ( #{visit o.expr, a} )"
14
+ def visit_Arel_Nodes_Regexp o, collector
15
+ infix_value o, collector, ' ~ '
16
+ end
17
+
18
+ def visit_Arel_Nodes_NotRegexp o, collector
19
+ infix_value o, collector, ' !~ '
20
+ end
21
+
22
+ def visit_Arel_Nodes_DistinctOn o, collector
23
+ collector << "DISTINCT ON ( "
24
+ visit(o.expr, collector) << " )"
18
25
  end
19
26
  end
20
27
  end
@@ -0,0 +1,25 @@
1
+ require 'arel/visitors/visitor'
2
+
3
+ module Arel
4
+ module Visitors
5
+ class Reduce < Arel::Visitors::Visitor
6
+ def accept object, collector
7
+ visit object, collector
8
+ end
9
+
10
+ private
11
+
12
+ def visit object, collector
13
+ send dispatch[object.class], object, collector
14
+ rescue NoMethodError => e
15
+ raise e if respond_to?(dispatch[object.class], true)
16
+ superklass = object.class.ancestors.find { |klass|
17
+ respond_to?(dispatch[klass], true)
18
+ }
19
+ raise(TypeError, "Cannot visit #{object.class}") unless superklass
20
+ dispatch[object.class] = dispatch[superklass]
21
+ retry
22
+ end
23
+ end
24
+ end
25
+ end