/* SQLCOM.C - interpret a SHSQL command
 * Copyright 1998-2001 Stephen C. Grubb  (www.sgpr.net) .
 * This code is covered under the GNU General Public License (GPL);
 * see the file ./Copyright for details. 
*/

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

#define MAXFRAG 80

extern int GL_make_unique_string(), TDH_dequote();
extern int unlink(), getpid();

static char qbuf[MAXSQLCOMMANDLEN];
static int initialized = 0;

static char *frag[MAXFRAG];
static int nfrag;
static int selflag = 0;
static int wherefrag;
static int sqlmisc();
static int already_dequoted = 0;
static char log_commands_file[MAXPATH] = "";


/*  ================================================
 *  COMMAND -  execute a SQL command or query.  
 *  Returns: 0 = execution of query ok 
 *	     other = error on execution of query 
 */
int
SHSQL_command( querystring )
char *querystring;	/* this can be modified herein */
{
int i, ix, ixx;
char op;
char tok[DATAMAXLEN+1];
int aftercomma;
int stat;
int first;
int dobreak;
int len;
int vflag;
char nullrep[20];
int byflag;


/* initialization.. */
if( !initialized ) {
	if( strcmp( SHSQL_projdir, "./" )==0 ) {  /* then better would read config file again..  7/8/02 */
		stat = SHSQL_readconfig();
		if( stat != 0 ) return( SHSQL_err( 101, "Cannot load config file", "" ) );
		}

	/* generate prefix for tmp table name */
	if( SHSQL_tmptblpfx[0] == '\0' ) {
		GL_make_unique_string( tok, 0 );
		sprintf( SHSQL_tmptblpfx, "tt%s", tok );
		}

	/* tell scanner that @variable names can contain dots (.) as in join results.. */
	TDH_valuesubst_settings( "dot_in_varnames", 1 ); /* 7/8/02 */

	initialized = 1;
	}


/* optionally log commands.. */
if( log_commands_file[0] != '\0' ) {
	FILE *logfp;
	int mon, day, yr, hr, min, sec;
	GL_sysdate( &mon, &day, &yr );
	GL_systime( &hr, &min, &sec );
	logfp = fopen( log_commands_file, "a" );
	if( logfp != NULL ) {
		fprintf( logfp, "------------------\n20%02d/%02d/%02d %02d:%02d:%02d\n%s\n", 
			yr, mon, day, hr, min, sec, querystring );
		}
	}
	

/* create, drop, alter, maintain, identity.. */
sscanf( querystring, "%s", tok );
if( GL_smemberi( tok, "identity create drop alter maintain sqlmode" )) return( sqlmisc( querystring ) );

SHSQL_nrows = 0;

/* convert quoted literals.. */
if( !already_dequoted ) {
	TDH_valuesubst_settings( "sqlmode", 1 );
	stat = TDH_dequote( qbuf, querystring, "QS" );
	TDH_valuesubst_settings( "sqlmode", 0 );
	if( stat != 0 ) return( stat );
	}
else strcpy( qbuf, querystring );

sprintf( nullrep, "%-4s", TDH_dbnull );

/* massage function constructs and remove parentheses.. */
len = strlen( qbuf );

for( i = 0; i < len; i++ ) {
	if( qbuf[i] == '\n' || qbuf[i] == '\t' ) qbuf[i] = BLANK;
	}

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

	/* convert "null" to internal representation */
	if( strnicmp( &qbuf[i], " null", 5 )==0 && (qbuf[i+5] == BLANK || qbuf[i+5] == ',' || qbuf[i+5] == '\0' ) ) {
		qbuf[i+1] = nullrep[0]; qbuf[i+2] = nullrep[1];
		qbuf[i+3] = nullrep[2]; qbuf[i+4] = nullrep[3];
		}

	/* mark functions eg. " min(" is rewritten as " $in(" and " avg(" is rewritten as " $vg(" */
	if( stricmp( tok, "select" ) ==0 ) {
		if( strnicmp( &qbuf[i], " min(", 5 )==0 || strnicmp( &qbuf[i], " max(", 5 )==0 ||
		    strnicmp( &qbuf[i], " avg(", 5 )==0 || strnicmp( &qbuf[i], " sum(", 5 )==0 ) {
				qbuf[i+1] = '$'; qbuf[i+3] = ','; qbuf[i+4] = BLANK; 
				}
		if( strnicmp( &qbuf[i], " count(", 7 )==0 ) { 
				qbuf[i+1] = '$'; qbuf[i+5] = ','; qbuf[i+6] = BLANK; 
				}
		}
	if( qbuf[ i ] == '(' || qbuf[i] == ')' ) qbuf[i] = BLANK;

	if( qbuf[ i ] == '@' && qbuf[i+1] != '_' ) {
		return( SHSQL_err( 102, "invalid @constant in where clause", "" ));
		}
	}
/* fprintf( stderr, "[%s]", qbuf ); */


/* scan querystring.. */
nfrag = 0;
aftercomma = 0;
wherefrag = -1;
ix = 0;
op = '\0';
byflag = 0;
first = 1;
while( 1 ) {
	while( isspace( (int) qbuf[ix] ) )ix++;
	i = ix;

	/* vflag == 1 indicates current token should be a tablename, fieldname or constant */
	if( !first && GL_smemberi( tok, "select from where and or join" )) vflag = 1; 
	else if( !first && byflag ) vflag = 1;
	else vflag = 0;


	strcpy( tok, GL_getok( qbuf, &ix ) );
	if( tok[0] == '\0' ) break;
	dobreak = 0;
	if( byflag && stricmp( tok, "by" )!= 0 ) byflag = 0;


	if( first && !aftercomma ) {
		if( GL_smemberi( tok, "select insert update delete" )) {
			op = tolower( tok[0] );
			if( SHSQL_readonly && op != 's' ) return( SHSQL_err( 20, "this is a read-only database, updates prohibited", "" ));
			dobreak = 1;
			}
		else return( SHSQL_err( 103, "sql: unrecognized keyword", tok ));
		}

	else if( GL_smemberi( tok, "limit maxrows" ) && op == 's' && !vflag && !first && !aftercomma )
		dobreak = 1;

	/* order by, group by */
	else if( GL_smemberi( tok, "order group" ) && op == 's' && !vflag && !first && !aftercomma ) {   
		/* "order by" is two tokens.. */
		ixx = ix;
		if( stricmp( GL_getok( qbuf, &ixx ), "by" )==0 ) {
			dobreak = 1;
			byflag = 1; /* indicates that we're immediately after keywords 'group by' or 'order by' */
			}
		}

	/* for update */
	else if( GL_smemberi( tok, "for" ) && op == 's' && !vflag && !first && !aftercomma ) {   
		/* "for update" is two tokens.. */
		ixx = ix;
		if( stricmp( GL_getok( qbuf, &ixx ), "update" )==0 ) dobreak = 1;
		}

	else if( !aftercomma && nfrag-1 != wherefrag ) {
		if( op == 's' && GL_smemberi( tok, "into from where" ) ) dobreak = 1;
		else if( op == 'i' && GL_smemberi( tok, "into values" ) ) dobreak = 1;
		else if( op == 'u' && GL_smemberi( tok, "set where" ) ) dobreak = 1;
		else if( op == 'd' && GL_smemberi( tok, "from where" ) ) dobreak = 1;
		}

	else if( op == 'u' && aftercomma ) dobreak = 1;     /* update fieldname = value pair.. */


	first = 0;

	if( stricmp( tok, "where" )==0 ) {
		wherefrag = nfrag;
		}

	if( dobreak ) {
		if( i > 0 ) qbuf[i-1] = '\0';
		frag[ nfrag++ ] = &qbuf[i];
		}
		

	if( strcmp( tok, "," ) == 0 || tok[ strlen( tok ) - 1 ] == ',' ) aftercomma = 1;
	else aftercomma = 0;
	}

if( SHSQL_debug ) {
	fprintf( stderr, "shsql pid=%d: got command:\n", getpid() );
	for( i = 0; i < nfrag; i++ ) fprintf( stderr, "   %s\n", frag[i] ); 
	}



selflag = 0;
if( op == 's' ) {
	selflag = 1;
	stat = 0;
	}
else if( op == 'i' ) stat = SHSQL_insert( frag, nfrag );
else if( op == 'u' ) stat = SHSQL_update( frag, nfrag, wherefrag );
else if( op == 'd' ) stat = SHSQL_delete( frag, nfrag );


return( stat );
}

/* ==================================== */
/* FETCHROWS = fetch the rows requested in previous select command */
int
SHSQL_fetchrows( rows, maxrows, sp )
char *rows[];
int maxrows;	/* send as 0 to get default maxrows, or use it to set maxrows */
struct selectparms *sp;
{
int stat;
if( ! selflag ) return( 0 ); /* fetchrows must be imm. preceded by select */
stat = SHSQL_select( frag, nfrag, wherefrag, rows, maxrows, sp );
selflag = 0;
return( stat );
}

/* ==================================== */
/* ROWCOUNT = return the # rows produced by previous command */
int
SHSQL_rowcount()
{
return( SHSQL_nrows );
}


/* ================================ */
/* SQLMISC - process other commands */
static int
sqlmisc( s )
char *s;
{
int i, ix;
char buf[2048], tok[MAXPATH];


ix = 0;
strcpy( tok, GL_getok( s, &ix ) );

if( SHSQL_readonly && stricmp( tok, "identity" ) != 0 ) 
	return( SHSQL_err( 104, "this is a read-only database, updates prohibited", "" ));


if( stricmp( tok, "identity" )==0 ) {
	while( isspace( (int) s[ ix ] )) ix++;
	for( i = 0; s[ix] != '\0'; ix++ ) {
		if( s[ix] == '\n' ) break;
		else if( isspace( (int) s[ix] )) buf[ i++ ] = '_';
		else if( s[ix] != '\'' && s[ix] != '"' ) buf[ i++ ] = s[ix];
		}
	buf[i] = '\0';
	TDH_setvar( "_IDENTITY", buf );
	}

else if( stricmp( tok, "sqlmode" )==0 ) {
	strcpy( tok, GL_getok( s, &ix ) );
	if( stricmp( tok, "retain_und" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		if( tolower( tok[0] ) == 'y' ) SHSQL_retainund = 1;
		else SHSQL_retainund = 0;
		}
	else if( stricmp( tok, "debugmode" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		SHSQL_debug = atoi( tok );
		}
	else if( stricmp( tok, "translog" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		SHSQL_translog = atoi( tok );
		}
	else if( stricmp( tok, "writelock_ntries" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		SHSQL_writelock_ntries = atoi( tok );
		}
	else if( stricmp( tok, "maxrows_select" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		SHSQL_maxrows_select = atoi( tok );
		}
	else if( stricmp( tok, "maxrows_update" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		SHSQL_maxrows_update = atoi( tok );
		}
	else if( stricmp( tok, "wildcard" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		SHSQL_wildchar = tok[0];
		}
	else if( stricmp( tok, "wildcard1" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		SHSQL_wildchar1 = tok[0];
		}
	else if( stricmp( tok, "errormode" )==0 ) {
		strcpy( tok, GL_getok( s, &ix ) );
		TDH_errmode( tok );
		}
	else if( stricmp( tok, "logcommands" )== 0 ) {
		strcpy( log_commands_file, GL_getok( s, &ix ) );
		if( stricmp( log_commands_file, "no" )==0 ) log_commands_file[0] = '\0';
		}
	return( 0 );
	}

else if( stricmp( tok, "create" )==0 ) {
	strcpy( tok, GL_getok( s, &ix ) );
	if( stricmp( tok, "stream" )==0 ) {  /* create stream */
		char tmptblname[80], pathname[ MAXPATH ];
		FILE *fp;
		strcpy( tmptblname, GL_getok( s, &ix ) ); /* tmp table name */
		if( tmptblname[0] == '$' ) strcpy( tmptblname, &tmptblname[1] );
		sprintf( pathname, "%s/%s.%s", SHSQL_tmptabledir, SHSQL_tmptblpfx, tmptblname );
		strcpy( tok, GL_getok( s, &ix ) ); /* as */
		if( stricmp( tok, "as" )!= 0 ) return( SHSQL_err( 105, "sql create stream: 'as' expected", "" ));
		fp = fopen( pathname, "w" );
		if( fp == NULL ) return( SHSQL_err( 106, "sql create stream: cannot open tmp table file", pathname ) );
		fprintf( fp, "%%-%%command%%-%% %s\n", &s[ ix + 1 ] );
		fclose( fp );
		return( 0 );
		}

	/* other 'creates' exec shsql_create */
	if( SHSQL_bin[0] == '\0' ) sprintf( buf, "shsql_create %s %s", tok, &s[ix] );
	else sprintf( buf, "%s/shsql_create %s %s", SHSQL_bin, tok, &s[ix] );
	for( i = 0; buf[i] != '\0'; i++ ) if( GL_member( buf[i], "(),\n" )) buf[i] = BLANK;
	if( SHSQL_debug ) fprintf( stderr, "invoking: %s\n", buf );
	system( buf );
	}

else if( stricmp( tok, "drop" )==0 ) {

	for( i = 0; s[i] != '\0'; i++ ) if( GL_member( s[i], "(),\n" )) buf[i] = BLANK;

	/* drop table|index|temptable */
	strcpy( tok, GL_getok( s, &ix ) );

	if( stricmp( tok, "table" )==0 ) {
		char fakename[MAXPATH];
		/* drop table t1, t2, .. tN */
		while( 1 ) {
			strcpy( tok, GL_getok( s, &ix ) );
			if( tok[0] == '\0' ) break;
			if( GL_member( tok[0], "./$" )) {
				SHSQL_err( 107, "drop table error: cannot remove", tok );
				continue;
				}
			strcpy( fakename, tok );
			for( i = 0; fakename[i] != '\0'; i++ ) if( fakename[i] == '/' ) fakename[i] = '!';
			sprintf( buf, "mv %s/data/%s %s/dropped.%s-", SHSQL_projdir, tok, TDH_tmpdir, fakename );
			system( buf );

			/* remove any indexes */
			sprintf( buf, "(cd %s/indexes; rm %s.*.[123] %s.0 2>/dev/null )", SHSQL_projdir, fakename, fakename );
			system( buf );

			/* remove lock file if any */
			sprintf( buf, "(cd %s/locks; rm %s.wlock 2>/dev/null )", SHSQL_projdir, fakename );
			system( buf );

			fprintf( stderr, "table %s removed from database and put in recycle bin (%s)\n", tok, TDH_tmpdir );
			}
		return( 0 );
		}

	else if( stricmp( tok, "temptable" )==0 ) {
		/* drop temptable *   ..or.. drop temptable t1, t2, .. tN */
		strcpy( buf, GL_getok( s, &ix ) );
		if( strcmp( buf, "*" )==0 ) {
			sprintf( buf, "rm %s/%s.* 2> /dev/null", SHSQL_tmptabledir, SHSQL_tmptblpfx );
			system( buf );
			return( 0 );
			}
		else	{
			while( 1 ) {
				strcpy( tok, GL_getok( s, &ix ) );
				if( tok[0] == '\0' ) break;
				else if( GL_member( tok[0], "./" )) {
					SHSQL_err( 108, "drop temptable error: cannot remove", tok );
					continue;
					}
				else if( tok[0] == '$' ) 
				  	sprintf( buf, "%s/%s.%s", SHSQL_tmptabledir, SHSQL_tmptblpfx, &tok[1] );
				else sprintf( buf, "%s/%s.%s", SHSQL_tmptabledir, SHSQL_tmptblpfx, tok );
				unlink( buf );
				}
			}
		}

	else if( stricmp( tok, "index" )==0 ) {
		char tablename[MAXPATH];
		strcpy( tok, GL_getok( s, &ix ) );
		if( stricmp( tok, "on" ) != 0 ) return( err( 44, "sql drop index: 'on' expected", "" ));
		strcpy( tablename, GL_getok( s, &ix ) );
		while( 1 ) {
			strcpy( tok, GL_getok( s, &ix ) );
			if( tok[0] == '\0' ) break;
			if( strcmp( tok, "*" )==0 ) 
				sprintf( buf, "(cd %s/indexes; rm %s.*.[123] 2>/dev/null; rm %s.0 2>/dev/null )", 
					SHSQL_projdir, tablename, tablename );
			else sprintf( buf, "(cd %s/indexes; rm %s.%s.[123] )", SHSQL_projdir, tablename, tok );
			system( buf );
			fprintf( stderr, "removed index %s (%s)\n", tablename, tok );
			}
		return( 0 );
		}
	else return( SHSQL_err( 109, "drop: 'index', 'table', or 'temptable' expected", "" ) );
	}


else if( stricmp( tok, "maintain" )==0 ) {
	if( SHSQL_bin[0] == '\0' ) sprintf( buf, "tabmaint %s", &s[ix] );
	else sprintf( buf, "%s/tabmaint %s", SHSQL_bin, &s[ix] );
	for( i = 0; buf[i] != '\0'; i++ ) if( GL_member( buf[i], "(),\n" )) buf[i] = BLANK;
	if( SHSQL_debug ) fprintf( stderr, "invoking: %s\n", buf );
	system( buf );
	}

else if( stricmp( tok, "alter" )==0 ) {
	if( SHSQL_bin[0] == '\0' ) sprintf( buf, "shsql_alter %s", &s[ix] );
	else sprintf( buf, "%s/shsql_alter %s", SHSQL_bin, &s[ix] );
	for( i = 0; buf[i] != '\0'; i++ ) if( GL_member( buf[i], "(),\n" )) buf[i] = BLANK;
	if( SHSQL_debug ) fprintf( stderr, "invoking: %s\n", buf );
	system( buf );
	}

return( 0 );
}

/* =================================== */
/* QUERY_ALREADY_DEQUOTED - inform us that incoming query has already been processed
 *		by TDH_dequote (used by join)
 */
int
SHSQL_query_already_dequoted( mode )
int mode;
{
already_dequoted = mode;
return( 0 );
}

