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