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,36 @@
1
+ module Arel
2
+ class Deletion < Compound
3
+ attributes :relation
4
+ deriving :initialize, :==
5
+
6
+ def call
7
+ engine.delete(self)
8
+ end
9
+ end
10
+
11
+ class Insert < Compound
12
+ attributes :relation, :record
13
+ deriving :==
14
+
15
+ def initialize(relation, record)
16
+ @relation, @record = relation, record.bind(relation)
17
+ end
18
+
19
+ def call
20
+ engine.create(self)
21
+ end
22
+ end
23
+
24
+ class Update < Compound
25
+ attributes :relation, :assignments
26
+ deriving :==
27
+
28
+ def initialize(relation, assignments)
29
+ @relation, @assignments = relation, assignments.bind(relation)
30
+ end
31
+
32
+ def call
33
+ engine.update(self)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,2 @@
1
+ require 'arel/engines/sql'
2
+ require 'arel/engines/memory'
@@ -0,0 +1,4 @@
1
+ require 'arel/engines/memory/relations'
2
+ require 'arel/engines/memory/primitives'
3
+ require 'arel/engines/memory/engine'
4
+ require 'arel/engines/memory/predicates'
@@ -0,0 +1,16 @@
1
+ module Arel
2
+ module Memory
3
+ class Engine
4
+ module CRUD
5
+ def read(relation)
6
+ relation.eval
7
+ end
8
+
9
+ def create(relation)
10
+ relation.eval
11
+ end
12
+ end
13
+ include CRUD
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ module Arel
2
+ class Binary < Predicate
3
+ def eval(row)
4
+ operand1.eval(row).send(operator, operand2.eval(row))
5
+ end
6
+ end
7
+
8
+ class Equality < Binary
9
+ def operator; :== end
10
+ end
11
+
12
+ class GreaterThanOrEqualTo < Binary
13
+ def operator; :>= end
14
+ end
15
+
16
+ class GreaterThan < Binary
17
+ def operator; :> end
18
+ end
19
+
20
+ class LessThanOrEqualTo < Binary
21
+ def operator; :<= end
22
+ end
23
+
24
+ class LessThan < Binary
25
+ def operator; :< end
26
+ end
27
+
28
+ class Match < Binary
29
+ def operator; :=~ end
30
+ end
31
+
32
+ class In < Binary
33
+ def operator; :include? end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ module Arel
2
+ class Attribute
3
+ def eval(row)
4
+ row[self]
5
+ end
6
+ end
7
+
8
+ class Value
9
+ def eval(row)
10
+ value
11
+ end
12
+ end
13
+
14
+ class Ordering
15
+ def eval(row1, row2)
16
+ (attribute.eval(row1) <=> attribute.eval(row2)) * direction
17
+ end
18
+ end
19
+
20
+ class Descending < Ordering
21
+ def direction; -1 end
22
+ end
23
+
24
+ class Ascending < Ordering
25
+ def direction; 1 end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ require 'arel/engines/memory/relations/array'
2
+ require 'arel/engines/memory/relations/operations'
3
+ require 'arel/engines/memory/relations/writes'
4
+ require 'arel/engines/memory/relations/compound'
5
+
@@ -0,0 +1,25 @@
1
+ module Arel
2
+ class Array < Relation
3
+ attributes :array, :attribute_names
4
+ include Recursion::BaseCase
5
+ deriving :==, :initialize
6
+
7
+ def engine
8
+ @engine ||= Memory::Engine.new
9
+ end
10
+
11
+ def attributes
12
+ @attributes ||= @attribute_names.collect do |name|
13
+ name.to_attribute(self)
14
+ end
15
+ end
16
+
17
+ def format(attribute, value)
18
+ value
19
+ end
20
+
21
+ def eval
22
+ @array.collect { |r| Row.new(self, r) }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module Arel
2
+ class Compound < Relation
3
+ delegate :array, :to => :relation
4
+
5
+ def unoperated_rows
6
+ relation.call.collect { |row| row.bind(self) }
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,61 @@
1
+ module Arel
2
+ class Where < Compound
3
+ def eval
4
+ unoperated_rows.select { |row| predicate.eval(row) }
5
+ end
6
+ end
7
+
8
+ class Order < Compound
9
+ def eval
10
+ unoperated_rows.sort do |row1, row2|
11
+ ordering = orderings.detect { |o| o.eval(row1, row2) != 0 } || orderings.last
12
+ ordering.eval(row1, row2)
13
+ end
14
+ end
15
+ end
16
+
17
+ class Project < Compound
18
+ def eval
19
+ unoperated_rows.collect { |r| r.slice(*projections) }
20
+ end
21
+ end
22
+
23
+ class Take < Compound
24
+ def eval
25
+ unoperated_rows[0, taken]
26
+ end
27
+ end
28
+
29
+ class Skip < Compound
30
+ def eval
31
+ unoperated_rows[skipped..-1]
32
+ end
33
+ end
34
+
35
+ class Group < Compound
36
+ def eval
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+
41
+ class Alias < Compound
42
+ def eval
43
+ unoperated_rows
44
+ end
45
+ end
46
+
47
+ class Join < Relation
48
+ def eval
49
+ result = []
50
+ relation1.call.each do |row1|
51
+ relation2.call.each do |row2|
52
+ combined_row = row1.combine(row2, self)
53
+ if predicates.all? { |p| p.eval(combined_row) }
54
+ result << combined_row
55
+ end
56
+ end
57
+ end
58
+ result
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,7 @@
1
+ module Arel
2
+ class Insert < Compound
3
+ def eval
4
+ unoperated_rows + [Row.new(self, record.values.collect(&:value))]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'arel/engines/sql/engine'
2
+ require 'arel/engines/sql/relations'
3
+ require 'arel/engines/sql/primitives'
4
+ require 'arel/engines/sql/predicates'
5
+ require 'arel/engines/sql/formatters'
6
+ require 'arel/engines/sql/extensions'
7
+ require 'arel/engines/sql/christener'
@@ -0,0 +1,13 @@
1
+ module Arel
2
+ module Sql
3
+ class Christener
4
+ def name_for(relation)
5
+ @used_names ||= Hash.new(0)
6
+ (@relation_names ||= Hash.new do |hash, relation|
7
+ @used_names[name = relation.name] += 1
8
+ hash[relation] = name + (@used_names[name] > 1 ? "_#{@used_names[name]}" : '')
9
+ end)[relation.table]
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module Arel
2
+ module Sql
3
+ class Engine
4
+ def initialize(ar = nil)
5
+ @ar = ar
6
+ end
7
+
8
+ def connection
9
+ @ar.connection
10
+ end
11
+
12
+ def method_missing(method, *args, &block)
13
+ @ar.connection.send(method, *args, &block)
14
+ end
15
+
16
+ module CRUD
17
+ def create(relation)
18
+ connection.insert(relation.to_sql)
19
+ end
20
+
21
+ def read(relation)
22
+ rows = connection.select_rows(relation.to_sql)
23
+ Array.new(rows, relation.attributes)
24
+ end
25
+
26
+ def update(relation)
27
+ connection.update(relation.to_sql)
28
+ end
29
+
30
+ def delete(relation)
31
+ connection.delete(relation.to_sql)
32
+ end
33
+ end
34
+ include CRUD
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ require 'arel/engines/sql/extensions/object'
2
+ require 'arel/engines/sql/extensions/array'
3
+ require 'arel/engines/sql/extensions/range'
4
+ require 'arel/engines/sql/extensions/nil_class'
@@ -0,0 +1,16 @@
1
+ module Arel
2
+ module Sql
3
+ module ArrayExtensions
4
+ def to_sql(formatter = nil)
5
+ "(" + collect { |e| e.to_sql(formatter) }.join(', ') + ")"
6
+ end
7
+
8
+ def inclusion_predicate_sql
9
+ "IN"
10
+ end
11
+
12
+ Array.send(:include, self)
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,11 @@
1
+ module Arel
2
+ module Sql
3
+ module NilClassExtensions
4
+ def equality_predicate_sql
5
+ 'IS'
6
+ end
7
+
8
+ NilClass.send(:include, self)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Arel
2
+ module Sql
3
+ module ObjectExtensions
4
+ def to_sql(formatter)
5
+ formatter.scalar self
6
+ end
7
+
8
+ def equality_predicate_sql
9
+ '='
10
+ end
11
+
12
+ Object.send(:include, self)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Arel
2
+ module Sql
3
+ module RangeExtensions
4
+ def to_sql(formatter = nil)
5
+ formatter.range self.begin, self.end
6
+ end
7
+
8
+ def inclusion_predicate_sql
9
+ "BETWEEN"
10
+ end
11
+
12
+ Range.send(:include, self)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,113 @@
1
+ module Arel
2
+ module Sql
3
+ class Formatter
4
+ attr_reader :environment
5
+ delegate :christener, :engine, :to => :environment
6
+ delegate :name_for, :to => :christener
7
+ delegate :quote_table_name, :quote_column_name, :quote, :to => :engine
8
+
9
+ def initialize(environment)
10
+ @environment = environment
11
+ end
12
+ end
13
+
14
+ class SelectClause < Formatter
15
+ def attribute(attribute)
16
+ # FIXME this should check that the column exists
17
+ "#{quote_table_name(name_for(attribute.original_relation))}.#{quote_column_name(attribute.name)}" +
18
+ (attribute.alias ? " AS #{quote(attribute.alias.to_s)}" : "")
19
+ end
20
+
21
+ def expression(expression)
22
+ if expression.function_sql == "DISTINCT"
23
+ "#{expression.function_sql} #{expression.attribute.to_sql(self)}" +
24
+ (expression.alias ? " AS #{quote_column_name(expression.alias)}" : '')
25
+ else
26
+ "#{expression.function_sql}(#{expression.attribute.to_sql(self)})" +
27
+ (expression.alias ? " AS #{quote_column_name(expression.alias)}" : " AS #{expression.function_sql.to_s.downcase}_id")
28
+ end
29
+ end
30
+
31
+ def select(select_sql, table)
32
+ "(#{select_sql}) AS #{quote_table_name(name_for(table))}"
33
+ end
34
+
35
+ def value(value)
36
+ value
37
+ end
38
+ end
39
+
40
+ class PassThrough < Formatter
41
+ def value(value)
42
+ value
43
+ end
44
+ end
45
+
46
+ class WhereClause < PassThrough
47
+ end
48
+
49
+ class OrderClause < PassThrough
50
+ def ordering(ordering)
51
+ "#{quote_table_name(name_for(ordering.attribute.original_relation))}.#{quote_column_name(ordering.attribute.name)} #{ordering.direction_sql}"
52
+ end
53
+ end
54
+
55
+ class GroupClause < PassThrough
56
+ def attribute(attribute)
57
+ "#{quote_table_name(name_for(attribute.original_relation))}.#{quote_column_name(attribute.name)}"
58
+ end
59
+ end
60
+
61
+ class WhereCondition < Formatter
62
+ def attribute(attribute)
63
+ "#{quote_table_name(name_for(attribute.original_relation))}.#{quote_column_name(attribute.name)}"
64
+ end
65
+
66
+ def expression(expression)
67
+ "#{expression.function_sql}(#{expression.attribute.to_sql(self)})"
68
+ end
69
+
70
+ def value(value)
71
+ value.to_sql(self)
72
+ end
73
+
74
+ def scalar(value, column = nil)
75
+ quote(value, column)
76
+ end
77
+
78
+ def select(select_sql, table)
79
+ "(#{select_sql})"
80
+ end
81
+ end
82
+
83
+ class SelectStatement < Formatter
84
+ def select(select_sql, table)
85
+ select_sql
86
+ end
87
+ end
88
+
89
+ class TableReference < Formatter
90
+ def select(select_sql, table)
91
+ "(#{select_sql}) AS #{quote_table_name(name_for(table))}"
92
+ end
93
+
94
+ def table(table)
95
+ quote_table_name(table.name) +
96
+ (table.name != name_for(table) ? " AS " + quote_table_name(name_for(table)) : '')
97
+ end
98
+ end
99
+
100
+ class Attribute < WhereCondition
101
+ def scalar(scalar)
102
+ quote(scalar, environment.column)
103
+ end
104
+
105
+ def range(left, right)
106
+ "#{left} AND #{right}"
107
+ end
108
+ end
109
+
110
+ class Value < WhereCondition
111
+ end
112
+ end
113
+ end