/*-------------------------------------------------------------------------
 *
 * regexp.c--
 *    regular expression handling code.
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *    /usr/local/devel/pglite/cvs/src/backend/utils/adt/regexp.c,v 1.13 1995/09/03 05:26:59 jolly Exp
 *
 *
 *      Alistair Crooks added the code for the regex caching
 *	agc - cached the regular expressions used - there's a good chance
 *	that we'll get a hit, so this saves a compile step for every
 *	attempted match. I haven't actually measured the speed improvement,
 *	but it `looks' a lot quicker visually when watching regression
 *	test output.
 *
 * OLD COMMENTS:
 *	Uses the old AT&T compile/step code.  While this is not very
 *	politically-correct (it has been removed from 4.x BSD) it is
 *	the most portable C library solution since everyone names their
 *	own regular expression routines something different (applying
 *	different standards)...
 *
 *-------------------------------------------------------------------------
 */
#include <string.h>
#include "postgres.h"		/* postgres system include file */
#include "utils/elog.h"		/* for logging postgres errors */
#include "utils/palloc.h"
#include "utils/builtins.h"	/* where the function declarations go */

/*
 *  macros to support the regexp(3) library calls
 */
#define INIT		register char *pg = instring;
#define GETC()		(*pg++)
#define PEEKC()		*(pg+1)
#define	UNGETC(c)	(*--pg = (c))
#define	RETURN(v)	return(v)
#define	ERROR(val)	elog(WARN, "regexp library reports error %d", (val));

#ifndef __linux__
#define	EXPBUFSZ	256
#else
#define EXPBUFSZ (sizeof (regex_t) + 4)
#endif
#define	P2CHARLEN	2
#define	P4CHARLEN	4
#define	P8CHARLEN	8
#define	P16CHARLEN	16

#if defined(DISABLE_XOPEN_NLS)
#undef _XOPEN_SOURCE
#endif /* DISABLE_XOPEN_NLS */

#ifndef WIN32

/* distinguish using regex.h and regexp.h */
#ifdef USE_REGEX
#include <sys/types.h>
#include <regex.h>
#ifndef REG_BASIC
#define REG_BASIC 0
#endif /* REG_BASIC */
#else
#include <regexp.h>
#endif  /* USE_REGEX */

#endif /* WIN32 why is this necessary? */

#ifdef USE_REGEX

/* this is the number of cached regular expressions held. */
#ifndef MAX_CACHED_RES
#define MAX_CACHED_RES	32
#endif

/* this structure describes a cached regular expression */
struct cached_re_str {
        struct varlena  *cre_text;       /* pattern as a text* */
	char		*cre_s;		/* pattern as null-terminated string */
	/*int		cre_type;*/	/* compiled-type: basic,extended etc */
	regex_t		cre_re;		/* the compiled regular expression */
	unsigned long	cre_lru;	/* lru tag */
};

static int			rec = 0;	        /* # of cached re's */
static struct cached_re_str	rev[MAX_CACHED_RES];	/* cached re's */
static unsigned long		lru;			/* system lru tag */

/* attempt to compile `re' as an re, then match it against text */
static int
RE_compile_and_execute(struct varlena *text_re, char *text)
{
	int	oldest;
	int	n;
	int	i;
	char    *re;
	int regcomp_result;

	/* check for direct pointer equality, 
	   this can happen when a constant is being used for comparison */
	for (i=0; i< rec;i++) {
	    if ( rev[i].cre_text == text_re )
		return(regexec(&rev[i].cre_re, text, 0,
			       (regmatch_t *) NULL, 0) == 0);
	}

	re = textout(text_re);
	/* find a previously compiled regular expression */
	for (i = 0 ; i < rec ; i++) {
	    if (rev[i].cre_s &&	strcmp(rev[i].cre_s,re) == 0 ) {
		rev[i].cre_lru = ++lru;
		pfree(re);
		return(regexec(&rev[i].cre_re, text, 0,
			       (regmatch_t *) NULL, 0) == 0);
	    }
	}
	/* we didn't find it - make room in the cache for it */
	if (rec == MAX_CACHED_RES) {
		/* cache is full - find the oldest entry */
		for (oldest = 0, i = 1 ; i < rec ; i++) {
			if (rev[i].cre_lru < rev[oldest].cre_lru) {
				oldest = i;
			}
		}
	} else {
		oldest = rec++;
	}
	/* if there was an old re, then de-allocate the space it used */
	if (rev[oldest].cre_s != (char *) NULL) {
		for (lru = i = 0 ; i < rec ; i++) {
			rev[i].cre_lru =
				(rev[i].cre_lru - rev[oldest].cre_lru) / 2;
			if (rev[i].cre_lru > lru) {
				lru = rev[i].cre_lru;
			}
 		}
		regfree(&rev[oldest].cre_re);
		/* use malloc/free for the cre_s field because the storage
		   has to persist across transactions */
		free(rev[oldest].cre_s); 
	}
	/* compile the re */
	regcomp_result = regcomp(&rev[oldest].cre_re, re, REG_BASIC);
	if ( regcomp_result == 0) {
		n = strlen(re);
		/* use malloc/free for the cre_s field because the storage
		   has to persist across transactions */
		rev[oldest].cre_s = (char *) malloc(n + 1);
		(void) memmove(rev[oldest].cre_s, re, n);
		rev[oldest].cre_s[n] = 0;
	        rev[oldest].cre_text = text_re;
		rev[oldest].cre_lru = ++lru;
		pfree(re);
		return(regexec(&rev[i].cre_re, text, 0,
						(regmatch_t *) NULL, 0) == 0);
	} else {
	    char errMsg[1000];
	    /* re didn't compile */
	    rev[oldest].cre_s = (char *) NULL;
	    regerror(regcomp_result, &rev[oldest].cre_re, errMsg, 1000);
	    elog(WARN,"regcomp failed with error %s",errMsg);
	}
	/* not reached */
	return(0);
}
#endif /* USE_REGEX */



/*
 *  interface routines called by the function manager
 */

/*
 *  routines that use the regexp stuff
 */
bool char2regexeq(uint16 arg1, struct varlena *p)
{
    char *expbuf, *endbuf;
    char *sterm, *pterm;
    int result;
    char *s = (char *) &arg1;
    
    if (!s || !p)
	return FALSE;
    
#if !defined(USE_REGEX)
    expbuf = (char *) palloc(EXPBUFSZ);
    endbuf = expbuf + (EXPBUFSZ - 1);
#endif
    
    /* be sure sterm is null-terminated */
    sterm = (char *) palloc(P2CHARLEN + 1);
    memset(sterm, 0, P2CHARLEN + 1);
    strncpy(sterm, s, P2CHARLEN);
    
#if defined(USE_REGEX)
    {
/*
	regex_t 	re;

	(void) regcomp(&re, pterm, REG_BASIC);
	result = (regexec(&re, sterm, 0, (regmatch_t *) NULL, 0) == 0);
	regfree(&re);
*/
      result = RE_compile_and_execute(p, sterm);
      pterm = 0;
    }
#else
    /* p is a text = varlena, not a string so we have to make 
     * a string from the vl_data field of the struct. */
    
    /* palloc the length of the text + the null character */
    pterm = (char *) palloc(p->vl_len - sizeof(int32) + 1);
    memmove(pterm, p->vl_dat, p->vl_len - sizeof(int32));
    *(pterm + p->vl_len - sizeof(int32)) = (char)NULL;
    
    /* compile the re */
    (void) compile(pterm, expbuf, endbuf, 0);
    
    /* do the regexp matching */
    result = step(sterm, expbuf);
    
    pfree(expbuf);
#endif /* USE_REGEX */

    pfree(sterm);
    if (pterm)
	pfree(pterm);
    
    return ((bool) result);
}

bool char2regexne(uint16 arg1, struct varlena *p)
{
    return (!char2regexeq(arg1, p));
}

bool char4regexeq(uint32 arg1, struct varlena *p)
{
    char *expbuf, *endbuf;
    char *sterm, *pterm;
    int result;
    char *s = (char *) &arg1;
    
    if (!s || !p)
	return FALSE;
    
#if !defined(USE_REGEX)
    expbuf = (char *) palloc(EXPBUFSZ);
    endbuf = expbuf + (EXPBUFSZ - 1);
#endif
    
    /* be sure sterm is null-terminated */
    sterm = (char *) palloc(P4CHARLEN + 1);
    memset(sterm, 0, P4CHARLEN + 1);
    strncpy(sterm, s, P4CHARLEN);
    
#if defined(USE_REGEX)
    {
/*
	regex_t 	re;

	(void) regcomp(&re, pterm, REG_BASIC);
	result = (regexec(&re, sterm, 0, (regmatch_t *) NULL, 0) == 0);
	regfree(&re);
*/
      result = RE_compile_and_execute(p, sterm);
      pterm = 0;
}
#else
    /* p is a text = varlena, not a string so we have to make 
     * a string from the vl_data field of the struct. */
    
    /* palloc the length of the text + the null character */
    pterm = (char *) palloc(p->vl_len - sizeof(int32) + 1);
    memmove(pterm, p->vl_dat, p->vl_len - sizeof(int32));
    *(pterm + p->vl_len - sizeof(int32)) = (char)NULL;
    
    /* compile the re */
    (void) compile(pterm, expbuf, endbuf, 0);
    
    /* do the regexp matching */
    result = step(sterm, expbuf);
    pfree(expbuf);
#endif
    
    pfree(sterm);
    if (pterm)
	pfree(pterm);
    
    return ((bool) result);
}

bool char4regexne(uint32 arg1, struct varlena *p)
{
    return (!char4regexeq(arg1, p));
}

bool char8regexeq(char *s, struct varlena *p)
{
    char *expbuf, *endbuf;
    char *sterm, *pterm;
    int result;
    
    if (!s || !p)
	return FALSE;
    
#if !defined(USE_REGEX)
    expbuf = (char *) palloc(EXPBUFSZ);
    endbuf = expbuf + (EXPBUFSZ - 1);
#endif
    
    /* be sure sterm is null-terminated */
    sterm = (char *) palloc(P8CHARLEN + 1);
    memset(sterm, 0, P8CHARLEN + 1);
    strncpy(sterm, s, P8CHARLEN);
    
#if defined(USE_REGEX)
    {
/*
	regex_t 	re;

	(void) regcomp(&re, pterm, REG_BASIC);
	result = (regexec(&re, sterm, 0, (regmatch_t *) NULL, 0) == 0);
	regfree(&re);
*/
      result = RE_compile_and_execute(p, sterm);
      pterm = 0;
    }
#else
    /* p is a text = varlena, not a string so we have to make 
     * a string from the vl_data field of the struct. */
    
    /* palloc the length of the text + the null character */
    pterm = (char *) palloc(p->vl_len - sizeof(int32) + 1);
    memmove(pterm, p->vl_dat, p->vl_len - sizeof(int32));
    *(pterm + p->vl_len - sizeof(int32)) = (char)NULL;
    
    /* compile the re */
    (void) compile(pterm, expbuf, endbuf, 0);
    
    /* do the regexp matching */
    result = step(sterm, expbuf);
    pfree(expbuf);
#endif
    
    pfree(sterm);
    if (pterm)
	pfree(pterm);
    
    return ((bool) result);
}

bool char8regexne(char *s, struct varlena *p)
{
    return (!char8regexeq(s, p));
}

bool char16regexeq(char *s, struct varlena *p)
{
    char *expbuf, *endbuf;
    char *sterm, *pterm;
    int result;
    
    if (!s || !p)
	return FALSE;
    
#if !defined(USE_REGEX)
    expbuf = (char *) palloc(EXPBUFSZ);
    endbuf = expbuf + (EXPBUFSZ - 1);
#endif
    
    /* be sure sterm is null-terminated */
    sterm = (char *) palloc(P16CHARLEN + 1);
    memset(sterm, 0, P16CHARLEN + 1);
    strncpy(sterm, s, P16CHARLEN);
    
#if defined(USE_REGEX)
    {
/*
	regex_t 	re;

	(void) regcomp(&re, pterm, REG_BASIC);
	result = (regexec(&re, sterm, 0, (regmatch_t *) NULL, 0) == 0);
	regfree(&re);
*/
      result = RE_compile_and_execute(p, sterm);
      pterm = 0;
    }
#else
    /* p is a text = varlena, not a string so we have to make 
     * a string from the vl_data field of the struct. */
    
    /* palloc the length of the text + the null character */
    pterm = (char *) palloc(p->vl_len - sizeof(int32) + 1);
    memmove(pterm, p->vl_dat, p->vl_len - sizeof(int32));
    *(pterm + p->vl_len - sizeof(int32)) = (char)NULL;
    
    /* compile the re */
    (void) compile(pterm, expbuf, endbuf, 0);
    
    /* do the regexp matching */
    result = step(sterm, expbuf);
    pfree(expbuf);
#endif /*USE_REGEX */
    
    pfree(sterm);
    if (pterm)
	pfree(pterm);
    
    return ((bool) result);
}

bool char16regexne(char *s, struct varlena *p)
{
    return (!char16regexeq(s, p));
}

bool textregexeq(struct varlena *s, struct varlena *p)
{
    char *expbuf, *endbuf;
    char *sterm, *pterm;
    int result;
    
    if (!s || !p)
	return FALSE;
    
    /* ---------------
     * text is a varlena, not a string so we have to make 
     * a string from the vl_data field of the struct. 
     * jeff 13 July 1991
     * ---------------
     */
    
    /* palloc the length of the text + the null character */
    sterm = (char *) palloc(s->vl_len - sizeof(int32) + 1);
    
#if !defined(USE_REGEX)
    expbuf = (char *) palloc(EXPBUFSZ);
    endbuf = expbuf + (EXPBUFSZ - 1);
#endif
    
    memmove(sterm, s->vl_dat, s->vl_len - sizeof(int32));
    *(sterm + s->vl_len - sizeof(int32)) = (char)NULL;
#if defined(USE_REGEX)
    {
/*
	regex_t 	re;

    pterm = (char *) palloc(p->vl_len - sizeof(int32) + 1);
    memmove(pterm, p->vl_dat, p->vl_len - sizeof(int32));
    *(pterm + p->vl_len - sizeof(int32)) = (char)NULL;

	(void) regcomp(&re, pterm, REG_BASIC);
	result = (regexec(&re, sterm, 0, (regmatch_t *) NULL, 0) == 0);
	regfree(&re);
*/

      result = RE_compile_and_execute(p, sterm);
      pterm = 0;
 
    }
#else

    pterm = (char *) palloc(p->vl_len - sizeof(int32) + 1);
    memmove(pterm, p->vl_dat, p->vl_len - sizeof(int32));
    *(pterm + p->vl_len - sizeof(int32)) = (char)NULL;
    
    /* compile the re */
    (void) compile(pterm, expbuf, endbuf, 0);
    
    /* do the regexp matching */
    result = step(sterm, expbuf);
    pfree(expbuf);
#endif /* USE_REGEX */
    
    pfree(sterm);
    if (pterm)
	pfree(pterm);
    
    return ((bool) result);
}

bool textregexne(struct varlena *s, struct varlena *p)
{
    return (!textregexeq(s, p));
}
