postgresql/src/bin/psql/large_obj.c

316 lines
6.0 KiB
C
Raw Normal View History

2000-01-19 00:30:24 +01:00
/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2018, PostgreSQL Global Development Group
2000-01-19 00:30:24 +01:00
*
2010-09-20 22:08:53 +02:00
* src/bin/psql/large_obj.c
2000-01-19 00:30:24 +01:00
*/
#include "postgres_fe.h"
#include "large_obj.h"
#include "settings.h"
#include "common.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);
}
}
1999-11-05 00:14:30 +01:00
/*
* 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)
{
psql_error("%s: not connected to a database\n", 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:
psql_error("%s: current transaction is aborted\n", operation);
return false;
default:
psql_error("%s: unknown transaction status\n", operation);
return false;
}
return true;
}
/*
* Clean up after a successful LO operation
*/
static bool
finish_lo_xact(const char *operation, bool own_transaction)
{
1999-11-05 00:14:30 +01:00
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);
}
1999-11-05 00:14:30 +01:00
return true;
}
1999-11-05 00:14:30 +01:00
/*
* 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)
1999-11-05 00:14:30 +01:00
{
/* close out our own xact */
res = PSQLexec("ROLLBACK");
PQclear(res);
1999-11-05 00:14:30 +01:00
}
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)
{
1999-11-05 00:14:30 +01:00
int status;
bool own_transaction;
1999-11-05 00:14:30 +01:00
if (!start_lo_xact("\\lo_export", &own_transaction))
1999-11-05 00:14:30 +01:00
return false;
SetCancelConn();
status = lo_export(pset.db, atooid(loid_arg), filename_arg);
ResetCancelConn();
/* of course this status is documented nowhere :( */
1999-11-05 00:14:30 +01:00
if (status != 1)
{
psql_error("%s", PQerrorMessage(pset.db));
return fail_lo_xact("\\lo_export", own_transaction);
}
1999-11-05 00:14:30 +01:00
if (!finish_lo_xact("\\lo_export", own_transaction))
return false;
print_lo_result("lo_export");
1999-11-05 00:14:30 +01:00
return true;
}
/*
* do_lo_import()
*
* Copy large object from file to database
*/
bool
do_lo_import(const char *filename_arg, const char *comment_arg)
{
1999-11-05 00:14:30 +01:00
PGresult *res;
Oid loid;
char oidbuf[32];
bool own_transaction;
1999-11-05 00:14:30 +01:00
if (!start_lo_xact("\\lo_import", &own_transaction))
1999-11-05 00:14:30 +01:00
return false;
SetCancelConn();
loid = lo_import(pset.db, filename_arg);
ResetCancelConn();
1999-11-05 00:14:30 +01:00
if (loid == InvalidOid)
{
psql_error("%s", PQerrorMessage(pset.db));
return fail_lo_xact("\\lo_import", own_transaction);
}
1999-11-05 00:14:30 +01:00
/* insert description if given */
if (comment_arg)
1999-11-05 00:14:30 +01:00
{
char *cmdbuf;
char *bufptr;
size_t slen = strlen(comment_arg);
cmdbuf = malloc(slen * 2 + 256);
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);
1999-11-05 00:14:30 +01:00
}
if (!finish_lo_xact("\\lo_import", own_transaction))
return false;
1999-11-05 00:14:30 +01:00
print_lo_result("lo_import %u", loid);
sprintf(oidbuf, "%u", loid);
SetVariable(pset.vars, "LASTOID", oidbuf);
1999-11-05 00:14:30 +01:00
return true;
}
/*
* do_lo_unlink()
*
* removes a large object out of the database
*/
1999-11-05 00:14:30 +01:00
bool
do_lo_unlink(const char *loid_arg)
{
1999-11-05 00:14:30 +01:00
int status;
Oid loid = atooid(loid_arg);
bool own_transaction;
if (!start_lo_xact("\\lo_unlink", &own_transaction))
1999-11-05 00:14:30 +01:00
return false;
SetCancelConn();
status = lo_unlink(pset.db, loid);
ResetCancelConn();
1999-11-05 00:14:30 +01:00
if (status == -1)
{
psql_error("%s", PQerrorMessage(pset.db));
return fail_lo_xact("\\lo_unlink", own_transaction);
}
1999-11-05 00:14:30 +01:00
if (!finish_lo_xact("\\lo_unlink", own_transaction))
return false;
print_lo_result("lo_unlink %u", loid);
1999-11-05 00:14:30 +01:00
return true;
}
/*
* do_lo_list()
*
* Show all large objects in database with comments
*/
1999-11-05 00:14:30 +01:00
bool
do_lo_list(void)
{
1999-11-05 00:14:30 +01:00
PGresult *res;
char buf[1024];
printQueryOpt myopt = pset.popt;
1999-11-05 00:14:30 +01:00
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);
1999-11-05 00:14:30 +01:00
if (!res)
return false;
1999-11-05 00:14:30 +01:00
myopt.topt.tuples_only = false;
myopt.nullPrint = NULL;
myopt.title = _("Large objects");
myopt.translate_header = true;
Fix behavior of printTable() and friends with externally-invoked pager. The formatting modes that depend on knowledge of the terminal window width did not work right when printing a query result that's been fetched in sections (as a result of FETCH_SIZE). ExecQueryUsingCursor() would force use of the pager as soon as there's more than one result section, and then print.c would see an output file pointer that's not stdout and incorrectly conclude that the terminal window width isn't relevant. This has been broken all along for non-expanded "wrapped" output format, and as of 9.5 the issue affects expanded mode as well. The problem also caused "\pset expanded auto" mode to invariably *not* switch to expanded output in a segmented result, which seems to me to be exactly backwards. To fix, we need to pass down an "is_pager" flag to inform the print.c subroutines that some calling level has already replaced stdout with a pager pipe, so they should (a) not do that again and (b) nonetheless honor the window size. (Notably, this makes the first is_pager test in print_aligned_text() not be dead code anymore.) This patch is a bit invasive because there are so many existing calls of printQuery()/printTable(), but fortunately all but a couple can just pass "false" for the added parameter. Back-patch to 9.5 but no further. Given the lack of field complaints, it's not clear that we should change the behavior in stable branches. Also, the API change for printQuery()/printTable() might possibly break third-party code, again something we don't like to do in stable branches. However, it's not quite too late to do this in 9.5, and with the larger scope of the problem there, it seems worth doing.
2015-12-03 00:20:33 +01:00
printQuery(res, &myopt, pset.queryFout, false, pset.logfile);
1999-11-05 00:14:30 +01:00
PQclear(res);
return true;
}