2013-01-16 11:28:58 +00:00
|
|
|
/* Part of SWI-Prolog
|
2008-12-22 12:02:22 +00:00
|
|
|
|
|
|
|
Author: Jan Wielemaker
|
2013-01-16 11:28:58 +00:00
|
|
|
E-mail: J.Wielemaker@vu.nl
|
2008-12-22 12:02:22 +00:00
|
|
|
WWW: http://www.swi-prolog.org
|
2013-01-16 11:28:58 +00:00
|
|
|
Copyright (C): 1985-2012, University of Amsterdam
|
|
|
|
VU University Amsterdam
|
2008-12-22 12:02:22 +00:00
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with this library; if not, write to the Free Software
|
2013-01-16 11:28:58 +00:00
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
2008-12-22 12:02:22 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*#define O_DEBUG 1*/
|
|
|
|
#include "pl-incl.h"
|
|
|
|
#ifdef O_PLMT
|
|
|
|
#define LOCK_TABLE(t) if ( t->mutex ) simpleMutexLock(t->mutex)
|
|
|
|
#define UNLOCK_TABLE(t) if ( t->mutex ) simpleMutexUnlock(t->mutex)
|
|
|
|
#else
|
|
|
|
#define LOCK_TABLE(t) (void)0
|
|
|
|
#define UNLOCK_TABLE(t) (void)0
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static inline Symbol rawAdvanceTableEnum(TableEnum e);
|
|
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
This file provides generic hash-tables. Most of the implementation is
|
|
|
|
rather straightforward. Special are the *TableEnum() functions to
|
|
|
|
create, advance over and destroy enumerator objects. These objects are
|
|
|
|
used to enumerate the symbols of these tables, used primarily for the
|
|
|
|
pl_current_* predicates.
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
The enumerators cause two things: (1) as long as enumerators are
|
2008-12-22 12:02:22 +00:00
|
|
|
associated, the table will not be rehashed and (2) if symbols are
|
|
|
|
deleted that are referenced by an enumerator, the enumerator is
|
2013-01-16 11:28:58 +00:00
|
|
|
automatically advanced to the next free symbol. This, in general, makes
|
2008-12-22 12:02:22 +00:00
|
|
|
the enumeration of hash-tables safe.
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
TBD: Resizing hash-tables causes major headaches for concurrent access.
|
|
|
|
We can avoid this by using a dynamic array for the list of hash-entries.
|
|
|
|
Ongoing work in the RDF store shows hash-tables that can handle
|
|
|
|
concurrent lock-free access.
|
2008-12-22 12:02:22 +00:00
|
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
static Symbol *
|
|
|
|
allocHTableEntries(int buckets)
|
|
|
|
{ size_t bytes = buckets * sizeof(Symbol);
|
2008-12-22 12:02:22 +00:00
|
|
|
Symbol *p;
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
p = allocHeapOrHalt(bytes);
|
|
|
|
memset(p, 0, bytes);
|
2008-12-22 12:02:22 +00:00
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
return p;
|
2008-12-22 12:02:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Table
|
|
|
|
newHTable(int buckets)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ Table ht;
|
2008-12-22 12:02:22 +00:00
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
ht = allocHeapOrHalt(sizeof(struct table));
|
2008-12-22 12:02:22 +00:00
|
|
|
ht->buckets = (buckets & ~TABLE_MASK);
|
|
|
|
ht->size = 0;
|
|
|
|
ht->enumerators = NULL;
|
|
|
|
ht->free_symbol = NULL;
|
|
|
|
ht->copy_symbol = NULL;
|
|
|
|
#ifdef O_PLMT
|
|
|
|
if ( (buckets & TABLE_UNLOCKED) )
|
|
|
|
ht->mutex = NULL;
|
|
|
|
else
|
2013-01-16 11:28:58 +00:00
|
|
|
{ ht->mutex = allocHeapOrHalt(sizeof(simpleMutex));
|
2008-12-22 12:02:22 +00:00
|
|
|
simpleMutexInit(ht->mutex);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
ht->entries = allocHTableEntries(ht->buckets);
|
2008-12-22 12:02:22 +00:00
|
|
|
return ht;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
destroyHTable(Table ht)
|
2013-01-16 11:28:58 +00:00
|
|
|
{
|
2008-12-22 12:02:22 +00:00
|
|
|
#ifdef O_PLMT
|
|
|
|
if ( ht->mutex )
|
|
|
|
{ simpleMutexDelete(ht->mutex);
|
|
|
|
freeHeap(ht->mutex, sizeof(*ht->mutex));
|
2013-01-16 11:28:58 +00:00
|
|
|
ht->mutex = NULL;
|
2008-12-22 12:02:22 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
clearHTable(ht);
|
|
|
|
freeHeap(ht->entries, ht->buckets * sizeof(Symbol));
|
|
|
|
freeHeap(ht, sizeof(struct table));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
#if O_DEBUG
|
2008-12-22 12:02:22 +00:00
|
|
|
static int lookups;
|
|
|
|
static int cmps;
|
|
|
|
|
2013-11-15 01:10:25 +00:00
|
|
|
int
|
2008-12-22 12:02:22 +00:00
|
|
|
exitTables(int status, void *arg)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ (void)status;
|
|
|
|
(void)arg;
|
|
|
|
|
|
|
|
Sdprintf("hashstat: Anonymous tables: %d lookups using %d compares\n",
|
2008-12-22 12:02:22 +00:00
|
|
|
lookups, cmps);
|
2013-11-15 01:10:25 +00:00
|
|
|
|
|
|
|
return 0;
|
2008-12-22 12:02:22 +00:00
|
|
|
}
|
2013-01-16 11:28:58 +00:00
|
|
|
#endif
|
2008-12-22 12:02:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
void
|
2011-02-10 00:02:05 +00:00
|
|
|
initTables(void)
|
2008-12-22 12:02:22 +00:00
|
|
|
{ static int done = FALSE;
|
|
|
|
|
|
|
|
if ( !done )
|
|
|
|
{ done = TRUE;
|
2011-02-10 00:01:19 +00:00
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
DEBUG(MSG_HASH_STAT, PL_on_halt(exitTables, NULL));
|
2008-12-22 12:02:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Symbol
|
|
|
|
lookupHTable(Table ht, void *name)
|
|
|
|
{ Symbol s = ht->entries[pointerHashValue(name, ht->buckets)];
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
DEBUG(MSG_HASH_STAT, lookups++);
|
2008-12-22 12:02:22 +00:00
|
|
|
for( ; s; s = s->next)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ DEBUG(MSG_HASH_STAT, cmps++);
|
2008-12-22 12:02:22 +00:00
|
|
|
if ( s->name == name )
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef O_DEBUG
|
|
|
|
void
|
|
|
|
checkHTable(Table ht)
|
|
|
|
{ int i;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
for(i=0; i<ht->buckets; i++)
|
|
|
|
{ Symbol s;
|
|
|
|
|
|
|
|
for(s=ht->entries[i]; s; s=s->next)
|
|
|
|
{ assert(lookupHTable(ht, s->name) == s);
|
|
|
|
n++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(n == ht->size);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* MT: Locked by calling addHTable()
|
|
|
|
*/
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
static Symbol
|
|
|
|
rehashHTable(Table ht, Symbol map)
|
|
|
|
{ Symbol *newentries, *oldentries;
|
|
|
|
int newbuckets, oldbuckets;
|
|
|
|
int i;
|
2013-11-15 01:10:25 +00:00
|
|
|
#if P_PLMT
|
2013-01-16 11:28:58 +00:00
|
|
|
int safe_copy = (ht->mutex != NULL);
|
2013-01-16 12:28:37 +00:00
|
|
|
#else
|
|
|
|
int safe_copy = TRUE;
|
|
|
|
#endif
|
2008-12-22 12:02:22 +00:00
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
newbuckets = ht->buckets*2;
|
|
|
|
newentries = allocHTableEntries(newbuckets);
|
2008-12-22 12:02:22 +00:00
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
DEBUG(MSG_HASH_STAT,
|
|
|
|
Sdprintf("Rehashing table %p to %d entries\n", ht, ht->buckets));
|
2008-12-22 12:02:22 +00:00
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
for(i=0; i<ht->buckets; i++)
|
2008-12-22 12:02:22 +00:00
|
|
|
{ Symbol s, n;
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
if ( safe_copy )
|
|
|
|
{ for(s=ht->entries[i]; s; s = n)
|
|
|
|
{ int v = (int)pointerHashValue(s->name, newbuckets);
|
|
|
|
Symbol s2 = allocHeapOrHalt(sizeof(*s2));
|
|
|
|
|
|
|
|
n = s->next;
|
|
|
|
if ( s == map )
|
|
|
|
map = s2;
|
|
|
|
*s2 = *s;
|
|
|
|
s2->next = newentries[v];
|
|
|
|
newentries[v] = s2;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
{ for(s=ht->entries[i]; s; s = n)
|
|
|
|
{ int v = (int)pointerHashValue(s->name, newbuckets);
|
|
|
|
|
|
|
|
n = s->next;
|
|
|
|
s->next = newentries[v];
|
|
|
|
newentries[v] = s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oldentries = ht->entries;
|
|
|
|
oldbuckets = ht->buckets;
|
|
|
|
ht->entries = newentries;
|
|
|
|
ht->buckets = newbuckets;
|
|
|
|
|
|
|
|
if ( safe_copy )
|
|
|
|
{ /* Here we should be waiting until */
|
|
|
|
/* active lookup are finished */
|
|
|
|
for(i=0; i<oldbuckets; i++)
|
|
|
|
{ Symbol s, n;
|
2008-12-22 12:02:22 +00:00
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
for(s=oldentries[i]; s; s = n)
|
|
|
|
{ n = s->next;
|
|
|
|
|
|
|
|
s->next = NULL; /* that causes old readers to stop */
|
|
|
|
freeHeap(s, sizeof(*s));
|
|
|
|
}
|
2008-12-22 12:02:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
freeHeap(oldentries, oldbuckets * sizeof(Symbol));
|
|
|
|
DEBUG(CHK_SECURE, checkHTable(ht));
|
|
|
|
|
|
|
|
return map;
|
2008-12-22 12:02:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Symbol
|
|
|
|
addHTable(Table ht, void *name, void *value)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ Symbol s;
|
2008-12-22 12:02:22 +00:00
|
|
|
int v;
|
|
|
|
|
|
|
|
LOCK_TABLE(ht);
|
|
|
|
v = (int)pointerHashValue(name, ht->buckets);
|
|
|
|
if ( lookupHTable(ht, name) )
|
|
|
|
{ UNLOCK_TABLE(ht);
|
|
|
|
return NULL;
|
|
|
|
}
|
2013-01-16 11:28:58 +00:00
|
|
|
s = allocHeapOrHalt(sizeof(struct symbol));
|
2008-12-22 12:02:22 +00:00
|
|
|
s->name = name;
|
|
|
|
s->value = value;
|
|
|
|
s->next = ht->entries[v];
|
|
|
|
ht->entries[v] = s;
|
|
|
|
ht->size++;
|
|
|
|
DEBUG(9, Sdprintf("addHTable(0x%x, 0x%x, 0x%x) --> size = %d\n",
|
|
|
|
ht, name, value, ht->size));
|
|
|
|
|
|
|
|
if ( ht->buckets * 2 < ht->size && !ht->enumerators )
|
2013-01-16 11:28:58 +00:00
|
|
|
s = rehashHTable(ht, s);
|
2008-12-22 12:02:22 +00:00
|
|
|
UNLOCK_TABLE(ht);
|
|
|
|
|
|
|
|
DEBUG(1, checkHTable(ht));
|
|
|
|
return s;
|
2011-02-10 00:01:19 +00:00
|
|
|
}
|
2008-12-22 12:02:22 +00:00
|
|
|
|
|
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
Note: s must be in the table!
|
|
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
|
|
void
|
|
|
|
deleteSymbolHTable(Table ht, Symbol s)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ int v;
|
2008-12-22 12:02:22 +00:00
|
|
|
Symbol *h;
|
|
|
|
TableEnum e;
|
|
|
|
|
|
|
|
LOCK_TABLE(ht);
|
|
|
|
v = (int)pointerHashValue(s->name, ht->buckets);
|
|
|
|
h = &ht->entries[v];
|
|
|
|
|
|
|
|
for( e=ht->enumerators; e; e = e->next )
|
|
|
|
{ if ( e->current == s )
|
|
|
|
rawAdvanceTableEnum(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
for( ; *h; h = &(*h)->next )
|
|
|
|
{ if ( *h == s )
|
|
|
|
{ *h = (*h)->next;
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
s->next = NULL; /* force crash */
|
|
|
|
s->name = NULL;
|
|
|
|
s->value = NULL;
|
2008-12-22 12:02:22 +00:00
|
|
|
freeHeap(s, sizeof(struct symbol));
|
|
|
|
ht->size--;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UNLOCK_TABLE(ht);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
clearHTable(Table ht)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ int n;
|
2008-12-22 12:02:22 +00:00
|
|
|
TableEnum e;
|
|
|
|
|
|
|
|
LOCK_TABLE(ht);
|
|
|
|
for( e=ht->enumerators; e; e = e->next )
|
|
|
|
{ e->current = NULL;
|
|
|
|
e->key = ht->buckets;
|
|
|
|
}
|
|
|
|
|
|
|
|
for(n=0; n < ht->buckets; n++)
|
|
|
|
{ Symbol s, q;
|
|
|
|
|
|
|
|
for(s = ht->entries[n]; s; s = q)
|
|
|
|
{ q = s->next;
|
|
|
|
|
|
|
|
if ( ht->free_symbol )
|
|
|
|
(*ht->free_symbol)(s);
|
|
|
|
|
|
|
|
freeHeap(s, sizeof(struct symbol));
|
|
|
|
}
|
|
|
|
|
|
|
|
ht->entries[n] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ht->size = 0;
|
|
|
|
UNLOCK_TABLE(ht);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
Table copyHTable(Table org)
|
|
|
|
Make a copy of a hash-table. This is used to realise the copy-on-write
|
|
|
|
as defined by SharedTable. The table is copied to have exactly the
|
|
|
|
same dimensions as the original. If the copy_symbol function is
|
|
|
|
provided, it is called to allow duplicating the symbols name or value
|
|
|
|
fields.
|
|
|
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
|
|
Table
|
|
|
|
copyHTable(Table org)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ Table ht;
|
2008-12-22 12:02:22 +00:00
|
|
|
int n;
|
|
|
|
|
2013-01-16 11:28:58 +00:00
|
|
|
ht = allocHeapOrHalt(sizeof(struct table));
|
2008-12-22 12:02:22 +00:00
|
|
|
LOCK_TABLE(org);
|
|
|
|
*ht = *org; /* copy all attributes */
|
|
|
|
#ifdef O_PLMT
|
|
|
|
ht->mutex = NULL;
|
|
|
|
#endif
|
2013-01-16 11:28:58 +00:00
|
|
|
ht->entries = allocHTableEntries(ht->buckets);
|
2008-12-22 12:02:22 +00:00
|
|
|
|
|
|
|
for(n=0; n < ht->buckets; n++)
|
|
|
|
{ Symbol s, *q;
|
|
|
|
|
|
|
|
q = &ht->entries[n];
|
|
|
|
for(s = org->entries[n]; s; s = s->next)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ Symbol s2 = allocHeapOrHalt(sizeof(*s2));
|
2008-12-22 12:02:22 +00:00
|
|
|
|
|
|
|
*q = s2;
|
|
|
|
q = &s2->next;
|
|
|
|
s2->name = s->name;
|
|
|
|
s2->value = s->value;
|
|
|
|
|
|
|
|
if ( ht->copy_symbol )
|
|
|
|
(*ht->copy_symbol)(s2);
|
|
|
|
}
|
|
|
|
*q = NULL;
|
|
|
|
}
|
2011-02-10 00:01:19 +00:00
|
|
|
#ifdef O_PLMT
|
2008-12-22 12:02:22 +00:00
|
|
|
if ( org->mutex )
|
2013-01-16 11:28:58 +00:00
|
|
|
{ ht->mutex = allocHeapOrHalt(sizeof(simpleMutex));
|
2008-12-22 12:02:22 +00:00
|
|
|
simpleMutexInit(ht->mutex);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
UNLOCK_TABLE(org);
|
|
|
|
|
|
|
|
return ht;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*******************************
|
|
|
|
* ENUMERATING *
|
|
|
|
*******************************/
|
|
|
|
|
|
|
|
TableEnum
|
|
|
|
newTableEnum(Table ht)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ TableEnum e = allocHeapOrHalt(sizeof(struct table_enum));
|
2008-12-22 12:02:22 +00:00
|
|
|
Symbol n;
|
|
|
|
|
|
|
|
LOCK_TABLE(ht);
|
|
|
|
e->table = ht;
|
|
|
|
e->key = 0;
|
|
|
|
e->next = ht->enumerators;
|
|
|
|
ht->enumerators = e;
|
|
|
|
|
|
|
|
n = ht->entries[0];
|
|
|
|
while(!n && ++e->key < ht->buckets)
|
|
|
|
n=ht->entries[e->key];
|
|
|
|
e->current = n;
|
|
|
|
UNLOCK_TABLE(ht);
|
|
|
|
|
|
|
|
return e;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
freeTableEnum(TableEnum e)
|
2013-01-16 11:28:58 +00:00
|
|
|
{ TableEnum *ep;
|
2008-12-22 12:02:22 +00:00
|
|
|
Table ht;
|
|
|
|
|
|
|
|
if ( !e )
|
|
|
|
return;
|
|
|
|
|
|
|
|
ht = e->table;
|
|
|
|
LOCK_TABLE(ht);
|
|
|
|
for( ep=&ht->enumerators; *ep ; ep = &(*ep)->next )
|
|
|
|
{ if ( *ep == e )
|
|
|
|
{ *ep = (*ep)->next;
|
|
|
|
|
|
|
|
freeHeap(e, sizeof(*e));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UNLOCK_TABLE(ht);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline Symbol
|
|
|
|
rawAdvanceTableEnum(TableEnum e)
|
|
|
|
{ Symbol s, n;
|
|
|
|
Table ht = e->table;
|
|
|
|
|
|
|
|
if ( !(s = e->current) )
|
|
|
|
return s;
|
|
|
|
n = s->next;
|
|
|
|
|
|
|
|
while(!n)
|
|
|
|
{ if ( ++e->key >= ht->buckets )
|
|
|
|
{ e->current = NULL;
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
n=ht->entries[e->key];
|
|
|
|
}
|
|
|
|
e->current = n;
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Symbol
|
|
|
|
advanceTableEnum(TableEnum e)
|
|
|
|
{ Symbol s;
|
|
|
|
#ifdef O_PLMT
|
|
|
|
Table ht = e->table;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
LOCK_TABLE(ht);
|
|
|
|
s = rawAdvanceTableEnum(e);
|
|
|
|
UNLOCK_TABLE(ht);
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|