cassandra 0.9.1 → 0.10.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 +12 -0
- data/Manifest +31 -12
- data/README.rdoc +3 -2
- data/Rakefile +53 -23
- data/cassandra.gemspec +6 -8
- data/conf/{cassandra.in.sh → 0.6/cassandra.in.sh} +0 -0
- data/conf/{log4j.properties → 0.6/log4j.properties} +0 -0
- data/conf/0.6/schema.json +48 -0
- data/conf/{storage-conf.xml → 0.6/storage-conf.xml} +5 -5
- 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 +48 -0
- data/conf/0.7/schema.txt +56 -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 +48 -0
- data/conf/0.8/schema.txt +56 -0
- data/lib/cassandra.rb +1 -1
- data/lib/cassandra/0.6/cassandra.rb +1 -0
- data/lib/cassandra/0.6/columns.rb +5 -7
- data/lib/cassandra/0.6/protocol.rb +1 -1
- data/lib/cassandra/0.7/cassandra.rb +5 -5
- data/lib/cassandra/0.7/columns.rb +5 -6
- data/lib/cassandra/0.7/protocol.rb +12 -3
- data/lib/cassandra/0.8.rb +7 -0
- data/lib/cassandra/0.8/cassandra.rb +272 -0
- data/lib/cassandra/0.8/column_family.rb +3 -0
- data/lib/cassandra/0.8/columns.rb +84 -0
- data/lib/cassandra/0.8/keyspace.rb +3 -0
- data/lib/cassandra/0.8/protocol.rb +120 -0
- data/lib/cassandra/cassandra.rb +6 -11
- data/lib/cassandra/helpers.rb +1 -0
- data/lib/cassandra/mock.rb +107 -64
- data/lib/cassandra/ordered_hash.rb +1 -6
- data/test/cassandra_mock_test.rb +7 -27
- data/test/cassandra_test.rb +41 -15
- data/test/eventmachine_test.rb +30 -30
- data/test/test_helper.rb +2 -1
- 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 +814 -0
- metadata +50 -27
- data/conf/cassandra.yaml +0 -113
@@ -0,0 +1,120 @@
|
|
1
|
+
|
2
|
+
class Cassandra
|
3
|
+
# Inner methods for actually doing the Thrift calls
|
4
|
+
module Protocol #:nodoc:
|
5
|
+
private
|
6
|
+
|
7
|
+
def _mutate(mutation_map, consistency_level)
|
8
|
+
client.batch_mutate(mutation_map, consistency_level)
|
9
|
+
end
|
10
|
+
|
11
|
+
def _remove(key, column_path, timestamp, consistency_level)
|
12
|
+
client.remove(key, column_path, timestamp, consistency_level)
|
13
|
+
end
|
14
|
+
|
15
|
+
def _count_columns(column_family, key, super_column, consistency)
|
16
|
+
client.get_count(key,
|
17
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => super_column),
|
18
|
+
CassandraThrift::SlicePredicate.new(:slice_range =>
|
19
|
+
CassandraThrift::SliceRange.new(
|
20
|
+
:start => '',
|
21
|
+
:finish => ''
|
22
|
+
)),
|
23
|
+
consistency
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def _get_columns(column_family, key, columns, sub_columns, consistency)
|
28
|
+
result = if is_super(column_family)
|
29
|
+
if sub_columns
|
30
|
+
columns_to_hash(column_family, client.get_slice(key,
|
31
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => columns),
|
32
|
+
CassandraThrift::SlicePredicate.new(:column_names => sub_columns),
|
33
|
+
consistency))
|
34
|
+
else
|
35
|
+
columns_to_hash(column_family, client.get_slice(key,
|
36
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family),
|
37
|
+
CassandraThrift::SlicePredicate.new(:column_names => columns),
|
38
|
+
consistency))
|
39
|
+
end
|
40
|
+
else
|
41
|
+
columns_to_hash(column_family, client.get_slice(key,
|
42
|
+
CassandraThrift::ColumnParent.new(:column_family => column_family),
|
43
|
+
CassandraThrift::SlicePredicate.new(:column_names => columns),
|
44
|
+
consistency))
|
45
|
+
end
|
46
|
+
|
47
|
+
klass = column_name_class(column_family)
|
48
|
+
(sub_columns || columns).map { |name| result[klass.new(name)] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def _multiget(column_family, keys, column, sub_column, count, start, finish, reversed, consistency)
|
52
|
+
# Single values; count and range parameters have no effect
|
53
|
+
if is_super(column_family) and sub_column
|
54
|
+
predicate = CassandraThrift::SlicePredicate.new(:column_names => [sub_column])
|
55
|
+
column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column)
|
56
|
+
column_hash = multi_sub_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
|
57
|
+
|
58
|
+
klass = sub_column_name_class(column_family)
|
59
|
+
keys.inject({}){|hash, key| hash[key] = column_hash[key][klass.new(sub_column)]; hash}
|
60
|
+
elsif !is_super(column_family) and column
|
61
|
+
predicate = CassandraThrift::SlicePredicate.new(:column_names => [column])
|
62
|
+
column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
|
63
|
+
column_hash = multi_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
|
64
|
+
|
65
|
+
keys.inject({}){|hash, key| hash[key] = column_hash[key][column]; hash}
|
66
|
+
|
67
|
+
# Slices
|
68
|
+
else
|
69
|
+
predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
|
70
|
+
CassandraThrift::SliceRange.new(
|
71
|
+
:reversed => reversed,
|
72
|
+
:count => count,
|
73
|
+
:start => start,
|
74
|
+
:finish => finish))
|
75
|
+
|
76
|
+
if is_super(column_family) and column
|
77
|
+
column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column)
|
78
|
+
multi_sub_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
|
79
|
+
else
|
80
|
+
column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
|
81
|
+
multi_columns_to_hash!(column_family, client.multiget_slice(keys, column_parent, predicate, consistency))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def _get_range(column_family, start, finish, count, consistency)
|
87
|
+
column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
|
88
|
+
predicate = CassandraThrift::SlicePredicate.new(:slice_range => CassandraThrift::SliceRange.new(:start => '', :finish => ''))
|
89
|
+
range = CassandraThrift::KeyRange.new(:start_key => start, :end_key => finish, :count => count)
|
90
|
+
client.get_range_slices(column_parent, predicate, range, 1)
|
91
|
+
end
|
92
|
+
|
93
|
+
def _get_range_keys(column_family, start, finish, count, consistency)
|
94
|
+
_get_range(column_family, start, finish, count, consistency).collect{|i| i.key }
|
95
|
+
end
|
96
|
+
|
97
|
+
# TODO: Supercolumn support
|
98
|
+
def _get_indexed_slices(column_family, idx_clause, column, count, start, finish, reversed, consistency)
|
99
|
+
column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family)
|
100
|
+
if column
|
101
|
+
predicate = CassandraThrift::SlicePredicate.new(:column_names => [column])
|
102
|
+
else
|
103
|
+
predicate = CassandraThrift::SlicePredicate.new(:slice_range =>
|
104
|
+
CassandraThrift::SliceRange.new(
|
105
|
+
:reversed => reversed,
|
106
|
+
:count => count,
|
107
|
+
:start => start,
|
108
|
+
:finish => finish))
|
109
|
+
end
|
110
|
+
client.get_indexed_slices(column_parent, idx_clause, predicate, consistency)
|
111
|
+
end
|
112
|
+
|
113
|
+
def each_key(column_family)
|
114
|
+
column_parent = CassandraThrift::ColumnParent.new(:column_family => column_family.to_s)
|
115
|
+
predicate = CassandraThrift::SlicePredicate.new(:column_names => [])
|
116
|
+
range = CassandraThrift::KeyRange.new(:start_key => '', :end_key => '')
|
117
|
+
client.get_range_slices(column_parent, predicate, range, 1).each{|i| yield i.key }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/lib/cassandra/cassandra.rb
CHANGED
@@ -146,9 +146,9 @@ class Cassandra
|
|
146
146
|
mutation_map =
|
147
147
|
{
|
148
148
|
key => {
|
149
|
-
column_family => [ _delete_mutation(column_family
|
149
|
+
column_family => [ _delete_mutation(column_family, column, sub_column, options[:timestamp]|| Time.stamp) ]
|
150
150
|
}
|
151
|
-
}
|
151
|
+
}
|
152
152
|
@batch << [mutation_map, options[:consistency]]
|
153
153
|
else
|
154
154
|
# Let's continue using the 'remove' thrift method...not sure about the implications/performance of using the mutate instead
|
@@ -276,7 +276,7 @@ class Cassandra
|
|
276
276
|
# Roll up queued mutations, to improve atomicity (and performance).
|
277
277
|
def compact_mutations!
|
278
278
|
used_clevels = {} # hash that lists the consistency levels seen in the batch array. key is the clevel, value is true
|
279
|
-
by_key = {}
|
279
|
+
by_key = Hash.new{|h,k | h[k] = {}}
|
280
280
|
# @batch is an array of mutation_ops.
|
281
281
|
# A mutation op is a 2-item array containing [mutationmap, consistency_number]
|
282
282
|
# a mutation map is a hash, by key (string) that has a hash by CF name, containing a list of column_mutations)
|
@@ -291,18 +291,13 @@ class Cassandra
|
|
291
291
|
# }, # [0]
|
292
292
|
# consistency # [1]
|
293
293
|
#]
|
294
|
-
# For a remove:
|
295
|
-
# [ :remove, # [0]
|
296
|
-
# [key, CassThrift:ColPath, timestamp, consistency ] # [1]
|
297
|
-
# ]
|
298
294
|
mmap = mutation_op[0] # :remove OR a hash like {"key"=> {"CF"=>[mutationclass1,...] } }
|
299
|
-
used_clevels[mutation_op[1]]=true #save the clevel required for this operation
|
295
|
+
used_clevels[mutation_op[1]] = true #save the clevel required for this operation
|
300
296
|
|
301
297
|
mmap.keys.each do |k|
|
302
|
-
by_key[k] = {} unless by_key.has_key? k #make sure the key exists
|
303
298
|
mmap[k].keys.each do |cf| # For each CF in that key
|
304
|
-
by_key[k][cf]
|
305
|
-
by_key[k][cf].concat
|
299
|
+
by_key[k][cf] ||= []
|
300
|
+
by_key[k][cf].concat(mmap[k][cf]) # Append the list of mutations for that key and CF
|
306
301
|
end
|
307
302
|
end
|
308
303
|
end
|
data/lib/cassandra/helpers.rb
CHANGED
@@ -17,6 +17,7 @@ class Cassandra
|
|
17
17
|
|
18
18
|
# Ranges
|
19
19
|
column, sub_column = args[0], args[1]
|
20
|
+
raise ArgumentError, "Invalid arguments: subcolumns specified for a non-supercolumn family" if sub_column && !is_super(column_family)
|
20
21
|
klass, sub_klass = column_name_class(column_family), sub_column_name_class(column_family)
|
21
22
|
range_class = column ? sub_klass : klass
|
22
23
|
|
data/lib/cassandra/mock.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
|
3
1
|
class SimpleUUID::UUID
|
4
2
|
def >=(other)
|
5
3
|
(self <=> other) >= 0
|
@@ -15,11 +13,13 @@ class Cassandra
|
|
15
13
|
include ::Cassandra::Helpers
|
16
14
|
include ::Cassandra::Columns
|
17
15
|
|
18
|
-
def initialize(keyspace,
|
16
|
+
def initialize(keyspace, schema)
|
17
|
+
@is_super = {}
|
19
18
|
@keyspace = keyspace
|
20
19
|
@column_name_class = {}
|
21
20
|
@sub_column_name_class = {}
|
22
|
-
@
|
21
|
+
@indexes = {}
|
22
|
+
@schema = schema[keyspace]
|
23
23
|
clear_keyspace!
|
24
24
|
end
|
25
25
|
|
@@ -39,7 +39,7 @@ class Cassandra
|
|
39
39
|
@batch << [:insert, column_family, key, hash_or_array, options]
|
40
40
|
else
|
41
41
|
raise ArgumentError if key.nil?
|
42
|
-
if
|
42
|
+
if !is_super(column_family)
|
43
43
|
insert_standard(column_family, key, hash_or_array)
|
44
44
|
else
|
45
45
|
insert_super(column_family, key, hash_or_array)
|
@@ -77,7 +77,7 @@ class Cassandra
|
|
77
77
|
def get(column_family, key, *columns_and_options)
|
78
78
|
column_family, column, sub_column, options =
|
79
79
|
extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
|
80
|
-
if
|
80
|
+
if !is_super(column_family)
|
81
81
|
get_standard(column_family, key, column, options)
|
82
82
|
else
|
83
83
|
get_super(column_family, key, column, sub_column, options)
|
@@ -85,7 +85,9 @@ class Cassandra
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def get_standard(column_family, key, column, options)
|
88
|
-
|
88
|
+
columns = cf(column_family)[key] || OrderedHash.new
|
89
|
+
row = columns_to_hash(column_family, columns)
|
90
|
+
|
89
91
|
if column
|
90
92
|
row[column]
|
91
93
|
else
|
@@ -95,26 +97,24 @@ class Cassandra
|
|
95
97
|
end
|
96
98
|
|
97
99
|
def get_super(column_family, key, column, sub_column, options)
|
100
|
+
columns = cf(column_family)[key] || OrderedHash.new
|
101
|
+
row = columns_to_hash(column_family, columns)
|
102
|
+
|
98
103
|
if column
|
99
104
|
if sub_column
|
100
|
-
if
|
101
|
-
|
102
|
-
|
103
|
-
cf(column_family)[key][column][sub_column]
|
105
|
+
if row[column] &&
|
106
|
+
row[column][sub_column]
|
107
|
+
row[column][sub_column]
|
104
108
|
else
|
105
109
|
nil
|
106
110
|
end
|
107
111
|
else
|
108
|
-
row =
|
109
|
-
cf(column_family)[key][column] :
|
110
|
-
OrderedHash.new
|
112
|
+
row = row[column] || OrderedHash.new
|
111
113
|
row = apply_range(row, column_family, options[:start], options[:finish], false)
|
112
114
|
apply_count(row, options[:count], options[:reversed])
|
113
115
|
end
|
114
|
-
elsif cf(column_family)[key]
|
115
|
-
cf(column_family)[key]
|
116
116
|
else
|
117
|
-
|
117
|
+
row
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
@@ -137,9 +137,9 @@ class Cassandra
|
|
137
137
|
else
|
138
138
|
if column
|
139
139
|
if sub_column
|
140
|
-
cf(column_family)[key][column].delete(sub_column)
|
140
|
+
cf(column_family)[key][column].delete(sub_column.to_s)
|
141
141
|
else
|
142
|
-
cf(column_family)[key].delete(column)
|
142
|
+
cf(column_family)[key].delete(column.to_s)
|
143
143
|
end
|
144
144
|
else
|
145
145
|
cf(column_family).delete(key)
|
@@ -151,7 +151,6 @@ class Cassandra
|
|
151
151
|
column_family, columns, sub_columns, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, READ_DEFAULTS)
|
152
152
|
d = get(column_family, key)
|
153
153
|
|
154
|
-
|
155
154
|
if sub_columns
|
156
155
|
sub_columns.collect do |sub_column|
|
157
156
|
d[columns][sub_column]
|
@@ -197,16 +196,74 @@ class Cassandra
|
|
197
196
|
count
|
198
197
|
end
|
199
198
|
|
200
|
-
def
|
201
|
-
if
|
202
|
-
[]
|
199
|
+
def create_index(ks_name, cf_name, c_name, v_class)
|
200
|
+
if @indexes[ks_name] &&
|
201
|
+
@indexes[ks_name][cf_name] &&
|
202
|
+
@indexes[ks_name][cf_name][c_name]
|
203
|
+
nil
|
204
|
+
|
205
|
+
else
|
206
|
+
@indexes[ks_name] ||= {}
|
207
|
+
@indexes[ks_name][cf_name] ||= {}
|
208
|
+
@indexes[ks_name][cf_name][c_name] = true
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def drop_index(ks_name, cf_name, c_name)
|
213
|
+
if @indexes[ks_name] &&
|
214
|
+
@indexes[ks_name][cf_name] &&
|
215
|
+
@indexes[ks_name][cf_name][c_name]
|
216
|
+
|
217
|
+
@indexes[ks_name][cf_name].delete(c_name)
|
203
218
|
else
|
204
|
-
|
219
|
+
nil
|
205
220
|
end
|
206
221
|
end
|
207
222
|
|
223
|
+
def create_idx_expr(c_name, value, op)
|
224
|
+
{:column_name => c_name, :value => value, :comparison => op}
|
225
|
+
end
|
226
|
+
|
227
|
+
def create_idx_clause(idx_expressions, start = "")
|
228
|
+
{:start => start, :index_expressions => idx_expressions}
|
229
|
+
end
|
230
|
+
|
231
|
+
def get_indexed_slices(column_family, idx_clause, *columns_and_options)
|
232
|
+
column_family, columns, _, options =
|
233
|
+
extract_and_validate_params_for_real(column_family, [], columns_and_options, READ_DEFAULTS)
|
234
|
+
|
235
|
+
ret = {}
|
236
|
+
cf(column_family).each do |key, row|
|
237
|
+
next if idx_clause[:start] != '' && key < idx_clause[:start]
|
238
|
+
|
239
|
+
matches = []
|
240
|
+
idx_clause[:index_expressions].each do |expr|
|
241
|
+
next if row[expr[:column_name]].nil?
|
242
|
+
next unless row[expr[:column_name]].send(expr[:comparison].to_sym, expr[:value])
|
243
|
+
|
244
|
+
matches << expr
|
245
|
+
end
|
246
|
+
|
247
|
+
ret[key] = row if matches.length == idx_clause[:index_expressions].length
|
248
|
+
end
|
249
|
+
|
250
|
+
ret
|
251
|
+
end
|
252
|
+
|
253
|
+
def schema(load=true)
|
254
|
+
@schema
|
255
|
+
end
|
256
|
+
|
257
|
+
def column_family_property(column_family, key)
|
258
|
+
schema[column_family.to_s][key]
|
259
|
+
end
|
260
|
+
|
208
261
|
private
|
209
262
|
|
263
|
+
def schema_for_keyspace(keyspace)
|
264
|
+
@schema
|
265
|
+
end
|
266
|
+
|
210
267
|
def _get_range(column_family, start, finish, count)
|
211
268
|
ret = OrderedHash.new
|
212
269
|
start = to_compare_with_type(start, column_family)
|
@@ -220,30 +277,6 @@ class Cassandra
|
|
220
277
|
ret
|
221
278
|
end
|
222
279
|
|
223
|
-
def schema_for_keyspace(keyspace)
|
224
|
-
doc = read_storage_xml
|
225
|
-
ret = {}
|
226
|
-
doc.css("Keyspaces Keyspace[@Name='#{keyspace}']").css('ColumnFamily').each do |cf|
|
227
|
-
ret[cf['Name']] = {}
|
228
|
-
if cf['CompareSubcolumnsWith']
|
229
|
-
ret[cf['Name']]['CompareSubcolumnsWith'] = 'org.apache.cassandra.db.marshal.' + cf['CompareSubcolumnsWith']
|
230
|
-
end
|
231
|
-
if cf['CompareWith']
|
232
|
-
ret[cf['Name']]['CompareWith'] = 'org.apache.cassandra.db.marshal.' + cf['CompareWith']
|
233
|
-
end
|
234
|
-
if cf['ColumnType'] == 'Super'
|
235
|
-
ret[cf['Name']]['Type'] = 'Super'
|
236
|
-
else
|
237
|
-
ret[cf['Name']]['Type'] = 'Standard'
|
238
|
-
end
|
239
|
-
end
|
240
|
-
ret
|
241
|
-
end
|
242
|
-
|
243
|
-
def read_storage_xml
|
244
|
-
@doc ||= Nokogiri::XML(open(@storage_xml))
|
245
|
-
end
|
246
|
-
|
247
280
|
def extract_and_validate_params_for_real(column_family, keys, args, options)
|
248
281
|
column_family, columns, sub_column, options = extract_and_validate_params(column_family, keys, args, options)
|
249
282
|
options[:start] = nil if options[:start] == ''
|
@@ -264,25 +297,12 @@ class Cassandra
|
|
264
297
|
def to_compare_with_type(column_name, column_family, standard=true)
|
265
298
|
return column_name if column_name.nil?
|
266
299
|
klass = if standard
|
267
|
-
|
300
|
+
column_name_class(column_family)
|
268
301
|
else
|
269
|
-
|
302
|
+
sub_column_name_class(column_family)
|
270
303
|
end
|
271
304
|
|
272
|
-
|
273
|
-
when "org.apache.cassandra.db.marshal.UTF8Type", "org.apache.cassandra.db.marshal.BytesType"
|
274
|
-
column_name
|
275
|
-
when "org.apache.cassandra.db.marshal.TimeUUIDType"
|
276
|
-
SimpleUUID::UUID.new(column_name)
|
277
|
-
when "org.apache.cassandra.db.marshal.LongType"
|
278
|
-
Long.new(column_name)
|
279
|
-
else
|
280
|
-
raise "Unknown column family type: #{klass.inspect}"
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def column_family_type(column_family)
|
285
|
-
schema[column_family.to_s]['Type']
|
305
|
+
klass.new(column_name)
|
286
306
|
end
|
287
307
|
|
288
308
|
def cf(column_family)
|
@@ -293,9 +313,32 @@ class Cassandra
|
|
293
313
|
if new_stuff.is_a?(Array)
|
294
314
|
new_stuff = new_stuff.inject({}){|h,k| h[k] = nil; h }
|
295
315
|
end
|
316
|
+
|
317
|
+
new_stuff = new_stuff.to_a.inject({}){|h,k| h[k[0].to_s] = k[1]; h }
|
318
|
+
|
296
319
|
OrderedHash[old_stuff.merge(new_stuff).sort{|a,b| a[0] <=> b[0]}]
|
297
320
|
end
|
298
321
|
|
322
|
+
def columns_to_hash(column_family, columns)
|
323
|
+
column_class, sub_column_class = column_name_class(column_family), sub_column_name_class(column_family)
|
324
|
+
output = OrderedHash.new
|
325
|
+
|
326
|
+
columns.each do |column_name, value|
|
327
|
+
column = column_class.new(column_name)
|
328
|
+
|
329
|
+
if [Hash, OrderedHash].include?(value.class)
|
330
|
+
output[column] ||= OrderedHash.new
|
331
|
+
value.each do |sub_column, sub_column_value|
|
332
|
+
output[column][sub_column_class.new(sub_column)] = sub_column_value
|
333
|
+
end
|
334
|
+
else
|
335
|
+
output[column_class.new(column_name)] = value
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
output
|
340
|
+
end
|
341
|
+
|
299
342
|
def apply_count(row, count, reversed=false)
|
300
343
|
if count
|
301
344
|
keys = row.keys.sort
|