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

Lines changed: 5 additions & 2 deletions
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

Lines changed: 196 additions & 70 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy