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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -2
- data/History.txt +9 -4
- data/Manifest.txt +9 -7
- data/README.markdown +85 -8
- data/Rakefile +1 -1
- data/arel.gemspec +15 -16
- data/lib/arel.rb +1 -12
- data/lib/arel/collectors/bind.rb +36 -0
- data/lib/arel/collectors/plain_string.rb +18 -0
- data/lib/arel/collectors/sql_string.rb +18 -0
- data/lib/arel/factory_methods.rb +1 -1
- data/lib/arel/insert_manager.rb +5 -1
- data/lib/arel/nodes.rb +41 -0
- data/lib/arel/nodes/and.rb +1 -5
- data/lib/arel/nodes/binary.rb +2 -0
- data/lib/arel/nodes/extract.rb +0 -2
- data/lib/arel/nodes/full_outer_join.rb +6 -0
- data/lib/arel/nodes/function.rb +0 -1
- data/lib/arel/nodes/insert_statement.rb +5 -2
- data/lib/arel/nodes/node.rb +5 -1
- data/lib/arel/nodes/right_outer_join.rb +6 -0
- data/lib/arel/nodes/window.rb +23 -5
- data/lib/arel/predications.rb +41 -33
- data/lib/arel/select_manager.rb +13 -37
- data/lib/arel/table.rb +13 -9
- data/lib/arel/tree_manager.rb +8 -2
- data/lib/arel/update_manager.rb +2 -2
- data/lib/arel/visitors.rb +0 -2
- data/lib/arel/visitors/bind_substitute.rb +9 -0
- data/lib/arel/visitors/bind_visitor.rb +10 -5
- data/lib/arel/visitors/depth_first.rb +60 -57
- data/lib/arel/visitors/dot.rb +84 -80
- data/lib/arel/visitors/ibm_db.rb +4 -2
- data/lib/arel/visitors/informix.rb +39 -21
- data/lib/arel/visitors/mssql.rb +41 -23
- data/lib/arel/visitors/mysql.rb +48 -22
- data/lib/arel/visitors/oracle.rb +33 -24
- data/lib/arel/visitors/postgresql.rb +15 -8
- data/lib/arel/visitors/reduce.rb +25 -0
- data/lib/arel/visitors/sqlite.rb +3 -2
- data/lib/arel/visitors/to_sql.rb +455 -248
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel/visitors/where_sql.rb +3 -2
- data/test/attributes/test_attribute.rb +12 -3
- data/test/collectors/test_bind_collector.rb +70 -0
- data/test/collectors/test_sql_string.rb +38 -0
- data/test/helper.rb +10 -1
- data/test/nodes/test_bin.rb +2 -2
- data/test/nodes/test_count.rb +0 -6
- data/test/nodes/test_equality.rb +1 -1
- data/test/nodes/test_grouping.rb +1 -1
- data/test/nodes/test_infix_operation.rb +1 -1
- data/test/nodes/test_select_core.rb +7 -7
- data/test/nodes/test_sql_literal.rb +10 -6
- data/test/nodes/test_window.rb +9 -3
- data/test/support/fake_record.rb +16 -4
- data/test/test_factory_methods.rb +1 -1
- data/test/test_insert_manager.rb +33 -4
- data/test/test_select_manager.rb +164 -92
- data/test/test_table.rb +49 -4
- data/test/visitors/test_bind_visitor.rb +18 -10
- data/test/visitors/test_depth_first.rb +12 -0
- data/test/visitors/test_dot.rb +4 -4
- data/test/visitors/test_ibm_db.rb +11 -5
- data/test/visitors/test_informix.rb +14 -8
- data/test/visitors/test_mssql.rb +12 -8
- data/test/visitors/test_mysql.rb +17 -12
- data/test/visitors/test_oracle.rb +25 -21
- data/test/visitors/test_postgres.rb +50 -12
- data/test/visitors/test_sqlite.rb +2 -2
- data/test/visitors/test_to_sql.rb +177 -81
- metadata +24 -19
- data/lib/arel/deprecated.rb +0 -4
- data/lib/arel/expression.rb +0 -5
- data/lib/arel/sql/engine.rb +0 -10
- data/lib/arel/sql_literal.rb +0 -4
- data/lib/arel/visitors/join_sql.rb +0 -19
- data/lib/arel/visitors/order_clauses.rb +0 -11
- data/test/visitors/test_join_sql.rb +0 -42
data/lib/arel/visitors/ibm_db.rb
CHANGED
@@ -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,
|
7
|
-
"FETCH FIRST
|
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,
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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,
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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,
|
25
|
-
"SKIP
|
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,
|
28
|
-
"LIMIT
|
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
|
data/lib/arel/visitors/mssql.rb
CHANGED
@@ -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
|
11
|
+
def visit_Arel_Nodes_Top o
|
10
12
|
""
|
11
13
|
end
|
12
14
|
|
13
|
-
def
|
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
|
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
|
-
|
22
|
-
core_order_by =
|
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 = [
|
29
|
+
x.projections = [core_order_by]
|
25
30
|
is_select_count = true
|
26
31
|
else
|
27
|
-
x.projections <<
|
32
|
+
x.projections << core_order_by
|
28
33
|
end
|
34
|
+
}
|
29
35
|
|
30
|
-
|
31
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
68
|
-
return
|
69
|
-
find_left_table_pk o.left
|
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
|
data/lib/arel/visitors/mysql.rb
CHANGED
@@ -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,
|
6
|
-
|
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,
|
12
|
+
visit_Arel_Nodes_Union o.left, collector, true
|
9
13
|
else
|
10
|
-
visit o.left,
|
14
|
+
visit o.left, collector
|
11
15
|
end
|
12
16
|
|
13
|
-
|
17
|
+
collector << " UNION "
|
18
|
+
|
19
|
+
collector = case o.right
|
14
20
|
when Arel::Nodes::Union
|
15
|
-
visit_Arel_Nodes_Union o.right,
|
21
|
+
visit_Arel_Nodes_Union o.right, collector, true
|
16
22
|
else
|
17
|
-
visit o.right,
|
23
|
+
visit o.right, collector
|
18
24
|
end
|
19
25
|
|
20
26
|
if suppress_parens
|
21
|
-
|
27
|
+
collector
|
22
28
|
else
|
23
|
-
|
29
|
+
collector << " )"
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
27
|
-
def visit_Arel_Nodes_Bin o,
|
28
|
-
"BINARY
|
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,
|
35
|
-
|
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,
|
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,
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
data/lib/arel/visitors/oracle.rb
CHANGED
@@ -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,
|
7
|
-
o = order_hacks(o
|
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.
|
20
|
+
limit = o.limit.expr.expr
|
21
21
|
offset = o.offset
|
22
22
|
o.offset = nil
|
23
|
-
|
24
|
-
return <<-eosql
|
23
|
+
collector << "
|
25
24
|
SELECT * FROM (
|
26
25
|
SELECT raw_sql_.*, rownum raw_rnum_
|
27
|
-
FROM (
|
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
|
31
|
-
|
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
|
-
|
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
|
-
|
45
|
-
return <<-eosql
|
46
|
-
SELECT * FROM (
|
49
|
+
collector << "SELECT * FROM (
|
47
50
|
SELECT raw_sql_.*, rownum raw_rnum_
|
48
|
-
FROM (
|
51
|
+
FROM ("
|
52
|
+
collector = super(o, collector)
|
53
|
+
collector << ") raw_sql_
|
49
54
|
)
|
50
|
-
WHERE
|
51
|
-
|
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,
|
62
|
+
def visit_Arel_Nodes_Limit o, collector
|
63
|
+
collector
|
58
64
|
end
|
59
65
|
|
60
|
-
def visit_Arel_Nodes_Offset o,
|
61
|
-
"raw_rnum_ >
|
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,
|
65
|
-
"(
|
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,
|
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
|
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
|
101
|
+
# orders = o.orders.map { |x| visit x }.join(', ').split(',')
|
93
102
|
orders = o.orders.map do |x|
|
94
|
-
string = visit
|
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,
|
7
|
-
|
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,
|
12
|
-
|
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
|
17
|
-
|
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
|