/*========================================================================
 *
 * FILE:
 * stubraw.c
 *
 * IDENTIFICATION:
 * $Header: /private/postgres/src/rules/stubs/RCS/stubraw.c,v 1.12 1991/08/08 16:07:14 sp Exp $
 *
 * DESCRIPTION:
 *
 * Routines that transform a 'Prs2RawStub' to a 'Prs2Stub' and vice versa.
 *
 *========================================================================
 */

#include <stdio.h>
#include "tmp/postgres.h"
#include "parser/parse.h"       /* for the AND, NOT, OR */
#include "utils/log.h"
#include "rules/prs2.h"
#include "rules/prs2stub.h"
#include "tmp/datum.h"
#include "nodes/primnodes.h"
#include "nodes/primnodes.a.h"

extern Const RMakeConst();
extern Param RMakeParam();
extern LispValue StringToPlan();


/*-----------------------------------------------------------------------
 *
 * prs2StubToRawStub
 *
 * given a 'Prs2Stub' (which is a spaghetti of pointers)
 * transform it to a stream of bytes suitable for disk storage
 *
 *-----------------------------------------------------------------------
 */
Prs2RawStub
prs2StubToRawStub(relstub)
Prs2Stub relstub;
{
    Prs2RawStub *pieces, res;
    int npieces;
    int maxsize;
    int i;

    maxsize = 1000000000;
    pieces = prs2StubToSmallRawStubs(relstub, maxsize, &npieces);
    if (npieces != 1) {
	elog(WARN, "Hey! this is a pretty hefty stub you've got there!");
    }

    res = pieces[0];
    pfree(pieces);

    return(res);
}

/*-----------------------------------------------------------------------
 *
 * prs2StubToSmallRawStubs
 *
 * Similar to 'prs2StubToRawStub' but if the resulting stub is too big,
 * then it breaks it in many pieces, each one of them being
 * less than 'maxSize' bytes long.
 * It returns an array of 'Prs2RawStub'. This array has '*nStubPiecesP'
 * entries.
 *
 * THE ECOLOGICAL MESSAGE OF THE WEEK:
 * Do not waste memory, recycle it! So, don't forget to pfree the stubs
 * and the array when you are done!
 *-----------------------------------------------------------------------
 */
Prs2RawStub *
prs2StubToSmallRawStubs(relstub, maxSize, nStubPiecesP)
Prs2Stub relstub;
int *nStubPiecesP;
{
    Size thisSize, headerSize, sumSizes, locksize;
    int nPieces;
    Prs2RawStub *res;
    Prs2OneStub oneStub;
    char **stubQual;
    int *stubSizes;
    int i,j;
    int len;
    char *s;
    int nstubs;

    /*
     * NOTE: this routine does a two pass "parsing" of 'relstub'.
     * The first one to find the size needed for the raw stub
     * representation and the second to actually store the information.
     * However as in order to complete the first step we must 
     * transform the stub qualifications to strings and then repeat
     * this process again during the second pass and as this takes
     * quite some time, we instead opt to store the strings
     * (the ascii reperesentations) of the stubs quals in an array
     * during the first pass so we do not have to recalculate them 
     * during the second!)
     */
    if (relstub->numOfStubs>0) {
	stubQual = (char **)palloc(sizeof(char *) * relstub->numOfStubs);
	if (stubQual == NULL) {
	    elog(WARN, "prs2StubToRawStub: Out of memory!\n");
	}
	stubSizes = (int *)palloc(sizeof(int) * relstub->numOfStubs);
	if (stubSizes == NULL) {
	    elog(WARN, "prs2StubToRawStub: Out of memory!\n");
	}
    } else {
	stubQual = NULL;
	stubSizes = NULL;
    }

    /*
     * calculate the number of bytes needed to hold
     * the raw representation of this stub record.
     * First we calculate the # of bytes needed for
     * each individual `Prs2OneStub' and we store it
     * in array 'stubSizes'
     */

    for (i=0; i< relstub->numOfStubs; i++) {
	/*
	 * calculate the size of each individual `Prs2OneStub'
	 * Start with the fixed size part of the struct.
	 */
	oneStub = relstub->stubRecords[i];
	thisSize = sizeof(oneStub->ruleId) +
		sizeof(oneStub->stubId) +
		sizeof(oneStub->counter);
	/*
	 * now add the rule lock size
	 */
	thisSize = thisSize +
		prs2LockSize( prs2GetNumberOfLocks(oneStub->lock));
	/*
	 * finally add the size of the stub's qualification.
	 * NOTE: we store it a string preceded by an integer
	 * holding its length.
	 * Don't forget to store this string to an array
	 * so that we don't have to recalculate it later.
	 */
	s = lispOut(oneStub->qualification);
	stubQual[i] = s;
	thisSize = thisSize + (strlen(s) + sizeof(int));
	/*
	 * store the size to 'stubSizes' and also
	 * add it to the total size
	 */
	stubSizes[i] = thisSize;

    }

    /*
     * calculate in how many pieces we have to break
     * the stub so that each piece is less than 'maxSize'
     * bytes long.
     *
     * NOTE: Just as like in the "varlena" structure, the "size" field
     * in the Prs2RawStubData is the sum of its own size + the (variable)
     * size of the data that is following.
     */
    nPieces = 0;
    headerSize = sizeof(res[0]->vl_len) + sizeof(relstub->numOfStubs);
    sumSizes = headerSize;
    for (i=0; i< relstub->numOfStubs; i++) {
	if (sumSizes + stubSizes[i] > maxSize) {
	    nPieces++;
	    sumSizes = stubSizes[i] + headerSize;
	} else {
	    sumSizes = sumSizes + stubSizes[i];
	}
    }
    nPieces++;

    /*
     * OK, allocate an array of that many raw stub pieces
     */
    res = (Prs2RawStub *) palloc(sizeof(Prs2RawStub) * nPieces);
    if (res==NULL) {
	elog(WARN,"prs2StubToRawStub: Out of memory");
    }

    /*
     * Now allocate enough space for each 
     * such piece
     */
    j=0;
    sumSizes = headerSize;
    for (i=0; i< relstub->numOfStubs; i++) {
	if (sumSizes + stubSizes[i] > maxSize) {
	    res[j] = (Prs2RawStub)palloc(sumSizes);
	    if (res[j]==NULL) {
		elog(WARN,"prs2StubToRawStub: Out of memory");
	    }
	    res[j]->vl_len = sumSizes;
	    j++;
	    sumSizes = stubSizes[i] + headerSize;
	} else {
	    sumSizes = sumSizes + stubSizes[i];
	}
    }
    res[j] = (Prs2RawStub)palloc(sumSizes);
    if (res[j]==NULL) {
	elog(WARN,"prs2StubToRawStub: Out of memory");
    }
    res[j]->vl_len = sumSizes;

    
    /*
     * now copy the stubs to the raw stub pieces.
     */
    j=0;
    s = &(res[j]->vl_dat[0]) + sizeof(relstub->numOfStubs);
    sumSizes = headerSize;
    nstubs = 0;


    for (i=0; i< relstub->numOfStubs; i++) {
	if (sumSizes + stubSizes[i] > maxSize) {
	    /*
	     * time to start a new piece...
	     * first update the 'numOfStubs' field of the previous
	     * piece
	     */
	    bcopy((char *) &nstubs, &(res[j]->vl_dat[0]), sizeof(nstubs));
	    /*
	     * now continue with the next stub
	     */
	    j++;
	    s = &(res[j]->vl_dat[0]) + sizeof(relstub->numOfStubs);
	    sumSizes = headerSize;
	    nstubs = 0;
	}
	sumSizes = sumSizes + stubSizes[i];
	nstubs ++;
	/*
	 * copy this prs2OneStub to the raw stub piece
	 */
	oneStub = relstub->stubRecords[i];
	/*
	 * copy the fixed size part of the record
	 */
	bcopy((char *)&(oneStub->ruleId), s, sizeof(oneStub->ruleId));
	s += sizeof(oneStub->ruleId);
	bcopy((char *)&(oneStub->stubId), s, sizeof(oneStub->stubId));
	s += sizeof(oneStub->stubId);
	bcopy((char *)&(oneStub->counter), s, sizeof(oneStub->counter));
	s += sizeof(oneStub->counter);
	/*
	 * now copy the rule lock
	 */
	locksize = prs2LockSize(prs2GetNumberOfLocks(oneStub->lock));
	bcopy((char *) oneStub->lock, s, locksize);
	s+=locksize;
	/*
	 * finally copy the qualification
	 * First the length of the string and then the string itself.
	 */
	len = strlen(stubQual[i]);
	bcopy((char *) &len, s, sizeof(int));
	s += sizeof(int);
	bcopy(stubQual[i], s, len);
	s += len;
    }
    /*
     * Don't forget to update the 'numOfStubs' field of the
     * last piece!
     */
    bcopy((char *) &nstubs, &(res[j]->vl_dat[0]), sizeof(nstubs));

    /*
     * Keep POSTGRES memory clean!
     */
    for (i=0; i< relstub->numOfStubs; i++) {
	pfree(stubQual[i]);
    }
    if (relstub->numOfStubs > 0) {
	pfree(stubQual);
	pfree(stubSizes);
    }

    /* 
     * OK we are done!
     */
    *nStubPiecesP = nPieces;
    return(res);
}

/*----------------------------------------------------------------------
 *
 * prs2RawStubToStub
 *
 * given a stream of bytes (like the one created by
 * 'prs2StubToRawStub') recreate the original stub record
 *
 *----------------------------------------------------------------------
 */
Prs2Stub
prs2RawStubToStub(rawStub)
Prs2RawStub rawStub;
{
    Prs2Stub relstub;
    Prs2OneStub oneStub;
    Size size;
    int i,j;
    int len;
    char *data;
    char *s;
    Prs2LocksData ruleLock;

    /*
     * create a new stub record
     */
    relstub = prs2MakeStub();
    if (relstub == NULL) {
	elog(WARN, "prs2RawStubToStub: Out of memory");
    }

    /*
     * copy the number of individual stub records
     */
    data = VARDATA(rawStub);
    bcopy(data, (char *)&(relstub->numOfStubs), sizeof(relstub->numOfStubs));
    data += sizeof(relstub->numOfStubs);

    /*
     * allocate space for the `relstub->numOfStubs' records of
     *type 'Prs2OneStubData'
     */
    if (relstub->numOfStubs > 0 ) {
	size = relstub->numOfStubs * sizeof(Prs2OneStub);
	relstub->stubRecords = (Prs2OneStub *) palloc(size);
	if (relstub->stubRecords == NULL) {
	    elog(WARN, "prs2RawStubToStub: Out of memory");
	}
    } else {
	relstub->stubRecords = NULL;
    }
    
    for (i=0; i< relstub->numOfStubs; i++) {
	oneStub = prs2MakeOneStub();
	relstub->stubRecords[i] = oneStub;
	/*
	 * copy the fixed part of the 'Prs2OneStub' first.
	 */
        bcopy(data, (char *)&(oneStub->ruleId), sizeof(oneStub->ruleId));
        data += sizeof(oneStub->ruleId);
        bcopy(data, (char *)&(oneStub->stubId), sizeof(oneStub->stubId));
        data += sizeof(oneStub->stubId);
        bcopy(data, (char *)&(oneStub->counter), sizeof(oneStub->counter));
        data += sizeof(oneStub->counter);

	/*
	 * now copy the rule lock
	 * XXX: NOTE:
	 * In order to copy the rule lock, we have to find its
	 * size. But in order to find its size we must read
	 * the 'numberOfLocks' field.
	 * We can not just cast 'data' to a RuleLock because
	 * of alignment problems, so we must copy it.
	 */
	bcopy(data, (char *) &ruleLock,
		sizeof(Prs2LocksData)-sizeof(Prs2OneLockData));
	size = prs2LockSize(prs2GetNumberOfLocks(&ruleLock));
	oneStub->lock = (RuleLock) palloc(size);
	if (oneStub->lock == NULL) {
	    elog(WARN, "prs2RawStubToStub: Out of memory");
	}
	bcopy(data, (char*)(oneStub->lock), size);
	data += size;

	/*
	 * now recreate the qualification (which is stored
	 * as a string containing the ascii representation
	 * of a LispValue).
	 * First read the length of the string & then 
	 * create the LispValue out of the string.
	 */
	bcopy(data, (char*)&len, sizeof(int));
	data += sizeof(int);
	oneStub->qualification = StringToPlan(data);
	data += len;
    }

    /*
     * At last! we are done...
     */
    return(relstub);
}

/*----------------------------------------------------------------------
 * prs2RawStubUnion
 *
 * Make and return the union of the two given raw stubs.
 *----------------------------------------------------------------------
 */
Prs2RawStub
prs2RawStubUnion(stub1, stub2)
Prs2RawStub stub1;
Prs2RawStub stub2;
{
    Prs2RawStub res;
    int size, size1, size2;
    int nstubs, nstubs1, nstubs2;
    char *data1, *data2;
    char *s;

    /*
     * the data of each stub consists of the 4 bytes used
     * to store the number of the "small stub record" (i.e.
     * the equivalent to 'Prs2OneStubData' structure) contained
     * in this stub + the data for these "small stub records".
     * All this data is stored in the "vl_dat" field.
     *
     * Note that the number stored in "vl_len" is the size of all
     * this stuff + the size of the "vl_len" field itself.
     *
     * The size of data  of the union will be the sum of space needed
     * for the "small stub records" for stub 1, the space
     * needed for the "small stub records" of stub2 and
     * finally the 4 bytes to store the total number of
     * new "small stub records".
     */

    /*
     * read the 'number of stubs' information into 'nstubs1'
     * and make 'data1' point to the first "small stub record"
     * 'size1' is the size of the information for the "small
     * stub records" only.
     */
    s = &(stub1->vl_dat[0]);
    bcopy(s, (char *) &nstubs1, sizeof(nstubs1));
    data1 = s + sizeof(nstubs1);
    size1 = stub1->vl_len - sizeof(stub1->vl_len) - sizeof(nstubs1);

    /*
     * same thing for stub 2
     */
    s = &(stub2->vl_dat[0]);
    bcopy(s, (char *) &nstubs2, sizeof(nstubs2));
    data2 = s + sizeof(nstubs2);
    size2 = stub2->vl_len - sizeof(stub2->vl_len) - sizeof(nstubs2);

    /*
     * find the total number of stubs and calculate the
     * total size of the their union (including the number of
     * small stub records and the size of 'vl_len')
     */
    nstubs = nstubs1 + nstubs2;
    size = size1 + size2 + sizeof(nstubs) + sizeof(res->vl_len);


    /*
     * allocate space for the new stub and copy there the
     * information about the number of stubs
     */
    res = (Prs2RawStub) palloc(size);
    if (res == NULL) {
	elog(WARN,"prs2RawStubUnion: Out of memory");
    }
    res->vl_len = size;
    s = &(res->vl_dat[0]);
    bcopy((char *)&nstubs, s, sizeof(nstubs));

    /*
     * now concatenate the data for the small stub records of
     * stub1 and stub2 and copy them in the new union stub
     */
    s += sizeof(nstubs);
    bcopy(data1, s, size1);
    s += size1;
    bcopy(data2, s, size2);

    /*
     * OK, we are done...
     */
    return(res);
}


