ok_hbase 0.0.5
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/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +22 -0
- data/examples/README.md +46 -0
- data/examples/advanced/README.md +36 -0
- data/examples/advanced/perf_read.rb +146 -0
- data/examples/advanced/perf_write.rb +143 -0
- data/examples/advanced/table_read.rb +115 -0
- data/examples/advanced/table_write.rb +128 -0
- data/examples/table_scan.rb +97 -0
- data/examples/table_write.rb +97 -0
- data/lib/ok_hbase/active_model.rb +35 -0
- data/lib/ok_hbase/client.rb +42 -0
- data/lib/ok_hbase/concerns/custom_row/class_methods.rb +13 -0
- data/lib/ok_hbase/concerns/custom_row.rb +40 -0
- data/lib/ok_hbase/concerns/indexable/class_methods.rb +13 -0
- data/lib/ok_hbase/concerns/indexable.rb +101 -0
- data/lib/ok_hbase/concerns/row.rb +85 -0
- data/lib/ok_hbase/concerns/table/batch.rb +95 -0
- data/lib/ok_hbase/concerns/table/class_methods.rb +13 -0
- data/lib/ok_hbase/concerns/table/instrumentation.rb +48 -0
- data/lib/ok_hbase/concerns/table.rb +241 -0
- data/lib/ok_hbase/concerns.rb +13 -0
- data/lib/ok_hbase/connection.rb +157 -0
- data/lib/ok_hbase/row.rb +21 -0
- data/lib/ok_hbase/table.rb +10 -0
- data/lib/ok_hbase/version.rb +3 -0
- data/lib/ok_hbase.rb +39 -0
- data/lib/thrift/hbase/hbase.rb +2643 -0
- data/lib/thrift/hbase/hbase_constants.rb +14 -0
- data/lib/thrift/hbase/hbase_types.rb +252 -0
- data/ok-hbase.gemspec +23 -0
- data/spec/ok_hbase/connection_spec.rb +99 -0
- data/spec/ok_hbase/table_spec.rb +149 -0
- data/spec/ok_hbase_spec.rb +24 -0
- data/spec/spec_helper.rb +20 -0
- data/tasks/bump.rb +30 -0
- metadata +122 -0
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|