Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 1b5e014

Browse files
committed
This patch allows pg_restore to recognize $-quotes in SQL queries. It
will treat any unquoted string that starts with a $ and has no preceding identifier chars as a potential $-quote tag, it then makes sure that the tag chars are valid. If so, it processes the $-quote. Philip Warner
1 parent fcc5b95 commit 1b5e014

File tree

2 files changed

+201
-72
lines changed

2 files changed

+201
-72
lines changed

src/bin/pg_dump/pg_backup_archiver.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*
1818
*
1919
* IDENTIFICATION
20-
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.h,v 1.58 2004/04/22 02:39:10 momjian Exp $
20+
* $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_archiver.h,v 1.59 2004/08/20 16:07:15 momjian Exp $
2121
*
2222
*-------------------------------------------------------------------------
2323
*/
@@ -137,7 +137,9 @@ typedef enum
137137
SQL_SCAN = 0,
138138
SQL_IN_SQL_COMMENT,
139139
SQL_IN_EXT_COMMENT,
140-
SQL_IN_QUOTE
140+
SQL_IN_QUOTE,
141+
SQL_IN_DOLLARTAG,
142+
SQL_IN_DOLLARQUOTE
141143
} sqlparseState;
142144

143145
typedef struct
@@ -147,6 +149,7 @@ typedef struct
147149
char lastChar;
148150
char quoteChar;
149151
int braceDepth;
152+
PQExpBuffer tagBuf;
150153
} sqlparseInfo;
151154

152155
typedef struct _archiveHandle

src/bin/pg_dump/pg_backup_db.c

+196-70
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
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);
3737
static char *_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos);
3838
static char *_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos);
3939

40+
static int _isIdentChar(char c);
41+
static int _isDQChar(char c, int atStart);
4042

4143
static int
4244
_parse_version(ArchiveHandle *AH, const char *versionString)
@@ -416,6 +418,9 @@ static char *
416418
_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos)
417419
{
418420
int 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)
433438
appendPQExpBufferChar(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+
}
507598
else
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

760851
destroyPQExpBuffer(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+
}

0 commit comments

Comments
 (0)