Ejemplo n.º 1
0
/*
 * ScanECPGKeywordLookup - see if a given word is a keyword
 *
 * Returns a pointer to the ScanKeyword table entry, or NULL if no match.
 * Keywords are matched using the same case-folding rules as in the backend.
 */
const ScanKeyword *
ScanECPGKeywordLookup(const char *text)
{
	const ScanKeyword *res;

	/* First check SQL symbols defined by the backend. */
	res = ScanKeywordLookup(text, SQLScanKeywords, NumSQLScanKeywords);
	if (res)
		return res;

	/* Try ECPG-specific keywords. */
	res = ScanKeywordLookup(text, ECPGScanKeywords, lengthof(ECPGScanKeywords));
	if (res)
		return res;

	return NULL;
}
Ejemplo n.º 2
0
/* check if ident is keyword that needs quoting */
static bool is_keyword(const char *ident)
{
	const ScanKeyword *kw;

	/* do the lookup */
#if PG_VERSION_NUM >= 80500
	kw = ScanKeywordLookup(ident, ScanKeywords, NumScanKeywords);
#else
	kw = ScanKeywordLookup(ident);
#endif

	/* unreserved? */
#if PG_VERSION_NUM >= 80300
	if (kw && kw->category == UNRESERVED_KEYWORD)
		return false;
#endif

	/* found anything? */
	return kw != NULL;
}
Ejemplo n.º 3
0
static bool needsQuoting(wxString &value, bool forTypes)
{
	// Is it a number?
	if (value.IsNumber())
		return true;
	else
	{
		// certain types should not be quoted even though it contains a space. Evilness.
		wxString valNoArray;
		if (forTypes && value.Right(2) == wxT("[]"))
			valNoArray = value.Mid(0, value.Len() - 2);
		else
			valNoArray = value;

		if (forTypes &&
		        (!valNoArray.CmpNoCase(wxT("character varying")) ||
		         !valNoArray.CmpNoCase(wxT("\"char\"")) ||
		         !valNoArray.CmpNoCase(wxT("bit varying")) ||
		         !valNoArray.CmpNoCase(wxT("double precision")) ||
		         !valNoArray.CmpNoCase(wxT("timestamp without time zone")) ||
		         !valNoArray.CmpNoCase(wxT("timestamp with time zone")) ||
		         !valNoArray.CmpNoCase(wxT("time without time zone")) ||
		         !valNoArray.CmpNoCase(wxT("time with time zone")) ||
		         !valNoArray.CmpNoCase(wxT("\"trigger\"")) ||
		         !valNoArray.CmpNoCase(wxT("\"unknown\""))))
			return false;

		int pos = 0;
		while (pos < (int)valNoArray.length())
		{
			wxChar c = valNoArray.GetChar(pos);
			if (!((pos > 0) && (c >= '0' && c <= '9')) &&
			        !(c >= 'a' && c  <= 'z') &&
			        !(c == '_'))
			{
				return true;
			}
			pos++;
		}
	}

	// is it a keyword?
	const ScanKeyword *sk = ScanKeywordLookup(value.ToAscii());
	if (!sk)
		return false;
	if (sk->category == UNRESERVED_KEYWORD)
		return false;
	if (forTypes && sk->category == COL_NAME_KEYWORD)
		return false;
	return true;
}
Ejemplo n.º 4
0
/*
 * This is the yylex routine called from the PL/pgSQL grammar.
 * It is a wrapper around the core lexer, with the ability to recognize
 * PL/pgSQL variables and return them as special T_DATUM tokens.  If a
 * word or compound word does not match any variable name, or if matching
 * is turned off by plpgsql_IdentifierLookup, it is returned as
 * T_WORD or T_CWORD respectively, or as an unreserved keyword if it
 * matches one of those.
 */
int
plpgsql_yylex(void)
{
	int			tok1;
	TokenAuxData aux1;
	const ScanKeyword *kw;

	tok1 = internal_yylex(&aux1);
	if (tok1 == IDENT || tok1 == PARAM)
	{
		int			tok2;
		TokenAuxData aux2;

		tok2 = internal_yylex(&aux2);
		if (tok2 == '.')
		{
			int			tok3;
			TokenAuxData aux3;

			tok3 = internal_yylex(&aux3);
			if (tok3 == IDENT)
			{
				int			tok4;
				TokenAuxData aux4;

				tok4 = internal_yylex(&aux4);
				if (tok4 == '.')
				{
					int			tok5;
					TokenAuxData aux5;

					tok5 = internal_yylex(&aux5);
					if (tok5 == IDENT)
					{
						if (plpgsql_parse_tripword(aux1.lval.str,
												   aux3.lval.str,
												   aux5.lval.str,
												   &aux1.lval.wdatum,
												   &aux1.lval.cword))
							tok1 = T_DATUM;
						else
							tok1 = T_CWORD;
					}
					else
					{
						/* not A.B.C, so just process A.B */
						push_back_token(tok5, &aux5);
						push_back_token(tok4, &aux4);
						if (plpgsql_parse_dblword(aux1.lval.str,
												  aux3.lval.str,
												  &aux1.lval.wdatum,
												  &aux1.lval.cword))
							tok1 = T_DATUM;
						else
							tok1 = T_CWORD;
					}
				}
				else
				{
					/* not A.B.C, so just process A.B */
					push_back_token(tok4, &aux4);
					if (plpgsql_parse_dblword(aux1.lval.str,
											  aux3.lval.str,
											  &aux1.lval.wdatum,
											  &aux1.lval.cword))
						tok1 = T_DATUM;
					else
						tok1 = T_CWORD;
				}
			}
			else
			{
				/* not A.B, so just process A */
				push_back_token(tok3, &aux3);
				push_back_token(tok2, &aux2);
				if (plpgsql_parse_word(aux1.lval.str,
									   core_yy.scanbuf + aux1.lloc,
									   &aux1.lval.wdatum,
									   &aux1.lval.word))
					tok1 = T_DATUM;
				else if (!aux1.lval.word.quoted &&
						 (kw = ScanKeywordLookup(aux1.lval.word.ident,
												 unreserved_keywords,
												 num_unreserved_keywords)))
				{
					aux1.lval.keyword = kw->name;
					tok1 = kw->value;
				}
				else
					tok1 = T_WORD;
			}
		}
		else
		{
			/* not A.B, so just process A */
			push_back_token(tok2, &aux2);

			/*
			 * If we are at start of statement, prefer unreserved keywords
			 * over variable names, unless the next token is assignment or
			 * '[', in which case prefer variable names.  (Note we need not
			 * consider '.' as the next token; that case was handled above,
			 * and we always prefer variable names in that case.)  If we are
			 * not at start of statement, always prefer variable names over
			 * unreserved keywords.
			 */
			if (AT_STMT_START(plpgsql_yytoken) &&
				!(tok2 == '=' || tok2 == COLON_EQUALS || tok2 == '['))
			{
				/* try for unreserved keyword, then for variable name */
				if (core_yy.scanbuf[aux1.lloc] != '"' &&
					(kw = ScanKeywordLookup(aux1.lval.str,
											unreserved_keywords,
											num_unreserved_keywords)))
				{
					aux1.lval.keyword = kw->name;
					tok1 = kw->value;
				}
				else if (plpgsql_parse_word(aux1.lval.str,
											core_yy.scanbuf + aux1.lloc,
											&aux1.lval.wdatum,
											&aux1.lval.word))
					tok1 = T_DATUM;
				else
					tok1 = T_WORD;
			}
			else
			{
				/* try for variable name, then for unreserved keyword */
				if (plpgsql_parse_word(aux1.lval.str,
									   core_yy.scanbuf + aux1.lloc,
									   &aux1.lval.wdatum,
									   &aux1.lval.word))
					tok1 = T_DATUM;
				else if (!aux1.lval.word.quoted &&
						 (kw = ScanKeywordLookup(aux1.lval.word.ident,
												 unreserved_keywords,
												 num_unreserved_keywords)))
				{
					aux1.lval.keyword = kw->name;
					tok1 = kw->value;
				}
				else
					tok1 = T_WORD;
			}
		}
	}
	else
	{
		/*
		 * Not a potential plpgsql variable name, just return the data.
		 *
		 * Note that we also come through here if the grammar pushed back a
		 * T_DATUM, T_CWORD, T_WORD, or unreserved-keyword token returned by a
		 * previous lookup cycle; thus, pushbacks do not incur extra lookup
		 * work, since we'll never do the above code twice for the same token.
		 * This property also makes it safe to rely on the old value of
		 * plpgsql_yytoken in the is-this-start-of-statement test above.
		 */
	}

	plpgsql_yylval = aux1.lval;
	plpgsql_yylloc = aux1.lloc;
	plpgsql_yyleng = aux1.leng;
	plpgsql_yytoken = tok1;
	return tok1;
}
Ejemplo n.º 5
0
/*
 *	Quotes input string if it's not a legitimate SQL identifier as-is.
 *
 *	Note that the returned string must be used before calling fmtId again,
 *	since we re-use the same return buffer each time.  Non-reentrant but
 *	reduces memory leakage. (On Windows the memory leakage will be one buffer
 *	per thread, which is at least better than one per call).
 */
const char *
fmtId(const char *rawid)
{
	/*
	 * The Tls code goes awry if we use a static var, so we provide for both
	 * static and auto, and omit any use of the static var when using Tls.
	 */
	static PQExpBuffer s_id_return = NULL;
	PQExpBuffer id_return;

	const char *cp;
	bool		need_quotes = false;

#ifdef WIN32
	if (parallel_init_done)
		id_return = (PQExpBuffer) TlsGetValue(tls_index);		/* 0 when not set */
	else
		id_return = s_id_return;
#else
	id_return = s_id_return;
#endif

	if (id_return)				/* first time through? */
	{
		/* same buffer, just wipe contents */
		resetPQExpBuffer(id_return);
	}
	else
	{
		/* new buffer */
		id_return = createPQExpBuffer();
#ifdef WIN32
		if (parallel_init_done)
			TlsSetValue(tls_index, id_return);
		else
			s_id_return = id_return;
#else
		s_id_return = id_return;
#endif

	}

	/*
	 * These checks need to match the identifier production in scan.l. Don't
	 * use islower() etc.
	 */
	if (quote_all_identifiers)
		need_quotes = true;
	/* slightly different rules for first character */
	else if (!((rawid[0] >= 'a' && rawid[0] <= 'z') || rawid[0] == '_'))
		need_quotes = true;
	else
	{
		/* otherwise check the entire string */
		for (cp = rawid; *cp; cp++)
		{
			if (!((*cp >= 'a' && *cp <= 'z')
				  || (*cp >= '0' && *cp <= '9')
				  || (*cp == '_')))
			{
				need_quotes = true;
				break;
			}
		}
	}

	if (!need_quotes)
	{
		/*
		 * Check for keyword.  We quote keywords except for unreserved ones.
		 * (In some cases we could avoid quoting a col_name or type_func_name
		 * keyword, but it seems much harder than it's worth to tell that.)
		 *
		 * Note: ScanKeywordLookup() does case-insensitive comparison, but
		 * that's fine, since we already know we have all-lower-case.
		 */
		const ScanKeyword *keyword = ScanKeywordLookup(rawid,
													   ScanKeywords,
													   NumScanKeywords);

		if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
			need_quotes = true;
	}

	if (!need_quotes)
	{
		/* no quoting needed */
		appendPQExpBufferStr(id_return, rawid);
	}
	else
	{
		appendPQExpBufferChar(id_return, '\"');
		for (cp = rawid; *cp; cp++)
		{
			/*
			 * Did we find a double-quote in the string? Then make this a
			 * double double-quote per SQL99. Before, we put in a
			 * backslash/double-quote pair. - thomas 2000-08-05
			 */
			if (*cp == '\"')
				appendPQExpBufferChar(id_return, '\"');
			appendPQExpBufferChar(id_return, *cp);
		}
		appendPQExpBufferChar(id_return, '\"');
	}

	return id_return->data;
}
Ejemplo n.º 6
0
/*
 *	Quotes input string if it's not a legitimate SQL identifier as-is.
 *
 *	Note that the returned string must be used before calling fmtId again,
 *	since we re-use the same return buffer each time.
 */
const char *
fmtId(const char *rawid)
{
	PQExpBuffer id_return = getLocalPQExpBuffer();

	const char *cp;
	bool		need_quotes = false;

	/*
	 * These checks need to match the identifier production in scan.l. Don't
	 * use islower() etc.
	 */
	if (quote_all_identifiers)
		need_quotes = true;
	/* slightly different rules for first character */
	else if (!((rawid[0] >= 'a' && rawid[0] <= 'z') || rawid[0] == '_'))
		need_quotes = true;
	else
	{
		/* otherwise check the entire string */
		for (cp = rawid; *cp; cp++)
		{
			if (!((*cp >= 'a' && *cp <= 'z')
				  || (*cp >= '0' && *cp <= '9')
				  || (*cp == '_')))
			{
				need_quotes = true;
				break;
			}
		}
	}

	if (!need_quotes)
	{
		/*
		 * Check for keyword.  We quote keywords except for unreserved ones.
		 * (In some cases we could avoid quoting a col_name or type_func_name
		 * keyword, but it seems much harder than it's worth to tell that.)
		 *
		 * Note: ScanKeywordLookup() does case-insensitive comparison, but
		 * that's fine, since we already know we have all-lower-case.
		 */
		const ScanKeyword *keyword = ScanKeywordLookup(rawid,
													   FEScanKeywords,
													   NumFEScanKeywords);

		if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
			need_quotes = true;
	}

	if (!need_quotes)
	{
		/* no quoting needed */
		appendPQExpBufferStr(id_return, rawid);
	}
	else
	{
		appendPQExpBufferChar(id_return, '\"');
		for (cp = rawid; *cp; cp++)
		{
			/*
			 * Did we find a double-quote in the string? Then make this a
			 * double double-quote per SQL99. Before, we put in a
			 * backslash/double-quote pair. - thomas 2000-08-05
			 */
			if (*cp == '\"')
				appendPQExpBufferChar(id_return, '\"');
			appendPQExpBufferChar(id_return, *cp);
		}
		appendPQExpBufferChar(id_return, '\"');
	}

	return id_return->data;
}
Ejemplo n.º 7
0
/*
 * slon_quote_identifier - Quote an identifier only if needed
 */
static int pgq_quote_ident(char *dst, const uint8 *src, int srclen)
{
    /*
     * Can avoid quoting if ident starts with a lowercase letter or
     * underscore and contains only lowercase letters, digits, and
     * underscores, *and* is not any SQL keyword.  Otherwise, supply
     * quotes.
     */
    int nquotes = 0;
    bool safe;
    const char *ptr;
    char *optr;
    char ident[NAMEDATALEN + 1];

    /* expect idents be not bigger than NAMEDATALEN */
    if (srclen > NAMEDATALEN)
        srclen = NAMEDATALEN;
    memcpy(ident, src, srclen);
    ident[srclen] = 0;

    /*
     * would like to use <ctype.h> macros here, but they might yield
     * unwanted locale-specific results...
     */
    safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');

    for (ptr = ident; *ptr; ptr++) {
        char ch = *ptr;

        if ((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '_'))
            continue;	/* okay */

        safe = false;
        if (ch == '"')
            nquotes++;
    }

    if (safe) {
        /*
         * Check for keyword.  This test is overly strong, since many of
         * the "keywords" known to the parser are usable as column names,
         * but the parser doesn't provide any easy way to test for whether
         * an identifier is safe or not... so be safe not sorry.
         *
         * Note: ScanKeywordLookup() does case-insensitive comparison, but
         * that's fine, since we already know we have all-lower-case.
         */
        if (ScanKeywordLookup(ident) != NULL)
            safe = false;
    }

    optr = dst;
    if (!safe)
        *optr++ = '"';

    for (ptr = ident; *ptr; ptr++) {
        char ch = *ptr;

        if (ch == '"')
            *optr++ = '"';
        *optr++ = ch;
    }
    if (!safe)
        *optr++ = '"';

    return optr - dst;
}
Ejemplo n.º 8
0
/*
 *	Quotes input string if it's not a legitimate SQL identifier as-is.
 *
 *	Note that the returned string must be used before calling fmtId again,
 *	since we re-use the same return buffer each time.  Non-reentrant but
 *	avoids memory leakage.
 */
const char *
fmtId(const char *rawid)
{
	static PQExpBuffer id_return = NULL;
	const char *cp;
	bool		need_quotes = false;

	if (id_return)				/* first time through? */
		resetPQExpBuffer(id_return);
	else
		id_return = createPQExpBuffer();

	/*
	 * These checks need to match the identifier production in scan.l. Don't
	 * use islower() etc.
	 */

	if (ScanKeywordLookup(rawid))
		need_quotes = true;
	/* slightly different rules for first character */
	else if (!((rawid[0] >= 'a' && rawid[0] <= 'z') || rawid[0] == '_'))
		need_quotes = true;
	else
	{
		/* otherwise check the entire string */
		for (cp = rawid; *cp; cp++)
		{
			if (!((*cp >= 'a' && *cp <= 'z')
				  || (*cp >= '0' && *cp <= '9')
				  || (*cp == '_')))
			{
				need_quotes = true;
				break;
			}
		}
	}

	if (!need_quotes)
	{
		/* no quoting needed */
		appendPQExpBufferStr(id_return, rawid);
	}
	else
	{
		appendPQExpBufferChar(id_return, '\"');
		for (cp = rawid; *cp; cp++)
		{
			/*
			 * Did we find a double-quote in the string? Then make this a
			 * double double-quote per SQL99. Before, we put in a
			 * backslash/double-quote pair. - thomas 2000-08-05
			 */
			if (*cp == '\"')
				appendPQExpBufferChar(id_return, '\"');
			appendPQExpBufferChar(id_return, *cp);
		}
		appendPQExpBufferChar(id_return, '\"');
	}

	return id_return->data;
}
Ejemplo n.º 9
0
/*
 * This is the yylex routine called from the PL/pgSQL grammar.
 * It is a wrapper around the core lexer, with the ability to recognize
 * PL/pgSQL variables and return them as special T_DATUM tokens.  If a
 * word or compound word does not match any variable name, or if matching
 * is turned off by plpgsql_IdentifierLookup, it is returned as
 * T_WORD or T_CWORD respectively, or as an unreserved keyword if it
 * matches one of those.
 */
int
plpgsql_yylex(void)
{
	int			tok1;
	TokenAuxData aux1;
	const ScanKeyword *kw;

	tok1 = internal_yylex(&aux1);
	if (tok1 == IDENT || tok1 == PARAM)
	{
		int			tok2;
		TokenAuxData aux2;

		tok2 = internal_yylex(&aux2);
		if (tok2 == '.')
		{
			int			tok3;
			TokenAuxData aux3;

			tok3 = internal_yylex(&aux3);
			if (tok3 == IDENT)
			{
				int			tok4;
				TokenAuxData aux4;

				tok4 = internal_yylex(&aux4);
				if (tok4 == '.')
				{
					int			tok5;
					TokenAuxData aux5;

					tok5 = internal_yylex(&aux5);
					if (tok5 == IDENT)
					{
						if (plpgsql_parse_tripword(aux1.lval.str,
												   aux3.lval.str,
												   aux5.lval.str,
												   &aux1.lval.wdatum,
												   &aux1.lval.cword))
							tok1 = T_DATUM;
						else
							tok1 = T_CWORD;
					}
					else
					{
						/* not A.B.C, so just process A.B */
						push_back_token(tok5, &aux5);
						push_back_token(tok4, &aux4);
						if (plpgsql_parse_dblword(aux1.lval.str,
												  aux3.lval.str,
												  &aux1.lval.wdatum,
												  &aux1.lval.cword))
							tok1 = T_DATUM;
						else
							tok1 = T_CWORD;
					}
				}
				else
				{
					/* not A.B.C, so just process A.B */
					push_back_token(tok4, &aux4);
					if (plpgsql_parse_dblword(aux1.lval.str,
											  aux3.lval.str,
											  &aux1.lval.wdatum,
											  &aux1.lval.cword))
						tok1 = T_DATUM;
					else
						tok1 = T_CWORD;
				}
			}
			else
			{
				/* not A.B, so just process A */
				push_back_token(tok3, &aux3);
				push_back_token(tok2, &aux2);
				if (plpgsql_parse_word(aux1.lval.str,
									   core_yy.scanbuf + aux1.lloc,
									   &aux1.lval.wdatum,
									   &aux1.lval.word))
					tok1 = T_DATUM;
				else if (!aux1.lval.word.quoted &&
						 (kw = ScanKeywordLookup(aux1.lval.word.ident,
												 unreserved_keywords,
												 num_unreserved_keywords)))
				{
					aux1.lval.keyword = kw->name;
					tok1 = kw->value;
				}
				else
					tok1 = T_WORD;
			}
		}
		else
		{
			/* not A.B, so just process A */
			push_back_token(tok2, &aux2);
			if (plpgsql_parse_word(aux1.lval.str,
								   core_yy.scanbuf + aux1.lloc,
								   &aux1.lval.wdatum,
								   &aux1.lval.word))
				tok1 = T_DATUM;
			else if (!aux1.lval.word.quoted &&
					 (kw = ScanKeywordLookup(aux1.lval.word.ident,
											 unreserved_keywords,
											 num_unreserved_keywords)))
			{
				aux1.lval.keyword = kw->name;
				tok1 = kw->value;
			}
			else
				tok1 = T_WORD;
		}
	}
	else
	{
		/* Not a potential plpgsql variable name, just return the data */
	}

	plpgsql_yylval = aux1.lval;
	plpgsql_yylloc = aux1.lloc;
	plpgsql_yyleng = aux1.leng;
	return tok1;
}
Ejemplo n.º 10
0
/*
 * Quote an identifier iif necessary. For quoting, consider the SQL rules and
 * also the list of PostgreSQL keywords. Those keywords come from PostgreSQL
 * version that pgquarrel was compiled with. Ensure that pgquarrel is compiled
 * with the latest PostgreSQL version to ensure that we don't have problems
 * with strings that become keywords. Returns an allocated string.
 */
char *
formatObjectIdentifier(char *s)
{
	bool	need_quotes = false;

	char	*p;
	char	*ret;
	int		i = 0;

	/* different rule for first character */
	if (!((s[0] >= 'a' && s[0] <= 'z') || s[0] == '_'))
		need_quotes = true;
	else
	{
		/* otherwise check the entire string */
		for (p = s; *p; p++)
		{
			if (!((*p >= 'a' && *p <= 'z')
					|| (*p >= '0' && *p <= '9')
					|| (*p == '_')))
			{
				need_quotes = true;
				break;
			}
		}
	}

	if (!need_quotes)
	{
		/* ScanKeywordLookup() was copied from postgres source code */
		const ScanKeyword *keyword = ScanKeywordLookup(s,
									 PQLScanKeywords,
									 NumPQLScanKeywords);

		if (keyword != NULL && keyword->category != UNRESERVED_KEYWORD)
			need_quotes = true;
	}

	if (!need_quotes)
	{
		/* no quotes needed */
		ret = strdup(s);
	}
	else
	{
		/*
		 * Maximum length for identifiers is NAMEDATALEN (64 bytes by default
		 * including trailing zero byte).
		 * */
		ret = malloc(NAMEDATALEN * sizeof(char));
		if (ret == NULL)
		{
			logError("could not allocate memory");
			exit(EXIT_FAILURE);
		}

		ret[i++] = '\"';
		for (p = s; *p; p++)
		{
			/* per SQL99, if quote is found, add another quote */
			if (*p == '\"')
				ret[i++] = '\"';
			ret[i++] = *p;
		}
		ret[i++] = '\"';
		ret[i] = '\0';
	}

	return ret;
}
Ejemplo n.º 11
0
/*
 * slon_quote_identifier					 - Quote an identifier only if needed
 *
 * When quotes are needed, we palloc the required space; slightly
 * space-wasteful but well worth it for notational simplicity.
 *
 * Version: pgsql/src/backend/utils/adt/ruleutils.c,v 1.188 2005/01/13 17:19:10
 */
static const char *
slon_quote_identifier(const char *ident)
{
	/*
	 * Can avoid quoting if ident starts with a lowercase letter or underscore
	 * and contains only lowercase letters, digits, and underscores, *and* is
	 * not any SQL keyword.  Otherwise, supply quotes.
	 */
	int			nquotes = 0;
	bool		safe;
	const char *ptr;
	char	   *result;
	char	   *optr;

	/*
	 * would like to use <ctype.h> macros here, but they might yield unwanted
	 * locale-specific results...
	 */
	safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');

	for (ptr = ident; *ptr; ptr++)
	{
		char		ch = *ptr;

		if ((ch >= 'a' && ch <= 'z') ||
			(ch >= '0' && ch <= '9') ||
			(ch == '_'))
		{
			/* okay */
		}
		else
		{
			safe = false;
			if (ch == '"')
				nquotes++;
		}
	}

	if (safe)
	{
		/*
		 * Check for keyword.  This test is overly strong, since many of the
		 * "keywords" known to the parser are usable as column names, but the
		 * parser doesn't provide any easy way to test for whether an
		 * identifier is safe or not... so be safe not sorry.
		 *
		 * Note: ScanKeywordLookup() does case-insensitive comparison, but
		 * that's fine, since we already know we have all-lower-case.
		 */

#ifdef SCANKEYWORDLOOKUP_1
		if (ScanKeywordLookup(ident) != NULL)
#endif
#ifdef SCANKEYWORDLOOKUP_3		   
			if (ScanKeywordLookup(ident,ScanKeywords,NumScanKeywords) != NULL)
#endif
			safe = false;
	}

	if (safe)
		return ident;			/* no change needed */

	result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);

	optr = result;
	*optr++ = '"';
	for (ptr = ident; *ptr; ptr++)
	{
		char		ch = *ptr;

		if (ch == '"')
			*optr++ = '"';
		*optr++ = ch;
	}
	*optr++ = '"';
	*optr = '\0';

	return result;
}