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,24 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class Timestamp < Long
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def pack(val)
|
7
|
+
# All dates are represented on the wire as Long values. This signed number represents the number of microseconds before or after Jan. 1 1970 00:00:00 GMT, the Unix epoch. Note that the units are microseconds, not milliseconds.
|
8
|
+
val = case val
|
9
|
+
when ::Integer then val
|
10
|
+
when ::Time then val.to_i*1000000 + val.usec # Microseconds
|
11
|
+
end
|
12
|
+
super(val)
|
13
|
+
end
|
14
|
+
|
15
|
+
def unpack(bytes)
|
16
|
+
if unpacked = super(bytes)
|
17
|
+
Time.at(unpacked/1000000.to_f) # Microseconds
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class Geography < Basic
|
4
|
+
DIRECTIVE = 'a'
|
5
|
+
NULL_INDICATOR = -1 # SQL NULL indicator for object type serializations
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def pack(polygon)
|
9
|
+
if polygon.nil?
|
10
|
+
# Like strings, GeographyValue begins with a 4-byte integer storing the number of bytes of raw data, followed by the raw data itself. The NULL GeographyValue has a length of -1 followed by 0 (zero) bytes of data. Unlike strings, there are no zero byte GeographyValue data values.
|
11
|
+
Integer.pack(self::NULL_INDICATOR)
|
12
|
+
else
|
13
|
+
raw_data =
|
14
|
+
Byte.pack(0) + # The first byte, byte0, is an encoding version, which tells whether certain fields need to be initialized by the Execution Engine. This is initially zero (0) and should be maintained on read.
|
15
|
+
Byte.pack(1) + # The next byte, byte 1, is internal. It should be initially 1, and should be maintained on read.
|
16
|
+
Byte.pack(polygon.has_holes? ? 1 : 0) + # The next byte, byte 2, is 1 if the polygon has holes and 0 if it does not.
|
17
|
+
Integer.pack(polygon.rings.size) + # The next four bytes, bytes 3, 4, 5 and 6, comprise a 32 bit integer which gives the number of rings. Call this value `NRINGS`
|
18
|
+
polygon.transform_rings.map do |ring| # NRINGS ring representations
|
19
|
+
Byte.pack(0) + # The first byte of a ring tells if the ring is initialized. It is initially zero (0) and should be maintained on read.
|
20
|
+
Integer.pack(ring.points.size) + # The next 4 bytes are a 32-bit integer containing the number of vertices in the ring. Call this number `NVERTS`.
|
21
|
+
ring.points.map do |point| #The next `NVERTS*3*8` bytes are `NVERTS` triples of double precision floating point numbers, in the order `X`, `Y` and `Z`.
|
22
|
+
xyz = point.to_XYZPoint
|
23
|
+
xyz.map {|double| Float.pack(double)}
|
24
|
+
end.join +
|
25
|
+
([0]*38).pack('c38') # The next 38 bytes contain a bounding box and some internal fields. They should all be initially zero (0) and should be maintained on read.
|
26
|
+
end.join +
|
27
|
+
([0]*33).pack('c33') # The next 33 bytes, after all the vertices, should be initially zero (0) and should be maintained on read.
|
28
|
+
raw_data.prepend(Integer.pack(raw_data.size))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def unpack(bytes)
|
33
|
+
if (length = Integer.unpack(bytes)) && (length != self::NULL_INDICATOR)
|
34
|
+
Byte.unpack(bytes) # The first byte, byte 0, is an encoding version, which tells whether certain fields need to be initialized by the Execution Engine. This is initially zero (0) and should be maintained on read.
|
35
|
+
Byte.unpack(bytes) # The next byte, byte 1, is internal. It should be initially 1, and should be maintained on read
|
36
|
+
has_holes = Byte.unpack(bytes) # The next byte, byte 2, is 1 if the polygon has holes and 0 if it does not.
|
37
|
+
rings_size = Integer.unpack(bytes)
|
38
|
+
polygon = Meta::Geography::Polygon.new
|
39
|
+
rings_size.times do
|
40
|
+
ring = Meta::Geography::Ring.new
|
41
|
+
Byte.unpack(bytes) # Ring is initialized. It is initially zero (0) and should be maintained on read.
|
42
|
+
verticles_size = Integer.unpack(bytes)
|
43
|
+
verticles_size.times do
|
44
|
+
x = Float.unpack(bytes)
|
45
|
+
y = Float.unpack(bytes)
|
46
|
+
z = Float.unpack(bytes)
|
47
|
+
point = Meta::Geography::Point.from_XYZPoint(x, y, z)
|
48
|
+
ring.add_point(point)
|
49
|
+
end
|
50
|
+
bytes.read(38).unpack1('c38') # Blob of zeros. 38 bytes contain a bounding box and some internal fields. They should all be initially zero (0) and should be maintained on read.
|
51
|
+
polygon.add_ring(ring)
|
52
|
+
end
|
53
|
+
bytes.read(33).unpack1('c33') # Blob of zeros. The next 33 bytes, after all the vertices, should be initially zero (0) and should be maintained on read.
|
54
|
+
polygon.transform_rings! if has_holes == 1
|
55
|
+
polygon
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class String < Varbinary
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def unpack(bytes)
|
7
|
+
if unpacked = super(bytes)
|
8
|
+
unpacked.force_encoding("utf-8")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def convert_input(val)
|
13
|
+
val.to_s.encode("utf-8")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class Varbinary < Basic
|
4
|
+
DIRECTIVE = 'a'
|
5
|
+
NULL_INDICATOR = -1 # SQL NULL indicator for object type serializations
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def pack(val)
|
9
|
+
if val.nil?
|
10
|
+
Integer.pack(self::NULL_INDICATOR)
|
11
|
+
else
|
12
|
+
val = convert_input(val)
|
13
|
+
Integer.pack(val.bytesize) + val
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def unpack(bytes)
|
18
|
+
length = Integer.unpack(bytes)
|
19
|
+
case length
|
20
|
+
when self::NULL_INDICATOR then nil
|
21
|
+
when 0 then ::String.new
|
22
|
+
else
|
23
|
+
bytes.read(length).unpack1("#{self::DIRECTIVE}#{length}")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert_input(val)
|
28
|
+
val.to_s.encode("ascii-8bit")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class Complex < self
|
4
|
+
end
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'ruby_volt/data_type/complex/volt_table'
|
9
|
+
require 'ruby_volt/data_type/complex/serializable_exception'
|
10
|
+
require 'ruby_volt/data_type/complex/parameter_set'
|
11
|
+
require 'ruby_volt/data_type/complex/parameter'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class Parameter < Complex
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def pack(val)
|
7
|
+
dataType = voltDataType(val)
|
8
|
+
WireTypeInfo.pack(dataType) + dataType.pack(val)
|
9
|
+
end
|
10
|
+
|
11
|
+
def unpack(bytes)
|
12
|
+
if dataType = WireTypeInfo.unpack(bytes)
|
13
|
+
dataType.unpack(bytes)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class ParameterSet < Complex
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def pack(*vals)
|
7
|
+
params_count = vals.size
|
8
|
+
Short.pack(params_count) + vals.map {|val| Parameter.pack(val)}.join
|
9
|
+
end
|
10
|
+
|
11
|
+
def unpack(bytes)
|
12
|
+
params_count = Short.unpack(bytes)
|
13
|
+
params_count.times.map {Parameter.unpack(bytes)}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class SerializableException < Basic
|
4
|
+
extend Extensions::CodeValuesHash
|
5
|
+
hash_codes [1, EEException],
|
6
|
+
[2, SQLException],
|
7
|
+
[3, ConstraintFailureException]
|
8
|
+
|
9
|
+
DIRECTIVE = 'a'
|
10
|
+
NULL_INDICATOR = 0 # The ordinal will not be present if the exception's length is 0.
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def pack(val, body = nil)
|
14
|
+
if val.nil?
|
15
|
+
Integer.pack(self::NULL_INDICATOR)
|
16
|
+
else
|
17
|
+
ordinal = val.is_a?(::Integer) ? val : code_values[val]
|
18
|
+
if body
|
19
|
+
Integer.pack(1 + body.bytesize) + Byte.pack(ordinal) + body # Exception ordinal (1 byte) + opaque length
|
20
|
+
else
|
21
|
+
Integer.pack(1) + Byte.pack(ordinal) # Exception ordinal (1 byte)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def unpack(bytes)
|
27
|
+
if (length = Integer.unpack(bytes)) && length > 0
|
28
|
+
ordinal = Byte.unpack(bytes)
|
29
|
+
body_size = length - 1
|
30
|
+
if body_size == 0 # Ordinal only, no body
|
31
|
+
[codes[ordinal], nil]
|
32
|
+
else
|
33
|
+
[codes[ordinal], bytes.read(body_size).unpack1("#{self::DIRECTIVE}#{body_size}")]
|
34
|
+
end
|
35
|
+
else
|
36
|
+
[nil, nil]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class VoltTable < Complex
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def pack(columns, rows)
|
7
|
+
# The "Table Metadata Length" stores the length in bytes of the contents of the table from byte 8 (the end of the metadata length field) all the way to the end of the "Column Names" sequence. NOTE: It does not include the row count value. See below for an example.
|
8
|
+
status_code = ProcedureCallStatusCode.pack(0) # OK Status code
|
9
|
+
columns_sequence = pack_columns(columns) # Columns data
|
10
|
+
rows_sequence = pack_rows(columns, rows) # Rows data
|
11
|
+
table_metadata_length = status_code.bytesize + columns_sequence.bytesize # Table Metadata Length
|
12
|
+
total_table_length = table_metadata_length + rows_sequence.bytesize # Total Table Length
|
13
|
+
[Integer.pack(total_table_length),
|
14
|
+
Integer.pack(table_metadata_length),
|
15
|
+
status_code,
|
16
|
+
columns_sequence,
|
17
|
+
rows_sequence].join
|
18
|
+
end
|
19
|
+
|
20
|
+
def unpack(bytes, &block)
|
21
|
+
table_info = unpack_table_info(bytes)
|
22
|
+
columns = unpack_columns(bytes)
|
23
|
+
rows = unpack_rows(columns, bytes, &block)
|
24
|
+
[*table_info, columns, rows]
|
25
|
+
end
|
26
|
+
|
27
|
+
def unpack_table_info(bytes)
|
28
|
+
[Integer.unpack(bytes), # total_table_length
|
29
|
+
Integer.unpack(bytes), # table_metadata_length
|
30
|
+
ProcedureCallStatusCode.unpack(bytes)] # status_code
|
31
|
+
end
|
32
|
+
|
33
|
+
def pack_columns(columns = []) # [["column", DataType]] ex.: [["city", :String], ["population", :Long]]
|
34
|
+
# The size of the "Column Types" and "Column Names" sequences is expected to equal the value stored in "Column Count".
|
35
|
+
# Column names are limited to the ASCII character set. Strings in row values are still UTF-8 encoded.
|
36
|
+
# Values with 4-byte (integer) length fields are signed and are limited to a max of 1 megabyte.
|
37
|
+
col_types, col_names = [], []
|
38
|
+
columns.each do |col|
|
39
|
+
col_name, dataType = *col
|
40
|
+
col_name = "modified_tuples" if col_name == ""
|
41
|
+
dataType = classifyDataType(dataType) unless dataType < DataType
|
42
|
+
col_types << WireTypeInfo.pack(dataType)
|
43
|
+
col_names << Varbinary.pack(col_name) # ASCII-8bit
|
44
|
+
end
|
45
|
+
Short.pack(columns.size) + col_types.join + col_names.join
|
46
|
+
end
|
47
|
+
|
48
|
+
def unpack_columns(bytes)
|
49
|
+
columns_count = Short.unpack(bytes)
|
50
|
+
columns = columns_count.times.map do
|
51
|
+
WireTypeInfo.unpack(bytes)
|
52
|
+
end
|
53
|
+
columns.map do |dataType|
|
54
|
+
col_name = Varbinary.unpack(bytes) # ASCII-8bit
|
55
|
+
col_name = "modified_tuples" if col_name == ""
|
56
|
+
[col_name, dataType]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def pack_rows(columns, rows = []) # [[val1, val2, ...]]
|
61
|
+
# Each row is preceded by a 4 byte integer that holds the length of the row not including the length. For example, if a row is a single 4-byte integer column, the value of this length prefix will be 4. Row size is artificially restricted to 2 megabytes.
|
62
|
+
# The body of the row is packed array of values. The value at index i is is of type specified by the column type field for index i.
|
63
|
+
Integer.pack(rows.size) + # Row count
|
64
|
+
rows.map do |row_values|
|
65
|
+
rlength = 0
|
66
|
+
row_values.map.with_index do |val, index|
|
67
|
+
dataType = columns[index][1]
|
68
|
+
packed = dataType.pack(val)
|
69
|
+
rlength += packed.bytesize
|
70
|
+
packed
|
71
|
+
end.join.prepend(Integer.pack(rlength))
|
72
|
+
end.join
|
73
|
+
end
|
74
|
+
|
75
|
+
def unpack_rows(columns, bytes, &block) # Block for parsing on fly
|
76
|
+
rows_count = Integer.unpack(bytes)
|
77
|
+
rows = []
|
78
|
+
rows_count.times do
|
79
|
+
Integer.unpack(bytes) # Bytesize of row length
|
80
|
+
row = columns.map do |c|
|
81
|
+
dataType = c[1]
|
82
|
+
dataType.unpack(bytes)
|
83
|
+
end
|
84
|
+
if block_given?
|
85
|
+
yield(row)
|
86
|
+
else
|
87
|
+
rows << row
|
88
|
+
end
|
89
|
+
end
|
90
|
+
rows
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RubyVolt
|
2
|
+
class DataType
|
3
|
+
class Array < Compound
|
4
|
+
# Arrays are represented as Byte value indicating the wire type of the elements and a 2 byte Short value indicating the number of elements in the array, followed by the specified number of elements. The length preceding value for the TINYINT (byte) type is length preceded by a 4 byte integer instead of a 2 byte short. This important exception allows large quantities of binary or string data to be passed as a byte array, and allows the serialization of and array of TINYINTs (bytes) to match the serialization of VARBINARY. Each array is serialized according to its type (Strings as Strings, VoltTables as VoltTables, Integers as Integers). Arrays are only present as parameters in parameter sets. Note that it is possible to pass an array of arrays of bytes if they are serialized as an array of VARBINARY types.
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def pack(val = []) # First element is a DataType indicator (DataType itself, Integer, String/Symbol)
|
8
|
+
array = val[1..-1]
|
9
|
+
dataType = classifyDataType(val[0])
|
10
|
+
unless dataType # No indicator recognized
|
11
|
+
array = val
|
12
|
+
dataType = autodetect_dataType(array)
|
13
|
+
end
|
14
|
+
countDataType = (dataType <= Byte) ? Integer : Short # The length preceding value for the TINYINT (byte) type is length preceded by a 4 byte integer instead of a 2 byte short.
|
15
|
+
WireTypeInfo.pack(dataType) + countDataType.pack(array.size) + array.map {|e| dataType.pack(e)}.join
|
16
|
+
end
|
17
|
+
|
18
|
+
def unpack(bytes)
|
19
|
+
if dataType = WireTypeInfo.unpack(bytes)
|
20
|
+
array = [dataType]
|
21
|
+
countDataType = (dataType <= Byte) ? Integer : Short
|
22
|
+
array_size = countDataType.unpack(bytes)
|
23
|
+
array_size.times do
|
24
|
+
array << dataType.unpack(bytes)
|
25
|
+
end
|
26
|
+
array
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def autodetect_dataType(array)
|
31
|
+
if array[0].is_a?(::Integer)
|
32
|
+
max_int = array.select {|e| e.is_a?(::Integer)}.max
|
33
|
+
voltDataType(max_int)
|
34
|
+
else
|
35
|
+
voltDataType(array[0])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|