isomorfeus-iodine 0.7.45 → 0.7.46
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -40
- data/.github/workflows/ruby.yml +2 -2
- data/ext/iodine/fio_tmpfile.h +50 -50
- data/ext/iodine/fiobj_hash.c +409 -409
- data/ext/iodine/fiobj_numbers.c +344 -344
- data/ext/iodine/fiobj_str.c +433 -433
- data/ext/iodine/iodine_caller.c +4 -6
- data/ext/iodine/iodine_caller.h +26 -27
- data/ext/iodine/iodine_connection.c +1 -1
- data/ext/iodine/iodine_http.c +2 -1
- data/ext/iodine/iodine_mustache.c +1 -1
- data/ext/iodine/iodine_rack_io.c +281 -281
- data/ext/iodine/iodine_store.c +142 -142
- data/ext/iodine/iodine_tls.c +1 -1
- data/isomorfeus-iodine.gemspec +1 -0
- data/lib/iodine/version.rb +3 -3
- metadata +5 -5
data/ext/iodine/fiobj_hash.c
CHANGED
@@ -1,409 +1,409 @@
|
|
1
|
-
/*
|
2
|
-
Copyright: Boaz Segev, 2017-2019
|
3
|
-
License: MIT
|
4
|
-
*/
|
5
|
-
|
6
|
-
#include <fiobject.h>
|
7
|
-
|
8
|
-
#include <assert.h>
|
9
|
-
#include <fiobj_hash.h>
|
10
|
-
|
11
|
-
#define FIO_SET_CALLOC(size, count) fio_calloc((size), (count))
|
12
|
-
#define FIO_SET_REALLOC(ptr, original_size, size, valid_data_length) \
|
13
|
-
fio_realloc2((ptr), (size), (valid_data_length))
|
14
|
-
#define FIO_SET_FREE(ptr, size) fio_free((ptr))
|
15
|
-
|
16
|
-
#define FIO_SET_NAME fio_hash__
|
17
|
-
#define FIO_SET_KEY_TYPE FIOBJ
|
18
|
-
#define FIO_SET_KEY_COMPARE(o1, o2) \
|
19
|
-
((o2) == ((FIOBJ)-1) || (o1) == ((FIOBJ)-1) || fiobj_iseq((o1), (o2)))
|
20
|
-
#define FIO_SET_KEY_COPY(dest, obj) ((dest) = fiobj_dup((obj)))
|
21
|
-
#define FIO_SET_KEY_DESTROY(obj) \
|
22
|
-
do { \
|
23
|
-
fiobj_free((obj)); \
|
24
|
-
(obj) = FIOBJ_INVALID; \
|
25
|
-
} while (0)
|
26
|
-
#define FIO_SET_OBJ_TYPE FIOBJ
|
27
|
-
#define FIO_SET_OBJ_COMPARE(o1, o2) fiobj_iseq((o1), (o2))
|
28
|
-
#define FIO_SET_OBJ_COPY(dest, obj) ((dest) = fiobj_dup(obj))
|
29
|
-
#define FIO_SET_OBJ_DESTROY(obj) \
|
30
|
-
do { \
|
31
|
-
fiobj_free((obj)); \
|
32
|
-
(obj) = FIOBJ_INVALID; \
|
33
|
-
} while (0)
|
34
|
-
|
35
|
-
#include <fio.h>
|
36
|
-
|
37
|
-
#include <errno.h>
|
38
|
-
|
39
|
-
#include <pthread.h>
|
40
|
-
|
41
|
-
/* *****************************************************************************
|
42
|
-
Hash types
|
43
|
-
***************************************************************************** */
|
44
|
-
typedef struct {
|
45
|
-
fiobj_object_header_s head;
|
46
|
-
fio_hash___s hash;
|
47
|
-
} fiobj_hash_s;
|
48
|
-
|
49
|
-
#define obj2hash(o) ((fiobj_hash_s *)(FIOBJ2PTR(o)))
|
50
|
-
|
51
|
-
void fiobj_hash_rehash(FIOBJ h) {
|
52
|
-
assert(h && FIOBJ_TYPE_IS(h, FIOBJ_T_HASH));
|
53
|
-
fio_hash___rehash(&obj2hash(h)->hash);
|
54
|
-
}
|
55
|
-
|
56
|
-
/* *****************************************************************************
|
57
|
-
Hash alloc + VTable
|
58
|
-
***************************************************************************** */
|
59
|
-
|
60
|
-
static void fiobj_hash_dealloc(FIOBJ o, void (*task)(FIOBJ, void *),
|
61
|
-
void *arg) {
|
62
|
-
FIO_SET_FOR_LOOP(&obj2hash(o)->hash, i) {
|
63
|
-
if (i->obj.key)
|
64
|
-
task((FIOBJ)i->obj.obj, arg);
|
65
|
-
fiobj_free((FIOBJ)i->obj.key);
|
66
|
-
i->obj.key = FIOBJ_INVALID;
|
67
|
-
i->obj.obj = FIOBJ_INVALID;
|
68
|
-
}
|
69
|
-
obj2hash(o)->hash.count = 0;
|
70
|
-
fio_hash___free(&obj2hash(o)->hash);
|
71
|
-
fio_free(FIOBJ2PTR(o));
|
72
|
-
}
|
73
|
-
|
74
|
-
static pthread_key_t each_at_key;
|
75
|
-
static pthread_once_t each_at_key_once = PTHREAD_ONCE_INIT;
|
76
|
-
static void init_each_at_key(void) {
|
77
|
-
pthread_key_create(&each_at_key, free);
|
78
|
-
}
|
79
|
-
static void init_each_at_key_ptr(void) {
|
80
|
-
FIOBJ *eak = malloc(sizeof(FIOBJ));
|
81
|
-
FIO_ASSERT_ALLOC(eak);
|
82
|
-
*eak = FIOBJ_INVALID;
|
83
|
-
pthread_setspecific(each_at_key, eak);
|
84
|
-
}
|
85
|
-
|
86
|
-
static size_t fiobj_hash_each1(FIOBJ o, size_t start_at,
|
87
|
-
int (*task)(FIOBJ obj, void *arg), void *arg) {
|
88
|
-
assert(o && FIOBJ_TYPE_IS(o, FIOBJ_T_HASH));
|
89
|
-
pthread_once(&each_at_key_once, init_each_at_key);
|
90
|
-
FIOBJ *each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
91
|
-
if (!each_at_key_ptr) {
|
92
|
-
init_each_at_key_ptr();
|
93
|
-
each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
94
|
-
}
|
95
|
-
FIOBJ old_each_at_key = *each_at_key_ptr;
|
96
|
-
fio_hash___s *hash = &obj2hash(o)->hash;
|
97
|
-
size_t count = 0;
|
98
|
-
if (hash->count == hash->pos) {
|
99
|
-
/* no holes in the hash, we can work as we please. */
|
100
|
-
for (count = start_at; count < hash->count; ++count) {
|
101
|
-
*each_at_key_ptr = hash->ordered[count].obj.key;
|
102
|
-
if (task((FIOBJ)hash->ordered[count].obj.obj, arg) == -1) {
|
103
|
-
++count;
|
104
|
-
goto end;
|
105
|
-
}
|
106
|
-
}
|
107
|
-
} else {
|
108
|
-
size_t pos = 0;
|
109
|
-
for (; pos < start_at && pos < hash->pos; ++pos) {
|
110
|
-
/* counting */
|
111
|
-
if (hash->ordered[pos].obj.key == FIOBJ_INVALID)
|
112
|
-
++start_at;
|
113
|
-
else
|
114
|
-
++count;
|
115
|
-
}
|
116
|
-
for (; pos < hash->pos; ++pos) {
|
117
|
-
/* performing */
|
118
|
-
if (hash->ordered[pos].obj.key == FIOBJ_INVALID)
|
119
|
-
continue;
|
120
|
-
++count;
|
121
|
-
*each_at_key_ptr = hash->ordered[pos].obj.key;
|
122
|
-
if (task((FIOBJ)hash->ordered[pos].obj.obj, arg) == -1)
|
123
|
-
break;
|
124
|
-
}
|
125
|
-
}
|
126
|
-
end:
|
127
|
-
*each_at_key_ptr = old_each_at_key;
|
128
|
-
return count;
|
129
|
-
}
|
130
|
-
|
131
|
-
FIOBJ fiobj_hash_key_in_loop(void) {
|
132
|
-
pthread_once(&each_at_key_once, init_each_at_key);
|
133
|
-
FIOBJ *each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
134
|
-
if (!each_at_key_ptr) {
|
135
|
-
init_each_at_key_ptr();
|
136
|
-
each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
137
|
-
}
|
138
|
-
return *each_at_key_ptr;
|
139
|
-
}
|
140
|
-
|
141
|
-
static size_t fiobj_hash_is_eq(const FIOBJ self, const FIOBJ other) {
|
142
|
-
if (fio_hash___count(&obj2hash(self)->hash) !=
|
143
|
-
fio_hash___count(&obj2hash(other)->hash))
|
144
|
-
return 0;
|
145
|
-
return 1;
|
146
|
-
}
|
147
|
-
|
148
|
-
/** Returns the number of elements in the Array. */
|
149
|
-
size_t fiobj_hash_count(const FIOBJ o) {
|
150
|
-
assert(o && FIOBJ_TYPE_IS(o, FIOBJ_T_HASH));
|
151
|
-
return fio_hash___count(&obj2hash(o)->hash);
|
152
|
-
}
|
153
|
-
|
154
|
-
intptr_t fiobj_hash2num(const FIOBJ o) { return (intptr_t)fiobj_hash_count(o); }
|
155
|
-
|
156
|
-
static size_t fiobj_hash_is_true(const FIOBJ o) {
|
157
|
-
return fiobj_hash_count(o) != 0;
|
158
|
-
}
|
159
|
-
|
160
|
-
fio_str_info_s fiobject___noop_to_str(const FIOBJ o);
|
161
|
-
intptr_t fiobject___noop_to_i(const FIOBJ o);
|
162
|
-
double fiobject___noop_to_f(const FIOBJ o);
|
163
|
-
|
164
|
-
const fiobj_object_vtable_s FIOBJECT_VTABLE_HASH = {
|
165
|
-
.class_name = "Hash",
|
166
|
-
.dealloc = fiobj_hash_dealloc,
|
167
|
-
.is_eq = fiobj_hash_is_eq,
|
168
|
-
.count = fiobj_hash_count,
|
169
|
-
.each = fiobj_hash_each1,
|
170
|
-
.is_true = fiobj_hash_is_true,
|
171
|
-
.to_str = fiobject___noop_to_str,
|
172
|
-
.to_i = fiobj_hash2num,
|
173
|
-
.to_f = fiobject___noop_to_f,
|
174
|
-
};
|
175
|
-
|
176
|
-
/* *****************************************************************************
|
177
|
-
Hash API
|
178
|
-
***************************************************************************** */
|
179
|
-
|
180
|
-
/**
|
181
|
-
* Creates a mutable empty Hash object. Use `fiobj_free` when done.
|
182
|
-
*
|
183
|
-
* Notice that these Hash objects are designed for smaller collections and
|
184
|
-
* retain order of object insertion.
|
185
|
-
*/
|
186
|
-
FIOBJ fiobj_hash_new(void) {
|
187
|
-
fiobj_hash_s *h = fio_malloc(sizeof(*h));
|
188
|
-
FIO_ASSERT_ALLOC(h);
|
189
|
-
*h = (fiobj_hash_s){.head = {.ref = 1, .type = FIOBJ_T_HASH},
|
190
|
-
.hash = FIO_SET_INIT};
|
191
|
-
return (FIOBJ)h | FIOBJECT_HASH_FLAG;
|
192
|
-
}
|
193
|
-
|
194
|
-
/**
|
195
|
-
* Creates a mutable empty Hash object with an initial capacity of `capa`. Use
|
196
|
-
* `fiobj_free` when done.
|
197
|
-
*
|
198
|
-
* Notice that these Hash objects are designed for smaller collections and
|
199
|
-
* retain order of object insertion.
|
200
|
-
*/
|
201
|
-
FIOBJ fiobj_hash_new2(size_t capa) {
|
202
|
-
fiobj_hash_s *h = fio_malloc(sizeof(*h));
|
203
|
-
FIO_ASSERT_ALLOC(h);
|
204
|
-
*h = (fiobj_hash_s){.head = {.ref = 1, .type = FIOBJ_T_HASH},
|
205
|
-
.hash = FIO_SET_INIT};
|
206
|
-
fio_hash___capa_require(&h->hash, capa);
|
207
|
-
return (FIOBJ)h | FIOBJECT_HASH_FLAG;
|
208
|
-
}
|
209
|
-
|
210
|
-
/**
|
211
|
-
* Returns a temporary theoretical Hash map capacity.
|
212
|
-
* This could be used for testing performance and memory consumption.
|
213
|
-
*/
|
214
|
-
size_t fiobj_hash_capa(const FIOBJ hash) {
|
215
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
216
|
-
return fio_hash___capa(&obj2hash(hash)->hash);
|
217
|
-
}
|
218
|
-
|
219
|
-
/**
|
220
|
-
* Sets a key-value pair in the Hash, duplicating the Symbol and **moving**
|
221
|
-
* the ownership of the object to the Hash.
|
222
|
-
*
|
223
|
-
* Returns -1 on error.
|
224
|
-
*/
|
225
|
-
int fiobj_hash_set(FIOBJ hash, FIOBJ key, FIOBJ obj) {
|
226
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
227
|
-
if (FIOBJ_TYPE_IS(key, FIOBJ_T_STRING))
|
228
|
-
fiobj_str_freeze(key);
|
229
|
-
fio_hash___insert(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, obj, NULL);
|
230
|
-
fiobj_free(obj); /* take ownership - free the user's reference. */
|
231
|
-
return 0;
|
232
|
-
}
|
233
|
-
|
234
|
-
/**
|
235
|
-
* Allows the Hash to be used as a stack.
|
236
|
-
*
|
237
|
-
* If a pointer `key` is provided, it will receive ownership of the key
|
238
|
-
* (remember to free).
|
239
|
-
*
|
240
|
-
* Returns FIOBJ_INVALID on error.
|
241
|
-
*
|
242
|
-
* Returns and object if successful (remember to free).
|
243
|
-
*/
|
244
|
-
FIOBJ fiobj_hash_pop(FIOBJ hash, FIOBJ *key) {
|
245
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
246
|
-
FIOBJ old;
|
247
|
-
if (fio_hash___count(&obj2hash(hash)->hash))
|
248
|
-
return FIOBJ_INVALID;
|
249
|
-
old = fiobj_dup(fio_hash___last(&obj2hash(hash)->hash).obj);
|
250
|
-
if (key)
|
251
|
-
*key = fiobj_dup(fio_hash___last(&obj2hash(hash)->hash).key);
|
252
|
-
fio_hash___pop(&obj2hash(hash)->hash);
|
253
|
-
return old;
|
254
|
-
}
|
255
|
-
|
256
|
-
/**
|
257
|
-
* Replaces the value in a key-value pair, returning the old value (and it's
|
258
|
-
* ownership) to the caller.
|
259
|
-
*
|
260
|
-
* A return value of NULL indicates that no previous object existed (but a new
|
261
|
-
* key-value pair was created.
|
262
|
-
*
|
263
|
-
* Errors are silently ignored.
|
264
|
-
*/
|
265
|
-
FIOBJ fiobj_hash_replace(FIOBJ hash, FIOBJ key, FIOBJ obj) {
|
266
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
267
|
-
FIOBJ old = FIOBJ_INVALID;
|
268
|
-
fio_hash___insert(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, obj, &old);
|
269
|
-
fiobj_free(obj); /* take ownership - free the user's reference. */
|
270
|
-
return old;
|
271
|
-
}
|
272
|
-
|
273
|
-
/**
|
274
|
-
* Removes a key-value pair from the Hash, if it exists.
|
275
|
-
*/
|
276
|
-
FIOBJ fiobj_hash_remove(FIOBJ hash, FIOBJ key) {
|
277
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
278
|
-
FIOBJ old = FIOBJ_INVALID;
|
279
|
-
fio_hash___remove(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, &old);
|
280
|
-
return old;
|
281
|
-
}
|
282
|
-
|
283
|
-
/**
|
284
|
-
* Removes a key-value pair from the Hash, if it exists, returning the old
|
285
|
-
* object (instead of freeing it).
|
286
|
-
*/
|
287
|
-
FIOBJ fiobj_hash_remove2(FIOBJ hash, uint64_t hash_value) {
|
288
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
289
|
-
FIOBJ old = FIOBJ_INVALID;
|
290
|
-
fio_hash___remove(&obj2hash(hash)->hash, hash_value, -1, &old);
|
291
|
-
return old;
|
292
|
-
}
|
293
|
-
|
294
|
-
/**
|
295
|
-
* Deletes a key-value pair from the Hash, if it exists, freeing the
|
296
|
-
* associated object.
|
297
|
-
*
|
298
|
-
* Returns -1 on type error or if the object never existed.
|
299
|
-
*/
|
300
|
-
int fiobj_hash_delete(FIOBJ hash, FIOBJ key) {
|
301
|
-
return fio_hash___remove(&obj2hash(hash)->hash, fiobj_obj2hash(key), key,
|
302
|
-
NULL);
|
303
|
-
}
|
304
|
-
|
305
|
-
/**
|
306
|
-
* Deletes a key-value pair from the Hash, if it exists, freeing the
|
307
|
-
* associated object.
|
308
|
-
*
|
309
|
-
* This function takes a `uintptr_t` Hash value (see `fio_siphash`) to
|
310
|
-
* perform a lookup in the HashMap, which is slightly faster than the other
|
311
|
-
* variations.
|
312
|
-
*
|
313
|
-
* Returns -1 on type error or if the object never existed.
|
314
|
-
*/
|
315
|
-
int fiobj_hash_delete2(FIOBJ hash, uint64_t key_hash) {
|
316
|
-
return fio_hash___remove(&obj2hash(hash)->hash, key_hash, -1, NULL);
|
317
|
-
}
|
318
|
-
|
319
|
-
/**
|
320
|
-
* Returns a temporary handle to the object associated with the Symbol, NULL
|
321
|
-
* if none.
|
322
|
-
*/
|
323
|
-
FIOBJ fiobj_hash_get(const FIOBJ hash, FIOBJ key) {
|
324
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
325
|
-
return fio_hash___find(&obj2hash(hash)->hash, fiobj_obj2hash(key), key);
|
326
|
-
;
|
327
|
-
}
|
328
|
-
|
329
|
-
/**
|
330
|
-
* Returns a temporary handle to the object associated hashed key value.
|
331
|
-
*
|
332
|
-
* This function takes a `uintptr_t` Hash value (see `fio_siphash`) to
|
333
|
-
* perform a lookup in the HashMap.
|
334
|
-
*
|
335
|
-
* Returns NULL if no object is associated with this hashed key value.
|
336
|
-
*/
|
337
|
-
FIOBJ fiobj_hash_get2(const FIOBJ hash, uint64_t key_hash) {
|
338
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
339
|
-
return fio_hash___find(&obj2hash(hash)->hash, key_hash, -1);
|
340
|
-
;
|
341
|
-
}
|
342
|
-
|
343
|
-
/**
|
344
|
-
* Returns 1 if the key (Symbol) exists in the Hash, even if value is NULL.
|
345
|
-
*/
|
346
|
-
int fiobj_hash_haskey(const FIOBJ hash, FIOBJ key) {
|
347
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
348
|
-
return fio_hash___find(&obj2hash(hash)->hash, fiobj_obj2hash(key), key) !=
|
349
|
-
FIOBJ_INVALID;
|
350
|
-
}
|
351
|
-
|
352
|
-
/**
|
353
|
-
* Empties the Hash.
|
354
|
-
*/
|
355
|
-
void fiobj_hash_clear(const FIOBJ hash) {
|
356
|
-
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
357
|
-
fio_hash___free(&obj2hash(hash)->hash);
|
358
|
-
}
|
359
|
-
|
360
|
-
/* *****************************************************************************
|
361
|
-
Simple Tests
|
362
|
-
***************************************************************************** */
|
363
|
-
|
364
|
-
#if DEBUG
|
365
|
-
void fiobj_test_hash(void) {
|
366
|
-
fprintf(stderr, "=== Testing Hash\n");
|
367
|
-
#define TEST_ASSERT(cond, ...) \
|
368
|
-
if (!(cond)) { \
|
369
|
-
fprintf(stderr, "* " __VA_ARGS__); \
|
370
|
-
fprintf(stderr, "Testing failed.\n"); \
|
371
|
-
exit(-1); \
|
372
|
-
}
|
373
|
-
FIOBJ o = fiobj_hash_new();
|
374
|
-
FIOBJ str_key = fiobj_str_new("Hello World!", 12);
|
375
|
-
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH), "Type identification error!\n");
|
376
|
-
TEST_ASSERT(fiobj_hash_count(o) == 0, "Hash should be empty!\n");
|
377
|
-
fiobj_hash_set(o, str_key, fiobj_true());
|
378
|
-
TEST_ASSERT(fiobj_str_write(str_key, "should fail...", 13) == 0,
|
379
|
-
"wrote to frozen string?");
|
380
|
-
TEST_ASSERT(fiobj_obj2cstr(str_key).len == 12,
|
381
|
-
"String was mutated (not frozen)!\n");
|
382
|
-
TEST_ASSERT(fiobj_hash_get(o, str_key) == fiobj_true(),
|
383
|
-
"full compare didn't get value back");
|
384
|
-
TEST_ASSERT(fiobj_hash_get2(o, fiobj_obj2hash(str_key)) == fiobj_true(),
|
385
|
-
"hash compare didn't get value back");
|
386
|
-
|
387
|
-
FIOBJ o2 = fiobj_hash_new2(3);
|
388
|
-
TEST_ASSERT(obj2hash(o2)->hash.capa >= 3,
|
389
|
-
"Hash capacity should be larger than 3! %zu != 4\n",
|
390
|
-
(size_t)obj2hash(o2)->hash.capa);
|
391
|
-
fiobj_hash_set(o2, str_key, fiobj_true());
|
392
|
-
TEST_ASSERT(fiobj_hash_is_eq(o, o2), "Hashes not equal at core! %zu != %zu\n",
|
393
|
-
fiobj_hash_count(o), fiobj_hash_count(o2));
|
394
|
-
TEST_ASSERT(fiobj_iseq(o, o2), "Hashes not equal!\n");
|
395
|
-
TEST_ASSERT(obj2hash(o2)->hash.capa > 3,
|
396
|
-
"Hash capacity should be larger than 3! %zu != 4\n",
|
397
|
-
(size_t)obj2hash(o2)->hash.capa);
|
398
|
-
|
399
|
-
fiobj_hash_delete(o, str_key);
|
400
|
-
|
401
|
-
TEST_ASSERT(fiobj_hash_get2(o, fiobj_obj2hash(str_key)) == 0,
|
402
|
-
"item wasn't deleted!");
|
403
|
-
fiobj_free(
|
404
|
-
str_key); /* note that a copy will remain in the Hash until rehashing. */
|
405
|
-
fiobj_free(o);
|
406
|
-
fiobj_free(o2);
|
407
|
-
fprintf(stderr, "* passed.\n");
|
408
|
-
}
|
409
|
-
#endif
|
1
|
+
/*
|
2
|
+
Copyright: Boaz Segev, 2017-2019
|
3
|
+
License: MIT
|
4
|
+
*/
|
5
|
+
|
6
|
+
#include <fiobject.h>
|
7
|
+
|
8
|
+
#include <assert.h>
|
9
|
+
#include <fiobj_hash.h>
|
10
|
+
|
11
|
+
#define FIO_SET_CALLOC(size, count) fio_calloc((size), (count))
|
12
|
+
#define FIO_SET_REALLOC(ptr, original_size, size, valid_data_length) \
|
13
|
+
fio_realloc2((ptr), (size), (valid_data_length))
|
14
|
+
#define FIO_SET_FREE(ptr, size) fio_free((ptr))
|
15
|
+
|
16
|
+
#define FIO_SET_NAME fio_hash__
|
17
|
+
#define FIO_SET_KEY_TYPE FIOBJ
|
18
|
+
#define FIO_SET_KEY_COMPARE(o1, o2) \
|
19
|
+
((o2) == ((FIOBJ)-1) || (o1) == ((FIOBJ)-1) || fiobj_iseq((o1), (o2)))
|
20
|
+
#define FIO_SET_KEY_COPY(dest, obj) ((dest) = fiobj_dup((obj)))
|
21
|
+
#define FIO_SET_KEY_DESTROY(obj) \
|
22
|
+
do { \
|
23
|
+
fiobj_free((obj)); \
|
24
|
+
(obj) = FIOBJ_INVALID; \
|
25
|
+
} while (0)
|
26
|
+
#define FIO_SET_OBJ_TYPE FIOBJ
|
27
|
+
#define FIO_SET_OBJ_COMPARE(o1, o2) fiobj_iseq((o1), (o2))
|
28
|
+
#define FIO_SET_OBJ_COPY(dest, obj) ((dest) = fiobj_dup(obj))
|
29
|
+
#define FIO_SET_OBJ_DESTROY(obj) \
|
30
|
+
do { \
|
31
|
+
fiobj_free((obj)); \
|
32
|
+
(obj) = FIOBJ_INVALID; \
|
33
|
+
} while (0)
|
34
|
+
|
35
|
+
#include <fio.h>
|
36
|
+
|
37
|
+
#include <errno.h>
|
38
|
+
|
39
|
+
#include <pthread.h>
|
40
|
+
|
41
|
+
/* *****************************************************************************
|
42
|
+
Hash types
|
43
|
+
***************************************************************************** */
|
44
|
+
typedef struct {
|
45
|
+
fiobj_object_header_s head;
|
46
|
+
fio_hash___s hash;
|
47
|
+
} fiobj_hash_s;
|
48
|
+
|
49
|
+
#define obj2hash(o) ((fiobj_hash_s *)(FIOBJ2PTR(o)))
|
50
|
+
|
51
|
+
void fiobj_hash_rehash(FIOBJ h) {
|
52
|
+
assert(h && FIOBJ_TYPE_IS(h, FIOBJ_T_HASH));
|
53
|
+
fio_hash___rehash(&obj2hash(h)->hash);
|
54
|
+
}
|
55
|
+
|
56
|
+
/* *****************************************************************************
|
57
|
+
Hash alloc + VTable
|
58
|
+
***************************************************************************** */
|
59
|
+
|
60
|
+
static void fiobj_hash_dealloc(FIOBJ o, void (*task)(FIOBJ, void *),
|
61
|
+
void *arg) {
|
62
|
+
FIO_SET_FOR_LOOP(&obj2hash(o)->hash, i) {
|
63
|
+
if (i->obj.key)
|
64
|
+
task((FIOBJ)i->obj.obj, arg);
|
65
|
+
fiobj_free((FIOBJ)i->obj.key);
|
66
|
+
i->obj.key = FIOBJ_INVALID;
|
67
|
+
i->obj.obj = FIOBJ_INVALID;
|
68
|
+
}
|
69
|
+
obj2hash(o)->hash.count = 0;
|
70
|
+
fio_hash___free(&obj2hash(o)->hash);
|
71
|
+
fio_free(FIOBJ2PTR(o));
|
72
|
+
}
|
73
|
+
|
74
|
+
static pthread_key_t each_at_key;
|
75
|
+
static pthread_once_t each_at_key_once = PTHREAD_ONCE_INIT;
|
76
|
+
static void init_each_at_key(void) {
|
77
|
+
pthread_key_create(&each_at_key, free);
|
78
|
+
}
|
79
|
+
static void init_each_at_key_ptr(void) {
|
80
|
+
FIOBJ *eak = malloc(sizeof(FIOBJ));
|
81
|
+
FIO_ASSERT_ALLOC(eak);
|
82
|
+
*eak = FIOBJ_INVALID;
|
83
|
+
pthread_setspecific(each_at_key, eak);
|
84
|
+
}
|
85
|
+
|
86
|
+
static size_t fiobj_hash_each1(FIOBJ o, size_t start_at,
|
87
|
+
int (*task)(FIOBJ obj, void *arg), void *arg) {
|
88
|
+
assert(o && FIOBJ_TYPE_IS(o, FIOBJ_T_HASH));
|
89
|
+
pthread_once(&each_at_key_once, init_each_at_key);
|
90
|
+
FIOBJ *each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
91
|
+
if (!each_at_key_ptr) {
|
92
|
+
init_each_at_key_ptr();
|
93
|
+
each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
94
|
+
}
|
95
|
+
FIOBJ old_each_at_key = *each_at_key_ptr;
|
96
|
+
fio_hash___s *hash = &obj2hash(o)->hash;
|
97
|
+
size_t count = 0;
|
98
|
+
if (hash->count == hash->pos) {
|
99
|
+
/* no holes in the hash, we can work as we please. */
|
100
|
+
for (count = start_at; count < hash->count; ++count) {
|
101
|
+
*each_at_key_ptr = hash->ordered[count].obj.key;
|
102
|
+
if (task((FIOBJ)hash->ordered[count].obj.obj, arg) == -1) {
|
103
|
+
++count;
|
104
|
+
goto end;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
} else {
|
108
|
+
size_t pos = 0;
|
109
|
+
for (; pos < start_at && pos < hash->pos; ++pos) {
|
110
|
+
/* counting */
|
111
|
+
if (hash->ordered[pos].obj.key == FIOBJ_INVALID)
|
112
|
+
++start_at;
|
113
|
+
else
|
114
|
+
++count;
|
115
|
+
}
|
116
|
+
for (; pos < hash->pos; ++pos) {
|
117
|
+
/* performing */
|
118
|
+
if (hash->ordered[pos].obj.key == FIOBJ_INVALID)
|
119
|
+
continue;
|
120
|
+
++count;
|
121
|
+
*each_at_key_ptr = hash->ordered[pos].obj.key;
|
122
|
+
if (task((FIOBJ)hash->ordered[pos].obj.obj, arg) == -1)
|
123
|
+
break;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
end:
|
127
|
+
*each_at_key_ptr = old_each_at_key;
|
128
|
+
return count;
|
129
|
+
}
|
130
|
+
|
131
|
+
FIOBJ fiobj_hash_key_in_loop(void) {
|
132
|
+
pthread_once(&each_at_key_once, init_each_at_key);
|
133
|
+
FIOBJ *each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
134
|
+
if (!each_at_key_ptr) {
|
135
|
+
init_each_at_key_ptr();
|
136
|
+
each_at_key_ptr = (FIOBJ *)pthread_getspecific(each_at_key);
|
137
|
+
}
|
138
|
+
return *each_at_key_ptr;
|
139
|
+
}
|
140
|
+
|
141
|
+
static size_t fiobj_hash_is_eq(const FIOBJ self, const FIOBJ other) {
|
142
|
+
if (fio_hash___count(&obj2hash(self)->hash) !=
|
143
|
+
fio_hash___count(&obj2hash(other)->hash))
|
144
|
+
return 0;
|
145
|
+
return 1;
|
146
|
+
}
|
147
|
+
|
148
|
+
/** Returns the number of elements in the Array. */
|
149
|
+
size_t fiobj_hash_count(const FIOBJ o) {
|
150
|
+
assert(o && FIOBJ_TYPE_IS(o, FIOBJ_T_HASH));
|
151
|
+
return fio_hash___count(&obj2hash(o)->hash);
|
152
|
+
}
|
153
|
+
|
154
|
+
intptr_t fiobj_hash2num(const FIOBJ o) { return (intptr_t)fiobj_hash_count(o); }
|
155
|
+
|
156
|
+
static size_t fiobj_hash_is_true(const FIOBJ o) {
|
157
|
+
return fiobj_hash_count(o) != 0;
|
158
|
+
}
|
159
|
+
|
160
|
+
fio_str_info_s fiobject___noop_to_str(const FIOBJ o);
|
161
|
+
intptr_t fiobject___noop_to_i(const FIOBJ o);
|
162
|
+
double fiobject___noop_to_f(const FIOBJ o);
|
163
|
+
|
164
|
+
const fiobj_object_vtable_s FIOBJECT_VTABLE_HASH = {
|
165
|
+
.class_name = "Hash",
|
166
|
+
.dealloc = fiobj_hash_dealloc,
|
167
|
+
.is_eq = fiobj_hash_is_eq,
|
168
|
+
.count = fiobj_hash_count,
|
169
|
+
.each = fiobj_hash_each1,
|
170
|
+
.is_true = fiobj_hash_is_true,
|
171
|
+
.to_str = fiobject___noop_to_str,
|
172
|
+
.to_i = fiobj_hash2num,
|
173
|
+
.to_f = fiobject___noop_to_f,
|
174
|
+
};
|
175
|
+
|
176
|
+
/* *****************************************************************************
|
177
|
+
Hash API
|
178
|
+
***************************************************************************** */
|
179
|
+
|
180
|
+
/**
|
181
|
+
* Creates a mutable empty Hash object. Use `fiobj_free` when done.
|
182
|
+
*
|
183
|
+
* Notice that these Hash objects are designed for smaller collections and
|
184
|
+
* retain order of object insertion.
|
185
|
+
*/
|
186
|
+
FIOBJ fiobj_hash_new(void) {
|
187
|
+
fiobj_hash_s *h = fio_malloc(sizeof(*h));
|
188
|
+
FIO_ASSERT_ALLOC(h);
|
189
|
+
*h = (fiobj_hash_s){.head = {.ref = 1, .type = FIOBJ_T_HASH},
|
190
|
+
.hash = FIO_SET_INIT};
|
191
|
+
return (FIOBJ)h | FIOBJECT_HASH_FLAG;
|
192
|
+
}
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Creates a mutable empty Hash object with an initial capacity of `capa`. Use
|
196
|
+
* `fiobj_free` when done.
|
197
|
+
*
|
198
|
+
* Notice that these Hash objects are designed for smaller collections and
|
199
|
+
* retain order of object insertion.
|
200
|
+
*/
|
201
|
+
FIOBJ fiobj_hash_new2(size_t capa) {
|
202
|
+
fiobj_hash_s *h = fio_malloc(sizeof(*h));
|
203
|
+
FIO_ASSERT_ALLOC(h);
|
204
|
+
*h = (fiobj_hash_s){.head = {.ref = 1, .type = FIOBJ_T_HASH},
|
205
|
+
.hash = FIO_SET_INIT};
|
206
|
+
fio_hash___capa_require(&h->hash, capa);
|
207
|
+
return (FIOBJ)h | FIOBJECT_HASH_FLAG;
|
208
|
+
}
|
209
|
+
|
210
|
+
/**
|
211
|
+
* Returns a temporary theoretical Hash map capacity.
|
212
|
+
* This could be used for testing performance and memory consumption.
|
213
|
+
*/
|
214
|
+
size_t fiobj_hash_capa(const FIOBJ hash) {
|
215
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
216
|
+
return fio_hash___capa(&obj2hash(hash)->hash);
|
217
|
+
}
|
218
|
+
|
219
|
+
/**
|
220
|
+
* Sets a key-value pair in the Hash, duplicating the Symbol and **moving**
|
221
|
+
* the ownership of the object to the Hash.
|
222
|
+
*
|
223
|
+
* Returns -1 on error.
|
224
|
+
*/
|
225
|
+
int fiobj_hash_set(FIOBJ hash, FIOBJ key, FIOBJ obj) {
|
226
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
227
|
+
if (FIOBJ_TYPE_IS(key, FIOBJ_T_STRING))
|
228
|
+
fiobj_str_freeze(key);
|
229
|
+
fio_hash___insert(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, obj, NULL);
|
230
|
+
fiobj_free(obj); /* take ownership - free the user's reference. */
|
231
|
+
return 0;
|
232
|
+
}
|
233
|
+
|
234
|
+
/**
|
235
|
+
* Allows the Hash to be used as a stack.
|
236
|
+
*
|
237
|
+
* If a pointer `key` is provided, it will receive ownership of the key
|
238
|
+
* (remember to free).
|
239
|
+
*
|
240
|
+
* Returns FIOBJ_INVALID on error.
|
241
|
+
*
|
242
|
+
* Returns and object if successful (remember to free).
|
243
|
+
*/
|
244
|
+
FIOBJ fiobj_hash_pop(FIOBJ hash, FIOBJ *key) {
|
245
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
246
|
+
FIOBJ old;
|
247
|
+
if (fio_hash___count(&obj2hash(hash)->hash))
|
248
|
+
return FIOBJ_INVALID;
|
249
|
+
old = fiobj_dup(fio_hash___last(&obj2hash(hash)->hash).obj);
|
250
|
+
if (key)
|
251
|
+
*key = fiobj_dup(fio_hash___last(&obj2hash(hash)->hash).key);
|
252
|
+
fio_hash___pop(&obj2hash(hash)->hash);
|
253
|
+
return old;
|
254
|
+
}
|
255
|
+
|
256
|
+
/**
|
257
|
+
* Replaces the value in a key-value pair, returning the old value (and it's
|
258
|
+
* ownership) to the caller.
|
259
|
+
*
|
260
|
+
* A return value of NULL indicates that no previous object existed (but a new
|
261
|
+
* key-value pair was created.
|
262
|
+
*
|
263
|
+
* Errors are silently ignored.
|
264
|
+
*/
|
265
|
+
FIOBJ fiobj_hash_replace(FIOBJ hash, FIOBJ key, FIOBJ obj) {
|
266
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
267
|
+
FIOBJ old = FIOBJ_INVALID;
|
268
|
+
fio_hash___insert(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, obj, &old);
|
269
|
+
fiobj_free(obj); /* take ownership - free the user's reference. */
|
270
|
+
return old;
|
271
|
+
}
|
272
|
+
|
273
|
+
/**
|
274
|
+
* Removes a key-value pair from the Hash, if it exists.
|
275
|
+
*/
|
276
|
+
FIOBJ fiobj_hash_remove(FIOBJ hash, FIOBJ key) {
|
277
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
278
|
+
FIOBJ old = FIOBJ_INVALID;
|
279
|
+
fio_hash___remove(&obj2hash(hash)->hash, fiobj_obj2hash(key), key, &old);
|
280
|
+
return old;
|
281
|
+
}
|
282
|
+
|
283
|
+
/**
|
284
|
+
* Removes a key-value pair from the Hash, if it exists, returning the old
|
285
|
+
* object (instead of freeing it).
|
286
|
+
*/
|
287
|
+
FIOBJ fiobj_hash_remove2(FIOBJ hash, uint64_t hash_value) {
|
288
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
289
|
+
FIOBJ old = FIOBJ_INVALID;
|
290
|
+
fio_hash___remove(&obj2hash(hash)->hash, hash_value, -1, &old);
|
291
|
+
return old;
|
292
|
+
}
|
293
|
+
|
294
|
+
/**
|
295
|
+
* Deletes a key-value pair from the Hash, if it exists, freeing the
|
296
|
+
* associated object.
|
297
|
+
*
|
298
|
+
* Returns -1 on type error or if the object never existed.
|
299
|
+
*/
|
300
|
+
int fiobj_hash_delete(FIOBJ hash, FIOBJ key) {
|
301
|
+
return fio_hash___remove(&obj2hash(hash)->hash, fiobj_obj2hash(key), key,
|
302
|
+
NULL);
|
303
|
+
}
|
304
|
+
|
305
|
+
/**
|
306
|
+
* Deletes a key-value pair from the Hash, if it exists, freeing the
|
307
|
+
* associated object.
|
308
|
+
*
|
309
|
+
* This function takes a `uintptr_t` Hash value (see `fio_siphash`) to
|
310
|
+
* perform a lookup in the HashMap, which is slightly faster than the other
|
311
|
+
* variations.
|
312
|
+
*
|
313
|
+
* Returns -1 on type error or if the object never existed.
|
314
|
+
*/
|
315
|
+
int fiobj_hash_delete2(FIOBJ hash, uint64_t key_hash) {
|
316
|
+
return fio_hash___remove(&obj2hash(hash)->hash, key_hash, -1, NULL);
|
317
|
+
}
|
318
|
+
|
319
|
+
/**
|
320
|
+
* Returns a temporary handle to the object associated with the Symbol, NULL
|
321
|
+
* if none.
|
322
|
+
*/
|
323
|
+
FIOBJ fiobj_hash_get(const FIOBJ hash, FIOBJ key) {
|
324
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
325
|
+
return fio_hash___find(&obj2hash(hash)->hash, fiobj_obj2hash(key), key);
|
326
|
+
;
|
327
|
+
}
|
328
|
+
|
329
|
+
/**
|
330
|
+
* Returns a temporary handle to the object associated hashed key value.
|
331
|
+
*
|
332
|
+
* This function takes a `uintptr_t` Hash value (see `fio_siphash`) to
|
333
|
+
* perform a lookup in the HashMap.
|
334
|
+
*
|
335
|
+
* Returns NULL if no object is associated with this hashed key value.
|
336
|
+
*/
|
337
|
+
FIOBJ fiobj_hash_get2(const FIOBJ hash, uint64_t key_hash) {
|
338
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
339
|
+
return fio_hash___find(&obj2hash(hash)->hash, key_hash, -1);
|
340
|
+
;
|
341
|
+
}
|
342
|
+
|
343
|
+
/**
|
344
|
+
* Returns 1 if the key (Symbol) exists in the Hash, even if value is NULL.
|
345
|
+
*/
|
346
|
+
int fiobj_hash_haskey(const FIOBJ hash, FIOBJ key) {
|
347
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
348
|
+
return fio_hash___find(&obj2hash(hash)->hash, fiobj_obj2hash(key), key) !=
|
349
|
+
FIOBJ_INVALID;
|
350
|
+
}
|
351
|
+
|
352
|
+
/**
|
353
|
+
* Empties the Hash.
|
354
|
+
*/
|
355
|
+
void fiobj_hash_clear(const FIOBJ hash) {
|
356
|
+
assert(hash && FIOBJ_TYPE_IS(hash, FIOBJ_T_HASH));
|
357
|
+
fio_hash___free(&obj2hash(hash)->hash);
|
358
|
+
}
|
359
|
+
|
360
|
+
/* *****************************************************************************
|
361
|
+
Simple Tests
|
362
|
+
***************************************************************************** */
|
363
|
+
|
364
|
+
#if DEBUG
|
365
|
+
void fiobj_test_hash(void) {
|
366
|
+
fprintf(stderr, "=== Testing Hash\n");
|
367
|
+
#define TEST_ASSERT(cond, ...) \
|
368
|
+
if (!(cond)) { \
|
369
|
+
fprintf(stderr, "* " __VA_ARGS__); \
|
370
|
+
fprintf(stderr, "Testing failed.\n"); \
|
371
|
+
exit(-1); \
|
372
|
+
}
|
373
|
+
FIOBJ o = fiobj_hash_new();
|
374
|
+
FIOBJ str_key = fiobj_str_new("Hello World!", 12);
|
375
|
+
TEST_ASSERT(FIOBJ_TYPE_IS(o, FIOBJ_T_HASH), "Type identification error!\n");
|
376
|
+
TEST_ASSERT(fiobj_hash_count(o) == 0, "Hash should be empty!\n");
|
377
|
+
fiobj_hash_set(o, str_key, fiobj_true());
|
378
|
+
TEST_ASSERT(fiobj_str_write(str_key, "should fail...", 13) == 0,
|
379
|
+
"wrote to frozen string?");
|
380
|
+
TEST_ASSERT(fiobj_obj2cstr(str_key).len == 12,
|
381
|
+
"String was mutated (not frozen)!\n");
|
382
|
+
TEST_ASSERT(fiobj_hash_get(o, str_key) == fiobj_true(),
|
383
|
+
"full compare didn't get value back");
|
384
|
+
TEST_ASSERT(fiobj_hash_get2(o, fiobj_obj2hash(str_key)) == fiobj_true(),
|
385
|
+
"hash compare didn't get value back");
|
386
|
+
|
387
|
+
FIOBJ o2 = fiobj_hash_new2(3);
|
388
|
+
TEST_ASSERT(obj2hash(o2)->hash.capa >= 3,
|
389
|
+
"Hash capacity should be larger than 3! %zu != 4\n",
|
390
|
+
(size_t)obj2hash(o2)->hash.capa);
|
391
|
+
fiobj_hash_set(o2, str_key, fiobj_true());
|
392
|
+
TEST_ASSERT(fiobj_hash_is_eq(o, o2), "Hashes not equal at core! %zu != %zu\n",
|
393
|
+
fiobj_hash_count(o), fiobj_hash_count(o2));
|
394
|
+
TEST_ASSERT(fiobj_iseq(o, o2), "Hashes not equal!\n");
|
395
|
+
TEST_ASSERT(obj2hash(o2)->hash.capa > 3,
|
396
|
+
"Hash capacity should be larger than 3! %zu != 4\n",
|
397
|
+
(size_t)obj2hash(o2)->hash.capa);
|
398
|
+
|
399
|
+
fiobj_hash_delete(o, str_key);
|
400
|
+
|
401
|
+
TEST_ASSERT(fiobj_hash_get2(o, fiobj_obj2hash(str_key)) == 0,
|
402
|
+
"item wasn't deleted!");
|
403
|
+
fiobj_free(
|
404
|
+
str_key); /* note that a copy will remain in the Hash until rehashing. */
|
405
|
+
fiobj_free(o);
|
406
|
+
fiobj_free(o2);
|
407
|
+
fprintf(stderr, "* passed.\n");
|
408
|
+
}
|
409
|
+
#endif
|