ruby_volt 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +20 -0
- data/README.md +282 -0
- data/lib/ruby_volt.rb +33 -0
- data/lib/ruby_volt/base.rb +105 -0
- data/lib/ruby_volt/connection.rb +150 -0
- data/lib/ruby_volt/data_type.rb +50 -0
- data/lib/ruby_volt/data_type/app_data_type.rb +24 -0
- data/lib/ruby_volt/data_type/app_data_type/procedure_call_status_code.rb +14 -0
- data/lib/ruby_volt/data_type/app_data_type/wire_type_info.rb +22 -0
- data/lib/ruby_volt/data_type/basic.rb +36 -0
- data/lib/ruby_volt/data_type/basic/fixed_size.rb +26 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/decimal.rb +45 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/float.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/geography_point.rb +28 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type.rb +20 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/byte.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/integer.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/long.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/short.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_byte.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_integer.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_long.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_short.rb +9 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/null.rb +13 -0
- data/lib/ruby_volt/data_type/basic/fixed_size/timestamp.rb +24 -0
- data/lib/ruby_volt/data_type/basic/geography.rb +61 -0
- data/lib/ruby_volt/data_type/basic/string.rb +19 -0
- data/lib/ruby_volt/data_type/basic/varbinary.rb +34 -0
- data/lib/ruby_volt/data_type/complex.rb +11 -0
- data/lib/ruby_volt/data_type/complex/parameter.rb +20 -0
- data/lib/ruby_volt/data_type/complex/parameter_set.rb +19 -0
- data/lib/ruby_volt/data_type/complex/serializable_exception.rb +43 -0
- data/lib/ruby_volt/data_type/complex/volt_table.rb +96 -0
- data/lib/ruby_volt/data_type/compound.rb +8 -0
- data/lib/ruby_volt/data_type/compound/array.rb +42 -0
- data/lib/ruby_volt/data_type/extensions.rb +20 -0
- data/lib/ruby_volt/exceptions.rb +67 -0
- data/lib/ruby_volt/helper.rb +29 -0
- data/lib/ruby_volt/message.rb +29 -0
- data/lib/ruby_volt/message/invocation_request.rb +18 -0
- data/lib/ruby_volt/message/login_message.rb +17 -0
- data/lib/ruby_volt/meta.rb +6 -0
- data/lib/ruby_volt/meta/geography.rb +165 -0
- data/lib/ruby_volt/read_partial.rb +23 -0
- data/lib/ruby_volt/response.rb +24 -0
- data/lib/ruby_volt/response/invocation_response.rb +64 -0
- data/lib/ruby_volt/response/login_response.rb +27 -0
- data/lib/ruby_volt/version.rb +3 -0
- data/lib/ruby_volt/volt_table.rb +37 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9c0cc1d7254943ed092af2bc346a25c84af35eae4285ce97e400f2a50fba899b
|
4
|
+
data.tar.gz: a5357c99c85e51158e953679a45c56d340520ba3f7b72f060abb994f16842e08
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f177966f32af3efa1cd31638101185ac82d46d49a2bb62671c0435a513775b10d3ea5c3c232fc1a10601cbacad6a454dc1b1359224f2a837f45f90d2554f222
|
7
|
+
data.tar.gz: c0655ddb572c884fba6314907f4944973c0bf851709d46551b8387c3341ded8945ac5c3fdd815e9ca9531e1bcc90ce1248a26f036c23d9a05a28c7ad25102134
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
# RubyVolt
|
2
|
+
|
3
|
+
### VoltDB Wire Protocol Client for Ruby programming language
|
4
|
+
|
5
|
+
#### Pure Ruby client for VoltDB - one of the fastest in-memory databases on the planet. Threadsafe and fast enough wire client implementation
|
6
|
+
|
7
|
+
Protocol Version 1 (01/26/2016).
|
8
|
+
|
9
|
+
## Reference docs
|
10
|
+
|
11
|
+
* [Welcome to VoltDB. A Tutorial](http://downloads.voltdb.com/documentation/tutorial.pdf)
|
12
|
+
* [VoltDB Client Wire Protocol](http://downloads.voltdb.com/documentation/wireprotocol.pdf)
|
13
|
+
* [Using VoltDB](http://downloads.voltdb.com/documentation/UsingVoltDB.pdf)
|
14
|
+
* [VoltDB Guide to Performance and Customization](http://downloads.voltdb.com/documentation/PerfGuide.pdf)
|
15
|
+
|
16
|
+
## Compatibility
|
17
|
+
|
18
|
+
Compatible with Ruby **MRI 2.5**> & **JRuby 9.2**>
|
19
|
+
|
20
|
+
Previous versions can be supported easily, and you can do it yourself forking this code. However, performance *will probably drop* with previous versions or Ruby, which is essential, since we are dealing with a really fast database! On my laptop VoltDB is responding 2-5 times faster than Ruby can dispatch the response within one process and few threads.
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
$ gem install ruby_volt
|
25
|
+
|
26
|
+
## Initialize
|
27
|
+
|
28
|
+
Require gem:
|
29
|
+
|
30
|
+
require 'ruby_volt'
|
31
|
+
|
32
|
+
Init the client with options:
|
33
|
+
|
34
|
+
voltdb = RubyVolt::Base.new(options)
|
35
|
+
|
36
|
+
Where *options* is a Hash with keys:
|
37
|
+
|
38
|
+
:cluster - Hash, Array, String of VoltDB cluster:
|
39
|
+
- String URI: 1 connection to "your.server" or "your.server:21212" or "user:password@your.server:21212",
|
40
|
+
- Array of Strings: 1 connection per server ["first.server", "second.server:21213", "user@third.server"],
|
41
|
+
- Hash: where keys are Strings and values are numbers of connections to particular servers {"first.server" => 2, "second_server:21213" => 1}
|
42
|
+
- Nil: if option is not defined then 1 connection to 'localhost' with default port 21212.
|
43
|
+
|
44
|
+
:username - default username for connection(s) if needed,
|
45
|
+
:password - default password for connection(s) if needed,
|
46
|
+
:connections - target number of connections to be enquired, if needed more than 1 by default,
|
47
|
+
:servicename - 'database' by default (see VoltDB documentation),
|
48
|
+
:connect_timeout - timeout (secs) for authentication, 8 by default,
|
49
|
+
:procedure_timeout - timeout (secs) for procedure call, 8 by default,
|
50
|
+
:login_protocol - VoltDB Client Wire Protocol version for authentication process, 1 by default,
|
51
|
+
:procedure_protocol - VoltDB Client Wire Protocol version for procedure invocation, 0 by default,
|
52
|
+
|
53
|
+
General example:
|
54
|
+
|
55
|
+
voltdb = RubyVolt::Base.new(:username => "username", :password => "secret") # established 1 connection to 'localhost@21212'
|
56
|
+
=> #<RubyVolt::Base:0x00007fb24f151388 @login_protocol=1, @procedure_protocol=0, @servicename="database", @connect_timeout=8, @procedure_timeout=8, @requests_queue=#<Thread::Queue:0x00007fb24f151360>, @connection_pool=[#<RubyVolt::Connection [localhost:21212]: server_host_id=0 connection_id=181 login_protocol=1 procedure_protocol=0>]>
|
57
|
+
|
58
|
+
## Usage
|
59
|
+
|
60
|
+
A client connection to a VoltDB instance consists of a TCP connection on port 21212. After the initial login process the only exchange between the client library and the VoltDB server is the invocation of and response to stored procedures.
|
61
|
+
|
62
|
+
In other words, *calling stored procedures is the only function* of VoltDB Client Wire Protocol at the moment.
|
63
|
+
|
64
|
+
For development and testing purposes I created one table named **_datatypes_** consists of all types of data VoltDB operates with and one stored procedure called **_datatypes_** which just fetch few rows from that table:
|
65
|
+
|
66
|
+
CREATE TABLE datatypes (
|
67
|
+
t_byte TINYINT,
|
68
|
+
t_short SMALLINT,
|
69
|
+
t_integer INTEGER,
|
70
|
+
t_long BIGINT,
|
71
|
+
t_float FLOAT,
|
72
|
+
t_string VARCHAR,
|
73
|
+
t_timestamp TIMESTAMP,
|
74
|
+
t_decimal DECIMAL,
|
75
|
+
t_varbinary VARBINARY,
|
76
|
+
t_geopoint GEOGRAPHY_POINT,
|
77
|
+
t_polygon GEOGRAPHY
|
78
|
+
);
|
79
|
+
|
80
|
+
CREATE PROCEDURE datatypes
|
81
|
+
AS SELECT *
|
82
|
+
FROM datatypes;
|
83
|
+
|
84
|
+
So, calling procedure looks like that:
|
85
|
+
|
86
|
+
voltdb.call_procedure("datatypes")
|
87
|
+
=> #<RubyVolt::InvocationResponse:0x00007fb2501191d0 @bytes=<RubyVolt::ReadPartial: bytes=0>, @data={:protocol=>0, :client_data=>"\eX\xE2\x90\xDFa*\x82", :present_fields=>0, :status=>RubyVolt::SuccessStatusCode, :app_status=>nil, :cluster_round_trip_time=>0}, @result=[#<RubyVolt::VoltTable index=0 total_table_length=1846 total_metadata_length=151 rows=5>]>
|
88
|
+
|
89
|
+
You've got the RubyVolt::InvocationResponse object having some datasets like metadata:
|
90
|
+
|
91
|
+
voltdb.call_procedure("datatypes").data # Metadata around calling procedure
|
92
|
+
=> {:protocol=>0, :client_data=>"4q#\xF0S\x8E\x83u", :present_fields=>0, :status=>RubyVolt::SuccessStatusCode, :app_status=>nil, :cluster_round_trip_time=>0}
|
93
|
+
|
94
|
+
Also it has invocation result itself consists of VoltTable sets:
|
95
|
+
|
96
|
+
Read [Using VoltDB documentation](http://downloads.voltdb.com/documentation/UsingVoltDB.pdf) explaining why we have an array of VoltTables. In our case we have just one VoltTable.
|
97
|
+
|
98
|
+
voltdb.call_procedure("datatypes").result
|
99
|
+
=> [#<RubyVolt::VoltTable index=0 total_table_length=1846 total_metadata_length=151 rows=5>]
|
100
|
+
|
101
|
+
Each VoltTable has array of rows, we've got *5 rows*:
|
102
|
+
|
103
|
+
voltdb.call_procedure("datatypes").result[0].rows
|
104
|
+
=> [#<struct T_BYTE=1, T_SHORT=23, T_INTEGER=19948544, T_LONG=123456789101, T_FLOAT=-23325.23425, T_STRING="Madagaskar", T_TIMESTAMP=2018-12-27 03:00:00 +0300, T_DECIMAL=-0.2332523425e5, T_VARBINARY="", T_GEOPOINT=POINT(-109.8223383 34.9766921), T_POLYGON=POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.1 0.1, 0.1 0.9, 0.9 0.9, 0.9 0.1, 0.1 0.1))>, #<struct T_BYTE=1, T_SHORT=23, T_INTEGER=19948544, T_LONG=123456789101, T_FLOAT=-23325.23425, T_STRING="Madagaskar", T_TIMESTAMP=2018-12-27 03:00:00 +0300, T_DECIMAL=-0.2332523425e5, T_VARBINARY=nil, T_GEOPOINT=POINT(-109.8223383 34.9766921), T_POLYGON=POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.1 0.1, 0.1 0.9, 0.9 0.9, 0.9 0.1, 0.1 0.1))>, #<struct T_BYTE=1, T_SHORT=23, T_INTEGER=19948544, T_LONG=123456789101, T_FLOAT=-23325.23425, T_STRING="", T_TIMESTAMP=2019-12-27 03:00:00 +0300, T_DECIMAL=-0.2322522425e5, T_VARBINARY=nil, T_GEOPOINT=POINT(-110.8223383 35.4766421), T_POLYGON=POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.1 0.1, 0.1 0.9, 0.9 0.9, 0.9 0.1, 0.1 0.1))>, #<struct T_BYTE=1, T_SHORT=23, T_INTEGER=19948544, T_LONG=123456789101, T_FLOAT=-23325.23425, T_STRING=nil, T_TIMESTAMP=2019-12-27 03:00:00 +0300, T_DECIMAL=-0.2322522425e5, T_VARBINARY=nil, T_GEOPOINT=POINT(-110.8223383 35.4766421), T_POLYGON=POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.1 0.1, 0.1 0.9, 0.9 0.9, 0.9 0.1, 0.1 0.1))>, #<struct T_BYTE=nil, T_SHORT=nil, T_INTEGER=nil, T_LONG=nil, T_FLOAT=nil, T_STRING=nil, T_TIMESTAMP=nil, T_DECIMAL=nil, T_VARBINARY=nil, T_GEOPOINT=nil, T_POLYGON=nil>]
|
105
|
+
|
106
|
+
Each row represented as instance of Ruby's Struct class, having attributes according to column names (case sensitive):
|
107
|
+
|
108
|
+
voltdb.call_procedure("datatypes").result[0].rows[3]
|
109
|
+
=> #<struct T_BYTE=1, T_SHORT=23, T_INTEGER=19948544, T_LONG=123456789101, T_FLOAT=-23325.23425, T_STRING=nil, T_TIMESTAMP=2019-12-27 03:00:00 +0300, T_DECIMAL=-0.2322522425e5, T_VARBINARY=nil, T_GEOPOINT=POINT(-110.8223383 35.4766421), T_POLYGON=POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.1 0.1, 0.1 0.9, 0.9 0.9, 0.9 0.1, 0.1 0.1))>
|
110
|
+
|
111
|
+
voltdb.call_procedure("datatypes").result[0].rows[3].T_FLOAT
|
112
|
+
=> -23325.23425
|
113
|
+
|
114
|
+
## Calling procedures with blocks
|
115
|
+
|
116
|
+
For deeper integration of the received data, you can send block with calling procedure method, which will allow to obtain data before VoltTable is formed and row is still an *Array* object. So you can just skip some internal operations transforming *Array row* to *Struct row*:
|
117
|
+
|
118
|
+
voltdb.call_procedure("datatypes") do |data, volttable, row|
|
119
|
+
puts volttable.columns.inspect # Do something with columns info
|
120
|
+
puts row.inspect # Do something extra with your data here ...
|
121
|
+
end
|
122
|
+
=> #<RubyVolt::InvocationResponse:0x00007fb250141950 @bytes=<RubyVolt::ReadPartial: bytes=0>, @data={:protocol=>0, :client_data=>"1\x11\xEB\x90\xFFS~\x89", :present_fields=>0, :status=>RubyVolt::SuccessStatusCode, :app_status=>nil, :cluster_round_trip_time=>0}, @result=[#<RubyVolt::VoltTable index=0 total_table_length=1846 total_metadata_length=151 rows=0>]>
|
123
|
+
|
124
|
+
In previous example note that VoltTable's rows are empty (rows=0), but you can reproduce the standard functionality, using *add_struct()* method:
|
125
|
+
|
126
|
+
voltdb.call_procedure("datatypes") do |data, volttable, row|
|
127
|
+
volttable.add_struct(row)
|
128
|
+
end
|
129
|
+
=> #<RubyVolt::InvocationResponse:0x00007fb250851510 @bytes=<RubyVolt::ReadPartial: bytes=0>, @data={:protocol=>0, :client_data=>"\x01-\xD9p\xBE\xA0H\xEA", :present_fields=>0, :status=>RubyVolt::SuccessStatusCode, :app_status=>nil, :cluster_round_trip_time=>0}, @result=[#<RubyVolt::VoltTable index=0 total_table_length=1846 total_metadata_length=151 rows=5>]>
|
130
|
+
|
131
|
+
## Сalling procedures Async
|
132
|
+
|
133
|
+
Frankly speaking, each invocation made *asynchronous* in nature. The difference is that *synchronous* call waits until data has been read.
|
134
|
+
But you can split *invocation* and *reading* in timeline, although you can read data just once!
|
135
|
+
|
136
|
+
async_call = voltdb.async_call_procedure("datatypes")
|
137
|
+
=> #<RubyVolt::Base::AsyncCall ready=false>
|
138
|
+
|
139
|
+
... some other stuff ... time spending ...
|
140
|
+
|
141
|
+
async_call.dispatch # You can read data just ONCE!
|
142
|
+
=> #<RubyVolt::InvocationResponse:0x00007fb2500fdcf0 @bytes=<RubyVolt::ReadPartial: bytes=0>, @data={:protocol=>0, :client_data=>"\x04\xB7\xE2\xC0 <*w", :present_fields=>0, :status=>RubyVolt::SuccessStatusCode, :app_status=>nil, :cluster_round_trip_time=>0}, @result=[#<RubyVolt::VoltTable index=0 total_table_length=1846 total_metadata_length=151 rows=5>]>
|
143
|
+
|
144
|
+
As soon as you read the data, they are erased from the buffer.
|
145
|
+
You can operate with the result as described before, using blocks as well.
|
146
|
+
|
147
|
+
## Passing parameters
|
148
|
+
|
149
|
+
See [Using VoltDB docs](http://downloads.voltdb.com/documentation/UsingVoltDB.pdf) how to create stored procedures. Let's say we have procedure named *leastpopulated* which accepts one integer parameter. Pass the parameter next to procedure name:
|
150
|
+
|
151
|
+
voltdb.call_procedure("leastpopulated", 3)
|
152
|
+
|
153
|
+
In general, you can pass any type of parameter data as method's argument next to procedure name.
|
154
|
+
|
155
|
+
RubyVolt converts Ruby datatypes and VoltDB datatypes vice-versa:
|
156
|
+
|
157
|
+
* Ruby's **Integer** (depends on bit length) - to VoltDB's *Byte*, *Short*, *Integer*, or *Long*
|
158
|
+
* Ruby's **Float** - to VoltDB's *Float*
|
159
|
+
* Ruby's **String** (depends on encoding) - to VoltDB's *Varbinary* or *String*
|
160
|
+
* Ruby's **BigDecimal** - to VoltDB's *Decimal*
|
161
|
+
* Ruby's **Time** - to VoltDB's *Timestamp*
|
162
|
+
* Ruby's **Array** - to VoltDB's *Array*
|
163
|
+
|
164
|
+
Two datatypes implemented additionaly, according to the protocol specifications and WKT:
|
165
|
+
|
166
|
+
* **RubyVolt::Meta::Geography::Point** - to VoltDB's *GeographyPointValue*
|
167
|
+
* **RubyVolt::Meta::Geography::Polygon** - to VoltDB's *GeographyValue*
|
168
|
+
|
169
|
+
See detailed description of these datatypes in [VoltDB Guide to Performance and Customization](http://downloads.voltdb.com/documentation/PerfGuide.pdf) (chapter 6.1. The Geospatial Datatypes).
|
170
|
+
|
171
|
+
### Passing Arrays
|
172
|
+
|
173
|
+
VoltDB Array is a single datatype set, so in Ruby you have to specify datatype as the first element:
|
174
|
+
|
175
|
+
voltdb.call_procedure("any_other_procedure", [RubyVolt::DataType::Byte, 1,2,3,4,5])
|
176
|
+
|
177
|
+
or
|
178
|
+
|
179
|
+
voltdb.call_procedure("any_other_procedure", [:Byte, 1,2,3,4,5])
|
180
|
+
|
181
|
+
or
|
182
|
+
|
183
|
+
voltdb.call_procedure("any_other_procedure", ["Byte", 1,2,3,4,5])
|
184
|
+
|
185
|
+
or let RubyVolt recognize it
|
186
|
+
|
187
|
+
voltdb.call_procedure("any_other_procedure", [1,2,3,4,5])
|
188
|
+
|
189
|
+
## Geospatial Data
|
190
|
+
|
191
|
+
VoltDB has two data structures for geospatial data:
|
192
|
+
|
193
|
+
* *GeographyPointValue* represent coordinates
|
194
|
+
* *GeographyValue* represents polygons
|
195
|
+
|
196
|
+
And official documentation says:
|
197
|
+
*"It should be noted that, although a description of the GeographyPointValue/GeographyValue structure is being provided here for completeness, in most cases the client interface does not need to interpret the structure. Generally the client passes the point representation unchanged between the server and the client application.*
|
198
|
+
|
199
|
+
However, for completeness, RubyVolt provides a classes for such structures.
|
200
|
+
|
201
|
+
### RubyVolt::Meta::Geography::Point
|
202
|
+
|
203
|
+
This class accepts two arguments: *longitude* and *latitude*
|
204
|
+
|
205
|
+
point = RubyVolt::Meta::Geography::Point.new(lng, lat)
|
206
|
+
|
207
|
+
But in the wire protocol a point is represented by a three dimensional point on the unit sphere. These three dimensional points are called XYZPoints. Each dimension is a double precision IEEE floating point number. The Euclidean length of each XYZPoint must be 1.0.
|
208
|
+
|
209
|
+
xyz_point = RubyVolt::Meta::Geography::Point.from_XYZPoint(x, y, z) # ex. 1.000000, 0.000000, 0.000000
|
210
|
+
|
211
|
+
### RubyVolt::Meta::Geography::Polygon
|
212
|
+
|
213
|
+
Polygon has Rings,
|
214
|
+
Rings has Points,
|
215
|
+
Points are represented in XYZ format.
|
216
|
+
|
217
|
+
For example, we have set of eight points:
|
218
|
+
|
219
|
+
p1 = RubyVolt::Meta::Geography::Point.from_XYZPoint 1.000000, 0.000000, 0.000000
|
220
|
+
p2 = RubyVolt::Meta::Geography::Point.from_XYZPoint 0.999848, 0.017452, 0.000000
|
221
|
+
p3 = RubyVolt::Meta::Geography::Point.from_XYZPoint 0.999695, 0.017450, 0.017452
|
222
|
+
p4 = RubyVolt::Meta::Geography::Point.from_XYZPoint 0.999848, 0.000000, 0.017452
|
223
|
+
p5 = RubyVolt::Meta::Geography::Point.from_XYZPoint 0.999997, 0.001745, 0.001745
|
224
|
+
p6 = RubyVolt::Meta::Geography::Point.from_XYZPoint 0.999875, 0.001745, 0.015707
|
225
|
+
p7 = RubyVolt::Meta::Geography::Point.from_XYZPoint 0.999753, 0.015705, 0.015707
|
226
|
+
p8 = RubyVolt::Meta::Geography::Point.from_XYZPoint 0.999875, 0.015707, 0.001745
|
227
|
+
|
228
|
+
Now we create the *ring1* from points *p1..p4*:
|
229
|
+
|
230
|
+
ring1 = RubyVolt::Meta::Geography::Ring.new # can accept array o points as argument
|
231
|
+
ring1.add_point(p1)
|
232
|
+
ring1.add_point(p2)
|
233
|
+
ring1.add_point(p3)
|
234
|
+
ring1.add_point(p4)
|
235
|
+
|
236
|
+
Next we create the *ring2* from points *p5..p8*:
|
237
|
+
|
238
|
+
ring2 = RubyVolt::Meta::Geography::Ring.new # can accept array o points as argument
|
239
|
+
ring2.add_point(p5)
|
240
|
+
ring2.add_point(p6)
|
241
|
+
ring2.add_point(p7)
|
242
|
+
ring2.add_point(p8)
|
243
|
+
|
244
|
+
Having two *rings* let's create polygon object from them:
|
245
|
+
|
246
|
+
polygon = RubyVolt::Meta::Geography::Polygon.new
|
247
|
+
polygon.add_ring(ring1)
|
248
|
+
polygon.add_ring(ring2)
|
249
|
+
|
250
|
+
Now we can operate with Polygon object within our environment. These objects (*Polygon* and/or *Point*) can be used as procedure parameters as well.
|
251
|
+
When we read geospatial data from the database, RubyVolt represents the same data structures for us.
|
252
|
+
|
253
|
+
voltdb.call_procedure("datatypes").result[0].rows[3].T_GEOPOINT
|
254
|
+
=> POINT(-110.8223383 35.4766421) # RubyVolt::Meta::Geography::Point object
|
255
|
+
|
256
|
+
voltdb.call_procedure("datatypes").result[0].rows[3].T_POLYGON
|
257
|
+
=> POLYGON((0.0 0.0, 1.0 0.0, 1.0 1.0, 0.0 1.0, 0.0 0.0), (0.1 0.1, 0.1 0.9, 0.9 0.9, 0.9 0.1, 0.1 0.1))
|
258
|
+
|
259
|
+
## Ping and Benchmark
|
260
|
+
|
261
|
+
Ping the connection:
|
262
|
+
|
263
|
+
voltdb.ping # invoke system @Ping procedure
|
264
|
+
|
265
|
+
To look how fast your connection is on minimal dataset:
|
266
|
+
|
267
|
+
voltdb.benchmark(100000) # pings 100k times
|
268
|
+
|
269
|
+
Same methods *ping()* and *benchmark()* can be run for particular connection from the pool (if you have few of them):
|
270
|
+
|
271
|
+
voltdb.connection_pool[1].ping
|
272
|
+
voltdb.connection_pool[1].benchmark(100000)
|
273
|
+
|
274
|
+
## Perfomance
|
275
|
+
|
276
|
+
As said before, VoltDB is responding 2-5 times faster than Ruby can dispatch the result (depends on particular data structures), so in fact Ruby is a bottleneck.
|
277
|
+
|
278
|
+
By the way, RubyVolt is a threadsafe and only 1 thread serves 1 connection (1 thread/per connection) by default. My testing shows that there is no need to parallelize (*forking process*) or artificially increase the number of threads per connection, because the specific application in the real-world environment, for example **Puma** or **Unicorn**, will do it best of all.
|
279
|
+
|
280
|
+
Therefore, in multi-threading or multi-processing environment you can squeeze maximum performance you can. In the MRI environment, multi-threading adds little to performance (~ 20% first 2 threads) due to GIL limitations, but multiprocessing (forking) makes this much better.
|
281
|
+
|
282
|
+
However, multi-threading *JRuby performance* due to the use of all CPU cores in real time, is approximately two times higher than in MRI.
|
data/lib/ruby_volt.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'digest'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'socket'
|
5
|
+
require 'ipaddr'
|
6
|
+
|
7
|
+
module RubyVolt
|
8
|
+
# VoltDB Wire Protocol specs at http://downloads.voltdb.com/documentation/wireprotocol.pdf
|
9
|
+
|
10
|
+
DEFAULT_HOSTNAME = "localhost"
|
11
|
+
DEFAULT_PORT = 21212
|
12
|
+
LOGIN_PROTOCOL=1
|
13
|
+
PROCEDURE_PROTOCOL=0
|
14
|
+
SERVICE_NAME = "database"
|
15
|
+
DEFAULT_TIMEOUT = 8
|
16
|
+
CONNECT_TIMEOUT = 8
|
17
|
+
PROCEDURE_TIMEOUT = 8
|
18
|
+
|
19
|
+
def self.config(const, value)
|
20
|
+
remove_const(const) if const_defined?(const)
|
21
|
+
const_set(const, value)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'ruby_volt/helper'
|
27
|
+
require 'ruby_volt/exceptions'
|
28
|
+
require 'ruby_volt/data_type'
|
29
|
+
require 'ruby_volt/meta'
|
30
|
+
require 'ruby_volt/read_partial'
|
31
|
+
require 'ruby_volt/connection'
|
32
|
+
require 'ruby_volt/base'
|
33
|
+
require 'ruby_volt/volt_table'
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class Base
|
3
|
+
|
4
|
+
class AsyncCall
|
5
|
+
attr_reader :opaque, :args, :response_msg
|
6
|
+
|
7
|
+
def initialize(procedure, *parameters)
|
8
|
+
@opaque = Helper.uniq_bytes(8) # Not even necessary
|
9
|
+
@args = [procedure, @opaque, *parameters]
|
10
|
+
@queue = SizedQueue.new(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"#<#{self.class} ready=#{!@queue.empty?}>"
|
15
|
+
end
|
16
|
+
|
17
|
+
def accept_response(response_msg)
|
18
|
+
@queue.push(response_msg)
|
19
|
+
@queue.close
|
20
|
+
end
|
21
|
+
|
22
|
+
def dispatch(&block)
|
23
|
+
if @response_msg ||= @queue.pop
|
24
|
+
InvocationResponse.new(@response_msg).unpack!(&block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :login_protocol, :procedure_protocol, :servicename, :connect_timeout, :procedure_timeout, :connection_pool, :requests_queue
|
30
|
+
|
31
|
+
def initialize(options = {})
|
32
|
+
@login_protocol = options[:login_protocol]||LOGIN_PROTOCOL
|
33
|
+
@procedure_protocol = options[:procedure_protocol]||PROCEDURE_PROTOCOL
|
34
|
+
@servicename = options[:servicename]||SERVICE_NAME
|
35
|
+
@connect_timeout = options[:connect_timeout]||CONNECT_TIMEOUT # timeout (secs) or Nil for authentication (default=8)
|
36
|
+
@procedure_timeout = options[:procedure_timeout]||PROCEDURE_TIMEOUT # timeout (secs) or Nil for procedure call (default=8)
|
37
|
+
@requests_queue = Queue.new
|
38
|
+
@connection_pool = configure_pool(options)
|
39
|
+
# You can create the connection to any of the nodes in the database cluster and your stored procedure will be routed appropriately. In fact, you can create connections to multiple nodes on the server and your subsequent requests will be distributed to the various connections.
|
40
|
+
@connection_pool.each do |connection|
|
41
|
+
Thread.new do
|
42
|
+
begin
|
43
|
+
while async_call = @requests_queue.pop
|
44
|
+
response_msg = connection.call_procedure(*async_call.args)
|
45
|
+
async_call.accept_response(response_msg)
|
46
|
+
end
|
47
|
+
rescue ThreadError
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def async_call_procedure(procedure, *parameters)
|
54
|
+
AsyncCall.new(procedure, *parameters).tap do |async_call|
|
55
|
+
@requests_queue.push(async_call)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def call_procedure(*args, &block)
|
60
|
+
async_call_procedure(*args).dispatch(&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def ping
|
64
|
+
call_procedure("@Ping")
|
65
|
+
end
|
66
|
+
|
67
|
+
def benchmark(cycle = 1000)
|
68
|
+
Helper.benchmark(cycle) {ping} # call @Ping - system stored procedure & dispatch
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def configure_pool(options)
|
74
|
+
cluster = case options[:cluster]
|
75
|
+
when nil then {DEFAULT_HOSTNAME => 0} # default hostname "localhost"
|
76
|
+
when String then {options[:cluster] => 0}
|
77
|
+
when Array then Hash[options[:cluster].map {|node| [node, 0]}]
|
78
|
+
when Hash then options[:cluster].transform_values {|cnum| cnum.to_i.abs}
|
79
|
+
else
|
80
|
+
raise(TypeError, "Cluster definition has to be Hash, Array, String or Nil (default)")
|
81
|
+
end
|
82
|
+
|
83
|
+
zero_nodes = cluster.values.count {|cnum| cnum == 0} # non defined connection number
|
84
|
+
if zero_nodes > 0
|
85
|
+
connections = options[:connections]||1
|
86
|
+
connections -= cluster.values.sum # user defined connections
|
87
|
+
connections = zero_nodes if zero_nodes > connections
|
88
|
+
for_each, extra = *connections.divmod(zero_nodes)
|
89
|
+
cluster.transform_values!.with_index do |cnum, i|
|
90
|
+
cnum > 0 ? cnum : (i < extra ? for_each + 1 : for_each)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
cluster.map do |node, per_node|
|
95
|
+
uri = URI.parse("//#{node}")
|
96
|
+
host = uri.host
|
97
|
+
port = uri.port||DEFAULT_PORT # default TCP port 21212
|
98
|
+
username = uri.user||options[:username]||"" # authentication user name for connection or Nil
|
99
|
+
password = uri.password||options[:password]||"" # authentication password for connection or Nil
|
100
|
+
per_node.times.map {Connection.new(self, host, port, username, password)}
|
101
|
+
end.flatten
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|