mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Add new SQL function, format(text).
Currently, three conversion format specifiers are supported: %s for a string, %L for an SQL literal, and %I for an SQL identifier. The latter two are deliberately designed not to overlap with what sprintf() already supports, in case we want to add more of sprintf()'s functionality here later. Patch by Pavel Stehule, heavily revised by me. Reviewed by Jeff Janes and, in earlier versions, by Itagaki Takahiro and Tom Lane.
This commit is contained in:
@ -15,6 +15,7 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "access/tuptoaster.h"
|
||||
#include "catalog/pg_type.h"
|
||||
@ -74,6 +75,8 @@ static bytea *bytea_substring(Datum str,
|
||||
bool length_not_specified);
|
||||
static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
|
||||
static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
|
||||
void text_format_string_conversion(StringInfo buf, char conversion,
|
||||
Oid typid, Datum value, bool isNull);
|
||||
|
||||
static Datum text_to_array_internal(PG_FUNCTION_ARGS);
|
||||
static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
|
||||
@ -3702,3 +3705,195 @@ text_reverse(PG_FUNCTION_ARGS)
|
||||
|
||||
PG_RETURN_TEXT_P(result);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a formated string
|
||||
*/
|
||||
Datum
|
||||
text_format(PG_FUNCTION_ARGS)
|
||||
{
|
||||
text *fmt;
|
||||
StringInfoData str;
|
||||
const char *cp;
|
||||
const char *start_ptr;
|
||||
const char *end_ptr;
|
||||
text *result;
|
||||
int arg = 0;
|
||||
|
||||
/* When format string is null, returns null */
|
||||
if (PG_ARGISNULL(0))
|
||||
PG_RETURN_NULL();
|
||||
|
||||
/* Setup for main loop. */
|
||||
fmt = PG_GETARG_TEXT_PP(0);
|
||||
start_ptr = VARDATA_ANY(fmt);
|
||||
end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt);
|
||||
initStringInfo(&str);
|
||||
|
||||
/* Scan format string, looking for conversion specifiers. */
|
||||
for (cp = start_ptr; cp < end_ptr; cp++)
|
||||
{
|
||||
Datum value;
|
||||
bool isNull;
|
||||
Oid typid;
|
||||
|
||||
/*
|
||||
* If it's not the start of a conversion specifier, just copy it to
|
||||
* the output buffer.
|
||||
*/
|
||||
if (*cp != '%')
|
||||
{
|
||||
appendStringInfoCharMacro(&str, *cp);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Did we run off the end of the string? */
|
||||
if (++cp >= end_ptr)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("unterminated conversion specifier")));
|
||||
|
||||
/* Easy case: %% outputs a single % */
|
||||
if (*cp == '%')
|
||||
{
|
||||
appendStringInfoCharMacro(&str, *cp);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the user hasn't specified an argument position, we just advance
|
||||
* to the next one. If they have, we must parse it.
|
||||
*/
|
||||
if (*cp < '0' || *cp > '9')
|
||||
++arg;
|
||||
else
|
||||
{
|
||||
bool unterminated = false;
|
||||
|
||||
/* Parse digit string. */
|
||||
arg = 0;
|
||||
do {
|
||||
/* Treat overflowing arg position as unterminated. */
|
||||
if (arg > INT_MAX / 10)
|
||||
break;
|
||||
arg = arg * 10 + (*cp - '0');
|
||||
++cp;
|
||||
} while (cp < end_ptr && *cp >= '0' && *cp <= '9');
|
||||
|
||||
/*
|
||||
* If we ran off the end, or if there's not a $ next, or if the $
|
||||
* is the last character, the conversion specifier is improperly
|
||||
* terminated.
|
||||
*/
|
||||
if (cp == end_ptr || *cp != '$')
|
||||
unterminated = true;
|
||||
else
|
||||
{
|
||||
++cp;
|
||||
if (cp == end_ptr)
|
||||
unterminated = true;
|
||||
}
|
||||
if (unterminated)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("unterminated conversion specifier")));
|
||||
|
||||
/* There's no argument 0. */
|
||||
if (arg == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
|
||||
}
|
||||
|
||||
/* Not enough arguments? Deduct 1 to avoid counting format string. */
|
||||
if (arg > PG_NARGS() - 1)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("too few arguments for format conversion")));
|
||||
|
||||
/*
|
||||
* At this point, we should see the main conversion specifier.
|
||||
* Whether or not an argument position was present, it's known
|
||||
* that at least one character remains in the string at this point.
|
||||
*/
|
||||
value = PG_GETARG_DATUM(arg);
|
||||
isNull = PG_ARGISNULL(arg);
|
||||
typid = get_fn_expr_argtype(fcinfo->flinfo, arg);
|
||||
|
||||
switch (*cp)
|
||||
{
|
||||
case 's':
|
||||
case 'I':
|
||||
case 'L':
|
||||
text_format_string_conversion(&str, *cp, typid, value, isNull);
|
||||
break;
|
||||
default:
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("unrecognized conversion specifier: %c",
|
||||
*cp)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate results. */
|
||||
result = cstring_to_text_with_len(str.data, str.len);
|
||||
pfree(str.data);
|
||||
|
||||
PG_RETURN_TEXT_P(result);
|
||||
}
|
||||
|
||||
/* Format a %s, %I, or %L conversion. */
|
||||
void
|
||||
text_format_string_conversion(StringInfo buf, char conversion,
|
||||
Oid typid, Datum value, bool isNull)
|
||||
{
|
||||
Oid typOutput;
|
||||
bool typIsVarlena;
|
||||
char *str;
|
||||
|
||||
/* Handle NULL arguments before trying to stringify the value. */
|
||||
if (isNull)
|
||||
{
|
||||
if (conversion == 'L')
|
||||
appendStringInfoString(buf, "NULL");
|
||||
else if (conversion == 'I')
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
|
||||
errmsg("NULL cannot be escaped as an SQL identifier")));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Stringify. */
|
||||
getTypeOutputInfo(typid, &typOutput, &typIsVarlena);
|
||||
str = OidOutputFunctionCall(typOutput, value);
|
||||
|
||||
/* Escape. */
|
||||
if (conversion == 'I')
|
||||
{
|
||||
/* quote_identifier may or may not allocate a new string. */
|
||||
appendStringInfoString(buf, quote_identifier(str));
|
||||
}
|
||||
else if (conversion == 'L')
|
||||
{
|
||||
char *qstr = quote_literal_cstr(str);
|
||||
appendStringInfoString(buf, qstr);
|
||||
/* quote_literal_cstr() always allocates a new string */
|
||||
pfree(qstr);
|
||||
}
|
||||
else
|
||||
appendStringInfoString(buf, str);
|
||||
|
||||
/* Cleanup. */
|
||||
pfree(str);
|
||||
}
|
||||
|
||||
/*
|
||||
* text_format_nv - nonvariadic wrapper for text_format function.
|
||||
*
|
||||
* note: this wrapper is necessary to be sanity_checks test ok
|
||||
*/
|
||||
Datum
|
||||
text_format_nv(PG_FUNCTION_ARGS)
|
||||
{
|
||||
return text_format(fcinfo);
|
||||
}
|
||||
|
Reference in New Issue
Block a user