sophia-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ }