sessionm-cassandra 1.0.0
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.
- data/CHANGELOG +135 -0
- data/Gemfile +8 -0
- data/LICENSE +202 -0
- data/Manifest +94 -0
- data/README.md +373 -0
- data/Rakefile +195 -0
- data/bin/cassandra_helper +16 -0
- data/conf/0.6/cassandra.in.sh +47 -0
- data/conf/0.6/log4j.properties +38 -0
- data/conf/0.6/schema.json +57 -0
- data/conf/0.6/storage-conf.xml +352 -0
- data/conf/0.7/cassandra.in.sh +46 -0
- data/conf/0.7/cassandra.yaml +336 -0
- data/conf/0.7/log4j-server.properties +41 -0
- data/conf/0.7/schema.json +57 -0
- data/conf/0.7/schema.txt +45 -0
- data/conf/0.8/cassandra.in.sh +41 -0
- data/conf/0.8/cassandra.yaml +61 -0
- data/conf/0.8/log4j-server.properties +40 -0
- data/conf/0.8/schema.json +72 -0
- data/conf/0.8/schema.txt +57 -0
- data/conf/1.0/cassandra.in.sh +41 -0
- data/conf/1.0/cassandra.yaml +415 -0
- data/conf/1.0/log4j-server.properties +40 -0
- data/conf/1.0/schema.json +72 -0
- data/conf/1.0/schema.txt +57 -0
- data/conf/1.1/cassandra.in.sh +41 -0
- data/conf/1.1/cassandra.yaml +567 -0
- data/conf/1.1/log4j-server.properties +44 -0
- data/conf/1.1/schema.json +72 -0
- data/conf/1.1/schema.txt +57 -0
- data/ext/cassandra_native.c +34 -0
- data/ext/extconf.rb +9 -0
- data/lib/cassandra/0.6/cassandra.rb +113 -0
- data/lib/cassandra/0.6/columns.rb +78 -0
- data/lib/cassandra/0.6/protocol.rb +91 -0
- data/lib/cassandra/0.6.rb +7 -0
- data/lib/cassandra/0.7/cassandra.rb +2 -0
- data/lib/cassandra/0.7/columns.rb +4 -0
- data/lib/cassandra/0.7/protocol.rb +5 -0
- data/lib/cassandra/0.7.rb +7 -0
- data/lib/cassandra/0.8/cassandra.rb +51 -0
- data/lib/cassandra/0.8/columns.rb +28 -0
- data/lib/cassandra/0.8/protocol.rb +10 -0
- data/lib/cassandra/0.8.rb +7 -0
- data/lib/cassandra/1.0/cassandra.rb +1 -0
- data/lib/cassandra/1.0/columns.rb +1 -0
- data/lib/cassandra/1.0/protocol.rb +1 -0
- data/lib/cassandra/1.0.rb +7 -0
- data/lib/cassandra/1.1/cassandra.rb +1 -0
- data/lib/cassandra/1.1/columns.rb +1 -0
- data/lib/cassandra/1.1/protocol.rb +1 -0
- data/lib/cassandra/1.1.rb +7 -0
- data/lib/cassandra/array.rb +8 -0
- data/lib/cassandra/batch.rb +41 -0
- data/lib/cassandra/cassandra.rb +1088 -0
- data/lib/cassandra/column_family.rb +3 -0
- data/lib/cassandra/columns.rb +172 -0
- data/lib/cassandra/comparable.rb +28 -0
- data/lib/cassandra/composite.rb +140 -0
- data/lib/cassandra/constants.rb +11 -0
- data/lib/cassandra/debug.rb +9 -0
- data/lib/cassandra/dynamic_composite.rb +96 -0
- data/lib/cassandra/helpers.rb +41 -0
- data/lib/cassandra/keyspace.rb +3 -0
- data/lib/cassandra/long.rb +58 -0
- data/lib/cassandra/mock.rb +525 -0
- data/lib/cassandra/ordered_hash.rb +192 -0
- data/lib/cassandra/protocol.rb +137 -0
- data/lib/cassandra/time.rb +11 -0
- data/lib/cassandra.rb +41 -0
- data/sessionm-cassandra.gemspec +47 -0
- data/test/cassandra_client_test.rb +20 -0
- data/test/cassandra_mock_test.rb +128 -0
- data/test/cassandra_test.rb +1353 -0
- data/test/comparable_types_test.rb +45 -0
- data/test/composite_type_test.rb +64 -0
- data/test/eventmachine_test.rb +42 -0
- data/test/ordered_hash_test.rb +386 -0
- data/test/test_helper.rb +19 -0
- data/vendor/0.6/gen-rb/cassandra.rb +1481 -0
- data/vendor/0.6/gen-rb/cassandra_constants.rb +12 -0
- data/vendor/0.6/gen-rb/cassandra_types.rb +482 -0
- data/vendor/0.7/gen-rb/cassandra.rb +1936 -0
- data/vendor/0.7/gen-rb/cassandra_constants.rb +12 -0
- data/vendor/0.7/gen-rb/cassandra_types.rb +681 -0
- data/vendor/0.8/gen-rb/cassandra.rb +2215 -0
- data/vendor/0.8/gen-rb/cassandra_constants.rb +12 -0
- data/vendor/0.8/gen-rb/cassandra_types.rb +824 -0
- data/vendor/1.0/gen-rb/cassandra.rb +2215 -0
- data/vendor/1.0/gen-rb/cassandra_constants.rb +12 -0
- data/vendor/1.0/gen-rb/cassandra_types.rb +857 -0
- data/vendor/1.1/gen-rb/cassandra.rb +2571 -0
- data/vendor/1.1/gen-rb/cassandra_constants.rb +12 -0
- data/vendor/1.1/gen-rb/cassandra_types.rb +928 -0
- metadata +287 -0
@@ -0,0 +1,172 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# A bunch of crap, mostly related to introspecting on column types
|
4
|
+
module Columns #:nodoc:
|
5
|
+
private
|
6
|
+
|
7
|
+
def is_super(column_family)
|
8
|
+
@is_super[column_family] ||= column_family_property(column_family, 'column_type') == "Super"
|
9
|
+
end
|
10
|
+
|
11
|
+
def column_name_class(column_family)
|
12
|
+
@column_name_class[column_family] ||= column_name_class_for_key(column_family, "comparator_type")
|
13
|
+
end
|
14
|
+
|
15
|
+
def sub_column_name_class(column_family)
|
16
|
+
@sub_column_name_class[column_family] ||= column_name_class_for_key(column_family, "subcomparator_type")
|
17
|
+
end
|
18
|
+
|
19
|
+
def column_name_maker(column_family)
|
20
|
+
@column_name_maker[column_family] ||=
|
21
|
+
begin
|
22
|
+
klass = column_name_class(column_family)
|
23
|
+
if klass == Composite
|
24
|
+
lambda {|name| klass.new_from_packed(name) }
|
25
|
+
else
|
26
|
+
lambda {|name| klass.new(name) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def sub_column_name_maker(column_family)
|
32
|
+
@sub_column_name_maker[column_family] ||=
|
33
|
+
begin
|
34
|
+
klass = sub_column_name_class(column_family)
|
35
|
+
if klass == Composite
|
36
|
+
lambda {|name| klass.new_from_packed(name) }
|
37
|
+
else
|
38
|
+
lambda {|name| klass.new(name) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def column_name_class_for_key(column_family, comparator_key)
|
44
|
+
property = column_family_property(column_family, comparator_key)
|
45
|
+
property =~ /[^(]*\.(.*?)$/
|
46
|
+
case $1
|
47
|
+
when "LongType" then Long
|
48
|
+
when "LexicalUUIDType", "TimeUUIDType" then SimpleUUID::UUID
|
49
|
+
when /^DynamicCompositeType\(/ then DynamicComposite
|
50
|
+
when /^CompositeType\(/ then Composite
|
51
|
+
else
|
52
|
+
String # UTF8, Ascii, Bytes, anything else
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def column_family_property(column_family, key)
|
57
|
+
cfdef = schema.cf_defs.find {|cfdef| cfdef.name == column_family }
|
58
|
+
unless cfdef
|
59
|
+
raise AccessError, "Invalid column family \"#{column_family}\""
|
60
|
+
end
|
61
|
+
cfdef.send(key)
|
62
|
+
end
|
63
|
+
|
64
|
+
def multi_key_slices_to_hash(column_family, array, return_empty_rows = false)
|
65
|
+
ret = OrderedHash.new
|
66
|
+
array.each do |value|
|
67
|
+
next if return_empty_rows == false && value.columns.length == 0
|
68
|
+
ret[value.key] = columns_to_hash(column_family, value.columns)
|
69
|
+
end
|
70
|
+
ret
|
71
|
+
end
|
72
|
+
|
73
|
+
def multi_column_to_hash!(hash)
|
74
|
+
hash.each do |key, column_or_supercolumn|
|
75
|
+
hash[key] = (column_or_supercolumn.column.value if column_or_supercolumn.column)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def multi_columns_to_hash!(column_family, hash)
|
80
|
+
hash.each do |key, columns|
|
81
|
+
hash[key] = columns_to_hash(column_family, columns)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def multi_sub_columns_to_hash!(column_family, hash)
|
86
|
+
hash.each do |key, sub_columns|
|
87
|
+
hash[key] = sub_columns_to_hash(column_family, sub_columns)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def columns_to_hash(column_family, columns)
|
92
|
+
columns_to_hash_for_classes(columns, column_name_maker(column_family), sub_column_name_maker(column_family))
|
93
|
+
end
|
94
|
+
|
95
|
+
def sub_columns_to_hash(column_family, columns)
|
96
|
+
columns_to_hash_for_classes(columns, sub_column_name_maker(column_family))
|
97
|
+
end
|
98
|
+
|
99
|
+
def columns_to_hash_for_classes(columns, column_name_maker, sub_column_name_maker = nil)
|
100
|
+
hash = OrderedHash.new
|
101
|
+
Array(columns).each do |c|
|
102
|
+
c = c.super_column || c.column || c.counter_column || c.counter_super_column if c.is_a?(CassandraThrift::ColumnOrSuperColumn)
|
103
|
+
case c
|
104
|
+
when CassandraThrift::SuperColumn
|
105
|
+
hash.[]=(column_name_maker.call(c.name), columns_to_hash_for_classes(c.columns, sub_column_name_maker)) # Pop the class stack, and recurse
|
106
|
+
when CassandraThrift::Column
|
107
|
+
hash.[]=(column_name_maker.call(c.name), c.value, c.timestamp)
|
108
|
+
when CassandraThrift::CounterColumn
|
109
|
+
hash.[]=(column_name_maker.call(c.name), c.value, 0)
|
110
|
+
when CassandraThrift::CounterSuperColumn
|
111
|
+
hash.[]=(column_name_maker.call(c.name), columns_to_hash_for_classes(c.columns, sub_column_name_maker)) # Pop the class stack, and recurse
|
112
|
+
end
|
113
|
+
end
|
114
|
+
hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def _standard_insert_mutation(column_family, column_name, value, timestamp, ttl = nil)
|
118
|
+
CassandraThrift::Mutation.new(
|
119
|
+
:column_or_supercolumn => CassandraThrift::ColumnOrSuperColumn.new(
|
120
|
+
:column => CassandraThrift::Column.new(
|
121
|
+
:name => column_name_class(column_family).new(column_name).to_s,
|
122
|
+
:value => value,
|
123
|
+
:timestamp => timestamp,
|
124
|
+
:ttl => ttl
|
125
|
+
)
|
126
|
+
)
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def _super_insert_mutation(column_family, super_column_name, sub_columns, timestamp, ttl = nil)
|
131
|
+
CassandraThrift::Mutation.new(:column_or_supercolumn =>
|
132
|
+
CassandraThrift::ColumnOrSuperColumn.new(
|
133
|
+
:super_column => CassandraThrift::SuperColumn.new(
|
134
|
+
:name => column_name_class(column_family).new(super_column_name).to_s,
|
135
|
+
:columns => sub_columns.collect { |sub_column_name, sub_column_value|
|
136
|
+
CassandraThrift::Column.new(
|
137
|
+
:name => sub_column_name_class(column_family).new(sub_column_name).to_s,
|
138
|
+
:value => sub_column_value.to_s,
|
139
|
+
:timestamp => timestamp,
|
140
|
+
:ttl => ttl
|
141
|
+
)
|
142
|
+
}
|
143
|
+
)
|
144
|
+
)
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
# General info about a deletion object within a mutation
|
149
|
+
# timestamp - required. If this is the only param, it will cause deletion of the whole key at that TS
|
150
|
+
# supercolumn - opt. If passed, the deletes will only occur within that supercolumn (only subcolumns
|
151
|
+
# will be deleted). Otherwise the normal columns will be deleted.
|
152
|
+
# predicate - opt. Defines how to match the columns to delete. if supercolumn passed, the slice will
|
153
|
+
# be scoped to subcolumns of that supercolumn.
|
154
|
+
|
155
|
+
# Deletes a single column from the containing key/CF (and possibly supercolumn), at a given timestamp.
|
156
|
+
# Although mutations (as opposed to 'remove' calls) support deleting slices and lists of columns in one shot, this is not implemented here.
|
157
|
+
# The main reason being that the batch function takes removes, but removes don't have that capability...so we'd need to change the remove
|
158
|
+
# methods to use delete mutation calls...although that might have performance implications. We'll leave that refactoring for later.
|
159
|
+
def _delete_mutation(cf, column, subcolumn, timestamp, options={})
|
160
|
+
deletion_hash = {:timestamp => timestamp}
|
161
|
+
if is_super(cf)
|
162
|
+
deletion_hash[:super_column] = column if column
|
163
|
+
deletion_hash[:predicate] = CassandraThrift::SlicePredicate.new(:column_names => [subcolumn]) if subcolumn
|
164
|
+
else
|
165
|
+
deletion_hash[:predicate] = CassandraThrift::SlicePredicate.new(:column_names => [column]) if column
|
166
|
+
end
|
167
|
+
CassandraThrift::Mutation.new(
|
168
|
+
:deletion => CassandraThrift::Deletion.new(deletion_hash)
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# Abstract base class for comparable numeric column name types
|
4
|
+
class Comparable
|
5
|
+
class TypeError < ::TypeError #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
def <=>(other)
|
9
|
+
self.to_i <=> other.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
def hash
|
13
|
+
@bytes.hash
|
14
|
+
end
|
15
|
+
|
16
|
+
def eql?(other)
|
17
|
+
other.is_a?(Comparable) and @bytes == other.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def ==(other)
|
21
|
+
other.respond_to?(:to_i) && self.to_i == other.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@bytes
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
class Cassandra
|
2
|
+
class Composite
|
3
|
+
include ::Comparable
|
4
|
+
attr_reader :parts
|
5
|
+
attr_reader :column_slice
|
6
|
+
|
7
|
+
def initialize(*parts)
|
8
|
+
return if parts.empty?
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
if parts.last.is_a?(Hash)
|
12
|
+
options = parts.pop
|
13
|
+
end
|
14
|
+
@column_slice = options[:slice]
|
15
|
+
raise ArgumentError if @column_slice != nil && ![:before, :after].include?(@column_slice)
|
16
|
+
|
17
|
+
if parts.length == 1 && parts[0].instance_of?(self.class)
|
18
|
+
@column_slice = parts[0].column_slice
|
19
|
+
@parts = parts[0].parts
|
20
|
+
elsif parts.length == 1 && parts[0].instance_of?(String) && @column_slice.nil? && try_packed_composite(parts[0])
|
21
|
+
@hash = parts[0].hash
|
22
|
+
else
|
23
|
+
@parts = parts
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.new_from_packed(packed)
|
28
|
+
obj = new
|
29
|
+
obj.fast_unpack(packed)
|
30
|
+
return obj
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](*args)
|
34
|
+
return @parts[*args]
|
35
|
+
end
|
36
|
+
|
37
|
+
def pack
|
38
|
+
packed = @parts.map do |part|
|
39
|
+
[part.length].pack('n') + part + "\x00"
|
40
|
+
end
|
41
|
+
if @column_slice
|
42
|
+
part = @parts[-1]
|
43
|
+
packed[-1] = [part.length].pack('n') + part + slice_end_of_component
|
44
|
+
end
|
45
|
+
return packed.join('')
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
return pack
|
50
|
+
end
|
51
|
+
|
52
|
+
def <=>(other)
|
53
|
+
if !other.instance_of?(self.class)
|
54
|
+
return @parts.first <=> other
|
55
|
+
end
|
56
|
+
eoc = slice_end_of_component.unpack('c')[0]
|
57
|
+
other_eoc = other.slice_end_of_component.unpack('c')[0]
|
58
|
+
@parts.zip(other.parts).each do |a, b|
|
59
|
+
next if a == b
|
60
|
+
if a.nil? && b.nil?
|
61
|
+
return eoc <=> other_eoc
|
62
|
+
end
|
63
|
+
|
64
|
+
if a.nil?
|
65
|
+
return @column_slice == :after ? 1 : -1
|
66
|
+
end
|
67
|
+
if b.nil?
|
68
|
+
return other.column_slice == :after ? -1 : 1
|
69
|
+
end
|
70
|
+
return -1 if a < b
|
71
|
+
return 1 if a > b
|
72
|
+
end
|
73
|
+
return 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def inspect
|
77
|
+
return "#<#{self.class}:#{@column_slice} #{@parts.inspect}>"
|
78
|
+
end
|
79
|
+
|
80
|
+
def slice_end_of_component
|
81
|
+
return "\x01" if @column_slice == :after
|
82
|
+
return "\xFF" if @column_slice == :before
|
83
|
+
return "\x00"
|
84
|
+
end
|
85
|
+
|
86
|
+
def fast_unpack(packed_string)
|
87
|
+
@hash = packed_string.hash
|
88
|
+
|
89
|
+
@parts = []
|
90
|
+
end_of_component = packed_string.slice(packed_string.length-1, 1)
|
91
|
+
while packed_string.length > 0
|
92
|
+
length = packed_string.unpack('n')[0]
|
93
|
+
@parts << packed_string.slice(2, length)
|
94
|
+
|
95
|
+
packed_string.slice!(0, length+3)
|
96
|
+
end
|
97
|
+
|
98
|
+
@column_slice = :after if end_of_component == "\x01"
|
99
|
+
@column_slice = :before if end_of_component == "\xFF"
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
def try_packed_composite(packed_string)
|
104
|
+
parts = []
|
105
|
+
end_of_component = nil
|
106
|
+
while packed_string.length > 0
|
107
|
+
length = packed_string.slice(0, 2).unpack('n')[0]
|
108
|
+
return false if length.nil? || length + 3 > packed_string.length
|
109
|
+
|
110
|
+
parts << packed_string.slice(2, length)
|
111
|
+
end_of_component = packed_string.slice(2 + length, 1)
|
112
|
+
if length + 3 != packed_string.length
|
113
|
+
return false if end_of_component != "\x00"
|
114
|
+
end
|
115
|
+
|
116
|
+
packed_string = packed_string.slice(3 + length, packed_string.length)
|
117
|
+
end
|
118
|
+
|
119
|
+
@column_slice = :after if end_of_component == "\x01"
|
120
|
+
@column_slice = :before if end_of_component == "\xFF"
|
121
|
+
@parts = parts
|
122
|
+
|
123
|
+
return true
|
124
|
+
end
|
125
|
+
|
126
|
+
def hash
|
127
|
+
return @hash ||= pack.hash
|
128
|
+
end
|
129
|
+
|
130
|
+
def eql?(other)
|
131
|
+
return to_s == other.to_s
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
begin
|
137
|
+
require "cassandra_native"
|
138
|
+
rescue LoadError
|
139
|
+
puts "Unable to load cassandra_native extension. Defaulting to pure Ruby libraries."
|
140
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# A helper module you can include in your own class. Makes it easier
|
4
|
+
# to work with Cassandra subclasses.
|
5
|
+
module Constants
|
6
|
+
include Cassandra::Consistency
|
7
|
+
|
8
|
+
Long = Cassandra::Long
|
9
|
+
OrderedHash = Cassandra::OrderedHash
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
class Cassandra
|
2
|
+
class DynamicComposite < Composite
|
3
|
+
attr_accessor :types
|
4
|
+
|
5
|
+
def initialize(*parts)
|
6
|
+
return if parts.empty?
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
if parts.last.is_a?(Hash)
|
10
|
+
options = parts.pop
|
11
|
+
end
|
12
|
+
@column_slice = options[:slice]
|
13
|
+
raise ArgumentError if @column_slice != nil && ![:before, :after].include?(@column_slice)
|
14
|
+
|
15
|
+
if parts.length == 1 && parts[0].instance_of?(self.class)
|
16
|
+
@column_slice = parts[0].column_slice
|
17
|
+
@parts = parts[0].parts
|
18
|
+
@types = parts[0].types
|
19
|
+
elsif parts.length == 1 && parts[0].instance_of?(String) && @column_slice.nil? && try_packed_composite(parts[0])
|
20
|
+
@hash = parts[0].hash
|
21
|
+
else
|
22
|
+
@types, @parts = parts.transpose
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def pack
|
27
|
+
packed_parts = @parts.map do |part|
|
28
|
+
[part.length].pack('n') + part + "\x00"
|
29
|
+
end
|
30
|
+
|
31
|
+
if @column_slice
|
32
|
+
part = @parts[-1]
|
33
|
+
packed_parts[-1] = [part.length].pack('n') + part + slice_end_of_component
|
34
|
+
end
|
35
|
+
|
36
|
+
packed_types = @types.map do |type|
|
37
|
+
if type.length == 1
|
38
|
+
[0x8000 | type[0].ord].pack('n')
|
39
|
+
else
|
40
|
+
[type.length].pack('n') + type
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
return packed_types.zip(packed_parts).flatten.join('')
|
45
|
+
end
|
46
|
+
|
47
|
+
def fast_unpack(packed_string)
|
48
|
+
result = try_packed_composite(packed_string)
|
49
|
+
raise ArgumentError.new("Invalid DynamicComposite column") if !result
|
50
|
+
@hash = packed_string.hash
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def try_packed_composite(packed_string)
|
55
|
+
types = []
|
56
|
+
parts = []
|
57
|
+
end_of_component = nil
|
58
|
+
offset = 0
|
59
|
+
|
60
|
+
read_bytes = proc do |length|
|
61
|
+
return false if offset + length > packed_string.length
|
62
|
+
out = packed_string.slice(offset, length)
|
63
|
+
offset += length
|
64
|
+
out
|
65
|
+
end
|
66
|
+
|
67
|
+
while offset < packed_string.length
|
68
|
+
header = read_bytes.call(2).unpack('n')[0]
|
69
|
+
is_alias = header & 0x8000 != 0
|
70
|
+
if is_alias
|
71
|
+
alias_char = (header & 0xFF).chr
|
72
|
+
types << alias_char
|
73
|
+
else
|
74
|
+
length = header
|
75
|
+
return false if length.nil? || length + offset > packed_string.length
|
76
|
+
type = read_bytes.call(length)
|
77
|
+
types << type
|
78
|
+
end
|
79
|
+
length = read_bytes.call(2).unpack('n')[0]
|
80
|
+
return false if length.nil? || length + offset > packed_string.length
|
81
|
+
parts << read_bytes.call(length)
|
82
|
+
end_of_component = read_bytes.call(1)
|
83
|
+
if offset < packed_string.length
|
84
|
+
return false if end_of_component != "\x00"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
@column_slice = :after if end_of_component == "\x01"
|
88
|
+
@column_slice = :before if end_of_component == "\xFF"
|
89
|
+
@types = types
|
90
|
+
@parts = parts
|
91
|
+
@hash = packed_string.hash
|
92
|
+
|
93
|
+
return true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|