OSDN Git Service

- Added --create, --no-owner, --superuser, --no-reconnect (pg_dump & pg_restore)
[pg-rex/syncrep.git] / src / bin / pg_dump / pg_backup_db.c
1 /*-------------------------------------------------------------------------
2  *
3  *
4 *-------------------------------------------------------------------------
5  */
6
7 #include <unistd.h>                             /* for getopt() */
8 #include <ctype.h>
9
10 #include "postgres.h"
11
12 #ifdef HAVE_TERMIOS_H
13 #include <termios.h>
14 #endif
15
16 #include "access/attnum.h"
17 #include "access/htup.h"
18 #include "catalog/pg_index.h"
19 #include "catalog/pg_language.h"
20 #include "catalog/pg_trigger.h"
21 #include "catalog/pg_type.h"
22
23 #include "libpq-fe.h"
24 #include <libpq/libpq-fs.h>
25 #ifndef HAVE_STRDUP
26 #include "strdup.h"
27 #endif
28
29 #include "pg_dump.h"
30 #include "pg_backup.h"
31 #include "pg_backup_archiver.h"
32 #include "pg_backup_db.h"
33
34 static const char       *progname = "Archiver(db)";
35
36 static void     _prompt_for_password(char *username, char *password);
37 static void     _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
38 static PGconn*  _connectDB(ArchiveHandle *AH, const char* newdbname, char *newUser);
39 static int              _executeSqlCommand(ArchiveHandle* AH, PGconn *conn, PQExpBuffer qry, char *desc);
40
41
42 static void
43 _prompt_for_password(char *username, char *password)
44 {
45         char            buf[512];
46         int                     length;
47
48 #ifdef HAVE_TERMIOS_H
49         struct termios t_orig,
50                                    t;
51 #endif
52
53         /*
54          * Allow for forcing a specific username
55          */
56         if (strlen(username) == 0)
57         {
58                 fprintf(stderr, "Username: ");
59                 fflush(stderr);
60                 fgets(username, 100, stdin);
61                 length = strlen(username);
62                 /* skip rest of the line */
63                 if (length > 0 && username[length - 1] != '\n')
64                 {
65                         do
66                         {
67                                 fgets(buf, 512, stdin);
68                         } while (buf[strlen(buf) - 1] != '\n');
69                 }
70                 if (length > 0 && username[length - 1] == '\n')
71                         username[length - 1] = '\0';
72         }
73
74 #ifdef HAVE_TERMIOS_H
75         tcgetattr(0, &t);
76         t_orig = t;
77         t.c_lflag &= ~ECHO;
78         tcsetattr(0, TCSADRAIN, &t);
79 #endif
80         fprintf(stderr, "Password: ");
81         fflush(stderr);
82         fgets(password, 100, stdin);
83 #ifdef HAVE_TERMIOS_H
84         tcsetattr(0, TCSADRAIN, &t_orig);
85 #endif
86
87         length = strlen(password);
88         /* skip rest of the line */
89         if (length > 0 && password[length - 1] != '\n')
90         {
91                 do
92                 {
93                         fgets(buf, 512, stdin);
94                 } while (buf[strlen(buf) - 1] != '\n');
95         }
96         if (length > 0 && password[length - 1] == '\n')
97                 password[length - 1] = '\0';
98
99         fprintf(stderr, "\n\n");
100 }
101
102
103 static void
104 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
105 {
106         PGresult   *res;
107         double      myversion;
108         const char *remoteversion_str;
109         double      remoteversion;
110         PGconn          *conn = AH->connection;
111
112         myversion = strtod(PG_VERSION, NULL);
113         res = PQexec(conn, "SELECT version()");
114         if (!res ||
115                 PQresultStatus(res) != PGRES_TUPLES_OK ||
116                 PQntuples(res) != 1)
117
118                 die_horribly(AH, "check_database_version(): command failed.  "
119                                 "Explanation from backend: '%s'.\n", PQerrorMessage(conn));
120
121         remoteversion_str = PQgetvalue(res, 0, 0);
122         remoteversion = strtod(remoteversion_str + 11, NULL);
123         if (myversion != remoteversion)
124         {
125                 fprintf(stderr, "Database version: %s\n%s version: %s\n",
126                                 remoteversion_str, progname, PG_VERSION);
127                 if (ignoreVersion)
128                         fprintf(stderr, "Proceeding despite version mismatch.\n");
129                 else
130                         die_horribly(AH, "Aborting because of version mismatch.\n"
131                                     "Use --ignore-version if you think it's safe to proceed anyway.\n");        
132         }
133         PQclear(res);
134 }
135
136 /* 
137  * Check if a given user is a superuser.
138  */
139 int UserIsSuperuser(ArchiveHandle *AH, char* user)
140 {
141         PQExpBuffer                     qry = createPQExpBuffer();
142         PGresult                        *res;
143         int                                     i_usesuper;
144         int                                     ntups;
145         int                                     isSuper;
146
147         /* Get the superuser setting */
148         appendPQExpBuffer(qry, "select usesuper from pg_user where usename = '%s'", user);
149         res = PQexec(AH->connection, qry->data);
150
151         if (!res)
152                 die_horribly(AH, "%s: null result checking superuser status of %s.\n",
153                                         progname, user);
154
155         if (PQresultStatus(res) != PGRES_TUPLES_OK)
156                 die_horribly(AH, "%s: Could not check superuser status of %s. Explanation from backend: %s\n",
157                                         progname, user, PQerrorMessage(AH->connection));
158
159         ntups = PQntuples(res);
160
161         if (ntups == 0)
162                 isSuper = 0;
163         else
164         {
165                 i_usesuper = PQfnumber(res, "usesuper");
166                 isSuper = (strcmp(PQgetvalue(res, 0, i_usesuper), "t") == 0);
167         }
168         PQclear(res);
169
170         return isSuper;
171 }
172
173 int ConnectedUserIsSuperuser(ArchiveHandle *AH)
174 {
175         return UserIsSuperuser(AH, PQuser(AH->connection));
176 }
177
178 char* ConnectedUser(ArchiveHandle *AH)
179 {
180         return PQuser(AH->connection);
181 }
182
183 /*
184  * Reconnect the DB associated with the archive handle 
185  */
186 int ReconnectDatabase(ArchiveHandle *AH, const char* newdbname, char *newUser)
187 {
188         PGconn          *newConn;
189         char            *dbname;
190
191         if (!newdbname || (strcmp(newdbname, "-") == 0) )
192                 dbname = PQdb(AH->connection);
193         else
194                 dbname = (char*)newdbname;
195
196         /* Let's see if the request is already satisfied */
197         if (strcmp(PQuser(AH->connection), newUser) == 0 && strcmp(newdbname, PQdb(AH->connection)) == 0)
198                 return 1;
199
200         newConn = _connectDB(AH, dbname, newUser);
201
202         PQfinish(AH->connection);
203         AH->connection = newConn;
204         strcpy(AH->username, newUser);
205
206         return 1;
207 }
208
209 /*
210  * Connect to the db again.
211  */
212 static PGconn* _connectDB(ArchiveHandle *AH, const char* reqdb, char *requser)
213 {
214         int                     need_pass;
215         PGconn          *newConn;
216         char            password[100];
217         char            *pwparam = NULL;
218         int                     badPwd = 0;
219         int                     noPwd = 0;
220         char            *newdb;
221         char            *newuser;
222
223         if (!reqdb || (strcmp(reqdb, "-") == 0) )
224                 newdb = PQdb(AH->connection);
225         else
226                 newdb = (char*)reqdb;
227
228         if (!requser || (strlen(requser) == 0))
229                 newuser = PQuser(AH->connection);
230         else
231                 newuser = (char*)requser;
232
233         ahlog(AH, 1, "Connecting to %s as %s\n", newdb, newuser);
234
235         do
236         {
237                 need_pass = false;
238                 newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
239                                                                 NULL, NULL, newdb, 
240                                                                 newuser, pwparam);
241                 if (!newConn)
242                         die_horribly(AH, "%s: Failed to reconnect (PQsetdbLogin failed).\n", progname);
243
244                 if (PQstatus(newConn) == CONNECTION_BAD)
245                 {
246                         noPwd = (strcmp(PQerrorMessage(newConn), "fe_sendauth: no password supplied\n") == 0);
247                         badPwd = (strncmp(PQerrorMessage(newConn), "Password authentication failed for user", 39)
248                                                 == 0);
249
250                         if (noPwd || badPwd) 
251                         {
252
253                                 if (badPwd)
254                                         fprintf(stderr, "Password incorrect\n");
255
256                                 fprintf(stderr, "Connecting to %s as %s\n", PQdb(AH->connection), newuser);
257
258                                 need_pass = true;
259                                 _prompt_for_password(newuser, password);
260                                 pwparam = password; 
261                         }
262                         else
263                                 die_horribly(AH, "%s: Could not reconnect. %s\n", progname, PQerrorMessage(newConn));
264                 }
265
266         } while (need_pass);
267
268         return newConn;
269 }
270
271
272 PGconn* ConnectDatabase(Archive *AHX, 
273                 const char*     dbname,
274                 const char*     pghost,
275                 const char*     pgport,
276                 const int               reqPwd,
277                 const int               ignoreVersion)
278 {
279         ArchiveHandle   *AH = (ArchiveHandle*)AHX;
280         char                    connect_string[512] = "";
281         char                    tmp_string[128];
282         char                    password[100];
283
284         if (AH->connection)
285                 die_horribly(AH, "%s: already connected to database\n", progname);
286
287         if (!dbname && !(dbname = getenv("PGDATABASE")) ) 
288                 die_horribly(AH, "%s: no database name specified\n", progname);
289
290         AH->dbname = strdup(dbname);
291
292         if (pghost != NULL)
293         {
294                 AH->pghost = strdup(pghost);
295                 sprintf(tmp_string, "host=%s ", AH->pghost);
296                 strcat(connect_string, tmp_string);
297         }
298         else
299             AH->pghost = NULL;
300
301         if (pgport != NULL)
302         {
303                 AH->pgport = strdup(pgport);
304                 sprintf(tmp_string, "port=%s ", AH->pgport);
305                 strcat(connect_string, tmp_string);
306         }
307         else
308             AH->pgport = NULL;
309
310         sprintf(tmp_string, "dbname=%s ", AH->dbname);
311         strcat(connect_string, tmp_string);
312
313         if (reqPwd)
314         {
315                 AH->username[0] = '\0';
316                 _prompt_for_password(AH->username, password);
317                 strcat(connect_string, "authtype=password ");
318                 sprintf(tmp_string, "user=%s ", AH->username);
319                 strcat(connect_string, tmp_string);
320                 sprintf(tmp_string, "password=%s ", password);
321                 strcat(connect_string, tmp_string);
322                 MemSet(tmp_string, 0, sizeof(tmp_string));
323                 MemSet(password, 0, sizeof(password));
324         }
325         AH->connection = PQconnectdb(connect_string);
326         MemSet(connect_string, 0, sizeof(connect_string));
327
328         /* check to see that the backend connection was successfully made */
329         if (PQstatus(AH->connection) == CONNECTION_BAD)
330                 die_horribly(AH, "Connection to database '%s' failed.\n%s\n",
331                                                 AH->dbname, PQerrorMessage(AH->connection));
332
333         /* check for version mismatch */
334         _check_database_version(AH, ignoreVersion);
335
336         /*
337      * AH->currUser = PQuser(AH->connection);
338          *      
339          * Removed because it prevented an initial \connect
340          * when dumping to SQL in pg_dump.
341          */
342
343         return AH->connection;
344 }
345
346 /* Public interface */
347 /* Convenience function to send a query. Monitors result to handle COPY statements */
348 int ExecuteSqlCommand(ArchiveHandle* AH, PQExpBuffer qry, char *desc)
349 {
350         return _executeSqlCommand(AH, AH->connection, qry, desc);
351 }
352
353 /* 
354  * Handle command execution. This is used to execute a command on more than one connection,
355  * but the 'pgCopyIn' setting assumes the COPY commands are ONLY executed on the primary
356  * setting...an error will be raised otherwise.
357  */
358 static int _executeSqlCommand(ArchiveHandle* AH, PGconn *conn, PQExpBuffer qry, char *desc)
359 {
360         PGresult                *res;
361
362         /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
363         res = PQexec(conn, qry->data);
364         if (!res)
365                 die_horribly(AH, "%s: %s. No result from backend.\n", progname, desc);
366
367     if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
368         {
369                 if (PQresultStatus(res) == PGRES_COPY_IN)
370                 {
371                         if (conn != AH->connection)
372                                 die_horribly(AH, "%s: COPY command execute in non-primary connection.\n", progname);
373
374                         AH->pgCopyIn = 1;
375                 }
376                 else 
377                         die_horribly(AH, "%s: %s. Code = %d. Explanation from backend: '%s'.\n",
378                                                 progname, desc, PQresultStatus(res), PQerrorMessage(AH->connection));
379         }
380
381         PQclear(res);
382
383         return strlen(qry->data);
384 }
385
386 /* Convenience function to send one or more queries. Monitors result to handle COPY statements */
387 int ExecuteSqlCommandBuf(ArchiveHandle* AH, void *qryv, int bufLen)
388 {
389         int                             loc;
390         int                             pos = 0;
391         int                             sPos = 0;
392         char                    *qry = (char*)qryv;
393         int                             isEnd = 0;
394         char                    *eos = qry + bufLen;
395
396         /* fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n", qry); */
397
398         /* If we're in COPY IN mode, then just break it into lines and send... */
399         if (AH->pgCopyIn) {
400                 for(;;) {
401
402                         /* Find a lf */
403                         loc = strcspn(&qry[pos], "\n") + pos;
404                         pos = 0;
405
406                         /* If no match, then wait */
407                         if (loc >= (eos - qry)) /* None found */
408                         {
409                                 appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
410                                 break;
411                         };
412
413                     /* fprintf(stderr, "Found cr at %d, prev char was %c, next was %c\n", loc, qry[loc-1], qry[loc+1]); */
414         
415                         /* Count the number of preceding slashes */
416                         sPos = loc;
417                         while (sPos > 0 && qry[sPos-1] == '\\')
418                                 sPos--;
419
420                         sPos = loc - sPos;
421
422                         /* If an odd number of preceding slashes, then \n was escaped 
423                          * so set the next search pos, and restart (if any left).
424                          */
425                         if ((sPos & 1) == 1)
426                         {
427                                 /* fprintf(stderr, "cr was escaped\n"); */
428                                 pos = loc + 1;
429                                 if (pos >= (eos - qry))
430                                 {
431                                         appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
432                                         break;
433                                 }
434                         }
435                         else
436                         {
437                                 /* We got a good cr */
438                                 qry[loc] = '\0';
439                                 appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
440                                 qry += loc + 1; 
441                                 isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
442
443                                 /* fprintf(stderr, "Sending '%s' via COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd); */ 
444                                 
445                                 PQputline(AH->connection, AH->pgCopyBuf->data);
446
447                                 resetPQExpBuffer(AH->pgCopyBuf);
448
449                                 /* fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data); */
450
451                                 if(isEnd) {
452                                         PQendcopy(AH->connection);
453                                         AH->pgCopyIn = 0;
454                                         break;
455                                 }
456
457                         }
458
459                         /* Make sure we're not past the original buffer end */
460                         if (qry >= eos)
461                                 break;
462
463                 }
464         }
465
466         /* We may have finished Copy In, and have a non-empty buffer */
467         if (!AH->pgCopyIn) {
468
469                 /* 
470                  * The following is a mini state machine to assess then of of an SQL statement.
471                  * It really only needs to parse good SQL, or at least that's the theory...
472                  * End-of-statement is assumed to be an unquoted, un commented semi-colon.
473                  */
474
475                 /* fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data); */
476
477                 for(pos=0; pos < (eos - qry); pos++)
478                 {
479                         appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
480                         /* fprintf(stderr, " %c",qry[pos]); */
481
482                         switch (AH->sqlparse.state) {
483
484                                 case SQL_SCAN: /* Default state == 0, set in _allocAH */
485
486                                         if (qry[pos] == ';')
487                                         {
488                                                 /* Send It & reset the buffer */
489                                                 /* fprintf(stderr, "    sending: '%s'\n\n", AH->sqlBuf->data); */
490                                                 ExecuteSqlCommand(AH, AH->sqlBuf, "Could not execute query");
491                                                 resetPQExpBuffer(AH->sqlBuf);
492                                                 AH->sqlparse.lastChar = '\0';
493                                         } 
494                                         else 
495                                         {
496                                                 if (qry[pos] == '"' || qry[pos] == '\'')
497                                                 {       
498                                                         /* fprintf(stderr,"[startquote]\n"); */
499                                                         AH->sqlparse.state = SQL_IN_QUOTE;
500                                                         AH->sqlparse.quoteChar = qry[pos];
501                                                         AH->sqlparse.backSlash = 0;
502                                                 } 
503                                                 else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
504                                                 {
505                                                         AH->sqlparse.state = SQL_IN_SQL_COMMENT;
506                                                 } 
507                                                 else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
508                                                 {
509                                                         AH->sqlparse.state = SQL_IN_EXT_COMMENT;
510                                                 }
511                                                 AH->sqlparse.lastChar = qry[pos];
512                                         }
513
514                                         break;
515
516                                 case SQL_IN_SQL_COMMENT:
517
518                                         if (qry[pos] == '\n')
519                                                 AH->sqlparse.state = SQL_SCAN;
520                                         break;
521
522                                 case SQL_IN_EXT_COMMENT:
523
524                                         if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
525                                                 AH->sqlparse.state = SQL_SCAN;
526                                         break;
527
528                                 case SQL_IN_QUOTE:
529
530                                         if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
531                                         {
532                                                 /* fprintf(stderr,"[endquote]\n"); */
533                                                 AH->sqlparse.state = SQL_SCAN;
534                                         } 
535                                         else 
536                                         {
537
538                                                 if (qry[pos] == '\\')
539                                                 {
540                                                         if (AH->sqlparse.lastChar == '\\')
541                                                                 AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
542                                                         else
543                                                                 AH->sqlparse.backSlash = 1;
544                                                         } else {
545                                                                 AH->sqlparse.backSlash = 0;
546                                                 }
547                                         }
548                                         break;
549
550                         }
551                         AH->sqlparse.lastChar = qry[pos];
552                         /* fprintf(stderr, "\n"); */
553                 }
554
555         }
556
557         return 1;
558 }
559
560 void FixupBlobRefs(ArchiveHandle *AH, char *tablename)
561 {
562         PQExpBuffer             tblQry = createPQExpBuffer();
563         PGresult                *res, *uRes;
564         int                             i, n;
565         char                    *attr;
566
567         for(i=0 ; i < strlen(tablename) ; i++)
568                 tablename[i] = tolower(tablename[i]);
569
570         if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
571                 return;
572
573         appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
574                                                                 " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
575                                                                 " AND t.typname = 'oid' AND c.relname = '%s';", tablename);
576
577         res = PQexec(AH->blobConnection, tblQry->data);
578         if (!res)
579                 die_horribly(AH, "%s: could not find OID attrs of %s. Explanation from backend '%s'\n",
580                                                 progname, tablename, PQerrorMessage(AH->connection));
581
582         if ((n = PQntuples(res)) == 0) {
583                 /* We're done */
584                 ahlog(AH, 1, "No OID attributes in table %s\n", tablename);
585                 PQclear(res);
586                 return;
587         }
588
589         for (i = 0 ; i < n ; i++)
590         {
591                 attr = PQgetvalue(res, i, 0);
592
593                 ahlog(AH, 1, " - %s.%s\n", tablename, attr);
594
595                 resetPQExpBuffer(tblQry);
596                 appendPQExpBuffer(tblQry, "Update \"%s\" Set \"%s\" = x.newOid From %s x "
597                                                                         "Where x.oldOid = \"%s\".\"%s\";",
598
599                                                                         tablename, attr, BLOB_XREF_TABLE, tablename, attr);
600
601                 ahlog(AH, 10, " - sql = %s\n", tblQry->data);
602
603                 uRes = PQexec(AH->blobConnection, tblQry->data);
604                 if (!uRes)
605                         die_horribly(AH, "%s: could not update attr %s of table %s. Explanation from backend '%s'\n",
606                                                                 progname, attr, tablename, PQerrorMessage(AH->connection));
607
608                 if ( PQresultStatus(uRes) != PGRES_COMMAND_OK )
609                         die_horribly(AH, "%s: error while updating attr %s of table %s. Explanation from backend '%s'\n",
610                                                                 progname, attr, tablename, PQerrorMessage(AH->connection));
611
612                 PQclear(uRes);
613         }
614
615         PQclear(res);
616
617 }
618
619 /**********
620  *      Convenient SQL calls
621  **********/
622 void CreateBlobXrefTable(ArchiveHandle* AH)
623 {
624         PQExpBuffer             qry = createPQExpBuffer();
625
626         /* IF we don't have a BLOB connection, then create one */
627         if (!AH->blobConnection)
628         {
629                 AH->blobConnection = _connectDB(AH, NULL, NULL);
630         }
631
632         ahlog(AH, 1, "Creating table for BLOBS xrefs\n");
633
634         appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
635
636         _executeSqlCommand(AH, AH->blobConnection, qry, "can not create BLOB xref table '" BLOB_XREF_TABLE "'");
637
638         resetPQExpBuffer(qry);
639
640         appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
641         _executeSqlCommand(AH, AH->blobConnection, qry, "can not create index on BLOB xref table '" BLOB_XREF_TABLE "'");
642 }
643
644 void InsertBlobXref(ArchiveHandle* AH, int old, int new)
645 {
646         PQExpBuffer     qry = createPQExpBuffer();
647
648         appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
649
650         _executeSqlCommand(AH, AH->blobConnection, qry, "can not create BLOB xref entry");
651 }
652
653 void StartTransaction(ArchiveHandle* AH)
654 {
655         PQExpBuffer             qry = createPQExpBuffer();
656
657         appendPQExpBuffer(qry, "Begin;");
658
659         ExecuteSqlCommand(AH, qry, "can not start database transaction");
660 }
661
662 void CommitTransaction(ArchiveHandle* AH)
663 {
664     PQExpBuffer     qry = createPQExpBuffer();
665
666     appendPQExpBuffer(qry, "Commit;");
667
668     ExecuteSqlCommand(AH, qry, "can not commit database transaction");
669 }
670
671