/*
 * PostgreSQL Tagged Types
 * Written by Martijn van Oosterhout (C) Aug 2005
 * kleptog@svana.org
 * http://svana.org/kleptog/pgsql/taggedtypes.html
 *
 * See COPYING file for licence details
 *
 *	$PostgreSQL: $
 *
 * Handles caching of the various tables used
 */

#include "taggedtypes.h"

static int TaggedTypeTimestamp;
static struct TaggedTypeInfo TaggedTypeCache[ TAGGED_TYPE_CACHE ];
static struct TaggedOperatorInfo TaggedOperatorCache[ TAGGED_OPERATOR_CACHE ];

static struct TaggedTypeInfo *
RetreiveTaggedTypeDetails( Oid typeoid, struct TaggedTypeInfo *tti )
{
	/* First we lookup the tagged types table */
	{
		Relation	tt_rel;
		ScanKeyData	skey;
		HeapScanDesc	scan;
		HeapTuple	tuple;
		RangeVar	*rangevar;
		TupleDesc	tupleDesc;
		bool		isNull;
	
		rangevar = makeRangeVar( TAGGED_TABLE_NAMESPACE , TAGGED_TABLE_NAME );
	
		// Open heap
		tt_rel = heap_openrv( rangevar, AccessShareLock );
		// Get tupleDesc
		tupleDesc = RelationGetDescr(tt_rel);
		// Setup 1 scankey
	// Changed in access/skey.h v1.23 (2003/11/09)
#if CATALOG_VERSION_NO < 200311090
		ScanKeyEntryInitialize(&skey, 0, 1,  /* attnum */
#else
		ScanKeyInit( &skey, (AttrNumber) 1,  BTEqualStrategyNumber,
#endif
	                                        F_OIDEQ,  /* function */
	                                        ObjectIdGetDatum(typeoid));  /* argument */
	        // Scan
		scan = heap_beginscan( tt_rel, SnapshotNow, 1, &skey );
	
		do
			tuple = heap_getnext( scan, ForwardScanDirection );
		while( tuple && !HeapTupleIsValid( tuple ) );
	
		if( !tuple )
		{
			elog( NOTICE, "Lookup tagged type %d failed", typeoid );
			heap_endscan( scan );
			heap_close( tt_rel, AccessShareLock );
			return NULL;
		}
		
		tti->tagtype =        DatumGetObjectId   ( SPI_getbinval( tuple, tupleDesc, 1, &isNull ) );
		tti->basetype =       DatumGetObjectId   ( SPI_getbinval( tuple, tupleDesc, 2, &isNull ) );
		tti->typmod =         DatumGetInt32      ( SPI_getbinval( tuple, tupleDesc, 3, &isNull ) );
		tti->tagtable =       DatumGetObjectId   ( SPI_getbinval( tuple, tupleDesc, 4, &isNull ) );
		tti->typapplytypmod = DatumGetObjectId   ( SPI_getbinval( tuple, tupleDesc, 5, &isNull ) );

		heap_endscan( scan );
		heap_close( tt_rel, AccessShareLock );
	}
	/* Then we lookup the pg_types table */
	{
	        HeapTuple       typeTuple;
	        Form_pg_type typeStruct;

	        typeTuple = SearchSysCache(TYPEOID,
                                                           ObjectIdGetDatum(tti->basetype),
                                                           0, 0, 0);
	        if (!HeapTupleIsValid(typeTuple))
	                elog(ERROR, "RetreiveTaggedTypeDetails: cache lookup failed for type %u", tti->basetype);
	        typeStruct = (Form_pg_type) GETSTRUCT(typeTuple);
	        
	        tti->typinput =  typeStruct->typinput;
	        tti->typoutput = typeStruct->typoutput;
	        tti->typlen =    typeStruct->typlen;
	        tti->typbyval =  typeStruct->typbyval;
	        
	        ReleaseSysCache( typeTuple );
	}
	
	tti->timestamp = TaggedTypeTimestamp++;

//	elog( NOTICE, "Cache Miss: %d", typeoid );	
	elog( NOTICE, "GetTypeDetails(%d) => %d, %d, %d, %d, %d, %d, %d, %d", typeoid, 
					tti->tagtype, tti->basetype, tti->typmod, 
					tti->tagtable, tti->typinput, tti->typoutput,
					tti->typlen, tti->typbyval );
	return tti;
}

static int
FindFreeTypeCacheSlot()
{
	int	oldest_ts = TaggedTypeTimestamp;
	int	oldest_id = -1;
	int	i;
	
	for( i=0; i<TAGGED_TYPE_CACHE; i++ )
	{
		/* So old it's wrapped */
		if( (TaggedTypeTimestamp - TaggedTypeCache[i].timestamp) < 0 )
			return i;
			
		/* Never used */
		if( TaggedTypeCache[i].timestamp == 0 )
			return i;
		
		if( TaggedTypeCache[i].timestamp < oldest_ts )
		{
			oldest_ts = TaggedTypeCache[i].timestamp;
			oldest_id = i;
		}
	}
	
	if( oldest_id == -1 )
		ereport( ERROR, (errmsg("FindFreeTypeCacheSlot couldn't find a slot") ) );

	return oldest_id;
}

struct TaggedTypeInfo *
GetTaggedTypeDetails( Oid typeoid )
{
	int	i;
	
	if( typeoid == 0 )
	{
		ereport( ERROR, (errmsg("GetTaggedTypeDetails() called with InvalidOid")));
	}
	
	for( i=0; i<TAGGED_TYPE_CACHE; i++ )
	{
		if( TaggedTypeCache[i].tagtype == typeoid )
			break;
	}
	
	if( i == TAGGED_TYPE_CACHE )   /* Cache miss */
	{
		i = FindFreeTypeCacheSlot();
		/* If not foudn, return NULL */
		if( !RetreiveTaggedTypeDetails( typeoid, &TaggedTypeCache[i] ) )
			return NULL;
		
		if( typeoid != TaggedTypeCache[i].tagtype )
			ereport( ERROR, (errmsg("Internal error, cache failure")) );
	}
	
	TaggedTypeCache[i].timestamp = TaggedTypeTimestamp++;
	
	return &TaggedTypeCache[i];
}

static struct TaggedOperatorInfo *
RetreiveTaggedOperatorDetails( Oid operfuncoid, struct TaggedOperatorInfo *tto )
{
	/* First we lookup the tagged operators table */
	{
		Relation	tt_rel;
		ScanKeyData	skey;
		HeapScanDesc	scan;
		HeapTuple	tuple;
		RangeVar	*rangevar;
		TupleDesc	tupleDesc;
		bool		isNull;
	
		rangevar = makeRangeVar( TAGGED_TABLE_NAMESPACE , TAGGED_OPERATOR_NAME );
	
		// Open heap
		tt_rel = heap_openrv( rangevar, AccessShareLock );
		// Get tupleDesc
		tupleDesc = RelationGetDescr(tt_rel);
		// Setup 1 scankey
#if CATALOG_VERSION_NO < 200311090
		ScanKeyEntryInitialize(&skey, 0, 3,  /* attnum (tagfunc) */
#else
		ScanKeyInit( &skey, (AttrNumber) 3,  BTEqualStrategyNumber,
#endif
	                                        F_OIDEQ,  /* function */
	                                        ObjectIdGetDatum(operfuncoid));  /* argument */
	        // Scan
		scan = heap_beginscan( tt_rel, SnapshotNow, 1, &skey );
	
		do
			tuple = heap_getnext( scan, ForwardScanDirection );
		while( tuple && !HeapTupleIsValid( tuple ) );
	
		if( !tuple )
		{
			elog( NOTICE, "Lookup tagged operator %d failed", operfuncoid );
			heap_endscan( scan );
			heap_close( tt_rel, AccessShareLock );
			return NULL;
		}
		
		tto->tagoper =        DatumGetObjectId   ( SPI_getbinval( tuple, tupleDesc, 1, &isNull ) );
		tto->baseoper =       DatumGetObjectId   ( SPI_getbinval( tuple, tupleDesc, 2, &isNull ) );
		tto->tagfunc =        DatumGetObjectId   ( SPI_getbinval( tuple, tupleDesc, 3, &isNull ) );

		heap_endscan( scan );
		heap_close( tt_rel, AccessShareLock );
	}
	/* Then we lookup the pg_operator table */
	{
	        HeapTuple       operTuple;
	        Form_pg_operator operStruct;

	        operTuple = SearchSysCache(OPEROID,
                                                           ObjectIdGetDatum(tto->baseoper),
                                                           0, 0, 0);
	        if (!HeapTupleIsValid(operTuple))
	                elog(ERROR, "RetreiveTaggedOperatorDetails: cache lookup failed for type %u", tto->baseoper);
	        operStruct = (Form_pg_operator) GETSTRUCT(operTuple);
	        
	        tto->basefunc =  operStruct->oprcode;

	        ReleaseSysCache( operTuple );
	}
	

	tto->timestamp = TaggedTypeTimestamp++;

//	elog( NOTICE, "Cache Miss: %d", operfuncoid );	
	elog( NOTICE, "GetOperatorDetails(%d) => %d, %d, %d", operfuncoid, 
					tto->tagoper, tto->baseoper, tto->basefunc );
	return tto;
}

static int
FindFreeOperatorCacheSlot()
{
	int	oldest_ts = TaggedTypeTimestamp;
	int	oldest_id = -1;
	int	i;
	
	for( i=0; i<TAGGED_TYPE_CACHE; i++ )
	{
		/* So old it's wrapped */
		if( (TaggedTypeTimestamp - TaggedOperatorCache[i].timestamp) < 0 )
			return i;
			
		/* Never used */
		if( TaggedOperatorCache[i].timestamp == 0 )
			return i;
		
		if( TaggedOperatorCache[i].timestamp < oldest_ts )
		{
			oldest_ts = TaggedOperatorCache[i].timestamp;
			oldest_id = i;
		}
	}
	
	if( oldest_id == -1 )
		ereport( ERROR, (errmsg("FindFreeOperatorCacheSlot couldn't find a slot") ) );

	return oldest_id;
}

struct TaggedOperatorInfo *
GetTaggedOperatorDetails( Oid operfuncoid )
{
	int	i;
	
	if( operfuncoid == 0 )
	{
		ereport( ERROR, (errmsg("GetTaggedOperatorDetails() called with InvalidOid")));
	}
	
	for( i=0; i<TAGGED_OPERATOR_CACHE; i++ )
	{
		if( TaggedOperatorCache[i].tagfunc == operfuncoid )
			break;
	}
	
	if( i == TAGGED_OPERATOR_CACHE )   /* Cache miss */
	{
		i = FindFreeOperatorCacheSlot();
		/* If not foudn, return NULL */
		if( !RetreiveTaggedOperatorDetails( operfuncoid, &TaggedOperatorCache[i] ) )
			return NULL;
		
		if( operfuncoid != TaggedOperatorCache[i].tagfunc )
			ereport( ERROR, (errmsg("Internal error, cache failure")) );
	}
	
	TaggedOperatorCache[i].timestamp = TaggedTypeTimestamp++;
	
	return &TaggedOperatorCache[i];
}

Oid
FindTagByName( struct TaggedTypeInfo *tti, text *tag_text )
{
//	sprintf( query, "select oid from %s where tag = '%s'", tagtable, tag );
	Relation	tt_rel;
	ScanKeyData	skey;
	HeapScanDesc	scan;
	HeapTuple	tuple;
	TupleDesc	tupleDesc;
	bool		isNull;
	Oid		result;
	Oid		tagtable = tti->tagtable;
	
	// Open heap
	tt_rel = heap_open( tagtable, AccessShareLock );
	// Get tupleDesc
	tupleDesc = RelationGetDescr(tt_rel);
	// Setup 1 scankey
#if CATALOG_VERSION_NO < 200311090
	ScanKeyEntryInitialize(&skey, 0, 1,  /* attnum */
#else
	ScanKeyInit( &skey, (AttrNumber) 1,  BTEqualStrategyNumber,
#endif
                                        F_TEXTEQ,  /* function */
                                        PointerGetDatum(tag_text));  /* argument */
        // Scan
	scan = heap_beginscan( tt_rel, SnapshotNow, 1, &skey );

	do
		tuple = heap_getnext( scan, ForwardScanDirection );
	while( tuple && !HeapTupleIsValid( tuple ) );

	if( !tuple )
		ereport( ERROR, (errmsg("Tag '%s' invalid for %s", text_to_charp(tag_text), get_rel_name( tagtable ) ) ) );
	
	result = DatumGetObjectId( SPI_getbinval( tuple, tupleDesc, ObjectIdAttributeNumber, &isNull ) );

	heap_endscan( scan );
	heap_close( tt_rel, AccessShareLock );

	return result;
}

text *
FindTagByOid( struct TaggedTypeInfo *tti, Oid tagoid )
{
//	sprintf( query, "select tag from %s where oid = %d", tagtable, tagoid );
	Relation	tt_rel;
	ScanKeyData	skey;
	HeapScanDesc	scan;
	HeapTuple	tuple;
	TupleDesc	tupleDesc;
	bool		isNull;
	text		*result;
	Oid		tagtable = tti->tagtable;
	
	// Open heap
	tt_rel = heap_open( tagtable, AccessShareLock );
	// Get tupleDesc
	tupleDesc = RelationGetDescr(tt_rel);
	// Setup 1 scankey
#if CATALOG_VERSION_NO < 200311090
	ScanKeyEntryInitialize(&skey, 0, ObjectIdAttributeNumber,  /* attnum */
#else
	ScanKeyInit( &skey, ObjectIdAttributeNumber,  BTEqualStrategyNumber,
#endif
                                        F_OIDEQ,  /* function */
                                        ObjectIdGetDatum(tagoid));  /* argument */
        // Scan
	scan = heap_beginscan( tt_rel, SnapshotNow, 1, &skey );

	do
		tuple = heap_getnext( scan, ForwardScanDirection );
	while( tuple && !HeapTupleIsValid( tuple ) );

	if( !tuple )
	{
		char tagstr[32];
		ereport( NOTICE, (errmsg("Tag oid '%d' invalid for %s", tagoid, get_rel_name(tagtable) ) ) );
		sprintf( tagstr, "#%d", tagoid );
		result = DatumGetTextP( DirectFunctionCall1( textin, CStringGetDatum( tagstr ) ) );
	}
	else
		result = DatumGetTextP( SPI_getbinval( tuple, tupleDesc, 1, &isNull ) );

	heap_endscan( scan );
	heap_close( tt_rel, AccessShareLock );

	return result;
}

