hbase-jruby 0.1.1-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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hbase-jruby.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Junegunn Choi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,623 @@
1
+ # hbase-jruby
2
+
3
+ *hbase-jruby* is a Ruby-esque interface for accessing HBase from JRuby.
4
+
5
+ You can of course just use the native Java APIs of HBase,
6
+ but doing so requires a lot of keystrokes even for the most basic operations and
7
+ can easily lead to overly verbose code that will be frowned upon by Rubyists.
8
+ Anyhow, JRuby is Ruby, not Java, right?
9
+
10
+ *hbase-jruby* provides the followings:
11
+ - Easy, Ruby-esque interface for the fundamental HBase operations
12
+ - ActiveRecord-like method chaining for scanning tables
13
+ - Automatic Hadoop/HBase dependency resolution
14
+
15
+ ## A quick example
16
+
17
+ ```ruby
18
+ require 'hbase-jruby'
19
+
20
+ hbase = HBase.new
21
+ table = hbase.table(:test_table)
22
+
23
+ # PUT
24
+ table.put :rowkey1 => { 'cf1:a' => 100, 'cf2:b' => "Hello" }
25
+
26
+ # GET
27
+ row = table.get(:rowkey1)
28
+ number = row.integer('cf1:a')
29
+ string = row.string('cf1:b')
30
+
31
+ # SCAN
32
+ table.range('rowkey1'..'rowkey9').
33
+ filter('cf1:a' => 100..200, # cf1:a between 100 and 200
34
+ 'cf2:b' => 'Hello', # cf1:b = 'Hello'
35
+ 'cf2:c' => ['foo', 'bar']). # cf2:c in ('foo', 'bar')
36
+ project('cf1:a', 'cf2').each do |row|
37
+ puts row.integer('cf1:a')
38
+ end
39
+
40
+ # DELETE
41
+ table.delete(:rowkey9)
42
+ ```
43
+
44
+ ## Installation
45
+
46
+ Add this line to your application's Gemfile:
47
+
48
+ gem 'hbase-jruby'
49
+
50
+ And then execute:
51
+
52
+ $ bundle
53
+
54
+ Or install it yourself as:
55
+
56
+ $ gem install hbase-jruby
57
+
58
+ ## Setting up
59
+
60
+ ### Resolving Hadoop/HBase dependency
61
+
62
+ To be able to access HBase from JRuby, Hadoop/HBase dependency must be satisfied.
63
+ This can be done by setting up CLASSPATH variable beforehand
64
+ or by `require`ing relevant JAR files after launch.
65
+ However, downloading all the JAR files and manually putting them in CLASSPATH is a PITA,
66
+ especially when HBase is not installed on local system.
67
+
68
+ *hbase-jruby* includes `HBase.resolve_dependency!` helper method,
69
+ which resolves Hadoop/HBase dependency.
70
+
71
+ #### Preconfigured dependencies
72
+
73
+ Apache Maven is the de facto standard dependency management mechanism for Java projects.
74
+ Current version of *hbase-jruby* is shipped with Maven dependency specifications
75
+ for the following Hadoop/HBase distributions.
76
+
77
+ * cdh4.1.2
78
+ * Recommended as of now
79
+ * cdh3u5
80
+ * Does not support some features
81
+
82
+ ```ruby
83
+ require 'hbase-jruby'
84
+
85
+ HBase.resolve_dependency! 'cdh4.1.2'
86
+ ```
87
+
88
+ #### Customized dependencies
89
+
90
+ If you use another version of HBase and Hadoop,
91
+ you can use your own Maven pom.xml file with its customized Hadoop/HBase dependency
92
+
93
+ ```ruby
94
+ HBase.resolve_dependency! '/project/my-hbase/pom.xml'
95
+ ```
96
+
97
+ #### Using `hbase classpath` command
98
+
99
+ If you have HBase installed on your system, it's possible to find the JAR files
100
+ for that local installation with `hbase classpath` command.
101
+ You can tell `resolve_dependency!` method to do so by passing it special `:hbase` parameter.
102
+
103
+ ```ruby
104
+ HBase.resolve_dependency! :hbase
105
+ ```
106
+
107
+ ### Connecting to HBase
108
+
109
+ ```ruby
110
+ # HBase on localhost
111
+ hbase = HBase.new
112
+
113
+ # HBase on remote host
114
+ hbase = HBase.new 'hbase.zookeeper.quorum' => 'remote-server.mydomain.net'
115
+
116
+ # Extra configuration
117
+ hbase = HBase.new 'hbase.zookeeper.quorum' => 'remote-server.mydomain.net',
118
+ 'hbase.client.retries.number' => 3
119
+
120
+ # Close HBase connection
121
+ hbase.close
122
+ ```
123
+
124
+ ## Accessing data with HBase::Table instance
125
+
126
+ `HBase#table` method creates an `HBase::Table` instance which represents a table on HBase.
127
+
128
+ ```ruby
129
+ table = hbase.table(:test_table)
130
+ ```
131
+
132
+ `HBase::Table` instance must be closed after use.
133
+
134
+ ```ruby
135
+ # Always close table instance after use
136
+ table.close
137
+
138
+ # If block is given, table is automatically closed at the end of the block
139
+ hbase.table(:test_table) do |table|
140
+ # ...
141
+ end
142
+ ```
143
+
144
+ ## Basic table administration
145
+
146
+ ### Creating tables
147
+
148
+ ```ruby
149
+ table = hbase.table(:my_table)
150
+
151
+ # Drop table if exists
152
+ table.drop! if table.exists?
153
+
154
+ # Create table with two column families
155
+ table.create! :cf1 => {},
156
+ :cf2 => { :compression => :snappy, :bloomfilter => :row }
157
+ ```
158
+
159
+ ### Table inspection
160
+
161
+ ```ruby
162
+ puts table.inspect
163
+ ```
164
+
165
+ ## Basic operations
166
+
167
+ ### PUT
168
+
169
+ ```ruby
170
+ # Putting a single row
171
+ table.put 'rowkey1', 'cf1:col1' => "Hello", 'cf2:col2' => "World"
172
+
173
+ # Putting multiple rows
174
+ table.put 'rowkey1' => { 'cf1:col1' => "Hello", 'cf2:col2' => "World" },
175
+ 'rowkey2' => { 'cf1:col1' => "Howdy", 'cf2:col2' => "World" },
176
+ 'rowkey3' => { 'cf1:col1' => "So long", 'cf2:col2' => "World" }
177
+ ```
178
+
179
+ ### GET
180
+
181
+ HBase stores everything as a byte array, so when you fetch data from HBase,
182
+ you need to explicitly specify the type of each value stored.
183
+
184
+ ```ruby
185
+ row = table.get('rowkey1')
186
+
187
+ # Rowkey
188
+ rowk = row.rowkey
189
+
190
+ # Column value as a raw Java byte array
191
+ col0 = row.raw 'cf1:col0'
192
+
193
+ # Decode column values
194
+ col1 = row.string 'cf1:col1'
195
+ col2 = row.fixnum 'cf1:col2'
196
+ col3 = row.bignum 'cf1:col3'
197
+ col4 = row.float 'cf1:col4'
198
+ col5 = row.boolean 'cf1:col5'
199
+ col6 = row.symbol 'cf1:col6'
200
+
201
+ # Decode multiple columns at once
202
+ row.string ['cf1:str1', 'cf1:str2']
203
+ # [ "Hello", "World" ]
204
+ ```
205
+
206
+ #### Batch GET
207
+
208
+ ```ruby
209
+ # Pass an array of row keys as the parameter
210
+ rows = table.get(['rowkey1', 'rowkey2', 'rowkey3'])
211
+ ```
212
+
213
+ #### Decode all versions with plural-form (-s) methods
214
+
215
+ ```ruby
216
+ # Decode all versions as Hash indexed by their timestamps
217
+ row.strings 'cf1:str'
218
+ # {1353143856665=>"Hello", 1353143856662=>"Goodbye"}
219
+
220
+ # Decode all versions of multiple columns
221
+ row.strings ['cf1:str1', 'cf1:str2']
222
+ # [
223
+ # {1353143856665=>"Hello", 1353143856662=>"Goodbye"},
224
+ # {1353143856665=>"World", 1353143856662=>"Cruel world"}
225
+ # ]
226
+
227
+ # Plural-form methods are provided for any other data types as well
228
+ cols0 = row.raws 'cf1:col0'
229
+ cols1 = row.strings 'cf1:col1'
230
+ cols2 = row.fixnums 'cf1:col2'
231
+ cols3 = row.bignums 'cf1:col3'
232
+ cols4 = row.floats 'cf1:col4'
233
+ cols5 = row.booleans 'cf1:col5'
234
+ cols6 = row.symbols 'cf1:col6'
235
+ ```
236
+
237
+ #### Intra-row scan
238
+
239
+ Intra-row scan can be done with `each` method which yields `HBase::Cell` instances.
240
+
241
+ ```ruby
242
+ # Intra-row scan (all versions)
243
+ row.each do |cell|
244
+ family = cell.family
245
+ qualifier = cell.qualifier(:string) # Column qualifier as String
246
+ timestamp = cell.timestamp
247
+
248
+ # Cell value as Java byte array
249
+ bytes = cell.bytes
250
+
251
+ # Typed access
252
+ # value_as_string = cell.string
253
+ # value_as_fixnum = cell.fixnum
254
+ # ...
255
+ end
256
+ ```
257
+
258
+ #### `to_hash`
259
+
260
+ ```ruby
261
+ # Returns the Hash representation of the record with the specified schema
262
+ schema = {
263
+ 'cf1:col1' => :string,
264
+ 'cf1:col2' => :fixnum,
265
+ 'cf1:col3' => :bignum,
266
+ 'cf1:col4' => :float,
267
+ 'cf1:col5' => :boolean,
268
+ 'cf1:col6' => :symbol }
269
+
270
+ table.get('rowkey1').to_hash(schema)
271
+
272
+ # Returns all versions for each column indexed by their timestamps
273
+ table.get('rowkey1').to_hash_with_versions(schema)
274
+ ```
275
+
276
+ ### DELETE
277
+
278
+ ```ruby
279
+ # Deletes a row
280
+ table.delete('rowkey1')
281
+
282
+ # Deletes all columns in the specified column family
283
+ table.delete('rowkey1', 'cf1')
284
+
285
+ # Deletes a column
286
+ table.delete('rowkey1', 'cf1:col1')
287
+
288
+ # Deletes a column with empty qualifier.
289
+ # (!= deleing the entire columns in the family. See the trailing colon.)
290
+ table.delete('rowkey1', 'cf1:')
291
+
292
+ # Deletes a version of a column
293
+ table.delete('rowkey1', 'cf1:col1', 1352978648642)
294
+
295
+ # Deletes multiple versions of a column
296
+ table.delete('rowkey1', 'cf1:col1', 1352978648642, 1352978649642)
297
+
298
+ # Batch delete
299
+ table.delete(['rowkey1'], ['rowkey2'], ['rowkey3', 'cf1:col1'])
300
+
301
+ # Truncate table
302
+ table.truncate!
303
+ ```
304
+
305
+ ### Atomic increment of column values
306
+
307
+ ```ruby
308
+ # Atomically increase cf1:counter by one
309
+ table.increment('rowkey1', 'cf1:counter', 1)
310
+
311
+ # Atomically increase two columns by one an two respectively
312
+ table.increment('rowkey1', 'cf1:counter' => 1, 'cf1:counter2' => 2)
313
+ ```
314
+
315
+ ### SCAN
316
+
317
+ `HBase::Table` itself is an enumerable object.
318
+
319
+ ```ruby
320
+ # Full scan
321
+ table.each do |row|
322
+ # ...
323
+ end
324
+ ```
325
+
326
+ ## Scoped access
327
+
328
+ SCAN and GET operations are actually implemented in enumerable `HBase::Scoped` class,
329
+ whose instance is created by `HBase::Table#each` call.
330
+
331
+ ```ruby
332
+ scoped = table.each
333
+ scoped.get(1)
334
+ scoped.to_a
335
+ ```
336
+
337
+ An `HBase::Scoped` object provides a set of methods for controlling data retrieval
338
+ such as `range`, `filter`, `project`, `versions`, `caching`, et cetera.
339
+ However, it doesn't respond to data manipulation methods (`put`, `delete` and `increment`),
340
+ and methods for table administration.
341
+
342
+ An `HBase::Table` object also responds to the data retrieval methods described above,
343
+ but those calls are simply forwarded to a new `HBase::Scoped` object implicitly created.
344
+ For example, `table.range(start, end)` is just a shorthand notation for
345
+ `table.each.range(start, end)`.
346
+
347
+ ### Chaining methods
348
+
349
+ Methods of `HBase::Scoped` can be chained as follows.
350
+
351
+ ```ruby
352
+ # Chaining methods
353
+ import org.apache.hadoop.hbase.filter.RandomRowFilter
354
+
355
+ table.range('A'..'Z'). # Row key range,
356
+ project('cf1:a'). # Select cf1:a column
357
+ project('cf2'). # Select cf2 family as well
358
+ filter('cf1:a' => 'Hello'). # Filter by cf1:a value
359
+ filter('cf2:d' => 100..200). # Range filter on cf2:d
360
+ filter('cf2:e' => [10, 20..30]). # Set-inclusion condition on cf2:e
361
+ filter(RandomRowFilter.new(0.5)). # Any Java HBase filter
362
+ limit(10). # Limits the size of the result set
363
+ versions(2). # Only fetches 2 versions for each value
364
+ batch(100). # Batch size for scan set to 100
365
+ caching(100). # Caching 100 rows
366
+ to_a # To Array
367
+ ```
368
+
369
+ ### range
370
+
371
+ `HBase::Scoped#range` method is used to filter rows based on their row keys.
372
+
373
+ ```ruby
374
+ # 100 ~ 900 (inclusive end)
375
+ table.range(100..900)
376
+
377
+ # 100 ~ 900 (exclusive end)
378
+ table.range(100...900)
379
+
380
+ # 100 ~ 900 (exclusive end)
381
+ table.range(100, 900)
382
+
383
+ # 100 ~
384
+ table.range(100)
385
+
386
+ # ~ 900 (exclusive end)
387
+ table.range(nil, 900)
388
+ ```
389
+
390
+ Optionally, prefix filter can be applied as follows.
391
+
392
+ ```ruby
393
+ # Prefix filter
394
+ # Row keys with "APPLE" prefix
395
+ # Start key is automatically set to "APPLE",
396
+ # stop key "APPLF" to avoid unnecessary disk access
397
+ table.range(:prefix => 'APPLE')
398
+
399
+ # Row keys with "ACE", "BLUE" or "APPLE" prefix
400
+ # Start key is automatically set to "ACE",
401
+ # stop key "BLUF"
402
+ table.range(:prefix => ['ACE', 'BLUE', 'APPLE'])
403
+
404
+ # Prefix filter with start key and stop key.
405
+ table.range('ACE', 'BLUEMARINE', :prefix => ['ACE', 'BLUE', 'APPLE'])
406
+ ```
407
+
408
+ Subsequent calls to `#range` override the range previously defined.
409
+
410
+ ```ruby
411
+ # Previous ranges are discarded
412
+ scope.range(1, 100).
413
+ range(50..100).
414
+ range(:prefix => 'A').
415
+ range(1, 1000)
416
+ # Same as `scope.range(1, 1000)`
417
+ ```
418
+
419
+ ### filter
420
+
421
+ You can configure server-side filtering of rows and columns with `HBase::Scoped#filter` calls.
422
+ Multiple calls have conjunctive effects.
423
+
424
+ ```ruby
425
+ # Range scanning the table with filters
426
+ table.range(nil, 1000).
427
+ filter('cf1:a' => 'Hello', # cf1:a = 'Hello'
428
+ 'cf1:b' => 100...200, # cf1:b between 100 and 200
429
+ 'cf1:c' => %w[A B C], # cf1:c in ('A', 'B', 'C')
430
+ 'cf1:d' => ['A'...'B', 'C'], # ('A' <= cf1:d < 'B') or cf1:d = 'C'
431
+ 'cf1:e' => { gt: 1000, lte: 2000 }). # cf1:e > 1000 and cf1:e <= 2000
432
+ 'cf1:f' => { ne: 1000 }). # cf1:f != 1000
433
+ # Supported operators: gt, lt, gte, lte, eq, ne
434
+ filter('cf1:g' => ['Alice'..'Bob', 'Cat']). # Multiple calls for conjunctive filtering
435
+ filter(org.apache.hadoop.hbase.filter.RandomRowFilter.new(0.5)).
436
+ # Any number of Java filters can be applied
437
+ each do |record|
438
+ # ...
439
+ end
440
+ ```
441
+
442
+ ### project
443
+
444
+ `HBase::Scoped#project` allows you to fetch only a subset of columns from each row.
445
+ Multiple calls have additive effects.
446
+
447
+ ```ruby
448
+ # Fetches cf1:a and all columns in column family cf2 and cf3
449
+ scoped.project('cf1:a', 'cf2').
450
+ project('cf3')
451
+ ```
452
+
453
+ HBase filters can not only filter rows but also columns.
454
+ Since column filtering can be thought of as a kind of projection,
455
+ it makes sense to internally apply column filters in `HBase::Scoped#project`,
456
+ instead of in `HBase::Scoped#filter`, although it's still perfectly valid
457
+ to pass column filter to filter method.
458
+
459
+ ```ruby
460
+ # Column prefix filter:
461
+ # Fetch columns whose qualifiers start with the specified prefixes
462
+ scoped.project(:prefix => 'alice').
463
+ project(:prefix => %w[alice bob])
464
+
465
+ # Column range filter:
466
+ # Fetch columns whose qualifiers within the ranges
467
+ scoped.project(:range => 'a'...'c').
468
+ project(:range => ['i'...'k', 'x'...'z'])
469
+
470
+ # Column pagination filter (Cannot be chained. Must be called exactly once.):
471
+ # Fetch columns within the specified intra-scan offset and limit
472
+ scoped.project(:offset => 1000, :limit => 10)
473
+ ```
474
+
475
+ When using column filters on *fat* rows with many columns,
476
+ it's advised that you limit the batch size with `HBase::Scoped#batch` call
477
+ to avoid fetching all columns at once.
478
+ However setting batch size allows multiple rows with the same row key are returned during scan.
479
+
480
+ ```ruby
481
+ # Let's say that we have rows with more than 10 columns whose qualifiers start with `str`
482
+ puts scoped.range(1..100).
483
+ project(:prefix => 'str').
484
+ batch(10).
485
+ map { |row| [row.rowkey(:fixnum), row.count].map(&:to_s).join ': ' }
486
+
487
+ # 1: 10
488
+ # 1: 10
489
+ # 1: 5
490
+ # 2: 10
491
+ # 2: 2
492
+ # 3: 10
493
+ # ...
494
+ ```
495
+
496
+ ### Scoped SCAN / GET
497
+
498
+ ```ruby
499
+ scoped = table.versions(1). # Limits the number of versions
500
+ filter('cf1:a' => 'Hello', # With filters
501
+ 'cf1:b' => 100...200,
502
+ 'cf1:c' => 'Alice'..'Bob').
503
+ range('rowkey0'..'rowkey2') # Range of rowkeys.
504
+ project('cf1', 'cf2:x') # Projection
505
+
506
+ # Scoped GET
507
+ # Nonexistent or filtered rows are returned as nils
508
+ scoped.get(['rowkey1', 'rowkey2', 'rowkey4'])
509
+
510
+ # Scoped SCAN
511
+ scoped.each do |row|
512
+ row.each do |cell|
513
+ # Intra-row scan
514
+ end
515
+ end
516
+
517
+ # Scoped COUNT
518
+ # When counting the number of rows, use `HTable::Scoped#count`
519
+ # instead of just iterating through the scope, as it internally
520
+ # minimizes amount of data fetched with KeyOnlyFilter
521
+ scoped.count
522
+ ```
523
+
524
+ ## Advanced topics
525
+
526
+ ### Lexicographic scan order
527
+
528
+ HBase stores rows in the lexicographic order of the rowkeys in their byte array representations.
529
+ Thus the type of row key affects the scan order.
530
+
531
+ ```ruby
532
+ (1..15).times do |i|
533
+ table.put i, data
534
+ table.put i.to_s, data
535
+ end
536
+
537
+ table.range(1..3).map { |r| r.rowkey :fixnum }
538
+ # [1, 2, 3]
539
+ table.range('1'..'3').map { |r| r.rowkey :string }
540
+ # %w[1 10 11 12 13 14 15 2 3]
541
+ ```
542
+
543
+ ### Non-string column qualifier
544
+
545
+ If a column qualifier is not a String, *an HBase::ColumnKey instance* should be used
546
+ instead of a conventional `FAMILY:QUALIFIER` String.
547
+
548
+ ```ruby
549
+ table.put 'rowkey',
550
+ 'cf1:col1' => 'Hello world',
551
+ HBase::ColumnKey(:cf1, 100) => "Byte representation of an 8-byte integer",
552
+ HBase::ColumnKey(:cf1, bytes) => "Qualifier is an arbitrary byte array"
553
+
554
+ table.get('rowkey').string('cf1:col1')
555
+ table.get('rowkey').string(HBase::ColumnKey(:cf1, 100))
556
+ # ...
557
+ ```
558
+
559
+
560
+ ### Table administration
561
+
562
+ `HBase#Table` provides a few *synchronous* table administration methods.
563
+
564
+ ```ruby
565
+ # Create a table with configurable table-level properties
566
+ table.create!(
567
+ # 1st Hash: Column family specification
568
+ { :cf1 => { :compression => :snappy }, :cf2 => {} },
569
+
570
+ # 2nd Hash: Table properties
571
+ :max_filesize => 256 * 1024 ** 2,
572
+ :deferred_log_flush => false)
573
+
574
+ # Alter table properties
575
+ table.alter!(
576
+ :max_filesize => 512 * 1024 ** 2,
577
+ :memstore_flushsize => 64 * 1024 ** 2,
578
+ :readonly => false,
579
+ :deferred_log_flush => true
580
+ )
581
+
582
+ # Add column family
583
+ table.add_family! :cf3, :compression => :snappy,
584
+ :bloomfilter => :row
585
+
586
+ # Alter column family
587
+ table.alter_family! :cf2, :bloomfilter => :rowcol
588
+
589
+ # Remove column family
590
+ table.delete_family! :cf1
591
+ ```
592
+
593
+ You can perform other types of administrative tasks
594
+ with Native Java [HBaseAdmin object](http://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/HBaseAdmin.html),
595
+ which can be obtained by `HBase#admin` method which will automatically close the object at the end of the given block.
596
+
597
+ ```ruby
598
+ # Advanced table administration with HBaseAdmin object
599
+ # http://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/HBaseAdmin.html
600
+ hbase.admin do |admin|
601
+ # ...
602
+ end
603
+
604
+ # Without the block
605
+ admin = hbase.admin
606
+ # ...
607
+ admin.close
608
+ ```
609
+
610
+ ## Test
611
+
612
+ ```
613
+ export HBASE_JRUBY_TEST_ZK='your-hbaase.domain.net'
614
+ rake test
615
+ ```
616
+
617
+ ## Contributing
618
+
619
+ 1. Fork it
620
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
621
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
622
+ 4. Push to the branch (`git push origin my-new-feature`)
623
+ 5. Create new Pull Request