/*
 * @(#) mut.h	2005-05-10

	Header file for the MUT cpp-files, contains several often used functions

    ========== licence begin  GPL
    Copyright (c) 2000-2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


	
*/

#define private public

#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <string.h>
#include "heo03.h"
#include "gsp01.h"
#include "SAPDB/Interfaces/SQLDBC/SQLDBC_ClientRuntime.h"
#include "SAPDB/Interfaces/Runtime/IFR_ConnectionItem.h"
#include "SAPDB/Interfaces/Runtime/IFR_Connection.h"
#include "SAPDB/Interfaces/SQLDBC/SQLDBC.h"

#ifdef   WIN32
#include "SAPDB/RunTime/System/RTESys_MSWindows.h" /*nocheck*/
#define  getpid    GetCurrentProcessId
#define  sleep(x)  Sleep (x * 1000)
#define	 STRICMP _stricmp
#else
#define  STRICMP strcasecmp
#endif

#ifdef _WIN32
#define SIGFUNC	(void (__cdecl *)(int)) 
#else
#define SIGFUNC	(void (*)(int))
#endif

using namespace SQLDBC;

//#define  ENV_DBNAME "DBNAME"
#define  CE(x,y,z,a)   {if ( protocol && ( z == 0 ) ) pe ( x, 0, z, a ) ;\
		    if ( z != 0 ) pe ( x, y, z, a ) ;}
#define  SE(x,y,z,a)   {if ((z != 0) && (z != 100)) pe ( x , y, z, a ) ; }
#define  DE(x,y,z,a)   {pe ( x , y , z , a) ;}
#define  ROLLBERR(z)  ( ( ( z >= 400  ) && ( z <=  800  ) ) \
	|| ( z == -106 ) || ( z == -1000 ) || ( z == -9205 ) || (z == -7067) || (z == -51) )
#define MAXLONGRAW 32760
#define MAXSCHEMAS 50 
#define MAXMAXSCHEMAS 400
//for definition of HOSTTYPE long on windows (long=4byte) and linux (long=8byte)
#define MY_HOSTTYPE_LONG ((sizeof(long)>4)?SQLDBC_HOSTTYPE_INT8:SQLDBC_HOSTTYPE_INT4)

static SQLDBC_IRuntime *runtime;
static SQLDBC_Environment *env=0;
static SQLDBC_Connection *conn;
static SQLDBC_Statement *stmt;
static SQLDBC_Retcode returncode;
static SQLDBC_ConnectProperties CP;
static SQLDBC_PreparedStatement *pstmt;
static SQLDBC_PreparedStatement *pstmt2;
static SQLDBC_PreparedStatement *pstmt3;
static SQLDBC_ResultSet *rs;
static SQLDBC_ResultSet *rs2;
#define ERRORCODE error().getErrorCode()
#define ERRORTEXT error().getErrorText()

void	check_params(int myargc, char **myargv);
void	print_f (const char *, int, const char *);
void	prot (const char *, ...);
void	rollback();
void	commit();
void	cwr();
void	Sqldump(int, SQLDBC_Connection*);
bool	FileExist(const char*);
void	is_database_unicode();
void	database_info_own();
SQLDBC_Retcode checkPreparedStatement(SQLDBC_PreparedStatement *, const char *);

void	pe (const char *, int, int, const char *) ;

SQLDBC_Bool use_unicode = SQLDBC_FALSE;

FILE	*fout;
int     pid = 0;
int     cycle = 0;
int		DumpOnLongCheck = 0;
int     protocol = 0;
int		col = 1;

int		check_longs = 0;
int		superusernumber = 0;
int		maxcycle = 1;
int		line = 2;
int		maxlongraw = 19000;
int		rows = 3000;
int     noinit = 0;
int		maxRuntime = 0;
char	callfile[40];
char	actfile[15];
char	DBNAME[10];
char	id[40];
char	pw[40];
char	DBM_id[40];
char	DBM_pw[40];
char	DBA_id[40];
char	DBA_pw[40];
char	DBNODE[40];
/* ATTENTION: edit MAXIGNORE, if you edit the call from the pl-script for rendlos.exe with more than two "-ignore"-values */
#define MAXIGNORE 2
int     ignore [MAXIGNORE];

char	DATABASE_SESSIONID[40];
char	DATABASE_APPLICATIONPROCESS[40];
char	DATABASE_USERNAME[40];
char	DATABASE_TASKID[40];
char	DATABASE_UID[40];

int		prepareErrors = 0;
/*------------------------------------------------------------------------*/
void check_params(int myargc, char **myargv)
/*
	checks the given parameters for the executables, all the same way
*/
{
/* ATTENTION: edit number of local arrays, if you edit the call from the pl-script for rendlos.exe with more than two "-ignore"-values */
	char		ignore1[10];
	char		ignore2[10];

	strcpy(callfile, myargv[0]);
	strcpy(DBNODE, "");

	for ( int argn = 2 ; argn <= myargc ; argn++ )
	{
		if ( strcmp ( myargv [argn-1] , "-noinit" ) == 0 )
		{
			noinit = 1 ;
		}
		if ( strcmp ( myargv [argn-1] , "-checklongrows" ) == 0 )
			check_longs = 1 ;

		if ( strcmp ( myargv [argn-1] , "-superusernumber" ) == 0 )
		{
			superusernumber = atoi ( myargv [argn] ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-maxruntime" ) == 0 )
		{
			maxRuntime = atoi ( myargv [argn] ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-database" ) == 0 )
		{
			strcpy(DBNAME, myargv [argn]);
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-user" ) == 0 )
		{
			sscanf ( myargv[argn] , "%[A-Za-z0-9]%*[,]%[A-Za-z0-9]" , id , pw ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-dbm" ) == 0 )
		{
			sscanf ( myargv[argn] , "%[A-Za-z0-9]%*[,]%[A-Za-z0-9]" , DBM_id , DBM_pw ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-dba" ) == 0 )
		{
			sscanf ( myargv[argn] , "%[A-Za-z0-9]%*[,]%[A-Za-z0-9]" , DBA_id , DBA_pw ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-ignore" ) == 0 )
		{
/* ATTENTION: edit number of arguments, if you edit the call from the pl-script for rendlos.exe with more than two "-ignore"-values */
			sscanf ( myargv[argn] , "%[A-Za-z0-9]%*[,]%[A-Za-z0-9]" , ignore1 , ignore2 ) ;
			ignore[0]=atoi(ignore1);
			ignore[1]=atoi(ignore2);
			argn++ ;
		}		
		if ( strcmp ( myargv [argn-1] , "-maxcycle" ) == 0 )
		{
			maxcycle = atoi ( myargv [argn] ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-rows" ) == 0 )
		{
			rows = atoi ( myargv [argn] ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-line" ) == 0 )
		{
			line = atoi ( myargv [argn] ) ;
			argn++ ;
		}
		if ( strcmp ( myargv [argn-1] , "-maxlongraw" ) == 0 )
		{
			maxlongraw = atoi ( myargv [argn] ) ;
			argn++ ;
		}		
		if ( strcmp ( myargv [argn-1] , "-node" ) == 0 )
		{
			if (strcmp(myargv[argn], "emptynode") == 0 )		//this option is set in mutrun.pl and means that there is no node set
			{
				strcpy(DBNODE, "");
			}
			else
			{
				strcpy(DBNODE, myargv [argn]);
			}
			argn++ ;
		}
	}
}
/*------------------------------------------------------------------------*/
void print_f (const char *s, int errorcode, const char *errortext)
/*
	prints the given text in the protocol file (only in case of errors [from function "pe"] or before sqldump)
*/
{
	char    da [40] ;
	time_t  tm ;

	tm = time ( (time_t *) 0) ;
	strncpy ( da , ctime ( &tm ) , 39 ) ;
    da [ 39 ] = 0 ;

	fprintf ( fout , "pid %d , %.19s , cycle %3d , %s :  ERR = %d, TXT = %s\n" ,
				pid , da , cycle , s , errorcode, errortext) ;
}
/*------------------------------------------------------------------------*/
void prot (const char *s1 , ... )
/*
	prints the given text in the protocol file
*/
{
	char	da [40] ;
    time_t  tm ;

    tm = time ( (time_t *) 0) ;
    strncpy ( da , ctime ( &tm ) , 39 ) ;
    da [ 39 ] = 0 ;

 	va_list paramlist;
	va_start(paramlist, s1);
    fprintf ( fout , "pid %d , %.19s , cycle %3d , " , pid , da , cycle ) ;
    vfprintf ( fout , s1 , paramlist ) ;
	va_end(paramlist);
    fflush ( fout ) ;
}
/*------------------------------------------------------------------------*/
void rollback ()
/*
	explicit ROLLBACK-statement for faster use of command in source code
*/
{
	returncode = conn->rollback();
	CE("rollback: ROLLBACK WORK", 1, conn->ERRORCODE, conn->ERRORTEXT);
}
/*------------------------------------------------------------------------*/
void commit ()
/*
	explicit COMMIT-statement for faster use of command in source code
*/
{
	returncode = conn->commit();
	CE("commit: COMMIT WORK", 1, conn->ERRORCODE, conn->ERRORTEXT);
}
/*------------------------------------------------------------------------*/
void cwr()
/*
	closes the file handle for the output to the protocol file
	CommitWorkRelease
*/
{
	if ( fout )
		fclose ( fout ) ;

	returncode = conn->commitWorkRelease();
	CE("cwr: CommitWorkRelease", 1, conn->ERRORCODE, conn->ERRORTEXT);

	remove( actfile );
	exit (0) ;
}
/*------------------------------------------------------------------------*/
void sqladump (SQLDBC_Connection *conn ){
    IFR_Connection *ifrconn =  conn->m_citem->m_item->getConnection();
    tsp00_ErrTextc cError;
    fprintf(fout, "Error with Client-ConnID: %d\n", (tsp00_Int4) ifrconn->m_connectionid);
    tsp01_CommErr_Enum err = SqlDBDump (  (tsp00_Int4) ifrconn->m_connectionid,  cError );
    if (err != commErrOk_esp01){ 
        fprintf(fout, "Error with sqladump: %s\n", cError.asCharp());
    } 
}
/*------------------------------------------------------------------------*/
SQLDBC_IRuntime* getRuntime(){
	//Every application has to initialize the SQLDBC library by getting a 
	//reference to the ClientRuntime and calling the SQLDBC_Environment constructor.
	char errorText[200];

	SQLDBC_IRuntime* runtime = //SQLDBC::GetClientRuntime 
		SQLDBC::GetSingleThreadClientRuntime
		(errorText, sizeof(errorText) );
	if (!runtime) {
		print_f("Getting instance of the ClientRuntime failed", -42, errorText);

		if ( fout )
			fclose ( fout ) ;

		remove( actfile );
		exit (1);
	}
	return runtime;
}
/*------------------------------------------------------------------------*/
void Sqldump (int errorcode, SQLDBC_Connection *conn)
/*
	stops the database kernel in case of emergency
*/
{
	FILE *fdump;
	char dumpfile [100] ;

	sprintf ( dumpfile , "dump.prt" ) ;

	if (!FileExist(dumpfile))
	{    
		if ( ( errorcode != -10709 ) && ( errorcode != -10807 ) /* && ( errorcode != 800 ) */ )
		{
			if ( ( fdump = fopen ( dumpfile , "w" ) ) == NULL )
			{
				char outtext [100] ;
				sprintf ( outtext , "Error opening file '%s'\n" , dumpfile ) ;
				perror ( outtext ) ;
				if ( fout )
					fclose ( fout ) ;
				remove( actfile );
				exit (-99) ;
			}
			
			fprintf(fdump, "The task with the following processID causes the SQLADUMP: %d\n", pid);
			if (!STRICMP("superu", id))
			{
				fprintf(fdump, "This is the superuser connected as %s %d\n", id, superusernumber);
			}
			else
			{
				fprintf(fdump, "This is the user connected as %s\n", id);
			}
			fprintf(fdump, "The reason was the errorcode: %d\n", errorcode);
			fprintf(fdump, "The DATABASE_TASKID was: %s\n", DATABASE_TASKID);

			SQLDBC_Statement *dumpStmt;
			dumpStmt = conn->createStatement();	
			CE("main: createStatement", 1, conn->ERRORCODE, conn->ERRORTEXT);
	   		returncode = dumpStmt->execute("SQLDUMP"); /* to find dumping task in vtrace */

			sqladump(conn);
		}
		
		if ( fdump )
			fclose ( fdump ) ;	
	}

	if ( fout )
		fclose ( fout ) ;

	remove( actfile );
	exit (1) ;
}
/*------------------------------------------------------------------------*/
bool FileExist(const char* FileName)
/*
	checks whether a file exists or not
*/
{
    struct stat my_stat;
    return (stat(FileName, &my_stat) == 0);
}
/*------------------------------------------------------------------------*/
void is_database_unicode()
/*
	checks whether the database is unicode or not and writes an output to the prot-file
*/
{
	use_unicode = conn->isUnicodeDatabase();
	
	if (use_unicode == SQLDBC_TRUE)
	{
		prot("Unicode will be used in the database.\n");
	}
	else
	{
		prot("Unicode will NOT be used in the database.\n");
	}
}
/*------------------------------------------------------------------------*/
void database_info_own()
/*
	gives an output of the database parameters for the current connection to the prot-file
*/
{
	pstmt = conn->createPreparedStatement();
	CE("database_info_own: createPreparedStatement", 1, conn->ERRORCODE, conn->ERRORTEXT);
	
	returncode = checkPreparedStatement(pstmt, "SELECT SESSIONID, APPLICATIONPROCESS, USERNAME, TASKID, UID INTO ?, ?, ?, ?, ? FROM SESSIONS WHERE OWN='YES'");
	CE("database_info_own: prepare", 1, pstmt->ERRORCODE, pstmt->ERRORTEXT);

	SQLDBC_Length indi1=sizeof(DATABASE_SESSIONID), indi2=sizeof(DATABASE_APPLICATIONPROCESS), indi3=sizeof(DATABASE_USERNAME),
		indi4=sizeof(DATABASE_TASKID), indi5=sizeof(DATABASE_UID);
	
	returncode = pstmt->bindParameter (1, SQLDBC_HOSTTYPE_ASCII, &DATABASE_SESSIONID, &indi1, sizeof(DATABASE_SESSIONID));
	if (returncode != SQLDBC_OK) prot("error with bindParameter in database_info_own (col 1): %d = %s (rc = %d)\n", pstmt->ERRORCODE, pstmt->ERRORTEXT, returncode);
	returncode = pstmt->bindParameter (2, SQLDBC_HOSTTYPE_ASCII, &DATABASE_APPLICATIONPROCESS, &indi2, sizeof(DATABASE_APPLICATIONPROCESS));
	if (returncode != SQLDBC_OK) prot("error with bindParameter in database_info_own (col 2): %d = %s (rc = %d)\n", pstmt->ERRORCODE, pstmt->ERRORTEXT, returncode);
	returncode = pstmt->bindParameter (3, SQLDBC_HOSTTYPE_ASCII, &DATABASE_USERNAME, &indi3, sizeof(DATABASE_USERNAME));
	if (returncode != SQLDBC_OK) prot("error with bindParameter in database_info_own (col 3): %d = %s (rc = %d)\n", pstmt->ERRORCODE, pstmt->ERRORTEXT, returncode);
	returncode = pstmt->bindParameter (4, SQLDBC_HOSTTYPE_ASCII, &DATABASE_TASKID, &indi4, sizeof(DATABASE_TASKID));
	if (returncode != SQLDBC_OK) prot("error with bindParameter in database_info_own (col 4): %d = %s (rc = %d)\n", pstmt->ERRORCODE, pstmt->ERRORTEXT, returncode);
	returncode = pstmt->bindParameter (5, SQLDBC_HOSTTYPE_ASCII, &DATABASE_UID, &indi5, sizeof(DATABASE_UID));
	if (returncode != SQLDBC_OK) prot("error with bindParameter in database_info_own (col 5): %d = %s (rc = %d)\n", pstmt->ERRORCODE, pstmt->ERRORTEXT, returncode);
	
	returncode = pstmt->execute();
	CE("database_info_own: SELECT ... FROM SESSIONS WHERE OWN='YES'", 1, pstmt->ERRORCODE, pstmt->ERRORTEXT);
	
	prot(" SESSIONID = %s, APPLICATIONPROCESS = %s, USERNAME = %s, TASKID = %s, UID = %s\n", DATABASE_SESSIONID, DATABASE_APPLICATIONPROCESS, DATABASE_USERNAME, DATABASE_TASKID, DATABASE_UID);

	conn->releaseStatement(pstmt);
	pstmt = 0;
}
/*------------------------------------------------------------------------*/
SQLDBC_Retcode checkPreparedStatement(SQLDBC_PreparedStatement *ps, const char *st)
/*
	retry to prepare a statement until no error 500 - lock request timeout occurs
		that is necessary because in the kernel it is possible that a prepare failed
		e.g. when an insert has a long duration and a create index try to access that exclusiv lock
	a counter requests a sqldump if 500er errors are too much
*/
{
	SQLDBC_Retcode rc;
	const int maxPrepareErrors = 5;

	do
	{
		rc = ps->prepare(st);
		if ((ps->ERRORCODE == 500) || (ps->ERRORCODE == 400))
		{
			prepareErrors = prepareErrors + 1;
			prot("st = %s, rc = %d, ec = %d, prepareErrors = %d\n", st, rc, ps->ERRORCODE, prepareErrors);
		}
	}while((ps->ERRORCODE == 500) || (ps->ERRORCODE == 400));

	if (prepareErrors > maxPrepareErrors) 
	{
		prot("More than %d 'lock request timeout' (500) or 'lock collision' (400) occured: SQLDUMP is called\n", maxPrepareErrors);
		Sqldump(500, conn);
	}

	return rc;
}
