*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1519,1539 ****
           <primary>format</primary>
          </indexterm>
          <literal><function>format</function>(<parameter>formatstr</parameter> <type>text</type>
!         [, <parameter>str</parameter> <type>"any"</type> [, ...] ])</literal>
         </entry>
         <entry><type>text</type></entry>
         <entry>
           Format arguments according to a format string.
!          This function is similar to the C function
!          <function>sprintf</>, but only the following conversion specifications
!          are recognized: <literal>%s</literal> interpolates the corresponding
!          argument as a string; <literal>%I</literal> escapes its argument as
!          an SQL identifier; <literal>%L</literal> escapes its argument as an
!          SQL literal; <literal>%%</literal> outputs a literal <literal>%</>.
!          A conversion can reference an explicit parameter position by preceding
!          the conversion specifier with <literal><replaceable>n</>$</>, where
!          <replaceable>n</replaceable> is the argument position.
!          See also <xref linkend="plpgsql-quote-literal-example">.
         </entry>
         <entry><literal>format('Hello %s, %1$s', 'World')</literal></entry>
         <entry><literal>Hello World, World</literal></entry>
--- 1519,1531 ----
           <primary>format</primary>
          </indexterm>
          <literal><function>format</function>(<parameter>formatstr</parameter> <type>text</type>
!         [, <parameter>formatarg</parameter> <type>"any"</type> [, ...] ])</literal>
         </entry>
         <entry><type>text</type></entry>
         <entry>
           Format arguments according to a format string.
!          This function is similar to the C function <function>sprintf</>.
!          See <xref linkend="functions-string-format">.
         </entry>
         <entry><literal>format('Hello %s, %1$s', 'World')</literal></entry>
         <entry><literal>Hello World, World</literal></entry>
***************
*** 2847,2852 ****
--- 2839,3024 ----
      </tgroup>
     </table>
  
+    <sect2 id="functions-string-format">
+     <title><function>format</function></title>
+ 
+     <indexterm>
+      <primary>format</primary>
+     </indexterm>
+ 
+     <para>
+      The function <function>format</> produces formatted output according to
+      a format string in a similar way to the C function <function>sprintf</>.
+     </para>
+ 
+     <para>
+ <synopsis>
+ format(<parameter>formatstr</> <type>text</> [, <parameter>formatarg</> <type>"any"</> [, ...] ])
+ </synopsis>
+      <replaceable>formatstr</> is a format string that specifies how the
+      result should be formatted.  Text in the format string is copied directly
+      to the result, except where <firstterm>format specifiers</> are used.
+      Format specifiers act as placeholders in the string, allowing subsequent
+      function arguments to be formatted and inserted into the result.
+     </para>
+ 
+     <para>
+      Format specifiers are introduced by a <literal>%</> character and take
+      the form
+ <synopsis>
+ %[<replaceable>parameter</>][<replaceable>flags</>][<replaceable>width</>]<replaceable>type</>
+ </synopsis>
+      <variablelist>
+       <varlistentry>
+        <term><replaceable>parameter</replaceable> (optional)</term>
+        <listitem>
+         <para>
+          An expression of the form <literal><replaceable>n</>$</> where
+          <replaceable>n</> is the index of the argument to use for the format
+          specifier's value.  An index of 1 means the first argument after
+          <replaceable>formatstr</>.  If the <replaceable>parameter</> field is
+          omitted, the default is to use the next argument.
+         </para>
+ <screen>
+ SELECT format('Testing %s, %s, %s', 'one', 'two', 'three');
+ <lineannotation>Result: </><computeroutput>Testing one, two, three</>
+ 
+ SELECT format('Testing %3$s, %2$s, %1$s', 'one', 'two', 'three');
+ <lineannotation>Result: </><computeroutput>Testing three, two, one</>
+ </screen>
+ 
+         <para>
+          Note that unlike the C function <function>sprintf</> defined in the
+          Single UNIX Specification, the <function>format</> function in
+          <productname>PostgreSQL</> allows format specifiers with and without
+          explicit <replaceable>parameter</> fields to be mixed in the same
+          format string.  A format specifier without a
+          <replaceable>parameter</> field always uses the next argument after
+          the last argument consumed.  In addition, the
+          <productname>PostgreSQL</> <function>format</> function does not
+          require all function arguments to be referred to in the format
+          string.
+         </para>
+ <screen>
+ SELECT format('Testing %3$s, %2$s, %s', 'one', 'two', 'three');
+ <lineannotation>Result: </><computeroutput>Testing three, two, three</>
+ </screen>
+        </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
+        <term><replaceable>flags</replaceable> (optional)</term>
+        <listitem>
+         <para>
+          Additional options controlling how the format specifier's output is
+          formatted.  Currently the only supported flag is an minus sign
+          (<literal>-</>) which will cause the format specifier's output to be
+          left-aligned.  This has no effect unless the <replaceable>width</>
+          field is also specified.
+         </para>
+ <screen>
+ SELECT format('|%10s|%-10s|', 'foo', 'bar');
+ <lineannotation>Result: </><computeroutput>|       foo|bar       |</>
+ </screen>
+        </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
+        <term><replaceable>width</replaceable> (optional)</term>
+        <listitem>
+         <para>
+          Specifies the <emphasis>minimum</> number of characters to use to
+          display the format specifier's output.  The width may be specified
+          using any of the following: a positive integer; an asterisk
+          (<literal>*</>) to use the next function argument as the width; or an
+          expression of the form <literal>*<replaceable>n</>$</> to use the
+          <replaceable>n</>th function argument as the width.
+         </para>
+ 
+         <para>
+          If the width comes from a function argument, that argument is
+          consumed <emphasis>before</> the argument that is used for the format
+          specifier's value.  If the width argument is negative, the result is
+          left aligned, as if the <literal>-</> flag had been specified.
+         </para>
+ <screen>
+ SELECT format('|%10s|', 'foo');
+ <lineannotation>Result: </><computeroutput>|       foo|</>
+ 
+ SELECT format('|%*s|', 10, 'foo');
+ <lineannotation>Result: </><computeroutput>|       foo|</>
+ 
+ SELECT format('|%*s|', -10, 'foo');
+ <lineannotation>Result: </><computeroutput>|foo       |</>
+ 
+ SELECT format('|%-*s|', 10, 'foo');
+ <lineannotation>Result: </><computeroutput>|foo       |</>
+ 
+ SELECT format('|%-*s|', -10, 'foo');
+ <lineannotation>Result: </><computeroutput>|foo       |</>
+ 
+ SELECT format('|%*2$s|', 'foo', 10, 'bar');
+ <lineannotation>Result: </><computeroutput>|       bar|</>
+ 
+ SELECT format('|%3$*2$s|', 'foo', 10, 'bar');
+ <lineannotation>Result: </><computeroutput>|       bar|</>
+ </screen>
+        </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
+        <term><replaceable>type</replaceable> (required)</term>
+        <listitem>
+         <para>
+          The type of format conversion to use to produce the format
+          specifier's output.  The following types are supported:
+          <itemizedlist>
+           <listitem>
+            <para>
+             <literal>s</literal> formats the argument value as a simple
+             string.  A null value is treated as an empty string.
+            </para>
+           </listitem>
+           <listitem>
+            <para>
+             <literal>I</literal> escapes the value as an SQL identifier.  It
+             is an error for the value to be null.
+            </para>
+           </listitem>
+           <listitem>
+            <para>
+             <literal>L</literal> escapes the value as an SQL literal.  A null
+             value is displayed as the literal value <literal>NULL</>.
+            </para>
+           </listitem>
+          </itemizedlist>
+         </para>
+ <screen>
+ SELECT format('Hello %s', 'World');
+ <lineannotation>Result: </lineannotation><computeroutput>Hello World</computeroutput>
+ 
+ SELECT format('DROP TABLE %I', 'Foo bar');
+ <lineannotation>Result: </lineannotation><computeroutput>DROP TABLE "Foo bar"</computeroutput>
+ 
+ SELECT format('SELECT %L', E'O\'Reilly');
+ <lineannotation>Result: </lineannotation><computeroutput>SELECT 'O''Reilly'</computeroutput>
+ </screen>
+ 
+         <para>
+          The <literal>%I</> and <literal>%L</> format specifiers may be used
+          to safely construct dynamic SQL statements.  See
+          <xref linkend="plpgsql-quote-literal-example">.
+         </para>
+        </listitem>
+       </varlistentry>
+      </variablelist>
+     </para>
+ 
+     <para>
+      In addition to the format specifiers above, the special escape sequence
+      <literal>%%</> may be used to output a literal <literal>%</> character.
+     </para>
+    </sect2>
    </sect1>
  
  
*** a/src/backend/utils/adt/varlena.c
--- b/src/backend/utils/adt/varlena.c
***************
*** 78,84 **** static bytea *bytea_overlay(bytea *t1, bytea *t2, int sp, int sl);
  static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static void text_format_string_conversion(StringInfo buf, char conversion,
  							  FmgrInfo *typOutputInfo,
! 							  Datum value, bool isNull);
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
--- 78,85 ----
  static StringInfo makeStringAggState(FunctionCallInfo fcinfo);
  static void text_format_string_conversion(StringInfo buf, char conversion,
  							  FmgrInfo *typOutputInfo,
! 							  Datum value, bool isNull,
! 							  int flags, int width);
  static Datum text_to_array_internal(PG_FUNCTION_ARGS);
  static text *array_to_text_internal(FunctionCallInfo fcinfo, ArrayType *v,
  					   const char *fldsep, const char *null_string);
***************
*** 3996,4001 **** text_reverse(PG_FUNCTION_ARGS)
--- 3997,4133 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ #define FORWARD_PARSE_POINT(ptr) \
+ do { \
+ 	if (++(ptr) >= (end_ptr)) \
+ 		ereport(ERROR, \
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE), \
+ 				 errmsg("unterminated conversion specifier"))); \
+ } while (0)
+ 
+ /*
+  * Parse congiguous digits into decimal number.
+  *
+  * Returns true if some digits could be parsed and *ptr moved to the next
+  * character to be parsed. The value is returned into *value.
+  */
+ static bool
+ text_format_parse_digits(const char **ptr, const char *end_ptr, int *value)
+ {
+ 	const char *cp = *ptr;
+ 	int wval = 0;
+ 	bool found;
+ 
+ 	/*
+ 	 * continue, only when start_ptr is less than end_ptr.
+ 	 * Overrun of cp is checked in FORWARD_PARSE_POINT.
+ 	 */
+ 	while (*cp >= '0' && *cp <= '9')
+ 	{
+ 		int	newnum = wval * 10 + (*cp - '0');
+ 
+ 		if (newnum / 10 != wval) /* overflow? */
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 					 errmsg("number is out of range")));
+ 		wval = newnum;
+ 		FORWARD_PARSE_POINT(cp);
+ 	}
+ 
+ 	found = (cp > *ptr);
+ 	*value = wval;
+ 	*ptr = cp;
+ 
+ 	return found;
+ }
+ 
+ #define TEXT_FORMAT_FLAG_MINUS	0x0001			/* is minus in format string? */
+ 
+ #define SAMESIGN(a,b)	(((a) < 0) == ((b) < 0))
+ 
+ /*
+  * parse format specification
+  *             [argpos][flags][width]type
+  *
+  * Return values are,
+  *   static const char * : Address to be parsed next.
+  *   valarg   : argument position for value to be printed. -1 means missing.
+  *   widtharg : argument position for width. Zero means that argument position
+  *              is not specified and -1 means missing.
+  *   flags    : flags
+  *   width    : the value for direct width specification, zero means that width
+  *              is not specified.
+  */
+ static const char *
+ text_format_parse_format(const char *start_ptr, const char *end_ptr,
+ 						 int *valarg, int *widtharg, int *flags, int *width)
+ {
+ 	const char *cp = start_ptr;
+ 	int	   n;
+ 
+ 	/* set defaults to out parameters */
+ 	*valarg =  -1;
+ 	*widtharg = -1;
+ 	*flags = 0;
+ 	*width = 0;
+ 
+ 	/* try to identify first number */
+ 	if (text_format_parse_digits(&cp, end_ptr, &n))
+ 	{
+ 		if (*cp != '$')
+ 		{
+ 			*width = n;    /* The number should be width */
+ 			return cp;
+ 		}
+ 		/* Explicit 0 for argument index is immediately refused */
+ 		if (n == 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
+ 		*valarg = n;   /* The number was argument position  */
+ 		FORWARD_PARSE_POINT(cp);
+ 	}
+ 
+ 	/* Check for flags, only minus is supported now. */
+ 	while (*cp == '-')
+ 	{
+ 		*flags = *flags | TEXT_FORMAT_FLAG_MINUS;
+ 		FORWARD_PARSE_POINT(cp);
+ 	}
+ 
+ 	/* try to parse indirect width */
+ 	if (*cp == '*')
+ 	{
+ 		FORWARD_PARSE_POINT(cp);
+ 
+ 		if (text_format_parse_digits(&cp, end_ptr, &n)){
+ 			/* number in this position should be closed by $ */
+ 			if (*cp != '$')
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("unexpected char \"%c\".",*cp)));
+ 			FORWARD_PARSE_POINT(cp);
+ 
+ 			/* Explicit 0 for argument index is immediately refused */
+ 			if (n == 0)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
+ 			*widtharg = n;
+ 		}
+ 		else
+ 			*widtharg = 0;  /* 0 means argument position is not specified */
+ 
+ 		return cp;
+ 	}
+ 
+ 	/* last possible number - width */
+ 	if (text_format_parse_digits(&cp, end_ptr, &n))
+ 		*width = n;
+ 
+ 	return cp;
+ }
+ 
  /*
   * Returns a formated string
   */
***************
*** 4016,4021 **** text_format(PG_FUNCTION_ARGS)
--- 4148,4155 ----
  	Oid			element_type = InvalidOid;
  	Oid			prev_type = InvalidOid;
  	FmgrInfo	typoutputfinfo;
+ 	FmgrInfo	typoutputinfo_width;
+ 	Oid			prev_type_width = InvalidOid;
  
  	/* When format string is null, returns null */
  	if (PG_ARGISNULL(0))
***************
*** 4077,4083 **** text_format(PG_FUNCTION_ARGS)
  	}
  
  	/* 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);
--- 4211,4217 ----
  	}
  
  	/* Setup for main loop. */
! 	fmt = PG_GETARG_TEXT_PP(arg++);
  	start_ptr = VARDATA_ANY(fmt);
  	end_ptr = start_ptr + VARSIZE_ANY_EXHDR(fmt);
  	initStringInfo(&str);
***************
*** 4085,4093 **** text_format(PG_FUNCTION_ARGS)
  	/* 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
--- 4219,4231 ----
  	/* Scan format string, looking for conversion specifiers. */
  	for (cp = start_ptr; cp < end_ptr; cp++)
  	{
! 		Datum	value;
! 		bool	isNull;
! 		Oid		typid;
! 		int 	valarg;
! 		int		widtharg;
! 		int 	flags;
! 		int 	width;
  
  		/*
  		 * If it's not the start of a conversion specifier, just copy it to
***************
*** 4099,4109 **** text_format(PG_FUNCTION_ARGS)
  			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 == '%')
--- 4237,4243 ----
  			continue;
  		}
  
! 		FORWARD_PARSE_POINT(cp);
  
  		/* Easy case: %% outputs a single % */
  		if (*cp == '%')
***************
*** 4112,4184 **** text_format(PG_FUNCTION_ARGS)
  			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;
! 			if (arg <= 0)		/* overflow? */
! 			{
! 				/*
! 				 * Should not happen, as you can't pass billions of arguments
! 				 * to a function, but better safe than sorry.
! 				 */
  				ereport(ERROR,
! 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
! 						 errmsg("argument number is out of range")));
! 			}
! 		}
! 		else
! 		{
! 			bool		unterminated = false;
  
! 			/* Parse digit string. */
! 			arg = 0;
! 			do
  			{
! 				int			newarg = arg * 10 + (*cp - '0');
  
! 				if (newarg / 10 != arg) /* overflow? */
! 					ereport(ERROR,
! 							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
! 							 errmsg("argument number is out of range")));
! 				arg = newarg;
! 				++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 > nargs - 1)
  			ereport(ERROR,
  					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  					 errmsg("too few arguments for format")));
! 
  		/* Get the value and type of the selected argument */
  		if (!funcvariadic)
  		{
--- 4246,4338 ----
  			continue;
  		}
  
! 		cp = text_format_parse_format(cp, end_ptr,
! 							  &valarg, &widtharg, &flags, &width);
! 
! 		if (widtharg >= 0)
  		{
! 			if (widtharg > 0)
! 				/* be consistent, move ordered argument together with
! 				 * positional */
! 				arg = widtharg;
! 
! 			if (arg >= nargs)
  				ereport(ERROR,
! 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! 						 errmsg("too few arguments for format")));
  
! 			if (!funcvariadic)
  			{
! 				value = PG_GETARG_DATUM(arg);
! 				isNull = PG_ARGISNULL(arg);
! 				typid = get_fn_expr_argtype(fcinfo->flinfo, arg);
! 			}
! 			else
! 			{
! 				value = elements[arg - 1];
! 				isNull = nulls[arg - 1];
! 				typid = element_type;
! 			}
! 			if (!OidIsValid(typid))
! 				elog(ERROR, "could not determine data type of format() input");
  
! 			arg++;
  
  			/*
! 			 * we don't need to different between NULL and zero in this moment,
! 			 * NULL means ignore this width - same as zero.
  			 */
! 			if (isNull)
! 				width = 0;
! 			else if (typid == INT4OID)
! 				width = DatumGetInt32(value);
! 			else if (typid == INT2OID)
! 				width = DatumGetInt16(value);
  			else
  			{
! 				char			*str;
! 
! 				/* simple IO cast to int */
! 				if (typid != prev_type_width)
! 				{
! 					Oid			typoutputfunc;
! 					bool		typIsVarlena;
! 
! 					getTypeOutputInfo(typid, &typoutputfunc, &typIsVarlena);
! 					fmgr_info(typoutputfunc, &typoutputinfo_width);
! 					prev_type_width = typid;
! 				}
! 
! 				/* Stringify. */
! 				str = OutputFunctionCall(&typoutputinfo_width, value);
! 
! 				/* get int value */
! 				width = pg_atoi(str, sizeof(int32), '\0');
! 				pfree(str);
  			}
! 		}
  
! 		/* Overflow check */
! 		if (width != 0)
! 		{
! 			int32 _width = -width;
! 
! 			if (SAMESIGN(width, _width))
  				ereport(ERROR,
! 						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
! 						 errmsg("number is out of range")));
  		}
  
! 		if (valarg >= 0)
! 			/* be consistent, move ordered argument together with
! 			 * positional */
! 			arg = valarg;
! 			
! 		if (arg >= nargs)
  			ereport(ERROR,
  					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
  					 errmsg("too few arguments for format")));
! 		
  		/* Get the value and type of the selected argument */
  		if (!funcvariadic)
  		{
***************
*** 4195,4200 **** text_format(PG_FUNCTION_ARGS)
--- 4349,4356 ----
  		if (!OidIsValid(typid))
  			elog(ERROR, "could not determine data type of format() input");
  
+ 		arg++;
+ 
  		/*
  		 * Get the appropriate typOutput function, reusing previous one if
  		 * same type as previous argument.  That's particularly useful in the
***************
*** 4221,4227 **** text_format(PG_FUNCTION_ARGS)
  			case 'I':
  			case 'L':
  				text_format_string_conversion(&str, *cp, &typoutputfinfo,
! 											  value, isNull);
  				break;
  			default:
  				ereport(ERROR,
--- 4377,4384 ----
  			case 'I':
  			case 'L':
  				text_format_string_conversion(&str, *cp, &typoutputfinfo,
! 											  value, isNull,
! 											  flags, width);
  				break;
  			default:
  				ereport(ERROR,
***************
*** 4244,4288 **** text_format(PG_FUNCTION_ARGS)
  	PG_RETURN_TEXT_P(result);
  }
  
  /* Format a %s, %I, or %L conversion. */
  static void
  text_format_string_conversion(StringInfo buf, char conversion,
  							  FmgrInfo *typOutputInfo,
! 							  Datum value, bool isNull)
  {
  	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 values cannot be formatted as an SQL identifier")));
  		return;
  	}
  
- 	/* Stringify. */
  	str = OutputFunctionCall(typOutputInfo, 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);
--- 4401,4490 ----
  	PG_RETURN_TEXT_P(result);
  }
  
+ /*
+  * Add spaces on begin or on end when it is necessary
+  */
+ static void
+ text_format_append_string(StringInfo buf, const char *str,
+ 					  int flags, int width)
+ {
+ 	bool		align_to_left = false;
+ 	int		len;
+ 
+ 	/* fast path */
+ 	if (width == 0)
+ 	{
+ 		appendStringInfoString(buf, str);
+ 		return;
+ 	}
+ 	else if (width < 0 || (flags & TEXT_FORMAT_FLAG_MINUS))
+ 	{
+ 		align_to_left = true;
+ 		if (width < 0)
+ 			width = -width;
+ 	}
+ 
+ 	len = pg_mbstrlen(str);
+ 	if (align_to_left)
+ 	{
+ 		appendStringInfoString(buf, str);
+ 		if (len < width)
+ 			appendStringInfoSpaces(buf, width - len);
+ 	}
+ 	else
+ 	{
+ 		/* align_to_right */
+ 		if (len < width)
+ 			appendStringInfoSpaces(buf, width - len);
+ 		appendStringInfoString(buf, str);
+ 	}
+ }
+ 
  /* Format a %s, %I, or %L conversion. */
  static void
  text_format_string_conversion(StringInfo buf, char conversion,
  							  FmgrInfo *typOutputInfo,
! 							  Datum value, bool isNull,
! 							  int flags, int width)
  {
  	char	   *str;
  
  	if (isNull)
  	{
! 		if (conversion == 's')
! 			text_format_append_string(buf, "",
! 								flags, width);
! 		else if (conversion == 'L')
! 			text_format_append_string(buf, "NULL",
! 								flags, width);
  		else if (conversion == 'I')
  			ereport(ERROR,
  					(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
  			errmsg("null values cannot be formatted as an SQL identifier")));
+ 
  		return;
  	}
  
  	str = OutputFunctionCall(typOutputInfo, value);
  
  	/* Escape. */
  	if (conversion == 'I')
  	{
  		/* quote_identifier may or may not allocate a new string. */
! 		text_format_append_string(buf, quote_identifier(str),
! 							flags, width);
  	}
  	else if (conversion == 'L')
! 	{		char	   *qstr = quote_literal_cstr(str);
  
! 		text_format_append_string(buf, qstr,
! 							flags, width);
  		/* quote_literal_cstr() always allocates a new string */
  		pfree(qstr);
  	}
  	else
! 		text_format_append_string(buf, str,
! 							flags, width);
  
  	/* Cleanup. */
  	pfree(str);
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 256,267 **** select format('%1$s %4$s', 1, 2, 3);
  ERROR:  too few arguments for format
  select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
  ERROR:  too few arguments for format
  select format('%1s', 1);
! ERROR:  unterminated conversion specifier
  select format('%1$', 1);
  ERROR:  unterminated conversion specifier
  select format('%1$1', 1);
! ERROR:  unrecognized conversion specifier "1"
  -- check mix of positional and ordered placeholders
  select format('Hello %s %1$s %s', 'World', 'Hello again');
              format             
--- 256,275 ----
  ERROR:  too few arguments for format
  select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
  ERROR:  too few arguments for format
+ select format('%0$s', 'Hello');
+ ERROR:  conversion specifies argument 0, but arguments are numbered from 1
+ select format('%*0$s', 'Hello');
+ ERROR:  conversion specifies argument 0, but arguments are numbered from 1
  select format('%1s', 1);
!  format 
! --------
!  1
! (1 row)
! 
  select format('%1$', 1);
  ERROR:  unterminated conversion specifier
  select format('%1$1', 1);
! ERROR:  unterminated conversion specifier
  -- check mix of positional and ordered placeholders
  select format('Hello %s %1$s %s', 'World', 'Hello again');
              format             
***************
*** 328,330 **** from generate_series(1,200) g(i);
--- 336,409 ----
   1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200
  (1 row)
  
+ -- left, right align
+ select format('>>%10s<<', 'Hello')
+ union all
+ select format('>>%10s<<', NULL)
+ union all
+ select format('>>%10s<<', '')
+ union all
+ select format('>>%-10s<<', '')
+ union all
+ select format('>>%-10s<<', 'Hello')
+ union all
+ select format('>>%-10s<<', NULL)
+ union all
+ select format('>>%1$10s<<', 'Hello')
+ union all
+ select format('>>%1$-10I<<', 'Hello')
+ union all
+ select format('>>%2$*1$L<<', 10, 'Hello')
+ union all
+ select format('>>%2$*1$L<<', 10, NULL)
+ union all
+ select format('>>%2$*1$L<<', -10, NULL)
+ union all
+ select format('>>%*s<<', 10, 'Hello');
+      format     
+ ----------------
+  >>     Hello<<
+  >>          <<
+  >>          <<
+  >>          <<
+  >>Hello     <<
+  >>          <<
+  >>     Hello<<
+  >>"Hello"   <<
+  >>   'Hello'<<
+  >>      NULL<<
+  >>NULL      <<
+  >>     Hello<<
+ (12 rows)
+ 
+ select format('>>%*1$s<<', 10, 'Hello');
+      format     
+ ----------------
+  >>     Hello<<
+ (1 row)
+ 
+ select format('>>%-s<<', 'Hello');
+   format   
+ -----------
+  >>Hello<<
+ (1 row)
+ 
+ -- NULL is not different to zero here
+ select format('>>%10L<<', NULL);
+      format     
+ ----------------
+  >>      NULL<<
+ (1 row)
+ 
+ select format('>>%2$*1$L<<', NULL, 'Hello');
+    format    
+ -------------
+  >>'Hello'<<
+ (1 row)
+ 
+ select format('>>%2$*1$L<<', 0, 'Hello');
+    format    
+ -------------
+  >>'Hello'<<
+ (1 row)
+ 
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 78,83 **** select format('%1$s %12$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
--- 78,85 ----
  -- should fail
  select format('%1$s %4$s', 1, 2, 3);
  select format('%1$s %13$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
+ select format('%0$s', 'Hello');
+ select format('%*0$s', 'Hello');
  select format('%1s', 1);
  select format('%1$', 1);
  select format('%1$1', 1);
***************
*** 97,99 **** select format('Hello', variadic NULL);
--- 99,134 ----
  -- variadic argument allows simulating more than FUNC_MAX_ARGS parameters
  select format(string_agg('%s',','), variadic array_agg(i))
  from generate_series(1,200) g(i);
+ 
+ -- left, right align
+ select format('>>%10s<<', 'Hello')
+ union all
+ select format('>>%10s<<', NULL)
+ union all
+ select format('>>%10s<<', '')
+ union all
+ select format('>>%-10s<<', '')
+ union all
+ select format('>>%-10s<<', 'Hello')
+ union all
+ select format('>>%-10s<<', NULL)
+ union all
+ select format('>>%1$10s<<', 'Hello')
+ union all
+ select format('>>%1$-10I<<', 'Hello')
+ union all
+ select format('>>%2$*1$L<<', 10, 'Hello')
+ union all
+ select format('>>%2$*1$L<<', 10, NULL)
+ union all
+ select format('>>%2$*1$L<<', -10, NULL)
+ union all
+ select format('>>%*s<<', 10, 'Hello');
+ 
+ select format('>>%*1$s<<', 10, 'Hello');
+ select format('>>%-s<<', 'Hello');
+ 
+ -- NULL is not different to zero here
+ select format('>>%10L<<', NULL);
+ select format('>>%2$*1$L<<', NULL, 'Hello');
+ select format('>>%2$*1$L<<', 0, 'Hello');
