arel 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/README.markdown +0 -2
- data/Rakefile +2 -2
- data/arel.gemspec +13 -3
- data/lib/arel.rb +2 -2
- data/lib/arel/algebra/relations/relation.rb +3 -2
- data/lib/arel/engines/sql/compilers/ibm_db_compiler.rb +62 -0
- data/lib/arel/engines/sql/compilers/mysql_compiler.rb +11 -0
- data/lib/arel/engines/sql/compilers/oracle_compiler.rb +95 -0
- data/lib/arel/engines/sql/compilers/postgresql_compiler.rb +42 -0
- data/lib/arel/engines/sql/compilers/sqlite_compiler.rb +9 -0
- data/lib/arel/engines/sql/engine.rb +17 -3
- data/lib/arel/engines/sql/formatters.rb +3 -3
- data/lib/arel/engines/sql/relations.rb +1 -0
- data/lib/arel/engines/sql/relations/compiler.rb +118 -0
- data/lib/arel/engines/sql/relations/relation.rb +38 -63
- data/lib/arel/engines/sql/relations/table.rb +23 -3
- data/lib/arel/engines/sql/relations/utilities/externalization.rb +1 -1
- data/lib/arel/engines/sql/relations/writes.rb +4 -71
- data/spec/arel/algebra/unit/relations/relation_spec.rb +1 -2
- data/spec/arel/algebra/unit/relations/table_spec.rb +0 -1
- data/spec/arel/engines/memory/integration/joins/cross_engine_spec.rb +9 -4
- data/spec/arel/engines/sql/integration/joins/with_adjacency_spec.rb +68 -19
- data/spec/arel/engines/sql/integration/joins/with_aggregations_spec.rb +74 -20
- data/spec/arel/engines/sql/integration/joins/with_compounds_spec.rb +33 -3
- data/spec/arel/engines/sql/unit/predicates/binary_spec.rb +22 -2
- data/spec/arel/engines/sql/unit/predicates/equality_spec.rb +15 -3
- data/spec/arel/engines/sql/unit/predicates/in_spec.rb +59 -5
- data/spec/arel/engines/sql/unit/predicates/predicates_spec.rb +12 -0
- data/spec/arel/engines/sql/unit/primitives/attribute_spec.rb +24 -1
- data/spec/arel/engines/sql/unit/primitives/expression_spec.rb +5 -1
- data/spec/arel/engines/sql/unit/primitives/literal_spec.rb +10 -2
- data/spec/arel/engines/sql/unit/relations/alias_spec.rb +11 -1
- data/spec/arel/engines/sql/unit/relations/delete_spec.rb +23 -3
- data/spec/arel/engines/sql/unit/relations/from_spec.rb +16 -2
- data/spec/arel/engines/sql/unit/relations/group_spec.rb +18 -2
- data/spec/arel/engines/sql/unit/relations/having_spec.rb +12 -3
- data/spec/arel/engines/sql/unit/relations/insert_spec.rb +37 -1
- data/spec/arel/engines/sql/unit/relations/join_spec.rb +53 -11
- data/spec/arel/engines/sql/unit/relations/lock_spec.rb +25 -0
- data/spec/arel/engines/sql/unit/relations/order_spec.rb +52 -4
- data/spec/arel/engines/sql/unit/relations/project_spec.rb +38 -5
- data/spec/arel/engines/sql/unit/relations/skip_spec.rb +10 -1
- data/spec/arel/engines/sql/unit/relations/table_spec.rb +26 -5
- data/spec/arel/engines/sql/unit/relations/take_spec.rb +18 -1
- data/spec/arel/engines/sql/unit/relations/update_spec.rb +47 -1
- data/spec/arel/engines/sql/unit/relations/where_spec.rb +18 -2
- data/spec/connections/oracle_connection.rb +19 -0
- data/spec/schemas/mysql_schema.rb +2 -1
- data/spec/schemas/oracle_schema.rb +20 -0
- data/spec/schemas/postgresql_schema.rb +2 -1
- data/spec/schemas/sqlite3_schema.rb +2 -1
- data/spec/spec_helper.rb +16 -7
- metadata +31 -9
@@ -0,0 +1,118 @@
|
|
1
|
+
module Arel
|
2
|
+
module SqlCompiler
|
3
|
+
class GenericCompiler
|
4
|
+
attr_reader :relation
|
5
|
+
|
6
|
+
def initialize(relation)
|
7
|
+
@relation = relation
|
8
|
+
end
|
9
|
+
|
10
|
+
def select_sql
|
11
|
+
build_query \
|
12
|
+
"SELECT #{select_clauses.join(', ')}",
|
13
|
+
"FROM #{from_clauses}",
|
14
|
+
(joins(self) unless joins(self).blank? ),
|
15
|
+
("WHERE #{where_clauses.join(" AND ")}" unless wheres.blank? ),
|
16
|
+
("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ),
|
17
|
+
("HAVING #{having_clauses.join(', ')}" unless havings.blank? ),
|
18
|
+
("ORDER BY #{order_clauses.join(', ')}" unless orders.blank? ),
|
19
|
+
("LIMIT #{taken}" unless taken.blank? ),
|
20
|
+
("OFFSET #{skipped}" unless skipped.blank? ),
|
21
|
+
("#{locked}" unless locked.blank?)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete_sql
|
25
|
+
build_query \
|
26
|
+
"DELETE",
|
27
|
+
"FROM #{table_sql}",
|
28
|
+
("WHERE #{wheres.collect(&:to_sql).join(' AND ')}" unless wheres.blank? ),
|
29
|
+
(add_limit_on_delete(taken) unless taken.blank? )
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_limit_on_delete(taken)
|
33
|
+
"LIMIT #{taken}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def insert_sql(include_returning = true)
|
37
|
+
insertion_attributes_values_sql = if record.is_a?(Value)
|
38
|
+
record.value
|
39
|
+
else
|
40
|
+
attributes = record.keys.sort_by do |attribute|
|
41
|
+
attribute.name.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
first = attributes.collect do |key|
|
45
|
+
engine.quote_column_name(key.name)
|
46
|
+
end.join(', ')
|
47
|
+
|
48
|
+
second = attributes.collect do |key|
|
49
|
+
key.format(record[key])
|
50
|
+
end.join(', ')
|
51
|
+
|
52
|
+
build_query "(#{first})", "VALUES (#{second})"
|
53
|
+
end
|
54
|
+
|
55
|
+
build_query \
|
56
|
+
"INSERT",
|
57
|
+
"INTO #{table_sql}",
|
58
|
+
insertion_attributes_values_sql,
|
59
|
+
("RETURNING #{engine.quote_column_name(primary_key)}" if include_returning && compiler.supports_insert_with_returning?)
|
60
|
+
end
|
61
|
+
|
62
|
+
def supports_insert_with_returning?
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_sql
|
67
|
+
build_query \
|
68
|
+
"UPDATE #{table_sql} SET",
|
69
|
+
assignment_sql,
|
70
|
+
build_update_conditions_sql
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
def method_missing(method, *args, &block)
|
75
|
+
relation.send(method, *args, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
def build_query(*parts)
|
79
|
+
parts.compact.join(" ")
|
80
|
+
end
|
81
|
+
|
82
|
+
def assignment_sql
|
83
|
+
if assignments.respond_to?(:collect)
|
84
|
+
attributes = assignments.keys.sort_by do |attribute|
|
85
|
+
attribute.name.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
attributes.map do |attribute|
|
89
|
+
value = assignments[attribute]
|
90
|
+
"#{engine.quote_column_name(attribute.name)} = #{attribute.format(value)}"
|
91
|
+
end.join(", ")
|
92
|
+
else
|
93
|
+
assignments.value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_update_conditions_sql
|
98
|
+
conditions = ""
|
99
|
+
conditions << " WHERE #{wheres.collect(&:to_sql).join(' AND ')}" unless wheres.blank?
|
100
|
+
conditions << " ORDER BY #{order_clauses.join(', ')}" unless orders.blank?
|
101
|
+
|
102
|
+
unless taken.blank?
|
103
|
+
conditions = limited_update_conditions(conditions, taken)
|
104
|
+
end
|
105
|
+
|
106
|
+
conditions
|
107
|
+
end
|
108
|
+
|
109
|
+
def limited_update_conditions(conditions, taken)
|
110
|
+
conditions << " LIMIT #{taken}"
|
111
|
+
quoted_primary_key = engine.quote_column_name(primary_key)
|
112
|
+
"WHERE #{quoted_primary_key} IN (SELECT #{quoted_primary_key} FROM #{engine.connection.quote_table_name table.name} #{conditions})"
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -1,86 +1,61 @@
|
|
1
1
|
module Arel
|
2
2
|
class Relation
|
3
|
-
|
4
|
-
formatter.select select_sql, self
|
5
|
-
end
|
6
|
-
|
7
|
-
def select_sql
|
8
|
-
if engine.adapter_name == "PostgreSQL" && !orders.blank? && using_distinct_on?
|
9
|
-
# PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
|
10
|
-
# by wrapping the +sql+ string as a sub-select and ordering in that query.
|
11
|
-
order = order_clauses.join(', ').split(',').map { |s| s.strip }.reject(&:blank?)
|
12
|
-
order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{'DESC' if s =~ /\bdesc$/i}" }.join(', ')
|
13
|
-
|
14
|
-
query = build_query \
|
15
|
-
"SELECT #{select_clauses.kind_of?(::Array) ? select_clauses.join("") : select_clauses.to_s}",
|
16
|
-
"FROM #{from_clauses}",
|
17
|
-
(joins(self) unless joins(self).blank? ),
|
18
|
-
("WHERE #{where_clauses.join(" AND ")}" unless wheres.blank? ),
|
19
|
-
("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ),
|
20
|
-
("HAVING #{having_clauses.join(', ')}" unless havings.blank? ),
|
21
|
-
("#{locked}" unless locked.blank? )
|
3
|
+
@@connection_tables_primary_keys = {}
|
22
4
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
else
|
30
|
-
build_query \
|
31
|
-
"SELECT #{select_clauses.join(', ')}",
|
32
|
-
"FROM #{from_clauses}",
|
33
|
-
(joins(self) unless joins(self).blank? ),
|
34
|
-
("WHERE #{where_clauses.join(" AND ")}" unless wheres.blank? ),
|
35
|
-
("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ),
|
36
|
-
("HAVING #{having_clauses.join(', ')}" unless havings.blank? ),
|
37
|
-
("ORDER BY #{order_clauses.join(', ')}" unless orders.blank? ),
|
38
|
-
("LIMIT #{taken}" unless taken.blank? ),
|
39
|
-
("OFFSET #{skipped}" unless skipped.blank? ),
|
40
|
-
("#{locked}" unless engine.adapter_name =~ /SQLite/ || locked.blank?)
|
5
|
+
def compiler
|
6
|
+
@compiler ||= begin
|
7
|
+
"Arel::SqlCompiler::#{engine.adapter_name}Compiler".constantize.new(self)
|
8
|
+
rescue
|
9
|
+
Arel::SqlCompiler::GenericCompiler.new(self)
|
41
10
|
end
|
42
11
|
end
|
43
12
|
|
44
|
-
def
|
45
|
-
|
13
|
+
def to_sql(formatter = Sql::SelectStatement.new(self))
|
14
|
+
formatter.select compiler.select_sql, self
|
46
15
|
end
|
47
16
|
|
48
17
|
def christener
|
49
18
|
@christener ||= Sql::Christener.new
|
50
19
|
end
|
51
20
|
|
52
|
-
|
53
|
-
|
54
|
-
def build_query(*parts)
|
55
|
-
parts.compact.join(" ")
|
21
|
+
def inclusion_predicate_sql
|
22
|
+
"IN"
|
56
23
|
end
|
57
24
|
|
58
|
-
def
|
59
|
-
|
25
|
+
def primary_key
|
26
|
+
connection_id = engine.connection.object_id
|
27
|
+
if @@connection_tables_primary_keys[connection_id] && @@connection_tables_primary_keys[connection_id].has_key?(table.name)
|
28
|
+
@@connection_tables_primary_keys[connection_id][table.name]
|
29
|
+
else
|
30
|
+
@@connection_tables_primary_keys[connection_id] ||= {}
|
31
|
+
@@connection_tables_primary_keys[connection_id][table.name] = engine.connection.primary_key(table.name)
|
32
|
+
end
|
60
33
|
end
|
61
34
|
|
62
|
-
|
63
|
-
attributes.collect { |a| a.to_sql(Sql::SelectClause.new(self)) }
|
64
|
-
end
|
35
|
+
protected
|
65
36
|
|
66
|
-
|
67
|
-
|
68
|
-
|
37
|
+
def from_clauses
|
38
|
+
sources.blank? ? table_sql(Sql::TableReference.new(self)) : sources
|
39
|
+
end
|
69
40
|
|
70
|
-
|
71
|
-
|
72
|
-
|
41
|
+
def select_clauses
|
42
|
+
attributes.collect { |a| a.to_sql(Sql::SelectClause.new(self)) }
|
43
|
+
end
|
73
44
|
|
74
|
-
|
75
|
-
|
76
|
-
|
45
|
+
def where_clauses
|
46
|
+
wheres.collect { |w| w.to_sql(Sql::WhereClause.new(self)) }
|
47
|
+
end
|
77
48
|
|
78
|
-
|
79
|
-
|
80
|
-
|
49
|
+
def group_clauses
|
50
|
+
groupings.collect { |g| g.to_sql(Sql::GroupClause.new(self)) }
|
51
|
+
end
|
81
52
|
|
82
|
-
|
83
|
-
|
84
|
-
|
53
|
+
def having_clauses
|
54
|
+
havings.collect { |g| g.to_sql(Sql::HavingClause.new(self)) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def order_clauses
|
58
|
+
orders.collect { |o| o.to_sql(Sql::OrderClause.new(self)) }
|
59
|
+
end
|
85
60
|
end
|
86
61
|
end
|
@@ -2,7 +2,7 @@ module Arel
|
|
2
2
|
class Table < Relation
|
3
3
|
include Recursion::BaseCase
|
4
4
|
|
5
|
-
cattr_accessor :engine
|
5
|
+
cattr_accessor :engine, :tables
|
6
6
|
attr_reader :name, :engine, :table_alias, :options
|
7
7
|
|
8
8
|
def initialize(name, options = {})
|
@@ -15,15 +15,35 @@ module Arel
|
|
15
15
|
else
|
16
16
|
@engine = options # Table.new('foo', engine)
|
17
17
|
end
|
18
|
+
|
19
|
+
if @engine.connection
|
20
|
+
begin
|
21
|
+
require "arel/engines/sql/compilers/#{@engine.adapter_name.downcase}_compiler"
|
22
|
+
@@tables ||= engine.tables
|
23
|
+
rescue LoadError
|
24
|
+
raise "#{@engine.adapter_name} is not supported by Arel."
|
25
|
+
end
|
26
|
+
end
|
18
27
|
end
|
19
28
|
|
20
29
|
def as(table_alias)
|
21
30
|
Table.new(name, options.merge(:as => table_alias))
|
22
31
|
end
|
23
32
|
|
33
|
+
def table_exists?
|
34
|
+
if @table_exists
|
35
|
+
true
|
36
|
+
else
|
37
|
+
@table_exists = @@tables.include?(name) || engine.table_exists?(name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
24
41
|
def attributes
|
25
|
-
@attributes
|
26
|
-
|
42
|
+
return @attributes if defined?(@attributes)
|
43
|
+
if table_exists?
|
44
|
+
@attributes = columns.collect { |column| Attribute.new(self, column.name.to_sym) }
|
45
|
+
else
|
46
|
+
[]
|
27
47
|
end
|
28
48
|
end
|
29
49
|
|
@@ -1,86 +1,19 @@
|
|
1
1
|
module Arel
|
2
2
|
class Deletion < Compound
|
3
3
|
def to_sql
|
4
|
-
|
5
|
-
"DELETE",
|
6
|
-
"FROM #{table_sql}",
|
7
|
-
("WHERE #{wheres.collect(&:to_sql).join(' AND ')}" unless wheres.blank? ),
|
8
|
-
("LIMIT #{taken}" unless taken.blank? )
|
4
|
+
compiler.delete_sql
|
9
5
|
end
|
10
6
|
end
|
11
7
|
|
12
8
|
class Insert < Compound
|
13
|
-
def to_sql
|
14
|
-
|
15
|
-
record.value
|
16
|
-
else
|
17
|
-
attributes = record.keys.sort_by do |attribute|
|
18
|
-
attribute.name.to_s
|
19
|
-
end
|
20
|
-
|
21
|
-
first = attributes.collect do |key|
|
22
|
-
engine.quote_column_name(key.name)
|
23
|
-
end.join(', ')
|
24
|
-
|
25
|
-
second = attributes.collect do |key|
|
26
|
-
key.format(record[key])
|
27
|
-
end.join(', ')
|
28
|
-
|
29
|
-
build_query "(#{first})", "VALUES (#{second})"
|
30
|
-
end
|
31
|
-
|
32
|
-
build_query \
|
33
|
-
"INSERT",
|
34
|
-
"INTO #{table_sql}",
|
35
|
-
insertion_attributes_values_sql
|
9
|
+
def to_sql(include_returning = true)
|
10
|
+
compiler.insert_sql(include_returning)
|
36
11
|
end
|
37
12
|
end
|
38
13
|
|
39
14
|
class Update < Compound
|
40
15
|
def to_sql
|
41
|
-
|
42
|
-
"UPDATE #{table_sql} SET",
|
43
|
-
assignment_sql,
|
44
|
-
build_update_conditions_sql
|
45
|
-
end
|
46
|
-
|
47
|
-
protected
|
48
|
-
|
49
|
-
def assignment_sql
|
50
|
-
if assignments.respond_to?(:collect)
|
51
|
-
attributes = assignments.keys.sort_by do |attribute|
|
52
|
-
attribute.name.to_s
|
53
|
-
end
|
54
|
-
|
55
|
-
attributes.map do |attribute|
|
56
|
-
value = assignments[attribute]
|
57
|
-
"#{engine.quote_column_name(attribute.name)} = #{attribute.format(value)}"
|
58
|
-
end.join(", ")
|
59
|
-
else
|
60
|
-
assignments.value
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def build_update_conditions_sql
|
65
|
-
conditions = ""
|
66
|
-
conditions << " WHERE #{wheres.collect(&:to_sql).join(' AND ')}" unless wheres.blank?
|
67
|
-
conditions << " ORDER BY #{order_clauses.join(', ')}" unless orders.blank?
|
68
|
-
|
69
|
-
unless taken.blank?
|
70
|
-
conditions << " LIMIT #{taken}"
|
71
|
-
|
72
|
-
if engine.adapter_name != "MySQL"
|
73
|
-
begin
|
74
|
-
quote_primary_key = engine.quote_column_name(table.name.classify.constantize.primary_key)
|
75
|
-
rescue NameError
|
76
|
-
quote_primary_key = engine.quote_column_name("id")
|
77
|
-
end
|
78
|
-
|
79
|
-
conditions = "WHERE #{quote_primary_key} IN (SELECT #{quote_primary_key} FROM #{engine.connection.quote_table_name table.name} #{conditions})"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
conditions
|
16
|
+
compiler.update_sql
|
84
17
|
end
|
85
18
|
end
|
86
19
|
end
|
@@ -16,10 +16,9 @@ module Arel
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe 'when given a', Symbol, String do
|
19
|
-
it "returns the attribute with the same name
|
19
|
+
it "returns the attribute with the same name" do
|
20
20
|
check @relation[:id].should == @attribute1
|
21
21
|
check @relation['id'].should == @attribute1
|
22
|
-
@relation[:does_not_exist].should be_nil
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
@@ -10,7 +10,6 @@ module Arel
|
|
10
10
|
describe 'when given a', Symbol do
|
11
11
|
it "manufactures an attribute if the symbol names an attribute within the relation" do
|
12
12
|
check @relation[:id].should == Attribute.new(@relation, :id)
|
13
|
-
@relation[:does_not_exist].should be_nil
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
@@ -12,6 +12,11 @@ module Arel
|
|
12
12
|
@photos.delete
|
13
13
|
@photos.insert(@photos[:id] => 1, @photos[:user_id] => 1, @photos[:camera_id] => 6)
|
14
14
|
@photos.insert(@photos[:id] => 2, @photos[:user_id] => 2, @photos[:camera_id] => 42)
|
15
|
+
# Oracle adapter returns database integers as Ruby integers and not strings
|
16
|
+
@adapter_returns_integer = false
|
17
|
+
adapter_is :oracle do
|
18
|
+
@adapter_returns_integer = true
|
19
|
+
end
|
15
20
|
end
|
16
21
|
|
17
22
|
describe 'when the in memory relation is on the left' do
|
@@ -22,8 +27,8 @@ module Arel
|
|
22
27
|
.project(@users[:name], @photos[:camera_id]) \
|
23
28
|
.let do |relation|
|
24
29
|
relation.call.should == [
|
25
|
-
Row.new(relation, ['bryan', '6']),
|
26
|
-
Row.new(relation, ['emilio', '42'])
|
30
|
+
Row.new(relation, ['bryan', @adapter_returns_integer ? 6 : '6']),
|
31
|
+
Row.new(relation, ['emilio', @adapter_returns_integer ? 42 : '42'])
|
27
32
|
]
|
28
33
|
end
|
29
34
|
end
|
@@ -37,8 +42,8 @@ module Arel
|
|
37
42
|
.project(@users[:name], @photos[:camera_id]) \
|
38
43
|
.let do |relation|
|
39
44
|
relation.call.should == [
|
40
|
-
Row.new(relation, ['bryan', '6']),
|
41
|
-
Row.new(relation, ['emilio', '42'])
|
45
|
+
Row.new(relation, ['bryan', @adapter_returns_integer ? 6 : '6']),
|
46
|
+
Row.new(relation, ['emilio', @adapter_returns_integer ? 42 : '42'])
|
42
47
|
]
|
43
48
|
end
|
44
49
|
end
|