/*-------------------------------------------------------------------------
 *
 * proclang.c
 *	  PostgreSQL LANGUAGE support code.
 *
 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/commands/proclang.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/proclang.h"
#include "miscadmin.h"
#include "parser/parse_func.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"


/*
 * CREATE LANGUAGE
 */
ObjectAddress
CreateProceduralLanguage(CreatePLangStmt *stmt)
{
	const char *languageName = stmt->plname;
	Oid			languageOwner = GetUserId();
	Oid			handlerOid,
				inlineOid,
				valOid;
	Oid			funcrettype;
	Oid			funcargtypes[1];
	Relation	rel;
	TupleDesc	tupDesc;
	Datum		values[Natts_pg_language];
	bool		nulls[Natts_pg_language];
	bool		replaces[Natts_pg_language];
	NameData	langname;
	HeapTuple	oldtup;
	HeapTuple	tup;
	Oid			langoid;
	bool		is_update;
	ObjectAddress myself,
				referenced;

	/*
	 * Check permission
	 */
	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to create custom procedural language")));

	/*
	 * Lookup the PL handler function and check that it is of the expected
	 * return type
	 */
	Assert(stmt->plhandler);
	handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
	funcrettype = get_func_rettype(handlerOid);
	if (funcrettype != LANGUAGE_HANDLEROID)
	{
		/*
		 * We allow OPAQUE just so we can load old dump files.  When we see a
		 * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
		 * (This is probably obsolete and removable?)
		 */
		if (funcrettype == OPAQUEOID)
		{
			ereport(WARNING,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("changing return type of function %s from %s to %s",
							NameListToString(stmt->plhandler),
							"opaque", "language_handler")));
			SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
		}
		else
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("function %s must return type %s",
							NameListToString(stmt->plhandler), "language_handler")));
	}

	/* validate the inline function */
	if (stmt->plinline)
	{
		funcargtypes[0] = INTERNALOID;
		inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
		/* return value is ignored, so we don't check the type */
	}
	else
		inlineOid = InvalidOid;

	/* validate the validator function */
	if (stmt->plvalidator)
	{
		funcargtypes[0] = OIDOID;
		valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
		/* return value is ignored, so we don't check the type */
	}
	else
		valOid = InvalidOid;

	/* ok to create it */
	rel = table_open(LanguageRelationId, RowExclusiveLock);
	tupDesc = RelationGetDescr(rel);

	/* Prepare data to be inserted */
	memset(values, 0, sizeof(values));
	memset(nulls, false, sizeof(nulls));
	memset(replaces, true, sizeof(replaces));

	namestrcpy(&langname, languageName);
	values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
	values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
	values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
	values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
	values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
	values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
	values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
	nulls[Anum_pg_language_lanacl - 1] = true;

	/* Check for pre-existing definition */
	oldtup = SearchSysCache1(LANGNAME, PointerGetDatum(languageName));

	if (HeapTupleIsValid(oldtup))
	{
		Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);

		/* There is one; okay to replace it? */
		if (!stmt->replace)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("language \"%s\" already exists", languageName)));

		/* This is currently pointless, since we already checked superuser */
#ifdef NOT_USED
		if (!pg_language_ownercheck(oldform->oid, languageOwner))
			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
						   languageName);
#endif

		/*
		 * Do not change existing oid, ownership or permissions.  Note
		 * dependency-update code below has to agree with this decision.
		 */
		replaces[Anum_pg_language_oid - 1] = false;
		replaces[Anum_pg_language_lanowner - 1] = false;
		replaces[Anum_pg_language_lanacl - 1] = false;

		/* Okay, do it... */
		tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces);
		CatalogTupleUpdate(rel, &tup->t_self, tup);

		langoid = oldform->oid;
		ReleaseSysCache(oldtup);
		is_update = true;
	}
	else
	{
		/* Creating a new language */
		langoid = GetNewOidWithIndex(rel, LanguageOidIndexId,
									 Anum_pg_language_oid);
		values[Anum_pg_language_oid - 1] = ObjectIdGetDatum(langoid);
		tup = heap_form_tuple(tupDesc, values, nulls);
		CatalogTupleInsert(rel, tup);
		is_update = false;
	}

	/*
	 * Create dependencies for the new language.  If we are updating an
	 * existing language, first delete any existing pg_depend entries.
	 * (However, since we are not changing ownership or permissions, the
	 * shared dependencies do *not* need to change, and we leave them alone.)
	 */
	myself.classId = LanguageRelationId;
	myself.objectId = langoid;
	myself.objectSubId = 0;

	if (is_update)
		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);

	/* dependency on owner of language */
	if (!is_update)
		recordDependencyOnOwner(myself.classId, myself.objectId,
								languageOwner);

	/* dependency on extension */
	recordDependencyOnCurrentExtension(&myself, is_update);

	/* dependency on the PL handler function */
	referenced.classId = ProcedureRelationId;
	referenced.objectId = handlerOid;
	referenced.objectSubId = 0;
	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);

	/* dependency on the inline handler function, if any */
	if (OidIsValid(inlineOid))
	{
		referenced.classId = ProcedureRelationId;
		referenced.objectId = inlineOid;
		referenced.objectSubId = 0;
		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
	}

	/* dependency on the validator function, if any */
	if (OidIsValid(valOid))
	{
		referenced.classId = ProcedureRelationId;
		referenced.objectId = valOid;
		referenced.objectSubId = 0;
		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
	}

	/* Post creation hook for new procedural language */
	InvokeObjectPostCreateHook(LanguageRelationId, myself.objectId, 0);

	table_close(rel, RowExclusiveLock);

	return myself;
}

/*
 * Guts of language dropping.
 */
void
DropProceduralLanguageById(Oid langOid)
{
	Relation	rel;
	HeapTuple	langTup;

	rel = table_open(LanguageRelationId, RowExclusiveLock);

	langTup = SearchSysCache1(LANGOID, ObjectIdGetDatum(langOid));
	if (!HeapTupleIsValid(langTup)) /* should not happen */
		elog(ERROR, "cache lookup failed for language %u", langOid);

	CatalogTupleDelete(rel, &langTup->t_self);

	ReleaseSysCache(langTup);

	table_close(rel, RowExclusiveLock);
}

/*
 * get_language_oid - given a language name, look up the OID
 *
 * If missing_ok is false, throw an error if language name not found.  If
 * true, just return InvalidOid.
 */
Oid
get_language_oid(const char *langname, bool missing_ok)
{
	Oid			oid;

	oid = GetSysCacheOid1(LANGNAME, Anum_pg_language_oid,
						  CStringGetDatum(langname));
	if (!OidIsValid(oid) && !missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("language \"%s\" does not exist", langname)));
	return oid;
}
