|
15 | 15 | #include "postgres.h"
|
16 | 16 |
|
17 | 17 | #include <ctype.h>
|
| 18 | +#include <limits.h> |
18 | 19 |
|
19 | 20 | #include "access/tuptoaster.h"
|
20 | 21 | #include "catalog/pg_type.h"
|
@@ -74,6 +75,8 @@ static bytea *bytea_substring(Datum str,
|
74 | 75 | bool length_not_specified);
|
75 | 76 | static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
|
76 | 77 | static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
|
| 78 | +void text_format_string_conversion(StringInfo buf, char conversion, |
| 79 | + Oid typid, Datum value, bool isNull); |
77 | 80 |
|
78 | 81 | static Datum text_to_array_internal(PG_FUNCTION_ARGS);
|
79 | 82 | static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
|
@@ -3702,3 +3705,195 @@ text_reverse(PG_FUNCTION_ARGS)
|
3702 | 3705 |
|
3703 | 3706 | PG_RETURN_TEXT_P(result);
|
3704 | 3707 | }
|
| 3708 | + |
| 3709 | +/* |
| 3710 | + * Returns a formated string |
| 3711 | + */ |
| 3712 | +Datum |
| 3713 | +text_format(PG_FUNCTION_ARGS) |
| 3714 | +{ |
| 3715 | + text *fmt; |
| 3716 | + StringInfoData str; |
| 3717 | + const char *cp; |
| 3718 | + const char *start_ptr; |
| 3719 | + const char *end_ptr; |
| 3720 | + text *result; |
| 3721 | + int arg = 0; |
| 3722 | + |
| 3723 | + /* When format string is null, returns null */ |
| 3724 | + if (PG_ARGISNULL(0)) |
| 3725 | + PG_RETURN_NULL(); |
| 3726 | + |
| 3727 | + /* Setup for main loop. */ |
| 3728 | + fmt = PG_GETARG_TEXT_PP(0); |
| 3729 | + start_ptr = VARDATA_ANY(fmt); |
| 3730 | + end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt); |
| 3731 | + initStringInfo(&str); |
| 3732 | + |
| 3733 | + /* Scan format string, looking for conversion specifiers. */ |
| 3734 | + for (cp = start_ptr; cp < end_ptr; cp++) |
| 3735 | + { |
| 3736 | + Datum value; |
| 3737 | + bool isNull; |
| 3738 | + Oid typid; |
| 3739 | + |
| 3740 | + /* |
| 3741 | + * If it's not the start of a conversion specifier, just copy it to |
| 3742 | + * the output buffer. |
| 3743 | + */ |
| 3744 | + if (*cp != '%') |
| 3745 | + { |
| 3746 | + appendStringInfoCharMacro(&str, *cp); |
| 3747 | + continue; |
| 3748 | + } |
| 3749 | + |
| 3750 | + /* Did we run off the end of the string? */ |
| 3751 | + if (++cp >= end_ptr) |
| 3752 | + ereport(ERROR, |
| 3753 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 3754 | + errmsg("unterminated conversion specifier"))); |
| 3755 | + |
| 3756 | + /* Easy case: %% outputs a single % */ |
| 3757 | + if (*cp == '%') |
| 3758 | + { |
| 3759 | + appendStringInfoCharMacro(&str, *cp); |
| 3760 | + continue; |
| 3761 | + } |
| 3762 | + |
| 3763 | + /* |
| 3764 | + * If the user hasn't specified an argument position, we just advance |
| 3765 | + * to the next one. If they have, we must parse it. |
| 3766 | + */ |
| 3767 | + if (*cp < '0' || *cp > '9') |
| 3768 | + ++arg; |
| 3769 | + else |
| 3770 | + { |
| 3771 | + bool unterminated = false; |
| 3772 | + |
| 3773 | + /* Parse digit string. */ |
| 3774 | + arg = 0; |
| 3775 | + do { |
| 3776 | + /* Treat overflowing arg position as unterminated. */ |
| 3777 | + if (arg > INT_MAX / 10) |
| 3778 | + break; |
| 3779 | + arg = arg * 10 + (*cp - '0'); |
| 3780 | + ++cp; |
| 3781 | + } while (cp < end_ptr && *cp >= '0' && *cp <= '9'); |
| 3782 | + |
| 3783 | + /* |
| 3784 | + * If we ran off the end, or if there's not a $ next, or if the $ |
| 3785 | + * is the last character, the conversion specifier is improperly |
| 3786 | + * terminated. |
| 3787 | + */ |
| 3788 | + if (cp == end_ptr || *cp != '$') |
| 3789 | + unterminated = true; |
| 3790 | + else |
| 3791 | + { |
| 3792 | + ++cp; |
| 3793 | + if (cp == end_ptr) |
| 3794 | + unterminated = true; |
| 3795 | + } |
| 3796 | + if (unterminated) |
| 3797 | + ereport(ERROR, |
| 3798 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 3799 | + errmsg("unterminated conversion specifier"))); |
| 3800 | + |
| 3801 | + /* There's no argument 0. */ |
| 3802 | + if (arg == 0) |
| 3803 | + ereport(ERROR, |
| 3804 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 3805 | + errmsg("conversion specifies argument 0, but arguments are numbered from 1"))); |
| 3806 | + } |
| 3807 | + |
| 3808 | + /* Not enough arguments? Deduct 1 to avoid counting format string. */ |
| 3809 | + if (arg > PG_NARGS() - 1) |
| 3810 | + ereport(ERROR, |
| 3811 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 3812 | + errmsg("too few arguments for format conversion"))); |
| 3813 | + |
| 3814 | + /* |
| 3815 | + * At this point, we should see the main conversion specifier. |
| 3816 | + * Whether or not an argument position was present, it's known |
| 3817 | + * that at least one character remains in the string at this point. |
| 3818 | + */ |
| 3819 | + value = PG_GETARG_DATUM(arg); |
| 3820 | + isNull = PG_ARGISNULL(arg); |
| 3821 | + typid = get_fn_expr_argtype(fcinfo->flinfo, arg); |
| 3822 | + |
| 3823 | + switch (*cp) |
| 3824 | + { |
| 3825 | + case 's': |
| 3826 | + case 'I': |
| 3827 | + case 'L': |
| 3828 | + text_format_string_conversion(&str, *cp, typid, value, isNull); |
| 3829 | + break; |
| 3830 | + default: |
| 3831 | + ereport(ERROR, |
| 3832 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 3833 | + errmsg("unrecognized conversion specifier: %c", |
| 3834 | + *cp))); |
| 3835 | + } |
| 3836 | + } |
| 3837 | + |
| 3838 | + /* Generate results. */ |
| 3839 | + result = cstring_to_text_with_len(str.data, str.len); |
| 3840 | + pfree(str.data); |
| 3841 | + |
| 3842 | + PG_RETURN_TEXT_P(result); |
| 3843 | +} |
| 3844 | + |
| 3845 | +/* Format a %s, %I, or %L conversion. */ |
| 3846 | +void |
| 3847 | +text_format_string_conversion(StringInfo buf, char conversion, |
| 3848 | + Oid typid, Datum value, bool isNull) |
| 3849 | +{ |
| 3850 | + Oid typOutput; |
| 3851 | + bool typIsVarlena; |
| 3852 | + char *str; |
| 3853 | + |
| 3854 | + /* Handle NULL arguments before trying to stringify the value. */ |
| 3855 | + if (isNull) |
| 3856 | + { |
| 3857 | + if (conversion == 'L') |
| 3858 | + appendStringInfoString(buf, "NULL"); |
| 3859 | + else if (conversion == 'I') |
| 3860 | + ereport(ERROR, |
| 3861 | + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), |
| 3862 | + errmsg("NULL cannot be escaped as an SQL identifier"))); |
| 3863 | + return; |
| 3864 | + } |
| 3865 | + |
| 3866 | + /* Stringify. */ |
| 3867 | + getTypeOutputInfo(typid, &typOutput, &typIsVarlena); |
| 3868 | + str = OidOutputFunctionCall(typOutput, value); |
| 3869 | + |
| 3870 | + /* Escape. */ |
| 3871 | + if (conversion == 'I') |
| 3872 | + { |
| 3873 | + /* quote_identifier may or may not allocate a new string. */ |
| 3874 | + appendStringInfoString(buf, quote_identifier(str)); |
| 3875 | + } |
| 3876 | + else if (conversion == 'L') |
| 3877 | + { |
| 3878 | + char *qstr = quote_literal_cstr(str); |
| 3879 | + appendStringInfoString(buf, qstr); |
| 3880 | + /* quote_literal_cstr() always allocates a new string */ |
| 3881 | + pfree(qstr); |
| 3882 | + } |
| 3883 | + else |
| 3884 | + appendStringInfoString(buf, str); |
| 3885 | + |
| 3886 | + /* Cleanup. */ |
| 3887 | + pfree(str); |
| 3888 | +} |
| 3889 | + |
| 3890 | +/* |
| 3891 | + * text_format_nv - nonvariadic wrapper for text_format function. |
| 3892 | + * |
| 3893 | + * note: this wrapper is necessary to be sanity_checks test ok |
| 3894 | + */ |
| 3895 | +Datum |
| 3896 | +text_format_nv(PG_FUNCTION_ARGS) |
| 3897 | +{ |
| 3898 | + return text_format(fcinfo); |
| 3899 | +} |
0 commit comments