/*
 * 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: $
 *
 * Main program file
 */

#include <string.h>
#include "taggedtypes.h"
#include "catalog/dependency.h"
#include "catalog/namespace.h"

// format_type_be
// get_rel_name

#define cc(x) ( (x < 0) ? x : (x == 0) ? sizeof(struct taggedtyped) : (x + sizeof(Oid)) )

static int using_SPI;

static inline TaggedTypeClass
DetermineTaggedTypeClass( struct TaggedTypeInfo *tti )
{
	Size typlen = tti->typlen;
	bool typbyval = tti->typbyval;
	
	if( typbyval )
		return 0;
	if( typlen > 0 )
		return typlen;
	return -1;
}

static inline Size
TaggedTypeNewSize( TaggedTypeClass tt_class )
{
	if( isTaggedTypeDatum( tt_class ) )
		return sizeof( struct taggedtyped );
	if( isTaggedTypeFixed( tt_class ) )
		return tt_class + sizeof(Oid);
	if( isTaggedTypeVar( tt_class ) )
		return -1;
}

static inline void
tt_SPI_start()
{
	if( SPI_connect() != SPI_OK_CONNECT )
	  ereport( ERROR, ( errmsg("SPI_connect failed") ) );
	  
	using_SPI = 1;
}

static inline void
tt_SPI_stop()
{
	SPI_finish();
	using_SPI = 0;
}

/* Allocate in the main function context, irrespective of using SPI */
static inline void *
tt_palloc( Size size )
{
	if( using_SPI )
		return SPI_palloc( size );
	return palloc( size );
}

static inline char *
GetDLLPath()
{
	char	*dll_path;
		
	if( SPI_exec("select probin from pg_proc where proname = 'create_tagged_type'", 1 ) != SPI_OK_SELECT )
	  ereport( ERROR, (errmsg("SPI_exec to find probin failed") ) );
	  
	return dll_path = SPI_getvalue( SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1 );
}

#ifdef DEBUG
static void
debugDump( void *ptr, int len )
{
	int i;
	char query[ len * 3 + 1];

	if( len > 200 )
	{
		elog( NOTICE, "debugDump asked to dump %d bytes...\n", len );
		len = 32;
	}
		
	query[0] = '\0';	  
	for( i=0; i < len; i++ )
		sprintf( query + 3*i, "%02X ", ((unsigned char*)ptr)[i] );

	elog( NOTICE, "Debug value: %s", query );
}

static inline void
debugDatum( TaggedTypeClass tt_class, Datum datum )
{
	if( isTaggedTypeDatum( tt_class ) )
  		return debugDump( &datum, sizeof( datum ) );
  	if( isTaggedTypeFixed( tt_class ) )
  		return debugDump( DatumGetPointer( datum ), tt_class );
  	if( isTaggedTypeVar( tt_class ) )
  		return debugDump( DatumGetPointer( datum ), VARSIZE( datum ) );
  		
  	elog( ERROR, "huh?3" );
}
#else
static inline void
debugDump( void *ptr, int len )
{
}

static inline void
debugDatum( TaggedTypeClass ttc, Datum datum )
{
}

#endif

static void 
find_tag( char *data, char **str, char **tag )
{
	char *ptr;
	
	/* Splits input data between 'str' and 'tag' */
	ptr = strrchr( data, ' ' );
	
	if( ptr == NULL || ptr[1] == '\0' )
		ereport( ERROR, (errmsg("Can't find tag in tagged type: \"%s\"", data) ) );

	*str = pstrdup( data );
	(*str)[ ptr - data ] = '\0';
	
	*tag = pstrdup( ptr+1 );
	if( strlen( *tag ) > MAX_TAG_LEN )  // We use this in query later, can't overflow
		ereport( ERROR, (errmsg("Oversize tag in: \"%s\"", data) ) );

	if( strpbrk( *tag, "'\"\\" ) != NULL )
		ereport( ERROR, (errmsg("Invalid tag in: \"%s\"", data) ) );

 	return;
}

static inline Datum
ComposeTaggedTypeDatum( TaggedTypeClass tt_class, Datum datum, Oid tagoid )
{
	struct taggedtyped *td = tt_palloc( sizeof( struct taggedtyped ) );
	
	td->tag = tagoid;
	td->val = datum;
	
	return PointerGetDatum( td );
}

static inline Datum
ComposeTaggedTypeFixed( TaggedTypeClass tt_class, Datum datum, Oid tagoid )
{
	struct taggedtypef *tf = tt_palloc( tt_class + sizeof(Oid) );
	
	tf->tag = tagoid;
	memcpy( &tf->val, DatumGetPointer( datum ), tt_class );
	
	return PointerGetDatum( tf );
}

static inline Datum
ComposeTaggedTypeVar( TaggedTypeClass tt_class, Datum datum, Oid tagoid )
{
	struct taggedtypev *tv = tt_palloc( VARSIZE( datum ) + sizeof(Oid) );
	
	tv->len = VARSIZE( datum ) + sizeof(Oid);
	tv->tag = tagoid;
	memcpy( &tv->val, DatumGetPointer( datum ) + VARHDRSZ, VARSIZE( datum ) - VARHDRSZ );
	
	return PointerGetDatum( tv );
}

/* The tt_class is the class of the passed datum (the base type). 
 * This function takes the base type and creates the tagged type. */
Datum
ComposeTaggedType( struct TaggedTypeInfo *tti, Datum datum, Oid tagoid )
{
	TaggedTypeClass tt_class = DetermineTaggedTypeClass( tti );

//	elog( NOTICE, "ComposeTaggedType( %d, %p, %d )", tt_class, DatumGetPointer(datum), tagoid );
	if( isTaggedTypeDatum( tt_class ) )
  		return ComposeTaggedTypeDatum( tt_class, datum, tagoid );
  	if( isTaggedTypeFixed( tt_class ) )
  		return ComposeTaggedTypeFixed( tt_class, datum, tagoid );
  	if( isTaggedTypeVar( tt_class ) )
  		return ComposeTaggedTypeVar( tt_class, datum, tagoid );

	elog( ERROR, "huh?" );
	PG_RETURN_VOID();
}

/* The tt_class is the class of the base type, although you pass the tagged type */
Oid
ExtractTaggedTypeOid( struct TaggedTypeInfo *tti, Datum datum )
{
	TaggedTypeClass tt_class = DetermineTaggedTypeClass( tti );

	if( isTaggedTypeDatum( tt_class ) )
  		return ((struct taggedtyped*)DatumGetPointer( datum ))->tag;
  	if( isTaggedTypeFixed( tt_class ) )
  		return ((struct taggedtypef*)DatumGetPointer( datum ))->tag;
  	if( isTaggedTypeVar( tt_class ) )
  		return ((struct taggedtypev*)DatumGetPointer( datum ))->tag;

	elog( ERROR, "huh?" );
	PG_RETURN_VOID();
	
}

/* The tt_class is the class of the base type, although you pass the tagged type */
Datum
ExtractTaggedTypeDatum( struct TaggedTypeInfo *tti, Datum datum )
{
	TaggedTypeClass tt_class = DetermineTaggedTypeClass( tti );

	if( isTaggedTypeDatum( tt_class ) )
  		return ((struct taggedtyped*)DatumGetPointer( datum ))->val;
  	if( isTaggedTypeFixed( tt_class ) )
  		return PointerGetDatum( &((struct taggedtypef*)DatumGetPointer( datum ))->val );
  	if( isTaggedTypeVar( tt_class ) )
  	{
  		struct varlena* tv = (struct varlena*)tt_palloc( VARSIZE( datum ) );
  		
  		tv->vl_len = VARSIZE( datum ) - sizeof(Oid);
  		memcpy( tv->vl_dat, &((struct taggedtypev*)DatumGetPointer( datum ))->val, VARSIZE( datum ) - sizeof(Oid) - VARHDRSZ );
  		
  		return PointerGetDatum( tv ) ;
	}
	elog( ERROR, "huh2?" );
	PG_RETURN_VOID();
	
}

/* Type input function. This part is seperated out so it can be used by real
 * type input function and the text-to-type conversion */
static Datum
_taggedtype_in( char *data, Oid returnoid )
{
	Datum	datum, return_datum;
	
	Oid	parsetag = 0;
	Size	datumSize;
	TaggedTypeClass	tt_class;	
	struct TaggedTypeInfo	*tti;
	char	*tag, *str;
	text	*tag_text;

	find_tag( data, &str, &tag );

	if( returnoid == 0 )
		ereport( ERROR, (errmsg("INTERNAL: Couldn't determine return type for function") ) );
		
	/* Now we know our return type, so now we look for our base type in
	 * the taggedtypes table. We primarily need:
	 * - the oid of the function to call to parse the data
	 * - the typmod to pass to that function
	 * - the textual name of the tag table */
	
	tti = GetTaggedTypeDetails( returnoid );
	if( !tti )
		ereport( ERROR, (errmsg("TaggedType lookup failure: %s", format_type_be(returnoid) ) ) );

	/* Convert C string to a value of the given type */
	datum = OidFunctionCall3( tti->typinput /*typinputoidfn*/,
				CStringGetDatum(str),
				ObjectIdGetDatum(tti->basetype /*typinputoid*/ ), // Should use getTypeIOParam(...),
				Int32GetDatum(tti->typmod /*typinputtypmod*/ ));
				
	tag_text = DatumGetTextP( DirectFunctionCall1( textin, CStringGetDatum( tag ) ) );

	datumSize = datumGetSize( datum, tti->typbyval, tti->typlen );
	tt_class = DetermineTaggedTypeClass( tti );

	parsetag = FindTagByName( tti, tag_text );

	return_datum = ComposeTaggedType( tti, datum, parsetag );

	PG_RETURN_DATUM( return_datum );
}

PG_FUNCTION_INFO_V1(taggedtype_in);

/* Type input function */
Datum taggedtype_in(PG_FUNCTION_ARGS)
{
	char	*data = PG_GETARG_CSTRING( 0 );
	Oid	returnoid = PG_GETARG_OID( 1 );
//	int4	typmod = PG_GETARG_INT32( 2 );

	/* Before PostgreSQL 8.1, base types received only zero for the
	 * second arg rather than their own type. So we have to work it out
	 * the long way. Given we might execute this often, maybe a cache is
	 * needed... */
	if( returnoid == 0 )
		returnoid = procLookupRettype( fcinfo->flinfo->fn_oid );
		
	return _taggedtype_in( data, returnoid );
}

PG_FUNCTION_INFO_V1(taggedtype_create_text);

/* cast from text function */
Datum taggedtype_create_text(PG_FUNCTION_ARGS)
{
	Oid	returnoid;
	text	*data = PG_GETARG_TEXT_P( 0 );

	returnoid = get_fn_expr_rettype( fcinfo->flinfo );

	return _taggedtype_in( text_to_charp(data), returnoid );
}

PG_FUNCTION_INFO_V1(taggedtype_out);

Datum taggedtype_out(PG_FUNCTION_ARGS)
{
	Datum	data = PG_GETARG_DATUM( 0 );
	Oid	argoid = 0;
	Size	datumSize;
	TaggedTypeClass	tt_class;
	struct TaggedTypeInfo	*tti;
	char	*outstr, *tag, *return_str;
	text	*tag_text;
	
	/* It used to be that all type output functions received 0 except if
	 * they were array or composite types. I was wanting to change that
	 * so all base types got their own type id. Unfortunatly in the
	 * meantime someone removed the second argument to the typout
	 * function altogether with makes the point moot... */
	 
	if( argoid == 0 )
		argoid = procLookupArgType( fcinfo->flinfo->fn_oid, 0 );
	if( argoid == 0 )
		ereport( ERROR, (errmsg("INTERNAL: Couldn't determine argument type for function %d", fcinfo->flinfo->fn_oid) ) );
		
	tti = GetTaggedTypeDetails( argoid );
	if( !tti )
		ereport( ERROR, (errmsg("TaggedType lookup failure: %d", argoid ) ) );

	tt_class = DetermineTaggedTypeClass( tti );
	datumSize = datumGetSize( PointerGetDatum( data ), tti->typbyval, tti->typlen );

//	debugDatum( cc(tt_class), data );
	
	outstr = DatumGetCString( OidFunctionCall3(tti->typoutput,
					ExtractTaggedTypeDatum( tti, data ),
					ObjectIdGetDatum(tti->basetype /*typinputoid*/), // Should use getTypeIOParam(...),
					Int32GetDatum(tti->typmod /*typinputtypmod*/)) );

	tag_text = FindTagByOid( tti, ExtractTaggedTypeOid( tti, data) );
	tag = text_to_charp( tag_text );
	
	return_str = (char*)tt_palloc( strlen( outstr ) + strlen( tag ) + 1 );
	sprintf( return_str, "%s %s", outstr, tag );
	
	PG_RETURN_CSTRING( return_str );
}

PG_FUNCTION_INFO_V1(taggedtype_apply_typmod);

/* This function is only used if the underlying type has a typmod (like say
 * numeric, timestamp of varchar). This will them be call to apply as
 * necessary. */

Datum taggedtype_apply_typmod(PG_FUNCTION_ARGS)
{
	Datum	datum = PG_GETARG_DATUM(0);
//	int32	typmod = PG_GETARG_INT32(1);
	bool	explicit = PG_GETARG_BOOL(2);

	TaggedTypeClass tt_class;
	struct TaggedTypeInfo	*tti;
	Datum	result_datum, raw_datum;
	Oid	tagoid;
	
	elog( NOTICE, "taggedtype_apply_typmod(%p)", DatumGetPointer( datum ) );
	
	tti = GetTaggedTypeDetails( get_fn_expr_rettype( fcinfo->flinfo ) );
	tt_class = DetermineTaggedTypeClass( tti );

	raw_datum = ExtractTaggedTypeDatum( tti, datum );
	tagoid = ExtractTaggedTypeOid( tti, datum ) ;
	
	raw_datum = OidFunctionCall3( tti->typapplytypmod, raw_datum, Int32GetDatum(tti->typmod), BoolGetDatum(explicit) );
//	debugDatum( tt_class, datum );
	
	result_datum = ComposeTaggedType( tti, raw_datum, tagoid );

//	debugDatum( cc(tt_class), result_datum );
	
	PG_RETURN_DATUM( result_datum );
}

PG_FUNCTION_INFO_V1(taggedtype_create);

/* Takes the composite type and returns base type */
Datum taggedtype_create(PG_FUNCTION_ARGS)
{
/* This changed between 7.4 and 8.0 in fmgr.h v1.34 (2004/04/01)*/
#if CATALOG_VERSION_NO < 200404010
	TupleTableSlot  *ctype = (TupleTableSlot *) PG_GETARG_POINTER(0);
#else
	HeapTupleHeader  ctype = PG_GETARG_HEAPTUPLEHEADER(0);
#endif
	bool	isNull;
	text	*tag_text = DatumGetTextP( GetAttributeByNum( ctype, 1, &isNull ) );
	Datum	datum = GetAttributeByNum( ctype, 2, &isNull );
	Datum	result_datum;
	TaggedTypeClass tt_class;
	struct TaggedTypeInfo	*tti;
	
	Oid	tagoid;
	
	tti = GetTaggedTypeDetails( get_fn_expr_rettype( fcinfo->flinfo ) );
	tt_class = DetermineTaggedTypeClass( tti );
	tagoid = FindTagByName( tti, tag_text );

//	debugDatum( tt_class, datum );
	
	if( tti->typapplytypmod )
		datum = OidFunctionCall3( tti->typapplytypmod, datum, Int32GetDatum(tti->typmod), BoolGetDatum(true) );

	result_datum = ComposeTaggedType( tti, datum, tagoid );

//	debugDatum( cc(tt_class), result_datum );
	
	PG_RETURN_DATUM( result_datum );
}

PG_FUNCTION_INFO_V1(taggedtype_compose);

/* Takes the base type and the tag and returns tagged type */
Datum taggedtype_compose(PG_FUNCTION_ARGS)
{
	Datum	datum = PG_GETARG_DATUM(0);
	text	*tag_text = PG_GETARG_TEXT_P(1);

	TaggedTypeClass tt_class;
	struct TaggedTypeInfo	*tti;
	Datum	result_datum;
	Oid	tagoid;
	
	tti = GetTaggedTypeDetails( get_fn_expr_rettype( fcinfo->flinfo ) );
	tt_class = DetermineTaggedTypeClass( tti );
	tagoid = FindTagByName( tti, tag_text );

//	debugDatum( tt_class, datum );
	
	if( tti->typapplytypmod )
		datum = OidFunctionCall3( tti->typapplytypmod, datum, Int32GetDatum(tti->typmod), BoolGetDatum(true) );

	result_datum = ComposeTaggedType( tti, datum, tagoid );

//	debugDatum( cc(tt_class), result_datum );
	
	PG_RETURN_DATUM( result_datum );
}

PG_FUNCTION_INFO_V1(taggedtype_extract);

/* Takes the base type and returns the composite type */
Datum taggedtype_extract(PG_FUNCTION_ARGS)
{
	Datum	datum = PG_GETARG_DATUM(0);
	Oid	rettypeoid = get_fn_expr_rettype( fcinfo->flinfo );
	
	TupleDesc tupleDesc = TypeGetTupleDesc( rettypeoid, NULL );
	TupleTableSlot *tupleSlot = TupleDescGetSlot( tupleDesc );
	HeapTuple	heapTuple;
	
	TaggedTypeClass	tt_class;
	
	char	nulls[2] = { ' ', ' ' };
	Datum	datums[2];
	
	// Gets the inputlen and byval of the base type
	struct TaggedTypeInfo	*tti = GetTaggedTypeDetails( get_fn_expr_argtype( fcinfo->flinfo, 0 ) );
	tt_class = DetermineTaggedTypeClass( tti );
	
	datums[0] = PointerGetDatum( FindTagByOid( tti, ExtractTaggedTypeOid( tti, datum ) ) );
	datums[1] = ExtractTaggedTypeDatum( tti, datum );

//	debugDatum( tt_class, datums[1] );
	
	heapTuple = heap_formtuple( tupleDesc, datums, nulls );
	
//	debugDump( heapTuple, sizeof(*heapTuple) );
//	debugDump( heapTuple->t_data, heapTuple->t_len );
	PG_RETURN_DATUM( TupleGetDatum( tupleSlot, heapTuple ) );
}

PG_FUNCTION_INFO_V1(taggedtype_extracttag);

/* Takes the base type and returns the tuple from the tagtable that matches */
Datum taggedtype_extracttag(PG_FUNCTION_ARGS)
{
        Datum   datum = PG_GETARG_DATUM(0);
        struct TaggedTypeInfo   *tti = GetTaggedTypeDetails( get_fn_expr_argtype( fcinfo->flinfo, 0 ) );
	TaggedTypeClass	tt_class = DetermineTaggedTypeClass( tti );

	Oid	tagoid = ExtractTaggedTypeOid( tti, datum );
	
	Relation	tt_rel;
	ScanKeyData	skey;
	HeapScanDesc	scan;
	HeapTuple	tuple;
	TupleDesc	tupleDesc;
	
#if CATALOG_VERSION_NO < 200404010
	HeapTuple	result;
	TupleTableSlot	*tupleSlot;
#else	
	HeapTupleHeader	result;
#endif
	
	// Open heap
	tt_rel = heap_open( tti->tagtable, 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, 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 )
	{
		elog( NOTICE, "Lookup tag %d failed", tagoid );
		heap_endscan( scan );
		heap_close( tt_rel, AccessShareLock );
		PG_RETURN_NULL();
	}

/* Before heaptuple changes */
#if CATALOG_VERSION_NO < 200404010
	result = heap_copytuple( tuple );
#else
	// Copies tuples and then 'blesses' it, typmod field and such...
	result = SPI_returntuple( tuple, tupleDesc );
#endif
	
	heap_endscan( scan );
	heap_close( tt_rel, AccessShareLock );
	
//	debugDump( tuple, sizeof(*tuple) );
//	debugDump( tuple->t_data, tuple->t_len );

#if CATALOG_VERSION_NO < 200404010
	tupleSlot = TupleDescGetSlot( tupleDesc );
	PG_RETURN_DATUM( TupleGetDatum( tupleSlot, result ) );
#else
	PG_RETURN_POINTER( result );
#endif
}

PG_FUNCTION_INFO_V1(create_tagged_type);

Datum create_tagged_type(PG_FUNCTION_ARGS)
{
	text	*name = PG_GETARG_TEXT_P( 0 );
	text	*basetype = PG_GETARG_TEXT_P( 1 );
	Oid	tagtable = PG_GETARG_OID( 2 );
	
	char	*dll_path, *query;
	
	struct TaggedTypeInfo	tti;
	
	Oid	castfuncoid = InvalidOid;
	
	TaggedTypeClass	tt_class;
	
	char	*basetypename = text_to_charp( basetype );
	char	*newtypename = text_to_charp( name );
	char	*newtypename_t = (char*)palloc( strlen(newtypename)+3 );
	
	sprintf( newtypename_t, "%s_t", newtypename );

	parseTypeString( basetypename, &tti.basetype, &tti.typmod );
//	elog( NOTICE, "Name: %s, Type: %d, Typmod: %d, Table: %d", newtypename, tti.basetype, tti.typmod, tagtable );
	
	if( tti.basetype == CSTRINGOID )
	  ereport( ERROR, (errmsg("Composite CStrings are not allowed")) );
	  
	get_typlenbyval( tti.basetype, &tti.typlen, &tti.typbyval );
/* We don't use SPI anymore, hence we don't care about column name */
//	if( get_attnum( tagtable, "tag" ) != 1 )
//	  ereport( ERROR, (errmsg("First column of table '%s' must be named 'tag'", get_rel_name( tagtable ) ) ) );
	if( get_atttype( tagtable, 1 ) != TEXTOID )
	  ereport( ERROR, (errmsg("First column of table '%s' must be of type text", get_rel_name( tagtable ) ) ) );

	if( get_attnum( tagtable, "oid" ) == InvalidAttrNumber )
	  ereport( ERROR, (errmsg("Tag table '%s' must have oids", get_rel_name( tagtable ) ) ) );
	  
	tt_class = DetermineTaggedTypeClass( &tti );
	
	tt_SPI_start();
	  
	dll_path = GetDLLPath();
	
//	elog( NOTICE, "Typlen: %d, Typbyval: %d, DLL path: %s", tti.typlen, tti.typbyval, dll_path );

	/********** Type input/output functions and the types themselves ********/
	query = (char*)palloc( 1024 + strlen( dll_path ) );
	sprintf( query, "CREATE FUNCTION "TAGGED_TABLE_NAMESPACE".%s_in(cstring, oid, integer) RETURNS %s AS '%s','taggedtype_in' LANGUAGE 'C' STABLE STRICT", 
		    newtypename, newtypename, dll_path );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE FUNCTION "TAGGED_TABLE_NAMESPACE".%s_out(%s) RETURNS cstring AS '%s','taggedtype_out' LANGUAGE 'C' STABLE STRICT", 
		    newtypename, newtypename, dll_path );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE TYPE %s ( INTERNALLENGTH = %d, INPUT = "TAGGED_TABLE_NAMESPACE".%s_in, OUTPUT = "TAGGED_TABLE_NAMESPACE".%s_out );",
		    newtypename, TaggedTypeNewSize( tt_class ), newtypename, newtypename );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE TYPE %s_t AS ( tag text, value %s );", newtypename, basetypename );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );

	/********** Conversion from text to tagged type **********/
	sprintf( query, "CREATE FUNCTION %s( text ) RETURNS %s AS '%s','taggedtype_create_text' LANGUAGE 'C' STABLE STRICT;", 
	                        newtypename, newtypename, dll_path );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );

	/********** Conversion between packed and unpacked versions *********/
	sprintf( query, "CREATE FUNCTION %s( %s_t ) RETURNS %s AS '%s','taggedtype_create' LANGUAGE 'C' IMMUTABLE STRICT;", 
	                        newtypename, newtypename, newtypename, dll_path );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE FUNCTION %s( %s, text ) RETURNS %s AS '%s','taggedtype_compose' LANGUAGE 'C' STABLE STRICT;", 
	                        newtypename, basetypename, newtypename, dll_path );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE FUNCTION %s_t( %s ) RETURNS %s_t AS '%s','taggedtype_extract' LANGUAGE 'C' IMMUTABLE STRICT;", 
	                        newtypename, newtypename, newtypename, dll_path );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );

	/*********** Functions to access components *********/
	sprintf( query, "CREATE FUNCTION tag( %s ) RETURNS text AS 'SELECT (%s_t($1)).tag' LANGUAGE 'SQL' IMMUTABLE STRICT;", 
	                        newtypename, newtypename );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE FUNCTION value( %s ) RETURNS %s AS 'SELECT (%s_t($1)).value' LANGUAGE 'SQL' IMMUTABLE STRICT;", 
	                        newtypename, basetypename, newtypename );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE FUNCTION tagdata( %s ) RETURNS %s AS '%s','taggedtype_extracttag' LANGUAGE 'C' STABLE STRICT;", 
	                        newtypename, get_rel_name( tagtable ), dll_path);
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );

	if( tti.typmod != -1 )   /* Base has typmod value, so we need to find the function to apply it */
	{
/* Before this catalog the requisite details wern't in pg_cast, and the core
 * hardcoded the functions for precision, so emit a warning */

#if CATALOG_VERSION_NO < 200406160
		if( tti.basetype == 1042 ) castfuncoid = 668;  /* char() */
		if( tti.basetype == 1043 ) castfuncoid = 669;  /* varchar() */
		if( tti.basetype == 1114 ) castfuncoid = 1961; /* timestamp w/o timezone */
		if( tti.basetype == 1184 ) castfuncoid = 1967; /* timestamp w/ timezone */
		if( tti.basetype == 1700 ) castfuncoid = 1703; /* numeric */
		
		if( !castfuncoid )
			ereport( WARNING, (errmsg("Cannot enforce precision in result in this version (<7.5)")) );
#else
		bool	isNull;
		
		sprintf( query, "SELECT castfunc FROM pg_cast WHERE castsource = casttarget and casttarget = %d and castcontext = 'i'",
				tti.basetype );
		elog( NOTICE, "Q: %s", query );
		if( SPI_exec( query, 1 ) != SPI_OK_SELECT || SPI_processed != 1 )
			ereport( ERROR, (errmsg("SPI_exec to find castfunc failed") ) );

		castfuncoid = SPI_getbinval( SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isNull );

		sprintf( query, "CREATE FUNCTION %s( %s, integer, bool ) RETURNS %s AS '%s','taggedtype_apply_typmod' LANGUAGE 'C' IMMUTABLE STRICT;", 
		                        newtypename, newtypename, newtypename, dll_path);
		elog( NOTICE, "Q: %s", query );
		SPI_exec( query, 1 );
#endif
	}
	/*********** Declare tagged type to backend *********/
	sprintf( query, "INSERT INTO " TAGGED_TABLE_FULLNAME " (taggedtype, basetype, typmod, tagtable, castfunc) VALUES ('%s','%s',%d,'%s','%d')",
		    newtypename, basetypename, tti.typmod, get_rel_name( tagtable ), castfuncoid );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );

	/*********** Create casts ***********/
	sprintf( query, "CREATE CAST (%s AS %s_t) WITH FUNCTION %s_t(%s) AS IMPLICIT",
		    newtypename, newtypename, newtypename, newtypename );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE CAST (%s_t AS %s) WITH FUNCTION %s(%s_t) AS IMPLICIT",
		    newtypename, newtypename, newtypename, newtypename );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE CAST (TEXT AS %s) WITH FUNCTION %s(TEXT)",
		    newtypename, newtypename );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
/* User-defined typmod functions are not yet permitted. When they are, enable here */
#if 0
	if( castfuncoid != 0 )
	{
		sprintf( query, "CREATE CAST (%s AS %s) WITH FUNCTION %s(%s, integer, bool) AS IMPLICIT",
			    newtypename, newtypename, newtypename, newtypename );
		elog( NOTICE, "Q: %s", query );
		SPI_exec( query, 1 );
	}
#endif

	/***** Record dependancies *****/
	{
#ifndef TypeRelationId
#define TypeRelationId RelOid_pg_type
#endif
#ifndef RelationRelationId
#define RelationRelationId RelOid_pg_class
#endif
		ObjectAddress	oa_new_type;
		ObjectAddress	oa_new_type_t;
		ObjectAddress	oa_base_type;
		ObjectAddress	oa_base_table;
		ObjectAddress	oa_tt_table;
		RangeVar	rv;
		int32	typmod;

		memset( &rv, 0, sizeof(rv) );
		
		oa_new_type.classId = TypeRelationId;
		parseTypeString( newtypename, &oa_new_type.objectId, &typmod );
		oa_new_type.objectSubId = 0;
		
		oa_new_type_t.classId = TypeRelationId;
		parseTypeString( newtypename_t, &oa_new_type_t.objectId, &typmod );
		oa_new_type_t.objectSubId = 0;
		
		oa_base_type.classId = TypeRelationId;
		parseTypeString( basetypename, &oa_base_type.objectId, &typmod );
		oa_base_type.objectSubId = 0;
		
		oa_base_table.classId = RelationRelationId;
		oa_base_table.objectId = tagtable;
		oa_base_table.objectSubId = 0;
		
		rv.relname = TAGGED_TABLE_NAME;
		rv.schemaname = TAGGED_TABLE_NAMESPACE;
		oa_tt_table.classId = RelationRelationId;
		oa_tt_table.objectId = RangeVarGetRelid( &rv, false );
		oa_tt_table.objectSubId = 0;
		
		recordDependencyOn( &oa_new_type, &oa_base_table, 'n' );
		recordDependencyOn( &oa_new_type, &oa_base_type, 'n' );
		recordDependencyOn( &oa_new_type_t, &oa_new_type, 'i' );
		recordDependencyOn( &oa_new_type, &oa_tt_table, 'n' );
	}
		
	tt_SPI_stop();
	
	PG_RETURN_NULL();
}

/*
  Tagged operators
  
  Case 1:
    Currency + Currency = Currency
  Case 2:
    Currency * Numeric = Currency
  Case 3:
    Currency / Currency = Numeric
    
  unified to:
  create_tagged_operator('+','currency','+','currency','currency');
  create_tagged_operator('*','currency','*','numeric','currency');
  create_tagged_operator('/','currency','/','currency','numeric');
  
  and:
  
  create_tagged_operator( regtype, 'text', regtype, regtype ) =>
    select create_tagged_operator( $2, $1, $2, $3, $4 );
*/

static inline void
FindTaggedTypeDetails( Oid taggedtype, Oid *basetype, Oid *tagtable )
{
	struct TaggedTypeInfo	*tti;
	
	tti = GetTaggedTypeDetails( taggedtype );
	
	if( tti )
	{
		*basetype = tti->basetype;
		*tagtable = tti->tagtable;
	}
	else
	{
		*basetype = taggedtype;
		*tagtable = InvalidOid;
	}
//	elog( NOTICE, "FindTaggedTypeDetails(%d)- => (%d,%d)", taggedtype, *basetype, *tagtable );
	
	return;
}

static Oid
FindOperator( char *op, Oid left_type, Oid right_type, Oid res_type )
{
	char	query[1024];
	bool	isNull;
	
	sprintf( query, "SELECT oid FROM pg_operator WHERE oprname = '%s' AND oprleft = %d AND oprright = %d AND oprresult = %d", 
			op, left_type, right_type, res_type );
	SPI_exec( query, 1 );
	
	if( SPI_processed != 1 )
		ereport( ERROR, (errmsg("Couldn't find operator %s '%s' %s = %s", 
				format_type_be(left_type), op, format_type_be(right_type), format_type_be(res_type) ) ) );
	
	return DatumGetObjectId( SPI_getbinval( SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isNull ) );
}

PG_FUNCTION_INFO_V1(create_tagged_operator);

Datum create_tagged_operator(PG_FUNCTION_ARGS)
{
	text	*new_op_t  = PG_GETARG_TEXT_P( 0 );
	Oid	left_type  = PG_GETARG_OID( 1 );
	text	*old_op_t  = PG_GETARG_TEXT_P( 2 );
	Oid	right_type = PG_GETARG_OID( 3 );
	Oid	res_type   = PG_GETARG_OID( 4 );

	Oid	left_base_type,  left_tag_table;
	Oid	right_base_type, right_tag_table;
	Oid	res_base_type,   res_tag_table;
	
	Oid	tag_table, operator_base_oid;
	
	char	*new_op = text_to_charp( new_op_t );
	char	*old_op = text_to_charp( old_op_t );
	
	char	*query = palloc( 1024 );
	
	char	*funcname = palloc( 1024 );
	char	*rawfuncname = palloc(32);
	char	*dll_path;
	
	if( strpbrk( old_op, "\\'" ) )
		ereport( ERROR, (errmsg("Invalid operator name '%s'", old_op) ) );
	if( strpbrk( new_op, "\\'" ) )
		ereport( ERROR, (errmsg("Invalid operator name '%s'", new_op) ) );

	tt_SPI_start();

	/* If taggedtype, basetype = basetype, tagtable = tagtable else basetype = tagtype and tagtable = 0 */
	FindTaggedTypeDetails( left_type,  &left_base_type,  &left_tag_table );
	FindTaggedTypeDetails( right_type, &right_base_type, &right_tag_table );
	FindTaggedTypeDetails( res_type,   &res_base_type,   &res_tag_table );

	if( (left_tag_table != InvalidOid) + (right_tag_table != InvalidOid) + (res_tag_table != InvalidOid) < 2 )
		ereport( ERROR, (errmsg("At least two arguments must be tagged types") ) );
	
	/* At least one of them must be tagged */
	tag_table = left_tag_table ? left_tag_table : right_tag_table;
	
	if( !(left_tag_table  == InvalidOid || left_tag_table  == tag_table ) ||
	    !(right_tag_table == InvalidOid || right_tag_table == tag_table ) ||
	    !(res_tag_table   == InvalidOid || res_tag_table   == tag_table ) )
		ereport( ERROR, (errmsg("All taggedtypes must have same tag table") ) );
		
	operator_base_oid = FindOperator( old_op, left_base_type, right_base_type, res_base_type );
	
	sprintf( funcname, TAGGED_TABLE_NAMESPACE ".taggedop_%d_%d_%d", operator_base_oid, left_type, right_type );
	sprintf( rawfuncname, "tagged_operator_%c%c%c", left_tag_table ? 't' : 'o',
							right_tag_table ? 't' : 'o',
							res_tag_table ? 't' : 'o' );
	dll_path = GetDLLPath();
	
	sprintf( query, "CREATE FUNCTION %s (%s,%s) RETURNS %s AS '%s','%s' LANGUAGE 'C' IMMUTABLE STRICT;",
		    funcname, format_type_be( left_type ), format_type_be( right_type ), format_type_be( res_type ),
		    dll_path, rawfuncname );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "CREATE OPERATOR %s ( PROCEDURE = %s, LEFTARG = %s, RIGHTARG = %s )",
		    new_op, funcname, format_type_be( left_type ), format_type_be( right_type ) );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );
	sprintf( query, "INSERT INTO " TAGGED_OPERATOR_FULLNAME " (taggedoperator,baseoperator,taggedfunc) VALUES ('%s(%s,%s)',%d,'%s'::regproc)",
			new_op, format_type_be( left_type ), format_type_be( right_type ), operator_base_oid, funcname );
	elog( NOTICE, "Q: %s", query );
	SPI_exec( query, 1 );

	ereport( NOTICE, ( errmsg( "%s(%s,%s) => %s maps to %s(%s,%s) => %s", 
				new_op, format_type_be(left_type), format_type_be(right_type), format_type_be(res_type), 
				old_op, format_type_be(left_base_type), format_type_be(right_base_type), format_type_be(res_base_type) ) ) );
	
	tt_SPI_stop();
		
	PG_RETURN_NULL();
}

/* These functions handle all the possibilities for binary operators. In the
 * suffix, t means tagged, o means ordinary. The first two are the
 * arguments, the last is the return type */

PG_FUNCTION_INFO_V1(tagged_operator_ttt);
PG_FUNCTION_INFO_V1(tagged_operator_tto);
PG_FUNCTION_INFO_V1(tagged_operator_tot);
PG_FUNCTION_INFO_V1(tagged_operator_ott);

/* Handles currency + currency = currency */
Datum tagged_operator_ttt(PG_FUNCTION_ARGS)
{
	Datum	arg1 = PG_GETARG_DATUM( 0 );
	Datum	arg2 = PG_GETARG_DATUM( 1 );
	Datum	res, newres;
	Datum	newarg1, newarg2;

	TaggedTypeClass	ttc1, ttc2, ttcr;
	
	Oid	type1 = get_fn_expr_argtype( fcinfo->flinfo, 0 );
	Oid	type2 = get_fn_expr_argtype( fcinfo->flinfo, 1 );
	Oid	typer = get_fn_expr_rettype( fcinfo->flinfo );
	
	Oid	tag;
	
	struct TaggedTypeInfo	*tti1 = GetTaggedTypeDetails( type1 );
	struct TaggedTypeInfo	*tti2 = GetTaggedTypeDetails( type2 );
	struct TaggedTypeInfo	*ttir = GetTaggedTypeDetails( typer );

	struct TaggedOperatorInfo	*tto = GetTaggedOperatorDetails( fcinfo->flinfo->fn_oid );
	
	if( !tto )
	{
		ereport( ERROR, (errmsg("tagged_operator_ttt: Tagged operator not identified") ) );
	}
	if( !tti1 || !tti2 || !ttir ) // Shouldn't happen...
	{
		ereport( ERROR, (errmsg("tagged_operator_ttt: Not all args tagged") ) );
	}
	
	ttc1 = DetermineTaggedTypeClass( tti1 );
	ttc2 = DetermineTaggedTypeClass( tti2 );
	ttcr = DetermineTaggedTypeClass( ttir );
	
	if( ExtractTaggedTypeOid( tti1, arg1 ) != ExtractTaggedTypeOid( tti2, arg2 ) )
	{
		char *tag1 = text_to_charp( FindTagByOid(  tti1, ExtractTaggedTypeOid( tti1, arg1 ) ) );
		char *tag2 = text_to_charp( FindTagByOid(  tti2, ExtractTaggedTypeOid( tti2, arg2 ) ) );
		ereport( ERROR, (errmsg( "Using tagged operator %s with incompatable tags (%s,%s)", 
		           format_operator( tto->tagoper ), tag1, tag2 ) ) );
	}
	
	tag = ExtractTaggedTypeOid( tti1, arg1 );
	
	newarg1 = ExtractTaggedTypeDatum( tti1, arg1 );
	newarg2 = ExtractTaggedTypeDatum( tti2, arg2 );

	newres = OidFunctionCall2( tto->basefunc, newarg1, newarg2 );

	if( ttir->typapplytypmod )
		newres = OidFunctionCall3( ttir->typapplytypmod, newres, Int32GetDatum(ttir->typmod), BoolGetDatum(false) );

	res = ComposeTaggedType( ttir, newres, tag );

	PG_RETURN_DATUM( res );
}

/* Handles currency * numeric = currency */
Datum tagged_operator_tot(PG_FUNCTION_ARGS)
{
	Datum	arg1 = PG_GETARG_DATUM( 0 );
	Datum	arg2 = PG_GETARG_DATUM( 1 );
	Datum	res, newres;
	Datum	newarg1;

	TaggedTypeClass	ttc1, ttcr;
	
	Oid	type1 = get_fn_expr_argtype( fcinfo->flinfo, 0 );
	Oid	typer = get_fn_expr_rettype( fcinfo->flinfo );
	
	Oid	tag;
	
	struct TaggedTypeInfo	*tti1 = GetTaggedTypeDetails( type1 );
	struct TaggedTypeInfo	*ttir = GetTaggedTypeDetails( typer );

	struct TaggedOperatorInfo	*tto = GetTaggedOperatorDetails( fcinfo->flinfo->fn_oid );
	
	if( !tto )
	{
		ereport( ERROR, (errmsg("tagged_operator_tot: Tagged operator not identified") ) );
	}
	if( !tti1 || !ttir ) // Shouldn't happen...
	{
		ereport( ERROR, (errmsg("tagged_operator_tot: Incorrect args tagged") ) );
	}
	
	ttc1 = DetermineTaggedTypeClass( tti1 );
	ttcr = DetermineTaggedTypeClass( ttir );
	
	tag = ExtractTaggedTypeOid( tti1, arg1 );
	
	newarg1 = ExtractTaggedTypeDatum( tti1, arg1 );

	newres = OidFunctionCall2( tto->basefunc, newarg1, arg2 );

	if( ttir->typapplytypmod )
		newres = OidFunctionCall3( ttir->typapplytypmod, newres, Int32GetDatum(ttir->typmod), BoolGetDatum(false) );

	res = ComposeTaggedType( ttir, newres, tag );

	PG_RETURN_DATUM( res );
}

/* Handles currency / currency = numeric */
Datum tagged_operator_tto(PG_FUNCTION_ARGS)
{
	Datum	arg1 = PG_GETARG_DATUM( 0 );
	Datum	arg2 = PG_GETARG_DATUM( 1 );
	Datum	newres;
	Datum	newarg1, newarg2;

	TaggedTypeClass	ttc1, ttc2;
	
	Oid	type1 = get_fn_expr_argtype( fcinfo->flinfo, 0 );
	Oid	type2 = get_fn_expr_argtype( fcinfo->flinfo, 1 );
//	Oid	typer = get_fn_expr_rettype( fcinfo->flinfo );
	
	struct TaggedTypeInfo	*tti1 = GetTaggedTypeDetails( type1 );
	struct TaggedTypeInfo	*tti2 = GetTaggedTypeDetails( type2 );

	struct TaggedOperatorInfo	*tto = GetTaggedOperatorDetails( fcinfo->flinfo->fn_oid );
	
	if( !tto )
	{
		ereport( ERROR, (errmsg("tagged_operator_tto: Tagged operator not identified") ) );
	}
	if( !tti1 || !tti2 ) // Shouldn't happen...
	{
		ereport( ERROR, (errmsg("tagged_operator_tto: Incorrect args tagged") ) );
	}
	
	ttc1 = DetermineTaggedTypeClass( tti1 );
	ttc2 = DetermineTaggedTypeClass( tti2 );

	/* Check tags and error on mismatch */	
	if( ExtractTaggedTypeOid( tti1, arg1 ) != ExtractTaggedTypeOid( tti2, arg2 ) )
	{
		char *tag1 = text_to_charp( FindTagByOid(  tti1, ExtractTaggedTypeOid( tti1, arg1 ) ) );
		char *tag2 = text_to_charp( FindTagByOid(  tti2, ExtractTaggedTypeOid( tti2, arg2 ) ) );
		ereport( ERROR, (errmsg( "Using tagged operator %s with incompatable tags (%s,%s)", 
		           format_operator( tto->tagoper ), tag1, tag2 ) ) );
	}
	
	newarg1 = ExtractTaggedTypeDatum( tti1, arg1 );
	newarg2 = ExtractTaggedTypeDatum( tti2, arg2 );

	newres = OidFunctionCall2( tto->basefunc, newarg1, newarg2 );

	PG_RETURN_DATUM( newres );
}

/* Handles numeric * currency = currency */
Datum tagged_operator_ott(PG_FUNCTION_ARGS)
{
	Datum	arg1 = PG_GETARG_DATUM( 0 );
	Datum	arg2 = PG_GETARG_DATUM( 1 );
	Datum	res, newres;
	Datum	newarg2;

	TaggedTypeClass	ttc2, ttcr;
	
	Oid	type2 = get_fn_expr_argtype( fcinfo->flinfo, 1 );
	Oid	typer = get_fn_expr_rettype( fcinfo->flinfo );
	
	Oid	tag;
	
	struct TaggedTypeInfo	*tti2 = GetTaggedTypeDetails( type2 );
	struct TaggedTypeInfo	*ttir = GetTaggedTypeDetails( typer );

	struct TaggedOperatorInfo	*tto = GetTaggedOperatorDetails( fcinfo->flinfo->fn_oid );
	
	if( !tto )
	{
		ereport( ERROR, (errmsg("tagged_operator_ott: Tagged operator not identified") ) );
	}
	if( !tti2 || !ttir ) // Shouldn't happen...
	{
		ereport( ERROR, (errmsg("tagged_operator_ott: Incorrect args tagged") ) );
	}
	
	ttc2 = DetermineTaggedTypeClass( tti2 );
	ttcr = DetermineTaggedTypeClass( ttir );
	
	tag = ExtractTaggedTypeOid( tti2, arg2 );
	
	newarg2 = ExtractTaggedTypeDatum( tti2, arg2 );

	newres = OidFunctionCall2( tto->basefunc, arg1, newarg2 );

	if( ttir->typapplytypmod )
		newres = OidFunctionCall3( ttir->typapplytypmod, newres, Int32GetDatum(ttir->typmod), BoolGetDatum(false) );

	res = ComposeTaggedType( ttir, newres, tag );

	PG_RETURN_DATUM( res );
}

