/*
 *	$Source: /afs/sipb.mit.edu/project/sipb-athena/repository/src/moira/server/qrtn.dc,v $
 *	$Author: ghudson $
 *	$Header: /afs/sipb.mit.edu/project/sipb-athena/repository/src/moira/server/qrtn.dc,v 2.15 1996/06/02 07:44:46 ghudson Exp $
 *
 *	Copyright (C) 1987, 1988 by the Massachusetts Institute of Technology
 *	For copying and distribution information, please see the file
 *	<mit-copyright.h>.
 * 
 */

#ifndef lint
static char *rcsid_qrtn_dc = "$Header: /afs/sipb.mit.edu/project/sipb-athena/repository/src/moira/server/qrtn.dc,v 2.15 1996/06/02 07:44:46 ghudson Exp $";
#endif lint

#include <mit-copyright.h>
#include <string.h>
#include "query.h"
#include "mr_server.h"
EXEC SQL INCLUDE sqlca;  /* SQL Communications Area */
EXEC SQL INCLUDE sqlda;  /* SQL Descriptor Area */
#include "qrtn.h"

MR_SQLDA_T *SQLDA;
EXEC SQL BEGIN DECLARE SECTION; 
int mr_sig_length;
int idummy;                            
char cdummy[MR_CDUMMY_LEN];            
char stmt_buf[MR_STMTBUF_LEN];
EXEC SQL END DECLARE SECTION; 

char *Argv[QMAXARGS];

int ingres_errno = 0;
int mr_errcode = 0;
EXEC SQL BEGIN DECLARE SECTION; 
int query_timeout = 30;
char *database = "moira";
EXEC SQL END DECLARE SECTION;
extern char *whoami;
extern FILE *journal;

#define INGRES_BAD_DATE1  40206
#define INGRES_BAD_DATE2  40207
#define INGRES_DEADLOCK   49900
#define INGRES_TIMEOUT    39100
#define INGRES_BAD_COLUMN 30110
#define INGRES_ASGN_ERR   40204
#define INGRES_NO_CURSOR  30120
#define INGRES_NO_STMT    30130

/*
 * ingerr: Called when Ingres indicates an error.
 */

void ingerr()
{
    EXEC SQL BEGIN DECLARE SECTION; 
    char err_msg[256];
    EXEC SQL END DECLARE SECTION;
    ingres_errno = -sqlca.sqlcode;

    switch (ingres_errno) {
      case INGRES_BAD_DATE1:
      case INGRES_BAD_DATE2:
	mr_errcode = MR_DATE;
	break;
      case INGRES_DEADLOCK:
	mr_errcode = MR_DEADLOCK;
	com_err(whoami, 0, "INGRES deadlock detected");
	break;
      case INGRES_TIMEOUT:
/* May be something other than timeout! #39100 is "Unknown error" 
 * Really should parse the error message enough to decide if it's a timeout */
  	mr_errcode = MR_BUSY;
  	com_err(whoami, 0, "timed out getting lock");
  	break;
/* These should never come up unless someone breaks the query table */
      case INGRES_NO_CURSOR:
	if (mr_errcode != MR_BUSY &&
	    mr_errcode != MR_DEADLOCK)
	  mr_errcode = MR_INTERNAL;
	com_err(whoami, 0, "Cursor not opened");
	break;
      case INGRES_NO_STMT:
	mr_errcode = MR_INTERNAL;
	com_err(whoami, 0, "Statement not declared");
	break;
      case INGRES_BAD_COLUMN:
	mr_errcode = MR_INTERNAL;
	com_err(whoami, 0, "Bad column name in query table");
        break;
      case INGRES_ASGN_ERR:
	mr_errcode = MR_INTERNAL;
	com_err(whoami, 0, "Error in SQL assignment statement");
	break;
/* Others ??? */
      default:
	mr_errcode = MR_INGRES_ERR;
	com_err(whoami, MR_INGRES_ERR, " code %d\n", ingres_errno);
	EXEC SQL INQUIRE_SQL(:err_msg = errortext);
	com_err(whoami, 0, "SQL error text = %s", err_msg);
	critical_alert("MOIRA", "Moira server encountered INGRES ERROR %d", ingres_errno);
    }
}

/* This is declarative, not executed.  Applies from here on, in this file. */
EXEC SQL WHENEVER SQLERROR CALL ingerr;

int mr_open_database()
{
    register int i;
    MR_SQLDA_T *mr_alloc_SQLDA();
    static first_open = 1;

    if (first_open) {
	first_open = 0;

	/* initialize local argv */
	for (i = 0; i < 16; i++)
	  Argv[i] = malloc(ARGLEN);

	SQLDA = mr_alloc_SQLDA();       

	incremental_init();
	flush_cache();
    }
	
    ingres_errno = 0;
    mr_errcode = 0;

    /* open the database */
#ifsql INGRES
    EXEC SQL CONNECT :database;
    if(ingres_errno) 
      return (ingres_errno);
    EXEC SQL set lockmode session where level = table, timeout = :query_timeout;
    EXEC SQL set lockmode on capacls where readlock = shared;
    EXEC SQL set lockmode on alias where readlock = shared;
#endsql
#ifsql INFORMIX
    EXEC SQL DATABASE moira
#endsql

    if(ingres_errno) 
        return(mr_errcode);

    EXEC SQL SELECT SIZE(signature) INTO :mr_sig_length FROM users WHERE users_id=0; /* Harmless on second open */
    EXEC SQL COMMIT WORK;
    if(ingres_errno)
        return(mr_errcode);

    return(MR_SUCCESS);
}

int mr_close_database()
{
    flush_cache();
#ifsql INGRES
    EXEC SQL DISCONNECT;
#endsql
}

mr_check_access(cl, name, argc, argv_ro)
    client *cl;
    char *name;
    int argc;
    char *argv_ro[];
{
    struct query *q;
    struct query *get_query_by_name();

    ingres_errno = 0;
    mr_errcode = 0;

    q = get_query_by_name(name, cl->args->mr_version_no);
    if (q == (struct query *)0)
	return(MR_NO_HANDLE);

    return(mr_verify_query(cl, q, argc, argv_ro));    
}

mr_process_query(cl, name, argc, argv_ro, action, actarg)
    client *cl;
    char *name;
    int argc;
    char *argv_ro[];
    int (*action)();
    char *actarg;
{
    register struct query *q;
    register int status;
    register struct validate *v;
    char qual[256];
    char sort[32];
    char *pqual;
    char *psort;
    EXEC SQL BEGIN DECLARE SECTION; 
    char *table_name;
    EXEC SQL END DECLARE SECTION; 
    struct save_queue *sq;
    struct query *get_query_by_name();
    int sq_save_args();
    struct save_queue *sq_create();
    char *build_sort();

    ingres_errno = 0;
    mr_errcode = 0;

    /* list queries command */
    if (!strcmp(name, "_list_queries")) {
	list_queries(cl->args->mr_version_no, action, actarg);
	return(MR_SUCCESS);
    }

    /* help query command */
    if (!strcmp(name, "_help")) {
	if (argc < 1)
	    return(MR_ARGS);
	q = get_query_by_name(argv_ro[0], cl->args->mr_version_no);
	if (q == (struct query *)0) return(MR_NO_HANDLE);
	help_query(q, action, actarg);
	return(MR_SUCCESS);
    }

    /* get query structure, return error if named query does not exist */
    q = get_query_by_name(name, cl->args->mr_version_no);
    if (q == (struct query *)0) return(MR_NO_HANDLE);
    v = q->validate;

    /* setup argument vector, verify access and arguments */
    if ((status = mr_verify_query(cl, q, argc, argv_ro)) != MR_SUCCESS)
	goto out;

    /* perform any special query pre-processing */
    if (v && v->pre_rtn) {
	status = (*v->pre_rtn)(q, Argv, cl, 0);
	if (status != MR_SUCCESS)
	    goto out;
    }

    switch (q->type) {
    case RETRIEVE:
	/* for queries that do not permit wildcarding, check if row
	   uniquely exists */
	if (v && v->field) {
	    status = validate_row(q, Argv, v); 
	    if (status != MR_EXISTS) break;
	}

	/* build "where" clause if needed */
	if (q->qual) {
	    build_qual(q->qual, q->argc, Argv, qual); 
	    pqual = qual;
	} else {
	    pqual = 0;
	}

	/* build "sort" clause if needed */
	if (v && v->valobj) {
	    psort = build_sort(v, sort);
	} else {
	    psort = 0;
	}

	/* if there is a followup routine, then we must save the results */
	/* of the first query for use by the followup routine */
	/* if q->rvar = NULL, perform post_rtn only */
	if (q->rvar) {
	    if (v && v->post_rtn) {
		sq = sq_create();
		status = do_retrieve(q, pqual, psort, sq_save_args, sq);
		if (status != MR_SUCCESS) {
		    sq_destroy(sq);
		    break;
		}
		status = (*v->post_rtn)(q, sq, v, action, actarg, cl);
	    } else {
		/* normal retrieve */
		status = do_retrieve(q, pqual, psort, action, actarg);
	    }
	    if (status != MR_SUCCESS) break;
	} else {
	    status = (*v->post_rtn)(q, Argv, cl, action, actarg);
	}

	break;

    case UPDATE:
	/* see if row already exists */
	if (v->field) {
	    status = validate_row(q, Argv, v);
	    if (status != MR_EXISTS) break;
	}

	/* build "where" clause and perform update */
	/* if q->rvar = NULL, perform post_rtn only */
	if (q->rvar) {
	    build_qual(q->qual, q->argc, Argv, qual);
	    incremental_before(q->rtable, qual, argv_ro);
	    status = do_update(q, &Argv[q->argc], qual, action, actarg);
	    incremental_after(q->rtable, qual, argv_ro);
	    if (status != MR_SUCCESS) break;
	    flush_name(argv_ro[0], q->rtable);
	    table_name = q->rtable;
	    if (strcmp(q->shortname, "sshi") && strcmp(q->shortname, "ssif")) {
		EXEC SQL UPDATE tblstats
                  SET updates = updates + 1, modtime = 'now'
		  WHERE table_name = :table_name;
	    }
	}

	/* execute followup routine (if any) */
	if (v->post_rtn) status = (*v->post_rtn)(q, Argv, cl);

	break;

    case APPEND:
	/* see if row already exists */
	if (v->field) {
	    status = validate_row(q, Argv, v);
	    if (status != MR_NO_MATCH) break;
	}

	/* build "where" clause if needed */
	if (q->qual) {
	    build_qual(q->qual, q->argc, Argv, qual);
	    pqual = qual;
	} else {
	    pqual = 0;
	}

	/* perform the append */
	/* if q->rvar = NULL, perform post_rtn only */
	if (q->rvar) {
	    incremental_clear_before();
	    status = do_append(q, &Argv[q->argc], pqual, action, actarg);
	    if (status != MR_SUCCESS) break;
	    if (v && v->object_id) {
		sprintf(qual, "%s.%s = %s",q->rvar, v->object_id, 
			Argv[q->argc+q->vcnt]);
		incremental_after(q->rtable, qual, argv_ro);
	    } else
	      incremental_after(q->rtable, pqual, argv_ro);

	    table_name = q->rtable;
	    EXEC SQL UPDATE tblstats
	      SET appends = appends + 1, modtime = 'now'
	      WHERE table_name = :table_name;
	}
	
	/* execute followup routine */
	if (v->post_rtn) status = (*v->post_rtn)(q, Argv, cl);
	break;

    case DELETE:
	/* see if row already exists */
	if (v->field) {
	    status = validate_row(q, Argv, v);
	    if (status != MR_EXISTS) break;
	}

	/* build "where" clause and perform delete */
	/* if q->rvar = NULL, perform post_rtn only */
	if (q->rvar) {
	    build_qual(q->qual, q->argc, Argv, qual);
	    table_name = q->rtable;
     	    incremental_before(q->rtable, qual, argv_ro);
	    status = do_delete(q, qual, action, actarg);
	    incremental_clear_after();
	    if (status != MR_SUCCESS) break;
	    flush_name(argv_ro[0], q->rtable);
	    EXEC SQL UPDATE tblstats
	      SET deletes = deletes + 1, modtime = 'now'
	      WHERE table_name = :table_name;
	}

	/* execute followup routine */
	if (v->post_rtn) status = (*v->post_rtn)(q, Argv, cl);
	break;

    }

out:
    if (status == MR_SUCCESS && ingres_errno != 0) {
	com_err(whoami, MR_INTERNAL, "Server didn't notice INGRES ERROR %d",
		       ingres_errno);
	status = mr_errcode;
    }

    if (q->type == RETRIEVE) {
        EXEC SQL COMMIT WORK;
    } else {
        if (status == MR_SUCCESS) {
        EXEC SQL COMMIT WORK;
	    if (journal) {
		char buf[1024], *bp;
		int i;
		extern time_t now;

		fprintf(journal, "%% %s %s %s",
			cl->clname, cl->entity, ctime(&now));
		fprintf(journal, "%s[%d] ", q->name, cl->args->mr_version_no);
		for (i = 0; i < argc; i++) {
		    if (i != 0) {
			putc(' ', journal);
		    }
		    requote(buf, argv_ro[i], sizeof(buf));
		    fputs(buf, journal);
		}
		putc('\n', journal);
		fflush(journal);
	    }
	    incremental_update();
	} else {
	    cache_abort();
	    if (ingres_errno != INGRES_DEADLOCK) {
                EXEC SQL ROLLBACK WORK;
	    }
	    incremental_flush();
	}
    }
    cache_commit(); /* commit following abort is safe */

    if (status != MR_SUCCESS && log_flags & LOG_RES)
	com_err(whoami, status, " (Query failed)");
    return(status);
}

build_qual(fmt, argc, argv, qual)
	char *fmt;
	int argc;
	char *argv[];
	char *qual;
{
    register char *c;
    register int i;
    char *args[4];

    c = fmt;
    for (i = 0; i < argc; i++) {
	c = index(c, '%');
	if (c++ == (char *)0) return(MR_ARGS);
	if (*c == 's')
	    args[i] = argv[i];
	else if (*c == 'd')
	    *(int *)&args[i] = *(int *)argv[i];	/* sigh */
	else
	    return(MR_INGRES_ERR);
    }
    if (c = index(c, '%')) {
	args[i] = args[i - 1];
    }

    switch (argc) {
    case 0:
	strcpy(qual, fmt);
	break;

    case 1:
	sprintf(qual, fmt, args[0]);
	break;

    case 2:
	sprintf(qual, fmt, args[0], args[1]);
	break;

    case 3:
	sprintf(qual, fmt, args[0], args[1], args[2]);
	break;

    case 4:
	sprintf(qual, fmt, args[0], args[1], args[2], args[3]);
	break;

    default:
	com_err(whoami, MR_INTERNAL,
		"Internal arg count error processing query");
	return(MR_INTERNAL);
    }
    return(MR_SUCCESS);
}

char *
build_sort(v, sort)
    register struct validate *v;
    char *sort;
{
    register struct valobj *vo;
    register int n;
    char elem[16];
#ifdef _DEBUG_MALLOC_INC
#undef index
#endif

    n = v->objcnt;
    vo = v->valobj;
    *sort = 0;

    while (--n >= 0) {
	if (vo->type == V_SORT) {
	    sprintf(elem, "%d", vo->index + 1);    /* Result column number */
	    if (*sort) strcat(sort, ", ");
	    strcat(sort, elem);
	}
	vo++;
    }

    return ((*sort) ? sort : 0);
}


/* Build arguement vector, verify query and arguments */

int privileged;

mr_verify_query(cl, q, argc, argv_ro)
    client *cl;
    struct query *q;
    int argc;
    char *argv_ro[];
{
    register int argreq;
    register int status;
    register struct validate *v = q->validate;
    register int i;
    register char *to,*fr,*stop;

    privileged = 0;

    /* copy the arguments into a local argv that we can modify */
    if (argc >= QMAXARGS)
      return(MR_ARGS);
    for (i = 0; i < argc; i++) {
	/* Single quotes must be doubled for SQL */
	for (to=Argv[i], fr=argv_ro[i], stop=to+ARGLEN; (*fr) && (to<stop);) {
	    if(*fr=='\'') 
	      *to++ = *fr;
	    *to++ = *fr++;
	}
	if (*fr) 
	  return(MR_ARG_TOO_LONG);
	*to='\0';

	if (*--to == '\\')
	  return(MR_BAD_CHAR);
    }

    /* check initial query access */
    status = check_query_access(q, Argv, cl);
    if (status != MR_SUCCESS && status != MR_PERM)
	return(status);
    if (status == MR_SUCCESS)
	privileged++;

    /* check argument count */
    argreq = q->argc;
    if (q->type == UPDATE || q->type == APPEND) argreq += q->vcnt;
    if (argc != argreq) return(MR_ARGS);

    /* validate arguments */
    if (v && v->valobj) {
	status = validate_fields(q, Argv, v->valobj, v->objcnt);
	if (status != MR_SUCCESS) return(status);
    }

    /* perform special query access check */
    if (!privileged && v && v->acs_rtn) {
	status = (*v->acs_rtn)(q, Argv, cl);
	if (status != MR_SUCCESS && status != MR_PERM)
	    return(status);
	if (status == MR_SUCCESS)
	    return(MR_SUCCESS);
    }

    return(privileged ? MR_SUCCESS : MR_PERM);
}


/* This routine caches info from the database.  Each query acl is stored
 * in the query structure, and whether that acl contains everybody.
 */

check_query_access(q, argv, cl)
    struct query *q;
    char *argv[];
    client *cl;
{
    EXEC SQL BEGIN DECLARE SECTION; 
    char *name;
    int acl_id;
    int rowcount;
    int errorno;
    static int def_uid;
    EXEC SQL END DECLARE SECTION; 
    int status;
    int client_id;
    char *client_type;

    /* initialize default uid */
    if (def_uid == 0) {
        EXEC SQL SELECT users_id INTO :def_uid FROM users WHERE login='default';
    }

    /* get query access control list */
    if (q->acl != 0)
      acl_id = q->acl;
    else {
	name = q->shortname;
	EXEC SQL SELECT list_id INTO :acl_id FROM capacls WHERE tag = :name;
        if (sqlca.sqlcode < 0) return(MR_INGRES_ERR);
        if (sqlca.sqlcode == 100) return(MR_PERM);
	q->acl = acl_id;

	/* check for default access */
        EXEC SQL SELECT member_id INTO :acl_id FROM imembers
          WHERE list_id = :acl_id AND member_type = 'USER' 
	    AND member_id = :def_uid;
        if (sqlca.sqlerrd[2] == 0)
          q->everybody = 0;
        else
          q->everybody = 1;
    }

    if (q->everybody)
      return(MR_SUCCESS);

    if (get_client(cl, &client_type, &client_id) != MR_SUCCESS)
      return(MR_PERM);
    if (find_member("LIST", acl_id, client_type, client_id, 0))
      return(MR_SUCCESS);
    else
      return(MR_PERM);
}


get_client(cl, client_type, client_id)
    client *cl;
    char **client_type;
    int *client_id;
{
    if (cl->users_id > 0) {
	*client_id = cl->users_id;
	*client_type = "USER";
	return(MR_SUCCESS);
    }

    if (cl->client_id < 0) {
	*client_id = -cl->users_id;
	*client_type = "KERBEROS";
	return(MR_SUCCESS);
    }

    return(MR_PERM);
}

find_member(list_type, list_id, member_type, member_id)
    char *list_type;
    EXEC SQL BEGIN DECLARE SECTION; 
    int list_id;
    char *member_type;
    int member_id;
    EXEC SQL END DECLARE SECTION;
{
    EXEC SQL BEGIN DECLARE SECTION; 
    int flag, errorno;
    EXEC SQL END DECLARE SECTION; 

    if (!strcmp(strtrim(list_type), strtrim(member_type)) &&
	list_id == member_id)
	return(1);

    /* see if client is a direct member of list */
    flag = 0;
    EXEC SQL SELECT member_id INTO :flag FROM imembers
      WHERE list_id = :list_id AND member_type = :member_type 
	AND member_id = :member_id;
    if(flag!=0) flag=1;             /** Not strictly necessary */
    if (sqlca.sqlcode == 0)
      return(flag);            
    return(0);
}


do_retrieve(q, pqual, psort, action, actarg)            
    register struct query *q;
EXEC SQL BEGIN DECLARE SECTION; 
    char *pqual;
    char *psort;
EXEC SQL END DECLARE SECTION; 
    int (*action)();
    char *actarg;
{
    static char **vaddrs = (char **)NULL;
    int j, rowcount;

    if (!vaddrs) {
	register int i;

	if ((vaddrs = (char **)malloc(sizeof(char *) * QMAXARGS)) == NULL) {
	    com_err(whoami, MR_NO_MEM, "setting up static argv");
	    exit(1);
	}
	for (i = 0; i < QMAXARGS; i++) {  
	    vaddrs[i]=SQLDA->sqlvar[i].sqldata;
	}
    }

    build_sql_stmt(stmt_buf,"SELECT",q->tlist,vaddrs,pqual);
    if(psort) { strcat(stmt_buf," ORDER BY "); strcat(stmt_buf,psort); }
    EXEC SQL PREPARE stmt INTO :SQLDA USING NAMES FROM :stmt_buf; 
    if(sqlca.sqlcode)
      return(MR_INTERNAL);
    EXEC SQL DECLARE csr001 CURSOR FOR stmt;
    EXEC SQL OPEN csr001;
    rowcount = 0;
    while(1) {
	EXEC SQL FETCH csr001 USING DESCRIPTOR :SQLDA;
	if(sqlca.sqlcode != 0) break;
	(*action)(q->vcnt, vaddrs, actarg);
	rowcount++;
    }
    EXEC SQL CLOSE csr001;

    if (mr_errcode) return(mr_errcode);
    return ((rowcount == 0) ? MR_NO_MATCH : MR_SUCCESS);
}

char *sqlstrstr(str,pat)
    char *str;
    char *pat;
{
    register char *p=pat;
    
    do {
	if(*str=='\'') {    /* Skip over single-quote delimited substrings */ 
	    while(*++str && (*str!='\'')) 
		; 
	    continue;
	}
	if(*str==*p) {
	    register char *s;
	    s=str; 
	    while(*++p && (*++s==*p))
		; 
	    if(*p) p=pat;  /* failed */
	}
    } while(*p && *++str);

    if(!*str) str=NULL;
    return(str);
}

void optimize_sql_stmt(buf)
char *buf;
{
    char *point=buf, *pat, *eopat, *esc1, *esc2, *csr;

    for(point=buf; point=sqlstrstr(point,"LIKE"); point++)  {
	/* Now pointing to string "LIKE" */

	/* Look at next word */
	for(pat=point+4; *pat==' '; pat++) ;

	/* Is it a single-quote delimited string? */
	if(*pat!='\'') continue;

	/* look for "escape" clause - save escape character */
	/* 1. Find end of pattern */
	for(eopat=pat+1; 1; eopat++) {
	    if(*eopat=='\'') {
		if(eopat[1]=='\'')  /* single-quote is self-escaping */
		    eopat++;
		else
		    break;
	    }
	}

	/* 2. Look at next word */
	for(esc1=eopat; *++esc1==' ';) ;

	/* 3. esc1=0 if not "ESCAPE '?'", where the ? may be any character. */
	if(strncmp(esc1,"ESCAPE",6)) esc1=NULL;

	if(esc1) {
	    for(esc2=esc1+6; *esc2==' '; esc2++) ;
	    
	    if(*esc2++!='\'') continue; /* Bad SQL syntax. Skip. */
	    /* esc2 now points at the escape character itself */
	    if(esc2[1]!='\'') continue; /* Weird escape string. Skip. */
	} else {
	    esc2="\\";
	}

	/* Is pattern free from special characters? */
	for(csr=pat; csr<eopat; csr++) 
	    if((*csr=='%') || (*csr=='_') || (*csr==*esc2)) break;
	if(csr!=eopat) continue; /* Uses pattern matching. Skip. */

	/* Optimize the query statement */
	/* 1. Change "LIKE" to " =  " */
	memcpy(point," =  ",4);
	
	/* 2. Change "ESCAPE" to "      " */
	if(esc1) {
	    memset(esc1,' ',6);
	    /* 3. Change  "'*'" to "   " */
	    /*    (Changes '''' to "    ") */
	    if(esc2) memset(esc2-1,' ',(*esc2=='\'')?4:3); 
	}
    }
}

build_sql_stmt(result_buf,cmd,targetlist,argv,qual)
    char *result_buf;
    char *cmd;
    char *targetlist;
    char *argv[];
    char *qual;
{
    char fmt_buf[MR_STMTBUF_LEN];
    register char *res, *fmt;

    if(qual)
      sprintf(fmt_buf,"%s %s WHERE %s",cmd,targetlist,qual);
    else
      sprintf(fmt_buf,"%s %s",cmd,targetlist);

    for(res=result_buf, fmt=fmt_buf; *fmt; fmt++) {
	if(*fmt=='%') {
	    if(*++fmt) {
		switch(*fmt) {                       
		  case '%':                              /* %% -> % */
		    *res++ = *fmt;           
		    break;
		  case 's':
		    if(*argv[0]) {
			*res='\0';
			strcat(res,*argv);
			while(*++res) ;
		    }
		    argv++;
		    break;
		  case 'd':
		    sprintf(res,"%d",*(int *)*argv++);   /* print to result buffer */
		    while(*++res) ;
		    break;
		  default:                               /* Swallow other %? pairs */
		    break;
		}
	    } else break;
	} else *res++ = *fmt;                            /* text -> result buffer */
    }
    *res='\0';

    optimize_sql_stmt(result_buf);
}

do_update(q, argv, qual, action, actarg)
    register struct query *q;
    char *argv[];
    char *qual;
    int (*action)();
    char *actarg;
{
    build_sql_stmt(stmt_buf,"UPDATE",q->tlist,argv,qual);
    EXEC SQL EXECUTE IMMEDIATE :stmt_buf;
    if (mr_errcode) return(mr_errcode);
    return(MR_SUCCESS);
}

do_append(q, argv, pqual, action, actarg)
    register struct query *q;
    char *argv[];
    char *pqual;
    int (*action)();
    char *actarg;
{
    build_sql_stmt(stmt_buf,"INSERT",q->tlist,argv,pqual);
    EXEC SQL EXECUTE IMMEDIATE :stmt_buf;
    if (mr_errcode) return(mr_errcode);
    return(MR_SUCCESS);
}

do_delete(q, qual, action, actarg)
    register struct query *q;
    char *qual;
    int (*action)();
    char *actarg;
{
    sprintf(stmt_buf,"DELETE FROM %s WHERE %s",q->rtable,qual);
    EXEC SQL EXECUTE IMMEDIATE :stmt_buf;
    if (mr_errcode) return(mr_errcode);
    return(MR_SUCCESS);
}


/**
 ** set_next_object_id - set next object id in values table
 **
 ** Inputs: object - object name in values table and in objects
 **	    table - name of table objects are found in
 **	    limit - should the ID be range limited
 **
 ** - called before an APPEND operation to set the next object id to
 **   be used for the new record to the next free value
 **
 **/

set_next_object_id(object, table_name, limit)
    EXEC SQL BEGIN DECLARE SECTION; 
    char *object;
    char *table_name;
    int limit;
    EXEC SQL END DECLARE SECTION; 
{
    EXEC SQL BEGIN DECLARE SECTION; 
    int value;
    EXEC SQL END DECLARE SECTION;
    int starting_value;
    
    EXEC SQL SELECT value INTO :value FROM numvalues WHERE name = :object;
    if (sqlca.sqlerrd[2] != 1)
	return(MR_NO_ID);

    starting_value=value;
    while (1) { 
	if (limit && value > MAX_ID_VALUE) 
	    value = MIN_ID_VALUE;

	sprintf(stmt_buf,"SELECT %s FROM %s WHERE %s=%d",object,table_name,object,value);  
	EXEC SQL PREPARE stmt INTO :SQLDA USING NAMES FROM :stmt_buf; 
	if(sqlca.sqlcode)
	  return(MR_INTERNAL);
	EXEC SQL DECLARE csr002 CURSOR FOR stmt;
	EXEC SQL OPEN csr002;
	EXEC SQL FETCH csr002 USING DESCRIPTOR :SQLDA;
	if (sqlca.sqlcode < 0) return(mr_errcode);
	if (sqlca.sqlcode == 100) break;

	EXEC SQL CLOSE csr002;
	value++;
	if (limit && value == starting_value) {   
	    com_err(whoami,0,"All id values have been used");
	    return(MR_NO_ID);
	}
    }
    EXEC SQL CLOSE csr002;

    if (LOG_RES)
        com_err(whoami, 0, "setting ID %s to %d", object, value);
    EXEC SQL UPDATE numvalues SET value = :value WHERE name = :object;
    return(MR_SUCCESS);
}


/* Turn a kerberos name into the user's ID of the account that principal
 * owns.  Sets the kerberos ID and user ID.
 */

int set_krb_mapping(name, login, ok, kid, uid)
char *name;
char *login;
int ok;
int *kid;
int *uid;
{
    EXEC SQL BEGIN DECLARE SECTION; 
    int u_id, k_id;
    char *krbname;
    EXEC SQL END DECLARE SECTION; 

    krbname = name;
    *kid = 0;
    *uid = 0;

    EXEC SQL SELECT km.users_id, km.string_id INTO :u_id, :k_id
      FROM krbmap km, strings str
      WHERE km.string_id = str.string_id AND str.string = :krbname;
    EXEC SQL COMMIT WORK;

    if (ingres_errno) return(mr_errcode);
    
    if (sqlca.sqlerrd[2] == 1) {    /* rowcount */
	*kid = -k_id;
	*uid = u_id;
	return(MR_SUCCESS);
    }

    if (name_to_id(name, "STRINGS", &k_id) == MR_SUCCESS)
      *kid = -k_id;

    if (!ok) {             
	*uid = *kid;
	return(MR_SUCCESS);
    }

    if (name_to_id(login, "USERS", uid) != MR_SUCCESS)
      *uid = 0;

    if (*kid == 0)
      *kid = *uid;
    if (ingres_errno) return(mr_errcode);
    return(MR_SUCCESS);
}


/* For now this just checks the argc's.  It should also see that there
 * are no duplicate names.
 */

sanity_check_queries()
{
    register int i;
    int maxv = 0, maxa = 0;
#ifdef MULTIPROTOCOLS
    extern int QueryCount1, QueryCount2;
    extern struct query Queries1[], Queries2[];
#else
    extern int QueryCount2;
    extern struct query Queries2[];
#endif MULTIPROTOCOLS

#define MAX(x,y) ((x) > (y) ? (x) : (y))

#ifdef MULTIPROTOCOLS
    for (i = 0; i < QueryCount1; i++) {
	maxv = MAX(maxv, Queries1[i].vcnt);
	maxa = MAX(maxa, Queries1[i].argc);
    }
#endif MULTIPROTOCOLS
    for (i = 0; i < QueryCount2; i++) {
	maxv = MAX(maxv, Queries2[i].vcnt);
	maxa = MAX(maxa, Queries2[i].argc);
    }
    if (MAX(maxv, maxa) > QMAXARGS) {
	com_err(whoami, 0, "A query has more args than QMAXARGS");
	exit(1);
    }
}

/* eof:qrtn.dc */
