static char *sccs_id= (sccs_id, "@(#)IngresSql.c.sc	3.6 7/28/92");

#ifndef NO_INGRES

#include "Types.h"
#include "stdio.h"

extern "C" {
#include <strings.h>
}
#include "IngresSql.h"

EXEC SQL include sqlca;
EXEC SQL whenever sqlerror call HandleError;

static int n_ignore;

void HandleError()
{
    EXEC SQL begin declare section;
	char   errormsg[201];
    EXEC SQL end declare section;

    EXEC SQL copy sqlerror into :errormsg WITH 200;

    cerr << "SQL ERROR: " << errormsg;
    if (n_ignore) {
	--n_ignore;
	cerr << " (Ignored, " << n_ignore << " left)\n";
	return;
    }
    exit(-1);
}

void IgnoreSqlError(unsigned n)
{
  n_ignore= n;
}

int SqlErrorsLeft()
{
  return n_ignore;
}

EXEC SQL begin declare section;
   extern char *dbname;
   extern char crsr[10];
EXEC SQL end declare section;

//--- IngresSql ---------------------------------------------------------------

IngresSql::IngresSql(char *name)
{
    dbname = strdup(name);
    EXEC SQL connect :dbname;
    EXEC SQL SET lockmode session where readlock=nolock;
}

IngresSql::~IngresSql()
{
    EXEC SQL disconnect;
    free(dbname);
}

char* IngresSql::GetDbName()
{
    return dbname;
}

void IngresSql::CreateTable( char *tabname, Sql_Field *fs, int fs_size )
{
    EXEC SQL begin declare section;
	char cmd[1000];
    EXEC SQL end declare section;

    // From 'fs' build the 'strct'-string for ingres
    char strct[1000], tmp[1000];
    strct[0]= '\0';

    for (int i=0 ; i < fs_size ; i++) {
	switch (fs[i].type) {
	case cUndefinedType:
	    break;
	case cStrType:
	case cVStrType:
	    if (fs[i].length <= 0 || fs[i].length > 2000) {
		cerr << "ERROR: IngresSql::CreateTable : invalid stringsize ("
		     << fs[i].length << ")!\n";
		exit(-1);
	    }
	    sprintf(tmp, "%s %s(%d)", fs[i].name,
		fs[i].type == cStrType ? "char": "vchar",
		fs[i].length);
	    break;
	case cCharType:
	    sprintf(tmp, "%s char(1)", fs[i].name);
	    break;
	case cShortType:
	    sprintf(tmp, "%s integer2", fs[i].name);
	    break;
	case cLongType:
	    sprintf(tmp, "%s integer4", fs[i].name);
	    break;
	case cFloatType:
	    sprintf(tmp, "%s float4", fs[i].name);
	    break;
	case cDoubleType:
	    sprintf(tmp, "%s float8", fs[i].name);
	    break;
	}
	// Add ',' except the first time
        if (strct[0])	
            strcat(strct, ", ");   
        strcat(strct, tmp);
    }

    sprintf(cmd, "CREATE TABLE %s (%s)", tabname, strct);
    EXEC SQL execute immediate :cmd;
}

void IngresSql::Insert( char *tabname, Sql_Field *fs, int fs_size )
{
    const int MaxCmd=	5000;
    const int MaxVal=	MaxCmd - 100;

    EXEC SQL begin declare section;
	char cmd[MaxCmd];
    EXEC SQL end declare section;

    char val[MaxVal];
    char *lastp= val;

    for (int i=0 ; i < fs_size ; i++) {
        int tmplen = (fs[i].type==cStrType || fs[i].type==cVStrType) ?
		fs[i].length : 20;
	if (tmplen >= MaxVal-1) {
		cerr << "Insert: Field too long (" << tmplen << ")\n";
		return;
	}

	char tmp[MaxVal];

	switch (fs[i].type) {
	case cUndefinedType:
	    break;
	case cStrType:
	case cVStrType:
	    char *pch;
	    while (pch = (char*)strchr(fs[i].strval, '\''))
		*pch = '`';
            sprintf(tmp, "'%s'", fs[i].strval);
	    break;
	case cCharType:
	    if (fs[i].charval == '\'')
		fs[i].charval = '`';
	    sprintf(tmp, "'%c'", fs[i].charval);
	    break;
	case cShortType:
            sprintf(tmp, "%d", fs[i].shortval);
	    break;
	case cLongType:
            sprintf(tmp, "%ld", fs[i].longval);
	    break;
	case cFloatType:
            sprintf(tmp, "%f", fs[i].floatval);
	    break;
	case cDoubleType:
            sprintf(tmp, "%lf", fs[i].doubleval);
	    break;
	}

	int extra_len= strlen(tmp);
	if (lastp + extra_len >= val + MaxVal - 3) { // 3 = len('\0' + ',' + ' ')
		cerr << "Insert: Fields too long\n";
		return;
	}
        if (lastp != val) {
	    *lastp++= ','; *lastp++= ' ';
	}
        strcpy(lastp, tmp);
	lastp+= extra_len;
    }

    sprintf(cmd, "INSERT INTO %s VALUES (%s)", tabname, val);
    EXEC SQL execute immediate :cmd;
}

void IngresSql::DropTable( char *tabname )
{
    EXEC SQL begin declare section;
	char cmd[100];
    EXEC SQL end declare section;

/*###    EXEC SQL COMMIT;*/

    sprintf(cmd, "DROP TABLE %s", tabname);
    EXEC SQL execute immediate :cmd;
}


//--- IngresSqlSelect ---------------------------------------------------------

int gCursorId = 0;

IngresSqlSelect::IngresSqlSelect( char *tabname, char *whstr, char *selstr, eIngresMode ingmode, char* fn )
{
    ingresmode = ingmode;
    Bad= 0;

    if (ingresmode == cWriteMode) {	// Open file for writing
	if (!(ingfp = fopen(fn, "w"))) {
	    fprintf(stderr, "ERROR: IngresSqlSelect: Can't open '%s' for writing!\n", fn);
	    exit(-1);
	}
    }
    if (ingresmode == cReadMode) { 	// Open file for reading
	if (!(ingfp = fopen(fn, "r"))) {
	    fprintf(stderr, "ERROR: IngresSqlSelect: Can't open '%s' for reading!\n", fn);
	    exit(-1);
	}
    }

    // Allocate memory for the table-description
    sqlda = (IISQLDA *)malloc(   IISQDA_HEAD_SIZE
			      + (IISQDA_VAR_SIZE * SQLDA_VARS));
    sqlda->sqln = SQLDA_VARS;
    IISQLVAR *sqlvar;

    if (ingresmode != cReadMode) {
	EXEC SQL begin declare section;
	    char cmd[1000];
	EXEC SQL end declare section;
	EXEC SQL declare selcmd statement;

	tablename = (char*)strdup(tabname);
	wherestr = (whstr) ? (char*)strdup(whstr) : NULL;
	if (wherestr)
	    sprintf(cmd, "SELECT %s FROM %s WHERE (%s)",
						selstr, tabname, wherestr);
	else
	    sprintf(cmd, "SELECT %s FROM %s", selstr, tabname);

	IgnoreSqlError(1);	// Handle non existent table
	EXEC SQL prepare selcmd from :cmd;
	if (SqlErrorsLeft() < 1) {
		Bad= 1;
		return;
	}
	IgnoreSqlError(0);

	EXEC SQL describe selcmd into :sqlda;
	if (sqlda->sqld > sqlda->sqln) {
	    cerr << "ERROR IngresSql: Not enough memory allocated\n";
	    exit(-1);
	}

	sqlvar = sqlda->sqlvar;
	if (ingresmode == cWriteMode) {		// write vital sqlda
	    fprintf(ingfp, "%8s %ld %d %d\n", sqlda->sqldaid, sqlda->sqldabc,
					      sqlda->sqln, sqlda->sqld);
	    for (int i=0 ; i < sqlda->sqld ; i++) {
		fprintf(ingfp, "%d %d %d %.*s\n",
					  sqlvar[i].sqltype, sqlvar[i].sqllen,
					  sqlvar[i].sqlname.sqlnamel,
					  sqlvar[i].sqlname.sqlnamel,
					  sqlvar[i].sqlname.sqlnamec);
	    }
	}
    }
    else {
	// initialize the sqlda-struct
	// read: sqlda->sqldaid, sqlda->sqldabc, sqlda->sqln, sqlda->sqld
	fscanf(ingfp, "%8s%ld%hd%hd",
					sqlda->sqldaid, &sqlda->sqldabc,
					&sqlda->sqln, &sqlda->sqld);
	sqlvar = sqlda->sqlvar;
	for (int i=0 ; i < sqlda->sqld ; i++) {
	    fscanf(ingfp, "%hd%hd%hd%s",
				      &sqlvar[i].sqltype, &sqlvar[i].sqllen,
				      &sqlvar[i].sqlname.sqlnamel,
				      sqlvar[i].sqlname.sqlnamec);
	}
    }

    long memsize;
    for (int i=0 ; i < sqlda->sqld ; i++) {
	switch(abs(sqlvar[i].sqltype)) {
	case IISQ_CHA_TYPE:
	    memsize = sqlvar[i].sqllen + 1;
	    break;
	case IISQ_VCH_TYPE:
	    memsize = sqlvar[i].sqllen + 3; // 2 bytes length info + '\0'
	    break;
	case IISQ_INT_TYPE:
	    switch (sqlvar[i].sqllen) {
	    case 1:
		memsize = sizeof(char);
		break;
	    case 2:
		memsize = sizeof(short);
		break;
	    case 4:
		memsize = sizeof(long);
		break;
	    default:
		cerr << "ERROR IngresSql: invalid 'int'-length\n";
		exit(-1);
	    }
	    break;
	case IISQ_FLT_TYPE:
	    switch (sqlvar[i].sqllen) {
	    case 4:
		memsize = sizeof(float);
		break;
	    case 8:
		memsize = sizeof(double);
		break;
	    default:
		cout << "WARNING: invalid 'int'-length\n";
		exit(-1);
	    }
	    break;
	default:
	    cerr << "ERROR IngresSqlSelect: invalid sqltype " << sqlvar[i].sqltype << '\n';
	    exit(-1);
	}  
    
	sqlvar[i].sqldata = (char *)malloc(memsize+5);
	if (sqlvar[i].sqltype < 0)
	    sqlvar[i].sqlind = (short *)malloc(sizeof(short));
    }

    if (ingresmode != cReadMode) {
	sprintf(crsr, "%4d",  gCursorId++);
	EXEC SQL declare :crsr cursor for selcmd;
	EXEC SQL open :crsr for readonly;
    }
}

IngresSqlSelect::~IngresSqlSelect()
{
    if (!IsBad()) {

      if (ingresmode != cReadMode) 
	EXEC SQL close :crsr;

      IISQLVAR *sqlvar = sqlda->sqlvar;

      for (int i=0 ; i < sqlda->sqld ; i++) {
	free(sqlvar[i].sqldata);
	if (sqlvar[i].sqltype < 0)
            free(sqlvar[i].sqlind);
      }
    }

    free(sqlda);
    if (ingresmode != cReadMode) {
	free(tablename);
	if (wherestr)
	    free(wherestr);
    }

    if (ingresmode == cWriteMode || ingresmode == cReadMode)
	fclose(ingfp);
}

void IngresSqlSelect::ShowSqlda()
{
    IISQLVAR *sqlvar = sqlda->sqlvar;

    for (int i=0 ; i < sqlda->sqld ; i++) {
	cout << "NAME: " << sqlvar[i].sqlname.sqlnamec << ", ";

	switch(abs(sqlvar[i].sqltype)) {
	case IISQ_CHA_TYPE:
	    cout << " TYPE: char, LEN: " << sqlvar[i].sqllen << '\n';
	    break;
	case IISQ_VCH_TYPE:
	    cout << " TYPE: vchar, LEN: " << sqlvar[i].sqllen << '\n';
	    break;
	case IISQ_INT_TYPE:
	    cout << " TYPE: int, LEN: " << sqlvar[i].sqllen << '\n';
	    break;
	case IISQ_FLT_TYPE:
	    cout << " TYPE: float, LEN: " << sqlvar[i].sqllen << '\n';
	    break;
	default:
	    cout << '\n';
	    cerr << " ERROR ShowSqlda: invalid sqltype (" << sqlvar[i].sqltype << '\n';
		exit(-1);
	    }
    }   
    cout << '\n';
}

int IngresSqlSelect::operator()()
{
    IISQLVAR *sqlvar = sqlda->sqlvar;
    int eof = 0;

    switch (ingresmode) {
    case cNormalMode:					// Nothing ingresmode
	EXEC SQL fetch :crsr using descriptor sqlda;
	break;
    case cWriteMode:					// Write to file
	EXEC SQL fetch :crsr using descriptor sqlda;

	// Now write data to file
	for (int i=0 ; !sqlca.sqlcode && i < sqlda->sqld ; i++) {
	    switch(abs(sqlvar[i].sqltype)) {
	    case IISQ_CHA_TYPE:
		fprintf(ingfp, "%.*s\n", sqlvar[i].sqllen,
				    (char*)sqlda->sqlvar[i].sqldata);
		break;
	    case IISQ_VCH_TYPE:
		char *data= sqlda->sqlvar[i].sqldata;
		short len= * (short*) data;
		fprintf(ingfp, "%.*s\n", len, data+2);
		break;
	    case IISQ_INT_TYPE:
		switch (sqlvar[i].sqllen) {
		case 1:
		    fprintf(ingfp, "%c\n", (*(char*)sqlda->sqlvar[i].sqldata));
		    break;
		case 2:
		    fprintf(ingfp, "%d\n",(*(short*)sqlda->sqlvar[i].sqldata));
		    break;
		case 4:
		    fprintf(ingfp, "%ld\n",(*(long*)sqlda->sqlvar[i].sqldata));
		    break;
		}
		break;
	    case IISQ_FLT_TYPE:
		switch (sqlvar[i].sqllen) {
		case 4:
		    fprintf(ingfp, "%f\n",(*(float*)sqlda->sqlvar[i].sqldata));
		    break;
		case 8:
		    fprintf(ingfp, "%lf\n", (*(double*)sqlda->sqlvar[i].sqldata));
		    break;
		}
		exit(-1);
		break;
	    default:
		cout << '\n';
		cerr << " ERROR ShowSqlda: invalid sqltype ("
		     << sqlvar[i].sqltype << '\n';     
		exit(-1);
	    }
	}
	break;
    case cReadMode:					// Read from file
	// Now read data from file
	for (i=0 ; i < sqlda->sqld ; i++) {
	    switch(abs(sqlvar[i].sqltype)) {
	    case IISQ_CHA_TYPE:
	    case IISQ_VCH_TYPE:
		if (fgets(sqlda->sqlvar[i].sqldata,
			  sqlda->sqlvar[i].sqllen+1, ingfp) == NULL)
		    eof = 1;
		getc(ingfp);
		break;
	    case IISQ_INT_TYPE:
		switch (sqlvar[i].sqllen) {
		case 1:
		    if (fscanf(ingfp, "%c\n", (char*)sqlda->sqlvar[i].sqldata) == EOF)
			eof = 1;
		    break;
		case 2:
		    if (fscanf(ingfp,"%hd\n", (short*)sqlda->sqlvar[i].sqldata) == EOF)
			eof = 1;
		    break;
		case 4:
		    if (fscanf(ingfp, "%ld\n",(long*)sqlda->sqlvar[i].sqldata) == EOF)
			eof = 1;
		    break;
		}
		break;
	    case IISQ_FLT_TYPE:
		switch (sqlvar[i].sqllen) {
		case 4:
		    if (fscanf(ingfp, "%f\n",(float*)sqlda->sqlvar[i].sqldata) == EOF)
			eof = 1;
		    break;
		case 8:
		    if (fscanf(ingfp, "%lf\n", (double*)sqlda->sqlvar[i].sqldata) == EOF)
			eof = 1;
		    break;
		}
		exit(-1);
		break;
	    default:
		cout << '\n';
		cerr << " ERROR ShowSqlda: invalid sqltype ("
		     << sqlvar[i].sqltype << '\n';     
		exit(-1);
	    }
	}
	break;
    }

    return (!sqlca.sqlcode && !eof);
}

int IngresSqlSelect::GetRecordCnt()
{
    if (wherematch == -1) {
	IngresSqlSelect countsel(tablename, wherestr, "COUNT(*)");
	if (!countsel()) {
	    cerr << "ERROR GetRecordCnt: no count-value returned\n";
	    exit(-1);
	}
	if (countsel.GetFieldType(0) != cLongType) {
	    cerr << "ERROR GetRecordCnt: type of count-value not 'int'\n";
	    exit(-1);
	}
	wherematch = *((int *)countsel.GetFieldValue(0));
    }

    return wherematch;
}

eFieldType IngresSqlSelect::GetFieldType(int fld)
{
    if (fld >= GetFieldCnt()) {
	cerr << "ERROR GetFieldType: wrong 'fld'-value (" << fld << ")\n";
	exit(-1);
    }

    eFieldType type = cUndefinedType;
    IISQLVAR *sqlvar = sqlda->sqlvar;

    switch(abs(sqlvar[fld].sqltype)) {
    case IISQ_CHA_TYPE:
	type = cStrType;
	break;
    case IISQ_VCH_TYPE:
	type = cVStrType;
	break;
    case IISQ_INT_TYPE:
	switch (sqlvar[fld].sqllen) {
	case 1:
	    type = cCharType;
	    break;
	case 2:
	    type = cShortType;
	    break;
	case 4:
	    type = cLongType;
	    break;
	}
	break;
    case IISQ_FLT_TYPE:
	switch (sqlvar[fld].sqllen) {
	case 4:
	    type = cFloatType;
	    break;
	case 8:
	    type = cDoubleType;
	    break;
	}
	break;
    }

    return type;
}

eFieldType IngresSqlSelect::GetFieldType(char* fldname)
{
    IISQLVAR *sqlvar = sqlda->sqlvar;

    int fld = -1;
    for (int i=0 ; i < sqlda->sqld && fld == -1 ; i++) {
	if (!strncmp(sqlvar[i].sqlname.sqlnamec, fldname, sqlvar[i].sqlname.sqlnamel))
	    fld = i;
    }
    
    if (fld == -1) {
	cerr << "ERROR GetFieldType: '" << fldname << "' not found.\n";
	exit(-1);
    }

    return GetFieldType(fld);
}

void* IngresSqlSelect::GetFieldValue(int fld)
{
    if (fld >= GetFieldCnt()) {
	cerr << "ERROR GetFieldValue: wrong 'fld'-value (" << fld << ")\n";
	exit(-1);
    }

    if (abs(sqlda->sqlvar[fld].sqltype) == IISQ_VCH_TYPE) {
	char *data= sqlda->sqlvar[fld].sqldata;
	short len= * (short*) data;
	data[2+len]= '\0';
	return data+2;
    } else
	return sqlda->sqlvar[fld].sqldata;
}

void* IngresSqlSelect::GetFieldValue(char *fldname)
{
    IISQLVAR *sqlvar = sqlda->sqlvar;

    for (int i=0 ; i < sqlda->sqld; i++) {
	if (!strncmp(sqlvar[i].sqlname.sqlnamec, fldname, sqlvar[i].sqlname.sqlnamel))
	    return GetFieldValue(i);
    }
    
    cerr << "ERROR GetFieldValue: '" << fldname << "' not found.\n";
    exit(-1);
}

int IngresSqlSelect::GetFieldSize(int fld)
{
    if (fld >= GetFieldCnt()) {
	cerr << "ERROR GetFieldSize: wrong 'fld'-value (" << fld << ")\n";
	exit(-1);
    }

    return (int)sqlda->sqlvar[fld].sqllen;
}

int IngresSqlSelect::GetFieldSize(char *fldname)
{
    IISQLVAR *sqlvar = sqlda->sqlvar;

    int fld = -1;
    for (int i=0 ; i < sqlda->sqld && fld == -1 ; i++) {
	if (!strncmp(sqlvar[i].sqlname.sqlnamec, fldname, sqlvar[i].sqlname.sqlnamel))
	    fld = i;
	}
	
    if (fld == -1) {
	cerr << "ERROR GetFieldSize: '" << fldname << "' not found.\n";
	exit(-1); 
    }

    return (int)sqlda->sqlvar[fld].sqllen;
}

#endif
