/*	cra.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[];




/*
** This is the mSQL 2.0 Candidate Row Abstraction code.  In short, it's
** the query optimsation module.
*/

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;
        }
}



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);
}





tname_t *msqlReorderTableList(tables, conds)
	tname_t *tables;
	cond_t	*conds;
{
	/*
	** This isn't part of the CRA but it still an important part
	** of the query optimiser so it's in this module.
	**
	** Work out the best order for the tables of a join to be
	** processed as the user may have specified them in a bad
	** order (like certain benchmark suites).
	*/

	cond_t	*curCond;
	tname_t	*head = NULL,
		*tail,
		*prevTable,
		*curTable,
		*pivotTable,
		*pivotPrev,
		*nextTable;
	int	condCount,
		maxCount;


	/*
	** Any table with a literal condition goes to the head
	** of the list
	*/
	curTable = tables;
	prevTable = NULL;
	while(curTable)
	{
		curCond = conds;
		while(curCond)
		{
			if (strcmp(curCond->table,curTable->name) == 0 &&
				curCond->value->type != IDENT_TYPE)
			{
				break;
			}
			curCond = curCond->next;
		}
		if (curCond)
		{
			/* 
			** Pull it out of the original list 
			*/
			if (prevTable)
				prevTable->next = curTable->next;
			else
				tables = curTable->next;

			/* 
			** Add it to the new list 
			*/
			if (head)
				tail->next = curTable;
			else
				head = curTable;
			tail = curTable;
			tail->next;

			/* 
			** Move on to the next 
			*/
			if (prevTable)
				curTable = prevTable->next;
			else
				curTable = tables;
			continue;
		}
		prevTable = curTable;
		curTable = curTable->next;
	}

	/*
	** Next, move the remaining tables onto the list ensuring that
	** the "pivot" table of the remainder is moved first.
	*/
	while(tables)
	{
		maxCount = 0;
		curTable = tables;
		prevTable = NULL;
		while(curTable)
		{
			curCond = conds;
			condCount = 0;
			while(curCond)
			{
				if (strcmp(curCond->table, curTable->name)==0)
				{
					condCount++;
				}
				else 
				if (curCond->value->type == IDENT_TYPE &&
				    strcmp(curCond->value->val.identVal->seg1, 
					curTable->name)==0)
				{
					condCount++;
				}
				curCond = curCond->next;
				continue;
			}
			if (condCount > maxCount)
			{
				pivotTable = curTable;
				pivotPrev = prevTable;
				maxCount = condCount;
			}
			prevTable = curTable;
			curTable = curTable->next;
		}
		if (maxCount == 0)
		{
			/* 
			** Not good.  Break out so we just append the 
			** rest and bail 
			*/
			break;
		}
		if (pivotPrev)
			pivotPrev->next = pivotTable->next;
		else
			tables = pivotTable->next;
		if (head)
			tail->next = pivotTable;
		else
			head = pivotTable;
		tail = pivotTable;
		tail->next = NULL;
	}
	if (head)
		tail->next = tables;
	else
		head = tables;
	return(head);
}


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,
		*idxCond;
	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)
	{
		idxCond = NULL;
		field = 0;
		identKey = 0;
		while(field < MAX_INDEX_WIDTH && curIndex->fields[field] != -1)
		{
			curCond = conds;
			while(curCond)
			{
				identKey = 0;
				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 && !
				         (identKey && 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]
				  && curCond->value->type != IDENT_TYPE)
				{
					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);
}

