зеркало из https://github.com/mozilla/gecko-dev.git
Initial checkin of tripledb, the 'triples' database engine.
This commit is contained in:
Родитель
5180239f72
Коммит
b9869ae526
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)));
|
||||
* }
|
||||
*/
|
|
@ -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
|
|
@ -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);
|
|
@ -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_ */
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)));
|
||||
* }
|
||||
*/
|
|
@ -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
|
|
@ -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);
|
|
@ -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_ */
|
Загрузка…
Ссылка в новой задаче