arel 0.1.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.
Files changed (132) hide show
  1. data/.gitignore +6 -0
  2. data/README.markdown +184 -0
  3. data/Rakefile +60 -0
  4. data/VERSION +1 -0
  5. data/arel.gemspec +233 -0
  6. data/doc/CONVENTIONS +17 -0
  7. data/doc/TODO +118 -0
  8. data/lib/arel.rb +10 -0
  9. data/lib/arel/algebra.rb +4 -0
  10. data/lib/arel/algebra/extensions.rb +4 -0
  11. data/lib/arel/algebra/extensions/class.rb +32 -0
  12. data/lib/arel/algebra/extensions/hash.rb +11 -0
  13. data/lib/arel/algebra/extensions/object.rb +17 -0
  14. data/lib/arel/algebra/extensions/symbol.rb +9 -0
  15. data/lib/arel/algebra/predicates.rb +41 -0
  16. data/lib/arel/algebra/primitives.rb +5 -0
  17. data/lib/arel/algebra/primitives/attribute.rb +150 -0
  18. data/lib/arel/algebra/primitives/expression.rb +43 -0
  19. data/lib/arel/algebra/primitives/ordering.rb +23 -0
  20. data/lib/arel/algebra/primitives/value.rb +14 -0
  21. data/lib/arel/algebra/relations.rb +14 -0
  22. data/lib/arel/algebra/relations/operations/alias.rb +7 -0
  23. data/lib/arel/algebra/relations/operations/group.rb +12 -0
  24. data/lib/arel/algebra/relations/operations/join.rb +64 -0
  25. data/lib/arel/algebra/relations/operations/order.rb +18 -0
  26. data/lib/arel/algebra/relations/operations/project.rb +20 -0
  27. data/lib/arel/algebra/relations/operations/skip.rb +6 -0
  28. data/lib/arel/algebra/relations/operations/take.rb +10 -0
  29. data/lib/arel/algebra/relations/operations/where.rb +16 -0
  30. data/lib/arel/algebra/relations/relation.rb +136 -0
  31. data/lib/arel/algebra/relations/row.rb +26 -0
  32. data/lib/arel/algebra/relations/utilities/compound.rb +30 -0
  33. data/lib/arel/algebra/relations/utilities/externalization.rb +24 -0
  34. data/lib/arel/algebra/relations/utilities/nil.rb +7 -0
  35. data/lib/arel/algebra/relations/writes.rb +36 -0
  36. data/lib/arel/engines.rb +2 -0
  37. data/lib/arel/engines/memory.rb +4 -0
  38. data/lib/arel/engines/memory/engine.rb +16 -0
  39. data/lib/arel/engines/memory/predicates.rb +35 -0
  40. data/lib/arel/engines/memory/primitives.rb +27 -0
  41. data/lib/arel/engines/memory/relations.rb +5 -0
  42. data/lib/arel/engines/memory/relations/array.rb +25 -0
  43. data/lib/arel/engines/memory/relations/compound.rb +9 -0
  44. data/lib/arel/engines/memory/relations/operations.rb +61 -0
  45. data/lib/arel/engines/memory/relations/writes.rb +7 -0
  46. data/lib/arel/engines/sql.rb +7 -0
  47. data/lib/arel/engines/sql/christener.rb +13 -0
  48. data/lib/arel/engines/sql/engine.rb +37 -0
  49. data/lib/arel/engines/sql/extensions.rb +4 -0
  50. data/lib/arel/engines/sql/extensions/array.rb +16 -0
  51. data/lib/arel/engines/sql/extensions/nil_class.rb +11 -0
  52. data/lib/arel/engines/sql/extensions/object.rb +15 -0
  53. data/lib/arel/engines/sql/extensions/range.rb +15 -0
  54. data/lib/arel/engines/sql/formatters.rb +113 -0
  55. data/lib/arel/engines/sql/predicates.rb +51 -0
  56. data/lib/arel/engines/sql/primitives.rb +85 -0
  57. data/lib/arel/engines/sql/relations.rb +9 -0
  58. data/lib/arel/engines/sql/relations/operations/alias.rb +5 -0
  59. data/lib/arel/engines/sql/relations/operations/join.rb +33 -0
  60. data/lib/arel/engines/sql/relations/relation.rb +50 -0
  61. data/lib/arel/engines/sql/relations/table.rb +52 -0
  62. data/lib/arel/engines/sql/relations/utilities/compound.rb +10 -0
  63. data/lib/arel/engines/sql/relations/utilities/externalization.rb +14 -0
  64. data/lib/arel/engines/sql/relations/utilities/nil.rb +6 -0
  65. data/lib/arel/engines/sql/relations/utilities/recursion.rb +13 -0
  66. data/lib/arel/engines/sql/relations/writes.rb +39 -0
  67. data/lib/arel/session.rb +48 -0
  68. data/spec/arel/algebra/unit/predicates/binary_spec.rb +33 -0
  69. data/spec/arel/algebra/unit/predicates/equality_spec.rb +27 -0
  70. data/spec/arel/algebra/unit/predicates/in_spec.rb +10 -0
  71. data/spec/arel/algebra/unit/primitives/attribute_spec.rb +183 -0
  72. data/spec/arel/algebra/unit/primitives/expression_spec.rb +45 -0
  73. data/spec/arel/algebra/unit/primitives/value_spec.rb +15 -0
  74. data/spec/arel/algebra/unit/relations/alias_spec.rb +16 -0
  75. data/spec/arel/algebra/unit/relations/delete_spec.rb +9 -0
  76. data/spec/arel/algebra/unit/relations/group_spec.rb +10 -0
  77. data/spec/arel/algebra/unit/relations/insert_spec.rb +9 -0
  78. data/spec/arel/algebra/unit/relations/join_spec.rb +26 -0
  79. data/spec/arel/algebra/unit/relations/order_spec.rb +21 -0
  80. data/spec/arel/algebra/unit/relations/project_spec.rb +34 -0
  81. data/spec/arel/algebra/unit/relations/relation_spec.rb +188 -0
  82. data/spec/arel/algebra/unit/relations/skip_spec.rb +10 -0
  83. data/spec/arel/algebra/unit/relations/table_spec.rb +39 -0
  84. data/spec/arel/algebra/unit/relations/take_spec.rb +10 -0
  85. data/spec/arel/algebra/unit/relations/update_spec.rb +9 -0
  86. data/spec/arel/algebra/unit/relations/where_spec.rb +18 -0
  87. data/spec/arel/algebra/unit/session/session_spec.rb +84 -0
  88. data/spec/arel/engines/memory/integration/joins/cross_engine_spec.rb +48 -0
  89. data/spec/arel/engines/memory/unit/relations/array_spec.rb +32 -0
  90. data/spec/arel/engines/memory/unit/relations/insert_spec.rb +28 -0
  91. data/spec/arel/engines/memory/unit/relations/join_spec.rb +31 -0
  92. data/spec/arel/engines/memory/unit/relations/order_spec.rb +27 -0
  93. data/spec/arel/engines/memory/unit/relations/project_spec.rb +27 -0
  94. data/spec/arel/engines/memory/unit/relations/skip_spec.rb +26 -0
  95. data/spec/arel/engines/memory/unit/relations/take_spec.rb +26 -0
  96. data/spec/arel/engines/memory/unit/relations/where_spec.rb +39 -0
  97. data/spec/arel/engines/sql/integration/joins/with_adjacency_spec.rb +209 -0
  98. data/spec/arel/engines/sql/integration/joins/with_aggregations_spec.rb +167 -0
  99. data/spec/arel/engines/sql/integration/joins/with_compounds_spec.rb +107 -0
  100. data/spec/arel/engines/sql/unit/engine_spec.rb +45 -0
  101. data/spec/arel/engines/sql/unit/predicates/binary_spec.rb +117 -0
  102. data/spec/arel/engines/sql/unit/predicates/equality_spec.rb +46 -0
  103. data/spec/arel/engines/sql/unit/predicates/in_spec.rb +86 -0
  104. data/spec/arel/engines/sql/unit/predicates/predicates_spec.rb +65 -0
  105. data/spec/arel/engines/sql/unit/primitives/attribute_spec.rb +32 -0
  106. data/spec/arel/engines/sql/unit/primitives/expression_spec.rb +24 -0
  107. data/spec/arel/engines/sql/unit/primitives/literal_spec.rb +23 -0
  108. data/spec/arel/engines/sql/unit/primitives/value_spec.rb +29 -0
  109. data/spec/arel/engines/sql/unit/relations/alias_spec.rb +43 -0
  110. data/spec/arel/engines/sql/unit/relations/delete_spec.rb +63 -0
  111. data/spec/arel/engines/sql/unit/relations/group_spec.rb +56 -0
  112. data/spec/arel/engines/sql/unit/relations/insert_spec.rb +107 -0
  113. data/spec/arel/engines/sql/unit/relations/join_spec.rb +57 -0
  114. data/spec/arel/engines/sql/unit/relations/order_spec.rb +113 -0
  115. data/spec/arel/engines/sql/unit/relations/project_spec.rb +110 -0
  116. data/spec/arel/engines/sql/unit/relations/skip_spec.rb +32 -0
  117. data/spec/arel/engines/sql/unit/relations/table_spec.rb +69 -0
  118. data/spec/arel/engines/sql/unit/relations/take_spec.rb +32 -0
  119. data/spec/arel/engines/sql/unit/relations/update_spec.rb +151 -0
  120. data/spec/arel/engines/sql/unit/relations/where_spec.rb +56 -0
  121. data/spec/connections/mysql_connection.rb +16 -0
  122. data/spec/connections/postgresql_connection.rb +15 -0
  123. data/spec/connections/sqlite3_connection.rb +25 -0
  124. data/spec/doubles/hash.rb +23 -0
  125. data/spec/matchers/be_like.rb +24 -0
  126. data/spec/matchers/disambiguate_attributes.rb +28 -0
  127. data/spec/matchers/hash_the_same_as.rb +26 -0
  128. data/spec/schemas/mysql_schema.rb +18 -0
  129. data/spec/schemas/postgresql_schema.rb +18 -0
  130. data/spec/schemas/sqlite3_schema.rb +18 -0
  131. data/spec/spec_helper.rb +47 -0
  132. metadata +250 -0
@@ -0,0 +1,51 @@
1
+ module Arel
2
+ class Binary < Predicate
3
+ def to_sql(formatter = nil)
4
+ "#{operand1.to_sql} #{predicate_sql} #{operand1.format(operand2)}"
5
+ end
6
+ end
7
+
8
+ class CompoundPredicate < Binary
9
+ def to_sql(formatter = nil)
10
+ "(#{operand1.to_sql(formatter)} #{predicate_sql} #{operand2.to_sql(formatter)})"
11
+ end
12
+ end
13
+
14
+ class Or < CompoundPredicate
15
+ def predicate_sql; "OR" end
16
+ end
17
+
18
+ class And < CompoundPredicate
19
+ def predicate_sql; "AND" end
20
+ end
21
+
22
+ class Equality < Binary
23
+ def predicate_sql
24
+ operand2.equality_predicate_sql
25
+ end
26
+ end
27
+
28
+ class GreaterThanOrEqualTo < Binary
29
+ def predicate_sql; '>=' end
30
+ end
31
+
32
+ class GreaterThan < Binary
33
+ def predicate_sql; '>' end
34
+ end
35
+
36
+ class LessThanOrEqualTo < Binary
37
+ def predicate_sql; '<=' end
38
+ end
39
+
40
+ class LessThan < Binary
41
+ def predicate_sql; '<' end
42
+ end
43
+
44
+ class Match < Binary
45
+ def predicate_sql; 'LIKE' end
46
+ end
47
+
48
+ class In < Binary
49
+ def predicate_sql; operand2.inclusion_predicate_sql end
50
+ end
51
+ end
@@ -0,0 +1,85 @@
1
+ module Arel
2
+ class SqlLiteral < String
3
+ def relation
4
+ nil
5
+ end
6
+
7
+ def to_sql(formatter = nil)
8
+ self
9
+ end
10
+ end
11
+
12
+ class Attribute
13
+ def column
14
+ original_relation.column_for(self)
15
+ end
16
+
17
+ def type_cast(value)
18
+ root.relation.format(self, value)
19
+ end
20
+
21
+ def format(object)
22
+ object.to_sql(Sql::Attribute.new(self))
23
+ end
24
+
25
+ def to_sql(formatter = Sql::WhereCondition.new(relation))
26
+ formatter.attribute self
27
+ end
28
+ end
29
+
30
+ class Value
31
+ delegate :inclusion_predicate_sql, :equality_predicate_sql, :to => :value
32
+
33
+ def to_sql(formatter = Sql::WhereCondition.new(relation))
34
+ formatter.value value
35
+ end
36
+
37
+ def format(object)
38
+ object.to_sql(Sql::Value.new(relation))
39
+ end
40
+ end
41
+
42
+ class Ordering
43
+ def to_sql(formatter = Sql::OrderClause.new(relation))
44
+ formatter.ordering self
45
+ end
46
+ end
47
+
48
+ class Ascending < Ordering
49
+ def direction_sql; 'ASC' end
50
+ end
51
+
52
+ class Descending < Ordering
53
+ def direction_sql; 'DESC' end
54
+ end
55
+
56
+ class Expression < Attribute
57
+ def to_sql(formatter = Sql::SelectClause.new(relation))
58
+ formatter.expression self
59
+ end
60
+ end
61
+
62
+ class Count < Expression
63
+ def function_sql; 'COUNT' end
64
+ end
65
+
66
+ class Distinct < Expression
67
+ def function_sql; 'DISTINCT' end
68
+ end
69
+
70
+ class Sum < Expression
71
+ def function_sql; 'SUM' end
72
+ end
73
+
74
+ class Maximum < Expression
75
+ def function_sql; 'MAX' end
76
+ end
77
+
78
+ class Minimum < Expression
79
+ def function_sql; 'MIN' end
80
+ end
81
+
82
+ class Average < Expression
83
+ def function_sql; 'AVG' end
84
+ end
85
+ end
@@ -0,0 +1,9 @@
1
+ require 'arel/engines/sql/relations/utilities/compound'
2
+ require 'arel/engines/sql/relations/utilities/recursion'
3
+ require 'arel/engines/sql/relations/utilities/externalization'
4
+ require 'arel/engines/sql/relations/utilities/nil'
5
+ require 'arel/engines/sql/relations/relation'
6
+ require 'arel/engines/sql/relations/table'
7
+ require 'arel/engines/sql/relations/operations/join'
8
+ require 'arel/engines/sql/relations/operations/alias'
9
+ require 'arel/engines/sql/relations/writes'
@@ -0,0 +1,5 @@
1
+ module Arel
2
+ class Alias < Compound
3
+ include Recursion::BaseCase
4
+ end
5
+ end
@@ -0,0 +1,33 @@
1
+ module Arel
2
+ class Join < Relation
3
+ def table_sql(formatter = Sql::TableReference.new(self))
4
+ relation1.externalize.table_sql(formatter)
5
+ end
6
+
7
+ def joins(environment, formatter = Sql::TableReference.new(environment))
8
+ @joins ||= begin
9
+ this_join = [
10
+ join_sql,
11
+ relation2.externalize.table_sql(formatter),
12
+ ("ON" unless predicates.blank?),
13
+ (ons + relation2.externalize.wheres).collect { |p| p.bind(environment).to_sql(Sql::WhereClause.new(environment)) }.join(' AND ')
14
+ ].compact.join(" ")
15
+ [relation1.joins(environment), this_join, relation2.joins(environment)].compact.join(" ")
16
+ end
17
+ end
18
+ end
19
+
20
+ class InnerJoin < Join
21
+ def join_sql; "INNER JOIN" end
22
+ end
23
+
24
+ class OuterJoin < Join
25
+ def join_sql; "LEFT OUTER JOIN" end
26
+ end
27
+
28
+ class StringJoin < Join
29
+ def joins(_, __ = nil)
30
+ relation2
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ module Arel
2
+ class Relation
3
+ def to_sql(formatter = Sql::SelectStatement.new(self))
4
+ formatter.select select_sql, self
5
+ end
6
+
7
+ def select_sql
8
+ build_query \
9
+ "SELECT #{select_clauses.join(', ')}",
10
+ "FROM #{table_sql(Sql::TableReference.new(self))}",
11
+ (joins(self) unless joins(self).blank? ),
12
+ ("WHERE #{where_clauses.join("\n\tAND ")}" unless wheres.blank? ),
13
+ ("GROUP BY #{group_clauses.join(', ')}" unless groupings.blank? ),
14
+ ("ORDER BY #{order_clauses.join(', ')}" unless orders.blank? ),
15
+ ("LIMIT #{taken}" unless taken.blank? ),
16
+ ("OFFSET #{skipped}" unless skipped.blank? )
17
+ end
18
+
19
+ def inclusion_predicate_sql
20
+ "IN"
21
+ end
22
+
23
+ def christener
24
+ @christener ||= Sql::Christener.new
25
+ end
26
+
27
+ protected
28
+
29
+ def build_query(*parts)
30
+ parts.compact.join("\n")
31
+ end
32
+
33
+ def select_clauses
34
+ attributes.collect { |a| a.to_sql(Sql::SelectClause.new(self)) }
35
+ end
36
+
37
+ def where_clauses
38
+ wheres.collect { |w| w.to_sql(Sql::WhereClause.new(self)) }
39
+ end
40
+
41
+ def group_clauses
42
+ groupings.collect { |g| g.to_sql(Sql::GroupClause.new(self)) }
43
+ end
44
+
45
+ def order_clauses
46
+ orders.collect { |o| o.to_sql(Sql::OrderClause.new(self)) }
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,52 @@
1
+ module Arel
2
+ class Table < Relation
3
+ include Recursion::BaseCase
4
+
5
+ cattr_accessor :engine
6
+ attr_reader :name, :engine
7
+
8
+ def initialize(name, engine = Table.engine)
9
+ @name, @engine = name.to_s, engine
10
+ end
11
+
12
+ def attributes
13
+ @attributes ||= columns.collect do |column|
14
+ Attribute.new(self, column.name.to_sym)
15
+ end
16
+ end
17
+
18
+ def eql?(other)
19
+ self == other
20
+ end
21
+
22
+ def hash
23
+ @hash ||= :name.hash
24
+ end
25
+
26
+ def format(attribute, value)
27
+ attribute.column.type_cast(value)
28
+ end
29
+
30
+ def column_for(attribute)
31
+ has_attribute?(attribute) and columns.detect { |c| c.name == attribute.name.to_s }
32
+ end
33
+
34
+ def columns
35
+ @columns ||= engine.columns(name, "#{name} Columns")
36
+ end
37
+
38
+ def reset
39
+ @attributes = @columns = nil
40
+ end
41
+
42
+ def ==(other)
43
+ Table === other and
44
+ name == other.name
45
+ end
46
+ end
47
+ end
48
+
49
+ def Table(name, engine = Arel::Table.engine)
50
+ Arel::Table.new(name, engine)
51
+ end
52
+
@@ -0,0 +1,10 @@
1
+ module Arel
2
+ class Compound < Relation
3
+ delegate :table, :table_sql, :to => :relation
4
+
5
+ def build_query(*parts)
6
+ parts.compact.join("\n")
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,14 @@
1
+ module Arel
2
+ class Externalization < Compound
3
+ include Recursion::BaseCase
4
+
5
+ def table_sql(formatter = Sql::TableReference.new(relation))
6
+ formatter.select relation.select_sql, self
7
+ end
8
+
9
+ # REMOVEME
10
+ def name
11
+ relation.name + '_external'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,6 @@
1
+ module Arel
2
+ class Nil < Relation
3
+ def table_sql(formatter = nil); '' end
4
+ def name; '' end
5
+ end
6
+ end
@@ -0,0 +1,13 @@
1
+ module Arel
2
+ module Recursion
3
+ module BaseCase
4
+ def table
5
+ self
6
+ end
7
+
8
+ def table_sql(formatter = Sql::TableReference.new(self))
9
+ formatter.table self
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ module Arel
2
+ class Deletion < Compound
3
+ def to_sql(formatter = nil)
4
+ build_query \
5
+ "DELETE",
6
+ "FROM #{table_sql}",
7
+ ("WHERE #{wheres.collect(&:to_sql).join('\n\tAND ')}" unless wheres.blank? ),
8
+ ("LIMIT #{taken}" unless taken.blank? )
9
+ end
10
+ end
11
+
12
+ class Insert < Compound
13
+ def to_sql(formatter = nil)
14
+ build_query \
15
+ "INSERT",
16
+ "INTO #{table_sql}",
17
+ "(#{record.keys.collect { |key| engine.quote_column_name(key.name) }.join(', ')})",
18
+ "VALUES (#{record.collect { |key, value| key.format(value) }.join(', ')})"
19
+ end
20
+ end
21
+
22
+ class Update < Compound
23
+ def to_sql(formatter = nil)
24
+ build_query \
25
+ "UPDATE #{table_sql} SET",
26
+ assignment_sql,
27
+ ("WHERE #{wheres.collect(&:to_sql).join('\n\tAND ')}" unless wheres.blank? ),
28
+ ("LIMIT #{taken}" unless taken.blank? )
29
+ end
30
+
31
+ protected
32
+
33
+ def assignment_sql
34
+ assignments.collect do |attribute, value|
35
+ "#{engine.quote_column_name(attribute.name)} = #{attribute.format(value)}"
36
+ end.join(",\n")
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ module Arel
2
+ class Session
3
+ class << self
4
+ attr_accessor :instance
5
+ alias_method :manufacture, :new
6
+
7
+ def start
8
+ if @started
9
+ yield
10
+ else
11
+ begin
12
+ @started = true
13
+ @instance = manufacture
14
+ metaclass.send :alias_method, :new, :instance
15
+ yield
16
+ ensure
17
+ metaclass.send :alias_method, :new, :manufacture
18
+ @started = false
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ module CRUD
25
+ def create(insert)
26
+ insert.call
27
+ insert
28
+ end
29
+
30
+ def read(select)
31
+ (@read ||= Hash.new do |hash, select|
32
+ hash[select] = select.call
33
+ end)[select]
34
+ end
35
+
36
+ def update(update)
37
+ update.call
38
+ update
39
+ end
40
+
41
+ def delete(delete)
42
+ delete.call
43
+ delete
44
+ end
45
+ end
46
+ include CRUD
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'spec_helper')
2
+
3
+ module Arel
4
+ describe Binary do
5
+ before do
6
+ @relation = Table.new(:users)
7
+ @attribute1 = @relation[:id]
8
+ @attribute2 = @relation[:name]
9
+ class ConcreteBinary < Binary
10
+ end
11
+ end
12
+
13
+ describe '#bind' do
14
+ before do
15
+ @another_relation = @relation.alias
16
+ end
17
+
18
+ describe 'when both operands are attributes' do
19
+ it "manufactures an expression with the attributes bound to the relation" do
20
+ ConcreteBinary.new(@attribute1, @attribute2).bind(@another_relation). \
21
+ should == ConcreteBinary.new(@another_relation[@attribute1], @another_relation[@attribute2])
22
+ end
23
+ end
24
+
25
+ describe 'when an operand is a value' do
26
+ it "manufactures an expression with unmodified values" do
27
+ ConcreteBinary.new(@attribute1, "asdf").bind(@another_relation). \
28
+ should == ConcreteBinary.new(@attribute1.find_correlate_in(@another_relation), "asdf".find_correlate_in(@another_relation))
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end