/*	index.c	- 
**
**
** Copyright (c) 1996  Hughes Technologies Pty Ltd
**
** Permission to use, copy, and distribute for non-commercial purposes,
** is hereby granted without fee, providing that the above copyright
** notice appear in all copies and that both the copyright notice and this
** permission notice appear in supporting documentation.
**
** The software may be modified for your own purposes, but modified versions
** may not be distributed.
**
** This software is provided "as is" without any expressed or implied warranty.
**
*/


#include <stdio.h>
#include <fcntl.h>

#ifdef OS2
#  include <io.h>
#  include <share.h>
#  include <types.h>
#  include <sys/types.h>
#  include <sys/stat.h>
#  include <sys/socket.h>
#  include <netinet/in.h>
#else
#  include <unistd.h>
#  include <stdlib.h>
#  include <sys/types.h>
#  include <sys/stat.h>
#  include <sys/socket.h>
#  include <netinet/in.h>
#  include <arpa/inet.h>
#endif
#include <netdb.h>
#include <string.h>
#include <errno.h>

#ifdef WIN32
#  include <winsock.h>
#endif

#include <common/debug.h>
#include <common/site.h>
#include <common/portability.h>


#if defined(OS2) || defined(WIN32)
#  include "msql_yacc.h"
#else
#  include "y.tab.h"
#endif

#include "avl_tree.h"

#define _MSQL_SERVER_SOURCE
#include "msql_priv.h"
#include "msql.h"
#include "errmsg.h"

#define REG             register

extern	char	*msqlHomeDir;
extern  char    errMsg[];



void closeIndices(entry)
	cache_t	*entry;
{
	mindex_t	*cur,
		*prev;

	cur = entry->indices;
	while(cur)
	{
		avlClose(cur->tree);
		if (cur->buf)
			free(cur->buf);
		prev = cur;
		cur = cur->next;
		free(prev);
	}
}



void zeroIndices(entry)
	cache_t *entry;
{
}


mindex_t *loadIndices(table,db)
        char    *table,
                *db;
{
        mindex_t *headIndex = NULL,
                *tmpIndex,
                *prevIndex,
                *curIndex,
		buf;
        char    path[MAXPATHLEN];
        int     numBytes,
		count,
                fd;

        msqlTrace(TRACE_IN,"loadIndices()");
        (void)snprintf(path,MAXPATHLEN,"%s/msqldb/%s/%s.idx",
		msqlHomeDir,db,table);
#ifdef OS2
        fd = _sopen(path ,O_RDONLY | O_BINARY, SH_DENYNO, S_IREAD | S_IWRITE);
#else
        fd = open(path,O_RDONLY,0);
#endif
        if (fd < 0)
        {
                snprintf(errMsg,MAX_ERR_MSG, 
			"Can't open index definition for '%s'",table);
                msqlTrace(TRACE_OUT,"loadIndices()");
                return(NULL);
        }
        numBytes = read(fd,&buf,sizeof(buf));
	count = 0;
	while(numBytes > 0)
	{
        	if (numBytes < sizeof(buf))
        	{
                	snprintf(errMsg,MAX_ERR_MSG, TABLE_READ_ERROR,table,
				(char*)strerror(errno));
                	msqlDebug(MOD_ERR,
				"Error reading \"%s\" index definition\n",
				table);
			close(fd);
                	msqlTrace(TRACE_OUT,"loadIndices()");
                	return(NULL);
        	}

                curIndex = (mindex_t *)fastMalloc(sizeof(mindex_t));
                bcopy(&buf, curIndex, sizeof(mindex_t));
                if (!headIndex)
                {
                        headIndex = prevIndex = curIndex;
                }
                else
                {
			/* ensure the list is sorted by DESC field count */
			tmpIndex = headIndex;
			prevIndex = NULL;
			while(tmpIndex)
			{
				if (curIndex->fieldCount > tmpIndex->fieldCount)
				{
					curIndex->next = tmpIndex;
					if (prevIndex)
						prevIndex->next=curIndex;
					else
						headIndex = curIndex;
					break;
				}
				prevIndex = tmpIndex;
				tmpIndex = tmpIndex->next;
			}
			if (!tmpIndex)
			{
                        	prevIndex->next = curIndex;
				curIndex->next = NULL;
			}
                }
		snprintf(path,MAXPATHLEN,"%s/msqldb/%s/%s.idx-%s",
			msqlHomeDir,db,table, curIndex->name);
		curIndex->tree = avlOpen(path);
		curIndex->buf = (char *)malloc(curIndex->length + 1);
        	numBytes = read(fd,&buf,sizeof(buf));
		count++;
        }
        close(fd);

        msqlTrace(TRACE_OUT,"loadIndices()");
        return(headIndex);
}



int copyValue(cp,value,type,length,nullOK)
	char	*cp;
	val_t	*value;
	int	type,
		length,
		nullOK;
{
	int	strLen;

	if (value->nullVal)
	{
		if (!nullOK)
		{
			return(-1);
		}
		else
		{
			bzero(cp,length);
			return(0);
		}
	}

	switch(type)
	{
		case INT_TYPE:
    		case UINT_TYPE:
		case DATE_TYPE:
		case MONEY_TYPE:
		case TIME_TYPE:
			bcopy(&(value->val.intVal),cp,4);
			break;

		case REAL_TYPE:
			bcopy(&(value->val.realVal),cp,8);
			break;

		case CHAR_TYPE:
			strLen = strlen((char *)value->val.charVal);
			bcopy((char *)value->val.charVal,cp, strLen);
			break;
	}
	return(0);
}



static int fillIndexBuffer(index,fields)
	mindex_t *index;
	field_t	*fields;
{
	char	*cp;
	int	count,
		fieldID;
	field_t	*curField;

	cp = index->buf;	
	bzero(cp, index->length + 1);
	count = 0;
	while(index->fields[count] != -1)
	{
		fieldID = index->fields[count];
		curField = fields;
		while(curField)
		{
			if (curField->fieldID == fieldID)
				break;
			curField = curField->next;
		}
		if (!curField)
		{
			/* missing field index field.  Trapped later */
			return(-1);
		}
		if (copyValue(cp,curField->value,curField->type,
			curField->length,0) < 0)
		{
			snprintf(errMsg,MAX_ERR_MSG,NULL_JOIN_COND_ERR,
				curField->name);
			return(-1);
		}
		cp += curField->length;
		count++;
	}
	return(0);
}



int insertIndex(entry,fields,pos,index)
	cache_t	*entry;
	field_t	*fields;
	u_int	pos;
	mindex_t *index;
{
	int	res;

	if (pos == NO_POS)
		pos = entry->sblk->numRows;
	if(fillIndexBuffer(index,fields) < 0)
	{
		return(-1);
	}
	res = avlInsert(index->tree,index->buf,(off_t)pos);
	if (res < 0)
		return(-1);
	return(0);
}



/****************************************************************************
**      _insertIndices
**
**      Purpose : 
**      Args    : 
**      Returns : 
**      Notes   : To reduce the size of the index table, we do a special
**		  case if there's only 1 field and it's a CHAR field. In
**		  that case we use the strlen of the field as the data
**		  size.  Otherwise we use the defined field width.
*/

int insertIndices(entry,fields,pos)
	cache_t	*entry;
	field_t	*fields;
	u_int	pos;
{
	mindex_t	*curIndex;

	curIndex = entry->indices;
	while(curIndex)
	{
		if (insertIndex(entry,fields,pos,curIndex) < 0)
			return(-1);
		curIndex = curIndex->next;
	}
	return(0);
}





/****************************************************************************
**      _deleteIndices
**
**      Purpose : 
**      Args    : 
**      Returns : 
**      Notes   : 
*/

int deleteIndices(entry,fields,pos)
	cache_t	*entry;
	field_t	*fields;
	u_int	pos;
{
	mindex_t	*curIndex;
	int	res;

	if (pos == NO_POS)
		pos = entry->sblk->numRows;
	curIndex = entry->indices;
	while(curIndex)
	{
		if (fillIndexBuffer(curIndex,fields) < 0)
			return(-1);
		res = avlDelete(curIndex->tree, curIndex->buf, (off_t)pos);
		if (res < 0)
			return(-1);
		curIndex = curIndex->next;
	}
	return(0);
}


static int compareIndexValues(val1,val2)
        val_t   *val1,
                *val2;
{
	int	res;

        switch(val1->type)
        {
                case CHAR_TYPE:
			res = strcmp(val1->val.charVal,val2->val.charVal);
			return(res == 0);

                case INT_TYPE:
                case UINT_TYPE:
                case DATE_TYPE:
                case TIME_TYPE:
                case MONEY_TYPE:
			res = val1->val.intVal == val2->val.intVal;
			return(res);

                case REAL_TYPE:
			res = val1->val.realVal == val2->val.realVal;
			return(res);
        }
}



/****************************************************************************
**      _updateIndices
**
**      Purpose : 
**      Args    : 
**      Returns : 
**      Notes   : 
*/

int updateIndices(entry,oldFields,pos,row,flist, updated, candIdx)
	cache_t	*entry;
	field_t	*oldFields;
	u_int	pos;
        row_t 	*row;
	int	*flist,
		*updated,
		candIdx;
{
	mindex_t	*curIndex;
	int		res,
			count,
			offset,
			idxCount,
			needUpdate;
	field_t		*newFields = NULL,
			*newTail = NULL,
			*newCur,
			*oldCur;

	/*
	** Create a duplicate field list for the complete row containing
	** the new values
	*/
	oldCur = oldFields;
	while(oldCur)
	{
		newCur = (field_t *)malloc(sizeof(field_t));
		bcopy(oldCur,newCur,sizeof(field_t));
		newCur->value = NULL;
		if (!newFields)
		{
			newFields = newTail = newCur;
			newFields->next = NULL;
		}
		else
		{
			newTail->next = newCur;
			newTail = newCur;
		}
		oldCur = oldCur->next;
	}
	extractValues(entry, row, newFields, flist);

	/*
	** Work through the indices
	*/

	idxCount = 0;
	*updated = 0;
	curIndex = entry->indices;
	while(curIndex)
	{
		/*
		** Do we need to update this index?
		*/
		count = 0;
		needUpdate = 0;
		while(curIndex->fields[count] != -1)
		{
			offset = curIndex->fields[count];
			oldCur = oldFields;
			newCur = newFields;
			while(offset)
			{
				oldCur = oldCur->next;
				newCur = newCur->next;
				offset--;
			}
			if (compareIndexValues(oldCur->value,newCur->value)!=1)
			{
				needUpdate = 1;
				break;
			}
			count++;
		}
		if (!needUpdate)
		{
			/* Skip this index */
			curIndex = curIndex->next;
			continue;
		}


		if (idxCount == candIdx)
			*updated = 1;
		if (fillIndexBuffer(curIndex,oldFields) < 0)
			return(-1);
		if (avlDelete(curIndex->tree, curIndex->buf, (off_t)pos) < 0)
			return(-1);
		if (fillIndexBuffer(curIndex,newFields) < 0)
			return(-1);
		if (avlInsert(curIndex->tree, curIndex->buf, (off_t)pos) < 0)
			return(-1);

		curIndex = curIndex->next;
		idxCount++;
	}

	/*
	** Free up the duplicatate field list and return
	*/
	newCur = newFields;
	while(newCur)
	{
		newCur = newCur->next;
		msqlFreeValue(newFields->value);
		free(newFields);
		newFields = newCur;
	}
	return(0);
}



int checkIndex(entry,fields, index, rowNum)
        cache_t *entry;
        field_t *fields;
	mindex_t *index;
	u_int	rowNum;
{
	avl_nod	*node;
	u_int	curRow;

	/*
	** If it's a non-unique index, just bail
	*/
	if (!index->unique)
		return(1);
		
	/*
	** Try to read the index value and bail if it's there
	*/
	if(fillIndexBuffer(index,fields) < 0)
		return(-1);
	node = avlLookup(index->tree, index->buf, AVL_EXACT);
	if (node)
	{
		curRow = (u_int)node->data;
		if (curRow != rowNum)
			return(0);
	}
	return(1);
}



int checkIndices(entry,fields, rowNum)
        cache_t *entry;
        field_t *fields;
	u_int	rowNum;
{
	mindex_t	*curIndex;

	curIndex = entry->indices;
	while(curIndex)
	{
		if (checkIndex(entry,fields,curIndex, rowNum) == 0)
			return(0);
		curIndex = curIndex->next;
	}
	return(1);
}



int checkIndexNullFields(entry,row,index,flist)
        cache_t *entry;
        row_t 	*row;
	mindex_t *index;
	int	*flist;
{
        REG     field_t *curField;
        REG     int     *offset,
			count,
			field;
        u_char  *data;

        msqlTrace(TRACE_IN,"checkIndexNullFields()");
        data = row->data;
	for (count=0; count<5; count++)
	{
       		offset = 0;
		field = index->fields[count];
		if(field == -1)
		{
			break;
		}
       		curField = entry->def;
		offset = flist;
       		while(curField && field)
       		{
			offset++;
			curField = curField->next;
			field--;
		}
               	if (!*(data + *offset))
               	{
                       	snprintf(errMsg,MAX_ERR_MSG, NULL_INDEX_ERROR, 
				curField->name);
                       	msqlTrace(TRACE_OUT,"checkIndexNullFields()");
                       	return(-1);
		}
	}
	msqlTrace(TRACE_OUT,"checkIndexNullFields()");
	return(0);
}


int checkIndicesNullFields(entry,row, flist)
        cache_t *entry;
        row_t 	*row;
	int	*flist;
{
	REG	mindex_t *curIndex;
        u_char  *data;

        msqlTrace(TRACE_IN,"checkIndexNullFields()");
	curIndex = entry->indices;
        data = row->data;
	while(curIndex)
	{
		if (checkIndexNullFields(entry,row,curIndex,flist) < 0)
			return(-1);
		curIndex = curIndex->next;
        }
        msqlTrace(TRACE_OUT,"checkIndexNullFields()");
        return(0);
}




static void extractFieldValue(buf,cond)
	char	*buf;
	cond_t	*cond;
{
	int	length;

	switch(cond->value->type)
	{
		case INT_TYPE:
		case UINT_TYPE:
		case DATE_TYPE:
		case MONEY_TYPE:
		case TIME_TYPE:
			bcopy(&(cond->value->val.intVal),buf,4);
			break;


		case REAL_TYPE:
			bcopy(&(cond->value->val.realVal),buf,8);
			break;

		case CHAR_TYPE:
			length = strlen((char *)cond->value->val.charVal);
			if (length > cond->length)
				length = cond->length;
			bcopy((char *)cond->value->val.charVal,buf,length);
			break;
	}
}






/*
** This is the mSQL 2.0 CRA code
*/


void freeCandidate(cand)
	cand_t	*cand;
{
	if (cand)
	{
		if (cand->buf)
			free(cand->buf);
		(void)free(cand);
	}
}




int setCandidateValues(inner, cand, fields, conds, row)
	cache_t	*inner;
	cand_t	*cand;
	field_t	*fields;
	cond_t	*conds;
	row_t	*row;
{
	mindex_t	*curIndex;
	field_t	*curField,
		tmpField;
	cond_t	*curCond;
	int	count,
		fieldID;
	char	*cp;

	if (cand->type == CAND_SEQ)
	{
		return(0);
	}
	curIndex = inner->indices;
	count = cand->index;
	while(count && curIndex)
	{
		count--;
		curIndex = curIndex->next;
	}
	if (!curIndex)
		return(0);

	/*
	** We can't use fillIndexBuffer() here as some of the
	** values are in the cond list and others are in the ident
	** fields list.  Sigh...
	*/
	curIndex = inner->indices;
	count = 0;
	while(count < cand->index)
	{
		curIndex = curIndex->next;
		count ++;
	}
	cp = cand->buf;	
	bzero(cp, curIndex->length + 1);
	count = 0;
	while(curIndex->fields[count] != -1)
	{
		fieldID = curIndex->fields[count];

		/*
		** Look for an ident value first
		*/
		curField = fields;
		while(curField)
		{
			if(curField->fieldID == fieldID)
			{
				if (copyValue(cp,curField->value,curField->type,
					curField->length,0) < 0)
				{
					snprintf(errMsg,MAX_ERR_MSG,
						NULL_JOIN_COND_ERR,
						curField->name);
					return(-1);
				}
				cp+=curField->length;
				break;
			}
			curField = curField->next;
		}
		if (curField)
		{
			/* Found it */	
			count++;
			continue;
		}


		/*
		** OK, check out the normal conditions
		*/
		curCond = conds;
		while(curCond)
		{
			if(curCond->fieldID == fieldID)
			{
				/*
				** Could be a sysvar ?
				*/
				strcpy(tmpField.name,curCond->name);
				if (checkSysVar(inner, &tmpField) == 0)
				{
					getSysVar(inner,row,&tmpField);
					if(copyValue(cp,tmpField.value,
					     tmpField.type,tmpField.length,0)<0)
					{
						snprintf(errMsg,MAX_ERR_MSG,
							NULL_JOIN_COND_ERR,
							curField->name);
						return(-1);
					}
					cp += tmpField.length;
				}
				else
				{
					/*
					** Nope, it's a normal field
					*/
					if(copyValue(cp,curCond->value,
					     curCond->type,curCond->length,0)<0)
					{
						snprintf(errMsg,MAX_ERR_MSG,
							NULL_JOIN_COND_ERR,
							curField->name);
						return(-1);
					}
					cp += curCond->length;
				}
				break;
			}
			curCond = curCond->next;
		}
		if (curCond)
		{
			/* Found it */	
			count++;
			continue;
		}
/***/		abort();
	}

	cand->lastPos = NO_POS;
	return(0);
}




cand_t *msqlSetupCandidate(entry, conds, fields, ignoreIdent)
	cache_t	*entry;
	cond_t	*conds;
	field_t	*fields;
	int	ignoreIdent;
{
	cand_t	*new;
	mindex_t	*curIndex,
		*candIndex;
	cond_t	*curCond;
	int	count,
		field,
		identKey,
		index,
		doRowID,
		rowID;
	char	*tableName,
		*cp;

	/*
	** This is the query optimiser!  The conditions are checked to
	** see which access mechanism is going to provide the fastest
	** query result.
	*/
	new = (cand_t *)malloc(sizeof(cand_t));

	/*
	** We can't handle OR's yet so do a quick scan through the
	** conditions
	*/
	curCond = conds;
	while(curCond)
	{
		if (curCond->bool == OR_BOOL)
		{
			new->type = CAND_SEQ;
			new->nextPos = 0;
			msqlDebug(MOD_ACCESS,
			 	"setupCandidate() : Using SEQ for %s\n",
				entry->table);
			return(new);
		}
		curCond = curCond->next;
	}

	/*
	** First test is to see if we can do a row_id based access
	*/
	curCond = conds;
	doRowID = 0;
	while(curCond)
	{
		if (strcmp(curCond->name, "_rowid")==0 && 
		    curCond->op == EQ_OP)
		{
			doRowID++;
			rowID = curCond->value->val.intVal;
		}
		curCond = curCond->next;
	}
	if (doRowID == 1)
	{
		new->type = CAND_ROWID;
		new->lastPos = NO_POS;
		new->length = 4;
		new->rowID = rowID;
		msqlDebug(MOD_ACCESS,
			"setupCandidate() : Using _ROWID for %s\n",
			entry->table);
		return(new);
	}


	/*
	** Look for the wierd _seq case.  We need this because it's
	** possible (in fact normal) to just select the seq value.  In
	** that situation we can't expect to just fill in the blanks
	** for a table row access as there may not be any table data
	** yet (e.g. the first insert into a table that uses the SEQ
	** as a key).  Use this for everything but _rowid and _timestamp. 
	** It's ugly but it works.
	*/
	if (fields)
	{
		if (fields->next == NULL && fields->sysvar == 1)
		{
			if (strcmp(fields->name, "_rowid") != 0 &&
			    strcmp(fields->name, "_timestamp") != 0)
			{
				new->type = CAND_SYS_VAR;
				new->lastPos = NO_POS;
				new->length = 0;
				new->rowID = 0;
				msqlDebug(MOD_ACCESS,
				"setupCandidate() : Fake sysvar for %s\n",
				entry->table);
				return(new);
			}
		}
	}

	/*
	** Check for an  equality index condition.  Match on the longest index
	** or the first unique
	*/
	new->type = CAND_SEQ;
	new->length = 0;
	curIndex = entry->indices;
	index = 0;
	if (*entry->cname != 0)
		tableName = entry->cname;
	else
		tableName = entry->table;

	while(curIndex)
	{
		field = 0;
		identKey = 0;
		while(field < MAX_INDEX_WIDTH && curIndex->fields[field] != -1)
		{
			curCond = conds;
			while(curCond)
			{
				if (strcmp(curCond->table, entry->table)!=0)
				{
					curCond=curCond->next;
					continue;
				}
				if (curCond->value->type == IDENT_TYPE ||
				    curCond->value->type == SYSVAR_TYPE)
				{
					identKey |= 1;
				}
				if(strcmp(tableName,curIndex->table)==0 &&
				   curCond->fieldID == curIndex->fields[field])
				{
					if(curCond->op == EQ_OP && !
				         ((curCond->value->type==IDENT_TYPE||
			  		    curCond->value->type==SYSVAR_TYPE)
					     &&ignoreIdent))
					{
						break;
					}
				}
				curCond = curCond->next;
			}
			if (!curCond)
			{
				break;
			}
			field++;
		}
		if (curCond)
		{
			if (curIndex->unique)
			{
				new->type = CAND_IDX_ABS;
				new->index = index;
				new->ident = identKey;
				new->lastPos = NO_POS;
				new->length = curIndex->length;
				strcpy(new->idx_name, curIndex->name);
				candIndex = curIndex;
				break;
			}
			if (curIndex->length > new->length)
			{
				new->type = CAND_IDX_ABS;
				new->index = index;
				new->ident = identKey;
				new->lastPos = NO_POS;
				new->length = curIndex->length;
				strcpy(new->idx_name, curIndex->name);
				candIndex = curIndex;
			}
		}
		curIndex = curIndex->next;
		index++;
	}

	/*
	** If we don't have an index lookup look for a range index comparison
	*/
	if (new->type == CAND_SEQ)
	{
	}


	/*
	** Setup the index stuff
	*/

	if (new->type == CAND_IDX_ABS || new->type == CAND_IDX_RANGE)
	{
		new->tree = candIndex->tree;
		new->buf = (char *)malloc(new->length + 1);


		/* Setup the key buffer */
		count = 0;
		cp = new->buf;
		bzero(new->buf,new->length + 1);
		while(candIndex->fields[count] != -1)
		{
			curCond = conds;
			while(curCond)
			{
				if (curCond->subCond)
				{
					curCond = curCond->next;
					continue;
				}
				if(curCond->fieldID==candIndex->fields[count])
				{
					if (curCond->value->nullVal)
					{
                				snprintf(errMsg,MAX_ERR_MSG,
							NULL_COND_ERR, 
							curCond->name);
               					return(NULL);
					}
					extractFieldValue(cp,curCond);
					cp += curCond->length;
					break;
				}
				curCond = curCond->next;
			}
			count++;
		}
		msqlDebug(MOD_ACCESS,"setupCandidate() : Using IDX %d for %s\n",
			new->index, entry->table);
		return(new);
	}
	msqlDebug(MOD_ACCESS, "setupCandidate() : Using SEQ for %s\n",
		entry->table);
	return(new);
}


void resetCandidate(cand, action)
	cand_t	*cand;
	int	action;
{
	/*
	** Is it's a SEQ search candidate then just start at the top
	** again.  We need to reset it in this way when the candidate
	** is the inner loop of a join
	*/
	if (cand->type == CAND_SEQ && action == MSQL_SELECT)
		cand->nextPos = 0;
	if (cand->type == CAND_IDX_ABS)
		cand->lastPos = NO_POS;
}




u_int getCandidate(entry, cand)
	cache_t	*entry;
	cand_t	*cand;
{
	int	length;
	u_int	pos;
	avl_nod	*node;

	switch(cand->type)
	{
	    case CAND_SEQ:
		cand->nextPos++;
		if (cand->nextPos > entry->sblk->numRows)
		{
			msqlDebug(MOD_ACCESS,
				"getCandidate() : SEQ on %s => NO_POS\n",
				entry->table);
			return(NO_POS);
		}
		else
		{
			msqlDebug(MOD_ACCESS,
				"getCandidate() : SEQ on %s => %d\n",
				entry->table, cand->nextPos -1);
			return(cand->nextPos -1);
		}
		break;


	    case CAND_IDX_ABS:
		msqlDebug(MOD_ACCESS,"getCandidate() : using IDX '%s' on %s\n",
			cand->idx_name, entry->table);
		msqlDebug(MOD_ACCESS,
			"getCandidate() : IDX key on %s = '%s','%d'\n",
			entry->table, cand->buf, (int) *(int*)cand->buf);
		length = cand->length;
		if (cand->lastPos == NO_POS)
		{
			node = avlLookup(cand->tree, cand->buf,AVL_EXACT);
			avlSetCursor(cand->tree, &(cand->cursor));
		}
		else
		{
			node = avlGetNext(cand->tree,&(cand->cursor));
		}
		if (node == NULL)
		{
			msqlDebug(MOD_ACCESS,
				"getCandidate() : IDX on %s => NO_POS\n",
				entry->table);
			return(NO_POS);
		}
		if (cand->tree->sblk->keyType == AVL_CHAR)
		{
			if (strcmp(node->key, cand->buf) != 0)
			{
				msqlDebug(MOD_ACCESS,
				    "getCandidate() : IDX on %s => NO_POS\n",
				    entry->table);
				return(NO_POS);
			}
		}
		else
		{
			if (bcmp(node->key, cand->buf, length) != 0)
			{
				msqlDebug(MOD_ACCESS,
				    "getCandidate() : IDX on %s => NO_POS\n",
				    entry->table);
				return(NO_POS);
			}
		}
		pos = node->data;
		if (cand->lastPos == NO_POS)
		{
			cand->lastPos = pos;
		}

		msqlDebug(MOD_ACCESS,"getCandidate() : IDX on %s => %d\n", 
			entry->table, pos);
		return(pos);


	    case CAND_ROWID:
		msqlDebug(MOD_ACCESS,
			"getCandidate() : using ROW ID '%d' on %s\n",
			cand->rowID, entry->table);
		if (cand->lastPos == NO_POS)
		{
			if (entry->sblk->numRows < cand->rowID)
			{
				cand->rowID = 0;
				return(NO_POS);
			}
			cand->lastPos = cand->rowID;
			return(cand->rowID);
		}	
		else
		{
			return(NO_POS);
		}
	}
	return(NO_POS);
}

