/*	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 <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 <common/debug.h>
#include <common/site.h>
#include <common/portability.h>


#ifdef OS2
#  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)sprintf(path,"%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)
        {
                sprintf(errMsg,"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))
        	{
                	sprintf(errMsg,TABLE_READ_ERROR,table);
                	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;
			}
                }
		sprintf(path,"%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);
}


/****************************************************************************
**      _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;
	field_t	*curField;
	int	fieldIF,
		count,
		res,
		useSingle;
	char	*cp;

	curIndex = entry->indices;
	if (pos == NO_POS)
		pos = entry->sblk->numRows;
	while(curIndex)
	{
		bzero(curIndex->buf, curIndex->length);
		cp = curIndex->buf;
		count = 0;
		if (curIndex->fields[1] == -1)
			useSingle = 1;
		else
			useSingle = 0;
		while(curIndex->fields[count] != -1)
		{
			fieldIF = curIndex->fields[count];
			curField = fields;
			while(curField)
			{
				if (curField->fieldID == fieldIF)
					break;
				curField = curField->next;
			}
			switch(curField->type)
			{
			    case INT_TYPE:
				bcopy(&(curField->value->val.intVal),cp,4);
				cp += 4;
				useSingle = 0;
				break;

			    case REAL_TYPE:
				bcopy(&(curField->value->val.realVal),cp,8);
				cp += 8;
				useSingle = 0;
				break;

			    case CHAR_TYPE:
				bcopy(curField->value->val.charVal,cp,
				  strlen((char *)curField->value->val.charVal));
				cp += curField->length;
				break;
			}
			count++;
		}

		res = avlInsert(curIndex->tree,curIndex->buf,pos);
		if (res < 0)
			return(-1);
		curIndex = curIndex->next;
	}
	return(0);
}


int insertIndex(entry,fields,pos,index)
	cache_t	*entry;
	field_t	*fields;
	u_int	pos;
	mindex_t *index;
{
	field_t	*curField;
	int	fieldIF,
		count,
		res,
		useSingle;
	char	*cp;

	if (pos == NO_POS)
		pos = entry->sblk->numRows;
	bzero(index->buf, index->length + 1);
	cp = index->buf;
	count = 0;
	if (index->fields[1] == -1)
		useSingle = 1;
	else
		useSingle = 0;
	while(index->fields[count] != -1)
	{
		fieldIF = index->fields[count];
		curField = fields;
		while(curField)
		{
			if (curField->fieldID == fieldIF)
				break;
			curField = curField->next;
		}
		switch(curField->type)
		{
		    case INT_TYPE:
			bcopy(&(curField->value->val.intVal),cp,4);
			cp += 4;
			useSingle = 0;
			break;

		    case REAL_TYPE:
			bcopy(&(curField->value->val.realVal),cp,8);
			cp += 8;
			useSingle = 0;
			break;

		    case CHAR_TYPE:
			bcopy(curField->value->val.charVal,cp,
			  strlen((char *)curField->value->val.charVal));
			cp += curField->length;
			break;
		}
		count++;
	}
	res = avlInsert(index->tree,index->buf,pos);
	if (res < 0)
		return(-1);
	return(0);
}



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

int deleteIndices(entry,fields,pos)
	cache_t	*entry;
	field_t	*fields;
	u_int	pos;
{
	mindex_t	*curIndex;
	field_t	*curField;
	int	offset,
		count,
		res,
		useSingle;
	char	*cp;
	u_int	curPos;

	if (pos == NO_POS)
		pos = entry->sblk->numRows;
	curIndex = entry->indices;
	while(curIndex)
	{
		bzero(curIndex->buf, curIndex->length + 1);
		cp = curIndex->buf;
		count = 0;
		if (curIndex->fields[1] == -1)
			useSingle = 1;
		else
			useSingle = 0;
		while(curIndex->fields[count] != -1)
		{
			offset = curIndex->fields[count];
			curField = fields;
			while(offset)
			{
				curField = curField->next;
				offset--;
			}
			switch(curField->type)
			{
			    case INT_TYPE:
				bcopy(&(curField->value->val.intVal),cp,4);
				cp += 4;
				useSingle = 0;
				break;

			    case REAL_TYPE:
				bcopy(&(curField->value->val.realVal),cp,8);
				cp += 8;
				useSingle = 0;
				break;

			    case CHAR_TYPE:
				bcopy((char *)curField->value->val.charVal,cp,
				  strlen((char *)curField->value->val.charVal));
				cp += curField->length;
				break;
			}
			count++;
		}

		res = avlDelete(curIndex->tree, curIndex->buf, pos);
		if (res < 0)
			return(-1);
		curIndex = curIndex->next;
	}
	return(0);
}



int checkIndex(entry,fields, index)
        cache_t *entry;
        field_t *fields;
	mindex_t *index;
{
	field_t	*curField;
	int	offset,
		count,
		res,
		useSingle;
	avl_nod	*node;
	char	*cp;

	/*
	** If it's a non-unique index, just bail
	*/
	if (!index->unique)
		return(1);
		
	/*
	** Determine the index value
	*/
	bzero(index->buf, index->length + 1);
	cp = index->buf;
	count = 0;
	if (index->fields[1] == -1)
		useSingle = 1;
	else
		useSingle = 0;
	while(index->fields[count] != -1)
	{
		offset = index->fields[count];
		curField = fields;
		while(offset)
		{
			curField = curField->next;
			offset--;
		}
		switch(curField->type)
		{
		    case INT_TYPE:
			bcopy(&(curField->value->val.intVal),cp,4);
			cp += 4;
			useSingle = 0;
			break;

		    case REAL_TYPE:
			bcopy(&(curField->value->val.realVal),cp,8);
			cp += 8;
			useSingle = 0;
			break;

		    case CHAR_TYPE:
			if ((int)strlen((char*)
				curField->value->val.charVal) >
				curField->length)
			{

				sprintf(errMsg,VALUE_SIZE_ERROR,
                                                       curField->name);
				return(-1);
			}
			bcopy((char *)curField->value->val.charVal,cp,
			  strlen((char*)curField->value->val.charVal));
			cp += curField->length;
			break;
		}
		count++;
	}

	/*
	** Try to read the index value and bail if it's there
	*/
	node = avlLookup(index->tree, index->buf, AVL_EXACT);
	if (node)
	{
		return(0);
	}
	return(1);
}



int checkIndexNullFields(entry,row,index)
        cache_t *entry;
        row_t 	*row;
	mindex_t *index;
{
        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;
       		while(curField && field)
       		{
			offset += curField->length + 1;
			curField = curField->next;
			field--;
		}
               	if (!*(data + offset))
               	{
                       	sprintf(errMsg,NULL_INDEX_ERROR, 
				curField->name);
                       	msqlTrace(TRACE_OUT,"checkIndexNullFields()");
                       	return(-1);
		}
	}
	msqlTrace(TRACE_OUT,"checkIndexNullFields()");
	return(0);
}


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

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




int checkIndices(entry,fields)
        cache_t *entry;
        field_t *fields;
{
	mindex_t	*curIndex;
	field_t	*curField;
	int	offset,
		count,
		res,
		useSingle;
	avl_nod	*node;
	char	*cp;

	curIndex = entry->indices;
	while(curIndex)
	{
	
		/*
		** If it's a non-unique index, skip it
		*/
		if (!curIndex->unique)
		{
			curIndex = curIndex->next;
			continue;
		}
		
		/*
		** Determine the index value
		*/
		bzero(curIndex->buf, curIndex->length + 1);
		cp = curIndex->buf;
		count = 0;
		if (curIndex->fields[1] == -1)
			useSingle = 1;
		else
			useSingle = 0;
		while(curIndex->fields[count] != -1)
		{
			offset = curIndex->fields[count];
			curField = fields;
			while(offset != curField->fieldID)
			{
				curField = curField->next;
				if (!curField)
					break;
			}
			if (!curField)
				break;
			switch(curField->type)
			{
			    case INT_TYPE:
				bcopy(&(curField->value->val.intVal),cp,4);
				cp += 4;
				useSingle = 0;
				break;

			    case REAL_TYPE:
				bcopy(&(curField->value->val.realVal),cp,8);
				cp += 8;
				useSingle = 0;
				break;

			    case CHAR_TYPE:
				if ((int)strlen((char*)
					curField->value->val.charVal) >
					curField->length)
				{

					sprintf(errMsg,VALUE_SIZE_ERROR,
                                                        curField->name);
					return(-1);
				}
				bcopy((char *)curField->value->val.charVal,cp,
				  strlen((char*)curField->value->val.charVal));
				cp += curField->length;
				break;
			}
			count++;
		}

		if (!curField)
		{
			/*
			** We are missing at least one field of this
			** index in the supplied data.  We must skip it
			** and let the NULL index check pick it up later
			*/
			curIndex = curIndex->next;
			continue;
		}


		/*
		** Try to read the index value and bail if it's there
		*/
		node = avlLookup(curIndex->tree, curIndex->buf, AVL_EXACT);
		if (node)
		{
			return(0);
		}
		curIndex = curIndex->next;
	}
	return(1);
}



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




void setCandidateValues(inner, cand, fields)
	cache_t	*inner;
	cand_t	*cand;
	field_t	*fields;
{
	mindex_t	*curIndex;
	field_t	*curField;
	int	count,
		field,
		strLen;
	char	*cp;

	if (cand->type == CAND_SEQ)
	{
		return;
	}
	curIndex = inner->indices;
	count = cand->index;
	while(count && curIndex)
	{
		count--;
		curIndex = curIndex->next;
	}
	if (!curIndex)
		return;
 
	field = 0;
	cp = cand->buf;
	while(field < MAX_INDEX_WIDTH && curIndex->fields[field] != -1)
	{
		curField = fields;
		while(curField)
		{
			if (curField->fieldID != curIndex->fields[field])
			{
				curField = curField->next;
				continue;
			}
			if (strcmp(curField->table,inner->table) != 0)
			{
				curField = curField->next;
				continue;
			}

			/*
			** We've found the field, grab the value
			*/
			switch(curField->type)
			{
			    case INT_TYPE:
				bcopy(&(curField->value->val.intVal),cp,4);
				cp += 4;
				break;

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

			    case CHAR_TYPE:
				strLen = strlen((char *)
					curField->value->val.charVal);
				bcopy((char *)curField->value->val.charVal,cp,
					strLen);
				if (strLen < curField->length)
				{
					bzero((cp+strLen), curField->length -
 						strLen);
				}
				cp += curField->length;
				break;
			}
			curField = curField->next;
		}
		field++;
	}
	cand->lastPos = NO_POS;
}





static void extractFieldValue(buf,cond)
	char	*buf;
	cond_t	*cond;
{
	/*switch(cond->type)*/
	switch(cond->value->type)
	{
		case INT_TYPE:
			bcopy(&(cond->value->val.intVal),buf,4);
			break;

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

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





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,
		doSingle,
		doRowID,
		rowID;
	char	*tableName,
		*cp,
		path[255];

	/*
	** 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->single = 0;
		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).  It's ugly but it works.
	*/
	if (fields)
	{
		if (fields->next == NULL && fields->sysvar == 1)
		{
			if (strcmp(fields->name, "_seq") == 0)
			{
				new->type = CAND_SYS_SEQ;
				new->lastPos = NO_POS;
				new->single = 0;
				new->length = 0;
				new->rowID = 0;
				msqlDebug(MOD_ACCESS,
				"setupCandidate() : Fake _seq access for %s\n",
				entry->table);
				return(new);
			}
		}
	}

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

	while(curIndex)
	{
		field = 0;
		identKey = 0;
		if (curIndex->fields[1] == -1)
			doSingle = 1;
		else
			doSingle = 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->type != CHAR_TYPE)
					doSingle = 0;
				if (curCond->value->type == IDENT_TYPE)
				{
					identKey |= 1;
				}
				if(strcmp(tableName,curIndex->table)==0 &&
				   curCond->fieldID == curIndex->fields[field])
				{
					if (curCond->op == EQ_OP &&
				           !(curCond->value->type==IDENT_TYPE&& 
					     ignoreIdent))
					{
						break;
					}
				}
				curCond = curCond->next;
			}
			if (!curCond)
			{
				break;
			}
			field++;
		}
		if (curCond)
		{
			new->type = CAND_IDX_ABS;
			new->index = index;
			new->ident = identKey;
			new->lastPos = NO_POS;
			new->single = doSingle;
			new->length = curIndex->length;
			strcpy(new->idx_name, curIndex->name);
			candIndex = curIndex;
			break;
		}
		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 = curIndex->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->fieldID==candIndex->fields[count])
				{
					if (curCond->value->nullVal)
					{
                				sprintf(errMsg,
							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)
	cand_t	*cand;
{
	/*
	** 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)
		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	res,
		length;
	u_int	pos;
	avl_nod	*node;
	char	tmpBuf[80];

	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);
		if (cand->single)
			length = strlen(cand->buf);
		else
			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)
		{
			cand->lastPos = cand->rowID;
			return(cand->rowID);
		}	
		else
		{
			return(NO_POS);
		}
	}
	return(NO_POS);
}
