/*-------------------------------------------------------------------------
 *
 * foreign_data_wrapper.c
 *    Commands for FOREIGN DATA WRAPPER statements.
 *
 * Copyright (c) Citus Data, Inc.
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/pg_foreign_data_wrapper.h"
#include "foreign/foreign.h"
#include "nodes/makefuncs.h"
#include "nodes/parsenodes.h"
#include "utils/syscache.h"

#include "distributed/commands.h"
#include "distributed/commands/utility_hook.h"
#include "distributed/deparser.h"
#include "distributed/listutils.h"
#include "distributed/metadata/distobject.h"
#include "distributed/metadata_sync.h"

static bool NameListHasFDWOwnedByDistributedExtension(List *FDWNames);
static ObjectAddress GetObjectAddressByFDWName(char *FDWName, bool missing_ok);


/*
 * PreprocessGrantOnFDWStmt is executed before the statement is applied to the
 * local postgres instance.
 *
 * In this stage we can prepare the commands that need to be run on all workers to grant
 * on foreign data wrappers.
 */
List *
PreprocessGrantOnFDWStmt(Node *node, const char *queryString,
						 ProcessUtilityContext processUtilityContext)
{
	GrantStmt *stmt = castNode(GrantStmt, node);
	Assert(stmt->objtype == OBJECT_FDW);

	if (!NameListHasFDWOwnedByDistributedExtension(stmt->objects))
	{
		/*
		 * We propagate granted privileges on a FDW only if it belongs to a distributed
		 * extension. For now, we skip for custom FDWs, as most of the users prefer
		 * extension FDWs.
		 */
		return NIL;
	}

	if (list_length(stmt->objects) > 1)
	{
		ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						errmsg("cannot grant on FDW with other FDWs"),
						errhint("Try granting on each object in separate commands")));
	}

	if (!ShouldPropagate())
	{
		return NIL;
	}

	EnsureCoordinator();

	/*  the code-path only supports a single object */
	Assert(list_length(stmt->objects) == 1);

	char *sql = DeparseTreeNode((Node *) stmt);

	List *commands = list_make3(DISABLE_DDL_PROPAGATION,
								(void *) sql,
								ENABLE_DDL_PROPAGATION);

	return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
}


/*
 * NameListHasFDWOwnedByDistributedExtension takes a namelist of FDWs and returns true
 * if at least one of them depends on a distributed extension. Returns false otherwise.
 */
static bool
NameListHasFDWOwnedByDistributedExtension(List *FDWNames)
{
	String *FDWValue = NULL;
	foreach_declared_ptr(FDWValue, FDWNames)
	{
		/* captures the extension address during lookup */
		ObjectAddress *extensionAddress = palloc0(sizeof(ObjectAddress));
		ObjectAddress FDWAddress = GetObjectAddressByFDWName(strVal(FDWValue), false);

		ObjectAddress *copyFDWAddress = palloc0(sizeof(ObjectAddress));
		*copyFDWAddress = FDWAddress;
		if (IsAnyObjectAddressOwnedByExtension(list_make1(copyFDWAddress),
											   extensionAddress))
		{
			if (IsAnyObjectDistributed(list_make1(extensionAddress)))
			{
				return true;
			}
		}
	}

	return false;
}


/*
 * GetObjectAddressByFDWName takes a FDW name and returns the object address.
 */
static ObjectAddress
GetObjectAddressByFDWName(char *FDWName, bool missing_ok)
{
	ForeignDataWrapper *FDW = GetForeignDataWrapperByName(FDWName, missing_ok);
	Oid FDWId = FDW->fdwid;
	ObjectAddress address = { 0 };
	ObjectAddressSet(address, ForeignDataWrapperRelationId, FDWId);

	return address;
}


/*
 * GetPrivilegesForFDW takes a FDW object id and returns the privileges granted
 * on that FDW as a Acl object. Returns NULL if there is no privilege granted.
 */
Acl *
GetPrivilegesForFDW(Oid FDWOid)
{
	HeapTuple fdwtup = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(FDWOid));

	bool isNull = true;
	Datum aclDatum = SysCacheGetAttr(FOREIGNDATAWRAPPEROID, fdwtup,
									 Anum_pg_foreign_data_wrapper_fdwacl, &isNull);
	if (isNull)
	{
		ReleaseSysCache(fdwtup);
		return NULL;
	}

	Acl *aclEntry = DatumGetAclPCopy(aclDatum);

	ReleaseSysCache(fdwtup);

	return aclEntry;
}
