rethinkdb 1.2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/base_classes.rb +39 -0
- data/lib/bt.rb +148 -0
- data/lib/data_collectors.rb +35 -0
- data/lib/jsons.rb +136 -0
- data/lib/net.rb +275 -0
- data/lib/protob_compiler.rb +137 -0
- data/lib/query.rb +111 -0
- data/lib/query_language.pb.rb +687 -0
- data/lib/rethinkdb.rb +16 -0
- data/lib/rql.rb +458 -0
- data/lib/sequence.rb +349 -0
- data/lib/streams.rb +101 -0
- data/lib/tables.rb +123 -0
- data/lib/utils.rb +118 -0
- data/lib/writes.rb +24 -0
- metadata +80 -0
data/lib/base_classes.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright 2010-2012 RethinkDB, all rights reserved.
|
2
|
+
#TODO: toplevel doc
|
3
|
+
module RethinkDB
|
4
|
+
class RQL_Query; end
|
5
|
+
class Read_Query < RQL_Query # :nodoc:
|
6
|
+
end
|
7
|
+
class Write_Query < RQL_Query # :nodoc:
|
8
|
+
end
|
9
|
+
class Meta_Query < RQL_Query # :nodoc:
|
10
|
+
end
|
11
|
+
|
12
|
+
module Sequence; end
|
13
|
+
class JSON_Expression < Read_Query; include Sequence; end
|
14
|
+
class Single_Row_Selection < JSON_Expression; end
|
15
|
+
class Stream_Expression < Read_Query; include Sequence; end
|
16
|
+
class Multi_Row_Selection < Stream_Expression; end
|
17
|
+
|
18
|
+
class Database; end
|
19
|
+
class Table; end
|
20
|
+
|
21
|
+
class Connection; end
|
22
|
+
class Query_Results; include Enumerable; end
|
23
|
+
|
24
|
+
module RQL; end
|
25
|
+
|
26
|
+
# Shortcuts to easily build RQL queries.
|
27
|
+
module Shortcuts
|
28
|
+
# The only shortcut right now. May be used as a wrapper to create
|
29
|
+
# RQL values or as a shortcut to access function in the RQL
|
30
|
+
# namespace. For example, the following are equivalent:
|
31
|
+
# r.add(1, 2)
|
32
|
+
# RethinkDB::RQL.add(1, 2)
|
33
|
+
# r(1) + r(2)
|
34
|
+
# r(3)
|
35
|
+
def r(*args)
|
36
|
+
(args == [] ) ? RethinkDB::RQL : RQL.expr(*args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/bt.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# Copyright 2010-2012 RethinkDB, all rights reserved.
|
2
|
+
module RethinkDB
|
3
|
+
module BT_Mixin
|
4
|
+
attr_accessor :highlight, :line
|
5
|
+
|
6
|
+
def sanitize_context(context)
|
7
|
+
if __FILE__ =~ /^(.*\/)[^\/]+.rb$/
|
8
|
+
prefix = $1;
|
9
|
+
context.reject{|x| x =~ /^#{prefix}/}
|
10
|
+
else
|
11
|
+
context
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def format_highlights(str)
|
16
|
+
str.chars.map{|x| x == "\000" ? "^" : " "}.join.rstrip
|
17
|
+
end
|
18
|
+
|
19
|
+
def force_raise(query, debug=false)
|
20
|
+
obj = query.run
|
21
|
+
#maybe_reformat_err(query, obj);
|
22
|
+
PP.pp obj if debug
|
23
|
+
if obj.class == Query_Results
|
24
|
+
obj = obj.to_a
|
25
|
+
elsif obj.class == Hash
|
26
|
+
raise RuntimeError,obj['first_error'] if obj['first_error']
|
27
|
+
end
|
28
|
+
obj
|
29
|
+
end
|
30
|
+
|
31
|
+
def maybe_reformat_err(query, obj)
|
32
|
+
if obj.class == Hash && (err = obj['first_error'])
|
33
|
+
obj['first_error'] = format_err(query, err)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def format_err(query, err)
|
38
|
+
parts = err.split("\nBacktrace:\n")
|
39
|
+
errline = parts[0] || ""
|
40
|
+
bt = (parts[1] && parts[1].split("\n")) || []
|
41
|
+
errline+"\n"+query.print_backtrace(bt)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(sym,val)
|
45
|
+
@map = {} if not @map
|
46
|
+
res, @map[sym] = @map[sym], val
|
47
|
+
return res
|
48
|
+
end
|
49
|
+
def consume sym
|
50
|
+
@map = {} if not @map
|
51
|
+
res, @map[sym] = @map[sym], nil
|
52
|
+
return res.nil? ? 0 : res
|
53
|
+
end
|
54
|
+
def expand arg
|
55
|
+
case arg
|
56
|
+
when 'attr' then []
|
57
|
+
when 'attrname' then []
|
58
|
+
when 'base' then [1+consume(:reduce)]
|
59
|
+
when 'body' then [4+consume(:reduce)]
|
60
|
+
when 'datacenter' then []
|
61
|
+
when 'db_name' then []
|
62
|
+
when 'expr' then [2]
|
63
|
+
when 'false' then [3]
|
64
|
+
when 'group_mapping' then [1, 1, 1]
|
65
|
+
when 'key' then [3]
|
66
|
+
when 'keyname' then []
|
67
|
+
when 'lowerbound' then [1, 2]
|
68
|
+
when 'mapping' then [1, 2]
|
69
|
+
when 'modify_map' then [2, 1]
|
70
|
+
when 'order_by' then []
|
71
|
+
when 'point_map' then [4, 1]
|
72
|
+
when 'predicate' then [1, 2]
|
73
|
+
when 'reduce' then [1]
|
74
|
+
when 'reduction' then set(:reduce, -1); [1, 3]
|
75
|
+
when 'stream' then [1]
|
76
|
+
when 'table_name' then []
|
77
|
+
when 'table_ref' then []
|
78
|
+
when 'test' then [1]
|
79
|
+
when 'true' then [2]
|
80
|
+
when 'upperbound' then [1, 3]
|
81
|
+
when 'value_mapping' then [1, 2, 1]
|
82
|
+
when 'view' then [1]
|
83
|
+
when /arg:([0-9]+)/ then [2, $1.to_i]
|
84
|
+
when /attrs:([0-9]+)/ then []
|
85
|
+
when /elem:([0-9]+)/ then [$1.to_i+1]
|
86
|
+
when /bind:(.+)$/ then [1, $1]
|
87
|
+
when /key:(.+)$/ then [1..-1, $1]
|
88
|
+
when /query:([0-9]+)/ then [3, $1.to_i]
|
89
|
+
when /term:([0-9]+)/ then [2, $1.to_i]
|
90
|
+
else [:error]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def recur(arr, lst, val)
|
95
|
+
if lst[0].class == String
|
96
|
+
entry = arr[0..-1].find{|x| x[0].to_s == lst[0]}
|
97
|
+
raise RuntimeError if not entry
|
98
|
+
mark(entry[1], lst[1..-1], val)
|
99
|
+
elsif lst[0].class == Fixnum || lst[0].class == Range
|
100
|
+
mark(arr[lst[0]], lst[1..-1], val) if lst != []
|
101
|
+
else
|
102
|
+
raise RuntimeError
|
103
|
+
end
|
104
|
+
end
|
105
|
+
def mark(query, lst, val)
|
106
|
+
#PP.pp [lst, query.class, query]
|
107
|
+
if query.class == Array
|
108
|
+
recur(query, lst, val) if lst != []
|
109
|
+
elsif query.kind_of? RQL_Query
|
110
|
+
if lst == []
|
111
|
+
@line = sanitize_context(query.context)[0]
|
112
|
+
val,query.marked = query.marked, val
|
113
|
+
val
|
114
|
+
else
|
115
|
+
recur(query.body, lst, val)
|
116
|
+
end
|
117
|
+
else raise RuntimeError
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def with_marked_error(query, bt)
|
122
|
+
@map = {}
|
123
|
+
bt = bt.map{|x| expand x}.flatten
|
124
|
+
raise RuntimeError if bt.any?{|x| x == :error}
|
125
|
+
old = mark(query, bt, :error)
|
126
|
+
res = yield
|
127
|
+
mark(query, bt, old)
|
128
|
+
return res
|
129
|
+
end
|
130
|
+
|
131
|
+
def with_highlight
|
132
|
+
@highlight = true
|
133
|
+
str = yield
|
134
|
+
@highlight = false
|
135
|
+
str.chars.map{|x| x == "\000" ? "^" : " "}.join.rstrip
|
136
|
+
end
|
137
|
+
|
138
|
+
def alt_inspect(query, &block)
|
139
|
+
class << query
|
140
|
+
attr_accessor :str_block
|
141
|
+
def inspect; real_inspect({:str => @str_block.call(self)}); end
|
142
|
+
end
|
143
|
+
query.str_block = block
|
144
|
+
query
|
145
|
+
end
|
146
|
+
end
|
147
|
+
module BT; extend BT_Mixin; end
|
148
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Copyright 2010-2012 RethinkDB, all rights reserved.
|
2
|
+
module RethinkDB
|
3
|
+
# Data collectors are ways of aggregating data (for use with
|
4
|
+
# Sequence#groupby). You can define your own; all they are is a hash
|
5
|
+
# containing a <b>+mapping+</b> to be applied to row, a
|
6
|
+
# <b>+base+</b>/<b>+reduction+</b> to reduce over the mappings, and an
|
7
|
+
# optional <b>+finalizer+</b> to call on the output of the reduction. You can
|
8
|
+
# expand the builtin data collectors below for examples.
|
9
|
+
#
|
10
|
+
# All of the builtin collectors can also be accessed using the <b>+r+</b>
|
11
|
+
# shortcut (like <b>+r.sum+</b>).
|
12
|
+
module Data_Collectors
|
13
|
+
# Count the number of rows in each group.
|
14
|
+
def self.count
|
15
|
+
{ :mapping => lambda{|row| 1},
|
16
|
+
:base => 0,
|
17
|
+
:reduction => lambda{|acc, val| acc + val} }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Sum a particular attribute of the rows in each group.
|
21
|
+
def self.sum(attr)
|
22
|
+
{ :mapping => lambda{|row| row[attr]},
|
23
|
+
:base => 0,
|
24
|
+
:reduction => lambda{|acc, val| acc + val} }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Average a particular attribute of the rows in each group.
|
28
|
+
def self.avg(attr)
|
29
|
+
{ :mapping => lambda{|row| [row[attr], 1]},
|
30
|
+
:base => [0, 0],
|
31
|
+
:reduction => lambda{|acc, val| [acc[0] + val[0], acc[1] + val[1]]},
|
32
|
+
:finalizer => lambda{|res| res[0] / res[1]} }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/jsons.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
# Copyright 2010-2012 RethinkDB, all rights reserved.
|
2
|
+
module RethinkDB
|
3
|
+
# A query returning a JSON expression. Most of the functions that you
|
4
|
+
# can run on a JSON object are found in RethinkDB::RQL and accessed
|
5
|
+
# with the <b>+r+</b> shortcut. For convenience, may of the
|
6
|
+
# functions in Rethinkdb::RQL can be run as if they were instance
|
7
|
+
# methods of JSON_Expression. For example, the following are
|
8
|
+
# equivalent:
|
9
|
+
# r.add(1, 2)
|
10
|
+
# r(1).add(2)
|
11
|
+
# r(3)
|
12
|
+
#
|
13
|
+
# Running a JSON_Expression query will return a literal Ruby
|
14
|
+
# representation of the resulting JSON, rather than a stream or a
|
15
|
+
# string. For example, the following are equivalent:
|
16
|
+
# r.add(1,2).run
|
17
|
+
# 3
|
18
|
+
# As are:
|
19
|
+
# r({:a => 1, :b => 2}).pick(:a).run
|
20
|
+
# {'a' => 1}
|
21
|
+
# (Note that the symbol keys were coerced into string keys in the
|
22
|
+
# object. JSON doesn't distinguish between symbols and strings.)
|
23
|
+
class JSON_Expression
|
24
|
+
# If <b>+ind+</b> is a symbol or a string, gets the corresponding
|
25
|
+
# attribute of an object. For example, the following are equivalent:
|
26
|
+
# r({:id => 1})[:id]
|
27
|
+
# r({:id => 1})['id']
|
28
|
+
# r(1)
|
29
|
+
# Otherwise, if <b>+ind+</b> is a number or a range, invokes RethinkDB::Sequence#[]
|
30
|
+
def [](ind)
|
31
|
+
if ind.class == Symbol || ind.class == String
|
32
|
+
BT.alt_inspect(JSON_Expression.new [:call, [:getattr, ind], [self]]) {
|
33
|
+
"#{self.inspect}[#{ind.inspect}]"
|
34
|
+
}
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Append a single element to an array. The following are equivalent:
|
41
|
+
# r([1,2,3,4])
|
42
|
+
# r([1,2,3]).append(4)
|
43
|
+
def append(el)
|
44
|
+
JSON_Expression.new [:call, [:arrayappend], [self, S.r(el)]]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get an attribute of a JSON object (the same as
|
48
|
+
# JSON_Expression#[]). The following are equivalent.
|
49
|
+
# r({:id => 1}).getattr(:id)
|
50
|
+
# r({:id => 1})[:id]
|
51
|
+
# r(1)
|
52
|
+
def getattr(attrname)
|
53
|
+
JSON_Expression.new [:call, [:getattr, attrname], [self]]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Check whether a JSON object has a particular attribute. The
|
57
|
+
# following are equivalent:
|
58
|
+
# r({:id => 1}).contains(:id)
|
59
|
+
# r(true)
|
60
|
+
def contains(attrname)
|
61
|
+
JSON_Expression.new [:call, [:contains, attrname], [self]]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Construct a JSON object that has a subset of the attributes of
|
65
|
+
# another JSON object by specifying which to keep. The following are equivalent:
|
66
|
+
# r({:a => 1, :b => 2, :c => 3}).pick(:a, :c)
|
67
|
+
# r({:a => 1, :c => 3})
|
68
|
+
def pick(*attrnames)
|
69
|
+
JSON_Expression.new [:call, [:pick, *attrnames], [self]]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Construct a JSON object that has a subset of the attributes of
|
73
|
+
# another JSON object by specifying which to drop. The following are equivalent:
|
74
|
+
# r({:a => 1, :b => 2, :c => 3}).without(:a, :c)
|
75
|
+
# r({:b => 2})
|
76
|
+
def unpick(*attrnames)
|
77
|
+
JSON_Expression.new [:call, [:unpick, *attrnames], [self]]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Convert from an array to a stream. While most sequence functions are polymorphic
|
81
|
+
# and handle both arrays and streams, when arrays or streams need to be
|
82
|
+
# combined (e.g. via <b>+union+</b>) you need to explicitly convert between
|
83
|
+
# the types. This is mostly for safety, but also because which type you're
|
84
|
+
# working with affects error handling. The following are equivalent:
|
85
|
+
# r([1,2,3]).arraytostream
|
86
|
+
# r([1,2,3]).to_stream
|
87
|
+
def array_to_stream(); Stream_Expression.new [:call, [:array_to_stream], [self]]; end
|
88
|
+
|
89
|
+
# Prefix numeric -. The following are equivalent:
|
90
|
+
# -r(1)
|
91
|
+
# r(-1)
|
92
|
+
def -@; JSON_Expression.new [:call, [:subtract], [self]]; end
|
93
|
+
# Prefix numeric +. The following are equivalent:
|
94
|
+
# +r(-1)
|
95
|
+
# r(-1)
|
96
|
+
def +@; JSON_Expression.new [:call, [:add], [self]]; end
|
97
|
+
end
|
98
|
+
|
99
|
+
# A query representing a variable. Produced by e.g. RQL::letvar. This
|
100
|
+
# is its own class because it needs to behave correctly when spliced
|
101
|
+
# into a string (see RQL::js).
|
102
|
+
class Var_Expression < JSON_Expression
|
103
|
+
# Convert from an RQL query representing a variable to the name of that
|
104
|
+
# variable. Used e.g. in constructing javascript functions.
|
105
|
+
def to_s
|
106
|
+
if @body.class == Array and @body[0] == :var
|
107
|
+
then @body[1]
|
108
|
+
else raise TypeError, 'Can only call to_s on RQL_Queries representing variables.'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Like Multi_Row_Selection, but for a single row. E.g.:
|
114
|
+
# table.get(0)
|
115
|
+
# yields a Single_Row_Selection
|
116
|
+
class Single_Row_Selection < JSON_Expression
|
117
|
+
# Analagous to Multi_Row_Selection#update
|
118
|
+
def update(variant=nil)
|
119
|
+
S.with_var {|vname,v|
|
120
|
+
Write_Query.new [:pointupdate, *(@body[1..-1] + [[vname, S.r(yield(v))]])]
|
121
|
+
}.apply_variant(variant)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Analagous to Multi_Row_Selection#mutate
|
125
|
+
def replace(variant=nil)
|
126
|
+
S.with_var {|vname,v|
|
127
|
+
Write_Query.new [:pointreplace, *(@body[1..-1] + [[vname, S.r(yield(v))]])]
|
128
|
+
}.apply_variant(variant)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Analagous to Multi_Row_Selection#delete
|
132
|
+
def delete
|
133
|
+
Write_Query.new [:pointdelete, *(@body[1..-1])]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/net.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
# Copyright 2010-2012 RethinkDB, all rights reserved.
|
2
|
+
require 'socket'
|
3
|
+
require 'thread'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module RethinkDB
|
7
|
+
module Faux_Abort # :nodoc:
|
8
|
+
class Abort # :nodoc:
|
9
|
+
end
|
10
|
+
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
|
20
|
+
def out_of_date # :nodoc:
|
21
|
+
@conn.conn_id != @conn_id
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect # :nodoc:
|
25
|
+
state = @run ? "(exhausted)" : "(enumerable)"
|
26
|
+
extra = out_of_date ? " (Connection #{@conn.inspect} reset!)" : ""
|
27
|
+
"#<RethinkDB::Query_Results:#{self.object_id} #{state}#{extra}: #{@query.inspect}>"
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(query, connection, token) # :nodoc:
|
31
|
+
@query = query
|
32
|
+
@run = false
|
33
|
+
@conn_id = connection.conn_id
|
34
|
+
@conn = connection
|
35
|
+
@token = token
|
36
|
+
end
|
37
|
+
|
38
|
+
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)
|
42
|
+
@run = true
|
43
|
+
return self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
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
|
+
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
|
67
|
+
|
68
|
+
@@last = nil
|
69
|
+
@@magic_number = 0xaf61ba35
|
70
|
+
|
71
|
+
def debug_socket # :nodoc:
|
72
|
+
@socket;
|
73
|
+
end
|
74
|
+
|
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
|
86
|
+
end
|
87
|
+
|
88
|
+
def dispatch msg # :nodoc:
|
89
|
+
PP.pp msg if $DEBUG
|
90
|
+
payload = msg.serialize_to_string
|
91
|
+
#File.open('sexp_payloads.txt', 'a') {|f| f.write(payload.inspect+"\n")}
|
92
|
+
packet = [payload.length].pack('L<') + payload
|
93
|
+
@socket.send(packet, 0)
|
94
|
+
return msg.token
|
95
|
+
end
|
96
|
+
|
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]
|
103
|
+
end
|
104
|
+
raise RuntimeError, "Connection closed by server!" if !@listener or !res
|
105
|
+
return res
|
106
|
+
end
|
107
|
+
|
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
|
120
|
+
end
|
121
|
+
|
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
|
159
|
+
end
|
160
|
+
|
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
|
179
|
+
|
180
|
+
# Change the default database of a connection.
|
181
|
+
def use(new_default_db)
|
182
|
+
@default_db = new_default_db
|
183
|
+
end
|
184
|
+
|
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
|
217
|
+
end
|
218
|
+
|
219
|
+
# Close the connection.
|
220
|
+
def close
|
221
|
+
@listener.terminate! if @listener
|
222
|
+
@listener = nil
|
223
|
+
@socket.close
|
224
|
+
@socket = nil
|
225
|
+
end
|
226
|
+
|
227
|
+
# Return the last opened connection, or throw if there is no such
|
228
|
+
# connection. Used by e.g. RQL_Query#run.
|
229
|
+
def self.last_connection
|
230
|
+
return @@last if @@last
|
231
|
+
raise RuntimeError, "No last connection. Use RethinkDB::Connection.new."
|
232
|
+
end
|
233
|
+
|
234
|
+
def start_listener # :nodoc:
|
235
|
+
class << @socket
|
236
|
+
def read_exn(len) # :nodoc:
|
237
|
+
buf = read len
|
238
|
+
raise RuntimeError,"Connection closed by server." if !buf or buf.length != len
|
239
|
+
return buf
|
240
|
+
end
|
241
|
+
end
|
242
|
+
@socket.send([@@magic_number].pack('L<'), 0)
|
243
|
+
@listener.terminate! if @listener
|
244
|
+
@listener = Thread.new do
|
245
|
+
loop do
|
246
|
+
begin
|
247
|
+
response_length = @socket.read_exn(4).unpack('L<')[0]
|
248
|
+
response = @socket.read_exn(response_length)
|
249
|
+
rescue RuntimeError => e
|
250
|
+
@mutex.synchronize do
|
251
|
+
@listener = nil
|
252
|
+
@waiters.each {|kv| kv[1].signal}
|
253
|
+
end
|
254
|
+
Thread.current.terminate!
|
255
|
+
abort("unreachable")
|
256
|
+
end
|
257
|
+
#TODO: Recovery
|
258
|
+
begin
|
259
|
+
protob = Response.new.parse_from_string(response)
|
260
|
+
rescue
|
261
|
+
p response
|
262
|
+
abort("Bad Protobuf.")
|
263
|
+
end
|
264
|
+
@mutex.synchronize do
|
265
|
+
@data[protob.token] = protob
|
266
|
+
if (@waiters[protob.token])
|
267
|
+
cond = @waiters.delete protob.token
|
268
|
+
cond.signal
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|