*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 1532,1538 ****
           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. A warnig is raised
           when positional and ordered placeholders are used together.
           See also <xref linkend="plpgsql-quote-literal-example">.
         </entry>
--- 1532,1540 ----
           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. Similary to C 
!          function <function>sprintf</>; is possible to specify a width for
!          <literal>%s</literal> conversion. A warnig is raised
           when positional and ordered placeholders are used together.
           See also <xref linkend="plpgsql-quote-literal-example">.
         </entry>
*** 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,
! 							  bool use_width, 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);
***************
*** 3984,3989 **** text_reverse(PG_FUNCTION_ARGS)
--- 3985,4065 ----
  }
  
  /*
+  * Returns ptr of first non digit char. Raise error,
+  * when no digit is processed or when result of parsing is not number
+  */
+ static const char *
+ parse_digit_subformat(const char *start_ptr, const char *end_ptr, int *value)
+ {
+ 	const char *cp = start_ptr;
+ 	bool	minus = false;
+ 	int	inum = 0;
+ 
+ 	/* continue, only when start_ptr is less than end_ptr */
+ 	if (cp < end_ptr)
+ 	{
+ 		if (*cp == '-')
+ 		{
+ 			minus = true;
+ 			start_ptr = cp++;
+ 		}
+ 
+ 		while (cp < end_ptr)
+ 		{
+ 			if (*cp >= '0' && *cp <= '9')
+ 			{
+ 				int	newnum = inum * 10 + (*cp - '0');
+ 
+ 				if (newnum / 10 != inum) /* overflow? */
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 							 errmsg("number is out of range")));
+ 				inum = newnum;
+ 				++cp;
+ 			}
+ 			else
+ 				break;
+ 		}
+ 
+ 		*value = minus ? - inum : inum;
+ 	}
+ 
+ 	/* digit cannot be last char */
+ 	if (cp >= end_ptr)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 				 errmsg("unterminated conversion specifier")));
+ 
+ 	/* we call this routine, only when we expected number */
+ 	if (cp == start_ptr)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ 				 errmsg("invalid format, missing expected number")));
+ 
+ 	/* argument number must by greather than zero */
+ 	if (*cp == '$')
+ 	{
+ 		if (minus)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
+ 
+ 		if (inum == 0)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("conversion specifies argument 0, but arguments are numbered from 1")));
+ 
+ 		/* $ must not be last char */
+ 		if (cp + 1 >= end_ptr)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("unterminated conversion specifier")));
+ 	}
+ 
+ 	return cp;
+ }
+ 
+ /*
   * Returns a formated string
   */
  Datum
***************
*** 4003,4009 **** text_format(PG_FUNCTION_ARGS)
  	Oid			element_type = InvalidOid;
  	Oid			prev_type = InvalidOid;
  	FmgrInfo	typoutputfinfo;
! 	int		position = 0;
  	bool		positional = false;
  	bool		ordered = false;
  	bool		warning_emmited = false;
--- 4079,4085 ----
  	Oid			element_type = InvalidOid;
  	Oid			prev_type = InvalidOid;
  	FmgrInfo	typoutputfinfo;
! 	int		ordered_nargs = 0;
  	bool		positional = false;
  	bool		ordered = false;
  	bool		warning_emmited = false;
***************
*** 4066,4071 **** text_format(PG_FUNCTION_ARGS)
--- 4142,4149 ----
  		Datum		value;
  		bool		isNull;
  		Oid			typid;
+ 		int		width = 0;
+ 		bool		use_width = false;
  
  		/*
  		 * If it's not the start of a conversion specifier, just copy it to
***************
*** 4090,4111 **** 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')
  		{
! 			/* don't allow mix styles - reflects glibc behave */
! 			if (positional && !warning_emmited)
  			{
! 				elog(WARNING, "ordered and positional placeholders are used together");
! 				warning_emmited = true;
  			}
! 			ordered = true;
  
! 			++position;
! 			if (position <= 0)		/* overflow? */
  			{
  				/*
  				 * Should not happen, as you can't pass billions of arguments
--- 4168,4211 ----
  			continue;
  		}
  
+ 		arg = 0;
  		/*
! 		 * User can specify a width or a position or both. The width should
! 		 * be negative.
  		 */
! 		if (*cp == '-' || (*cp >= '0' && *cp <= '9'))
  		{
! 			cp = parse_digit_subformat(cp, end_ptr, &arg);
! 
! 			if (*cp == '$')
  			{
! 				++cp;
! 				if (*cp == '-' || (*cp >= '0' && *cp <= '9'))
! 				{
! 					cp = parse_digit_subformat(cp, end_ptr, &width);
! 
! 					if (*cp == '$')
! 						ereport(ERROR,
! 								(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! 								 errmsg("argument number is defined yet")));
! 
! 					use_width = true;
! 				}
  			}
! 			else
! 			{
! 				/* previous digits was a width not argument number */
! 				width = arg;
! 				arg = 0;
! 				use_width = true;
! 			}
! 		}
  
! 		/* when argument not specified yet, use a next one */
! 		if (arg == 0)
! 		{
! 			++ordered_nargs;
! 			if (ordered_nargs <= 0)		/* overflow? */
  			{
  				/*
  				 * Should not happen, as you can't pass billions of arguments
***************
*** 4115,4170 **** text_format(PG_FUNCTION_ARGS)
  						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  						 errmsg("argument number is out of range")));
  			}
! 			arg = position;
! 		}
! 		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")));
  
  			if (ordered && !warning_emmited)
  			{
  				elog(WARNING, "ordered and positional placeholders are used together");
  				warning_emmited = true;
  			}
- 			positional = true;
  
! 			/* 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. */
--- 4215,4240 ----
  						(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
  						 errmsg("argument number is out of range")));
  			}
! 			arg = ordered_nargs;
  
! 			/* raise warning when found mixed placeholders, but only once */
! 			if (positional && !warning_emmited)
  			{
! 				elog(WARNING, "ordered and positional placeholders are used together");
! 				warning_emmited = true;
  			}
  
+ 			ordered = true;
+ 		}
+ 		else
+ 		{
  			if (ordered && !warning_emmited)
  			{
  				elog(WARNING, "ordered and positional placeholders are used together");
  				warning_emmited = true;
  			}
  
! 			positional = true;
  		}
  
  		/* Not enough arguments?  Deduct 1 to avoid counting format string. */
***************
*** 4212,4221 **** text_format(PG_FUNCTION_ARGS)
  		switch (*cp)
  		{
  			case 's':
  			case 'I':
  			case 'L':
  				text_format_string_conversion(&str, *cp, &typoutputfinfo,
! 											  value, isNull);
  				break;
  			default:
  				ereport(ERROR,
--- 4282,4301 ----
  		switch (*cp)
  		{
  			case 's':
+ 				text_format_string_conversion(&str, *cp, &typoutputfinfo,
+ 											  value, isNull,
+ 											  use_width, width);
+ 				break;
+ 
  			case 'I':
  			case 'L':
+ 				if (use_width)
+ 					ereport(ERROR,
+ 							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 							 errmsg("conversion \"%c\" doesn't support the width specification", *cp)));
  				text_format_string_conversion(&str, *cp, &typoutputfinfo,
! 											  value, isNull,
! 											  false, 0);
  				break;
  			default:
  				ereport(ERROR,
***************
*** 4242,4248 **** text_format(PG_FUNCTION_ARGS)
  static void
  text_format_string_conversion(StringInfo buf, char conversion,
  							  FmgrInfo *typOutputInfo,
! 							  Datum value, bool isNull)
  {
  	char	   *str;
  
--- 4322,4329 ----
  static void
  text_format_string_conversion(StringInfo buf, char conversion,
  							  FmgrInfo *typOutputInfo,
! 							  Datum value, bool isNull,
! 							  bool use_width, int width)
  {
  	char	   *str;
  
***************
*** 4276,4282 **** text_format_string_conversion(StringInfo buf, char conversion,
  		pfree(qstr);
  	}
  	else
! 		appendStringInfoString(buf, str);
  
  	/* Cleanup. */
  	pfree(str);
--- 4357,4386 ----
  		pfree(qstr);
  	}
  	else
! 	{
! 		/* fast path */
! 		if (!use_width)
! 			appendStringInfoString(buf, str);
! 		else
! 		{
! 			int	len = pg_mbstrlen(str);
! 
! 			if (width < 0)
! 			{
! 				/* allign to left */
! 				appendStringInfoString(buf, str);
! 				if (len < (-width))
! 					appendStringInfoSpaces(buf, - (len + width));
! 			}
! 			else
! 			{
! 				/* allign to right */
! 				if (len < width)
! 					appendStringInfoSpaces(buf, width - len);
! 				appendStringInfoString(buf, str);
! 			}
! 		}
! 	}
  
  	/* Cleanup. */
  	pfree(str);
*** a/src/test/regress/expected/text.out
--- b/src/test/regress/expected/text.out
***************
*** 258,269 **** 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
  -- should raise warning
  select format('Hello %s %1$s %s', 'World', 'Hello again');
--- 258,267 ----
  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('%1$', 1);
  ERROR:  unterminated conversion specifier
  select format('%1$1', 1);
! ERROR:  unterminated conversion specifier
  -- check mix of positional and ordered placeholders
  -- should raise warning
  select format('Hello %s %1$s %s', 'World', 'Hello again');
***************
*** 340,342 **** from generate_series(1,200) g(i);
--- 338,372 ----
   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)
  
+ -- format with a width
+ select format('>>%10s<<', 'Hello');
+      format     
+ ----------------
+  >>     Hello<<
+ (1 row)
+ 
+ select format('>>%-10s<<', 'Hello');
+      format     
+ ----------------
+  >>Hello     <<
+ (1 row)
+ 
+ select format('>>%1$10s<<', 'Hello');
+      format     
+ ----------------
+  >>     Hello<<
+ (1 row)
+ 
+ select format('>>%1$-10s<<', 'Hello');
+      format     
+ ----------------
+  >>Hello     <<
+ (1 row)
+ 
+ -- should fail
+ select format('>>%1$1$s<<', 'Hello');
+ ERROR:  argument number is defined yet
+ select format('>>%10I<<', 'tablename');
+ ERROR:  conversion "I" doesn't support the width specification
+ select format('>>%1$10L<<', 'tablename');
+ ERROR:  conversion "L" doesn't support the width specification
*** a/src/test/regress/sql/text.sql
--- b/src/test/regress/sql/text.sql
***************
*** 78,84 **** select format('%1$s %12$s', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12);
  -- 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('%1s', 1);
  select format('%1$', 1);
  select format('%1$1', 1);
  -- check mix of positional and ordered placeholders
--- 78,83 ----
***************
*** 99,101 **** select format('Hello', variadic NULL::int[]);
--- 98,112 ----
  -- 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);
+ 
+ 
+ -- format with a width
+ select format('>>%10s<<', 'Hello');
+ select format('>>%-10s<<', 'Hello');
+ select format('>>%1$10s<<', 'Hello');
+ select format('>>%1$-10s<<', 'Hello');
+ 
+ -- should fail
+ select format('>>%1$1$s<<', 'Hello');
+ select format('>>%10I<<', 'tablename');
+ select format('>>%1$10L<<', 'tablename');
