sophia-ruby 0.0.1

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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.gitmodules +3 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +29 -0
  7. data/Rakefile +22 -0
  8. data/ext/extconf.rb +13 -0
  9. data/ext/sophia.c +220 -0
  10. data/lib/sophia-ruby.rb +1 -0
  11. data/lib/sophia/version.rb +3 -0
  12. data/sophia-ruby.gemspec +47 -0
  13. data/test/test_sophia.rb +33 -0
  14. data/vendor/sophia/.gitignore +18 -0
  15. data/vendor/sophia/COPYRIGHT +29 -0
  16. data/vendor/sophia/README +5 -0
  17. data/vendor/sophia/db/a.h +58 -0
  18. data/vendor/sophia/db/cat.c +195 -0
  19. data/vendor/sophia/db/cat.h +32 -0
  20. data/vendor/sophia/db/core.h +129 -0
  21. data/vendor/sophia/db/crc.c +343 -0
  22. data/vendor/sophia/db/crc.h +14 -0
  23. data/vendor/sophia/db/cursor.c +551 -0
  24. data/vendor/sophia/db/cursor.h +47 -0
  25. data/vendor/sophia/db/e.c +49 -0
  26. data/vendor/sophia/db/e.h +49 -0
  27. data/vendor/sophia/db/file.c +355 -0
  28. data/vendor/sophia/db/file.h +106 -0
  29. data/vendor/sophia/db/gc.c +71 -0
  30. data/vendor/sophia/db/gc.h +14 -0
  31. data/vendor/sophia/db/i.c +368 -0
  32. data/vendor/sophia/db/i.h +155 -0
  33. data/vendor/sophia/db/list.h +91 -0
  34. data/vendor/sophia/db/lock.h +77 -0
  35. data/vendor/sophia/db/macro.h +20 -0
  36. data/vendor/sophia/db/makefile +44 -0
  37. data/vendor/sophia/db/merge.c +662 -0
  38. data/vendor/sophia/db/merge.h +14 -0
  39. data/vendor/sophia/db/meta.h +87 -0
  40. data/vendor/sophia/db/recover.c +433 -0
  41. data/vendor/sophia/db/recover.h +14 -0
  42. data/vendor/sophia/db/ref.h +111 -0
  43. data/vendor/sophia/db/rep.c +128 -0
  44. data/vendor/sophia/db/rep.h +120 -0
  45. data/vendor/sophia/db/sophia.h +84 -0
  46. data/vendor/sophia/db/sp.c +626 -0
  47. data/vendor/sophia/db/sp.h +50 -0
  48. data/vendor/sophia/db/task.h +70 -0
  49. data/vendor/sophia/db/track.h +99 -0
  50. data/vendor/sophia/db/util.c +105 -0
  51. data/vendor/sophia/db/util.h +25 -0
  52. data/vendor/sophia/makefile +7 -0
  53. data/vendor/sophia/sophia.gyp +30 -0
  54. data/vendor/sophia/test/common.c +870 -0
  55. data/vendor/sophia/test/crash.c +492 -0
  56. data/vendor/sophia/test/i.c +403 -0
  57. data/vendor/sophia/test/limit.c +65 -0
  58. data/vendor/sophia/test/makefile +30 -0
  59. data/vendor/sophia/test/merge.c +890 -0
  60. data/vendor/sophia/test/recover.c +1550 -0
  61. data/vendor/sophia/test/test.h +66 -0
  62. metadata +134 -0
@@ -0,0 +1,14 @@
1
+ #ifndef SP_MERGE_H_
2
+ #define SP_MERGE_H_
3
+
4
+ /*
5
+ * sophia database
6
+ * sphia.org
7
+ *
8
+ * Copyright (c) Dmitry Simonenko
9
+ * BSD License
10
+ */
11
+
12
+ int sp_merge(sp*);
13
+
14
+ #endif
@@ -0,0 +1,87 @@
1
+ #ifndef SP_META_H_
2
+ #define SP_META_H_
3
+
4
+ /*
5
+ * sophia database
6
+ * sphia.org
7
+ *
8
+ * Copyright (c) Dmitry Simonenko
9
+ * BSD License
10
+ */
11
+
12
+ /* on-disk */
13
+
14
+ typedef struct splogh splogh;
15
+ typedef struct speofh speofh;
16
+ typedef struct sppageh sppageh;
17
+ typedef struct spvh spvh;
18
+
19
+ #define SPEOF 0x00aaeefdL
20
+ #define SPMAGIC 0x00f0e0d0L
21
+
22
+ struct splogh {
23
+ uint32_t magic;
24
+ uint8_t version[2];
25
+ } sppacked;
26
+
27
+ struct speofh {
28
+ uint32_t magic;
29
+ } sppacked;
30
+
31
+ struct sppageh {
32
+ uint32_t crc;
33
+ uint64_t id;
34
+ uint16_t count;
35
+ uint32_t size;
36
+ uint32_t bsize;
37
+ } sppacked;
38
+
39
+ struct spvh {
40
+ uint32_t crc;
41
+ uint32_t size;
42
+ uint32_t voffset;
43
+ uint32_t vsize;
44
+ uint8_t flags;
45
+ char key[];
46
+ } sppacked;
47
+
48
+ /* in-memory */
49
+
50
+ typedef struct spv spv;
51
+ typedef struct sppage sppage;
52
+
53
+ #define SPSET 1
54
+ #define SPDEL 2
55
+
56
+ struct spv {
57
+ uint32_t epoch;
58
+ uint32_t crc; /* key-value crc without header */
59
+ uint16_t size;
60
+ uint8_t flags;
61
+ char key[];
62
+ /* uint32_t vsize */
63
+ /* char v[] */
64
+ } sppacked;
65
+
66
+ struct sppage {
67
+ uint64_t id;
68
+ uint64_t offset;
69
+ void *epoch;
70
+ uint32_t size;
71
+ spv *min;
72
+ spv *max;
73
+ splist link;
74
+ } sppacked;
75
+
76
+ static inline char*
77
+ sp_vv(spv *v) {
78
+ return v->key + v->size + sizeof(uint32_t);
79
+ }
80
+
81
+ static inline uint32_t
82
+ sp_vvsize(spv *v) {
83
+ register char *p = (char*)(v->key + v->size);
84
+ return *(uint32_t*)p;
85
+ }
86
+
87
+ #endif
@@ -0,0 +1,433 @@
1
+
2
+ /*
3
+ * sophia database
4
+ * sphia.org
5
+ *
6
+ * Copyright (c) Dmitry Simonenko
7
+ * BSD License
8
+ */
9
+
10
+ #include <sp.h>
11
+ #include <track.h>
12
+ #include <ctype.h>
13
+ #include <sys/types.h>
14
+ #include <sys/stat.h>
15
+ #include <dirent.h>
16
+ #include <unistd.h>
17
+
18
+ static inline int sp_dircreate(sp *s) {
19
+ int rc = mkdir(s->e->dir, 0700);
20
+ if (spunlikely(rc == -1)) {
21
+ sp_e(s, SPE, "failed to create directory %s (errno: %d, %s)",
22
+ s->e->dir, errno, strerror(errno));
23
+ return -1;
24
+ }
25
+ return 0;
26
+ }
27
+
28
+ static inline ssize_t
29
+ sp_epochof(char *s) {
30
+ size_t v = 0;
31
+ while (*s && *s != '.') {
32
+ if (spunlikely(!isdigit(*s)))
33
+ return -1;
34
+ v = (v * 10) + *s - '0';
35
+ s++;
36
+ }
37
+ return v;
38
+ }
39
+
40
+ static int sp_diropen(sp *s)
41
+ {
42
+ /* read repository and determine states */
43
+ DIR *d = opendir(s->e->dir);
44
+ if (spunlikely(d == NULL)) {
45
+ sp_e(s, SPE, "failed to open directory %s (errno: %d, %s)",
46
+ s->e->dir, errno, strerror(errno));
47
+ return -1;
48
+ }
49
+ struct dirent *de;
50
+ while ((de = readdir(d))) {
51
+ if (*de->d_name == '.')
52
+ continue;
53
+ ssize_t epoch = sp_epochof(de->d_name);
54
+ if (epoch == -1)
55
+ continue;
56
+ spepoch *e = sp_repmatch(&s->rep, epoch);
57
+ if (e == NULL) {
58
+ e = sp_repalloc(&s->rep, epoch);
59
+ if (spunlikely(e == NULL)) {
60
+ closedir(d);
61
+ sp_e(s, SPEOOM, "failed to allocate repository");
62
+ return -1;
63
+ }
64
+ sp_repattach(&s->rep, e);
65
+ }
66
+ char *ext = strstr(de->d_name, ".db");
67
+ if (ext) {
68
+ ext = strstr(de->d_name, ".incomplete");
69
+ e->recover |= (ext? SPRDBI: SPRDB);
70
+ continue;
71
+ }
72
+ ext = strstr(de->d_name, ".log");
73
+ if (ext) {
74
+ ext = strstr(de->d_name, ".incomplete");
75
+ e->recover |= (ext? SPRLOGI: SPRLOG);
76
+ }
77
+ continue;
78
+ }
79
+ closedir(d);
80
+ if (s->rep.n == 0)
81
+ return 0;
82
+ /* set current and sort by epoch */
83
+ int rc = sp_repprepare(&s->rep);
84
+ if (spunlikely(rc == -1))
85
+ return sp_e(s, SPEOOM, "failed to allocate repository");
86
+ return 0;
87
+ }
88
+
89
+ static int sp_recoverdb(sp *s, spepoch *x, sptrack *t)
90
+ {
91
+ int rc = sp_mapepoch(&x->db, s->e->dir, x->epoch, "db");
92
+ if (spunlikely(rc == -1))
93
+ return sp_e(s, SPEIO, "failed to open db file", x->epoch);
94
+
95
+ sppageh *h = (sppageh*)(x->db.map);
96
+
97
+ for(;;)
98
+ {
99
+ if (spunlikely((uint64_t)((char*)h - x->db.map) >= x->db.size))
100
+ break;
101
+
102
+ /* validate header */
103
+ uint32_t crc = sp_crc32c(0, &h->id, sizeof(sppageh) - sizeof(h->crc));
104
+ if (crc != h->crc) {
105
+ sp_mapclose(&x->db);
106
+ return sp_e(s, SPE, "page crc failed %"PRIu32".db", x->epoch);
107
+ }
108
+ assert(h->id > 0);
109
+
110
+ x->n++;
111
+ x->nupdate += h->count;
112
+
113
+ /* match page in hash by h.id, skip if matched */
114
+ if (sp_trackhas(t, h->id)) {
115
+ /* skip to a next page */
116
+ h = (sppageh*)((char*)h + sizeof(sppageh) + h->size);
117
+ x->ngc++;
118
+ continue;
119
+ }
120
+
121
+ /* track page id */
122
+ rc = sp_trackset(t, h->id);
123
+ if (spunlikely(rc == -1)) {
124
+ sp_mapclose(&x->db);
125
+ return sp_e(s, SPEOOM, "failed to allocate track item");
126
+ }
127
+
128
+ /* if this is a page delete marker, then skip to
129
+ * a next page */
130
+ if (h->count == 0) {
131
+ h = (sppageh*)((char*)h + sizeof(sppageh) + h->size);
132
+ continue;
133
+ }
134
+
135
+ /* set page min (first block)*/
136
+ spvh *minp = (spvh*)((char*)h + sizeof(sppageh));
137
+ crc = sp_crc32c(0, minp->key, minp->size);
138
+ crc = sp_crc32c(crc, (char*)h + minp->voffset, minp->vsize);
139
+ crc = sp_crc32c(crc, (char*)&minp->size, sizeof(spvh) - sizeof(minp->crc));
140
+ if (crc != minp->crc) {
141
+ sp_mapclose(&x->db);
142
+ return sp_e(s, SPE, "page min key crc failed %"PRIu32".db", x->epoch);
143
+ }
144
+ assert(minp->flags == SPSET);
145
+
146
+ /* set page max (last block) */
147
+ spvh *maxp = (spvh*)((char*)h + sizeof(sppageh) + h->bsize * (h->count - 1));
148
+ crc = sp_crc32c(0, maxp->key, maxp->size);
149
+ crc = sp_crc32c(crc, (char*)h + maxp->voffset, maxp->vsize);
150
+ crc = sp_crc32c(crc, (char*)&maxp->size, sizeof(spvh) - sizeof(maxp->crc));
151
+ if (crc != maxp->crc) {
152
+ sp_mapclose(&x->db);
153
+ return sp_e(s, SPE, "page max key crc failed %"PRIu32".db", x->epoch);
154
+ }
155
+ assert(maxp->flags == SPSET);
156
+
157
+ spv *min = sp_vnewh(s, minp);
158
+ if (spunlikely(min == NULL)) {
159
+ sp_mapclose(&x->db);
160
+ return sp_e(s, SPEOOM, "failed to allocate key");
161
+ }
162
+ assert(min->flags == SPSET);
163
+ min->epoch = x->epoch;
164
+
165
+ spv *max = sp_vnewh(s, maxp);
166
+ if (spunlikely(max == NULL)) {
167
+ sp_free(&s->a, min);
168
+ sp_mapclose(&x->db);
169
+ return sp_e(s, SPEOOM, "failed to allocate key");
170
+ }
171
+ assert(max->flags == SPSET);
172
+ max->epoch = x->epoch;
173
+
174
+ /* allocate and insert new page */
175
+ sppage *page = sp_pagenew(s, x);
176
+ if (spunlikely(page == NULL)) {
177
+ sp_free(&s->a, min);
178
+ sp_free(&s->a, max);
179
+ sp_mapclose(&x->db);
180
+ return sp_e(s, SPEOOM, "failed to allocate page");
181
+ }
182
+ page->id = h->id;
183
+ page->offset = (char*)h - x->db.map;
184
+ page->size = sizeof(sppageh) + h->size;
185
+ page->min = min;
186
+ page->max = max;
187
+
188
+ sppage *o = NULL;
189
+ rc = sp_catset(&s->s, page, &o);
190
+ if (spunlikely(rc == -1)) {
191
+ sp_pagefree(s, page);
192
+ sp_mapclose(&x->db);
193
+ return sp_e(s, SPEOOM, "failed to allocate page index page");
194
+ }
195
+ assert(o == NULL);
196
+
197
+ /* attach page to the source */
198
+ sp_pageattach(page);
199
+
200
+ /* skip to a next page */
201
+ h = (sppageh*)((char*)h + sizeof(sppageh) + h->size);
202
+ }
203
+
204
+ return 0;
205
+ }
206
+
207
+ static int sp_recoverlog(sp *s, spepoch *x, int incomplete)
208
+ {
209
+ /* open and map log file */
210
+ char *ext = (incomplete ? "log.incomplete" : "log");
211
+ int rc;
212
+ rc = sp_mapepoch(&x->log, s->e->dir, x->epoch, ext);
213
+ if (spunlikely(rc == -1))
214
+ return sp_e(s, SPEIO, "failed to open log file", x->epoch);
215
+
216
+ /* validate log header */
217
+ if (spunlikely(! sp_mapinbound(&x->log, sizeof(splogh)) ))
218
+ return sp_e(s, SPE, "bad log file %"PRIu32".log", x->epoch);
219
+
220
+ splogh *h = (splogh*)(x->log.map);
221
+ if (spunlikely(h->magic != SPMAGIC))
222
+ return sp_e(s, SPE, "log bad magic %"PRIu32".log", x->epoch);
223
+ if (spunlikely(h->version[0] != SP_VERSION_MAJOR &&
224
+ h->version[1] != SP_VERSION_MINOR))
225
+ return sp_e(s, SPE, "unknown file version of %"PRIu32".log", x->epoch);
226
+
227
+ uint64_t offset = sizeof(splogh);
228
+ uint32_t unique = 0;
229
+ int eof = 0;
230
+ while (offset < x->log.size)
231
+ {
232
+ /* check for a eof */
233
+ if (spunlikely(offset == (x->log.size - sizeof(speofh)))) {
234
+ speofh *eofh = (speofh*)(x->log.map + offset);
235
+ if (eofh->magic != SPEOF) {
236
+ sp_mapclose(&x->log);
237
+ return sp_e(s, SPE, "bad log eof magic %"PRIu32".log", x->epoch);
238
+ }
239
+ eof++;
240
+ offset += sizeof(speofh);
241
+ break;
242
+ }
243
+
244
+ /* validate a record */
245
+ if (spunlikely(! sp_mapinbound(&x->log, offset + sizeof(spvh)) )) {
246
+ sp_mapclose(&x->log);
247
+ return sp_e(s, SPE, "log file corrupted %"PRIu32".log", x->epoch);
248
+ }
249
+ spvh *vh = (spvh*)(x->log.map + offset);
250
+
251
+ uint32_t crc0, crc1;
252
+ crc0 = sp_crc32c(0, vh->key, vh->size);
253
+ crc0 = sp_crc32c(crc0, vh->key + vh->size, vh->vsize);
254
+ crc1 = sp_crc32c(crc0, &vh->size, sizeof(spvh) - sizeof(vh->crc));
255
+ if (spunlikely(crc1 != vh->crc)) {
256
+ sp_mapclose(&x->log);
257
+ return sp_e(s, SPE, "log record crc failed %"PRIu32".log", x->epoch);
258
+ }
259
+
260
+ int c0 = vh->flags != SPSET && vh->flags != SPDEL;
261
+ int c1 = vh->voffset != 0;
262
+ int c2 = !sp_mapinbound(&x->log, offset + sizeof(spvh) + vh->size +
263
+ vh->vsize);
264
+
265
+ if (spunlikely((c0 + c1 + c2) > 0)) {
266
+ sp_mapclose(&x->log);
267
+ return sp_e(s, SPE, "bad log record %"PRIu32".log", x->epoch);
268
+ }
269
+
270
+ /* add a key to the key index.
271
+ *
272
+ * key index have only actual key, replace should be done
273
+ * within the same epoch by a newest records only and skipped
274
+ * in a older epochs.
275
+ */
276
+ spv *v = sp_vnewv(s, vh->key, vh->size, vh->key + vh->size, vh->vsize);
277
+ if (spunlikely(v == NULL)) {
278
+ sp_mapclose(&x->log);
279
+ return sp_e(s, SPEOOM, "failed to allocate key");
280
+ }
281
+ v->flags = vh->flags;
282
+ v->epoch = x->epoch;
283
+ v->crc = crc0;
284
+
285
+ spii pos;
286
+ switch (sp_isetorget(s->i, v, &pos)) {
287
+ case 1: {
288
+ spv *old = sp_ival(&pos);
289
+ if (old->epoch == x->epoch) {
290
+ sp_ivalset(&pos, v);
291
+ sp_free(&s->a, old);
292
+ } else {
293
+ sp_free(&s->a, v);
294
+ }
295
+ break;
296
+ }
297
+ case 0:
298
+ unique++;
299
+ break;
300
+ case -1:
301
+ sp_mapclose(&x->log);
302
+ return sp_e(s, SPEOOM, "failed to allocate key index page");
303
+ }
304
+
305
+ offset += sizeof(spvh) + vh->size + vh->vsize;
306
+ x->nupdate++;
307
+ }
308
+
309
+ if ((offset > x->log.size) || ((offset < x->log.size) && !eof)) {
310
+ sp_mapclose(&x->log);
311
+ return sp_e(s, SPE, "log file corrupted %"PRIu32".log", x->epoch);
312
+ }
313
+
314
+ /* unmap file only, unlink-close will ocurre in merge or
315
+ * during shutdown */
316
+ rc = sp_mapunmap(&x->log);
317
+ if (spunlikely(rc == -1))
318
+ return sp_e(s, SPEIO, "failed to unmap log file", x->epoch);
319
+
320
+ /*
321
+ * if there is eof marker missing, try to add one
322
+ * (only for incomplete files), otherwise indicate corrupt
323
+ */
324
+ if (incomplete == 0 && !eof)
325
+ return sp_e(s, SPE, "bad log eof marker %"PRIu32".log", x->epoch);
326
+
327
+ if (incomplete) {
328
+ if (! eof) {
329
+ rc = sp_logclose(&x->log);
330
+ if (spunlikely(rc == -1))
331
+ return sp_e(s, SPEIO, "failed to close log file", x->epoch);
332
+ rc = sp_logcontinue(&x->log, s->e->dir, x->epoch);
333
+ if (spunlikely(rc == -1)) {
334
+ sp_logclose(&x->log);
335
+ return sp_e(s, SPEIO, "failed to reopen log file", x->epoch);
336
+ }
337
+ rc = sp_logeof(&x->log);
338
+ if (spunlikely(rc == -1)) {
339
+ sp_logclose(&x->log);
340
+ return sp_e(s, SPEIO, "failed to add eof marker", x->epoch);
341
+ }
342
+ }
343
+ rc = sp_logcompleteforce(&x->log);
344
+ if (spunlikely(rc == -1)) {
345
+ sp_logclose(&x->log);
346
+ return sp_e(s, SPEIO, "failed to complete log file", x->epoch);
347
+ }
348
+ }
349
+ return 0;
350
+ }
351
+
352
+ static int sp_dirrecover(sp *s)
353
+ {
354
+ sptrack t;
355
+ int rc = sp_trackinit(&t, &s->a, 1024);
356
+ if (spunlikely(rc == -1))
357
+ return sp_e(s, SPEOOM, "failed to allocate track");
358
+
359
+ /* recover from yongest epochs (biggest numbers) */
360
+ splist *i;
361
+ sp_listforeach_reverse(&s->rep.l, i){
362
+ spepoch *e = spcast(i, spepoch, link);
363
+ switch (e->recover) {
364
+ case SPRDB|SPRLOG:
365
+ case SPRDB:
366
+ sp_repset(&s->rep, e, SPDB);
367
+ rc = sp_recoverdb(s, e, &t);
368
+ if (spunlikely(rc == -1))
369
+ goto err;
370
+ if (e->recover == (SPRDB|SPRLOG)) {
371
+ rc = sp_epochrm(s->e->dir, e->epoch, "log");
372
+ if (spunlikely(rc == -1))
373
+ goto err;
374
+ }
375
+ break;
376
+ case SPRLOG|SPRDBI:
377
+ rc = sp_epochrm(s->e->dir, e->epoch, "db.incomplete");
378
+ if (spunlikely(rc == -1))
379
+ goto err;
380
+ case SPRLOG:
381
+ sp_repset(&s->rep, e, SPXFER);
382
+ rc = sp_recoverlog(s, e, 0);
383
+ if (spunlikely(rc == -1))
384
+ goto err;
385
+ break;
386
+ case SPRLOGI:
387
+ sp_repset(&s->rep, e, SPXFER);
388
+ rc = sp_recoverlog(s, e, 1);
389
+ if (spunlikely(rc == -1))
390
+ goto err;
391
+ break;
392
+ default:
393
+ /* corrupted states: */
394
+ /* db.incomplete */
395
+ /* log.incomplete + db.incomplete */
396
+ /* log.incomplete + db */
397
+ sp_trackfree(&t);
398
+ return sp_e(s, SPE, "repository is corrupted");
399
+ }
400
+ }
401
+
402
+ /*
403
+ * set maximum loaded psn as current one.
404
+ */
405
+ s->psn = t.max;
406
+
407
+ sp_trackfree(&t);
408
+ return 0;
409
+ err:
410
+ sp_trackfree(&t);
411
+ return -1;
412
+ }
413
+
414
+ int sp_recover(sp *s)
415
+ {
416
+ int exists = sp_fileexists(s->e->dir);
417
+ int rc;
418
+ if (!exists) {
419
+ if (! (s->e->flags & SPO_CREAT))
420
+ return sp_e(s, SPE, "directory doesn't exists and no SPO_CREAT specified");
421
+ if (s->e->flags & SPO_RDONLY)
422
+ return sp_e(s, SPE, "directory doesn't exists");
423
+ rc = sp_dircreate(s);
424
+ } else {
425
+ rc = sp_diropen(s);
426
+ if (spunlikely(rc == -1))
427
+ return -1;
428
+ if (s->rep.n == 0)
429
+ return 0;
430
+ rc = sp_dirrecover(s);
431
+ }
432
+ return rc;
433
+ }