inmemory_kv 0.1.0
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.
- checksums.yaml +15 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +82 -0
- data/Rakefile +6 -0
- data/ext/extconf.rb +4 -0
- data/ext/inmemory_kv.c +809 -0
- data/inmemory_kv.gemspec +25 -0
- data/lib/inmemory_kv.rb +6 -0
- data/lib/inmemory_kv/version.rb +3 -0
- data/test/test_str2str.rb +198 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YWFiZGI1MGRlM2I4MDg3NzFlZGEyYzBkMzAwOWU0MjFkNWY4ZGRiNQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZjkxOTg3MTUzOWY0OTM1N2JhOGFiMmNkMDUzYjQ0ODQ3NDg3M2JhOQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzFlNGQ3MmU1ODE1NDI4ZGEyOWU0MTYzYzdjYTlmMTgzMzRiMTM0ZjQ1YzUy
|
10
|
+
ZjI1MjM0YTBjNTUyMjEwODFhMzVjMmFmM2FlZTUwZmNhOWZkMjdiODUyYWY5
|
11
|
+
MjUxZDEzZGI4ZjAyNDAwZjI1MGIyNzM2MzVmMTcxOTU3MzI4ZDg=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGVjZWZmMDM5YTQzYmVmMjEyNmU5YjVkYTAxNTExMjllMGJhM2VhNjczMDc0
|
14
|
+
OWIzYTczMGNmYTBlMzhlMzIwNTI2NDIxN2NmODNjNGJhZTdkZWYxYWY5MzVk
|
15
|
+
MTYwZjE4MGQ1N2Q2ZDYwYmE3MDI2NGYwNDI0YTZjZWFiZDY5MmI=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Sokolov Yura aka funny_falcon
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# InMemoryKV
|
2
|
+
|
3
|
+
This is simple off-gc in-memory hash table.
|
4
|
+
|
5
|
+
Currently, `InMemoryKV::Str2Str` is a string to string hash table
|
6
|
+
with LRU built in (a lot like builtin Hash).
|
7
|
+
|
8
|
+
It doesn't participate in GC and not encounted in. It uses `malloc` for simplicity.
|
9
|
+
|
10
|
+
If you do not clone and not mutate it in a fork, than it is as fork-frienly as your malloc is.
|
11
|
+
|
12
|
+
It is not thread-safe, so protect it by you self. (builtin hash is also not thread-safe)
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'inmemory_kv'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install inmemory_kv
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
s2s = InMemoryKV::Str2Str.new
|
34
|
+
s2s['asdf'] = 'qwer'
|
35
|
+
s2s.keys
|
36
|
+
s2s.values
|
37
|
+
s2s.entries
|
38
|
+
# attention: iteration is not as safe as iteration of builtin Hash.
|
39
|
+
# Builtin Hash checks if you mutate hash during iteratation, this hash doesn't.
|
40
|
+
s2s.each_key{|k| }
|
41
|
+
s2s.each_value{|k| }
|
42
|
+
s2s.each{|k,v| }
|
43
|
+
|
44
|
+
s2s.up(k) # touch entry to be most recent in LRU order
|
45
|
+
s2s.down(k) # touch entry to be first to expire
|
46
|
+
s2s.first # first/oldest entry in LRU
|
47
|
+
s2s.shift # shift oldest entry
|
48
|
+
s2s.data_size # size of key+value entries
|
49
|
+
s2s.total_size # size of key+value entries + internal structures
|
50
|
+
|
51
|
+
# Str2Str is more memory efficient than storing string in a builtin hash
|
52
|
+
# also it is a bit faster.
|
53
|
+
# It tries to overwrite value inplace if it value's size not larger.
|
54
|
+
def timeit; t=Time.now; r=yield; ensure puts "Lasts: #{Time.now - t}"; r; end
|
55
|
+
|
56
|
+
timeit{ 1000000.times{|i| s2s[i.to_s] = "qwer#{i}"} }
|
57
|
+
timeit{ 1000000.times{|i| s2s[i.to_s] = "qwer#{i}"} }
|
58
|
+
hsh = {}
|
59
|
+
timeit{ 1000000.times{|i| hsh[i.to_s] = "qwer#{i}"} }
|
60
|
+
timeit{ 1000000.times{|i| hsh[i.to_s] = "qwer#{i}"} }
|
61
|
+
|
62
|
+
# cloning is made to be very fast:
|
63
|
+
# it does not copy key/value entries
|
64
|
+
# only internal structures are alloced and copied with memcpy
|
65
|
+
# key/value's reference count is incremented
|
66
|
+
timeit{ sts.dup }
|
67
|
+
timeit{ hsh.dup }
|
68
|
+
# clone is copy on write
|
69
|
+
cpy = sts.dup
|
70
|
+
sts['2'] = '!'
|
71
|
+
cpy['3'] = '!!'
|
72
|
+
sts['2'] != cpy['2']
|
73
|
+
sts['3'] != cpy['3']
|
74
|
+
```
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
1. Fork it ( https://github.com/funny-falcon/inmemory_kv/fork )
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/ext/extconf.rb
ADDED
data/ext/inmemory_kv.c
ADDED
@@ -0,0 +1,809 @@
|
|
1
|
+
#include <ruby/ruby.h>
|
2
|
+
#include <ruby/intern.h>
|
3
|
+
#include <assert.h>
|
4
|
+
#include <malloc.h>
|
5
|
+
|
6
|
+
#include <stdio.h>
|
7
|
+
#ifdef HAV_STDLIB_H
|
8
|
+
#include <stdlib.h>
|
9
|
+
#endif
|
10
|
+
#include <string.h>
|
11
|
+
|
12
|
+
typedef unsigned int u32;
|
13
|
+
|
14
|
+
typedef struct hash_item {
|
15
|
+
u32 pos;
|
16
|
+
u32 rc;
|
17
|
+
u32 key_size;
|
18
|
+
u32 val_size;
|
19
|
+
u32 val_size_max;
|
20
|
+
char key[0];
|
21
|
+
} hash_item;
|
22
|
+
|
23
|
+
static inline char*
|
24
|
+
item_val(hash_item* item) {
|
25
|
+
return item->key + item->key_size;
|
26
|
+
}
|
27
|
+
|
28
|
+
typedef struct hash_entry {
|
29
|
+
u32 hash;
|
30
|
+
u32 next;
|
31
|
+
u32 fwd;
|
32
|
+
u32 prev;
|
33
|
+
hash_item* item;
|
34
|
+
} hash_entry;
|
35
|
+
|
36
|
+
typedef struct hash_table {
|
37
|
+
hash_entry* entries;
|
38
|
+
u32* buckets;
|
39
|
+
u32 size;
|
40
|
+
u32 alloced;
|
41
|
+
u32 empty;
|
42
|
+
u32 first;
|
43
|
+
u32 last;
|
44
|
+
u32 nbuckets;
|
45
|
+
} hash_table;
|
46
|
+
|
47
|
+
static const u32 end = (u32)0 - 1;
|
48
|
+
|
49
|
+
static u32 hash_first(hash_table* tab);
|
50
|
+
static u32 hash_next(hash_table* tab, u32 pos);
|
51
|
+
static u32 hash_hash_first(hash_table* tab, u32 hash);
|
52
|
+
static u32 hash_hash_next(hash_table* tab, u32 hash, u32 pos);
|
53
|
+
static u32 hash_insert(hash_table* tab, u32 hash);
|
54
|
+
static void hash_up(hash_table* tab, u32 pos);
|
55
|
+
static void hash_delete(hash_table* tab, u32 pos);
|
56
|
+
static void hash_destroy(hash_table* tab);
|
57
|
+
static size_t hash_memsize(const hash_table* tab) {
|
58
|
+
return tab->alloced * sizeof(hash_entry) +
|
59
|
+
tab->nbuckets * sizeof(u32);
|
60
|
+
}
|
61
|
+
|
62
|
+
static u32
|
63
|
+
hash_first(hash_table* tab) {
|
64
|
+
return tab->first - 1;
|
65
|
+
}
|
66
|
+
|
67
|
+
static u32
|
68
|
+
hash_next(hash_table* tab, u32 pos) {
|
69
|
+
if (pos == end || tab->alloced < pos) {
|
70
|
+
return end;
|
71
|
+
}
|
72
|
+
return tab->entries[pos].fwd - 1;
|
73
|
+
}
|
74
|
+
|
75
|
+
static u32
|
76
|
+
hash_hash_first(hash_table* tab, u32 hash) {
|
77
|
+
u32 buc, pos;
|
78
|
+
if (tab->size == 0) return end;
|
79
|
+
buc = hash % tab->nbuckets;
|
80
|
+
pos = tab->buckets[buc] - 1;
|
81
|
+
while (pos != end && tab->entries[pos].hash != hash) {
|
82
|
+
pos = tab->entries[pos].next - 1;
|
83
|
+
}
|
84
|
+
return pos;
|
85
|
+
}
|
86
|
+
|
87
|
+
static u32
|
88
|
+
hash_hash_next(hash_table* tab, u32 hash, u32 pos) {
|
89
|
+
if (pos == end || tab->size == 0) return end;
|
90
|
+
do {
|
91
|
+
pos = tab->entries[pos].next - 1;
|
92
|
+
} while (pos != end && tab->entries[pos].hash != hash);
|
93
|
+
return pos;
|
94
|
+
}
|
95
|
+
|
96
|
+
#if 0
|
97
|
+
static void
|
98
|
+
hash_print(hash_table* tab, const char* act, u32 pos) {
|
99
|
+
u32 i;
|
100
|
+
printf("%s %d size: %d first: %d last: %d\n", act, pos, tab->size, tab->first-1, tab->last-1);
|
101
|
+
i = tab->first;
|
102
|
+
while(i-1!=end) {
|
103
|
+
hash_entry* e = tab->entries + (i-1);
|
104
|
+
printf("\tpos: %d prev: %d fwd: %d\n", i-1, e->prev-1, e->fwd-1);
|
105
|
+
i = tab->entries[i-1].fwd;
|
106
|
+
}
|
107
|
+
}
|
108
|
+
#else
|
109
|
+
#define hash_print(tab, act, pos)
|
110
|
+
#endif
|
111
|
+
|
112
|
+
static inline void
|
113
|
+
hash_enchain(hash_table* tab, u32 pos) {
|
114
|
+
tab->entries[pos].prev = tab->last;
|
115
|
+
if (tab->first == 0) {
|
116
|
+
tab->first = pos+1;
|
117
|
+
} else {
|
118
|
+
tab->entries[tab->last-1].fwd = pos+1;
|
119
|
+
}
|
120
|
+
tab->last = pos+1;
|
121
|
+
hash_print(tab, "enchain", pos);
|
122
|
+
}
|
123
|
+
|
124
|
+
static inline void
|
125
|
+
hash_enchain_first(hash_table* tab, u32 pos) {
|
126
|
+
tab->entries[pos].fwd = tab->first;
|
127
|
+
if (tab->last == 0) {
|
128
|
+
tab->last = pos+1;
|
129
|
+
} else {
|
130
|
+
tab->entries[tab->first-1].prev = pos+1;
|
131
|
+
}
|
132
|
+
tab->first = pos+1;
|
133
|
+
hash_print(tab, "enchain first", pos);
|
134
|
+
}
|
135
|
+
|
136
|
+
static inline void
|
137
|
+
hash_unchain(hash_table* tab, u32 pos) {
|
138
|
+
if (tab->first == pos+1) {
|
139
|
+
tab->first = tab->entries[pos].fwd;
|
140
|
+
} else {
|
141
|
+
tab->entries[tab->entries[pos].prev-1].fwd = tab->entries[pos].fwd;
|
142
|
+
}
|
143
|
+
if (tab->last == pos+1) {
|
144
|
+
tab->last = tab->entries[pos].prev;
|
145
|
+
} else {
|
146
|
+
tab->entries[tab->entries[pos].fwd-1].prev = tab->entries[pos].prev;
|
147
|
+
}
|
148
|
+
tab->entries[pos].fwd = 0;
|
149
|
+
tab->entries[pos].prev = 0;
|
150
|
+
hash_print(tab, "unchain", pos);
|
151
|
+
}
|
152
|
+
|
153
|
+
static void
|
154
|
+
hash_up(hash_table* tab, u32 pos) {
|
155
|
+
assert(tab->entries[pos].hash != 0);
|
156
|
+
if (tab->last == pos+1) return;
|
157
|
+
hash_unchain(tab, pos);
|
158
|
+
hash_enchain(tab, pos);
|
159
|
+
}
|
160
|
+
|
161
|
+
static void
|
162
|
+
hash_down(hash_table* tab, u32 pos) {
|
163
|
+
assert(tab->entries[pos].hash != 0);
|
164
|
+
if (tab->first == pos+1) return;
|
165
|
+
hash_unchain(tab, pos);
|
166
|
+
hash_enchain_first(tab, pos);
|
167
|
+
}
|
168
|
+
|
169
|
+
static u32
|
170
|
+
hash_insert(hash_table* tab, u32 hash) {
|
171
|
+
u32 i, pos, buc, npos;
|
172
|
+
if (tab->size == tab->alloced) {
|
173
|
+
u32 new_alloced = tab->alloced ? tab->alloced * 1.5 : 32;
|
174
|
+
tab->entries = realloc(tab->entries,
|
175
|
+
sizeof(hash_entry)*new_alloced);
|
176
|
+
assert(tab->entries);
|
177
|
+
memset(tab->entries + tab->alloced, 0,
|
178
|
+
sizeof(hash_entry)*(new_alloced - tab->alloced));
|
179
|
+
for (i=tab->alloced; i<new_alloced-1; i++) {
|
180
|
+
tab->entries[i].next = i+2;
|
181
|
+
}
|
182
|
+
tab->empty = tab->alloced+1;
|
183
|
+
tab->alloced = new_alloced;
|
184
|
+
}
|
185
|
+
if (tab->size >= tab->nbuckets * 2) {
|
186
|
+
u32 new_nbuckets = tab->nbuckets ? (tab->nbuckets+1)*2-1 : 15;
|
187
|
+
free(tab->buckets);
|
188
|
+
tab->buckets = calloc(new_nbuckets, sizeof(u32));
|
189
|
+
for (i=0; i<tab->alloced; i++) {
|
190
|
+
if (tab->entries[i].hash == 0)
|
191
|
+
continue;
|
192
|
+
buc = tab->entries[i].hash % new_nbuckets;
|
193
|
+
npos = tab->buckets[buc];
|
194
|
+
tab->entries[i].next = npos;
|
195
|
+
tab->buckets[buc] = i+1;
|
196
|
+
}
|
197
|
+
tab->nbuckets = new_nbuckets;
|
198
|
+
}
|
199
|
+
buc = hash % tab->nbuckets;
|
200
|
+
npos = tab->buckets[buc];
|
201
|
+
pos = tab->empty - 1;
|
202
|
+
assert(pos != end);
|
203
|
+
tab->buckets[buc] = pos + 1;
|
204
|
+
tab->empty = tab->entries[pos].next;
|
205
|
+
tab->entries[pos].hash = hash;
|
206
|
+
tab->entries[pos].item = NULL;
|
207
|
+
tab->entries[pos].next = npos;
|
208
|
+
tab->entries[pos].fwd = 0;
|
209
|
+
hash_enchain(tab, pos);
|
210
|
+
tab->size++;
|
211
|
+
return pos;
|
212
|
+
}
|
213
|
+
|
214
|
+
static void
|
215
|
+
hash_delete(hash_table* tab, u32 pos) {
|
216
|
+
u32 hash, buc, i, j;
|
217
|
+
hash = tab->entries[pos].hash;
|
218
|
+
buc = hash % tab->nbuckets;
|
219
|
+
i = tab->buckets[buc] - 1;
|
220
|
+
j = end;
|
221
|
+
while (i != pos && i != end) {
|
222
|
+
j = i;
|
223
|
+
i = tab->entries[i].next - 1;
|
224
|
+
}
|
225
|
+
assert(i != end && i == pos);
|
226
|
+
if (j == end) {
|
227
|
+
tab->buckets[buc] = tab->entries[i].next;
|
228
|
+
} else {
|
229
|
+
tab->entries[j].next = tab->entries[i].next;
|
230
|
+
}
|
231
|
+
tab->entries[i].next = tab->empty;
|
232
|
+
hash_unchain(tab, i);
|
233
|
+
tab->empty = i+1;
|
234
|
+
tab->entries[i].hash = 0;
|
235
|
+
tab->size--;
|
236
|
+
}
|
237
|
+
|
238
|
+
static void
|
239
|
+
hash_destroy(hash_table* tab) {
|
240
|
+
free(tab->entries);
|
241
|
+
free(tab->buckets);
|
242
|
+
}
|
243
|
+
|
244
|
+
typedef struct inmemory_kv {
|
245
|
+
hash_table tab;
|
246
|
+
size_t total_size;
|
247
|
+
} inmemory_kv;
|
248
|
+
|
249
|
+
static hash_item* kv_insert(inmemory_kv *kv, const char* key, u32 key_size, const char* val, u32 val_size);
|
250
|
+
static hash_item* kv_fetch(inmemory_kv *kv, const char* key, u32 key_size);
|
251
|
+
static void kv_up(inmemory_kv *kv, hash_item* item);
|
252
|
+
static void kv_delete(inmemory_kv *kv, hash_item* item);
|
253
|
+
static hash_item* kv_first(inmemory_kv *kv);
|
254
|
+
|
255
|
+
typedef void (*kv_each_cb)(hash_item* item, void* arg);
|
256
|
+
static void kv_each(inmemory_kv *kv, kv_each_cb cb, void* arg);
|
257
|
+
|
258
|
+
static void kv_copy_to(inmemory_kv *from, inmemory_kv *to);
|
259
|
+
|
260
|
+
#ifdef HAV_RB_MEMHASH
|
261
|
+
static inline u32
|
262
|
+
kv_hash(const char* key, u32 key_size) {
|
263
|
+
u32 hash = rb_memhash(key, key_size);
|
264
|
+
return hash ? hash : ~(u32)0;
|
265
|
+
}
|
266
|
+
#else
|
267
|
+
static inline u32
|
268
|
+
kv_hash(const char* key, u32 key_size) {
|
269
|
+
u32 a1 = 0xdeadbeef, a2 = 0x71fefeed;
|
270
|
+
u32 i;
|
271
|
+
for (i = 0; i<key_size; i++) {
|
272
|
+
unsigned char k = key[i];
|
273
|
+
a1 = (a1 + k) * 5;
|
274
|
+
a2 = (a2 ^ k) * 9;
|
275
|
+
}
|
276
|
+
a1 ^= key_size; a1 *= 5; a2 *= 9;
|
277
|
+
a1 ^= a2;
|
278
|
+
return a1 ? a1 : ~(u32)0;
|
279
|
+
}
|
280
|
+
#endif
|
281
|
+
|
282
|
+
static hash_item*
|
283
|
+
kv_insert(inmemory_kv *kv, const char* key, u32 key_size, const char* val, u32 val_size) {
|
284
|
+
u32 hash = kv_hash(key, key_size);
|
285
|
+
u32 pos;
|
286
|
+
hash_item* item;
|
287
|
+
pos = hash_hash_first(&kv->tab, hash);
|
288
|
+
while (pos != end) {
|
289
|
+
item = kv->tab.entries[pos].item;
|
290
|
+
if (item->key_size == key_size &&
|
291
|
+
memcmp(key, item->key, key_size) == 0) {
|
292
|
+
break;
|
293
|
+
}
|
294
|
+
pos = hash_hash_next(&kv->tab, hash, pos);
|
295
|
+
}
|
296
|
+
if (pos == end) {
|
297
|
+
pos = hash_insert(&kv->tab, hash);
|
298
|
+
item = NULL;
|
299
|
+
} else {
|
300
|
+
hash_up(&kv->tab, pos);
|
301
|
+
if (val_size > item->val_size_max || item->rc > 0) {
|
302
|
+
kv->total_size -= item->key_size + item->val_size_max + sizeof(*item);
|
303
|
+
if (item->rc > 0)
|
304
|
+
item->rc--;
|
305
|
+
else
|
306
|
+
free(item);
|
307
|
+
item = NULL;
|
308
|
+
}
|
309
|
+
}
|
310
|
+
if (item == NULL) {
|
311
|
+
#ifdef HAVE_MALLOC_USABLE_SIZE
|
312
|
+
item = malloc(sizeof(*item) + key_size + val_size);
|
313
|
+
assert(item);
|
314
|
+
item->rc = 0;
|
315
|
+
item->val_size_max = malloc_usable_size(item) - sizeof(*item) - key_size;
|
316
|
+
#else
|
317
|
+
u32 val_size_max = sizeof(*item) + key_size + val_size;
|
318
|
+
val_size_max = (val_size_max + 15) & 15;
|
319
|
+
item = malloc(sizeof(*item) + key_size + val_size_max);
|
320
|
+
assert(item);
|
321
|
+
item->rc = 0;
|
322
|
+
item->val_size_max = val_size_max;
|
323
|
+
#endif
|
324
|
+
kv->total_size += key_size + item->val_size_max + sizeof(*item);
|
325
|
+
item->key_size = key_size;
|
326
|
+
item->pos = pos;
|
327
|
+
memcpy(item->key, key, key_size);
|
328
|
+
}
|
329
|
+
item->val_size = val_size;
|
330
|
+
memcpy(item_val(item), val, val_size);
|
331
|
+
kv->tab.entries[pos].item = item;
|
332
|
+
return item;
|
333
|
+
}
|
334
|
+
|
335
|
+
static hash_item*
|
336
|
+
kv_fetch(inmemory_kv *kv, const char* key, u32 key_size) {
|
337
|
+
u32 hash = kv_hash(key, key_size);
|
338
|
+
u32 pos;
|
339
|
+
hash_item* item;
|
340
|
+
pos = hash_hash_first(&kv->tab, hash);
|
341
|
+
while (pos != end) {
|
342
|
+
item = kv->tab.entries[pos].item;
|
343
|
+
if (item->key_size == key_size &&
|
344
|
+
memcmp(key, item->key, key_size) == 0) {
|
345
|
+
break;
|
346
|
+
}
|
347
|
+
pos = hash_hash_next(&kv->tab, hash, pos);
|
348
|
+
}
|
349
|
+
return pos == end ? NULL : kv->tab.entries[pos].item;
|
350
|
+
}
|
351
|
+
|
352
|
+
static void
|
353
|
+
kv_up(inmemory_kv *kv, hash_item* item) {
|
354
|
+
hash_up(&kv->tab, item->pos);
|
355
|
+
}
|
356
|
+
|
357
|
+
static void
|
358
|
+
kv_down(inmemory_kv *kv, hash_item* item) {
|
359
|
+
hash_down(&kv->tab, item->pos);
|
360
|
+
}
|
361
|
+
|
362
|
+
static void
|
363
|
+
kv_delete(inmemory_kv *kv, hash_item* item) {
|
364
|
+
hash_delete(&kv->tab, item->pos);
|
365
|
+
kv->total_size -= sizeof(*item) + item->key_size + item->val_size_max;
|
366
|
+
if (item->rc > 0) {
|
367
|
+
item->rc--;
|
368
|
+
} else {
|
369
|
+
free(item);
|
370
|
+
}
|
371
|
+
}
|
372
|
+
|
373
|
+
static hash_item*
|
374
|
+
kv_first(inmemory_kv *kv) {
|
375
|
+
u32 pos = hash_first(&kv->tab);
|
376
|
+
if (pos != end) {
|
377
|
+
return kv->tab.entries[pos].item;
|
378
|
+
}
|
379
|
+
return NULL;
|
380
|
+
}
|
381
|
+
|
382
|
+
static void
|
383
|
+
kv_each(inmemory_kv *kv, kv_each_cb cb, void* arg) {
|
384
|
+
u32 pos = hash_first(&kv->tab);
|
385
|
+
while (pos != end) {
|
386
|
+
cb(kv->tab.entries[pos].item, arg);
|
387
|
+
pos = hash_next(&kv->tab, pos);
|
388
|
+
}
|
389
|
+
}
|
390
|
+
|
391
|
+
static void
|
392
|
+
kv_destroy(inmemory_kv *kv) {
|
393
|
+
u32 i;
|
394
|
+
for (i=0; i<kv->tab.alloced; i++) {
|
395
|
+
if (kv->tab.entries[i].hash != 0) {
|
396
|
+
hash_item* item = kv->tab.entries[i].item;
|
397
|
+
if (item->rc > 0) {
|
398
|
+
item->rc--;
|
399
|
+
} else {
|
400
|
+
free(item);
|
401
|
+
}
|
402
|
+
}
|
403
|
+
}
|
404
|
+
hash_destroy(&kv->tab);
|
405
|
+
}
|
406
|
+
|
407
|
+
static void
|
408
|
+
kv_copy_to(inmemory_kv *from, inmemory_kv *to) {
|
409
|
+
kv_destroy(to);
|
410
|
+
*to = *from;
|
411
|
+
if (to->tab.alloced) {
|
412
|
+
u32 i;
|
413
|
+
to->tab.entries = malloc(to->tab.alloced*sizeof(hash_entry));
|
414
|
+
memcpy(to->tab.entries, from->tab.entries,
|
415
|
+
sizeof(hash_entry)*to->tab.alloced);
|
416
|
+
to->tab.buckets = malloc(to->tab.nbuckets*sizeof(u32));
|
417
|
+
memcpy(to->tab.buckets, from->tab.buckets,
|
418
|
+
sizeof(u32)*from->tab.nbuckets);
|
419
|
+
for (i=0; i<to->tab.alloced; i++) {
|
420
|
+
if (to->tab.entries[i].hash != 0) {
|
421
|
+
to->tab.entries[i].item->rc++;
|
422
|
+
}
|
423
|
+
}
|
424
|
+
}
|
425
|
+
}
|
426
|
+
|
427
|
+
static size_t
|
428
|
+
rb_kv_memsize(const void *p) {
|
429
|
+
if (p) {
|
430
|
+
const inmemory_kv* kv = p;
|
431
|
+
return sizeof(*kv) + kv->total_size + hash_memsize(&kv->tab);
|
432
|
+
}
|
433
|
+
return 0;
|
434
|
+
}
|
435
|
+
|
436
|
+
static void
|
437
|
+
rb_kv_destroy(void *p) {
|
438
|
+
if (p) {
|
439
|
+
inmemory_kv *kv = p;
|
440
|
+
kv_destroy(kv);
|
441
|
+
free(kv);
|
442
|
+
}
|
443
|
+
}
|
444
|
+
|
445
|
+
static const rb_data_type_t InMemoryKV_data_type = {
|
446
|
+
"InMemoryKV_C",
|
447
|
+
{NULL, rb_kv_destroy, rb_kv_memsize}
|
448
|
+
};
|
449
|
+
#define GetKV(value, pointer) \
|
450
|
+
TypedData_Get_Struct((value), inmemory_kv, &InMemoryKV_data_type, (pointer))
|
451
|
+
|
452
|
+
static VALUE
|
453
|
+
rb_kv_alloc(VALUE klass) {
|
454
|
+
inmemory_kv* kv = calloc(1, sizeof(inmemory_kv));
|
455
|
+
return TypedData_Wrap_Struct(klass, &InMemoryKV_data_type, kv);
|
456
|
+
}
|
457
|
+
|
458
|
+
static VALUE
|
459
|
+
rb_kv_get(VALUE self, VALUE vkey) {
|
460
|
+
inmemory_kv* kv;
|
461
|
+
const char *key;
|
462
|
+
size_t size;
|
463
|
+
hash_item* item;
|
464
|
+
|
465
|
+
GetKV(self, kv);
|
466
|
+
StringValue(vkey);
|
467
|
+
key = RSTRING_PTR(vkey);
|
468
|
+
size = RSTRING_LEN(vkey);
|
469
|
+
item = kv_fetch(kv, key, size);
|
470
|
+
if (item == NULL) return Qnil;
|
471
|
+
return rb_str_new(item_val(item), item->val_size);
|
472
|
+
}
|
473
|
+
|
474
|
+
static VALUE
|
475
|
+
rb_kv_up(VALUE self, VALUE vkey) {
|
476
|
+
inmemory_kv* kv;
|
477
|
+
const char *key;
|
478
|
+
size_t size;
|
479
|
+
hash_item* item;
|
480
|
+
|
481
|
+
GetKV(self, kv);
|
482
|
+
StringValue(vkey);
|
483
|
+
key = RSTRING_PTR(vkey);
|
484
|
+
size = RSTRING_LEN(vkey);
|
485
|
+
item = kv_fetch(kv, key, size);
|
486
|
+
if (item == NULL) return Qnil;
|
487
|
+
kv_up(kv, item);
|
488
|
+
return rb_str_new(item_val(item), item->val_size);
|
489
|
+
}
|
490
|
+
|
491
|
+
static VALUE
|
492
|
+
rb_kv_down(VALUE self, VALUE vkey) {
|
493
|
+
inmemory_kv* kv;
|
494
|
+
const char *key;
|
495
|
+
size_t size;
|
496
|
+
hash_item* item;
|
497
|
+
|
498
|
+
GetKV(self, kv);
|
499
|
+
StringValue(vkey);
|
500
|
+
key = RSTRING_PTR(vkey);
|
501
|
+
size = RSTRING_LEN(vkey);
|
502
|
+
item = kv_fetch(kv, key, size);
|
503
|
+
if (item == NULL) return Qnil;
|
504
|
+
kv_down(kv, item);
|
505
|
+
return rb_str_new(item_val(item), item->val_size);
|
506
|
+
}
|
507
|
+
|
508
|
+
static VALUE
|
509
|
+
rb_kv_include(VALUE self, VALUE vkey) {
|
510
|
+
inmemory_kv* kv;
|
511
|
+
const char *key;
|
512
|
+
size_t size;
|
513
|
+
|
514
|
+
GetKV(self, kv);
|
515
|
+
StringValue(vkey);
|
516
|
+
key = RSTRING_PTR(vkey);
|
517
|
+
size = RSTRING_LEN(vkey);
|
518
|
+
return kv_fetch(kv, key, size) ? Qtrue : Qfalse;
|
519
|
+
}
|
520
|
+
|
521
|
+
static VALUE
|
522
|
+
rb_kv_set(VALUE self, VALUE vkey, VALUE vval) {
|
523
|
+
inmemory_kv* kv;
|
524
|
+
const char *key, *val;
|
525
|
+
size_t ksize, vsize;
|
526
|
+
|
527
|
+
GetKV(self, kv);
|
528
|
+
StringValue(vkey);
|
529
|
+
StringValue(vval);
|
530
|
+
key = RSTRING_PTR(vkey);
|
531
|
+
ksize = RSTRING_LEN(vkey);
|
532
|
+
val = RSTRING_PTR(vval);
|
533
|
+
vsize = RSTRING_LEN(vval);
|
534
|
+
|
535
|
+
kv_insert(kv, key, ksize, val, vsize);
|
536
|
+
|
537
|
+
return vval;
|
538
|
+
}
|
539
|
+
|
540
|
+
static VALUE
|
541
|
+
rb_kv_del(VALUE self, VALUE vkey) {
|
542
|
+
inmemory_kv* kv;
|
543
|
+
const char *key;
|
544
|
+
size_t size;
|
545
|
+
hash_item* item;
|
546
|
+
VALUE res;
|
547
|
+
|
548
|
+
GetKV(self, kv);
|
549
|
+
StringValue(vkey);
|
550
|
+
key = RSTRING_PTR(vkey);
|
551
|
+
size = RSTRING_LEN(vkey);
|
552
|
+
item = kv_fetch(kv, key, size);
|
553
|
+
if (item == NULL) return Qnil;
|
554
|
+
res = rb_str_new(item_val(item), item->val_size);
|
555
|
+
kv_delete(kv, item);
|
556
|
+
return res;
|
557
|
+
}
|
558
|
+
|
559
|
+
static VALUE
|
560
|
+
rb_kv_first(VALUE self) {
|
561
|
+
inmemory_kv* kv;
|
562
|
+
hash_item* item;
|
563
|
+
VALUE key, val;
|
564
|
+
|
565
|
+
GetKV(self, kv);
|
566
|
+
item = kv_first(kv);
|
567
|
+
if (item == NULL) return Qnil;
|
568
|
+
key = rb_str_new(item->key, item->key_size);
|
569
|
+
val = rb_str_new(item_val(item), item->val_size);
|
570
|
+
return rb_assoc_new(key, val);
|
571
|
+
}
|
572
|
+
|
573
|
+
static VALUE
|
574
|
+
rb_kv_shift(VALUE self) {
|
575
|
+
inmemory_kv* kv;
|
576
|
+
hash_item* item;
|
577
|
+
VALUE key, val;
|
578
|
+
|
579
|
+
GetKV(self, kv);
|
580
|
+
item = kv_first(kv);
|
581
|
+
if (item == NULL) return Qnil;
|
582
|
+
key = rb_str_new(item->key, item->key_size);
|
583
|
+
val = rb_str_new(item_val(item), item->val_size);
|
584
|
+
kv_delete(kv, item);
|
585
|
+
return rb_assoc_new(key, val);
|
586
|
+
}
|
587
|
+
|
588
|
+
static VALUE
|
589
|
+
rb_kv_unshift(VALUE self, VALUE vkey, VALUE vval) {
|
590
|
+
inmemory_kv* kv;
|
591
|
+
const char *key, *val;
|
592
|
+
size_t ksize, vsize;
|
593
|
+
hash_item* item;
|
594
|
+
|
595
|
+
GetKV(self, kv);
|
596
|
+
StringValue(vkey);
|
597
|
+
StringValue(vval);
|
598
|
+
key = RSTRING_PTR(vkey);
|
599
|
+
ksize = RSTRING_LEN(vkey);
|
600
|
+
val = RSTRING_PTR(vval);
|
601
|
+
vsize = RSTRING_LEN(vval);
|
602
|
+
|
603
|
+
item = kv_insert(kv, key, ksize, val, vsize);
|
604
|
+
kv_down(kv, item);
|
605
|
+
|
606
|
+
return vval;
|
607
|
+
}
|
608
|
+
|
609
|
+
static VALUE
|
610
|
+
rb_kv_size(VALUE self) {
|
611
|
+
inmemory_kv* kv;
|
612
|
+
GetKV(self, kv);
|
613
|
+
return UINT2NUM(kv->tab.size);
|
614
|
+
}
|
615
|
+
|
616
|
+
static VALUE
|
617
|
+
rb_kv_empty_p(VALUE self) {
|
618
|
+
inmemory_kv* kv;
|
619
|
+
GetKV(self, kv);
|
620
|
+
return kv->tab.size ? Qfalse : Qtrue;
|
621
|
+
}
|
622
|
+
|
623
|
+
static VALUE
|
624
|
+
rb_kv_data_size(VALUE self) {
|
625
|
+
inmemory_kv* kv;
|
626
|
+
GetKV(self, kv);
|
627
|
+
return SIZET2NUM(kv->total_size);
|
628
|
+
}
|
629
|
+
|
630
|
+
static VALUE
|
631
|
+
rb_kv_total_size(VALUE self) {
|
632
|
+
inmemory_kv* kv;
|
633
|
+
GetKV(self, kv);
|
634
|
+
return SIZET2NUM(rb_kv_memsize(kv));
|
635
|
+
}
|
636
|
+
|
637
|
+
static void
|
638
|
+
keys_i(hash_item* item, void* arg) {
|
639
|
+
VALUE ary = (VALUE)arg;
|
640
|
+
rb_ary_push(ary, rb_str_new(item->key, item->key_size));
|
641
|
+
}
|
642
|
+
|
643
|
+
static void
|
644
|
+
vals_i(hash_item* item, void* arg) {
|
645
|
+
VALUE ary = (VALUE)arg;
|
646
|
+
rb_ary_push(ary, rb_str_new(item_val(item), item->val_size));
|
647
|
+
}
|
648
|
+
|
649
|
+
static void
|
650
|
+
pairs_i(hash_item* item, void* arg) {
|
651
|
+
VALUE ary = (VALUE)arg;
|
652
|
+
VALUE key, val;
|
653
|
+
key = rb_str_new(item->key, item->key_size);
|
654
|
+
val = rb_str_new(item_val(item), item->val_size);
|
655
|
+
rb_ary_push(ary, rb_assoc_new(key, val));
|
656
|
+
}
|
657
|
+
|
658
|
+
static VALUE
|
659
|
+
rb_kv_keys(VALUE self) {
|
660
|
+
inmemory_kv* kv;
|
661
|
+
VALUE res;
|
662
|
+
GetKV(self, kv);
|
663
|
+
res = rb_ary_new2(kv->tab.size);
|
664
|
+
kv_each(kv, keys_i, (void*)res);
|
665
|
+
return res;
|
666
|
+
}
|
667
|
+
|
668
|
+
static VALUE
|
669
|
+
rb_kv_vals(VALUE self) {
|
670
|
+
inmemory_kv* kv;
|
671
|
+
VALUE res;
|
672
|
+
GetKV(self, kv);
|
673
|
+
res = rb_ary_new2(kv->tab.size);
|
674
|
+
kv_each(kv, vals_i, (void*)res);
|
675
|
+
return res;
|
676
|
+
}
|
677
|
+
|
678
|
+
static VALUE
|
679
|
+
rb_kv_entries(VALUE self) {
|
680
|
+
inmemory_kv* kv;
|
681
|
+
VALUE res;
|
682
|
+
GetKV(self, kv);
|
683
|
+
res = rb_ary_new2(kv->tab.size);
|
684
|
+
kv_each(kv, pairs_i, (void*)res);
|
685
|
+
return res;
|
686
|
+
}
|
687
|
+
|
688
|
+
static void
|
689
|
+
key_i(hash_item* item, void* _ __attribute__((unused))) {
|
690
|
+
rb_yield(rb_str_new(item->key, item->key_size));
|
691
|
+
}
|
692
|
+
|
693
|
+
static void
|
694
|
+
val_i(hash_item* item, void* _ __attribute__((unused))) {
|
695
|
+
rb_yield(rb_str_new(item_val(item), item->val_size));
|
696
|
+
}
|
697
|
+
|
698
|
+
static void
|
699
|
+
pair_i(hash_item* item, void* _ __attribute__((unused))) {
|
700
|
+
VALUE key, val;
|
701
|
+
key = rb_str_new(item->key, item->key_size);
|
702
|
+
val = rb_str_new(item_val(item), item->val_size);
|
703
|
+
rb_yield(rb_assoc_new(key, val));
|
704
|
+
}
|
705
|
+
|
706
|
+
static VALUE
|
707
|
+
rb_kv_each_key(VALUE self) {
|
708
|
+
inmemory_kv* kv;
|
709
|
+
GetKV(self, kv);
|
710
|
+
RETURN_ENUMERATOR(self, 0, 0);
|
711
|
+
kv_each(kv, key_i, NULL);
|
712
|
+
return self;
|
713
|
+
}
|
714
|
+
|
715
|
+
static VALUE
|
716
|
+
rb_kv_each_val(VALUE self) {
|
717
|
+
inmemory_kv* kv;
|
718
|
+
GetKV(self, kv);
|
719
|
+
RETURN_ENUMERATOR(self, 0, 0);
|
720
|
+
kv_each(kv, val_i, NULL);
|
721
|
+
return self;
|
722
|
+
}
|
723
|
+
|
724
|
+
static VALUE
|
725
|
+
rb_kv_each(VALUE self) {
|
726
|
+
inmemory_kv* kv;
|
727
|
+
GetKV(self, kv);
|
728
|
+
RETURN_ENUMERATOR(self, 0, 0);
|
729
|
+
kv_each(kv, pair_i, NULL);
|
730
|
+
return self;
|
731
|
+
}
|
732
|
+
|
733
|
+
struct inspect_arg {
|
734
|
+
VALUE str, tmp;
|
735
|
+
};
|
736
|
+
static void
|
737
|
+
inspect_i(hash_item* item, void* arg) {
|
738
|
+
struct inspect_arg* a = arg;
|
739
|
+
VALUE ins;
|
740
|
+
rb_str_cat(a->tmp, item->key, item->key_size);
|
741
|
+
ins = rb_inspect(a->tmp);
|
742
|
+
rb_str_cat(a->str, " ", 1);
|
743
|
+
rb_str_cat(a->str, RSTRING_PTR(ins), RSTRING_LEN(ins));
|
744
|
+
rb_str_cat(a->str, "=>", 2);
|
745
|
+
rb_str_resize(ins, 0);
|
746
|
+
rb_str_resize(a->tmp, 0);
|
747
|
+
rb_str_buf_cat(a->tmp, item_val(item), item->val_size);
|
748
|
+
ins = rb_inspect(a->tmp);
|
749
|
+
rb_str_append(a->str, ins);
|
750
|
+
rb_str_resize(ins, 0);
|
751
|
+
rb_str_resize(a->tmp, 0);
|
752
|
+
}
|
753
|
+
|
754
|
+
static VALUE
|
755
|
+
rb_kv_inspect(VALUE self) {
|
756
|
+
struct inspect_arg ins;
|
757
|
+
inmemory_kv* kv;
|
758
|
+
GetKV(self, kv);
|
759
|
+
ins.str = rb_str_buf_new2("<");
|
760
|
+
rb_str_append(ins.str, rb_class_name(CLASS_OF(self)));
|
761
|
+
if (kv->tab.size != 0) {
|
762
|
+
ins.tmp = rb_str_buf_new(0);
|
763
|
+
kv_each(kv, inspect_i, &ins);
|
764
|
+
}
|
765
|
+
rb_str_buf_cat2(ins.str, ">");
|
766
|
+
return ins.str;
|
767
|
+
}
|
768
|
+
|
769
|
+
static VALUE
|
770
|
+
rb_kv_init_copy(VALUE self, VALUE orig) {
|
771
|
+
inmemory_kv *origin, *new;
|
772
|
+
GetKV(self, new);
|
773
|
+
GetKV(orig, origin);
|
774
|
+
kv_copy_to(origin, new);
|
775
|
+
return self;
|
776
|
+
}
|
777
|
+
|
778
|
+
void
|
779
|
+
Init_inmemory_kv() {
|
780
|
+
VALUE mod_inmemory_kv, cls_str2str;
|
781
|
+
mod_inmemory_kv = rb_define_module("InMemoryKV");
|
782
|
+
cls_str2str = rb_define_class_under(mod_inmemory_kv, "Str2Str", rb_cObject);
|
783
|
+
rb_define_alloc_func(cls_str2str, rb_kv_alloc);
|
784
|
+
rb_define_method(cls_str2str, "[]", rb_kv_get, 1);
|
785
|
+
rb_define_method(cls_str2str, "up", rb_kv_up, 1);
|
786
|
+
rb_define_method(cls_str2str, "down", rb_kv_down, 1);
|
787
|
+
rb_define_method(cls_str2str, "[]=", rb_kv_set, 2);
|
788
|
+
rb_define_method(cls_str2str, "unshift", rb_kv_unshift, 2);
|
789
|
+
rb_define_method(cls_str2str, "delete", rb_kv_del, 1);
|
790
|
+
rb_define_method(cls_str2str, "empty?", rb_kv_empty_p, 0);
|
791
|
+
rb_define_method(cls_str2str, "size", rb_kv_size, 0);
|
792
|
+
rb_define_method(cls_str2str, "count", rb_kv_size, 0);
|
793
|
+
rb_define_method(cls_str2str, "data_size", rb_kv_data_size, 0);
|
794
|
+
rb_define_method(cls_str2str, "total_size", rb_kv_total_size, 0);
|
795
|
+
rb_define_method(cls_str2str, "include?", rb_kv_include, 1);
|
796
|
+
rb_define_method(cls_str2str, "has_key?", rb_kv_include, 1);
|
797
|
+
rb_define_method(cls_str2str, "first", rb_kv_first, 0);
|
798
|
+
rb_define_method(cls_str2str, "shift", rb_kv_shift, 0);
|
799
|
+
rb_define_method(cls_str2str, "keys", rb_kv_keys, 0);
|
800
|
+
rb_define_method(cls_str2str, "values", rb_kv_vals, 0);
|
801
|
+
rb_define_method(cls_str2str, "entries", rb_kv_entries, 0);
|
802
|
+
rb_define_method(cls_str2str, "each_key", rb_kv_each_key, 0);
|
803
|
+
rb_define_method(cls_str2str, "each_value", rb_kv_each_val, 0);
|
804
|
+
rb_define_method(cls_str2str, "each_pair", rb_kv_each, 0);
|
805
|
+
rb_define_method(cls_str2str, "each", rb_kv_each, 0);
|
806
|
+
rb_define_method(cls_str2str, "inspect", rb_kv_inspect, 0);
|
807
|
+
rb_define_method(cls_str2str, "initialize_copy", rb_kv_init_copy, 1);
|
808
|
+
rb_include_module(cls_str2str, rb_mEnumerable);
|
809
|
+
}
|
data/inmemory_kv.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'inmemory_kv/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "inmemory_kv"
|
8
|
+
spec.version = InMemoryKV::VERSION
|
9
|
+
spec.authors = ["Sokolov Yura aka funny_falcon"]
|
10
|
+
spec.email = ["funny.falcon@gmail.com"]
|
11
|
+
spec.summary = %q{Simple in memory string/string hash}
|
12
|
+
spec.description = %q{Simple in memory string/string hash}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.extensions = ["ext/extconf.rb"]
|
20
|
+
spec.require_paths = ["lib", "ext"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "minitest"
|
25
|
+
end
|
data/lib/inmemory_kv.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'inmemory_kv'
|
2
|
+
require 'minitest/spec'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
|
5
|
+
describe InMemoryKV::Str2Str do
|
6
|
+
let(:s2s) { InMemoryKV::Str2Str.new }
|
7
|
+
it "should be setable" do
|
8
|
+
s2s['asdf'] = 'qwer'
|
9
|
+
end
|
10
|
+
it "should be fetchable" do
|
11
|
+
s2s['asdf'].must_be_nil
|
12
|
+
end
|
13
|
+
it "should be inspected" do
|
14
|
+
s2s.inspect.must_equal '<InMemoryKV::Str2Str>'
|
15
|
+
end
|
16
|
+
it "is empty" do
|
17
|
+
s2s.must_be_empty
|
18
|
+
end
|
19
|
+
it "has zero size" do
|
20
|
+
s2s.size.must_equal 0
|
21
|
+
end
|
22
|
+
it "has zero data_size" do
|
23
|
+
s2s.data_size.must_equal 0
|
24
|
+
end
|
25
|
+
it "should have nil first" do
|
26
|
+
s2s.first.must_be_nil
|
27
|
+
end
|
28
|
+
it "should have no keys nor values nor entries" do
|
29
|
+
s2s.keys.must_be_empty
|
30
|
+
s2s.values.must_be_empty
|
31
|
+
s2s.entries.must_be_empty
|
32
|
+
end
|
33
|
+
it "should not iterate" do
|
34
|
+
s2s.each_key{ raise }
|
35
|
+
s2s.each_value{ raise }
|
36
|
+
s2s.each{ raise }
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "filled with one item" do
|
40
|
+
before do
|
41
|
+
s2s['asdf'] = 'qwer'
|
42
|
+
end
|
43
|
+
it "should be inspectable" do
|
44
|
+
s2s.inspect.must_equal '<InMemoryKV::Str2Str "asdf"=>"qwer">'
|
45
|
+
end
|
46
|
+
it "should be fetchable" do
|
47
|
+
s2s['asdf'].must_equal 'qwer'
|
48
|
+
end
|
49
|
+
it "should has size == 1" do
|
50
|
+
s2s.size.must_equal 1
|
51
|
+
end
|
52
|
+
it "should has nonzero data_size" do
|
53
|
+
s2s.data_size.wont_equal 0
|
54
|
+
end
|
55
|
+
it "first should not be nil" do
|
56
|
+
s2s.first.must_equal ["asdf", "qwer"]
|
57
|
+
end
|
58
|
+
it "should react on delete" do
|
59
|
+
s2s.delete("asdf").must_equal('qwer')
|
60
|
+
s2s.size.must_equal 0
|
61
|
+
s2s.data_size.must_equal 0
|
62
|
+
s2s.first.must_be_nil
|
63
|
+
s2s.each{ raise }
|
64
|
+
end
|
65
|
+
it "should react on shift" do
|
66
|
+
s2s.shift.must_equal(['asdf','qwer'])
|
67
|
+
s2s.size.must_equal 0
|
68
|
+
s2s.data_size.must_equal 0
|
69
|
+
s2s.first.must_be_nil
|
70
|
+
s2s.each{ raise }
|
71
|
+
end
|
72
|
+
it "should allow rewrite value" do
|
73
|
+
s2s['asdf'] = 'zxcv'
|
74
|
+
s2s['asdf'].must_equal 'zxcv'
|
75
|
+
end
|
76
|
+
it "should allow dup, and perform copy on write" do
|
77
|
+
copy = s2s.dup
|
78
|
+
copy['asdf'].must_equal 'qwer'
|
79
|
+
s2s['asdf'] = 'zxcv'
|
80
|
+
s2s['asdf'].must_equal 'zxcv'
|
81
|
+
copy['asdf'].must_equal 'qwer'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "filled with two items" do
|
86
|
+
before do
|
87
|
+
s2s['asdf'] = 'qwer'
|
88
|
+
s2s['qwer'] = 'zxcv'
|
89
|
+
end
|
90
|
+
it "should be inspectable" do
|
91
|
+
s2s.inspect.must_equal '<InMemoryKV::Str2Str "asdf"=>"qwer" "qwer"=>"zxcv">'
|
92
|
+
end
|
93
|
+
it "should be fetchable" do
|
94
|
+
s2s['asdf'].must_equal 'qwer'
|
95
|
+
s2s['qwer'].must_equal 'zxcv'
|
96
|
+
end
|
97
|
+
it "should has size == 2" do
|
98
|
+
s2s.size.must_equal 2
|
99
|
+
end
|
100
|
+
it "first should not be nil" do
|
101
|
+
s2s.first.must_equal ["asdf", "qwer"]
|
102
|
+
end
|
103
|
+
it "should react on delete" do
|
104
|
+
s2s.delete("asdf").must_equal('qwer')
|
105
|
+
s2s.size.must_equal 1
|
106
|
+
s2s.first.must_equal ['qwer', 'zxcv']
|
107
|
+
s2s.delete("qwer").must_equal('zxcv')
|
108
|
+
s2s.data_size.must_equal 0
|
109
|
+
s2s.each{ raise }
|
110
|
+
end
|
111
|
+
it "should react on shift" do
|
112
|
+
s2s.shift.must_equal(['asdf','qwer'])
|
113
|
+
s2s.size.must_equal 1
|
114
|
+
s2s.shift.must_equal ['qwer', 'zxcv']
|
115
|
+
s2s.size.must_equal 0
|
116
|
+
s2s.data_size.must_equal 0
|
117
|
+
s2s.first.must_be_nil
|
118
|
+
s2s.each{ raise }
|
119
|
+
end
|
120
|
+
it "should reorder on set" do
|
121
|
+
s2s['asdf'] = 'yuio'
|
122
|
+
s2s.first.must_equal ['qwer', 'zxcv']
|
123
|
+
s2s.entries.must_equal [
|
124
|
+
['qwer', 'zxcv'],
|
125
|
+
['asdf', 'yuio']
|
126
|
+
]
|
127
|
+
end
|
128
|
+
it "should allow explicit reorder" do
|
129
|
+
s2s.up 'asdf'
|
130
|
+
s2s.entries.must_equal [
|
131
|
+
['qwer', 'zxcv'],
|
132
|
+
['asdf', 'qwer']
|
133
|
+
]
|
134
|
+
s2s.down 'asdf'
|
135
|
+
s2s.entries.must_equal [
|
136
|
+
['asdf', 'qwer'],
|
137
|
+
['qwer', 'zxcv']
|
138
|
+
]
|
139
|
+
end
|
140
|
+
it "should allow dup, and perform copy on write" do
|
141
|
+
copy = s2s.dup
|
142
|
+
copy['asdf'].must_equal 'qwer'
|
143
|
+
s2s['asdf'] = 'zxcv'
|
144
|
+
s2s['asdf'].must_equal 'zxcv'
|
145
|
+
copy['asdf'].must_equal 'qwer'
|
146
|
+
|
147
|
+
s2s['qwer'].must_equal 'zxcv'
|
148
|
+
copy['qwer'].must_equal 'zxcv'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "huge filled" do
|
153
|
+
let(:num) { 1000 }
|
154
|
+
before do
|
155
|
+
num.times do |i|
|
156
|
+
s2s[i.to_s] = "q#{i}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
let(:hsh) do
|
160
|
+
h = {}
|
161
|
+
num.times do |i|
|
162
|
+
h[i.to_s] = "q#{i}"
|
163
|
+
end
|
164
|
+
h
|
165
|
+
end
|
166
|
+
it "should store all values" do
|
167
|
+
num.times do |i|
|
168
|
+
s2s[i.to_s].must_equal "q#{i}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
it "should report entries" do
|
172
|
+
s2s.entries.must_equal hsh.entries
|
173
|
+
end
|
174
|
+
it "should delete entries" do
|
175
|
+
2.step(num, 2) do |i|
|
176
|
+
s2s.delete(i.to_s).must_equal hsh.delete(i.to_s)
|
177
|
+
end
|
178
|
+
1.step(num, 2) do |i|
|
179
|
+
s2s[i.to_s].must_equal hsh[i.to_s]
|
180
|
+
end
|
181
|
+
s2s.size.must_equal hsh.size
|
182
|
+
s2s.entries.must_equal hsh.entries
|
183
|
+
end
|
184
|
+
it "should reorder on set" do
|
185
|
+
s2s.first.must_equal ['0', 'q0']
|
186
|
+
s2s['0'] = 'ya'
|
187
|
+
s2s.first.must_equal ['1', 'q1']
|
188
|
+
s2s.entries.last.must_equal ['0', 'ya']
|
189
|
+
end
|
190
|
+
it "should allow explicit reorder" do
|
191
|
+
s2s.up '235'
|
192
|
+
s2s.entries.last.must_equal ['235', 'q235']
|
193
|
+
s2s.down '235'
|
194
|
+
s2s.first.must_equal ['235', 'q235']
|
195
|
+
s2s.entries.last.wont_equal ['235', 'q235']
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: inmemory_kv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sokolov Yura aka funny_falcon
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Simple in memory string/string hash
|
56
|
+
email:
|
57
|
+
- funny.falcon@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions:
|
60
|
+
- ext/extconf.rb
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- ext/extconf.rb
|
69
|
+
- ext/inmemory_kv.c
|
70
|
+
- inmemory_kv.gemspec
|
71
|
+
- lib/inmemory_kv.rb
|
72
|
+
- lib/inmemory_kv/version.rb
|
73
|
+
- test/test_str2str.rb
|
74
|
+
homepage: ''
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
- ext
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.4.3
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Simple in memory string/string hash
|
99
|
+
test_files:
|
100
|
+
- test/test_str2str.rb
|