/*
 * localam.c --
 *    POSTGRES heap access method "fast" code for temp or "retrieve into"
 *  relations.  NOTE:  Since these relations are not visible to anyone but
 *  the backend that is generating them, this code completely ignores the
 *  multi-user buffer pool and utilities, and is optimized for fast appends.
 *
 *  ! DO NOT USE THIS CODE FOR ANYTHING BUT TEMP OR "RETRIEVE INTO" RELATIONS !
 *
 *	mao says 13 oct 92:  what a crock.  the user creates a temp or
 *	'into' relation, and we execute this code to avoid going through
 *	the multi-user buffer pool.  the very next command will have to
 *	fault the pages into the buffer pool in almost every case.  this
 *	is really dumb -- all this complexity for zero benefit.
 */

#include <strings.h>
#include <sys/file.h>

#include "tmp/c.h"

RcsId("/usr/local/devel/postgres-v4r2/src/backend/access/heap/RCS/localam.c,v 1.14 1994/02/07 11:29:50 aoki Exp");

#include "access/heapam.h"
#include "access/hio.h"
#include "access/htup.h"
#include "access/xact.h"

#include "storage/block.h"
#include "storage/buf.h"
#include "storage/bufmgr.h"
#include "storage/bufpage.h"
#include "storage/itemid.h"
#include "storage/itemptr.h"

#include "utils/memutils.h"
#include "utils/palloc.h"
#include "utils/log.h"
#include "utils/rel.h"


typedef struct l_rellist
{
    Relation relation;

    Page read_page, write_page;
    BlockNumber read_blocknum, write_blocknum;

    OffsetIndex offset_index;
    bool active;
    struct l_rellist *next;
}
LocalRelList;

LocalRelList *head = NULL, *current = NULL, *tail = NULL;
LocalRelList *GetRelListEntry(), *AddToRelList(); 

/*
 * Public interface routines:
 */

local_heap_close(relation)

Relation relation;

{
    LocalRelList *p = GetRelListEntry(relation);
    if (p->write_page != NULL) WriteLocalPage(p);
    DeleteRelListEntry(relation);
}

local_heap_destroy(relation)
Relation relation;
{
    LocalRelList *p = GetRelListEntry(relation);
    smgrunlink(relation->rd_rel->relsmgr, relation);
}

local_heap_open(relation)
Relation relation;
{
    MemoryContext    oldCxt;
    MemoryContext    portalContext;
    Portal	     portal;

    /* ----------------
     *	get the blank portal and its memory context
     * ----------------
     */
    portal = GetPortalByName(NULL);
    portalContext = (MemoryContext) PortalGetHeapMemory(portal);

    oldCxt = MemoryContextSwitchTo(portalContext);
    AddToRelList(relation);
    (void) MemoryContextSwitchTo(oldCxt);
}

void
local_heap_insert(relation, tup)
    Relation    relation;
    HeapTuple    tup;
{
    /* ----------------
     *    increment access statistics
     * ----------------
     */
    IncrHeapAccessStat(local_insert);
    IncrHeapAccessStat(global_insert);

    if (!ObjectIdIsValid(tup->t_oid)) {
        tup->t_oid = newoid();
        LastOidProcessed = tup->t_oid;
    }

    TransactionIdStore(GetCurrentTransactionId(), &(tup->t_xmin));
    tup->t_cmin = GetCurrentCommandId();
    PointerStoreInvalidTransactionId((Pointer)&(tup->t_xmax));
    tup->t_tmin = INVALID_ABSTIME;
    tup->t_tmax = CURRENT_ABSTIME;

    local_doinsert(relation, tup);
}

/*
 * These routines are called by the transaction manager.  They are useful
 * in cleaning up dead temp relations and such from an AbortTransaction
 * or an elog(WARN).  They do not completely solve the problem of dead
 * tmp relations, however; we have to do something about those left over from
 * a crashed backend.
 */

AtAbort_LocalRels()
{
    DestroyLocalRelList();
}

AtCommit_LocalRels()
{
    DestroyLocalRelList();
}

/*
 * Private routines
 */

LocalRelList *
AddToRelList(relation)
    Relation relation;
{
    if (tail == NULL)
    {
        Assert(head == NULL);
        head = (LocalRelList *) palloc(sizeof(LocalRelList));
        tail = head;
    }
    else
    {
        tail->next = (LocalRelList *) palloc(sizeof(LocalRelList));
        tail = tail->next;
    }

    tail->relation = relation;
    tail->read_page = tail->write_page = NULL;
    tail->read_blocknum = tail->write_blocknum = 0;
    tail->offset_index = 0;
    tail->next = NULL;
    tail->active = true;
    current = tail;
    return(tail);
}

LocalRelList *
GetRelListEntry(relation)
    Relation relation;
{
    LocalRelList *p;

    Assert(current != NULL);
    if (current->relation == relation) return(current);

    for (p = head; p != NULL && p->relation != relation; p = p->next);

    if (p == NULL) elog(WARN, "GetRelListEntry: can't find rel in list!");
    return(p);
}

/*
 * This does not do an actual list delete, but instead marks the list
 * entry as inactive and frees its page.  This is done so that all the
 * temp relations can be saved and blown away at TransactionAbort time
 * if necessary by a call to DestroyLocalRelList.
 */

DeleteRelListEntry(relation)
    Relation relation;
{
    LocalRelList *p;

    Assert(head != NULL);

    for (p = head; p != NULL && p->relation != relation; p = p->next);

    Assert(p != NULL);

    p->active = false;

    /* don't waste excessive memory */
    if (p->read_page != NULL) pfree(p->read_page);
    if (p->write_page != NULL) pfree(p->write_page);

    p->read_page = p->write_page = NULL;

    if (current == p)
    {
        for (p = head; p->next != NULL && ! p->active; p = p->next);

        if (p->next == NULL) current = NULL;
        else current = p;
    }
}

/*
 * This routine is useful for AbortTransaction - it deletes the temp
 * relations in the list if they are still active.  They should not ever
 * be active at TransactionCommit time, although this routine needs to
 * be called at the end of a transaction no matter what happens.
 */

DestroyLocalRelList()
{
    LocalRelList *p, *q;
    for (p = head; p != NULL; p = q)
    {
        if (p->active)
        {
            FileUnlink(p->relation->rd_fd);
            if (p->read_page != NULL) pfree(p->read_page);
            if (p->write_page != NULL) pfree(p->write_page);
        }
        q = p->next;
        pfree(p);
    }
    head = current = tail = NULL;
}

local_doinsert(relation, tuple)
    Relation relation;
    HeapTuple tuple;
{
    LocalRelList *rel_info;
    Size len = LONGALIGN(tuple->t_len);
    OffsetIndex offsetIndex;
    ItemId      itemId;
    HeapTuple   page_tuple;

    rel_info = GetRelListEntry(relation);

    if (rel_info->write_page == NULL)
    {
        rel_info->write_page = (Page) palloc(BLCKSZ);

	/*
	 *  (This comment copied from _bt_pageinit :-)
	 *
	 *  Cargo-cult programming -- don't really need this to be zero, but
	 *  creating new pages is an infrequent occurrence and it makes me feel
	 *  good when I know they're empty.
	 */
	bzero((char *) rel_info->write_page, BLCKSZ);	/* XXX PURIFY */

        PageInit(rel_info->write_page, BLCKSZ, 0);
    }

    if (len > PageGetFreeSpace(rel_info->write_page))
    {
        WriteLocalPage(rel_info);
        rel_info->write_blocknum++;
        bzero(rel_info->write_page, BLCKSZ);
        PageInit(rel_info->write_page, BLCKSZ, 0);
    }

    offsetIndex = PageAddItem((Page)rel_info->write_page, (Item)tuple,
                              tuple->t_len, InvalidOffsetNumber, LP_USED) - 1;

    itemId = PageGetItemId((Page)rel_info->write_page, offsetIndex);

    page_tuple = (HeapTuple) PageGetItem(rel_info->write_page, itemId);

    /*
     * As this code is being used for "retrieve into" or tmp relations, 
     * the RuleLock can be set to invalid as no tuple rule can exist on
     * such relations.
     */

    page_tuple->t_locktype = DISK_RULE_LOCK;
    ItemPointerSetInvalid(&page_tuple->t_lock.l_ltid);

    ItemPointerSimpleSet(&page_tuple->t_ctid,
                         rel_info->write_blocknum,
                         1 + offsetIndex);
}

WriteLocalPage(rel_info)
    LocalRelList *rel_info;
{
    Relation r;

    /*
     *  Use the storage manager switch to be sure that we write to the
     *  correct device.  We call smgrextend() instead of smgrwrite()
     *  because we know that this routine is called only with new pages
     *  for the temp relation.
     */

    r = rel_info->relation;
    (void) smgrextend(r->rd_rel->relsmgr, r, (char *) rel_info->write_page);
}
