Code review and cleanup.

This commit is contained in:
Tom Lane 2000-11-21 17:54:21 +00:00
parent 22c528fc5a
commit 280a77d3ee
2 changed files with 101 additions and 60 deletions

View File

@ -1,35 +1,43 @@
$Header: /cvsroot/pgsql/contrib/vacuumlo/Attic/README.vacuumlo,v 1.1 2000/06/19 14:02:16 momjian Exp $ $Header: /cvsroot/pgsql/contrib/vacuumlo/Attic/README.vacuumlo,v 1.2 2000/11/21 17:54:21 tgl Exp $
This is a simple utility that will remove any orphaned large objects out of a This is a simple utility that will remove any orphaned large objects out of a
PostgreSQL database. PostgreSQL database. An orphaned LO is considered to be any LO whose OID
does not appear in any OID data column of the database.
Compiling Compiling
-------- --------
Simply run make. A single executable "vacuumlo" is created. Simply run make. A single executable "vacuumlo" is created.
Useage
------ Usage
-----
vacuumlo [-v] database [db2 ... dbn] vacuumlo [-v] database [db2 ... dbn]
The -v flag outputs some progress messages to stdout. The -v flag outputs some progress messages to stdout.
Method Method
------ ------
First, it builds a temporary table which contains all of the oid's of the First, it builds a temporary table which contains all of the oid's of the
large objects in that database. large objects in that database.
It then scans through any columns in the database that are of type 'oid', and It then scans through all columns in the database that are of type 'oid',
removes any entries from the temporary table. and removes any matching entries from the temporary table.
Finally, it runs through the first table, and removes from the second table, any The remaining entries in the temp table identify orphaned LOs. These are
oid's it finds. What is left are the orphans, and these are removed. removed.
Notes
-----
I decided to place this in contrib as it needs further testing, but hopefully, I decided to place this in contrib as it needs further testing, but hopefully,
this (or a variant of it) would make it into the backed as a "vacuum lo" command this (or a variant of it) would make it into the backend as a "vacuum lo"
in a later release. command in a later release.
Peter Mount <peter@retep.org.uk> Peter Mount <peter@retep.org.uk>
http://www.retep.org.uk http://www.retep.org.uk

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/contrib/vacuumlo/vacuumlo.c,v 1.6 2000/10/24 01:38:20 tgl Exp $ * $Header: /cvsroot/pgsql/contrib/vacuumlo/vacuumlo.c,v 1.7 2000/11/21 17:54:21 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -24,13 +24,15 @@
#include "libpq-fe.h" #include "libpq-fe.h"
#include "libpq/libpq-fs.h" #include "libpq/libpq-fs.h"
#define atooid(x) ((Oid) strtoul((x), NULL, 10))
#define BUFSIZE 1024 #define BUFSIZE 1024
int vacuumlo(char *, int); int vacuumlo(char *, int);
/* /*
* This vacuums a database. It returns 1 on success, -1 on failure. * This vacuums LOs of one database. It returns 0 on success, -1 on failure.
*/ */
int int
vacuumlo(char *database, int verbose) vacuumlo(char *database, int verbose)
@ -39,7 +41,8 @@ vacuumlo(char *database, int verbose)
PGresult *res, PGresult *res,
*res2; *res2;
char buf[BUFSIZE]; char buf[BUFSIZE];
int matched = 0; /* Number matched per scan */ int matched;
int deleted;
int i; int i;
conn = PQsetdb(NULL, NULL, NULL, NULL, database); conn = PQsetdb(NULL, NULL, NULL, NULL, database);
@ -47,8 +50,9 @@ vacuumlo(char *database, int verbose)
/* check to see that the backend connection was successfully made */ /* check to see that the backend connection was successfully made */
if (PQstatus(conn) == CONNECTION_BAD) if (PQstatus(conn) == CONNECTION_BAD)
{ {
fprintf(stderr, "Connection to database '%s' failed.\n", database); fprintf(stderr, "Connection to database '%s' failed:\n", database);
fprintf(stderr, "%s", PQerrorMessage(conn)); fprintf(stderr, "%s", PQerrorMessage(conn));
PQfinish(conn);
return -1; return -1;
} }
@ -56,23 +60,49 @@ vacuumlo(char *database, int verbose)
fprintf(stdout, "Connected to %s\n", database); fprintf(stdout, "Connected to %s\n", database);
/* /*
* First we create and populate the lo temp table * First we create and populate the LO temp table
*/ */
buf[0] = '\0'; buf[0] = '\0';
strcat(buf, "SELECT DISTINCT loid AS lo "); strcat(buf, "SELECT DISTINCT loid AS lo ");
strcat(buf, "INTO TEMP TABLE vacuum_l "); strcat(buf, "INTO TEMP TABLE vacuum_l ");
strcat(buf, "FROM pg_largeobject "); strcat(buf, "FROM pg_largeobject ");
if (!(res = PQexec(conn, buf))) res = PQexec(conn, buf);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{ {
fprintf(stderr, "Failed to create temp table.\n"); fprintf(stderr, "Failed to create temp table:\n");
fprintf(stderr, "%s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn);
return -1;
}
PQclear(res);
/*
* Vacuum the temp table so that planner will generate decent plans
* for the DELETEs below.
*/
buf[0] = '\0';
strcat(buf, "VACUUM ANALYZE vacuum_l ");
res = PQexec(conn, buf);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "Failed to vacuum temp table:\n");
fprintf(stderr, "%s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn); PQfinish(conn);
return -1; return -1;
} }
PQclear(res); PQclear(res);
/* /*
* Now find any candidate tables who have columns of type oid (the * Now find any candidate tables who have columns of type oid.
* column oid is ignored, as it has attnum < 1) *
* NOTE: the temp table formed above is ignored, because its real
* table name will be pg_something. Also, pg_largeobject will be
* ignored. If either of these were scanned, obviously we'd end up
* with nothing to delete...
*
* NOTE: the system oid column is ignored, as it has attnum < 1.
* This shouldn't matter for correctness, but it saves time.
*/ */
buf[0] = '\0'; buf[0] = '\0';
strcat(buf, "SELECT c.relname, a.attname "); strcat(buf, "SELECT c.relname, a.attname ");
@ -81,13 +111,18 @@ vacuumlo(char *database, int verbose)
strcat(buf, " AND a.attrelid = c.oid "); strcat(buf, " AND a.attrelid = c.oid ");
strcat(buf, " AND a.atttypid = t.oid "); strcat(buf, " AND a.atttypid = t.oid ");
strcat(buf, " AND t.typname = 'oid' "); strcat(buf, " AND t.typname = 'oid' ");
strcat(buf, " AND c.relkind = 'r'");
strcat(buf, " AND c.relname NOT LIKE 'pg_%'"); strcat(buf, " AND c.relname NOT LIKE 'pg_%'");
if (!(res = PQexec(conn, buf))) res = PQexec(conn, buf);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
fprintf(stderr, "Failed to create temp table.\n"); fprintf(stderr, "Failed to find OID columns:\n");
fprintf(stderr, "%s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn); PQfinish(conn);
return -1; return -1;
} }
for (i = 0; i < PQntuples(res); i++) for (i = 0; i < PQntuples(res); i++)
{ {
char *table, char *table,
@ -97,50 +132,36 @@ vacuumlo(char *database, int verbose)
field = PQgetvalue(res, i, 1); field = PQgetvalue(res, i, 1);
if (verbose) if (verbose)
{ fprintf(stdout, "Checking %s in %s\n", field, table);
fprintf(stdout, "Checking %s in %s: ", field, table);
fflush(stdout);
}
res2 = PQexec(conn, "begin"); /*
PQclear(res2); * We use a DELETE with implicit join for efficiency. This
* is a Postgres-ism and not portable to other DBMSs, but
buf[0] = '\0'; * then this whole program is a Postgres-ism.
strcat(buf, "DELETE FROM vacuum_l "); */
strcat(buf, "WHERE lo IN ("); sprintf(buf, "DELETE FROM vacuum_l WHERE lo = \"%s\".\"%s\" ",
strcat(buf, "SELECT "); table, field);
strcat(buf, field); res2 = PQexec(conn, buf);
strcat(buf, " FROM ");
strcat(buf, table);
strcat(buf, ");");
if (!(res2 = PQexec(conn, buf)))
{
fprintf(stderr, "Failed to check %s in table %s\n", field, table);
PQclear(res);
PQfinish(conn);
return -1;
}
if (PQresultStatus(res2) != PGRES_COMMAND_OK) if (PQresultStatus(res2) != PGRES_COMMAND_OK)
{ {
fprintf(stderr, fprintf(stderr, "Failed to check %s in table %s:\n",
"Failed to check %s in table %s\n%s\n", field, table);
field, table, fprintf(stderr, "%s", PQerrorMessage(conn));
PQerrorMessage(conn)
);
PQclear(res2); PQclear(res2);
PQclear(res); PQclear(res);
PQfinish(conn); PQfinish(conn);
return -1; return -1;
} }
PQclear(res2); PQclear(res2);
res2 = PQexec(conn, "end");
PQclear(res2);
} }
PQclear(res); PQclear(res);
/* Start the transaction */ /*
* Run the actual deletes in a single transaction. Note that this
* would be a bad idea in pre-7.1 Postgres releases (since rolling
* back a table delete used to cause problems), but it should
* be safe now.
*/
res = PQexec(conn, "begin"); res = PQexec(conn, "begin");
PQclear(res); PQclear(res);
@ -150,25 +171,35 @@ vacuumlo(char *database, int verbose)
buf[0] = '\0'; buf[0] = '\0';
strcat(buf, "SELECT lo "); strcat(buf, "SELECT lo ");
strcat(buf, "FROM vacuum_l"); strcat(buf, "FROM vacuum_l");
if (!(res = PQexec(conn, buf))) res = PQexec(conn, buf);
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
fprintf(stderr, "Failed to read temp table.\n"); fprintf(stderr, "Failed to read temp table:\n");
fprintf(stderr, "%s", PQerrorMessage(conn));
PQclear(res);
PQfinish(conn); PQfinish(conn);
return -1; return -1;
} }
matched = PQntuples(res); matched = PQntuples(res);
deleted = 0;
for (i = 0; i < matched; i++) for (i = 0; i < matched; i++)
{ {
Oid lo = (Oid) atoi(PQgetvalue(res, i, 0)); Oid lo = atooid(PQgetvalue(res, i, 0));
if (verbose) if (verbose)
{ {
fprintf(stdout, "\rRemoving lo %6d \n", lo); fprintf(stdout, "\rRemoving lo %6u ", lo);
fflush(stdout); fflush(stdout);
} }
if (lo_unlink(conn, lo) < 0) if (lo_unlink(conn, lo) < 0)
fprintf(stderr, "Failed to remove lo %d\n", lo); {
fprintf(stderr, "\nFailed to remove lo %u: ", lo);
fprintf(stderr, "%s", PQerrorMessage(conn));
}
else
deleted++;
} }
PQclear(res); PQclear(res);
@ -177,10 +208,12 @@ vacuumlo(char *database, int verbose)
*/ */
res = PQexec(conn, "end"); res = PQexec(conn, "end");
PQclear(res); PQclear(res);
PQfinish(conn); PQfinish(conn);
if (verbose) if (verbose)
fprintf(stdout, "\rRemoved %d large objects from %s.\n", matched, database); fprintf(stdout, "\rRemoved %d large objects from %s.\n",
deleted, database);
return 0; return 0;
} }
@ -204,7 +237,7 @@ main(int argc, char **argv)
if (strcmp("-v", argv[arg]) == 0) if (strcmp("-v", argv[arg]) == 0)
verbose = !verbose; verbose = !verbose;
else else
rc += vacuumlo(argv[arg], verbose); rc += (vacuumlo(argv[arg], verbose) != 0);
} }
return rc; return rc;