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 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