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 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: :fixnum,
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: :string,
42
- reviews: :fixnum,
43
- stars: :fixnum,
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 a column
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: :fixnum,
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: :string,
243
- reviews: :fixnum,
244
- stars: :fixnum,
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
- title: {
271
- 1353143856665 => "Hello world",
272
- 1352978648642 => "Goodbye world"
273
- },
274
- year: 2013
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
- # Deletes a row
393
+ # Delete a row
393
394
  table.delete('rowkey1')
394
395
 
395
- # Deletes all columns in the specified column family
396
+ # Delete all columns in the specified column family
396
397
  table.delete('rowkey1', :cf1)
397
398
 
398
- # Deletes a column
399
+ # Delete a column
399
400
  table.delete('rowkey1', :author)
400
401
 
401
- # Deletes a column with empty qualifier.
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
- # Deletes a version of a column
409
+ # Delete a version of a column
406
410
  table.delete('rowkey1', :author, 1352978648642)
407
411
 
408
- # Deletes multiple versions of a column
412
+ # Delete multiple versions of a column
409
413
  table.delete('rowkey1', :author, 1352978648642, 1352978649642)
410
414
 
411
- # Batch delete
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:4' => 400) # Ordinary 8-byte integer
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
 
@@ -269,7 +269,7 @@ class Row
269
269
 
270
270
  private
271
271
  def get_value col, with_versions = false
272
- cf, cq = Util.parse_column_name(col)
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
@@ -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
- filters = [FirstKeyOnlyFilter.new, KeyOnlyFilter.new]
558
- if flist = scan.getFilter
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
+
@@ -69,11 +69,17 @@ class Table
69
69
  def put *args
70
70
  check_closed
71
71
 
72
- return put(args.first => args.last) if args.length == 2
73
-
74
- puts = args.first.map { |rowkey, props| putify rowkey, props }
75
- htable.put puts
76
- puts.length
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, column_family)
87
- # Deletes columns with the given column family from the row
92
+ # @overload delete(rowkey, *extra)
93
+ # Deletes columns in the row.
88
94
  # @param [Object] rowkey
89
- # @param [String] column_family
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
- # @overload delete(rowkey, column, *timestamps)
101
- # Deletes specified versions of a column
102
- # @param [Object] rowkey
103
- # @param [String, Array] column Column expression in String "FAMILY:QUALIFIER", or in Array [FAMILY, QUALIFIER]
104
- # @param [*Fixnum] timestamps Timestamps.
105
- # @return [nil]
106
- # @example
107
- # table.delete('a000', 'cf1:col1', 1352978648642)
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<Array>] delete_specs
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 putify rowkey, props
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
@@ -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.to_bytes val
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
@@ -1,5 +1,5 @@
1
1
  class HBase
2
2
  module JRuby
3
- VERSION = '0.3.2'
3
+ VERSION = '0.3.3'
4
4
  end
5
5
  end
data/lib/hbase-jruby.rb CHANGED
@@ -14,6 +14,7 @@ require 'hbase-jruby/schema'
14
14
  require 'hbase-jruby/table'
15
15
  require 'hbase-jruby/table/admin'
16
16
  require 'hbase-jruby/table/inspection'
17
+ require 'hbase-jruby/table/checked_operation'
17
18
  require 'hbase-jruby/row'
18
19
  require 'hbase-jruby/hbase'
19
20
 
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 => :fixnum,
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, book.to_h
216
- assert_equal data[:title], book['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], book[:title]
247
+ assert_equal data[:title], book[:title]
232
248
  assert_equal data[:reviews], book[:reviews]
233
- assert_equal nil, book[:summary]
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 test_null_value
281
+ def test_empty_value
282
282
  10.times do |i|
283
- @table.put i, 'cf1:nil' => i % 2 == 0 ? nil : true
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:nil' => nil).count
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.2
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-03 00:00:00.000000000 Z
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