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

/* RNS
 * ISA_NULL_OP -- macro to determine if an operator is one of the NULL
 * family of operators.  This should probably move to msql_priv.h.
 */
#ifndef ISA_NULL_OP
#  define ISA_NULL_OP(op) ((op == EQ_OP) || (op == NE_OP))
#endif

/* RNS
 * ISA_LIKE_OP -- macro to determine if an operator is one of the LIKE
 * family of operators.  This should probably move to msql_priv.h.
 */
#ifndef ISA_LIKE_OP
#  define ISA_LIKE_OP(op) ((op >= LIKE_OP) && (op <= NOT_SLIKE_OP))
#endif


/****************************************************************************
** Regexp related routines
*/

/* RNS
 * msqlStringLength -- determine the actual string length of mSQL data string.
 * dptr -- character data to determine length.
 * maxLen -- maximum possible length. (Shorter if a nul character occurs.)
 * returns an integer that is the actual number of characters (not
 *   including a nul, if there is one.
 */
int msqlStringLength( dptr, maxLen )
     char *dptr;
     int maxLen;
{
  REG int len;

  len = 0;
  while ((maxLen > 0) && (*dptr != '\0')) {
    len++;
    maxLen--;
    dptr++;
  }

  return len;
}

/* RNS
 * Using basic ANSI SQL regular expression specification,
 * see if a expression matches some data.  The expression
 * is nul terminated and the data is terminated by the
 * length dlen which is enforced by the use of msqlStringLength.
 * (See charMatch.)  It would really be nice if mSQL kept
 * track of the actual data lengths in the db (and of literals).
 * In the meantime, using msqlStringLength is a win for
 * expressions with multiple % wildcards, especially if the data is
 * at least of moderate length.  It does slow down expressions
 * without %'s or matches to very short data.
 */

/* RNS
 * likeTest -- (simple) ANSI SQL regular expression matcher.
 * dptr -- character data to be matched.  May or may not be nul terminated.
 * eptr -- regular expression to match. Must be nul terminated.
 * dlen -- length of the data pointed to by dptr.  Determines data end when
 *     it is not nul terminated.
 * returns true (non-zero) or false (0) for match or no match, respectively.
 */

int likeTest( dptr, eptr, dlen )
     char *dptr;
     char *eptr;
     int  dlen;
{
  char eval; /* temporary used for performance and % handling */

  eval = *eptr;
  while (eval != '\0') { /* use expression as the "program" */

    if (eval == '%') { /* ignore 0 or more characters */

      /* try to find the next character that must match */

      /* collapse special characters: %'s and _'s */
      eptr++;
      eval = *eptr;
      while (eval != '\0') { /* while not at end */
	if (eval == '%') { /* any number of %'s same as one */
	  eptr++;
	  eval = *eptr;
	  continue;
	}
	if (eval == '_') { /* try to consume one char for each _ */
	  if (dlen == 0) {
	    /* data ended first: no match. */
	    return 0;
	  }
	  /* else consume a character and continue */
	  eptr++;
	  eval = *eptr;
	  dptr++;
	  dlen--;
	  continue;
	}
	break; /* no more % or _ together here, so on to something specific. */
      }

      /* special case of ending with enough characters for wild cards. */
      if (eval == '\0') {
	return 1;
      }

      if (eval == '\\') {
	/* backslash escapes everything including self, so skip it always. */
	eptr++;
	eval = *eptr;
	if (eval == '\0') {
	  /* end with match (skipping an ending backslash) */
	  return 1;
	}
	/* else we just go on to compare */
      }

      /*
	 At this point, we have a non-special character to look for in data,
	 then we recursively match remainder of expression at points where
	 that character exists in the data.
	 */
      /* note: leave eval alone, but eptr positioned for recursion. */
      eptr++;
      while (dlen != 0) {
	while ((dlen != 0) && (*dptr != eval)) {
	  dptr++;
	  dlen--;
	}
	if (dlen == 0) { /* there is no eval in data */
	  return 0;
	}

	/* we matched eval and need to check rest of exp and data */
	dptr++;
	dlen--;
	if (likeTest( dptr, eptr, dlen )) {
	  /* match */
	  return 1;
	} /* else just try to see if this position is an eval */

      } /* end try to match rest */

      /* at this point there is something after % and it never matched */
      return 0;
    } /* end if % */

    if (eval ==  '_') { /* try to consume one char */
      if (dlen == 0) {
	/* data ended first: no match. */
	return 0;
      }
      /* else consume a character and continue */
      eptr++;
      eval = *eptr;
      dptr++;
      dlen--;
      continue;
    } /* end if _ */

    if (eval == '\\') {
      /* backslash escapes everything including self, so skip it always. */
      eptr++;
      eval = *eptr;
      if (eval == '\0') {
	/*
	   if dlen == 0 then end with match (skipping an ending backslash)
	   else expr ended before data
	   */
	return (dlen == 0);
      }
      /* else we just go on to compare */
    }

    if ((dlen == 0) || (eval != *dptr)) {
      /* data ends before expr or they don't match */
      return 0;
    }
    /* else this character matched, consume it and continue */
    eptr++;
    eval = *eptr;
    dptr++;
    dlen--;
  } /* end while */

  /* if we matched everything, return true. */
  return (dlen == 0);
}


/* RNS
 * cLikeTest -- case-insensitive ANSI SQL regular expression matcher.
 *   see description of likeTest as this is a copy save that comparisions
 *   use toupper to make cases the same.  Note one's locale must be
 *   properly set.
 */
int cLikeTest( dptr, eptr, dlen )
     u_char *dptr;
     u_char *eptr;
     int  dlen;
{
  u_char eval; /* temporary used for performance and % handling */

  eval = *eptr;
  while (eval != '\0') { /* use expression as the "program" */

    if (eval == '%') { /* ignore 0 or more characters */

      /* try to find the next character that must match */

      /* collapse special characters: %'s and _'s */
      eptr++;
      eval = *eptr;
      while (eval != '\0') { /* while not at end */
	if (eval == '%') { /* any number of %'s same as one */
	  eptr++;
	  eval = *eptr;
	  continue;
	}
	if (eval == '_') { /* try to consume one char for each _ */
	  if (dlen == 0) {
	    /* data ended first: no match. */
	    return 0;
	  }
	  /* else consume a character and continue */
	  eptr++;
	  eval = *eptr;
	  dptr++;
	  dlen--;
	  continue;
	}
	break; /* no more % or _ together here, so on to something specific. */
      }

      /* special case of ending with enough characters for wild cards. */
      if (eval == '\0') {
	return 1;
      }

      if (eval == '\\') {
	/* backslash escapes everything including self, so skip it always. */
	eptr++;
	eval = *eptr;
	if (eval == '\0') {
	  /* end with match (skipping an ending backslash) */
	  return 1;
	}
	/* else we just go on to compare */
      }

      /*
	 At this point, we have a non-special character to look for in data,
	 then we recursively match remainder of expression at points where
	 that character exists in the data.
	 */
      /* note: eval kept except for case, position eptr for recursion.*/
      eval = toupper(eval);
      eptr++;
      while (dlen != 0) {
	while ((dlen != 0) && (toupper(*dptr) != eval)) {
	  dptr++;
	  dlen--;
	}
	if (dlen == 0) { /* there is no eval in data */
	  return 0;
	}

	/* we matched eval and need to check rest of exp and data */
	dptr++;
	dlen--;
	if (cLikeTest( dptr, eptr, dlen )) {
	  /* match */
	  return 1;
	} /* else just try to see if this position is an eval */

      } /* end try to match rest */

      /* at this point there is something after % and it never matched */
      return 0;
    } /* end if % */

    if (eval ==  '_') { /* try to consume one char */
      if (dlen == 0) {
	/* data ended first: no match. */
	return 0;
      }
      /* else consume a character and continue */
      eptr++;
      eval = *eptr;
      dptr++;
      dlen--;
      continue;
    } /* end if _ */

    if (eval == '\\') {
      /* backslash escapes everything including self, so skip it always. */
      eptr++;
      eval = *eptr;
      if (eval == '\0') {
	/*
	   if dlen == 0 then end with match (skipping an ending backslash)
	   else expr ended before data
	   */
	return (dlen == 0);
      }
      /* else we just go on to compare */
    }

    if ((dlen == 0) || (toupper(eval) != toupper(*dptr))) {
      /* data ends before expr or they don't match */
      return 0;
    }
    /* else this character matched, consume it and continue */
    eptr++;
    eval = *eptr;
    dptr++;
    dlen--;
  } /* end while */

  /* if we matched everything, return true. */
  return (dlen == 0);
}

/* RNS
 * Non-ANSI, full-functioned regular expressions courtesy of
 * Henry Spencer.
 */

/*
 * RLIKE_DATA_MAXLEN -- the maximum length of a string that the
 * static data buffer can hold in order to use it rather than malloc.
 * 1024 is big enough for full pathnames on most UNIX boxes.
 */
#define RLIKE_DATA_MAXLEN 1024

/*
 * rLikeBuffer -- a static character buffer that should be faster
 * than using malloc.  If RLIKE_DATA_MAXLEN is too small, then
 * malloc is used.
 */
static char rLikeBuffer[RLIKE_DATA_MAXLEN + 1];

/*
 * RLIKE_NUM_REGEXPS -- the number of regular expression compilations
 * to cache (i.e., the RLIKE cache size).  RLIKE_NUM_REGEXPS must be
 * at least 1, but can be as large as needed.  Normally, a small number
 * like 5 is probably about right.  However, if you have an application
 * or set of applications that repeatedly use the same set of expressions,
 * then increasing the number to the size of the set of expressions (or
 * a bit larger) is the right thing to do.
 */
#define RLIKE_NUM_REGEXPS 5

/*
 * rLikePatternLengths -- an array in correspondence to rLikePatterns
 * that maintains the lengths of the cached expressions.
*/
static int rLikePatternLengths[RLIKE_NUM_REGEXPS];

/*
 * rLikePatterns -- an array in correspondence to the other rLike variables
 * that maintains the strings for the cached, compiled expressions.
*/
static char *rLikePatterns[RLIKE_NUM_REGEXPS];

/*
 * rLikeRegexps -- an array in correspondence to rLikePatterns
 * that maintains the compiled regular expressions.
*/
static regexp *rLikeRegexps[RLIKE_NUM_REGEXPS];

/*
 * regErrFlag -- regular expression error indicator
 */
static char regErrFlag;

/*
 * regerror -- function called by regular expression compiler and
 * executor in order to indicate that they have experienced errors.
 * In mSQL's case, simply incrementing regErrFlag is all that is needed.
 */
void regerror()
{
  regErrFlag++;
}

/*
 * rLikeTest -- use Henry Spencer's regular expressions to effect the
 * RLIKE comparison for a given data string, expression, and data string
 * length. It uses a simple cache to avoid compiling the expressions.
 * If msql*d is effectively repeating or alternating multiple expressions,
 * then this cache will be more effective than the stock 2.0B4 and
 * earlier versions.
 */
int rLikeTest(s,re,slen)
	char	*s,
		*re;
	int	slen;
{
  char *data; /* pointer to the string to match */
  int i;      /* loop index */
  int length; /* length of the current regular expression (re) */

  static char *lastRE = NULL; /*
			       * between uses, the previous expression used
			       * (copy of previous re)
			       */
  static regexp *reg = NULL;  /* between uses, the compilation of lastRE */
  int result;                 /* result to return from entire function */

  /* global initialization (done once; should be part of whole daemon init) */
  if (lastRE == NULL) {
    for( i = 0; i < RLIKE_NUM_REGEXPS; i++ ) {
      rLikePatternLengths[i] = -1;
      rLikePatterns[i] = NULL;
      rLikeRegexps[i] = NULL;
    }
  }

  /* prepare for cache search */
  lastRE = NULL;
  reg = NULL;
  length = strlen(re);

  for (i = 0; i < RLIKE_NUM_REGEXPS; i++) {

    if ((length == rLikePatternLengths[i])
	&& (strcmp(re, rLikePatterns[i]) == 0)) {

      /* We have a cache hit. */
      if (i != 0) {
	/*
	 * Move the matched pattern to the first slot in the
	 * cache and shift the other patterns down one position.
	 */
	int j;
	char *cachedString;

	cachedString = rLikePatterns[i];
	reg = rLikeRegexps[i];
	for (j = i-1; j >= 0; j--) {
	  rLikePatterns[j+1] = rLikePatterns[j];
	  rLikePatternLengths[j+1] = rLikePatternLengths[j];
	  rLikeRegexps[j+1] = rLikeRegexps[j];
	}
	rLikePatterns[0] = cachedString;
	rLikePatternLengths[0] = length;
	rLikeRegexps[0] = reg;
      }

      lastRE = rLikePatterns[0];
      reg = rLikeRegexps[0];
      break; /* we found it, so no need to check rest of cache. */
    }

  }

  /* initialize regular expression routines */
  regErrFlag = 0;

  /*
   * if needed, compile the regular expression, check for errors,
   * and add it to the cache
   */
  if (lastRE == NULL) {

    /* No match in the cache.  Compile the string and add it to the cache. */
    reg = regcomp(re);
    if (regErrFlag) {
      strcpy(errMsg, "RLIKE expression is malformed" );
      msqlDebug(MOD_ERR, "Compilation of RLIKE expression failed\n");
      return(-1);
    }

    /* check to see if cache is full, free old one, if full. */
    if (rLikePatterns[RLIKE_NUM_REGEXPS-1] != NULL) {
	free(rLikePatterns[RLIKE_NUM_REGEXPS-1]);
	free(rLikeRegexps[RLIKE_NUM_REGEXPS-1]);
    }

    /* add to cache by shifting, then filling slot 0 */
    for (i = RLIKE_NUM_REGEXPS - 2; i >= 0; i--) {
      rLikePatterns[i+1] = rLikePatterns[i];
      rLikePatternLengths[i+1] = rLikePatternLengths[i];
      rLikeRegexps[i+1] = rLikeRegexps[i];
    }
    if ((rLikePatterns[0] = malloc(length+1)) == NULL) {
      strcpy(errMsg, "RLIKE failed to get pattern memory." );
      msqlDebug(MOD_ERR, "RLIKE failed to get pattern memory\n");
      return(-1);
    }
    strcpy(rLikePatterns[0], re);
    rLikePatternLengths[0] = length;
    rLikeRegexps[0] = reg;
    lastRE = rLikePatterns[0];
  }

  /* RNS
   * As noted with the other LIKE operations, the data is
   * NOT guaranteed to be nul-terminated.  So, do that here.
   * We try to use a static buffer most of the time to avoid malloc/free.
   */
  if (slen > RLIKE_DATA_MAXLEN) {
    /* malloc: static buffer not big enough */
    if ((data = malloc(slen + 1)) == NULL) {
      strcpy(errMsg, "RLIKE nul-termination of data failed." );
      msqlDebug(MOD_ERR, "RLIKE failed to get data memory\n");
      return(-1);
    }
  } else {
    /* use static buffer */
    data = &rLikeBuffer[0];
  }
  /* copy the data and terminate it */
  strncpy( data, s, slen );
  data[slen] = '\0';

  /* evaluate the expression for a match */
  result = regexec( reg, data );

  /* if necessary, free the copy of data */
  if (slen > RLIKE_DATA_MAXLEN) {
    free( data );
  }

  /* check for errors */
  if (regErrFlag) {
    strcpy(errMsg, BAD_LIKE_ERROR);
    msqlDebug(MOD_ERR, "Evaluation of RLIKE clause failed\n");
    return(-1);
  }

  return(result);
}

/* RNS
 * Data management for the SLIKE operator.
 */
/*
 * SLIKE_DATA_MAXLEN -- the maximum length of a string that the
 * static data buffer can hold in order to use it rather than malloc.
 * 1024 is big enough for full pathnames on most UNIX boxes.
 */
#define SLIKE_DATA_MAXLEN 1024

/*
 * sLikeBuffer -- a static character buffer that should be faster
 * than using malloc.  If SLIKE_DATA_MAXLEN is too small, then
 * malloc is used.
 */
static char sLikeBuffer[SLIKE_DATA_MAXLEN + 1];

/*
** This is a modification of the soundex algorithm. It returns a
** numeric value rather than a char string containing a char and a
** string of digits.  This allows the final comparison to be done 
** using an int compare rather than a string compare which will 
** help performance.  It also attempts to include vowel sounds.
**
** Bambi
*/

static int isvowel(c)
	char	c;
{
	c = tolower(c);
	if (c=='a'||c=='e'||c=='i'||c=='o'||c=='u')
		return(1);
	return(0);
}


static int soundex (word)
	char	*word;
{
  	int	result,
		sound,
		prevSound,
		numBits;

	prevSound = result = 0;
  	if (word == NULL || *word == 0)
    		return(0);

	result = toupper(*word) - 'A';
	numBits = 5;

  	while(*word)
   	{
		sound = 0;
		switch (toupper(*word))
		{
			/* Replace Labials with "1" */
			case 'B':
	  		case 'F':
	  		case 'P':
	  		case 'V': 
				if (prevSound != 1)
					sound = 1;
		    		break;

	  		/* Replace Gutterals & sibilants with "2" */
	  		case 'C':
	  		case 'G':
	  		case 'J':
	  		case 'K':
	  		case 'Q':
	  		case 'S':
	  		case 'X':
	  		case 'Z': 
				if (prevSound != 2)
					sound = 2;
		    		break;

	  		/* Replace Dentals with "3" */
	  		case 'D':
	  		case 'T': 
				if (prevSound != 3)
					sound = 3;
		    		break;

	  		/* Replace Longliquids with "4" */
	  		case 'L': 
				if (prevSound != 4)
					sound = 4;
		     		break;

	  		/* Replace Nasals with "5" */
	  		case 'M':
	  		case 'N': 
				if (prevSound != 5)
					sound = 5;
		    		break;

	  		/* Replace Shortliquids with "6" */
	  		case 'R': 
				if (prevSound != 6)
					sound = 6;
		    		break;

#ifdef NOTDEF
			/* Vowel sounds */
			case 'A':
				if (*(word+1)=='A' || *(word+1)=='E')
					sound = 7;
				if (!isvowel(*(word+1)))
					sound = 7;
				if (sound == 0)
					break;
				if (prevSound == 7)
					sound = 0;
				break;

			case 'E':
				if (*(word+1)=='E' || *(word+1)=='I')
					sound = 8;
				if (sound == 0)
					break;
				if (prevSound == 8)
					sound = 0;
				break;

			case 'I':
				if (*(word+1)=='E')
					sound = 8;
				if (sound == 0)
					break;
				if (prevSound == 8)
					sound = 0;
				break;

			case 'O':
				if (*(word+1)=='O')
					sound = 9;
				if (sound == 0)
					break;
				if (prevSound == 9)
					sound = 0;
				break;

			case 'U':
				if (*(word+1)=='E' || !isvowel(*(word+1)))
					sound = 9;
				if (sound == 0)
					break;
				if (prevSound == 9)
					sound = 0;
				break;
#endif
				
		}  

		if (sound != 0)
		{
			prevSound = sound;
			if (numBits < 29)
			{
				result = result << 3;
				result += sound;
				numBits += 3;
			}
			if (numBits >= 30)
				break;
		}
		word++;
   	}  
	return(result);
}


int sLikeTest(str1, str2, str1len)
     char *str1;
     char *str2;
     int str1len;
{
  char *data;
  int sound1;
  int sound2;

   /* RNS
    * At least as of 2.0 B6, it is still possible that data for the
    * first argument will be non-nul terminated as in rLikeTest.
    * We add a similar nul-terminating mechanism here.
    * We try to use a static buffer most of the time to avoid malloc/free.
    */
  if (str1len > SLIKE_DATA_MAXLEN) {
    /* malloc: static buffer not big enough */
    if ((data = malloc(str1len + 1)) == NULL) {
      strcpy(errMsg, "SLIKE nul-termination of data failed." );
      msqlDebug(MOD_ERR, "SLIKE failed to get data memory\n");
      return(-1);
    }
  } else {
    /* use static buffer */
    data = &sLikeBuffer[0];
  }
  /* copy the data and terminate it */
  strncpy( data, str1, str1len );
  data[str1len] = '\0';

  /* evaluate the expression for a match */
  sound1 = soundex(data);
  sound2 = soundex(str2);

  /* if necessary, free the copy of data */
  if (str1len > SLIKE_DATA_MAXLEN) {
    free( data );
  }

  return( (sound1 == sound2) );
}



/****************************************************************************
** Simple type comparison routines
*/

/****************************************************************************
**      _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;				\
	}						\
}



/****************************************************************************
** varchar and overflow buffer routines
*/


/****************************************************************************
**      _
**
**      Purpose : 
**      Args    : 
**      Returns : 
**      Notes   :
*/

u_int readOverflowFreeList(cacheEntry,pos)
	cache_t	*cacheEntry;
	u_int	pos;
{
	char	*cp;
	u_int	*next;

	/*
	** Note, skip the leading freelist header
	*/
	cp = ((char *)cacheEntry->overflowMap) + sizeof(u_int) +
		(pos * (OFB_SIZE + sizeof(u_int)));
	next = (u_int *)cp;
	return(*next);
}


int writeOverflowFreeList(cacheEntry,pos,value)
	cache_t	*cacheEntry;
	u_int	pos,
		value;
{
	char	*cp;

	cp = ((char *)cacheEntry->overflowMap) + sizeof(u_int) +
		(pos * (OFB_SIZE + sizeof(u_int)));
	bcopy(&value, cp, sizeof(u_int));
	return(0);
}



u_int popOverflowPos(cacheEntry)
        cache_t *cacheEntry;
{
        u_int   pos,
		*posPtr;

        msqlTrace(TRACE_IN,"popOverflowPos()");
        posPtr = (u_int *)cacheEntry->overflowMap;
        if (!posPtr)
                return(NO_POS);
	pos = *posPtr;
        if (pos != NO_POS)
        {
                *posPtr = readOverflowFreeList(cacheEntry,pos);
        }
        return(pos);
}





int pushOverflowPos(cacheEntry, pos)
        cache_t *cacheEntry;
        u_int   pos;
{
	u_int	*posPtr;

        msqlTrace(TRACE_IN,"pushOverflowPos()");

        posPtr = (u_int *)cacheEntry->overflowMap;
        if ((*posPtr = NO_POS))
        {
                *posPtr = pos;
        }
        else
        {
		if (writeOverflowFreeList(cacheEntry,*posPtr,pos) < 0)
		{
                        return(-1);
                }
        }
        if (writeOverflowFreeList(cacheEntry,pos,NO_POS) < 0)
        {
                return(-1);
        }
        return(0);
}



u_int writeOverflow(entry, pos, lastPos, data, length)
	cache_t	*entry;
	u_int	pos,
		lastPos;
	u_char	*data;
	int	length;
{
	u_char	*cp;
	off_t	offset;
	static  u_char	buf[OFB_SIZE + sizeof(u_int)];

	if (pos != NO_POS)
	{
		cp = (u_char *)entry->overflowMap + sizeof(u_int) +
			(pos * (OFB_SIZE + sizeof(u_int)));
		bcopy(&lastPos, cp, sizeof(u_int));
		bcopy(data, cp + sizeof(u_int), length);
	}
	else
	{
		lseek(entry->overflowFD, 0L, SEEK_END);
		offset = lseek(entry->overflowFD, 0L, SEEK_CUR);
		bzero(buf,sizeof(buf));
		cp = buf;
		bcopy(&lastPos,cp,sizeof(u_int));
		bcopy(data, cp + sizeof(u_int), length);
		write(entry->overflowFD,buf, sizeof(buf));
		entry->remapOverflow = 1;
		pos = (offset - sizeof(u_int)) / (OFB_SIZE + sizeof(u_int));
	}
	return(pos);
}


void readOverflow(entry, pos, nextPos, buf, numBytes)
	cache_t	*entry;
	u_int	pos,
		*nextPos;
	u_char	*buf;
	int	numBytes;
{
	u_char	*cp;

	cp = (u_char *)entry->overflowMap + sizeof(u_int) + 
		(pos * (OFB_SIZE + sizeof(u_int)));
	bcopy(cp, nextPos, sizeof(u_int));
	bcopy(cp + sizeof(u_int), buf, numBytes);
	/* RNS
	 * Filling past the end is not needed, so we no longer do it.
	 * This also means we can save the storage space in the other
	 * routines' buf* variables.
	 * *(buf + numBytes) = 0;
	 */
}

/* RNS
 * nextOverflow -- just find the next overflow buffer in the chain.
 * NOTE:  The caller is responsible to call this routine *only* when
 * there is a next buffer.  See, for example, deleteVarChar.
 * NOTE:  This is basically readOverflow without the data transfer.
 */
void nextOverflow(entry, pos, nextPos)
	cache_t	*entry;
	u_int	pos,
		*nextPos;
{
	u_char	*cp;

	cp = (u_char *)entry->overflowMap + sizeof(u_int) + 
		(pos * (OFB_SIZE + sizeof(u_int)));
	bcopy(cp, nextPos, sizeof(u_int));
}


u_int writeVarChar(entry, data, length)
	cache_t	*entry;
	u_char	*data;
	int	length;
{
	u_char	*cp;
	int	remain,
		numBytes;
	u_int	pos,
		lastPos;

	remain = strlen((char *)data) - length;
	numBytes = remain % OFB_SIZE;
	lastPos = NO_POS;
	while(remain)
	{
		pos = popOverflowPos(entry);
		cp = data + length + remain - numBytes;
		pos = writeOverflow(entry, pos, lastPos, cp, numBytes);
                msqlDebug(MOD_TEXT,"Wrote %d bytes at overflow %d\n",
			numBytes, pos);
		remain -= numBytes;
		numBytes = OFB_SIZE;
		lastPos = pos;
	}
	return(lastPos);
}




u_char *readVarChar(entry, data, fieldLen)
	cache_t	*entry;
	u_char	*data;
	int	fieldLen;
{
	u_char	*value,
		*cp;
	u_int	pos,
		nextPos;
	int	length,
		numBytes;

	bcopy(data,&length, sizeof(int));
	if (length == 0)
		return((u_char *)strdup(""));
	bcopy(data + sizeof(int) ,&pos, sizeof(u_int));
	value = (u_char *)malloc(length + 1);
	if (!value)
	{
		return(NULL);
	}
	cp = value;
	if (fieldLen > length)
		numBytes = length;
	else
		numBytes = fieldLen;
	bcopy(data + sizeof(int)+ sizeof(u_int), cp, numBytes);
	cp += numBytes;
	length -= numBytes;
	while(pos != NO_POS)
	{
		if (length > OFB_SIZE)
			numBytes = OFB_SIZE;
		else
			numBytes = length;
		readOverflow(entry, pos, &nextPos, cp, numBytes);
                msqlDebug(MOD_TEXT,"Read %d bytes at overflow %d\n",
			numBytes, pos);
		pos = nextPos;
		cp += numBytes;
		length -= numBytes;
	}
	/* RNS
	 * malloc is not guaranteed to initialize memory and
	 * varChars (TEXT) are not stored with nul-termination.
	 */
	*cp = '\0';
	return(value);
}



void deleteVarChar(entry, pos)
	cache_t	*entry;
	u_int	pos;
{
	u_int	nextPos;

	while(pos != NO_POS)
	{
		nextOverflow(entry, pos, &nextPos);
		pushOverflowPos(entry, pos);
		pos = nextPos;
	}
}


/* RNS
 * compareVarChar -- used to compare two TEXT fields from same table
 * (the same entry and the same fieldLen).
 */
int compareVarChar(entry, data1, data2, fieldLen)
	cache_t	*entry;
	u_char	*data1,
		*data2;
	int	fieldLen;
{
	u_char	*cp1, 	*cp2,
		buf1[OFB_SIZE],
		buf2[OFB_SIZE];
	int	count1, count2,
		d1Len, 	d2Len,
		segLen1,segLen2;
	u_int	pos1, 	pos2;

	/* RNS Get the lengths of the data */
	bcopy(data1,&d1Len, sizeof(int));
	bcopy(data2,&d2Len, sizeof(int));

	/* RNS Short circuit for both or either of zero length */
	if (d1Len == 0 && d2Len == 0)
		return(0);
	if (d1Len == 0)
		return(-1);
	if (d2Len == 0)
		return(1);

	/* RNS Get overflow buffers, if any */
	bcopy(data1 + sizeof(int) ,&pos1, sizeof(u_int));
	bcopy(data2 + sizeof(int) ,&pos2, sizeof(u_int));

	/* RNS Position pointers to row data to get comparison started */
	cp1 = data1 + sizeof(int) + sizeof(u_int);
	cp2 = data2 + sizeof(int) + sizeof(u_int);

	/* RNS
	 * Comparison is segmented, row followed by overflow buffers.
	 * Lengths need to be watched carefully.
	 */
	segLen1 = count1 = d1Len > fieldLen? fieldLen : d1Len;
	segLen2 = count2 = d2Len > fieldLen? fieldLen : d2Len;

	while(d1Len && d2Len)
	{
		while(count1 && count2)
		{
			/* RNS
			 * I have re-arranged checks because I think (perhaps
			 * erroneously) inequality likely more common.
			 * It also eliminates need for equality comparison.
			 */
			if (*cp1 < *cp2)
				return(-1);
			if (*cp2 < *cp1)
				return(1);
			/* RNS
			 * Here we have identical characters and because
			 * lengths are used, we don't need to bother with
			 * nul character check.
			 */
			count1--;
			count2--;
			cp1++;
			cp2++;
		}

		/* RNS
		 * Check counts for when strings end in same "segment":
		 * We know that at least one has reached the end, but
		 * both must end in order to continue on to another
		 * segment.
		 */
		if (count1) /* d1 is longer */
			return(1);
		if (count2) /* d2 is longer */
			return(-1);

		/* RNS
		 * If both have more buffers (segments), then
		 *   go on to another buffer full,
		 * else
		 *   get out of loop (and compare the lengths).
		 */
		d1Len -= segLen1;
		d2Len -= segLen2;
		if (d1Len == 0 || d2Len == 0)
			break;

		/* RNS Get next d1 buffer */
		readOverflow(entry, pos1, &pos1, buf1,
			d1Len>OFB_SIZE? OFB_SIZE : d1Len);
		cp1 = buf1;
		count1 = segLen1 = d1Len>OFB_SIZE? OFB_SIZE : d1Len;

		/* RNS Get next d2 buffer */
		readOverflow(entry, pos2, &pos2, buf2,
			d2Len>OFB_SIZE? OFB_SIZE : d2Len);
		cp2 = buf2;
		count2 = segLen2 = d2Len>OFB_SIZE? OFB_SIZE : d2Len;
	}

	/* RNS
	 * At least one has reached the end, we use the same check as
	 * during short circuit at beginning.
	 */
	if (d1Len == 0 && d2Len == 0)
		return(0);
	if (d1Len == 0)
		return(-1);
	if (d2Len == 0)
		return(1);
}


/* RNS
 * matchVarChar -- compare (op) a TEXT (entry, data, length) to
 * a nul-terminated string (cp).
 */
int matchVarChar(entry, data, cp, length, op)
	cache_t	*entry;
	u_char	*data;
	char	*cp;
	int	length,
		op;
{
	u_char	*cp1, 	*cp2,
		buf[OFB_SIZE];
	int	count,
		dLen,
		segLen,
		cmp,
		cpLen,
		result;
	u_int	pos;

	cmp = 0; /* current value of match, initially equal */
	bcopy(data,&dLen, sizeof(int)); /* get length of TEXT data */
	cpLen = strlen(cp); /* get length of string */

	if (dLen == 0 || cpLen == 0)
	{
		/* if either or both are zero length,
		 * set cmp so that inequalities work.
		 * Longer string is greater.
		 */
		cmp = dLen - cpLen;
	}
	else
	{
		/* examine individual characters */

		bcopy(data + sizeof(int) ,&pos, sizeof(u_int));

		cp1 = data + sizeof(int) + sizeof(u_int);
		cp2 = (u_char *)cp;
		segLen = count = dLen > length ? length : dLen;
		while(dLen && cpLen)
		{
			while(count && cpLen)
			{
				if ((cmp = *cp1 - *cp2) != 0)
					break;
				count--;
				cp1++;
				cpLen--;
				cp2++;
			}

			/* RNS
			 * If inner loop decided the value,
			 * break this loop keeping value.
			 */
			if (cmp != 0)
				break;

			/* RNS
			 * If TEXT is longer than cp,
			 * artificially set cmp and break
			 */
			if (count)
			{
				cmp = 1;
				break;
			}

			/* RNS
			 * At this point count is 0.
			 * If both TEXT and string have more, then
			 *   go on
			 * else
			 *   get out of loop (and compare the lengths).
			 */
			dLen -= segLen;
			if (dLen == 0 || cpLen == 0)
				break;

			/* RNS Get next buffer of TEXT */
			readOverflow(entry, pos, &pos, buf,
				dLen>OFB_SIZE? OFB_SIZE : dLen);
			cp1 = buf;
			count = segLen = dLen>OFB_SIZE? OFB_SIZE : dLen;
		}

		/* RNS
		 * Either they aren't equal or at least one has reached
		 * the end:
		 * If they are still equal,
		 * then the lengths must be used as the final arbiter.
		 */
		if (cmp == 0)
			cmp = dLen - cpLen;
	}

	switch(op)
	{
		case EQ_OP:
			result = (cmp == 0);
			break;
			
		case NE_OP:
			result = (cmp != 0);
			break;

		case LT_OP:
			result = (cmp < 0);
			break;

		case GT_OP:
			result = (cmp > 0);
			break;

		case LE_OP:
			result = (cmp <= 0);
			break;

		case GE_OP:
			result = (cmp >= 0);
			break;

		default:
			strcpy(errMsg, TEXT_REGEX_ERROR);
			return(-1);
	}
	return(result);
}




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

/****************************************************************************
** 	_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,
		iv,
		res;
	double	fv;
	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()");
	/* bzero(&tmpVal, sizeof(tmpVal)); */
	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);
					msqlDebug(MOD_ERR,
					   "Unqualified field in comparison\n");
					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;
				}

				(void)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)
			{
				sprintf(errMsg,BAD_FIELD_ERROR,
					value->val.identVal->seg1,
					value->val.identVal->seg2);
				msqlDebug(MOD_ERR,"Unknown field '%s.%s'\n",
					value->val.identVal->seg1,
					value->val.identVal->seg2);
				msqlTrace(TRACE_OUT,"matchRow()");
				(void)free(value->val.charVal);
				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)
			{
                        	sprintf(errMsg, SYSVAR_ERROR, 
					curCond->value->val.identVal->seg2);
                        	msqlDebug(MOD_ERR,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: /* RNS: Added for proper NULL handling */
		    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
		*/
		/* RNS
		 * First, do NULL handling. Because mSQL 2.x does not yet have
		 * IS NULL and IS NOT NULL, NULL handling is a bit awkward.
		 */
		lhsIsNull = (*(data + *offset) == 0);
		if ((rhsType == NULL_TYPE) && ISA_NULL_OP(curCond->op))
		{
			/* RNS
			 * An explicit comparison to NULL.
			 */
			byteMatch( *(data + *offset), 0, curCond->op, tmp );
		}
		else if (rhsType == NULL_TYPE)
		{
			/* RNS
			 * SQL does not allow other operators for NULL.
			 */
			strcpy(errMsg, "Illegal operator applied to NULL.\n" );
			msqlDebug(MOD_ERR, 
				"Illegal operator applied to NULL.\n" );
			msqlTrace(TRACE_OUT,"matchRow()");
			return(-1);
		}
		else if (value->nullVal || lhsIsNull)
		{
			/* RNS
			 * SQL says that any compare of implicit NULL values
			 * should always fail (return false for now).
			 */
			tmp = 0;
		}
		else switch(curCond->type)
		{
			/* RNS
			 * We use Bambi's original switch, but with
			 * the incomplete NULL handling removed.
			 */
			case INT_TYPE:
			case MONEY_TYPE:
				if (curCond->sysvar)
				{
					tmp = compareSysVar(cacheEntry,row,
						curCond, value);
					break;
				}
#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);
					msqlDebug(MOD_ERR,
					   "Can't use LIKE operators on int values\n");
					msqlTrace(TRACE_OUT,"matchRow()");
					return(-1);
				}
				intMatch(iv,value->val.intVal,curCond->op,tmp);
				break;


			case UINT_TYPE:
			case DATE_TYPE:
			case TIME_TYPE:
				if (curCond->sysvar)
				{
					tmp = compareSysVar(cacheEntry,row,
						curCond, value);
					break;
				}
#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);
					msqlDebug(MOD_ERR,
					   "Can't use LIKE operators on uint, date, or time values\n");
					msqlTrace(TRACE_OUT,"matchRow()");
					return(-1);
				}
				uintMatch(iv,value->val.intVal,curCond->op,tmp);
				break;

			case CHAR_TYPE:
				if (curCond->sysvar)
				{
					tmp = compareSysVar(cacheEntry,row,
						curCond, value);
					break;
				}
				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(-1);
				}
				break;

			case TEXT_TYPE:
				cp = (char *)data + *offset +1;
				if (ISA_LIKE_OP(curCond->op))
				{
					strcpy(errMsg, TEXT_REGEX_ERROR);
					msqlDebug(MOD_ERR,
					  "Can't use LIKE operators on TEXT values\n");
					msqlTrace(TRACE_OUT,"matchRow()");
					return(-1);
				}
				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(-1);
				}
				break;

			case REAL_TYPE:
				if (curCond->sysvar)
				{
					tmp = compareSysVar(cacheEntry,row,
						curCond, value);
					break;
				}
				bcopy8((data + *offset + 2),&fv);
				if (ISA_LIKE_OP(curCond->op))
				{
					strcpy(errMsg, REAL_LIKE_ERROR);
					msqlDebug(MOD_ERR,
					  "Can't use LIKE operators on real values\n");
					msqlTrace(TRACE_OUT,"matchRow()");
					return(-1);
				}
				realMatch(fv,value->val.realVal,
					curCond->op, tmp);
				break;
		}
	

		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)
	{
		cp1 = data1+offset+1;
		cp2 = data2+offset+1;
		switch (curField->type)
		{
			case CHAR_TYPE:
			case REAL_TYPE:
			case INT_TYPE:
			case UINT_TYPE:
			case DATE_TYPE:
				res = bcmp(cp1, cp2, curField->dataLength);
				break;

			case TEXT_TYPE:
				res = compareVarChar(entry,cp1,cp2,
					curField->dataLength) == 1;
				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);
}






void printMoney(buf,val)
	char	*buf;
	int	val;
{
	double	tmp;

	tmp = (double)val / 100;
	sprintf(buf,"%.2f",tmp);
}


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