hbase-jruby 0.2.6-java → 0.3.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,115 @@
1
+ require 'forwardable'
2
+
3
+ class HBase
4
+ class Schema
5
+ extend Forwardable
6
+ def_delegators :@schema, :inspect, :to_s
7
+
8
+ def initialize
9
+ @schema = {}
10
+ @lookup = {}
11
+ end
12
+
13
+ # [cq]
14
+ # [cf:cq]
15
+ # @param [Symbol] table
16
+ # @param [Hash] definition
17
+ def []= table, definition
18
+ if definition.nil? || definition.empty?
19
+ delete table
20
+ return nil
21
+ end
22
+
23
+ unless definition.is_a?(Hash)
24
+ raise ArgumentError, 'invalid schema definition: Hash required'
25
+ end
26
+ definition = definition.dup.freeze
27
+ lookup = empty_lookup_table
28
+
29
+ definition.each do |cf, cols|
30
+ unless [Symbol, String].any? { |k| cf.is_a? k }
31
+ raise ArgumentError,
32
+ "invalid schema: use String or Symbol for column family name"
33
+ end
34
+
35
+ # CF:CQ => Type shortcut
36
+ cf = cf.to_s
37
+ if cf.index(':')
38
+ cf, q = key.to_s.split ':', 2
39
+ cols = { q => cols }
40
+ else
41
+ raise ArgumentError, "invalid schema: expected Hash" unless cols.is_a?(Hash)
42
+ end
43
+
44
+ # Family => { Column => Type }
45
+ cols.each do |cq, type|
46
+ raise ArgumentError, "invalid schema" unless type.is_a?(Symbol)
47
+
48
+ # Pattern
49
+ case cq
50
+ when Regexp
51
+ lookup[:pattern][cq] = [cf, nil, type]
52
+ # Exact
53
+ when String, Symbol
54
+ cq = cq.to_s
55
+ cfcq = [cf, cq].join(':')
56
+ [cq, cq.to_sym, cfcq].each do |key|
57
+ lookup[:exact][key] = [cf, cq.to_sym, type]
58
+ end
59
+ else
60
+ raise ArgumentError, "invalid schema"
61
+ end
62
+ end
63
+ end
64
+
65
+ table = table.to_sym
66
+ @lookup[table] = lookup
67
+ @schema[table] = definition
68
+ end
69
+
70
+ # @private
71
+ # @param [Symbol] table
72
+ # @return [Array] CF, CQ, Type. When not found, nil.
73
+ def lookup table, col
74
+ return nil unless lookup = @lookup[table]
75
+
76
+ if match = lookup[:exact][col]
77
+ return match
78
+ elsif pair = lookup[:pattern].find { |k, v| col.to_s =~ k }
79
+ return pair[1].dup.tap { |e| e[1] = col.to_sym }
80
+ end
81
+ end
82
+
83
+ # @private
84
+ # @param [Symbol] table
85
+ def lookup_and_parse table, col
86
+ cf, cq, type = lookup table, col
87
+ cf, cq = Util.parse_column_name(cf ? [cf, cq] : col)
88
+ return [cf, cq, type]
89
+ end
90
+
91
+ # Delete schema for the table
92
+ # @param [Symbol] table
93
+ def delete table
94
+ table = table.to_sym
95
+ @lookup.delete table
96
+ @schema.delete table
97
+ nil
98
+ end
99
+
100
+ # @return [Hash]
101
+ def to_h
102
+ @schema
103
+ end
104
+
105
+ private
106
+ def empty_lookup_table
107
+ {
108
+ :exact => {},
109
+ :pattern => {},
110
+ }
111
+ end
112
+
113
+ end
114
+ end
115
+
@@ -17,6 +17,20 @@ module Aggregation
17
17
  cpc = 'org.apache.hadoop.hbase.coprocessor.AggregateImplementation'
18
18
  add_coprocessor! cpc, &block unless has_coprocessor?(cpc)
19
19
  end
20
+
21
+ # Disables aggregation support for the table (asynchronous)
22
+ # @return [nil]
23
+ def disable_aggregation
24
+ cpc = 'org.apache.hadoop.hbase.coprocessor.AggregateImplementation'
25
+ remove_coprocessor cpc if has_coprocessor?(cpc)
26
+ end
27
+
28
+ # Disables aggregation support for the table (synchronous)
29
+ # @return [nil]
30
+ def disable_aggregation! &block
31
+ cpc = 'org.apache.hadoop.hbase.coprocessor.AggregateImplementation'
32
+ remove_coprocessor! cpc, &block if has_coprocessor?(cpc)
33
+ end
20
34
  end
21
35
 
22
36
  # Performs aggregation with coprocessor
@@ -24,7 +24,7 @@ class Scoped
24
24
  if block_given?
25
25
  scanner = htable.getScanner(filtered_scan)
26
26
  scanner.each do |result|
27
- cnt += 1 if yield(Row.send(:new, result))
27
+ cnt += 1 if yield(Row.send(:new, @table, result))
28
28
  end
29
29
  else
30
30
  scanner = htable.getScanner(filtered_scan_minimum)
@@ -53,11 +53,11 @@ class Scoped
53
53
  case rowkeys
54
54
  when Array
55
55
  htable.get(rowkeys.map { |rk| getify rk }).map { |result|
56
- result.isEmpty ? nil : Row.new(result)
56
+ result.isEmpty ? nil : Row.send(:new, @table, result)
57
57
  }
58
58
  else
59
59
  result = htable.get(getify rowkeys)
60
- result.isEmpty ? nil : Row.new(result)
60
+ result.isEmpty ? nil : Row.send(:new, @table, result)
61
61
  end
62
62
  end
63
63
 
@@ -67,17 +67,15 @@ class Scoped
67
67
  def each
68
68
  check_closed
69
69
 
70
- if block_given?
71
- begin
72
- scanner = htable.getScanner(filtered_scan)
73
- scanner.each do |result|
74
- yield Row.send(:new, result)
75
- end
76
- ensure
77
- scanner.close if scanner
70
+ return enum_for(:each) unless block_given?
71
+
72
+ begin
73
+ scanner = htable.getScanner(filtered_scan)
74
+ scanner.each do |result|
75
+ yield Row.send(:new, @table, result)
78
76
  end
79
- else
80
- self
77
+ ensure
78
+ scanner.close if scanner
81
79
  end
82
80
  end
83
81
 
@@ -208,7 +206,10 @@ class Scoped
208
206
  end
209
207
  end
210
208
  end
211
- spawn :@project, @project + columns
209
+ spawn :@project, @project + columns.map { |c|
210
+ cf, cq, type = @table.lookup_schema(c)
211
+ cf ? [cf, cq] : c
212
+ }
212
213
  end
213
214
 
214
215
  # Returns an HBase::Scoped object with the specified version number limit.
@@ -408,10 +409,10 @@ private
408
409
  }
409
410
  end
410
411
 
411
- def filter_for cf, cq, val
412
+ def filter_for cf, cq, type, val
412
413
  case val
413
414
  when Range
414
- min, max = [val.begin, val.end].map { |k| Util.to_bytes k }
415
+ min, max = [val.begin, val.end].map { |k| Util.to_typed_bytes type, k }
415
416
  FilterList.new(FilterList::Operator::MUST_PASS_ALL, [
416
417
  SingleColumnValueFilter.new(
417
418
  cf, cq,
@@ -440,7 +441,7 @@ private
440
441
  CompareFilter::CompareOp::NOT_EQUAL
441
442
  else
442
443
  if val.length == 1
443
- return filter_for(cf, cq, Util.to_bytes(val))
444
+ return filter_for(cf, cq, nil, Util.to_typed_bytes(type, val))
444
445
  else
445
446
  raise ArgumentError, "Unknown operator: #{op}"
446
447
  end
@@ -456,11 +457,11 @@ private
456
457
  FilterList::Operator::MUST_PASS_ONE
457
458
  end,
458
459
  v.map { |vv|
459
- SingleColumnValueFilter.new(cf, cq, operator, Util.to_bytes(vv))
460
+ SingleColumnValueFilter.new(cf, cq, operator, Util.to_typed_bytes(type, vv))
460
461
  }
461
462
  )
462
463
  else
463
- SingleColumnValueFilter.new(cf, cq, operator, Util.to_bytes(v))
464
+ SingleColumnValueFilter.new(cf, cq, operator, Util.to_typed_bytes(type, v))
464
465
  end
465
466
  }
466
467
  )
@@ -474,7 +475,7 @@ private
474
475
  SingleColumnValueFilter.new(
475
476
  cf, cq,
476
477
  CompareFilter::CompareOp::EQUAL,
477
- Util.to_bytes(val))
478
+ Util.to_typed_bytes(type, val))
478
479
  end
479
480
  end
480
481
 
@@ -593,14 +594,14 @@ private
593
594
  case f
594
595
  when Hash
595
596
  f.map { |col, val|
596
- cf, cq = Util.parse_column_name col
597
+ cf, cq, type = @table.lookup_and_parse col
597
598
 
598
599
  case val
599
600
  when Array
600
601
  FilterList.new(FilterList::Operator::MUST_PASS_ONE,
601
- val.map { |v| filter_for cf, cq, v })
602
+ val.map { |v| filter_for cf, cq, type, v })
602
603
  else
603
- filter_for cf, cq, val
604
+ filter_for cf, cq, type, val
604
605
  end
605
606
  }.flatten
606
607
  when FilterBase, FilterList
@@ -622,6 +623,12 @@ private
622
623
  end
623
624
  end
624
625
 
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
+
625
632
  def check_closed
626
633
  raise RuntimeError, "HBase connection is already closed" if @table.closed?
627
634
  end
@@ -44,16 +44,16 @@ class Table
44
44
  :time_range, :at
45
45
  ].each do |method|
46
46
  define_method(method) do |*args|
47
- self.each.send(method, *args)
47
+ self.scoped.send(method, *args)
48
48
  end
49
49
  end
50
50
 
51
51
  def with_java_scan &block
52
- self.each.with_java_scan(&block)
52
+ self.scoped.with_java_scan(&block)
53
53
  end
54
54
 
55
55
  def with_java_get &block
56
- self.each.with_java_get(&block)
56
+ self.scoped.with_java_get(&block)
57
57
  end
58
58
 
59
59
  # Performs PUT operations
@@ -122,7 +122,9 @@ class Table
122
122
 
123
123
  htable.delete specs.map { |spec|
124
124
  rowkey, cfcq, *ts = spec
125
- cf, cq = Util.parse_column_name(cfcq) if cfcq
125
+ if cfcq
126
+ cf, cq, _ = lookup_and_parse cfcq
127
+ end
126
128
 
127
129
  Delete.new(Util.to_bytes rowkey).tap { |del|
128
130
  if !ts.empty?
@@ -163,20 +165,30 @@ class Table
163
165
  # @param [Hash] column_by_hash Column expression to increment amount pairs
164
166
  # @example
165
167
  # table.increment('a000', 'cf1:col1' => 1, 'cf1:col2' => 2)
168
+ # @overload increment(inc_spec)
169
+ # Increase values of multiple columns from multiple rows.
170
+ # @param [Hash] inc_spec { rowkey => { col => by } }
171
+ # @example
172
+ # table.increment 'a000' => { 'cf1:col1' => 1, 'cf1:col2' => 2 },
173
+ # 'a001' => { 'cf1:col1' => 3, 'cf1:col2' => 4 }
166
174
  def increment rowkey, *args
167
175
  check_closed
168
176
 
169
- if args.first.is_a?(Hash)
177
+ if args.empty? && rowkey.is_a?(Hash)
178
+ rowkey.each do |key, spec|
179
+ increment key, spec
180
+ end
181
+ elsif args.first.is_a?(Hash)
170
182
  cols = args.first
171
183
  htable.increment Increment.new(Util.to_bytes rowkey).tap { |inc|
172
184
  cols.each do |col, by|
173
- cf, cq = Util.parse_column_name(col)
185
+ cf, cq, _ = lookup_and_parse col
174
186
  inc.addColumn cf, cq, by
175
187
  end
176
188
  }
177
189
  else
178
190
  col, by = args
179
- cf, cq = Util.parse_column_name(col)
191
+ cf, cq = lookup_and_parse col
180
192
  htable.incrementColumnValue Util.to_bytes(rowkey), cf, cq, by || 1
181
193
  end
182
194
  end
@@ -184,23 +196,32 @@ class Table
184
196
  # Scan through the table
185
197
  # @yield [row] Yields each row in the scope
186
198
  # @yieldparam [HBase::Row] row
199
+ # @return [Enumerator]
200
+ def each &block
201
+ scoped.each(&block)
202
+ end
203
+
204
+ # Returns HBase::Scoped object for this table
187
205
  # @return [HBase::Scoped]
188
- def each
189
- check_closed
206
+ def scoped
207
+ Scoped.send(:new, self)
208
+ end
190
209
 
191
- if block_given?
192
- Scoped.send(:new, self).each { |r| yield r }
193
- else
194
- Scoped.send(:new, self)
195
- end
210
+ def lookup_schema col
211
+ @hbase.schema.lookup @name_sym, col
212
+ end
213
+
214
+ def lookup_and_parse col
215
+ @hbase.schema.lookup_and_parse @name_sym, col
196
216
  end
197
217
 
198
218
  private
199
219
  def initialize hbase, config, htable_pool, name
200
- @hbase = hbase
201
- @config = config
202
- @pool = htable_pool
203
- @name = name.to_s
220
+ @hbase = hbase
221
+ @config = config
222
+ @pool = htable_pool
223
+ @name = name.to_s
224
+ @name_sym = name.to_sym
204
225
  end
205
226
 
206
227
  def check_closed
@@ -210,21 +231,22 @@ private
210
231
  def putify rowkey, props
211
232
  Put.new(Util.to_bytes rowkey).tap { |put|
212
233
  props.each do |col, val|
213
- cf, cq = Util.parse_column_name(col)
234
+ cf, cq, type = lookup_and_parse col
235
+
214
236
  case val
215
237
  when Hash
216
238
  val.each do |t, v|
217
239
  case t
218
240
  # Timestamp / Ruby Time
219
241
  when Time, Fixnum
220
- put.add cf, cq, time_to_long(t), Util.to_bytes(v)
242
+ put.add cf, cq, time_to_long(t), Util.to_typed_bytes(type, v)
221
243
  # Types: :byte, :short, :int, ...
222
244
  else
223
- put.add cf, cq, Util.to_bytes(t => v)
245
+ put.add cf, cq, Util.to_typed_bytes(t, v)
224
246
  end
225
247
  end
226
248
  else
227
- put.add cf, cq, Util.to_bytes(val)
249
+ put.add cf, cq, Util.to_typed_bytes(type, val)
228
250
  end
229
251
  end
230
252
  }
@@ -65,6 +65,41 @@ module Util
65
65
  end
66
66
  end
67
67
 
68
+ def to_typed_bytes type, val
69
+ return Util.to_bytes val if type.nil?
70
+
71
+ import_java_classes!
72
+ case type
73
+ when :string, :str, :symbol, :sym
74
+ val.to_s.to_java_bytes
75
+ when :byte
76
+ [val].to_java(Java::byte)
77
+ when :boolean, :bool
78
+ Bytes.to_bytes val
79
+ when :int
80
+ Bytes.java_send :toBytes, [Java::int], val
81
+ when :short
82
+ Bytes.java_send :toBytes, [Java::short], val
83
+ when :long, :fixnum
84
+ Bytes.java_send :toBytes, [Java::long], val
85
+ when :float, :double
86
+ Bytes.java_send :toBytes, [Java::double], val
87
+ when :bigdecimal
88
+ case val
89
+ when BigDecimal
90
+ Bytes.java_send :toBytes, [java.math.BigDecimal], val.to_java
91
+ when java.math.BigDecimal
92
+ Bytes.java_send :toBytes, [java.math.BigDecimal], val
93
+ else
94
+ raise ArgumentError, "not BigDecimal"
95
+ end
96
+ when :raw
97
+ val
98
+ else
99
+ raise ArgumentError, "invalid type: #{type}"
100
+ end
101
+ end
102
+
68
103
  # Returns Ruby object decoded from the byte array according to the given type
69
104
  # @param [Symbol, Class] type Type to convert to
70
105
  # @param [byte[]] val Java byte array
@@ -92,7 +127,7 @@ module Util
92
127
  Bytes.to_double val
93
128
  when :boolean, :bool
94
129
  Bytes.to_boolean val
95
- when :raw
130
+ when :raw, nil
96
131
  val
97
132
  else
98
133
  raise ArgumentError, "Invalid type: #{type}"
@@ -113,8 +148,6 @@ module Util
113
148
  # @param [Object, Array, KeyValue] col
114
149
  def parse_column_name col
115
150
  case col
116
- when ColumnKey
117
- return col.cf.to_java_bytes, col.cq(:raw)
118
151
  when KeyValue
119
152
  return col.getFamily, col.getQualifier
120
153
  when Array
@@ -122,8 +155,9 @@ module Util
122
155
  when '', nil
123
156
  raise ArgumentError, "Column family not specified"
124
157
  else
125
- cf, cq = KeyValue.parseColumn(col.to_s.to_java_bytes)
126
- cq = JAVA_BYTE_ARRAY_EMPTY if cq.nil? && col.to_s[-1, 1] == ':'
158
+ col = col.to_s
159
+ cf, cq = KeyValue.parseColumn(col.to_java_bytes)
160
+ cq = JAVA_BYTE_ARRAY_EMPTY if cq.nil? && col[-1, 1] == ':'
127
161
  return cf, cq
128
162
  end
129
163
  end
@@ -1,5 +1,5 @@
1
1
  class HBase
2
2
  module JRuby
3
- VERSION = '0.2.6'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
data/lib/hbase-jruby.rb CHANGED
@@ -2,19 +2,19 @@ unless RUBY_PLATFORM =~ /java/
2
2
  raise LoadError, 'Only supports JRuby'
3
3
  end
4
4
 
5
- require "hbase-jruby/version"
6
- require "hbase-jruby/dependency"
7
- require "hbase-jruby/util"
8
- require "hbase-jruby/byte_array"
9
- require "hbase-jruby/column_key"
10
- require "hbase-jruby/cell"
11
- require "hbase-jruby/admin"
12
- require "hbase-jruby/scoped/aggregation"
13
- require "hbase-jruby/scoped"
14
- require "hbase-jruby/table"
15
- require "hbase-jruby/table/admin"
16
- require "hbase-jruby/table/inspection"
17
- require "hbase-jruby/row"
5
+ require 'hbase-jruby/version'
6
+ require 'hbase-jruby/dependency'
7
+ require 'hbase-jruby/util'
8
+ require 'hbase-jruby/byte_array'
9
+ require 'hbase-jruby/cell'
10
+ require 'hbase-jruby/admin'
11
+ require 'hbase-jruby/scoped/aggregation'
12
+ require 'hbase-jruby/scoped'
13
+ require 'hbase-jruby/schema'
14
+ require 'hbase-jruby/table'
15
+ require 'hbase-jruby/table/admin'
16
+ require 'hbase-jruby/table/inspection'
17
+ require 'hbase-jruby/row'
18
18
  require 'hbase-jruby/hbase'
19
19
 
20
20
  HBase.import_java_classes!
data/test/helper.rb CHANGED
@@ -7,10 +7,16 @@ SimpleCov.start
7
7
 
8
8
  RECREATE = false
9
9
 
10
+ unless defined? Enumerator
11
+ Enumerator = Enumerable::Enumerator
12
+ end
13
+
10
14
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
11
15
 
12
16
  require "hbase-jruby"
13
- HBase.resolve_dependency!(ENV.fetch('HBASE_JRUBY_TEST_DIST'), :verbose => true)
17
+ if dist = ENV['HBASE_JRUBY_TEST_DIST']
18
+ HBase.resolve_dependency!(dist, :verbose => true)
19
+ end
14
20
  HBase.log4j = { 'log4j.threshold' => 'ERROR' }
15
21
 
16
22
  class TestHBaseJRubyBase < Test::Unit::TestCase
@@ -30,7 +30,7 @@ class TestAggregation < TestHBaseJRubyBase
30
30
 
31
31
  # No projection
32
32
  assert_raise(ArgumentError) { @table.aggregate(:sum) }
33
- assert_raise(ArgumentError) { @table.each.aggregate(:sum) }
33
+ assert_raise(ArgumentError) { @table.scoped.aggregate(:sum) }
34
34
 
35
35
  # Invalid type
36
36
  assert_raise(ArgumentError) { @table.project('cf1:a').aggregate(:sum, :float) }
@@ -39,7 +39,7 @@ class TestByteArray < Test::Unit::TestCase
39
39
  end
40
40
 
41
41
  def test_concat
42
- concat = HBase::ByteArray(100) + HBase::ByteArray(200)
42
+ concat = HBase::ByteArray(100) + HBase::ByteArray[200]
43
43
  assert_instance_of HBase::ByteArray, concat
44
44
  assert_equal 16, concat.to_java_bytes.to_a.length
45
45
 
data/test/test_cell.rb CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift File.expand_path('..', __FILE__)
4
4
  require 'helper'
5
5
  require 'bigdecimal'
6
6
 
7
- class TestCell < Test::Unit::TestCase
7
+ class TestCell < TestHBaseJRubyBase
8
8
  import org.apache.hadoop.hbase.KeyValue
9
9
  Util = HBase::Util
10
10
 
@@ -23,9 +23,9 @@ class TestCell < Test::Unit::TestCase
23
23
  { :byte => 100 } => :byte
24
24
  }.each do |value, type|
25
25
  kv = KeyValue.new("rowkey".to_java_bytes, "hello".to_java_bytes, "world".to_java_bytes, ts, Util.to_bytes(value))
26
- cell = HBase::Cell.new(kv)
26
+ cell = HBase::Cell.new(@table, kv) # FIXME
27
27
 
28
- assert_equal "rowkey", cell.rowkey
28
+ assert_equal "rowkey", cell.rowkey(:string)
29
29
  assert_equal "hello", cell.cf, cell.family
30
30
  assert_equal "world", cell.cq, cell.qualifier
31
31
  assert_equal ts, cell.ts
@@ -35,7 +35,6 @@ class TestCell < Test::Unit::TestCase
35
35
  assert_equal value, cell.send(type)
36
36
  end
37
37
  assert HBase::Util.java_bytes?(cell.raw)
38
- assert_equal HBase::ColumnKey.new(:hello, :world), cell.column_key
39
38
  assert_instance_of String, cell.inspect
40
39
  end
41
40
  end
@@ -51,7 +50,7 @@ class TestCell < Test::Unit::TestCase
51
50
  KeyValue.new("rowkey".to_java_bytes, "apple".to_java_bytes, "beta".to_java_bytes, ts, val),
52
51
  KeyValue.new("rowkey".to_java_bytes, "banana".to_java_bytes, "beta".to_java_bytes, ts, val),
53
52
  KeyValue.new("rowkey".to_java_bytes, "banana".to_java_bytes, "gamma".to_java_bytes, ts, val),
54
- ].map { |kv| HBase::Cell.new kv }
53
+ ].map { |kv| HBase::Cell.new @table, kv }
55
54
 
56
55
  assert_equal cells, cells.reverse.sort
57
56
  end