/*
 * psql - the PostgreSQL interactive terminal
 *
 * Copyright (c) 2000-2019, PostgreSQL Global Development Group
 *
 * src/bin/psql/large_obj.c
 */
#include "postgres_fe.h"
#include "large_obj.h"

#include "settings.h"
#include "common.h"

#include "fe_utils/logging.h"

static void print_lo_result(const char *fmt,...) pg_attribute_printf(1, 2);

static void
print_lo_result(const char *fmt,...)
{
	va_list		ap;

	if (!pset.quiet)
	{
		if (pset.popt.topt.format == PRINT_HTML)
			fputs("<p>", pset.queryFout);

		va_start(ap, fmt);
		vfprintf(pset.queryFout, fmt, ap);
		va_end(ap);

		if (pset.popt.topt.format == PRINT_HTML)
			fputs("</p>\n", pset.queryFout);
		else
			fputs("\n", pset.queryFout);
	}

	if (pset.logfile)
	{
		va_start(ap, fmt);
		vfprintf(pset.logfile, fmt, ap);
		va_end(ap);
		fputs("\n", pset.logfile);
	}
}


/*
 * Prepare to do a large-object operation.  We *must* be inside a transaction
 * block for all these operations, so start one if needed.
 *
 * Returns true if okay, false if failed.  *own_transaction is set to indicate
 * if we started our own transaction or not.
 */
static bool
start_lo_xact(const char *operation, bool *own_transaction)
{
	PGTransactionStatusType tstatus;
	PGresult   *res;

	*own_transaction = false;

	if (!pset.db)
	{
		pg_log_error("%s: not connected to a database", operation);
		return false;
	}

	tstatus = PQtransactionStatus(pset.db);

	switch (tstatus)
	{
		case PQTRANS_IDLE:
			/* need to start our own xact */
			if (!(res = PSQLexec("BEGIN")))
				return false;
			PQclear(res);
			*own_transaction = true;
			break;
		case PQTRANS_INTRANS:
			/* use the existing xact */
			break;
		case PQTRANS_INERROR:
			pg_log_error("%s: current transaction is aborted", operation);
			return false;
		default:
			pg_log_error("%s: unknown transaction status", operation);
			return false;
	}

	return true;
}

/*
 * Clean up after a successful LO operation
 */
static bool
finish_lo_xact(const char *operation, bool own_transaction)
{
	PGresult   *res;

	if (own_transaction && pset.autocommit)
	{
		/* close out our own xact */
		if (!(res = PSQLexec("COMMIT")))
		{
			res = PSQLexec("ROLLBACK");
			PQclear(res);
			return false;
		}
		PQclear(res);
	}

	return true;
}

/*
 * Clean up after a failed LO operation
 */
static bool
fail_lo_xact(const char *operation, bool own_transaction)
{
	PGresult   *res;

	if (own_transaction && pset.autocommit)
	{
		/* close out our own xact */
		res = PSQLexec("ROLLBACK");
		PQclear(res);
	}

	return false;				/* always */
}


/*
 * do_lo_export()
 *
 * Write a large object to a file
 */
bool
do_lo_export(const char *loid_arg, const char *filename_arg)
{
	int			status;
	bool		own_transaction;

	if (!start_lo_xact("\\lo_export", &own_transaction))
		return false;

	SetCancelConn();
	status = lo_export(pset.db, atooid(loid_arg), filename_arg);
	ResetCancelConn();

	/* of course this status is documented nowhere :( */
	if (status != 1)
	{
		pg_log_info("%s", PQerrorMessage(pset.db));
		return fail_lo_xact("\\lo_export", own_transaction);
	}

	if (!finish_lo_xact("\\lo_export", own_transaction))
		return false;

	print_lo_result("lo_export");

	return true;
}


/*
 * do_lo_import()
 *
 * Copy large object from file to database
 */
bool
do_lo_import(const char *filename_arg, const char *comment_arg)
{
	PGresult   *res;
	Oid			loid;
	char		oidbuf[32];
	bool		own_transaction;

	if (!start_lo_xact("\\lo_import", &own_transaction))
		return false;

	SetCancelConn();
	loid = lo_import(pset.db, filename_arg);
	ResetCancelConn();

	if (loid == InvalidOid)
	{
		pg_log_info("%s", PQerrorMessage(pset.db));
		return fail_lo_xact("\\lo_import", own_transaction);
	}

	/* insert description if given */
	if (comment_arg)
	{
		char	   *cmdbuf;
		char	   *bufptr;
		size_t		slen = strlen(comment_arg);

		cmdbuf = pg_malloc_extended(slen * 2 + 256, MCXT_ALLOC_NO_OOM);
		if (!cmdbuf)
			return fail_lo_xact("\\lo_import", own_transaction);
		sprintf(cmdbuf, "COMMENT ON LARGE OBJECT %u IS '", loid);
		bufptr = cmdbuf + strlen(cmdbuf);
		bufptr += PQescapeStringConn(pset.db, bufptr, comment_arg, slen, NULL);
		strcpy(bufptr, "'");

		if (!(res = PSQLexec(cmdbuf)))
		{
			free(cmdbuf);
			return fail_lo_xact("\\lo_import", own_transaction);
		}

		PQclear(res);
		free(cmdbuf);
	}

	if (!finish_lo_xact("\\lo_import", own_transaction))
		return false;

	print_lo_result("lo_import %u", loid);

	sprintf(oidbuf, "%u", loid);
	SetVariable(pset.vars, "LASTOID", oidbuf);

	return true;
}


/*
 * do_lo_unlink()
 *
 * removes a large object out of the database
 */
bool
do_lo_unlink(const char *loid_arg)
{
	int			status;
	Oid			loid = atooid(loid_arg);
	bool		own_transaction;

	if (!start_lo_xact("\\lo_unlink", &own_transaction))
		return false;

	SetCancelConn();
	status = lo_unlink(pset.db, loid);
	ResetCancelConn();

	if (status == -1)
	{
		pg_log_info("%s", PQerrorMessage(pset.db));
		return fail_lo_xact("\\lo_unlink", own_transaction);
	}

	if (!finish_lo_xact("\\lo_unlink", own_transaction))
		return false;

	print_lo_result("lo_unlink %u", loid);

	return true;
}



/*
 * do_lo_list()
 *
 * Show all large objects in database with comments
 */
bool
do_lo_list(void)
{
	PGresult   *res;
	char		buf[1024];
	printQueryOpt myopt = pset.popt;

	if (pset.sversion >= 90000)
	{
		snprintf(buf, sizeof(buf),
				 "SELECT oid as \"%s\",\n"
				 "  pg_catalog.pg_get_userbyid(lomowner) as \"%s\",\n"
				 "  pg_catalog.obj_description(oid, 'pg_largeobject') as \"%s\"\n"
				 "  FROM pg_catalog.pg_largeobject_metadata "
				 "  ORDER BY oid",
				 gettext_noop("ID"),
				 gettext_noop("Owner"),
				 gettext_noop("Description"));
	}
	else
	{
		snprintf(buf, sizeof(buf),
				 "SELECT loid as \"%s\",\n"
				 "  pg_catalog.obj_description(loid, 'pg_largeobject') as \"%s\"\n"
				 "FROM (SELECT DISTINCT loid FROM pg_catalog.pg_largeobject) x\n"
				 "ORDER BY 1",
				 gettext_noop("ID"),
				 gettext_noop("Description"));
	}

	res = PSQLexec(buf);
	if (!res)
		return false;

	myopt.topt.tuples_only = false;
	myopt.nullPrint = NULL;
	myopt.title = _("Large objects");
	myopt.translate_header = true;

	printQuery(res, &myopt, pset.queryFout, false, pset.logfile);

	PQclear(res);
	return true;
}
