Initial checkin of tripledb, the 'triples' database engine.

This commit is contained in:
terry%mozilla.org 1999-07-06 23:36:39 +00:00
Родитель 5180239f72
Коммит b9869ae526
20 изменённых файлов: 5848 добавлений и 0 удалений

553
db/tripledb/add.c Normal file
Просмотреть файл

@ -0,0 +1,553 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Routines that add or remove things to the database. */
#include "tdbtypes.h"
#ifdef DEBUG
#include "tdbdebug.h"
#include "prprf.h"
static PRBool makedots = PR_FALSE; /* If true, print out dot-graphs of
every step of balancing, to help my
poor mind debug. */
static int dotcount = 0;
#endif
PRStatus TDBAdd(TDB* db, TDBNodePtr triple[3])
{
PRStatus status = PR_FAILURE;
TDBRecord* record;
TDBPtr position;
PRInt32 tree;
PRInt32 i;
PRInt64 cmp;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
PR_Lock(db->mutex);
/* First, see if we already have this triple around... */
tree = 0; /* Hard-coded knowledge that tree zero does
things in [0], [1], [2] order. ### */
position = db->roots[tree];
while (position) {
record = tdbLoadRecord(db, position);
PR_ASSERT(record);
if (!record) {
goto DONE;
}
PR_ASSERT(record->position == position);
if (record->position != position) {
goto DONE;
}
for (i=0 ; i<3 ; i++) {
cmp = tdbCompareNodes(triple[i], record->data[i]);
if (cmp < 0) {
position = record->entry[tree].child[0];
break;
} else if (cmp > 0) {
position = record->entry[tree].child[1];
break;
}
}
if (position == record->position) {
/* This means that our new entry exactly matches this one. So,
we're done. */
status = PR_SUCCESS;
goto DONE;
}
}
tdbThrowOutCursorCaches(db);
record = tdbAllocateRecord(db, triple);
if (record == NULL) {
goto DONE;
}
for (tree=0 ; tree<NUMTREES ; tree++) {
status = tdbAddToTree(db, record, tree);
if (status != PR_SUCCESS) goto DONE;
}
status = tdbQueueMatchingCallbacks(db, record, TDBACTION_ADDED);
DONE:
if (status == PR_SUCCESS) {
tdbFlush(db);
} else {
tdbThrowAwayUnflushedChanges(db);
}
PR_Unlock(db->mutex);
return status;
}
PRStatus TDBReplace(TDB* db, TDBNodePtr triple[3])
{
/* Write me correctly!!! This works, but is inefficient. ### */
PRStatus status;
TDBNodeRange range[3];
range[0].min = triple[0];
range[0].max = triple[0];
range[1].min = triple[1];
range[1].max = triple[1];
range[2].min = NULL;
range[2].max = NULL;
status = TDBRemove(db, range);
if (status == PR_SUCCESS) {
status = TDBAdd(db, triple);
}
return status;
}
PRStatus TDBRemove(TDB* db, TDBNodeRange range[3])
{
/* This could definitely be faster. We're querying the database for
a matching item, then we go search for it again when we delete it.
The two operations probably ought to be merged. ### */
PRStatus status;
TDBCursor* cursor;
TDBTriple* triple;
TDBPtr position;
TDBRecord* record;
PRInt32 tree;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
PR_Lock(db->mutex);
tdbThrowOutCursorCaches(db);
for (;;) {
cursor = tdbQueryNolock(db, range, NULL);
if (!cursor) goto FAIL;
triple = tdbGetResultNolock(cursor);
if (triple) {
/* Probably ought to play refcnt games with this to prevent it
from being removed from the cache. ### */
position = cursor->lasthit->position;
}
tdbFreeCursorNolock(cursor);
cursor = NULL;
if (!triple) {
/* No more hits; all done. */
break;
}
record = tdbLoadRecord(db, position);
if (!record) goto FAIL;
for (tree=0 ; tree<NUMTREES ; tree++) {
status = tdbRemoveFromTree(db, record, tree);
if (status != PR_SUCCESS) goto FAIL;
}
status = tdbAddToTree(db, record, -1);
if (status != PR_SUCCESS) goto FAIL;
status = tdbQueueMatchingCallbacks(db, record, TDBACTION_REMOVED);
if (status != PR_SUCCESS) goto FAIL;
tdbFlush(db);
}
PR_Unlock(db->mutex);
return PR_SUCCESS;
FAIL:
tdbThrowAwayUnflushedChanges(db);
PR_Unlock(db->mutex);
return PR_FAILURE;
}
#ifdef MIN
#undef MIN
#endif
#ifdef MAX
#undef MAX
#endif
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
static PRBool
rotateOnce(TDB* db, TDBPtr* rootptr, TDBRecord* oldroot, PRInt32 tree,
PRInt32 dir)
{
PRInt32 otherdir = 1 - dir;
PRBool heightChanged;
TDBPtr otherptr;
TDBRecord* other;
PRInt8 oldrootbal;
PRInt8 otherbal;
#ifdef DEBUG
if (makedots) {
char* filename;
filename = PR_smprintf("/tmp/balance%d-%d.dot", tree, dotcount++);
TDBMakeDotGraph(db, filename, tree);
PR_smprintf_free(filename);
}
#endif
PR_ASSERT(dir == 0 || dir == 1);
if (dir != 0 && dir != 1) return PR_FALSE;
otherptr = oldroot->entry[tree].child[otherdir];
if (otherptr == 0) {
tdbMarkCorrupted(db);
return PR_FALSE;
}
other = tdbLoadRecord(db, otherptr);
heightChanged = (other->entry[tree].balance != 0);
*rootptr = otherptr;
oldroot->entry[tree].child[otherdir] = other->entry[tree].child[dir];
other->entry[tree].child[dir] = oldroot->position;
/* update balances */
oldrootbal = oldroot->entry[tree].balance;
otherbal = other->entry[tree].balance;
if (dir == 0) {
oldrootbal -= 1 + MAX(otherbal, 0);
otherbal -= 1 - MIN(oldrootbal, 0);
} else {
oldrootbal += 1 - MIN(otherbal, 0);
otherbal += 1 + MAX(oldrootbal, 0);
}
oldroot->entry[tree].balance = oldrootbal;
other->entry[tree].balance = otherbal;
oldroot->dirty = PR_TRUE;
other->dirty = PR_TRUE;
return heightChanged;
}
static void
rotateTwice(TDB* db, TDBPtr* rootptr, TDBRecord* oldroot, PRInt32 tree,
PRInt32 dir)
{
PRInt32 otherdir = 1 - dir;
TDBRecord* child;
PR_ASSERT(dir == 0 || dir == 1);
if (dir != 0 && dir != 1) return;
child = tdbLoadRecord(db, oldroot->entry[tree].child[otherdir]);
PR_ASSERT(child);
if (child == NULL) return;
rotateOnce(db, &(oldroot->entry[tree].child[otherdir]), child, tree,
otherdir);
oldroot->dirty = PR_TRUE;
rotateOnce(db, rootptr, oldroot, tree, dir);
}
static PRStatus
balance(TDB* db, TDBPtr* rootptr, TDBRecord* oldroot, PRInt32 tree,
PRBool* heightchange)
{
PRInt8 oldbalance;
TDBRecord* child;
*heightchange = PR_FALSE;
oldbalance = oldroot->entry[tree].balance;
if (oldbalance < -1) { /* need a right rotation */
child = tdbLoadRecord(db, oldroot->entry[tree].child[0]);
PR_ASSERT(child);
if (child == NULL) return PR_FAILURE;
if (child->entry[tree].balance == 1) {
rotateTwice(db, rootptr, oldroot, tree, 1); /* double RL rotation
needed */
*heightchange = PR_TRUE;
} else { /* single RR rotation needed */
*heightchange = rotateOnce(db, rootptr, oldroot, tree, 1);
}
} else if (oldbalance > 1) { /* need a left rotation */
child = tdbLoadRecord(db, oldroot->entry[tree].child[1]);
PR_ASSERT(child);
if (child == NULL) return PR_FAILURE;
if (child->entry[tree].balance == -1) {
rotateTwice(db, rootptr, oldroot, tree, 0); /* double LR rotation
needed */
*heightchange = PR_TRUE;
} else { /* single LL rotation needed */
*heightchange = rotateOnce(db, rootptr, oldroot, tree, 0);
}
}
return PR_SUCCESS;
}
static PRStatus doAdd(TDB* db, TDBRecord* record, PRInt32 tree,
PRInt32 comparerule, TDBPtr* rootptr,
PRBool* heightchange)
{
PRBool increase = PR_FALSE;
PRInt64 cmp;
PRInt32 kid;
PRStatus status;
TDBRecord* cur;
TDBPtr origptr;
if (*rootptr == 0) {
*rootptr = record->position;
*heightchange = PR_TRUE;
return PR_SUCCESS;
}
cur = tdbLoadRecord(db, *rootptr);
if (!cur) return PR_FAILURE;
cmp = tdbCompareRecords(record, cur, comparerule);
PR_ASSERT(cmp != 0); /* We carefully should never insert a
record that we already have. */
if (cmp == 0) return PR_FAILURE;
kid = (cmp < 0) ? 0 : 1;
origptr = cur->entry[tree].child[kid];
status = doAdd(db, record, tree, comparerule,
&(cur->entry[tree].child[kid]), &increase);
if (origptr != cur->entry[tree].child[kid]) {
cur->dirty = PR_TRUE;
}
if (increase) {
cur->entry[tree].balance += (kid == 0 ? -1 : 1);
cur->dirty = PR_TRUE;
}
if (status != PR_SUCCESS) return status;
if (increase && cur->entry[tree].balance != 0) {
status = balance(db, rootptr, cur, tree, &increase);
*heightchange = ! increase; /* If we did a rotate that absorbed
the height change, then we don't want
to propagate it on up. */
}
return PR_SUCCESS;
}
PRStatus tdbAddToTree(TDB* db, TDBRecord* record, PRInt32 tree)
{
TDBTreeEntry* entry;
PRBool checklinks;
PRBool ignore;
PRStatus status = PR_SUCCESS;
TDBPtr* rootptr;
TDBPtr origroot;
PRInt32 comparerule = tree;
PR_ASSERT(record != NULL);
if (record == NULL) return PR_FAILURE;
if (tree == -1) {
tree = 0;
rootptr = &(db->freeroot);
} else {
PR_ASSERT(tree >= 0 && tree < NUMTREES);
if (tree < 0 || tree >= NUMTREES) {
return PR_FAILURE;
}
rootptr = &(db->roots[tree]);
}
/* Check that this record does not seem to be already in this tree. */
entry = record->entry + tree;
checklinks = (entry->child[0] == 0 &&
entry->child[1] == 0 &&
entry->balance == 0);
PR_ASSERT(checklinks);
if (!checklinks) return PR_FAILURE;
origroot = *rootptr;
if (origroot == 0) {
*rootptr = record->position;
} else {
status = doAdd(db, record, tree, comparerule, rootptr, &ignore);
}
if (origroot != *rootptr) {
db->rootdirty = PR_TRUE;
}
db->dirty = PR_TRUE;
return status;
}
static PRStatus doRemove(TDB* db, TDBRecord* record, PRInt32 tree,
PRInt32 comparerule, TDBPtr* rootptr,
TDBPtr** foundleafptr, TDBRecord** foundleaf,
PRBool* heightchange)
{
PRStatus status;
TDBRecord* cur;
TDBPtr* leafptr;
TDBRecord* leaf;
PRInt32 kid;
TDBPtr origptr;
TDBPtr kid0;
TDBPtr kid1;
PRInt64 cmp;
PRBool decrease = PR_FALSE;
PRInt32 tmp;
PRInt8 tmpbal;
PRInt32 i;
PR_ASSERT(*rootptr != 0);
if (*rootptr == 0) return PR_FAILURE;
cur = tdbLoadRecord(db, *rootptr);
if (!cur) return PR_FAILURE;
if (record == NULL) {
cur->dirty = PR_TRUE; /* Oh, what a bad, bad hack. Need a better
way to make sure not to miss a parent
pointer that we've changed. ### */
}
kid0 = cur->entry[tree].child[0];
kid1 = cur->entry[tree].child[1];
if (record == NULL) {
/* We're looking for the smallest possible leaf node. */
PR_ASSERT(foundleafptr != NULL && foundleaf != NULL);
if (kid0) cmp = -1;
else {
cmp = 0;
*foundleafptr = rootptr;
*foundleaf = cur;
}
} else {
PR_ASSERT(foundleafptr == NULL && foundleaf == NULL);
cmp = tdbCompareRecords(record, cur, comparerule);
}
if (cmp == 0) {
PR_ASSERT(record == cur || record == NULL);
if (record != cur && record != NULL) return PR_FAILURE;
if (kid0 == 0 && kid1 == 0) {
/* We're a leaf node. */
*rootptr = 0;
*heightchange = PR_TRUE;
return PR_SUCCESS;
} else if (kid0 == 0 || kid1 == 0) {
/* Replace us with our single child. */
*rootptr = kid0 != 0 ? kid0 : kid1;
cur->entry[tree].child[0] = 0;
cur->entry[tree].child[1] = 0;
cur->entry[tree].balance = 0;
cur->dirty = PR_TRUE;
*heightchange = PR_TRUE;
return PR_SUCCESS;
} else {
/* Ick. We're a node in the middle of the tree. Find who to
replace us with. */
kid = 1; /* Informs balancing code later that we are
taking things from the right subtree. */
status = doRemove(db, NULL, tree, comparerule,
&(cur->entry[tree].child[1]),
&leafptr, &leaf, &decrease);
/* Swap us in the tree with leaf. Don't use the kid0/kid1
variables any more, as they may no longer be valid. */
if (record != NULL) {
leaf->entry[tree].child[0] = cur->entry[tree].child[0];
leaf->entry[tree].child[1] = cur->entry[tree].child[1];
leaf->entry[tree].balance = cur->entry[tree].balance;
cur->entry[tree].child[0] = 0;
cur->entry[tree].child[1] = 0;
cur->entry[tree].balance = 0;
cur->dirty = PR_TRUE;
*rootptr = leaf->position;
cur = leaf;
cur->dirty = PR_TRUE;
} else {
*foundleaf = cur;
*foundleafptr = leafptr;
PR_ASSERT(**foundleafptr == leaf->position);
*leafptr = cur->position;
*rootptr = leaf->position;
for (i=0 ; i<2 ; i++) {
tmp = leaf->entry[tree].child[i];
leaf->entry[tree].child[i] = cur->entry[tree].child[i];
cur->entry[tree].child[i] = tmp;
}
tmpbal = leaf->entry[tree].balance;
leaf->entry[tree].balance = cur->entry[tree].balance;
cur->entry[tree].balance = tmpbal;
cur->dirty = PR_TRUE;
leaf->dirty = PR_TRUE;
}
}
} else {
kid = (cmp < 0) ? 0 : 1;
origptr = cur->entry[tree].child[kid];
status = doRemove(db, record, tree, comparerule,
&(cur->entry[tree].child[kid]), foundleafptr,
foundleaf, &decrease);
if (origptr != cur->entry[tree].child[kid]) {
cur->dirty = PR_TRUE;
}
}
if (decrease) {
cur->entry[tree].balance += (kid == 0 ? 1 : -1);
cur->dirty = PR_TRUE;
}
if (status != PR_SUCCESS) return status;
if (decrease) {
if (cur->entry[tree].balance != 0) {
status = balance(db, rootptr, cur, tree, &decrease);
*heightchange = decrease;
} else {
*heightchange = PR_TRUE;
}
}
return PR_SUCCESS;
}
PRStatus tdbRemoveFromTree(TDB* db, TDBRecord* record, PRInt32 tree)
{
PRStatus status;
PRInt32 comparerule = tree;
TDBPtr* rootptr;
TDBPtr origroot;
PRBool ignore;
PR_ASSERT(record != NULL);
if (record == NULL) return PR_FAILURE;
if (tree == -1) {
tree = 0;
rootptr = &(db->freeroot);
} else {
PR_ASSERT(tree >= 0 && tree < NUMTREES);
if (tree < 0 || tree >= NUMTREES) {
return PR_FAILURE;
}
rootptr = &(db->roots[tree]);
}
origroot = *rootptr;
PR_ASSERT(origroot != 0);
if (origroot == 0) return PR_FAILURE;
status = doRemove(db, record, tree, comparerule, rootptr, NULL, NULL,
&ignore);
if (origroot != *rootptr) {
db->rootdirty = PR_TRUE;
}
db->dirty = PR_TRUE;
record->dirty = PR_TRUE;
return status;
}

212
db/tripledb/callback.c Normal file
Просмотреть файл

@ -0,0 +1,212 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Routines that query things from the database. */
#include "tdbtypes.h"
static void
tdbFreeCallbackInfo(TDBCallbackInfo* info)
{
PRInt32 i;
for (i=0 ; i<3 ; i++) {
if (info->range[i].min != NULL) TDBFreeNode(info->range[i].min);
if (info->range[i].max != NULL) TDBFreeNode(info->range[i].max);
}
PR_Free(info);
}
static void
tdbFreePendingCall(TDBPendingCall* call)
{
PRInt32 i;
for (i=0 ; i<3 ; i++) {
if (call->triple.data[i] != NULL) {
TDBFreeNode(call->triple.data[i]);
}
}
PR_Free(call);
}
void
tdbCallbackThread(void* closure)
{
PRStatus status;
TDB* db = (TDB*) closure;
PRLock* mutex = db->mutex;
PRCondVar* cvar = db->callbackcvargo;
TDBPendingCall* call;
TDBPendingCall* tmp;
PR_Lock(mutex);
while (1) {
while (db->firstpendingcall == NULL) {
db->callbackidle = PR_TRUE;
PR_NotifyAllCondVar(db->callbackcvaridle); /* Inform anyone who
cares that we are
idle. */
if (db->killcallbackthread) {
/* This db is being closed; go away now. */
PR_Unlock(db->mutex);
return;
}
status = PR_WaitCondVar(cvar, PR_INTERVAL_NO_TIMEOUT);
db->callbackidle = PR_FALSE;
}
call = db->firstpendingcall;
db->firstpendingcall = NULL;
db->lastpendingcall = NULL;
PR_Unlock(db->mutex);
while (call) {
(*call->func)(db, call->closure, &(call->triple), call->action);
tmp = call;
call = call->next;
tdbFreePendingCall(tmp);
}
PR_Lock(db->mutex);
}
}
PRStatus tdbQueueMatchingCallbacks(TDB* db, TDBRecord* record,
PRInt32 action)
{
TDBCallbackInfo* info;
TDBPendingCall* call;
PRInt32 i;
for (info = db->firstcallback ; info ; info = info->nextcallback) {
if (tdbMatchesRange(record, info->range)) {
call = PR_NEWZAP(TDBPendingCall);
if (!call) return PR_FAILURE;
call->func = info->func;
call->closure = info->closure;
call->action = action;
for (i=0 ; i<3 ; i++) {
call->triple.data[i] = tdbNodeDup(record->data[i]);
if (call->triple.data[i] == NULL) {
tdbFreePendingCall(call);
return PR_FAILURE;
}
}
if (db->lastpendingcall) {
db->lastpendingcall->next = call;
}
db->lastpendingcall = call;
if (db->firstpendingcall == NULL) {
db->firstpendingcall = call;
}
}
}
if (db->firstpendingcall != NULL) {
/* Kick the background thread. */
PR_NotifyAllCondVar(db->callbackcvargo);
}
return PR_SUCCESS;
}
PRStatus TDBAddCallback(TDB* db, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure)
{
TDBCallbackInfo* info;
PRInt32 i;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
info = PR_NEWZAP(TDBCallbackInfo);
if (!info) return PR_FAILURE;
for (i=0 ; i<3 ; i++) {
if (range[i].min) {
info->range[i].min = tdbNodeDup(range[i].min);
if (info->range[i].min == NULL) goto FAIL;
}
if (range[i].max) {
info->range[i].max = tdbNodeDup(range[i].max);
if (info->range[i].max == NULL) goto FAIL;
}
}
info->func = func;
info->closure = closure;
info->nextcallback = db->firstcallback;
PR_Lock(db->mutex);
db->firstcallback = info;
PR_Unlock(db->mutex);
return PR_SUCCESS;
FAIL:
tdbFreeCallbackInfo(info);
return PR_FAILURE;
}
PRStatus TDBRemoveCallback(TDB* db, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure)
{
PRStatus status = PR_FAILURE;
TDBCallbackInfo** ptr;
TDBCallbackInfo* info;
PRInt32 i;
PRBool match;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
PR_Lock(db->mutex);
for (ptr = &(db->firstcallback) ; *ptr ; ptr = &((*ptr)->nextcallback)) {
info = *ptr;
if (info->func == func && info->closure == closure) {
match = PR_TRUE;
for (i=0 ; i<3 ; i++) {
if (range[i].min != info->range[i].min &&
(range[i].min == NULL || info->range[i].min == NULL ||
tdbCompareNodes(range[i].min, info->range[i].min) != 0)) {
match = PR_FALSE;
break;
}
if (range[i].max != info->range[i].max &&
(range[i].max == NULL || info->range[i].max == NULL ||
tdbCompareNodes(range[i].max, info->range[i].max) != 0)) {
match = PR_FALSE;
break;
}
}
if (match) {
*ptr = info->nextcallback;
tdbFreeCallbackInfo(info);
status = PR_SUCCESS;
/* We now make sure that we have no outstanding callbacks
queued up to the callback we just removed. It would be
real bad to call that callback after we return. So, we
make sure to call it now. */
while (db->firstpendingcall) {
PR_NotifyAllCondVar(db->callbackcvargo);
PR_WaitCondVar(db->callbackcvaridle,
PR_INTERVAL_NO_TIMEOUT);
}
break;
}
}
}
PR_Unlock(db->mutex);
return status;
}

300
db/tripledb/debug.c Normal file
Просмотреть файл

@ -0,0 +1,300 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Various debugging routines. */
#include "tdbtypes.h"
#include "tdbdebug.h"
#include "prprf.h"
void TDBDumpNode(PRFileDesc* fid, TDBNode* node)
{
switch(node->type) {
case TDBTYPE_STRING:
PR_fprintf(fid, "%s", node->d.str.string);
break;
default:
PR_fprintf(fid, "%ld", node->d.i);
}
}
void TDBDumpRecord(PRFileDesc* fid, TDB* db, TDBPtr ptr, PRInt32 tree, int indent, const char* kidname)
{
TDBRecord* record;
int i;
TDBPtr kid0;
TDBPtr kid1;
if (ptr == 0) return;
record = tdbLoadRecord(db, ptr);
for (i=0 ; i<indent ; i++) {
PR_fprintf(fid, " ");
}
PR_fprintf(fid, "[%s] pos=%d, len=%d, bal=%d ", kidname, record->position,
record->length, record->entry[tree].balance);
TDBDumpNode(fid, record->data[0]);
PR_fprintf(fid, ",");
TDBDumpNode(fid, record->data[1]);
PR_fprintf(fid, ",");
TDBDumpNode(fid, record->data[2]);
PR_fprintf(fid, "\n");
kid0 = record->entry[tree].child[0];
kid1 = record->entry[tree].child[1];
tdbFlush(db);
TDBDumpRecord(fid, db, kid0, tree, indent + 1, "0");
TDBDumpRecord(fid, db, kid1, tree, indent + 1, "1");
}
void TDBDumpTree(PRFileDesc* fid, TDB* db, PRInt32 tree)
{
TDBPtr root;
if (tree < 0) {
root = db->freeroot;
tree = 0;
} else {
root = db->roots[tree];
}
TDBDumpRecord(fid, db, root, tree, 0, "root");
}
typedef struct _TDBRangeSet {
PRInt32 position;
PRInt32 length;
struct _TDBRangeSet* next;
struct _TDBRangeSet* prev;
} TDBRangeSet;
static PRStatus checkMerges(TDBRangeSet* set, TDBRangeSet* tmp)
{
while (tmp->next != set &&
tmp->position + tmp->length == tmp->next->position) {
TDBRangeSet* t = tmp->next;
tmp->next = t->next;
tmp->next->prev = tmp;
tmp->length += t->length;
PR_Free(t);
}
while (tmp->prev != set &&
tmp->position == tmp->prev->position + tmp->prev->length) {
TDBRangeSet* t = tmp->prev;
tmp->prev = t->prev;
tmp->prev->next = tmp;
tmp->position = t->position;
t->length += t->length;
PR_Free(t);
}
return PR_SUCCESS;
}
static PRStatus addRange(TDBRangeSet* set, PRInt32 position, PRInt32 length)
{
TDBRangeSet* tmp;
TDBRangeSet* this;
for (tmp = set->next ; tmp != set ; tmp = tmp->next) {
if ((position >= tmp->position &&
position < tmp->position + tmp->length) ||
(tmp->position >= position &&
tmp->position < position + length)) {
PR_ASSERT(0); /* Collision! */
return PR_FAILURE;
}
if (position + length == tmp->position) {
tmp->position = position;
tmp->length += length;
return checkMerges(set, tmp);
} else if (tmp->position + tmp->length == position) {
tmp->length += length;
return checkMerges(set, tmp);
}
if (tmp->position > position + length) {
break;
}
}
this = PR_NEWZAP(TDBRangeSet);
if (!this) return PR_FAILURE;
this->next = tmp;
this->prev = tmp->prev;
this->next->prev = this;
this->prev->next = this;
this->position = position;
this->length = length;
return PR_SUCCESS;
}
typedef struct _TDBTreeInfo {
TDB* db;
PRInt32 tree; /* Which tree we're analyzing */
PRInt32 comparerule;
PRInt32 count; /* Number of nodes in tree. */
TDBRangeSet set; /* Range of bytes used by tree. */
PRInt32 maxdepth; /* Maximum depth of tree. */
} TDBTreeInfo;
static PRStatus checkTree(TDBTreeInfo* info, TDBPtr ptr, int depth,
int* maxdepth, TDBRecord* parent, int kid)
{
TDBRecord* record;
PRStatus status;
TDBPtr kid0;
TDBPtr kid1;
int d0 = depth;
int d1 = depth;
PRInt8 bal;
TDBRecord keep;
PRInt64 cmp;
int i;
if (ptr == 0) return PR_SUCCESS;
info->count++;
if (*maxdepth < depth) {
*maxdepth = depth;
}
record = tdbLoadRecord(info->db, ptr);
if (parent) {
cmp = tdbCompareRecords(record, parent, info->comparerule);
PR_ASSERT((kid == 0 && cmp < 0) || (kid == 1 && cmp > 0));
if (! ((kid == 0 && cmp < 0) || (kid == 1 && cmp > 0))) {
return PR_FAILURE;
}
}
status = addRange(&(info->set), record->position, record->length);
if (status != PR_SUCCESS) return status;
bal = record->entry[info->tree].balance;
if (bal < -1 || bal > 1) {
PR_ASSERT(0);
return PR_FAILURE;
}
kid0 = record->entry[info->tree].child[0];
kid1 = record->entry[info->tree].child[1];
memcpy(&keep, record, sizeof(TDBRecord));
for (i=0 ; i<3 ; i++) {
keep.data[i] = tdbNodeDup(keep.data[i]);
}
tdbFlush(info->db);
status = checkTree(info, kid0, depth + 1, &d0, &keep, 0);
if (status != PR_SUCCESS) return status;
status = checkTree(info, kid1, depth + 1, &d1, &keep, 1);
if (status != PR_SUCCESS) return status;
for (i=0 ; i<3 ; i++) {
TDBFreeNode(keep.data[i]);
}
if (d1 - d0 != bal) {
PR_ASSERT(0);
return PR_FAILURE;
}
if (*maxdepth < d0) {
*maxdepth = d0;
}
if (*maxdepth < d1) {
*maxdepth = d1;
}
return PR_SUCCESS;
}
PRStatus TDBSanityCheck(TDB* db, PRFileDesc* fid)
{
TDBTreeInfo info;
TDBRangeSet* tmp;
int i;
for (i=-1 ; i<4 ; i++) {
info.set.next = &(info.set);
info.set.prev = &(info.set);
info.db = db;
info.tree = (i < 0 ? 0 : i);
info.comparerule = i;
info.maxdepth = 0;
info.count = 0;
PR_fprintf(fid, "Checking tree %d...\n", i);
if (checkTree(&info, i < 0 ? db->freeroot : db->roots[i], 0,
&(info.maxdepth), NULL, 0) != PR_SUCCESS) {
PR_fprintf(fid, "Problem found!\n");
return PR_FAILURE;
}
for (tmp = info.set.next; tmp != &(info.set) ; tmp = tmp->next) {
PR_fprintf(fid, " %d - %d (%d)\n",
tmp->position, tmp->position + tmp->length,
tmp->length);
}
PR_fprintf(fid, " maxdepth: %d count %d\n", info.maxdepth,
info.count);
}
return PR_SUCCESS;
}
extern void TDBGetCursorStats(TDBCursor* cursor,
PRInt32* hits,
PRInt32* misses)
{
*hits = cursor->hits;
*misses = cursor->misses;
}
void makeDotEntry(TDB* db, PRFileDesc* fid, TDBPtr ptr, PRInt32 tree)
{
TDBRecord* record;
TDBPtr kid[2];
int i;
record = tdbLoadRecord(db, ptr);
PR_fprintf(fid, "%d [label=\"%d\\n", record->position, record->position);
for (i=0 ; i<3 ; i++) {
TDBDumpNode(fid, record->data[i]);
if (i<2) PR_fprintf(fid, ",");
}
PR_fprintf(fid, "\\nbal=%d\"]\n", record->entry[tree].balance);
kid[0] = record->entry[tree].child[0];
kid[1] = record->entry[tree].child[1];
/* tdbFlush(db); */
for (i=0 ; i<2 ; i++) {
if (kid[i] != 0) {
makeDotEntry(db, fid, kid[i], tree);
PR_fprintf(fid, "%d -> %d [label=\"%d\"]\n", ptr, kid[i], i);
}
}
}
void TDBMakeDotGraph(TDB* db, const char* filename, PRInt32 tree)
{
TDBPtr root;
PRFileDesc* fid;
fid = PR_Open(filename, PR_WRONLY | PR_CREATE_FILE, 0666);
if (tree == -1) {
root = db->freeroot;
tree = 0;
} else {
root = db->roots[tree];
}
PR_fprintf(fid, "digraph G {\n");
makeDotEntry(db, fid, root, tree);
PR_fprintf(fid, "}\n");
PR_Close(fid);
}

185
db/tripledb/node.c Normal file
Просмотреть файл

@ -0,0 +1,185 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Operations that do things on nodes. */
#include "tdbtypes.h"
TDBNodePtr TDBCreateStringNode(const char* string)
{
TDBNode* result;
PRInt32 length;
PR_ASSERT(string != NULL);
if (string == NULL) return NULL;
length = strlen(string);
result = (TDBNode*) PR_Malloc(sizeof(TDBNode) + length + 1);
if (result == NULL) return NULL;
result->type = TDBTYPE_STRING;
result->d.str.length = length;
strcpy(result->d.str.string, string);
return result;
}
TDBNodePtr TDBCreateIntNode(PRInt64 value, PRInt8 type)
{
TDBNode* result;
PR_ASSERT(type >= TDBTYPE_INT8 && type <= TDBTYPE_TIME);
if (type < TDBTYPE_INT8 || type > TDBTYPE_TIME) {
return NULL;
}
result = PR_NEW(TDBNode);
if (result == NULL) return NULL;
result->type = type;
result->d.i = value;
return result;
}
void TDBFreeNode(TDBNode* node)
{
PR_Free(node);
}
TDBNode* tdbNodeDup(TDBNode* node)
{
TDBNode* result;
PRInt32 length;
PR_ASSERT(node != NULL);
if (node == NULL) return NULL;
if (node->type == TDBTYPE_STRING) {
length = node->d.str.length;
result = PR_Malloc(sizeof(TDBNode) + length + 1);
if (result == NULL) return NULL;
result->type = TDBTYPE_STRING;
result->d.str.length = length;
memcpy(result->d.str.string, node->d.str.string, length);
result->d.str.string[length] = '\0';
return result;
}
return TDBCreateIntNode(node->d.i, node->type);
}
PRInt32 tdbNodeSize(TDBNode* node)
{
PR_ASSERT(node != NULL);
if (node == NULL) return 0;
switch (node->type) {
case TDBTYPE_INT8:
return 1 + sizeof(PRInt8);
case TDBTYPE_INT16:
return 1 + sizeof(PRInt16);
case TDBTYPE_INT32:
return 1 + sizeof(PRInt32);
case TDBTYPE_INT64:
return 1 + sizeof(PRInt64);
case TDBTYPE_TIME:
return 1 + sizeof(PRTime);
case TDBTYPE_STRING:
return 1 + sizeof(PRUint16) + node->d.str.length;
default:
PR_ASSERT(0);
return 0;
}
}
PRInt64 tdbCompareNodes(TDBNode* n1, TDBNode* n2)
{
if (n1->type == TDBTYPE_STRING && n2->type == TDBTYPE_STRING) {
return strcmp(n1->d.str.string, n2->d.str.string);
} else if (n1->type != TDBTYPE_STRING && n2->type != TDBTYPE_STRING) {
return n1->d.i - n2->d.i;
} else {
return n1->type - n2->type;
}
}
TDBNode* tdbGetNode(TDB* db, char** ptr)
{
TDBNode* result;
PRInt8 type = tdbGetInt8(ptr);
PRInt64 i;
if (type == TDBTYPE_STRING) {
PRUint16 length = tdbGetUInt16(ptr);
result = (TDBNode*) PR_Malloc(sizeof(TDBNode) + length + 1);
if (result == NULL) {
return NULL;
}
result->type = type;
result->d.str.length = length;
memcpy(result->d.str.string, *ptr, length);
result->d.str.string[length] = '\0';
(*ptr) += length;
} else {
switch(type) {
case TDBTYPE_INT8:
i = tdbGetInt8(ptr);
break;
case TDBTYPE_INT16:
i = tdbGetInt16(ptr);
break;
case TDBTYPE_INT32:
i = tdbGetInt32(ptr);
break;
case TDBTYPE_INT64:
case TDBTYPE_TIME:
i = tdbGetInt64(ptr);
break;
default:
tdbMarkCorrupted(db);
return NULL;
}
result = TDBCreateIntNode(i, type);
}
return result;
}
void tdbPutNode(TDB* db, char** ptr, TDBNode* node)
{
tdbPutInt8(ptr, node->type);
switch (node->type) {
case TDBTYPE_STRING:
tdbPutUInt16(ptr, node->d.str.length);
memcpy(*ptr, node->d.str.string, node->d.str.length);
*ptr += node->d.str.length;
break;
case TDBTYPE_INT8:
tdbPutInt8(ptr, (PRInt8) node->d.i);
break;
case TDBTYPE_INT16:
tdbPutInt16(ptr, (PRInt16) node->d.i);
break;
case TDBTYPE_INT32:
tdbPutInt32(ptr, (PRInt32) node->d.i);
break;
case TDBTYPE_INT64:
case TDBTYPE_TIME:
tdbPutInt64(ptr, node->d.i);
break;
default:
tdbMarkCorrupted(db);
break;
}
}

488
db/tripledb/query.c Normal file
Просмотреть файл

@ -0,0 +1,488 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Routines that query things from the database. */
#include "tdbtypes.h"
/* Argh, this same static array appears in record.c. Don't do that. ### */
static int key[NUMTREES][3] = {
{0, 1, 2},
{1, 0, 2},
{2, 1, 0},
{1, 2, 0}
};
static void
freeParentChain(TDBCursor* cursor)
{
TDBParentChain* tmp;
while (cursor->parent) {
tmp = cursor->parent->next;
cursor->parent->record->refcnt--;
PR_ASSERT(cursor->parent->record->refcnt >= 0);
PR_Free(cursor->parent);
cursor->parent = tmp;
}
}
static PRStatus
moveCursorForward(TDBCursor* cursor)
{
TDBRecord* cur;
PRBool found;
PRInt32 fwd;
PRInt32 rev;
TDBPtr ptr;
PRInt32 tree = cursor->tree;
TDBParentChain* parent;
TDBParentChain* tmp;
cur = cursor->cur;
PR_ASSERT(cur != NULL);
if (cur == NULL) {
return PR_FAILURE;
}
cur->refcnt--;
PR_ASSERT(cur->refcnt >= 0);
cursor->cur = NULL;
fwd = cursor->reverse ? 0 : 1;
rev = cursor->reverse ? 1 : 0;
ptr = cur->entry[tree].child[fwd];
if (ptr != 0) {
do {
cur->refcnt++;
parent = PR_NEWZAP(TDBParentChain);
parent->record = cur;
parent->next = cursor->parent;
cursor->parent = parent;
cur = tdbLoadRecord(cursor->db, ptr);
PR_ASSERT(cur);
if (!cur) {
return PR_FAILURE;
}
ptr = cur->entry[tree].child[rev];
} while (ptr != 0);
} else {
found = PR_FALSE;
while (cursor->parent) {
ptr = cur->position;
cur = cursor->parent->record;
cur->refcnt--;
PR_ASSERT(cur->refcnt >= 0);
tmp = cursor->parent->next;
PR_Free(cursor->parent);
cursor->parent = tmp;
if (cur->entry[tree].child[rev] == ptr) {
found = PR_TRUE;
break;
}
if (cur->entry[tree].child[fwd] != ptr) {
tdbMarkCorrupted(cursor->db);
return PR_FAILURE;
}
}
if (!found) cur = NULL;
}
if (cur) cur->refcnt++;
cursor->cur = cur;
return PR_SUCCESS;
}
static PRStatus findFirstNode(TDBCursor* cursor, TDBNodeRange range[3])
{
PRInt32 fwd;
PRInt32 rev;
PRBool reverse;
PRInt32 tree;
TDBParentChain* parent;
TDBPtr curptr;
TDBRecord* cur;
PRInt64 cmp;
reverse = cursor->reverse;
fwd = reverse ? 0 : 1;
rev = reverse ? 1 : 0;
tree = cursor->tree;
freeParentChain(cursor);
curptr = cursor->db->roots[tree];
cur = NULL;
while (curptr) {
if (cur) {
cur->refcnt++;
parent = PR_NEWZAP(TDBParentChain);
parent->record = cur;
parent->next = cursor->parent;
cursor->parent = parent;
}
cur = tdbLoadRecord(cursor->db, curptr);
PR_ASSERT(cur);
if (!cur) return PR_FAILURE;
cmp = tdbCompareToRange(cur, range, tree);
if (reverse) cmp = -cmp;
if (cmp >= 0) {
curptr = cur->entry[tree].child[rev];
} else {
curptr = cur->entry[tree].child[fwd];
}
}
if (cursor->cur) {
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
}
cursor->cur = cur;
if (cursor->cur) {
cursor->cur->refcnt++;
}
while (cursor->cur != NULL &&
tdbCompareToRange(cursor->cur, range, tree) < 0) {
moveCursorForward(cursor);
}
return PR_SUCCESS;
}
TDBCursor* tdbQueryNolock(TDB* db, TDBNodeRange range[3],
TDBSortSpecification* sortspec)
{
PRInt32 rangescore[3];
PRInt32 tree = -1;
PRInt32 curscore;
PRInt32 bestscore = -1;
PRInt32 i;
TDBCursor* result;
PRBool reverse;
PRStatus status;
result = PR_NEWZAP(TDBCursor);
if (!result) return NULL;
for (i=0 ; i<3 ; i++) {
if (range[i].min) {
result->range[i].min = tdbNodeDup(range[i].min);
if (result->range[i].min == NULL) goto FAIL;
}
if (range[i].max) {
result->range[i].max = tdbNodeDup(range[i].max);
if (result->range[i].max == NULL) goto FAIL;
}
rangescore[i] = 0;
if (range[i].min != NULL || range[i].max != NULL) {
/* Hey, some limitations were specified, we like this key some. */
rangescore[i]++;
if (range[i].min != NULL && range[i].max != NULL) {
/* Ooh, we were given both minimum and maximum, that's better
than only getting one.*/
rangescore[i]++;
if (tdbCompareNodes(range[i].min, range[i].max) == 0) {
/* Say! This key was exactly specified. We like it
best. */
rangescore[i]++;
}
}
}
}
for (i=0 ; i<NUMTREES ; i++) {
curscore = rangescore[key[i][0]] * 100 +
rangescore[key[i][1]] * 10 +
rangescore[key[i][2]];
if (bestscore < curscore) {
bestscore = curscore;
tree = i;
}
}
reverse = sortspec != NULL && sortspec->reverse;
result->reverse = reverse;
result->db = db;
result->tree = tree;
status = findFirstNode(result, range);
if (status != PR_SUCCESS) goto FAIL;
result->nextcursor = db->firstcursor;
if (result->nextcursor) {
result->nextcursor->prevcursor = result;
}
db->firstcursor = result;
tdbFlush(db);
return result;
FAIL:
tdbFreeCursorNolock(result);
return NULL;
}
TDBCursor* TDBQuery(TDB* db, TDBNodeRange range[3],
TDBSortSpecification* sortspec)
{
TDBCursor* result;
PR_Lock(db->mutex);
result = tdbQueryNolock(db, range, sortspec);
PR_Unlock(db->mutex);
return result;
}
PRStatus tdbFreeCursorNolock(TDBCursor* cursor)
{
PRInt32 i;
TDB* db;
PR_ASSERT(cursor);
if (!cursor) return PR_FAILURE;
db = cursor->db;
if (cursor->cur) {
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
}
if (cursor->lasthit) {
cursor->lasthit->refcnt--;
PR_ASSERT(cursor->lasthit->refcnt >= 0);
}
freeParentChain(cursor);
tdbFlush(db);
for (i=0 ; i<3 ; i++) {
PR_FREEIF(cursor->range[i].min);
PR_FREEIF(cursor->range[i].max);
}
if (cursor->prevcursor) {
cursor->prevcursor->nextcursor = cursor->nextcursor;
} else {
db->firstcursor = cursor->nextcursor;
}
if (cursor->nextcursor) {
cursor->nextcursor->prevcursor = cursor->prevcursor;
}
PR_Free(cursor);
return PR_SUCCESS;
}
PRStatus TDBFreeCursor(TDBCursor* cursor)
{
PRStatus status;
TDB* db;
PR_ASSERT(cursor);
if (!cursor) return PR_FAILURE;
db = cursor->db;
PR_Lock(db->mutex);
status = tdbFreeCursorNolock(cursor);
#ifdef DEBUG
if (db->firstcursor == 0) {
/* There are no more cursors. No other thread can be in the middle of
writing stuff, because we have the mutex. And so, there shouldn't
be anything left in our cache of records; they should all be
flushed out. So... */
PR_ASSERT(db->firstrecord == NULL);
}
#endif
PR_Unlock(db->mutex);
return status;
}
TDBTriple* tdbGetResultNolock(TDBCursor* cursor)
{
PRStatus status;
PRInt32 i;
PRInt64 cmp;
TDBNodeRange range[3];
PR_ASSERT(cursor);
if (!cursor) return NULL;
if (cursor->cur == NULL && cursor->lasthit == NULL &&
cursor->triple.data[0] != NULL) {
/* Looks like someone did a write to the database since we last were
here, and therefore threw away all our cached information about
where we were. Go find our place again. */
for (i=0 ; i<3 ; i++) {
range[i].min = cursor->triple.data[i];
range[i].max = NULL;
if (cursor->reverse) {
range[i].max = range[i].min;
range[i].min = NULL;
}
}
status = findFirstNode(cursor, range);
if (status != PR_SUCCESS) return NULL;
if (cursor->cur) {
PRBool match = PR_TRUE;
for (i=0 ; i<3 ; i++) {
if (tdbCompareNodes(cursor->cur->data[i],
cursor->triple.data[i]) != 0) {
match = PR_FALSE;
break;
}
}
if (match) {
/* OK, this node we found was exactly the one we were at
last time. Bump it up one. */
moveCursorForward(cursor);
}
}
}
for (i=0 ; i<3 ; i++) {
PR_FREEIF(cursor->triple.data[i]);
}
if (cursor->lasthit) {
cursor->lasthit->refcnt--;
cursor->lasthit = NULL;
}
if (cursor->cur == NULL) return NULL;
while (cursor->cur != NULL) {
if (tdbMatchesRange(cursor->cur, cursor->range)) {
break;
}
cmp = tdbCompareToRange(cursor->cur, cursor->range, cursor->tree);
if (cursor->reverse ? (cmp < 0) : (cmp > 0)) {
/* We're off the end of the range, all done. */
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
cursor->cur = NULL;
break;
}
cursor->misses++;
moveCursorForward(cursor);
}
if (cursor->cur == NULL) {
tdbFlush(cursor->db);
return NULL;
}
for (i=0 ; i<3 ; i++) {
cursor->triple.data[i] = tdbNodeDup(cursor->cur->data[i]);
}
cursor->lasthit = cursor->cur;
cursor->lasthit->refcnt++;
moveCursorForward(cursor);
cursor->hits++;
#ifdef DEBUG
{
TDBParentChain* tmp;
PR_ASSERT(cursor->cur == NULL || cursor->cur->refcnt > 0);
for (tmp = cursor->parent ; tmp ; tmp = tmp->next) {
PR_ASSERT(tmp->record->refcnt > 0);
}
}
#endif
tdbFlush(cursor->db);
return &(cursor->triple);
}
TDBTriple* TDBGetResult(TDBCursor* cursor)
{
TDBTriple* result;
PR_ASSERT(cursor && cursor->db);
if (!cursor || !cursor->db) return NULL;
PR_Lock(cursor->db->mutex);
result = tdbGetResultNolock(cursor);
PR_Unlock(cursor->db->mutex);
return result;
}
/* Determines where this item falls within the range of items defined.
Negative means this item is too early, and positive means too late.
Zero means that it seems to fall within the range, but you need to do
a call to tdbMatchesRange() to really make sure. The idea here is that
as you slowly walk along the appropriate tree in the DB, this routine
will always return a nondecreasing result. */
PRInt64 tdbCompareToRange(TDBRecord* record, TDBNodeRange* range,
PRInt32 comparerule)
{
int i;
int k;
PR_ASSERT(record != NULL && range != NULL);
if (record == NULL || range == NULL) return PR_FALSE;
PR_ASSERT(comparerule >= 0 && comparerule < NUMTREES);
if (comparerule < 0 || comparerule >= NUMTREES) {
return 0;
}
for (i=0 ; i<3 ; i++) {
k = key[comparerule][i];
if (range[k].min != NULL &&
tdbCompareNodes(record->data[k], range[k].min) < 0) {
return -1;
}
if (range[k].max != NULL &&
tdbCompareNodes(record->data[k], range[k].max) > 0) {
return 1;
}
if (range[k].min == NULL || range[k].max == NULL ||
tdbCompareNodes(range[k].min, range[k].max) != 0) {
return 0;
}
}
return 0;
}
PRBool tdbMatchesRange(TDBRecord* record, TDBNodeRange* range)
{
PRInt32 i;
PR_ASSERT(record != NULL && range != NULL);
if (record == NULL || range == NULL) return PR_FALSE;
for (i=0 ; i<3 ; i++) {
if (range[i].min != NULL &&
tdbCompareNodes(record->data[i], range[i].min) < 0) {
return PR_FALSE;
}
if (range[i].max != NULL &&
tdbCompareNodes(record->data[i], range[i].max) > 0) {
return PR_FALSE;
}
}
return PR_TRUE;
}
void tdbThrowOutCursorCaches(TDB* db)
{
TDBCursor* cursor;
for (cursor = db->firstcursor ; cursor ; cursor = cursor->nextcursor) {
freeParentChain(cursor);
if (cursor->cur) {
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
cursor->cur = NULL;
}
if (cursor->lasthit) {
cursor->lasthit->refcnt--;
PR_ASSERT(cursor->lasthit->refcnt >= 0);
cursor->lasthit = NULL;
}
}
}

241
db/tripledb/record.c Normal file
Просмотреть файл

@ -0,0 +1,241 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Operations that do things on file record. */
#include "tdbtypes.h"
TDBRecord* tdbLoadRecord(TDB* db, TDBPtr position)
{
TDBRecord* result;
char buf[sizeof(PRUint32)];
char* ptr;
PRInt32 i;
PRInt32 num;
PRInt32 numtoread;
for (result = db->firstrecord ; result ; result = result->next) {
if (result->position == position) {
goto DONE;
}
}
if (PR_Seek(db->fid, position, PR_SEEK_SET) < 0) {
goto DONE;
}
num = PR_Read(db->fid, buf, sizeof(buf));
if (num != sizeof(buf)) {
goto DONE;
}
result = PR_NEWZAP(TDBRecord);
if (result == NULL) goto DONE;
result->position = position;
ptr = buf;
result->length = tdbGetInt32(&ptr);
if (result->length < MINRECORDSIZE || result->length > MAXRECORDSIZE) {
tdbMarkCorrupted(db);
PR_Free(result);
result = NULL;
goto DONE;
}
if (tdbGrowIobuf(db, result->length) != PR_SUCCESS) {
PR_Free(result);
result = NULL;
goto DONE;
}
numtoread = result->length - sizeof(PRInt32);
num = PR_Read(db->fid, db->iobuf, numtoread);
if (num < numtoread) {
PR_Free(result);
result = NULL;
goto DONE;
}
ptr = db->iobuf;
for (i=0 ; i<NUMTREES ; i++) {
result->entry[i].child[0] = tdbGetInt32(&ptr);
result->entry[i].child[1] = tdbGetInt32(&ptr);
result->entry[i].balance = tdbGetInt8(&ptr);
}
for (i=0 ; i<3 ; i++) {
result->data[i] = tdbGetNode(db, &ptr);
if (result->data[i] == NULL) {
while (--i >= 0) {
PR_Free(result->data[i]);
}
PR_Free(result);
result = NULL;
goto DONE;
}
}
if (ptr - db->iobuf != numtoread) {
tdbMarkCorrupted(db);
for (i=0 ; i<3 ; i++) {
PR_Free(result->data[i]);
}
PR_Free(result);
result = NULL;
goto DONE;
}
PR_ASSERT(db->firstrecord != result);
result->next = db->firstrecord;
db->firstrecord = result;
DONE:
return result;
}
PRStatus tdbSaveRecord(TDB* db, TDBRecord* record)
{
PRStatus status;
PRInt32 i;
char* ptr;
if (PR_Seek(db->fid, record->position, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
status = tdbGrowIobuf(db, record->length);
if (status != PR_SUCCESS) return status;
ptr = db->iobuf;
tdbPutInt32(&ptr, record->length);
for (i=0 ; i<NUMTREES ; i++) {
tdbPutInt32(&ptr, record->entry[i].child[0]);
tdbPutInt32(&ptr, record->entry[i].child[1]);
tdbPutInt8(&ptr, record->entry[i].balance);
}
for (i=0 ; i<3 ; i++) {
tdbPutNode(db, &ptr, record->data[i]);
}
PR_ASSERT(ptr - db->iobuf == record->length);
if (PR_Write(db->fid, db->iobuf, record->length) != record->length) {
return PR_FAILURE;
}
return PR_SUCCESS;
}
PRStatus tdbFreeRecord(TDBRecord* record)
{
PRInt32 i;
for (i=0 ; i<3 ; i++) {
PR_Free(record->data[i]);
}
PR_Free(record);
return PR_SUCCESS;
}
TDBRecord* tdbAllocateRecord(TDB* db, TDBNodePtr triple[3])
{
PRInt32 i;
PRInt32 size;
TDBRecord* result = NULL;
TDBRecord* tmp;
TDBPtr position;
size = sizeof(PRInt32) +
NUMTREES * (sizeof(PRInt32) + sizeof(PRInt32) + sizeof(PRInt8)) +
tdbNodeSize(triple[0]) +
tdbNodeSize(triple[1]) +
tdbNodeSize(triple[2]);
position = db->freeroot;
if (position > 0) {
do {
result = tdbLoadRecord(db, position);
if (result->length > size) {
position = result->entry[0].child[0];
} else if (result->length < size) {
position = result->entry[0].child[1];
} else {
/* Hey, we found one! */
if (tdbRemoveFromTree(db, result, -1) != PR_SUCCESS) {
tdbMarkCorrupted(db);
return NULL;
}
break;
}
} while (position != 0);
}
if (position == 0) {
result = PR_NEWZAP(TDBRecord);
if (!result) return NULL;
position = db->filelength;
db->filelength += size;
PR_ASSERT(db->firstrecord != result);
result->next = db->firstrecord;
db->firstrecord = result;
} else {
for (i=0 ; i<3 ; i++) {
PR_Free(result->data[i]);
}
tmp = result->next;
memset(result, 0, sizeof(TDBRecord));
result->next = tmp;
}
result->position = position;
result->length = size;
for (i=0 ; i<3 ; i++) {
result->data[i] = tdbNodeDup(triple[i]);
if (result->data[i] == NULL) {
while (--i >= 0) {
PR_Free(result->data[i]);
}
PR_Free(result);
return NULL;
}
}
result->dirty = PR_TRUE;
return result;
}
/* Argh, this same static array appears in query.c. Don't do that. ### */
static int key[4][3] = {
{0, 1, 2},
{1, 0, 2},
{2, 1, 0},
{1, 2, 0}
};
PRInt64 tdbCompareRecords(TDBRecord* r1, TDBRecord* r2, PRInt32 comparerule)
{
int i, k;
PRInt64 cmp;
if (comparerule == -1) {
cmp = r1->length - r2->length;
if (cmp != 0) return cmp;
return r1->position - r2->position;
}
PR_ASSERT(comparerule >= 0 && comparerule < NUMTREES);
for (i=0 ; i<3 ; i++) {
k = key[comparerule][i];
cmp = tdbCompareNodes(r1->data[k], r2->data[k]);
if (cmp != 0) return cmp;
}
return 0;
}

440
db/tripledb/tdb.c Normal file
Просмотреть файл

@ -0,0 +1,440 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* tdb.c -- Routines operating on the database as a whole. */
#include "tdbtypes.h"
#include "prprf.h"
TDB* TDBOpen(const char* filename)
{
PRStatus status;
TDB* db;
PR_ASSERT(filename != NULL);
if (filename == NULL) return NULL;
db = PR_NEWZAP(TDB);
if (db == NULL) return NULL;
db->filename = PR_Malloc(strlen(filename) + 1);
if (db->filename == NULL) {
PR_Free(db);
return NULL;
}
strcpy(db->filename, filename);
db->fid = PR_Open(filename, PR_RDWR | PR_CREATE_FILE, 0666);
if (db->fid == NULL) {
/* Can't open file. */
goto FAIL;
}
db->mutex = PR_NewLock();
if (db->mutex == NULL) goto FAIL;
db->callbackcvargo = PR_NewCondVar(db->mutex);
if (db->callbackcvargo == NULL) goto FAIL;
db->callbackcvaridle = PR_NewCondVar(db->mutex);
if (db->callbackcvaridle == NULL) goto FAIL;
db->callbackthread =
PR_CreateThread(PR_SYSTEM_THREAD, tdbCallbackThread, db,
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
PR_JOINABLE_THREAD, 0);
if (db->callbackthread == NULL) goto FAIL;
PR_Lock(db->mutex);
while (!db->callbackidle) {
PR_WaitCondVar(db->callbackcvaridle, PR_INTERVAL_NO_TIMEOUT);
}
status = tdbLoadRoots(db);
PR_Unlock(db->mutex);
if (status == PR_FAILURE) goto FAIL;
return db;
FAIL:
TDBClose(db);
return NULL;
}
PRStatus tdbLoadRoots(TDB* db)
{
char buf[TDB_FILEHEADER_SIZE];
char* ptr;
PRInt32 i, length;
PRFileInfo info;
if (PR_Seek(db->fid, 0, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
i = 4 + sizeof(PRInt32) + (NUMTREES + 1) * sizeof(TDBPtr);
length = PR_Read(db->fid, buf, i);
if (length > 0 && length < i) {
/* Ick. This appears to be a short file. Punt. */
return PR_FAILURE;
}
if (length > 0) {
if (memcmp(TDB_MAGIC, buf, 4) != 0) {
/* Bad magic number. */
return PR_FAILURE;
}
ptr = buf + 4;
i = tdbGetInt32(&ptr);
if (i != TDB_VERSION) {
/* Bad version number. */
return PR_FAILURE;
}
db->freeroot = tdbGetInt32(&ptr);
for (i=0 ; i<NUMTREES ; i++) {
db->roots[i] = tdbGetInt32(&ptr);
}
} else {
db->dirty = db->rootdirty = PR_TRUE;
tdbFlush(db); /* Write out the roots the first time, so
that we can accurately read the file
size and stuff. */
}
PR_GetOpenFileInfo(db->fid, &info);
if (info.size < TDB_FILEHEADER_SIZE) {
int length = TDB_FILEHEADER_SIZE - info.size;
if (PR_Seek(db->fid, info.size, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
memset(buf, 0, length);
if (length != PR_Write(db->fid, buf, length)) {
return PR_FAILURE;
}
PR_GetOpenFileInfo(db->fid, &info);
PR_ASSERT(info.size == TDB_FILEHEADER_SIZE);
}
db->filelength = info.size;
return PR_SUCCESS;
}
PRStatus TDBClose(TDB* db)
{
PRStatus result = PR_SUCCESS;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_SUCCESS;
if (db->mutex) PR_Lock(db->mutex);
PR_ASSERT(db->firstcursor == NULL);
if (db->firstcursor != NULL) {
if (db->mutex) PR_Unlock(db->mutex);
return PR_FAILURE;
}
if (db->dirty) {
tdbFlush(db);
}
if (db->callbackthread) {
db->killcallbackthread = PR_TRUE;
PR_NotifyAllCondVar(db->callbackcvargo);
PR_Unlock(db->mutex);
PR_JoinThread(db->callbackthread);
PR_Lock(db->mutex);
}
if (db->callbackcvargo) {
PR_DestroyCondVar(db->callbackcvargo);
}
if (db->callbackcvaridle) {
PR_DestroyCondVar(db->callbackcvaridle);
}
if (db->mutex) {
PR_Unlock(db->mutex);
PR_DestroyLock(db->mutex);
}
if (db->fid) {
result = PR_Close(db->fid);
}
PR_Free(db->filename);
PR_Free(db);
return result;
}
PRStatus TDBRebuildDatabase(TDB* db)
{
PRStatus status;
char* tmpfile;
PRFileInfo info;
PRInt32 count = 0;
TDB* tmpdb;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_SUCCESS;
PR_Lock(db->mutex);
do {
tmpfile = PR_smprintf("%s-tmp-%d", db->filename, ++count);
status = PR_GetFileInfo(tmpfile, &info);
} while (status == PR_SUCCESS);
tmpdb = TDBOpen(tmpfile);
if (!tmpdb) return PR_FAILURE;
/* ### Finish writing me! */
return PR_FAILURE;
}
PRStatus tdbFlush(TDB* db)
{
PRInt32 length;
PRInt32 i;
PRStatus status;
TDBRecord* record;
TDBRecord** recptr;
char* ptr;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
if (db->rootdirty) {
PR_ASSERT(db->dirty);
if (PR_Seek(db->fid, 0, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
length = 4 + sizeof(PRInt32) + (NUMTREES + 1) * sizeof(TDBPtr);
status = tdbGrowIobuf(db, length);
if (status != PR_SUCCESS) return status;
ptr = db->iobuf;
memcpy(ptr, TDB_MAGIC, 4);
ptr += 4;
tdbPutInt32(&ptr, TDB_VERSION);
tdbPutInt32(&ptr, db->freeroot);
for (i=0 ; i<NUMTREES ; i++) {
tdbPutInt32(&ptr, db->roots[i]);
}
PR_ASSERT(ptr - db->iobuf <= TDB_FILEHEADER_SIZE);
if (PR_Write(db->fid, db->iobuf, ptr - db->iobuf) != ptr - db->iobuf) {
return PR_FAILURE;
}
db->rootdirty = PR_FALSE;
}
recptr = &(db->firstrecord);
while (*recptr) {
record = *recptr;
if (record->dirty) {
PR_ASSERT(db->dirty);
PR_ASSERT(record->refcnt == 0);
status = tdbSaveRecord(db, record);
if (status != PR_SUCCESS) return status;
}
if (record->refcnt == 0) {
*recptr = record->next;
status = tdbFreeRecord(record);
if (status != PR_SUCCESS) return status;
} else {
PR_ASSERT(record->refcnt > 0);
recptr = (&record->next);
}
}
db->dirty = PR_FALSE;
return PR_SUCCESS;
}
PRStatus tdbThrowAwayUnflushedChanges(TDB* db)
{
TDBRecord* record;
db->dirty = PR_FALSE;
db->rootdirty = PR_FALSE;
for (record = db->firstrecord ; record ; record = record->next) {
record->dirty = PR_FALSE;
}
tdbFlush(db);
return tdbLoadRoots(db);
}
PRStatus tdbGrowIobuf(TDB* db, PRInt32 length)
{
if (length > db->iobuflength) {
db->iobuflength = length;
if (db->iobuf == NULL) {
db->iobuf = PR_Malloc(db->iobuflength);
} else {
db->iobuf = PR_Realloc(db->iobuf, db->iobuflength);
}
if (db->iobuf == NULL) {
return PR_FAILURE;
}
}
return PR_SUCCESS;
}
void tdbMarkCorrupted(TDB* db)
{
PR_ASSERT(0);
/* ### Write me!!! ### */
}
PRInt32 tdbGetInt32(char** ptr)
{
PRInt32 result;
memcpy(&result, *ptr, sizeof(PRInt32));
*ptr += sizeof(PRInt32);
return result;
}
PRInt32 tdbGetInt16(char** ptr)
{
PRInt16 result;
memcpy(&result, *ptr, sizeof(PRInt16));
*ptr += sizeof(PRInt16);
return result;
}
PRInt8 tdbGetInt8(char** ptr)
{
PRInt8 result;
memcpy(&result, *ptr, sizeof(PRInt8));
*ptr += sizeof(PRInt8);
return result;
}
PRInt64 tdbGetInt64(char** ptr)
{
PRInt64 result;
memcpy(&result, *ptr, sizeof(PRInt64));
*ptr += sizeof(PRInt64);
return result;
}
PRUint16 tdbGetUInt16(char** ptr)
{
PRUint16 result;
memcpy(&result, *ptr, sizeof(PRUint16));
*ptr += sizeof(PRUint16);
return result;
}
void tdbPutInt32(char** ptr, PRInt32 value)
{
memcpy(*ptr, &value, sizeof(PRInt32));
*ptr += sizeof(PRInt32);
}
void tdbPutInt16(char** ptr, PRInt16 value)
{
memcpy(*ptr, &value, sizeof(PRInt16));
*ptr += sizeof(PRInt16);
}
void tdbPutUInt16(char** ptr, PRUint16 value)
{
memcpy(*ptr, &value, sizeof(PRUint16));
*ptr += sizeof(PRUint16);
}
void tdbPutInt8(char** ptr, PRInt8 value)
{
memcpy(*ptr, &value, sizeof(PRInt8));
*ptr += sizeof(PRInt8);
}
void tdbPutInt64(char** ptr, PRInt64 value)
{
memcpy(*ptr, &value, sizeof(PRInt64));
*ptr += sizeof(PRInt64);
}
/*
* The below was an attempt to write all these routines in a nice way, so
* that the database would be platform independent. But I keep choking
* sign bits and things. I give up for now. These probably should be
* resurrected and fixed up.
*
* PRInt32 tdbGetInt32(char** ptr)
* {
* PRInt32 result = ((*ptr)[0] << 24) | ((*ptr)[1] << 16) |
* ((*ptr)[2] << 8) | (*ptr)[3];
* (*ptr) += 4;
* return result;
* }
*
* PRInt32 tdbGetInt16(char** ptr)
* {
* PRInt16 result = ((*ptr)[0] << 8) | (*ptr)[1];
* (*ptr) += 2;
* return result;
* }
*
* PRUint16 tdbGetUInt16(char** ptr)
* {
* PRUint16 result = ((*ptr)[0] << 8) | (*ptr)[1];
* (*ptr) += 2;
* return result;
* }
*
* PRInt8 tdbGetInt8(char** ptr)
* {
* return (PRInt8) (*((*ptr)++));
* }
*
*
* PRInt64 tdbGetInt64(char** ptr)
* {
* PRInt64 a = tdbGetInt32(ptr);
* return (a << 32) | tdbGetInt32(ptr);
* }
*
*
* void tdbPutInt32(char** ptr, PRInt32 value)
* {
* *((*ptr)++) = (value >> 24) & 0xff;
* *((*ptr)++) = (value >> 16) & 0xff;
* *((*ptr)++) = (value >> 8) & 0xff;
* *((*ptr)++) = (value) & 0xff;
* }
*
*
* void tdbPutInt16(char** ptr, PRInt16 value)
* {
* *((*ptr)++) = (value >> 8) & 0xff;
* *((*ptr)++) = (value) & 0xff;
* }
*
* void tdbPutUInt16(char** ptr, PRUint16 value)
* {
* *((*ptr)++) = (value >> 8) & 0xff;
* *((*ptr)++) = (value) & 0xff;
* }
*
* void tdbPutInt8(char** ptr, PRInt32 value)
* {
* *((*ptr)++) = value;
* }
*
* void tdbPutInt64(char** ptr, PRInt64 value)
* {
* tdbPutInt32(ptr, ((PRInt32) (value >> 32)));
* tdbPutInt32(ptr, ((PRInt32) (value & 0xffffffff)));
* }
*/

239
db/tripledb/tdbapi.h Normal file
Просмотреть файл

@ -0,0 +1,239 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* All things are prefixed with TDB, which stands for "Triples
DataBase". Suggestions for better names are always welcome. */
/* A TDBNode contains one of the three items in a triple. This is a
structure that defines a very basic type, strings or ints or dates.
If we decide to add other kinds of basic types (floats? bools? unsigned?),
then this is where we muck things.
It is important that all nodes be strictly ordered. All the integer values
sort together in the obvious way. PRTimes get sorted with them by treating
them as if they were PRInt64's. All strings are considered to be greater
than all integers. */
#define TDBTYPE_INT8 1 /* 8-bit signed integer */
#define TDBTYPE_INT16 2 /* 16-bit signed integer */
#define TDBTYPE_INT32 3 /* 32-bit signed integer */
#define TDBTYPE_INT64 4 /* 64-bit signed integer */
#define TDBTYPE_TIME 5 /* NSPR date&time stamp (PRTime) */
#define TDBTYPE_STRING 6 /* A string (up to 65535 chars long) */
typedef struct {
PRInt8 type;
union {
PRInt64 i; /* All the int types are stored here, as an
Int64. The type just indicates how it is to be
stored in the database. */
struct {
PRUint16 length;
char string[1];
} str;
PRTime time;
} d;
} TDBNode, *TDBNodePtr;
/* A TDBTriple specifies one entry in the database. This is generally thought
of as (subject, verb, object). */
typedef struct {
TDBNodePtr data[3];
} TDBTriple;
/* A TDBNodeRange specifies a range of values for a node. If min is
not NULL, then this matches only nodes that are greater than or
equal to min. If max is not NULL, then this matches only nodes
that are less than or equal to max. Therefore, if both min and
max are NULL, then this specifies the range of all possible nodes.
If min and max are not NULL, and are the same value, then it specifies
exactly that value. */
typedef struct {
TDBNodePtr min;
TDBNodePtr max;
} TDBNodeRange;
/* A TDBSortSpecification specifies what order results from a request should
come in. I suspect that there will someday be much more to it than this. */
typedef struct {
PRBool reverse; /* If true, then return biggest results
first. Otherwise, the smallest
stuff comes first. */
} TDBSortSpecification;
/* A TDB* is an opaque pointer that represents an entire database. */
typedef struct _TDB TDB;
/* A TDBCursor* is an opaque pointer that represents a query that you
are getting results for. */
typedef struct _TDBCursor TDBCursor;
/* TDBCallbackFunction is for callbacks notifying of certain database
changes. */
typedef void (*TDBCallbackFunction)(TDB* database, void* closure,
TDBTriple* triple,
PRInt32 action); /* One of the below
TDBACTION_* values. */
#define TDBACTION_ADDED 1 /* The given triple has been added to the
database. */
#define TDBACTION_REMOVED 2 /* The given triple has been removed from the
database. */
PR_BEGIN_EXTERN_C
/* Open a database (creating it if non-existant). Returns NULL on failure. */
PR_EXTERN(TDB*) TDBOpen(const char* filename);
/* Close an opened database. Frees the storage for TDB; you may not use
that pointer after that call. Will flush out any changes that have
been made. This call will fail if you have not freed up all of the
cursors that were created with TDBQuery. */
PR_EXTERN(PRStatus) TDBClose(TDB* database);
/* TDBQuery() returns a cursor that you can use with TDBNextResult() to get
the results of a query. It will return NULL on failure. If the query
is legal, but there are no matching results, this will *not* return
NULL; it will return a cursor that will have no results.
If a single value is specified for both range[0] and range[1], then the
results are guaranteed to be sorted by range[2]. If a single value is
specified for both range[1] and range[2], then the results are guaranteed
to be sorted by range[0]. No other sorting rules are promised (at least,
not yet.)
A NULL TDBSortSpecification can be provided, which will sort in the
default manner. */
PR_EXTERN(TDBCursor*) TDBQuery(TDB* database, TDBNodeRange range[3],
TDBSortSpecification* sortspec);
/* TDBGetResult returns the next result that matches the cursor, and
advances the cursor to the next matching entry. It will return NULL
when there are no more matching entries. The returned triple must
not be modified in any way by the caller, and is valid only until
the next call to TDBGetResult(), or until the cursor is freed. */
PR_EXTERN(TDBTriple*) TDBGetResult(TDBCursor* cursor);
/* TDBFreeCursor frees the cursor. */
PR_EXTERN(PRStatus) TDBFreeCursor(TDBCursor* cursor);
/* TDBAddCallback calls the given function whenever a change to the database
is made that matches the given range. Note that no guarantees are made
about which thread this call will be done in. It is also possible (though
not very likely) that by the time the callback gets called, further changes
may have been made to the database, and the action being notified here has
already been undone. (If that happens, though, you will get another
callback soon.)
It is legal for the receiver of the callback to make any queries or
modifications it wishes to the database. It is probably not a good idea
to undo the action being notified about; this kind of policy leads to
infinite loops.
The receiver should not take unduly long before returning from the callback;
until the callback returns, no other callbacks will occur.
*/
PR_EXTERN(PRStatus) TDBAddCallback(TDB* database, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure);
/* TDBRemoveCallback will remove a callback that was earlier registered with a
call to TDBAddCallback. */
PR_EXTERN(PRStatus) TDBRemoveCallback(TDB* database, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure);
/* TDBRemove() removes all entries matching the given parameters from
the database. */
PR_EXTERN(PRStatus) TDBRemove(TDB* database, TDBNodeRange range[3]);
/* TDBAdd() adds a triple into the database. */
PR_EXTERN(PRStatus) TDBAdd(TDB* database, TDBNodePtr triple[3]);
/* TDBReplace() looks for an existing entry that matches triple[0] and
triple[1]. It deletes the first such entry found (if any), and
then inserts a new entry. The intention is to replace the "object"
part of an existing triple with a new value. It really only makes
sense if you know up-front that there is no more than one existing
triple with the given "subject" and "verb". */
PR_EXTERN(PRStatus) TDBReplace(TDB* database, TDBNodePtr triple[3]);
/* TDBCreateStringNode() is just a utility routine that correctly
allocates a new TDBNode that represents the given string. The
TDBNode can be free'd using TDBFreeNode(). */
PR_EXTERN(TDBNodePtr) TDBCreateStringNode(const char* string);
/* TDBCreateIntNode() is just a utility routine that correctly
allocates a new TDBNode that represents the given integer. The
TDBNode can be free'd using TDBFreeNode(). You must specify
the correct TDBTYPE_* value for it. */
PR_EXTERN(TDBNodePtr) TDBCreateIntNode(PRInt64 value, PRInt8 type);
/* Free up a node created with TDBCreateStringNode or TDBCreateIntNode. */
PR_EXTERN(void) TDBFreeNode(TDBNodePtr node);
PR_END_EXTERN_C

39
db/tripledb/tdbdebug.h Normal file
Просмотреть файл

@ -0,0 +1,39 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* These are debugging routines for the TDB. Don't use any of these calls
in production code! */
extern void TDBDumpNode(PRFileDesc* fid, TDBNode* node);
extern void TDBDumpTree(PRFileDesc* fid, TDB* db, PRInt32 tree);
extern PRStatus TDBSanityCheck(TDB* db, PRFileDesc* fid);
extern void TDBGetCursorStats(TDBCursor* cursor,
PRInt32* hits,
PRInt32* misses);
/* Create a "dot" graph file. To view these, and learn more about them,
see http://www.research.att.com/~north/cgi-bin/webdot.cgi */
extern void TDBMakeDotGraph(TDB* db, const char* filename, PRInt32 tree);

227
db/tripledb/tdbtypes.h Normal file
Просмотреть файл

@ -0,0 +1,227 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
#ifndef _TDBtypes_h_
#define _TDBtypes_h_ 1
/* These are private, internal type definitions, */
#include <string.h>
#include "pratom.h"
#include "prcvar.h"
#include "prio.h"
#include "prlock.h"
#include "prlog.h"
#include "prmem.h"
#include "prthread.h"
#include "prtime.h"
#include "prtypes.h"
#include "tdbapi.h"
/* Magic number that appears as first four bytes of db files. */
#define TDB_MAGIC "TDB\n"
/* Total number of bytes we reserve at the beginning of the db file for
special stuff. */
#define TDB_FILEHEADER_SIZE 1024
/* Current version number for db files. */
#define TDB_VERSION 1
#define NUMTREES 4 /* How many binary trees we're keeping,
indexing the data. */
/* The trees index things in the following orders:
0: 0, 1, 2
1: 1, 0, 2
2: 2, 1, 0
3: 1, 2, 0
*/
/* MINRECORDSIZE and MAXRECORDSIZE are the minimum and maximum possible record
size. They're useful for sanity checking. */
#define BASERECORDSIZE (sizeof(PRInt32) + NUMTREES * (2*sizeof(TDBPtr)+sizeof(PRInt8)))
#define MINRECORDSIZE (BASERECORDSIZE + 3 * 2)
#define MAXRECORDSIZE (BASERECORDSIZE + 3 * 65540)
typedef PRInt32 TDBPtr;
typedef struct {
TDBPtr child[2];
PRInt8 balance;
} TDBTreeEntry;
typedef struct _TDBRecord {
TDBPtr position;
PRInt32 length;
PRInt32 refcnt;
PRBool dirty;
struct _TDBRecord* next;
PRUint8 flags;
TDBTreeEntry entry[NUMTREES];
TDBNode* data[3];
} TDBRecord;
typedef struct _TDBParentChain {
TDBRecord* record;
struct _TDBParentChain* next;
} TDBParentChain;
struct _TDBCursor {
TDB* db;
TDBNodeRange range[3];
TDBRecord* cur;
TDBRecord* lasthit;
TDBParentChain* parent;
PRInt32 tree;
PRBool reverse;
TDBTriple triple;
PRInt32 hits;
PRInt32 misses;
struct _TDBCursor* nextcursor;
struct _TDBCursor* prevcursor;
};
typedef struct _TDBCallbackInfo {
struct _TDBCallbackInfo* nextcallback;
TDBNodeRange range[3];
TDBCallbackFunction func;
void* closure;
} TDBCallbackInfo;
typedef struct _TDBPendingCall {
struct _TDBPendingCall* next;
TDBCallbackFunction func;
void* closure;
TDBTriple triple;
PRInt32 action;
} TDBPendingCall;
struct _TDB {
char* filename;
PRInt32 filelength;
PRFileDesc* fid;
TDBPtr roots[NUMTREES];
TDBPtr freeroot;
PRBool dirty;
PRBool rootdirty;
PRLock* mutex; /* Used to prevent more than one thread
from doing anything in DB code at the
same time. */
PRThread* callbackthread; /* Thread ID of the background
callback-calling thread. */
PRCondVar* callbackcvargo; /* Used to wake up the callback-calling
thread. */
PRCondVar* callbackcvaridle; /* Used by the callback-calling to indicate
that it is now idle. */
PRBool killcallbackthread;
PRBool callbackidle;
char* iobuf;
PRInt32 iobuflength;
TDBRecord* firstrecord;
TDBCursor* firstcursor;
TDBCallbackInfo* firstcallback;
TDBPendingCall* firstpendingcall;
TDBPendingCall* lastpendingcall;
};
/* ----------------------------- From add.c: ----------------------------- */
extern PRStatus tdbAddToTree(TDB* db, TDBRecord* record, PRInt32 tree);
extern PRStatus tdbRemoveFromTree(TDB* db, TDBRecord* record, PRInt32 tree);
/* ----------------------------- From callback.c: ------------------------- */
extern void tdbCallbackThread(void* closure);
extern PRStatus tdbQueueMatchingCallbacks(TDB* db, TDBRecord* record,
PRInt32 action);
/* ----------------------------- From node.c: ----------------------------- */
extern TDBNode* tdbNodeDup(TDBNode* node);
extern PRInt32 tdbNodeSize(TDBNode* node);
extern PRInt64 tdbCompareNodes(TDBNode* n1, TDBNode* n2);
extern TDBNode* tdbGetNode(TDB* db, char** ptr);
extern void tdbPutNode(TDB* db, char** ptr, TDBNode* node);
/* ----------------------------- From query.c: ----------------------------- */
extern TDBCursor* tdbQueryNolock(TDB* db, TDBNodeRange range[3],
TDBSortSpecification* sortspec);
extern PRStatus tdbFreeCursorNolock(TDBCursor* cursor);
extern TDBTriple* tdbGetResultNolock(TDBCursor* cursor);
extern PRInt64 tdbCompareToRange(TDBRecord* record, TDBNodeRange* range,
PRInt32 comparerule);
extern PRBool tdbMatchesRange(TDBRecord* record, TDBNodeRange* range);
extern void tdbThrowOutCursorCaches(TDB* db);
/* ----------------------------- From record.c: ---------------------------- */
extern TDBRecord* tdbLoadRecord(TDB* db, TDBPtr position);
extern PRStatus tdbSaveRecord(TDB* db, TDBRecord* record);
extern PRStatus tdbFreeRecord(TDBRecord* record);
extern TDBRecord* tdbAllocateRecord(TDB* db, TDBNodePtr triple[3]);
extern PRInt64 tdbCompareRecords(TDBRecord* r1, TDBRecord* r2,
PRInt32 comparerule);
/* ----------------------------- From tdb.c: ----------------------------- */
extern PRStatus tdbFlush(TDB* db);
extern PRStatus tdbThrowAwayUnflushedChanges(TDB* db);
extern PRStatus tdbGrowIobuf(TDB* db, PRInt32 length);
extern PRStatus tdbLoadRoots(TDB* db);
extern void tdbMarkCorrupted(TDB* db);
extern PRInt32 tdbGetInt32(char** ptr);
extern PRInt32 tdbGetInt16(char** ptr);
extern PRInt8 tdbGetInt8(char** ptr);
extern PRInt64 tdbGetInt64(char** ptr);
extern PRUint16 tdbGetUInt16(char** ptr);
extern void tdbPutInt32(char** ptr, PRInt32 value);
extern void tdbPutInt16(char** ptr, PRInt16 value) ;
extern void tdbPutUInt16(char** ptr, PRUint16 value) ;
extern void tdbPutInt8(char** ptr, PRInt8 value);
extern void tdbPutInt64(char** ptr, PRInt64 value);
#endif /* _TDBtypes_h_ */

553
tripledb/add.c Normal file
Просмотреть файл

@ -0,0 +1,553 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Routines that add or remove things to the database. */
#include "tdbtypes.h"
#ifdef DEBUG
#include "tdbdebug.h"
#include "prprf.h"
static PRBool makedots = PR_FALSE; /* If true, print out dot-graphs of
every step of balancing, to help my
poor mind debug. */
static int dotcount = 0;
#endif
PRStatus TDBAdd(TDB* db, TDBNodePtr triple[3])
{
PRStatus status = PR_FAILURE;
TDBRecord* record;
TDBPtr position;
PRInt32 tree;
PRInt32 i;
PRInt64 cmp;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
PR_Lock(db->mutex);
/* First, see if we already have this triple around... */
tree = 0; /* Hard-coded knowledge that tree zero does
things in [0], [1], [2] order. ### */
position = db->roots[tree];
while (position) {
record = tdbLoadRecord(db, position);
PR_ASSERT(record);
if (!record) {
goto DONE;
}
PR_ASSERT(record->position == position);
if (record->position != position) {
goto DONE;
}
for (i=0 ; i<3 ; i++) {
cmp = tdbCompareNodes(triple[i], record->data[i]);
if (cmp < 0) {
position = record->entry[tree].child[0];
break;
} else if (cmp > 0) {
position = record->entry[tree].child[1];
break;
}
}
if (position == record->position) {
/* This means that our new entry exactly matches this one. So,
we're done. */
status = PR_SUCCESS;
goto DONE;
}
}
tdbThrowOutCursorCaches(db);
record = tdbAllocateRecord(db, triple);
if (record == NULL) {
goto DONE;
}
for (tree=0 ; tree<NUMTREES ; tree++) {
status = tdbAddToTree(db, record, tree);
if (status != PR_SUCCESS) goto DONE;
}
status = tdbQueueMatchingCallbacks(db, record, TDBACTION_ADDED);
DONE:
if (status == PR_SUCCESS) {
tdbFlush(db);
} else {
tdbThrowAwayUnflushedChanges(db);
}
PR_Unlock(db->mutex);
return status;
}
PRStatus TDBReplace(TDB* db, TDBNodePtr triple[3])
{
/* Write me correctly!!! This works, but is inefficient. ### */
PRStatus status;
TDBNodeRange range[3];
range[0].min = triple[0];
range[0].max = triple[0];
range[1].min = triple[1];
range[1].max = triple[1];
range[2].min = NULL;
range[2].max = NULL;
status = TDBRemove(db, range);
if (status == PR_SUCCESS) {
status = TDBAdd(db, triple);
}
return status;
}
PRStatus TDBRemove(TDB* db, TDBNodeRange range[3])
{
/* This could definitely be faster. We're querying the database for
a matching item, then we go search for it again when we delete it.
The two operations probably ought to be merged. ### */
PRStatus status;
TDBCursor* cursor;
TDBTriple* triple;
TDBPtr position;
TDBRecord* record;
PRInt32 tree;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
PR_Lock(db->mutex);
tdbThrowOutCursorCaches(db);
for (;;) {
cursor = tdbQueryNolock(db, range, NULL);
if (!cursor) goto FAIL;
triple = tdbGetResultNolock(cursor);
if (triple) {
/* Probably ought to play refcnt games with this to prevent it
from being removed from the cache. ### */
position = cursor->lasthit->position;
}
tdbFreeCursorNolock(cursor);
cursor = NULL;
if (!triple) {
/* No more hits; all done. */
break;
}
record = tdbLoadRecord(db, position);
if (!record) goto FAIL;
for (tree=0 ; tree<NUMTREES ; tree++) {
status = tdbRemoveFromTree(db, record, tree);
if (status != PR_SUCCESS) goto FAIL;
}
status = tdbAddToTree(db, record, -1);
if (status != PR_SUCCESS) goto FAIL;
status = tdbQueueMatchingCallbacks(db, record, TDBACTION_REMOVED);
if (status != PR_SUCCESS) goto FAIL;
tdbFlush(db);
}
PR_Unlock(db->mutex);
return PR_SUCCESS;
FAIL:
tdbThrowAwayUnflushedChanges(db);
PR_Unlock(db->mutex);
return PR_FAILURE;
}
#ifdef MIN
#undef MIN
#endif
#ifdef MAX
#undef MAX
#endif
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
static PRBool
rotateOnce(TDB* db, TDBPtr* rootptr, TDBRecord* oldroot, PRInt32 tree,
PRInt32 dir)
{
PRInt32 otherdir = 1 - dir;
PRBool heightChanged;
TDBPtr otherptr;
TDBRecord* other;
PRInt8 oldrootbal;
PRInt8 otherbal;
#ifdef DEBUG
if (makedots) {
char* filename;
filename = PR_smprintf("/tmp/balance%d-%d.dot", tree, dotcount++);
TDBMakeDotGraph(db, filename, tree);
PR_smprintf_free(filename);
}
#endif
PR_ASSERT(dir == 0 || dir == 1);
if (dir != 0 && dir != 1) return PR_FALSE;
otherptr = oldroot->entry[tree].child[otherdir];
if (otherptr == 0) {
tdbMarkCorrupted(db);
return PR_FALSE;
}
other = tdbLoadRecord(db, otherptr);
heightChanged = (other->entry[tree].balance != 0);
*rootptr = otherptr;
oldroot->entry[tree].child[otherdir] = other->entry[tree].child[dir];
other->entry[tree].child[dir] = oldroot->position;
/* update balances */
oldrootbal = oldroot->entry[tree].balance;
otherbal = other->entry[tree].balance;
if (dir == 0) {
oldrootbal -= 1 + MAX(otherbal, 0);
otherbal -= 1 - MIN(oldrootbal, 0);
} else {
oldrootbal += 1 - MIN(otherbal, 0);
otherbal += 1 + MAX(oldrootbal, 0);
}
oldroot->entry[tree].balance = oldrootbal;
other->entry[tree].balance = otherbal;
oldroot->dirty = PR_TRUE;
other->dirty = PR_TRUE;
return heightChanged;
}
static void
rotateTwice(TDB* db, TDBPtr* rootptr, TDBRecord* oldroot, PRInt32 tree,
PRInt32 dir)
{
PRInt32 otherdir = 1 - dir;
TDBRecord* child;
PR_ASSERT(dir == 0 || dir == 1);
if (dir != 0 && dir != 1) return;
child = tdbLoadRecord(db, oldroot->entry[tree].child[otherdir]);
PR_ASSERT(child);
if (child == NULL) return;
rotateOnce(db, &(oldroot->entry[tree].child[otherdir]), child, tree,
otherdir);
oldroot->dirty = PR_TRUE;
rotateOnce(db, rootptr, oldroot, tree, dir);
}
static PRStatus
balance(TDB* db, TDBPtr* rootptr, TDBRecord* oldroot, PRInt32 tree,
PRBool* heightchange)
{
PRInt8 oldbalance;
TDBRecord* child;
*heightchange = PR_FALSE;
oldbalance = oldroot->entry[tree].balance;
if (oldbalance < -1) { /* need a right rotation */
child = tdbLoadRecord(db, oldroot->entry[tree].child[0]);
PR_ASSERT(child);
if (child == NULL) return PR_FAILURE;
if (child->entry[tree].balance == 1) {
rotateTwice(db, rootptr, oldroot, tree, 1); /* double RL rotation
needed */
*heightchange = PR_TRUE;
} else { /* single RR rotation needed */
*heightchange = rotateOnce(db, rootptr, oldroot, tree, 1);
}
} else if (oldbalance > 1) { /* need a left rotation */
child = tdbLoadRecord(db, oldroot->entry[tree].child[1]);
PR_ASSERT(child);
if (child == NULL) return PR_FAILURE;
if (child->entry[tree].balance == -1) {
rotateTwice(db, rootptr, oldroot, tree, 0); /* double LR rotation
needed */
*heightchange = PR_TRUE;
} else { /* single LL rotation needed */
*heightchange = rotateOnce(db, rootptr, oldroot, tree, 0);
}
}
return PR_SUCCESS;
}
static PRStatus doAdd(TDB* db, TDBRecord* record, PRInt32 tree,
PRInt32 comparerule, TDBPtr* rootptr,
PRBool* heightchange)
{
PRBool increase = PR_FALSE;
PRInt64 cmp;
PRInt32 kid;
PRStatus status;
TDBRecord* cur;
TDBPtr origptr;
if (*rootptr == 0) {
*rootptr = record->position;
*heightchange = PR_TRUE;
return PR_SUCCESS;
}
cur = tdbLoadRecord(db, *rootptr);
if (!cur) return PR_FAILURE;
cmp = tdbCompareRecords(record, cur, comparerule);
PR_ASSERT(cmp != 0); /* We carefully should never insert a
record that we already have. */
if (cmp == 0) return PR_FAILURE;
kid = (cmp < 0) ? 0 : 1;
origptr = cur->entry[tree].child[kid];
status = doAdd(db, record, tree, comparerule,
&(cur->entry[tree].child[kid]), &increase);
if (origptr != cur->entry[tree].child[kid]) {
cur->dirty = PR_TRUE;
}
if (increase) {
cur->entry[tree].balance += (kid == 0 ? -1 : 1);
cur->dirty = PR_TRUE;
}
if (status != PR_SUCCESS) return status;
if (increase && cur->entry[tree].balance != 0) {
status = balance(db, rootptr, cur, tree, &increase);
*heightchange = ! increase; /* If we did a rotate that absorbed
the height change, then we don't want
to propagate it on up. */
}
return PR_SUCCESS;
}
PRStatus tdbAddToTree(TDB* db, TDBRecord* record, PRInt32 tree)
{
TDBTreeEntry* entry;
PRBool checklinks;
PRBool ignore;
PRStatus status = PR_SUCCESS;
TDBPtr* rootptr;
TDBPtr origroot;
PRInt32 comparerule = tree;
PR_ASSERT(record != NULL);
if (record == NULL) return PR_FAILURE;
if (tree == -1) {
tree = 0;
rootptr = &(db->freeroot);
} else {
PR_ASSERT(tree >= 0 && tree < NUMTREES);
if (tree < 0 || tree >= NUMTREES) {
return PR_FAILURE;
}
rootptr = &(db->roots[tree]);
}
/* Check that this record does not seem to be already in this tree. */
entry = record->entry + tree;
checklinks = (entry->child[0] == 0 &&
entry->child[1] == 0 &&
entry->balance == 0);
PR_ASSERT(checklinks);
if (!checklinks) return PR_FAILURE;
origroot = *rootptr;
if (origroot == 0) {
*rootptr = record->position;
} else {
status = doAdd(db, record, tree, comparerule, rootptr, &ignore);
}
if (origroot != *rootptr) {
db->rootdirty = PR_TRUE;
}
db->dirty = PR_TRUE;
return status;
}
static PRStatus doRemove(TDB* db, TDBRecord* record, PRInt32 tree,
PRInt32 comparerule, TDBPtr* rootptr,
TDBPtr** foundleafptr, TDBRecord** foundleaf,
PRBool* heightchange)
{
PRStatus status;
TDBRecord* cur;
TDBPtr* leafptr;
TDBRecord* leaf;
PRInt32 kid;
TDBPtr origptr;
TDBPtr kid0;
TDBPtr kid1;
PRInt64 cmp;
PRBool decrease = PR_FALSE;
PRInt32 tmp;
PRInt8 tmpbal;
PRInt32 i;
PR_ASSERT(*rootptr != 0);
if (*rootptr == 0) return PR_FAILURE;
cur = tdbLoadRecord(db, *rootptr);
if (!cur) return PR_FAILURE;
if (record == NULL) {
cur->dirty = PR_TRUE; /* Oh, what a bad, bad hack. Need a better
way to make sure not to miss a parent
pointer that we've changed. ### */
}
kid0 = cur->entry[tree].child[0];
kid1 = cur->entry[tree].child[1];
if (record == NULL) {
/* We're looking for the smallest possible leaf node. */
PR_ASSERT(foundleafptr != NULL && foundleaf != NULL);
if (kid0) cmp = -1;
else {
cmp = 0;
*foundleafptr = rootptr;
*foundleaf = cur;
}
} else {
PR_ASSERT(foundleafptr == NULL && foundleaf == NULL);
cmp = tdbCompareRecords(record, cur, comparerule);
}
if (cmp == 0) {
PR_ASSERT(record == cur || record == NULL);
if (record != cur && record != NULL) return PR_FAILURE;
if (kid0 == 0 && kid1 == 0) {
/* We're a leaf node. */
*rootptr = 0;
*heightchange = PR_TRUE;
return PR_SUCCESS;
} else if (kid0 == 0 || kid1 == 0) {
/* Replace us with our single child. */
*rootptr = kid0 != 0 ? kid0 : kid1;
cur->entry[tree].child[0] = 0;
cur->entry[tree].child[1] = 0;
cur->entry[tree].balance = 0;
cur->dirty = PR_TRUE;
*heightchange = PR_TRUE;
return PR_SUCCESS;
} else {
/* Ick. We're a node in the middle of the tree. Find who to
replace us with. */
kid = 1; /* Informs balancing code later that we are
taking things from the right subtree. */
status = doRemove(db, NULL, tree, comparerule,
&(cur->entry[tree].child[1]),
&leafptr, &leaf, &decrease);
/* Swap us in the tree with leaf. Don't use the kid0/kid1
variables any more, as they may no longer be valid. */
if (record != NULL) {
leaf->entry[tree].child[0] = cur->entry[tree].child[0];
leaf->entry[tree].child[1] = cur->entry[tree].child[1];
leaf->entry[tree].balance = cur->entry[tree].balance;
cur->entry[tree].child[0] = 0;
cur->entry[tree].child[1] = 0;
cur->entry[tree].balance = 0;
cur->dirty = PR_TRUE;
*rootptr = leaf->position;
cur = leaf;
cur->dirty = PR_TRUE;
} else {
*foundleaf = cur;
*foundleafptr = leafptr;
PR_ASSERT(**foundleafptr == leaf->position);
*leafptr = cur->position;
*rootptr = leaf->position;
for (i=0 ; i<2 ; i++) {
tmp = leaf->entry[tree].child[i];
leaf->entry[tree].child[i] = cur->entry[tree].child[i];
cur->entry[tree].child[i] = tmp;
}
tmpbal = leaf->entry[tree].balance;
leaf->entry[tree].balance = cur->entry[tree].balance;
cur->entry[tree].balance = tmpbal;
cur->dirty = PR_TRUE;
leaf->dirty = PR_TRUE;
}
}
} else {
kid = (cmp < 0) ? 0 : 1;
origptr = cur->entry[tree].child[kid];
status = doRemove(db, record, tree, comparerule,
&(cur->entry[tree].child[kid]), foundleafptr,
foundleaf, &decrease);
if (origptr != cur->entry[tree].child[kid]) {
cur->dirty = PR_TRUE;
}
}
if (decrease) {
cur->entry[tree].balance += (kid == 0 ? 1 : -1);
cur->dirty = PR_TRUE;
}
if (status != PR_SUCCESS) return status;
if (decrease) {
if (cur->entry[tree].balance != 0) {
status = balance(db, rootptr, cur, tree, &decrease);
*heightchange = decrease;
} else {
*heightchange = PR_TRUE;
}
}
return PR_SUCCESS;
}
PRStatus tdbRemoveFromTree(TDB* db, TDBRecord* record, PRInt32 tree)
{
PRStatus status;
PRInt32 comparerule = tree;
TDBPtr* rootptr;
TDBPtr origroot;
PRBool ignore;
PR_ASSERT(record != NULL);
if (record == NULL) return PR_FAILURE;
if (tree == -1) {
tree = 0;
rootptr = &(db->freeroot);
} else {
PR_ASSERT(tree >= 0 && tree < NUMTREES);
if (tree < 0 || tree >= NUMTREES) {
return PR_FAILURE;
}
rootptr = &(db->roots[tree]);
}
origroot = *rootptr;
PR_ASSERT(origroot != 0);
if (origroot == 0) return PR_FAILURE;
status = doRemove(db, record, tree, comparerule, rootptr, NULL, NULL,
&ignore);
if (origroot != *rootptr) {
db->rootdirty = PR_TRUE;
}
db->dirty = PR_TRUE;
record->dirty = PR_TRUE;
return status;
}

212
tripledb/callback.c Normal file
Просмотреть файл

@ -0,0 +1,212 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Routines that query things from the database. */
#include "tdbtypes.h"
static void
tdbFreeCallbackInfo(TDBCallbackInfo* info)
{
PRInt32 i;
for (i=0 ; i<3 ; i++) {
if (info->range[i].min != NULL) TDBFreeNode(info->range[i].min);
if (info->range[i].max != NULL) TDBFreeNode(info->range[i].max);
}
PR_Free(info);
}
static void
tdbFreePendingCall(TDBPendingCall* call)
{
PRInt32 i;
for (i=0 ; i<3 ; i++) {
if (call->triple.data[i] != NULL) {
TDBFreeNode(call->triple.data[i]);
}
}
PR_Free(call);
}
void
tdbCallbackThread(void* closure)
{
PRStatus status;
TDB* db = (TDB*) closure;
PRLock* mutex = db->mutex;
PRCondVar* cvar = db->callbackcvargo;
TDBPendingCall* call;
TDBPendingCall* tmp;
PR_Lock(mutex);
while (1) {
while (db->firstpendingcall == NULL) {
db->callbackidle = PR_TRUE;
PR_NotifyAllCondVar(db->callbackcvaridle); /* Inform anyone who
cares that we are
idle. */
if (db->killcallbackthread) {
/* This db is being closed; go away now. */
PR_Unlock(db->mutex);
return;
}
status = PR_WaitCondVar(cvar, PR_INTERVAL_NO_TIMEOUT);
db->callbackidle = PR_FALSE;
}
call = db->firstpendingcall;
db->firstpendingcall = NULL;
db->lastpendingcall = NULL;
PR_Unlock(db->mutex);
while (call) {
(*call->func)(db, call->closure, &(call->triple), call->action);
tmp = call;
call = call->next;
tdbFreePendingCall(tmp);
}
PR_Lock(db->mutex);
}
}
PRStatus tdbQueueMatchingCallbacks(TDB* db, TDBRecord* record,
PRInt32 action)
{
TDBCallbackInfo* info;
TDBPendingCall* call;
PRInt32 i;
for (info = db->firstcallback ; info ; info = info->nextcallback) {
if (tdbMatchesRange(record, info->range)) {
call = PR_NEWZAP(TDBPendingCall);
if (!call) return PR_FAILURE;
call->func = info->func;
call->closure = info->closure;
call->action = action;
for (i=0 ; i<3 ; i++) {
call->triple.data[i] = tdbNodeDup(record->data[i]);
if (call->triple.data[i] == NULL) {
tdbFreePendingCall(call);
return PR_FAILURE;
}
}
if (db->lastpendingcall) {
db->lastpendingcall->next = call;
}
db->lastpendingcall = call;
if (db->firstpendingcall == NULL) {
db->firstpendingcall = call;
}
}
}
if (db->firstpendingcall != NULL) {
/* Kick the background thread. */
PR_NotifyAllCondVar(db->callbackcvargo);
}
return PR_SUCCESS;
}
PRStatus TDBAddCallback(TDB* db, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure)
{
TDBCallbackInfo* info;
PRInt32 i;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
info = PR_NEWZAP(TDBCallbackInfo);
if (!info) return PR_FAILURE;
for (i=0 ; i<3 ; i++) {
if (range[i].min) {
info->range[i].min = tdbNodeDup(range[i].min);
if (info->range[i].min == NULL) goto FAIL;
}
if (range[i].max) {
info->range[i].max = tdbNodeDup(range[i].max);
if (info->range[i].max == NULL) goto FAIL;
}
}
info->func = func;
info->closure = closure;
info->nextcallback = db->firstcallback;
PR_Lock(db->mutex);
db->firstcallback = info;
PR_Unlock(db->mutex);
return PR_SUCCESS;
FAIL:
tdbFreeCallbackInfo(info);
return PR_FAILURE;
}
PRStatus TDBRemoveCallback(TDB* db, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure)
{
PRStatus status = PR_FAILURE;
TDBCallbackInfo** ptr;
TDBCallbackInfo* info;
PRInt32 i;
PRBool match;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
PR_Lock(db->mutex);
for (ptr = &(db->firstcallback) ; *ptr ; ptr = &((*ptr)->nextcallback)) {
info = *ptr;
if (info->func == func && info->closure == closure) {
match = PR_TRUE;
for (i=0 ; i<3 ; i++) {
if (range[i].min != info->range[i].min &&
(range[i].min == NULL || info->range[i].min == NULL ||
tdbCompareNodes(range[i].min, info->range[i].min) != 0)) {
match = PR_FALSE;
break;
}
if (range[i].max != info->range[i].max &&
(range[i].max == NULL || info->range[i].max == NULL ||
tdbCompareNodes(range[i].max, info->range[i].max) != 0)) {
match = PR_FALSE;
break;
}
}
if (match) {
*ptr = info->nextcallback;
tdbFreeCallbackInfo(info);
status = PR_SUCCESS;
/* We now make sure that we have no outstanding callbacks
queued up to the callback we just removed. It would be
real bad to call that callback after we return. So, we
make sure to call it now. */
while (db->firstpendingcall) {
PR_NotifyAllCondVar(db->callbackcvargo);
PR_WaitCondVar(db->callbackcvaridle,
PR_INTERVAL_NO_TIMEOUT);
}
break;
}
}
}
PR_Unlock(db->mutex);
return status;
}

300
tripledb/debug.c Normal file
Просмотреть файл

@ -0,0 +1,300 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Various debugging routines. */
#include "tdbtypes.h"
#include "tdbdebug.h"
#include "prprf.h"
void TDBDumpNode(PRFileDesc* fid, TDBNode* node)
{
switch(node->type) {
case TDBTYPE_STRING:
PR_fprintf(fid, "%s", node->d.str.string);
break;
default:
PR_fprintf(fid, "%ld", node->d.i);
}
}
void TDBDumpRecord(PRFileDesc* fid, TDB* db, TDBPtr ptr, PRInt32 tree, int indent, const char* kidname)
{
TDBRecord* record;
int i;
TDBPtr kid0;
TDBPtr kid1;
if (ptr == 0) return;
record = tdbLoadRecord(db, ptr);
for (i=0 ; i<indent ; i++) {
PR_fprintf(fid, " ");
}
PR_fprintf(fid, "[%s] pos=%d, len=%d, bal=%d ", kidname, record->position,
record->length, record->entry[tree].balance);
TDBDumpNode(fid, record->data[0]);
PR_fprintf(fid, ",");
TDBDumpNode(fid, record->data[1]);
PR_fprintf(fid, ",");
TDBDumpNode(fid, record->data[2]);
PR_fprintf(fid, "\n");
kid0 = record->entry[tree].child[0];
kid1 = record->entry[tree].child[1];
tdbFlush(db);
TDBDumpRecord(fid, db, kid0, tree, indent + 1, "0");
TDBDumpRecord(fid, db, kid1, tree, indent + 1, "1");
}
void TDBDumpTree(PRFileDesc* fid, TDB* db, PRInt32 tree)
{
TDBPtr root;
if (tree < 0) {
root = db->freeroot;
tree = 0;
} else {
root = db->roots[tree];
}
TDBDumpRecord(fid, db, root, tree, 0, "root");
}
typedef struct _TDBRangeSet {
PRInt32 position;
PRInt32 length;
struct _TDBRangeSet* next;
struct _TDBRangeSet* prev;
} TDBRangeSet;
static PRStatus checkMerges(TDBRangeSet* set, TDBRangeSet* tmp)
{
while (tmp->next != set &&
tmp->position + tmp->length == tmp->next->position) {
TDBRangeSet* t = tmp->next;
tmp->next = t->next;
tmp->next->prev = tmp;
tmp->length += t->length;
PR_Free(t);
}
while (tmp->prev != set &&
tmp->position == tmp->prev->position + tmp->prev->length) {
TDBRangeSet* t = tmp->prev;
tmp->prev = t->prev;
tmp->prev->next = tmp;
tmp->position = t->position;
t->length += t->length;
PR_Free(t);
}
return PR_SUCCESS;
}
static PRStatus addRange(TDBRangeSet* set, PRInt32 position, PRInt32 length)
{
TDBRangeSet* tmp;
TDBRangeSet* this;
for (tmp = set->next ; tmp != set ; tmp = tmp->next) {
if ((position >= tmp->position &&
position < tmp->position + tmp->length) ||
(tmp->position >= position &&
tmp->position < position + length)) {
PR_ASSERT(0); /* Collision! */
return PR_FAILURE;
}
if (position + length == tmp->position) {
tmp->position = position;
tmp->length += length;
return checkMerges(set, tmp);
} else if (tmp->position + tmp->length == position) {
tmp->length += length;
return checkMerges(set, tmp);
}
if (tmp->position > position + length) {
break;
}
}
this = PR_NEWZAP(TDBRangeSet);
if (!this) return PR_FAILURE;
this->next = tmp;
this->prev = tmp->prev;
this->next->prev = this;
this->prev->next = this;
this->position = position;
this->length = length;
return PR_SUCCESS;
}
typedef struct _TDBTreeInfo {
TDB* db;
PRInt32 tree; /* Which tree we're analyzing */
PRInt32 comparerule;
PRInt32 count; /* Number of nodes in tree. */
TDBRangeSet set; /* Range of bytes used by tree. */
PRInt32 maxdepth; /* Maximum depth of tree. */
} TDBTreeInfo;
static PRStatus checkTree(TDBTreeInfo* info, TDBPtr ptr, int depth,
int* maxdepth, TDBRecord* parent, int kid)
{
TDBRecord* record;
PRStatus status;
TDBPtr kid0;
TDBPtr kid1;
int d0 = depth;
int d1 = depth;
PRInt8 bal;
TDBRecord keep;
PRInt64 cmp;
int i;
if (ptr == 0) return PR_SUCCESS;
info->count++;
if (*maxdepth < depth) {
*maxdepth = depth;
}
record = tdbLoadRecord(info->db, ptr);
if (parent) {
cmp = tdbCompareRecords(record, parent, info->comparerule);
PR_ASSERT((kid == 0 && cmp < 0) || (kid == 1 && cmp > 0));
if (! ((kid == 0 && cmp < 0) || (kid == 1 && cmp > 0))) {
return PR_FAILURE;
}
}
status = addRange(&(info->set), record->position, record->length);
if (status != PR_SUCCESS) return status;
bal = record->entry[info->tree].balance;
if (bal < -1 || bal > 1) {
PR_ASSERT(0);
return PR_FAILURE;
}
kid0 = record->entry[info->tree].child[0];
kid1 = record->entry[info->tree].child[1];
memcpy(&keep, record, sizeof(TDBRecord));
for (i=0 ; i<3 ; i++) {
keep.data[i] = tdbNodeDup(keep.data[i]);
}
tdbFlush(info->db);
status = checkTree(info, kid0, depth + 1, &d0, &keep, 0);
if (status != PR_SUCCESS) return status;
status = checkTree(info, kid1, depth + 1, &d1, &keep, 1);
if (status != PR_SUCCESS) return status;
for (i=0 ; i<3 ; i++) {
TDBFreeNode(keep.data[i]);
}
if (d1 - d0 != bal) {
PR_ASSERT(0);
return PR_FAILURE;
}
if (*maxdepth < d0) {
*maxdepth = d0;
}
if (*maxdepth < d1) {
*maxdepth = d1;
}
return PR_SUCCESS;
}
PRStatus TDBSanityCheck(TDB* db, PRFileDesc* fid)
{
TDBTreeInfo info;
TDBRangeSet* tmp;
int i;
for (i=-1 ; i<4 ; i++) {
info.set.next = &(info.set);
info.set.prev = &(info.set);
info.db = db;
info.tree = (i < 0 ? 0 : i);
info.comparerule = i;
info.maxdepth = 0;
info.count = 0;
PR_fprintf(fid, "Checking tree %d...\n", i);
if (checkTree(&info, i < 0 ? db->freeroot : db->roots[i], 0,
&(info.maxdepth), NULL, 0) != PR_SUCCESS) {
PR_fprintf(fid, "Problem found!\n");
return PR_FAILURE;
}
for (tmp = info.set.next; tmp != &(info.set) ; tmp = tmp->next) {
PR_fprintf(fid, " %d - %d (%d)\n",
tmp->position, tmp->position + tmp->length,
tmp->length);
}
PR_fprintf(fid, " maxdepth: %d count %d\n", info.maxdepth,
info.count);
}
return PR_SUCCESS;
}
extern void TDBGetCursorStats(TDBCursor* cursor,
PRInt32* hits,
PRInt32* misses)
{
*hits = cursor->hits;
*misses = cursor->misses;
}
void makeDotEntry(TDB* db, PRFileDesc* fid, TDBPtr ptr, PRInt32 tree)
{
TDBRecord* record;
TDBPtr kid[2];
int i;
record = tdbLoadRecord(db, ptr);
PR_fprintf(fid, "%d [label=\"%d\\n", record->position, record->position);
for (i=0 ; i<3 ; i++) {
TDBDumpNode(fid, record->data[i]);
if (i<2) PR_fprintf(fid, ",");
}
PR_fprintf(fid, "\\nbal=%d\"]\n", record->entry[tree].balance);
kid[0] = record->entry[tree].child[0];
kid[1] = record->entry[tree].child[1];
/* tdbFlush(db); */
for (i=0 ; i<2 ; i++) {
if (kid[i] != 0) {
makeDotEntry(db, fid, kid[i], tree);
PR_fprintf(fid, "%d -> %d [label=\"%d\"]\n", ptr, kid[i], i);
}
}
}
void TDBMakeDotGraph(TDB* db, const char* filename, PRInt32 tree)
{
TDBPtr root;
PRFileDesc* fid;
fid = PR_Open(filename, PR_WRONLY | PR_CREATE_FILE, 0666);
if (tree == -1) {
root = db->freeroot;
tree = 0;
} else {
root = db->roots[tree];
}
PR_fprintf(fid, "digraph G {\n");
makeDotEntry(db, fid, root, tree);
PR_fprintf(fid, "}\n");
PR_Close(fid);
}

185
tripledb/node.c Normal file
Просмотреть файл

@ -0,0 +1,185 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Operations that do things on nodes. */
#include "tdbtypes.h"
TDBNodePtr TDBCreateStringNode(const char* string)
{
TDBNode* result;
PRInt32 length;
PR_ASSERT(string != NULL);
if (string == NULL) return NULL;
length = strlen(string);
result = (TDBNode*) PR_Malloc(sizeof(TDBNode) + length + 1);
if (result == NULL) return NULL;
result->type = TDBTYPE_STRING;
result->d.str.length = length;
strcpy(result->d.str.string, string);
return result;
}
TDBNodePtr TDBCreateIntNode(PRInt64 value, PRInt8 type)
{
TDBNode* result;
PR_ASSERT(type >= TDBTYPE_INT8 && type <= TDBTYPE_TIME);
if (type < TDBTYPE_INT8 || type > TDBTYPE_TIME) {
return NULL;
}
result = PR_NEW(TDBNode);
if (result == NULL) return NULL;
result->type = type;
result->d.i = value;
return result;
}
void TDBFreeNode(TDBNode* node)
{
PR_Free(node);
}
TDBNode* tdbNodeDup(TDBNode* node)
{
TDBNode* result;
PRInt32 length;
PR_ASSERT(node != NULL);
if (node == NULL) return NULL;
if (node->type == TDBTYPE_STRING) {
length = node->d.str.length;
result = PR_Malloc(sizeof(TDBNode) + length + 1);
if (result == NULL) return NULL;
result->type = TDBTYPE_STRING;
result->d.str.length = length;
memcpy(result->d.str.string, node->d.str.string, length);
result->d.str.string[length] = '\0';
return result;
}
return TDBCreateIntNode(node->d.i, node->type);
}
PRInt32 tdbNodeSize(TDBNode* node)
{
PR_ASSERT(node != NULL);
if (node == NULL) return 0;
switch (node->type) {
case TDBTYPE_INT8:
return 1 + sizeof(PRInt8);
case TDBTYPE_INT16:
return 1 + sizeof(PRInt16);
case TDBTYPE_INT32:
return 1 + sizeof(PRInt32);
case TDBTYPE_INT64:
return 1 + sizeof(PRInt64);
case TDBTYPE_TIME:
return 1 + sizeof(PRTime);
case TDBTYPE_STRING:
return 1 + sizeof(PRUint16) + node->d.str.length;
default:
PR_ASSERT(0);
return 0;
}
}
PRInt64 tdbCompareNodes(TDBNode* n1, TDBNode* n2)
{
if (n1->type == TDBTYPE_STRING && n2->type == TDBTYPE_STRING) {
return strcmp(n1->d.str.string, n2->d.str.string);
} else if (n1->type != TDBTYPE_STRING && n2->type != TDBTYPE_STRING) {
return n1->d.i - n2->d.i;
} else {
return n1->type - n2->type;
}
}
TDBNode* tdbGetNode(TDB* db, char** ptr)
{
TDBNode* result;
PRInt8 type = tdbGetInt8(ptr);
PRInt64 i;
if (type == TDBTYPE_STRING) {
PRUint16 length = tdbGetUInt16(ptr);
result = (TDBNode*) PR_Malloc(sizeof(TDBNode) + length + 1);
if (result == NULL) {
return NULL;
}
result->type = type;
result->d.str.length = length;
memcpy(result->d.str.string, *ptr, length);
result->d.str.string[length] = '\0';
(*ptr) += length;
} else {
switch(type) {
case TDBTYPE_INT8:
i = tdbGetInt8(ptr);
break;
case TDBTYPE_INT16:
i = tdbGetInt16(ptr);
break;
case TDBTYPE_INT32:
i = tdbGetInt32(ptr);
break;
case TDBTYPE_INT64:
case TDBTYPE_TIME:
i = tdbGetInt64(ptr);
break;
default:
tdbMarkCorrupted(db);
return NULL;
}
result = TDBCreateIntNode(i, type);
}
return result;
}
void tdbPutNode(TDB* db, char** ptr, TDBNode* node)
{
tdbPutInt8(ptr, node->type);
switch (node->type) {
case TDBTYPE_STRING:
tdbPutUInt16(ptr, node->d.str.length);
memcpy(*ptr, node->d.str.string, node->d.str.length);
*ptr += node->d.str.length;
break;
case TDBTYPE_INT8:
tdbPutInt8(ptr, (PRInt8) node->d.i);
break;
case TDBTYPE_INT16:
tdbPutInt16(ptr, (PRInt16) node->d.i);
break;
case TDBTYPE_INT32:
tdbPutInt32(ptr, (PRInt32) node->d.i);
break;
case TDBTYPE_INT64:
case TDBTYPE_TIME:
tdbPutInt64(ptr, node->d.i);
break;
default:
tdbMarkCorrupted(db);
break;
}
}

488
tripledb/query.c Normal file
Просмотреть файл

@ -0,0 +1,488 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Routines that query things from the database. */
#include "tdbtypes.h"
/* Argh, this same static array appears in record.c. Don't do that. ### */
static int key[NUMTREES][3] = {
{0, 1, 2},
{1, 0, 2},
{2, 1, 0},
{1, 2, 0}
};
static void
freeParentChain(TDBCursor* cursor)
{
TDBParentChain* tmp;
while (cursor->parent) {
tmp = cursor->parent->next;
cursor->parent->record->refcnt--;
PR_ASSERT(cursor->parent->record->refcnt >= 0);
PR_Free(cursor->parent);
cursor->parent = tmp;
}
}
static PRStatus
moveCursorForward(TDBCursor* cursor)
{
TDBRecord* cur;
PRBool found;
PRInt32 fwd;
PRInt32 rev;
TDBPtr ptr;
PRInt32 tree = cursor->tree;
TDBParentChain* parent;
TDBParentChain* tmp;
cur = cursor->cur;
PR_ASSERT(cur != NULL);
if (cur == NULL) {
return PR_FAILURE;
}
cur->refcnt--;
PR_ASSERT(cur->refcnt >= 0);
cursor->cur = NULL;
fwd = cursor->reverse ? 0 : 1;
rev = cursor->reverse ? 1 : 0;
ptr = cur->entry[tree].child[fwd];
if (ptr != 0) {
do {
cur->refcnt++;
parent = PR_NEWZAP(TDBParentChain);
parent->record = cur;
parent->next = cursor->parent;
cursor->parent = parent;
cur = tdbLoadRecord(cursor->db, ptr);
PR_ASSERT(cur);
if (!cur) {
return PR_FAILURE;
}
ptr = cur->entry[tree].child[rev];
} while (ptr != 0);
} else {
found = PR_FALSE;
while (cursor->parent) {
ptr = cur->position;
cur = cursor->parent->record;
cur->refcnt--;
PR_ASSERT(cur->refcnt >= 0);
tmp = cursor->parent->next;
PR_Free(cursor->parent);
cursor->parent = tmp;
if (cur->entry[tree].child[rev] == ptr) {
found = PR_TRUE;
break;
}
if (cur->entry[tree].child[fwd] != ptr) {
tdbMarkCorrupted(cursor->db);
return PR_FAILURE;
}
}
if (!found) cur = NULL;
}
if (cur) cur->refcnt++;
cursor->cur = cur;
return PR_SUCCESS;
}
static PRStatus findFirstNode(TDBCursor* cursor, TDBNodeRange range[3])
{
PRInt32 fwd;
PRInt32 rev;
PRBool reverse;
PRInt32 tree;
TDBParentChain* parent;
TDBPtr curptr;
TDBRecord* cur;
PRInt64 cmp;
reverse = cursor->reverse;
fwd = reverse ? 0 : 1;
rev = reverse ? 1 : 0;
tree = cursor->tree;
freeParentChain(cursor);
curptr = cursor->db->roots[tree];
cur = NULL;
while (curptr) {
if (cur) {
cur->refcnt++;
parent = PR_NEWZAP(TDBParentChain);
parent->record = cur;
parent->next = cursor->parent;
cursor->parent = parent;
}
cur = tdbLoadRecord(cursor->db, curptr);
PR_ASSERT(cur);
if (!cur) return PR_FAILURE;
cmp = tdbCompareToRange(cur, range, tree);
if (reverse) cmp = -cmp;
if (cmp >= 0) {
curptr = cur->entry[tree].child[rev];
} else {
curptr = cur->entry[tree].child[fwd];
}
}
if (cursor->cur) {
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
}
cursor->cur = cur;
if (cursor->cur) {
cursor->cur->refcnt++;
}
while (cursor->cur != NULL &&
tdbCompareToRange(cursor->cur, range, tree) < 0) {
moveCursorForward(cursor);
}
return PR_SUCCESS;
}
TDBCursor* tdbQueryNolock(TDB* db, TDBNodeRange range[3],
TDBSortSpecification* sortspec)
{
PRInt32 rangescore[3];
PRInt32 tree = -1;
PRInt32 curscore;
PRInt32 bestscore = -1;
PRInt32 i;
TDBCursor* result;
PRBool reverse;
PRStatus status;
result = PR_NEWZAP(TDBCursor);
if (!result) return NULL;
for (i=0 ; i<3 ; i++) {
if (range[i].min) {
result->range[i].min = tdbNodeDup(range[i].min);
if (result->range[i].min == NULL) goto FAIL;
}
if (range[i].max) {
result->range[i].max = tdbNodeDup(range[i].max);
if (result->range[i].max == NULL) goto FAIL;
}
rangescore[i] = 0;
if (range[i].min != NULL || range[i].max != NULL) {
/* Hey, some limitations were specified, we like this key some. */
rangescore[i]++;
if (range[i].min != NULL && range[i].max != NULL) {
/* Ooh, we were given both minimum and maximum, that's better
than only getting one.*/
rangescore[i]++;
if (tdbCompareNodes(range[i].min, range[i].max) == 0) {
/* Say! This key was exactly specified. We like it
best. */
rangescore[i]++;
}
}
}
}
for (i=0 ; i<NUMTREES ; i++) {
curscore = rangescore[key[i][0]] * 100 +
rangescore[key[i][1]] * 10 +
rangescore[key[i][2]];
if (bestscore < curscore) {
bestscore = curscore;
tree = i;
}
}
reverse = sortspec != NULL && sortspec->reverse;
result->reverse = reverse;
result->db = db;
result->tree = tree;
status = findFirstNode(result, range);
if (status != PR_SUCCESS) goto FAIL;
result->nextcursor = db->firstcursor;
if (result->nextcursor) {
result->nextcursor->prevcursor = result;
}
db->firstcursor = result;
tdbFlush(db);
return result;
FAIL:
tdbFreeCursorNolock(result);
return NULL;
}
TDBCursor* TDBQuery(TDB* db, TDBNodeRange range[3],
TDBSortSpecification* sortspec)
{
TDBCursor* result;
PR_Lock(db->mutex);
result = tdbQueryNolock(db, range, sortspec);
PR_Unlock(db->mutex);
return result;
}
PRStatus tdbFreeCursorNolock(TDBCursor* cursor)
{
PRInt32 i;
TDB* db;
PR_ASSERT(cursor);
if (!cursor) return PR_FAILURE;
db = cursor->db;
if (cursor->cur) {
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
}
if (cursor->lasthit) {
cursor->lasthit->refcnt--;
PR_ASSERT(cursor->lasthit->refcnt >= 0);
}
freeParentChain(cursor);
tdbFlush(db);
for (i=0 ; i<3 ; i++) {
PR_FREEIF(cursor->range[i].min);
PR_FREEIF(cursor->range[i].max);
}
if (cursor->prevcursor) {
cursor->prevcursor->nextcursor = cursor->nextcursor;
} else {
db->firstcursor = cursor->nextcursor;
}
if (cursor->nextcursor) {
cursor->nextcursor->prevcursor = cursor->prevcursor;
}
PR_Free(cursor);
return PR_SUCCESS;
}
PRStatus TDBFreeCursor(TDBCursor* cursor)
{
PRStatus status;
TDB* db;
PR_ASSERT(cursor);
if (!cursor) return PR_FAILURE;
db = cursor->db;
PR_Lock(db->mutex);
status = tdbFreeCursorNolock(cursor);
#ifdef DEBUG
if (db->firstcursor == 0) {
/* There are no more cursors. No other thread can be in the middle of
writing stuff, because we have the mutex. And so, there shouldn't
be anything left in our cache of records; they should all be
flushed out. So... */
PR_ASSERT(db->firstrecord == NULL);
}
#endif
PR_Unlock(db->mutex);
return status;
}
TDBTriple* tdbGetResultNolock(TDBCursor* cursor)
{
PRStatus status;
PRInt32 i;
PRInt64 cmp;
TDBNodeRange range[3];
PR_ASSERT(cursor);
if (!cursor) return NULL;
if (cursor->cur == NULL && cursor->lasthit == NULL &&
cursor->triple.data[0] != NULL) {
/* Looks like someone did a write to the database since we last were
here, and therefore threw away all our cached information about
where we were. Go find our place again. */
for (i=0 ; i<3 ; i++) {
range[i].min = cursor->triple.data[i];
range[i].max = NULL;
if (cursor->reverse) {
range[i].max = range[i].min;
range[i].min = NULL;
}
}
status = findFirstNode(cursor, range);
if (status != PR_SUCCESS) return NULL;
if (cursor->cur) {
PRBool match = PR_TRUE;
for (i=0 ; i<3 ; i++) {
if (tdbCompareNodes(cursor->cur->data[i],
cursor->triple.data[i]) != 0) {
match = PR_FALSE;
break;
}
}
if (match) {
/* OK, this node we found was exactly the one we were at
last time. Bump it up one. */
moveCursorForward(cursor);
}
}
}
for (i=0 ; i<3 ; i++) {
PR_FREEIF(cursor->triple.data[i]);
}
if (cursor->lasthit) {
cursor->lasthit->refcnt--;
cursor->lasthit = NULL;
}
if (cursor->cur == NULL) return NULL;
while (cursor->cur != NULL) {
if (tdbMatchesRange(cursor->cur, cursor->range)) {
break;
}
cmp = tdbCompareToRange(cursor->cur, cursor->range, cursor->tree);
if (cursor->reverse ? (cmp < 0) : (cmp > 0)) {
/* We're off the end of the range, all done. */
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
cursor->cur = NULL;
break;
}
cursor->misses++;
moveCursorForward(cursor);
}
if (cursor->cur == NULL) {
tdbFlush(cursor->db);
return NULL;
}
for (i=0 ; i<3 ; i++) {
cursor->triple.data[i] = tdbNodeDup(cursor->cur->data[i]);
}
cursor->lasthit = cursor->cur;
cursor->lasthit->refcnt++;
moveCursorForward(cursor);
cursor->hits++;
#ifdef DEBUG
{
TDBParentChain* tmp;
PR_ASSERT(cursor->cur == NULL || cursor->cur->refcnt > 0);
for (tmp = cursor->parent ; tmp ; tmp = tmp->next) {
PR_ASSERT(tmp->record->refcnt > 0);
}
}
#endif
tdbFlush(cursor->db);
return &(cursor->triple);
}
TDBTriple* TDBGetResult(TDBCursor* cursor)
{
TDBTriple* result;
PR_ASSERT(cursor && cursor->db);
if (!cursor || !cursor->db) return NULL;
PR_Lock(cursor->db->mutex);
result = tdbGetResultNolock(cursor);
PR_Unlock(cursor->db->mutex);
return result;
}
/* Determines where this item falls within the range of items defined.
Negative means this item is too early, and positive means too late.
Zero means that it seems to fall within the range, but you need to do
a call to tdbMatchesRange() to really make sure. The idea here is that
as you slowly walk along the appropriate tree in the DB, this routine
will always return a nondecreasing result. */
PRInt64 tdbCompareToRange(TDBRecord* record, TDBNodeRange* range,
PRInt32 comparerule)
{
int i;
int k;
PR_ASSERT(record != NULL && range != NULL);
if (record == NULL || range == NULL) return PR_FALSE;
PR_ASSERT(comparerule >= 0 && comparerule < NUMTREES);
if (comparerule < 0 || comparerule >= NUMTREES) {
return 0;
}
for (i=0 ; i<3 ; i++) {
k = key[comparerule][i];
if (range[k].min != NULL &&
tdbCompareNodes(record->data[k], range[k].min) < 0) {
return -1;
}
if (range[k].max != NULL &&
tdbCompareNodes(record->data[k], range[k].max) > 0) {
return 1;
}
if (range[k].min == NULL || range[k].max == NULL ||
tdbCompareNodes(range[k].min, range[k].max) != 0) {
return 0;
}
}
return 0;
}
PRBool tdbMatchesRange(TDBRecord* record, TDBNodeRange* range)
{
PRInt32 i;
PR_ASSERT(record != NULL && range != NULL);
if (record == NULL || range == NULL) return PR_FALSE;
for (i=0 ; i<3 ; i++) {
if (range[i].min != NULL &&
tdbCompareNodes(record->data[i], range[i].min) < 0) {
return PR_FALSE;
}
if (range[i].max != NULL &&
tdbCompareNodes(record->data[i], range[i].max) > 0) {
return PR_FALSE;
}
}
return PR_TRUE;
}
void tdbThrowOutCursorCaches(TDB* db)
{
TDBCursor* cursor;
for (cursor = db->firstcursor ; cursor ; cursor = cursor->nextcursor) {
freeParentChain(cursor);
if (cursor->cur) {
cursor->cur->refcnt--;
PR_ASSERT(cursor->cur->refcnt >= 0);
cursor->cur = NULL;
}
if (cursor->lasthit) {
cursor->lasthit->refcnt--;
PR_ASSERT(cursor->lasthit->refcnt >= 0);
cursor->lasthit = NULL;
}
}
}

241
tripledb/record.c Normal file
Просмотреть файл

@ -0,0 +1,241 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* Operations that do things on file record. */
#include "tdbtypes.h"
TDBRecord* tdbLoadRecord(TDB* db, TDBPtr position)
{
TDBRecord* result;
char buf[sizeof(PRUint32)];
char* ptr;
PRInt32 i;
PRInt32 num;
PRInt32 numtoread;
for (result = db->firstrecord ; result ; result = result->next) {
if (result->position == position) {
goto DONE;
}
}
if (PR_Seek(db->fid, position, PR_SEEK_SET) < 0) {
goto DONE;
}
num = PR_Read(db->fid, buf, sizeof(buf));
if (num != sizeof(buf)) {
goto DONE;
}
result = PR_NEWZAP(TDBRecord);
if (result == NULL) goto DONE;
result->position = position;
ptr = buf;
result->length = tdbGetInt32(&ptr);
if (result->length < MINRECORDSIZE || result->length > MAXRECORDSIZE) {
tdbMarkCorrupted(db);
PR_Free(result);
result = NULL;
goto DONE;
}
if (tdbGrowIobuf(db, result->length) != PR_SUCCESS) {
PR_Free(result);
result = NULL;
goto DONE;
}
numtoread = result->length - sizeof(PRInt32);
num = PR_Read(db->fid, db->iobuf, numtoread);
if (num < numtoread) {
PR_Free(result);
result = NULL;
goto DONE;
}
ptr = db->iobuf;
for (i=0 ; i<NUMTREES ; i++) {
result->entry[i].child[0] = tdbGetInt32(&ptr);
result->entry[i].child[1] = tdbGetInt32(&ptr);
result->entry[i].balance = tdbGetInt8(&ptr);
}
for (i=0 ; i<3 ; i++) {
result->data[i] = tdbGetNode(db, &ptr);
if (result->data[i] == NULL) {
while (--i >= 0) {
PR_Free(result->data[i]);
}
PR_Free(result);
result = NULL;
goto DONE;
}
}
if (ptr - db->iobuf != numtoread) {
tdbMarkCorrupted(db);
for (i=0 ; i<3 ; i++) {
PR_Free(result->data[i]);
}
PR_Free(result);
result = NULL;
goto DONE;
}
PR_ASSERT(db->firstrecord != result);
result->next = db->firstrecord;
db->firstrecord = result;
DONE:
return result;
}
PRStatus tdbSaveRecord(TDB* db, TDBRecord* record)
{
PRStatus status;
PRInt32 i;
char* ptr;
if (PR_Seek(db->fid, record->position, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
status = tdbGrowIobuf(db, record->length);
if (status != PR_SUCCESS) return status;
ptr = db->iobuf;
tdbPutInt32(&ptr, record->length);
for (i=0 ; i<NUMTREES ; i++) {
tdbPutInt32(&ptr, record->entry[i].child[0]);
tdbPutInt32(&ptr, record->entry[i].child[1]);
tdbPutInt8(&ptr, record->entry[i].balance);
}
for (i=0 ; i<3 ; i++) {
tdbPutNode(db, &ptr, record->data[i]);
}
PR_ASSERT(ptr - db->iobuf == record->length);
if (PR_Write(db->fid, db->iobuf, record->length) != record->length) {
return PR_FAILURE;
}
return PR_SUCCESS;
}
PRStatus tdbFreeRecord(TDBRecord* record)
{
PRInt32 i;
for (i=0 ; i<3 ; i++) {
PR_Free(record->data[i]);
}
PR_Free(record);
return PR_SUCCESS;
}
TDBRecord* tdbAllocateRecord(TDB* db, TDBNodePtr triple[3])
{
PRInt32 i;
PRInt32 size;
TDBRecord* result = NULL;
TDBRecord* tmp;
TDBPtr position;
size = sizeof(PRInt32) +
NUMTREES * (sizeof(PRInt32) + sizeof(PRInt32) + sizeof(PRInt8)) +
tdbNodeSize(triple[0]) +
tdbNodeSize(triple[1]) +
tdbNodeSize(triple[2]);
position = db->freeroot;
if (position > 0) {
do {
result = tdbLoadRecord(db, position);
if (result->length > size) {
position = result->entry[0].child[0];
} else if (result->length < size) {
position = result->entry[0].child[1];
} else {
/* Hey, we found one! */
if (tdbRemoveFromTree(db, result, -1) != PR_SUCCESS) {
tdbMarkCorrupted(db);
return NULL;
}
break;
}
} while (position != 0);
}
if (position == 0) {
result = PR_NEWZAP(TDBRecord);
if (!result) return NULL;
position = db->filelength;
db->filelength += size;
PR_ASSERT(db->firstrecord != result);
result->next = db->firstrecord;
db->firstrecord = result;
} else {
for (i=0 ; i<3 ; i++) {
PR_Free(result->data[i]);
}
tmp = result->next;
memset(result, 0, sizeof(TDBRecord));
result->next = tmp;
}
result->position = position;
result->length = size;
for (i=0 ; i<3 ; i++) {
result->data[i] = tdbNodeDup(triple[i]);
if (result->data[i] == NULL) {
while (--i >= 0) {
PR_Free(result->data[i]);
}
PR_Free(result);
return NULL;
}
}
result->dirty = PR_TRUE;
return result;
}
/* Argh, this same static array appears in query.c. Don't do that. ### */
static int key[4][3] = {
{0, 1, 2},
{1, 0, 2},
{2, 1, 0},
{1, 2, 0}
};
PRInt64 tdbCompareRecords(TDBRecord* r1, TDBRecord* r2, PRInt32 comparerule)
{
int i, k;
PRInt64 cmp;
if (comparerule == -1) {
cmp = r1->length - r2->length;
if (cmp != 0) return cmp;
return r1->position - r2->position;
}
PR_ASSERT(comparerule >= 0 && comparerule < NUMTREES);
for (i=0 ; i<3 ; i++) {
k = key[comparerule][i];
cmp = tdbCompareNodes(r1->data[k], r2->data[k]);
if (cmp != 0) return cmp;
}
return 0;
}

440
tripledb/tdb.c Normal file
Просмотреть файл

@ -0,0 +1,440 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* tdb.c -- Routines operating on the database as a whole. */
#include "tdbtypes.h"
#include "prprf.h"
TDB* TDBOpen(const char* filename)
{
PRStatus status;
TDB* db;
PR_ASSERT(filename != NULL);
if (filename == NULL) return NULL;
db = PR_NEWZAP(TDB);
if (db == NULL) return NULL;
db->filename = PR_Malloc(strlen(filename) + 1);
if (db->filename == NULL) {
PR_Free(db);
return NULL;
}
strcpy(db->filename, filename);
db->fid = PR_Open(filename, PR_RDWR | PR_CREATE_FILE, 0666);
if (db->fid == NULL) {
/* Can't open file. */
goto FAIL;
}
db->mutex = PR_NewLock();
if (db->mutex == NULL) goto FAIL;
db->callbackcvargo = PR_NewCondVar(db->mutex);
if (db->callbackcvargo == NULL) goto FAIL;
db->callbackcvaridle = PR_NewCondVar(db->mutex);
if (db->callbackcvaridle == NULL) goto FAIL;
db->callbackthread =
PR_CreateThread(PR_SYSTEM_THREAD, tdbCallbackThread, db,
PR_PRIORITY_NORMAL, PR_LOCAL_THREAD,
PR_JOINABLE_THREAD, 0);
if (db->callbackthread == NULL) goto FAIL;
PR_Lock(db->mutex);
while (!db->callbackidle) {
PR_WaitCondVar(db->callbackcvaridle, PR_INTERVAL_NO_TIMEOUT);
}
status = tdbLoadRoots(db);
PR_Unlock(db->mutex);
if (status == PR_FAILURE) goto FAIL;
return db;
FAIL:
TDBClose(db);
return NULL;
}
PRStatus tdbLoadRoots(TDB* db)
{
char buf[TDB_FILEHEADER_SIZE];
char* ptr;
PRInt32 i, length;
PRFileInfo info;
if (PR_Seek(db->fid, 0, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
i = 4 + sizeof(PRInt32) + (NUMTREES + 1) * sizeof(TDBPtr);
length = PR_Read(db->fid, buf, i);
if (length > 0 && length < i) {
/* Ick. This appears to be a short file. Punt. */
return PR_FAILURE;
}
if (length > 0) {
if (memcmp(TDB_MAGIC, buf, 4) != 0) {
/* Bad magic number. */
return PR_FAILURE;
}
ptr = buf + 4;
i = tdbGetInt32(&ptr);
if (i != TDB_VERSION) {
/* Bad version number. */
return PR_FAILURE;
}
db->freeroot = tdbGetInt32(&ptr);
for (i=0 ; i<NUMTREES ; i++) {
db->roots[i] = tdbGetInt32(&ptr);
}
} else {
db->dirty = db->rootdirty = PR_TRUE;
tdbFlush(db); /* Write out the roots the first time, so
that we can accurately read the file
size and stuff. */
}
PR_GetOpenFileInfo(db->fid, &info);
if (info.size < TDB_FILEHEADER_SIZE) {
int length = TDB_FILEHEADER_SIZE - info.size;
if (PR_Seek(db->fid, info.size, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
memset(buf, 0, length);
if (length != PR_Write(db->fid, buf, length)) {
return PR_FAILURE;
}
PR_GetOpenFileInfo(db->fid, &info);
PR_ASSERT(info.size == TDB_FILEHEADER_SIZE);
}
db->filelength = info.size;
return PR_SUCCESS;
}
PRStatus TDBClose(TDB* db)
{
PRStatus result = PR_SUCCESS;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_SUCCESS;
if (db->mutex) PR_Lock(db->mutex);
PR_ASSERT(db->firstcursor == NULL);
if (db->firstcursor != NULL) {
if (db->mutex) PR_Unlock(db->mutex);
return PR_FAILURE;
}
if (db->dirty) {
tdbFlush(db);
}
if (db->callbackthread) {
db->killcallbackthread = PR_TRUE;
PR_NotifyAllCondVar(db->callbackcvargo);
PR_Unlock(db->mutex);
PR_JoinThread(db->callbackthread);
PR_Lock(db->mutex);
}
if (db->callbackcvargo) {
PR_DestroyCondVar(db->callbackcvargo);
}
if (db->callbackcvaridle) {
PR_DestroyCondVar(db->callbackcvaridle);
}
if (db->mutex) {
PR_Unlock(db->mutex);
PR_DestroyLock(db->mutex);
}
if (db->fid) {
result = PR_Close(db->fid);
}
PR_Free(db->filename);
PR_Free(db);
return result;
}
PRStatus TDBRebuildDatabase(TDB* db)
{
PRStatus status;
char* tmpfile;
PRFileInfo info;
PRInt32 count = 0;
TDB* tmpdb;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_SUCCESS;
PR_Lock(db->mutex);
do {
tmpfile = PR_smprintf("%s-tmp-%d", db->filename, ++count);
status = PR_GetFileInfo(tmpfile, &info);
} while (status == PR_SUCCESS);
tmpdb = TDBOpen(tmpfile);
if (!tmpdb) return PR_FAILURE;
/* ### Finish writing me! */
return PR_FAILURE;
}
PRStatus tdbFlush(TDB* db)
{
PRInt32 length;
PRInt32 i;
PRStatus status;
TDBRecord* record;
TDBRecord** recptr;
char* ptr;
PR_ASSERT(db != NULL);
if (db == NULL) return PR_FAILURE;
if (db->rootdirty) {
PR_ASSERT(db->dirty);
if (PR_Seek(db->fid, 0, PR_SEEK_SET) < 0) {
return PR_FAILURE;
}
length = 4 + sizeof(PRInt32) + (NUMTREES + 1) * sizeof(TDBPtr);
status = tdbGrowIobuf(db, length);
if (status != PR_SUCCESS) return status;
ptr = db->iobuf;
memcpy(ptr, TDB_MAGIC, 4);
ptr += 4;
tdbPutInt32(&ptr, TDB_VERSION);
tdbPutInt32(&ptr, db->freeroot);
for (i=0 ; i<NUMTREES ; i++) {
tdbPutInt32(&ptr, db->roots[i]);
}
PR_ASSERT(ptr - db->iobuf <= TDB_FILEHEADER_SIZE);
if (PR_Write(db->fid, db->iobuf, ptr - db->iobuf) != ptr - db->iobuf) {
return PR_FAILURE;
}
db->rootdirty = PR_FALSE;
}
recptr = &(db->firstrecord);
while (*recptr) {
record = *recptr;
if (record->dirty) {
PR_ASSERT(db->dirty);
PR_ASSERT(record->refcnt == 0);
status = tdbSaveRecord(db, record);
if (status != PR_SUCCESS) return status;
}
if (record->refcnt == 0) {
*recptr = record->next;
status = tdbFreeRecord(record);
if (status != PR_SUCCESS) return status;
} else {
PR_ASSERT(record->refcnt > 0);
recptr = (&record->next);
}
}
db->dirty = PR_FALSE;
return PR_SUCCESS;
}
PRStatus tdbThrowAwayUnflushedChanges(TDB* db)
{
TDBRecord* record;
db->dirty = PR_FALSE;
db->rootdirty = PR_FALSE;
for (record = db->firstrecord ; record ; record = record->next) {
record->dirty = PR_FALSE;
}
tdbFlush(db);
return tdbLoadRoots(db);
}
PRStatus tdbGrowIobuf(TDB* db, PRInt32 length)
{
if (length > db->iobuflength) {
db->iobuflength = length;
if (db->iobuf == NULL) {
db->iobuf = PR_Malloc(db->iobuflength);
} else {
db->iobuf = PR_Realloc(db->iobuf, db->iobuflength);
}
if (db->iobuf == NULL) {
return PR_FAILURE;
}
}
return PR_SUCCESS;
}
void tdbMarkCorrupted(TDB* db)
{
PR_ASSERT(0);
/* ### Write me!!! ### */
}
PRInt32 tdbGetInt32(char** ptr)
{
PRInt32 result;
memcpy(&result, *ptr, sizeof(PRInt32));
*ptr += sizeof(PRInt32);
return result;
}
PRInt32 tdbGetInt16(char** ptr)
{
PRInt16 result;
memcpy(&result, *ptr, sizeof(PRInt16));
*ptr += sizeof(PRInt16);
return result;
}
PRInt8 tdbGetInt8(char** ptr)
{
PRInt8 result;
memcpy(&result, *ptr, sizeof(PRInt8));
*ptr += sizeof(PRInt8);
return result;
}
PRInt64 tdbGetInt64(char** ptr)
{
PRInt64 result;
memcpy(&result, *ptr, sizeof(PRInt64));
*ptr += sizeof(PRInt64);
return result;
}
PRUint16 tdbGetUInt16(char** ptr)
{
PRUint16 result;
memcpy(&result, *ptr, sizeof(PRUint16));
*ptr += sizeof(PRUint16);
return result;
}
void tdbPutInt32(char** ptr, PRInt32 value)
{
memcpy(*ptr, &value, sizeof(PRInt32));
*ptr += sizeof(PRInt32);
}
void tdbPutInt16(char** ptr, PRInt16 value)
{
memcpy(*ptr, &value, sizeof(PRInt16));
*ptr += sizeof(PRInt16);
}
void tdbPutUInt16(char** ptr, PRUint16 value)
{
memcpy(*ptr, &value, sizeof(PRUint16));
*ptr += sizeof(PRUint16);
}
void tdbPutInt8(char** ptr, PRInt8 value)
{
memcpy(*ptr, &value, sizeof(PRInt8));
*ptr += sizeof(PRInt8);
}
void tdbPutInt64(char** ptr, PRInt64 value)
{
memcpy(*ptr, &value, sizeof(PRInt64));
*ptr += sizeof(PRInt64);
}
/*
* The below was an attempt to write all these routines in a nice way, so
* that the database would be platform independent. But I keep choking
* sign bits and things. I give up for now. These probably should be
* resurrected and fixed up.
*
* PRInt32 tdbGetInt32(char** ptr)
* {
* PRInt32 result = ((*ptr)[0] << 24) | ((*ptr)[1] << 16) |
* ((*ptr)[2] << 8) | (*ptr)[3];
* (*ptr) += 4;
* return result;
* }
*
* PRInt32 tdbGetInt16(char** ptr)
* {
* PRInt16 result = ((*ptr)[0] << 8) | (*ptr)[1];
* (*ptr) += 2;
* return result;
* }
*
* PRUint16 tdbGetUInt16(char** ptr)
* {
* PRUint16 result = ((*ptr)[0] << 8) | (*ptr)[1];
* (*ptr) += 2;
* return result;
* }
*
* PRInt8 tdbGetInt8(char** ptr)
* {
* return (PRInt8) (*((*ptr)++));
* }
*
*
* PRInt64 tdbGetInt64(char** ptr)
* {
* PRInt64 a = tdbGetInt32(ptr);
* return (a << 32) | tdbGetInt32(ptr);
* }
*
*
* void tdbPutInt32(char** ptr, PRInt32 value)
* {
* *((*ptr)++) = (value >> 24) & 0xff;
* *((*ptr)++) = (value >> 16) & 0xff;
* *((*ptr)++) = (value >> 8) & 0xff;
* *((*ptr)++) = (value) & 0xff;
* }
*
*
* void tdbPutInt16(char** ptr, PRInt16 value)
* {
* *((*ptr)++) = (value >> 8) & 0xff;
* *((*ptr)++) = (value) & 0xff;
* }
*
* void tdbPutUInt16(char** ptr, PRUint16 value)
* {
* *((*ptr)++) = (value >> 8) & 0xff;
* *((*ptr)++) = (value) & 0xff;
* }
*
* void tdbPutInt8(char** ptr, PRInt32 value)
* {
* *((*ptr)++) = value;
* }
*
* void tdbPutInt64(char** ptr, PRInt64 value)
* {
* tdbPutInt32(ptr, ((PRInt32) (value >> 32)));
* tdbPutInt32(ptr, ((PRInt32) (value & 0xffffffff)));
* }
*/

239
tripledb/tdbapi.h Normal file
Просмотреть файл

@ -0,0 +1,239 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* All things are prefixed with TDB, which stands for "Triples
DataBase". Suggestions for better names are always welcome. */
/* A TDBNode contains one of the three items in a triple. This is a
structure that defines a very basic type, strings or ints or dates.
If we decide to add other kinds of basic types (floats? bools? unsigned?),
then this is where we muck things.
It is important that all nodes be strictly ordered. All the integer values
sort together in the obvious way. PRTimes get sorted with them by treating
them as if they were PRInt64's. All strings are considered to be greater
than all integers. */
#define TDBTYPE_INT8 1 /* 8-bit signed integer */
#define TDBTYPE_INT16 2 /* 16-bit signed integer */
#define TDBTYPE_INT32 3 /* 32-bit signed integer */
#define TDBTYPE_INT64 4 /* 64-bit signed integer */
#define TDBTYPE_TIME 5 /* NSPR date&time stamp (PRTime) */
#define TDBTYPE_STRING 6 /* A string (up to 65535 chars long) */
typedef struct {
PRInt8 type;
union {
PRInt64 i; /* All the int types are stored here, as an
Int64. The type just indicates how it is to be
stored in the database. */
struct {
PRUint16 length;
char string[1];
} str;
PRTime time;
} d;
} TDBNode, *TDBNodePtr;
/* A TDBTriple specifies one entry in the database. This is generally thought
of as (subject, verb, object). */
typedef struct {
TDBNodePtr data[3];
} TDBTriple;
/* A TDBNodeRange specifies a range of values for a node. If min is
not NULL, then this matches only nodes that are greater than or
equal to min. If max is not NULL, then this matches only nodes
that are less than or equal to max. Therefore, if both min and
max are NULL, then this specifies the range of all possible nodes.
If min and max are not NULL, and are the same value, then it specifies
exactly that value. */
typedef struct {
TDBNodePtr min;
TDBNodePtr max;
} TDBNodeRange;
/* A TDBSortSpecification specifies what order results from a request should
come in. I suspect that there will someday be much more to it than this. */
typedef struct {
PRBool reverse; /* If true, then return biggest results
first. Otherwise, the smallest
stuff comes first. */
} TDBSortSpecification;
/* A TDB* is an opaque pointer that represents an entire database. */
typedef struct _TDB TDB;
/* A TDBCursor* is an opaque pointer that represents a query that you
are getting results for. */
typedef struct _TDBCursor TDBCursor;
/* TDBCallbackFunction is for callbacks notifying of certain database
changes. */
typedef void (*TDBCallbackFunction)(TDB* database, void* closure,
TDBTriple* triple,
PRInt32 action); /* One of the below
TDBACTION_* values. */
#define TDBACTION_ADDED 1 /* The given triple has been added to the
database. */
#define TDBACTION_REMOVED 2 /* The given triple has been removed from the
database. */
PR_BEGIN_EXTERN_C
/* Open a database (creating it if non-existant). Returns NULL on failure. */
PR_EXTERN(TDB*) TDBOpen(const char* filename);
/* Close an opened database. Frees the storage for TDB; you may not use
that pointer after that call. Will flush out any changes that have
been made. This call will fail if you have not freed up all of the
cursors that were created with TDBQuery. */
PR_EXTERN(PRStatus) TDBClose(TDB* database);
/* TDBQuery() returns a cursor that you can use with TDBNextResult() to get
the results of a query. It will return NULL on failure. If the query
is legal, but there are no matching results, this will *not* return
NULL; it will return a cursor that will have no results.
If a single value is specified for both range[0] and range[1], then the
results are guaranteed to be sorted by range[2]. If a single value is
specified for both range[1] and range[2], then the results are guaranteed
to be sorted by range[0]. No other sorting rules are promised (at least,
not yet.)
A NULL TDBSortSpecification can be provided, which will sort in the
default manner. */
PR_EXTERN(TDBCursor*) TDBQuery(TDB* database, TDBNodeRange range[3],
TDBSortSpecification* sortspec);
/* TDBGetResult returns the next result that matches the cursor, and
advances the cursor to the next matching entry. It will return NULL
when there are no more matching entries. The returned triple must
not be modified in any way by the caller, and is valid only until
the next call to TDBGetResult(), or until the cursor is freed. */
PR_EXTERN(TDBTriple*) TDBGetResult(TDBCursor* cursor);
/* TDBFreeCursor frees the cursor. */
PR_EXTERN(PRStatus) TDBFreeCursor(TDBCursor* cursor);
/* TDBAddCallback calls the given function whenever a change to the database
is made that matches the given range. Note that no guarantees are made
about which thread this call will be done in. It is also possible (though
not very likely) that by the time the callback gets called, further changes
may have been made to the database, and the action being notified here has
already been undone. (If that happens, though, you will get another
callback soon.)
It is legal for the receiver of the callback to make any queries or
modifications it wishes to the database. It is probably not a good idea
to undo the action being notified about; this kind of policy leads to
infinite loops.
The receiver should not take unduly long before returning from the callback;
until the callback returns, no other callbacks will occur.
*/
PR_EXTERN(PRStatus) TDBAddCallback(TDB* database, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure);
/* TDBRemoveCallback will remove a callback that was earlier registered with a
call to TDBAddCallback. */
PR_EXTERN(PRStatus) TDBRemoveCallback(TDB* database, TDBNodeRange range[3],
TDBCallbackFunction func, void* closure);
/* TDBRemove() removes all entries matching the given parameters from
the database. */
PR_EXTERN(PRStatus) TDBRemove(TDB* database, TDBNodeRange range[3]);
/* TDBAdd() adds a triple into the database. */
PR_EXTERN(PRStatus) TDBAdd(TDB* database, TDBNodePtr triple[3]);
/* TDBReplace() looks for an existing entry that matches triple[0] and
triple[1]. It deletes the first such entry found (if any), and
then inserts a new entry. The intention is to replace the "object"
part of an existing triple with a new value. It really only makes
sense if you know up-front that there is no more than one existing
triple with the given "subject" and "verb". */
PR_EXTERN(PRStatus) TDBReplace(TDB* database, TDBNodePtr triple[3]);
/* TDBCreateStringNode() is just a utility routine that correctly
allocates a new TDBNode that represents the given string. The
TDBNode can be free'd using TDBFreeNode(). */
PR_EXTERN(TDBNodePtr) TDBCreateStringNode(const char* string);
/* TDBCreateIntNode() is just a utility routine that correctly
allocates a new TDBNode that represents the given integer. The
TDBNode can be free'd using TDBFreeNode(). You must specify
the correct TDBTYPE_* value for it. */
PR_EXTERN(TDBNodePtr) TDBCreateIntNode(PRInt64 value, PRInt8 type);
/* Free up a node created with TDBCreateStringNode or TDBCreateIntNode. */
PR_EXTERN(void) TDBFreeNode(TDBNodePtr node);
PR_END_EXTERN_C

39
tripledb/tdbdebug.h Normal file
Просмотреть файл

@ -0,0 +1,39 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
/* These are debugging routines for the TDB. Don't use any of these calls
in production code! */
extern void TDBDumpNode(PRFileDesc* fid, TDBNode* node);
extern void TDBDumpTree(PRFileDesc* fid, TDB* db, PRInt32 tree);
extern PRStatus TDBSanityCheck(TDB* db, PRFileDesc* fid);
extern void TDBGetCursorStats(TDBCursor* cursor,
PRInt32* hits,
PRInt32* misses);
/* Create a "dot" graph file. To view these, and learn more about them,
see http://www.research.att.com/~north/cgi-bin/webdot.cgi */
extern void TDBMakeDotGraph(TDB* db, const char* filename, PRInt32 tree);

227
tripledb/tdbtypes.h Normal file
Просмотреть файл

@ -0,0 +1,227 @@
/* -*- Mode: C; indent-tabs-mode: nil; -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the TripleDB database library.
*
* The Initial Developer of the Original Code is Geocast Network Systems.
* Portions created by Geocast are Copyright (C) 1999 Geocast Network Systems.
* All Rights Reserved.
*
* Contributor(s): Terry Weissman <terry@mozilla.org>
*/
#ifndef _TDBtypes_h_
#define _TDBtypes_h_ 1
/* These are private, internal type definitions, */
#include <string.h>
#include "pratom.h"
#include "prcvar.h"
#include "prio.h"
#include "prlock.h"
#include "prlog.h"
#include "prmem.h"
#include "prthread.h"
#include "prtime.h"
#include "prtypes.h"
#include "tdbapi.h"
/* Magic number that appears as first four bytes of db files. */
#define TDB_MAGIC "TDB\n"
/* Total number of bytes we reserve at the beginning of the db file for
special stuff. */
#define TDB_FILEHEADER_SIZE 1024
/* Current version number for db files. */
#define TDB_VERSION 1
#define NUMTREES 4 /* How many binary trees we're keeping,
indexing the data. */
/* The trees index things in the following orders:
0: 0, 1, 2
1: 1, 0, 2
2: 2, 1, 0
3: 1, 2, 0
*/
/* MINRECORDSIZE and MAXRECORDSIZE are the minimum and maximum possible record
size. They're useful for sanity checking. */
#define BASERECORDSIZE (sizeof(PRInt32) + NUMTREES * (2*sizeof(TDBPtr)+sizeof(PRInt8)))
#define MINRECORDSIZE (BASERECORDSIZE + 3 * 2)
#define MAXRECORDSIZE (BASERECORDSIZE + 3 * 65540)
typedef PRInt32 TDBPtr;
typedef struct {
TDBPtr child[2];
PRInt8 balance;
} TDBTreeEntry;
typedef struct _TDBRecord {
TDBPtr position;
PRInt32 length;
PRInt32 refcnt;
PRBool dirty;
struct _TDBRecord* next;
PRUint8 flags;
TDBTreeEntry entry[NUMTREES];
TDBNode* data[3];
} TDBRecord;
typedef struct _TDBParentChain {
TDBRecord* record;
struct _TDBParentChain* next;
} TDBParentChain;
struct _TDBCursor {
TDB* db;
TDBNodeRange range[3];
TDBRecord* cur;
TDBRecord* lasthit;
TDBParentChain* parent;
PRInt32 tree;
PRBool reverse;
TDBTriple triple;
PRInt32 hits;
PRInt32 misses;
struct _TDBCursor* nextcursor;
struct _TDBCursor* prevcursor;
};
typedef struct _TDBCallbackInfo {
struct _TDBCallbackInfo* nextcallback;
TDBNodeRange range[3];
TDBCallbackFunction func;
void* closure;
} TDBCallbackInfo;
typedef struct _TDBPendingCall {
struct _TDBPendingCall* next;
TDBCallbackFunction func;
void* closure;
TDBTriple triple;
PRInt32 action;
} TDBPendingCall;
struct _TDB {
char* filename;
PRInt32 filelength;
PRFileDesc* fid;
TDBPtr roots[NUMTREES];
TDBPtr freeroot;
PRBool dirty;
PRBool rootdirty;
PRLock* mutex; /* Used to prevent more than one thread
from doing anything in DB code at the
same time. */
PRThread* callbackthread; /* Thread ID of the background
callback-calling thread. */
PRCondVar* callbackcvargo; /* Used to wake up the callback-calling
thread. */
PRCondVar* callbackcvaridle; /* Used by the callback-calling to indicate
that it is now idle. */
PRBool killcallbackthread;
PRBool callbackidle;
char* iobuf;
PRInt32 iobuflength;
TDBRecord* firstrecord;
TDBCursor* firstcursor;
TDBCallbackInfo* firstcallback;
TDBPendingCall* firstpendingcall;
TDBPendingCall* lastpendingcall;
};
/* ----------------------------- From add.c: ----------------------------- */
extern PRStatus tdbAddToTree(TDB* db, TDBRecord* record, PRInt32 tree);
extern PRStatus tdbRemoveFromTree(TDB* db, TDBRecord* record, PRInt32 tree);
/* ----------------------------- From callback.c: ------------------------- */
extern void tdbCallbackThread(void* closure);
extern PRStatus tdbQueueMatchingCallbacks(TDB* db, TDBRecord* record,
PRInt32 action);
/* ----------------------------- From node.c: ----------------------------- */
extern TDBNode* tdbNodeDup(TDBNode* node);
extern PRInt32 tdbNodeSize(TDBNode* node);
extern PRInt64 tdbCompareNodes(TDBNode* n1, TDBNode* n2);
extern TDBNode* tdbGetNode(TDB* db, char** ptr);
extern void tdbPutNode(TDB* db, char** ptr, TDBNode* node);
/* ----------------------------- From query.c: ----------------------------- */
extern TDBCursor* tdbQueryNolock(TDB* db, TDBNodeRange range[3],
TDBSortSpecification* sortspec);
extern PRStatus tdbFreeCursorNolock(TDBCursor* cursor);
extern TDBTriple* tdbGetResultNolock(TDBCursor* cursor);
extern PRInt64 tdbCompareToRange(TDBRecord* record, TDBNodeRange* range,
PRInt32 comparerule);
extern PRBool tdbMatchesRange(TDBRecord* record, TDBNodeRange* range);
extern void tdbThrowOutCursorCaches(TDB* db);
/* ----------------------------- From record.c: ---------------------------- */
extern TDBRecord* tdbLoadRecord(TDB* db, TDBPtr position);
extern PRStatus tdbSaveRecord(TDB* db, TDBRecord* record);
extern PRStatus tdbFreeRecord(TDBRecord* record);
extern TDBRecord* tdbAllocateRecord(TDB* db, TDBNodePtr triple[3]);
extern PRInt64 tdbCompareRecords(TDBRecord* r1, TDBRecord* r2,
PRInt32 comparerule);
/* ----------------------------- From tdb.c: ----------------------------- */
extern PRStatus tdbFlush(TDB* db);
extern PRStatus tdbThrowAwayUnflushedChanges(TDB* db);
extern PRStatus tdbGrowIobuf(TDB* db, PRInt32 length);
extern PRStatus tdbLoadRoots(TDB* db);
extern void tdbMarkCorrupted(TDB* db);
extern PRInt32 tdbGetInt32(char** ptr);
extern PRInt32 tdbGetInt16(char** ptr);
extern PRInt8 tdbGetInt8(char** ptr);
extern PRInt64 tdbGetInt64(char** ptr);
extern PRUint16 tdbGetUInt16(char** ptr);
extern void tdbPutInt32(char** ptr, PRInt32 value);
extern void tdbPutInt16(char** ptr, PRInt16 value) ;
extern void tdbPutUInt16(char** ptr, PRUint16 value) ;
extern void tdbPutInt8(char** ptr, PRInt8 value);
extern void tdbPutInt64(char** ptr, PRInt64 value);
#endif /* _TDBtypes_h_ */