diff options
Diffstat (limited to '')
-rw-r--r-- | src/bin/psql/large_obj.c | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/src/bin/psql/large_obj.c b/src/bin/psql/large_obj.c new file mode 100644 index 0000000..c15fcc0 --- /dev/null +++ b/src/bin/psql/large_obj.c @@ -0,0 +1,316 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2021, PostgreSQL Global Development Group + * + * src/bin/psql/large_obj.c + */ +#include "postgres_fe.h" + +#include "common.h" +#include "common/logging.h" +#include "fe_utils/cancel.h" +#include "large_obj.h" +#include "settings.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(NULL); + 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(NULL); + 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(NULL); + 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; +} |