]> granicus.if.org Git - postgresql/blob - src/bin/pg_dump/pg_backup_db.c
pgindent run.
[postgresql] / src / bin / pg_dump / pg_backup_db.c
1 /*-------------------------------------------------------------------------
2  *
3  * pg_backup_db.c
4  *
5  *      Implements the basic DB functions used by the archiver.
6  *
7  * IDENTIFICATION
8  *        $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.40 2002/09/04 20:31:34 momjian Exp $
9  *
10  *-------------------------------------------------------------------------
11  */
12
13 #include "pg_backup.h"
14 #include "pg_backup_archiver.h"
15 #include "pg_backup_db.h"
16 #include "dumputils.h"
17
18 #include <unistd.h>
19 #include <ctype.h>
20
21 #ifdef HAVE_TERMIOS_H
22 #include <termios.h>
23 #endif
24
25 #include "libpq-fe.h"
26 #include "libpq/libpq-fs.h"
27 #ifndef HAVE_STRDUP
28 #include "strdup.h"
29 #endif
30
31 static const char *modulename = gettext_noop("archiver (db)");
32
33 static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
34 static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser);
35 static int      _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc);
36 static void notice_processor(void *arg, const char *message);
37 static char *_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos);
38 static char *_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos);
39
40
41 static int
42 _parse_version(ArchiveHandle *AH, const char *versionString)
43 {
44         int                     cnt;
45         int                     vmaj,
46                                 vmin,
47                                 vrev;
48
49         cnt = sscanf(versionString, "%d.%d.%d", &vmaj, &vmin, &vrev);
50
51         if (cnt < 2)
52                 die_horribly(AH, modulename, "unable to parse version string \"%s\"\n", versionString);
53
54         if (cnt == 2)
55                 vrev = 0;
56
57         return (100 * vmaj + vmin) * 100 + vrev;
58 }
59
60 static void
61 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
62 {
63         PGresult   *res;
64         int                     myversion;
65         const char *remoteversion_str;
66         int                     remoteversion;
67         PGconn     *conn = AH->connection;
68
69         myversion = _parse_version(AH, PG_VERSION);
70
71         res = PQexec(conn, "SELECT version();");
72         if (!res ||
73                 PQresultStatus(res) != PGRES_TUPLES_OK ||
74                 PQntuples(res) != 1)
75
76                 die_horribly(AH, modulename, "could not get version from server: %s", PQerrorMessage(conn));
77
78         remoteversion_str = PQgetvalue(res, 0, 0);
79         remoteversion = _parse_version(AH, remoteversion_str + 11);
80
81         PQclear(res);
82
83         AH->public.remoteVersion = remoteversion;
84
85         if (myversion != remoteversion
86                 && (remoteversion < AH->public.minRemoteVersion || remoteversion > AH->public.maxRemoteVersion))
87         {
88                 write_msg(NULL, "server version: %s; %s version: %s\n",
89                                   remoteversion_str, progname, PG_VERSION);
90                 if (ignoreVersion)
91                         write_msg(NULL, "proceeding despite version mismatch\n");
92                 else
93                         die_horribly(AH, NULL, "aborting because of version mismatch  (Use the -i option to proceed anyway.)\n");
94         }
95 }
96
97 /*
98  * Reconnect to the server.  If dbname is not NULL, use that database,
99  * else the one associated with the archive handle.  If username is
100  * not NULL, use that user name, else the one from the handle.  If
101  * both the database and the user and match the existing connection
102  * already, nothing will be done.
103  *
104  * Returns 1 in any case.
105  */
106 int
107 ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
108 {
109         PGconn     *newConn;
110         const char *newdbname;
111         const char *newusername;
112
113         if (!dbname)
114                 newdbname = PQdb(AH->connection);
115         else
116                 newdbname = dbname;
117
118         if (!username)
119                 newusername = PQuser(AH->connection);
120         else
121                 newusername = username;
122
123         /* Let's see if the request is already satisfied */
124         if (strcmp(newusername, PQuser(AH->connection)) == 0
125                 && strcmp(newdbname, PQdb(AH->connection)) == 0)
126                 return 1;
127
128         newConn = _connectDB(AH, newdbname, newusername);
129
130         PQfinish(AH->connection);
131         AH->connection = newConn;
132
133         /* don't assume we still know the output schema */
134         if (AH->currSchema)
135                 free(AH->currSchema);
136         AH->currSchema = strdup("");
137
138         return 1;
139 }
140
141 /*
142  * Connect to the db again.
143  */
144 static PGconn *
145 _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
146 {
147         int                     need_pass;
148         PGconn     *newConn;
149         char       *password = NULL;
150         int                     badPwd = 0;
151         int                     noPwd = 0;
152         char       *newdb;
153         char       *newuser;
154
155         if (!reqdb)
156                 newdb = PQdb(AH->connection);
157         else
158                 newdb = (char *) reqdb;
159
160         if (!requser || (strlen(requser) == 0))
161                 newuser = PQuser(AH->connection);
162         else
163                 newuser = (char *) requser;
164
165         ahlog(AH, 1, "connecting to database %s as user %s\n", newdb, newuser);
166
167         if (AH->requirePassword)
168         {
169                 password = simple_prompt("Password: ", 100, false);
170                 if (password == NULL)
171                         die_horribly(AH, modulename, "out of memory\n");
172         }
173
174         do
175         {
176                 need_pass = false;
177                 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
178                                                            NULL, NULL, newdb,
179                                                            newuser, password);
180                 if (!newConn)
181                         die_horribly(AH, modulename, "failed to reconnect to database\n");
182
183                 if (PQstatus(newConn) == CONNECTION_BAD)
184                 {
185                         noPwd = (strcmp(PQerrorMessage(newConn),
186                                                         "fe_sendauth: no password supplied\n") == 0);
187                         badPwd = (strncmp(PQerrorMessage(newConn),
188                                         "Password authentication failed for user", 39) == 0);
189
190                         if (noPwd || badPwd)
191                         {
192
193                                 if (badPwd)
194                                         fprintf(stderr, "Password incorrect\n");
195
196                                 fprintf(stderr, "Connecting to %s as %s\n",
197                                                 PQdb(AH->connection), newuser);
198
199                                 need_pass = true;
200                                 if (password)
201                                         free(password);
202                                 password = simple_prompt("Password: ", 100, false);
203                         }
204                         else
205                                 die_horribly(AH, modulename, "could not reconnect to database: %s",
206                                                          PQerrorMessage(newConn));
207                 }
208
209         } while (need_pass);
210
211         if (password)
212                 free(password);
213
214         PQsetNoticeProcessor(newConn, notice_processor, NULL);
215
216         return newConn;
217 }
218
219
220 /*
221  * Make a database connection with the given parameters.  The
222  * connection handle is returned, the parameters are stored in AHX.
223  * An interactive password prompt is automatically issued if required.
224  */
225 PGconn *
226 ConnectDatabase(Archive *AHX,
227                                 const char *dbname,
228                                 const char *pghost,
229                                 const char *pgport,
230                                 const char *username,
231                                 const int reqPwd,
232                                 const int ignoreVersion)
233 {
234         ArchiveHandle *AH = (ArchiveHandle *) AHX;
235         char       *password = NULL;
236         bool            need_pass = false;
237
238         if (AH->connection)
239                 die_horribly(AH, modulename, "already connected to a database\n");
240
241         if (reqPwd)
242         {
243                 password = simple_prompt("Password: ", 100, false);
244                 if (password == NULL)
245                         die_horribly(AH, modulename, "out of memory\n");
246                 AH->requirePassword = true;
247         }
248         else
249                 AH->requirePassword = false;
250
251         /*
252          * Start the connection.  Loop until we have a password if requested
253          * by backend.
254          */
255         do
256         {
257                 need_pass = false;
258                 AH->connection = PQsetdbLogin(pghost, pgport, NULL, NULL,
259                                                                           dbname, username, password);
260
261                 if (!AH->connection)
262                         die_horribly(AH, modulename, "failed to connect to database\n");
263
264                 if (PQstatus(AH->connection) == CONNECTION_BAD &&
265                         strcmp(PQerrorMessage(AH->connection), "fe_sendauth: no password supplied\n") == 0 &&
266                         !feof(stdin))
267                 {
268                         PQfinish(AH->connection);
269                         need_pass = true;
270                         free(password);
271                         password = NULL;
272                         password = simple_prompt("Password: ", 100, false);
273                 }
274         } while (need_pass);
275
276         if (password)
277                 free(password);
278
279         /* check to see that the backend connection was successfully made */
280         if (PQstatus(AH->connection) == CONNECTION_BAD)
281                 die_horribly(AH, modulename, "connection to database \"%s\" failed: %s",
282                                    PQdb(AH->connection), PQerrorMessage(AH->connection));
283
284         /* check for version mismatch */
285         _check_database_version(AH, ignoreVersion);
286
287         PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
288
289         return AH->connection;
290 }
291
292
293 static void
294 notice_processor(void *arg, const char *message)
295 {
296         write_msg(NULL, "%s", message);
297 }
298
299
300 /* Public interface */
301 /* Convenience function to send a query. Monitors result to handle COPY statements */
302 int
303 ExecuteSqlCommand(ArchiveHandle *AH, PQExpBuffer qry, char *desc, bool use_blob)
304 {
305         if (use_blob)
306                 return _executeSqlCommand(AH, AH->blobConnection, qry, desc);
307         else
308                 return _executeSqlCommand(AH, AH->connection, qry, desc);
309 }
310
311 /*
312  * Handle command execution. This is used to execute a command on more than one connection,
313  * but the 'pgCopyIn' setting assumes the COPY commands are ONLY executed on the primary
314  * setting...an error will be raised otherwise.
315  */
316 static int
317 _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc)
318 {
319         PGresult   *res;
320
321         /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
322         res = PQexec(conn, qry->data);
323         if (!res)
324                 die_horribly(AH, modulename, "%s: no result from server\n", desc);
325
326         if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
327         {
328                 if (PQresultStatus(res) == PGRES_COPY_IN)
329                 {
330                         if (conn != AH->connection)
331                                 die_horribly(AH, modulename, "COPY command executed in non-primary connection\n");
332
333                         AH->pgCopyIn = 1;
334                 }
335                 else
336                         die_horribly(AH, modulename, "%s: %s",
337                                                  desc, PQerrorMessage(AH->connection));
338         }
339
340         PQclear(res);
341
342         return strlen(qry->data);
343 }
344
345 /*
346  * Used by ExecuteSqlCommandBuf to send one buffered line when running a COPY command.
347  */
348 static char *
349 _sendCopyLine(ArchiveHandle *AH, char *qry, char *eos)
350 {
351         size_t          loc;                    /* Location of next newline */
352         int                     pos = 0;                /* Current position */
353         int                     sPos = 0;               /* Last pos of a slash char */
354         int                     isEnd = 0;
355
356         /* loop to find unquoted newline ending the line of COPY data */
357         for (;;)
358         {
359                 loc = strcspn(&qry[pos], "\n") + pos;
360
361                 /* If no match, then wait */
362                 if (loc >= (eos - qry)) /* None found */
363                 {
364                         appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
365                         return eos;
366                 }
367
368                 /*
369                  * fprintf(stderr, "Found cr at %d, prev char was %c, next was
370                  * %c\n", loc, qry[loc-1], qry[loc+1]);
371                  */
372
373                 /* Count the number of preceding slashes */
374                 sPos = loc;
375                 while (sPos > 0 && qry[sPos - 1] == '\\')
376                         sPos--;
377
378                 sPos = loc - sPos;
379
380                 /*
381                  * If an odd number of preceding slashes, then \n was escaped so
382                  * set the next search pos, and loop (if any left).
383                  */
384                 if ((sPos & 1) == 1)
385                 {
386                         /* fprintf(stderr, "cr was escaped\n"); */
387                         pos = loc + 1;
388                         if (pos >= (eos - qry))
389                         {
390                                 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
391                                 return eos;
392                         }
393                 }
394                 else
395                         break;
396         }
397
398         /* We found an unquoted newline */
399         qry[loc] = '\0';
400         appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
401         isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
402
403         /*---------
404          * fprintf(stderr, "Sending '%s' via
405          *              COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd);
406          *---------
407          */
408
409         if (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
410                 die_horribly(AH, modulename, "error returned by PQputline\n");
411
412         resetPQExpBuffer(AH->pgCopyBuf);
413
414         /*
415          * fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data);
416          */
417
418         if (isEnd)
419         {
420                 if (PQendcopy(AH->connection) != 0)
421                         die_horribly(AH, modulename, "error returned by PQendcopy\n");
422
423                 AH->pgCopyIn = 0;
424         }
425
426         return qry + loc + 1;
427 }
428
429 /*
430  * Used by ExecuteSqlCommandBuf to send one buffered line of SQL (not data for the copy command).
431  */
432 static char *
433 _sendSQLLine(ArchiveHandle *AH, char *qry, char *eos)
434 {
435         int                     pos = 0;                /* Current position */
436
437         /*
438          * The following is a mini state machine to assess the end of an SQL
439          * statement. It really only needs to parse good SQL, or at least
440          * that's the theory... End-of-statement is assumed to be an unquoted,
441          * un commented semi-colon.
442          */
443
444         /*
445          * fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data);
446          */
447
448         for (pos = 0; pos < (eos - qry); pos++)
449         {
450                 appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
451                 /* fprintf(stderr, " %c",qry[pos]); */
452
453                 switch (AH->sqlparse.state)
454                 {
455
456                         case SQL_SCAN:          /* Default state == 0, set in _allocAH */
457
458                                 if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
459                                 {
460                                         /* Send It & reset the buffer */
461
462                                         /*
463                                          * fprintf(stderr, "    sending: '%s'\n\n",
464                                          * AH->sqlBuf->data);
465                                          */
466                                         ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query", false);
467                                         resetPQExpBuffer(AH->sqlBuf);
468                                         AH->sqlparse.lastChar = '\0';
469
470                                         /*
471                                          * Remove any following newlines - so that embedded
472                                          * COPY commands don't get a starting newline.
473                                          */
474                                         pos++;
475                                         for (; pos < (eos - qry) && qry[pos] == '\n'; pos++);
476
477                                         /* We've got our line, so exit */
478                                         return qry + pos;
479                                 }
480                                 else
481                                 {
482                                         if (qry[pos] == '"' || qry[pos] == '\'')
483                                         {
484                                                 /* fprintf(stderr,"[startquote]\n"); */
485                                                 AH->sqlparse.state = SQL_IN_QUOTE;
486                                                 AH->sqlparse.quoteChar = qry[pos];
487                                                 AH->sqlparse.backSlash = 0;
488                                         }
489                                         else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
490                                                 AH->sqlparse.state = SQL_IN_SQL_COMMENT;
491                                         else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
492                                                 AH->sqlparse.state = SQL_IN_EXT_COMMENT;
493                                         else if (qry[pos] == '(')
494                                                 AH->sqlparse.braceDepth++;
495                                         else if (qry[pos] == ')')
496                                                 AH->sqlparse.braceDepth--;
497
498                                         AH->sqlparse.lastChar = qry[pos];
499                                 }
500
501                                 break;
502
503                         case SQL_IN_SQL_COMMENT:
504
505                                 if (qry[pos] == '\n')
506                                         AH->sqlparse.state = SQL_SCAN;
507                                 break;
508
509                         case SQL_IN_EXT_COMMENT:
510
511                                 if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
512                                         AH->sqlparse.state = SQL_SCAN;
513                                 break;
514
515                         case SQL_IN_QUOTE:
516
517                                 if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
518                                 {
519                                         /* fprintf(stderr,"[endquote]\n"); */
520                                         AH->sqlparse.state = SQL_SCAN;
521                                 }
522                                 else
523                                 {
524
525                                         if (qry[pos] == '\\')
526                                         {
527                                                 if (AH->sqlparse.lastChar == '\\')
528                                                         AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
529                                                 else
530                                                         AH->sqlparse.backSlash = 1;
531                                         }
532                                         else
533                                                 AH->sqlparse.backSlash = 0;
534                                 }
535                                 break;
536
537                 }
538                 AH->sqlparse.lastChar = qry[pos];
539                 /* fprintf(stderr, "\n"); */
540         }
541
542         /*
543          * If we get here, we've processed entire string with no complete SQL
544          * stmt
545          */
546         return eos;
547
548 }
549
550
551 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
552 int
553 ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, size_t bufLen)
554 {
555         char       *qry = (char *) qryv;
556         char       *eos = qry + bufLen;
557
558         /*
559          * fprintf(stderr, "\n\n*****\n
560          * Buffer:\n\n%s\n*******************\n\n", qry);
561          */
562
563         /* Could switch between command and COPY IN mode at each line */
564         while (qry < eos)
565         {
566                 if (AH->pgCopyIn)
567                         qry = _sendCopyLine(AH, qry, eos);
568                 else
569                         qry = _sendSQLLine(AH, qry, eos);
570         }
571
572         return 1;
573 }
574
575 void
576 FixupBlobRefs(ArchiveHandle *AH, TocEntry *te)
577 {
578         PQExpBuffer tblName;
579         PQExpBuffer tblQry;
580         PGresult   *res,
581                            *uRes;
582         int                     i,
583                                 n;
584         char       *attr;
585
586         if (strcmp(te->tag, BLOB_XREF_TABLE) == 0)
587                 return;
588
589         tblName = createPQExpBuffer();
590         tblQry = createPQExpBuffer();
591
592         if (te->namespace && strlen(te->namespace) > 0)
593                 appendPQExpBuffer(tblName, "%s.",
594                                                   fmtId(te->namespace));
595         appendPQExpBuffer(tblName, "%s",
596                                           fmtId(te->tag));
597
598         appendPQExpBuffer(tblQry,
599                                           "SELECT a.attname FROM "
600                                           "pg_catalog.pg_attribute a, pg_catalog.pg_type t "
601                  "WHERE a.attnum > 0 AND a.attrelid = '%s'::pg_catalog.regclass "
602                                  "AND a.atttypid = t.oid AND t.typname in ('oid', 'lo')",
603                                           tblName->data);
604
605         res = PQexec(AH->blobConnection, tblQry->data);
606         if (!res)
607                 die_horribly(AH, modulename, "could not find oid columns of table \"%s\": %s",
608                                          te->tag, PQerrorMessage(AH->connection));
609
610         if ((n = PQntuples(res)) == 0)
611         {
612                 /* nothing to do */
613                 ahlog(AH, 1, "no OID type columns in table %s\n", te->tag);
614         }
615
616         for (i = 0; i < n; i++)
617         {
618                 attr = PQgetvalue(res, i, 0);
619
620                 ahlog(AH, 1, "fixing large object cross-references for %s.%s\n",
621                           te->tag, attr);
622
623                 resetPQExpBuffer(tblQry);
624
625                 /* Can't use fmtId twice in one call... */
626                 appendPQExpBuffer(tblQry,
627                                                   "UPDATE %s SET %s = %s.newOid",
628                                                   tblName->data, fmtId(attr),
629                                                   BLOB_XREF_TABLE);
630                 appendPQExpBuffer(tblQry,
631                                                   " FROM %s WHERE %s.oldOid = %s.%s",
632                                                   BLOB_XREF_TABLE,
633                                                   BLOB_XREF_TABLE,
634                                                   tblName->data, fmtId(attr));
635
636                 ahlog(AH, 10, "SQL: %s\n", tblQry->data);
637
638                 uRes = PQexec(AH->blobConnection, tblQry->data);
639                 if (!uRes)
640                         die_horribly(AH, modulename,
641                                         "could not update column \"%s\" of table \"%s\": %s",
642                                           attr, te->tag, PQerrorMessage(AH->blobConnection));
643
644                 if (PQresultStatus(uRes) != PGRES_COMMAND_OK)
645                         die_horribly(AH, modulename,
646                                 "error while updating column \"%s\" of table \"%s\": %s",
647                                           attr, te->tag, PQerrorMessage(AH->blobConnection));
648
649                 PQclear(uRes);
650         }
651
652         PQclear(res);
653         destroyPQExpBuffer(tblName);
654         destroyPQExpBuffer(tblQry);
655 }
656
657 /**********
658  *      Convenient SQL calls
659  **********/
660 void
661 CreateBlobXrefTable(ArchiveHandle *AH)
662 {
663         PQExpBuffer qry = createPQExpBuffer();
664
665         /* IF we don't have a BLOB connection, then create one */
666         if (!AH->blobConnection)
667                 AH->blobConnection = _connectDB(AH, NULL, NULL);
668
669         ahlog(AH, 1, "creating table for large object cross-references\n");
670
671         appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid pg_catalog.oid, newOid pg_catalog.oid);", BLOB_XREF_TABLE);
672
673         ExecuteSqlCommand(AH, qry, "could not create large object cross-reference table", true);
674
675         resetPQExpBuffer(qry);
676
677         appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
678         ExecuteSqlCommand(AH, qry, "could not create index on large object cross-reference table", true);
679
680         destroyPQExpBuffer(qry);
681 }
682
683 void
684 InsertBlobXref(ArchiveHandle *AH, Oid old, Oid new)
685 {
686         PQExpBuffer qry = createPQExpBuffer();
687
688         appendPQExpBuffer(qry,
689                                    "Insert Into %s(oldOid, newOid) Values ('%u', '%u');",
690                                           BLOB_XREF_TABLE, old, new);
691
692         ExecuteSqlCommand(AH, qry, "could not create large object cross-reference entry", true);
693
694         destroyPQExpBuffer(qry);
695 }
696
697 void
698 StartTransaction(ArchiveHandle *AH)
699 {
700         PQExpBuffer qry = createPQExpBuffer();
701
702         appendPQExpBuffer(qry, "Begin;");
703
704         ExecuteSqlCommand(AH, qry, "could not start database transaction", false);
705         AH->txActive = true;
706
707         destroyPQExpBuffer(qry);
708 }
709
710 void
711 StartTransactionXref(ArchiveHandle *AH)
712 {
713         PQExpBuffer qry = createPQExpBuffer();
714
715         appendPQExpBuffer(qry, "Begin;");
716
717         ExecuteSqlCommand(AH, qry,
718                                           "could not start transaction for large object cross-references", true);
719         AH->blobTxActive = true;
720
721         destroyPQExpBuffer(qry);
722 }
723
724 void
725 CommitTransaction(ArchiveHandle *AH)
726 {
727         PQExpBuffer qry = createPQExpBuffer();
728
729         appendPQExpBuffer(qry, "Commit;");
730
731         ExecuteSqlCommand(AH, qry, "could not commit database transaction", false);
732         AH->txActive = false;
733
734         destroyPQExpBuffer(qry);
735 }
736
737 void
738 CommitTransactionXref(ArchiveHandle *AH)
739 {
740         PQExpBuffer qry = createPQExpBuffer();
741
742         appendPQExpBuffer(qry, "Commit;");
743
744         ExecuteSqlCommand(AH, qry, "could not commit transaction for large object cross-references", true);
745         AH->blobTxActive = false;
746
747         destroyPQExpBuffer(qry);
748 }