* Implements the basic DB functions used by the archiver.
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.64 2005/07/27 05:14:12 neilc Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.65 2005/09/11 04:10:25 tgl Exp $
*
*-------------------------------------------------------------------------
*/
static char *_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos);
static char *_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos);
-static int _isIdentChar(unsigned char c);
-static int _isDQChar(unsigned char c, int atStart);
+static bool _isIdentChar(unsigned char c);
+static bool _isDQChar(unsigned char c, bool atStart);
#define DB_MAX_ERR_STMT 128
}
/*
- * Used by ExecuteSqlCommandBuf to send one buffered line of SQL (not data for the copy command).
+ * Used by ExecuteSqlCommandBuf to send one buffered line of SQL
+ * (not data for the copy command).
*/
static char *
_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos)
{
- int pos = 0; /* Current position */
- char *sqlPtr;
- int consumed;
- int startDT = 0;
-
/*
* The following is a mini state machine to assess the end of an SQL
* statement. It really only needs to parse good SQL, or at least
* that's the theory... End-of-statement is assumed to be an unquoted,
- * un commented semi-colon.
- */
-
- /*
- * fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data);
+ * un-commented semi-colon that's not within any parentheses.
+ *
+ * Note: the input can be split into bufferloads at arbitrary boundaries.
+ * Therefore all state must be kept in AH->sqlparse, not in local
+ * variables of this routine. We assume that AH->sqlparse was
+ * filled with zeroes when created.
*/
-
- for (pos = 0; pos < (eos - qry); pos++)
+ for (; qry < eos; qry++)
{
- appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
- /* fprintf(stderr, " %c",qry[pos]); */
-
- /* Loop until character consumed */
- do
+ switch (AH->sqlparse.state)
{
- /*
- * If a character needs to be scanned in a different state,
- * consumed can be set to 0 to avoid advancing. Care must be
- * taken to ensure internal state is not damaged.
- */
- consumed = 1;
-
- switch (AH->sqlparse.state)
- {
-
- case SQL_SCAN: /* Default state == 0, set in _allocAH */
- if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
- {
- /*
- * We've got the end of a statement. Send It &
- * reset the buffer.
- */
-
- /*
- * fprintf(stderr, " sending: '%s'\n\n",
- * AH->sqlBuf->data);
- */
- ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query");
- resetPQExpBuffer(AH->sqlBuf);
- AH->sqlparse.lastChar = '\0';
-
- /*
- * Remove any following newlines - so that
- * embedded COPY commands don't get a starting
- * newline.
- */
- pos++;
- for (; pos < (eos - qry) && qry[pos] == '\n'; pos++);
-
- /* We've got our line, so exit */
- return qry + pos;
- }
- else
- {
- /*
- * Look for normal boring quote chars, or
- * dollar-quotes. We make the assumption that
- * $-quotes will not have an ident character
- * before them in all pg_dump output.
- */
- if (qry[pos] == '"'
- || qry[pos] == '\''
- || (qry[pos] == '$' && _isIdentChar(AH->sqlparse.lastChar) == 0)
- )
- {
- /* fprintf(stderr,"[startquote]\n"); */
- AH->sqlparse.state = SQL_IN_QUOTE;
- AH->sqlparse.quoteChar = qry[pos];
- AH->sqlparse.backSlash = 0;
- if (qry[pos] == '$')
- {
- /* override the state */
- AH->sqlparse.state = SQL_IN_DOLLARTAG;
- /* Used for checking first char of tag */
- startDT = 1;
- /* We store the tag for later comparison. */
- AH->sqlparse.tagBuf = createPQExpBuffer();
- /* Get leading $ */
- appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
- }
- }
- else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
- AH->sqlparse.state = SQL_IN_SQL_COMMENT;
- else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
- AH->sqlparse.state = SQL_IN_EXT_COMMENT;
- else if (qry[pos] == '(')
- AH->sqlparse.braceDepth++;
- else if (qry[pos] == ')')
- AH->sqlparse.braceDepth--;
-
- AH->sqlparse.lastChar = qry[pos];
- }
- break;
-
- case SQL_IN_DOLLARTAG:
-
+ case SQL_SCAN: /* Default state == 0, set in _allocAH */
+ if (*qry == ';' && AH->sqlparse.braceDepth == 0)
+ {
/*
- * Like a quote, we look for a closing char *but* we
- * only allow a very limited set of contained chars,
- * and no escape chars. If invalid chars are found, we
- * abort tag processing.
+ * We've found the end of a statement. Send it and
+ * reset the buffer.
*/
-
- if (qry[pos] == '$')
- {
- /* fprintf(stderr,"[endquote]\n"); */
- /* Get trailing $ */
- appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
- AH->sqlparse.state = SQL_IN_DOLLARQUOTE;
- }
- else
- {
- if (_isDQChar(qry[pos], startDT))
- {
- /* Valid, so add */
- appendPQExpBufferChar(AH->sqlparse.tagBuf, qry[pos]);
- }
- else
- {
- /*
- * Jump back to 'scan' state, we're not really
- * in a tag, and valid tag chars do not
- * include the various chars we look for in
- * this state machine, so it's safe to just
- * jump from this state back to SCAN. We set
- * consumed = 0 so that this char gets
- * rescanned in new state.
- */
- destroyPQExpBuffer(AH->sqlparse.tagBuf);
- AH->sqlparse.state = SQL_SCAN;
- consumed = 0;
- }
- }
- startDT = 0;
- break;
-
-
- case SQL_IN_DOLLARQUOTE:
+ appendPQExpBufferChar(AH->sqlBuf, ';'); /* inessential */
+ ExecuteSqlCommand(AH, AH->sqlBuf,
+ "could not execute query");
+ resetPQExpBuffer(AH->sqlBuf);
+ AH->sqlparse.lastChar = '\0';
/*
- * Comparing the entire string backwards each time is
- * NOT efficient, but dollar quotes in pg_dump are
- * small and the code is a lot simpler.
+ * Remove any following newlines - so that
+ * embedded COPY commands don't get a starting newline.
*/
- sqlPtr = AH->sqlBuf->data + AH->sqlBuf->len - AH->sqlparse.tagBuf->len;
-
- if (strncmp(AH->sqlparse.tagBuf->data, sqlPtr, AH->sqlparse.tagBuf->len) == 0)
- {
- /* End of $-quote */
- AH->sqlparse.state = SQL_SCAN;
- destroyPQExpBuffer(AH->sqlparse.tagBuf);
- }
- break;
-
- case SQL_IN_SQL_COMMENT:
- if (qry[pos] == '\n')
- AH->sqlparse.state = SQL_SCAN;
- break;
-
- case SQL_IN_EXT_COMMENT:
- if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
- AH->sqlparse.state = SQL_SCAN;
- break;
-
- case SQL_IN_QUOTE:
-
- if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
- {
- /* fprintf(stderr,"[endquote]\n"); */
- AH->sqlparse.state = SQL_SCAN;
- }
+ qry++;
+ while (qry < eos && *qry == '\n')
+ qry++;
+
+ /* We've finished one line, so exit */
+ return qry;
+ }
+ else if (*qry == '\'')
+ {
+ if (AH->sqlparse.lastChar == 'E')
+ AH->sqlparse.state = SQL_IN_E_QUOTE;
else
- {
- if (qry[pos] == '\\')
- {
- if (AH->sqlparse.lastChar == '\\')
- AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
- else
- AH->sqlparse.backSlash = 1;
- }
- else
- AH->sqlparse.backSlash = 0;
- }
- break;
-
- }
-
- } while (consumed == 0);
+ AH->sqlparse.state = SQL_IN_SINGLE_QUOTE;
+ AH->sqlparse.backSlash = false;
+ }
+ else if (*qry == '"')
+ {
+ AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE;
+ }
+ /*
+ * Look for dollar-quotes. We make the assumption that
+ * $-quotes will not have an ident character just
+ * before them in pg_dump output. XXX is this
+ * good enough?
+ */
+ else if (*qry == '$' && !_isIdentChar(AH->sqlparse.lastChar))
+ {
+ AH->sqlparse.state = SQL_IN_DOLLAR_TAG;
+ /* initialize separate buffer with possible tag */
+ if (AH->sqlparse.tagBuf == NULL)
+ AH->sqlparse.tagBuf = createPQExpBuffer();
+ else
+ resetPQExpBuffer(AH->sqlparse.tagBuf);
+ appendPQExpBufferChar(AH->sqlparse.tagBuf, *qry);
+ }
+ else if (*qry == '-' && AH->sqlparse.lastChar == '-')
+ AH->sqlparse.state = SQL_IN_SQL_COMMENT;
+ else if (*qry == '*' && AH->sqlparse.lastChar == '/')
+ AH->sqlparse.state = SQL_IN_EXT_COMMENT;
+ else if (*qry == '(')
+ AH->sqlparse.braceDepth++;
+ else if (*qry == ')')
+ AH->sqlparse.braceDepth--;
+ break;
+
+ case SQL_IN_SQL_COMMENT:
+ if (*qry == '\n')
+ AH->sqlparse.state = SQL_SCAN;
+ break;
+
+ case SQL_IN_EXT_COMMENT:
+ /*
+ * This isn't fully correct, because we don't account for
+ * nested slash-stars, but pg_dump never emits such.
+ */
+ if (AH->sqlparse.lastChar == '*' && *qry == '/')
+ AH->sqlparse.state = SQL_SCAN;
+ break;
+
+ case SQL_IN_SINGLE_QUOTE:
+ /* We needn't handle '' specially */
+ if (*qry == '\'' && !AH->sqlparse.backSlash)
+ AH->sqlparse.state = SQL_SCAN;
+ else if (*qry == '\\')
+ AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
+ else
+ AH->sqlparse.backSlash = false;
+ break;
+
+ case SQL_IN_E_QUOTE:
+ /*
+ * Eventually we will need to handle '' specially, because
+ * after E'...''... we should still be in E_QUOTE state.
+ *
+ * XXX problem: how do we tell whether the dump was made
+ * by a version that thinks backslashes aren't special
+ * in non-E literals??
+ */
+ if (*qry == '\'' && !AH->sqlparse.backSlash)
+ AH->sqlparse.state = SQL_SCAN;
+ else if (*qry == '\\')
+ AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
+ else
+ AH->sqlparse.backSlash = false;
+ break;
+
+ case SQL_IN_DOUBLE_QUOTE:
+ /* We needn't handle "" specially */
+ if (*qry == '"')
+ AH->sqlparse.state = SQL_SCAN;
+ break;
+
+ case SQL_IN_DOLLAR_TAG:
+ if (*qry == '$')
+ {
+ /* Do not add the closing $ to tagBuf */
+ AH->sqlparse.state = SQL_IN_DOLLAR_QUOTE;
+ AH->sqlparse.minTagEndPos = AH->sqlBuf->len + AH->sqlparse.tagBuf->len + 1;
+ }
+ else if (_isDQChar(*qry, (AH->sqlparse.tagBuf->len == 1)))
+ {
+ /* Valid, so add to tag */
+ appendPQExpBufferChar(AH->sqlparse.tagBuf, *qry);
+ }
+ else
+ {
+ /*
+ * Ooops, we're not really in a dollar-tag. Valid tag
+ * chars do not include the various chars we look for
+ * in this state machine, so it's safe to just jump
+ * from this state back to SCAN. We have to back up
+ * the qry pointer so that the current character gets
+ * rescanned in SCAN state; and then "continue" so that
+ * the bottom-of-loop actions aren't done yet.
+ */
+ AH->sqlparse.state = SQL_SCAN;
+ qry--;
+ continue;
+ }
+ break;
+
+ case SQL_IN_DOLLAR_QUOTE:
+ /*
+ * If we are at a $, see whether what precedes it matches
+ * tagBuf. (Remember that the trailing $ of the tag was
+ * not added to tagBuf.) However, don't compare until we
+ * have enough data to be a possible match --- this is
+ * needed to avoid false match on '$a$a$...'
+ */
+ if (*qry == '$' &&
+ AH->sqlBuf->len >= AH->sqlparse.minTagEndPos &&
+ strcmp(AH->sqlparse.tagBuf->data,
+ AH->sqlBuf->data + AH->sqlBuf->len - AH->sqlparse.tagBuf->len) == 0)
+ AH->sqlparse.state = SQL_SCAN;
+ break;
+ }
- AH->sqlparse.lastChar = qry[pos];
- /* fprintf(stderr, "\n"); */
+ appendPQExpBufferChar(AH->sqlBuf, *qry);
+ AH->sqlparse.lastChar = *qry;
}
/*
- * If we get here, we've processed entire string with no complete SQL
+ * If we get here, we've processed entire bufferload with no complete SQL
* stmt
*/
return eos;
destroyPQExpBuffer(qry);
}
-static int
+static bool
_isIdentChar(unsigned char c)
{
if ((c >= 'a' && c <= 'z')
|| (c >= (unsigned char) '\200') /* no need to check <=
* \377 */
)
- return 1;
+ return true;
else
- return 0;
+ return false;
}
-static int
-_isDQChar(unsigned char c, int atStart)
+static bool
+_isDQChar(unsigned char c, bool atStart)
{
if ((c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c == '_')
- || (atStart == 0 && c >= '0' && c <= '9')
+ || (!atStart && c >= '0' && c <= '9')
|| (c >= (unsigned char) '\200') /* no need to check <=
* \377 */
)
- return 1;
+ return true;
else
- return 0;
+ return false;
}