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