/* INDEXLOOK - handle index traversal for a retrieval */

#include <stdio.h>
#include <ctype.h>
#include "tdhkit.h"
#include "shsql.h"

#define MAXTERMS 15

/* #define READSEQUENTIAL 0
 * #define READISAM 1
 * #define READISAM_D 2
 * #define LATE_ISAM_CANCEL 10
 */

#define ALPHA 0
#define NUM 1
#define EQ 0
#define LIKE 1
#define LT 5
#define LE 6
#define GE 7
#define GT 8
#define IN 10
#define INRANGE 15

int GL_getcword(), GL_wildcmp();

static FILE *x1fp, *x2fp, *x3fp;	/* the index files */
static char key[DATAMAXLEN+1] = "";	/* key value */
static int keylen = 0;			/* length of key value */
static long curofs = 0L;		/* current phys loc in primary data file */
static int toplevel = 0;		/* top level index that exists for this field */
static long ovfl_ofs = 0L;		/* phys loc of beginning of overflow area (new write area) in data file */
static int sorttype = ALPHA;		/* ALPHA or NUM */
static int dflag = 0;			/* if 1 it indicates that level 2 index points directly to data file */
static int state = 0; 			/* current state of operations:  0 = initial walk   1 = getting  2 = done  */
static int relop;			/* integer symbol for the relational operator */
static char inlist[DATAMAXLEN+1] = "";	/* list, when working with repeating ops */
static int inlistlen = 0;
static int nrep = -1;			/* number of repeat cycles (used in working with repeating ops)*/
static double lowval, highval;	
static int debug = 0;			
static char prevb64ofs[20] = "0";
static int containsflag = 0;		/* 1 if doing 'contains' */
static int n_orterms = 0;		/* number of 'or' terms in condex (0 if 'or' wasn't used) */
static int orloc[MAXTERMS] = { 0 };	/* location of beginning of each 'or' term */
static int n_or_cyc = -1;		/* number of 'or' cycles */
static char fieldname[80] = "";
static int oflag;			/* 1 if tablename is ordinary file */
static char ixtypestr[255];		/* can contain list of table names */

static int compare();

static int setup_cond();

double atof();
long atol();


/* Presence of one or more OR requires iteration of the entire index setup/lookup mechanism.
 *      Within this, certain ops such as in, inlike, outrange, and 'contains' requires iteration
 *      of the lookup mechanism only.
 */


/* Index file header - The first line in every index file contains these fields:
 * 
 * ! index-type: T sort-type: S  top-index-is: TL  datafile-new-area-begins-at: B
 * 
 * T can be one of: standard  direct  word  combinedword,f2,..,fN  combined,f2,..,fN
 * S can be one of: alpha, num
 * TL is 3 or 2
 * B is a byte location
 */


/* ====================================================================
  USE_INDEX - determine if we can use an index.  Check the first 3 tokens 
  of condex.  Token 1 is fieldname.  If token 2 is "=" and token 3 is a 
  constant (@_QSO*), open the index file (tablename.fieldname.2) and indicate
  that we can use indexing by setting access_mode to READISAM.
  If we can't use indexing, access_mode will be set to READSEQUENTIAL.

*/

int
SHSQL_use_index( condex, tablename, access_mode, distinct )
char *condex, *tablename;
int *access_mode;	/* the access mode that sqlsel() should use */
int *distinct;		/* may report back that 'distinct' processing should be used.. */
{
int ix;
char tok[80];
int stat;


/* initialize for total index retrieval.. */
n_orterms = 0;
n_or_cyc = -1;
*access_mode = READSEQUENTIAL; 
if( SHSQL_debug == 2 ) debug = 1;
else debug = 0;
if( GL_member( tablename[0], "./" )) oflag = 1;
else oflag = 0;

toplevel = 0; 
dflag = 0;    
	strcpy( fieldname, "" ); 
	sorttype = ALPHA;	 

	if( debug ) fprintf( stderr, "[Checking to see if indexes can be used..]\n" );

	/* scan ahead and make note of OR terms.. */
	ix = 0;
	while( 1 ) {
		/* skip 3 tokens.. */
		GL_getok( condex, &ix ); GL_getok( condex, &ix ); GL_getok( condex, &ix );

		strcpy( tok, GL_getok( condex, &ix ));
		if( tok[0] == '\0' ) break;
		if( stricmp( tok, "or" )==0 ) { 
			n_orterms++;
			orloc[ n_orterms ] = ix;
			n_or_cyc = 0;
			*distinct = 1; /* inform sqlsel that distinct processing must be done on result */
			}
		}

	if( debug && n_orterms > 0 ) fprintf( stderr, "[OR terms detected (%d of 'em)..]\n", n_orterms+1 );

	if( SHSQL_suppressix ) { 
		SHSQL_mustix = 0; 
		if( debug ) fprintf( stderr, "[NOX: indexing turned off by user]\n" ); 
		return( 0 ); 
		}

	/* do setup work (once per 'or' term).. */
	stat = setup_cond( &condex[ orloc[ n_or_cyc ] ], tablename, distinct );
	if( stat != 0 ) return( stat );

	*access_mode = READISAM;
	return( 0 );
	}



/* ======================================================== */
/* SETUP_COND - set up for one 'or term' iteration..  

   examine conditional, open index files (if not already open) and examine index headers
*/

static int
setup_cond( cond, tablename, distinct )
char *cond;
char *tablename;
int *distinct;
{
int i, j, ix;
char buf[512];
char str[40];
char tok[80];
char op[80];			/* the actual relational operator */
char constant[80];
char filename[MAXPATH];
char prevfield[80];
int already_open;
char fakename[MAXPATH];

/* initialize for current 'or' iteration.. */

key[0] = '\0';
keylen = 0;
curofs = 0L;
state = 0;
nrep = -1;
strcpy( inlist, "" );
lowval = 0.0; highval = 0.0;
strcpy( prevb64ofs, "0" );
containsflag = 0;

if( debug && n_orterms > 0 ) fprintf( stderr, "\n[Evalutating OR term: %s (1st 3 tokens only)]\n", cond );


/* remember previous fieldname (if any) */
strcpy( prevfield, fieldname ); 


/* parse the where clause.. */
ix = 0;
strcpy( fieldname, GL_getok( cond, &ix ));
strcpy( op, GL_getok( cond, &ix ));
strcpy( constant, GL_getok( cond, &ix ));
if( constant[0] == '\0' ) { if( debug ) fprintf( stderr, "[NOX: no where clause]\n" ); return( 1 ); }


/* be sure we are comparing with a constant; get constant value */
if( strncmp( constant, "@_QS", 4 ) == 0 ) TDH_getvar( &constant[1], key );
else if( constant[0] != '@' ) strcpy( key, constant );
else	{ if( debug ) fprintf( stderr, "[NOX: not a constant]\n" ); return( 1 ); }

keylen = strlen( key );

/* convert whitespace characters in key to underscore.. 8/2/02 */
for( i = 0; i < keylen; i++ ) {
	if( isspace( (int) key[i] ) && isspace( (int) SHSQL_delim ) ) key[i] = '_';	/* datadelim */
	else key[i] = tolower( key[i] );
	}


/* handle in and inlike.. rewrite op and we will be processing individual members of list.. */
if( GL_smemberi( op, "in inlike" )) {
	if( stricmp( op, "in" )==0 ) strcpy( op, "=" );
	else if( stricmp( op, "inlike" )==0 ) strcpy( op, "like" );
	nrep = 0;
	strcpy( inlist, key );
	inlistlen = keylen;
	}

/* handle outrange.. rewrite op and we will be processing individual members of list.. */
if( GL_smemberi( op, "outrange" ) ) {
	strcpy( op, "<" );
	nrep = 0;
	strcpy( inlist, key );
	inlistlen = keylen;
	}

/* handle 'contains'.. rewrite op and we will be processing indiv. members of list.. */
if( stricmp( op, "contains" )==0 ) {
	containsflag = 1; /* standard */
	nrep = 0;
	strcpy( inlist, key );
	inlistlen = keylen;
	}


/* if we are repeating over members of a list op... */

/* 'contains'.. get first target word.. */
if( nrep >= 0 && containsflag ) {
	i = 0;
	GL_getcword( key, inlist, &i );
	if( stricmp( key, "null" )==0 ) strcpy( key, TDH_dbnull );  
	keylen = strlen( key );
	key[ keylen++ ] = '*';
	key[ keylen ] = '\0';
	strcpy( op, "like" );
	}

/* commalist operations: in, inlike, outrange.. get next first member.. */
else if( nrep >= 0 ) { 
	i = 0;
	while( 1 ) {	/* skip over empty list members - scg 1/27/03 */
		GL_getseg( key, inlist, &i, "," );  
		if( key[0] != '\0' ) break;
		if( i >= inlistlen ) { strcpy( key, TDH_dbnull ); break; }
		}
	if( stricmp( key, "null" )==0 ) strcpy( key, TDH_dbnull );  /* 'null' in list doesn't get converted up front */
	keylen = strlen( key );
	/* ..and make sure no members begin with wild card.. */
        j = 0; i = 0;
        while( 1 ) {
                j++;
                GL_getseg( tok, inlist, &i, "," );  /* parse list */
                /* if( tok[0] == '\0' ) break; */
		if( tok[0] == SHSQL_wildchar || tok[0] == '?' ) {
			if( debug ) fprintf( stderr, "[NOX: wc starts a list mem]\n"); return( 1 );
			}
		if( i >= inlistlen ) break; /* changed from above to allow empty list members - scg 1/27/03 */
		}
	}



/* examine relational operation */

if( GL_smember( op, "= == is" )) relop = EQ;
else if( stricmp( op, "like" )==0 ) {
	relop = LIKE;
	/* remove everything after first '*' */
	/* if ? encountered, change to * and chop off remainder */
	for( i = 0; key[i] != '\0'; i++ ) {
		if( key[i] == SHSQL_wildchar ) { i++; key[i] = '\0'; break; }
		if( key[i] == '?' ) { key[i] = SHSQL_wildchar; i++; key[i] = '\0'; break; }
		}
	if( key[0] == SHSQL_wildchar || key[0] == '?' ) { if( debug ) fprintf( stderr, "[NOX: wc at beginning]\n" ); return( 1 ); }
	keylen = i; /* was i+1 .. fixed loop break conditions above  - scg 10/23/03 */
	}
else if( strcmp( op, ">" )==0 ) relop = GT;
else if( strcmp( op, ">=" )==0 ) relop = GE;
else if( strcmp( op, "<" )==0 ) relop = LT;
else if( strcmp( op, "<=" )==0 ) relop = LE;
else if( stricmp( op, "inrange" )==0 ) {
	relop = INRANGE;
	i = 0;
        GL_getseg( tok, key, &i, "," );  
	lowval = atof( tok );
        GL_getseg( tok, key, &i, "," );  
	highval = atof( tok );
	}
else	{  if( debug ) fprintf( stderr, "[NOX: can't index with op %s]\n", op ); return( 1 ); }


/* determine if we need to close existing ix files, open new ones, or rewind existing ones.. */
already_open = 0;
if( prevfield[0] == '\0' ) ;
else if( strcmp( fieldname, prevfield ) == 0 ) { /* same field as before, just rewind.. and skip over headers.. */
	if( toplevel == 3 ) rewind( x3fp ); 
	rewind( x2fp ); 
	if( !dflag ) rewind( x1fp ); 
	strcpy( prevb64ofs, "0" );
	already_open = 1;
	}
else	{
	if( toplevel == 3 ) fclose( x3fp );
	fclose( x2fp );
	if( !dflag ) fclose( x1fp );

	/* only when we know that we're changing to a different field do we reinitialize these.. -scg 3/26/04 */
	dflag = 0; 
	toplevel = 0; 
	}

if( !already_open ) {

	strcpy( fakename, tablename );
	for( i = 0; fakename[i]; i++ ) if( fakename[i] == '/' ) fakename[i] = '!';
	
	/* open level 2 file.. */
	if( !oflag ) sprintf( filename, "%s/indexes/%s.%s.2", SHSQL_projdir, fakename, &fieldname[1] );
	else sprintf( filename, "%s.%s.2", tablename, &fieldname[1] );
	
	x2fp = fopen( filename, "r" );
	if( x2fp == NULL ) { if( debug ) fprintf( stderr, "[NOX: no index %s]\n", filename ); return( 1 ); }
	
	/* examine the .2 index file header.. */
	fgets( buf, 511, x2fp );
	if( buf[0] != '!' && buf[1] != BLANK ) { SHSQL_err( 170, "cannot find header in level 2 index file", "" ); return( 1 ); }
	
	/* ! index-type: T sort-type: S  top-index-is: TL  datafile-new-area-begins-at: B */
	sscanf( buf, "%*s %*s %s %*s %s %*s %d %*s %s", ixtypestr, tok, &toplevel, str );
	if( strcmp( tok, "num" )==0 ) sorttype = NUM;
	else sorttype = ALPHA;   /* scg 3/26/04 */

	rewind( x2fp ); /* always start at beginning again */
	
	/* inform sqlsel that distinct processing must be done on result */
	if( stricmp( ixtypestr, "word" )==0 ) *distinct = 1;
	else if( strnicmp( ixtypestr, "combined", 8 )==0 ) {
		*distinct = 1;  
		n_orterms = 0;  /* cancel additional 'or term' cycles */
		if( debug ) fprintf( stderr, "[Only first OR-term will be examined since a combined index type is in use]\n" );
		}


	if( stricmp( ixtypestr, "direct" )==0 ) { 
		ovfl_ofs = atol( str );
		dflag = 1;

		/* list op (such as in or inrange) with direct index triggers distinct.. 2/17/03 */
		if( inlist[0] != '\0' ) *distinct = 1;
		}
	else	{
		/* open level 1 file.. */
		if( !oflag ) sprintf( filename, "%s/indexes/%s.%s.1", SHSQL_projdir, fakename, &fieldname[1] );
		else sprintf( filename, "%s.%s.1", tablename, &fieldname[1] );
	
		x1fp = fopen( filename, "r" );
		if( x1fp == NULL ) { SHSQL_err( 171, "level 1 index not found", filename ); return( 1 ); }
		/* examine the .1 index file header.. */
		fgets( buf, 511, x1fp );
		if( buf[0] != '!' && buf[1] != BLANK ) { SHSQL_err( 172, "cannot find header in level 1 index file", "" ); return( 1 ); }
		sscanf( buf, "%*s %*s %*s %*s %*s %*s %*s %*s %s", str );
		ovfl_ofs = atol( str );
		rewind( x1fp ); /* always start at beginning again */
		}
	
	if( toplevel == 3 ) {	 /* note: this could be replicated for a 4th level */
		/* open level 3 file.. */
		if( !oflag ) sprintf( filename, "%s/indexes/%s.%s.3", SHSQL_projdir, fakename, &fieldname[1] );
		else sprintf( filename, "%s.%s.3", tablename, &fieldname[1] );
	
		x3fp = fopen( filename, "r" );
		if( x3fp == NULL ) { SHSQL_err( 173, "level 3 index file not found", filename ); return( 1 ); }
		}
	}

/* truncate key.. */
if( sorttype != NUM ) {
	key[ SHSQL_ixtrunclen ] = '\0';
	if( keylen > SHSQL_ixtrunclen ) keylen = SHSQL_ixtrunclen;
	}

if( debug ) {
	if( n_orterms ) fprintf( stderr, "[Yes, indexing can be used on this OR term..]\n" );
	else fprintf( stderr, "[Yes, indexing can be used..]\n" );
	if( nrep >= 0 ) fprintf( stderr, "  [We'll be iterating across a list (%s)]\n  [First list member.. key: ", inlist );
	else fprintf( stderr, "  [Using key: " );
	fprintf( stderr, "%s keylen=%d op=%d sorttype=%d  toplevel=%d  ixtype=%s  ovfl@%ld]\n", 
		key, keylen, relop, sorttype, toplevel, ixtypestr, ovfl_ofs ); 
	}

return( 0 ); /* indicates ready to go with lookup.. */
}



/* ======================================================== */
/* INDEXLOOK - access indexes, send back phys loc of next eligible data record,
	and possibly change sqlsel's accessing mode.
 */

int
SHSQL_indexlook( pos, access_mode, stoppos, condex, table, distinct )
long *pos;
int *access_mode;	/* may be updated by this routine */
long *stoppos;		/* place to stop, in case nothing found, for direct indexes only) */
char *condex;		/* condex from sqlselect, to allow finding the next 'or' term */
char *table;		/* table from sqlselect */
int *distinct;
{
int i, j, ix;
char buf[512], tok[DATAMAXLEN+1], b64ofs[20];
long ofs, atol();
int diff;
int stat;
long prevofs;


AGAIN:

/* if done, shouldn't be back here.. */
if( state == 2 ) { SHSQL_err( 174, "extra call to indexlook", "" ); return( 0 ); }

*pos = 0L;
*access_mode = READISAM;


if( state == 0 ) {

	if( toplevel == 3 ) {
		/* level 3 - find entry that points to level 2 before 1st occurance of key.. */
		if( strcmp( prevb64ofs, "0" )==0 ) fgets( buf, 511, x3fp ); /* skip over header */
		strcpy( prevb64ofs, "0" );
		while( fgets( buf, 511, x3fp ) != NULL ) {
			ix = 0;
			SHSQL_getfld( tok, buf, &ix );
			SHSQL_getfld( b64ofs, buf, &ix );
			diff = compare( tok, key, keylen );
			if( diff >= 0 ) break;
			strcpy( prevb64ofs, b64ofs );
			}
		stat = SHSQL_b64_to_l( prevb64ofs, &prevofs );
		if( stat != 0 ) { SHSQL_err( 175, "offset value too large", prevb64ofs ); goto AGAIN; }

		if( debug ) fprintf( stderr, "    [index3 says to jump into in index2 at physloc %ld (%s)]\n", prevofs, prevb64ofs ); 

		stat = fseek( x2fp, prevofs, SEEK_SET );
		if( stat ) { SHSQL_err( 176, "level 2 seek error", "" ); return( 0 ); }
		}

	
	/* level 2 - find entry that points to level 1 (or data) before 1st occurance of key.. */

	/* if( prevofs == 0L ) fgets( buf, 511, x2fp ); */ /* skip over header */
	if( strcmp( prevb64ofs, "0" )==0 ) fgets( buf, 511, x2fp ); /* skip over header */
	strcpy( prevb64ofs, "0" );

	while( fgets( buf, 511, x2fp ) != NULL ) {
		ix = 0;
		SHSQL_getfld( tok, buf, &ix );
		SHSQL_getfld( b64ofs, buf, &ix );
		/* ofs = atol( tok2 ); */
		diff = compare( tok, key, keylen );
		if( diff >= 0 ) break;
		strcpy( prevb64ofs, b64ofs );
		}
	stat = SHSQL_b64_to_l( prevb64ofs, &prevofs );
	if( stat != 0 ) { SHSQL_err( 177, "offset value too large", prevb64ofs ); goto AGAIN; }

	if( dflag ) {  
		*pos = prevofs;

		/* note stopping position.. this is sometimes necessary when nothing's found so we know
		   when to give up on scanning the data file.. */
		if( relop == GT || relop == GE || strcmp( prevb64ofs, b64ofs )==0 ) 
			*stoppos = ovfl_ofs; /* GT, GE, ix bottom */

		else	{
			int foundstop;
			/* find 1st entry that is past entry of interest.. */
			
			/* 1/13/03 - fixed this section (old code CUT below) */
			foundstop = 0;
			while( fgets( buf, 511, x2fp ) != NULL ) {
				ix = 0;
				SHSQL_getfld( tok, buf, &ix );
				SHSQL_getfld( b64ofs, buf, &ix );
				diff = compare( tok, key, keylen );
				if( diff > 0 ) { foundstop = 1; break; }
				}
			if( foundstop ) SHSQL_b64_to_l( b64ofs, stoppos );
			else *stoppos = ovfl_ofs;
			}


		if( debug ) fprintf( stderr, "    [index2 (direct) says to jump into data file at physloc %ld (%s); if nothing found stop @ %ld (%s?)]\n", 
			*pos, prevb64ofs, *stoppos, b64ofs ); 
		strcpy( prevb64ofs, b64ofs );
		state = 1;

		*access_mode = READISAM_D;
		return( 0 );
		}

	if( debug ) fprintf( stderr, "    [index2 says to jump into level 1 at physloc %ld (%s)]\n", prevofs, prevb64ofs );  

	stat = fseek( x1fp, prevofs, SEEK_SET ); /* get ready to scan level 1 */
	if( stat ) { SHSQL_err( 178, "level 1 seek error", "" ); return( 0 ); }
	}

if( !dflag ) {
	
	/* level 1 - find entries that match the key.. */
	if( strcmp( prevb64ofs, "0" )==0 ) fgets( buf, 511, x1fp ); /* skip over header */
	while( fgets( buf, 511, x1fp ) != NULL ) {
		ix = 0;
		SHSQL_getfld( tok, buf, &ix );
		SHSQL_getfld( b64ofs, buf, &ix );
		/* ofs = atol( tok2 ); */
		diff = compare( tok, key, keylen );
		if( diff == 0 ) {
			stat = SHSQL_b64_to_l( b64ofs, &ofs );
			if( stat != 0 ) { SHSQL_err( 179, "offset value too large", b64ofs ); goto AGAIN; }
			if( debug )fprintf( stderr, "    [index1 says to access data file at physloc %ld (%s)]\n", ofs, b64ofs );
			*pos = ofs;
			strcpy( prevb64ofs, b64ofs );
			state = 1;
			return( 0 );
			}
		else if( diff > 0 ) break;     /* passed it, nothing found .. */
		}
	}

/* if we reach here, we're done with index scan (maybe nothing was found)..  */


/* if we are repeating over members of a list op..  set up for next round and then cycle again.. */
if( nrep >= 0 ) {
	nrep++;


	/* skip thru inlist to current point.. */
        j = 0; i = 0;
        while( 1 ) {
		if( containsflag ) GL_getcword( key, inlist, &i );
                else GL_getseg( key, inlist, &i, "," );  /* parse list */
		if( key[0] == '\0' ) {
			if( i < inlistlen ) continue; /* skip over 0-length list members */
			else { nrep = -1; goto DONE; } /* added scg 3/29/04 */
			}
                j++;
		keylen = strlen( key );
                if( j == (nrep+1) ) break;
		if( i >= inlistlen ) { nrep = -1; goto DONE; } 
		}

	if( stricmp( key, "null" )==0 ) { 
		strcpy( key, TDH_dbnull ); /* 'null' in list doesn't get converted up front */
		keylen = 1; /* added scg 3/29/04 */
		}

	/* process for wild cards */
	if( containsflag ) { key[ keylen++ ] = '*'; key[ keylen ] = '\0'; } /* append star */
	else if( relop == LIKE ) {
		/* fixed bug 10/24/03 .. keylen one too many when no wildcards in key.. */
		for( i = 0; key[i] != '\0'; i++ ) {
       			if( key[i] == SHSQL_wildchar ) { key[i+1] = '\0'; keylen=i+1; break; }
              		if( key[i] == '?' ) { key[i] = SHSQL_wildchar; key[i+1] = '\0'; keylen=i+1; break; }
			}
		}

	/* flip relational ops for next cycle (inrange/outrange).. */
	else if( relop == GE ) relop = LE;
	else if( relop == LT ) relop = GT;

	/* convert ' ' to '_' in key?  not needed here, since IN lists can't contain embedded spaces */

	/* truncate key.. */
	if( sorttype != NUM ) {
		key[ SHSQL_ixtrunclen ] = '\0';
		if( keylen > SHSQL_ixtrunclen ) keylen = SHSQL_ixtrunclen;
		}

	if( debug ) fprintf( stderr, "  [Next list member.. key: %s  keylen=%d  op=%d  rep=%d]\n", 
		key, keylen, relop, nrep );

	/* get ready to cycle again */
	state = 0;
	if( toplevel == 3 ) rewind( x3fp ); 
	rewind( x2fp ); 
	if( !dflag ) rewind( x1fp ); 
	strcpy( prevb64ofs, "0" );
	
	goto AGAIN;
	}

DONE:


/* see if we need to interate again for another 'or' term.. */
if( n_orterms > 0 && n_or_cyc < n_orterms ) {
	n_or_cyc++;

	/* do setup work (once per 'or' term).. */
	stat = setup_cond( &condex[ orloc[ n_or_cyc ] ], table, distinct );
	if( stat ) return( LATE_ISAM_CANCEL ); /* send back message that entire retrieval must be begun again
							as a table scan, eg if 3rd 'or' term has a problem.. */
	/* prevofs = 0L; */
	strcpy( prevb64ofs, "0" );
	goto AGAIN;
	}
	
if( debug ) fprintf( stderr, "[Index walk done, let's jump to the new record area and see if we find anything there..]\n" );

/* finally, tell caller to go scan the overflow area.. and close down.. */
*pos = ovfl_ofs;
*access_mode = READSEQUENTIAL;
if( !dflag ) fclose( x1fp ); 
fclose( x2fp );
if( toplevel == 3 ) fclose( x3fp );
state = 2;
return( 0 );
}

/* =========================== */
/* GET_OVFL - get the location of the overflow area */
int
SHSQL_get_ovfl( loc )
long *loc;
{
*loc = ovfl_ofs;
return( 0 );
}

/* =========================== */
/* COMPARE - compare s1 and s2, and return comparison value of <0, 0, or >0 */
/* If alphanumeric, s1 and s2 must both be in lowercase */
static 
int compare( s1, s2, s2len )
char *s1, *s2;
int s2len;
{
int n1, n2, p;
double f, g;
int diff;


if( sorttype == NUM ) {
	/* Numeric sort - what to do with non-numerics.
	   sort(1) puts them at the top of the index, so this logic needs to behave accordingly.. */
	n1 = GL_goodnum( s1, &p ); n2 = GL_goodnum( s2, &p ); /* detect non-numerics */
	if( relop == INRANGE ) {       /* RHS is always a list (never considered numeric) */
		if( n1 == 0 ) return( -1 ); /* LHS non-numeric */
		}
	else if( n1 == 0 && n2 == 0 ) return( strcmp( s1, s2 ) ); /* both non-numeric - return strcmp result */ /* locale-collate */
	else if( relop == GT || relop == GE || relop == LT || relop == LE ) {  /* added 9/27/02 */
		if( n1 == 0 ) return( -1 ); /* LHS not num but RHS is */
		else if( n2 == 0 ) return( 1 );  /* LHS is num but RHS not */
		}
	else if( n1 == 0 ) return( 1 ); /* LHS not num but RHS is */
	else if( n2 == 0 ) return( -1 );  /* LHS is num but RHS not */

	f = atof( s1 ); g = atof( s2 );

	if( relop == EQ ) {
		if( f == g ) return( 0 );
		else if( f > g ) return( 1 );
		else return( -1 );
		}
	else if( relop == LT ) {
		if( f < g ) return( 0 );
		else return( 1 );
		}
	else if( relop == LE ) {
		if( f <= g ) return( 0 );
		else return( 1 );
		}
	else if( relop == GE ) {
		if( f < g ) return( -1 );
		else return( 0 );
		}
	else if( relop == GT ) {
		if( f <= g ) return( -1 );
		else return( 0 );
		}
	else if( relop == INRANGE ) {
		if( f < lowval ) return( -1 );
		else if( f > highval ) return( 1 );
		else return( 0 );
		}
	}

else if( relop == LIKE ) {
	diff =  GL_wildcmp( s1, s2, s2len, 0 );
	return( diff );
	}
	
else return( strcmp( s1, s2 ) );  /* both s1 and s2 are always lowercase */  /* locale-collate */
return( 0 );
}


/* ===================================== */
/* GETIXFIELDS - read in the tablename.0 file and return a list of indexed fields.. */
int
SHSQL_getixfields( tablename, fieldlist )
char *tablename, *fieldlist;
{
char buf[256];
char tok[80];
FILE *fp;
int i, ix, j;
char fakename[256];

strcpy( fieldlist, "" );
strcpy( fakename, tablename );
for( i = 0; fakename[i] != '\0'; i++ ) if( fakename[i] == '/' ) fakename[i] = '!';
sprintf( buf, "%s/indexes/%s.0", SHSQL_projdir, fakename );
fp = fopen( buf, "r" );
if( fp == NULL ) return( 0 );

j = 0;
while( fgets( buf, 255, fp ) != NULL ) {
	ix = 0;
	GL_getseg( tok, buf, &ix, "." );
	GL_getseg( tok, buf, &ix, "." );
	strcpy( &fieldlist[j], tok );
	j+= strlen( tok );
	strcpy( &fieldlist[j], " " );
	j++;
	}
fclose( fp );
return( 0 );
}
