/*-------------------------------------------------------------------------
 *
 * partitioning.c
 *	  Functions for dealing with partitioned tables.
 *
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "fmgr.h"
#include "funcapi.h"

#include "access/htup.h"
#include "access/htup_details.h"
#include "nodes/parsenodes.h"
#include "nodes/pg_list.h"
#include "utils/builtins.h"
#include "utils/elog.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

#include "distributed/metadata_cache.h"
#include "distributed/metadata_utility.h"


/* exports for SQL callable functions */
PG_FUNCTION_INFO_V1(time_partition_range);


/*
 * time_partition_range returns the lower and upper bound of partition
 * key values for the partition of a time-partitioned table.
 */
Datum
time_partition_range(PG_FUNCTION_ARGS)
{
	CheckCitusVersion(ERROR);

	Oid relationId = PG_GETARG_OID(0);

	/* create tuple descriptor for return value */
	TupleDesc metadataDescriptor = NULL;
	TypeFuncClass resultTypeClass = get_call_result_type(fcinfo, NULL,
														 &metadataDescriptor);
	if (resultTypeClass != TYPEFUNC_COMPOSITE)
	{
		ereport(ERROR, (errmsg("return type must be a row type")));
	}

	/* get the pg_class record */
	HeapTuple tuple = SearchSysCache1(RELOID, relationId);
	if (!HeapTupleIsValid(tuple))
	{
		ereport(ERROR, (errmsg("relation with OID %u does not exist", relationId)));
	}

	/* get the pg_class record */
	bool isNull = false;
	Datum partitionBoundDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relpartbound,
												&isNull);
	if (isNull)
	{
		ereport(ERROR, (errmsg("relation \"%s\" is not a partition",
							   get_rel_name(relationId))));
	}

	PartitionBoundSpec *partitionBoundSpec =
		(PartitionBoundSpec *) stringToNode(TextDatumGetCString(partitionBoundDatum));

	if (!IsA(partitionBoundSpec, PartitionBoundSpec))
	{
		ereport(ERROR, (errmsg("expected PartitionBoundSpec")));
	}

	if (partitionBoundSpec->strategy != PARTITION_STRATEGY_RANGE)
	{
		ereport(ERROR, (errmsg("relation \"%s\" is not a range partition",
							   get_rel_name(relationId)),
						errdetail("time_partition_range can only be used for "
								  "partitions of range-partitioned tables with a single "
								  "partition column")));
	}

	Datum values[2];
	bool isNulls[2];

	memset(values, 0, sizeof(values));
	memset(isNulls, false, sizeof(isNulls));

	if (partitionBoundSpec->is_default)
	{
		/* return NULL for default partition */
		isNulls[0] = true;
		isNulls[1] = true;
	}
	else
	{
		if (list_length(partitionBoundSpec->lowerdatums) != 1 ||
			list_length(partitionBoundSpec->upperdatums) != 1)
		{
			ereport(ERROR, (errmsg("relation \"%s\" is a partition with multiple "
								   "partition columns",
								   get_rel_name(relationId)),
							errdetail("time_partition_range can only be used for "
									  "partitions of range-partitioned tables with a "
									  "single partition column")));
		}

		PartitionRangeDatum *lowerBoundDatum =
			castNode(PartitionRangeDatum, linitial(partitionBoundSpec->lowerdatums));
		PartitionRangeDatum *upperBoundDatum =
			castNode(PartitionRangeDatum, linitial(partitionBoundSpec->upperdatums));

		Const *lowerConst = castNode(Const, lowerBoundDatum->value);
		Const *upperConst = castNode(Const, upperBoundDatum->value);

		char *lowerConstStr = DatumToString(lowerConst->constvalue,
											lowerConst->consttype);

		char *upperConstStr = DatumToString(upperConst->constvalue,
											upperConst->consttype);

		values[0] = CStringGetTextDatum(lowerConstStr);
		values[1] = CStringGetTextDatum(upperConstStr);
	}

	HeapTuple metadataTuple = heap_form_tuple(metadataDescriptor, values, isNulls);
	Datum metadataDatum = HeapTupleGetDatum(metadataTuple);

	ReleaseSysCache(tuple);

	PG_RETURN_DATUM(metadataDatum);
}
