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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/LICENSE +20 -0
  4. data/README.md +282 -0
  5. data/lib/ruby_volt.rb +33 -0
  6. data/lib/ruby_volt/base.rb +105 -0
  7. data/lib/ruby_volt/connection.rb +150 -0
  8. data/lib/ruby_volt/data_type.rb +50 -0
  9. data/lib/ruby_volt/data_type/app_data_type.rb +24 -0
  10. data/lib/ruby_volt/data_type/app_data_type/procedure_call_status_code.rb +14 -0
  11. data/lib/ruby_volt/data_type/app_data_type/wire_type_info.rb +22 -0
  12. data/lib/ruby_volt/data_type/basic.rb +36 -0
  13. data/lib/ruby_volt/data_type/basic/fixed_size.rb +26 -0
  14. data/lib/ruby_volt/data_type/basic/fixed_size/decimal.rb +45 -0
  15. data/lib/ruby_volt/data_type/basic/fixed_size/float.rb +9 -0
  16. data/lib/ruby_volt/data_type/basic/fixed_size/geography_point.rb +28 -0
  17. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type.rb +20 -0
  18. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/byte.rb +9 -0
  19. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/integer.rb +9 -0
  20. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/long.rb +9 -0
  21. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/short.rb +9 -0
  22. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_byte.rb +9 -0
  23. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_integer.rb +9 -0
  24. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_long.rb +9 -0
  25. data/lib/ruby_volt/data_type/basic/fixed_size/integer_type/u_short.rb +9 -0
  26. data/lib/ruby_volt/data_type/basic/fixed_size/null.rb +13 -0
  27. data/lib/ruby_volt/data_type/basic/fixed_size/timestamp.rb +24 -0
  28. data/lib/ruby_volt/data_type/basic/geography.rb +61 -0
  29. data/lib/ruby_volt/data_type/basic/string.rb +19 -0
  30. data/lib/ruby_volt/data_type/basic/varbinary.rb +34 -0
  31. data/lib/ruby_volt/data_type/complex.rb +11 -0
  32. data/lib/ruby_volt/data_type/complex/parameter.rb +20 -0
  33. data/lib/ruby_volt/data_type/complex/parameter_set.rb +19 -0
  34. data/lib/ruby_volt/data_type/complex/serializable_exception.rb +43 -0
  35. data/lib/ruby_volt/data_type/complex/volt_table.rb +96 -0
  36. data/lib/ruby_volt/data_type/compound.rb +8 -0
  37. data/lib/ruby_volt/data_type/compound/array.rb +42 -0
  38. data/lib/ruby_volt/data_type/extensions.rb +20 -0
  39. data/lib/ruby_volt/exceptions.rb +67 -0
  40. data/lib/ruby_volt/helper.rb +29 -0
  41. data/lib/ruby_volt/message.rb +29 -0
  42. data/lib/ruby_volt/message/invocation_request.rb +18 -0
  43. data/lib/ruby_volt/message/login_message.rb +17 -0
  44. data/lib/ruby_volt/meta.rb +6 -0
  45. data/lib/ruby_volt/meta/geography.rb +165 -0
  46. data/lib/ruby_volt/read_partial.rb +23 -0
  47. data/lib/ruby_volt/response.rb +24 -0
  48. data/lib/ruby_volt/response/invocation_response.rb +64 -0
  49. data/lib/ruby_volt/response/login_response.rb +27 -0
  50. data/lib/ruby_volt/version.rb +3 -0
  51. data/lib/ruby_volt/volt_table.rb +37 -0
  52. 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,6 @@
1
+ module RubyVolt
2
+ module Meta
3
+ end
4
+ end
5
+
6
+ require 'ruby_volt/meta/geography'
@@ -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