55 *Implements the basic DB functions used by the archiver.
66 *
77 * IDENTIFICATION
8- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.53 2004/04/22 02:39:10 momjian Exp $
8+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.54 2004/08/20 16:07:15 momjian Exp $
99 *
1010 *-------------------------------------------------------------------------
1111 */
@@ -37,6 +37,8 @@ static void notice_processor(void *arg, const char *message);
3737static char * _sendSQLLine (ArchiveHandle * AH ,char * qry ,char * eos );
3838static char * _sendCopyLine (ArchiveHandle * AH ,char * qry ,char * eos );
3939
40+ static int _isIdentChar (char c );
41+ static int _isDQChar (char c ,int atStart );
4042
4143static int
4244_parse_version (ArchiveHandle * AH ,const char * versionString )
@@ -416,6 +418,9 @@ static char *
416418_sendSQLLine (ArchiveHandle * AH ,char * qry ,char * eos )
417419{
418420int pos = 0 ;/* Current position */
421+ char * sqlPtr ;
422+ int consumed ;
423+ int startDT = 0 ;
419424
420425/*
421426 * The following is a mini state machine to assess the end of an SQL
@@ -433,88 +438,174 @@ _sendSQLLine(ArchiveHandle *AH, char *qry, char *eos)
433438appendPQExpBufferChar (AH -> sqlBuf ,qry [pos ]);
434439/* fprintf(stderr, " %c",qry[pos]); */
435440
436- switch (AH -> sqlparse .state )
441+ /* Loop until character consumed */
442+ do
437443{
444+ /* If a character needs to be scanned in a different state,
445+ * consumed can be set to 0 to avoid advancing. Care must
446+ * be taken to ensure internal state is not damaged.
447+ */
448+ consumed = 1 ;
438449
439- case SQL_SCAN :/* Default state == 0, set in _allocAH */
440- if (qry [pos ]== ';' && AH -> sqlparse .braceDepth == 0 )
450+ switch (AH -> sqlparse .state )
441451{
442- /* Send It & reset the buffer */
443-
444- /*
445- * fprintf(stderr, " sending: '%s'\n\n",
446- * AH->sqlBuf->data);
452+
453+ case SQL_SCAN :/* Default state == 0, set in _allocAH */
454+ if (qry [pos ]== ';' && AH -> sqlparse .braceDepth == 0 )
455+ {
456+ /* We've got the end of a statement.
457+ * Send It & reset the buffer.
458+ */
459+
460+ /*
461+ * fprintf(stderr, " sending: '%s'\n\n",
462+ * AH->sqlBuf->data);
463+ */
464+ ExecuteSqlCommand (AH ,AH -> sqlBuf ,"could not execute query" , false);
465+ resetPQExpBuffer (AH -> sqlBuf );
466+ AH -> sqlparse .lastChar = '\0' ;
467+
468+ /*
469+ * Remove any following newlines - so that embedded
470+ * COPY commands don't get a starting newline.
471+ */
472+ pos ++ ;
473+ for (;pos < (eos - qry )&& qry [pos ]== '\n' ;pos ++ );
474+
475+ /* We've got our line, so exit */
476+ return qry + pos ;
477+ }
478+ else
479+ {
480+ /*
481+ * Look for normal boring quote chars, or dollar-quotes. We make
482+ * the assumption that $-quotes will not have an ident character
483+ * before them in all pg_dump output.
484+ */
485+ if (qry [pos ]== '"'
486+ || qry [pos ]== '\''
487+ || (qry [pos ]== '$' && _isIdentChar (AH -> sqlparse .lastChar )== 0 )
488+ )
489+ {
490+ /* fprintf(stderr,"[startquote]\n"); */
491+ AH -> sqlparse .state = SQL_IN_QUOTE ;
492+ AH -> sqlparse .quoteChar = qry [pos ];
493+ AH -> sqlparse .backSlash = 0 ;
494+ if (qry [pos ]== '$' )
495+ {
496+ /* override the state */
497+ AH -> sqlparse .state = SQL_IN_DOLLARTAG ;
498+ /* Used for checking first char of tag */
499+ startDT = 1 ;
500+ /* We store the tag for later comparison. */
501+ AH -> sqlparse .tagBuf = createPQExpBuffer ();
502+ /* Get leading $ */
503+ appendPQExpBufferChar (AH -> sqlparse .tagBuf ,qry [pos ]);
504+ }
505+ }
506+ else if (qry [pos ]== '-' && AH -> sqlparse .lastChar == '-' )
507+ AH -> sqlparse .state = SQL_IN_SQL_COMMENT ;
508+ else if (qry [pos ]== '*' && AH -> sqlparse .lastChar == '/' )
509+ AH -> sqlparse .state = SQL_IN_EXT_COMMENT ;
510+ else if (qry [pos ]== '(' )
511+ AH -> sqlparse .braceDepth ++ ;
512+ else if (qry [pos ]== ')' )
513+ AH -> sqlparse .braceDepth -- ;
514+
515+ AH -> sqlparse .lastChar = qry [pos ];
516+ }
517+ break ;
518+
519+ case SQL_IN_DOLLARTAG :
520+
521+ /* Like a quote, we look for a closing char *but* we only
522+ * allow a very limited set of contained chars, and no escape chars.
523+ * If invalid chars are found, we abort tag processing.
447524 */
448- ExecuteSqlCommand (AH ,AH -> sqlBuf ,"could not execute query" , false);
449- resetPQExpBuffer (AH -> sqlBuf );
450- AH -> sqlparse .lastChar = '\0' ;
525+
526+ if (qry [pos ]== '$' )
527+ {
528+ /* fprintf(stderr,"[endquote]\n"); */
529+ /* Get trailing $ */
530+ appendPQExpBufferChar (AH -> sqlparse .tagBuf ,qry [pos ]);
531+ AH -> sqlparse .state = SQL_IN_DOLLARQUOTE ;
532+ }
533+ else
534+ {
535+ if (_isDQChar (qry [pos ],startDT ) )
536+ {
537+ /* Valid, so add */
538+ appendPQExpBufferChar (AH -> sqlparse .tagBuf ,qry [pos ]);
539+ }
540+ else
541+ {
542+ /* Jump back to 'scan' state, we're not really in a tag,
543+ * and valid tag chars do not include the various chars
544+ * we look for in this state machine, so it's safe to just
545+ * jump from this state back to SCAN. We set consumed = 0
546+ * so that this char gets rescanned in new state.
547+ */
548+ destroyPQExpBuffer (AH -> sqlparse .tagBuf );
549+ AH -> sqlparse .state = SQL_SCAN ;
550+ consumed = 0 ;
551+ }
552+ }
553+ startDT = 0 ;
554+ break ;
555+
451556
557+ case SQL_IN_DOLLARQUOTE :
452558/*
453- *Remove any following newlines - so that embedded
454- *COPY commands don't get a starting newline .
559+ *Comparing the entire string backwards each time is NOT efficient,
560+ *but dollar quotes in pg_dump are small and the code is a lot simpler .
455561 */
456- pos ++ ;
457- for (;pos < (eos - qry )&& qry [pos ]== '\n' ;pos ++ );
458-
459- /* We've got our line, so exit */
460- return qry + pos ;
461- }
462- else
463- {
464- if (qry [pos ]== '"' || qry [pos ]== '\'' )
562+ sqlPtr = AH -> sqlBuf -> data + AH -> sqlBuf -> len - AH -> sqlparse .tagBuf -> len ;
563+
564+ if (strncmp (AH -> sqlparse .tagBuf -> data ,sqlPtr ,AH -> sqlparse .tagBuf -> len )== 0 ) {
565+ /* End of $-quote */
566+ AH -> sqlparse .state = SQL_SCAN ;
567+ destroyPQExpBuffer (AH -> sqlparse .tagBuf );
568+ }
569+ break ;
570+
571+ case SQL_IN_SQL_COMMENT :
572+ if (qry [pos ]== '\n' )
573+ AH -> sqlparse .state = SQL_SCAN ;
574+ break ;
575+
576+ case SQL_IN_EXT_COMMENT :
577+ if (AH -> sqlparse .lastChar == '*' && qry [pos ]== '/' )
578+ AH -> sqlparse .state = SQL_SCAN ;
579+ break ;
580+
581+ case SQL_IN_QUOTE :
582+
583+ if (!AH -> sqlparse .backSlash && AH -> sqlparse .quoteChar == qry [pos ])
465584{
466- /* fprintf(stderr,"[startquote]\n"); */
467- AH -> sqlparse .state = SQL_IN_QUOTE ;
468- AH -> sqlparse .quoteChar = qry [pos ];
469- AH -> sqlparse .backSlash = 0 ;
585+ /* fprintf(stderr,"[endquote]\n"); */
586+ AH -> sqlparse .state = SQL_SCAN ;
470587}
471- else if (qry [pos ]== '-' && AH -> sqlparse .lastChar == '-' )
472- AH -> sqlparse .state = SQL_IN_SQL_COMMENT ;
473- else if (qry [pos ]== '*' && AH -> sqlparse .lastChar == '/' )
474- AH -> sqlparse .state = SQL_IN_EXT_COMMENT ;
475- else if (qry [pos ]== '(' )
476- AH -> sqlparse .braceDepth ++ ;
477- else if (qry [pos ]== ')' )
478- AH -> sqlparse .braceDepth -- ;
479-
480- AH -> sqlparse .lastChar = qry [pos ];
481- }
482- break ;
483-
484- case SQL_IN_SQL_COMMENT :
485- if (qry [pos ]== '\n' )
486- AH -> sqlparse .state = SQL_SCAN ;
487- break ;
488-
489- case SQL_IN_EXT_COMMENT :
490- if (AH -> sqlparse .lastChar == '*' && qry [pos ]== '/' )
491- AH -> sqlparse .state = SQL_SCAN ;
492- break ;
493-
494- case SQL_IN_QUOTE :
495- if (!AH -> sqlparse .backSlash && AH -> sqlparse .quoteChar == qry [pos ])
496- {
497- /* fprintf(stderr,"[endquote]\n"); */
498- AH -> sqlparse .state = SQL_SCAN ;
499- }
500- else
501- {
502-
503- if (qry [pos ]== '\\' )
588+ else
504589{
505- if (AH -> sqlparse .lastChar == '\\' )
506- AH -> sqlparse .backSlash = !AH -> sqlparse .backSlash ;
590+
591+ if (qry [pos ]== '\\' )
592+ {
593+ if (AH -> sqlparse .lastChar == '\\' )
594+ AH -> sqlparse .backSlash = !AH -> sqlparse .backSlash ;
595+ else
596+ AH -> sqlparse .backSlash = 1 ;
597+ }
507598else
508- AH -> sqlparse .backSlash = 1 ;
599+ AH -> sqlparse .backSlash = 0 ;
509600}
510- else
511- AH -> sqlparse .backSlash = 0 ;
512- }
513- break ;
601+ break ;
602+
603+ }
514604
515- }
516- AH -> sqlparse .lastChar = qry [pos ];
517- /* fprintf(stderr, "\n"); */
605+ }while (consumed == 0 );
606+
607+ AH -> sqlparse .lastChar = qry [pos ];
608+ /* fprintf(stderr, "\n"); */
518609}
519610
520611/*
@@ -759,3 +850,38 @@ CommitTransactionXref(ArchiveHandle *AH)
759850
760851destroyPQExpBuffer (qry );
761852}
853+
854+ static int _isIdentChar (char c )
855+ {
856+ if ((c >='a' && c <='z' )
857+ || (c >='A' && c <='Z' )
858+ || (c >='0' && c <='9' )
859+ || (c == '_' )
860+ || (c == '$' )
861+ || (c >='\200' && c <='\377' )
862+ )
863+ {
864+ return 1 ;
865+ }
866+ else
867+ {
868+ return 0 ;
869+ }
870+ }
871+
872+ static int _isDQChar (char c ,int atStart )
873+ {
874+ if ((c >='a' && c <='z' )
875+ || (c >='A' && c <='Z' )
876+ || (c == '_' )
877+ || (atStart == 0 && c >='0' && c <='9' )
878+ || (c >='\200' && c <='\377' )
879+ )
880+ {
881+ return 1 ;
882+ }
883+ else
884+ {
885+ return 0 ;
886+ }
887+ }