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/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