/* SQLSEL.C - implement shsql SELECT
 * Copyright 1998-2002 Stephen C. Grubb 
 * This code is covered under the GNU General Public License (GPL);
 * see the file ./Copyright for details. 
 */

#include <ctype.h>
#include <unistd.h>  /* for usleep() */
#include "tdhkit.h"
#include "shsql.h"

/* #define READSEQUENTIAL 0
 * #define READISAM 1
 * #define READISAM_D 2
 * #define LATE_ISAM_CANCEL 10
 */
#define AVG 'v'
#define MIN 'i'
#define MAX 'a'
#define SUM 'u'
#define COUNT 'o'

int GL_make_unique_string(), GL_wildchar(), GL_member_nullmode();
int TDH_geterrmode(), TDH_condex_matchscore(), TDH_setfmdelim(), TDH_condex_nofunc();

static int compare( void *lhs, void *rhs );
static int discmp( ), reset_accums( ), copy_agg(), buildrow_agg();

static int Nsortflds;
static int Sortfld[ MAXSORTFIELDS ];
static char Sortmeth[ MAXSORTFIELDS ];
static char Sortdir[ MAXSORTFIELDS ];
static char Nullrep[20];
static int Nullreplen;
static struct selectparms *SP;
static char *Prevfields[ MAXITEMS ];
static char *Aggbuffld[ MAXITEMS ];
static int Naturalorder;
static long *Loclist;
static int *Lenlist;
static long *Locp;
static int *Lenp;
static int Nloclist = 0, Curloclist; 
static int Getloclist = 0; 	/* if 1, indicates that the purpose of the select is to get record locations only */
static int Maxrows = 0;
static int Free_ok = 0;
static int Selectforupdate = 0;

extern char * TDH_getvarp();
extern char * TDH_fieldn();
extern char TDH_configfile[];

void *malloc();

/* ===================== */
int
SHSQL_select( frag, nfrag, wherefrag, rows_p, maxrows, sp )
char *frag[];			/* sql command buffer */
int nfrag;			/* # fragments */
int wherefrag;			/* fragment holding where clause or -1 if none */
char ***rows_p; 		/* returned.  cell 0 contains field names.  cell 1, and every 3rd cell
					after (4, 7..) contains record */
					/* was *rows[] */
int maxrows;			/* pass as 0 to get default maxrows, or use it to set max # of rows */
struct selectparms *sp;	/* various parameters */
{
char buf[MAXRECORDLEN];
int buflen;
char reqname[MAXITEMS][NAMEMAXLEN+1];	/* request list (fieldnames or literals) */
char reqagg[MAXITEMS];
char alias[MAXITEMS][NAMEMAXLEN+1];  	/* select fieldname aliases */
char condex[SCRIPTLINELEN] = "";	/* conditional expression */
FILE *datafp = NULL;
int nreq;
int processmode;
char inpipe[ 255 ] = ""; 		
char jtmp[ MAXPATH ];
int key1begin, key2begin;
char data[MAXITEMS][DATAMAXLEN+1];
char condbuf[SCRIPTLINELEN];
int itempos[ MAXITEMS ];
char intotable[ MAXPATH ], intotablemode[5];
char *flds[ MAXITEMS ]; 	/* used by 'select into' */
long physloc;
long stoppos;

int k;	/* current frag */
int i, j, len, ix, stat, m;
char tok[DATAMAXLEN+1];
char table[ MAXPATH ];
int aftercomma;
int irow;
int ixhold;
int alcount;		/* count of actual # of rows malloc'ed */
char functag[40];
int groupbyfrag;
int oldk;
char **rows;
char errmsg[100];
int aggused, nonaggused;
int do_matchscore;  /* if 1, an additional column (called _matchscore) containing an integer measure of match quality 
				(used with 'contains') will be appended to each record */
int nparsedfields;
int errorcode;
int cattab, curcattab, ncattab;		/* if cattab == 1, multiple tables are on the FROM line.. */
char *cattabnames[MAXCATTAB];		/* table names, when doing cattab */




/* ---------- initialize select-related parameters.. */
k = 0;
alcount = 0;
nreq = 0;
strcpy( table, "" );
strcpy( condex, "" );
Nsortflds = 0;
processmode = READSEQUENTIAL;
for( i = 0; i < MAXITEMS; i++ ) {
	strcpy( alias[i], "" );
	reqagg[i] = '\0';
	}
strcpy( inpipe, "" );
strcpy( jtmp, "" );
sp->firstrow = 0;
sp->lastrow = 0;
sp->nrows = 0;
sp->outfp = NULL;
sp->distinct = 0;
strcpy( intotable, "" );
sp->irow = 0;
strcpy( functag, "" );
sp->ngbflds = 0;
sp->nagg = 0;
sp->aggbuf = NULL;
sp->intermed = 0;
Naturalorder = 0;
groupbyfrag = 0;
Nloclist = 0;
Curloclist = 0;
if( maxrows == 0 ) Maxrows = SHSQL_maxrows_select;
else Maxrows = maxrows;
Selectforupdate = 0;
aggused = nonaggused = 0;
do_matchscore = 0;
cattab = curcattab = 0;
irow = 1;



/* first, check last fragment of command for MAXROWS [=] N .. */
k = nfrag-1; 
ix = 0;
strcpy( tok, GL_getok( frag[k], &ix ) ); 
if( stricmp( tok, "maxrows" )==0 ) {
	strcpy( tok, GL_getok( frag[k], &ix ) );
	if( strcmp( tok, "=" )== 0 ) strcpy( tok, GL_getok( frag[k], &ix ) );
	Maxrows = atoi( tok );
	if( Maxrows == 0 ) Maxrows = SHSQL_maxrows_select;
	nfrag--;
	}
else if( stricmp( tok, "for" )==0 ) {
	strcpy( tok, GL_getok( frag[k], &ix ) );
	if( stricmp( tok, "update" )==0 ) {
		if( ! SHSQL_readonly ) Selectforupdate = 1;
		nfrag--;
		}
	}

/* allocate row storage.. */
Free_ok = 1;
rows = (char **) malloc( (Maxrows+1) * 3 * sizeof( char * ) );
*rows_p = rows;
if( Getloclist ) {
	Loclist = malloc( Maxrows * sizeof( long ) );
	Locp = Loclist;
	Lenlist = malloc( Maxrows * sizeof( int ) );
	Lenp = Lenlist;
	}



/* ---------- now start from the beginning and parse the select command.. */
k = 0;

/* SELECT.. */
ix = 0;
strcpy( tok, GL_getok( frag[k], &ix ) ); /* skip over 'select' */

/* distinct */
ixhold = ix;
strcpy( tok, GL_getok( frag[k], &ix ) ); /* skip over 'select' */
if( stricmp( tok, "distinct" )==0 ) sp->distinct = 1;
else ix = ixhold;

aftercomma = 1;
i = 0;
while( 1 ) {
	strcpy( tok, GL_getok( frag[k], &ix ) ); 
	if( tok[0] == '\0' ) break;
	if( strcmp( tok, "," )==0 ) {
		aftercomma = 1;
		continue;
		}
	if( aftercomma ) {
		len = strlen( tok );
		if( tok[ len -1 ] == ',' ) {
			aftercomma = 1;
			if( len == 1 ) continue; /* lone comma */
			tok[ len -1 ] = '\0';
			}
		else aftercomma = 0;
		if( tok[0] == '$' ) strcpy( functag, tok ); /* screen out functions */
		else 	{
			strcpy( reqname[i], tok );
			if( functag[0] != '\0' ) {  /* apply function (previous item) */
				reqagg[ i ] = tolower( functag[1] ); /* v, i., a, u, o */
				strcpy( functag, "" );
				aggused = 1; 
				}
			else nonaggused = 1;
			i++;
			}
		}
	else	{
		if( stricmp( tok, "as" )==0 || strcmp( tok, "=" )==0 ) {
			GL_getchunk( alias[i-1], frag[k], &ix, " ," ); 
			aftercomma = 1;
			}
		else return( SHSQL_err( 120, "sql select: comma expected in select item list", "" ) );
		continue;
		}
	}
nreq = i;

if( Selectforupdate && ( nreq != 1 || strcmp( reqname[0] , "*" )!= 0 ) ) 
		return( SHSQL_err( 121, "sql select for update: 'select * from..' expected", "" ) );

/* for( i = 0; i < nreq; i++ ) fprintf( stderr, "[req: %s (%s)]", reqname[i], alias[i] );
 * fprintf( stderr, "\n" );
 */

k++;
if( k >= nfrag ) return( SHSQL_err( 122, "sql select: incomplete command", "" ));

	
sscanf( frag[k], "%s", tok );

/* INTO */
if( stricmp( tok, "into" ) ==0 ) {	/* into table [append|table] */
	ix = 0;
	GL_getok( frag[k], &ix ); /* skip 'into' */
	strcpy( intotablemode, "w" );
	strcpy( intotable, GL_getok( frag[k], &ix ) ); 
	if( intotable[0] == '\0' ) return( SHSQL_err( 164, "sql select into: temp table name expected", "" ));
	strcpy( tok, GL_getok( frag[k], &ix ) ); 
	if( stricmp( tok, "append" )==0 || stricmp( tok, "table" )==0 ) strcpy( intotablemode, "a" );
	k++;
	}


/* FROM */
ix = 0;
strcpy( tok, GL_getok( frag[k], &ix ) );
if( stricmp( tok, "from" ) != 0 ) return( SHSQL_err( 123, "sql select: 'from' expected", "" ) );
strcpy( tok, GL_getok( frag[k], &ix ) );
strcpy( table, tok );
if( table[0] == '\0' ) return( SHSQL_err( 158, "sql select: table name expected", "" ));

/* if additional words on line, we will be doing a JOIN (by executing shsql_join) and then 
   collecting join result via inpipe.. */
ixhold = ix;
strcpy( tok, GL_getok( frag[k], &ix ) );

/* cat'ed tables */
if( stricmp( tok, "and" )==0 ) { 
	if( Selectforupdate ) return( SHSQL_err( 162, "sql select for update: simple select command expected", "" ));
	cattab = 1; 
	curcattab = 0; 
	/* scan the line and count tables.. */
	for( ncattab = 1; ; ncattab++ ) {
		if( ncattab >= (MAXCATTAB-1) ) return( SHSQL_err( 163, "sql select: too many table names", "" ));
		cattabnames[ncattab] = &frag[k][ix];
		strcpy( tok, GL_getok( frag[k], &ix ) );
		if( tok[0] == '\0' ) return( SHSQL_err( 160, "sql select: an additional table name is expected", "" ));
		strcpy( tok, GL_getok( frag[k], &ix ) );
		if( tok[0] == '\0' ) break;
		if( stricmp( tok, "and" )!= 0 ) return( SHSQL_err( 161, "sql select: extra content in 'from' clause", "" ));
		}
	ncattab++;
	/* fprintf( stderr, "%d tables\n%s\n", ncattab, table );
	 * for( i = 1; i < ncattab; i++ ) {
	 *	sscanf( cattabnames[i], "%s", tok );
	 *	fprintf( stderr, "%s\n", tok );
	 *	}
	 */
	}


/* JOIN */
else if( tok[0] != '\0' ) {
	FILE *fp;
	char errmsgmode[20];

	if( Selectforupdate ) return( SHSQL_err( 124, "sql select for update: simple select command expected", "" ));
	/* put sql in a tmp file.. */
	GL_make_unique_string( tok, 0 );
	sprintf( jtmp, "%s/sj%s", TDH_tmpdir, tok );
	fp = fopen( jtmp, "w" );
	if( fp == NULL ) return( SHSQL_err( 125, "sql select: cannot open temp file for join", jtmp ));

	/* write 'from' clause */
	fprintf( fp, "%s %s\n", table, &frag[k][ixhold+1] );

	/* write 'where' clause */
	if( wherefrag == -1 ) fprintf( fp, "none\n" );
	else 	{
		/* convert QS to literal then write whereclause out.. */
		TDH_valuesubst_settings( "showwithquotes", 1 );
		TDH_value_subst( buf, frag[wherefrag], data, "", NORMAL, 0 ); /* This is only to convert variables; 
										 4th arg ("") prevents fieldmap() from
										 trying to read an fdf file. */
		TDH_valuesubst_settings( "showwithquotes", 0 );
		fprintf( fp, "%s\n", buf );
		}

	/* write temp table prefix */
	TDH_geterrmode( errmsgmode );
	fprintf( fp, "ttp: %s maxrows: %d debug: %d errmsgmode: %s\n", SHSQL_tmptblpfx, Maxrows, SHSQL_debug, errmsgmode );

	fclose( fp ); fp = NULL;

	if( SHSQL_bin[0] != '\0' ) sprintf( inpipe, "%s/shsql_join -comfile %s -config %s", SHSQL_bin, jtmp, TDH_configfile );
	else sprintf( inpipe, "shsql_join -comfile %s -config %s", jtmp, TDH_configfile );
	if( SHSQL_debug ) fprintf( stderr, "shsql pid=%d: invoking: %s\n", (int)getpid(), inpipe ); 
		
	strcpy( table, "" );
	}


/* old location of record lock and read lock code sections.. */


/* GROUP BY */
k++;
if( k >= nfrag ) goto ACCESS;  /* nothing more.. skip down.. */
ix = 0;
strcpy( tok, GL_getok( frag[k], &ix ) );
if( stricmp( tok, "group" )==0 ) {
	if( sp->distinct ) return( SHSQL_err( 126, "sql select : use of both 'distinct' and 'group by' is not supported", "" ));
	groupbyfrag = k;
	/* Nsortflds++;
	groupbystate = 2;
	*/
	k++;
	if( k >= nfrag ) goto ACCESS;  /* nothing more; skip down.. */
	ix = 0;
	strcpy( tok, GL_getok( frag[k], &ix ) );
	}


/* WHERE */
if( stricmp( tok, "where" )==0 ) {
	while( isspace( (int) frag[k][ix] ) ) ix++;

	/* see if a _matchscore column must be set up.. */
	TDH_condex( &frag[k][ix], 0 ); /* send the expression and see if condex() finds a 'contains'.. */
	if( TDH_condex_matchscore() > -1 ) {  /* -1 indicates matchscore isn't applicable in this where clause */
		do_matchscore = 1;
		TDH_setvar( "_matchscore", "@_matchscore" ); /* condex looks for "@_matchscore" token */
		/* strcpy( reqname[ nreq++ ], "_matchscore" ); */
		}

	SHSQL_atvar( condex, &frag[k][ix] );
	k++;
	}


/* ORDER BY, LIMIT is handled below.. */

if( aggused && nonaggused && !groupbyfrag ) 
	return( SHSQL_err( 127, "sql select: aggregate functions such as count(*) cannot be mixed with fieldnames or constants unless 'group by' is used", "" ));


/* ------- open datafiles, map requested fields, etc.. -------- */
ACCESS:


/* if doing select .. for update, write-lock table file now..  */
if( Selectforupdate ) {
	stat = SHSQL_sfu_init( table );
	if( stat != 0 ) return( stat );
	stat = SHSQL_lock( table ); /* lock the table file so we can update record lock fields later.. */
	if( stat != 0 ) return( SHSQL_err( SHSQL_TABLELOCKED, "reservation refused.. try again in a few minutes", table ));
	}

/* check for read lock.. if found wait indefinitely.. */ 
if( inpipe[0] == '\0' && ! GL_member( table[0], "$./" ) ) 
	while( SHSQL_readlock( table, 2 ) != 0 ) {
		usleep( 900000 );
		SHSQL_err( 159, "waiting for read lock to clear", table );
		}

/* open the data file.. */
if( table[0] != '\0' ) {
	if( table[0] == '/' || table[0] == '.' ) strcpy( buf, table );
	else if( table[0] == '$' ) sprintf( buf, "%s/%s.%s", SHSQL_tmptabledir, SHSQL_tmptblpfx, &table[1] );
	else sprintf( buf, "%s/data/%s", SHSQL_projdir, table );
	datafp = fopen( buf, "r" );
	if( datafp == NULL ) return( SHSQL_err( 128, "sql select: cannot open table file", buf ) );

	/* check 1st line of file for %-%command%-% magic symbol 
	   (it will be there if this is a temp table associated with a PIPEDEF) (embedded comments not possible) */
	if( table[0] == '$' || table[0] == '/' || table[0] == '.' ) {
		if( fgets( buf, 512, datafp ) != NULL ) {
			ix = 0;
			strcpy( tok, GL_getok( buf, &ix ) );
			if( strcmp( tok, "%-%command%-%" )==0 ) sprintf( inpipe, "(cd %s/data; %s)", SHSQL_projdir, &buf[ix+1] );
			else rewind( datafp );
			}
		}
	}


/* open input pipe for join results.. */
if( inpipe[0] != '\0' ) {   
	datafp = popen( inpipe, "r" );
	if( datafp == NULL ) return( SHSQL_err( 129, "sql select: cannot open input stream", inpipe ) );
	}


/* get fieldname header */
TRYHEADER:
if( fgets( buf, MAXRECORDLEN-1, datafp )== NULL ) {
	if( jtmp[0] != '\0' ) return( SHSQL_err( 130, "sql select: join failed", "" ) );
	else return( SHSQL_err( 131, "sql select: mangled join temp file", "" ) );
	}
if( strncmp( buf, "//", 2 )==0 || isdelim( buf[0] ) || buf[0] == '\0' ) goto TRYHEADER;

/* trap cgi-formatted error messages that might be emitted by shsql_join.. echo them all to stdout then return.. */
if( strncmp( buf, "<br>", 4 )==0 ) {
	printf( "%s", buf ); /* error message */
	while( fgets( buf, MAXRECORDLEN-1, datafp ) != NULL ) {
		if( strncmp( buf, "<br>", 4 )==0 ) printf( "%s", buf ); /* error message */
		else return( 27 );
		}
	}

if( SHSQL_debug ) fprintf( stderr, "shsql pid=%d: read field name header: %s", (int) getpid(), buf ); 

TDH_altfmap( 1 );
TDH_setfmdelim( SHSQL_delim );
TDH_loadfieldmap( "shsql_header", buf );		/* tagvalue */
SHSQL_lfmcurtable( "" );
TDH_setfmdelim( BLANK );
sp->nfdf = fieldmap( "" ); /* macro */
TDH_altfmap( 0 );

if( cattab && curcattab > 0 ) goto RETREIVE;

/* open file for SELECT INTO.. */
if( intotable[0] != '\0' ) {
	if( intotable[0] == '/' || intotable[0] == '.' ) {
		/* ordinary file */
		if( SHSQL_readonly ) return( SHSQL_err( 20, "this is a read-only database; select into ordinary file prohibited", "" ));
		sp->outfp = fopen( intotable, intotablemode ); /* ordinary file */
		if( sp->outfp == NULL ) return( SHSQL_err( 132, "sql select into: cannot open destination file", intotable ) );
		}
	else if( intotable[0] == '$' ) {
		/* temp table */
		sprintf( buf, "%s/%s.%s", SHSQL_tmptabledir, SHSQL_tmptblpfx, &intotable[1] );
		sp->outfp = fopen( buf, intotablemode );
		if( sp->outfp == NULL ) return( SHSQL_err( 133, "sql select into: cannot open destination temp table", buf ) );
		}
	else	{
		/* database table */
		FILE *testfp;
		if( SHSQL_readonly ) return( SHSQL_err( 20, "this is a readonly database; select into regular table prohibited", "" ));
		if( strcmp( intotablemode, "a" )!= 0 ) 
			return( SHSQL_err( 134, "sql select into: regular table destination requires 'append'", intotable ));
		/* check and see if file exists.. */
		sprintf( buf, "%s/data/%s", SHSQL_projdir, intotable );
		testfp = fopen( buf, "r" );
		if( testfp == NULL ) {
			strcpy( intotablemode, "w" );
			SHSQL_err( 155, "warning: destination table doesn't exist.. creating it", intotable );
			}
		else fclose( testfp );
		stat = SHSQL_lock( intotable );
		if( stat != 0 ) return( SHSQL_err( 134, "sql select into: regular table locked and unavailable", intotable ));
		sp->outfp = fopen( buf, intotablemode );
		if( sp->outfp == NULL ) return( SHSQL_err( 135, "sql select into: cannot open destination table", buf ) );
		}
	}
else sp->outfp = NULL;

/* if select count(*) , map the * to a field name and set a special flag.. */
for( i = 0; i < nreq; i++ ) {
	if( reqagg[i] == COUNT && strcmp( reqname[i], "*" )==0 ) {
		TDH_altfmap( 1 );
		strcpy( reqname[i], TDH_fieldn( 0 ) );
		TDH_altfmap( 0 );
		reqagg[i] = '*';
		}
	}



/* expand * specs and set up for extracting fields during data scan later.. */
buflen = 0;


for( i = 0, j = 0; i < nreq; i++ ) {
	if( strncmp( reqname[i], "@_QS", 4 )==0 ) {
		itempos[j] = buflen;
		strcpy( &buf[buflen], &reqname[i][1] );
		buflen += strlen( reqname[i] ) +1;
		sp->fldpos[j] = -1;
		j++;
		}
	else 	{
		if( GL_member( '*', reqname[i] )) {
			TDH_altfmap( 1 );
			/* go through all fields and retain matches.. */
			for( m = 0; m < sp->nfdf; m++ ) {
				if( GL_slmember( TDH_fieldn( m ), reqname[i] )) {
					itempos[j] = buflen;
					strcpy( &buf[buflen], TDH_fieldn( m ) );
					buflen += strlen( TDH_fieldn( m ) ) +1;
					sp->fldpos[j] = m;
					j++;
					}
				}
			TDH_altfmap( 0 );
			}
		else	{
			TDH_altfmap( 1 );
			if( do_matchscore && strcmp( reqname[i], "_matchscore" )==0 ) 
				m = fieldmap( "" ); /* matchscore will be at end of record */
			else m = fieldmap( reqname[i] );
			TDH_altfmap( 0 );
			if( m < 0 ) return( SHSQL_err( 137, "sql select: unrecognized field requested", reqname[i] ) );
			sp->fldpos[j] = m;

			sp->aggflag[j] = reqagg[i];
			if( sp->aggflag[j] != '\0' ) (sp->nagg)++;

			itempos[j] = buflen;

			/* if an aggregate result, append a func tag */
			strcpy( functag, "" );
			if( reqagg[i] == COUNT ) strcpy( functag, "__count" );
			else if( reqagg[i] == SUM ) strcpy( functag, "__sum" );
			else if( reqagg[i] == AVG ) strcpy( functag, "__avg" );
			else if( reqagg[i] == MIN ) strcpy( functag, "__min" );
			else if( reqagg[i] == MAX ) strcpy( functag, "__max" );

			if( alias[i][0] != '\0' ) strcpy( &buf[buflen], alias[i] ); /* aliases should be left as is - scg 3/30/04 */
			/* was:  if( alias[i][0] != '\0' ) sprintf( &buf[buflen], "%s%s", alias[i], functag ); */

			else if( reqagg[i] == '*' ) strcpy( &buf[ buflen ], "__rowcount" );
			else sprintf( &buf[buflen], "%s%s", reqname[i], functag );
			buflen += strlen( &buf[buflen] ) +1;

			j++;
			}
		}
	}
sp->nitems = j;


/* rows[0] will contain field names */
rows[ 0 ] = (char *) malloc( buflen + 1 );
for( i = 0; i < buflen; i++ ) rows[0][i] = buf[i]; 

/* build itemlist (array of pointers to item names) */
for( i = 0; i < sp->nitems; i++ ) {
	sp->itemlist[i] = &rows[0][itempos[i]];
	}


/* sanity check that # of fields matches */
/* if( intotable[0] != '\0' && !GL_member( intotable[0], "./$" ) ) {
 *	stat = SHSQL_loadfieldmap( intotable );
 *	if( stat != 0 ) return( stat );
 *	stat = fieldmap( "" );
 *	if( stat != sp->nitems ) {
 *		fclose( sp->outfp ); sp->outfp = NULL;
 * 		return( SHSQL_err( 27, "sql select into: result incompatible with existing table", intotable ));
 *	 	}
 *	} 
 *** omitted, would require a 3rd fieldmap (?)
 */

/* write outfp header lines.. */
if( sp->outfp != NULL && strcmp( intotablemode, "a" )!= 0  ) {
	int dotpos;
	for( i = 0; i < sp->nitems; i++ ) {

		/* strip off leading table prefixes.. */
		dotpos = GL_member( '.', sp->itemlist[i] );
		if( sp->aggflag[i] == '*' ) fprintf( sp->outfp, "__rowcount%c", SHSQL_delim ); 	/* datadelim */
		else if( dotpos ) fprintf( sp->outfp, "%s%c", &(sp->itemlist[i][dotpos]), SHSQL_delim ); 	/* datadelim */
		else if( strncmp( sp->itemlist[i], "_QS", 3 )==0 ) {
			/* see if quoted string has an alias.. if so use that.. added scg 1/30/02 */
			int jal, alfound;
			for( jal = 0, alfound = 0; jal < nreq; jal++ ) {
				if( strcmp( sp->itemlist[i], &reqname[jal][1] )==0 ) {
					if( alias[jal][0] != '\0' ) {
						fprintf( sp->outfp, "%s%c", alias[jal], SHSQL_delim ); 	/* datadelim */
						alfound = 1;
						}
					}
				}
			if( !alfound ) fprintf( sp->outfp, "%s%c", sp->itemlist[i], SHSQL_delim );	/* datadelim */
			}
		else fprintf( sp->outfp, "%s%c", sp->itemlist[i], SHSQL_delim ); 			/* datadelim */
		}
	fprintf( sp->outfp, "\n" );	
	fflush( sp->outfp );
	}

/* ORDER BY and GROUP BY.. */
if( !groupbyfrag ) {
	if( k >= nfrag ) goto RETREIVE; /* nothing more.. skip down.. */
	ix = 0;
	strcpy( tok, GL_getok( frag[k], &ix ) );
	}
if( stricmp( tok, "order" )==0 || groupbyfrag ) {
	int isfld;
	if( stricmp( tok, "order" )==0 && groupbyfrag ) 
		return( SHSQL_err( 138, "sql select: use of both 'order by' and 'group by' is not supported", "" ) );
	if( groupbyfrag ) { /* jump back to group by fragment.. will be restored after */
		oldk = k;
		k = groupbyfrag;
		ix = 0;
		GL_getok( frag[k], &ix ); /* 'order' */
		}
	GL_getok( frag[k], &ix ); /* 'by' */
	
	Nsortflds = -1;
	aftercomma = 1;
	while( 1 ) {
		if( aftercomma ) isfld = 1;
		else isfld = 0;
		strcpy( tok, GL_getok( frag[k], &ix ));
		if( tok[0] == '\0' ) break;
		if( tok[ strlen( tok ) - 1 ] == ',' ) {
			tok[ strlen( tok ) - 1 ] = '\0';
			aftercomma = 1;
			}
		else aftercomma = 0;
		if( isfld ) {
			if( strcmp( tok, "*" )==0 ) {
				Naturalorder = 1;
				continue;
				}
			TDH_altfmap( 1 );
			if( do_matchscore && strcmp( tok, "_matchscore" )==0 ) i = fieldmap( "" );
			else i = fieldmap( tok );
			TDH_altfmap( 0 );
			if( i < 0 ) {
				if( groupbyfrag ) return( SHSQL_err( 139, "sql select: unrecognized field in 'group by'", tok ) );
				else return( SHSQL_err( 140, "sql select: unrecognized field in 'order by'", tok ) );
				}
			Nsortflds++;
			if( Nsortflds > MAXSORTFIELDS ) return( SHSQL_err( 141, "sql select: too many 'order by' fields", "" ));

			Sortfld[ Nsortflds ] = i;
			Sortmeth[ Nsortflds ] = 'a';
			Sortdir[ Nsortflds ] = 'a'; /* (ascending).. may be changed by next token.. */
			}
		else	{
			if( strnicmp( tok, "desc", 4 )==0 ) Sortdir[ Nsortflds ] = 'd'; /* descending */
			else if( strnicmp( tok, "num", 3 )==0 ) Sortmeth[ Nsortflds ] = 'n'; /* numeric */
			else if( strnicmp( tok, "dic", 3 )==0 ) Sortmeth[ Nsortflds ] = 'd'; /* alpha- dict */
			else if( strnicmp( tok, "mag", 3 )==0 ) Sortmeth[ Nsortflds ] = 'm'; /* numeric - magnitude */
			/* other modifiers ? */
			}
		}
	k++;
	}
Nsortflds++;


/* keep a list of GROUP BY fields.. */
if( groupbyfrag ) {
	k = oldk; /* restore previous frag position.. */
	TDH_altfmap( 1 );
	for( i = 0; i < sp->nitems; i++ ) {
		int ii;
		m = fieldmap( sp->itemlist[i] );
		if( m < 0 ) continue; 
		/* see if this is a groupby field.. if so add it to the list..*/
		for( ii = 0; ii < Nsortflds; ii++ ) if( Sortfld[ ii ] == m ) break;
		if( ii < Nsortflds ) sp->gbflds[ (sp->ngbflds)++ ] = i;
		}
	TDH_altfmap( 0 );
	}

if( k >= nfrag ) goto RETREIVE;
ix = 0;
strcpy( tok, GL_getok( frag[k], &ix ) );


/* LIMIT */
if( stricmp( tok, "limit" )==0 ) {
	GL_getchunk( tok, frag[k], &ix, ", " );
	sp->lastrow = atoi( tok ) -1;
	GL_getchunk( tok, frag[k], &ix, ", " );
	if( tok[0] != '\0' ) {
		sp->firstrow = sp->lastrow;
		sp->lastrow = atoi( tok ) -1;
		}
	k++;
	}


else 	{
	if( stricmp( tok, "order" )==0 ) 
		return( SHSQL_err( 142, "sql select: use of both 'order by' and 'group by' is not supported", "" ) );
	else return( SHSQL_err( 143, "sql select: unrecognized keyword or invalid usage", tok ) );
	}
	


/* -------------------------------------------  */
/* ---------- begin retrieving data.. --------  */
/* -------------------------------------------  */

RETREIVE:

if( inpipe[0] == '\0' && table[0] != '$' ) {       /* see if indexing can be done (tables and ord. files only) */
	/* See if we should use ISAM indexing.
   	   If so, the use_index() routine will initialize the index lookup system,
	   and change processmode to READISAM. */

	SHSQL_use_index( condex, table, &processmode, &(sp->distinct) );  /* can set sp->distinct */

	}

if( SHSQL_debug ) {
	fprintf( stderr, "shsql pid=%d: access method will be: ", (int) getpid() );
	if( processmode == READISAM ) fprintf( stderr, "ISAM\n" ); 
	else if( inpipe[0] != '\0' ) fprintf( stderr, "PIPE\n" );
	else if( processmode == READSEQUENTIAL ) fprintf( stderr, "TABLE SCAN\n" ); 
	}


/* Set some modes that are in effect only during processing of rows... */
errorcode = 0;
TDH_altfmap( 1 );	/* use alternate map so as not to collide with any fieldmap being used by script interpreter.. */
GL_wildchar( SHSQL_wildchar, SHSQL_wildchar1 ); /* use defined wild char to process where clause.. */
GL_member_nullmode( TDH_dbnull );  		/* allow null to be recognized in lists.. */
TDH_condex_nofunc( 1 );				/* don't trip over dollar signs in fields or constants */



/* get candidate rows..    NOTE: DON'T USE return() FROM WITHIN THIS LOOP  */
while( 1 ) {
	char *s;

	if( processmode == READISAM ) {
		/* get next location candidate from index system.. Note: this can change the processmode.. */
	 	stat = SHSQL_indexlook( &physloc, &processmode, &stoppos, condex, table, &(sp->distinct) );  /* can alter sp_distinct*/

		if( stat == LATE_ISAM_CANCEL ) {
			/* free */
			/* debug message */
			return( SHSQL_err( 144, "sql select: indexes cannot be exploited for this query - late cancel", "" ));
			/* goto above */
			}

	 	stat = fseek( datafp, physloc, SEEK_SET );
		if( stat ) { SHSQL_err( 145, "sql select: seek error on data file", table ); /* where to? */ }
	 	}
	else if( (processmode == READSEQUENTIAL || processmode == READISAM_D) && inpipe[0] == '\0' ) 
		physloc = ftell( datafp ); /* added 5/6/02 */

	s = fgets( buf, MAXRECORDLEN-1, datafp );

	/* end of file encountered.. */
	if( s == NULL ) {
		/* may not be done if doing isam.. scg 3/29/04 */
		if( processmode == READSEQUENTIAL ) break; /* done */
		else	{
			processmode = READISAM;
			/* may need to go back for more.. */
			continue;
			}
		}

	if( strncmp( buf, "//", 2 )==0 || isdelim( buf[0] ) || buf[0] == '\n' ) continue;

	buflen = strlen( buf );
	if( buflen >= (MAXRECORDLEN-1) ) return( SHSQL_err( 181, "sql select: a record is too long - retrieval cancelled", "" ));
	if( buf[ buflen-1 ] == '\n' ) {		/* tagvalue */
		buf[ buflen-1] = '\0';
		buflen--;
		}

	/* parse out fields.. */
       	i = 0; ix = 0; 
	key1begin = key2begin = 0;
       	while( 1 ) {

		/* if sorting, keep a pointer to primary sort field.. */
		if( Nsortflds > 0 ) {
			if( i == Sortfld[0] ) {
				while( isdelim( s[ix] )) ix++; /* datadelim */
				key1begin = ix;
				}
			if( Nsortflds > 1 && i == Sortfld[1] ) {
				while( isdelim( s[ix] )) ix++;  /* datadelim */
				key2begin = ix;
				}
			}

		/* get field.. */
		ixhold = ix;
		SHSQL_getfld( data[i], s, &ix );		/* datadelim */	/* tagvalue */
		if( data[i][0] == '\0' ) break;
		else if( ix - ixhold > DATAMAXLEN ) return( SHSQL_err( 183, "sql select: a field is too long - retrieval cancelled", "" ));
               	i++;
		if( i >= MAXITEMS ) return( SHSQL_err( 182, "sql select: too many fields - retrieval cancelled", "" ));
		}
	nparsedfields = i;


	/* if where clause given, test data row.. */
	if( condex[0] != '\0' ) { 
		stat = TDH_value_subst( condbuf, condex, data, "shsql_header", FOR_CONDEX, 1 ); /* erroronbadvar = 1, 9/17/02 */
		if( stat > 1 ) { errorcode = stat; break; }

		stat = TDH_condex( condbuf, 0 );

		/* if mustix mode is set in config file and we're doing a table scan, see if any indexes exist 
	           for this table; if so, error.  (This appears here to avoid a misleading 
		   "table scan prohibited" error message when a bad fieldname is used in the where clause) */
		if( SHSQL_mustix && irow < 2 && inpipe[0] != '\0' && processmode == READSEQUENTIAL ) {
			FILE *ifp;
			char zerofile[MAXPATH], fakename[MAXPATH];
			strcpy( fakename, table );
			for( i = 0; fakename[i] != '\0'; i++ ) if( fakename[i] == '/' ) fakename[i] = '!';
			sprintf( zerofile, "%s/indexes/%s.0", SHSQL_projdir, fakename );
			ifp = fopen( zerofile, "r" );
			if( ifp != NULL ) { 
				fclose( ifp ); 
				errorcode = SHSQL_err( 147, "sql select: query results in a table scan which is prohibited", table); 
				break;
				}
			}

		/* changed.. this was executing only when the condex stat == 0 ...  
		   situation: x in '3604,3605,3606,3607,3608..'   scg 4/11/04 */
		if( processmode == READISAM_D ) {
			/* see if it's time to stop.. */
			/* physloc = ftell( datafp ); */ /* removed this.. physloc should refer to loc _before_ record
								is read.. if we do it here the loc will be after - scg 4/24/04 */
			if( physloc > stoppos ) {
				processmode = READISAM;   /* go back to the index system next time around */
				continue;
				}
			}
				
		if( stat == 0 ) continue;   /* reject this row.. */
			
		else if( stat < 0 ) {
			errorcode = SHSQL_err( 148, "sql select: error in 'where' clause", condex );
			break;
			}

		}

	if( SHSQL_debug == 2 && inpipe[0] == '\0' ) fprintf( stderr, "      [HIT!! at physloc %ld ]\n", physloc );

	if( Getloclist ) {
		if( Nloclist >= Maxrows ) {
			sprintf( errmsg, "more matching rows than specified (MAXROWS is %d) - no update was done", Maxrows );
			errorcode = SHSQL_err( 149, errmsg, "" );
			break;
			}
		if( sp->distinct ) {
			/* keep location only if unique (not in list yet) .. scg 3/29/04 */
			int jj, already_in_list;
			already_in_list = 0;
			for( jj = 0; jj < Nloclist; jj++ ) {
				if( physloc == *(Loclist + (jj)) ) { already_in_list = 1; break; }
				}
			if( already_in_list ) continue;
			}

		*Lenp = buflen+1;
		Lenp ++;
		*Locp = physloc;
		Locp ++;
		Nloclist++;

		irow += 3; /* to conform */
		sp->nrows++;
		continue; 
		}

	/* ensure that last field is terminated with a delim char.. scg 12/24/02 (was in do_matchscore 
	   section below but we always need to do this so sorting works properly) */
	if( !isdelim( buf[buflen-1] ) ) sprintf( &buf[buflen++], "%c", SHSQL_delim );


	if( do_matchscore ) {
		/* add matchscore field to end of row.. note, this must come after above loclist section since
			it articifically modifies row and row length.. */
		int matchscore;
		/* if user is sorting on _matchscore, need to assign key?begin.. */
		if( Nsortflds > 0 ) {
			if( nparsedfields == Sortfld[0] ) key1begin = buflen;
			else if( Nsortflds > 1 && nparsedfields == Sortfld[1] ) key2begin = buflen;
			}
		/* score will be from 0 (best) to 9 (worst) - can just use default (alpha) sort.. */
		matchscore = TDH_condex_matchscore();
		if( matchscore < 0 || matchscore > 99 ) matchscore = 99; /* sanity */
		sprintf( &buf[ buflen ], "%02d%c", matchscore, SHSQL_delim );
		buflen += 3;
		}

		

	/* add row to recordset */
	if( irow >= (Maxrows*3) ) {
		sprintf( errmsg, "sql SELECT: %d row(s) retrieved, additional row(s) found but ignored (see MAXROWS)", 
			(irow-1)/3 );
		SHSQL_err( 150, errmsg, "" );
		break;
		}

	rows[ irow ] = (char *) malloc( buflen + 1 ); alcount++; 
	if( rows[ irow ] == NULL ) { errorcode = SHSQL_err( 151, "sql select: memory allocation failed", "" ); break; }

	/* copy buf to row, converting tab to space.. */
	/* was: strcpy( rows[ irow ], buf ); */
	for( i = 0; i <= buflen; i++ ) {
		if( SHSQL_delim == BLANK && buf[i] == '\t' ) rows[irow][i] = BLANK; 	/* datadelim */
		else rows[irow][i] = buf[i]; 			/* datadelim */
		}
	rows[ irow + 1 ] = &rows[ irow ][ key1begin ];
	rows[ irow + 2 ] = &rows[ irow ][ key2begin ];
	irow += 3;

	if( Selectforupdate ) SHSQL_sfu_rec( physloc );

	} /* end- one cycle per row */


/* restore modes now.. */
GL_member_nullmode( "" );	/* restore */
TDH_altfmap( 0 );		/* restore */
GL_wildchar( '*', '?' );	/* restore */
TDH_condex_nofunc( 0 );		/* restore */

/* close table file.. */
if( inpipe[0] != '\0' ) pclose( datafp );
else fclose( datafp );
datafp = NULL;

if( SHSQL_debug ) fprintf( stderr, "shsql pid=%d: retrieval completed, found %d records, errorcode=%d, distinct=%d\n", 
	(int) getpid(), (irow-1)/3, errorcode, sp->distinct );

/* if an error occurred while getting records above, return now.. */
if( errorcode > 0 ) return( errorcode );

/* if cattab, loop back from here */
if( cattab && curcattab < (ncattab-1) ) {
	curcattab++;
	sscanf( cattabnames[curcattab], "%s", table );
	if( SHSQL_debug ) fprintf( stderr, "Repeating for additional table: %s\n", table );
	goto ACCESS; 
	}
	

/* allocate storage for aggbuf.. */
if( sp->nagg > 0 ) { /* added surrounding 'if'.. the malloc shouldn't be done if sp->nagg is zero.. 5/13/02 */
	sp->aggbuf = (char *) malloc( sp->nagg * 24 );
	}

/* fprintf( stderr, "[malloced %d cells]", alcount ); fflush( stderr );  */


sp->nrows = (irow-1) / 3;
SHSQL_nrows = sp->nrows; 
sp->finalrowcount = 0;

SP = sp;
/* if( Nsortflds > 0 || sp->distinct || Naturalorder ) { */
if( ( Nsortflds > 0 || sp->distinct || Naturalorder ) && !Getloclist ) {   /* in Getloclist mode we don't need to sort - scg 3/29/04 */
	/* do the sort */
	sprintf( Nullrep, "%s%c", TDH_dbnull, SHSQL_delim ); 			/* datadelim */
	Nullreplen = strlen( Nullrep );

	/* for( i = 0, j = 1; i < sp->nrows; i++, j+=3 ) fprintf( stderr, "1.%s$\n2.%s$\n3.%s$\n", rows[j], rows[j+1], rows[j+2] ); */

	qsort( &rows[1], sp->nrows, sizeof(char *) * 3, compare );
	}


/* if doing aggregates, initialize accumulators.. */
if( sp->nagg > 0 ) reset_accums( sp );


if( sp->ngbflds > 0 && sp->nagg == 0 ) 
	return( SHSQL_err( 152, "sql select: expecting aggregate function(s) such as 'count()' with 'group by'", "" ) );

/* if 'select into' .. write out all rows */
if( sp->outfp != NULL ) { 
	sp->intermed = 1;
	for( i = 0; ; i++ ) {
		stat = SHSQL_row( flds, rows, sp );
		if( stat != 0 ) break;
		for( j = 0; j < sp->nitems; j++ ) fprintf( sp->outfp, "%s%c", flds[j], SHSQL_delim ); 	/* datadelim */
		fprintf( sp->outfp, "\n" );
		}
	SHSQL_nrows = i; /* official row count */
	fclose( sp->outfp );
	/* SHSQL_free( rows, sp ); */
	sp->nrows = 0; /* so that if caller tries SHSQL_row(), it will return 1 on first try.. */
	if( !GL_member( intotable[0], "./$" ) ) SHSQL_unlock( );
	}


return( 0 );
}


/* ======================================= */
/* ======================================= */
/* ======================================= */

/* SHSQL_ROW - get one row of results, doing certain conversions. */
int
SHSQL_row( fields, rows, sp )
char *fields[]; 	/* data fields */
char *rows[];		/* row list as filled by SHSQL_fetchrows() or SHSQL_select() */
struct selectparms *sp;
{
int i, j, k, maxrow, len, stat;
char *dt[MAXITEMS];
double atof();

/* if no more rows, return 1.. exception is when aggregate functions used without 'group by', when a row 
	containing the aggregate(s) will need to be returned */
if( (sp->nrows < 1) && (sp->nagg == 0 || sp->ngbflds > 0) ) {

	if( Selectforupdate ) { SHSQL_sfu_close(); SHSQL_unlock( ); }

	return( 1 ); 
	}

/* if a "limit" was specified, stay within it.. */
if( sp->lastrow != 0 && sp->lastrow  <  sp->nrows ) maxrow = sp->lastrow + 1;  /* +1 added 5/25/01 */
/* otherwise.. */
else maxrow = sp->nrows;


GETNEXT:

/* fprintf( stderr, "accums: " );
 * for( i = 0; i < sp->nitems; i++ ) fprintf( stderr, "%g ", sp->aggaccum[i] );
 * fprintf( stderr, "\n" );
 */

j = ((sp->firstrow + sp->irow) * 3) +1 ;  /* determine actual rows[] cell to work with.. */

/* fprintf( stderr, "irow=%d  j=%d  maxrow=%d  stop_when_j>=%d  nrows=%d  lastrow=%d\n", 
 *	sp->irow, j, maxrow, (maxrow * 3)+1, sp->nrows, sp->lastrow );
 */


if( j >= (maxrow * 3)+1 ) {
	if( sp->nagg > 0 ) { /* if doing aggregate ops.. */
		copy_agg( sp );
		buildrow_agg( fields, sp ); /* build last row */
		sp->nagg = 0; /* signal that this has been done */
		(sp->irow)++;
		(sp->finalrowcount)++;
		return( 0 );     /* and return last row */
		}
	return( 1 );  /* changed from > to >= scg 5/22/01 */
	}

(sp->irow)++;

/* break row into fields.. */
/* len = strlen( rows[j] ); */
k = 0;
dt[k++] = &rows[j][0];
/* for( i = 0; i < len; i++ ) { */
for( i = 0; rows[j][i] != '\0'; i++ ) {
	if( isdelim( rows[j][i] )) { 			/* datadelim */
		/* if( rows[j][i-1] == '\0' ) break; */
		if( rows[j][i-1] == '\0' ) {  /* two delim chars in a row.. skip over (it's not a null item) */
			dt[k-1] = &rows[j][i+1]; /* update */
			rows[j][i] = '\0';
			continue;
			}
			
		rows[j][i] = '\0';
		dt[k++] = &rows[j][i+1];
		}
	}

/* set fields[] to point to rows[] location or to variable contents.. */
k = 0;
for( i = 0; i < sp->nitems; i++ ) {
	if( strncmp( sp->itemlist[i], "_QS", 3 )==0 ) {
		fields[i] = TDH_getvarp( sp->itemlist[i] );
		if( fields[i] == NULL ) fields[i] = TDH_dbnull; /* sanity */
		}
	else	{
		len = strlen( dt[ sp->fldpos[i] ] );

		/* convert spacereps to spaces, unless we shouldn't */
		if( !sp->intermed && !SHSQL_retainund && isspace( (int) SHSQL_delim )) 
			for( j = 0; j < len; j++ ) 
				if( dt[ sp->fldpos[i] ][j] == SHSQL_spacerep ) dt[ sp->fldpos[i] ][j] = BLANK; /* datadelim */

		fields[i] = dt[ sp->fldpos[i] ];
		}
	}

/* fprintf( stderr, "current: " );
 * for( i = 0; i < sp->nitems; i++ ) fprintf( stderr, "%s ", fields[i] );
 * fprintf( stderr, "\n" );
 * fflush( stderr );
 */

if( Selectforupdate ) {

 	/* check for incompatible select options such as distinct, group by, etc. */
	if( sp->distinct || sp->nagg > 0 ) {
		SHSQL_unlock();
		return( SHSQL_err( 153, "sql select for update: simple select command expected", "" ) );
		}

	stat = SHSQL_sfu( fields, sp );  /* sfu() already has parameters sent via initsfu() above */

	/* sp->finalrowcount = 0; */
	(sp->finalrowcount)++;
	return( stat );
	}


/* process for SELECT DISTINCT.. */
if( sp->distinct ) {
	if( sp->irow > 1 ) {
		for( i = 0; i < sp->nitems; i++ ) 
			if( sp->aggflag[i] == '\0' && strcmp( Prevfields[i], fields[i] ) != 0 ) break;
		if( i == sp->nitems ) {
			if( sp->lastrow > 0 ) { /* adjust limit.. 9/27/02 */
				maxrow++; 
				if( maxrow > sp->nrows ) maxrow = sp->nrows;
				(sp->lastrow)++; 
				} 
			/* fprintf( stderr, "eliminating a row..\n" ); */
			goto GETNEXT;
			}
		}
	for( i = 0; i < sp->nitems; i++ ) Prevfields[i] = fields[i];
	}

/* process for aggregate ops.. */
if( sp->nagg > 0 ) {
	double f;
	int prec;
	int breakflag;

	breakflag = 0;
	if( sp->ngbflds > 0 && sp->irow > 1 ) { /* check group-by fields for break.. */
		for( i = 0; i < sp->ngbflds; i++ ) {
			if( strcmp( Prevfields[ sp->gbflds[i]], fields[ sp->gbflds[i]] ) != 0 ) break;
			}
		if( i != sp->ngbflds ) {
			breakflag = 1;
			copy_agg( sp ); /* print aggregate ops into char buffer.. */
			reset_accums( sp );
			}
		}

	/* update accumulators.. */
	for( i = 0; i < sp->nitems; i++ ) {
		if( sp->aggflag[i] == '\0' ) continue;
		else if( sp->aggflag[i] == '*' ) sp->aggcount[i]++;
		else if( sp->aggflag[i] == COUNT && strcmp( fields[i], TDH_dbnull) != 0 ) sp->aggcount[i]++;
		else if( GL_goodnum( fields[i], &prec )) {
			f = atof( fields[i] );
			if( sp->aggflag[i] == SUM || sp->aggflag[i] == AVG ) {
				sp->aggaccum[i] += atof(fields[i]);
				sp->aggcount[i]++;
				}
			else if( sp->aggflag[i] == MIN ) { if( f < sp->aggaccum[i] ) sp->aggaccum[i] = f; }
			else if( sp->aggflag[i] == MAX ) { if( f > sp->aggaccum[i] ) sp->aggaccum[i] = f; }
			}
		}


	/* if aggregate op(s) in use, only return a record on a break.. */
	if( breakflag ) {
		buildrow_agg( fields, sp ); /* build row including aggregate ops.. also sets Prevfields */
		(sp->finalrowcount)++;
		return( 0 ); 		    /* and return the row */
		}
	else	{ 
		for( i = 0; i < sp->nitems; i++ ) Prevfields[i] = fields[i];
		goto GETNEXT; /* skip */
		}
	
	}


(sp->finalrowcount)++;
return( 0 );
}

/* ============================================== */
/* COMPARE - sort comparison routine */
static int compare( void *lhs, void *rhs )
{
int round;
int i, j, k, ix;
char **p1, **p2, **p3, **p4;
char *s, *t, *skey, *tkey;
char ss, tt;
double snum, tnum, atof();
int snull, tnull, stat;

p1 = (char **) lhs;
p2 = (char **) rhs;
s = *p1;
t = *p2;


/* compare keys.. */
for( round = 0; round < Nsortflds; round++ ) {
	
	if( round < 2 ) {  /* 1st two keys -- fastest */
		p3 = (char **) lhs + (round+1); 
		p4 = (char **) rhs + (round+1);
		if( Sortdir[ round ] == 'd' ) { /* descending - flip */
			skey = *p4;
			tkey = *p3;
			}
		else	{
			skey = *p3;
			tkey = *p4;
			}
		}

	else	{    /* keys 3..up - slower */
		for( i = 0, ix = 0; i < Sortfld[ round ]; i++ ) GL_getok( s, &ix );
		while( isdelim( s[ix] ) ) ix++;				/* datadelim */
		if( Sortdir[ round ] == 'd' ) tkey = &s[ ix ];
		else skey = &s[ ix ];

		for( i = 0, ix = 0; i < Sortfld[ round ]; i++ ) GL_getok( t, &ix );
		while( isdelim( t[ix] ) ) ix++;  			/* datadelim */
		if( Sortdir[ round ] == 'd' ) skey = &t[ ix ];
		else tkey = &t[ ix ];
		}

	/* 
	 * fprintf( stderr, "round=%d field=%d dir=%c skey=%.10s tkey=%.10s\n", 
	 *	round, Sortfld[ round ], Sortdir[ round ], skey, tkey );
	 */

	/* handle nulls */
	snull = 0; tnull = 0;
	if( strncmp( skey, Nullrep, Nullreplen )==0 ) snull = 1;
	if( strncmp( tkey, Nullrep, Nullreplen )==0 ) tnull = 1;
	if( snull && tnull ) continue;  /* was: return( 0 ); */
	if( snull ) return( -1 );
	if( tnull ) return( 1 );


	/* alpha sort */
	if( Sortmeth[ round ] == 'a' ) {
		ss = tolower( skey[0] ); 
		tt = tolower( tkey[0] );
		if( ss > tt ) return( 1 );			/* locale-collate */
		else if( ss < tt ) return( -1 );		/* locale-collate */
		for( i = 1; i < DATAMAXLEN+1; i++ ) {
			if( isdelim( skey[i] ) && isdelim( tkey[i] ) ) break; 	/* datadelim */
			else if( isdelim( skey[i] )) return( -1 );
			else if( isdelim( tkey[i] )) return( 1 );  /* datadelim */
			ss = tolower( skey[i] ); 
			tt = tolower( tkey[i] );
			if( ss > tt ) return( 1 );		/* locale-collate */
			if( ss < tt ) return( -1 );		/* locale-collate */
			}
		}

	/* numeric sort */
	else if( Sortmeth[ round ] == 'n' ) {
		snum = atof( skey );
		tnum = atof( tkey );
		if( snum > tnum ) return( 1 );
		else if( snum < tnum ) return( -1 );
		}

	/* magnitude sort */
	else if( Sortmeth[ round ] == 'm' ) {
		double fabs();
		snum = fabs( atof( skey ) );
		tnum = fabs( atof( tkey ) );
		if( snum > tnum ) return( 1 );
		else if( snum < tnum ) return( -1 );
		}

	/* dictionary order - punctuation chars ignored */
	else if( Sortmeth[ round ] == 'd' ) {
		for( i = 0, j = 0; i < DATAMAXLEN+1; i++, j++ ) {
			if( isdelim( skey[i] ) && isdelim( tkey[j] ) ) break;	/* datadelim */
			else if( isdelim( skey[i] ) ) return( -1 );		/* datadelim */
			else if( isdelim( tkey[j] ) ) return( 1 );		/* datadelim */
			ss = tolower( skey[i] ); 
			tt = tolower( tkey[j] );
			if( ispunct( (int) ss )) { j--; continue; }
			if( ispunct( (int) tt )) { i--; continue; }
			if( ss > tt ) return( 1 );		/* locale-collate */
			if( ss < tt ) return( -1 );		/* locale-collate */
			}
		}
	}

/* if we reach here and are doing select distinct, or 'order by *', differentiate records.. */
if( SP->distinct || Naturalorder ) {
	j = 0; k = 0; 
	for( i = 0; i < SP->nfdf; i++ ) {
		stat = discmp( s, &j, t, &k, i );
		if( stat != 0 ) break;
		}

	/* if( i == SP->nfdf ) fprintf( stderr, "same: s = %s | t = %s\n", s, t ); */
	/* else fprintf( stderr, "diff: s = %s | t = %s\n", s, t ); */

	return( stat );
	}

return( 0 );
}

/* ================================= */
/* DISCMP - compare 2 records for sake of SELECT DISTINCT.. */
static int discmp( s, jj, t, kk, item )
char *s, *t;
int *jj, *kk;
int item;
{
int i, j, k, jdone, kdone, stat, skip;
j = *jj;
k = *kk;
/* see if item is in list of requested items.. */
for( i = 0; i < SP->nitems; i++ ) if( SP->fldpos[i] == item ) break;
if( i == SP->nitems ) skip = 1;  /* not a requested field */
else if( SP->aggflag[ SP->fldpos[i] ] != '\0' ) skip = 1; /* aggregate */
else skip = 0;

jdone = kdone = 0; 
stat = 0;
while( 1 ) {
	if( !stat && !skip && !( jdone || kdone )) {
		if( s[j] > t[k] ) stat = 1;			/* locale-collate ? */
		else if( s[j] < t[k] ) stat = -1;		/* locale-collate ? */
		}
	if( isdelim( s[j] ) && isdelim( s[j+1] ) ) j++; 	/* scg 6/2/03 */
	else if( jdone || isdelim( s[j] ) || s[j] == '\0' ) jdone = 1;		/* datadelim */
	else j++;
	if( isdelim( t[k] ) && isdelim( t[k+1] ) ) k++; 	/* scg 6/2/03 */
	else if( kdone || isdelim( t[k] ) || t[k] == '\0' ) kdone = 1;		/* datadelim */
	else k++;

	if( jdone && kdone ) break;
	}
*jj = j+1;
*kk = k+1;
/* fprintf( stderr, "%d stat=%d skip=%d | s = %s | t = %s\n", item, stat, skip, s, t ); */
return( stat );
}


/* ====================================== */
/* RESET_ACCUMS  - reset all aggregate accums  */
static int reset_accums( sp )
struct selectparms *sp;
{
int i;
for( i = 0; i < sp->nitems; i++ ) {
	sp->aggcount[i] = 0;
	if( sp->aggflag[i] == MIN ) sp->aggaccum[i] = 9999999999999.0;
	else if( sp->aggflag[i] == MAX ) sp->aggaccum[i] = -9999999999999.0;
	else sp->aggaccum[i] = 0.0;
	}
return( 0 );
}

/* ====================================== */
/* COPY_AGG - print accumulators to character buffer */
static int copy_agg( sp )
struct selectparms *sp;
{
int i, j, aggtyp, len;

for( i = 0, j = 0; i < sp->nitems; i++ ) {
	Aggbuffld[ i ] = NULL;
	aggtyp = sp->aggflag[i];
	if( aggtyp != '\0' ) {
		Aggbuffld[ i ] = &(sp->aggbuf[j]);

		if( aggtyp == COUNT || aggtyp == '*' ) 
			sprintf( &(sp->aggbuf[j]), "%d", sp->aggcount[i] );

		else if( aggtyp == SUM || aggtyp == MIN || aggtyp == MAX ) {
			if( sp->nrows == 0 ) strcpy( &(sp->aggbuf[j]), TDH_dbnull );
			else sprintf( &(sp->aggbuf[j]), "%g", sp->aggaccum[i] );
			}

		else if( aggtyp == AVG ) {
			if( sp->aggcount[i] == 0 ) strcpy( &(sp->aggbuf[j]),  TDH_dbnull );
			else sprintf( &(sp->aggbuf[j]), "%g", sp->aggaccum[i]/(double)sp->aggcount[i]);
			}

		len = strlen( &(sp->aggbuf[j]) );
		j+= (len+1);
		}
	}
return( 0 );
}

/* ====================================== */
/* BUILDROW_AGG - set fields[] to point to appropriate data items.  Also set Prevfields.  */
static int buildrow_agg( fields, sp )
char *fields[];
struct selectparms *sp;
{
int i;
char *tmp;
for( i = 0; i < sp->nitems; i++ ) {
	if( Aggbuffld[i] == NULL ) {
		tmp = Prevfields[i];
		Prevfields[i] = fields[i];
		fields[i] = tmp;
		}
	else 	{
		Prevfields[i] = fields[i];
		fields[i] = Aggbuffld[i];
		}
	}
return( 0 );
}


/* ====================================== */
/* FREE - free allocated memory */
/*    loclistmode cannot be turned off until after this routine is called. */
int
SHSQL_free( rows, sp )
char *rows[];
struct selectparms *sp;
{
int i, j;
if( ! Free_ok ) return( 0 );
Free_ok = 0;
if( sp->nrows < 1 ) return( 1 );
free( rows[0] );
if( ! Getloclist ) {
	for( i = 1, j = 0; i < ((sp->nrows)*3)+1; i+=3 ) {
		free( rows[i] );
		j++;
		}
	/* fprintf( stderr, "[freed %d cells]", j );  */
	if( sp->aggbuf != NULL ) free( sp->aggbuf );
	}
if( rows != NULL ) free( rows );
return( 0 );
}
/* ========================================= */
/* FREELOCLIST - free the loclist */
int
SHSQL_freeloclist()
{
free( Loclist );
free( Lenlist );
return( 0 );
}

/* ======================================== */
/* LOCLISTMODE - turn loclist mode on/off */
int
SHSQL_loclistmode( stat )
int stat;
{
Getloclist = stat;
return( 0 );
}

/* ========================================= */
/* NEXTLOC - get next loc in loclist */
int
SHSQL_nextloc( physloc, reclen )
long *physloc;
int *reclen;
{
if( Curloclist >= Nloclist ) return( 1 );
*physloc = *(Loclist + (Curloclist));
*reclen = *(Lenlist + (Curloclist));
/* fprintf( stderr, "[returning %ld %d]\n", *physloc, *reclen ); */
Curloclist++;
return( 0 );
}

