mcmire-cassandra 0.12.2

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 (65) hide show
  1. data/CHANGELOG +108 -0
  2. data/LICENSE +202 -0
  3. data/Manifest +63 -0
  4. data/README.md +352 -0
  5. data/Rakefile +169 -0
  6. data/bin/cassandra_helper +16 -0
  7. data/conf/0.6/cassandra.in.sh +47 -0
  8. data/conf/0.6/log4j.properties +38 -0
  9. data/conf/0.6/schema.json +57 -0
  10. data/conf/0.6/storage-conf.xml +352 -0
  11. data/conf/0.7/cassandra.in.sh +46 -0
  12. data/conf/0.7/cassandra.yaml +336 -0
  13. data/conf/0.7/log4j-server.properties +41 -0
  14. data/conf/0.7/schema.json +57 -0
  15. data/conf/0.7/schema.txt +45 -0
  16. data/conf/0.8/cassandra.in.sh +41 -0
  17. data/conf/0.8/cassandra.yaml +61 -0
  18. data/conf/0.8/log4j-server.properties +40 -0
  19. data/conf/0.8/schema.json +66 -0
  20. data/conf/0.8/schema.txt +51 -0
  21. data/lib/cassandra/0.6/cassandra.rb +113 -0
  22. data/lib/cassandra/0.6/columns.rb +78 -0
  23. data/lib/cassandra/0.6/protocol.rb +90 -0
  24. data/lib/cassandra/0.6.rb +7 -0
  25. data/lib/cassandra/0.7/cassandra.rb +2 -0
  26. data/lib/cassandra/0.7/columns.rb +4 -0
  27. data/lib/cassandra/0.7/protocol.rb +5 -0
  28. data/lib/cassandra/0.7.rb +7 -0
  29. data/lib/cassandra/0.8/cassandra.rb +10 -0
  30. data/lib/cassandra/0.8/columns.rb +4 -0
  31. data/lib/cassandra/0.8/protocol.rb +21 -0
  32. data/lib/cassandra/0.8.rb +7 -0
  33. data/lib/cassandra/array.rb +8 -0
  34. data/lib/cassandra/cassandra.rb +1070 -0
  35. data/lib/cassandra/column_family.rb +3 -0
  36. data/lib/cassandra/columns.rb +144 -0
  37. data/lib/cassandra/comparable.rb +28 -0
  38. data/lib/cassandra/constants.rb +11 -0
  39. data/lib/cassandra/debug.rb +9 -0
  40. data/lib/cassandra/helpers.rb +41 -0
  41. data/lib/cassandra/keyspace.rb +3 -0
  42. data/lib/cassandra/long.rb +58 -0
  43. data/lib/cassandra/mock.rb +511 -0
  44. data/lib/cassandra/ordered_hash.rb +192 -0
  45. data/lib/cassandra/protocol.rb +120 -0
  46. data/lib/cassandra/time.rb +11 -0
  47. data/lib/cassandra.rb +38 -0
  48. data/mcmire-cassandra.gemspec +43 -0
  49. data/test/cassandra_client_test.rb +20 -0
  50. data/test/cassandra_mock_test.rb +116 -0
  51. data/test/cassandra_test.rb +863 -0
  52. data/test/comparable_types_test.rb +45 -0
  53. data/test/eventmachine_test.rb +42 -0
  54. data/test/ordered_hash_test.rb +386 -0
  55. data/test/test_helper.rb +15 -0
  56. data/vendor/0.6/gen-rb/cassandra.rb +1481 -0
  57. data/vendor/0.6/gen-rb/cassandra_constants.rb +12 -0
  58. data/vendor/0.6/gen-rb/cassandra_types.rb +482 -0
  59. data/vendor/0.7/gen-rb/cassandra.rb +1936 -0
  60. data/vendor/0.7/gen-rb/cassandra_constants.rb +12 -0
  61. data/vendor/0.7/gen-rb/cassandra_types.rb +681 -0
  62. data/vendor/0.8/gen-rb/cassandra.rb +2215 -0
  63. data/vendor/0.8/gen-rb/cassandra_constants.rb +12 -0
  64. data/vendor/0.8/gen-rb/cassandra_types.rb +824 -0
  65. metadata +200 -0
@@ -0,0 +1,511 @@
1
+ class SimpleUUID::UUID
2
+ def >=(other)
3
+ (self <=> other) >= 0
4
+ end
5
+
6
+ def <=(other)
7
+ (self <=> other) <= 0
8
+ end
9
+ end
10
+
11
+ class Cassandra
12
+ class Mock
13
+ include ::Cassandra::Helpers
14
+ include ::Cassandra::Columns
15
+
16
+ attr_reader :keyspace
17
+
18
+ def initialize(keyspace, schema)
19
+ @is_super = {}
20
+ @keyspace = keyspace
21
+ @column_name_class = {}
22
+ @sub_column_name_class = {}
23
+ @indexes = {}
24
+ @schema = schema[keyspace]
25
+ clear_keyspace!
26
+ end
27
+
28
+ def disconnect!
29
+ end
30
+
31
+ def clear_keyspace!
32
+ @data = {}
33
+ end
34
+
35
+ def clear_column_family!(column_family)
36
+ @data[column_family.to_sym] = OrderedHash.new
37
+ end
38
+
39
+ def default_write_consistency=(value)
40
+ WRITE_DEFAULTS[:consistency] = value
41
+ end
42
+
43
+ def default_read_consistency=(value)
44
+ READ_DEFAULTS[:consistency] = value
45
+ end
46
+
47
+ def insert(column_family, key, hash_or_array, options = {})
48
+ if @batch
49
+ @batch << [:insert, column_family, key, hash_or_array, options]
50
+ else
51
+ raise ArgumentError if key.nil?
52
+ if !is_super(column_family)
53
+ insert_standard(column_family, key, hash_or_array)
54
+ else
55
+ insert_super(column_family, key, hash_or_array)
56
+ end
57
+ end
58
+ end
59
+
60
+ def insert_standard(column_family, key, hash_or_array)
61
+ old = cf(column_family)[key] || OrderedHash.new
62
+ cf(column_family)[key] = merge_and_sort(old, hash_or_array)
63
+ end
64
+
65
+ def insert_super(column_family, key, hash)
66
+ raise ArgumentError unless hash.is_a?(Hash)
67
+ cf(column_family)[key] ||= OrderedHash.new
68
+
69
+ hash.keys.each do |sub_key|
70
+ old = cf(column_family)[key][sub_key] || OrderedHash.new
71
+ cf(column_family)[key][sub_key] = merge_and_sort(old, hash[sub_key])
72
+ end
73
+ end
74
+
75
+ def batch
76
+ @batch = []
77
+ yield
78
+ b = @batch
79
+ @batch = nil
80
+ b.each do |mutation|
81
+ send(*mutation)
82
+ end
83
+ ensure
84
+ @batch = nil
85
+ end
86
+
87
+ def get(column_family, key, *columns_and_options)
88
+ column_family, column, sub_column, options =
89
+ extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
90
+ if !is_super(column_family)
91
+ get_standard(column_family, key, column, options)
92
+ else
93
+ get_super(column_family, key, column, sub_column, options)
94
+ end
95
+ end
96
+
97
+ def get_standard(column_family, key, column, options)
98
+ columns = cf(column_family)[key] || OrderedHash.new
99
+ row = columns_to_hash(column_family, columns)
100
+
101
+ if column
102
+ row[column]
103
+ else
104
+ row = apply_range(row, column_family, options[:start], options[:finish])
105
+ row = apply_count(row, options[:count], options[:reversed])
106
+ end
107
+ end
108
+
109
+ def get_super(column_family, key, column, sub_column, options)
110
+ columns = cf(column_family)[key] || OrderedHash.new
111
+ row = columns_to_hash(column_family, columns)
112
+
113
+ if column
114
+ if sub_column
115
+ if row[column] &&
116
+ row[column][sub_column]
117
+ row[column][sub_column]
118
+ else
119
+ nil
120
+ end
121
+ else
122
+ row = row[column] || OrderedHash.new
123
+ row = apply_range(row, column_family, options[:start], options[:finish], false)
124
+ row = apply_count(row, options[:count], options[:reversed])
125
+ end
126
+ else
127
+ row
128
+ end
129
+ end
130
+
131
+ def exists?(column_family, key, *columns_and_options)
132
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, [key], columns_and_options, READ_DEFAULTS)
133
+ results = get(column_family, key, column, sub_column)
134
+
135
+ ![{}, nil].include?(results)
136
+ end
137
+
138
+ def multi_get(column_family, keys, *columns_and_options)
139
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, keys, columns_and_options, READ_DEFAULTS)
140
+ keys.inject(OrderedHash.new) do |hash, key|
141
+ hash[key] = get(column_family, key)
142
+ hash
143
+ end
144
+ end
145
+
146
+ def remove(column_family, key, *columns_and_options)
147
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, WRITE_DEFAULTS)
148
+ if @batch
149
+ @batch << [:remove, column_family, key, column, sub_column]
150
+ else
151
+ if column
152
+ if sub_column
153
+ cf(column_family)[key][column].delete(sub_column.to_s) if cf(column_family)[key][column]
154
+ else
155
+ cf(column_family)[key].delete(column.to_s) if cf(column_family)[key]
156
+ end
157
+ else
158
+ cf(column_family).delete(key)
159
+ end
160
+ end
161
+ end
162
+
163
+ def get_columns(column_family, key, *columns_and_options)
164
+ column_family, columns, sub_columns, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, READ_DEFAULTS)
165
+ d = get(column_family, key)
166
+
167
+ if sub_columns
168
+ sub_columns.collect do |sub_column|
169
+ d[columns][sub_column]
170
+ end
171
+ else
172
+ columns.collect do |column|
173
+ d[column]
174
+ end
175
+ end
176
+ end
177
+
178
+ def count_columns(column_family, key, *columns_and_options)
179
+ column_family, columns, sub_columns, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, READ_DEFAULTS)
180
+
181
+ get(column_family, key, columns, options).keys.length
182
+ end
183
+
184
+ def multi_get_columns(column_family, keys, columns)
185
+ keys.inject(OrderedHash.new) do |hash, key|
186
+ hash[key] = get_columns(column_family, key, columns)
187
+ hash
188
+ end
189
+ end
190
+
191
+ def multi_count_columns(column_family, keys)
192
+ keys.inject(OrderedHash.new) do |hash, key|
193
+ hash[key] = count_columns(column_family, key)
194
+ hash
195
+ end
196
+ end
197
+
198
+ def get_range(column_family, options = {}, &blk)
199
+ column_family, _, _, options = extract_and_validate_params_for_real(column_family, "", [options],
200
+ READ_DEFAULTS.merge(:start_key => nil,
201
+ :finish_key => nil,
202
+ :key_count => 100,
203
+ :columns => nil,
204
+ :reversed => false
205
+ )
206
+ )
207
+ res = _get_range(column_family,
208
+ options[:start_key],
209
+ options[:finish_key],
210
+ options[:key_count],
211
+ options[:columns],
212
+ options[:start],
213
+ options[:finish],
214
+ options[:count],
215
+ options[:consistency],
216
+ options[:reversed], &blk)
217
+
218
+ if blk.nil?
219
+ res
220
+ else
221
+ nil
222
+ end
223
+ end
224
+
225
+ def get_range_keys(column_family, options = {})
226
+ get_range(column_family,options.merge!(:columns => [])).keys
227
+ end
228
+
229
+ def count_range(column_family, options = {})
230
+ Hash[get_range(column_family, options).select{|k,v| v.length > 0}].keys.compact.length
231
+ end
232
+
233
+ def each_key(column_family, options = {})
234
+ each(column_family, options.merge!(:columns => [])) do |key, value|
235
+ yield key
236
+ end
237
+ end
238
+
239
+ def each(column_family, options = {})
240
+ batch_size = options.delete(:batch_size) || 100
241
+ count = options.delete(:key_count)
242
+ yielded_count = 0
243
+
244
+ options[:start_key] ||= ''
245
+ last_key = nil
246
+
247
+ while options[:start_key] != last_key && (count.nil? || count > yielded_count)
248
+ options[:start_key] = last_key
249
+ res = get_range(column_family, options.merge!(:start_key => last_key, :key_count => batch_size))
250
+ res.each do |key, columns|
251
+ next if options[:start_key] == key
252
+ next if yielded_count == count
253
+ yield key, columns
254
+ yielded_count += 1
255
+ last_key = key
256
+ end
257
+ end
258
+ end
259
+
260
+ def create_index(ks_name, cf_name, c_name, v_class)
261
+ if @indexes[ks_name] &&
262
+ @indexes[ks_name][cf_name] &&
263
+ @indexes[ks_name][cf_name][c_name]
264
+ nil
265
+
266
+ else
267
+ @indexes[ks_name] ||= {}
268
+ @indexes[ks_name][cf_name] ||= {}
269
+ @indexes[ks_name][cf_name][c_name] = true
270
+ end
271
+ end
272
+
273
+ def drop_index(ks_name, cf_name, c_name)
274
+ if @indexes[ks_name] &&
275
+ @indexes[ks_name][cf_name] &&
276
+ @indexes[ks_name][cf_name][c_name]
277
+
278
+ @indexes[ks_name][cf_name].delete(c_name)
279
+ else
280
+ nil
281
+ end
282
+ end
283
+
284
+ def create_index_expression(c_name, value, op)
285
+ {:column_name => c_name, :value => value, :comparison => op}
286
+ end
287
+ alias :create_idx_expr :create_index_expression
288
+
289
+ def create_index_clause(idx_expressions, start = "", count = 100)
290
+ {:start => start, :index_expressions => idx_expressions, :count => count, :type => :index_clause}
291
+ end
292
+ alias :create_idx_clause :create_index_clause
293
+
294
+ def get_indexed_slices(column_family, idx_clause, *columns_and_options)
295
+ column_family, columns, _, options =
296
+ extract_and_validate_params_for_real(column_family, [], columns_and_options, READ_DEFAULTS.merge(:key_count => 100, :key_start => ""))
297
+
298
+ unless [Hash, OrderedHash].include?(idx_clause.class) && idx_clause[:type] == :index_clause
299
+ idx_clause = create_index_clause(idx_clause, options[:key_start], options[:key_count])
300
+ end
301
+
302
+ ret = {}
303
+ cf(column_family).each do |key, row|
304
+ next if idx_clause[:start] != '' && key < idx_clause[:start]
305
+ next if ret.length == idx_clause[:count]
306
+
307
+ matches = []
308
+ idx_clause[:index_expressions].each do |expr|
309
+ next if row[expr[:column_name]].nil?
310
+ next unless row[expr[:column_name]].send(expr[:comparison].to_sym, expr[:value])
311
+
312
+ matches << expr
313
+ end
314
+
315
+ ret[key] = row if matches.length == idx_clause[:index_expressions].length
316
+ end
317
+
318
+ ret
319
+ end
320
+
321
+ def add(column_family, key, value, *columns_and_options)
322
+ column_family, column, sub_column, options = extract_and_validate_params_for_real(column_family, key, columns_and_options, WRITE_DEFAULTS)
323
+
324
+ if is_super(column_family)
325
+ cf(column_family)[key] ||= OrderedHash.new
326
+ cf(column_family)[key][column] ||= OrderedHash.new
327
+ cf(column_family)[key][column][sub_column] ||= 0
328
+ cf(column_family)[key][column][sub_column] += value
329
+ else
330
+ cf(column_family)[key] ||= OrderedHash.new
331
+ cf(column_family)[key][column] ||= 0
332
+ cf(column_family)[key][column] += value
333
+ end
334
+
335
+ nil
336
+ end
337
+
338
+ def column_families
339
+ cf_defs = {}
340
+ schema.each do |key, value|
341
+ cf_def = Cassandra::ColumnFamily.new
342
+
343
+ value.each do |property, property_value|
344
+ cf_def.send(:"#{property}=", property_value)
345
+ end
346
+
347
+ cf_defs[key] = cf_def
348
+ end
349
+
350
+ cf_defs
351
+ end
352
+
353
+ def schema(load=true)
354
+ @schema
355
+ end
356
+
357
+ def column_family_property(column_family, key)
358
+ schema[column_family.to_s][key]
359
+ end
360
+
361
+ def add_column_family(cf)
362
+ @schema[cf.name.to_s] ||= OrderedHash.new
363
+
364
+ cf.instance_variables.each do |var|
365
+ @schema[cf.name.to_s][var.slice(1..-1)] = cf.instance_variable_get(var)
366
+ end
367
+ end
368
+
369
+ def update_column_family(cf)
370
+ return false unless @schema.include?(cf.name.to_s)
371
+
372
+ cf.instance_variables.each do |var|
373
+ @schema[cf.name.to_s][var.slice(1..-1)] = cf.instance_variable_get(var)
374
+ end
375
+ end
376
+
377
+ def drop_column_family(column_family_name)
378
+ @schema.delete(column_family_name)
379
+ end
380
+
381
+ private
382
+
383
+ def schema_for_keyspace(keyspace)
384
+ @schema
385
+ end
386
+
387
+ def _get_range(column_family, start_key, finish_key, key_count, columns, start, finish, count, consistency, reversed, &blk)
388
+ ret = OrderedHash.new
389
+ start = to_compare_with_type(start, column_family)
390
+ finish = to_compare_with_type(finish, column_family)
391
+ cf(column_family).keys.sort.each do |key|
392
+ break if ret.keys.size >= key_count
393
+ if (start_key.nil? || key >= start_key) && (finish_key.nil? || key <= finish_key)
394
+ if columns
395
+ #ret[key] = columns.inject(OrderedHash.new){|hash, column_name| hash[column_name] = cf(column_family)[key][column_name]; hash;}
396
+ selected_hash = OrderedHash.new
397
+ cf(column_family)[key].each do |k, v|
398
+ selected_hash.[]=(k, v, cf(column_family)[key].timestamps[k]) if columns.include?(k)
399
+ end
400
+ ret[key] = columns_to_hash(column_family, selected_hash)
401
+ ret[key] = apply_count(ret[key], count, reversed)
402
+ blk.call(key,ret[key]) unless blk.nil?
403
+ else
404
+ #ret[key] = apply_range(cf(column_family)[key], column_family, start, finish, !is_super(column_family))
405
+ start, finish = finish, start if reversed
406
+ ret[key] = apply_range(columns_to_hash(column_family, cf(column_family)[key]), column_family, start, finish)
407
+ ret[key] = apply_count(ret[key], count, reversed)
408
+ blk.call(key,ret[key]) unless blk.nil?
409
+ end
410
+ end
411
+ end
412
+ ret
413
+ end
414
+
415
+ def extract_and_validate_params_for_real(column_family, keys, args, options)
416
+ column_family, columns, sub_column, options = extract_and_validate_params(column_family, keys, args, options)
417
+ options[:start] = nil if options[:start] == ''
418
+ options[:finish] = nil if options[:finish] == ''
419
+ [column_family, to_compare_with_types(columns, column_family), to_compare_with_types(sub_column, column_family, false), options]
420
+ end
421
+
422
+ def to_compare_with_types(column_names, column_family, standard=true)
423
+ if column_names.is_a?(Array)
424
+ column_names.collect do |name|
425
+ to_compare_with_type(name, column_family, standard)
426
+ end
427
+ else
428
+ to_compare_with_type(column_names, column_family, standard)
429
+ end
430
+ end
431
+
432
+ def to_compare_with_type(column_name, column_family, standard=true)
433
+ return column_name if column_name.nil?
434
+ klass = if standard
435
+ column_name_class(column_family)
436
+ else
437
+ sub_column_name_class(column_family)
438
+ end
439
+
440
+ klass.new(column_name)
441
+ end
442
+
443
+ def cf(column_family)
444
+ @data[column_family.to_sym] ||= OrderedHash.new
445
+ end
446
+
447
+ def merge_and_sort(old_stuff, new_stuff)
448
+ if new_stuff.is_a?(Array)
449
+ new_stuff = new_stuff.inject({}){|h,k| h[k] = nil; h }
450
+ end
451
+
452
+ new_stuff = new_stuff.to_a.inject({}){|h,k| h[k[0].to_s] = k[1]; h }
453
+
454
+ new_stuff.each { |k,v| old_stuff.[]=(k, v, (Time.now.to_f * 1000000).to_i) }
455
+ hash = OrderedHash.new
456
+ old_stuff.sort{ |a,b| a[0] <=> b[0] }.each do |k, v|
457
+ hash.[]=(k, v, old_stuff.timestamps[k])
458
+ end
459
+ hash
460
+ end
461
+
462
+ def columns_to_hash(column_family, columns)
463
+ column_class, sub_column_class = column_name_class(column_family), sub_column_name_class(column_family)
464
+ output = OrderedHash.new
465
+
466
+ columns.each do |column_name, value|
467
+ timestamp = columns.timestamps[column_name]
468
+ column = column_class.new(column_name)
469
+
470
+ if [Hash, OrderedHash].include?(value.class)
471
+ output[column] ||= OrderedHash.new
472
+ value.each do |sub_column, sub_column_value|
473
+ timestamp = value.timestamps[sub_column]
474
+ output[column].[]=(sub_column_class.new(sub_column), sub_column_value, timestamp)
475
+ end
476
+ else
477
+ output.[]=(column_class.new(column_name), value, timestamp)
478
+ end
479
+ end
480
+
481
+ output
482
+ end
483
+
484
+ def apply_count(row, count, reversed=false)
485
+ if count
486
+ keys = row.keys.sort
487
+ keys = keys.reverse if reversed
488
+ keys = keys[0...count]
489
+ keys.inject(OrderedHash.new) do |memo, key|
490
+ memo.[]=(key, row[key], row.timestamps[key])
491
+ memo
492
+ end
493
+ else
494
+ row
495
+ end
496
+ end
497
+
498
+ def apply_range(row, column_family, strt, fin, standard=true)
499
+ start = to_compare_with_type(strt, column_family, standard)
500
+ finish = to_compare_with_type(fin, column_family, standard)
501
+ ret = OrderedHash.new
502
+ row.keys.each do |key|
503
+ if (start.nil? || key >= start) && (finish.nil? || key <= finish)
504
+ ret.[]=(key, row[key], row.timestamps[key])
505
+ end
506
+ end
507
+ ret
508
+ end
509
+
510
+ end
511
+ end
@@ -0,0 +1,192 @@
1
+ # OrderedHash is namespaced to prevent conflicts with other implementations
2
+ class Cassandra
3
+ class OrderedHashInt < Hash #:nodoc:
4
+ def initialize(*args, &block)
5
+ super
6
+ @keys = []
7
+ end
8
+
9
+ def self.[](*args)
10
+ ordered_hash = new
11
+
12
+ if (args.length == 1 && args.first.is_a?(Array))
13
+ args.first.each do |key_value_pair|
14
+ next unless (key_value_pair.is_a?(Array))
15
+ ordered_hash[key_value_pair[0]] = key_value_pair[1]
16
+ end
17
+
18
+ return ordered_hash
19
+ end
20
+
21
+ unless (args.size % 2 == 0)
22
+ raise ArgumentError.new("odd number of arguments for Hash")
23
+ end
24
+
25
+ args.each_with_index do |val, ind|
26
+ next if (ind % 2 != 0)
27
+ ordered_hash[val] = args[ind + 1]
28
+ end
29
+
30
+ ordered_hash
31
+ end
32
+
33
+ def initialize_copy(other)
34
+ super
35
+ # make a deep copy of keys
36
+ @keys = other.keys
37
+ end
38
+
39
+ def []=(key, value)
40
+ @keys << key if !has_key?(key)
41
+ super
42
+ end
43
+
44
+ def delete(key)
45
+ if has_key? key
46
+ index = @keys.index(key)
47
+ @keys.delete_at index
48
+ end
49
+ super
50
+ end
51
+
52
+ def delete_if
53
+ super
54
+ sync_keys!
55
+ self
56
+ end
57
+
58
+ def reject!
59
+ super
60
+ sync_keys!
61
+ self
62
+ end
63
+
64
+ def reject(&block)
65
+ dup.reject!(&block)
66
+ end
67
+
68
+ def keys
69
+ @keys.dup
70
+ end
71
+
72
+ def values
73
+ @keys.collect { |key| self[key] }
74
+ end
75
+
76
+ def to_hash
77
+ self
78
+ end
79
+
80
+ def to_a
81
+ @keys.map { |key| [ key, self[key] ] }
82
+ end
83
+
84
+ def each_key
85
+ @keys.each { |key| yield key }
86
+ end
87
+
88
+ def each_value
89
+ @keys.each { |key| yield self[key]}
90
+ end
91
+
92
+ def each
93
+ @keys.each {|key| yield [key, self[key]]}
94
+ end
95
+
96
+ alias_method :each_pair, :each
97
+
98
+ def clear
99
+ super
100
+ @keys.clear
101
+ self
102
+ end
103
+
104
+ def shift
105
+ k = @keys.first
106
+ v = delete(k)
107
+ [k, v]
108
+ end
109
+
110
+ def merge!(other_hash)
111
+ other_hash.each {|k,v| self[k] = v }
112
+ self
113
+ end
114
+
115
+ def merge(other_hash)
116
+ dup.merge!(other_hash)
117
+ end
118
+
119
+ # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
120
+ def replace(other)
121
+ super
122
+ @keys = other.keys
123
+ self
124
+ end
125
+
126
+ def reverse
127
+ OrderedHashInt[self.to_a.reverse]
128
+ end
129
+
130
+ private
131
+
132
+ def sync_keys!
133
+ @keys.delete_if {|k| !has_key?(k)}
134
+ end
135
+ end
136
+
137
+ class OrderedHash < OrderedHashInt #:nodoc:
138
+ def initialize(*args, &block)
139
+ @timestamps = OrderedHashInt.new
140
+ super
141
+ end
142
+
143
+ def initialize_copy(other)
144
+ @timestamps = other.timestamps
145
+ super
146
+ end
147
+
148
+ def []=(key, value, timestamp = nil)
149
+ @timestamps[key] = timestamp
150
+ super(key, value)
151
+ end
152
+
153
+ def delete(key)
154
+ @timestamps.delete(key)
155
+ super
156
+ end
157
+
158
+ def delete_if(&block)
159
+ @timestamps.delete_if(&block)
160
+ super
161
+ end
162
+
163
+ def reject!(&block)
164
+ @timestamps.reject!(&block)
165
+ super
166
+ end
167
+
168
+ def timestamps
169
+ @timestamps.dup
170
+ end
171
+
172
+ def clear
173
+ @timestamps.clear
174
+ super
175
+ end
176
+
177
+ def shift
178
+ k, v = super
179
+ @timestamps.delete(k)
180
+ [k, v]
181
+ end
182
+
183
+ def replace(other)
184
+ @timestamps = other.timestamps
185
+ super
186
+ end
187
+
188
+ def inspect
189
+ "#<OrderedHash #{super}\n#{@timestamps.inspect}>"
190
+ end
191
+ end
192
+ end