hbase-jruby 0.3.2-java → 0.3.3-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|