ruby_volt 0.0.5
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.
- 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
@@ -0,0 +1,20 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
module Extensions
|
4
|
+
|
5
|
+
module CodeValuesHash
|
6
|
+
attr_reader :codes, :code_values
|
7
|
+
|
8
|
+
def inherited(child)
|
9
|
+
child.instance_variable_set(:@codes, Hash[@codes.map {|a| [a[0], a[1].dup]}]) if instance_variable_defined?("@codes")
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash_codes(*arrays)
|
13
|
+
@codes ||= Hash[arrays]
|
14
|
+
@code_values ||= Hash[arrays.map(&:reverse)]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
# Local exceptions
|
3
|
+
|
4
|
+
class SocketDialogTimeout < ::Exception
|
5
|
+
end
|
6
|
+
|
7
|
+
class SendMsgTimeout < SocketDialogTimeout
|
8
|
+
end
|
9
|
+
|
10
|
+
class ReadTimeout < SocketDialogTimeout
|
11
|
+
end
|
12
|
+
|
13
|
+
# Login Message Exceptions
|
14
|
+
|
15
|
+
class AuthenticationRejected < ::Exception
|
16
|
+
end
|
17
|
+
|
18
|
+
class TooManyConnections < ::Exception
|
19
|
+
end
|
20
|
+
|
21
|
+
class TooLongToTransmitCredentials < ::Exception
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvalidLoginMessage < ::Exception
|
25
|
+
end
|
26
|
+
|
27
|
+
class ClientDataInvalid < ::Exception
|
28
|
+
end
|
29
|
+
|
30
|
+
# Procedure Call Status Codes
|
31
|
+
|
32
|
+
class ProcedureCallException < ::Exception
|
33
|
+
end
|
34
|
+
|
35
|
+
class SuccessStatusCode < ProcedureCallException
|
36
|
+
end
|
37
|
+
|
38
|
+
class UserAbortStatusCode < ProcedureCallException
|
39
|
+
end
|
40
|
+
|
41
|
+
class GracefulFailureStatusCode < ProcedureCallException
|
42
|
+
end
|
43
|
+
|
44
|
+
class UnexpectedFailureStatusCode < ProcedureCallException
|
45
|
+
end
|
46
|
+
|
47
|
+
class ConnectionLostStatusCode < ProcedureCallException
|
48
|
+
end
|
49
|
+
|
50
|
+
# Serializable Exceptions
|
51
|
+
|
52
|
+
class SerializableException < ::Exception
|
53
|
+
end
|
54
|
+
|
55
|
+
class EEException < SerializableException
|
56
|
+
# This is a generic failure in Volt. Should indicate a failure in the serv- er and not the application code. These should not occur in normal operation.
|
57
|
+
end
|
58
|
+
|
59
|
+
class SQLException < SerializableException
|
60
|
+
# This is the base class for all excep- tions that can occur during normal op- eration. This includes things like con- straint failures (unique, string length, not null) that are caught and handled correct by Volt.
|
61
|
+
end
|
62
|
+
|
63
|
+
class ConstraintFailureException < SerializableException
|
64
|
+
# This is a specialization of SQLExcep- tion for constraint failures during the execution of a stored procedure.
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
module Helper
|
3
|
+
|
4
|
+
def self.benchmark(cycle = 1000, &block)
|
5
|
+
start = Time.now
|
6
|
+
cycle.times { yield }
|
7
|
+
spend = Time.now - start
|
8
|
+
puts "Execution time: #{spend} sec. #{(cycle/spend).round(2)} TPS."
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.uniq_bytes(length = 8)
|
12
|
+
bytes = ::String.new(:capacity => length)
|
13
|
+
bytes <<
|
14
|
+
if length > 0
|
15
|
+
nsec = Time.now.nsec
|
16
|
+
int = DataType::Integer.pack(nsec)
|
17
|
+
if length/2 < 4
|
18
|
+
int.b[-length/2..-1]
|
19
|
+
else
|
20
|
+
int
|
21
|
+
end
|
22
|
+
else
|
23
|
+
::String.new
|
24
|
+
end
|
25
|
+
bytes << SecureRandom.random_bytes(length - bytes.bytesize)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class Message
|
3
|
+
attr_reader :msg, :protocol
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@protocol = args[0]
|
7
|
+
@msg = ::String.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def inspect
|
11
|
+
to_str.dump
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_str
|
15
|
+
msg
|
16
|
+
end
|
17
|
+
|
18
|
+
def wrap(&block)
|
19
|
+
msg << DataType::Byte.pack(protocol) # VoltDB Wire Protocol Version
|
20
|
+
yield(msg)
|
21
|
+
msg.prepend(DataType::Integer.pack(msg.bytesize)) # Message length preceded
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'ruby_volt/message/login_message'
|
29
|
+
require 'ruby_volt/message/invocation_request'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class InvocationRequest < Message
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
procedure, client_data = *args[1..2]
|
7
|
+
raise(ClientDataInvalid, "Client data 8 bytes awaiting") if client_data.bytesize != 8
|
8
|
+
parameters = args[3..-1]
|
9
|
+
wrap do
|
10
|
+
msg << DataType::String.pack(procedure) # Procedure name
|
11
|
+
msg << [client_data].pack('a8') # Opaque client data (8 bytes)
|
12
|
+
msg << DataType::ParameterSet.pack(*parameters) # Parameter set
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class LoginMessage < Message
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
servicename, username, password = *args[1..-1]
|
7
|
+
wrap do
|
8
|
+
msg << DataType::Byte.pack(protocol) if protocol > 0 # Password hash version SHA-1 / SHA-256
|
9
|
+
msg << DataType::String.pack(servicename) # Service name
|
10
|
+
msg << DataType::String.pack(username) # Username
|
11
|
+
msg << (protocol > 0 ? Digest::SHA2.digest(password) : Digest::SHA1.digest(password)) # Password
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
module Meta
|
3
|
+
module Geography
|
4
|
+
class Point
|
5
|
+
attr_reader :lng, :lat
|
6
|
+
|
7
|
+
# The wire protocol representation is similar to the S2 representation which is used in the Execution Engine. This format is different from the Java longitude/latitude format, so some explanation is in order. In Java a point is represented by a longitude and latitude. 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.
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def from_XYZPoint(x, y, z)
|
11
|
+
degreesPerRadian = 180.0/Math::PI
|
12
|
+
lngRadians = Math.atan2(y, x)
|
13
|
+
latRadians = Math.atan2(z, Math.sqrt(x * x + y * y))
|
14
|
+
lng = (lngRadians * degreesPerRadian).round(6)
|
15
|
+
lat = (latRadians * degreesPerRadian).round(6)
|
16
|
+
new(lng, lat)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(lng, lat)
|
21
|
+
raise(::ArgumentError, "Longitude must be in the range -180 ≤ longitude ≤ 180") unless lng >= -180 && lng <= 180
|
22
|
+
raise(::ArgumentError, "Latitude must be in the range -90 ≤ latitude ≤ 90") unless lat >= -90 && lat <= 90
|
23
|
+
@lng = lng
|
24
|
+
@lat = lat
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_XYZPoint
|
28
|
+
radiansPerDegree = (Math::PI/180.0) # A conversion factor.
|
29
|
+
latRadians = lat * radiansPerDegree # latitude is in degrees.
|
30
|
+
lngRadians = lng * radiansPerDegree # longitude is in degrees.
|
31
|
+
cosPhi = Math.cos(latRadians)
|
32
|
+
x = Math.cos(lngRadians) * cosPhi
|
33
|
+
y = Math.sin(lngRadians) * cosPhi
|
34
|
+
z = Math.sin(latRadians)
|
35
|
+
[x, y, z]
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
to_wkt
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"#{lng} #{lat}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_wkt
|
47
|
+
"POINT(#{to_s})"
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(other)
|
51
|
+
other.is_a?(Point) && (lng == other.lng) && (lat == other.lat)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
class Ring
|
57
|
+
attr_reader :points
|
58
|
+
|
59
|
+
def initialize(points = [])
|
60
|
+
@points = []
|
61
|
+
points.each {|point| add_point(point)}
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_point(point)
|
65
|
+
raise(::ArgumentError, "Point has to be represented by a pseudo container RubyVolt::Meta::Geography::Point") unless point.is_a?(Point)
|
66
|
+
@points << point unless @points.include?(point)
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect
|
70
|
+
to_wkt
|
71
|
+
end
|
72
|
+
|
73
|
+
def fork_reverse_points
|
74
|
+
Ring.new(reverse_points)
|
75
|
+
end
|
76
|
+
|
77
|
+
def reverse_points!
|
78
|
+
@points = reverse_points
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def reverse_points
|
83
|
+
points[1..-1].reverse.prepend(points[0])
|
84
|
+
end
|
85
|
+
|
86
|
+
def close_points
|
87
|
+
(points[-1] == points[0]) ? points : points + [points[0]]
|
88
|
+
end
|
89
|
+
|
90
|
+
def to_s
|
91
|
+
"(#{close_points.join(", ")})"
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_wkt
|
95
|
+
"LINESTRING#{to_s}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def ==(other)
|
99
|
+
other.is_a?(Ring) && (points.size == other.points.size) && eval(points.map.with_index {|point, index| point == other.points[index]}.join("&&"))||true
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
class Polygon
|
105
|
+
attr_reader :rings
|
106
|
+
|
107
|
+
# In the wire protocol the first ring is still the exterior boundary and subsequent rings are holes. However, in the wire protocol all rings, exterior and hole alike, must be counter clockwise, and the last point should not be equal to the first point.
|
108
|
+
|
109
|
+
def initialize(rings = [])
|
110
|
+
@rings = []
|
111
|
+
rings.each {|ring| add_ring(ring)}
|
112
|
+
end
|
113
|
+
|
114
|
+
def add_ring(ring)
|
115
|
+
raise(::ArgumentError, "LineString has to be represented by a pseudo container RubyVolt::Meta::Geography::Ring") unless ring.is_a?(Ring)
|
116
|
+
@rings << ring
|
117
|
+
end
|
118
|
+
|
119
|
+
def holes
|
120
|
+
has_holes? ? rings[1..-1] : []
|
121
|
+
end
|
122
|
+
|
123
|
+
def has_holes?
|
124
|
+
rings.size > 1
|
125
|
+
end
|
126
|
+
|
127
|
+
def fork_transform_rings!
|
128
|
+
Polygon.new(transform_rings)
|
129
|
+
end
|
130
|
+
|
131
|
+
def transform_rings!
|
132
|
+
@rings = transform_rings
|
133
|
+
self
|
134
|
+
end
|
135
|
+
|
136
|
+
def transform_rings
|
137
|
+
# To transform a ring [from Java] representation to wire protocol representation one must:
|
138
|
+
# • Remove the last vertex, which is the same as the first vertex,
|
139
|
+
# • Transform the coordinates to XYZPoint values, and
|
140
|
+
# • Reverse the order of the rings from the second to the end.
|
141
|
+
rings.map.with_index do |ring, index|
|
142
|
+
index > 0 ? ring.fork_reverse_points : ring
|
143
|
+
end.compact
|
144
|
+
end
|
145
|
+
|
146
|
+
def inspect
|
147
|
+
to_wkt
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_s
|
151
|
+
rings.join(", ")
|
152
|
+
end
|
153
|
+
|
154
|
+
def to_wkt
|
155
|
+
"POLYGON(#{to_s})"
|
156
|
+
end
|
157
|
+
|
158
|
+
def ==(other)
|
159
|
+
other.is_a?(Polygon) && (rings.size == other.rings.size) && eval(rings.map.with_index {|ring, index| ring == other.rings[index]}.join("&&"))||true
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class ReadPartial
|
3
|
+
def initialize(bytes)
|
4
|
+
@bytes = bytes
|
5
|
+
end
|
6
|
+
|
7
|
+
def inspect
|
8
|
+
"<#{self.class.name}: bytes=#{@bytes.bytesize}>"
|
9
|
+
end
|
10
|
+
|
11
|
+
def nread
|
12
|
+
@bytes.bytesize
|
13
|
+
end
|
14
|
+
|
15
|
+
def read(num)
|
16
|
+
exp = @bytes.byteslice(0, num)
|
17
|
+
@bytes.delete_prefix!(exp)
|
18
|
+
exp
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :recv, :read
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class Response
|
3
|
+
attr_reader :bytes, :data
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
@bytes = case args[0]
|
7
|
+
when ::IO, ReadPartial then args[0]
|
8
|
+
else ReadPartial.new(args[0]) # Partial reader
|
9
|
+
end
|
10
|
+
@data = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def wrap(&block)
|
14
|
+
# data[:length] = DataType::Integer.unpack(bytes) # Message length
|
15
|
+
data[:protocol] = DataType::Byte.unpack(bytes) # Protocol version
|
16
|
+
yield
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'ruby_volt/response/login_response'
|
24
|
+
require 'ruby_volt/response/invocation_response'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class InvocationResponse < Response
|
3
|
+
attr_reader :result
|
4
|
+
|
5
|
+
# An invocation response contains the results of the server's attempt to execute the stored procedure. The response includes optional fields and the first byte after the header is used to indicate which optional fields are present. The status string, application status string, and serializable exception are all optional fields. Bit 7 indicates the presence of a serializable exception, bit 6 indicates the presence of a status string, and bit 8 indicates the presence of an app status string. The serializable exception that can be included in some responses is currently not a part of the wire protocol. The exception length value should be used to skip exceptions if they are present. The status string is used to return any human readable information the server or stored procedure wants to return with the response. The app status code and app status string can be set by application code from within stored procedures and is returned with the response.
|
6
|
+
|
7
|
+
def unpack!(&block)
|
8
|
+
wrap do
|
9
|
+
data[:client_data] = bytes.read(8).unpack1('a8')
|
10
|
+
|
11
|
+
presentFields = DataType::Byte.unpack(bytes)
|
12
|
+
data[:present_fields] = presentFields
|
13
|
+
|
14
|
+
data[:status] = DataType::ProcedureCallStatusCode.unpack(bytes) # Status
|
15
|
+
|
16
|
+
if presentFields & (1 << 5) != 0 # Bit 6 indicates the presence of a status string
|
17
|
+
data[:status_string] = DataType::String.unpack(bytes) # Status string
|
18
|
+
end
|
19
|
+
|
20
|
+
data[:app_status] = DataType::Byte.unpack(bytes) # App status
|
21
|
+
|
22
|
+
if presentFields & (1 << 7) != 0 # Bit 8 indicates the presence of an app status string
|
23
|
+
data[:app_status_string] = DataType::String.unpack(bytes) # App status string
|
24
|
+
end
|
25
|
+
|
26
|
+
data[:cluster_round_trip_time] = DataType::Integer.unpack(bytes) # Cluster round trip time
|
27
|
+
|
28
|
+
if presentFields & (1 << 6) != 0 # Bit 7 indicates the presence of a serializable exception
|
29
|
+
data[:serialized_exception] = DataType::SerializableException.unpack(bytes) # Serialized exception
|
30
|
+
end
|
31
|
+
|
32
|
+
if data[:status] != SuccessStatusCode
|
33
|
+
raise(data[:status], data[:status_string])
|
34
|
+
end
|
35
|
+
|
36
|
+
if data[:serialized_exception]
|
37
|
+
raise(*data[:serialized_exception])
|
38
|
+
end
|
39
|
+
|
40
|
+
result_count = DataType::Short.unpack(bytes) # Result count
|
41
|
+
|
42
|
+
# VoltTables data
|
43
|
+
@result = result_count.times.map do |index|
|
44
|
+
table_info = DataType::VoltTable.unpack_table_info(bytes) # Total Table Length, Table Metadata Length, Status Code
|
45
|
+
columns = DataType::VoltTable.unpack_columns(bytes) # Columns data
|
46
|
+
volt_table = VoltTable.new(index, *table_info, columns)
|
47
|
+
|
48
|
+
# Rows data
|
49
|
+
DataType::VoltTable.unpack_rows(columns, bytes) do |row|
|
50
|
+
if block_given?
|
51
|
+
yield(data, volt_table, row)
|
52
|
+
else
|
53
|
+
volt_table.add_struct(row)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
volt_table
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|