/*-------------------------------------------------------------------------
 *
 * qualify_function_stmt.c
 *	  Functions specialized in fully qualifying all function statements. These
 *	  functions are dispatched from qualify.c
 *
 *	  Fully qualifying function statements consists of adding the schema name
 *	  to the subject of the function and types as well as any other branch of
 *    the parsetree.
 *
 *	  Goal would be that the deparser functions for these statements can
 *	  serialize the statement without any external lookups.
 *
 * Copyright (c), Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "catalog/namespace.h"
#include "catalog/pg_proc.h"
#include "parser/parse_func.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"

#include "distributed/deparser.h"
#include "distributed/version_compat.h"

/* forward declaration for qualify functions */
static void QualifyFunction(ObjectWithArgs *func, ObjectType type);
static void QualifyFunctionSchemaName(ObjectWithArgs *func, ObjectType type);


/* AssertObjectTypeIsFunctionType asserts we aren't receiving something we shouldn't */
void
AssertObjectTypeIsFunctional(ObjectType type)
{
	Assert(type == OBJECT_AGGREGATE ||
		   type == OBJECT_FUNCTION ||
		   type == OBJECT_PROCEDURE ||
		   type == OBJECT_ROUTINE);
}


/*
 * QualifyAlterFunctionStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} ..
 * statement in place and makes all (supported) statements fully qualified.
 *
 * Note that not all queries of this form are valid AlterFunctionStmt
 * (e.g. ALTER FUNCTION .. RENAME .. queries are RenameStmt )
 */
void
QualifyAlterFunctionStmt(Node *node)
{
	AlterFunctionStmt *stmt = castNode(AlterFunctionStmt, node);
	AssertObjectTypeIsFunctional(stmt->objtype);

	QualifyFunction(stmt->func, stmt->objtype);
}


/*
 * QualifyRenameFunctionStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} .. RENAME TO ..
 * statement in place and makes the function name fully qualified.
 */
void
QualifyRenameFunctionStmt(Node *node)
{
	RenameStmt *stmt = castNode(RenameStmt, node);
	AssertObjectTypeIsFunctional(stmt->renameType);

	QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->renameType);
}


/*
 * QualifyAlterFunctionSchemaStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} .. SET SCHEMA ..
 * statement in place and makes the function name fully qualified.
 */
void
QualifyAlterFunctionSchemaStmt(Node *node)
{
	AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
	AssertObjectTypeIsFunctional(stmt->objectType);

	QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType);
}


/*
 * QualifyAlterFunctionOwnerStmt transforms a
 * ALTER {AGGREGATE|FUNCTION|PROCEDURE} .. OWNER TO ..
 * statement in place and makes the function name fully qualified.
 */
void
QualifyAlterFunctionOwnerStmt(Node *node)
{
	AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
	AssertObjectTypeIsFunctional(stmt->objectType);

	QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType);
}


/*
 * QualifyAlterFunctionDependsStmt transforms a
 * ALTER {FUNCTION|PROCEDURE} .. DEPENDS ON EXTENSION ..
 * statement in place and makes the function name fully qualified.
 */
void
QualifyAlterFunctionDependsStmt(Node *node)
{
	AlterObjectDependsStmt *stmt = castNode(AlterObjectDependsStmt, node);
	AssertObjectTypeIsFunctional(stmt->objectType);

	QualifyFunction(castNode(ObjectWithArgs, stmt->object), stmt->objectType);
}


/*
 * QualifyFunction transforms a function in place and makes its name fully qualified.
 */
void
QualifyFunction(ObjectWithArgs *func, ObjectType type)
{
	char *functionName = NULL;
	char *schemaName = NULL;

	/* check if the function name is already qualified */
	DeconstructQualifiedName(func->objname, &schemaName, &functionName);

	/* do a lookup for the schema name if the statement does not include one */
	if (schemaName == NULL)
	{
		QualifyFunctionSchemaName(func, type);
	}
}


/*
 * QualifyFunction transforms a function in place using a catalog lookup for its schema name to make it fully qualified.
 */
void
QualifyFunctionSchemaName(ObjectWithArgs *func, ObjectType type)
{
	char *schemaName = NULL;
	char *functionName = NULL;

	Oid funcid = LookupFuncWithArgs(type, func, true);
	HeapTuple proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));

	/*
	 * We can not qualify the function if the catalogs do not have any records.
	 *
	 * e.g. DROP FUNC IF EXISTS non_existent_func() do not result in a valid heap tuple
	 */
	if (HeapTupleIsValid(proctup))
	{
		Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
		schemaName = get_namespace_name(procform->pronamespace);
		functionName = NameStr(procform->proname);
		functionName = pstrdup(functionName); /* we release the tuple before used */

		ReleaseSysCache(proctup);

		/* update the function using the schema name */
		func->objname = list_make2(makeString(schemaName), makeString(functionName));
	}
}
