/*	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>

#  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>

#include <netdb.h>
#include <string.h>
#include <errno.h>

#if defined(_OS_WIN32)
#  include <winsock.h>
#endif

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


#if defined(_OS_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	*msqlDbDir;
extern  char    errMsg[];



void closeIndices(entry)
	cache_t	*entry;
{
	REG 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/%s/%s.idx",
		msqlDbDir,db,table);
        fd = open(path,  O_RDWR | O_CREAT | O_BINARY, 0600);
        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));
                	msqlDebug1(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/%s/%s.idx-%s",
			msqlDbDir,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,newFields, oldFields)
	mindex_t *index;
	field_t	*newFields,
		*oldFields;
{
	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 = newFields;
		while(curField)
		{
			if (curField->fieldID == fieldID)
				break;
			curField = curField->next;
		}
		if (!curField)
		{
			curField = oldFields;
			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,NULL) < 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,NULL) < 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);
			res  = res == 0;
			break;

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

                case REAL_TYPE:
			res = val1->val.realVal == val2->val.realVal;
			break;
        }
	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		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;
			idxCount++;
			continue;
		}


		if (idxCount == candIdx)
			*updated = 1;
		if (fillIndexBuffer(curIndex,oldFields,NULL) < 0)
			return(-1);
		if (avlDelete(curIndex->tree, curIndex->buf, (off_t)pos) < 0)
			return(-1);
		if (fillIndexBuffer(curIndex,newFields,NULL) < 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, newFields, oldFields, index, rowNum)
        cache_t *entry;
        field_t *newFields,
		*oldFields;
	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,newFields,oldFields) < 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, newFields, oldFields, rowNum)
        cache_t *entry;
        field_t *newFields,
		*oldFields;
	u_int	rowNum;
{
	mindex_t	*curIndex;

	curIndex = entry->indices;
	while(curIndex)
	{
		if (checkIndex(entry,newFields,oldFields,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);
}


