blob: 24aefa8479138ff768ed836ed7a7afcaa32c7abc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*-------------------------------------------------------------------------
*
* cdbcellbuf.c
* a container for small items of constant size
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "cdb/cdbcellbuf.h" /* me */
#include "utils/memutils.h" /* STANDARDCHUNKHEADERSIZE */
/*-------------------------------------------------------------------------
* Initialize/Reset caller's CdbCellBuf
*-------------------------------------------------------------------------
*/
/*
* CdbCellBuf_InitEasy
*
* Initialize caller's CdbCellBuf struct (easy-to-call version).
*
* 'cellbuf' is the area to be initialized as a CdbCellBuf.
* 'cellbytes' is the size of each cell.
*/
void
CdbCellBuf_InitEasy(CdbCellBuf *cellbuf, Size cellbytes)
{
CdbCellBuf_Init(cellbuf,
cellbytes,
NULL, /* initial_cell_area */
0, /* num_initial_cells */
25, /* num_cells_expand */
NULL); /* context */
} /* CdbCellBuf_InitEasy */
/*
* CdbCellBuf_Init
*
* Initialize caller's CdbCellBuf struct.
*
* 'cellbuf' is the area to be initialized as a CdbCellBuf.
* 'cellbytes' is the size of each cell.
* 'initial_cell_area' is an optional memory area, properly aligned, where
* the first few cells can be stored. It must be nearby the CdbCellBuf
* itself, typically in the same struct or local variable stack frame.
* This allows a small number of cells to be managed with good locality
* and no palloc/pfree. If NULL, cell memory is allocated from 'context'
* when needed.
* 'num_initial_cells' is the exact number of cells in the initial_cell_area.
* 'num_cells_expand' is the suggested number of cell slots to be allocated
* whenever additional memory is needed. The actual number will be
* rounded to fully utilize a power-of-2 chunk. However, no expansion
* is done if num_cells_expand is zero; instead, after the initial
* cells are exhausted an error is raised.
* 'context' is the memory context from which to allocate chunks of space
* for additional cells when there is no more room in initial_cell_area.
* If NULL, the CurrentMemoryContext is used.
*/
void
CdbCellBuf_Init(CdbCellBuf *cellbuf,
size_t cellbytes,
void *initial_cell_area,
int num_initial_cells,
int num_cells_expand,
MemoryContext context)
{
Assert(cellbuf != NULL &&
cellbytes > 0 &&
cellbytes < (65535U) &&
num_initial_cells >= 0 &&
num_cells_expand >= 0);
memset(cellbuf, 0, sizeof(*cellbuf));
cellbuf->cellbytes = (uint16)cellbytes;
/* Did caller provide some nearby memory to be used for first few cells? */
if (num_initial_cells > 0)
{
ptrdiff_t offset_nearby = (char *)initial_cell_area - (char *)cellbuf;
cellbuf->offset_nearby = (int16)offset_nearby;
cellbuf->nearby_bunch_len = (int)(num_initial_cells * cellbytes);
cellbuf->cbeg = (char *)initial_cell_area;
cellbuf->cfree = cellbuf->cbeg;
cellbuf->cend = cellbuf->cfree + cellbuf->nearby_bunch_len;
/* Make sure the initial_cell_area is truly nearby. */
Assert(initial_cell_area &&
(char *)cellbuf + cellbuf->offset_nearby == (char *)initial_cell_area);
}
/* Does caller want us to get more space when initial cells are used up? */
if (num_cells_expand > 0)
{
cellbuf->expand_bunch_len = (int)(num_cells_expand * cellbytes);
cellbuf->context = context ? context : CurrentMemoryContext;
}
} /* CdbCellBuf_Init */
/*
* CdbCellBuf_Reset
*
* Free memory held by a cellbuf. Does not free the CdbCellBuf struct.
*
* Should be used to clean up cellbufs initialized by CdbCellBuf_Init()
* or CdbCellBuf_InitEasy(). May be used to empty any cellbuf for reuse.
*
* (NB: An acceptable alternative for cleaning up would be to destroy or
* reset the associated MemoryContext.)
*/
void
CdbCellBuf_Reset(CdbCellBuf *cellbuf)
{
cellbuf->nfull_total = 0;
/* Empty the initial cell area (if any). */
if (cellbuf->nearby_bunch_len > 0)
{
cellbuf->cbeg = (char *)cellbuf + cellbuf->offset_nearby;
cellbuf->cfree = cellbuf->cbeg;
cellbuf->cend = cellbuf->cbeg + cellbuf->nearby_bunch_len;
}
else
{
cellbuf->cbeg = NULL;
cellbuf->cfree = NULL;
cellbuf->cend = NULL;
}
/* Free all expansion bunches. */
if (cellbuf->head)
{
CdbCellBuf_Bumper *prefix = cellbuf->head;
CdbCellBuf_Bumper *tailprefix = cellbuf->tail;
Size cellspace;
cellbuf->head = NULL;
cellbuf->tail = NULL;
cellspace = cellbuf->expand_bunch_len;
/* Free all but the last expansion bunch. */
while (prefix != tailprefix)
{
char *cbeg = (char *)prefix + CDBCELLBUF_BUMPER_BYTES;
CdbCellBuf_Bumper *suffix = (CdbCellBuf_Bumper *)(cbeg + cellspace);
CdbCellBuf_Bumper *nextprefix = suffix->link;
if (prefix->cellbuf != cellbuf ||
suffix->cellbuf != cellbuf ||
!nextprefix ||
nextprefix->cellbuf != cellbuf ||
nextprefix->link != prefix)
break;
pfree(prefix);
prefix = nextprefix;
}
/* Free the last one. */
if (prefix == tailprefix &&
prefix->cellbuf == cellbuf)
pfree(prefix);
/* Warn if bad linkage. */
else
{
char mcname[100];
ereport(WARNING, (errcode(ERRCODE_INTERNAL_ERROR),
errmsg("Unexpected internal error"),
errdetail("Cellbuf error in context '%s'.",
MemoryContextName(cellbuf->context,
NULL,
mcname,
sizeof(mcname))) ));
}
}
} /* CdbCellBuf_Reset */
/*-------------------------------------------------------------------------
* Create/Destroy dynamically allocated CdbCellBuf
*-------------------------------------------------------------------------
*/
/*
* CdbCellBuf_Create
*
* Returns a newly allocated CdbCellBuf.
*
* 'cellbytes' is the size of each cell.
* 'num_initial_cells' is the suggested number of cell slots to be created
* immediately following the CdbCellBuf. This allows a small number
* of cells to be managed with good locality and no palloc/pfree.
* If NULL, all cells are stored in chunks allocated from 'context'.
* The actual number will be rounded up to minimize wasted space.
* 'num_cells_expand' is the suggested number of cell slots to be allocated
* whenever additional memory is needed. The actual number will be
* rounded to fully utilize a power-of-2 chunk. However, no expansion
* is done if num_cells_expand is zero; instead, after the initial
* cells are exhausted an error is raised.
* 'context' is the memory context from which the CdbCellBuf and cells are
* to be allocated. If NULL, the CurrentMemoryContext is used.
*/
CdbCellBuf *
CdbCellBuf_Create(size_t cellbytes,
int num_initial_cells,
int num_cells_expand,
MemoryContext context)
{
return (CdbCellBuf *)CdbCellBuf_CreateWrap(sizeof(CdbCellBuf),
0, /* offsetof_cellbuf_in_struct */
cellbytes,
num_initial_cells,
num_cells_expand,
context);
} /* CdbCellBuf_Create */
/*
* CdbCellBuf_Destroy
*
* Free a CdbCellBuf struct allocated by CdbCellBuf_Create().
*
* Do not use this to free a cellbuf allocated by CdbCellBuf_CreateWrap()
* unless offsetof_cellbuf_in_struct == 0.
*
* NB: CdbCellBuf_Reset() can be used to free the memory owned by a cellbuf
* without freeing the CdbCellBuf struct itself. That is safe for all
* cellbufs.
*
* NB: An acceptable alternative for cleaning up would be to destroy or
* reset the associated MemoryContext.
*/
void
CdbCellBuf_Destroy(CdbCellBuf *cellbuf)
{
if (cellbuf)
{
CdbCellBuf_Reset(cellbuf);
cellbuf->cellbytes = 0;
pfree(cellbuf);
}
} /* CdbCellBuf_Destroy */
/*
* CdbCellBuf_CreateWrap
*
* Returns zeroed, newly allocated memory for a structure of given size,
* with a CdbCellBuf initialized at a given offset within the structure,
* and followed by space for initial cells.
*
* 'sizeof_outer_struct' is the size of the struct in which the CdbCellBuf is
* embedded.
* 'offsetof_cellbuf_in_struct' is the offset of the CdbCellBuf within that.
* 'cellbytes' is the size of each cell.
* 'num_initial_cells' is the suggested number of cell slots to be created
* immediately following the outer struct. This allows a small number
* of cells to be managed with good locality and no palloc/pfree.
* If NULL, all cells are stored in chunks allocated from 'context'.
* The actual number will be rounded up to minimize wasted space.
* 'num_cells_expand' is the suggested number of cell slots to be allocated
* whenever additional memory is needed. The actual number will be
* rounded to fully utilize a power-of-2 chunk. However, no expansion
* is done if num_cells_expand is zero; instead, after the initial
* cells are exhausted an error is raised.
* 'context' is the memory context from which the CdbCellBuf and cells are
* to be allocated. If NULL, the CurrentMemoryContext is used.
*/
void *
CdbCellBuf_CreateWrap(size_t sizeof_outer_struct,
size_t offsetof_cellbuf_in_struct,
size_t cellbytes,
int num_initial_cells,
int num_cells_expand,
MemoryContext context)
{
char *outer_struct;
Assert(cellbytes > 0 &&
num_initial_cells >= 0 &&
num_cells_expand >= 0);
Assert(offsetof_cellbuf_in_struct >= 0 &&
offsetof_cellbuf_in_struct + sizeof(CdbCellBuf) <= sizeof_outer_struct);
sizeof_outer_struct = MAXALIGN(sizeof_outer_struct);
if (!context)
context = CurrentMemoryContext;
/* No initial cells? That's easy... */
if (num_initial_cells == 0)
{
/* Allocate memory for outer struct. */
outer_struct = (char *)MemoryContextAlloc(context, sizeof_outer_struct);
memset(outer_struct, 0, sizeof_outer_struct);
/* Set up CdbCellBuf structure within the outer struct. */
CdbCellBuf_Init((CdbCellBuf *)(outer_struct + offsetof_cellbuf_in_struct),
cellbytes,
NULL, /* initial_cell_area */
0, /* num_initial_cells */
num_cells_expand,
context);
}
/* Allocate enough space to include initial cells following the struct. */
else
{
Size allocbytes;
Size chunkbytes;
Size overhead = sizeof_outer_struct;
/* Round up number of initial cells to fill a power-of-2 chunk. */
chunkbytes = 32;
while (chunkbytes < overhead + cellbytes * num_initial_cells)
chunkbytes <<= 1;
num_initial_cells = (int)((chunkbytes - overhead) / cellbytes);
/* Allocate memory for outer struct plus initial cells. */
allocbytes = sizeof_outer_struct + cellbytes * num_initial_cells;
outer_struct = (char *)MemoryContextAlloc(context, allocbytes);
memset(outer_struct, 0, sizeof_outer_struct);
/* Set up CdbCellBuf structure within the outer struct. */
CdbCellBuf_Init((CdbCellBuf *)(outer_struct + offsetof_cellbuf_in_struct),
cellbytes,
outer_struct + sizeof_outer_struct,
num_initial_cells,
num_cells_expand,
context);
}
return outer_struct;
} /* CdbCellBuf_CreateWrap */
/*-------------------------------------------------------------------------
* Examine cellbuf state
*-------------------------------------------------------------------------
*/
/*
* CdbCellBuf_IsOk
*
* Returns true if the cellbuf passes a consistency check.
*
* 'cellbytes' must be the cell size which was given at initialization.
*/
bool
CdbCellBuf_IsOk(const CdbCellBuf *cellbuf, Size cellbytes)
{
bool ok;
ok = (cellbuf->cbeg <= cellbuf->cfree &&
cellbuf->cfree <= cellbuf->cend &&
cellbuf->cellbytes == cellbytes);
/* No nearby or expansion bunch */
if (cellbuf->cbeg == NULL)
ok = (cellbuf->cend == NULL &&
cellbuf->head == NULL &&
cellbuf->tail == NULL &&
cellbuf->nearby_bunch_len == 0 &&
cellbuf->nfull_total == 0);
/* Does cellbuf have only a nearby bunch? */
else if (cellbuf->cbeg == (char *)cellbuf + cellbuf->offset_nearby)
ok = (cellbuf->cend == cellbuf->cbeg + cellbuf->nearby_bunch_len &&
cellbuf->cfree == cellbuf->nfull_total * cellbytes + cellbuf->cbeg);
/* Does cellbuf end in an expansion bunch? */
else if (cellbuf->head)
ok = (cellbuf->tail &&
cellbuf->cend == cellbuf->cbeg + cellbuf->expand_bunch_len &&
(long)(cellbuf->cfree - cellbuf->cbeg) + cellbuf->nearby_bunch_len <=
cellbuf->nfull_total * cellbytes);
else
ok = false;
return ok;
} /* CdbCellBuf_IsOk */
/*
* CdbCellBuf_LastCellHelper
*
* Helper called by inline function CdbCellBuf_LastCell() when cfree points
* to the first cell in a bunch and the cellbuf is nonempty. Returns ptr to
* the last cell in the next-to-last bunch (never NULL).
*/
void *
CdbCellBuf_LastCellHelper(CdbCellBuf *cellbuf)
{
CdbCellBuf_Iterator it;
CdbCellBuf_Iterator_InitToEnd(&it, cellbuf);
return CdbCellBuf_Iterator_CellAtHelper(&it, -cellbuf->cellbytes);
} /* CdbCellBuf_LastCellHelper */
/*-------------------------------------------------------------------------
* Cell operations
*-------------------------------------------------------------------------
*/
/*
* CdbCellBuf_Append_More
*
* Helper called by inline function CdbCellBuf_Append() to expand cellbuf.
* Returns ptr to an uninitialized, newly allocated cell at tail of cellbuf.
*/
void *
CdbCellBuf_AppendMore(CdbCellBuf *cellbuf)
{
CdbCellBuf_Bumper *bunch;
char *cell;
Size allocbytes;
int overhead = 2 * CDBCELLBUF_BUMPER_BYTES;
Assert(cellbuf->cfree == cellbuf->cend);
/*
* On first call, round the suggested number of cells per bunch to
* fully utilize a power-of-2 chunk.
*
* At the end of every expansion bunch is a suffix bumper, immediately
* adjacent to the last cell. The number of cells per bunch will be
* adjusted if necessary to ensure proper alignment of the suffix bumper.
* For instance, if cellbytes == 6 and MAXALIGN(1) == 8, we adjust the
* number of cells per bunch to a multiple of 4, making expand_bunch_len
* a multiple of 6*4 = 24 bytes.
*/
if (!cellbuf->tail)
{
int bunchbytes;
int rounddown;
int adjcellbytes;
Assert(!cellbuf->head);
/* Error if not enough initial cells and expansion not enabled. */
if (cellbuf->expand_bunch_len == 0)
elog(ERROR, "Cellbuf overflow; %ld cells in use",
cellbuf->nfull_total);
/* Adjust bytes per bunch so suffix bumper will be MAXALIGNed. */
adjcellbytes = cellbuf->cellbytes;
while (adjcellbytes & (MAXALIGN(1)-1))
{
adjcellbytes <<= 1;
cellbuf->expand_bunch_len += adjcellbytes;
}
/* Expansion bunch consists of prefix bumper + cells + suffix bumper. */
allocbytes = overhead + cellbuf->expand_bunch_len;
/* Round to closest power of 2, but don't round down if small. */
bunchbytes = 128;
while (bunchbytes < allocbytes)
bunchbytes <<= 1;
rounddown = bunchbytes >> 1;
if (bunchbytes - allocbytes > allocbytes - rounddown &&
rounddown >= 50 * cellbuf->cellbytes + overhead)
bunchbytes = rounddown;
/* Now deduct overhead and round down to a multiple of the cell size. */
bunchbytes -= overhead;
bunchbytes -= bunchbytes % adjcellbytes;
Assert(bunchbytes >= adjcellbytes);
cellbuf->expand_bunch_len = bunchbytes;
}
else
Assert(cellbuf->head &&
cellbuf->head->cellbuf == cellbuf &&
cellbuf->tail->cellbuf == cellbuf);
/* Allocate memory for prefix bumper + cells + suffix bumper. */
allocbytes = overhead + cellbuf->expand_bunch_len;
bunch = (CdbCellBuf_Bumper *)MemoryContextAlloc(cellbuf->context, allocbytes);
/* Will this be the first expansion bunch? */
if (cellbuf ->tail == NULL)
{
/* Initialize prefix bumper at beginning of new bunch. */
bunch->cellbuf = cellbuf;
bunch->link = NULL;
cellbuf->head = bunch;
}
/* Add to tail of bunch list. */
else
{
CdbCellBuf_Bumper *oldtailsuffix = (CdbCellBuf_Bumper *)cellbuf->cend;
/* Fill in suffix bumper at end of old tail (uninitialized until now) */
Assert(cellbuf->cend == (char *)cellbuf->tail + allocbytes - CDBCELLBUF_BUMPER_BYTES);
oldtailsuffix->cellbuf = cellbuf;
oldtailsuffix->link = bunch;
/* Initialize prefix bumper at beginning of new bunch. */
bunch->cellbuf = cellbuf;
bunch->link = cellbuf->tail;
}
cellbuf->tail = bunch;
/* All cells in new bunch are free. */
cellbuf->cbeg = (char *)bunch + CDBCELLBUF_BUMPER_BYTES;
cellbuf->cend = (char *)bunch + allocbytes - CDBCELLBUF_BUMPER_BYTES;
/* Allocate a cell for caller. */
cell = cellbuf->cbeg;
cellbuf->cfree = cell + cellbuf->cellbytes;
return cell;
} /* CdbCellBuf_AppendMore */
/*
* CdbCellBuf_PopMore
*
* Helper called by inline function CdbCellBuf_Pop() when cfree points
* to the first cell in a bunch and the cellbuf is nonempty. (Note that
* this can occur only when there is a full bunch preceding the empty tail
* bunch.)
*/
void *
CdbCellBuf_PopMore(CdbCellBuf *cellbuf)
{
CdbCellBuf_Bumper *poptail = cellbuf->tail;
CdbCellBuf_Bumper *predecessor = poptail->link;
Assert(cellbuf->cbeg == (char *)poptail + CDBCELLBUF_BUMPER_BYTES &&
cellbuf->cfree == cellbuf->cbeg &&
cellbuf->nfull_total > 0);
/* Freeing the only expansion bunch? */
if (poptail == cellbuf->head)
{
/* Validate before freeing. */
Insist(!predecessor && poptail->cellbuf == cellbuf);
/* Empty the list. */
cellbuf->head = NULL;
cellbuf->tail = NULL;
/* There have to be some nearby cells and they are all occupied. */
Assert(cellbuf->nearby_bunch_len == cellbuf->nfull_total * cellbuf->cellbytes);
cellbuf->cbeg = (char *)cellbuf + cellbuf->offset_nearby;
cellbuf->cend = cellbuf->cbeg + cellbuf->nearby_bunch_len;
}
/* Retreat to the full expansion bunch preceding the empty tail. */
else
{
CdbCellBuf_Bumper *suffix;
/* Validate tail's prefix before following the backward link. */
Insist(predecessor && poptail->cellbuf == cellbuf);
/* Predecessor's prefix isn't touched here except in a debug build. */
Assert(predecessor->cellbuf == cellbuf &&
cellbuf->nfull_total * cellbuf->cellbytes >=
cellbuf->nearby_bunch_len + cellbuf->expand_bunch_len);
cellbuf->tail = predecessor;
cellbuf->cbeg = (char *)predecessor + CDBCELLBUF_BUMPER_BYTES;
cellbuf->cend = cellbuf->cbeg + cellbuf->expand_bunch_len;
/* Caller is about to touch last cell; might as well validate suffix */
suffix = (CdbCellBuf_Bumper *)cellbuf->cend;
Insist(suffix->cellbuf == cellbuf &&
suffix->link == poptail);
suffix->link = NULL;
}
/* Pop the last cell of the full bunch which is now at the tail. */
cellbuf->cfree = cellbuf->cend - cellbuf->cellbytes;
cellbuf->nfull_total--;
/* Free the empty former tail bunch. */
poptail->cellbuf = NULL;
poptail->link = NULL;
pfree(poptail);
/* Let the caller examine the popped cell. */
return cellbuf->cfree;
} /* CdbCellBuf_PopMore */
/*-------------------------------------------------------------------------
* CdbCellBuf_Iterator
*-------------------------------------------------------------------------
*/
/*
* CdbCellBuf_Iterator_Init
*
* Initializes caller's CdbCellBuf_Iterator struct, positioning the iterator
* before the first cell.
*/
void
CdbCellBuf_Iterator_Init(CdbCellBuf_Iterator *it, CdbCellBuf *cellbuf)
{
char *beg;
int len;
Assert(it &&
cellbuf &&
cellbuf->cellbytes > 0);
it->cellbuf = cellbuf;
it->begoff = 0;
/* Position before first nearby cell. */
if (cellbuf->nearby_bunch_len > 0)
{
beg = (char *)cellbuf + cellbuf->offset_nearby;
len = cellbuf->nearby_bunch_len;
}
/* No nearby cells... position before first cell of first expansion bunch */
else if (cellbuf->head)
{
Assert(cellbuf->tail &&
cellbuf->tail->cellbuf == cellbuf &&
cellbuf->head->cellbuf == cellbuf);
/* Start at first bunch */
beg = (char *)cellbuf->head + CDBCELLBUF_BUMPER_BYTES;
len = cellbuf->expand_bunch_len;
}
/* No nearby cells and no bunches */
else
{
Assert(cellbuf->tail == NULL &&
cellbuf->nfull_total == 0);
beg = NULL;
len = 0;
}
it->cbeg = beg;
it->cnext = beg;
it->cend = beg + len;
} /* CdbCellBuf_Iterator_Init */
/*
* CdbCellBuf_Iterator_NextCellHelper
*
* Private helper called by inline function CdbCellBuf_Iterator_Next().
*
* Advances the iterator 'it' to the beginning of the next bunch. If the
* bunch is empty, returns NULL. Otherwise returns a pointer to the first
* cell of the bunch and advances the iterator past that cell.
*/
char *
CdbCellBuf_Iterator_NextCellHelper(CdbCellBuf_Iterator *it)
{
CdbCellBuf *cellbuf = it->cellbuf;
CdbCellBuf_Bumper *nextbunch;
char *cell;
/* Advancing from initial state or nearby bunch into 1st expansion bunch? */
if (it->begoff == 0 &&
(!it->cbeg ||
it->cbeg == (char *)cellbuf + cellbuf->offset_nearby))
{
it->begoff = cellbuf->nearby_bunch_len;
nextbunch = cellbuf->head;
/* Caller is about to touch next cell, so might as well check prefix. */
Insist(nextbunch &&
nextbunch->cellbuf == cellbuf);
Assert(nextbunch->link == NULL);
}
/* Advancing into an expansion bunch after the first. */
else
{
CdbCellBuf_Bumper *suffix = (CdbCellBuf_Bumper *)it->cend;
nextbunch = suffix->link;
/* Caller is about to touch next cell, so might as well check prefix. */
Insist(nextbunch &&
suffix->cellbuf == cellbuf &&
nextbunch->cellbuf == cellbuf);
Assert((char *)nextbunch->link == it->cbeg - CDBCELLBUF_BUMPER_BYTES);
it->begoff += cellbuf->expand_bunch_len;
}
/* Compute beginning and ending cell ptrs for the new bunch. */
it->cbeg = (char *)nextbunch + CDBCELLBUF_BUMPER_BYTES;
it->cend = it->cbeg + cellbuf->expand_bunch_len;
/* If new bunch is tail, it might be empty; if so, this is end of data. */
if (it->cbeg == cellbuf->cfree)
{
it->cnext = it->cbeg;
cell = NULL;
}
else
{
cell = it->cbeg;
it->cnext = cell + cellbuf->cellbytes;
}
return cell;
} /* CdbCellBuf_Iterator_NextCellHelper */
/*
* CdbCellBuf_Iterator_PrevCellHelper
*
* Private helper called by inline function CdbCellBuf_Iterator_Prev().
*
* Suppose C is a bunch of cells in which lies the current position of
* iterator 'it'. If C has no predecessor, this function returns NULL and
* leaves the state unchanged. If bunch B is C's immediate predecessor,
* this function positions the iterator before the last cell of B and
* returns a pointer to that cell.
*/
char *
CdbCellBuf_Iterator_PrevCellHelper(CdbCellBuf_Iterator *it)
{
CdbCellBuf *cellbuf = it->cellbuf;
CdbCellBuf_Bumper *prefix;
CdbCellBuf_Bumper *suffix;
/* Return NULL if in initial state or first bunch. */
if (it->begoff == 0)
return NULL;
Assert(it->cend - it->cbeg == cellbuf->expand_bunch_len &&
it->begoff <= cellbuf->nfull_total * cellbuf->cellbytes);
/* Step back to last cell of nearby bunch. */
if (it->begoff == cellbuf->nearby_bunch_len)
{
it->begoff = 0;
it->cbeg = (char *)cellbuf + cellbuf->offset_nearby;
it->cend = it->cbeg + cellbuf->nearby_bunch_len;
}
/* Step back to last cell of an expansion bunch. */
else
{
Assert(it->begoff >= cellbuf->nearby_bunch_len + cellbuf->expand_bunch_len);
/* Find prefix of current bunch. It points to predecessor's prefix. */
prefix = (CdbCellBuf_Bumper *)(it->cbeg - CDBCELLBUF_BUMPER_BYTES);
Insist(prefix->link &&
prefix->cellbuf == cellbuf);
it->cbeg = (char *)prefix->link + CDBCELLBUF_BUMPER_BYTES;
it->cend = it->cbeg + cellbuf->expand_bunch_len;
it->begoff -= cellbuf->expand_bunch_len;
/* Caller is about to touch last cell, so might as well check suffix. */
suffix = (CdbCellBuf_Bumper *)it->cend;
Insist(suffix->cellbuf == cellbuf &&
suffix->link == prefix);
/* In debug build, check the new front bumper too. */
Assert(prefix->link->cellbuf == cellbuf);
}
it->cnext = it->cend - cellbuf->cellbytes;
return it->cnext;
} /* CdbCellBuf_Iterator_PrevCellHelper */
/*
* CdbCellBuf_Iterator_CellAtHelper
*
* Private helper called by inline function CdbCellBuf_Iterator_CellAt().
*
* Returns a pointer to the cell at a specified offset relative to the
* iterator's current position; or NULL if out of bounds. Does not
* affect the position or state of the iterator.
*/
char *
CdbCellBuf_Iterator_CellAtHelper(CdbCellBuf_Iterator *it, long relCellOff)
{
CdbCellBuf *cellbuf = it->cellbuf;
long absCellOff;
CdbCellBuf_Iterator tempit;
/* Compute offset to requested cell from beginning of cellbuf. */
absCellOff = it->cnext - it->cbeg + it->begoff + relCellOff;
/* Return NULL if request is beyond the bounds of the cellbuf. */
if (absCellOff < 0 ||
absCellOff >= cellbuf->nfull_total * cellbuf->cellbytes)
return NULL;
/* Find the cell, using a local copy of caller's iterator. */
tempit = *it;
CdbCellBuf_Iterator_SetPosHelper(&tempit, absCellOff);
return CdbCellBuf_Iterator_NextCell(&tempit);
} /* CdbCellBuf_Iterator_CellAtHelper */
/*
* CdbCellBuf_Iterator_SetPosHelper
*
* Private helper called by inline function CdbCellBuf_Iterator_SetPos()
* to reposition into a bunch other than the iterator's current bunch.
*
* An internal error is raised if nextCellIndex is outside the bounds
* 0 <= nextCellIndex <= CdbCellBuf_Length(it->cellbuf)
*/
void
CdbCellBuf_Iterator_SetPosHelper(CdbCellBuf_Iterator *it, long nextCellOff)
{
CdbCellBuf *cellbuf = it->cellbuf;
CdbCellBuf_Bumper *prefix;
CdbCellBuf_Bumper *suffix;
/* Backward */
if (nextCellOff < it->begoff)
{
/* Position into nearby bunch */
if (nextCellOff < cellbuf->nearby_bunch_len)
{
Insist(nextCellOff >= 0);
it->begoff = 0;
it->cbeg = (char *)cellbuf + cellbuf->offset_nearby;
it->cend = it->cbeg + cellbuf->nearby_bunch_len;
it->cnext = it->cbeg + nextCellOff;
return;
}
/* If nextCellOff is closer to head, jump to head and advance. */
if (nextCellOff < cellbuf->nearby_bunch_len + it->begoff - nextCellOff)
{
it->begoff = cellbuf->nearby_bunch_len;
prefix = cellbuf->head;
}
/* Retreat from current position. */
else
prefix = (CdbCellBuf_Bumper *)(it->cbeg - CDBCELLBUF_BUMPER_BYTES);
}
/* Forward */
else
{
long endoff = cellbuf->nfull_total * cellbuf->cellbytes;
long tailbegoff = endoff - (cellbuf->cfree - cellbuf->cbeg);
Assert(nextCellOff >= it->cend - it->cbeg + it->begoff);
Insist(nextCellOff <= endoff);
/* If currently in nearby bunch, advance to first expansion bunch. */
if (it->begoff <= cellbuf->nearby_bunch_len)
{
it->begoff = cellbuf->nearby_bunch_len;
prefix = cellbuf->head;
}
/* Current position is in an expansion bunch. Get its prefix. */
else
prefix = (CdbCellBuf_Bumper *)(it->cbeg - CDBCELLBUF_BUMPER_BYTES);
/* If nextCellOff is closer to tail, jump to tail and retreat. */
if (nextCellOff - it->begoff > tailbegoff - nextCellOff)
{
it->begoff = tailbegoff;
prefix = cellbuf->tail;
}
}
Insist(it->cbeg &&
cellbuf->head &&
cellbuf->tail);
/* Retreat to bunch which contains nextCellOff. */
while (nextCellOff < it->begoff)
{
Insist(prefix->cellbuf == cellbuf &&
prefix->link);
Assert(prefix->link->cellbuf == cellbuf);
prefix = prefix->link;
it->begoff -= cellbuf->expand_bunch_len;
}
/* Advance to bunch which contains nextCellOff. */
while (prefix != cellbuf->tail &&
nextCellOff >= it->begoff + cellbuf->expand_bunch_len)
{
suffix = (CdbCellBuf_Bumper *)((char *)prefix + cellbuf->expand_bunch_len);
Insist(suffix->cellbuf == cellbuf &&
suffix->link);
Assert(suffix->link->cellbuf == cellbuf &&
suffix->link->link == prefix);
prefix = suffix->link;
it->begoff += cellbuf->expand_bunch_len;
}
/* Position before specified cell index. */
it->cbeg = (char *)prefix + CDBCELLBUF_BUMPER_BYTES;
it->cend = it->cbeg + cellbuf->expand_bunch_len;
it->cnext = nextCellOff - it->begoff + it->cbeg;
Assert(it->cbeg <= it->cnext &&
it->cnext <= it->cend);
} /* CdbCellBuf_Iterator_SetPosHelper */