arel 0.2.1 → 0.3.0
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.
- 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
|