arel-compat 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. data/History.txt +25 -0
  2. data/README.markdown +182 -0
  3. data/lib/arel.rb +13 -0
  4. data/lib/arel/algebra.rb +10 -0
  5. data/lib/arel/algebra/attributes.rb +7 -0
  6. data/lib/arel/algebra/attributes/attribute.rb +270 -0
  7. data/lib/arel/algebra/attributes/boolean.rb +21 -0
  8. data/lib/arel/algebra/attributes/decimal.rb +9 -0
  9. data/lib/arel/algebra/attributes/float.rb +9 -0
  10. data/lib/arel/algebra/attributes/integer.rb +10 -0
  11. data/lib/arel/algebra/attributes/string.rb +10 -0
  12. data/lib/arel/algebra/attributes/time.rb +6 -0
  13. data/lib/arel/algebra/core_extensions.rb +4 -0
  14. data/lib/arel/algebra/core_extensions/class.rb +32 -0
  15. data/lib/arel/algebra/core_extensions/hash.rb +11 -0
  16. data/lib/arel/algebra/core_extensions/object.rb +30 -0
  17. data/lib/arel/algebra/core_extensions/symbol.rb +9 -0
  18. data/lib/arel/algebra/expression.rb +43 -0
  19. data/lib/arel/algebra/header.rb +67 -0
  20. data/lib/arel/algebra/ordering.rb +23 -0
  21. data/lib/arel/algebra/predicates.rb +190 -0
  22. data/lib/arel/algebra/relations.rb +17 -0
  23. data/lib/arel/algebra/relations/operations/alias.rb +7 -0
  24. data/lib/arel/algebra/relations/operations/from.rb +6 -0
  25. data/lib/arel/algebra/relations/operations/group.rb +12 -0
  26. data/lib/arel/algebra/relations/operations/having.rb +17 -0
  27. data/lib/arel/algebra/relations/operations/join.rb +69 -0
  28. data/lib/arel/algebra/relations/operations/lock.rb +12 -0
  29. data/lib/arel/algebra/relations/operations/order.rb +19 -0
  30. data/lib/arel/algebra/relations/operations/project.rb +20 -0
  31. data/lib/arel/algebra/relations/operations/skip.rb +7 -0
  32. data/lib/arel/algebra/relations/operations/take.rb +11 -0
  33. data/lib/arel/algebra/relations/operations/where.rb +17 -0
  34. data/lib/arel/algebra/relations/relation.rb +136 -0
  35. data/lib/arel/algebra/relations/row.rb +26 -0
  36. data/lib/arel/algebra/relations/utilities/compound.rb +54 -0
  37. data/lib/arel/algebra/relations/utilities/externalization.rb +24 -0
  38. data/lib/arel/algebra/relations/utilities/nil.rb +7 -0
  39. data/lib/arel/algebra/relations/writes.rb +36 -0
  40. data/lib/arel/algebra/value.rb +14 -0
  41. data/lib/arel/engines.rb +2 -0
  42. data/lib/arel/engines/memory.rb +4 -0
  43. data/lib/arel/engines/memory/engine.rb +16 -0
  44. data/lib/arel/engines/memory/predicates.rb +99 -0
  45. data/lib/arel/engines/memory/primitives.rb +27 -0
  46. data/lib/arel/engines/memory/relations.rb +5 -0
  47. data/lib/arel/engines/memory/relations/array.rb +35 -0
  48. data/lib/arel/engines/memory/relations/compound.rb +9 -0
  49. data/lib/arel/engines/memory/relations/operations.rb +67 -0
  50. data/lib/arel/engines/memory/relations/writes.rb +7 -0
  51. data/lib/arel/engines/sql.rb +8 -0
  52. data/lib/arel/engines/sql/attributes.rb +40 -0
  53. data/lib/arel/engines/sql/christener.rb +14 -0
  54. data/lib/arel/engines/sql/compilers/ibm_db_compiler.rb +48 -0
  55. data/lib/arel/engines/sql/compilers/mysql_compiler.rb +11 -0
  56. data/lib/arel/engines/sql/compilers/oracle_compiler.rb +95 -0
  57. data/lib/arel/engines/sql/compilers/postgresql_compiler.rb +42 -0
  58. data/lib/arel/engines/sql/compilers/sqlite_compiler.rb +9 -0
  59. data/lib/arel/engines/sql/core_extensions.rb +4 -0
  60. data/lib/arel/engines/sql/core_extensions/array.rb +24 -0
  61. data/lib/arel/engines/sql/core_extensions/nil_class.rb +15 -0
  62. data/lib/arel/engines/sql/core_extensions/object.rb +19 -0
  63. data/lib/arel/engines/sql/core_extensions/range.rb +19 -0
  64. data/lib/arel/engines/sql/engine.rb +55 -0
  65. data/lib/arel/engines/sql/formatters.rb +122 -0
  66. data/lib/arel/engines/sql/predicates.rb +103 -0
  67. data/lib/arel/engines/sql/primitives.rb +97 -0
  68. data/lib/arel/engines/sql/relations.rb +10 -0
  69. data/lib/arel/engines/sql/relations/compiler.rb +118 -0
  70. data/lib/arel/engines/sql/relations/operations/alias.rb +5 -0
  71. data/lib/arel/engines/sql/relations/operations/join.rb +33 -0
  72. data/lib/arel/engines/sql/relations/relation.rb +65 -0
  73. data/lib/arel/engines/sql/relations/table.rb +88 -0
  74. data/lib/arel/engines/sql/relations/utilities/compound.rb +10 -0
  75. data/lib/arel/engines/sql/relations/utilities/externalization.rb +14 -0
  76. data/lib/arel/engines/sql/relations/utilities/nil.rb +6 -0
  77. data/lib/arel/engines/sql/relations/utilities/recursion.rb +13 -0
  78. data/lib/arel/engines/sql/relations/writes.rb +19 -0
  79. data/lib/arel/session.rb +51 -0
  80. data/lib/arel/version.rb +3 -0
  81. data/spec/algebra/unit/predicates/binary_spec.rb +35 -0
  82. data/spec/algebra/unit/predicates/equality_spec.rb +29 -0
  83. data/spec/algebra/unit/predicates/in_spec.rb +12 -0
  84. data/spec/algebra/unit/primitives/attribute_spec.rb +181 -0
  85. data/spec/algebra/unit/primitives/expression_spec.rb +45 -0
  86. data/spec/algebra/unit/primitives/value_spec.rb +15 -0
  87. data/spec/algebra/unit/relations/alias_spec.rb +16 -0
  88. data/spec/algebra/unit/relations/delete_spec.rb +9 -0
  89. data/spec/algebra/unit/relations/group_spec.rb +10 -0
  90. data/spec/algebra/unit/relations/insert_spec.rb +9 -0
  91. data/spec/algebra/unit/relations/join_spec.rb +25 -0
  92. data/spec/algebra/unit/relations/order_spec.rb +21 -0
  93. data/spec/algebra/unit/relations/project_spec.rb +34 -0
  94. data/spec/algebra/unit/relations/relation_spec.rb +187 -0
  95. data/spec/algebra/unit/relations/skip_spec.rb +10 -0
  96. data/spec/algebra/unit/relations/table_spec.rb +38 -0
  97. data/spec/algebra/unit/relations/take_spec.rb +10 -0
  98. data/spec/algebra/unit/relations/update_spec.rb +9 -0
  99. data/spec/algebra/unit/relations/where_spec.rb +19 -0
  100. data/spec/algebra/unit/session/session_spec.rb +84 -0
  101. data/spec/attributes/boolean_spec.rb +57 -0
  102. data/spec/attributes/float_spec.rb +119 -0
  103. data/spec/attributes/header_spec.rb +42 -0
  104. data/spec/attributes/integer_spec.rb +119 -0
  105. data/spec/attributes/string_spec.rb +43 -0
  106. data/spec/attributes/time_spec.rb +24 -0
  107. data/spec/engines/memory/integration/joins/cross_engine_spec.rb +51 -0
  108. data/spec/engines/memory/unit/relations/array_spec.rb +32 -0
  109. data/spec/engines/memory/unit/relations/insert_spec.rb +28 -0
  110. data/spec/engines/memory/unit/relations/join_spec.rb +31 -0
  111. data/spec/engines/memory/unit/relations/order_spec.rb +27 -0
  112. data/spec/engines/memory/unit/relations/project_spec.rb +27 -0
  113. data/spec/engines/memory/unit/relations/skip_spec.rb +26 -0
  114. data/spec/engines/memory/unit/relations/take_spec.rb +26 -0
  115. data/spec/engines/memory/unit/relations/where_spec.rb +39 -0
  116. data/spec/engines/sql/integration/joins/with_adjacency_spec.rb +258 -0
  117. data/spec/engines/sql/integration/joins/with_aggregations_spec.rb +221 -0
  118. data/spec/engines/sql/integration/joins/with_compounds_spec.rb +137 -0
  119. data/spec/engines/sql/unit/engine_spec.rb +45 -0
  120. data/spec/engines/sql/unit/predicates/binary_spec.rb +140 -0
  121. data/spec/engines/sql/unit/predicates/equality_spec.rb +75 -0
  122. data/spec/engines/sql/unit/predicates/in_spec.rb +179 -0
  123. data/spec/engines/sql/unit/predicates/noteq_spec.rb +75 -0
  124. data/spec/engines/sql/unit/predicates/predicates_spec.rb +79 -0
  125. data/spec/engines/sql/unit/primitives/attribute_spec.rb +36 -0
  126. data/spec/engines/sql/unit/primitives/expression_spec.rb +28 -0
  127. data/spec/engines/sql/unit/primitives/literal_spec.rb +43 -0
  128. data/spec/engines/sql/unit/primitives/value_spec.rb +29 -0
  129. data/spec/engines/sql/unit/relations/alias_spec.rb +53 -0
  130. data/spec/engines/sql/unit/relations/delete_spec.rb +83 -0
  131. data/spec/engines/sql/unit/relations/from_spec.rb +64 -0
  132. data/spec/engines/sql/unit/relations/group_spec.rb +72 -0
  133. data/spec/engines/sql/unit/relations/having_spec.rb +78 -0
  134. data/spec/engines/sql/unit/relations/insert_spec.rb +143 -0
  135. data/spec/engines/sql/unit/relations/join_spec.rb +180 -0
  136. data/spec/engines/sql/unit/relations/lock_spec.rb +86 -0
  137. data/spec/engines/sql/unit/relations/order_spec.rb +161 -0
  138. data/spec/engines/sql/unit/relations/project_spec.rb +143 -0
  139. data/spec/engines/sql/unit/relations/skip_spec.rb +41 -0
  140. data/spec/engines/sql/unit/relations/table_spec.rb +129 -0
  141. data/spec/engines/sql/unit/relations/take_spec.rb +49 -0
  142. data/spec/engines/sql/unit/relations/update_spec.rb +203 -0
  143. data/spec/engines/sql/unit/relations/where_spec.rb +72 -0
  144. data/spec/relations/join_spec.rb +42 -0
  145. data/spec/relations/relation_spec.rb +31 -0
  146. data/spec/shared/relation_spec.rb +255 -0
  147. data/spec/spec_helper.rb +36 -0
  148. data/spec/support/check.rb +6 -0
  149. data/spec/support/connections/mysql_connection.rb +14 -0
  150. data/spec/support/connections/oracle_connection.rb +17 -0
  151. data/spec/support/connections/postgresql_connection.rb +13 -0
  152. data/spec/support/connections/sqlite3_connection.rb +24 -0
  153. data/spec/support/guards.rb +28 -0
  154. data/spec/support/matchers.rb +4 -0
  155. data/spec/support/matchers/be_like.rb +24 -0
  156. data/spec/support/matchers/disambiguate_attributes.rb +28 -0
  157. data/spec/support/matchers/hash_the_same_as.rb +26 -0
  158. data/spec/support/matchers/have_rows.rb +18 -0
  159. data/spec/support/model.rb +62 -0
  160. data/spec/support/schemas/mysql_schema.rb +26 -0
  161. data/spec/support/schemas/oracle_schema.rb +20 -0
  162. data/spec/support/schemas/postgresql_schema.rb +26 -0
  163. data/spec/support/schemas/sqlite3_schema.rb +26 -0
  164. metadata +258 -0
@@ -0,0 +1,25 @@
1
+ == 0.3.0 / 2010-03-10
2
+
3
+ * Enhancements
4
+
5
+ * Introduced "SQL compilers" for query generation.
6
+ * Added support for Oracle (Raimonds Simanovskis) and IBM/DB (Praveen Devarao).
7
+ * Improvements to give better support to ActiveRecord.
8
+
9
+ == 0.2.1 / 2010-02-05
10
+
11
+ * Enhancements
12
+
13
+ * Bump dependency version of activesupport to 3.0.0.beta
14
+
15
+ == 0.2.0 / 2010-01-31
16
+
17
+ * Ruby 1.9 compatibility
18
+ * Many improvements to support the Arel integration into ActiveRecord (see `git log v0.1.0..v0.2.0`)
19
+ * Thanks to Emilio Tagua and Pratik Naik for many significant contributions!
20
+
21
+ == 0.1.0 / 2009-08-06
22
+
23
+ * 1 major enhancement
24
+
25
+ * Birthday!
@@ -0,0 +1,182 @@
1
+ ## Abstract ##
2
+
3
+ Arel is a Relational Algebra for Ruby. It 1) simplifies the generation complex of SQL queries and it 2) adapts to various RDBMS systems. It is intended to be a framework framework; that is, you can build your own ORM with it, focusing on innovative object and collection modeling as opposed to database compatibility and query generation.
4
+
5
+ ## Status ##
6
+
7
+ For the moment, Arel uses ActiveRecord's connection adapters to connect to the various engines, connection pooling, perform quoting, and do type conversion. On the horizon is the use of DataObjects instead.
8
+
9
+ The long term goal, following both LINQ and DataMapper, is to have Arel adapt to engines beyond RDBMS, including XML, IMAP, YAML, etc.
10
+
11
+ ## A Gentle Introduction ##
12
+
13
+ Generating a query with ARel is simple. For example, in order to produce
14
+
15
+ SELECT * FROM users
16
+
17
+ you construct a table relation and convert it to sql:
18
+
19
+ users = Table(:users)
20
+ users.to_sql
21
+
22
+ In fact, you will probably never call `#to_sql`. Rather, you'll work with data from the table directly. You can iterate through all rows in the `users` table like this:
23
+
24
+ users.each { |user| ... }
25
+
26
+ In other words, Arel relations implement Ruby's Enumerable interface. Let's have a look at a concrete example:
27
+
28
+ users.first # => { users[:id] => 1, users[:name] => 'bob' }
29
+
30
+ As you can see, Arel converts the rows from the database into a hash, the values of which are sublimated to the appropriate Ruby primitive (integers, strings, and so forth).
31
+
32
+ ### More Sophisticated <strike>Queries</strike> Relations ###
33
+
34
+ Here is a whirlwind tour through the most common relational operators. These will probably cover 80% of all interaction with the database.
35
+
36
+ First is the 'restriction' operator, `where`:
37
+
38
+ users.where(users[:name].eq('amy'))
39
+ # => SELECT * FROM users WHERE users.name = 'amy'
40
+
41
+ What would, in SQL, be part of the `SELECT` clause is called in Arel a `projection`:
42
+
43
+ users.project(users[:id]) # => SELECT users.id FROM users
44
+
45
+ Joins resemble SQL strongly:
46
+
47
+ users.join(photos).on(users[:id].eq(photos[:user_id]))
48
+ # => SELECT * FROM users INNER JOIN photos ON users.id = photos.user_id
49
+
50
+ What are called `LIMIT` and `OFFSET` in SQL are called `take` and `skip` in Arel:
51
+
52
+ users.take(5) # => SELECT * FROM users LIMIT 5
53
+ users.skip(4) # => SELECT * FROM users OFFSET 4
54
+
55
+ `GROUP BY` is called `group`:
56
+
57
+ users.group(users[:name]) # => SELECT * FROM users GROUP BY name
58
+
59
+ The best property of the Relational Algebra is its "composability", or closure under all operations. For example, to select AND project, just "chain" the method invocations:
60
+
61
+ users \
62
+ .where(users[:name].eq('amy')) \
63
+ .project(users[:id]) \
64
+ # => SELECT users.id FROM users WHERE users.name = 'amy'
65
+
66
+ All operators are chainable in this way, and they are chainable any number of times, in any order.
67
+
68
+ users.where(users[:name].eq('bob')).where(users[:age].lt(25))
69
+
70
+ Of course, many of the operators take multiple arguments, so the last example can be written more tersely:
71
+
72
+ users.where(users[:name].eq('bob'), users[:age].lt(25))
73
+
74
+ The `OR` operator is not yet supported. It will work like this:
75
+
76
+ users.where(users[:name].eq('bob').or(users[:age].lt(25)))
77
+
78
+ The `AND` operator will behave similarly.
79
+
80
+ Finally, most operations take a block form. For example:
81
+
82
+ Table(:users) \
83
+ .where { |u| u[:id].eq(1) } \
84
+ .project { |u| u[:id] }
85
+
86
+ This provides a (sometimes) convenient alternative syntax.
87
+
88
+ ### The Crazy Features ###
89
+
90
+ The examples above are fairly simple and other libraries match or come close to matching the expressiveness of Arel (e.g., `Sequel` in Ruby).
91
+
92
+ #### Complex Joins ####
93
+
94
+ Where Arel really shines in its ability to handle complex joins and aggregations. As a first example, let's consider an "adjacency list", a tree represented in a table. Suppose we have a table `comments`, representing a threaded discussion:
95
+
96
+ comments = Table(:comments)
97
+
98
+ And this table has the following attributes:
99
+
100
+ comments.attributes # => [comments[:id], comments[:body], comments[:parent_id]]
101
+
102
+ The `parent_id` column is a foreign key from the `comments` table to itself. Now, joining a table to itself requires aliasing in SQL. In fact, you may alias in Arel as well:
103
+
104
+ replies = comments.alias
105
+ comments_with_replies = \
106
+ comments.join(replies).on(replies[:parent_id].eq(comments[:id]))
107
+ # => SELECT * FROM comments INNER JOIN comments AS comments_2 WHERE comments_2.parent_id = comments.id
108
+
109
+ The call to `#alias` is actually optional: Arel will always produce a unique name for every table joined in the relation, and it will always do so deterministically to exploit query caching. Explicit aliasing is more common, however. When you want to extract specific slices of data, aliased tables are a necessity. For example to get just certain columns from the row, treat a row like a hash:
110
+
111
+ comments_with_replies.first[replies[:body]]
112
+
113
+ This will return the first comment's reply's body.
114
+
115
+ If you don't need to extract the data later (for example, you're simply doing a join to find comments that have replies, you don't care what the content of the replies are), the block form may be preferable:
116
+
117
+ comments.join(comments) { |comments, replies| replies[:parent_id].eq(comments[:id]) }
118
+ # => SELECT * FROM comments INNER JOIN comments AS comments_2 WHERE comments_2.parent_id = comments.id
119
+
120
+ Note that you do NOT want to do something like:
121
+
122
+ comments.join(comments, comments[:parent_id].eq(comments[:id]))
123
+ # => SELECT * FROM comments INNER JOIN comments AS comments_2 WHERE comments.parent_id = comments.id
124
+
125
+ This does NOT have the same meaning as the previous query, since the comments[:parent_id] reference is effectively ambiguous.
126
+
127
+ #### Complex Aggregations ####
128
+
129
+ My personal favorite feature of Arel, certainly the most difficult to implement, and possibly only of marginal value, is **closure under joining even in the presence of aggregations**. This is a feature where the Relational Algebra is fundamentally easier to use than SQL. Think of this as a preview of the kind of radical functionality that is to come, stuff no other "ORM" is doing.
130
+
131
+ The easiest way to introduce this is in SQL. Your task is to get all users and the **count** of their associated photos. Let's start from the inside out:
132
+
133
+ SELECT count(*)
134
+ FROM photos
135
+ GROUP BY user_id
136
+
137
+ Now, we'd like to join this with the user table. Naively, you might try to do this:
138
+
139
+ SELECT users.*, count(photos.id)
140
+ FROM users
141
+ LEFT OUTER JOIN photos
142
+ ON users.id = photos.user_id
143
+ GROUP BY photos.user_id
144
+
145
+ Of course, this has a slightly different meaning than our intended query. This is actually a fairly advanced topic in SQL so let's see why this doesn't work *step by step*. Suppose we have these records in our `users` table:
146
+
147
+ mysql> select * from users;
148
+ +------+--------+
149
+ | id | name |
150
+ +------+--------+
151
+ | 1 | hai |
152
+ | 2 | bai |
153
+ | 3 | dumpty |
154
+ +------+--------+
155
+
156
+ And these in the photos table:
157
+
158
+ mysql> select * from photos;
159
+ +------+---------+-----------+
160
+ | id | user_id | camera_id |
161
+ +------+---------+-----------+
162
+ | 1 | 1 | 1 |
163
+ | 2 | 1 | 1 |
164
+ | 3 | 1 | 1 |
165
+ +------+---------+-----------+
166
+
167
+ If we perform the above, incorrect query, we get the following:
168
+
169
+ mysql> select users.*, count(photos.id) from users left outer join photos on users.id = photos.user_id limit 3 group by user_id;
170
+ +------+------+------------------+
171
+ | id | name | count(photos.id) |
172
+ +------+------+------------------+
173
+ | 2 | bai | 0 |
174
+ | 1 | hai | 3 |
175
+ +------+------+------------------+
176
+
177
+ As you can see, we're completely missing data for user with id 3. `dumpty` has no photos, neither does `bai`. But strangely `bai` appeared and `dumpty` didn't! The reason is that the `GROUP BY` clause is aggregating on both tables, not just the `photos` table. All users without photos have a `photos.id` of `null` (thanks to the left outer join). These are rolled up together and an arbitrary user wins. In this case, `bai` not `dumpty`.
178
+
179
+ SELECT users.*, photos_aggregation.cnt
180
+ FROM users
181
+ LEFT OUTER JOIN (SELECT user_id, count(*) as cnt FROM photos GROUP BY user_id) AS photos_aggregation
182
+ ON photos_aggregation.user_id = users.id
@@ -0,0 +1,13 @@
1
+ require 'active_support'
2
+ # require 'active_support/inflector'
3
+ # require 'active_support/core_ext/class/attribute_accessors'
4
+ # require 'active_support/core_ext/module/delegation'
5
+ # require 'active_support/core_ext/object/blank'
6
+
7
+ module Arel
8
+ require 'arel/algebra'
9
+ require 'arel/engines'
10
+ require 'arel/version'
11
+
12
+ autoload :Session, 'arel/session'
13
+ end
@@ -0,0 +1,10 @@
1
+ require 'arel/algebra/core_extensions'
2
+
3
+ require 'arel/algebra/attributes'
4
+ require 'arel/algebra/header'
5
+ require 'arel/algebra/expression'
6
+ require 'arel/algebra/ordering'
7
+ require 'arel/algebra/predicates'
8
+ require 'arel/algebra/relations'
9
+ require 'arel/algebra/value'
10
+
@@ -0,0 +1,7 @@
1
+ require "arel/algebra/attributes/attribute"
2
+ require "arel/algebra/attributes/boolean"
3
+ require "arel/algebra/attributes/decimal"
4
+ require "arel/algebra/attributes/float"
5
+ require "arel/algebra/attributes/integer"
6
+ require "arel/algebra/attributes/string"
7
+ require "arel/algebra/attributes/time"
@@ -0,0 +1,270 @@
1
+ require 'set'
2
+
3
+ module Arel
4
+ class TypecastError < StandardError ; end
5
+ class Attribute
6
+ attributes :relation, :name, :alias, :ancestor
7
+ deriving :==
8
+ delegate :engine, :christener, :to => :relation
9
+
10
+ def initialize(relation, name, options = {})
11
+ @relation, @name, @alias, @ancestor = relation, name, options[:alias], options[:ancestor]
12
+ end
13
+
14
+ def named?(hypothetical_name)
15
+ (@alias || name).to_s == hypothetical_name.to_s
16
+ end
17
+
18
+ def aggregation?
19
+ false
20
+ end
21
+
22
+ def inspect
23
+ "<Attribute #{name}>"
24
+ end
25
+
26
+ module Transformations
27
+ def self.included(klass)
28
+ klass.send :alias_method, :eql?, :==
29
+ end
30
+
31
+ def hash
32
+ @hash ||= name.hash + root.relation.hash
33
+ end
34
+
35
+ def as(aliaz = nil)
36
+ Attribute.new(relation, name, :alias => aliaz, :ancestor => self)
37
+ end
38
+
39
+ def bind(new_relation)
40
+ relation == new_relation ? self : Attribute.new(new_relation, name, :alias => @alias, :ancestor => self)
41
+ end
42
+
43
+ def to_attribute(relation)
44
+ bind(relation)
45
+ end
46
+ end
47
+ include Transformations
48
+
49
+ module Congruence
50
+ def history
51
+ @history ||= [self] + (ancestor ? ancestor.history : [])
52
+ end
53
+
54
+ def join?
55
+ relation.join?
56
+ end
57
+
58
+ def root
59
+ history.last
60
+ end
61
+
62
+ def original_relation
63
+ @original_relation ||= original_attribute.relation
64
+ end
65
+
66
+ def original_attribute
67
+ @original_attribute ||= history.detect { |a| !a.join? }
68
+ end
69
+
70
+ def find_correlate_in(relation)
71
+ relation[self] || self
72
+ end
73
+
74
+ def descends_from?(other)
75
+ history.include?(other)
76
+ end
77
+
78
+ def /(other)
79
+ other ? (history & other.history).size : 0
80
+ end
81
+ end
82
+ include Congruence
83
+
84
+ module Predications
85
+ def eq(other)
86
+ Predicates::Equality.new(self, other)
87
+ end
88
+
89
+ def eq_any(*others)
90
+ Predicates::Any.build(Predicates::Equality, self, *others)
91
+ end
92
+
93
+ def eq_all(*others)
94
+ Predicates::All.build(Predicates::Equality, self, *others)
95
+ end
96
+
97
+ def not_eq(other)
98
+ Predicates::Inequality.new(self, other)
99
+ end
100
+
101
+ def not_eq_any(*others)
102
+ Predicates::Any.build(Predicates::Inequality, self, *others)
103
+ end
104
+
105
+ def not_eq_all(*others)
106
+ Predicates::All.build(Predicates::Inequality, self, *others)
107
+ end
108
+
109
+ def lt(other)
110
+ Predicates::LessThan.new(self, other)
111
+ end
112
+
113
+ def lt_any(*others)
114
+ Predicates::Any.build(Predicates::LessThan, self, *others)
115
+ end
116
+
117
+ def lt_all(*others)
118
+ Predicates::All.build(Predicates::LessThan, self, *others)
119
+ end
120
+
121
+ def lteq(other)
122
+ Predicates::LessThanOrEqualTo.new(self, other)
123
+ end
124
+
125
+ def lteq_any(*others)
126
+ Predicates::Any.build(Predicates::LessThanOrEqualTo, self, *others)
127
+ end
128
+
129
+ def lteq_all(*others)
130
+ Predicates::All.build(Predicates::LessThanOrEqualTo, self, *others)
131
+ end
132
+
133
+ def gt(other)
134
+ Predicates::GreaterThan.new(self, other)
135
+ end
136
+
137
+ def gt_any(*others)
138
+ Predicates::Any.build(Predicates::GreaterThan, self, *others)
139
+ end
140
+
141
+ def gt_all(*others)
142
+ Predicates::All.build(Predicates::GreaterThan, self, *others)
143
+ end
144
+
145
+ def gteq(other)
146
+ Predicates::GreaterThanOrEqualTo.new(self, other)
147
+ end
148
+
149
+ def gteq_any(*others)
150
+ Predicates::Any.build(Predicates::GreaterThanOrEqualTo, self, *others)
151
+ end
152
+
153
+ def gteq_all(*others)
154
+ Predicates::All.build(Predicates::GreaterThanOrEqualTo, self, *others)
155
+ end
156
+
157
+ def matches(other)
158
+ Predicates::Match.new(self, other)
159
+ end
160
+
161
+ def matches_any(*others)
162
+ Predicates::Any.build(Predicates::Match, self, *others)
163
+ end
164
+
165
+ def matches_all(*others)
166
+ Predicates::All.build(Predicates::Match, self, *others)
167
+ end
168
+
169
+ def not_matches(other)
170
+ Predicates::NotMatch.new(self, other)
171
+ end
172
+
173
+ def not_matches_any(*others)
174
+ Predicates::Any.build(Predicates::NotMatch, self, *others)
175
+ end
176
+
177
+ def not_matches_all(*others)
178
+ Predicates::All.build(Predicates::NotMatch, self, *others)
179
+ end
180
+
181
+ def in(other)
182
+ Predicates::In.new(self, other)
183
+ end
184
+
185
+ def in_any(*others)
186
+ Predicates::Any.build(Predicates::In, self, *others)
187
+ end
188
+
189
+ def in_all(*others)
190
+ Predicates::All.build(Predicates::In, self, *others)
191
+ end
192
+
193
+ def not_in(other)
194
+ Predicates::NotIn.new(self, other)
195
+ end
196
+
197
+ def not_in_any(*others)
198
+ Predicates::Any.build(Predicates::NotIn, self, *others)
199
+ end
200
+
201
+ def not_in_all(*others)
202
+ Predicates::All.build(Predicates::NotIn, self, *others)
203
+ end
204
+ end
205
+ include Predications
206
+
207
+ module Expressions
208
+ def count(distinct = false)
209
+ distinct ? Distinct.new(self).count : Count.new(self)
210
+ end
211
+
212
+ def sum
213
+ Sum.new(self)
214
+ end
215
+
216
+ def maximum
217
+ Maximum.new(self)
218
+ end
219
+
220
+ def minimum
221
+ Minimum.new(self)
222
+ end
223
+
224
+ def average
225
+ Average.new(self)
226
+ end
227
+ end
228
+ include Expressions
229
+
230
+ module Orderings
231
+ def asc
232
+ Ascending.new(self)
233
+ end
234
+
235
+ def desc
236
+ Descending.new(self)
237
+ end
238
+
239
+ alias_method :to_ordering, :asc
240
+ end
241
+ include Orderings
242
+
243
+ module Types
244
+ def type_cast(value)
245
+ if root == self
246
+ raise NotImplementedError, "#type_cast should be implemented in a subclass."
247
+ else
248
+ root.type_cast(value)
249
+ end
250
+ end
251
+
252
+ def type_cast_to_numeric(value, method)
253
+ return unless value
254
+ if value.respond_to?(:to_str)
255
+ str = value.to_str.strip
256
+ return if str.empty?
257
+ return $1.send(method) if str =~ /\A(-?(?:0|[1-9]\d*)(?:\.\d+)?|(?:\.\d+))\z/
258
+ elsif value.respond_to?(method)
259
+ return value.send(method)
260
+ end
261
+ raise typecast_error(value)
262
+ end
263
+
264
+ def typecast_error(value)
265
+ raise TypecastError, "could not typecast #{value.inspect} to #{self.class.name.split('::').last}"
266
+ end
267
+ end
268
+ include Types
269
+ end
270
+ end