hbase-jruby 0.3.2-java → 0.3.3-java
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.md +15 -0
- data/README.md +83 -41
- data/lib/hbase-jruby/row.rb +1 -1
- data/lib/hbase-jruby/scoped.rb +22 -17
- data/lib/hbase-jruby/table/checked_operation.rb +24 -0
- data/lib/hbase-jruby/table.rb +89 -45
- data/lib/hbase-jruby/util.rb +2 -1
- data/lib/hbase-jruby/version.rb +1 -1
- data/lib/hbase-jruby.rb +1 -0
- data/test/test_schema.rb +68 -7
- data/test/test_scoped.rb +26 -3
- data/test/test_table.rb +150 -0
- metadata +3 -2
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,21 @@
|
|
1
1
|
Changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
0.3.3
|
5
|
+
-----
|
6
|
+
|
7
|
+
0.3.3 changes the way null values are handled, and introduces interface for CAS operations.
|
8
|
+
It is strongly advised that you upgrade to 0.3.3 since it contains important fixes.
|
9
|
+
|
10
|
+
- [PUT will not store null values](https://github.com/junegunn/hbase-jruby/issues/15)
|
11
|
+
- `filter(column: nil)` will match rows without the column
|
12
|
+
- `filter(column: value)` will *NOT* match rows without the column
|
13
|
+
- However, `filter(column: { ne: value })` *WILL* match rows without the column
|
14
|
+
- [Added `HBase::Table#check` method for check-and-put and check-and-delete operations](https://github.com/junegunn/hbase-jruby/issues/14)
|
15
|
+
- `bool = table.check(1, in_print: false).delete(:price)`
|
16
|
+
- `bool = table.check(1, in_print: true).put(price: 100)`
|
17
|
+
- Fix: invalid count when filter is used
|
18
|
+
|
4
19
|
0.3.2
|
5
20
|
-----
|
6
21
|
- Added CDH4.3 dependency profile
|
data/README.md
CHANGED
@@ -24,23 +24,25 @@ hbase = HBase.new
|
|
24
24
|
|
25
25
|
# Define table schema for easier data access
|
26
26
|
hbase.schema = {
|
27
|
+
# Schema for `book` table
|
27
28
|
book: {
|
28
29
|
# Columns in cf1 family
|
29
30
|
cf1: {
|
30
|
-
title: :string,
|
31
|
+
title: :string, # String (UTF-8)
|
31
32
|
author: :string,
|
32
33
|
category: :string,
|
33
|
-
year: :short,
|
34
|
-
pages: :
|
35
|
-
price: :bigdecimal,
|
36
|
-
weight: :float,
|
37
|
-
in_print: :boolean
|
34
|
+
year: :short, # Short integer (2-byte)
|
35
|
+
pages: :int, # Integer (4-byte)
|
36
|
+
price: :bigdecimal, # BigDecimal
|
37
|
+
weight: :float, # Double-precision floating-point number
|
38
|
+
in_print: :boolean, # Boolean (true | false)
|
39
|
+
image: :raw # Java byte array; no automatic type conversion
|
38
40
|
},
|
39
41
|
# Columns in cf2 family
|
40
42
|
cf2: {
|
41
|
-
summary:
|
42
|
-
reviews:
|
43
|
-
stars:
|
43
|
+
summary: :string,
|
44
|
+
reviews: :fixnum, # Long integer (8-byte)
|
45
|
+
stars: :fixnum,
|
44
46
|
/^comment\d+/ => :string
|
45
47
|
}
|
46
48
|
}
|
@@ -54,7 +56,7 @@ unless table.exists?
|
|
54
56
|
end
|
55
57
|
|
56
58
|
# PUT
|
57
|
-
table.put 1
|
59
|
+
table.put 1,
|
58
60
|
title: 'The Golden Bough: A Study of Magic and Religion',
|
59
61
|
author: 'Sir James G. Frazer',
|
60
62
|
category: 'Occult',
|
@@ -63,12 +65,12 @@ table.put 1 => {
|
|
63
65
|
price: BigDecimal('21.50'),
|
64
66
|
weight: 3.0,
|
65
67
|
in_print: true,
|
68
|
+
image: File.open('thumbnail.png', 'rb') { |f| f.read }.to_java_bytes,
|
66
69
|
summary: 'A wide-ranging, comparative study of mythology and religion',
|
67
70
|
reviews: 52,
|
68
71
|
stars: 226,
|
69
72
|
comment1: 'A must-have',
|
70
73
|
comment2: 'Rewarding purchase'
|
71
|
-
}
|
72
74
|
|
73
75
|
# GET
|
74
76
|
book = table.get(1)
|
@@ -78,12 +80,12 @@ as_hash = book.to_h
|
|
78
80
|
|
79
81
|
# SCAN
|
80
82
|
table.range(0..100)
|
83
|
+
.project(:cf1, :reviews, :summary)
|
81
84
|
.filter(year: 1880...1900,
|
82
85
|
in_print: true,
|
83
86
|
category: ['Comics', 'Fiction', /cult/i],
|
84
87
|
price: { lt: BigDecimal('30.00') },
|
85
88
|
summary: /myth/i)
|
86
|
-
.project(:cf1, :reviews)
|
87
89
|
.each do |book|
|
88
90
|
|
89
91
|
# Update columns
|
@@ -92,8 +94,8 @@ table.range(0..100)
|
|
92
94
|
# Atomic increment
|
93
95
|
table.increment book.rowkey, reviews: 1, stars: 5
|
94
96
|
|
95
|
-
# Delete
|
96
|
-
table.delete book.rowkey, :comment1
|
97
|
+
# Delete two columns
|
98
|
+
table.delete book.rowkey, :comment1, :comment2
|
97
99
|
end
|
98
100
|
|
99
101
|
# Delete row
|
@@ -228,20 +230,21 @@ hbase.schema = {
|
|
228
230
|
book: {
|
229
231
|
# Columns in cf1 family
|
230
232
|
cf1: {
|
231
|
-
title: :string,
|
233
|
+
title: :string, # String (UTF-8)
|
232
234
|
author: :string,
|
233
235
|
category: :string,
|
234
|
-
year: :short,
|
235
|
-
pages: :
|
236
|
-
price: :bigdecimal,
|
237
|
-
weight: :float,
|
238
|
-
in_print: :boolean
|
236
|
+
year: :short, # Short integer (2-byte)
|
237
|
+
pages: :int, # Integer (4-byte)
|
238
|
+
price: :bigdecimal, # BigDecimal
|
239
|
+
weight: :float, # Double-precision floating-point number
|
240
|
+
in_print: :boolean, # Boolean (true | false)
|
241
|
+
image: :raw # Java byte array; no automatic type conversion
|
239
242
|
},
|
240
243
|
# Columns in cf2 family
|
241
244
|
cf2: {
|
242
|
-
summary:
|
243
|
-
reviews:
|
244
|
-
stars:
|
245
|
+
summary: :string,
|
246
|
+
reviews: :fixnum, # Long integer (8-byte)
|
247
|
+
stars: :fixnum,
|
245
248
|
/^comment\d+/ => :string
|
246
249
|
}
|
247
250
|
}
|
@@ -266,13 +269,12 @@ table.put 'rowkey1' => { title: 'foo', year: 2013 },
|
|
266
269
|
'rowkey3' => { title: 'foobar', year: 2015 }
|
267
270
|
|
268
271
|
# Putting values with timestamps
|
269
|
-
table.put 'rowkey1'
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
}
|
272
|
+
table.put 'rowkey1',
|
273
|
+
title: {
|
274
|
+
1353143856665 => "Hello world",
|
275
|
+
1352978648642 => "Goodbye world"
|
276
|
+
},
|
277
|
+
year: 2013
|
276
278
|
```
|
277
279
|
|
278
280
|
### GET
|
@@ -322,12 +324,11 @@ Hash is returned with which you can also reference them with `FAMILY:QUALIFIER`
|
|
322
324
|
notation or `[cf, cq]` array notation.
|
323
325
|
|
324
326
|
```ruby
|
325
|
-
table.put 1000
|
327
|
+
table.put 1000,
|
326
328
|
title: 'Hello world', # Known column
|
327
329
|
comment100: 'foo', # Known column
|
328
330
|
'cf2:extra' => 'bar', # Unknown column
|
329
331
|
[:cf2, 10] => 'foobar' # Unknown column, non-string qualifier
|
330
|
-
}
|
331
332
|
|
332
333
|
book = table.get 10000
|
333
334
|
hash = book.to_h
|
@@ -389,26 +390,35 @@ cells = row.to_a
|
|
389
390
|
### DELETE
|
390
391
|
|
391
392
|
```ruby
|
392
|
-
#
|
393
|
+
# Delete a row
|
393
394
|
table.delete('rowkey1')
|
394
395
|
|
395
|
-
#
|
396
|
+
# Delete all columns in the specified column family
|
396
397
|
table.delete('rowkey1', :cf1)
|
397
398
|
|
398
|
-
#
|
399
|
+
# Delete a column
|
399
400
|
table.delete('rowkey1', :author)
|
400
401
|
|
401
|
-
#
|
402
|
+
# Delete multiple columns
|
403
|
+
table.delete('rowkey1', :author, :title, :image)
|
404
|
+
|
405
|
+
# Delete a column with empty qualifier.
|
402
406
|
# (!= deleing the entire columns in the family. See the trailing colon.)
|
403
407
|
table.delete('rowkey1', 'cf1:')
|
404
408
|
|
405
|
-
#
|
409
|
+
# Delete a version of a column
|
406
410
|
table.delete('rowkey1', :author, 1352978648642)
|
407
411
|
|
408
|
-
#
|
412
|
+
# Delete multiple versions of a column
|
409
413
|
table.delete('rowkey1', :author, 1352978648642, 1352978649642)
|
410
414
|
|
411
|
-
#
|
415
|
+
# Delete multiple versions of multiple columns
|
416
|
+
# - Two versions of :author
|
417
|
+
# - One version of :title
|
418
|
+
# - All versions of :image
|
419
|
+
table.delete('rowkey1', :author, 1352978648642, 1352978649642, :title, 1352978649642, :image)
|
420
|
+
|
421
|
+
# Batch delete; combination of aforementioned arguments each given as an Array
|
412
422
|
table.delete(['rowkey1'], ['rowkey2'], ['rowkey3', :author, 1352978648642, 135297864964])
|
413
423
|
```
|
414
424
|
|
@@ -421,6 +431,19 @@ table.delete_row 'rowkey1'
|
|
421
431
|
table.delete_row 'rowkey1', 'rowkey2', 'rowkey3'
|
422
432
|
```
|
423
433
|
|
434
|
+
### Checked PUT and DELETE
|
435
|
+
|
436
|
+
```ruby
|
437
|
+
table.check(:rowkey, in_print: false)
|
438
|
+
.put(in_print: true, price: BigDecimal('10.0'))
|
439
|
+
|
440
|
+
table.check(:rowkey, in_print: false)
|
441
|
+
.delete(:price, :image)
|
442
|
+
# Takes the same parameters as those of HBase::Table#delete
|
443
|
+
# except for the first rowkey
|
444
|
+
# https://github.com/junegunn/hbase-jruby#delete
|
445
|
+
```
|
446
|
+
|
424
447
|
### Atomic increment of column values
|
425
448
|
|
426
449
|
```ruby
|
@@ -567,9 +590,12 @@ table.range(nil, 1000).
|
|
567
590
|
year: 2013,
|
568
591
|
|
569
592
|
# Range of numbers or characters: Checks if the value falls within the range
|
570
|
-
weight: 2.0..4.0
|
593
|
+
weight: 2.0..4.0,
|
571
594
|
author: 'A'..'C'
|
572
595
|
|
596
|
+
# Will match rows *without* price column
|
597
|
+
price: nil,
|
598
|
+
|
573
599
|
# Regular expression: Checks if the value matches the regular expression
|
574
600
|
summary: /classic$/i,
|
575
601
|
|
@@ -995,8 +1021,24 @@ you can then use the special Hash representation of integers.
|
|
995
1021
|
table.put({ int: 12345 }, 'cf1:a' => { byte: 100 }, # 1-byte integer
|
996
1022
|
'cf1:b' => { short: 200 }, # 2-byte integer
|
997
1023
|
'cf1:c' => { int: 300 }, # 4-byte integer
|
998
|
-
'cf1:
|
1024
|
+
'cf1:d' => 400) # Ordinary 8-byte integer
|
1025
|
+
|
1026
|
+
row = table.get(int: 12345)
|
1027
|
+
```
|
1028
|
+
|
1029
|
+
The use of these Hash-notations can be minimized if we define table schema as follows.
|
1030
|
+
|
1031
|
+
```ruby
|
1032
|
+
hbase.schema[table.name] = {
|
1033
|
+
cf1: {
|
1034
|
+
a: :byte,
|
1035
|
+
b: :short,
|
1036
|
+
c: :int,
|
1037
|
+
d: :fixnum
|
1038
|
+
}
|
1039
|
+
}
|
999
1040
|
|
1041
|
+
table.put({ int: 12345 }, a: 100, b: 200, c: 300, d: 400)
|
1000
1042
|
row = table.get(int: 12345)
|
1001
1043
|
```
|
1002
1044
|
|
data/lib/hbase-jruby/row.rb
CHANGED
@@ -269,7 +269,7 @@ class Row
|
|
269
269
|
|
270
270
|
private
|
271
271
|
def get_value col, with_versions = false
|
272
|
-
cf, cq =
|
272
|
+
cf, cq, _ = @table.lookup_and_parse col
|
273
273
|
if with_versions
|
274
274
|
# Need to make it a Ruby hash:
|
275
275
|
# Prevents implicit conversion from ruby type to java type when updating the Hash
|
data/lib/hbase-jruby/scoped.rb
CHANGED
@@ -416,11 +416,13 @@ private
|
|
416
416
|
FilterList.new(FilterList::Operator::MUST_PASS_ALL, [
|
417
417
|
SingleColumnValueFilter.new(
|
418
418
|
cf, cq,
|
419
|
-
CompareFilter::CompareOp::GREATER_OR_EQUAL, min
|
419
|
+
CompareFilter::CompareOp::GREATER_OR_EQUAL, min
|
420
|
+
).tap { |f| f.setFilterIfMissing(true) },
|
420
421
|
SingleColumnValueFilter.new(
|
421
422
|
cf, cq,
|
422
423
|
(val.exclude_end? ? CompareFilter::CompareOp::LESS :
|
423
|
-
CompareFilter::CompareOp::LESS_OR_EQUAL), max
|
424
|
+
CompareFilter::CompareOp::LESS_OR_EQUAL), max
|
425
|
+
).tap { |f| f.setFilterIfMissing(true) }
|
424
426
|
])
|
425
427
|
when Hash
|
426
428
|
FilterList.new(FilterList::Operator::MUST_PASS_ALL,
|
@@ -457,11 +459,15 @@ private
|
|
457
459
|
FilterList::Operator::MUST_PASS_ONE
|
458
460
|
end,
|
459
461
|
v.map { |vv|
|
460
|
-
SingleColumnValueFilter.new(cf, cq, operator, Util.to_typed_bytes(type, vv))
|
462
|
+
SingleColumnValueFilter.new(cf, cq, operator, Util.to_typed_bytes(type, vv)).tap { |f|
|
463
|
+
f.setFilterIfMissing( op != :ne )
|
464
|
+
}
|
461
465
|
}
|
462
466
|
)
|
463
467
|
else
|
464
|
-
SingleColumnValueFilter.new(cf, cq, operator, Util.to_typed_bytes(type, v))
|
468
|
+
SingleColumnValueFilter.new(cf, cq, operator, Util.to_typed_bytes(type, v)).tap { |f|
|
469
|
+
f.setFilterIfMissing( op != :ne )
|
470
|
+
}
|
465
471
|
end
|
466
472
|
}
|
467
473
|
)
|
@@ -470,12 +476,21 @@ private
|
|
470
476
|
cf, cq,
|
471
477
|
CompareFilter::CompareOp::EQUAL,
|
472
478
|
RegexStringComparator.new(val.to_s)
|
479
|
+
).tap { |f| f.setFilterIfMissing(true) }
|
480
|
+
when nil
|
481
|
+
# - has value < '' -> not ok
|
482
|
+
# - no value -> ok
|
483
|
+
SingleColumnValueFilter.new(
|
484
|
+
cf, cq,
|
485
|
+
CompareFilter::CompareOp::LESS,
|
486
|
+
HBase::Util::JAVA_BYTE_ARRAY_EMPTY
|
473
487
|
)
|
474
488
|
else
|
475
489
|
SingleColumnValueFilter.new(
|
476
490
|
cf, cq,
|
477
491
|
CompareFilter::CompareOp::EQUAL,
|
478
|
-
Util.to_typed_bytes(type, val)
|
492
|
+
Util.to_typed_bytes(type, val)
|
493
|
+
).tap { |f| f.setFilterIfMissing(true) }
|
479
494
|
end
|
480
495
|
end
|
481
496
|
|
@@ -554,12 +569,8 @@ private
|
|
554
569
|
|
555
570
|
# A filter that will only return the first KV from each row
|
556
571
|
# A filter that will only return the key component of each KV
|
557
|
-
|
558
|
-
|
559
|
-
filters.each do |filter|
|
560
|
-
flist.addFilter filter
|
561
|
-
end
|
562
|
-
else
|
572
|
+
unless scan.getFilter
|
573
|
+
filters = [FirstKeyOnlyFilter.new, KeyOnlyFilter.new]
|
563
574
|
scan.setFilter FilterList.new(filters)
|
564
575
|
end
|
565
576
|
end
|
@@ -623,12 +634,6 @@ private
|
|
623
634
|
end
|
624
635
|
end
|
625
636
|
|
626
|
-
# @private
|
627
|
-
def typed_bytes col, v
|
628
|
-
_, _, type = @table.lookup_schema(col)
|
629
|
-
Util.to_typed_bytes(type || :raw, v)
|
630
|
-
end
|
631
|
-
|
632
637
|
def check_closed
|
633
638
|
raise RuntimeError, "HBase connection is already closed" if @table.closed?
|
634
639
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class HBase
|
2
|
+
class Table
|
3
|
+
class CheckedOperation
|
4
|
+
def initialize table, rowkey, cf, cq, val
|
5
|
+
@table = table
|
6
|
+
@rowkey = rowkey
|
7
|
+
@cf = cf
|
8
|
+
@cq = cq
|
9
|
+
@val = val
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [Hash] props
|
13
|
+
def put props
|
14
|
+
@table.htable.checkAndPut @rowkey, @cf, @cq, @val, @table.send(:make_put, @rowkey, props)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Object] *extra Optional delete specification. Column family, qualifier, and timestamps
|
18
|
+
def delete *extra
|
19
|
+
@table.htable.checkAndDelete @rowkey, @cf, @cq, @val, @table.send(:make_delete, @rowkey, *extra)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
data/lib/hbase-jruby/table.rb
CHANGED
@@ -69,11 +69,17 @@ class Table
|
|
69
69
|
def put *args
|
70
70
|
check_closed
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
72
|
+
case args.length
|
73
|
+
when 1
|
74
|
+
puts = args.first.map { |rowkey, props| make_put rowkey, props }
|
75
|
+
htable.put puts
|
76
|
+
puts.length
|
77
|
+
when 2
|
78
|
+
htable.put make_put(*args)
|
79
|
+
1
|
80
|
+
else
|
81
|
+
raise ArgumentError, 'invalid number of arguments'
|
82
|
+
end
|
77
83
|
end
|
78
84
|
|
79
85
|
# Deletes data
|
@@ -83,31 +89,31 @@ class Table
|
|
83
89
|
# @return [nil]
|
84
90
|
# @example
|
85
91
|
# table.delete('a000')
|
86
|
-
# @overload delete(rowkey,
|
87
|
-
# Deletes columns
|
92
|
+
# @overload delete(rowkey, *extra)
|
93
|
+
# Deletes columns in the row.
|
88
94
|
# @param [Object] rowkey
|
89
|
-
# @param [
|
90
|
-
# @return [nil]
|
91
|
-
# @example
|
92
|
-
# table.delete('a000', 'cf1')
|
93
|
-
# @overload delete(rowkey, column)
|
94
|
-
# Deletes a column
|
95
|
-
# @param [Object] rowkey
|
96
|
-
# @param [String, Array] column Column expression in String "FAMILY:QUALIFIER", or in Array [FAMILY, QUALIFIER]
|
95
|
+
# @param [*Object] extra [Family|Qualifier [Timestamp ...]] ...
|
97
96
|
# @return [nil]
|
98
97
|
# @example
|
98
|
+
# # A column (with schema)
|
99
|
+
# table.delete('a000', :title)
|
100
|
+
# # Two columns (with schema)
|
101
|
+
# table.delete('a000', :title, :author)
|
102
|
+
# # A column (without schema)
|
99
103
|
# table.delete('a000', 'cf1:col1')
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
# table.delete('a000',
|
104
|
+
# # Columns in cf1 family
|
105
|
+
# table.delete('a000', 'cf1')
|
106
|
+
# # A version
|
107
|
+
# table.delete('a000', :author, 1352978648642)
|
108
|
+
# # Two versions
|
109
|
+
# table.delete('a000', :author, 1352978648642, 1352978647642)
|
110
|
+
# # Combination of columns and versions
|
111
|
+
# table.delete('a000', :author, 1352978648642, 1352978647642,
|
112
|
+
# :title,
|
113
|
+
# :image, 1352978648642)
|
108
114
|
# @overload delete(*delete_specs)
|
109
115
|
# Batch deletion
|
110
|
-
# @param [Array
|
116
|
+
# @param [*Array] delete_specs
|
111
117
|
# @return [nil]
|
112
118
|
# @example
|
113
119
|
# table.delete(
|
@@ -120,25 +126,7 @@ class Table
|
|
120
126
|
|
121
127
|
specs = args.first.is_a?(Array) ? args : [args]
|
122
128
|
|
123
|
-
htable.delete specs.map { |spec|
|
124
|
-
rowkey, cfcq, *ts = spec
|
125
|
-
if cfcq
|
126
|
-
cf, cq, _ = lookup_and_parse cfcq
|
127
|
-
end
|
128
|
-
|
129
|
-
Delete.new(Util.to_bytes rowkey).tap { |del|
|
130
|
-
if !ts.empty?
|
131
|
-
ts.each do |t|
|
132
|
-
del.deleteColumn cf, cq, time_to_long(t)
|
133
|
-
end
|
134
|
-
elsif cq
|
135
|
-
# Delete all versions
|
136
|
-
del.deleteColumns cf, cq
|
137
|
-
elsif cf
|
138
|
-
del.deleteFamily cf
|
139
|
-
end
|
140
|
-
}
|
141
|
-
}
|
129
|
+
htable.delete specs.map { |spec| spec.empty? ? nil : make_delete(*spec) }.compact
|
142
130
|
end
|
143
131
|
|
144
132
|
# Delete rows.
|
@@ -207,10 +195,28 @@ class Table
|
|
207
195
|
Scoped.send(:new, self)
|
208
196
|
end
|
209
197
|
|
198
|
+
# Returns CheckedOperation instance for check-and-put and check-and-delete
|
199
|
+
# @param [Object] rowkey
|
200
|
+
# @param [Hash] cond
|
201
|
+
# @return [HBase::Table::CheckedOperation]
|
202
|
+
def check rowkey, cond
|
203
|
+
raise ArgumentError, 'invalid check condition' unless cond.length == 1
|
204
|
+
col, val = cond.first
|
205
|
+
|
206
|
+
cf, cq, type = lookup_and_parse(col)
|
207
|
+
|
208
|
+
# If the passed value is null, the check is for the lack of column
|
209
|
+
CheckedOperation.new self, Util.to_bytes(rowkey),
|
210
|
+
cf, cq,
|
211
|
+
(val.nil? ? nil : Util.to_typed_bytes(type, val))
|
212
|
+
end
|
213
|
+
|
214
|
+
# @private
|
210
215
|
def lookup_schema col
|
211
216
|
@hbase.schema.lookup @name_sym, col
|
212
217
|
end
|
213
218
|
|
219
|
+
# @private
|
214
220
|
def lookup_and_parse col
|
215
221
|
@hbase.schema.lookup_and_parse @name_sym, col
|
216
222
|
end
|
@@ -228,9 +234,11 @@ private
|
|
228
234
|
raise RuntimeError, "HBase connection is already closed" if @hbase.closed?
|
229
235
|
end
|
230
236
|
|
231
|
-
def
|
237
|
+
def make_put rowkey, props
|
232
238
|
Put.new(Util.to_bytes rowkey).tap { |put|
|
233
239
|
props.each do |col, val|
|
240
|
+
next if val.nil?
|
241
|
+
|
234
242
|
cf, cq, type = lookup_and_parse col
|
235
243
|
|
236
244
|
case val
|
@@ -243,12 +251,48 @@ private
|
|
243
251
|
# Types: :byte, :short, :int, ...
|
244
252
|
else
|
245
253
|
put.add cf, cq, Util.to_typed_bytes(t, v)
|
246
|
-
end
|
254
|
+
end unless v.nil?
|
247
255
|
end
|
248
256
|
else
|
249
257
|
put.add cf, cq, Util.to_typed_bytes(type, val)
|
250
258
|
end
|
251
259
|
end
|
260
|
+
raise ArgumentError, "no column to put" if put.empty?
|
261
|
+
}
|
262
|
+
end
|
263
|
+
|
264
|
+
def make_delete rowkey, *extra
|
265
|
+
Delete.new(Util.to_bytes rowkey).tap { |del|
|
266
|
+
cf = cq = nil
|
267
|
+
prcd = false
|
268
|
+
|
269
|
+
prc = lambda do
|
270
|
+
unless prcd
|
271
|
+
if cq
|
272
|
+
# Delete all versions
|
273
|
+
del.deleteColumns cf, cq
|
274
|
+
elsif cf
|
275
|
+
del.deleteFamily cf
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
extra.each do |x|
|
281
|
+
case x
|
282
|
+
when Fixnum, Time
|
283
|
+
if cq
|
284
|
+
del.deleteColumn cf, cq, time_to_long(x)
|
285
|
+
prcd = true
|
286
|
+
else
|
287
|
+
raise ArgumentError, 'qualifier not given'
|
288
|
+
end
|
289
|
+
else
|
290
|
+
prc.call
|
291
|
+
cf, cq, _ = lookup_and_parse x
|
292
|
+
prcd = false
|
293
|
+
end
|
294
|
+
end
|
295
|
+
prc.call
|
252
296
|
}
|
253
297
|
end
|
254
298
|
end#Table
|
data/lib/hbase-jruby/util.rb
CHANGED
@@ -66,6 +66,7 @@ module Util
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def to_typed_bytes type, val
|
69
|
+
return nil if val.nil?
|
69
70
|
return Util.to_bytes val if type.nil?
|
70
71
|
|
71
72
|
import_java_classes!
|
@@ -75,7 +76,7 @@ module Util
|
|
75
76
|
when :byte
|
76
77
|
[val].to_java(Java::byte)
|
77
78
|
when :boolean, :bool
|
78
|
-
Bytes.
|
79
|
+
Bytes.java_send :toBytes, [Java::boolean], val
|
79
80
|
when :int
|
80
81
|
Bytes.java_send :toBytes, [Java::int], val
|
81
82
|
when :short
|
data/lib/hbase-jruby/version.rb
CHANGED
data/lib/hbase-jruby.rb
CHANGED
data/test/test_schema.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
2
3
|
|
3
4
|
$LOAD_PATH.unshift File.expand_path('..', __FILE__)
|
4
5
|
require 'helper'
|
@@ -177,10 +178,11 @@ class TestSchema < TestHBaseJRubyBase
|
|
177
178
|
:author => :string,
|
178
179
|
:category => :string,
|
179
180
|
:year => :short,
|
180
|
-
:pages => :
|
181
|
+
:pages => :int,
|
181
182
|
:price => :bigdecimal,
|
182
183
|
:weight => :float,
|
183
|
-
:in_print => :boolean
|
184
|
+
:in_print => :boolean,
|
185
|
+
:image => :raw
|
184
186
|
},
|
185
187
|
# Columns in cf2 family
|
186
188
|
:cf2 => {
|
@@ -201,6 +203,7 @@ class TestSchema < TestHBaseJRubyBase
|
|
201
203
|
:price => BigDecimal('21.50'),
|
202
204
|
:weight => 3.0,
|
203
205
|
:in_print => true,
|
206
|
+
:image => File.open(__FILE__, 'rb') { |f| f.read }.to_java_bytes, # 안녕?
|
204
207
|
:summary => 'A wide-ranging, comparative study of mythology and religion',
|
205
208
|
:reviews => 52,
|
206
209
|
:stars => 226,
|
@@ -208,36 +211,52 @@ class TestSchema < TestHBaseJRubyBase
|
|
208
211
|
:comment2 => 'Rewarding purchase'
|
209
212
|
}
|
210
213
|
table.put 1, data
|
214
|
+
# Since we can't directly compare java byte arrays
|
215
|
+
data[:image] = HBase::ByteArray[ data[:image] ]
|
211
216
|
|
212
217
|
# Get data (rowkey: 1)
|
213
218
|
book = table.get 1
|
214
219
|
|
215
|
-
assert_equal data,
|
216
|
-
assert_equal data[:title],
|
220
|
+
assert_equal data, book.to_h.tap { |h| h[:image] = HBase::ByteArray[ h[:image] ] }
|
221
|
+
assert_equal data[:title], book['title']
|
217
222
|
assert_equal data[:comment2], book['comment2']
|
218
223
|
|
224
|
+
assert HBase::Util.java_bytes?(book[:image])
|
225
|
+
if defined?(Encoding) # --1.8
|
226
|
+
assert_equal Encoding::ASCII_8BIT, book[:image].to_s.encoding
|
227
|
+
end
|
228
|
+
|
219
229
|
assert_equal true, book.to_H.values.map(&:keys).flatten.all? { |e| e.is_a? Fixnum }
|
220
230
|
|
221
231
|
# Scan table
|
232
|
+
assert_equal 1890, table.range(0..100).first[:year]
|
233
|
+
assert_equal 2, table.range(0..100).first.raw(:year).length
|
234
|
+
assert_equal 1, table.range(0..100).filter(:year => 1890).to_a.length
|
235
|
+
assert_equal 1, table.range(0..100).filter(:year => 1890).count
|
236
|
+
assert_equal 1, table.range(0..100).filter(:year => 1880...1900).count
|
237
|
+
cnt = 0
|
222
238
|
table.range(0..100).
|
223
239
|
filter(:year => 1880...1900,
|
224
240
|
:in_print => true,
|
225
241
|
:category => ['Comics', 'Fiction', /cult/i],
|
226
242
|
:price => { :lt => BigDecimal('30.00') },
|
227
243
|
:summary => /myth/i).
|
228
|
-
project(:cf1, :reviews).
|
244
|
+
project(:cf1, :reviews, :summary).
|
229
245
|
each do |book|
|
230
246
|
|
231
|
-
assert_equal data[:title],
|
247
|
+
assert_equal data[:title], book[:title]
|
232
248
|
assert_equal data[:reviews], book[:reviews]
|
233
|
-
assert_equal
|
249
|
+
assert_equal data[:summary], book[:summary]
|
250
|
+
assert_equal nil, book[:comment1]
|
234
251
|
|
235
252
|
# Update price
|
236
253
|
table.put book.rowkey => { :price => book[:price] + BigDecimal('1') }
|
237
254
|
|
238
255
|
# Atomic increment
|
239
256
|
table.increment book.rowkey, :reviews => 1, :stars => 5
|
257
|
+
cnt += 1
|
240
258
|
end
|
259
|
+
assert_equal 1, cnt
|
241
260
|
|
242
261
|
assert_equal data[:price] + 1.0, table.get(1)[:price]
|
243
262
|
assert_equal data[:reviews] + 1, table.get(1)[:reviews]
|
@@ -293,5 +312,47 @@ class TestSchema < TestHBaseJRubyBase
|
|
293
312
|
# Drop table for subsequent tests
|
294
313
|
table.drop!
|
295
314
|
end
|
315
|
+
|
316
|
+
def test_schema_nil_values
|
317
|
+
@hbase.schema[@table.name] = {
|
318
|
+
:cf1 => {
|
319
|
+
:a => :fixnum,
|
320
|
+
:b => :string,
|
321
|
+
:c => :short,
|
322
|
+
:d => :string
|
323
|
+
}
|
324
|
+
}
|
325
|
+
|
326
|
+
assert_raise(ArgumentError) {
|
327
|
+
@table.put 1, :a => nil, :b => nil, :c => nil, 'cf1:z' => nil
|
328
|
+
}
|
329
|
+
@table.put 1, :a => nil, :b => nil, :c => nil, :d => 'yo', 'cf1:z' => 1000
|
330
|
+
h = @table.get(1).to_h
|
331
|
+
assert !h.has_key?(:a)
|
332
|
+
assert !h.has_key?(:b)
|
333
|
+
assert !h.has_key?(:c)
|
334
|
+
assert h.has_key?(:d)
|
335
|
+
|
336
|
+
assert_equal nil, h[:a]
|
337
|
+
assert_equal nil, h[:b]
|
338
|
+
assert_equal nil, h[:c]
|
339
|
+
assert_equal 'yo', h[:d]
|
340
|
+
assert_equal 1000, HBase::Util.from_bytes(:fixnum, h['cf1:z'])
|
341
|
+
end
|
342
|
+
|
343
|
+
def test_schema_delete
|
344
|
+
@hbase.schema[@table.name] = {
|
345
|
+
:cf1 => { :a => :fixnum }
|
346
|
+
}
|
347
|
+
|
348
|
+
@table.put 1, :a => 100
|
349
|
+
assert_equal 100, @table.get(1)[:a]
|
350
|
+
assert_equal 100, @table.get(1)['cf1:a']
|
351
|
+
|
352
|
+
@hbase.schema.delete @table.name
|
353
|
+
assert_equal nil, @table.get(1)[:a]
|
354
|
+
assert_equal true, HBase::Util.java_bytes?(@table.get(1)['cf1:a'])
|
355
|
+
assert_equal 100, HBase::Util.from_bytes(:fixnum, @table.get(1)['cf1:a'])
|
356
|
+
end
|
296
357
|
end
|
297
358
|
|
data/test/test_scoped.rb
CHANGED
@@ -278,12 +278,35 @@ class TestScoped < TestHBaseJRubyBase
|
|
278
278
|
}
|
279
279
|
end
|
280
280
|
|
281
|
-
def
|
281
|
+
def test_empty_value
|
282
282
|
10.times do |i|
|
283
|
-
@table.put i, 'cf1:
|
283
|
+
@table.put i, 'cf1:empty' => i % 2 == 0 ? '' : true
|
284
284
|
end
|
285
285
|
assert_equal 10, @table.count
|
286
|
-
assert_equal 5, @table.filter('cf1:
|
286
|
+
assert_equal 5, @table.filter('cf1:empty' => '').count
|
287
|
+
end
|
288
|
+
|
289
|
+
def test_null_filter
|
290
|
+
10.times do |i|
|
291
|
+
if i % 2 == 0
|
292
|
+
@table.put i, 'cf1:col1' => true
|
293
|
+
else
|
294
|
+
@table.put i, 'cf1:col2' => true
|
295
|
+
end
|
296
|
+
end
|
297
|
+
20.times do |i|
|
298
|
+
@table.put i + 10, 'cf1:col1' => 100, 'cf1:col2' => 100
|
299
|
+
end
|
300
|
+
|
301
|
+
assert_equal 30, @table.count
|
302
|
+
assert_equal 30, @table.filter('cf1:what' => nil).count
|
303
|
+
assert_equal 5, @table.filter('cf1:col1' => nil).count
|
304
|
+
assert_equal 5, @table.filter('cf1:col2' => nil).count
|
305
|
+
assert_equal 5, @table.filter('cf1:col1' => true).count
|
306
|
+
assert_equal 5, @table.filter('cf1:col2' => true).count
|
307
|
+
assert_equal 20, @table.filter('cf1:col1' => 90..100).count
|
308
|
+
assert_equal 20, @table.filter('cf1:col2' => 100..102).count
|
309
|
+
assert_equal 10, @table.filter('cf1:col1' => { :ne => 100 }).count
|
287
310
|
end
|
288
311
|
|
289
312
|
def test_scoped_get_intra_row_scan
|
data/test/test_table.rb
CHANGED
@@ -230,6 +230,68 @@ class TestTable < TestHBaseJRubyBase
|
|
230
230
|
assert_equal 2, @table.get('row3').fixnum('cf1:b')
|
231
231
|
end
|
232
232
|
|
233
|
+
def test_delete_advanced
|
234
|
+
@table.put('row1', 'cf1' => 0, 'cf1:a' => 1, 'cf1:b' => 2, 'cf2:c' => 3, 'cf2:d' => 4)
|
235
|
+
@table.delete('row1', 'cf1:', 'cf1:b', 'cf2')
|
236
|
+
assert_equal 1, @table.get('row1').to_h.keys.length
|
237
|
+
assert_equal 1, @table.get('row1').fixnum('cf1:a')
|
238
|
+
|
239
|
+
ts = Time.now
|
240
|
+
@table.put('drow2', 'cf1:a' => { 1000 => 1, 2000 => 2, 3000 => 3 },
|
241
|
+
'cf1:b' => { 4000 => 4, 5000 => 5, 6000 => 6 },
|
242
|
+
'cf2:c' => 3, 'cf2:d' => 4, 'cf3:e' => 5)
|
243
|
+
@table.delete('drow2', 'cf1:a', 1000, Time.at(2),
|
244
|
+
'cf2:c',
|
245
|
+
'cf1:b', 5000,
|
246
|
+
'cf3')
|
247
|
+
|
248
|
+
assert_equal 3, @table.get('drow2').to_h.keys.length
|
249
|
+
|
250
|
+
assert_equal 1, @table.get('drow2').to_H['cf1:a'].length
|
251
|
+
assert_equal 2, @table.get('drow2').to_H['cf1:b'].length
|
252
|
+
assert_equal 3000, @table.get('drow2').to_H['cf1:a'].keys.first
|
253
|
+
assert_equal [6000, 4000], @table.get('drow2').to_H['cf1:b'].keys
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_delete_advanced_with_schema
|
257
|
+
@hbase.schema[@table.name] = {
|
258
|
+
:cf1 => {
|
259
|
+
:a => :int,
|
260
|
+
:b => :short,
|
261
|
+
},
|
262
|
+
:cf2 => {
|
263
|
+
:c => :long,
|
264
|
+
:d => :byte
|
265
|
+
},
|
266
|
+
:cf3 => {
|
267
|
+
:e => :fixnum
|
268
|
+
}
|
269
|
+
}
|
270
|
+
@table.put('row1', 'cf1' => 0, :a => 1, :b => 2, :c => 3, :d => 4)
|
271
|
+
@table.delete('row1', 'cf1:', :b, 'cf2')
|
272
|
+
assert_equal 1, @table.get('row1').to_h.keys.length
|
273
|
+
assert_equal 1, @table.get('row1').to_h[:a]
|
274
|
+
assert_equal 1, @table.get('row1').int(:a)
|
275
|
+
|
276
|
+
ts = Time.now
|
277
|
+
@table.put('drow3', :a => { 1000 => 1, 2000 => 2, 3000 => 3 },
|
278
|
+
:b => { 4000 => 4, 5000 => 5, 6000 => 6 },
|
279
|
+
:c => 3,
|
280
|
+
:d => 4,
|
281
|
+
:e => 5)
|
282
|
+
@table.delete('drow3', :a, 1000, Time.at(2),
|
283
|
+
:c,
|
284
|
+
[:cf1, :b], 5000,
|
285
|
+
'cf3')
|
286
|
+
|
287
|
+
assert_equal 3, @table.get('drow3').to_h.keys.length
|
288
|
+
|
289
|
+
assert_equal 1, @table.get('drow3').to_H[:a].length
|
290
|
+
assert_equal 2, @table.get('drow3').to_H[:b].length
|
291
|
+
assert_equal 3000, @table.get('drow3').to_H[:a].keys.first
|
292
|
+
assert_equal [6000, 4000], @table.get('drow3').to_H[:b].keys
|
293
|
+
end
|
294
|
+
|
233
295
|
def test_delete_row
|
234
296
|
@table.put(1 => { 'cf1:a' => 1 }, 's' => { 'cf1:a' => 2 }, { :short => 3 } => { 'cf1:a' => 3 })
|
235
297
|
|
@@ -245,5 +307,93 @@ class TestTable < TestHBaseJRubyBase
|
|
245
307
|
assert_equal nil, @table.get({ :short => 3 })
|
246
308
|
assert_equal 1, @table.count
|
247
309
|
end
|
310
|
+
|
311
|
+
def test_check
|
312
|
+
assert_raise(ArgumentError) { @table.check(1, :a => 1, :b => 2) }
|
313
|
+
assert_raise(ArgumentError) { @table.check(1) }
|
314
|
+
end
|
315
|
+
|
316
|
+
def test_check_and_put
|
317
|
+
@hbase.schema[@table.name] = {
|
318
|
+
:cf1 => {
|
319
|
+
:a => :short,
|
320
|
+
:b => :short,
|
321
|
+
:c => :short
|
322
|
+
}
|
323
|
+
}
|
324
|
+
|
325
|
+
[
|
326
|
+
# Without schema
|
327
|
+
[1, 'cf1:a', 'cf1:b', 'cf1:c'],
|
328
|
+
# [2, 'cf1:a', 'cf1:b', 'cf1:c'],
|
329
|
+
# With schema
|
330
|
+
[1, :a, :b, :c],
|
331
|
+
# [2, :a, :b, :c],
|
332
|
+
].each do |args|
|
333
|
+
@table.delete_row 1
|
334
|
+
@table.put 1, 'cf1:a' => 100
|
335
|
+
|
336
|
+
rk, a, b, c = args
|
337
|
+
|
338
|
+
# not nil
|
339
|
+
assert_equal false, @table.check(rk, a => 200).put(b => 300)
|
340
|
+
assert_equal nil, @table.get(rk).short(b)
|
341
|
+
|
342
|
+
assert_equal true, @table.check(rk, a => 100).put(b => 300)
|
343
|
+
assert_equal 300, @table.get(rk).short(b)
|
344
|
+
|
345
|
+
# nil
|
346
|
+
assert_equal false, @table.check(rk, a => nil).put(c => 300)
|
347
|
+
assert_equal nil, @table.get(rk).short(c)
|
348
|
+
|
349
|
+
assert_equal true, @table.check(rk, c => nil).put(c => 300)
|
350
|
+
assert_equal 300, @table.get(rk).short(c)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def test_check_and_delete
|
355
|
+
@hbase.schema[@table.name] = {
|
356
|
+
:cf1 => {
|
357
|
+
:a => :short,
|
358
|
+
:b => :short,
|
359
|
+
:c => :short
|
360
|
+
}
|
361
|
+
}
|
362
|
+
|
363
|
+
[
|
364
|
+
['cf1:a', 'cf1:b', 'cf1:c', 'cf2:d'],
|
365
|
+
[:a, :b, :c, :d]
|
366
|
+
].each do |abcd|
|
367
|
+
a, b, c, d = abcd
|
368
|
+
|
369
|
+
rk = (Time.now.to_f * 1000).to_i
|
370
|
+
ts = Time.now
|
371
|
+
@table.put rk, a => 100, b => 200,
|
372
|
+
c => { ts => 300, (ts - 1000) => 400, (ts - 2000).to_i => 500 },
|
373
|
+
d => 1000
|
374
|
+
assert_equal 3, @table.get(rk).to_H[:c].length
|
375
|
+
|
376
|
+
assert_equal false, @table.check(rk, a => 200).delete(b)
|
377
|
+
assert_equal 200, @table.get(rk)[b]
|
378
|
+
|
379
|
+
assert_equal true, @table.check(rk, a => 100).delete(b)
|
380
|
+
assert_equal nil, @table.get(rk)[b]
|
381
|
+
|
382
|
+
assert_equal true, @table.check(rk, a => 100).delete(c, ts, (ts - 2000).to_i, 'cf2')
|
383
|
+
assert_equal 1, @table.get(rk).to_H[:c].length
|
384
|
+
assert_equal (ts - 1000).to_i, @table.get(rk).to_H[:c].keys.first / 1000
|
385
|
+
assert_equal nil, @table.get(rk)[d]
|
386
|
+
|
387
|
+
assert_equal true, @table.check(rk, a => 100).delete
|
388
|
+
assert_equal nil, @table.get(rk)
|
389
|
+
|
390
|
+
@table.delete rk
|
391
|
+
|
392
|
+
@hbase.schema[@table.name] = {
|
393
|
+
:cf1 => { :a => :fixnum, :b => :fixnum, :c => :fixnum },
|
394
|
+
:cf2 => { :d => :fixnum }
|
395
|
+
}
|
396
|
+
end
|
397
|
+
end
|
248
398
|
end
|
249
399
|
|
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: hbase-jruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.3.
|
5
|
+
version: 0.3.3
|
6
6
|
platform: java
|
7
7
|
authors:
|
8
8
|
- Junegunn Choi
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-06-
|
12
|
+
date: 2013-06-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: test-unit
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- lib/hbase-jruby/scoped/aggregation.rb
|
71
71
|
- lib/hbase-jruby/table.rb
|
72
72
|
- lib/hbase-jruby/table/admin.rb
|
73
|
+
- lib/hbase-jruby/table/checked_operation.rb
|
73
74
|
- lib/hbase-jruby/table/inspection.rb
|
74
75
|
- lib/hbase-jruby/util.rb
|
75
76
|
- lib/hbase-jruby/version.rb
|