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,150 @@
1
+ require 'ruby_volt/message'
2
+ require 'ruby_volt/response'
3
+
4
+ module RubyVolt
5
+ class Connection
6
+ attr_reader :host, :port, :socket, :login_data
7
+
8
+ def initialize(base, host, port, username, password)
9
+ @host, @port = host, port
10
+ @mutex = Mutex.new # Semaphore
11
+ @opaque = Helper.uniq_bytes(8)
12
+ define_singleton_method :base do
13
+ base
14
+ end
15
+ define_singleton_method :login! do
16
+ request = LoginMessage.new(login_protocol, servicename, username, password)
17
+ response_msg = dialog!(request, connect_timeout)
18
+ response = LoginResponse.new(response_msg).unpack!
19
+ @login_data = response.data
20
+ if logged_in?
21
+ puts "=== VoltDB wired [#{host}:#{port}]: connection_id=#{login_data[:connection_id]}" if logged_in?
22
+ end
23
+ end
24
+ establish!
25
+ end
26
+
27
+ def login_protocol
28
+ base.login_protocol
29
+ end
30
+
31
+ def procedure_protocol
32
+ base.procedure_protocol
33
+ end
34
+
35
+ def servicename
36
+ base.servicename
37
+ end
38
+
39
+ def connect_timeout
40
+ base.connect_timeout
41
+ end
42
+
43
+ def procedure_timeout
44
+ base.procedure_timeout
45
+ end
46
+
47
+ def inspect
48
+ "#<#{self.class.name} [#{host}:#{port}]: server_host_id=#{login_data[:server_host_id]} connection_id=#{login_data[:connection_id]} login_protocol=#{login_protocol} procedure_protocol=#{procedure_protocol}>"
49
+ end
50
+
51
+ def establish!
52
+ close!
53
+ attempts = 0
54
+ begin
55
+ @socket = ::Socket.tcp(host, port, nil, nil, {:connect_timeout => connect_timeout})
56
+ rescue ::SystemCallError => e
57
+ warn "#{e.class}: #{e}. Reinitializing connection..." unless attempts > 0
58
+ attempts += 1
59
+ retry
60
+ else
61
+ login!
62
+ end
63
+ end
64
+
65
+ def socket_open?
66
+ socket && !socket.closed?
67
+ end
68
+
69
+ def close!
70
+ @login_data = nil
71
+ if socket
72
+ begin
73
+ socket.close
74
+ rescue
75
+ ensure
76
+ @socket = nil
77
+ end
78
+ end
79
+ end
80
+
81
+ def require_login!
82
+ establish! unless logged_in?
83
+ end
84
+
85
+ def logged_in?
86
+ login_data && login_data[:auth_result] == 0
87
+ end
88
+
89
+ def unavailable?
90
+ !logged_in?||!socket_open?
91
+ end
92
+
93
+ def available?
94
+ !unavailable?
95
+ end
96
+
97
+ def call_procedure(procedure, client_data = @opaque, *parameters)
98
+ # The procedure invocation request contains the procedure to be called by name, and the serialized parameters to the procedure. The message also includes an opaque 8 byte client data value which will be returned with the response, and can be used by the client to correlate requests with responses.
99
+ @mutex.synchronize do
100
+ require_login!
101
+ request = InvocationRequest.new(procedure_protocol, procedure, client_data, *parameters)
102
+ dialog!(request, procedure_timeout) # Byte string
103
+ end
104
+ end
105
+
106
+ def ping
107
+ call_procedure("@Ping")
108
+ end
109
+
110
+ def benchmark(cycle = 1000)
111
+ Helper.benchmark(cycle) {ping} # call @Ping - system stored procedure
112
+ end
113
+
114
+ private
115
+
116
+ def dialog!(request, timeout = nil)
117
+ begin
118
+ socket.read(socket.nread) if socket_open? && socket.nread > 0 # Flush data waiting to be read
119
+ socket.send(request.msg, 0) # Starting dialog
120
+ io_select(:read, ReadTimeout, timeout) do # Waiting socket to be readable
121
+ length = DataType::Integer.unpack(socket) # Unpacking 32-bit integer of message length
122
+ readmsg(length) # Receiving message body
123
+ end
124
+ rescue ::SystemCallError, SocketDialogTimeout, ConnectionLostStatusCode => e
125
+ warn "#{e.class}: #{e}."
126
+ establish! unless request.is_a?(LoginMessage)
127
+ retry
128
+ end
129
+ end
130
+
131
+ def readmsg(length)
132
+ msg = ::String.new(:capacity => length)
133
+ socket.read(length, msg) # Reading message body
134
+ ReadPartial.new(msg)
135
+ end
136
+
137
+ def io_select(action, exception_class, timeout = DEFAULT_TIMEOUT, &block)
138
+ ready = case action
139
+ when :read then IO.select([socket], nil, nil, timeout) # ready for read
140
+ when :send then IO.select(nil, [socket], nil, timeout) # ready for write
141
+ when :exception then IO.select(nil, nil, [socket], timeout) # ready for exceptions
142
+ end
143
+ # 1) IO.select takes a set of sockets and waits until it's possible to read or write with them (or if error happens). It returns sockets event happened with.
144
+ # 2) array contains sockets that are checked for events. In your case you specify only sockets for reading.
145
+ # 3) IO.select returns an array of arrays of sockets. Element 0 contains sockets you can read from, element 1 - sockets you can write to and element 2 - sockets with errors.
146
+ ready ? yield : raise(exception_class, "#{timeout} sec data #{action} timed out")
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,50 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class << self
4
+ def voltDataType(val) # Ruby data type => Wire::DataType conversion
5
+ case val
6
+ when ::Array then Array
7
+ when NilClass then Null # Byte::NULL_INDICATOR
8
+ when ::Integer then
9
+ case val.bit_length
10
+ when (0..7) then Byte
11
+ when (8..15) then Short
12
+ when (16..31) then Integer
13
+ else
14
+ Long if val.bit_length >= 32
15
+ end
16
+ when ::Float then Float
17
+ when ::String then
18
+ val.encoding.name == "UTF-8" ? String : Varbinary # See "sometext".encoding
19
+ when ::Time then Timestamp
20
+ when ::BigDecimal then Decimal
21
+ when Meta::Geography::Point then GeographyPoint
22
+ when Meta::Geography::Polygon then Geography
23
+ end
24
+ end
25
+
26
+ def classifyDataType(val)
27
+ case val
28
+ when Class then val if val < DataType
29
+ when ::String, ::Symbol then
30
+ begin
31
+ DataType.const_get(val)
32
+ rescue
33
+ nil
34
+ end
35
+ end
36
+ end
37
+
38
+ def testpacking(*vals)
39
+ unpack(ReadPartial.new(pack(*vals)))
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ require 'ruby_volt/data_type/extensions'
47
+ require 'ruby_volt/data_type/basic'
48
+ require 'ruby_volt/data_type/compound'
49
+ require 'ruby_volt/data_type/complex'
50
+ require 'ruby_volt/data_type/app_data_type'
@@ -0,0 +1,24 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class AppDataType < Byte
4
+ extend Extensions::CodeValuesHash
5
+
6
+ class << self
7
+ def pack(val)
8
+ val = val.is_a?(::Integer) ? val : code_values[val]
9
+ super(val)
10
+ end
11
+
12
+ def unpack(bytes)
13
+ if unpacked = super(bytes)
14
+ codes[unpacked]
15
+ end
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+
23
+ require 'ruby_volt/data_type/app_data_type/procedure_call_status_code'
24
+ require 'ruby_volt/data_type/app_data_type/wire_type_info'
@@ -0,0 +1,14 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class ProcedureCallStatusCode < AppDataType
4
+ hash_codes [1, SuccessStatusCode], # SUCCESS
5
+ [-1, UserAbortStatusCode], # USER_ABORT
6
+ [-2, GracefulFailureStatusCode], # GRACEFUL_FAILURE
7
+ [-3, UnexpectedFailureStatusCode], # UNEXPECTED_FAILURE
8
+ [-4, ConnectionLostStatusCode] # CONNECTION_LOST
9
+
10
+
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class WireTypeInfo < AppDataType
4
+ hash_codes [-99, Array], # ARRAY
5
+ [1, Null], # NULL
6
+ [3, Byte], # TINYINT
7
+ [4, Short], # SMALLINT
8
+ [5, Integer], # INTEGER
9
+ [6, Long], # BIGINT
10
+ [8, Float], # FLOAT
11
+ [9, String], # STRING
12
+ [11, Timestamp], # TIMESTAMP
13
+ [22, Decimal], # DECIMAL
14
+ [25, Varbinary], # VARBINARY
15
+ [26, GeographyPoint], # GEOGRAPHY_POINT
16
+ [27, Geography] # GEOGRAPHY
17
+
18
+
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class Basic < self
4
+
5
+ class << self
6
+ def intToBytes(length, value)
7
+ bitlength = length*8
8
+ div = nil
9
+ x = [[32, 'N'], [16, 'n'], [8, 'C']].find {|a| (div = bitlength.divmod(a[0])) && div[0] != 0 && div[1] == 0}
10
+ b = (2**x[0])-1
11
+ ([value & b] + (2..div[0]).map {|i| (value >> x[0]*(i-1)) & b}).reverse.pack(x[1]*div[0])
12
+ end
13
+
14
+ def bytesToInt(length, bytes) # bytes = array of 8-bit unsigned
15
+ bytes = bytes.unpack("C#{length}")
16
+ most_significant_bit = 1 << 7
17
+ negative = (bytes[0] & most_significant_bit) != 0
18
+ unscaled_value = -(bytes[0] & most_significant_bit) << length*8-8
19
+ # Clear the highest bit
20
+ # Unleash the powers of the butterfly
21
+ bytes[0] &= ~most_significant_bit
22
+ # Get the 2's complement
23
+ (0..length-1).each {|i| unscaled_value += bytes[i] << ((length-1 - i) * 8)}
24
+ unscaled_value * -1 if negative
25
+ unscaled_value
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+
33
+ require 'ruby_volt/data_type/basic/fixed_size'
34
+ require 'ruby_volt/data_type/basic/varbinary'
35
+ require 'ruby_volt/data_type/basic/string'
36
+ require 'ruby_volt/data_type/basic/geography'
@@ -0,0 +1,26 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class FixedSize < Basic
4
+
5
+ class << self
6
+ def pack(val)
7
+ val ||= self::NULL_INDICATOR
8
+ [val].pack(self::DIRECTIVE)
9
+ end
10
+
11
+ def unpack(bytes)
12
+ unpacked = bytes.read(self::LENGTH).unpack1(self::DIRECTIVE)
13
+ unpacked unless unpacked == self::NULL_INDICATOR
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+
21
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type'
22
+ require 'ruby_volt/data_type/basic/fixed_size/float'
23
+ require 'ruby_volt/data_type/basic/fixed_size/geography_point'
24
+ require 'ruby_volt/data_type/basic/fixed_size/timestamp' # Date
25
+ require 'ruby_volt/data_type/basic/fixed_size/decimal'
26
+ require 'ruby_volt/data_type/basic/fixed_size/null'
@@ -0,0 +1,45 @@
1
+ require 'bigdecimal'
2
+ require 'bigdecimal/util'
3
+
4
+ module RubyVolt
5
+ class DataType
6
+ class Decimal < FixedSize
7
+ DIRECTIVE = 'C16'
8
+ NULL_INDICATOR = -170141183460469231731687303715884105728 # SQL NULL indicator for object type serializations
9
+ LENGTH = 16
10
+ PRECISION = 38
11
+ SCALE = 12
12
+
13
+ class << self
14
+ def pack(val)
15
+ ((val == self::NULL_INDICATOR) || val.nil?) ? intToBytes(self::LENGTH, self::NULL_INDICATOR) : serialize(val)
16
+ end
17
+
18
+ def unpack(bytes)
19
+ if (unscaled = bytesToInt(self::LENGTH, bytes.read(self::LENGTH))) && (unscaled != self::NULL_INDICATOR)
20
+ unscaled = unscaled.to_s
21
+ scaled = unscaled.insert(unscaled.size - self::SCALE, ".")
22
+ BigDecimal(scaled)
23
+ end
24
+ end
25
+
26
+ def serialize(val)
27
+ num = case val
28
+ when BigDecimal then val
29
+ else val.to_d
30
+ end
31
+ sign, digits, base, exponent = *num.split
32
+ scale = digits.size - exponent
33
+ precision = digits.size
34
+ raise(::ArgumentError, "Scale of this decimal is #{scale} and the max is #{self::SCALE}") if scale > self::SCALE
35
+ rest = precision - scale
36
+ raise(::ArgumentError, "Precision to the left of the decimal point is #{rest} and the max is #{self::PRECISION-self::SCALE}") if rest > 26
37
+ scale_factor = self::SCALE - scale
38
+ unscaled_int = sign * digits.to_i * base ** scale_factor # Unscaled integer
39
+ intToBytes(self::LENGTH, unscaled_int)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class Float < FixedSize
4
+ DIRECTIVE = 'G'
5
+ LENGTH = 8
6
+ NULL_INDICATOR = -1.7E308 # SQL NULL indicator for object type serializations
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,28 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class GeographyPoint < FixedSize
4
+ DIRECTIVE = "G2"
5
+ LENGTH = 16
6
+
7
+ class << self
8
+ def pack(point)
9
+ # A GeographyPointValue's wire protocol representation is simply two double precision numbers in sequence. The first is the longitude, and must be in the range -180 ≤ longitude ≤ 180. The second is the latitude, and must be in the range -90 ≤ latitude ≤ 90. The null GeographyPoint value has longitude and latitude both equal to 360.0.
10
+ if point.nil?
11
+ lng, lat = 360.0, 360.0
12
+ else
13
+ lng, lat = point.lng, point.lat
14
+ end
15
+ [lng, lat].pack(self::DIRECTIVE)
16
+ end
17
+
18
+ def unpack(bytes)
19
+ lng, lat = *bytes.read(self::LENGTH).unpack(self::DIRECTIVE)
20
+ if (lng != 360.0) && (lat != 360.0)
21
+ Meta::Geography::Point.new(lng, lat)
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class IntegerType < FixedSize
4
+ # Reserved class
5
+
6
+ class << self
7
+ end
8
+
9
+ end
10
+ end
11
+ end
12
+
13
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/byte'
14
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/u_byte'
15
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/short'
16
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/u_short'
17
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/integer'
18
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/u_integer'
19
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/long'
20
+ require 'ruby_volt/data_type/basic/fixed_size/integer_type/u_long'
@@ -0,0 +1,9 @@
1
+ module RubyVolt
2
+ class DataType
3
+ class Byte < IntegerType
4
+ DIRECTIVE = 'c'
5
+ LENGTH = 1
6
+ NULL_INDICATOR = -128 # SQL NULL indicator for object type serializations
7
+ end
8
+ end
9
+ end