/*	types.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.
**
** ID = "$Id:"
**
*/


#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.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 <time.h>

#ifdef HAVE_DIRENT_H
#  include <dirent.h>
#endif

#ifdef HAVE_SYS_DIR_H
#  include <sys/dir.h>
#endif

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

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


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

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

#define REG     register
extern	char    errMsg[];



/*
** Operator class macros
*/
#define ISA_NULL_OP(op) ((op == EQ_OP) || (op == NE_OP))
#define ISA_LIKE_OP(op) ((op >= LIKE_OP) && (op <= NOT_SLIKE_OP))



/****************************************************************************
**      _byteMatch
**
**      Purpose : comparison suite for single bytes.
**      Args    :
**      Returns :
**      Notes   : in-lined for performance
**
*/

#define byteMatch(v1,v2,op, result)				\
{								\
        switch(op)						\
        {							\
                case EQ_OP:					\
                        result = ((char)v1 == (char)v2);	\
                        break;					\
                case NE_OP:					\
                        result = ((char)v1 != (char)v2);	\
                        break;					\
                case LT_OP:					\
                        result = ((char)v1 < (char)v2);		\
                        break;					\
                case LE_OP:					\
                        result = ((char)v1 <= (char)v2);	\
                        break;					\
                case GT_OP:					\
                        result = ((char)v1 > (char)v2);		\
                        break;					\
                case GE_OP:					\
                        result = ((char)v1 >= (char)v2);	\
                        break;					\
        }							\
}




/****************************************************************************
** 	_intMatch
**
**	Purpose	: comparison suite for integer fields.
**	Args	: 
**	Returns	: 
**	Notes	: in-lined for performance
*/

#define intMatch(v1,v2,op,result)			\
{							\
	switch(op)					\
	{						\
		case EQ_OP:				\
			result = (v1 == v2); 		\
			break;				\
		case NE_OP:				\
			result = (v1 != v2);		\
			break;				\
		case LT_OP:				\
			result = (v1 < v2);		\
			break;				\
		case LE_OP:				\
			result = (v1 <= v2);		\
			break;				\
		case GT_OP:				\
			result = (v1 > v2);		\
			break;				\
		case GE_OP:				\
			result = (v1 >= v2);		\
			break;				\
	}						\
}
/****************************************************************************
** 	_uintMatch
**
**	Purpose	: comparison suite for unsigned integer fields.
**	Args	: 
**	Returns	: 
**	Notes	: in-lined for performance
*/

#define uintMatch(v1,v2,op,result)			\
{							\
	switch(op)					\
	{						\
		case EQ_OP:				\
			result = ((u_int)v1 == (u_int)v2); \
			break;				\
		case NE_OP:				\
			result = ((u_int)v1 != (u_int)v2);\
			break;				\
		case LT_OP:				\
			result = ((u_int)v1 < (u_int)v2);\
			break;				\
		case LE_OP:				\
			result = ((u_int)v1 <= (u_int)v2);\
			break;				\
		case GT_OP:				\
			result = ((u_int)v1 > (u_int)v2);\
			break;				\
		case GE_OP:				\
			result = ((u_int)v1 >= (u_int)v2);\
			break;				\
	}						\
}






/****************************************************************************
** 	_charMatch
**
**	Purpose	: Comparison suite for text fields
**	Args	: 
**	Returns	: 
**	Notes	: 
*/

int charMatch(v1,v2,op,maxLen)
	char	*v1,
		*v2;
	int	op,
		maxLen;
{
	int	v1Len, v2Len; /* actual length of input data */
	int	result,
		cmp;

	/* needed for both ordinary and *LIKE operators */
	v1Len = msqlStringLength( v1, maxLen ); 

	/* common stuff for ordinary operators (=, <, ...) */
	if (!ISA_LIKE_OP(op))
	{
		v2Len = strlen( v2 );
		cmp = strncmp( v1, v2, (v1Len < v2Len) ? v1Len : v2Len );
		if (cmp == 0)
		{
			cmp = v1Len - v2Len;
		}
	}

	switch(op)
	{
		case EQ_OP:
			result = (cmp == 0);
			break;
			
		case NE_OP:
			result = (cmp != 0);
			break;
			
		case LT_OP:
			result = (cmp < 0);
			break;
			
		case LE_OP:
			result = (cmp <= 0);
			break;
			
		case GT_OP:
			result = (cmp > 0);
			break;
			
		case GE_OP:
			result = (cmp >= 0);
			break;

		case RLIKE_OP:
			result = rLikeTest(v1,v2,v1Len);
			break;

		case LIKE_OP:
			result = likeTest(v1,v2,v1Len);
			break;

		case CLIKE_OP:
			result = cLikeTest(v1,v2,v1Len);
			break;

		case SLIKE_OP:
			result = sLikeTest(v1,v2,v1Len);
			break;

		case NOT_RLIKE_OP:
			result = !(rLikeTest(v1,v2,v1Len));
			break;

		case NOT_LIKE_OP:
			result = !(likeTest(v1,v2,v1Len));
			break;

		case NOT_CLIKE_OP:
			result = !(cLikeTest(v1,v2,v1Len));
			break;

		case NOT_SLIKE_OP:
			result = !(sLikeTest(v1,v2,v1Len));
			break;
	}
	return(result);
}






/****************************************************************************
** 	_realMatch
**
**	Purpose	: Comparison suite for real fields
**	Args	: 
**	Returns	: 
**	Notes	: in-lined for performance
*/

#define realMatch(v1,v2,op, result)			\
{							\
	switch(op)					\
	{						\
		case EQ_OP:				\
			result = (v1 == v2);		\
			break;				\
		case NE_OP:				\
			result = (v1 != v2);		\
			break;				\
		case LT_OP:				\
			result = (v1 < v2);		\
			break;				\
		case LE_OP:				\
			result = (v1 <= v2);		\
			break;				\
		case GT_OP:				\
			result = (v1 > v2);		\
			break;				\
		case GE_OP:				\
			result = (v1 >= v2);		\
			break;				\
	}						\
}



void printMoney(buf,bufLen,val)
	char	*buf;
	int	val;
{
	char 	*cp;
	int	len;

	/* 
	** Due to some wierd floating point rounding errors we have to
	** do this as text (because 894/100 = 8.93999999999)
	*/
	if (val == 0)
	{
		strcpy(buf,"0.00");
		return;
	}
	snprintf(buf,bufLen,"%d",val);
	len = strlen(buf);
	if (len > 2)
	{
		cp = buf + len;
		*(cp + 1) = 0;
		*cp = *(cp - 1);
		cp--;
		*cp = *(cp - 1);
		cp--;
		*cp = '.';
	}
	if (len == 2)
	{
		cp = buf + len+1;
		*(cp + 1) = 0;
		*cp = *(cp - 2);
		cp--;
		*cp = *(cp - 2);
		cp--;
		*cp = '.';
		cp--;
		*cp = '0';
	}
	if (len == 1)
	{
		cp = buf + len+2;
		*(cp + 1) = 0;
		*cp = *buf;
		cp--;
		*cp = '0';
		cp--;
		*cp = '.';
		cp--;
		*cp = '0';
	}
}


int scanMoney(value)
	val_t	*value;
{
	int	val;

	if (value->type == REAL_TYPE)
	{
		val = value->val.realVal * 100;
	}
	else
	{
		val = value->val.intVal * 100;
	}
	return(val);
}


int msqlBaseType(type)
	int	type;
{
	switch(type)
	{
		case IDENT_TYPE:
			return(IDENT_TYPE);
			break;

		case CHAR_TYPE:
			return(CHAR_TYPE);
			break;

		case INT_TYPE:
		case MONEY_TYPE:
			return(INT_TYPE);
			break;

		case UINT_TYPE:
		case DATE_TYPE:
		case TIME_TYPE:
			return(UINT_TYPE);
			break;


		case REAL_TYPE:
			return(REAL_TYPE);
			break;

		case TEXT_TYPE:
			return(TEXT_TYPE);
			break;

		default:
			fprintf(stderr,"\n\nERROR : Unknown type '%d'\n\n",
				type);
			exit(0);
	}
}

int msqlRepType(type)
	int	type;
{
	switch(type)
	{
		case CHAR_TYPE:
		case DATE_TYPE:
		case TIME_TYPE:
		case TEXT_TYPE:
			return(CHAR_TYPE);
			break;

		default:
			return(INT_TYPE);
	}
}



/****************************************************************************
** Row comparison routines
*/



static int processCondMatch(cacheEntry,curCond,value,row, data, offset,
		tmpVal)
	cache_t	*cacheEntry;
	cond_t	*curCond;
	val_t	*value;
	row_t	*row;
	u_char	*data;
	int	*offset;
	val_t	*tmpVal;
{
	int	tmp;
	int	iv;
	double	fv;
	char	*cp;


	switch(curCond->type)
	{
		case INT_TYPE:
		case MONEY_TYPE:
			if (curCond->sysvar)
			{
				tmp = compareSysVar(cacheEntry,row,
					curCond, value);
				return(tmp);
			}
#ifdef _CRAY
			iv = unpackInt32(data + *offset + 1);
#else
			bcopy4((data + *offset +1),&iv);
#endif
			if (ISA_LIKE_OP(curCond->op))
			{
				strcpy(errMsg, INT_LIKE_ERROR);
				msqlTrace(TRACE_OUT,"matchRow()");
				return(-2);
			}
			intMatch(iv,value->val.intVal,curCond->op,tmp);
			return(tmp);


		case UINT_TYPE:
		case DATE_TYPE:
		case TIME_TYPE:
			if (curCond->sysvar)
			{
				tmp = compareSysVar(cacheEntry,row,
					curCond, value);
				return(tmp);
			}
#ifdef _CRAY
			iv = unpackInt32(data + *offset + 1);
#else
			bcopy4((data + *offset +1),&iv);
#endif
			if (ISA_LIKE_OP(curCond->op))
			{
				strcpy(errMsg, INT_LIKE_ERROR);
				msqlTrace(TRACE_OUT,"matchRow()");
				return(-2);
			}
			uintMatch(iv,value->val.intVal,curCond->op,tmp);
			return(tmp);

		case CHAR_TYPE:
			if (curCond->sysvar)
			{
				tmp = compareSysVar(cacheEntry,row,
					curCond, value);
				return(tmp);
			}
			cp = (char *)data + *offset +1;
			tmp = charMatch(cp,value->val.charVal,
				curCond->op, curCond->length);
			if (value == tmpVal)
			{
				free(tmpVal->val.charVal);
			}
			if (tmp < 0)
			{
				msqlTrace(TRACE_OUT,"matchRow()");
				return(-2);
			}
			return(tmp);

		case TEXT_TYPE:
			cp = (char *)data + *offset +1;
			if (ISA_LIKE_OP(curCond->op))
			{
				strcpy(errMsg, TEXT_REGEX_ERROR);
				msqlTrace(TRACE_OUT,"matchRow()");
				return(-2);
			}
			tmp = matchVarChar(cacheEntry,cp, value->val.charVal,
				curCond->length, curCond->op);
			if (value == tmpVal)
			{
				free(tmpVal->val.charVal);
			}
			if (tmp < 0)
			{
				msqlTrace(TRACE_OUT,"matchRow()");
				return(-2);
			}
			break;

		case REAL_TYPE:
			if (curCond->sysvar)
			{
				tmp = compareSysVar(cacheEntry,row,
					curCond, value);
				return(tmp);
			}
			bcopy8((data + *offset + 2),&fv);
			if (ISA_LIKE_OP(curCond->op))
			{
				strcpy(errMsg, REAL_LIKE_ERROR);
				msqlTrace(TRACE_OUT,"matchRow()");
				return(-2);
			}
			realMatch(fv,value->val.realVal, curCond->op, tmp);
			return(tmp);
	}
}
	

/****************************************************************************
** 	_matchRow
**
**	Purpose	: Determine if the given row matches the required data
**	Args	: 
**	Returns	: 
**	Notes	: Used by "where" clauses
*/

int matchRow(cacheEntry,row,conds)
	cache_t	*cacheEntry;
	row_t	*row;
	cond_t	*conds;
{
	REG 	cond_t	*curCond;
	REG 	char	*cp;
	REG 	int	result,
			tmp;
	int	*offset,
		init=1,
		res;
	u_char	*data;
	val_t	*value,
		tmpVal;
	field_t	*curField,
		tmpField;
	int	tmpFlist[2],
		foundField;
	int	rhsType; 	/* saves rhs type prior to data fetch */
	int	lhsIsNull; 	/* temporary that indicates nullness of lhs */


	msqlTrace(TRACE_IN,"matchRow()");
	result=0;
	if (!conds)
	{
		msqlTrace(TRACE_OUT,"matchRow()");
		return(1);
	}
	data = row->data;
	curCond = conds;
	offset = conds->clist;
	while(curCond)
	{
		/*
		** If this is a subcond just recurse and continue
		*/
		if (curCond->subCond)
		{
			tmp = matchRow(cacheEntry, row, curCond->subCond);
			if (tmp < 0)
				return(tmp);
			if (init)
			{
				result = tmp;
				init = 0;
			}
			else
			{
                        	switch(curCond->bool)
                        	{
                                	case NO_BOOL:
                                        	result = tmp;
                                        	break;

                                	case AND_BOOL:
                                        	result &= tmp;
                                        	break;

                                	case OR_BOOL:
                                        	result |= tmp;
                                        	break;
                        	}
			}
			curCond = curCond->next;
			continue;
		}



		/*
		** OK, it wasn't a sub cond.  Proceded as normal.
		**
		** If we are comparing 2 fields (e.g. in a join) then
		** grab the value of the second field so that we can do
		** the comparison.  Watch for type mismatches!
		*/
		foundField = 0;
		tmpField.value = NULL;
		rhsType = curCond->value->type;
		switch(curCond->value->type)
		{
		    case IDENT_TYPE:
			value = curCond->value;
			curField = cacheEntry->def;
			if (!*(value->val.identVal->seg1))
			{
				if (!cacheEntry->result)
				{
					strcpy(value->val.identVal->seg1,
						cacheEntry->table);
				}
				else
				{
					strcpy(errMsg,UNQUAL_ERROR);
					msqlTrace(TRACE_OUT,"matchRow()");
					return(-1);
				}
			}
			while(curField)
			{
				if (*(curField->table) != 
				    *(value->val.identVal->seg1) ||
				    *(curField->name) !=
				    *(value->val.identVal->seg2))
				{
					curField = curField->next;
					continue;
				}
				if (strcmp(curField->table,
					value->val.identVal->seg1) != 0 ||
				    strcmp(curField->name,
					value->val.identVal->seg2) != 0)
				{
					curField = curField->next;
					continue;
				}

				bcopy(curField,&tmpField,sizeof(field_t));
				tmpField.value=NULL;
				tmpField.next = NULL;
				msqlSetupFields(cacheEntry,tmpFlist, &tmpField);
				extractValues(cacheEntry,row,&tmpField,
					tmpFlist);
				bcopy(tmpField.value,&tmpVal,sizeof(val_t));
				/* RNS
				 * Character data needs to be copied, but
				 * only if there is data.
				 */
				if (tmpVal.type == CHAR_TYPE && !tmpVal.nullVal)
				{
				    tmpVal.val.charVal= (u_char*)
					fastMalloc(curField->length + 1);
				    bcopy(tmpField.value->val.charVal,
					tmpVal.val.charVal, curField->length);
				    *(tmpVal.val.charVal+curField->length) = 0;
				}
				msqlFreeValue(tmpField.value);
				tmpField.value = NULL;
				value = &tmpVal;
				foundField = 1;
				break;
			}
			if (!foundField)
			{
				snprintf(errMsg, MAX_ERR_MSG, BAD_FIELD_ERROR,
					value->val.identVal->seg1,
					value->val.identVal->seg2);
				msqlTrace(TRACE_OUT,"matchRow()");
				return(-1);
			}
			break;

		    case SYSVAR_TYPE:
			strcpy(tmpField.name,
				curCond->value->val.identVal->seg2);
			res = checkSysVar(cacheEntry, &tmpField);
                        if (res == -2)
                                return(-1);
			if (res == -1)
			{
                        	snprintf(errMsg, MAX_ERR_MSG, SYSVAR_ERROR, 
					curCond->value->val.identVal->seg2);
				return(-1);
			}

			getSysVar(cacheEntry,row,&tmpField);
			value = tmpField.value;
			break;

		    case TEXT_TYPE:
		    case INT_TYPE:
		    case UINT_TYPE:
		    case DATE_TYPE:
		    case TIME_TYPE:
		    case MONEY_TYPE:
		    case REAL_TYPE:
		    case CHAR_TYPE:
		    case NULL_TYPE: 
		    default:
			value = curCond->value;
			break;
		}


		/*
		** Ensure that the comparison is with the correct type.
		** We do this here and in msqlSetupConds() as we have to wait
		** for the evaluation of field to field comparisons.  We
		** also fudge it for real/int comparisons.  It's done
		** in msqlSetupConds() to handle cases going to the
		** index lookup code and for literal comparisons.
		*/

		if(msqlSetCondValueType(curCond, value) < 0)
		{
			return(-1);
		}


		/*
		** O.K. do the actual comparison
		*/
		lhsIsNull = (*(data + *offset) == 0);
		if ((rhsType == NULL_TYPE) && ISA_NULL_OP(curCond->op))
		{
			/* 
			** An explicit comparison to NULL.  
			*/
			byteMatch( *(data + *offset), 0, curCond->op, tmp );
		}
		else if (rhsType == NULL_TYPE)
		{
			/* 
			** SQL does not allow other operators for NULL.
			*/
			strcpy(errMsg, "Illegal operator applied to NULL.\n" );
			msqlTrace(TRACE_OUT,"matchRow()");
			return(-1);
		}
		else if (value->nullVal || lhsIsNull)
		{
			/* 
			 * SQL says that any compare of implicit NULL values
			 * should always fail (return false for now).
			 */
			tmp = 0;
		}
		else
		{
			tmp = processCondMatch(cacheEntry,curCond,
					value,row, data, offset, &tmpVal);
			if (tmp == -2)
				return(-1);
		}

		if (init)
		{
			result = tmp;
			init = 0;
		}
		else
		{
			switch(curCond->bool)
			{
				case NO_BOOL:
					result = tmp;
					break;
	
				case AND_BOOL:
					result &= tmp;
					break;
	
				case OR_BOOL:
					result |= tmp;
					break;
			}

			/*
			if ( (result == 0) && (curCond->bool == AND_BOOL))
				break;
			if ( (result == 1) && (curCond->bool == OR_BOOL))
				break;
			*/
		}
		curCond = curCond->next;
		offset++;
	}
	msqlTrace(TRACE_OUT,"matchRow()");
	return(result);
}




int checkDupRow(entry,data1,data2)
	cache_t	*entry;
	u_char	*data1,
		*data2;
{
	field_t	*curField;
	u_char	*cp1, *cp2;
	int	res,
		offset;


	curField = entry->def;
	res = offset = 0;
	while(curField)
	{
		/* RNS
		 * Start by checking for NULL values.
		 * For purpose of DISTINCT we will consider
		 * NULLs equal or not to each other.
		 */
		cp1 = data1+offset;
		cp2 = data2+offset;
		if (*cp1 != *cp2) {
			/* RNS
			 * Here, one is NULL and the other is not,
			 * therefore, we are distinct and are done.
			 */
			res = 1;
			break;
		}
		/* RNS
		 * Here, both are NULL or both have a value.
		 */
		if (*cp1 == 0) {
			/* RNS
			 * Both are NULL, we want to continue with the
			 * next field.
			 */
			offset += curField->dataLength + 1;
			curField = curField->next;
			continue;
		}
		/* RNS
		 * Now that we're done with NULL checking,
		 * move to actual data area.
		 */
		cp1++;
		cp2++;
		switch (curField->type)
		{
			case INT_TYPE:
			case UINT_TYPE:
			case DATE_TYPE:
			case TIME_TYPE:
			case MONEY_TYPE:
				res = bcmp(cp1, cp2, curField->dataLength);
				break;

			case CHAR_TYPE:
				res = bcmp(cp1, cp2, curField->length);
				break;

			case REAL_TYPE:
				res = bcmp(cp1+1, cp2+1, curField->length);
				break;

			case TEXT_TYPE:
				res = compareVarChar(entry,cp1,cp2,
					curField->length);
				break;
		}
		if (res != 0)
			break;
		offset += curField->dataLength + 1;
		curField = curField->next;
	}
	return(res);
}





int compareRows(entry,r1,r2,order,olist)
	cache_t	*entry;
	row_t	*r1,
		*r2;
	order_t	*order;
	int	*olist;
{
	REG 	order_t *curOrder;
	char	buf[sizeof(double)],
		*cp1,
		*cp2;
	u_char	*data1,
		*data2;
	int	res,
		*offset,
		d1IsNull,
		d2IsNull,
		ip1,
		ip2;
	double	fp1,
		fp2;


	/*
	** Allow for cases when rows are not defined
	*/
	msqlTrace(TRACE_IN,"compareRows()");
	if (r1 && !r2)
	{
		msqlTrace(TRACE_OUT,"compareRows()");
		return(-1);
	}
	if (!r1 && r2)
	{
		msqlTrace(TRACE_OUT,"compareRows()");
		return(1);
	}
	if (!r1 && !r2)
	{
		msqlTrace(TRACE_OUT,"compareRows()");
		return(0);
	}

	/*
	** OK, we have both rows.
	*/
	data1 = r1->data;
	data2 = r2->data;
	curOrder = order;
	offset = olist;
	while(curOrder)
	{
		/* RNS
		 * Allow for cases where data is not defined i.e.,
		 * try to do something reasonable with null values.
		 * How should we compare them?
		 * For now, treat them as less than anything else.
		 */
		d1IsNull = (*(data1 + *offset) == 0);
		d2IsNull = (*(data2 + *offset) == 0);
		if (d1IsNull || d2IsNull)
		{
			if (d1IsNull && d2IsNull)
			{
				res = 0;
			}
			else if (d1IsNull)
			{
				res = -1;
			}
			else
			{
				res = 1;
			}
		}
		else switch(curOrder->type)
		{
			case INT_TYPE:
			case UINT_TYPE:
			case DATE_TYPE:
			case TIME_TYPE:
			case MONEY_TYPE:
#ifndef _CRAY
				bcopy4((data1 + *offset +1),buf);
				ip1 = (int) * (int*)buf;
				bcopy4((data2 + *offset +1),buf);
				ip2 = (int) * (int*)buf;
#else
				ip1 = unpackInt32(data1 + *offset + 1);
				ip2 = unpackInt32(data2 + *offset + 1);
#endif

				if (ip1 == ip2)
					res = 0;
				if (ip1 > ip2)
					res = 1;
				if (ip1 < ip2)
					res = -1;
				break;

			case CHAR_TYPE:
				cp1 = (char *)data1 + *offset +1;
				cp2 = (char *)data2 + *offset +1;
				res = strncmp(cp1,cp2,curOrder->length);
				break;

			case REAL_TYPE:
				bcopy8((data1+*offset+2),buf);
				fp1 = (double) * (double *)(buf);
				bcopy8((data2+*offset+2),buf);
				fp2 = (double) * (double *)(buf);
				if (fp1 == fp2)
					res = 0;
				if (fp1 > fp2)
					res = 1;
				if (fp1 < fp2)
					res = -1;
				break;

			case TEXT_TYPE:
				cp1 = (char *)data1 + *offset +1;
				cp2 = (char *)data2 + *offset +1;
				res = compareVarChar(curOrder->entry, cp1, 
					cp2, curOrder->length);
		}
		if (curOrder->dir == DESC)
		{
			res = 0 - res;
		}
		if (res != 0)
		{
			msqlTrace(TRACE_OUT,"compareRows()");
			return(res);
		}
		curOrder = curOrder->next;
		offset++;
	}
	msqlTrace(TRACE_OUT,"compareRows()");
	return(0);
}




