packages feed

bindings-sophia 0.2.0 → 0.2.0.2

raw patch · 28 files changed

+4424/−544 lines, 28 files

Files

bindings-sophia.cabal view
@@ -1,5 +1,5 @@ name:         bindings-sophia-version:      0.2.0+version:      0.2.0.2 category:     Database  author:       Eyal Lotem <eyal.lotem+sophia@gmail.com>@@ -66,6 +66,8 @@     cbits/wrappers.c    cc-options: -g -Wall -O2++  extra-libraries: pthread  -------------------------------------------------------------------------------- 
sophia/README view
@@ -1,5 +1,5 @@ -sophia - is an modern embeddable key-value database-designed for a highload.+sophia - is a modern embeddable key-value database+designed for a high load.  http://sphia.org
sophia/db/cat.c view
@@ -125,7 +125,7 @@ 	return 1; } -inline sppage*+sppage* sp_catfind(spcat *c, char *rkey, int size, uint32_t *index) { 	register int min = 0;
sophia/db/core.h view
@@ -24,6 +24,12 @@  typedef enum spmagic spmagic; +enum sptxn {+	SPTSS, SPTMS+};++typedef enum sptxn sptxn;+ struct spenv { 	spmagic m; 	spe e;@@ -45,47 +51,53 @@  struct sp { 	spmagic m;-	spenv *e;+	spenv *env; 	spa a; 	sprep rep;+	sptxn txn;          /* transaction mode: single or multi-stmt */ 	spi *i, i0, i1;+	spi itxn; 	int iskip;          /* skip second index during read */ 	uint64_t psn;       /* page sequence number */ 	spcat s;+	spbatch lb;         /* log batch related */ 	volatile int stop; 	sptask merger; 	sprefset refs;      /* pre allocated key buffer (page merge) */+	spfile lockdb;      /* per-process database lock */ 	int lockc;          /* incremental cursor lock */ 	spspinlock lockr;   /* repository lock */ 	spspinlock locks;   /* space lock */ 	spspinlock locki;   /* index lock */+	spe e, em;          /* separate error contexts: fe and merger */ }; -int sp_rotate(sp*);+int sp_rotate(sp*, spe*);  static inline int sp_active(sp *s) { 	return !s->stop; } -static inline int-sp_e(sp *s, int type, ...) {-	va_list args;-	va_start(args, type);-	sp_ve(&s->e->e, type, args);-	va_end(args);-	return -1;+static inline spi*+sp_ipair(sp *s) {+	return (s->i == &s->i0 ? &s->i1 : &s->i0); } -static inline int-sp_ee(spenv *e, int type, ...) {-	va_list args;-	va_start(args, type);-	sp_ve(&e->e, type, args);-	va_end(args);-	return -1;+static inline spi*+sp_iswap(sp *s) {+	spi *old = s->i;+	s->i = sp_ipair(s);+	return old; }  static inline void+sp_iskipset(sp *s, int v) {+	sp_lock(&s->locki);+	s->iskip = v;+	sp_unlock(&s->locki);+}++static inline void sp_glock(sp *s) { 	if (s->lockc > 0) 		return;@@ -107,23 +119,36 @@ 	sp_unlock(&s->lockr); } -static inline void-sp_iskipset(sp *s, int v) {-	sp_lock(&s->locki);-	s->iskip = v;-	sp_unlock(&s->locki);+static inline int+sp_evalidate(sp *s) {+	return sp_echeck(&s->e) + sp_echeck(&s->em); } -static inline spi*-sp_ipair(sp *s) {-	return (s->i == &s->i0 ? &s->i1 : &s->i0);+static inline int+sp_e(sp *s, int type, ...) {+	va_list args;+	va_start(args, type);+	sp_vef(&s->e, type, args);+	va_end(args);+	return -1; } -static inline spi*-sp_iswap(sp *s) {-	spi *old = s->i;-	s->i = sp_ipair(s);-	return old;+static inline int+sp_em(sp *s, int type, ...) {+	va_list args;+	va_start(args, type);+	sp_vef(&s->em, type, args);+	va_end(args);+	return -1;+}++static inline int+sp_ee(spenv *e, int type, ...) {+	va_list args;+	va_start(args, type);+	sp_vef(&e->e, type, args);+	va_end(args);+	return -1; }  #endif
sophia/db/cursor.c view
@@ -22,7 +22,6 @@ 	assert(c->ph->id > 0); 	c->pvi = 0 ; 	c->pv  = (spvh*)((char*)c->ph + sizeof(sppageh));-	c->dup = 0; }  static inline void sp_pageclose(spc *c) {@@ -49,7 +48,7 @@ 		mid = min + ((max - min) >> 1); 		spvh *a = (spvh*)((char*)c->ph + sizeof(sppageh) + c->ph->bsize * mid); 		register int rc =-			c->s->e->cmp(a->key, a->size, rkey, size, c->s->e->cmparg);+			c->s->env->cmp(a->key, a->size, rkey, size, c->s->env->cmparg); 		switch (rc) { 		case -1: min = mid + 1; 			continue;@@ -80,14 +79,10 @@ 	} else { 		match = min; 	}-	/* not found, set minimal */-	if (match >= c->ph->count) {-		c->pv  = (spvh*)((char*)c->ph + sizeof(sppageh));-		c->pvi = 0;-	} else {-		c->pv  = (spvh*)((char*)c->ph + sizeof(sppageh) + c->ph->bsize * match);-		c->pvi = match;-	}+	if (match >= c->ph->count)+		match = c->ph->count; /* skip */+	c->pv  = (spvh*)((char*)c->ph + sizeof(sppageh) + c->ph->bsize * match);+	c->pvi = match; 	return eq; } @@ -103,7 +98,9 @@ 	} else { 		match = max; 	}-	/* not found, set max */+	if (match < 0)+		match = c->ph->count; /* skip */+	else 	if (match >= c->ph->count) 		match = c->ph->count - 1; 	c->pv  = (spvh*)((char*)c->ph + sizeof(sppageh) + c->ph->bsize * match);@@ -144,18 +141,22 @@ 	sp_iopen(&c->i0, c->s->i); 	if (! c->s->iskip) 		sp_iopen(&c->i1, sp_ipair(c->s));+	if (c->s->txn == SPTMS)+		sp_iopen(&c->itxn, &c->s->itxn); 	if (spunlikely(c->s->s.count == 0)) 		return; 	sp_pageopen(c, 0); }  static inline int-sp_firstkey(spc *c, char *rkey, int size)+sp_lte(spc *c, char *rkey, int size) { 	/* do lte search on all indexes for the key */ 	int eq = sp_ilte(c->s->i, &c->i0, rkey, size); 	if (! c->s->iskip) 		eq = eq + sp_ilte(sp_ipair(c->s), &c->i1, rkey, size);+	if (c->s->txn == SPTMS)+		eq = eq + sp_ilte(&c->s->itxn, &c->itxn, rkey, size); 	/* match page for the key */ 	if (spunlikely(c->s->s.count == 0)) 		return eq;@@ -166,6 +167,8 @@ 	assert(p != NULL); 	sp_pageopen(c, idx); 	eq = eq + sp_pagesetlte(c, rkey, size);+	if (spunlikely(c->pvi == c->ph->count))+		sp_pageclose(c); 	return eq; } @@ -177,18 +180,25 @@ 		sp_iopen(&c->i1, sp_ipair(c->s)); 		sp_ilast(&c->i1); 	}+	if (c->s->txn == SPTMS) {+		sp_iopen(&c->itxn, &c->s->itxn);+		sp_ilast(&c->itxn);+	} 	if (spunlikely(c->s->s.count == 0)) 		return; 	sp_pageopen(c, c->s->s.count-1); 	sp_pagesetlast(c); } -static inline int sp_lastkey(spc *c, char *rkey, int size)+static inline int+sp_gte(spc *c, char *rkey, int size) { 	/* do gte search the index for the key */ 	int eq = sp_igte(c->s->i, &c->i0, rkey, size); 	if (! c->s->iskip) 		eq = eq + sp_igte(sp_ipair(c->s), &c->i1, rkey, size);+	if (c->s->txn == SPTMS)+		eq = eq + sp_igte(&c->s->itxn, &c->itxn, rkey, size); 	/* match page for the key */ 	if (spunlikely(c->s->s.count == 0)) 		return eq;@@ -199,6 +209,8 @@ 	assert(p != NULL); 	sp_pageopen(c, idx); 	eq = eq + sp_pagesetgte(c, rkey, size);+	if (spunlikely(c->pvi == c->ph->count))+		sp_pageclose(c); 	return eq; } @@ -210,14 +222,14 @@ 	c->m = SPMCUR; 	c->o = o; 	c->s = s;-	c->dup = 0;+	c->mask = SPCNONE; 	c->p = NULL; 	c->ph = NULL; 	c->pi = 0; 	c->pvi = 0; 	c->pv = NULL;-	c->vsrc = SPCNONE; +	sp_iinv(&c->s->itxn, &c->itxn); 	sp_iinv(c->s->i, &c->i0); 	sp_iinv(sp_ipair(c->s), &c->i1); @@ -227,7 +239,7 @@ 	case SPGT: 		if (rkey) { 			/* init first key and skip on gt */-			if (sp_firstkey(c, rkey, size) && o == SPGT)+			if (sp_lte(c, rkey, size) && o == SPGT) 				sp_iterate(c); 			return; 		}@@ -237,7 +249,7 @@ 	case SPLT: 		if (rkey) { 			/* init last key and skip on lt */-			if (sp_lastkey(c, rkey, size) && o == SPLT)+			if (sp_gte(c, rkey, size) && o == SPLT) 				sp_iterate(c); 			return; 		}@@ -253,187 +265,184 @@ }  static inline void sp_reset(spc *c) {-	c->vsrc = SPCNONE;+	c->mask   = SPCNONE; 	c->r.type = SPREFNONE;-	c->dup = 0; }  static inline int sp_next(spc *c) {-	switch (c->vsrc) {-	case SPCI0:+	/* apply last iteration skip mask */+	if (c->mask & SPCITXN)+		sp_inext(&c->itxn);+	if (c->mask & SPCI0) 		sp_inext(&c->i0);-		if (c->dup & SPCPDUP)-			sp_pagenext(c);-		if (c->dup & SPCVDUP)-			sp_inext(&c->i1);-		break;-	case SPCI1:+	if (c->mask & SPCI1) 		sp_inext(&c->i1);-		if (c->dup & SPCPDUP)-			sp_pagenext(c);-		if (c->dup & SPCVDUP)-			sp_inext(&c->i0);-		break;-	case SPCP:+	if (c->mask & SPCP) { 		assert(c->p != NULL); 		assert(c->r.v.vh == c->pv); 		sp_pagenext(c);-		break;-	case SPCNONE:-		break; 	} 	sp_reset(c);-	++	/* do priority multi-index LTE search */ 	int rc;-	spcsrc src = SPCNONE;-	spv *v0 = sp_ival(&c->i0);-	spv *v1 = sp_ival(&c->i1);-	spv *v  = NULL;+	int dup = SPCNONE;+	int src = SPCNONE;+	spv *v = NULL;+	struct {+		spv *v;+		int mask;+	} vv[3] = {+		{ sp_ival(&c->itxn), SPCITXN },+		{ sp_ival(&c->i0),   SPCI0   },+		{ sp_ival(&c->i1),   SPCI1   }+	}; -	/* END */-	if (v0 == NULL && v1 == NULL && c->pv == NULL) {-		c->vsrc = SPCNONE;+	/* end of iteration */+	if (spunlikely(vv[0].v == NULL &&+	               vv[1].v == NULL &&+	               vv[2].v == NULL && c->pv == NULL)) { 		return 0; 	} -	if (v0 && v1) {-		rc = c->s->e->cmp(v0->key, v0->size, v1->key, v1->size,-						  c->s->e->cmparg);+	int i;+	for (i = 0; i < 3; i++) {+		if (vv[i].v == NULL)+			continue;+		if (v == NULL) {+			src = vv[i].mask;+			v = vv[i].v;+			continue;+		}+		rc = c->s->env->cmp(vv[i].v->key, vv[i].v->size, v->key, v->size,+		                    c->s->env->cmparg); 		switch (rc) {-		case  0: c->dup |= SPCVDUP;-		case -1: v = v0, src = SPCI0;+		case  0: dup |= vv[i].mask; 			break;-		case  1: v = v1, src = SPCI1;+		case -1:+			src = vv[i].mask;+			v = vv[i].v; 			break; 		}-	} else if (v0) {-		v = v0, src = SPCI0;-	} else if (v1) {-		v = v1, src = SPCI1; 	}- 	/* no page key */-	if (c->pv == NULL) {-		c->vsrc = src;-		c->r.type = SPREFM;-		c->r.v.v = v; -		return 1;-	}+	if (c->pv == NULL)+		goto index; 	/* no index key */-	if (v == NULL) {-		c->vsrc = SPCP;-		c->r.type = SPREFD;-		c->r.v.vh = c->pv;-		return 1;-	}+	if (v == NULL)+		goto page; -	/* compare in-memory key and page one's */-	rc = c->s->e->cmp(v->key, v->size, c->pv->key, c->pv->size,-	                  c->s->e->cmparg);+	/* compare in-memory and on-disk key */+	rc = c->s->env->cmp(v->key, v->size, c->pv->key, c->pv->size,+	                    c->s->env->cmparg); 	switch (rc) {-	case 0: c->dup |= SPCPDUP;-	case -1:-		c->vsrc = src;-		c->r.type = SPREFM;-		c->r.v.v = v; -		break;-	case 1:-		c->vsrc = SPCP;-		c->r.type = SPREFD;-		c->r.v.vh = c->pv;-		break;+	case  0: dup |= SPCP;+	case -1: goto index;+	case  1: goto page; 	}++	/* unreach */+	assert(0); 	return 1;+index:+	c->mask   = dup|src;+	c->r.type = SPREFM;+	c->r.v.v  = v;+	return 1;+page:+	c->mask   = dup|SPCP;+	c->r.type = SPREFD;+	c->r.v.vh = c->pv;+	return 1; }  static inline int sp_prev(spc *c) {-	switch (c->vsrc) {-	case SPCI0:+	/* apply last iteration skip mask */+	if (c->mask & SPCITXN)+		sp_iprev(&c->itxn);+	if (c->mask & SPCI0) 		sp_iprev(&c->i0);-		if (c->dup & SPCPDUP)-			sp_pageprev(c);-		if (c->dup & SPCVDUP)-			sp_iprev(&c->i1);-		break;-	case SPCI1:+	if (c->mask & SPCI1) 		sp_iprev(&c->i1);-		if (c->dup & SPCPDUP)-			sp_pageprev(c);-		if (c->dup & SPCVDUP)-			sp_iprev(&c->i0);-		break;-	case SPCP:+	if (c->mask & SPCP) { 		assert(c->p != NULL); 		assert(c->r.v.vh == c->pv); 		sp_pageprev(c);-		break;-	case SPCNONE:-		break; 	} 	sp_reset(c);-	++	/* do priority multi-index LTE search */ 	int rc;-	spcsrc src = SPCNONE;-	spv *v0 = sp_ival(&c->i0);-	spv *v1 = sp_ival(&c->i1);-	spv *v  = NULL;+	int dup = SPCNONE;+	int src = SPCNONE;+	spv *v = NULL;+	struct {+		spv *v;+		int mask;+	} vv[3] = {+		{ sp_ival(&c->itxn), SPCITXN },+		{ sp_ival(&c->i0),   SPCI0   },+		{ sp_ival(&c->i1),   SPCI1   }+	}; -	/* END */-	if (v0 == NULL && v1 == NULL && c->pv == NULL) {-		c->vsrc = SPCNONE;+	/* end of iteration */+	if (spunlikely(vv[0].v == NULL &&+	               vv[1].v == NULL &&+	               vv[2].v == NULL && c->pv == NULL)) { 		return 0; 	} -	if (v0 && v1) {-		rc = c->s->e->cmp(v0->key, v0->size, v1->key, v1->size,-						  c->s->e->cmparg);+	int i;+	for (i = 0; i < 3; i++) {+		if (vv[i].v == NULL)+			continue;+		if (v == NULL) {+			src = vv[i].mask;+			v = vv[i].v;+			continue;+		}+		rc = c->s->env->cmp(vv[i].v->key, vv[i].v->size, v->key, v->size,+		                    c->s->env->cmparg); 		switch (rc) {-		case  0: c->dup |= SPCVDUP;-		case -1: v = v1, src = SPCI1;+		case 0: dup |= vv[i].mask; 			break;-		case  1: v = v0, src = SPCI0;+		case 1:+			src = vv[i].mask;+			v = vv[i].v; 			break; 		}-	} else if (v0) {-		v = v0, src = SPCI0;-	} else if (v1) {-		v = v1, src = SPCI1; 	}- 	/* no page key */-	if (c->pv == NULL) {-		c->vsrc = src;-		c->r.type = SPREFM;-		c->r.v.v = v; -		return 1;-	}+	if (c->pv == NULL)+		goto index; 	/* no index key */-	if (v == NULL) {-		c->vsrc = SPCP;-		c->r.type = SPREFD;-		c->r.v.vh = c->pv;-		return 1;-	}+	if (v == NULL)+		goto page; -	/* compare in-memory key and page one's */-	rc = c->s->e->cmp(v->key, v->size, c->pv->key, c->pv->size,-	                  c->s->e->cmparg);+	/* compare in-memory and on-disk key */+	rc = c->s->env->cmp(v->key, v->size, c->pv->key, c->pv->size,+	                    c->s->env->cmparg); 	switch (rc) {-	case  0: c->dup |= SPCPDUP;-	case  1:-		c->vsrc = src;-		c->r.type = SPREFM;-		c->r.v.v = v; -		break;-	case -1:-		c->vsrc = SPCP;-		c->r.type = SPREFD;-		c->r.v.vh = c->pv;-		break;+	case  0: dup |= SPCP;+	case  1: goto index;+	case -1: goto page; 	}++	/* unreach */+	assert(0); 	return 1;+index:+	c->mask   = dup|src;+	c->r.type = SPREFM;+	c->r.v.v  = v;+	return 1;+page:+	c->mask   = dup|SPCP;+	c->r.type = SPREFD;+	c->r.v.vh = c->pv;+	return 1; }  int sp_iterate(spc *c) {@@ -475,19 +484,26 @@  int sp_match(sp *s, void *k, size_t ksize, void **v, size_t *vsize) {-	/* I. match both in-memory index'es for the key */+	/*+	 * if multi-stmt transaction is active, try to search the key+	 * in the transaction findex first */+	int rc;+	if (s->txn == SPTMS) {+		rc = sp_matchi(s, &s->itxn, k, ksize, v, vsize);+		if (rc == -1 || rc == 1)+			return rc;+	}+	/* match both in-memory index'es for the key */ 	sp_lock(&s->locki);-	int rc = sp_matchi(s, s->i, k, ksize, v, vsize);+	rc = sp_matchi(s, s->i, k, ksize, v, vsize); 	if (rc == -1 || rc == 1) { 		sp_unlock(&s->locki); 		return rc; 	}-	/*-	 * skip the second index if it is been truncating at the-	 * somement, all updates are on disk pages.-	*/+	/* skip the second index if it is been truncating at the+	 * somement, all updates are on disk pages */ 	if (! s->iskip) {-		int rc = sp_matchi(s, sp_ipair(s), k, ksize, v, vsize);+		rc = sp_matchi(s, sp_ipair(s), k, ksize, v, vsize); 		if (rc == -1 || rc == 1) { 			sp_unlock(&s->locki); 			return rc;@@ -495,7 +511,7 @@ 	} 	sp_unlock(&s->locki); -	/* II. match the page */+	/* match the page */ 	sp_lock(&s->locks); 	uint32_t page = 0; 	sppage *p = sp_catfind(&s->s, k, ksize, &page);@@ -504,7 +520,7 @@ 		return 0; 	} -	/* III. match the key in the page */+	/* match the key in the page */ 	spepoch *e = (spepoch*)p->epoch; 	sp_lock(&e->lock); @@ -518,7 +534,7 @@ 		register spvh *v = 			(spvh*)(e->db.map + p->offset + sizeof(sppageh) + 			        ph->bsize * mid);-		switch (s->e->cmp(v->key, v->size, k, ksize, s->e->cmparg)) {+		switch (s->env->cmp(v->key, v->size, k, ksize, s->env->cmparg)) { 		case -1: min = mid + 1; 			continue; 		case  1: max = mid - 1;
sophia/db/cursor.h view
@@ -11,30 +11,24 @@  typedef struct spc spc; -enum spcsrc {-	SPCNONE,-	SPCI0,-	SPCI1,-	SPCP-};--#define SPCVDUP 1-#define SPCPDUP 2--typedef enum spcsrc spcsrc;+#define SPCNONE  0+#define SPCITXN  1+#define SPCI0    2+#define SPCI1    4+#define SPCP    16  struct spc { 	spmagic m; 	sporder o; 	sp *s; 	spii i0, i1;-	int dup;     /* last iteration duplicate flags */+	spii itxn; 	sppageh *ph; 	sppage *p; 	int pi;      /* page space index */ 	spvh *pv; 	int pvi;     /* version page index */-	spcsrc vsrc; /* last iteration source */+	int mask;    /* last iteration advance mask */ 	spref r;     /* last iteration result */ }; 
sophia/db/e.c view
@@ -9,39 +9,48 @@  #include <sp.h> -void sp_ve(spe *e, int type, va_list args)+void sp_vef(spe *e, int type, va_list args) { 	sp_lock(&e->lock);-	if (e->type != SPENONE) {+	if (e->type & SPEF) { 		sp_unlock(&e->lock); 		return; 	}+	const char *fmt;+	int len;+	uint32_t epoch;+ 	e->type = type;-	switch (type) {+	switch (type & ~SPEF) { 	case SPE: {-		char *fmt = va_arg(args, char*);-		int len = snprintf(e->e, sizeof(e->e), "error: ");+		fmt = va_arg(args, const char*);+		len = snprintf(e->e, sizeof(e->e), "error: "); 		vsnprintf(e->e + len, sizeof(e->e) - len, fmt, args); 		break; 	} 	case SPEOOM: {-		char *msg = va_arg(args, char*);-		snprintf(e->e, sizeof(e->e), "out-of-memory error: %s", msg);+		fmt = va_arg(args, const char*);+		len = snprintf(e->e, sizeof(e->e), "out-of-memory error: ");+		vsnprintf(e->e + len, sizeof(e->e) - len, fmt, args); 		break; 	} 	case SPESYS: { 		e->errno_ = errno;-		char *msg = va_arg(args, char*);-		snprintf(e->e, sizeof(e->e), "system error: %s (errno: %d, %s)",-		         msg, e->errno_, strerror(e->errno_));+		fmt  = va_arg(args, const char*);+		len  = snprintf(e->e, sizeof(e->e), "system error: ");+		len += vsnprintf(e->e + len, sizeof(e->e) - len, fmt, args);+		snprintf(e->e + len, sizeof(e->e) - len, " (errno: %d, %s)",+		         e->errno_, strerror(e->errno_)); 		break; 	} 	case SPEIO: { 		e->errno_ = errno;-		char *msg = va_arg(args, char*);-		uint32_t epoch = va_arg(args, uint32_t);-		snprintf(e->e, sizeof(e->e), "io error: [epoch %"PRIu32"] %s (errno: %d, %s)",-		         epoch, msg, e->errno_, strerror(e->errno_));+		epoch = va_arg(args, uint32_t);+		fmt  = va_arg(args, const char*);+		len  = snprintf(e->e, sizeof(e->e), "io error: [epoch %"PRIu32"] ", epoch);+		len += vsnprintf(e->e + len, sizeof(e->e) - len, fmt, args);+		snprintf(e->e + len, sizeof(e->e) - len, " (errno: %d, %s)",+		         e->errno_, strerror(e->errno_)); 		break; 	} 	}
sophia/db/e.h view
@@ -11,13 +11,12 @@  typedef struct spe spe; -enum {-	SPENONE,-	SPE,     /* general error (with format) */-	SPEOOM,  /* out of memory */-	SPESYS,  /* system + errno */-	SPEIO    /* system-io + errno */-};+#define SPENONE 0+#define SPE     1+#define SPEOOM  2+#define SPESYS  4+#define SPEIO   8+#define SPEF    16  struct spe { 	spspinlock lock;@@ -26,24 +25,77 @@ 	char e[256]; }; -static inline void sp_einit(spe *e) {-	e->lock = 0;+static inline void+sp_einit(spe *e) { 	e->type = SPENONE; 	e->e[0] = 0; 	sp_lockinit(&e->lock); } -static inline void sp_efree(spe *e) {+static inline void+sp_efree(spe *e) { 	sp_lockfree(&e->lock); } -static inline int sp_eis(spe *e) {+static inline int+sp_eis(spe *e) { 	sp_lock(&e->lock); 	register int is = e->type != SPENONE; 	sp_unlock(&e->lock); 	return is; } -void sp_ve(spe *e, int type, va_list args);+static inline void+sp_esetfatal(spe *e) {+	sp_lock(&e->lock);+	assert(e->type != SPENONE);+	e->type |= SPEF;+	sp_unlock(&e->lock);+}++static inline int+sp_eisfatal(spe *e) {+	sp_lock(&e->lock);+	register int is = e->type != SPENONE;+	sp_unlock(&e->lock);+	return is;+}++static inline int+sp_echeck(spe *e) {+	sp_lock(&e->lock);+	register int panic = 0;+	if (spunlikely(e->type != SPENONE)) {+		panic = (e->type & SPEF) > 0;+		if (splikely(! panic)) {+			e->type = SPENONE;+			e->e[0] = 0;+		}+	}+	sp_unlock(&e->lock);+	return panic;+}++static inline void+sp_edup(spe *dst, spe *src) {+	sp_lock(&dst->lock);+	sp_lock(&src->lock);+	dst->type = src->type;+	dst->errno_ = src->errno_;+	memcpy(dst->e, src->e, sizeof(dst->e));+	sp_unlock(&src->lock);+	sp_unlock(&dst->lock);+}++void sp_vef(spe *e, int type, va_list args);++static inline int+sp_ef(spe *e, int type, ...) {+	va_list args;+	va_start(args, type);+	sp_vef(e, type, args);+	va_end(args);+	return -1;+}  #endif
sophia/db/file.c view
@@ -158,14 +158,12 @@ static inline int sp_filecomplete(spfile *f) {-	if (f->creat == 0)-		return 0;-	/* remove .incomplete part */ 	f->creat = 0; 	char path[1024]; 	snprintf(path, sizeof(path), "%s", f->file);-	char *ext = strrchr(path, '.');-	assert(ext != NULL);+	char *ext = strstr(path, ".incomplete");+	if (spunlikely(ext == NULL))+		return 0; 	*ext = 0; 	int rc = rename(f->file, path); 	if (spunlikely(rc == -1))@@ -212,14 +210,15 @@  int sp_mapensure(spfile *f, uint64_t size, float grow) {-	if (splikely((f->used + size) < f->size))+	if (splikely((f->used + size) <= f->size)) 		return 0; 	int rc = sp_unmap(f); 	if (spunlikely(rc == -1)) 		return -1;-	long double nsz = f->size * grow;+	uint64_t nsz = f->used + f->size * grow; 	if (spunlikely(nsz < size))-		nsz = size;+		nsz = f->used + size;+	assert(nsz >= (f->used + size)); 	rc = sp_mapresize(f, nsz); 	if (spunlikely(rc == -1)) 		return -1;@@ -298,18 +297,34 @@ 	return sp_filerm(f->file); } -int sp_logflush(spfile *f)+int sp_logwrite(spfile *f, void *buf, size_t size) {-	register struct iovec *v = f->iov;+	size_t n = 0;+	do {+		ssize_t r;+		do {+			r = write(f->fd, (char*)buf + n, size - n);+		} while (r == -1 && errno == EINTR);+		if (r <= 0)+			return -1;+		n += r;+	} while (n != size);+	f->used += size;+	return 0;+}++int sp_logput(spfile *f, spbatch *b)+{+	register struct iovec *v = b->iov; 	register uint64_t size = 0;-	register int n = f->iovc;+	register int n = b->iovc; 	do { 		int r; 		do { 			r = writev(f->fd, v, n); 		} while (r == -1 && errno == EINTR); 		if (r < 0) {-			f->iovc = 0;+			b->iovc = 0; 			return -1; 		} 		size += r;@@ -326,13 +341,12 @@ 		} 	} while (n > 0); 	f->used += size;-	f->iovc = 0;+	b->iovc = 0; 	return 0; }  int sp_logrlb(spfile *f) {-	assert(f->iovc == 0); 	int rc = ftruncate(f->fd, f->svp); 	if (spunlikely(rc == -1)) 		return -1;@@ -345,11 +359,51 @@ { 	sp_filesvp(f); 	speofh eof = { SPEOF };-	sp_logadd(f, (char*)&eof, sizeof(eof));-	int rc = sp_logflush(f);+	int rc = sp_logwrite(f, &eof, sizeof(eof)); 	if (spunlikely(rc == -1)) { 		sp_logrlb(f); 		return -1; 	} 	return 0;+}++int sp_lockfile(spfile *f, char *path)+{+	f->creat = 0;+	f->fd = open(path, O_CREAT|O_WRONLY, 0600);+	if (spunlikely(f->fd == -1))+		return -1;+	f->file = sp_strdup(f->a, path);+	if (spunlikely(f->file == NULL)) {+		close(f->fd);+		f->fd = -1;+		return -1;+	}+	struct flock l;+	memset(&l, 0, sizeof(l));+	l.l_whence = SEEK_SET;+	l.l_start = 0;+	l.l_len = 0;+	l.l_type = F_WRLCK;+	int rc = fcntl(f->fd, F_SETLK, &l);+	if (spunlikely(rc == -1)) {+		sp_fileclose(f);+		return 1;+	}+	return 0;+}++int sp_unlockfile(spfile *f)+{+	if (spunlikely(f->fd == -1))+		return 0;+	struct flock l;+	memset(&l, 0, sizeof(l));+	l.l_whence = SEEK_SET;+	l.l_start = 0;+	l.l_len = 0;+	l.l_type = F_UNLCK;+	fcntl(f->fd, F_SETLK, &l);+	unlink(f->file);+	return sp_fileclose(f); }
sophia/db/file.h view
@@ -9,8 +9,16 @@  * BSD License */ +typedef struct spbatch spbatch; typedef struct spfile spfile; +#define SPIOVMAX 1024++struct spbatch {+	struct iovec iov[SPIOVMAX];+	int iovc;+};+ struct spfile { 	spa *a; 	int creat;@@ -20,13 +28,18 @@ 	char *file; 	int fd; 	char *map;-	struct iovec iov[8];-	int iovc; };  int sp_fileexists(char*); int sp_filerm(char*); +static inline int+sp_epochrm(char *dir, uint32_t epoch, char *ext) {+	char path[1024];+	snprintf(path, sizeof(path), "%s/%"PRIu32".%s", dir, epoch, ext);+	return sp_filerm(path);+}+ static inline void sp_fileinit(spfile *f, spa *a) { 	memset(f, 0, sizeof(*f));@@ -78,29 +91,41 @@ 	return off <= f->size; } +static inline void+sp_batchinit(spbatch *b) {+	b->iovc = 0;+}++static inline int+sp_batchhas(spbatch *b) {+	return b->iovc > 0;+}++static inline int+sp_batchensure(spbatch *b, int count) {+	return (b->iovc + count) < SPIOVMAX;+}++static inline void+sp_batchadd(spbatch *b, void *ptr, size_t size) {+	assert(b->iovc < SPIOVMAX);+	b->iov[b->iovc].iov_base = ptr;+	b->iov[b->iovc].iov_len = size;+	b->iovc++;+}+ int sp_lognew(spfile*, char*, uint32_t); int sp_logcontinue(spfile*, char*, uint32_t); int sp_logclose(spfile*); int sp_logcomplete(spfile*); int sp_logcompleteforce(spfile*); int sp_logunlink(spfile*);-int sp_logflush(spfile*);+int sp_logwrite(spfile*, void*, size_t);+int sp_logput(spfile*, spbatch*); int sp_logrlb(spfile*); int sp_logeof(spfile*); -static inline void-sp_logadd(spfile *f, const void *buf, int size) {-	assert(f->iovc < 8);-	f->iov[f->iovc].iov_base = (void*)buf;-	f->iov[f->iovc].iov_len = size;-	f->iovc++;-}--static inline int-sp_epochrm(char *dir, uint32_t epoch, char *ext) {-	char path[1024];-	snprintf(path, sizeof(path), "%s/%"PRIu32".%s", dir, epoch, ext);-	return sp_filerm(path);-}+int sp_lockfile(spfile*, char*);+int sp_unlockfile(spfile*);  #endif
sophia/db/gc.c view
@@ -19,7 +19,7 @@ 	for (;;) 	{ 		sp_lock(&s->lockr);-		spepoch *g = sp_repgc(&s->rep, s->e->gcfactor);+		spepoch *g = sp_repgc(&s->rep, s->env->gcfactor); 		sp_unlock(&s->lockr); 		if (g == NULL) 			break;@@ -32,10 +32,10 @@ 			/* map origin page and copy to db file */ 			sppageh *h = (sppageh*)(g->db.map + p->offset); 			sp_lock(&x->lock);-			rc = sp_mapensure(&x->db, sizeof(sppageh) + h->size, s->e->dbgrow);+			rc = sp_mapensure(&x->db, sizeof(sppageh) + h->size, s->env->dbgrow); 			if (spunlikely(rc == -1)) { 				sp_unlock(&x->lock);-				return sp_e(s, SPEIO, "failed to remap db file", x->epoch);+				return sp_em(s, SPEIO|SPEF, x->epoch, "failed to remap db file"); 			} 			sp_unlock(&x->lock); 			memcpy(x->db.map + x->db.used, h, sizeof(sppageh) + h->size);@@ -58,10 +58,10 @@ 		*/ 		rc = sp_mapunlink(&g->db); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to unlink db file", g->epoch);+			return sp_em(s, SPEIO|SPEF, g->epoch, "failed to unlink db file"); 		rc = sp_mapclose(&g->db); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to close db file", g->epoch);+			return sp_em(s, SPEIO|SPEF, g->epoch, "failed to close db file"); 		sp_lock(&s->lockr); 		sp_repdetach(&s->rep, g); 		sp_free(&s->a, g);
sophia/db/i.c view
@@ -79,6 +79,18 @@ 	return sp_iinit(i, i->a, i->pagesize, i->cmp, i->cmparg); } +int sp_ireset(spi *i)+{+	uint32_t p = 0;+	while (p < i->icount) {+		sp_free(i->a, i->i[p]);+		p++;+	}+	sp_free(i->a, i->i);+	i->i = NULL;+	return sp_iinit(i, i->a, i->pagesize, i->cmp, i->cmparg);+}+ static inline void* sp_iminof(spi *i, spipage *p, char *rkey, int size, uint32_t *idx) {@@ -293,15 +305,15 @@ 	/* inside index range */ 	if (l <= 0 && r >= 0) 		return 0;-	/* index min < key */+	/* key > index min */ 	if (l == -1) 		return -1;-	/* index max > key */+	/* key < index max */ 	assert(r == 1); 	return 1; } -inline int sp_ilte(spi *i, spii *ii, char *k, int size)+int sp_ilte(spi *i, spii *ii, char *k, int size) { 	if (spunlikely(i->count == 0)) { 		sp_iinv(i, ii);@@ -318,11 +330,16 @@ 			break; 		case  1: 			ii->i = i;-			ii->p = i->icount - 1;-			ii->n = i->i[i->icount - 1]->count - 1;+			ii->p = 0;+			ii->n = 0; 			break; 		case  0:-			assert(0);+			if (spunlikely(a >= i->icount))+				a = i->icount - 1;+			ii->i = i;+			ii->p = a;+			ii->n = 0;+			break; 		} 		return 0; 	}@@ -334,7 +351,7 @@ 	return eq; } -inline int sp_igte(spi *i, spii *ii, char *k, int size)+int sp_igte(spi *i, spii *ii, char *k, int size) { 	if (spunlikely(i->count == 0)) { 		sp_iinv(i, ii);@@ -355,7 +372,12 @@ 			sp_iinv(i, ii); 			break; 		case  0:-			assert(0);+			if (spunlikely(a >= i->icount))+				a = i->icount - 1;+			ii->i = i;+			ii->p = a;+			ii->n = i->i[a]->count - 1;+			break; 		} 		return 0; 	}
sophia/db/i.h view
@@ -37,6 +37,7 @@ int sp_iinit(spi*, spa*, int, spcmpf, void*); void sp_ifree(spi*); int sp_itruncate(spi*);+int sp_ireset(spi*); int sp_isetorget(spi *i, spv*, spii*); int sp_idelraw(spi*, char*, int, spv**); spv *sp_igetraw(spi*, char*, int);
sophia/db/lock.h view
@@ -9,8 +9,16 @@  * BSD License */ +#include <unistd.h>+ typedef uint8_t spspinlock; +#if defined(__x86_64__) || defined(__i386) || defined(_X86_)+# define CPU_PAUSE __asm__ ("pause")+#else+# define CPU_PAUSE do { } while(0)+#endif+ static inline void sp_lockinit(volatile spspinlock *l) { 	*l = 0;@@ -23,7 +31,16 @@  static inline void sp_lock(volatile spspinlock *l) {-	while (__sync_lock_test_and_set(l, 1)) {}+	if (__sync_lock_test_and_set(l, 1) != 0) {+		unsigned int spin_count = 0U;+		for (;;) {+			CPU_PAUSE;+			if (*l == 0U && __sync_lock_test_and_set(l, 1) == 0)+				break;+			if (++spin_count > 100U)+				usleep(0);+		}+	} }  static inline void
sophia/db/macro.h view
@@ -11,7 +11,13 @@  #define sppacked __attribute__((packed)) #define spunused __attribute__((unused))-#define sphot __attribute__((hot))++#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 3+#  define sphot __attribute__((hot))+#else+#  define sphot+#endif+ #define splikely(EXPR) __builtin_expect(!! (EXPR), 1) #define spunlikely(EXPR) __builtin_expect(!! (EXPR), 0) #define spdiv(a, b) ((a) + (b) - 1) / (b)
sophia/db/merge.c view
@@ -53,16 +53,16 @@ 	{ 		/* get the new page properties and a data */ 		spupdate0 u;-		sp_mergeget0(&i, s->e->page, &u);+		sp_mergeget0(&i, s->env->page, &u); 		if (spunlikely(u.count == 0)) 			 break;  		/* ensure enough space for the page in the file */ 		sp_lock(&x->lock);-		rc = sp_mapensure(&x->db, u.psize, s->e->dbgrow);+		rc = sp_mapensure(&x->db, u.psize, s->env->dbgrow); 		if (spunlikely(rc == -1)) { 			sp_unlock(&x->lock);-			sp_e(s, SPEIO, "failed to remap db file", x->epoch);+			sp_em(s, SPEIO|SPEF, x->epoch, "failed to remap db file"); 			goto err; 		} 		sp_unlock(&x->lock);@@ -95,7 +95,7 @@ 			if (spunlikely(min == NULL)) { 				min = sp_vdup(s, v); 				if (spunlikely(min == NULL)) {-					sp_e(s, SPEOOM, "failed to allocate key");+					sp_em(s, SPEOOM|SPEF, "failed to allocate key"); 					goto err; 				} 			}@@ -123,12 +123,12 @@ 		/* create in-memory page */ 		sppage *page = sp_pagenew(s, x); 		if (spunlikely(page == NULL)) {-			sp_e(s, SPEOOM, "failed to allocate page");+			sp_em(s, SPEOOM|SPEF, "failed to allocate page"); 			goto err; 		} 		max = sp_vdup(s, last); 		if (spunlikely(max == NULL)) {-			sp_e(s, SPEOOM, "failed to allocate key");+			sp_em(s, SPEOOM|SPEF, "failed to allocate key"); 			goto err; 		} 		assert(min != NULL);@@ -145,7 +145,7 @@ 		if (spunlikely(rc == -1)) { 			sp_unlock(&s->locks); 			sp_pagefree(s, page);-			sp_e(s, SPEOOM, "failed to allocate page index page");+			sp_em(s, SPEOOM|SPEF, "failed to allocate page index page"); 			goto err; 		} 		sp_unlock(&s->locks);@@ -254,9 +254,9 @@ 	if (m->A < m->a_count && m->B < m->b_count) 	{ 		register int cmp =-			s->e->cmp(m->a->key, m->a->size,-		              m->b->key,-		              m->b->size, s->e->cmparg);+			s->env->cmp(m->a->key, m->a->size,+		                m->b->key,+		                m->b->size, s->env->cmparg); 		switch (cmp) { 		case  0: 			/* use updated key B */@@ -326,13 +326,14 @@ 	int bsize = m->b_bsize; 	uint32_t pagesize = sizeof(sppageh); 	uint32_t count = 0;+ 	/* 	 * merge in-memory keys with the origin page keys, 	 * skip any deletes and calculate result 	 * page size. 	*/ 	sp_refsetreset(&s->refs);-	while (count < s->e->page && sp_mergenext(s, m)) {+	while (count < s->env->page && sp_mergenext(s, m)) { 		if (sp_refisdel(&m->last)) 			continue; 		sp_refsetadd(&s->refs, &m->last);@@ -349,11 +350,10 @@  	/* ensure enough space for the page in the file */ 	sp_lock(&m->x->lock);-	rc = sp_mapensure(&m->x->db, pagesize, s->e->dbgrow);+	rc = sp_mapensure(&m->x->db, pagesize, s->env->dbgrow); 	if (spunlikely(rc == -1)) { 		sp_unlock(&m->x->lock);-		return sp_e(s, SPEIO, "failed to remap db file",-		            m->x->epoch);+		return sp_em(s, SPEIO|SPEF, m->x->epoch, "failed to remap db file"); 	} 	sp_unlock(&m->x->lock); @@ -429,20 +429,20 @@ 	/* create in-memory page */ 	sppage *p = sp_pagenew(s, m->x); 	if (spunlikely(p == NULL))-		return sp_e(s, SPEOOM, "failed to allocate page");+		return sp_em(s, SPEOOM|SPEF, "failed to allocate page"); 	p->id     = psn; 	p->offset = m->x->db.used; 	p->size   = pagesize; 	p->min    = sp_vdupref(s, min, m->x->epoch); 	if (spunlikely(p->min == NULL)) { 		sp_free(&s->a, p);-		return sp_e(s, SPEOOM, "failed to allocate key");+		return sp_em(s, SPEOOM|SPEF, "failed to allocate key"); 	} 	p->max    = sp_vdupref(s, max, m->x->epoch); 	if (spunlikely(p->max == NULL)) { 		sp_free(&s->a, p->min); 		sp_free(&s->a, p);-		return sp_e(s, SPEOOM, "failed to allocate key");+		return sp_em(s, SPEOOM|SPEF, "failed to allocate key"); 	}  	/* add page to split list */@@ -498,7 +498,7 @@ 		int rc = sp_catset(&s->s, p, &o); 		if (spunlikely(rc == -1)) { 			sp_unlock(&s->locks);-			return sp_e(s, SPEOOM, "failed to allocate page index page");+			return sp_em(s, SPEOOM|SPEF, "failed to allocate page index page"); 		} 		assert(o == NULL); 		sp_pageattach(p);@@ -550,8 +550,9 @@ 	spepoch *x = sp_replive(&s->rep); 	/* rotate current live epoch */ 	sp_repset(&s->rep, x, SPXFER);-	int rc = sp_rotate(s);+	int rc = sp_rotate(s, &s->em); 	if (spunlikely(rc == -1)) {+		sp_esetfatal(&s->em); 		sp_lock(&s->lockr); 		sp_lock(&s->locki); 		return -1;@@ -565,15 +566,16 @@ 	/* complete old live epoch log */  	rc = sp_logeof(&x->log); 	if (spunlikely(rc == -1))-		return sp_e(s, SPEIO, "failed to write eof marker", x->epoch);+		return sp_em(s, SPEIO|SPEF, x->epoch, "failed to write eof marker"); 	rc = sp_logcomplete(&x->log); 	if (spunlikely(rc == -1))-		return sp_e(s, SPEIO, "failed to complete log file", x->epoch);+		return sp_em(s, SPEIO|SPEF, x->epoch, "failed to complete log file");  	/* create db file */-	rc = sp_mapepochnew(&x->db, s->e->dbnewsize, s->e->dir, x->epoch, "db");+	rc = sp_mapepochnew(&x->db, s->env->dbnewsize, s->env->dir,+	                    x->epoch, "db"); 	if (spunlikely(rc == -1))-		return sp_e(s, SPEIO, "failed to create db file", x->epoch);+		return sp_em(s, SPEIO|SPEF, x->epoch, "failed to create db file");  	/* merge index */ 	if (splikely(s->s.count > 0))@@ -591,7 +593,7 @@ 		return -1;  	/* gc */-	if (s->e->gc) {+	if (s->env->gc) { 		rc = sp_gc(s, x); 		if (spunlikely(rc == -1)) 			return -1;@@ -604,7 +606,7 @@ 		rc = sp_mapcomplete(&x->db); 		if (spunlikely(rc == -1)) { 			sp_unlock(&x->lock);-			return sp_e(s, SPEIO, "failed to complete db file", x->epoch);+			return sp_em(s, SPEIO|SPEF, x->epoch, "failed to complete db file"); 		} 		sp_unlock(&x->lock); 		/* set epoch as db */@@ -614,19 +616,19 @@ 		/* remove log file */ 		rc = sp_logunlink(&x->log); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to unlink log file", x->epoch);+			return sp_em(s, SPEIO|SPEF, x->epoch, "failed to unlink log file"); 		rc = sp_logclose(&x->log); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to close log file", x->epoch);+			return sp_em(s, SPEIO|SPEF, x->epoch, "failed to close log file"); 	} else { 		/* there are possible situation when all keys has 		 * been deleted. */ 		rc = sp_mapunlink(&x->db); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to unlink db file", x->epoch);+			return sp_em(s, SPEIO|SPEF, x->epoch, "failed to unlink db file"); 		rc = sp_mapclose(&x->db); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to close db file", x->epoch);+			return sp_em(s, SPEIO|SPEF, x->epoch, "failed to close db file"); 	}  	/* remove all xfer epochs that took part in the merge@@ -640,10 +642,10 @@ 			break; 		rc = sp_logunlink(&e->log); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to unlink log file", e->epoch);+			return sp_em(s, SPEIO|SPEF, e->epoch, "failed to unlink log file"); 		rc = sp_logclose(&e->log); 		if (spunlikely(rc == -1))-			return sp_e(s, SPEIO, "failed to close log file", e->epoch);+			return sp_em(s, SPEIO|SPEF, e->epoch, "failed to close log file"); 		sp_lock(&s->lockr); 		sp_repdetach(&s->rep, e); 		sp_free(&s->a, e);@@ -655,7 +657,7 @@ 	rc = sp_itruncate(index); 	if (spunlikely(rc == -1)) { 		sp_iskipset(s, 0);-		return sp_e(s, SPE, "failed create index");+		return sp_em(s, SPE|SPEF, "failed create index"); 	} 	sp_iskipset(s, 0); 	return 0;
sophia/db/recover.c view
@@ -15,16 +15,89 @@ #include <dirent.h> #include <unistd.h> -static inline int sp_dircreate(sp *s) {-	int rc = mkdir(s->e->dir, 0700);+static inline int sp_dircreate(sp *s)+{+	int rc = mkdir(s->env->dir, 0700); 	if (spunlikely(rc == -1)) { 		sp_e(s, SPE, "failed to create directory %s (errno: %d, %s)",-		     s->e->dir, errno, strerror(errno));+		     s->env->dir, errno, strerror(errno)); 		return -1; 	} 	return 0; } +typedef struct {+	splist link;+	char dir[];+} spdirlock;++static spspinlock dirlock = 0;+static int dirlockn = 0;+static splist dirlocks;++static int sp_dirlock(sp *s)+{+	/* implement multi-process database exclusive access by creating+	 * lock file. */+	char path[1024];+	snprintf(path, sizeof(path), "%s/lock", s->env->dir);+	int rc = sp_lockfile(&s->lockdb, path);+	if (spunlikely(rc == -1))+		sp_e(s, SPE, "failed to create lock file (errno: %d, %s)",+		     errno, strerror(errno));+	if (rc == 1)+		return sp_e(s, SPE, "database is locked");+	/* within a single process, add database path to the list and+	 * check the presence on a open. */+	sp_lock(&dirlock);+	if (dirlockn == 0) {+		sp_listinit(&dirlocks);+	} else {+		splist *i;+		sp_listforeach(&dirlocks, i) {+			spdirlock *l = spcast(i, spdirlock, link);+			if (! strcmp(s->env->dir, l->dir)) {+				sp_unlock(&dirlock);+				return sp_e(s, SPE, "database is locked");+			}+		}+	}+	int lendir = strlen(s->env->dir) + 1;+	int len = sizeof(spdirlock) + lendir;+	spdirlock *l = malloc(len);+	if (spunlikely(l == NULL)) {+		sp_unlock(&dirlock);+		return sp_e(s, SPEOOM, "failed to allocate memory");+	}+	sp_listinit(&l->link);+	memcpy(l->dir, s->env->dir, lendir);+	sp_listappend(&dirlocks, &l->link);+	dirlockn++;+	sp_unlock(&dirlock);+	return 0;+}++static int sp_dirunlock(sp *s)+{+	sp_lock(&dirlock);+	if (spunlikely(dirlockn == 0)) {+		sp_unlock(&dirlock);+		return 0;+	}+	splist *i, *n;+	sp_listforeach_safe(&dirlocks, i, n) {+		spdirlock *l = spcast(i, spdirlock, link);+		if (! strcmp(s->env->dir, l->dir)) {+			sp_listunlink(&l->link);+			free(l);+			break;+		}+	}+	dirlockn--;+	sp_unlock(&dirlock);+	return sp_unlockfile(&s->lockdb);+}+ static inline ssize_t sp_epochof(char *s) { 	size_t v = 0;@@ -40,10 +113,10 @@ static int sp_diropen(sp *s) { 	/* read repository and determine states */-	DIR *d = opendir(s->e->dir);+	DIR *d = opendir(s->env->dir); 	if (spunlikely(d == NULL)) { 		sp_e(s, SPE, "failed to open directory %s (errno: %d, %s)",-		     s->e->dir, errno, strerror(errno));+		     s->env->dir, errno, strerror(errno)); 		return -1; 	} 	struct dirent *de;@@ -88,9 +161,9 @@  static int sp_recoverdb(sp *s, spepoch *x, sptrack *t) {-	int rc = sp_mapepoch(&x->db, s->e->dir, x->epoch, "db");+	int rc = sp_mapepoch(&x->db, s->env->dir, x->epoch, "db"); 	if (spunlikely(rc == -1))-		return sp_e(s, SPEIO, "failed to open db file", x->epoch);+		return sp_e(s, SPEIO, x->epoch, "failed to open db file");  	sppageh *h = (sppageh*)(x->db.map); @@ -209,9 +282,9 @@ 	/* open and map log file */ 	char *ext = (incomplete ? "log.incomplete" : "log"); 	int rc;-	rc = sp_mapepoch(&x->log, s->e->dir, x->epoch, ext);+	rc = sp_mapepoch(&x->log, s->env->dir, x->epoch, ext); 	if (spunlikely(rc == -1))-		return sp_e(s, SPEIO, "failed to open log file", x->epoch);+		return sp_e(s, SPEIO, x->epoch, "failed to open log file");  	/* validate log header */ 	if (spunlikely(! sp_mapinbound(&x->log, sizeof(splogh)) ))@@ -315,7 +388,7 @@ 	 * during shutdown */ 	rc = sp_mapunmap(&x->log); 	if (spunlikely(rc == -1))-		return sp_e(s, SPEIO, "failed to unmap log file", x->epoch);+		return sp_e(s, SPEIO, x->epoch, "failed to unmap log file");  	/* 	 * if there is eof marker missing, try to add one@@ -328,22 +401,22 @@ 		if (! eof) { 			rc = sp_logclose(&x->log); 			if (spunlikely(rc == -1))-				return sp_e(s, SPEIO, "failed to close log file", x->epoch);-			rc = sp_logcontinue(&x->log, s->e->dir, x->epoch);+				return sp_e(s, SPEIO, x->epoch, "failed to close log file");+			rc = sp_logcontinue(&x->log, s->env->dir, x->epoch); 			if (spunlikely(rc == -1)) { 				sp_logclose(&x->log);-				return sp_e(s, SPEIO, "failed to reopen log file", x->epoch);+				return sp_e(s, SPEIO, x->epoch, "failed to reopen log file"); 			} 			rc = sp_logeof(&x->log); 			if (spunlikely(rc == -1)) { 				sp_logclose(&x->log);-				return sp_e(s, SPEIO, "failed to add eof marker", x->epoch);+				return sp_e(s, SPEIO, x->epoch, "failed to add eof marker"); 			} 		} 		rc = sp_logcompleteforce(&x->log); 		if (spunlikely(rc == -1)) { 			sp_logclose(&x->log);-			return sp_e(s, SPEIO, "failed to complete log file", x->epoch);+			return sp_e(s, SPEIO, x->epoch, "failed to complete log file"); 		} 	} 	return 0;@@ -368,13 +441,13 @@ 			if (spunlikely(rc == -1)) 				goto err; 			if (e->recover == (SPRDB|SPRLOG)) {-				rc = sp_epochrm(s->e->dir, e->epoch, "log");+				rc = sp_epochrm(s->env->dir, e->epoch, "log"); 				if (spunlikely(rc == -1)) 					goto err; 			} 			break; 		case SPRLOG|SPRDBI:-			rc = sp_epochrm(s->e->dir, e->epoch, "db.incomplete");+			rc = sp_epochrm(s->env->dir, e->epoch, "db.incomplete"); 			if (spunlikely(rc == -1)) 				goto err; 		case SPRLOG:@@ -411,17 +484,27 @@ 	return -1; } +int sp_recoverunlock(sp *s) {+	return sp_dirunlock(s);+}+ int sp_recover(sp *s) {-	int exists = sp_fileexists(s->e->dir);+	int exists = sp_fileexists(s->env->dir); 	int rc; 	if (!exists) {-		if (! (s->e->flags & SPO_CREAT))+		if (! (s->env->flags & SPO_CREAT)) 			return sp_e(s, SPE, "directory doesn't exists and no SPO_CREAT specified");-		if (s->e->flags & SPO_RDONLY)+		if (s->env->flags & SPO_RDONLY) 			return sp_e(s, SPE, "directory doesn't exists"); 		rc = sp_dircreate(s);+		if (spunlikely(rc == -1))+			return -1;+		rc = sp_dirlock(s); 	} else {+		rc = sp_dirlock(s);+		if (spunlikely(rc == -1))+			return -1; 		rc = sp_diropen(s); 		if (spunlikely(rc == -1)) 			return -1;
sophia/db/recover.h view
@@ -9,6 +9,7 @@  * BSD License */ +int sp_recoverunlock(sp*); int sp_recover(sp*);  #endif
sophia/db/sophia.h view
@@ -9,12 +9,22 @@  * BSD License */ +#ifdef __cplusplus+extern "C" {+#endif+ #include <stdlib.h> #include <stdint.h> -typedef void *(*spallocf)(void*, size_t, void*);-typedef int (*spcmpf)(char*, size_t, char*, size_t, void*);+#if __GNUC__ >= 4+#  define SP_API __attribute__((visibility("default")))+#else+#  define SP_API+#endif +typedef void *(*spallocf)(void *ptr, size_t size, void *arg);+typedef int (*spcmpf)(char *a, size_t asz, char *b, size_t bsz, void *arg);+ typedef enum { 	/* env related */ 	SPDIR,     /* uint32_t, char* */@@ -32,12 +42,12 @@ 	SPVERSION  /* uint32_t*, uint32_t* */ } spopt; -enum {+typedef enum { 	SPO_RDONLY = 1, 	SPO_RDWR   = 2, 	SPO_CREAT  = 4, 	SPO_SYNC   = 8-};+} spflags;  typedef enum { 	SPGT,@@ -57,20 +67,27 @@ 	uint32_t indexpages; } spstat; -void *sp_env(void);-void *sp_open(void*);-int sp_ctl(void*, spopt, ...);-int sp_destroy(void*);-int sp_set(void*, const void*, size_t, const void*, size_t);-int sp_delete(void*, const void*, size_t);-int sp_get(void*, const void*, size_t, void**, size_t*);-void *sp_cursor(void*, sporder, const void*, size_t);-int sp_fetch(void*);-const char *sp_key(void*);-size_t sp_keysize(void*);-const char *sp_value(void*);-size_t sp_valuesize(void*);-char *sp_error(void*);-void sp_stat(void*, spstat*);+SP_API void *sp_env(void);+SP_API void *sp_open(void *env);+SP_API int sp_ctl(void*, spopt, ...);+SP_API int sp_destroy(void *ptr);+SP_API int sp_begin(void *db);+SP_API int sp_commit(void *db);+SP_API int sp_rollback(void *db);+SP_API int sp_set(void *db, const void *k, size_t ksize, const void *v, size_t vsize);+SP_API int sp_delete(void *db, const void *k, size_t ksize);+SP_API int sp_get(void *db, const void *k, size_t ksize, void **v, size_t *vsize);+SP_API void *sp_cursor(void *db, sporder, const void *k, size_t ksize);+SP_API int sp_fetch(void *cur);+SP_API const char *sp_key(void *cur);+SP_API size_t sp_keysize(void *cur);+SP_API const char *sp_value(void *cur);+SP_API size_t sp_valuesize(void *cur);+SP_API char *sp_error(void *ptr);+SP_API void sp_stat(void *ptr, spstat*);++#ifdef __cplusplus+}+#endif  #endif
sophia/db/sp.c view
@@ -13,7 +13,9 @@ cmpstd(char *a, size_t asz, char *b, size_t bsz, void *arg spunused) { 	register size_t sz = (asz < bsz ? asz : bsz); 	register int rc = memcmp(a, b, sz);-	return (rc == 0 ? rc : (rc > 0 ? 1 : -1));+	if (rc == 0)+		return (asz == bsz) ? 0 : (asz > bsz ? 1 : -1);+	return rc > 0 ? 1 : -1; }  static inline void sp_envinit(spenv *e) {@@ -127,7 +129,7 @@ { 	switch (opt) { 	case SPMERGEFORCE:-		if (s->e->merge)+		if (s->env->merge) 			return sp_e(s, SPE, "force merge doesn't work with merger thread active"); 		return sp_merge(s); 	default:@@ -161,30 +163,29 @@ 	return rc; } -int sp_rotate(sp *s)+int sp_rotate(sp *s, spe *err) { 	int rc; 	sp_repepochincrement(&s->rep); 	/* allocate new epoch */ 	spepoch *e = sp_repalloc(&s->rep, sp_repepoch(&s->rep)); 	if (spunlikely(s == NULL))-		return sp_e(s, SPEOOM, "failed to allocate repository");+		return sp_ef(err, SPEOOM, "failed to allocate repository"); 	/* create log file */-	rc = sp_lognew(&e->log, s->e->dir, sp_repepoch(&s->rep));+	rc = sp_lognew(&e->log, s->env->dir, sp_repepoch(&s->rep)); 	if (spunlikely(rc == -1)) { 		sp_free(&s->a, e);-		return sp_e(s, SPEIO, "failed to create log file", e->epoch);+		return sp_ef(err, SPEIO, e->epoch, "failed to create log file"); 	} 	splogh h; 	h.magic = SPMAGIC; 	h.version[0] = SP_VERSION_MAJOR; 	h.version[1] = SP_VERSION_MINOR;-	sp_logadd(&e->log, (char*)&h, sizeof(h));-	rc = sp_logflush(&e->log);+	rc = sp_logwrite(&e->log, &h, sizeof(h)); 	if (spunlikely(rc == -1)) { 		sp_logclose(&e->log); 		sp_free(&s->a, e);-		return sp_e(s, SPEIO, "failed to write log file", e->epoch);+		return sp_ef(err, SPEIO, e->epoch, "failed to write log file"); 	} 	/* attach epoch and mark it is as live */ 	sp_repattach(&s->rep, e);@@ -209,28 +210,28 @@ 			if (e->nupdate == 0) { 				rc = sp_logunlink(&e->log); 				if (spunlikely(rc == -1))-					rcret = sp_e(s, SPEIO, "failed to unlink log file", e->epoch);+					rcret = sp_e(s, SPEIO, e->epoch, "failed to unlink log file"); 				rc = sp_logclose(&e->log); 				if (spunlikely(rc == -1))-					rcret = sp_e(s, SPEIO, "failed to close log file", e->epoch);+					rcret = sp_e(s, SPEIO, e->epoch, "failed to close log file"); 				break; 			} else { 				rc = sp_logeof(&e->log); 				if (spunlikely(rc == -1))-					rcret = sp_e(s, SPEIO, "failed to write eof marker", e->epoch);+					rcret = sp_e(s, SPEIO, e->epoch, "failed to write eof marker"); 			} 		case SPXFER: 			rc = sp_logcomplete(&e->log); 			if (spunlikely(rc == -1))-				rcret = sp_e(s, SPEIO, "failed to complete log file", e->epoch);+				rcret = sp_e(s, SPEIO, e->epoch, "failed to complete log file"); 			rc = sp_logclose(&e->log); 			if (spunlikely(rc == -1))-				rcret = sp_e(s, SPEIO, "failed to close log file", e->epoch);+				rcret = sp_e(s, SPEIO, e->epoch, "failed to close log file"); 			break; 		case SPDB: 			rc = sp_mapclose(&e->db); 			if (spunlikely(rc == -1))-				rcret = sp_e(s, SPEIO, "failed to close db file", e->epoch);+				rcret = sp_e(s, SPEIO, e->epoch, "failed to close db file"); 			break; 		} 		sp_free(&s->a, e);@@ -243,7 +244,7 @@ 	int rcret = 0; 	int rc = 0; 	s->stop = 1;-	if (s->e->merge) {+	if (s->env->merge) { 		rc = sp_taskstop(&s->merger); 		if (spunlikely(rc == -1)) 			rcret = sp_e(s, SPESYS, "failed to stop merger thread");@@ -252,13 +253,19 @@ 	rc = sp_closerep(s); 	if (spunlikely(rc == -1)) 		rcret = -1;+	rc = sp_recoverunlock(s);+	if (spunlikely(rc == -1))+		rcret = -1; 	sp_ifree(&s->i0); 	sp_ifree(&s->i1);+	sp_ifree(&s->itxn); /* equal to rollback */ 	sp_catfree(&s->s);-	s->e->inuse = 0;+	s->env->inuse = 0; 	sp_lockfree(&s->lockr); 	sp_lockfree(&s->locks); 	sp_lockfree(&s->locki);+	sp_efree(&s->e);+	sp_efree(&s->em); 	return rcret; } @@ -268,7 +275,7 @@ 	sp *s = self->arg; 	do { 		sp_lock(&s->locki);-		int merge = s->i->count > s->e->mergewm;+		int merge = s->i->count > s->env->mergewm; 		sp_unlock(&s->locki); 		if (! merge) 			continue;@@ -293,34 +300,45 @@ 	sp_allocinit(&a, env->alloc, env->allocarg); 	sp *s = sp_malloc(&a, sizeof(sp)); 	if (spunlikely(s == NULL)) {-		sp_e(s, SPEOOM, "failed to allocate db handle");+		sp_ee(env, SPEOOM, "failed to allocate db handle"); 		return NULL; 	} 	memset(s, 0, sizeof(sp));+	sp_einit(&s->e);+	sp_einit(&s->em); 	s->m = SPMDB;-	s->e = env;-	s->e->inuse = 1;+	s->env = env;+	s->env->inuse = 1; 	memcpy(&s->a, &a, sizeof(s->a)); 	/* init locks */+	sp_fileinit(&s->lockdb, &s->a); 	sp_lockinit(&s->lockr); 	sp_lockinit(&s->locks); 	sp_lockinit(&s->locki); 	s->lockc = 0; 	/* init key index */-	rc = sp_iinit(&s->i0, &s->a, 1024, s->e->cmp, s->e->cmparg);+	rc = sp_iinit(&s->i0, &s->a, 1024, s->env->cmp, s->env->cmparg); 	if (spunlikely(rc == -1)) { 		sp_e(s, SPEOOM, "failed to allocate key index"); 		goto e0; 	}-	rc = sp_iinit(&s->i1, &s->a, 1024, s->e->cmp, s->e->cmparg);+	rc = sp_iinit(&s->i1, &s->a, 1024, s->env->cmp, s->env->cmparg); 	if (spunlikely(rc == -1)) { 		sp_e(s, SPEOOM, "failed to allocate key index"); 		goto e1; 	} 	s->i = &s->i0;+	/* init transaction index */+	rc = sp_iinit(&s->itxn, &s->a, 1024, s->env->cmp, s->env->cmparg);+	if (spunlikely(rc == -1)) {+		sp_e(s, SPEOOM, "failed to allocate transaction index");+		goto e2;+	}+	/* set current transaction state as single-stmt */+	s->txn = SPTSS; 	/* init page index */ 	s->psn = 0;-	rc = sp_catinit(&s->s, &s->a, 512, s->e->cmp, s->e->cmparg);+	rc = sp_catinit(&s->s, &s->a, 512, s->env->cmp, s->env->cmparg); 	if (spunlikely(rc == -1)) { 		sp_e(s, SPEOOM, "failed to allocate page index"); 		goto e2;@@ -330,18 +348,18 @@ 	if (spunlikely(rc == -1)) 		goto e3; 	/* do not create new live epoch in read-only mode */-	if (! (s->e->flags & SPO_RDONLY)) {-		rc = sp_rotate(s);+	if (! (s->env->flags & SPO_RDONLY)) {+		rc = sp_rotate(s, &s->e); 		if (spunlikely(rc == -1)) 			goto e3; 	} 	s->stop = 0;-	rc = sp_refsetinit(&s->refs, &s->a, s->e->page);+	rc = sp_refsetinit(&s->refs, &s->a, s->env->page); 	if (spunlikely(rc == -1)) { 		sp_e(s, SPEOOM, "failed to allocate key buffer"); 		goto e3; 	}-	if (s->e->merge) {+	if (s->env->merge) { 		rc = sp_taskstart(&s->merger, merger, s); 		if (spunlikely(rc == -1)) { 			sp_e(s, SPESYS, "failed to start merger thread");@@ -350,15 +368,25 @@ 		sp_taskwakeup(&s->merger); 	} 	return s;-e4: sp_refsetfree(&s->refs, &s->a);-e3: sp_closerep(s);+e4:+	sp_refsetfree(&s->refs, &s->a);+e3:+	sp_closerep(s);+	sp_recoverunlock(s); 	sp_catfree(&s->s);-e2: sp_ifree(&s->i1);-e1:	sp_ifree(&s->i0);-e0: s->e->inuse = 0;+e2:+	sp_ifree(&s->itxn);+	sp_ifree(&s->i1);+e1:+	sp_ifree(&s->i0);+e0:+	s->env->inuse = 0; 	sp_lockfree(&s->lockr); 	sp_lockfree(&s->locks); 	sp_lockfree(&s->locki);+	sp_edup(&env->e, &s->e);+	sp_efree(&s->e);+	sp_efree(&s->em); 	sp_free(&a, s); 	return NULL; }@@ -404,27 +432,158 @@ char *sp_error(void *o) { 	spmagic *magic = (spmagic*)o;-	spe *e = NULL;+	spenv *env; 	switch (*magic) {-	case SPMDB: {-		sp *s = o;-		e = &s->e->e;-		break;-	}-	case SPMENV: {-		spenv *env = o;-		e = &env->e;-		break;-	}+	case SPMENV:+		env = o;+		if (! sp_eis(&env->e))+			return NULL;+		return env->e.e;+	case SPMDB: break; 	default: 		assert(0); 		return NULL; 	}-	if (! sp_eis(e))-		return NULL;-	return e->e;+	sp *s = o;+	if (sp_eis(&s->em))+		return s->em.e;+	if (sp_eis(&s->e))+		return s->e.e;+	return NULL; } +int sp_begin(void *o)+{+	sp *s = o;+	assert(s->m == SPMDB);+	if (spunlikely(sp_evalidate(s)))+		return -1;+	if (spunlikely(s->txn == SPTMS))+		return -1;+	if (spunlikely(s->lockc))+		return sp_e(s, SPE, "begin with open cursor");+	s->txn = SPTMS;+	return 0;+}++int sp_commit(void *o)+{+	sp *s = o;+	assert(s->m == SPMDB);+	if (spunlikely(sp_evalidate(s)))+		return -1;+	if (spunlikely(s->txn == SPTSS))+		return sp_e(s, SPE, "no active transaction to commit");+	if (spunlikely(s->lockc))+		return sp_e(s, SPE, "commit with open cursor");+	if (spunlikely(s->itxn.count == 0)) {+		s->txn = SPTSS;+		return 0;+	}++	/* prepare to write the transaction+	 * to the log */+	int n = s->itxn.count;++	sp_lock(&s->lockr);+	sp_lock(&s->locki);++	spepoch *live = sp_replive(&s->rep);+	sp_filesvp(&live->log);++	char hbuf[sizeof(spvh) * 512];+	unsigned int hpos = 0;++	spii it;+	sp_iopen(&it, &s->itxn);+	int rc;+	do {+		spv *v = sp_ival(&it);++		if (spunlikely(! sp_batchensure(&s->lb, 3))) {+			rc = sp_logput(&live->log, &s->lb);+			if (spunlikely(rc == -1)) {+				sp_e(s, SPEIO|SPEF, live->epoch, "failed to write log file");+				goto abort;+			}+			hpos = 0;+		}++		v->epoch = live->epoch;+		assert(hpos < sizeof(hbuf));+		spvh *hp = (spvh*)(hbuf + hpos);+		hp->crc     = 0;+		hp->size    = v->size;+		hp->voffset = 0;+		hp->vsize   = sp_vvsize(v);+		hp->flags   = v->flags;+		hp->crc     = sp_crc32c(v->crc, &hp->size, sizeof(spvh) - sizeof(uint32_t));+		sp_batchadd(&s->lb, hp, sizeof(spvh));+		sp_batchadd(&s->lb, v->key, v->size);+		sp_batchadd(&s->lb, sp_vv(v), hp->vsize);+		hpos += sizeof(spvh);++		spv *old = NULL;+		rc = sp_iset(s->i, v, &old);+		if (spunlikely(rc == -1)) {+			sp_e(s, SPEOOM|SPEF, "failed to allocate key index page");+			goto abort;+		}+		if (old)+			sp_free(&s->a, old);++	} while (sp_inext(&it));++	if (sp_batchhas(&s->lb)) {+		rc = sp_logput(&live->log, &s->lb);+		if (spunlikely(rc == -1)) {+			sp_e(s, SPEIO|SPEF, live->epoch, "failed to write log file");+			goto abort;+		}+	}++	/* clean up transaction index (pages only) */+	sp_ireset(&s->itxn);++	sp_unlock(&s->locki);+	sp_unlock(&s->lockr);++	/* set transaction as single-stmt */+	s->txn = SPTSS;++	/* wake up merger if necessary */+	live->nupdate += n;+	if (live->nupdate >= s->env->mergewm) {+		if (splikely(s->env->merge))+			sp_taskwakeup(&s->merger);+	}+	return 0;++abort:+	sp_rollback(o);+	sp_logrlb(&live->log);+	sp_unlock(&s->locki);+	sp_unlock(&s->lockr);+	return -1;+}++int sp_rollback(void *o)+{+	sp *s = o;+	assert(s->m == SPMDB);+	if (spunlikely(sp_evalidate(s)))+		return -1;+	if (spunlikely(s->txn == SPTSS))+		return sp_e(s, SPE, "no active transaction to rollback");+	if (spunlikely(s->lockc))+		return sp_e(s, SPE, "rollback with open cursor");+	int rc = sp_itruncate(&s->itxn);+	if (spunlikely(rc == -1))+		return sp_e(s, SPEOOM, "failed to allocate key index page");+	s->txn = SPTSS;+	return 0;+}+ static inline int sp_do(sp *s, int op, void *k, size_t ksize, void *v, size_t vsize) {@@ -450,35 +609,57 @@ 	crc   = sp_crc32c(crc, v, vsize); 	h.crc = sp_crc32c(crc, &h.size, sizeof(spvh) - sizeof(uint32_t)); +	n->flags = op;+	n->crc = crc;++	/* in case of multi-stmt transaction, simply add version to the+	 * transaction index only. */+	int rc;+	if (s->txn == SPTMS) {+		spv *old = NULL;+		rc = sp_iset(&s->itxn, n, &old);+		if (spunlikely(rc == -1)) {+			sp_free(&s->a, n);+			return sp_e(s, SPEOOM, "failed to allocate transacton key index page");+		}+		if (old)+			sp_free(&s->a, old);+		return 0;+	}+ 	sp_lock(&s->lockr); 	sp_lock(&s->locki);  	/* write to current live epoch log */ 	spepoch *live = sp_replive(&s->rep); 	sp_filesvp(&live->log);-	sp_logadd(&live->log, &h, sizeof(spvh));-	sp_logadd(&live->log, k, ksize);-	sp_logadd(&live->log, v, vsize);-	int rc = sp_logflush(&live->log);+	sp_batchadd(&s->lb, &h, sizeof(spvh));+	sp_batchadd(&s->lb, k, ksize);+	sp_batchadd(&s->lb, v, vsize);+	rc = sp_logput(&live->log, &s->lb); 	if (spunlikely(rc == -1)) { 		sp_free(&s->a, n);-		sp_logrlb(&live->log);+		rc = sp_logrlb(&live->log);+		if (spunlikely(rc == -1))+			sp_esetfatal(&s->e); 		sp_unlock(&s->locki); 		sp_unlock(&s->lockr);-		return sp_e(s, SPEIO, "failed to write log file", live->epoch);+		return sp_e(s, SPEIO, live->epoch, "failed to write log file"); 	}  	/* add new version to the index */-	spv *old = NULL; 	n->epoch = live->epoch;-	n->flags = op;-	n->crc   = crc;+	spv *old = NULL; 	rc = sp_iset(s->i, n, &old); 	if (spunlikely(rc == -1)) { 		sp_free(&s->a, n);+		rc = sp_logrlb(&live->log);+		if (spunlikely(rc == -1))+			sp_esetfatal(&s->e); 		sp_unlock(&s->locki); 		sp_unlock(&s->lockr);-		return sp_e(s, SPEOOM, "failed to allocate key index page");+		return (spunlikely(rc == -1)) ? -1 :+		        sp_e(s, SPEOOM, "failed to allocate key index page"); 	}  	sp_unlock(&s->locki);@@ -489,8 +670,8 @@  	/* wake up merger on merge watermark reached */ 	live->nupdate++;-	if ((live->nupdate % s->e->mergewm) == 0) {-		if (splikely(s->e->merge))+	if ((live->nupdate % s->env->mergewm) == 0) {+		if (splikely(s->env->merge)) 			sp_taskwakeup(&s->merger); 	} 	return 0;@@ -500,14 +681,16 @@ { 	sp *s = o; 	assert(s->m == SPMDB);-	if (spunlikely(sp_eis(&s->e->e)))+	if (spunlikely(sp_evalidate(s))) 		return -1;-	if (spunlikely(s->e->flags & SPO_RDONLY))+	if (spunlikely(s->env->flags & SPO_RDONLY)) 		return sp_e(s, SPE, "db handle is read-only"); 	if (spunlikely(ksize > UINT16_MAX)) 		return sp_e(s, SPE, "key size limit reached"); 	if (spunlikely(vsize > UINT32_MAX)) 		return sp_e(s, SPE, "value size limit reached");+	if (spunlikely(s->lockc))+		return sp_e(s, SPE, "modify with open cursor"); 	return sp_do(s, SPSET, (char*)k, ksize, (char*)v, vsize); } @@ -515,31 +698,25 @@ { 	sp *s = o; 	assert(s->m == SPMDB);-	if (spunlikely(sp_eis(&s->e->e)))+	if (spunlikely(sp_evalidate(s))) 		return -1;-	if (spunlikely(s->e->flags & SPO_RDONLY))+	if (spunlikely(s->env->flags & SPO_RDONLY)) 		return sp_e(s, SPE, "db handle is read-only"); 	if (spunlikely(ksize > UINT16_MAX)) 		return sp_e(s, SPE, "key size limit reached");+	if (spunlikely(s->lockc))+		return sp_e(s, SPE, "modify with open cursor"); 	return sp_do(s, SPDEL, (char*)k, ksize, NULL, 0); } -static inline int-sp_checkro(sp *s, size_t ksize)-{-	if (spunlikely(sp_eis(&s->e->e)))-		return -1;-	if (spunlikely(ksize > UINT16_MAX))-		return sp_e(s, SPE, "key size limit reached");-	return 0;-}- int sp_get(void *o, const void *k, size_t ksize, void **v, size_t *vsize) { 	sp *s = o; 	assert(s->m == SPMDB);-	if (spunlikely(sp_checkro(s, ksize) == -1))+	if (spunlikely(sp_evalidate(s))) 		return -1;+	if (spunlikely(ksize > UINT16_MAX))+		return sp_e(s, SPE, "key size limit reached"); 	return sp_match(s, (char*)k, ksize, v, vsize); } @@ -547,8 +724,12 @@ { 	sp *s = o; 	assert(s->m == SPMDB);-	if (spunlikely(sp_checkro(s, ksize) == -1))+	if (spunlikely(sp_evalidate(s))) 		return NULL;+	if (spunlikely(ksize > UINT16_MAX)) {+		sp_e(s, SPE, "key size limit reached");+		return NULL;+	} 	spc *c = sp_malloc(&s->a, sizeof(spc)); 	if (spunlikely(c == NULL)) { 		sp_e(s, SPEOOM, "failed to allocate cursor handle");@@ -562,7 +743,7 @@ int sp_fetch(void *o) { 	spc *c = o; 	assert(c->m == SPMCUR);-	if (spunlikely(sp_eis(&c->s->e->e)))+	if (spunlikely(sp_evalidate(c->s))) 		return -1; 	return sp_iterate(c); }@@ -606,7 +787,6 @@ 	sp_lock(&s->lockr); 	sp_lock(&s->locki); 	sp_lock(&s->locks);- 	stat->epoch = s->rep.epoch; 	stat->psn = s->psn; 	stat->repn = s->rep.n;@@ -615,7 +795,6 @@ 	stat->catn = s->s.count; 	stat->indexn = s->i->count; 	stat->indexpages = s->i->icount;- 	sp_unlock(&s->locks); 	sp_unlock(&s->locki); 	sp_unlock(&s->lockr);
sophia/makefile view
@@ -1,7 +1,6 @@- all:-	@(cd db; make --no-print-directory)-	@(cd test; make --no-print-directory)+	@(cd db; $(MAKE))+	@(cd test; $(MAKE)) clean:-	@(cd db; make clean --no-print-directory)-	@(cd test; make clean --no-print-directory)+	@(cd db; $(MAKE) clean)+	@(cd test; $(MAKE) clean)
sophia/test/common.c view
@@ -331,6 +331,24 @@ }  static void+cursor_set(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == -1 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void fetch_gte_empty(void) { 	void *env = sp_env(); 	t( env != NULL );@@ -807,62 +825,447 @@ 	t( rmrf(dbrep) == 0 ); } -#if 0 static void-rotate_empty(void) {+fetch_kgte_random(void) { 	void *env = sp_env(); 	t( env != NULL ); 	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 ); 	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 ); 	t( sp_ctl(env, SPMERGE, 0) == 0 );-	t( sp_ctl(env, SPTTL, 2) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 ); 	void *db = sp_open(env);-	t( exists(dbrep, 1, "log.incomplete") == 1 );+	t( db != NULL );++	uint32_t k = 0;+	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 0;+	while (k < 1024) {+		void *cur = sp_cursor(db, SPGTE, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k + n );+			t( *(uint32_t*)sp_value(cur) == k + n );+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (1024 - k) );+		k++;+	}+ 	t( sp_destroy(db) == 0 );-	t( exists(dbrep, 1, "log.incomplete") == 0 );-	t( exists(dbrep, 1, "log") == 0 ); 	t( sp_destroy(env) == 0 ); 	t( rmrf(dbrep) == 0 ); }  static void-rotate(void) {+fetch_kgte_random_merge(void) { 	void *env = sp_env(); 	t( env != NULL ); 	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 ); 	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 ); 	t( sp_ctl(env, SPMERGE, 0) == 0 );-	t( sp_ctl(env, SPTTL, 2) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 ); 	void *db = sp_open(env); 	t( db != NULL );-	uint32_t k = 1, v = 1;-	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);-	t( exists(dbrep, 1, "log.incomplete") == 1 );-	t( exists(dbrep, 1, "db.incomplete") == 0 );-	t( exists(dbrep, 1, "db") == 0 );-	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);-	t( exists(dbrep, 1, "log.incomplete") == 1 );-	t( exists(dbrep, 2, "log.incomplete") == 1 );-	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);-	t( exists(dbrep, 3, "log.incomplete") == 0 );-	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);-	t( exists(dbrep, 1, "log.incomplete") == 1 );-	t( exists(dbrep, 2, "log.incomplete") == 1 );-	t( exists(dbrep, 3, "log.incomplete") == 1 );++	uint32_t k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 0;+	while (k < 1024) {+		void *cur = sp_cursor(db, SPGTE, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k + n );+			t( *(uint32_t*)sp_value(cur) == k + n );+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (1024 - k) );+		k++;+	}+ 	t( sp_destroy(db) == 0 );-	t( exists(dbrep, 1, "db.incomplete") == 0 );-	t( exists(dbrep, 1, "db") == 0 );-	t( exists(dbrep, 1, "log.incomplete") == 0 );-	t( exists(dbrep, 2, "log.incomplete") == 0 );-	t( exists(dbrep, 3, "log.incomplete") == 0 );-	t( exists(dbrep, 1, "log") == 1 );-	t( exists(dbrep, 2, "log") == 1 );-	t( exists(dbrep, 3, "log") == 0 ); 	t( sp_destroy(env) == 0 ); 	t( rmrf(dbrep) == 0 ); }-#endif +static void+fetch_kgte_random_merge_rev(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	uint32_t k = 512;+	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 0;+	while (k < 1024) {+		void *cur = sp_cursor(db, SPGTE, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k + n );+			t( *(uint32_t*)sp_value(cur) == k + n );+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (1024 - k) );+		k++;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+fetch_klte_random(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	int32_t k = 0;+	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 1023;+	while (k >= 0) {+		void *cur = sp_cursor(db, SPLTE, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k - n );+			t( *(uint32_t*)sp_value(cur) == k - n );+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (k + 1) );+		k--;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+fetch_klte_random_merge(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	int32_t k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 1023;+	while (k >= 0) {+		void *cur = sp_cursor(db, SPLTE, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k - n );+			t( *(uint32_t*)sp_value(cur) == k - n );+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (k + 1) );+		k--;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+fetch_klte_random_merge_rev(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	int32_t k = 512;+	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 1023;+	while (k >= 0) {+		void *cur = sp_cursor(db, SPLTE, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k - n );+			t( *(uint32_t*)sp_value(cur) == k - n );+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (k + 1) );+		k--;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}+++static void+fetch_kgt_random_merge(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	uint32_t k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 0;+	while (k < 1024) {+		void *cur = sp_cursor(db, SPGT, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k + (n + 1));+			t( *(uint32_t*)sp_key(cur) == k + (n + 1));+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (1023 - k) );+		k++;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+fetch_kgt_random_merge_rev(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	uint32_t k = 512;+	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 0;+	while (k < 1024) {+		void *cur = sp_cursor(db, SPGT, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k + (n + 1));+			t( *(uint32_t*)sp_key(cur) == k + (n + 1));+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == (1023 - k) );+		k++;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+fetch_klt_random_merge(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	int32_t k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 1023;+	while (k >= 0) {+		void *cur = sp_cursor(db, SPLT, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k - (n + 1));+			t( *(uint32_t*)sp_value(cur) == k - (n + 1));+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == k );+		k--;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+fetch_klt_random_merge_rev(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPPAGE, 64) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPMERGEWM, 256) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	int32_t k = 512;+	while (k < 1024) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	t( sp_ctl(db, SPMERGEFORCE) == 0 );++	k = 0;+	while (k < 512) {+		t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+		k++;+	}++	k = 1023;+	while (k >= 0) {+		void *cur = sp_cursor(db, SPLT, &k, sizeof(k));+		t( cur != NULL );+		uint32_t n = 0;+		while (sp_fetch(cur)) {+			t( *(uint32_t*)sp_key(cur) == k - (n + 1));+			t( *(uint32_t*)sp_value(cur) == k - (n + 1));+			n++;+		}+		t( sp_destroy(cur) == 0 );+		t( n == k );+		k--;+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}+ int main(int argc, char *argv[]) {@@ -886,6 +1289,7 @@ 	test(delete); 	test(delete_set_get); 	test(cursor);+	test(cursor_set); 	test(fetch_gte_empty); 	test(fetch_gt_empty); 	test(fetch_lte_empty);@@ -903,9 +1307,16 @@ 	test(fetch_klte); 	test(fetch_klt); 	test(fetch_after_end);-	/*-	test(rotate_empty);-	test(rotate);-	*/++	test(fetch_kgte_random);+	test(fetch_kgte_random_merge);+	test(fetch_kgte_random_merge_rev);+	test(fetch_klte_random);+	test(fetch_klte_random_merge);+	test(fetch_klte_random_merge_rev);+	test(fetch_kgt_random_merge);+	test(fetch_kgt_random_merge_rev);+	test(fetch_klt_random_merge);+	test(fetch_klt_random_merge_rev); 	return 0; }
+ sophia/test/concurrent.c view
@@ -0,0 +1,173 @@++/*+ * sophia database+ * sphia.org+ *+ * Copyright (c) Dmitry Simonenko+ * BSD License+*/++#include <sophia.h>+#include <sys/types.h>+#include <sys/wait.h>+#include <unistd.h>+#include "test.h"++static char *dbrep1 = "./rep";+static char *dbrep2 = "./rep2";++static inline int+cmp(char *a, size_t asz, char *b, size_t bsz, void *arg) {+	register uint32_t av = *(uint32_t*)a;+	register uint32_t bv = *(uint32_t*)b;+	if (av == bv)+		return 0;+	return (av > bv) ? 1 : -1;+}++static void+single_process(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep1) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	void *env2 = sp_env();+	t( env2 != NULL );+	t( sp_ctl(env2, SPDIR, SPO_CREAT|SPO_RDWR, dbrep1) == 0 );+	void *db2 = sp_open(env2);+	t( db2 == NULL );+	t( sp_destroy(env2) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep1);+}++static void+single_process_2(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep1) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	void *env2 = sp_env();+	t( env != NULL );+	t( sp_ctl(env2, SPDIR, SPO_CREAT|SPO_RDWR, dbrep2) == 0 );+	t( sp_ctl(env2, SPCMP, cmp, NULL) == 0 );+	void *db2 = sp_open(env2);+	t( db2 != NULL );+	uint32_t k = 1;+	t( sp_set(db2, &k, sizeof(k), &k, sizeof(k)) == 0 );+	size_t vsize = 0;+	void *vp = NULL;+	t( sp_get(db2, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( sp_destroy(db2) == 0 );+	t( sp_destroy(env2) == 0 );+	t( rmrf(dbrep1) == 0 );+	t( rmrf(dbrep2) == 0 );+}++static void+multi_process(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep1) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	int pid = fork();+	t( pid != -1 );+	if (pid == 0) {+		/* new process */+		void *env2 = sp_env();+		t( env2 != NULL );+		t( sp_ctl(env2, SPDIR, SPO_CREAT|SPO_RDWR, dbrep1) == 0 );+		void *db2 = sp_open(env2);+		t( db2 == NULL );+		t( sp_destroy(env2) == 0 );+		/* valgrind: parent db and env are unfreed here, and that+		 *           is correct otherwise destroy would+		 *           corrupt the database.+		*/+		exit(0);+	} else {+		int status = 0;+		t( waitpid(pid, &status, 0) == pid );+		t( status == 0 );+	}+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep1);+}++static void+multi_process_2(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep1) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	void *env2 = sp_env();+	t( env != NULL );+	t( sp_ctl(env2, SPDIR, SPO_CREAT|SPO_RDWR, dbrep2) == 0 );+	t( sp_ctl(env2, SPCMP, cmp, NULL) == 0 );+	uint32_t k = 1;+	size_t vsize = 0;+	void *vp = NULL;+	int pid = fork();+	t( pid != -1 );+	if (pid == 0) {+		void *db2 = sp_open(env2);+		t( db2 != NULL );+		t( sp_set(db2, &k, sizeof(k), &k, sizeof(k)) == 0 );+		t( sp_get(db2, &k, sizeof(k), &vp, &vsize) == 1 );+		t( vsize == sizeof(k) );+		t( *(uint32_t*)vp == k );+		free(vp);+		t( sp_destroy(db2) == 0 );+		t( sp_destroy(env2) == 0 );+		t( rmrf(dbrep2) == 0 );+		/* valgrind: parent db and env are unfreed here, and that+		 *           is correct otherwise destroy would+		 *           corrupt the database.+		*/+		exit(0);+	}+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0 );+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep1) == 0 );+	int status = 0;+	t( waitpid(pid, &status, 0) == pid );+	t( status == 0 );+}++int+main(int argc, char *argv[])+{+	rmrf(dbrep1);+	rmrf(dbrep2);++	test(single_process);+	test(single_process_2);+	test(multi_process);+	test(multi_process_2);+	return 0;+}
sophia/test/crash.c view
@@ -83,15 +83,17 @@ 	}; 	speofh eof = { SPEOF }; 	spfile f;+	spbatch b; 	sp_fileinit(&f, &a);+	sp_batchinit(&b); 	t( mkdir(dbrep, 0755) == 0 ); 	t( sp_lognew(&f, dbrep, 1) == 0 );-	sp_logadd(&f, &h, sizeof(h));-	sp_logadd(&f, &vh, sizeof(vh));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &eof, sizeof(eof));-	t( sp_logflush(&f) == 0 );+	sp_batchadd(&b, &h, sizeof(h));+	sp_batchadd(&b, &vh, sizeof(vh));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &eof, sizeof(eof));+	t( sp_logput(&f, &b) == 0 ); 	t( sp_logcomplete(&f)  == 0 ); 	t( sp_logclose(&f)  == 0 ); 	t( exists(dbrep, 1, "log") == 1 );@@ -124,15 +126,17 @@ 	}; 	speofh eof = { SPEOF }; 	spfile f;+	spbatch b; 	sp_fileinit(&f, &a);+	sp_batchinit(&b); 	t( mkdir(dbrep, 0755) == 0 ); 	t( sp_lognew(&f, dbrep, 1) == 0 );-	sp_logadd(&f, &h, sizeof(h));-	sp_logadd(&f, &vh, sizeof(vh));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &eof, sizeof(eof));-	t( sp_logflush(&f) == 0 );+	sp_batchadd(&b, &h, sizeof(h));+	sp_batchadd(&b, &vh, sizeof(vh));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &eof, sizeof(eof));+	t( sp_logput(&f, &b) == 0 ); 	t( sp_logclose(&f)  == 0 ); 	t( exists(dbrep, 1, "log.incomplete") == 1 ); 	void *env = sp_env();@@ -175,14 +179,16 @@ 	}; 	speofh eof = { SPEOF }; 	spfile f;+	spbatch b;+	sp_batchinit(&b); 	sp_fileinit(&f, &a); 	t( sp_lognew(&f, dbrep, 2) == 0 );-	sp_logadd(&f, &h, sizeof(h));-	sp_logadd(&f, &vh, sizeof(vh));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &eof, sizeof(eof));-	t( sp_logflush(&f) == 0 );+	sp_batchadd(&b, &h, sizeof(h));+	sp_batchadd(&b, &vh, sizeof(vh));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &eof, sizeof(eof));+	t( sp_logput(&f, &b) == 0 ); 	t( sp_logcomplete(&f)  == 0 ); 	t( sp_logclose(&f)  == 0 ); 	t( exists(dbrep, 2, "log") == 1 );@@ -214,14 +220,16 @@ 	crc    = sp_crc32c(crc, &k, sizeof(k)); 	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t)); 	spfile f;+	spbatch b;+	sp_batchinit(&b); 	sp_fileinit(&f, &a); 	t( mkdir(dbrep, 0755) == 0 ); 	t( sp_lognew(&f, dbrep, 1) == 0 );-	sp_logadd(&f, &h, sizeof(h));-	sp_logadd(&f, &vh, sizeof(vh));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &k, sizeof(k));-	t( sp_logflush(&f) == 0 );+	sp_batchadd(&b, &h, sizeof(h));+	sp_batchadd(&b, &vh, sizeof(vh));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &k, sizeof(k));+	t( sp_logput(&f, &b) == 0 ); 	t( sp_logclose(&f)  == 0 ); 	t( exists(dbrep, 1, "log.incomplete") == 1 ); 	void *env = sp_env();@@ -262,14 +270,17 @@ 	crc    = sp_crc32c(crc, &k, sizeof(k)); 	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t)); 	spfile f;+	spbatch b;+	sp_batchinit(&b); 	sp_fileinit(&f, &a);+	sp_batchinit(&b); 	t( mkdir(dbrep, 0755) == 0 ); 	t( sp_lognew(&f, dbrep, 1) == 0 );-	sp_logadd(&f, &h, sizeof(h));-	sp_logadd(&f, &vh, sizeof(vh));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &k, sizeof(k));-	t( sp_logflush(&f) == 0 );+	sp_batchadd(&b, &h, sizeof(h));+	sp_batchadd(&b, &vh, sizeof(vh));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &k, sizeof(k));+	t( sp_logput(&f, &b) == 0 ); 	t( sp_logcomplete(&f)  == 0 ); 	t( sp_logclose(&f)  == 0 ); 	t( exists(dbrep, 1, "log") == 1 );@@ -382,15 +393,17 @@ 	crc    = sp_crc32c(crc, &k, sizeof(k)); 	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t)); 	spfile f;+	spbatch b;+	sp_batchinit(&b); 	sp_fileinit(&f, &a); 	t( mkdir(dbrep, 0755) == 0 ); 	t( sp_lognew(&f, dbrep, 1) == 0 );-	sp_logadd(&f, &h, sizeof(h));-	sp_logadd(&f, &vh, sizeof(vh));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &eof, sizeof(eof));-	t( sp_logflush(&f) == 0 );+	sp_batchadd(&b, &h, sizeof(h));+	sp_batchadd(&b, &vh, sizeof(vh));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &eof, sizeof(eof));+	t( sp_logput(&f, &b) == 0 ); 	t( sp_logclose(&f)  == 0 ); 	char path[1024]; 	snprintf(path, sizeof(path), "%s/%"PRIu32".db.incomplete",@@ -431,15 +444,17 @@ 	crc    = sp_crc32c(crc, &k, sizeof(k)); 	vh.crc = sp_crc32c(crc, &vh.size, sizeof(spvh) - sizeof(uint32_t)); 	spfile f;+	spbatch b;+	sp_batchinit(&b); 	sp_fileinit(&f, &a); 	t( mkdir(dbrep, 0755) == 0 ); 	t( sp_lognew(&f, dbrep, 1) == 0 );-	sp_logadd(&f, &h, sizeof(h));-	sp_logadd(&f, &vh, sizeof(vh));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &k, sizeof(k));-	sp_logadd(&f, &eof, sizeof(eof));-	t( sp_logflush(&f) == 0 );+	sp_batchadd(&b, &h, sizeof(h));+	sp_batchadd(&b, &vh, sizeof(vh));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &k, sizeof(k));+	sp_batchadd(&b, &eof, sizeof(eof));+	t( sp_logput(&f, &b) == 0 ); 	t( sp_logcomplete(&f) == 0 ); 	t( sp_logclose(&f)  == 0 ); 	t( exists(dbrep, 1, "log") == 1 );
+ sophia/test/issues.c view
@@ -0,0 +1,129 @@++/*+ * sophia database+ * sphia.org+ *+ * Copyright (c) Dmitry Simonenko+ * BSD License+*/++#include <string.h>+#include <sophia.h>+#include "test.h"++static char *dbrep = "./rep";++static void+gh_5(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	void *db = sp_open(env);+	t( db != NULL );++	char k1[] = "key-1";+	char v1[] = "val-1";+	t( sp_set(db, k1, sizeof(k1), v1, sizeof(v1)) == 0);++	char k2[] = "key-10";+	char v2[] = "val-10";+	t( sp_set(db, k2, sizeof(k2), v2, sizeof(v2)) == 0);++	size_t ressize = 0;+	void *res = NULL;+	t( sp_get(db, k1, sizeof(k1), &res, &ressize) == 1 );++	t( ressize == sizeof(v1) );+	t( strcmp(v1, res) == 0 );++	free(res);++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+gh_29(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	int i;+	for (i = 1; i <= 10; i++)  {+		char key[4];+		int len = snprintf(key, sizeof(key), "%d", i);+		t( sp_set(db, key, len, key, len) == 0 );+	}+	for (i = 1; i <= 10; i++) {+		char key[4];+		int len = snprintf(key, sizeof(key), "%d", i);+		size_t vsize = 0;+		void *vp = NULL;+		t( sp_get(db, key, len, &vp, &vsize) == 1 );+		t( vsize == len );+		t( memcmp(key, vp, len) == 0 );+		free(vp);+	}+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+gh_37(void)+{+	const int size = 999;+	const int watermark = 3996;+	const int pagesize = 1022;++	char data[size];+	memset(data, 0, sizeof(data));++	void *env = sp_env();+	int rc = sp_ctl(env, SPDIR, SPO_CREAT | SPO_RDWR, dbrep);+	t(rc == 0);+	if (rc == -1)++	rc = sp_ctl(env, SPMERGE, 0);+	t(rc == 0);++	rc = sp_ctl(env, SPMERGEWM, watermark);+	if (rc == -1)+	t(rc == 0);++	sp_ctl(env, SPPAGE, pagesize);+	t(rc == 0);++	void *db = sp_open(env);+	t(db != NULL);++	int byteCount = 0;+	int i;+	for (i = 0; i < 100000; ++i) {+		rc = sp_set(db, &i, sizeof(int), data, size);+		t(rc == 0);+	}++	byteCount += size;+	if (i != 0 && i % watermark == 0) {+		rc = sp_ctl(db, SPMERGEFORCE);+		t(rc == 0);+	}++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++int+main(int argc, char *argv[])+{+	rmrf(dbrep);++	test(gh_5);+	test(gh_29);+	test(gh_37);+	return 0;+}
sophia/test/limit.c view
@@ -31,7 +31,7 @@ 	t( db != NULL ); 	char buf[1]; 	t( sp_set(db, buf, UINT16_MAX + 1, buf, sizeof(buf)) == -1 );-	t( sp_error(env) != NULL );+	t( sp_error(db) != NULL ); 	t( sp_destroy(db) == 0 ); 	t( sp_destroy(env) == 0 ); 	rmrf(dbrep);@@ -47,12 +47,32 @@ 	t( db != NULL ); 	char buf[1]; 	t( sp_set(db, buf, sizeof(buf), buf, UINT32_MAX + 1ULL) == -1 );-	t( sp_error(env) != NULL );+	t( sp_error(db) != NULL ); 	t( sp_destroy(db) == 0 ); 	t( sp_destroy(env) == 0 ); 	rmrf(dbrep); } +static void+error_nonfatal(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_set(db, &k, UINT16_MAX + 1 , &v, sizeof(v)) == -1);+	t( sp_error(db) != NULL );+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_error(db) == NULL );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++ int main(int argc, char *argv[]) {@@ -61,5 +81,7 @@ 	test(limit_key); 	if (sizeof(size_t) > 4) 		test(limit_value);++	test(error_nonfatal); 	return 0; }
sophia/test/recover.c view
@@ -1506,45 +1506,1656 @@ 	t( rmrf(dbrep) == 0 ); } -int-main(int argc, char *argv[])-{-	rmrf(dbrep);--	test(recover_log_set_get);-	test(recover_log_replace_get);-	test(recover_log_set_get_replace_get);-	test(recover_log_delete_get);-	test(recover_log_delete_set_get);-	test(recover_log_fetch_gte);-	test(recover_log_fetch_lte);-	test(recover_log_fetch_kgte);-	test(recover_log_fetch_kgt);-	test(recover_log_fetch_klte);-	test(recover_log_fetch_klt);-	test(recover_log_n_get);-	test(recover_log_n_replace);--	test(recover_page_set_get);-	test(recover_page_replace_get);-	test(recover_page_set_get_replace_get);-	test(recover_page_delete_get);-	test(recover_page_delete_set_get);-	test(recover_page_fetch_gte);-	test(recover_page_fetch_lte);-	test(recover_page_fetch_kgte);-	test(recover_page_fetch_kgt);-	test(recover_page_fetch_klte);-	test(recover_page_fetch_klt);-	test(recover_page_n_get);-	test(recover_page_n_replace);--	test(recover_page_log_fetch_gte);-	test(recover_page_log_fetch_lte);-	test(recover_page_log_fetch_kgte);-	test(recover_page_log_fetch_kgt);-	test(recover_page_log_fetch_klte);-	test(recover_page_log_fetch_klt);-	test(recover_page_log_n_replace);+static void+txn_recover_log_set_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == v );+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == v );+	free(vp);+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == v );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_replace_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	v = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 3 );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_set_get_replace_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 1 );+	free(vp);+	v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	vsize = 0;+	vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 2 );+	free(vp);+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	vsize = 0;+	vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 2 );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_delete_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_delete(db, &k, sizeof(k)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_delete_set_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_delete(db, &k, sizeof(k)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	size_t vsize = 0;+	void *vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	vsize = 0;+	vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 2 );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_fetch_gte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_fetch_lte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPLTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_fetch_kgte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPGTE, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_fetch_kgt(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPGT, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_fetch_klte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPLTE, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_fetch_klt(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPLT, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_keysize(cur) == 0 );+	t( sp_key(cur) == NULL );+	t( sp_valuesize(cur) == 0 );+	t( sp_value(cur) == NULL );+	t( sp_fetch(cur) == 0 );+	t( sp_keysize(cur) == 0 );+	t( sp_key(cur) == NULL );+	t( sp_valuesize(cur) == 0 );+	t( sp_value(cur) == NULL );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_n_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 0, v;+	while (k < 12) {+		v = k;+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		k++;+	}+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 1, "log") == 1 );+	db = sp_open(env);+	t( db != NULL );+	k = 0;+	while (k < 12) {+		size_t vsize = 0;+		void *vp = NULL;+		t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+		t( vsize == sizeof(v) );+		t( *(uint32_t*)vp == k );+		free(vp);+		k++;+	}+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_log_n_replace(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( exists(dbrep, 1, "log.incomplete") == 1 );+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 0, v = 1;+	while (k < 12) {+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		k++;+	}+	k = 0;+	v = 2;+	while (k < 12) {+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		k++;+	}+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 1, "log") == 1 );+	db = sp_open(env);+	t( exists(dbrep, 1, "log") == 1 );+	t( exists(dbrep, 2, "log.incomplete") == 1 );+	t( db != NULL );+	k = 0;+	while (k < 12) {+		size_t vsize = 0;+		void *vp = NULL;+		t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+		t( vsize == sizeof(v) );+		t( *(uint32_t*)vp == 2 );+		free(vp);+		k++;+	}+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 1, "log") == 1 );+	t( exists(dbrep, 2, "log") == 0 );+	t( exists(dbrep, 2, "log.incomplete") == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_set_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( exists(dbrep, 1, "log.incomplete") == 0 );+	t( exists(dbrep, 1, "log") == 0 );+	t( exists(dbrep, 1, "db") == 1 );+	t( exists(dbrep, 2, "log.incomplete") == 1 );+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 1, "db") == 1 );+	t( exists(dbrep, 2, "log") == 0 );+	t( exists(dbrep, 2, "log.incomplete") == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == v );+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == v );+	free(vp);+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == v );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_replace_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	v = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( exists(dbrep, 1, "log.incomplete") == 0 );+	t( exists(dbrep, 1, "log") == 0 );+	t( exists(dbrep, 2, "log.incomplete") == 1 );+	t( exists(dbrep, 2, "log") == 0 );+	t( exists(dbrep, 1, "db") == 1 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 3 );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_set_get_replace_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	v = 8;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 8 );+	free(vp);+	v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	k = 2;+	v = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	vsize = 0;+	vp = NULL;+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 2 );+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 3 );+	free(vp);+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	vsize = 0;+	vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 2 );+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 3 );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_delete_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_delete(db, &k, sizeof(k)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( exists(dbrep, 1, "log.incomplete") == 0 );+	t( exists(dbrep, 1, "log") == 0 );+	t( exists(dbrep, 1, "db.incomplete") == 0 );+	t( exists(dbrep, 1, "db") == 0 );+	t( exists(dbrep, 2, "log.incomplete") == 1 );+	t( exists(dbrep, 2, "log") == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_delete_set_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_delete(db, &k, sizeof(k)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( exists(dbrep, 1, "log.incomplete") == 0 );+	t( exists(dbrep, 1, "log") == 0 );+	t( exists(dbrep, 1, "db.incomplete") == 0 );+	t( exists(dbrep, 1, "db") == 0 );+	t( exists(dbrep, 2, "log.incomplete") == 1 );+	t( exists(dbrep, 2, "log") == 0 );+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 1, "log.incomplete") == 0 );+	t( exists(dbrep, 1, "log") == 0 );+	t( exists(dbrep, 1, "db.incomplete") == 0 );+	t( exists(dbrep, 1, "db") == 0 );+	t( exists(dbrep, 2, "log.incomplete") == 0 );+	t( exists(dbrep, 2, "log") == 0 );+	db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	size_t vsize = 0;+	void *vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_delete(db, &k, sizeof(k)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( exists(dbrep, 1, "log.incomplete") == 0 );+	t( exists(dbrep, 1, "log") == 0 );+	t( exists(dbrep, 1, "db.incomplete") == 0 );+	t( exists(dbrep, 1, "db") == 1 );+	t( exists(dbrep, 2, "log.incomplete") == 1 );+	t( exists(dbrep, 2, "log") == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	vsize = 0;+	vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(v) );+	t( *(uint32_t*)vp == 1 );+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_fetch_gte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_fetch_lte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPLTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_fetch_kgte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPGTE, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_fetch_kgt(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPGT, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_fetch_klte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPLTE, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_fetch_klt(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPLT, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_n_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 0, v;+	while (k < 12) {+		v = k;+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		k++;+	}+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 1, "db") == 1 );+	t( exists(dbrep, 1, "log.incomplete") == 0 );+	t( exists(dbrep, 1, "log") == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 0;+	while (k < 12) {+		size_t vsize = 0;+		void *vp = NULL;+		t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+		t( vsize == sizeof(v) );+		t( *(uint32_t*)vp == k );+		free(vp);+		k++;+	}+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_n_replace(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 0, v = 1;+	while (k < 12) {+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		k++;+	}+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_begin(db) == 0 );+	t( exists(dbrep, 1, "db") == 1 );+	k = 0;+	v = 2;+	while (k < 12) {+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		k++;+	}+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 2, "db") == 1 );+	t( exists(dbrep, 1, "log") == 0 );+	t( exists(dbrep, 2, "log") == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 0;+	while (k < 12) {+		size_t vsize = 0;+		void *vp = NULL;+		t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+		t( vsize == sizeof(v) );+		t( *(uint32_t*)vp == 2 );+		free(vp);+		k++;+	}+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_log_fetch_gte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_begin(db) == 0 );+	k = 4;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 5;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 6;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 4 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 5 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 6 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_log_fetch_lte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_begin(db) == 0 );+	k = 4;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 5;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 6;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPLTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 6 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 5 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 4 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_log_fetch_kgte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_begin(db) == 0 );+	k = 4;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 5;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 6;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPGTE, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 4 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 5 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 6 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_log_fetch_kgt(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_begin(db) == 0 );+	k = 4;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 5;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 6;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 2;+	void *cur = sp_cursor(db, SPGT, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 4 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 5 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 6 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_log_fetch_klte(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_begin(db) == 0 );+	k = 4;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 5;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 6;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 4;+	void *cur = sp_cursor(db, SPLTE, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 4 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_log_fetch_klt(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( sp_begin(db) == 0 );+	k = 4;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 5;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 6;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	k = 4;+	void *cur = sp_cursor(db, SPLT, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+txn_recover_page_log_n_replace(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	t( sp_ctl(env, SPGC, 0) == 0 );+	t( sp_ctl(env, SPMERGE, 0) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 0, v = 1;+	while (k < 12) {+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		if (k > 0 && k % 3 == 0) {+			t( sp_commit(db) == 0 );+			t( sp_ctl(db, SPMERGEFORCE) == 0 );+			t( sp_begin(db) == 0 );+		}+		k++;+	}+	t( sp_commit(db) == 0 );+	k = 0;+	v = 2;+	while (k < 12) {+		t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+		k++;+	}+	t( sp_destroy(db) == 0 );+	t( exists(dbrep, 1, "db") == 1 );+	t( exists(dbrep, 2, "db") == 1 );+	t( exists(dbrep, 3, "db") == 1 );+	t( exists(dbrep, 4, "log") == 1 );+	db = sp_open(env);+	t( db != NULL );+	k = 0;+	while (k < 12) {+		size_t vsize = 0;+		void *vp = NULL;+		t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+		t( vsize == sizeof(v) );+		t( *(uint32_t*)vp == 2 );+		free(vp);+		k++;+	}+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	t( sp_ctl(db, SPMERGEFORCE) == 0 );+	t( exists(dbrep, 5, "db") == 1 );+	t( exists(dbrep, 5, "log") == 0 );+	t( exists(dbrep, 5, "log.incomplete") == 0 );+	t( exists(dbrep, 6, "log.incomplete") == 1 );+	k = 0;+	while (k < 12) {+		size_t vsize = 0;+		void *vp = NULL;+		t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+		t( vsize == sizeof(v) );+		t( *(uint32_t*)vp == 2 );+		free(vp);+		k++;+	}+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++int+main(int argc, char *argv[])+{+	rmrf(dbrep);++	test(recover_log_set_get);+	test(recover_log_replace_get);+	test(recover_log_set_get_replace_get);+	test(recover_log_delete_get);+	test(recover_log_delete_set_get);+	test(recover_log_fetch_gte);+	test(recover_log_fetch_lte);+	test(recover_log_fetch_kgte);+	test(recover_log_fetch_kgt);+	test(recover_log_fetch_klte);+	test(recover_log_fetch_klt);+	test(recover_log_n_get);+	test(recover_log_n_replace);++	test(recover_page_set_get);+	test(recover_page_replace_get);+	test(recover_page_set_get_replace_get);+	test(recover_page_delete_get);+	test(recover_page_delete_set_get);+	test(recover_page_fetch_gte);+	test(recover_page_fetch_lte);+	test(recover_page_fetch_kgte);+	test(recover_page_fetch_kgt);+	test(recover_page_fetch_klte);+	test(recover_page_fetch_klt);+	test(recover_page_n_get);+	test(recover_page_n_replace);++	test(recover_page_log_fetch_gte);+	test(recover_page_log_fetch_lte);+	test(recover_page_log_fetch_kgte);+	test(recover_page_log_fetch_kgt);+	test(recover_page_log_fetch_klte);+	test(recover_page_log_fetch_klt);+	test(recover_page_log_n_replace);++	test(txn_recover_log_set_get);+	test(txn_recover_log_replace_get);+	test(txn_recover_log_set_get_replace_get);+	test(txn_recover_log_delete_get);+	test(txn_recover_log_delete_set_get);+	test(txn_recover_log_fetch_gte);+	test(txn_recover_log_fetch_lte);+	test(txn_recover_log_fetch_kgte);+	test(txn_recover_log_fetch_kgt);+	test(txn_recover_log_fetch_klte);+	test(txn_recover_log_fetch_klt);+	test(txn_recover_log_n_get);+	test(txn_recover_log_n_replace);++	test(txn_recover_page_set_get);+	test(txn_recover_page_replace_get);+	test(txn_recover_page_set_get_replace_get);+	test(txn_recover_page_delete_get);+	test(txn_recover_page_delete_set_get);+	test(txn_recover_page_fetch_gte);+	test(txn_recover_page_fetch_lte);+	test(txn_recover_page_fetch_kgte);+	test(txn_recover_page_fetch_kgt);+	test(txn_recover_page_fetch_klte);+	test(txn_recover_page_fetch_klt);+	test(txn_recover_page_n_get);+	test(txn_recover_page_n_replace);++	test(txn_recover_page_log_fetch_gte);+	test(txn_recover_page_log_fetch_lte);+	test(txn_recover_page_log_fetch_kgte);+	test(txn_recover_page_log_fetch_kgt);+	test(txn_recover_page_log_fetch_klte);+	test(txn_recover_page_log_fetch_klt);+	test(txn_recover_page_log_n_replace); 	return 0; }
+ sophia/test/transaction.c view
@@ -0,0 +1,1015 @@++/*+ * sophia database+ * sphia.org+ *+ * Copyright (c) Dmitry Simonenko+ * BSD License+*/++#include <sophia.h>+#include "test.h"++static char *dbrep = "./rep";++static inline int+cmp(char *a, size_t asz, char *b, size_t bsz, void *arg) {+	register uint32_t av = *(uint32_t*)a;+	register uint32_t bv = *(uint32_t*)b;+	if (av == bv)+		return 0;+	return (av > bv) ? 1 : -1;+}++static void+begin(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_begin(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	t( sp_begin(db) == -1 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_commit(db) == -1 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+rollback(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_rollback(db) == -1 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_commit_empty(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_rollback_empty(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_commit_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_commit(db) == 0 );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_get_commit_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	size_t vsize = 0;+	void *vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	t( sp_commit(db) == 0 );+	vsize = 0;+	vp = NULL;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k );+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_rollback(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_rollback(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_rollback_get(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_rollback(db) == 0 );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_rollback_consistent(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_begin(db) == 0 );+	k = 1;+	uint32_t v = k + 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	k = 2;+	v = k + 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);+	k = 3;+	v = k + 1;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0);++	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k + 1);+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k + 1);+	free(vp);+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k + 1);+	free(vp);+	t( sp_rollback(db) == 0 );++	vsize = 0;+	vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k);+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k);+	free(vp);+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k);+	free(vp);++	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_commit_2x(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_begin(db) == 0 );+	k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+rollback_on_close(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+rollback_recover(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_rollback(db) == 0);+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+commit_recover(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 2;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	k = 3;+	t( sp_set(db, &k, sizeof(k), &k, sizeof(k)) == 0);+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	db = sp_open(env);+	t( db != NULL );+	size_t vsize = 0;+	void *vp = NULL;+	k = 1;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k);+	free(vp);+	k = 2;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k);+	free(vp);+	k = 3;+	t( sp_get(db, &k, sizeof(k), &vp, &vsize) == 1 );+	t( vsize == sizeof(k) );+	t( *(uint32_t*)vp == k);+	free(vp);+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+cursor_begin(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_begin(db) == -1 );+	t( sp_commit(db) == -1 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+cursor_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_commit(db) == -1 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+cursor_closed_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+cursor_rollback(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_rollback(db) == -1 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	rmrf(dbrep);+}++static void+begin_fetch_gte_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_gt_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	void *cur = sp_cursor(db, SPGT, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_lte_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	void *cur = sp_cursor(db, SPLTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_lt_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	void *cur = sp_cursor(db, SPLT, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_kgte_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );++	k = 2;+	void *cur = sp_cursor(db, SPGTE, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_kgt_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	void *cur = sp_cursor(db, SPGT, &k, sizeof(k));+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_klte_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	void *cur = sp_cursor(db, SPLTE, &k, sizeof(k) );+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_klt_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	void *cur = sp_cursor(db, SPLT, &k, sizeof(k) );+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_after_end_commit(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_keysize(cur) == 0 );+	t( sp_key(cur) == NULL );+	t( sp_valuesize(cur) == 0 );+	t( sp_value(cur) == NULL );+	t( sp_destroy(cur) == 0 );+	t( sp_commit(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_fetch_gte_rollback(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_rollback(db) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_commit_fetch(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_commit(db) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 1 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 2 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 1 );+	t( *(uint32_t*)sp_key(cur) == 3 );+	t( sp_keysize(cur) == sizeof(k) );+	t( *(uint32_t*)sp_value(cur) == 2 );+	t( sp_valuesize(cur) == sizeof(v) );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++static void+begin_rollback_fetch(void) {+	void *env = sp_env();+	t( env != NULL );+	t( sp_ctl(env, SPDIR, SPO_CREAT|SPO_RDWR, dbrep) == 0 );+	t( sp_ctl(env, SPCMP, cmp, NULL) == 0 );+	void *db = sp_open(env);+	t( db != NULL );+	t( sp_begin(db) == 0 );+	uint32_t k = 1, v = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 2;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	k = 3;+	t( sp_set(db, &k, sizeof(k), &v, sizeof(v)) == 0 );+	t( sp_rollback(db) == 0 );+	void *cur = sp_cursor(db, SPGTE, NULL, 0);+	t( cur != NULL );+	t( sp_fetch(cur) == 0 );+	t( sp_fetch(cur) == 0 );+	t( sp_destroy(cur) == 0 );+	t( sp_destroy(db) == 0 );+	t( sp_destroy(env) == 0 );+	t( rmrf(dbrep) == 0 );+}++int+main(int argc, char *argv[])+{+	rmrf(dbrep);++	test(begin);+	test(begin_begin);+	test(begin);+	test(commit);+	test(rollback);+	test(begin_commit_empty);+	test(begin_rollback_empty);+	test(begin_commit);+	test(begin_commit_get);+	test(begin_get_commit_get);+	test(begin_rollback);+	test(begin_rollback_get);+	test(begin_rollback_consistent);+	test(begin_commit_2x);+	test(rollback_on_close);+	test(rollback_recover);+	test(commit_recover);+	test(cursor_begin);+	test(cursor_commit);+	test(cursor_closed_commit);+	test(cursor_rollback);+	test(begin_fetch_gte_commit);+	test(begin_fetch_gt_commit);+	test(begin_fetch_lte_commit);+	test(begin_fetch_lt_commit);+	test(begin_fetch_kgte_commit);+	test(begin_fetch_kgt_commit);+	test(begin_fetch_klte_commit);+	test(begin_fetch_klt_commit);+	test(begin_fetch_after_end_commit);+	test(begin_fetch_gte_rollback);+	test(begin_commit_fetch);+	test(begin_rollback_fetch);+	return 0;+}