rethinkdb 1.2.6.1 → 1.4.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sequence.rb DELETED
@@ -1,350 +0,0 @@
1
- # Copyright 2010-2012 RethinkDB, all rights reserved.
2
- module RethinkDB
3
- # A "Sequence" is either a JSON array or a stream. The functions in
4
- # this module may be invoked as instance methods of both JSON_Expression and
5
- # Stream_Expression, but you will get a runtime error if you invoke
6
- # them on a JSON_Expression that turns out not to be an array.
7
- module Sequence
8
- # For each element of the sequence, execute 1 or more write queries (to
9
- # execute more than 1, yield a list of write queries in the block). For
10
- # example:
11
- # table.for_each{|row| [table2.get(row[:id]).delete, table3.insert(row)]}
12
- # will, for each row in <b>+table+</b>, delete the row that shares its id
13
- # in <b>+table2+</b> and insert the row into <b>+table3+</b>.
14
- def for_each
15
- S.with_var { |vname,v|
16
- queries = yield(v)
17
- queries = [queries] if queries.class != Array
18
- queries.each{|q|
19
- if q.class != Write_Query
20
- raise TypeError, "Foreach requires query #{q.inspect} to be a write query."
21
- end}
22
- Write_Query.new [:foreach, self, vname, queries]
23
- }
24
- end
25
-
26
- # Filter the sequence based on a predicate. The provided block should take a
27
- # single variable, an element of the sequence, and return either <b>+true+</b> if
28
- # it should be in the resulting sequence or <b>+false+</b> otherwise. For example:
29
- # table.filter {|row| row[:id] < 5}
30
- # Alternatively, you may provide an object as an argument, in which case the
31
- # <b>+filter+</b> will match JSON objects which match the provided object's
32
- # attributes. For example, if we have a table <b>+people+</b>, the
33
- # following are equivalent:
34
- # people.filter{|row| row[:name].eq('Bob') & row[:age].eq(50)}
35
- # people.filter({:name => 'Bob', :age => 50})
36
- # Note that the values of attributes may themselves be queries. For
37
- # instance, here is a query that matches anyone whose age is double their height:
38
- # people.filter({:age => r.mul(2, 3)})
39
- def filter(obj=nil)
40
- if obj
41
- if obj.class == Hash then self.filter { |row|
42
- JSON_Expression.new [:call, [:all], obj.map{|kv|
43
- row.getattr(kv[0]).eq(S.r(kv[1]))}]}
44
- else raise ArgumentError,"Filter: Not a hash: #{obj.inspect}."
45
- end
46
- else
47
- S.with_var{|vname,v|
48
- self.class.new [:call, [:filter, vname, S.r(yield(v))], [self]]}
49
- end
50
- end
51
-
52
- # Map a function over the sequence, then concatenate the results together. The
53
- # provided block should take a single variable, an element in the sequence, and
54
- # return a list of elements to include in the resulting sequence. If you have a
55
- # table <b>+table+</b>, the following are all equivalent:
56
- # table.concat_map {|row| [row[:id], row[:id]*2]}
57
- # table.map{|row| [row[:id], row[:id]*2]}.reduce([]){|a,b| r.union(a,b)}
58
- def concat_map
59
- S.with_var { |vname,v|
60
- self.class.new [:call, [:concatmap, vname, S.r(yield(v))], [self]]}
61
- end
62
-
63
- # Gets all rows with keys between <b>+start_key+</b> and
64
- # <b>+end_key+</b> (inclusive). You may also optionally specify the name of
65
- # the attribute to use as your key (<b>+keyname+</b>), but note that your
66
- # table must be indexed by that attribute. Either <b>+start_key+</b> or
67
- # <b>+end_key+</b> may be nil, in which case that side of the range is
68
- # unbounded. For example, if we have a table <b>+table+</b>, these are
69
- # equivalent:
70
- # r.between(table, 3, 7)
71
- # table.filter{|row| (row[:id] >= 3) & (row[:id] <= 7)}
72
- # as are these:
73
- # table.between(nil,7,:index)
74
- # table.filter{|row| row[:index] <= 7}
75
- def between(start_key, end_key, keyname=:id)
76
- start_key = S.r(start_key || S.skip)
77
- end_key = S.r(end_key || S.skip)
78
- self.class.new [:call, [:between, keyname, start_key, end_key], [self]]
79
- end
80
-
81
- # Map a function over a sequence. The provided block should take
82
- # a single variable, an element of the sequence, and return an
83
- # element of the resulting sequence. For example:
84
- # table.map {|row| row[:id]}
85
- def map
86
- S.with_var{|vname,v|
87
- self.class.new [:call, [:map, vname, S.r(yield(v))], [self]]}
88
- end
89
-
90
- # For each element of a sequence, picks out the specified
91
- # attributes from the object and returns only those. If the input
92
- # is not an array, fails when the query is run. The folling are
93
- # equivalent:
94
- # r([{:a => 1, :b => 1, :c => 1},
95
- # {:a => 2, :b => 2, :c => 2}]).pluck('a', 'b')
96
- # r([{:a => 1, :b => 1}, {:a => 2, :b => 2}])
97
- def pluck(*args)
98
- self.map {|x| x.pick(*args)}
99
- end
100
-
101
- # For each element of a sequence, picks out the specified
102
- # attributes from the object and returns the residual object. If
103
- # the input is not an array, fails when the query is run. The
104
- # following are equivalent:
105
- # r([{:a => 1, :b => 1, :c => 1},
106
- # {:a => 2, :b => 2, :c => 2}]).without('a', 'b')
107
- # r([{:c => 1}, {:c => 2}])
108
- def without(*args)
109
- self.map {|x| x.unpick(*args)}
110
- end
111
-
112
- # Order a sequence of objects by one or more attributes. For
113
- # example, to sort first by name and then by social security
114
- # number for the table <b>+people+</b>, you could do:
115
- # people.order_by(:name, :ssn)
116
- # By default order_by sorts in ascending order. To explicitly specify the
117
- # ordering wrap the attribute to be ordered by with r.asc or r.desc as in:
118
- # people.order_by(r.desc(:name), :ssn)
119
- # which sorts first by name from Z-A and then by ssn from 0-9.
120
- def order_by(*orderings)
121
- orderings.map!{|x| x.class == Array ? x : [x, true]}
122
- self.class.new [:call, [:orderby, *orderings], [self]]
123
- end
124
-
125
- # Reduce a function over the sequence. Note that unlike Ruby's reduce, you
126
- # cannot omit the base case. The block you provide should take two
127
- # arguments, just like Ruby's reduce. For example, if we have a table
128
- # <b>+table+</b>, the following will add up the <b>+count+</b> attribute of
129
- # all the rows:
130
- # table.map{|row| row[:count]}.reduce(0){|a,b| a+b}
131
- # <b>NOTE:</b> unlike Ruby's reduce, this reduce only works on
132
- # sequences with elements of the same type as the base case. For
133
- # example, the following is incorrect:
134
- # table.reduce(0){|a,b| a + b[:count]} # INCORRECT
135
- # because the base case is a number but the sequence contains
136
- # objects. RQL reduce has this limitation so that it can be
137
- # distributed across shards efficiently.
138
- def reduce(base)
139
- S.with_var { |aname,a|
140
- S.with_var { |bname,b|
141
- JSON_Expression.new [:call,
142
- [:reduce, S.r(base), aname, bname, S.r(yield(a,b))],
143
- [self]]}}
144
- end
145
-
146
- # This one is a little complicated. The logic is as follows:
147
- # 1. Use <b>+grouping+</b> sort the elements into groups. <b>+grouping+</b> should be a callable that takes one argument, the current element of the sequence, and returns a JSON expression representing its group.
148
- # 2. Map <b>+mapping+</b> over each of the groups. Mapping should be a callable that behaves the same as the block passed to Sequence#map.
149
- # 3. Reduce the groups with <b>+base+</b> and <b>+reduction+</b>. Base should be the base term of the reduction, and <b>+reduction+</b> should be a callable that behaves the same as the block passed to Sequence#reduce.
150
- #
151
- # For example, the following are equivalent:
152
- # table.grouped_map_reduce(lambda {|row| row[:id] % 4},
153
- # lambda {|row| row[:id]},
154
- # 0,
155
- # lambda {|a,b| a+b})
156
- # r([0,1,2,3]).map {|n|
157
- # table.filter{|row| row[:id].eq(n)}.map{|row| row[:id]}.reduce(0){|a,b| a+b}
158
- # }
159
- # Groupedmapreduce is more efficient than the second form because
160
- # it only has to traverse <b>+table+</b> once.
161
- def grouped_map_reduce(grouping, mapping, base, reduction)
162
- grouping_term = S.with_var{|vname,v| [vname, S.r(grouping.call(v))]}
163
- mapping_term = S.with_var{|vname,v| [vname, S.r(mapping.call(v))]}
164
- reduction_term = S.with_var {|aname, a| S.with_var {|bname, b|
165
- [S.r(base), aname, bname, S.r(reduction.call(a, b))]}}
166
- JSON_Expression.new [:call, [:groupedmapreduce,
167
- grouping_term,
168
- mapping_term,
169
- reduction_term],
170
- [self]]
171
- end
172
-
173
- # Group a sequence by one or more attributes and return some data about each
174
- # group. For example, if you have a table <b>+people+</b>:
175
- # people.group_by(:name, :town, r.count).filter{|row| row[:reduction] > 1}
176
- # Will find all cases where two people in the same town share a name, and
177
- # return a list of those name/town pairs along with the number of people who
178
- # share that name in that town. You can find a list of builtin data
179
- # collectors at Data_Collectors (which will also show you how to
180
- # define your own).
181
- def group_by(*args)
182
- raise ArgumentError,"group_by requires at least one argument" if args.length < 1
183
- attrs, opts = args[0..-2], args[-1]
184
- S.check_opts(opts, [:mapping, :base, :reduction, :finalizer])
185
- map = opts.has_key?(:mapping) ? opts[:mapping] : lambda {|row| row}
186
- if !opts.has_key?(:base) || !opts.has_key?(:reduction)
187
- raise TypeError, "Group by requires a reduction and base to be specified"
188
- end
189
- base = opts[:base]
190
- reduction = opts[:reduction]
191
-
192
- gmr = self.grouped_map_reduce(lambda{|r| attrs.map{|a| r[a]}}, map, base, reduction)
193
- if (f = opts[:finalizer])
194
- gmr = gmr.map{|group| group.merge({:reduction => f.call(group[:reduction])})}
195
- end
196
- return gmr
197
- end
198
-
199
- # Gets one or more elements from the sequence, much like [] in Ruby.
200
- # The following are all equivalent:
201
- # r([1,2,3])
202
- # r([0,1,2,3])[1...4]
203
- # r([0,1,2,3])[1..3]
204
- # r([0,1,2,3])[1..-1]
205
- # As are:
206
- # r(1)
207
- # r([0,1,2])[1]
208
- # And:
209
- # r(2)
210
- # r({:a => 2})[:a]
211
- # <b>NOTE:</b> If you are slicing an array, you can provide any negative index you
212
- # want, but if you're slicing a stream then for efficiency reasons the only
213
- # allowable negative index is '-1', and you must be using a closed range
214
- # ('..', not '...').
215
- def [](ind)
216
- case ind.class.hash
217
- when Fixnum.hash then
218
- JSON_Expression.new [:call, [:nth], [self, RQL.expr(ind)]]
219
- when Range.hash then
220
- b = RQL.expr(ind.begin)
221
- if ind.exclude_end? then e = ind.end
222
- else e = (ind.end == -1 ? nil : RQL.expr(ind.end+1))
223
- end
224
- self.class.new [:call, [:slice], [self, RQL.expr(b), RQL.expr(e)]]
225
- else raise ArgumentError, "RQL_Query#[] can't handle #{ind.inspect}."
226
- end
227
- end
228
-
229
- # Return at most <b>+n+</b> elements from the sequence. The
230
- # following are equivalent:
231
- # r([1,2,3])
232
- # r([1,2,3,4]).limit(3)
233
- # r([1,2,3,4])[0...3]
234
- def limit(n); self[0...n]; end
235
-
236
- # Skip the first <b>+n+</b> elements of the sequence. The following are equivalent:
237
- # r([2,3,4])
238
- # r([1,2,3,4]).skip(1)
239
- # r([1,2,3,4])[1..-1]
240
- def skip(n); self[n..-1]; end
241
-
242
- # Removes duplicate values from the sequence (similar to the *nix
243
- # <b>+uniq+</b> function). Does not work for sequences of
244
- # compound data types like objects or arrays, but in the case of
245
- # objects (e.g. rows of a table), you may provide an attribute and
246
- # it will first map the selector for that attribute over the
247
- # sequence. If we have a table <b>+table+</b>, the following are
248
- # equivalent:
249
- # table.map{|row| row[:id]}.distinct
250
- # table.distinct(:id)
251
- # As are:
252
- # r([1,2,3])
253
- # r([1,2,3,1]).distinct
254
- # And:
255
- # r([1,2])
256
- # r([{:x => 1}, {:x => 2}, {:x => 1}]).distinct(:x)
257
- def distinct(attr=nil);
258
- if attr then self.map{|row| row[attr]}.distinct
259
- else self.class.new [:call, [:distinct], [self]];
260
- end
261
- end
262
-
263
- # Get the length of the sequence. If we have a table
264
- # <b>+table+</b> with at least 5 elements, the following are
265
- # equivalent:
266
- # table[0...5].count
267
- # r([1,2,3,4,5]).count
268
- def count(); JSON_Expression.new [:call, [:count], [self]]; end
269
-
270
- # Get element <b>+n+</b> of the sequence. For example, the following are
271
- # equivalent:
272
- # r(2)
273
- # r([0,1,2,3]).nth(2)
274
- # (Note the 0-indexing.)
275
- def nth(n)
276
- JSON_Expression.new [:call, [:nth], [self, S.r(n)]]
277
- end
278
-
279
- # A normal inner join. Takes as an argument the table to join with and a
280
- # block. The block you provide should accept two tows and return
281
- # <b>+true+</b> if they should be joined or <b>+false+</b> otherwise. For
282
- # example:
283
- # table1.inner_join(table2) {|row1, row2| row1[:attr1] > row2[:attr2]}
284
- # Note that we don't merge the two tables when you do this. The output will
285
- # be a list of objects like:
286
- # {'left' => ..., 'right' => ...}
287
- # You can use Sequence#zip to get back a list of merged rows.
288
- def inner_join(other)
289
- self.concat_map {|row|
290
- other.concat_map {|row2|
291
- RQL.branch(yield(row, row2), [{:left => row, :right => row2}], [])
292
- }
293
- }
294
- end
295
-
296
-
297
- # A normal outer join. Takes as an argument the table to join with and a
298
- # block. The block you provide should accept two tows and return
299
- # <b>+true+</b> if they should be joined or <b>+false+</b> otherwise. For
300
- # example:
301
- # table1.outer_join(table2) {|row1, row2| row1[:attr1] > row2[:attr2]}
302
- # Note that we don't merge the two tables when you do this. The output will
303
- # be a list of objects like:
304
- # {'left' => ..., 'right' => ...}
305
- # You can use Sequence#zip to get back a list of merged rows.
306
- def outer_join(other)
307
- S.with_var {|vname, v|
308
- self.concat_map {|row|
309
- RQL.let({vname => other.concat_map {|row2|
310
- RQL.branch(yield(row, row2),
311
- [{:left => row, :right => row2}],
312
- [])}.to_array}) {
313
- RQL.branch(v.count() > 0, v, [{:left => row}])
314
- }
315
- }
316
- }
317
- end
318
-
319
- # A special case of Sequence#inner_join that is guaranteed to run in
320
- # O(n*log(n)) time. It does equality comparison between <b>+leftattr+</b> of
321
- # the invoking stream and the primary key of the <b>+other+</b> stream. For
322
- # example, the following are equivalent (if <b>+id+</b> is the primary key
323
- # of <b>+table2+</b>):
324
- # table1.eq_join(:a, table2)
325
- # table2.inner_join(table2) {|row1, row2| r.eq row1[:a],row2[:id]}
326
- # As this function defaults to <b>+id+</b> for the primary key of the right
327
- # table, you will need to specify the primary key if it is otherwise:
328
- # table1.eq_join(:a, table2, :t2pk)
329
- def eq_join(leftattr, other, rightattr=:id)
330
- S.with_var {|vname, v|
331
- self.concat_map {|row|
332
- RQL.let({vname => other.get(row[leftattr], rightattr)}) {
333
- RQL.branch(v.ne(nil), [{:left => row, :right => v}], [])
334
- }
335
- }
336
- }
337
- end
338
-
339
- # Take the output of Sequence#inner_join, Sequence#outer_join, or
340
- # Sequence#eq_join and merge the results together. The following are
341
- # equivalent:
342
- # table1.eq_join(:id, table2).zip
343
- # table1.eq_join(:id, table2).map{|obj| obj['left'].merge(obj['right'])}
344
- def zip
345
- self.map {|row|
346
- RQL.branch(row.contains('right'), row['left'].merge(row['right']), row['left'])
347
- }
348
- end
349
- end
350
- end
data/lib/streams.rb DELETED
@@ -1,101 +0,0 @@
1
- # Copyright 2010-2012 RethinkDB, all rights reserved.
2
- module RethinkDB
3
- # A lazy sequence of rows, e.g. what we get when reading from a table.
4
- # Includes the Sequence module, so look there for the methods.
5
- class Stream_Expression
6
- # Convert a stream into an array. THINK CAREFULLY BEFORE DOING
7
- # THIS. You can do more with an array than you can with a stream
8
- # (e.g., you can store an array in a variable), but that's because
9
- # arrays are stored in memory. If your stream is big (e.g. you're
10
- # reading a giant table), that has serious performance
11
- # implications. Also, if you return an array instead of a stream
12
- # from a query, the whole thing gets sent over the network at once
13
- # instead of lazily consuming chunks. If you have a table <b>+table+</b> with at
14
- # least 3 elements, the following are equivalent:
15
- # r[[1,1,1]]
16
- # table.limit(3).map{1}.stream_to_array
17
- def stream_to_array(); JSON_Expression.new [:call, [:stream_to_array], [self]]; end
18
- end
19
-
20
- # A special case of Stream_Expression that you can write to. You
21
- # will get a Multi_Row_Selection from most operations that access
22
- # tables. For example, consider the following two queries:
23
- # q1 = table.filter{|row| row[:id] < 5}
24
- # q2 = table.map{|row| row[:id]}
25
- # The first query simply accesses some elements of a table, while the
26
- # second query does some work to produce a new stream.
27
- # Correspondingly, the first query returns a Multi_Row_Selection
28
- # while the second query returns a Stream_Expression. So:
29
- # q1.delete
30
- # is a legal query that will delete everything with <b>+id+</b> less
31
- # than 5 in <b>+table+</b>. But:
32
- # q2.delete
33
- # will raise an error.
34
- class Multi_Row_Selection < Stream_Expression
35
- attr_accessor :opts
36
- def initialize(body, context=nil, opts=nil) # :nodoc:
37
- super(body, context)
38
- if opts
39
- @opts = opts
40
- elsif @body[0] == :call and @body[2] and @body[2][0].kind_of? Multi_Row_Selection
41
- @opts = @body[2][0].opts
42
- end
43
- end
44
-
45
- def raise_if_outdated # :nodoc:
46
- if @opts and @opts[:use_outdated]
47
- raise RuntimeError, "Cannot write to outdated table."
48
- end
49
- end
50
-
51
- # Delete all of the selected rows. For example, if we have
52
- # a table <b>+table+</b>:
53
- # table.filter{|row| row[:id] < 5}.delete
54
- # will delete everything with <b>+id+</b> less than 5 in <b>+table+</b>.
55
- def delete
56
- raise_if_outdated
57
- Write_Query.new [:delete, self]
58
- end
59
-
60
- # Update all of the selected rows. For example, if we have a table <b>+table+</b>:
61
- # table.filter{|row| row[:id] < 5}.update{|row| {:score => row[:score]*2}}
62
- # will double the score of everything with <b>+id+</b> less than 5.
63
- # If the object returned in <b>+update+</b> has attributes
64
- # which are not present in the original row, those attributes will
65
- # still be added to the new row.
66
- #
67
- # If you want to do a non-atomic update, you should pass
68
- # <b>+:non_atomic+</b> as the optional variant:
69
- # table.update(:non_atomic){|row| r.js("...")}
70
- # You need to do a non-atomic update when the block provided to update can't
71
- # be proved deterministic (e.g. if it contains javascript or reads from
72
- # another table).
73
- def update(variant=nil)
74
- raise_if_outdated
75
- S.with_var {|vname,v|
76
- Write_Query.new [:update, self, [vname, S.r(yield(v))]]
77
- }.apply_variant(variant)
78
- end
79
-
80
- # Replace all of the selected rows. Unlike <b>+update+</b>, must return the
81
- # new row rather than an object containing attributes to be updated (may be
82
- # combined with RQL::merge to mimic <b>+update+</b>'s behavior).
83
- # May also return <b>+nil+</b> to delete the row. For example, if we have a
84
- # table <b>+table+</b>, then:
85
- # table.replace{|row| r.if(row[:id] < 5, nil, row)}
86
- # will delete everything with id less than 5, but leave the other rows untouched.
87
- #
88
- # If you want to do a non-atomic replace, you should pass
89
- # <b>+:non_atomic+</b> as the optional variant:
90
- # table.replace(:non_atomic){|row| r.js("...")}
91
- # You need to do a non-atomic replace when the block provided to replace can't
92
- # be proved deterministic (e.g. if it contains javascript or reads from
93
- # another table).
94
- def replace(variant=nil)
95
- raise_if_outdated
96
- S.with_var {|vname,v|
97
- Write_Query.new [:replace, self, [vname, S.r(yield(v))]]
98
- }.apply_variant(variant)
99
- end
100
- end
101
- end