mirror of https://gitee.com/bigwinds/arangodb
2691 lines
103 KiB
C
2691 lines
103 KiB
C
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief skiplist implementation
|
|
///
|
|
/// @file
|
|
///
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2004-2012 triagens GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// @author Dr. O
|
|
/// @author Copyright 2006-2012, triAGENS GmbH, Cologne, Germany
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <BasicsC/locks.h>
|
|
#include <BasicsC/logging.h>
|
|
#include <BasicsC/random.h>
|
|
|
|
#include "skiplistEx.h"
|
|
#include "compareEx.h"
|
|
|
|
#define SKIPLIST_EX_ABSOLUTE_MAX_HEIGHT 100
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- SKIPLIST_EX
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- STATIC FORWARD DECLARATIONS
|
|
// --SECTION-- common private functions
|
|
// -----------------------------------------------------------------------------
|
|
|
|
static void DestroyBaseSkipListEx (TRI_skiplistEx_base_t*);
|
|
static void DestroySkipListExNode (TRI_skiplistEx_base_t*, TRI_skiplistEx_node_t*);
|
|
static void FreeSkipListExNode (TRI_skiplistEx_base_t*, TRI_skiplistEx_node_t*);
|
|
static bool GrowNodeHeight (TRI_skiplistEx_node_t*, uint32_t);
|
|
static void* NextNodeBaseSkipListEx (TRI_skiplistEx_base_t*, void*, uint64_t);
|
|
static void* PrevNodeBaseSkipListEx (TRI_skiplistEx_base_t*, void*, uint64_t);
|
|
static int32_t RandLevel (TRI_skiplistEx_base_t*);
|
|
|
|
|
|
// ..............................................................................
|
|
// These operations have to be handled very carefully for transactions which are
|
|
// supposedly lock free.
|
|
// ..............................................................................
|
|
|
|
// ..............................................................................
|
|
// DestroyNodeCAS: Attempts to physically (as opposed to logically) remove the
|
|
// node from the skip list. In the first iteration of a lock
|
|
// free skip list can we call this directly without Garbage
|
|
// Collection -- knowning that no other writers are operating
|
|
// at the same time? No other insertions/deletions are happening.
|
|
//
|
|
// InsertNodeCAS: inserts a node into the skip list - with transaction and
|
|
// lock-free support.
|
|
//
|
|
// RemoveNodeCAS: marks nodes as ghosts to be destroyed as some future time.
|
|
// ..............................................................................
|
|
|
|
static void DestroyNodeCAS (TRI_skiplistEx_node_t*, uint64_t);
|
|
static void InsertNodeCAS (TRI_skiplistEx_node_t*, uint64_t);
|
|
static bool JoinNodesCAS (TRI_skiplistEx_node_t*, TRI_skiplistEx_node_t*, uint32_t, uint32_t);
|
|
static void RemoveNodeCAS (TRI_skiplistEx_node_t*, uint64_t);
|
|
|
|
// ..............................................................................
|
|
// DestroyNodeNoCAS: simply removes the node from its nearest neighbours and
|
|
// joins these nearest neighbours together without being
|
|
// directly concerned about transactions. NOT USED
|
|
//
|
|
// InsertNodeNoCAS: simply joins two nodes and is not concerned directly with
|
|
// transactions. It will however store the transaction number
|
|
// of the writer in the node.
|
|
//
|
|
// RemoveNodeNoCAS: simply marks the node as a ghost node which should be removed
|
|
// at some future time. It will store the transaction number
|
|
// of the writer in the node. NOT USED
|
|
// ..............................................................................
|
|
|
|
static void DestroyNodeNoCAS (TRI_skiplistEx_node_t*, uint64_t);
|
|
static void InsertNodeNoCAS (TRI_skiplistEx_node_t*, uint64_t);
|
|
static void JoinNodesNoCAS (TRI_skiplistEx_node_t*, TRI_skiplistEx_node_t*, uint32_t, uint32_t);
|
|
static void RemoveNodeNoCAS (TRI_skiplistEx_node_t*, uint64_t);
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- unique skiplist constructors and destructors
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @addtogroup Skiplist_unique
|
|
/// @{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief initialises an skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_InitSkipListEx (TRI_skiplistEx_t* skiplist, size_t elementSize,
|
|
int (*compareElementElement) (TRI_skiplistEx_t*, void*, void*, int),
|
|
int (*compareKeyElement) (TRI_skiplistEx_t*, void*, void*, int),
|
|
TRI_skiplistEx_prob_e probability,
|
|
uint32_t maximumHeight,
|
|
uint64_t lastKnownTransID) {
|
|
|
|
bool growResult;
|
|
if (skiplist == NULL) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
// ..........................................................................
|
|
// Assign the STATIC comparision call back functions
|
|
// ..........................................................................
|
|
|
|
skiplist->compareElementElement = IndexStaticCompareElementElement; // compareElementElement;
|
|
skiplist->compareKeyElement = IndexStaticCompareKeyElement; // compareKeyElement;
|
|
|
|
// ..........................................................................
|
|
// Assign the maximum height of the skip list. This maximum height must be
|
|
// no greater than the absolute max height defined as a compile time parameter
|
|
// ..........................................................................
|
|
skiplist->_base._maxHeight = maximumHeight;
|
|
if (maximumHeight > SKIPLIST_EX_ABSOLUTE_MAX_HEIGHT) {
|
|
LOG_ERROR("Invalid maximum height for skiplist");
|
|
assert(false);
|
|
}
|
|
|
|
// ..........................................................................
|
|
// Assign the probability and determine the number of random numbers which
|
|
// we will require -- do it once off here
|
|
// ..........................................................................
|
|
skiplist->_base._prob = probability;
|
|
skiplist->_base._numRandom = 0;
|
|
switch (skiplist->_base._prob) {
|
|
case TRI_SKIPLIST_EX_PROB_HALF: {
|
|
// determine the number of random numbers which we require.
|
|
skiplist->_base._numRandom = (skiplist->_base._maxHeight / 32);
|
|
if ((skiplist->_base._maxHeight % 32) != 0) {
|
|
++(skiplist->_base._numRandom);
|
|
}
|
|
break;
|
|
}
|
|
case TRI_SKIPLIST_EX_PROB_THIRD: {
|
|
// determine the number of random numbers which we require.
|
|
skiplist->_base._numRandom = (skiplist->_base._maxHeight / 16);
|
|
if ((skiplist->_base._maxHeight % 16) != 0) {
|
|
++(skiplist->_base._numRandom);
|
|
}
|
|
break;
|
|
}
|
|
case TRI_SKIPLIST_EX_PROB_QUARTER: {
|
|
// determine the number of random numbers which we require.
|
|
skiplist->_base._numRandom = (skiplist->_base._maxHeight / 16);
|
|
if ((skiplist->_base._maxHeight % 16) != 0) {
|
|
++(skiplist->_base._numRandom);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
// todo: log error
|
|
break;
|
|
}
|
|
} // end of switch statement
|
|
|
|
// ..........................................................................
|
|
// Create storage for where to store the random numbers which we generated
|
|
// do it here once off.
|
|
// ..........................................................................
|
|
skiplist->_base._random = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(uint32_t) * skiplist->_base._numRandom, false);
|
|
// TODO: memory allocation might fail
|
|
|
|
// ..........................................................................
|
|
// Assign the element size
|
|
// ..........................................................................
|
|
skiplist->_base._elementSize = elementSize;
|
|
|
|
|
|
// ..........................................................................
|
|
// Initialise the vertical storage of the lists and the place where we
|
|
// are going to store elements
|
|
// ..........................................................................
|
|
skiplist->_base._startNode._column = NULL;
|
|
skiplist->_base._startNode._colLength = 0;
|
|
skiplist->_base._startNode._extraData = NULL;
|
|
skiplist->_base._startNode._element = NULL;
|
|
skiplist->_base._startNode._delTransID = UINT64_MAX;
|
|
skiplist->_base._startNode._insTransID = lastKnownTransID;
|
|
|
|
|
|
skiplist->_base._endNode._column = NULL;
|
|
skiplist->_base._endNode._colLength = 0;
|
|
skiplist->_base._endNode._extraData = NULL;
|
|
skiplist->_base._endNode._element = NULL;
|
|
skiplist->_base._endNode._delTransID = UINT64_MAX;
|
|
skiplist->_base._endNode._insTransID = lastKnownTransID;
|
|
|
|
// ..........................................................................
|
|
// Whenever a probability of 1/2, 1/3, 1/4 is used, on average there will be
|
|
// each node will have a height of two. So initialise the start and end nodes
|
|
// with this 'average' height
|
|
// ..........................................................................
|
|
growResult = GrowNodeHeight(&(skiplist->_base._startNode), 2); // may fail
|
|
growResult = growResult && GrowNodeHeight(&(skiplist->_base._endNode), 2); // may fail
|
|
if (! growResult) {
|
|
// TODO: undo growth by cutting down the node height
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
// ..........................................................................
|
|
// Join the empty lists together
|
|
// no locking requirements for joining nodes since the skip list index is not known
|
|
// to anyone yet!
|
|
// [N]<----------------------------------->[N]
|
|
// [N]<----------------------------------->[N]
|
|
// ..........................................................................
|
|
JoinNodesNoCAS(&(skiplist->_base._startNode), &(skiplist->_base._endNode), 0, 1); // joins list 0 & 1
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroys a skip list, but does not free the pointer
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void TRI_DestroySkipListEx(TRI_skiplistEx_t* skiplist) {
|
|
if (skiplist != NULL) {
|
|
DestroyBaseSkipListEx( (TRI_skiplistEx_base_t*)(skiplist) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroys a skip list and frees the pointer
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void TRI_FreeSkipListEx(TRI_skiplistEx_t* skiplist) {
|
|
TRI_DestroySkipListEx(skiplist);
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, skiplist);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- unique skiplist public functions
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @addtogroup Skiplist_unique
|
|
/// @{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns the end node associated with a skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// .............................................................................
|
|
// Observe that this is some sort of read transaction. The only possibilitiy
|
|
// we have is that the index must have been created AFTER this read transaction
|
|
// occurred (given that the skip list is valid of course). We do not check
|
|
// for this. (Note: the START (HEAD) and END (TAIL) nodes never change once
|
|
// the skip list is created.)
|
|
// .............................................................................
|
|
|
|
void* TRI_EndNodeSkipListEx(TRI_skiplistEx_t* skiplist) {
|
|
if (skiplist != NULL) {
|
|
return &(skiplist->_base._endNode);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief adds an key/element to the skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_InsertElementSkipListEx(TRI_skiplistEx_t* skiplist, void* element, bool overwrite, uint64_t thisTransID) {
|
|
// Use TRI_InsertKeySkipList instead of calling this method
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief adds an element to the skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_InsertKeySkipListEx (TRI_skiplistEx_t* skiplist,
|
|
void* key,
|
|
void* element,
|
|
bool overwrite,
|
|
uint64_t thisTransID) {
|
|
//This uses the compareKeyElement callback
|
|
int32_t newHeight;
|
|
int32_t currentLevel;
|
|
uint32_t oldColLength;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
TRI_skiplistEx_node_t* newNode;
|
|
TRI_skiplistEx_node_t* tempLeftNode;
|
|
TRI_skiplistEx_node_t* tempRightNode;
|
|
int compareResult;
|
|
bool growResult;
|
|
int j;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the number of levels in which to add the item. That is, determine
|
|
// the height of the node so that it participates in that many lists.
|
|
// ...........................................................................
|
|
|
|
newHeight = RandLevel(&(skiplist->_base));
|
|
|
|
// ...........................................................................
|
|
// Something wrong since the newHeight must be non-negative
|
|
// ...........................................................................
|
|
|
|
if (newHeight < 0) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// convert the level to a height
|
|
// ...........................................................................
|
|
newHeight += 1;
|
|
|
|
|
|
// ...........................................................................
|
|
// Grow lists if required by increasing the height of the start and end nodes
|
|
// ...........................................................................
|
|
oldColLength = skiplist->_base._startNode._colLength;
|
|
if ((uint32_t)(newHeight) > oldColLength) {
|
|
growResult = GrowNodeHeight(&(skiplist->_base._startNode), newHeight);
|
|
growResult = growResult && GrowNodeHeight(&(skiplist->_base._endNode), newHeight);
|
|
if (!growResult) {
|
|
// todo: undo growth by cutting down the node height
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// .........................................................................
|
|
// We use CAS since we want other readers to be able to traverse the
|
|
// skiplist while we are busy making modifications to the start & end nodes
|
|
// .........................................................................
|
|
|
|
growResult = JoinNodesCAS(&(skiplist->_base._startNode),&(skiplist->_base._endNode), oldColLength , newHeight - 1);
|
|
if (!growResult) {
|
|
// todo: yeh exactly what to do if the CAS join nodes failed?
|
|
LOG_ERROR("TRI_InsertKeySkipListEx:CAS Failure:10");
|
|
abort();
|
|
}
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Create the new node to be inserted. If there is some sort of failure,
|
|
// then we delete the node memory.
|
|
// ...........................................................................
|
|
newNode = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_skiplistEx_node_t) + skiplist->_base._elementSize, false);
|
|
if (newNode == NULL) { // out of memory?
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Copy the contents of element into the new node to be inserted.
|
|
// If a duplicate has been found, then we destroy the allocated memory.
|
|
// ...........................................................................
|
|
newNode->_column = NULL;
|
|
newNode->_colLength = 0;
|
|
newNode->_extraData = NULL;
|
|
|
|
j = IndexStaticCopyElementElement(&(skiplist->_base), &(newNode->_element), element);
|
|
if (j != TRI_ERROR_NO_ERROR) {
|
|
return j;
|
|
}
|
|
|
|
growResult = GrowNodeHeight(newNode, newHeight);
|
|
if (!growResult) {
|
|
FreeSkipListExNode(&(skiplist->_base), newNode);
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Assign the deletion transaction id and the insertion transaction id
|
|
// ...........................................................................
|
|
|
|
newNode->_delTransID = UINT64_MAX; // since we are inserting this new node it can not be deleted
|
|
newNode->_insTransID = thisTransID; // this is what was given to us
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the path where the new item is to be inserted. If the item
|
|
// already exists either replace it or return false. Recall that this
|
|
// skip list is used for unique key/value pairs. Use the skiplist-multi
|
|
// non-unique key/value pairs.
|
|
// ...........................................................................
|
|
currentLevel = skiplist->_base._startNode._colLength - 1; // NOT current height BUT current level is required here
|
|
currentNode = &(skiplist->_base._startNode);
|
|
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
nextNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._next);
|
|
|
|
|
|
// .........................................................................
|
|
// Since this skiplist apparently supports transactions we have a few
|
|
// things to consider. ** NOTE ** we are here INSERTING a new node in a
|
|
// UNIQUE skiplist.
|
|
//
|
|
// We attempt to locate the 'position' of where the node should be inserted
|
|
// by imagining we have a KEY-> key + epsilon. Now with the ASSUMPTION that
|
|
// while this WRITER is writing (here) NO NEW writers and readers can be started
|
|
// we check the previous node
|
|
// .........................................................................
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (nextNode == &(skiplist->_base._endNode)) {
|
|
|
|
|
|
// .......................................................................
|
|
// Store the current node and level in the path
|
|
// .......................................................................
|
|
if (currentLevel < newHeight) {
|
|
newNode->_column[currentLevel]._prev = currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// if we are at the lowest level of the lists, insert the item to the
|
|
// right of the current node
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
goto END;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
compareResult = IndexStaticCompareKeyElement(skiplist,key,&(nextNode->_element), 0);
|
|
|
|
|
|
// .......................................................................
|
|
// The element matches the next element.
|
|
// However since we support transactions some things are different and we
|
|
// we have to traead carefully.
|
|
// .......................................................................
|
|
|
|
if (compareResult == 0) {
|
|
|
|
// .....................................................................
|
|
// It may happen that this node is NOT deleted and simply there -
|
|
// check the ins & del transaction numbers.
|
|
// .....................................................................
|
|
|
|
if (nextNode->_insTransID <= thisTransID) {
|
|
// ...................................................................
|
|
// node has been previously inserted
|
|
// ...................................................................
|
|
|
|
if (nextNode->_delTransID > thisTransID) {
|
|
// ...................................................................
|
|
// Node has NOT been deleted (e.g. imagine it will be deleted some time in the future).
|
|
// Treat this as a duplicate key, overwrite if possible and return.
|
|
// We do not allow elements with duplicate 'keys'.
|
|
// ...................................................................
|
|
FreeSkipListExNode(&(skiplist->_base), newNode);
|
|
|
|
if (overwrite) {
|
|
j = IndexStaticCopyElementElement(&(skiplist->_base), &(nextNode->_element), element);
|
|
return j;
|
|
}
|
|
return TRI_set_errno(TRI_ERROR_ARANGO_UNIQUE_CONSTRAINT_VIOLATED);
|
|
}
|
|
|
|
// ...................................................................
|
|
// The only case left here is that the node has been deleted by either
|
|
// this transaction (which could happen in an UPDATE) or by some
|
|
// previous write transaction. Treat this case as if the element is
|
|
// greater than the next node element. Keep going on this level.
|
|
// ...................................................................
|
|
currentNode = nextNode;
|
|
goto START;
|
|
|
|
}
|
|
|
|
|
|
if (nextNode->_insTransID > thisTransID) {
|
|
// ...................................................................
|
|
// Something terrible has happened since writers have been serialized,
|
|
// how is that an existing node has a higher transaction number than
|
|
// this transaction
|
|
// ...................................................................
|
|
printf("%s:%s:%d:Can not be here!\n",__FILE__,__FUNCTION__,__LINE__);
|
|
assert(false); // there is no way we can be here
|
|
}
|
|
|
|
}
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult > 0) {
|
|
currentNode = nextNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// The element is less than the next node. Can we drop down the list?
|
|
// Store the current node and level in the path.
|
|
// .......................................................................
|
|
if (currentLevel < newHeight) {
|
|
newNode->_column[currentLevel]._prev = currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists. Time to insert item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
goto END;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
END:
|
|
|
|
// ..........................................................................
|
|
// Ok finished with the loop and we should have a path with AT MOST
|
|
// SKIPLIST_EX_ABSOLUTE_MAX_HEIGHT number of elements.
|
|
// ..........................................................................
|
|
|
|
|
|
// ..........................................................................
|
|
// this is the tricky part II since we have to attempt to do this as
|
|
// 'lock-free' as possible
|
|
// ..........................................................................
|
|
|
|
// this should be called JoinTower!
|
|
for (j = 0; j < newHeight; ++j) {
|
|
tempLeftNode = newNode->_column[j]._prev;
|
|
tempRightNode = tempLeftNode->_column[j]._next;
|
|
JoinNodesCAS(tempLeftNode, newNode, j, j);
|
|
JoinNodesCAS(newNode, tempRightNode, j, j);
|
|
}
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns greatest node less than a given key
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_LeftLookupByKeySkipListEx(TRI_skiplistEx_t* skiplist, void* key, uint64_t thisTransID) {
|
|
int32_t currentLevel;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the starting level and the starting node
|
|
// ...........................................................................
|
|
|
|
currentLevel = skiplist->_base._startNode._colLength - 1;
|
|
currentNode = &(skiplist->_base._startNode);
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
nextNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._next);
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (nextNode == &(skiplist->_base._endNode)) {
|
|
|
|
|
|
// .......................................................................
|
|
// We are at the lowest level of the lists, and we haven't found the item
|
|
// yet.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
int compareResult;
|
|
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
compareResult = IndexStaticCompareKeyElement(skiplist,key,&(nextNode->_element), -1);
|
|
|
|
|
|
// .......................................................................
|
|
// -1 is returned if the number of fields (attributes) in the key is LESS
|
|
// than the number of fields in the index definition. This has the effect
|
|
// of being slightly less efficient since we have to proceed to the level
|
|
// 0 list in the set of skip lists.
|
|
// .......................................................................
|
|
|
|
|
|
// .......................................................................
|
|
// We have found the item!
|
|
// .......................................................................
|
|
if (compareResult == 0) {
|
|
assert(false);
|
|
}
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult > 0) {
|
|
currentNode = nextNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists -- no such item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// END:
|
|
|
|
assert(false); // there is no way we can be here
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief locate a node using an element
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_LookupByElementSkipListEx (TRI_skiplistEx_t* skiplist, void* element, uint64_t thisTransID) {
|
|
assert(false); // there is no way we can be here
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns node which matches a key
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_LookupByKeySkipListEx (TRI_skiplistEx_t* skiplist, void* key, uint64_t thisTransID) {
|
|
int32_t currentLevel;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the starting level and the starting node
|
|
// ...........................................................................
|
|
|
|
currentLevel = skiplist->_base._startNode._colLength - 1;
|
|
currentNode = &(skiplist->_base._startNode);
|
|
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
nextNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._next);
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (nextNode == &(skiplist->_base._endNode)) {
|
|
|
|
|
|
// .......................................................................
|
|
// We are at the lowest level of the lists, and we haven't found the item
|
|
// yet. Eventually we would like to return iterators.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
int compareResult;
|
|
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
compareResult = IndexStaticCompareKeyElement(skiplist,key,&(nextNode->_element), 0);
|
|
|
|
// .......................................................................
|
|
// We have found the item!
|
|
// .......................................................................
|
|
if (compareResult == 0) {
|
|
return nextNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult > 0) {
|
|
currentNode = nextNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists -- no such item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// END:
|
|
|
|
assert(false); // there is no way we can be here
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief given a node returns the next node (if possible) in the skiplist
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_NextNodeSkipListEx(TRI_skiplistEx_t* skiplist, void* currentNode, uint64_t thisTransID) {
|
|
if (skiplist != NULL) {
|
|
return NextNodeBaseSkipListEx( (TRI_skiplistEx_base_t*)(skiplist), currentNode, thisTransID);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief given a node returns the previous node (if possible) in the skiplist
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_PrevNodeSkipListEx(TRI_skiplistEx_t* skiplist, void* currentNode, uint64_t thisTransID) {
|
|
if (skiplist != NULL) {
|
|
return PrevNodeBaseSkipListEx( (TRI_skiplistEx_base_t*)(skiplist), currentNode, thisTransID);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief removes an element from the skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_RemoveElementSkipListEx (TRI_skiplistEx_t* skiplist, void* element, void* old, uint64_t thisTransID) {
|
|
// This uses the compareElementElement callback
|
|
int32_t currentLevel;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
TRI_skiplistEx_node_t* tempLeftNode;
|
|
TRI_skiplistEx_node_t* tempRightNode;
|
|
int compareResult;
|
|
unsigned int j;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Start at the top most list and left most position of that list.
|
|
// ...........................................................................
|
|
currentLevel = skiplist->_base._startNode._colLength - 1; // we want 'level' not 'height'
|
|
currentNode = &(skiplist->_base._startNode);
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
|
|
nextNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._next);
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (nextNode == &(skiplist->_base._endNode)) {
|
|
|
|
// .......................................................................
|
|
// We are at the lowest level of the lists, and we haven't found the item
|
|
// yet. Nothing to remove so return.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return TRI_WARNING_ARANGO_INDEX_SKIPLIST_REMOVE_ITEM_MISSING;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
compareResult = IndexStaticCompareElementElement(skiplist,element,&(nextNode->_element), -1);
|
|
|
|
// .......................................................................
|
|
// We have found the item!
|
|
// .......................................................................
|
|
if (compareResult == 0) {
|
|
currentNode = nextNode;
|
|
goto END;
|
|
}
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult > 0) {
|
|
currentNode = nextNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists -- no such item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return TRI_WARNING_ARANGO_INDEX_SKIPLIST_REMOVE_ITEM_MISSING;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
END:
|
|
|
|
|
|
// ..........................................................................
|
|
// If requested copy the contents of the element we have located into the
|
|
// storage sent.
|
|
// ..........................................................................
|
|
|
|
if (old != NULL) {
|
|
IndexStaticCopyElementElement(&(skiplist->_base), old, &(currentNode->_element));
|
|
}
|
|
|
|
|
|
// ..........................................................................
|
|
// Attempt to remove the node which will then remove element as well
|
|
// ..........................................................................
|
|
|
|
|
|
for (j = 0; j < currentNode->_colLength; ++j) {
|
|
tempLeftNode = currentNode->_column[j]._prev;
|
|
tempRightNode = currentNode->_column[j]._next;
|
|
JoinNodesCAS(tempLeftNode, tempRightNode, j, j);
|
|
}
|
|
|
|
FreeSkipListExNode(&(skiplist->_base), currentNode);
|
|
return TRI_ERROR_NO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief removes an key/element to the skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_RemoveKeySkipListEx(TRI_skiplistEx_t* skiplist, void* key, void* old, uint64_t thisTransID) {
|
|
// Use the TRI_RemoveElementSkipList method instead.
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns smallest node greater than a given key
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_RightLookupByKeySkipListEx(TRI_skiplistEx_t* skiplist, void* key, uint64_t thisTransID) {
|
|
int32_t currentLevel;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* prevNode;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the starting level and the starting node
|
|
// ...........................................................................
|
|
|
|
currentLevel = skiplist->_base._startNode._colLength - 1;
|
|
currentNode = &(skiplist->_base._endNode);
|
|
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
prevNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._prev);
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (prevNode == &(skiplist->_base._startNode)) {
|
|
|
|
// .......................................................................
|
|
// We are at the lowest level of the lists, and we haven't found the item
|
|
// yet. Eventually we would like to return iterators.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
int compareResult = IndexStaticCompareKeyElement(skiplist, key, &(prevNode->_element), 1);
|
|
|
|
|
|
// .......................................................................
|
|
// If the number of fields (attributes) in the key is LESS than the number
|
|
// of fields in the element to be compared to, then EVEN if the keys which
|
|
// which are common to both equate as EQUAL, we STILL return 1 rather than
|
|
// 0! This ensures that the right interval end point is correctly positioned
|
|
// -- slightly inefficient since the lowest level skip list 0 has to be reached
|
|
// in this case.
|
|
// .......................................................................
|
|
|
|
|
|
// .......................................................................
|
|
// We have found the item!
|
|
// .......................................................................
|
|
if (compareResult == 0) {
|
|
assert(false);
|
|
}
|
|
|
|
// .......................................................................
|
|
// The key is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult < 0) {
|
|
currentNode = prevNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists -- no such item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// END:
|
|
|
|
assert(false); // there is no way we can be here
|
|
return NULL;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns the start node associated with a skip list.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
void* TRI_StartNodeSkipListEx(TRI_skiplistEx_t* skiplist) {
|
|
if (skiplist != NULL) {
|
|
return &(skiplist->_base._startNode);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- non-unique skiplist constructors and destructors
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @addtogroup Skiplist_non_unique
|
|
/// @{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief initialises a multi skip list which allows duplicate entries
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_InitSkipListExMulti (TRI_skiplistEx_multi_t* skiplist,
|
|
size_t elementSize,
|
|
int (*compareElementElement) (TRI_skiplistEx_multi_t*, void*, void*, int),
|
|
int (*compareKeyElement) (TRI_skiplistEx_multi_t*, void*, void*, int),
|
|
bool (*equalElementElement) (TRI_skiplistEx_multi_t*, void*, void*),
|
|
TRI_skiplistEx_prob_e probability,
|
|
uint32_t maximumHeight,
|
|
uint64_t lastKnownTransID) {
|
|
|
|
bool growResult;
|
|
|
|
if (skiplist == NULL) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
// ..........................................................................
|
|
// Assign the comparision call back functions
|
|
// ..........................................................................
|
|
|
|
skiplist->compareElementElement = IndexStaticMultiCompareElementElement; //compareElementElement;
|
|
skiplist->compareKeyElement = IndexStaticMultiCompareKeyElement; // compareKeyElement;
|
|
skiplist->equalElementElement = IndexStaticMultiEqualElementElement; //equalElementElement;
|
|
|
|
// ..........................................................................
|
|
// Assign the maximum height of the skip list. This maximum height must be
|
|
// no greater than the absolute max height defined as a compile time parameter
|
|
// ..........................................................................
|
|
skiplist->_base._maxHeight = maximumHeight;
|
|
if (maximumHeight > SKIPLIST_EX_ABSOLUTE_MAX_HEIGHT) {
|
|
LOG_ERROR("Invalid maximum height for skiplist");
|
|
assert(false);
|
|
}
|
|
|
|
// ..........................................................................
|
|
// Assign the probability and determine the number of random numbers which
|
|
// we will require -- do it once off here
|
|
// ..........................................................................
|
|
skiplist->_base._prob = probability;
|
|
skiplist->_base._numRandom = 0;
|
|
switch (skiplist->_base._prob) {
|
|
case TRI_SKIPLIST_EX_PROB_HALF: {
|
|
// determine the number of random numbers which we require.
|
|
skiplist->_base._numRandom = (skiplist->_base._maxHeight / 32);
|
|
if ((skiplist->_base._maxHeight % 32) != 0) {
|
|
++(skiplist->_base._numRandom);
|
|
}
|
|
break;
|
|
}
|
|
case TRI_SKIPLIST_EX_PROB_THIRD: {
|
|
// determine the number of random numbers which we require.
|
|
skiplist->_base._numRandom = (skiplist->_base._maxHeight / 16);
|
|
if ((skiplist->_base._maxHeight % 16) != 0) {
|
|
++(skiplist->_base._numRandom);
|
|
}
|
|
break;
|
|
}
|
|
case TRI_SKIPLIST_EX_PROB_QUARTER: {
|
|
// determine the number of random numbers which we require.
|
|
skiplist->_base._numRandom = (skiplist->_base._maxHeight / 16);
|
|
if ((skiplist->_base._maxHeight % 16) != 0) {
|
|
++(skiplist->_base._numRandom);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
assert(false);
|
|
// TODO: log error
|
|
break;
|
|
}
|
|
} // end of switch statement
|
|
|
|
// ..........................................................................
|
|
// Create storage for where to store the random numbers which we generated
|
|
// do it here once off.
|
|
// ..........................................................................
|
|
skiplist->_base._random = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(uint32_t) * skiplist->_base._numRandom, false);
|
|
/* TODO: memory allocation might fail */
|
|
|
|
// ..........................................................................
|
|
// Assign the element size
|
|
// ..........................................................................
|
|
skiplist->_base._elementSize = elementSize;
|
|
|
|
|
|
// ..........................................................................
|
|
// Initialise the vertical storage of the lists and the place where we
|
|
// are going to store elements
|
|
// ..........................................................................
|
|
skiplist->_base._startNode._column = NULL;
|
|
skiplist->_base._startNode._colLength = 0;
|
|
skiplist->_base._startNode._extraData = NULL;
|
|
skiplist->_base._startNode._element = NULL;
|
|
|
|
skiplist->_base._endNode._column = NULL;
|
|
skiplist->_base._endNode._colLength = 0;
|
|
skiplist->_base._endNode._extraData = NULL;
|
|
skiplist->_base._endNode._element = NULL;
|
|
|
|
// ..........................................................................
|
|
// Whenever a probability of 1/2, 1/3, 1/4 is used, on average
|
|
// each node will have a height of two. So initialise the start and end nodes
|
|
// with this 'average' height
|
|
// ..........................................................................
|
|
growResult = GrowNodeHeight(&(skiplist->_base._startNode), 2);
|
|
growResult = growResult && GrowNodeHeight(&(skiplist->_base._endNode), 2);
|
|
if (!growResult) {
|
|
// todo: truncate the nodes and return
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
// ..........................................................................
|
|
// Join the empty lists together
|
|
// [N]<----------------------------------->[N]
|
|
// [N]<----------------------------------->[N]
|
|
// ..........................................................................
|
|
JoinNodesNoCAS(&(skiplist->_base._startNode),&(skiplist->_base._endNode),0,1); // joins list 0 & 1
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroys a multi skip list, but does not free the pointer
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void TRI_DestroySkipListExMulti (TRI_skiplistEx_multi_t* skiplist) {
|
|
if (skiplist == NULL) {
|
|
return;
|
|
}
|
|
DestroyBaseSkipListEx( (TRI_skiplistEx_base_t*)(skiplist) );
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroys a multi skip list and frees the pointer
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void TRI_FreeSkipListExMulti (TRI_skiplistEx_multi_t* skiplist) {
|
|
TRI_DestroySkipListExMulti(skiplist);
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, skiplist);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// --SECTION-- non-unique skiplist public methods
|
|
// -----------------------------------------------------------------------------
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @addtogroup Skiplist_non_unique
|
|
/// @{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief Returns the end node associated with a skip list.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_EndNodeSkipListExMulti(TRI_skiplistEx_multi_t* skiplist) {
|
|
if (skiplist != NULL) {
|
|
return &(skiplist->_base._endNode);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns greatest node less than a given key
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_LeftLookupByKeySkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* key, uint64_t thistransID) {
|
|
int32_t currentLevel;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the starting level and the starting node
|
|
// ...........................................................................
|
|
|
|
currentLevel = skiplist->_base._startNode._colLength - 1;
|
|
currentNode = &(skiplist->_base._startNode);
|
|
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
nextNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._next);
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (nextNode == &(skiplist->_base._endNode)) {
|
|
|
|
|
|
// .......................................................................
|
|
// We are at the lowest level of the lists, and we haven't found the item
|
|
// yet. Eventually we would like to return iterators.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
int compareResult = IndexStaticMultiCompareKeyElement(skiplist,key,&(nextNode->_element), -1);
|
|
|
|
|
|
// .......................................................................
|
|
// We have found the item! Not possible
|
|
// .......................................................................
|
|
if (compareResult == 0) {
|
|
//return &(nextNode->_element);
|
|
//return currentNode;
|
|
assert(false);
|
|
return nextNode->_column[0]._prev;
|
|
}
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult > 0) {
|
|
currentNode = nextNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists -- no such item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// END:
|
|
|
|
assert(false); // there is no way we can be here
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief locate a node using an element
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_LookupByElementSkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* element, uint64_t thisTransID) {
|
|
assert(false); // there is no way you should be here
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns node which matches a key
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_LookupByKeySkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* key, uint64_t thisTransID) {
|
|
assert(false); // there is no way you should be here
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief adds an element to a multi skip list using an element for searching
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_InsertElementSkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* element, bool overwrite, uint64_t thisTransID) {
|
|
//This uses the compareElementElement callback
|
|
int32_t newHeight;
|
|
int32_t currentLevel;
|
|
uint32_t oldColLength;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
TRI_skiplistEx_node_t* newNode;
|
|
TRI_skiplistEx_node_t* tempLeftNode;
|
|
TRI_skiplistEx_node_t* tempRightNode;
|
|
bool growResult;
|
|
int compareResult;
|
|
int j;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the number of levels in which to add the item. That is, determine
|
|
// the height of the node so that it participates in that many lists.
|
|
// ...........................................................................
|
|
|
|
newHeight = RandLevel(&(skiplist->_base));
|
|
|
|
// ...........................................................................
|
|
// Something wrong since the newHeight must be non-negative
|
|
// ...........................................................................
|
|
|
|
if (newHeight < 0) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// convert the level to a height
|
|
// ...........................................................................
|
|
newHeight += 1;
|
|
|
|
|
|
// ...........................................................................
|
|
// Grow lists if required by increasing the height of the start and end nodes
|
|
// ...........................................................................
|
|
oldColLength = skiplist->_base._startNode._colLength;
|
|
if ((uint32_t)(newHeight) > oldColLength) {
|
|
growResult = GrowNodeHeight(&(skiplist->_base._startNode), newHeight);
|
|
growResult = growResult && GrowNodeHeight(&(skiplist->_base._endNode), newHeight);
|
|
if (!growResult) {
|
|
// todo: truncate the nodes and return;
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
JoinNodesCAS(&(skiplist->_base._startNode),&(skiplist->_base._endNode), oldColLength , newHeight - 1);
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Create the new node to be inserted. If there is some sort of failure,
|
|
// then we delete the node memory.
|
|
// ...........................................................................
|
|
newNode = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_skiplistEx_node_t) + skiplist->_base._elementSize, false);
|
|
if (newNode == NULL) { // out of memory?
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Copy the contents of element into the new node to be inserted.
|
|
// If a duplicate has been found, then we destroy the allocated memory.
|
|
// ...........................................................................
|
|
newNode->_column = NULL;
|
|
newNode->_colLength = 0;
|
|
newNode->_extraData = NULL;
|
|
|
|
j = IndexStaticCopyElementElement(&(skiplist->_base), &(newNode->_element),element);
|
|
|
|
if (j != TRI_ERROR_NO_ERROR) {
|
|
return j;
|
|
}
|
|
|
|
growResult = GrowNodeHeight(newNode, newHeight);
|
|
if (!growResult) {
|
|
FreeSkipListExNode(&(skiplist->_base), newNode);
|
|
return TRI_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// Determine the path where the new item is to be inserted. If the item
|
|
// already exists either replace it or return false. Recall that this
|
|
// skip list is used for unique key/value pairs. Use the skiplist-multi
|
|
// non-unique key/value pairs.
|
|
// ...........................................................................
|
|
currentLevel = skiplist->_base._startNode._colLength - 1; // NOT current height BUT current level is required here
|
|
currentNode = &(skiplist->_base._startNode);
|
|
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
nextNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._next);
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (nextNode == &(skiplist->_base._endNode)) {
|
|
|
|
|
|
// .......................................................................
|
|
// Store the current node and level in the path
|
|
// .......................................................................
|
|
if (currentLevel < newHeight) {
|
|
newNode->_column[currentLevel]._prev = currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// if we are at the lowest level of the lists, insert the item to the
|
|
// right of the current node
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
goto END;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
compareResult = IndexStaticMultiCompareElementElement(skiplist,element,&(nextNode->_element), -1);
|
|
|
|
|
|
// .......................................................................
|
|
// The element matches the next element. Overwrite if possible and return.
|
|
// We do not allow non-unique elements (non-unique 'keys' ok).
|
|
// .......................................................................
|
|
if (compareResult == 0) {
|
|
FreeSkipListExNode(&(skiplist->_base), newNode);
|
|
if (overwrite) {
|
|
j = IndexStaticCopyElementElement(&(skiplist->_base), &(nextNode->_element),element);
|
|
return j;
|
|
}
|
|
return TRI_ERROR_ARANGO_INDEX_SKIPLIST_INSERT_ITEM_DUPLICATED;
|
|
}
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult > 0) {
|
|
currentNode = nextNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// The element is less than the next node. Can we drop down the list?
|
|
// Store the current node and level in the path.
|
|
// .......................................................................
|
|
if (currentLevel < newHeight) {
|
|
newNode->_column[currentLevel]._prev = currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists. Time to insert item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
goto END;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
END:
|
|
|
|
// ..........................................................................
|
|
// Ok finished with the loop and we should have a path with AT MOST
|
|
// SKIPLIST_ABSOLUTE_MAX_HEIGHT number of elements.
|
|
// ..........................................................................
|
|
|
|
for (j = 0; j < newHeight; ++j) {
|
|
tempLeftNode = newNode->_column[j]._prev;
|
|
tempRightNode = tempLeftNode->_column[j]._next;
|
|
JoinNodesCAS(tempLeftNode, newNode, j, j);
|
|
JoinNodesCAS(newNode, tempRightNode, j, j);
|
|
}
|
|
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief adds an key/element to a multi skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_InsertKeySkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* key, void* element, bool overwrite, uint64_t thisTransID) {
|
|
// Use TRI_InsertelementSkipListEx instead of calling this method
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief given a node returns the next node (if possible) in the skiplist
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_NextNodeSkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* currentNode, uint64_t thisTransID) {
|
|
if (skiplist != NULL) {
|
|
return NextNodeBaseSkipListEx( (TRI_skiplistEx_base_t*)(skiplist), currentNode, thisTransID);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief given a node returns the previous node (if possible) in the skiplist
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_PrevNodeSkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* currentNode, uint64_t thisTransID) {
|
|
if (skiplist != NULL) {
|
|
return PrevNodeBaseSkipListEx( (TRI_skiplistEx_base_t*)(skiplist), currentNode, thisTransID);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief removes a key/element from a multi skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
int TRI_RemoveElementSkipListExMulti (TRI_skiplistEx_multi_t* skiplist, void* element, void* old, uint64_t thisTransID) {
|
|
|
|
int32_t currentLevel;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
TRI_skiplistEx_node_t* tempLeftNode;
|
|
TRI_skiplistEx_node_t* tempRightNode;
|
|
int compareResult;
|
|
unsigned int j;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return TRI_ERROR_INTERNAL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Start at the top most list and left most position of that list.
|
|
// ...........................................................................
|
|
currentLevel = skiplist->_base._startNode._colLength - 1; // current level not height
|
|
currentNode = &(skiplist->_base._startNode);
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
nextNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._next);
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (nextNode == &(skiplist->_base._endNode)) {
|
|
|
|
// .......................................................................
|
|
// We are at the lowest level of the lists, and we haven't found the item
|
|
// yet. Nothing to remove so return.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return TRI_WARNING_ARANGO_INDEX_SKIPLIST_REMOVE_ITEM_MISSING;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
compareResult = IndexStaticMultiCompareElementElement(skiplist,element,&(nextNode->_element), TRI_SKIPLIST_EX_COMPARE_SLIGHTLY_LESS);
|
|
|
|
// .......................................................................
|
|
// We have found an item which matches the key
|
|
// .......................................................................
|
|
if (compareResult == TRI_SKIPLIST_EX_COMPARE_STRICTLY_EQUAL) {
|
|
currentNode = nextNode;
|
|
goto END;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult > 0) {
|
|
currentNode = nextNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists -- no such item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
|
|
// .....................................................................
|
|
// The element could not be located
|
|
// .....................................................................
|
|
if (compareResult == TRI_SKIPLIST_EX_COMPARE_STRICTLY_LESS) {
|
|
return TRI_WARNING_ARANGO_INDEX_SKIPLIST_REMOVE_ITEM_MISSING;
|
|
}
|
|
|
|
// .....................................................................
|
|
// The element could be located (by matching the key) and we are at the lowest level
|
|
// .....................................................................
|
|
if (compareResult == TRI_SKIPLIST_EX_COMPARE_SLIGHTLY_LESS) {
|
|
goto END;
|
|
}
|
|
|
|
// can not occur
|
|
assert(false);
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
END:
|
|
|
|
// ..........................................................................
|
|
// locate the correct elemet -- since we allow duplicates
|
|
// ..........................................................................
|
|
|
|
while (currentNode != NULL) {
|
|
if (IndexStaticMultiEqualElementElement(skiplist, element, &(currentNode->_element))) {
|
|
break;
|
|
}
|
|
currentNode = NextNodeBaseSkipListEx(&(skiplist->_base), currentNode, thisTransID);
|
|
}
|
|
|
|
|
|
// ..........................................................................
|
|
// The actual element could not be located - an element with a matching key
|
|
// may exist, but the same data stored within the element could not be located
|
|
// ..........................................................................
|
|
|
|
if (currentNode == NULL) {
|
|
return TRI_WARNING_ARANGO_INDEX_SKIPLIST_REMOVE_ITEM_MISSING;
|
|
}
|
|
|
|
|
|
// ..........................................................................
|
|
// Perhaps the user wants a copy before we destory the data?
|
|
// ..........................................................................
|
|
|
|
if (old != NULL) {
|
|
IndexStaticCopyElementElement(&(skiplist->_base), old, &(currentNode->_element));
|
|
}
|
|
|
|
|
|
// ..........................................................................
|
|
// remove element
|
|
// ..........................................................................
|
|
|
|
for (j = 0; j < currentNode->_colLength; ++j) {
|
|
tempLeftNode = currentNode->_column[j]._prev;
|
|
tempRightNode = currentNode->_column[j]._next;
|
|
JoinNodesCAS(tempLeftNode, tempRightNode, j, j);
|
|
}
|
|
|
|
FreeSkipListExNode(&(skiplist->_base), currentNode);
|
|
|
|
return TRI_ERROR_NO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief removes a key/element from a multi skip list
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int TRI_RemoveKeySkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* key, void* old, uint64_t thisTransID) {
|
|
// Use the TRI_RemoveElementSkipListExMulti method instead.
|
|
assert(false);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns smallest node greater than a given key
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_RightLookupByKeySkipListExMulti(TRI_skiplistEx_multi_t* skiplist, void* key, uint64_t thisTransID) {
|
|
int32_t currentLevel;
|
|
TRI_skiplistEx_node_t* currentNode;
|
|
TRI_skiplistEx_node_t* prevNode;
|
|
|
|
// ...........................................................................
|
|
// Just in case
|
|
// ...........................................................................
|
|
|
|
if (skiplist == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Determine the starting level and the starting node
|
|
// ...........................................................................
|
|
|
|
currentLevel = skiplist->_base._startNode._colLength - 1;
|
|
currentNode = &(skiplist->_base._endNode);
|
|
|
|
|
|
START:
|
|
|
|
|
|
// .........................................................................
|
|
// Find the next node in the current level of the lists.
|
|
// .........................................................................
|
|
prevNode = (TRI_skiplistEx_node_t*)(currentNode->_column[currentLevel]._prev);
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// WE HAVE FOUR CASES TO CONSIDER
|
|
// .........................................................................
|
|
|
|
// .........................................................................
|
|
// CASE ONE:
|
|
// At this level we have the smallest (start) and largest (end) nodes ONLY.
|
|
// CASE TWO:
|
|
// We have arrived at the end of the nodes and we are not at the
|
|
// start of the nodes either.
|
|
// .........................................................................
|
|
|
|
if (prevNode == &(skiplist->_base._startNode)) {
|
|
|
|
// .......................................................................
|
|
// We are at the lowest level of the lists, and we haven't found the item
|
|
// yet. Eventually we would like to return iterators.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// We have not yet reached the lowest level continue down.
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// .........................................................................
|
|
// CASE THREE:
|
|
// We are the smallest left most node and the NEXT node is NOT the end node.
|
|
// Compare this element with the element in the right node to see what we do.
|
|
// CASE FOUR:
|
|
// We are somewhere in the middle of a list, away from the smallest and
|
|
// largest nodes.
|
|
// .........................................................................
|
|
|
|
else { // nextNode != &(skiplist->_endNode
|
|
// .......................................................................
|
|
// Use the callback to determine if the element is less or greater than
|
|
// the next node element.
|
|
// .......................................................................
|
|
int compareResult = IndexStaticMultiCompareKeyElement(skiplist,key,&(prevNode->_element), 1);
|
|
|
|
|
|
// .......................................................................
|
|
// We have found the item! Not possible since we are searching by key!
|
|
// .......................................................................
|
|
if (compareResult == 0) {
|
|
assert(false);
|
|
}
|
|
|
|
// .......................................................................
|
|
// The element is greater than the next node element. Keep going on this
|
|
// level.
|
|
// .......................................................................
|
|
if (compareResult < 0) {
|
|
currentNode = prevNode;
|
|
goto START;
|
|
}
|
|
|
|
|
|
// .......................................................................
|
|
// We have reached the lowest level of the lists -- no such item.
|
|
// .......................................................................
|
|
if (currentLevel == 0) {
|
|
return currentNode;
|
|
}
|
|
|
|
// .......................................................................
|
|
// Drop down the list
|
|
// .......................................................................
|
|
--currentLevel;
|
|
|
|
goto START;
|
|
}
|
|
|
|
|
|
|
|
// END:
|
|
|
|
assert(false); // there is no way we can be here
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns the start node associated with a multi skip list.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void* TRI_StartNodeSkipListExMulti(TRI_skiplistEx_multi_t* skiplist) {
|
|
return &(skiplist->_base._startNode);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// IMPLEMENTATION OF STATIC FORWARD DECLARED FUNCTIONS
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @addtogroup SkiplistEx_common
|
|
/// @{
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroys a skip list, but does not free the pointer
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void DestroyBaseSkipListEx(TRI_skiplistEx_base_t* baseSkiplist) {
|
|
TRI_skiplistEx_node_t* nextNode;
|
|
TRI_skiplistEx_node_t* oldNextNode;
|
|
|
|
if (baseSkiplist == NULL) {
|
|
return;
|
|
}
|
|
|
|
nextNode = &(baseSkiplist->_startNode);
|
|
while (nextNode != NULL) {
|
|
oldNextNode = nextNode->_column[0]._next;
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, (void*)(nextNode->_column));
|
|
if ((nextNode != &(baseSkiplist->_startNode)) && (nextNode != &(baseSkiplist->_endNode))) {
|
|
IndexStaticDestroyElement(baseSkiplist, &(nextNode->_element));
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, nextNode);
|
|
}
|
|
nextNode = oldNextNode;
|
|
}
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, baseSkiplist->_random);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroys the internal structure allocation for a node
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void DestroySkipListExNode (TRI_skiplistEx_base_t* skiplist, TRI_skiplistEx_node_t* node) {
|
|
if (node == NULL) {
|
|
return;
|
|
}
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, (void*)(node->_column));
|
|
IndexStaticDestroyElement(skiplist, &(node->_element));
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief frees a node, destroying it first
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void FreeSkipListExNode (TRI_skiplistEx_base_t* skiplist, TRI_skiplistEx_node_t* node) {
|
|
DestroySkipListExNode(skiplist, node);
|
|
if ( (node == &(skiplist->_startNode)) ||
|
|
(node == &(skiplist->_endNode)) ) {
|
|
return;
|
|
}
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, node);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief Grow the node at the height specified.
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// .............................................................................
|
|
// WARNING: This function is NOT SAFE to use when we have multiple writers.
|
|
// Tweek required to make it safe.
|
|
// .............................................................................
|
|
|
|
static bool GrowNodeHeight (TRI_skiplistEx_node_t* node, uint32_t newHeight) {
|
|
volatile TRI_skiplistEx_nb_t* newColumn = NULL;
|
|
volatile TRI_skiplistEx_nb_t* oldColumn = node->_column;
|
|
uint32_t j;
|
|
bool result;
|
|
|
|
if (node->_colLength >= newHeight) {
|
|
return true;
|
|
}
|
|
|
|
|
|
// ............................................................................
|
|
// PROBLEM: extending the start & end nodes in particular.
|
|
// We are moving memory around while other readers are reading the start & end
|
|
// nodes! This is what we do.
|
|
// ............................................................................
|
|
|
|
newColumn = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_skiplistEx_nb_t) * newHeight, false);
|
|
|
|
if (newColumn == NULL) { // out of memory?
|
|
return false;
|
|
}
|
|
|
|
if (oldColumn != NULL) {
|
|
memcpy( (void*)(newColumn), (void*)(oldColumn), (node->_colLength * sizeof(TRI_skiplistEx_nb_t)) );
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Initialise the storage
|
|
// ...........................................................................
|
|
|
|
for (j = node->_colLength; j < newHeight; ++j) {
|
|
newColumn[j]._prev = NULL;
|
|
newColumn[j]._next = NULL;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// Unfortunately, this assignment must be done with a CAS statement.
|
|
// ...........................................................................
|
|
|
|
result = TRI_CompareAndSwapPointer((void* volatile*)(&(node->_column)), (void*)(oldColumn), (void*)(newColumn));
|
|
if (!result) {
|
|
// .........................................................................
|
|
// If we have failed here remove the allocated memory and return false,
|
|
// it is up to the calling function to either try again or give up
|
|
// .........................................................................
|
|
TRI_Free(TRI_UNKNOWN_MEM_ZONE, (void*)(newColumn));
|
|
return false;
|
|
}
|
|
|
|
// ...........................................................................
|
|
// Unfortunately, this assignment must be done with a CAS statement.
|
|
// ...........................................................................
|
|
|
|
result = TRI_CompareAndSwapIntegerUInt32 (&(node->_colLength), node->_colLength, newHeight);
|
|
|
|
if (!result) {
|
|
// .........................................................................
|
|
// What to do? The CAS statement has failed. Of course since we only allow
|
|
// one insert at a time, the question arises why this failed.
|
|
// For now we abort. Note that, where we would allow simultaneous inserts,
|
|
// then we must lock the two special nodes (start & end). Also note, that these
|
|
// are the only two nodes we want to lock! Also note, that the locks DO NOT
|
|
// belong in the node structure but are currently defined in the skiplist
|
|
// structure.
|
|
// .........................................................................
|
|
abort();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief performs a Deep copy of a column
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void* CopyDeepColumn(TRI_skiplistEx_nb_t* oldColumn, uint32_t colLength) {
|
|
TRI_skiplistEx_nb_t* newColumn;
|
|
|
|
newColumn = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_skiplistEx_nb_t) * colLength, false);
|
|
if (newColumn == NULL) { // out of memory?
|
|
return NULL;
|
|
}
|
|
return newColumn;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief performs a Quasi Deep copy of a node
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void* CopyQuasiDeepNode(TRI_skiplistEx_node_t* oldNode) {
|
|
TRI_skiplistEx_node_t* newNode;
|
|
TRI_skiplistEx_nb_t* newColumn;
|
|
|
|
newNode = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_skiplistEx_node_t), false);
|
|
if (newNode == NULL) { // out of memory
|
|
return NULL;
|
|
}
|
|
|
|
newColumn = TRI_Allocate(TRI_UNKNOWN_MEM_ZONE, sizeof(TRI_skiplistEx_nb_t) * oldNode->_colLength, false);
|
|
if (newColumn == NULL) { // out of memory?
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// All memory related to the skip list has been deeply assigned. Now do the assignments.
|
|
// Observe that we do not (necessarily) require to deep copy the _element and _extraData
|
|
// fields.
|
|
// ...........................................................................
|
|
memcpy(newColumn, (void*)(oldNode->_column), oldNode->_colLength * sizeof(TRI_skiplistEx_nb_t) );
|
|
|
|
newNode->_flag = oldNode->_flag;
|
|
newNode->_column = oldNode->_column;
|
|
newNode->_extraData = oldNode->_extraData;
|
|
newNode->_element = oldNode->_element;
|
|
newNode->_delTransID = oldNode->_delTransID;
|
|
newNode->_insTransID = oldNode->_insTransID;
|
|
newNode->_colLength = oldNode->_colLength;
|
|
|
|
return newNode;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief destroys node
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
static void DestroyNodeCAS (TRI_skiplistEx_node_t* theNode,
|
|
uint64_t thisTransID) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
|
|
static void InsertNodeCAS (TRI_skiplistEx_node_t* theNode,
|
|
uint64_t thisTransID) {
|
|
assert(false);
|
|
}
|
|
|
|
static bool JoinNodesCAS(TRI_skiplistEx_node_t* leftNode,
|
|
TRI_skiplistEx_node_t* rightNode,
|
|
uint32_t startLevel,
|
|
uint32_t endLevel) {
|
|
uint32_t j;
|
|
|
|
if (startLevel > endLevel) { // something terribly wrong
|
|
assert(false); // internal logic error of some sort
|
|
return false;
|
|
}
|
|
|
|
// change level to height
|
|
endLevel += 1;
|
|
|
|
if (leftNode->_colLength < endLevel) {
|
|
assert(false); // internal logic error of some sort
|
|
return false;
|
|
}
|
|
|
|
if (rightNode->_colLength < endLevel) {
|
|
assert(false); // internal logic error of some sort
|
|
return false;
|
|
}
|
|
|
|
|
|
// ...........................................................................
|
|
// Join these two nodes from the bottom up. We may have a reader which is
|
|
// reading these nodes while we are trying to modifiy them! Notes that, since
|
|
// we do not allow multiple writers, if the CAS fails then something seriously
|
|
// is wrong.
|
|
// ...........................................................................
|
|
|
|
for (j = startLevel; j < endLevel; ++j) {
|
|
bool ok;
|
|
ok = TRI_CompareAndSwapPointer(&((leftNode->_column)[j]._next), (leftNode->_column)[j]._next, rightNode);
|
|
// what to do if not ok? for now abort
|
|
if (!ok) {
|
|
LOG_ERROR("JoinNodesCAS:CAS Failure:10");
|
|
abort();
|
|
}
|
|
ok = TRI_CompareAndSwapPointer(&((rightNode->_column)[j]._prev), (rightNode->_column)[j]._prev, leftNode);
|
|
// what to do if not ok?
|
|
if (!ok) {
|
|
LOG_ERROR("JoinNodesCAS:CAS Failure:20");
|
|
abort();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
static void RemoveNodeCAS (TRI_skiplistEx_node_t* theNode, uint64_t thisTransID) {
|
|
assert(false);
|
|
}
|
|
|
|
|
|
static void DestroyNodeNoCAS (TRI_skiplistEx_node_t* theNode, uint64_t thisTransID) {
|
|
assert(false);
|
|
}
|
|
|
|
static void InsertNodeNoCAS (TRI_skiplistEx_node_t* theNode, uint64_t thisTransID) {
|
|
assert(false);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief joins a left node and right node together
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
static void JoinNodesNoCAS(TRI_skiplistEx_node_t* leftNode,
|
|
TRI_skiplistEx_node_t* rightNode,
|
|
uint32_t startLevel,
|
|
uint32_t endLevel) {
|
|
uint32_t j;
|
|
|
|
if (startLevel > endLevel) { // something wrong
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
// change level to height
|
|
endLevel += 1;
|
|
|
|
if (leftNode->_colLength < endLevel) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
if (rightNode->_colLength < endLevel) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
|
|
for (j = startLevel; j < endLevel; ++j) {
|
|
(leftNode->_column)[j]._next = rightNode;
|
|
(rightNode->_column)[j]._prev = leftNode;
|
|
}
|
|
}
|
|
|
|
|
|
static void RemoveNodeNoCAS (TRI_skiplistEx_node_t* theNode, uint64_t thisTransID) {
|
|
assert(false);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief given a node returns the next node (if possible) in the skiplist
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void* NextNodeBaseSkipListEx(TRI_skiplistEx_base_t* skiplist, void* currentNode, uint64_t thisTransID) {
|
|
TRI_skiplistEx_node_t* node;
|
|
|
|
if (currentNode == NULL) {
|
|
return &(skiplist->_startNode);
|
|
}
|
|
if (currentNode == &(skiplist->_endNode)) {
|
|
return NULL;
|
|
}
|
|
node = (TRI_skiplistEx_node_t*)(currentNode);
|
|
return(node->_column[0]._next);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief given a node returns the next node (if possible) in the skiplist
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static void* PrevNodeBaseSkipListEx(TRI_skiplistEx_base_t* skiplist, void* currentNode, uint64_t thisTransID) {
|
|
TRI_skiplistEx_node_t* node;
|
|
|
|
if (currentNode == NULL) {
|
|
return &(skiplist->_endNode);
|
|
}
|
|
if (currentNode == &(skiplist->_startNode)) {
|
|
return NULL;
|
|
}
|
|
node = (TRI_skiplistEx_node_t*)(currentNode);
|
|
return(node->_column[0]._prev);
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief determines at what 'height' the item is to be added
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
static int32_t RandLevel (TRI_skiplistEx_base_t* skiplist) {
|
|
|
|
uint32_t level = 0;
|
|
int counter = 0;
|
|
uint32_t* ptr = skiplist->_random;
|
|
int j;
|
|
|
|
|
|
// ...........................................................................
|
|
// Obtain the random numbers and store them in the pre allocated storage
|
|
// ...........................................................................
|
|
for (j = 0; j < skiplist->_numRandom; ++j) {
|
|
*ptr = TRI_UInt32Random();
|
|
++ptr;
|
|
}
|
|
ptr = skiplist->_random;
|
|
|
|
|
|
// ...........................................................................
|
|
// Use the bit list to determine the probability of the level.
|
|
// For 1/2: if bit (0) we stop, otherwise increase level.
|
|
// For 1/3: if bits (0,0) we stop, if bits (1,1) ignore and continue, otherwise increase level
|
|
// For 1/4: if bits (0,0) we stop, otherwise increase level
|
|
// ...........................................................................
|
|
switch (skiplist->_prob) {
|
|
|
|
case TRI_SKIPLIST_EX_PROB_HALF: {
|
|
counter = 0;
|
|
while (level < skiplist->_maxHeight) {
|
|
if ((1 & (*ptr)) == 0) {
|
|
break;
|
|
}
|
|
++level;
|
|
(*ptr) = (*ptr) >> 1;
|
|
++counter;
|
|
if (counter == 32) {
|
|
++ptr;
|
|
counter = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TRI_SKIPLIST_EX_PROB_THIRD: {
|
|
while (level < skiplist->_maxHeight) {
|
|
if ((3 & (*ptr)) == 0) {
|
|
break;
|
|
}
|
|
else if ((3 & (*ptr)) == 3) {
|
|
// do nothing do not increase level
|
|
}
|
|
else {
|
|
++level;
|
|
}
|
|
(*ptr) = (*ptr) >> 2;
|
|
++counter;
|
|
if (counter == 16) {
|
|
++ptr;
|
|
counter = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case TRI_SKIPLIST_EX_PROB_QUARTER: {
|
|
counter = 0;
|
|
while (level < skiplist->_maxHeight) {
|
|
if ((3 & (*ptr)) == 0) {
|
|
break;
|
|
}
|
|
++level;
|
|
(*ptr) = (*ptr) >> 2;
|
|
++counter;
|
|
if (counter == 16) {
|
|
++ptr;
|
|
counter = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
return -1;
|
|
}
|
|
}
|
|
return level;
|
|
}
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @}
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// Local Variables:
|
|
// mode: outline-minor
|
|
// outline-regexp: "^\\(/// @brief\\|/// {@inheritDoc}\\|/// @addtogroup\\|// --SECTION--\\|/// @\\}\\)"
|
|
// End:
|