rethinkdb 1.2.6.1 → 1.4.0.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/lib/exc.rb ADDED
@@ -0,0 +1,30 @@
1
+ module RethinkDB
2
+ class RqlError < RuntimeError; end
3
+ class RqlRuntimeError < RqlError; end
4
+ class RqlDriverError < RqlError; end
5
+ class RqlCompileError < RqlError; end
6
+
7
+ # class Error < StandardError
8
+ # def initialize(msg, bt)
9
+ # @msg = msg
10
+ # @bt = bt
11
+ # @pp_query_bt = "UNIMPLEMENTED #{bt.inspect}"
12
+ # super inspect
13
+ # end
14
+ # def inspect
15
+ # "#{@msg}\n#{@pp_query_bt}"
16
+ # end
17
+ # attr_accessor :msg, :bt, :pp_query_bt
18
+ # end
19
+
20
+ # class Connection_Error < Error; end
21
+
22
+ # class Compile_Error < Error; end
23
+ # class Malformed_Protobuf_Error < Compile_Error; end
24
+ # class Malformed_Query_Error < Compile_Error; end
25
+
26
+ # class Runtime_Error < Error; end
27
+ # class Data_Error < Runtime_Error; end
28
+ # class Type_Error < Data_Error; end
29
+ # class Resource_Error < Runtime_Error; end
30
+ end
data/lib/func.rb ADDED
@@ -0,0 +1,156 @@
1
+ module RethinkDB
2
+ class RQL
3
+ @@gensym_cnt = 0
4
+ def new_func(&b)
5
+ args = (0...b.arity).map{@@gensym_cnt += 1}
6
+ body = b.call(*(args.map{|i| RQL.new.var i}))
7
+ RQL.new.func(args, body)
8
+ end
9
+
10
+ @@special_optargs = {
11
+ :replace => :non_atomic, :update => :non_atomic, :insert => :upsert
12
+ }
13
+ @@opt_off = {
14
+ :reduce => -1, :between => -1, :grouped_map_reduce => -1,
15
+ :table => -1, :table_create => -1
16
+ }
17
+ @@rewrites = {
18
+ :< => :lt, :<= => :le, :> => :gt, :>= => :ge,
19
+ :+ => :add, :- => :sub, :* => :mul, :/ => :div, :% => :mod,
20
+ :"|" => :any, :or => :any,
21
+ :"&" => :all, :and => :all,
22
+ :order_by => :orderby,
23
+ :group_by => :groupby,
24
+ :concat_map => :concatmap,
25
+ :for_each => :foreach,
26
+ :js => :javascript,
27
+ :type_of => :typeof
28
+ }
29
+ def method_missing(m, *a, &b)
30
+ bitop = [:"|", :"&"].include?(m) ? [m, a, b] : nil
31
+ if [:<, :<=, :>, :>=, :+, :-, :*, :/, :%].include?(m)
32
+ a.each {|arg|
33
+ if arg.class == RQL && arg.bitop
34
+ err = "Calling #{m} on result of infix bitwise operator:\n" +
35
+ "#{arg.inspect}.\n" +
36
+ "This is almost always a precedence error.\n" +
37
+ "Note that `a < b | b < c` <==> `a < (b | b) < c`.\n" +
38
+ "If you really want this behavior, use `.or` or `.and` instead."
39
+ raise ArgumentError, err
40
+ end
41
+ }
42
+ end
43
+
44
+ m = @@rewrites[m] || m
45
+ termtype = Term::TermType.values[m.to_s.upcase.to_sym]
46
+ unbound_if(!termtype, m)
47
+
48
+ if (opt_name = @@special_optargs[m])
49
+ a = optarg_jiggle(a, opt_name)
50
+ opt_offset = -1
51
+ end
52
+ if (opt_offset ||= @@opt_off[m])
53
+ optargs = a.delete_at(opt_offset) if a[opt_offset].class == Hash
54
+ end
55
+
56
+ args = (@body ? [self] : []) + a + (b ? [new_func(&b)] : [])
57
+
58
+ t = Term.new
59
+ t.type = termtype
60
+ t.args = args.map{|x| RQL.new.expr(x).to_pb}
61
+ t.optargs = (optargs || {}).map {|k,v|
62
+ ap = Term::AssocPair.new
63
+ ap.key = k.to_s
64
+ ap.val = RQL.new.expr(v).to_pb
65
+ ap
66
+ }
67
+ return RQL.new(t, bitop)
68
+ end
69
+
70
+ def group_by(*a, &b)
71
+ a = [self] + a if @body
72
+ RQL.new.method_missing(:group_by, a[0], a[1..-2], a[-1], &b)
73
+ end
74
+ def groupby(*a, &b); group_by(*a, &b); end
75
+
76
+ def optarg_jiggle(args, optarg)
77
+ if (ind = args.map{|x| x.class == Symbol ? x : nil}.index(optarg))
78
+ args << {args.delete_at(ind) => true}
79
+ else
80
+ args << {}
81
+ end
82
+ return args
83
+ end
84
+
85
+ def connect(*args)
86
+ unbound_if @body
87
+ Connection.new(*args)
88
+ end
89
+
90
+ def avg(attr)
91
+ unbound_if @body
92
+ {:AVG => attr}
93
+ end
94
+ def sum(attr)
95
+ unbound_if @body
96
+ {:SUM => attr}
97
+ end
98
+ def count(*a, &b)
99
+ !@body && a == [] ? {:COUNT => true} : super(*a, &b)
100
+ end
101
+
102
+ def reduce(*a, &b)
103
+ a = a[1..-2] + [{:base => a[-1]}] if a.size + (@body ? 1 : 0) == 2
104
+ super(*a, &b)
105
+ end
106
+
107
+ def grouped_map_reduce(*a, &b)
108
+ a << {:base => a.delete_at(-2)} if a.size >= 2 && a[-2].class != Proc
109
+ super(*a, &b)
110
+ end
111
+
112
+ def between(l=nil, r=nil)
113
+ super(Hash[(l ? [['left_bound', l]] : []) + (r ? [['right_bound', r]] : [])])
114
+ end
115
+
116
+ def -@; RQL.new.sub(0, self); end
117
+
118
+ def [](ind)
119
+ if ind.class == Fixnum
120
+ return nth(ind)
121
+ elsif ind.class == Symbol || ind.class == String
122
+ return getattr(ind)
123
+ elsif ind.class == Range
124
+ if ind.end == 0 && ind.exclude_end?
125
+ raise ArgumentError, "Cannot slice to an excluded end of 0."
126
+ end
127
+ return slice(ind.begin, ind.end - (ind.exclude_end? ? 1 : 0))
128
+ end
129
+ raise ArgumentError, "[] cannot handle #{ind.inspect} of type #{ind.class}."
130
+ end
131
+
132
+ def ==(rhs)
133
+ raise ArgumentError,"
134
+ Cannot use inline ==/!= with RQL queries, use .eq() instead if
135
+ you want a query that does equality comparison.
136
+
137
+ If you need to see whether two queries are the same, compare
138
+ their protobufs like: `query1.to_pb == query2.to_pb`."
139
+ end
140
+
141
+
142
+ def do(*args, &b)
143
+ a = (@body ? [self] : []) + args.dup
144
+ if a == [] && !b
145
+ raise RqlDriverError, "Expected 1 or more argument(s) but found 0."
146
+ end
147
+ RQL.new.funcall(*((b ? [new_func(&b)] : [a.pop]) + a))
148
+ end
149
+
150
+ def row
151
+ unbound_if @body
152
+ raise NoMethodError, ("Sorry, r.row is not available in the ruby driver. " +
153
+ "Use blocks instead.")
154
+ end
155
+ end
156
+ end
data/lib/net.rb CHANGED
@@ -1,34 +1,53 @@
1
1
  # Copyright 2010-2012 RethinkDB, all rights reserved.
2
2
  require 'socket'
3
3
  require 'thread'
4
- require 'json'
4
+
5
+ # $f = File.open("fuzz_seed.rb", "w")
5
6
 
6
7
  module RethinkDB
7
- module Faux_Abort # :nodoc:
8
- class Abort # :nodoc:
8
+ module Faux_Abort
9
+ class Abort
10
+ end
11
+ end
12
+
13
+ class RQL
14
+ def self.set_default_conn c; @@default_conn = c; end
15
+ def run(c=@@default_conn, opts=nil)
16
+ # $f.puts "("+RPP::pp(@body)+"),"
17
+ unbound_if !@body
18
+ c, opts = @@default_conn, c if opts.nil? && !c.kind_of?(RethinkDB::Connection)
19
+ opts = {} if opts.nil?
20
+ opts = {opts => true} if opts.class != Hash
21
+ if !c
22
+ raise ArgumentError, "No connection specified!\n" \
23
+ "Use `query.run(conn)` or `conn.repl(); query.run`."
24
+ end
25
+ c.run(@body, opts)
9
26
  end
10
27
  end
11
- # The result of a calling Connection#run on a query that returns a stream.
12
- # This class is <b>+Enumerable+</b>. You can find documentation on Enumerable
13
- # classes at http://ruby-doc.org/core-1.9.3/Enumerable.html .
14
- #
15
- # <b>NOTE:</b> unlike most enumerable objects, you can only iterate over Query
16
- # results once. The results are fetched lazily from the server in chunks
17
- # of 1000. If you need to access values multiple times, use the <b>+to_a+</b>
18
- # instance method to get an Array, and then work with that.
19
- class Query_Results
28
+
29
+ class Cursor
30
+ include Enumerable
20
31
  def out_of_date # :nodoc:
21
32
  @conn.conn_id != @conn_id
22
33
  end
23
34
 
24
35
  def inspect # :nodoc:
36
+ preview_res = @results[0...10]
37
+ if (@results.size > 10 || @more)
38
+ preview_res << (dots = "..."; class << dots; def inspect; "..."; end; end; dots)
39
+ end
40
+ preview = preview_res.pretty_inspect[0...-1]
25
41
  state = @run ? "(exhausted)" : "(enumerable)"
26
42
  extra = out_of_date ? " (Connection #{@conn.inspect} reset!)" : ""
27
- "#<RethinkDB::Query_Results:#{self.object_id} #{state}#{extra}: #{@query.inspect}>"
43
+ "#<RethinkDB::Cursor:#{self.object_id} #{state}#{extra}: #{RPP.pp(@msg)}" +
44
+ (@run ? "" : "\n#{preview}") + ">"
28
45
  end
29
46
 
30
- def initialize(query, connection, token) # :nodoc:
31
- @query = query
47
+ def initialize(results, msg, connection, token, more = true) # :nodoc:
48
+ @more = more
49
+ @results = results
50
+ @msg = msg
32
51
  @run = false
33
52
  @conn_id = connection.conn_id
34
53
  @conn = connection
@@ -36,187 +55,129 @@ module RethinkDB
36
55
  end
37
56
 
38
57
  def each (&block) # :nodoc:
39
- raise RuntimeError, "Can only iterate over Query_Results once!" if @run
40
- raise RuntimeError, "Connection has been reset!" if out_of_date
41
- @conn.token_iter(@query, @token, &block)
58
+ raise RqlRuntimeError, "Can only iterate over Query_Results once!" if @run
42
59
  @run = true
43
- return self
60
+ raise RqlRuntimeError, "Connection has been reset!" if out_of_date
61
+ while true
62
+ @results.each(&block)
63
+ return self if !@more
64
+ q = Query.new
65
+ q.type = Query::QueryType::CONTINUE
66
+ q.token = @token
67
+ res = @conn.run_internal q
68
+ @results = Shim.response_to_native(res, @msg)
69
+ if res.type == Response::ResponseType::SUCCESS_SEQUENCE
70
+ @more = false
71
+ end
72
+ end
44
73
  end
45
74
  end
46
75
 
47
- # TODO: Make sure tokens don't overflow.
48
-
49
- # A connection to the RethinkDB
50
- # cluster. You need to create at least one connection before you can run
51
- # queries. After creating a connection <b>+conn+</b> and constructing a
52
- # query <b>+q+</b>, the following are equivalent:
53
- # conn.run(q)
54
- # q.run
55
- # (This is because by default, invoking the <b>+run+</b> instance method on a
56
- # query runs it on the most-recently-opened connection.)
57
- #
58
- # ==== Attributes
59
- # * +default_db+ - The current default database (can be set with Connection#use). Defaults to 'test'.
60
- # * +conn_id+ - You probably don't care about this. Used in some advanced situations where you care about whether a connection object has reconnected.
61
76
  class Connection
62
- def inspect # :nodoc:
63
- properties = "(#{@host}:#{@port}) (Default Database: '#{@default_db}')"
64
- state = @listener ? "(listening)" : "(closed)"
65
- "#<RethinkDB::Connection:#{self.object_id} #{properties} #{state}>"
66
- end
77
+ def repl; RQL.set_default_conn self; end
67
78
 
68
- @@last = nil
69
- @@magic_number = 0xaf61ba35
79
+ def initialize(host='localhost', port=28015, default_db=nil)
80
+ begin
81
+ @abort_module = ::IRB
82
+ rescue NameError => e
83
+ @abort_module = Faux_Abort
84
+ end
85
+ @@last = self
86
+ @host = host
87
+ @port = port
88
+ @default_opts = default_db ? {:db => RQL.new.db(default_db)} : {}
89
+ @conn_id = 0
90
+ reconnect
91
+ end
92
+ attr_reader :default_db, :conn_id
70
93
 
71
- def debug_socket # :nodoc:
72
- @socket;
94
+ @@token_cnt = 0
95
+ def run_internal q
96
+ dispatch q
97
+ wait q.token
98
+ end
99
+ def run(msg, opts)
100
+ q = Query.new
101
+ q.type = Query::QueryType::START
102
+ q.query = msg
103
+ q.token = @@token_cnt += 1
104
+
105
+ @default_opts.merge(opts).each {|k,v|
106
+ ap = Query::AssocPair.new
107
+ ap.key = k.to_s
108
+ ap.val = v.to_pb
109
+ q.global_optargs << ap
110
+ }
111
+
112
+ res = run_internal q
113
+ if res.type == Response::ResponseType::SUCCESS_PARTIAL
114
+ Cursor.new(Shim.response_to_native(res, msg), msg, self, q.token, true)
115
+ elsif res.type == Response::ResponseType::SUCCESS_SEQUENCE
116
+ Cursor.new(Shim.response_to_native(res, msg), msg, self, q.token, false)
117
+ else
118
+ Shim.response_to_native(res, msg)
119
+ end
73
120
  end
74
121
 
75
- # Reconnect to the server. This will interrupt all queries on the
76
- # server and invalidate all outstanding enumerables on the client.
77
- def reconnect
78
- @socket.close if @socket
79
- @socket = TCPSocket.open(@host, @port)
80
- @waiters = {}
81
- @data = {}
82
- @mutex = Mutex.new
83
- @conn_id += 1
84
- start_listener
85
- self
122
+ def send packet
123
+ @socket.send(packet, 0)
86
124
  end
87
125
 
88
- def dispatch msg # :nodoc:
126
+ def dispatch msg
89
127
  PP.pp msg if $DEBUG
90
128
  payload = msg.serialize_to_string
91
129
  #File.open('sexp_payloads.txt', 'a') {|f| f.write(payload.inspect+"\n")}
92
- packet = [payload.length].pack('L<') + payload
93
- @socket.send(packet, 0)
130
+ send([payload.length].pack('L<') + payload)
94
131
  return msg.token
95
132
  end
96
133
 
97
- def wait token # :nodoc:
98
- res = nil
99
- raise RuntimeError, "Connection closed by server!" if not @listener
100
- @mutex.synchronize do
101
- (@waiters[token] = ConditionVariable.new).wait(@mutex) if not @data[token]
102
- res = @data.delete token if @data[token]
134
+ def wait token
135
+ begin
136
+ res = nil
137
+ raise RqlRuntimeError, "Connection closed by server!" if not @listener
138
+ @mutex.synchronize do
139
+ (@waiters[token] = ConditionVariable.new).wait(@mutex) if not @data[token]
140
+ res = @data.delete token if @data[token]
141
+ end
142
+ raise RqlRuntimeError, "Connection closed by server!" if !@listener or !res
143
+ return res
144
+ rescue @abort_module::Abort => e
145
+ print "\nAborting query and reconnecting...\n"
146
+ reconnect
147
+ raise e
103
148
  end
104
- raise RuntimeError, "Connection closed by server!" if !@listener or !res
105
- return res
106
149
  end
107
150
 
108
- def continue token # :nodoc:
109
- msg = Query.new
110
- msg.type = Query::QueryType::CONTINUE
111
- msg.token = token
112
- dispatch msg
113
- end
114
-
115
- def error(query,protob,err=RuntimeError) # :nodoc:
116
- bt = protob.backtrace ? protob.backtrace.frame : []
117
- #PP.pp bt
118
- msg = "RQL: #{protob.error_message}\n" + query.print_backtrace(bt)
119
- raise err,msg
151
+ # Change the default database of a connection.
152
+ def use(new_default_db)
153
+ @default_opts[:db] = RQL.new.db(new_default_db)
120
154
  end
121
155
 
122
- def token_iter(query, token) # :nodoc:
123
- done = false
124
- data = []
125
- loop do
126
- if (data == [])
127
- break if done
128
-
129
- begin
130
- protob = wait token
131
- rescue @abort_module::Abort => e
132
- print "\nAborting query and reconnecting...\n"
133
- self.reconnect()
134
- raise e
135
- end
136
-
137
- case protob.status_code
138
- when Response::StatusCode::SUCCESS_JSON then
139
- yield JSON.parse('['+protob.response[0]+']')[0]
140
- return false
141
- when Response::StatusCode::SUCCESS_PARTIAL then
142
- continue token
143
- data.push *protob.response
144
- when Response::StatusCode::SUCCESS_STREAM then
145
- data.push *protob.response
146
- done = true
147
- when Response::StatusCode::SUCCESS_EMPTY then
148
- return false
149
- when Response::StatusCode::BAD_QUERY then error query,protob,ArgumentError
150
- when Response::StatusCode::RUNTIME_ERROR then error query,protob,RuntimeError
151
- else error query,protob
152
- end
153
- end
154
- #yield JSON.parse("["+data.shift+"]")[0] if data != []
155
- yield JSON.parse('['+data.shift+']')[0] if data != []
156
- #yield data.shift if data != []
157
- end
158
- return true
156
+ def inspect
157
+ db = @default_opts[:db] || RQL.new.db('test')
158
+ properties = "(#{@host}:#{@port}) (Default DB: #{db.inspect})"
159
+ state = @listener ? "(listening)" : "(closed)"
160
+ "#<RethinkDB::Connection:#{self.object_id} #{properties} #{state}>"
159
161
  end
160
162
 
161
- # Create a new connection to <b>+host+</b> on port <b>+port+</b>.
162
- # You may also optionally provide a default database to use when
163
- # running queries over that connection. Example:
164
- # c = Connection.new('localhost', 28015, 'default_db')
165
- def initialize(host='localhost', port=28015, default_db='test')
166
- begin
167
- @abort_module = ::IRB
168
- rescue NameError => e
169
- @abort_module = Faux_Abort
170
- end
171
- @@last = self
172
- @host = host
173
- @port = port
174
- @default_db = default_db
175
- @conn_id = 0
176
- reconnect
177
- end
178
- attr_reader :default_db, :conn_id
163
+ @@last = nil
164
+ @@magic_number = 0x3f61ba36
179
165
 
180
- # Change the default database of a connection.
181
- def use(new_default_db)
182
- @default_db = new_default_db
183
- end
166
+ def debug_socket; @socket; end
184
167
 
185
- # Run a query over the connection. If you run a query that returns a JSON
186
- # expression (e.g. a reduce), you get back that JSON expression. If you run
187
- # a query that returns a stream of results (e.g. filtering a table), you get
188
- # back an enumerable object of class RethinkDB::Query_Results.
189
- #
190
- # You may also provide some options as an optional second
191
- # argument. The only useful option right now is
192
- # <b>+:use_outdated+</b>, which specifies whether tables can use
193
- # outdated information. (If you specified this on a per-table
194
- # basis in your query, specifying it again here won't override the
195
- # original choice.)
196
- #
197
- # <b>NOTE:</b> unlike most enumerable objects, you can only iterate over the
198
- # result once. See RethinkDB::Query_Results for more details.
199
- def run (query, opts={:use_outdated => false})
200
- #File.open('sexp.txt', 'a') {|f| f.write(query.sexp.inspect+"\n")}
201
- is_atomic = (query.kind_of?(JSON_Expression) ||
202
- query.kind_of?(Meta_Query) ||
203
- query.kind_of?(Write_Query))
204
- map = {}
205
- map[:default_db] = @default_db if @default_db
206
- S.check_opts(opts, [:use_outdated])
207
- map[S.conn_outdated] = !!opts[:use_outdated]
208
- protob = query.query(map)
209
- if is_atomic
210
- a = []
211
- singular = token_iter(query, dispatch(protob)){|row| a.push row}
212
- a.each{|o| BT.maybe_reformat_err(query, o)}
213
- singular ? a : a[0]
214
- else
215
- return Query_Results.new(query, self, dispatch(protob))
216
- end
168
+ # Reconnect to the server. This will interrupt all queries on the
169
+ # server and invalidate all outstanding enumerables on the client.
170
+ def reconnect
171
+ @socket.close if @socket
172
+ @socket = TCPSocket.open(@host, @port)
173
+ @waiters = {}
174
+ @data = {}
175
+ @mutex = Mutex.new
176
+ @conn_id += 1
177
+ start_listener
178
+ self
217
179
  end
218
180
 
219
- # Close the connection.
220
181
  def close
221
182
  @listener.terminate if @listener
222
183
  @listener = nil
@@ -224,18 +185,18 @@ module RethinkDB
224
185
  @socket = nil
225
186
  end
226
187
 
227
- # Return the last opened connection, or throw if there is no such
228
- # connection. Used by e.g. RQL_Query#run.
229
188
  def self.last
230
189
  return @@last if @@last
231
- raise RuntimeError, "No last connection. Use RethinkDB::Connection.new."
190
+ raise RqlRuntimeError, "No last connection. Use RethinkDB::Connection.new."
232
191
  end
233
192
 
234
- def start_listener # :nodoc:
193
+ def start_listener
235
194
  class << @socket
236
- def read_exn(len) # :nodoc:
195
+ def read_exn(len)
237
196
  buf = read len
238
- raise RuntimeError,"Connection closed by server." if !buf or buf.length != len
197
+ if !buf or buf.length != len
198
+ raise RqlRuntimeError, "Connection closed by server."
199
+ end
239
200
  return buf
240
201
  end
241
202
  end
@@ -246,7 +207,7 @@ module RethinkDB
246
207
  begin
247
208
  response_length = @socket.read_exn(4).unpack('L<')[0]
248
209
  response = @socket.read_exn(response_length)
249
- rescue RuntimeError => e
210
+ rescue RqlRuntimeError => e
250
211
  @mutex.synchronize do
251
212
  @listener = nil
252
213
  @waiters.each {|kv| kv[1].signal}
@@ -258,8 +219,7 @@ module RethinkDB
258
219
  begin
259
220
  protob = Response.new.parse_from_string(response)
260
221
  rescue
261
- p response
262
- abort("Bad Protobuf.")
222
+ raise RqlRuntimeError, "Bad Protobuf #{response}, server is buggy."
263
223
  end
264
224
  @mutex.synchronize do
265
225
  @data[protob.token] = protob