ok_hbase 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +17 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +47 -0
  7. data/Rakefile +22 -0
  8. data/examples/README.md +46 -0
  9. data/examples/advanced/README.md +36 -0
  10. data/examples/advanced/perf_read.rb +146 -0
  11. data/examples/advanced/perf_write.rb +143 -0
  12. data/examples/advanced/table_read.rb +115 -0
  13. data/examples/advanced/table_write.rb +128 -0
  14. data/examples/table_scan.rb +97 -0
  15. data/examples/table_write.rb +97 -0
  16. data/lib/ok_hbase/active_model.rb +35 -0
  17. data/lib/ok_hbase/client.rb +42 -0
  18. data/lib/ok_hbase/concerns/custom_row/class_methods.rb +13 -0
  19. data/lib/ok_hbase/concerns/custom_row.rb +40 -0
  20. data/lib/ok_hbase/concerns/indexable/class_methods.rb +13 -0
  21. data/lib/ok_hbase/concerns/indexable.rb +101 -0
  22. data/lib/ok_hbase/concerns/row.rb +85 -0
  23. data/lib/ok_hbase/concerns/table/batch.rb +95 -0
  24. data/lib/ok_hbase/concerns/table/class_methods.rb +13 -0
  25. data/lib/ok_hbase/concerns/table/instrumentation.rb +48 -0
  26. data/lib/ok_hbase/concerns/table.rb +241 -0
  27. data/lib/ok_hbase/concerns.rb +13 -0
  28. data/lib/ok_hbase/connection.rb +157 -0
  29. data/lib/ok_hbase/row.rb +21 -0
  30. data/lib/ok_hbase/table.rb +10 -0
  31. data/lib/ok_hbase/version.rb +3 -0
  32. data/lib/ok_hbase.rb +39 -0
  33. data/lib/thrift/hbase/hbase.rb +2643 -0
  34. data/lib/thrift/hbase/hbase_constants.rb +14 -0
  35. data/lib/thrift/hbase/hbase_types.rb +252 -0
  36. data/ok-hbase.gemspec +23 -0
  37. data/spec/ok_hbase/connection_spec.rb +99 -0
  38. data/spec/ok_hbase/table_spec.rb +149 -0
  39. data/spec/ok_hbase_spec.rb +24 -0
  40. data/spec/spec_helper.rb +20 -0
  41. data/tasks/bump.rb +30 -0
  42. metadata +122 -0
@@ -0,0 +1,14 @@
1
+ #
2
+ # Autogenerated by Thrift Compiler (0.9.0)
3
+ #
4
+ # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
+ #
6
+
7
+ module Apache
8
+ module Hadoop
9
+ module Hbase
10
+ module Thrift
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,252 @@
1
+ #
2
+ # Autogenerated by Thrift Compiler (0.9.0)
3
+ #
4
+ # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
5
+ #
6
+
7
+ module Apache
8
+ module Hadoop
9
+ module Hbase
10
+ module Thrift
11
+ # TCell - Used to transport a cell value (byte[]) and the timestamp it was
12
+ # stored with together as a result for get and getRow methods. This promotes
13
+ # the timestamp of a cell to a first-class value, making it easy to take
14
+ # note of temporal data. Cell is used all the way from HStore up to HTable.
15
+ class TCell
16
+ include ::Thrift::Struct, ::Thrift::Struct_Union
17
+ VALUE = 1
18
+ TIMESTAMP = 2
19
+
20
+ FIELDS = {
21
+ VALUE => {:type => ::Thrift::Types::STRING, :name => 'value', :binary => true},
22
+ TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp'}
23
+ }
24
+
25
+ def struct_fields; FIELDS; end
26
+
27
+ def validate
28
+ end
29
+
30
+ ::Thrift::Struct.generate_accessors self
31
+ end
32
+
33
+ # An HColumnDescriptor contains information about a column family
34
+ # such as the number of versions, compression settings, etc. It is
35
+ # used as input when creating a table or adding a column.
36
+ class ColumnDescriptor
37
+ include ::Thrift::Struct, ::Thrift::Struct_Union
38
+ NAME = 1
39
+ MAXVERSIONS = 2
40
+ COMPRESSION = 3
41
+ INMEMORY = 4
42
+ BLOOMFILTERTYPE = 5
43
+ BLOOMFILTERVECTORSIZE = 6
44
+ BLOOMFILTERNBHASHES = 7
45
+ BLOCKCACHEENABLED = 8
46
+ TIMETOLIVE = 9
47
+
48
+ FIELDS = {
49
+ NAME => {:type => ::Thrift::Types::STRING, :name => 'name', :binary => true},
50
+ MAXVERSIONS => {:type => ::Thrift::Types::I32, :name => 'maxVersions', :default => 3},
51
+ COMPRESSION => {:type => ::Thrift::Types::STRING, :name => 'compression', :default => %q"NONE"},
52
+ INMEMORY => {:type => ::Thrift::Types::BOOL, :name => 'inMemory', :default => false},
53
+ BLOOMFILTERTYPE => {:type => ::Thrift::Types::STRING, :name => 'bloomFilterType', :default => %q"NONE"},
54
+ BLOOMFILTERVECTORSIZE => {:type => ::Thrift::Types::I32, :name => 'bloomFilterVectorSize', :default => 0},
55
+ BLOOMFILTERNBHASHES => {:type => ::Thrift::Types::I32, :name => 'bloomFilterNbHashes', :default => 0},
56
+ BLOCKCACHEENABLED => {:type => ::Thrift::Types::BOOL, :name => 'blockCacheEnabled', :default => false},
57
+ TIMETOLIVE => {:type => ::Thrift::Types::I32, :name => 'timeToLive', :default => -1}
58
+ }
59
+
60
+ def struct_fields; FIELDS; end
61
+
62
+ def validate
63
+ end
64
+
65
+ ::Thrift::Struct.generate_accessors self
66
+ end
67
+
68
+ # A TRegionInfo contains information about an HTable region.
69
+ class TRegionInfo
70
+ include ::Thrift::Struct, ::Thrift::Struct_Union
71
+ STARTKEY = 1
72
+ ENDKEY = 2
73
+ ID = 3
74
+ NAME = 4
75
+ VERSION = 5
76
+
77
+ FIELDS = {
78
+ STARTKEY => {:type => ::Thrift::Types::STRING, :name => 'startKey', :binary => true},
79
+ ENDKEY => {:type => ::Thrift::Types::STRING, :name => 'endKey', :binary => true},
80
+ ID => {:type => ::Thrift::Types::I64, :name => 'id'},
81
+ NAME => {:type => ::Thrift::Types::STRING, :name => 'name', :binary => true},
82
+ VERSION => {:type => ::Thrift::Types::BYTE, :name => 'version'}
83
+ }
84
+
85
+ def struct_fields; FIELDS; end
86
+
87
+ def validate
88
+ end
89
+
90
+ ::Thrift::Struct.generate_accessors self
91
+ end
92
+
93
+ # A Mutation object is used to either update or delete a column-value.
94
+ class Mutation
95
+ include ::Thrift::Struct, ::Thrift::Struct_Union
96
+ ISDELETE = 1
97
+ COLUMN = 2
98
+ VALUE = 3
99
+
100
+ FIELDS = {
101
+ ISDELETE => {:type => ::Thrift::Types::BOOL, :name => 'isDelete', :default => false},
102
+ COLUMN => {:type => ::Thrift::Types::STRING, :name => 'column', :binary => true},
103
+ VALUE => {:type => ::Thrift::Types::STRING, :name => 'value', :binary => true}
104
+ }
105
+
106
+ def struct_fields; FIELDS; end
107
+
108
+ def validate
109
+ end
110
+
111
+ ::Thrift::Struct.generate_accessors self
112
+ end
113
+
114
+ # A BatchMutation object is used to apply a number of Mutations to a single row.
115
+ class BatchMutation
116
+ include ::Thrift::Struct, ::Thrift::Struct_Union
117
+ ROW = 1
118
+ MUTATIONS = 2
119
+
120
+ FIELDS = {
121
+ ROW => {:type => ::Thrift::Types::STRING, :name => 'row', :binary => true},
122
+ MUTATIONS => {:type => ::Thrift::Types::LIST, :name => 'mutations', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Apache::Hadoop::Hbase::Thrift::Mutation}}
123
+ }
124
+
125
+ def struct_fields; FIELDS; end
126
+
127
+ def validate
128
+ end
129
+
130
+ ::Thrift::Struct.generate_accessors self
131
+ end
132
+
133
+ # Holds row name and then a map of columns to cells.
134
+ class TRowResult
135
+ include ::Thrift::Struct, ::Thrift::Struct_Union
136
+ ROW = 1
137
+ COLUMNS = 2
138
+
139
+ FIELDS = {
140
+ ROW => {:type => ::Thrift::Types::STRING, :name => 'row', :binary => true},
141
+ COLUMNS => {:type => ::Thrift::Types::MAP, :name => 'columns', :key => {:type => ::Thrift::Types::STRING, :binary => true}, :value => {:type => ::Thrift::Types::STRUCT, :class => ::Apache::Hadoop::Hbase::Thrift::TCell}}
142
+ }
143
+
144
+ def struct_fields; FIELDS; end
145
+
146
+ def validate
147
+ end
148
+
149
+ ::Thrift::Struct.generate_accessors self
150
+ end
151
+
152
+ # A Scan object is used to specify scanner parameters when opening a scanner.
153
+ class TScan
154
+ include ::Thrift::Struct, ::Thrift::Struct_Union
155
+ STARTROW = 1
156
+ STOPROW = 2
157
+ TIMESTAMP = 3
158
+ COLUMNS = 4
159
+ CACHING = 5
160
+ FILTERSTRING = 6
161
+
162
+ FIELDS = {
163
+ STARTROW => {:type => ::Thrift::Types::STRING, :name => 'startRow', :binary => true, :optional => true},
164
+ STOPROW => {:type => ::Thrift::Types::STRING, :name => 'stopRow', :binary => true, :optional => true},
165
+ TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp', :optional => true},
166
+ COLUMNS => {:type => ::Thrift::Types::LIST, :name => 'columns', :element => {:type => ::Thrift::Types::STRING, :binary => true}, :optional => true},
167
+ CACHING => {:type => ::Thrift::Types::I32, :name => 'caching', :optional => true},
168
+ FILTERSTRING => {:type => ::Thrift::Types::STRING, :name => 'filterString', :binary => true, :optional => true}
169
+ }
170
+
171
+ def struct_fields; FIELDS; end
172
+
173
+ def validate
174
+ end
175
+
176
+ ::Thrift::Struct.generate_accessors self
177
+ end
178
+
179
+ # An IOError exception signals that an error occurred communicating
180
+ # to the Hbase master or an Hbase region server. Also used to return
181
+ # more general Hbase error conditions.
182
+ class IOError < ::Thrift::Exception
183
+ include ::Thrift::Struct, ::Thrift::Struct_Union
184
+ def initialize(message=nil)
185
+ super()
186
+ self.message = message
187
+ end
188
+
189
+ MESSAGE = 1
190
+
191
+ FIELDS = {
192
+ MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message'}
193
+ }
194
+
195
+ def struct_fields; FIELDS; end
196
+
197
+ def validate
198
+ end
199
+
200
+ ::Thrift::Struct.generate_accessors self
201
+ end
202
+
203
+ # An IllegalArgument exception indicates an illegal or invalid
204
+ # argument was passed into a procedure.
205
+ class IllegalArgument < ::Thrift::Exception
206
+ include ::Thrift::Struct, ::Thrift::Struct_Union
207
+ def initialize(message=nil)
208
+ super()
209
+ self.message = message
210
+ end
211
+
212
+ MESSAGE = 1
213
+
214
+ FIELDS = {
215
+ MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message'}
216
+ }
217
+
218
+ def struct_fields; FIELDS; end
219
+
220
+ def validate
221
+ end
222
+
223
+ ::Thrift::Struct.generate_accessors self
224
+ end
225
+
226
+ # An AlreadyExists exceptions signals that a table with the specified
227
+ # name already exists
228
+ class AlreadyExists < ::Thrift::Exception
229
+ include ::Thrift::Struct, ::Thrift::Struct_Union
230
+ def initialize(message=nil)
231
+ super()
232
+ self.message = message
233
+ end
234
+
235
+ MESSAGE = 1
236
+
237
+ FIELDS = {
238
+ MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message'}
239
+ }
240
+
241
+ def struct_fields; FIELDS; end
242
+
243
+ def validate
244
+ end
245
+
246
+ ::Thrift::Struct.generate_accessors self
247
+ end
248
+
249
+ end
250
+ end
251
+ end
252
+ end
data/ok-hbase.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'ok_hbase/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "ok_hbase"
8
+ gem.version = OkHbase::VERSION
9
+ gem.authors = ["Nathan Keyes"]
10
+ gem.email = ["keyes@okcupidlabs.com"]
11
+ gem.description = %q{Lightweight Ruby Hbase Client}
12
+ gem.summary = %q{Lightweight Ruby Hbase Client}
13
+ gem.homepage = "http://okcwest.github.io/ok_hbase/"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+
21
+ gem.add_dependency 'thrift', '0.9.0'
22
+ gem.add_dependency 'activesupport'
23
+ end
@@ -0,0 +1,99 @@
1
+ require 'spec_helper'
2
+
3
+ module OkHbase
4
+ describe Connection do
5
+ describe ".create_table" do
6
+ let(:conn) { Connection.new auto_connect: true, timeout: 60 }
7
+
8
+ it "should create tables with the right column families" do
9
+ name = "ok_hbase_test_table"
10
+ column_families = {
11
+ 'a' => {
12
+ 'max_versions' => 5,
13
+ 'compression' => 'GZ',
14
+ 'in_memory' => true,
15
+ 'bloom_filter_type' => 'ROW',
16
+ 'block_cache_enabled' => true,
17
+
18
+ #TODO find out if these aren't being set properly or just aren't reported properly. 0 is the default
19
+ 'bloom_filter_vector_size' => 0,
20
+ 'bloom_filter_nb_hashes' => 0,
21
+
22
+ #TODO find out why this doesn't get reported properly. -1 is the default
23
+ 'time_to_live' => -1
24
+ },
25
+ 'b' => {
26
+ 'max_versions' => 15,
27
+ 'compression' => 'NONE',
28
+ 'in_memory' => true,
29
+ 'bloom_filter_type' => 'ROWCOL',
30
+ 'block_cache_enabled' => true,
31
+
32
+ 'bloom_filter_vector_size' => 0,
33
+ 'bloom_filter_nb_hashes' => 0,
34
+
35
+ 'time_to_live' => -1
36
+ }
37
+ }
38
+
39
+ expected_families = Hash[column_families.map { |cf, data| [cf, { 'name' => "#{cf}:" }.merge(data)] }]
40
+
41
+ #sanity check
42
+ conn.tables.should_not include(name)
43
+
44
+ conn.create_table(name, column_families)
45
+
46
+ conn.tables.should include(name)
47
+
48
+ table = conn.table(name)
49
+
50
+ table.families.should == expected_families
51
+
52
+ #cleanup
53
+ conn.delete_table(name, true)
54
+ end
55
+
56
+
57
+ it "should create tables with the right name" do
58
+ name = "ok_hbase_test_table"
59
+ column_families = {
60
+ 'd' => {}
61
+ }
62
+
63
+ #sanity check
64
+ conn.tables.should_not include(name)
65
+
66
+ conn.create_table(name, column_families)
67
+
68
+ conn.tables.should include(name)
69
+
70
+ #cleanup
71
+ conn.delete_table(name, true)
72
+ end
73
+ end
74
+
75
+ describe ".open" do
76
+ let(:conn) { Connection.new }
77
+
78
+ it "should open a connection" do
79
+ expect { conn.open }.to change { conn.open? }.to(true)
80
+ end
81
+ end
82
+
83
+ describe ".close" do
84
+ let(:conn) { Connection.new auto_connect: true }
85
+
86
+ it "should close a connection" do
87
+ expect { conn.close }.to change { conn.open? }.to(false)
88
+ end
89
+ end
90
+
91
+ describe ".tables" do
92
+ let(:conn) { Connection.new auto_connect: true }
93
+
94
+ it "should return an array of table names" do
95
+ conn.tables.should be_an Array
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ module OkHbase
4
+ describe Table do
5
+ let(:row_key1) { 'row1' }
6
+ let(:row_key2) { 'row2' }
7
+ let(:row_data1) { { 'd:foo' => 'Foo1', 'd:bar' => 'Bar1', 'd:baz' => 'Baz1' } }
8
+ let(:row_data2) { { 'd:foo' => 'Foo2', 'd:bar' => 'Bar2', 'd:baz' => 'Baz2' } }
9
+ let!(:timestamp) { (Time.now.to_f * 1000).to_i } # hbase timestamps are in milisecnds
10
+
11
+ test_table_name = 'ok_hbase_test_table'
12
+ conn = Connection.new(auto_connect: true, timeout: 60)
13
+
14
+ before(:all) do
15
+ conn.create_table(test_table_name, d: { max_versions: 3 })
16
+ end
17
+
18
+ after(:all) do
19
+ conn.delete_table(test_table_name, true)
20
+ end
21
+
22
+ subject { Table.new(test_table_name, conn) }
23
+
24
+ describe '#_scanner' do
25
+ let(:scanner_opts) { {
26
+ start_row: 'start',
27
+ stop_row: 'stop',
28
+ timestamp: Time.now,
29
+ columns: ['d:foo', 'd:bar', 'i:baz'],
30
+ caching: 1000,
31
+ filter_string: 'filter',
32
+ } }
33
+
34
+ it 'should set all the options' do
35
+ scanner = subject._scanner(scanner_opts)
36
+
37
+ scanner.startRow.should == scanner_opts[:start_row]
38
+ scanner.stopRow.should == scanner_opts[:stop_row]
39
+ scanner.timestamp.should == scanner_opts[:timestamp]
40
+ scanner.columns.should == scanner_opts[:columns]
41
+ scanner.caching.should == scanner_opts[:caching]
42
+ scanner.filterString.should == scanner_opts[:filter_string]
43
+ end
44
+ end
45
+
46
+ describe '.scan' do
47
+
48
+ it 'should convert a row prefix to a start and stop rows' do
49
+ subject.put('scan_row1', row_data1)
50
+ subject.put('scan_row2', row_data1)
51
+
52
+ opts = { row_prefix: 'scan' }
53
+ subject.should_receive(:_scanner).with(hash_including(start_row: 'scan', stop_row: 'scao')).and_return {
54
+ Apache::Hadoop::Hbase::Thrift::TScan.new(
55
+ startRow: 'scan',
56
+ stopRow: 'scao',
57
+ caching: 1000,
58
+ )
59
+ }
60
+
61
+ subject.scan(opts) do |row_key, cols|
62
+ cols.should == row_data1
63
+ end
64
+ end
65
+ end
66
+
67
+ describe '.regions' do
68
+ it 'should list all region having rows for the table' do
69
+ regions = subject.regions
70
+ regions.should be_an Array
71
+ regions.size.should be >= 1
72
+
73
+ regions.each do |region|
74
+ region.keys.should include('start_key', 'end_key', 'id', 'name', 'version')
75
+ end
76
+
77
+ end
78
+ end
79
+
80
+ describe 'CRUD' do
81
+
82
+ describe '.put' do
83
+
84
+ it 'should write to the row' do
85
+ subject.put(row_key1, row_data1)
86
+
87
+ subject.row(row_key1).should == row_data1
88
+ end
89
+
90
+ it 'should write to the row with a timestamp' do
91
+
92
+ subject.put(row_key1, row_data1, timestamp)
93
+
94
+ subject.row(row_key1, nil, nil, true).should == Hash[row_data1.map { |k, v| [k, [v, timestamp]] }]
95
+ end
96
+ end
97
+
98
+ describe '.cells' do
99
+ it 'should retrieve values' do
100
+
101
+ subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo1'), timestamp-10)
102
+ subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo2'), timestamp-5)
103
+ subject.put(row_key2, row_data2, timestamp)
104
+
105
+ subject.cells(row_key2, 'd:foo').should == ['Foo2', 'OldFoo2', 'OldFoo1']
106
+
107
+ subject.cells(row_key2, 'd:foo', nil, timestamp-1).should == ['OldFoo2', 'OldFoo1']
108
+ subject.cells(row_key2, 'd:foo', nil, timestamp-5).should == ['OldFoo1']
109
+
110
+ subject.cells(row_key2, 'd:foo', nil, nil, true).should == [['Foo2', timestamp], ['OldFoo2', timestamp-5], ['OldFoo1', timestamp-10]]
111
+ subject.cells(row_key2, 'd:foo', 2, nil, true).should == [['Foo2', timestamp], ['OldFoo2', timestamp-5]]
112
+ subject.cells(row_key2, 'd:foo', 1, nil, true).should == [['Foo2', timestamp]]
113
+
114
+ subject.cells(row_key2, 'd:foo', nil, nil, false).should == ['Foo2', 'OldFoo2', 'OldFoo1']
115
+ subject.cells(row_key2, 'd:foo', 2, nil, false).should == ['Foo2', 'OldFoo2']
116
+ subject.cells(row_key2, 'd:foo', 1, nil, false).should == ['Foo2']
117
+
118
+ end
119
+ end
120
+
121
+ describe ".rows" do
122
+ it 'should retrieve the specified rows' do
123
+
124
+ subject.put(row_key1, row_data1, timestamp-10)
125
+ subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo1'), timestamp-10)
126
+ subject.put(row_key2, row_data2.merge('d:foo' => 'OldFoo2'), timestamp-5)
127
+ subject.put(row_key2, row_data2, timestamp)
128
+
129
+ subject.rows([row_key1, row_key2]).should == [row_data1, row_data2]
130
+ subject.rows([row_key1, row_key2], nil, timestamp-9).should == [row_data1, row_data2.merge('d:foo' => 'OldFoo1')]
131
+
132
+ subject.rows([row_key1, row_key2], row_data1.keys - ['d:foo']).should == [row_data1.except('d:foo'), row_data2.except('d:foo')]
133
+ subject.rows([row_key1, row_key2], row_data1.keys - ['d:foo'], timestamp-5).should == [row_data1.except('d:foo'), row_data2.except('d:foo')]
134
+
135
+ end
136
+ end
137
+
138
+ describe ".delete" do
139
+ it 'should retrieve the specified rows' do
140
+ subject.put(row_key1, row_data1)
141
+
142
+ subject.delete(row_key1, ['d:bar', 'd:baz'])
143
+
144
+ subject.row(row_key1).should == row_data1.except('d:bar', 'd:baz')
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # require 'spec_helper'
3
+
4
+ describe OkHbase do
5
+
6
+ describe "#increment_string" do
7
+ let(:normal_string) { "foo baq" }
8
+ let(:incremented_normal_string) { "foo bar" }
9
+
10
+ let(:utf8_string) { "Vulgar fraction one half: ¼".force_encoding(Encoding::UTF_8) }
11
+ let(:incremented_utf8_string) { "Vulgar fraction one half: ½".force_encoding(Encoding::UTF_8) }
12
+ let(:max_byte_string) { [255, 255, 255].pack('c*') }
13
+
14
+ it "should increment the last byte byte < 255" do
15
+ OkHbase::increment_string(normal_string).should == incremented_normal_string
16
+ OkHbase::increment_string(utf8_string).should == incremented_utf8_string
17
+ end
18
+
19
+ it "should return nil when all bytes are 255" do
20
+ OkHbase::increment_string(max_byte_string).should be_nil
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
4
+
5
+ # needs to be at the top
6
+ require 'simplecov'
7
+ SimpleCov.start do
8
+ add_filter "/thrift/"
9
+ end
10
+
11
+ Bundler.require :default, :test
12
+
13
+
14
+ require 'rubygems'
15
+ require 'bundler'
16
+ require 'awesome_print'
17
+
18
+ require 'ok_hbase'
19
+
20
+
data/tasks/bump.rb ADDED
@@ -0,0 +1,30 @@
1
+ desc 'shortcut for bump:patch'
2
+ task :bump => 'bump:patch'
3
+
4
+ namespace :bump do
5
+ desc 'Bump x.y.Z'
6
+ task :patch
7
+
8
+ desc 'Bump x.Y.z'
9
+ task :minor
10
+
11
+ desc 'Bump X.y.z'
12
+ task :major
13
+ end
14
+
15
+ # extracted and modified from https://github.com/grosser/project_template
16
+ rule /^bump:.*/ do |t|
17
+ sh "git status | grep 'nothing to commit'" # ensure we are not dirty
18
+ index = ['major', 'minor','patch'].index(t.name.split(':').last)
19
+ file = 'lib/ok_hbase/version.rb'
20
+
21
+ version_file = File.read(file)
22
+ old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
23
+ version_parts[index] = version_parts[index].to_i + 1
24
+ version_parts[2] = 0 if index < 2
25
+ version_parts[1] = 0 if index < 1
26
+ new_version = version_parts * '.'
27
+ File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
28
+
29
+ sh "bundle && git add #{file} && git commit -m 'bump version to #{new_version}'"
30
+ end