summaryrefslogtreecommitdiffstats
path: root/src/bin/psql/large_obj.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/large_obj.c')
-rw-r--r--src/bin/psql/large_obj.c316
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;
+}