mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-02 15:15:23 +00:00
554 lines
17 KiB
C
554 lines
17 KiB
C
/* -*- 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;
|
|
}
|