Thread: Verbosity of Function Return Type Checks

Verbosity of Function Return Type Checks

From
Volkan YAZICI
Date:
Hi,

Yesterday I needed to fiddle with PostgreSQL internals to be able to
debug a PL/pgSQL procedure returning a set of records. I attached the
patch I used to increase the verbosity of error messages related with
function return type checks. I'll be appreciated if any developer could
commit this patch (or a similar one) into the core.


Regards.

Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.216
diff -u -r1.216 pl_exec.c
--- src/pl/plpgsql/src/pl_exec.c    16 May 2008 18:34:51 -0000    1.216
+++ src/pl/plpgsql/src/pl_exec.c    8 Aug 2008 11:52:02 -0000
@@ -190,7 +190,7 @@
                        Oid reqtype, int32 reqtypmod,
                        bool isnull);
 static void exec_init_tuple_store(PLpgSQL_execstate *estate);
-static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
+static void validate_tupdesc_compat(TupleDesc td1, TupleDesc td2);
 static void exec_set_found(PLpgSQL_execstate *estate, bool state);
 static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
 static void free_var(PLpgSQL_var *var);
@@ -386,11 +386,12 @@
             {
                 case TYPEFUNC_COMPOSITE:
                     /* got the expected result rowtype, now check it */
-                    if (estate.rettupdesc == NULL ||
-                        !compatible_tupdesc(estate.rettupdesc, tupdesc))
+                    if (!estate.rettupdesc)
                         ereport(ERROR,
                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                 errmsg("returned record type does not match expected record type")));
+                                 errmsg("returned record type does not match "
+                                        "expected record type")));
+                    validate_tupdesc_compat(tupdesc, estate.rettupdesc);
                     break;
                 case TYPEFUNC_RECORD:

@@ -707,11 +708,8 @@
         rettup = NULL;
     else
     {
-        if (!compatible_tupdesc(estate.rettupdesc,
-                                trigdata->tg_relation->rd_att))
-            ereport(ERROR,
-                    (errcode(ERRCODE_DATATYPE_MISMATCH),
-                     errmsg("returned tuple structure does not match table of trigger event")));
+        validate_tupdesc_compat(trigdata->tg_relation->rd_att,
+                                estate.rettupdesc);
         /* Copy tuple to upper executor memory */
         rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
     }
@@ -2202,10 +2200,7 @@
                            errmsg("record \"%s\" is not assigned yet",
                                   rec->refname),
                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
-                    if (!compatible_tupdesc(tupdesc, rec->tupdesc))
-                        ereport(ERROR,
-                                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                        errmsg("wrong record type supplied in RETURN NEXT")));
+                    validate_tupdesc_compat(rec->tupdesc, tupdesc);
                     tuple = rec->tup;
                 }
                 break;
@@ -2311,10 +2306,7 @@
                                            stmt->params);
     }

-    if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
-        ereport(ERROR,
-                (errcode(ERRCODE_DATATYPE_MISMATCH),
-          errmsg("structure of query does not match function result type")));
+    validate_tupdesc_compat(portal->tupDesc, estate->rettupdesc);

     while (true)
     {
@@ -5138,23 +5130,32 @@
 }

 /*
- * Check two tupledescs have matching number and types of attributes
+ * Validates compatibility of supplied TupleDesc's by checking # and type of
+ * available arguments.
  */
-static bool
-compatible_tupdesc(TupleDesc td1, TupleDesc td2)
+static void
+validate_tupdesc_compat(TupleDesc td1, TupleDesc td2)
 {
-    int            i;
+    int i;

     if (td1->natts != td2->natts)
-        return false;
+        ereport(ERROR,
+                (errcode(ERRCODE_DATATYPE_MISMATCH),
+                 errmsg("Number of returned columns (%d) does not match "
+                        "expected column count (%d).",
+                        td1->natts, td2->natts)));

     for (i = 0; i < td1->natts; i++)
-    {
         if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
-            return false;
-    }
-
-    return true;
+            ereport(ERROR,
+                    (errcode(ERRCODE_DATATYPE_MISMATCH),
+                     errmsg("Returned record type (%s) does not match "
+                            "expected record type (%s) in column %d (%s).",
+                            format_type_with_typemod(td1->attrs[i]->atttypid,
+                                                     td1->attrs[i]->atttypmod),
+                            format_type_with_typemod(td2->attrs[i]->atttypid,
+                                                     td2->attrs[i]->atttypmod),
+                            (1+i), NameStr(td2->attrs[i]->attname))));
 }

 /* ----------

Re: Verbosity of Function Return Type Checks

From
Alvaro Herrera
Date:
Volkan YAZICI wrote:

> Yesterday I needed to fiddle with PostgreSQL internals to be able to
> debug a PL/pgSQL procedure returning a set of records. I attached the
> patch I used to increase the verbosity of error messages related with
> function return type checks. I'll be appreciated if any developer could
> commit this patch (or a similar one) into the core.

I think this is a good idea, but the new error messages need more work.
Have a look at the message style guidelines please,
http://www.postgresql.org/docs/8.3/static/error-style-guide.html

Particularly I think you need to keep the original errmsg() and add the
new messages as errdetail().  (I notice that there's the slight problem
that the error messages are different for the different callers.)

Also, please use context diffs.

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support


Re: Verbosity of Function Return Type Checks

From
Volkan YAZICI
Date:
On Fri, 8 Aug 2008, Alvaro Herrera <alvherre@commandprompt.com> writes:
> I think this is a good idea, but the new error messages need more work.
> Have a look at the message style guidelines please,
> http://www.postgresql.org/docs/8.3/static/error-style-guide.html

Right. Done -- I hope.

> Particularly I think you need to keep the original errmsg() and add the
> new messages as errdetail().  (I notice that there's the slight problem
> that the error messages are different for the different callers.)

Done.

> Also, please use context diffs.

Done.


Regards.

Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.216
diff -r1.216 pl_exec.c
193c193
< static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
---
> static void validate_tupdesc_compat(TupleDesc td1, TupleDesc td2);
389,393c389
<                     if (estate.rettupdesc == NULL ||
<                         !compatible_tupdesc(estate.rettupdesc, tupdesc))
<                         ereport(ERROR,
<                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
<                                  errmsg("returned record type does not match expected record type")));
---
>                     validate_tupdesc_compat(tupdesc, estate.rettupdesc);
710,714c706,707
<         if (!compatible_tupdesc(estate.rettupdesc,
<                                 trigdata->tg_relation->rd_att))
<             ereport(ERROR,
<                     (errcode(ERRCODE_DATATYPE_MISMATCH),
<                      errmsg("returned tuple structure does not match table of trigger event")));
---
>         validate_tupdesc_compat(trigdata->tg_relation->rd_att,
>                                 estate.rettupdesc);
2204,2208c2197,2199
<                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
<                     if (!compatible_tupdesc(tupdesc, rec->tupdesc))
<                         ereport(ERROR,
<                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
<                         errmsg("wrong record type supplied in RETURN NEXT")));
---
>                            errdetail("The tuple structure of a not-yet-assigned"
>                                      " record is indeterminate.")));
>                     validate_tupdesc_compat(rec->tupdesc, tupdesc);
2314,2317c2305
<     if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
<         ereport(ERROR,
<                 (errcode(ERRCODE_DATATYPE_MISMATCH),
<           errmsg("structure of query does not match function result type")));
---
>     validate_tupdesc_compat(portal->tupDesc, estate->rettupdesc);
5141c5129,5130
<  * Check two tupledescs have matching number and types of attributes
---
>  * Validates compatibility of supplied TupleDesc couple by checking # and type
>  * of available arguments.
5143,5144c5132,5133
< static bool
< compatible_tupdesc(TupleDesc td1, TupleDesc td2)
---
> static void
> validate_tupdesc_compat(TupleDesc td1, TupleDesc td2)
5146c5135,5141
<     int            i;
---
>     int i;
>
>     if (!td1 || !td2)
>         ereport(ERROR,
>                 (errcode(ERRCODE_DATATYPE_MISMATCH),
>                  errmsg("returned record type does not match expected "
>                         "record type")));
5149c5144,5150
<         return false;
---
>         ereport(ERROR,
>                 (errcode(ERRCODE_DATATYPE_MISMATCH),
>                  errmsg("returned record type does not match expected "
>                         "record type"),
>                  errdetail("Number of returned columns (%d) does not match "
>                            "expected column count (%d).",
>                            td1->natts, td2->natts)));
5152d5152
<     {
5154,5157c5154,5164
<             return false;
<     }
<
<     return true;
---
>             ereport(ERROR,
>                     (errcode(ERRCODE_DATATYPE_MISMATCH),
>                      errmsg("returned record type does not match expected "
>                             "record type"),
>                      errdetail("Returned record type (%s) does not match "
>                                "expected record type (%s) in column %d (%s).",
>                                format_type_with_typemod(td1->attrs[i]->atttypid,
>                                                         td1->attrs[i]->atttypmod),
>                                format_type_with_typemod(td2->attrs[i]->atttypid,
>                                                         td2->attrs[i]->atttypmod),
>                                (1+i), NameStr(td2->attrs[i]->attname))));

Re: Verbosity of Function Return Type Checks

From
Volkan YAZICI
Date:
[Please ignore the previous reply.]

On Fri, 8 Aug 2008, Alvaro Herrera <alvherre@commandprompt.com> writes:
> I think this is a good idea, but the new error messages need more work.
> Have a look at the message style guidelines please,
> http://www.postgresql.org/docs/8.3/static/error-style-guide.html

Right. Done -- I hope.

> Particularly I think you need to keep the original errmsg() and add the
> new messages as errdetail().

Made callers pass related error message as a string parameter, and
appended required details using errdetail().

> (I notice that there's the slight problem
> that the error messages are different for the different callers.)

Above mentioned change should have addressed this issue too.

> Also, please use context diffs.

Done.


Regards.

Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.216
diff -c -r1.216 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c    16 May 2008 18:34:51 -0000    1.216
--- src/pl/plpgsql/src/pl_exec.c    9 Aug 2008 10:10:32 -0000
***************
*** 190,196 ****
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
--- 190,196 ----
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static void validate_tupdesc_compat(TupleDesc td1, TupleDesc td2, char *msg);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
***************
*** 386,396 ****
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     if (estate.rettupdesc == NULL ||
!                         !compatible_tupdesc(estate.rettupdesc, tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                                  errmsg("returned record type does not match expected record type")));
                      break;
                  case TYPEFUNC_RECORD:

--- 386,394 ----
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     validate_tupdesc_compat(tupdesc, estate.rettupdesc,
!                                             "returned record type does not "
!                                             "match expected record type");
                      break;
                  case TYPEFUNC_RECORD:

***************
*** 707,717 ****
          rettup = NULL;
      else
      {
!         if (!compatible_tupdesc(estate.rettupdesc,
!                                 trigdata->tg_relation->rd_att))
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg("returned tuple structure does not match table of trigger event")));
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
--- 705,714 ----
          rettup = NULL;
      else
      {
!         validate_tupdesc_compat(trigdata->tg_relation->rd_att,
!                                 estate.rettupdesc,
!                                 "returned tuple structure does not match "
!                                 "table of trigger event");
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
***************
*** 2201,2211 ****
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
!                     if (!compatible_tupdesc(tupdesc, rec->tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                         errmsg("wrong record type supplied in RETURN NEXT")));
                      tuple = rec->tup;
                  }
                  break;
--- 2198,2208 ----
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned"
!                                      " record is indeterminate.")));
!                     validate_tupdesc_compat(rec->tupdesc, tupdesc,
!                                             "wrong record type supplied in "
!                                             "RETURN NEXT");
                      tuple = rec->tup;
                  }
                  break;
***************
*** 2311,2320 ****
                                             stmt->params);
      }

!     if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!           errmsg("structure of query does not match function result type")));

      while (true)
      {
--- 2308,2316 ----
                                             stmt->params);
      }

!     validate_tupdesc_compat(portal->tupDesc, estate->rettupdesc,
!                             "structure of query does not match function "
!                             "result type");

      while (true)
      {
***************
*** 5138,5160 ****
  }

  /*
!  * Check two tupledescs have matching number and types of attributes
   */
! static bool
! compatible_tupdesc(TupleDesc td1, TupleDesc td2)
  {
!     int            i;

      if (td1->natts != td2->natts)
!         return false;

      for (i = 0; i < td1->natts; i++)
-     {
          if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
!             return false;
!     }
!
!     return true;
  }

  /* ----------
--- 5134,5170 ----
  }

  /*
!  * Validates compatibility of supplied TupleDesc couple by checking # and type
!  * of available arguments.
   */
! static void
! validate_tupdesc_compat(TupleDesc td1, TupleDesc td2, char *msg)
  {
!     int i;
!
!     if (!td1 || !td2)
!         ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg(msg)));

      if (td1->natts != td2->natts)
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                  errmsg(msg),
!                  errdetail("Number of returned columns (%d) does not match "
!                            "expected column count (%d).",
!                            td1->natts, td2->natts)));

      for (i = 0; i < td1->natts; i++)
          if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg(msg),
!                      errdetail("Returned record type (%s) does not match "
!                                "expected record type (%s) in column %d (%s).",
!                                format_type_with_typemod(td1->attrs[i]->atttypid,
!                                                         td1->attrs[i]->atttypmod),
!                                format_type_with_typemod(td2->attrs[i]->atttypid,
!                                                         td2->attrs[i]->atttypmod),
!                                (1+i), NameStr(td2->attrs[i]->attname))));
  }

  /* ----------

Re: Verbosity of Function Return Type Checks

From
Alvaro Herrera
Date:
Volkan YAZICI wrote:

> Made callers pass related error message as a string parameter, and
> appended required details using errdetail().

Cool, thanks.  I had a look and you had some of the expected vs.
returned reversed.  This patch should be OK.  Amazingly, none of the
regression tests need changing, which is a bit worrisome.

I wasn't able to run the tests in contrib, I don't know why, and I have
to go out now.  I'll commit this tomorrow.

--
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachment

Re: Verbosity of Function Return Type Checks

From
Tom Lane
Date:
Alvaro Herrera <alvherre@commandprompt.com> writes:
> I wasn't able to run the tests in contrib, I don't know why, and I have
> to go out now.  I'll commit this tomorrow.

This is not ready to go: you've lost the ability to localize most of the
error message strings.  Also, "char *msg" should be "const char *msg"
if you're going to pass literal constants to it, and this gives me
the willies even though the passed-in strings are supposedly all fixed:    errmsg(msg),
Use    errmsg("%s", msg),
to be safe.

Actually, the entire concept of varying the main message to suit the
context exactly, while the detail messages are *not* changing, seems
pretty bogus...

Another problem with it is it's likely going to fail completely on
dropped columns (which will have atttypid = 0).
        regards, tom lane


Re: Verbosity of Function Return Type Checks

From
Volkan YAZICI
Date:
On Thu, 4 Sep 2008, Alvaro Herrera <alvherre@commandprompt.com> writes:
> Cool, thanks.  I had a look and you had some of the expected vs.
> returned reversed.

I'll happy to fix the reversed ones if you can report them in more
details.


Regards.


Re: Verbosity of Function Return Type Checks

From
Volkan YAZICI
Date:
On Thu, 04 Sep 2008, Tom Lane <tgl@sss.pgh.pa.us> writes:
> This is not ready to go: you've lost the ability to localize most of
> the error message strings.

How can I make this available? What's your suggestion?

> Also, "char *msg" should be "const char *msg"

Done.

> if you're going to pass literal constants to it, and this gives me the
> willies even though the passed-in strings are supposedly all fixed:
>         errmsg(msg),
> Use
>         errmsg("%s", msg),
> to be safe.

Done.

> Actually, the entire concept of varying the main message to suit the
> context exactly, while the detail messages are *not* changing, seems
> pretty bogus...

I share your concerns but couldn't come up with a better approach. I'd
be happy to hear your suggestions.

> Another problem with it is it's likely going to fail completely on
> dropped columns (which will have atttypid = 0).

Is it ok if I'd replace

  if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)

line in validate_tupdesc_compat with

  if (td1->attrs[i]->atttypid &&
      td2->attrs[i]->atttypid &&
      td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)

expression to fix this?


Regards.

Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.219
diff -c -r1.219 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c    1 Sep 2008 22:30:33 -0000    1.219
--- src/pl/plpgsql/src/pl_exec.c    5 Sep 2008 06:13:18 -0000
***************
*** 188,194 ****
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
--- 188,195 ----
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static void validate_tupdesc_compat(TupleDesc td1, TupleDesc td2,
!                                     const char *msg);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
***************
*** 384,394 ****
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     if (estate.rettupdesc == NULL ||
!                         !compatible_tupdesc(estate.rettupdesc, tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                                  errmsg("returned record type does not match expected record type")));
                      break;
                  case TYPEFUNC_RECORD:

--- 385,393 ----
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     validate_tupdesc_compat(tupdesc, estate.rettupdesc,
!                                             "returned record type does not "
!                                             "match expected record type");
                      break;
                  case TYPEFUNC_RECORD:

***************
*** 705,715 ****
          rettup = NULL;
      else
      {
!         if (!compatible_tupdesc(estate.rettupdesc,
!                                 trigdata->tg_relation->rd_att))
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg("returned tuple structure does not match table of trigger event")));
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
--- 704,713 ----
          rettup = NULL;
      else
      {
!         validate_tupdesc_compat(trigdata->tg_relation->rd_att,
!                                 estate.rettupdesc,
!                                 "returned tuple structure does not match "
!                                 "table of trigger event");
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
***************
*** 2199,2209 ****
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
!                     if (!compatible_tupdesc(tupdesc, rec->tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                         errmsg("wrong record type supplied in RETURN NEXT")));
                      tuple = rec->tup;
                  }
                  break;
--- 2197,2207 ----
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned"
!                                      " record is indeterminate.")));
!                     validate_tupdesc_compat(rec->tupdesc, tupdesc,
!                                             "wrong record type supplied in "
!                                             "RETURN NEXT");
                      tuple = rec->tup;
                  }
                  break;
***************
*** 2309,2318 ****
                                             stmt->params);
      }

!     if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!           errmsg("structure of query does not match function result type")));

      while (true)
      {
--- 2307,2315 ----
                                             stmt->params);
      }

!     validate_tupdesc_compat(portal->tupDesc, estate->rettupdesc,
!                             "structure of query does not match function "
!                             "result type");

      while (true)
      {
***************
*** 5145,5167 ****
  }

  /*
!  * Check two tupledescs have matching number and types of attributes
   */
! static bool
! compatible_tupdesc(TupleDesc td1, TupleDesc td2)
  {
!     int            i;

      if (td1->natts != td2->natts)
!         return false;

      for (i = 0; i < td1->natts; i++)
-     {
          if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
!             return false;
!     }
!
!     return true;
  }

  /* ----------
--- 5142,5178 ----
  }

  /*
!  * Validates compatibility of supplied TupleDesc couple by checking # and type
!  * of available arguments.
   */
! static void
! validate_tupdesc_compat(TupleDesc td1, TupleDesc td2, const char *msg)
  {
!     int i;
!
!     if (!td1 || !td2)
!         ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg(msg)));

      if (td1->natts != td2->natts)
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                  errmsg("%s", msg),
!                  errdetail("Number of returned columns (%d) does not match "
!                            "expected column count (%d).",
!                            td1->natts, td2->natts)));

      for (i = 0; i < td1->natts; i++)
          if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg("%s", msg),
!                      errdetail("Returned record type (%s) does not match "
!                                "expected record type (%s) in column %d (%s).",
!                                format_type_with_typemod(td1->attrs[i]->atttypid,
!                                                         td1->attrs[i]->atttypmod),
!                                format_type_with_typemod(td2->attrs[i]->atttypid,
!                                                         td2->attrs[i]->atttypmod),
!                                (1+i), NameStr(td2->attrs[i]->attname))));
  }

  /* ----------

Re: Verbosity of Function Return Type Checks

From
Alvaro Herrera
Date:
Volkan YAZICI wrote:
> On Thu, 4 Sep 2008, Alvaro Herrera <alvherre@commandprompt.com> writes:
> > Cool, thanks.  I had a look and you had some of the expected vs.
> > returned reversed.
> 
> I'll happy to fix the reversed ones if you can report them in more
> details.

Please use the patch I posted yesterday, as it had all the issues I
found fixed.  There were other changes in that patch too.

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support


Re: Verbosity of Function Return Type Checks

From
Tom Lane
Date:
Volkan YAZICI <yazicivo@ttmail.com> writes:
> On Thu, 04 Sep 2008, Tom Lane <tgl@sss.pgh.pa.us> writes:
>> This is not ready to go: you've lost the ability to localize most of
>> the error message strings.

> How can I make this available? What's your suggestion?

I think the best way is to use
subroutine(..., gettext_noop("special error message here"))

at the call sites, and then
errmsg("%s", _(msg))

when throwing the error.  gettext_noop() is needed to have the string
be put into the message catalog, but it doesn't represent any run-time
effort.  The _() macro is what actually makes the translation lookup
happen.  We don't want to incur that cost in the normal no-error case,
which is why you shouldn't just do _() at the call site and pass an
already-translated string to the subroutine.

>> Another problem with it is it's likely going to fail completely on
>> dropped columns (which will have atttypid = 0).

> Is it ok if I'd replace

>   if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)

> line in validate_tupdesc_compat with

>   if (td1->attrs[i]->atttypid &&
>       td2->attrs[i]->atttypid &&
>       td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)

> expression to fix this?

No, that's weakening the compatibility check.  (There's a separate issue
here of teaching plpgsql to actually cope nicely with rowtypes
containing dropped columns, but that's well beyond the scope of this
patch.)

What I'm on about is protecting format_type_be() from being passed
a zero and then failing.  Perhaps it would be good enough to do
something like
OidIsValid(td1->attrs[i]->atttypid) ?       format_type_with_typemod(td1->attrs[i]->atttypid,
td1->attrs[i]->atttypmod):       "dropped column"
 

while throwing the error.

BTW, since what's actually being looked at is just the type OID and not
the typmod, it seems inappropriate to me to show the typmod in the
error.  I'd go with just format_type_be(td1->attrs[i]->atttypid) here
I think.
        regards, tom lane


Re: Verbosity of Function Return Type Checks

From
Volkan YAZICI
Date:
On Fri, 05 Sep 2008, Tom Lane <tgl@sss.pgh.pa.us> writes:
> I think the best way is to use
>
>     subroutine(..., gettext_noop("special error message here"))
>
> at the call sites, and then
>
>     errmsg("%s", _(msg))
>
> when throwing the error.  gettext_noop() is needed to have the string
> be put into the message catalog, but it doesn't represent any run-time
> effort.  The _() macro is what actually makes the translation lookup
> happen.  We don't want to incur that cost in the normal no-error case,
> which is why you shouldn't just do _() at the call site and pass an
> already-translated string to the subroutine.

Modified as you suggested. BTW, will there be a similar i18n scenario
for "dropped column" you mentioned below?

>>   if (td1->attrs[i]->atttypid &&
>>       td2->attrs[i]->atttypid &&
>>       td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
>
>> expression to fix this?
>
> No, that's weakening the compatibility check.  (There's a separate issue
> here of teaching plpgsql to actually cope nicely with rowtypes
> containing dropped columns, but that's well beyond the scope of this
> patch.)
>
> What I'm on about is protecting format_type_be() from being passed
> a zero and then failing.  Perhaps it would be good enough to do
> something like
>
>     OidIsValid(td1->attrs[i]->atttypid) ?
>            format_type_with_typemod(td1->attrs[i]->atttypid,
>                         td1->attrs[i]->atttypmod) :
>            "dropped column"
>
> while throwing the error.
>
> BTW, since what's actually being looked at is just the type OID and not
> the typmod, it seems inappropriate to me to show the typmod in the
> error.  I'd go with just format_type_be(td1->attrs[i]->atttypid) here
> I think.

Done with format_type_be() usage.

BTW, Alvaro fixed my string concatenations which yielded in lines
exceeding 80 characters width, but I'd want to ask twice if you're sure
with this. Because, IMHO, PostgreSQL is also famous with the quality and
readability of its source code -- that I'm quite proud of as a user,
kudos to developers -- and I think it'd be better to stick with 80
characters width convention as much as one can.


Regards.

Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.219
diff -c -r1.219 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c    1 Sep 2008 22:30:33 -0000    1.219
--- src/pl/plpgsql/src/pl_exec.c    5 Sep 2008 18:19:50 -0000
***************
*** 188,194 ****
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
--- 188,195 ----
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
!                                     const char *msg);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
***************
*** 384,394 ****
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     if (estate.rettupdesc == NULL ||
!                         !compatible_tupdesc(estate.rettupdesc, tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                                  errmsg("returned record type does not match expected record type")));
                      break;
                  case TYPEFUNC_RECORD:

--- 385,392 ----
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     validate_tupdesc_compat(tupdesc, estate.rettupdesc,
!                                             gettext_noop("returned record type does not match expected record
type"));
                      break;
                  case TYPEFUNC_RECORD:

***************
*** 705,715 ****
          rettup = NULL;
      else
      {
!         if (!compatible_tupdesc(estate.rettupdesc,
!                                 trigdata->tg_relation->rd_att))
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg("returned tuple structure does not match table of trigger event")));
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
--- 703,711 ----
          rettup = NULL;
      else
      {
!         validate_tupdesc_compat(trigdata->tg_relation->rd_att,
!                                 estate.rettupdesc,
!                                 gettext_noop("returned tuple structure does not match table of trigger event"));
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
***************
*** 2199,2209 ****
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
!                     if (!compatible_tupdesc(tupdesc, rec->tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                         errmsg("wrong record type supplied in RETURN NEXT")));
                      tuple = rec->tup;
                  }
                  break;
--- 2195,2204 ----
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned"
!                                      " record is indeterminate.")));
!                     validate_tupdesc_compat(tupdesc, rec->tupdesc,
!                                             gettext_noop("wrong record type supplied in RETURN NEXT"));
                      tuple = rec->tup;
                  }
                  break;
***************
*** 2309,2318 ****
                                             stmt->params);
      }

!     if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!           errmsg("structure of query does not match function result type")));

      while (true)
      {
--- 2304,2311 ----
                                             stmt->params);
      }

!     validate_tupdesc_compat(estate->rettupdesc, portal->tupDesc,
!                             gettext_noop("structure of query does not match function result type"));

      while (true)
      {
***************
*** 5145,5167 ****
  }

  /*
!  * Check two tupledescs have matching number and types of attributes
   */
! static bool
! compatible_tupdesc(TupleDesc td1, TupleDesc td2)
  {
!     int            i;

!     if (td1->natts != td2->natts)
!         return false;

!     for (i = 0; i < td1->natts; i++)
!     {
!         if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
!             return false;
!     }

!     return true;
  }

  /* ----------
--- 5138,5176 ----
  }

  /*
!  * Validates compatibility of supplied TupleDesc pair by checking number and type
!  * of attributes.
   */
! static void
! validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, const char *msg)
  {
!     int        i;

!     if (!expected || !returned)
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                  errmsg("%s", _(msg))));

!     if (expected->natts != returned->natts)
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                  errmsg("%s", _(msg)),
!                  errdetail("Number of returned columns (%d) does not match expected column count (%d).",
!                            returned->natts, expected->natts)));

!     for (i = 0; i < expected->natts; i++)
!         if (expected->attrs[i]->atttypid != returned->attrs[i]->atttypid)
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg("%s", _(msg)),
!                      errdetail("Returned type \"%s\" does not match expected type \"%s\" in column \"%s\".",
!                                OidIsValid(returned->attrs[i]->atttypid)
!                                ? format_type_be(returned->attrs[i]->atttypid)
!                                : "dropped column",
!                                OidIsValid(expected->attrs[i]->atttypid)
!                                ? format_type_be(expected->attrs[i]->atttypid)
!                                : "dropped column",
!                                NameStr(expected->attrs[i]->attname))));
  }

  /* ----------

Re: Verbosity of Function Return Type Checks

From
Alvaro Herrera
Date:
Volkan YAZICI wrote:

> BTW, Alvaro fixed my string concatenations which yielded in lines
> exceeding 80 characters width, but I'd want to ask twice if you're sure
> with this. Because, IMHO, PostgreSQL is also famous with the quality and
> readability of its source code -- that I'm quite proud of as a user,
> kudos to developers -- and I think it'd be better to stick with 80
> characters width convention as much as one can.

Yeah, I'm quite anal about that.  What will happen is that pgindent will
"push back" the strings so that they start earlier in the line to keep
the 80 char limit.  IMHO that's actually clearer than truncating the
string.

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
The PostgreSQL Company - Command Prompt, Inc.


Re: Verbosity of Function Return Type Checks

From
Alvaro Herrera
Date:
Volkan YAZICI wrote:
> On Fri, 05 Sep 2008, Tom Lane <tgl@sss.pgh.pa.us> writes:

> > at the call sites, and then
> >
> >     errmsg("%s", _(msg))
> >
> > when throwing the error.
> 
> Modified as you suggested. BTW, will there be a similar i18n scenario
> for "dropped column" you mentioned below?

Yes, you need _() around those too.

> Done with format_type_be() usage.

BTW you forgot to remove the quotes around the type names (I know I told
you to add them but Tom gave the reasons why it's not needed) :-)

Those are minor problems that are easily fixed.  However there's a
larger issue that Tom mentioned earlier and I concur, which is that the
caller is forming the primary error message and passing it down.  It
gets a bit silly if you consider the ways the messages end up worded:
  errmsg("returned record type does not match expected record type"));  errdetail("Returned type \"%s\" does not match
expectedtype \"%s\" in column \"%s\".",  ---> this is the case where it's OK
 
  errmsg("wrong record type supplied in RETURN NEXT"));  errdetail("Returned type \"%s\" does not match expected type
\"%s\"in column \"%s\".",  --> this is strange
 
  errmsg("returned tuple structure does not match table of trigger event"));  errdetail("Returned type \"%s\" does not
matchexpected type \"%s\" in column \"%s\".",  --> this is not OK
 

I've been thinking how to pass down the context information without
feeding the complete string, but I don't find a way without doing
message construction.  Construction is to be avoided because it's a pain
for translators.

Maybe we should just use something generic like errmsg("mismatching record type") 
and have the caller pass two strings specifying what's the "returned"
tuple and what's the "expected", but I don't see how ...  (BTW this is
worth fixing, because every case seems to have appeared independently
without much thought as to other callsites with the same pattern.)

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support


Re: Verbosity of Function Return Type Checks

From
Volkan YAZICI
Date:
On Mon, 8 Sep 2008, Alvaro Herrera <alvherre@commandprompt.com> writes:
>> Modified as you suggested. BTW, will there be a similar i18n scenario
>> for "dropped column" you mentioned below?
>
> Yes, you need _() around those too.

For this purpose, I introduced a dropped_column_type variable in
validate_tupdesc_compat() function:

  const char dropped_column_type[] = _("n/a (dropped column)");

And used this in below fashion:

  OidIsValid(returned->attrs[i]->atttypid)
  ? format_type_be(returned->attrs[i]->atttypid)
  : dropped_column_type

>> Done with format_type_be() usage.
>
> BTW you forgot to remove the quotes around the type names (I know I told
> you to add them but Tom gave the reasons why it's not needed) :-)

Done.

> Those are minor problems that are easily fixed.  However there's a
> larger issue that Tom mentioned earlier and I concur, which is that the
> caller is forming the primary error message and passing it down.  It
> gets a bit silly if you consider the ways the messages end up worded:
>
>    errmsg("returned record type does not match expected record type"));
>    errdetail("Returned type \"%s\" does not match expected type \"%s\" in column \"%s\".",
>    ---> this is the case where it's OK
>
>    errmsg("wrong record type supplied in RETURN NEXT"));
>    errdetail("Returned type \"%s\" does not match expected type \"%s\" in column \"%s\".",
>    --> this is strange
>
>    errmsg("returned tuple structure does not match table of trigger event"));
>    errdetail("Returned type \"%s\" does not match expected type \"%s\" in column \"%s\".",
>    --> this is not OK

What we're trying to do is to avoid code duplication while checking the
returned tuple types against expected tuple types. For this purpose we
implemented a generic function (validate_tupdesc_compat) to handle all
possible cases. But, IMHO, it's important to remember that there is no
perfect generic function to satisfy all possible cases at best.

> I've been thinking how to pass down the context information without
> feeding the complete string, but I don't find a way without doing
> message construction. Construction is to be avoided because it's a
> pain for translators.
>
> Maybe we should just use something generic like errmsg("mismatching
> record type") and have the caller pass two strings specifying what's
> the "returned" tuple and what's the "expected", but I don't see how
> ...  (BTW this is worth fixing, because every case seems to have
> appeared independently without much thought as to other callsites with
> the same pattern.)

I considered the subject with identical constraints but couldn't come up
with a more rational solution. Nevertheless, I'm open to any suggestion.


Regards.

Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.219
diff -c -r1.219 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c    1 Sep 2008 22:30:33 -0000    1.219
--- src/pl/plpgsql/src/pl_exec.c    9 Sep 2008 05:48:57 -0000
***************
*** 188,194 ****
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
--- 188,195 ----
                         Oid reqtype, int32 reqtypmod,
                         bool isnull);
  static void exec_init_tuple_store(PLpgSQL_execstate *estate);
! static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
!                                     const char *msg);
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void free_var(PLpgSQL_var *var);
***************
*** 384,394 ****
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     if (estate.rettupdesc == NULL ||
!                         !compatible_tupdesc(estate.rettupdesc, tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                                  errmsg("returned record type does not match expected record type")));
                      break;
                  case TYPEFUNC_RECORD:

--- 385,392 ----
              {
                  case TYPEFUNC_COMPOSITE:
                      /* got the expected result rowtype, now check it */
!                     validate_tupdesc_compat(tupdesc, estate.rettupdesc,
!                                             gettext_noop("returned record type does not match expected record
type"));
                      break;
                  case TYPEFUNC_RECORD:

***************
*** 705,715 ****
          rettup = NULL;
      else
      {
!         if (!compatible_tupdesc(estate.rettupdesc,
!                                 trigdata->tg_relation->rd_att))
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg("returned tuple structure does not match table of trigger event")));
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
--- 703,711 ----
          rettup = NULL;
      else
      {
!         validate_tupdesc_compat(trigdata->tg_relation->rd_att,
!                                 estate.rettupdesc,
!                                 gettext_noop("returned tuple structure does not match table of trigger event"));
          /* Copy tuple to upper executor memory */
          rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
      }
***************
*** 2199,2209 ****
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
!                     if (!compatible_tupdesc(tupdesc, rec->tupdesc))
!                         ereport(ERROR,
!                                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                         errmsg("wrong record type supplied in RETURN NEXT")));
                      tuple = rec->tup;
                  }
                  break;
--- 2195,2204 ----
                            (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
                             errmsg("record \"%s\" is not assigned yet",
                                    rec->refname),
!                            errdetail("The tuple structure of a not-yet-assigned"
!                                      " record is indeterminate.")));
!                     validate_tupdesc_compat(tupdesc, rec->tupdesc,
!                                             gettext_noop("wrong record type supplied in RETURN NEXT"));
                      tuple = rec->tup;
                  }
                  break;
***************
*** 2309,2318 ****
                                             stmt->params);
      }

!     if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!           errmsg("structure of query does not match function result type")));

      while (true)
      {
--- 2304,2311 ----
                                             stmt->params);
      }

!     validate_tupdesc_compat(estate->rettupdesc, portal->tupDesc,
!                             gettext_noop("structure of query does not match function result type"));

      while (true)
      {
***************
*** 5145,5167 ****
  }

  /*
!  * Check two tupledescs have matching number and types of attributes
   */
! static bool
! compatible_tupdesc(TupleDesc td1, TupleDesc td2)
  {
!     int            i;

!     if (td1->natts != td2->natts)
!         return false;

!     for (i = 0; i < td1->natts; i++)
!     {
!         if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
!             return false;
!     }

!     return true;
  }

  /* ----------
--- 5138,5177 ----
  }

  /*
!  * Validates compatibility of supplied TupleDesc pair by checking number and type
!  * of attributes.
   */
! static void
! validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, const char *msg)
  {
!     int           i;
!     const char dropped_column_type[] = _("n/a (dropped column)");

!     if (!expected || !returned)
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                  errmsg("%s", _(msg))));

!     if (expected->natts != returned->natts)
!         ereport(ERROR,
!                 (errcode(ERRCODE_DATATYPE_MISMATCH),
!                  errmsg("%s", _(msg)),
!                  errdetail("Number of returned columns (%d) does not match expected column count (%d).",
!                            returned->natts, expected->natts)));

!     for (i = 0; i < expected->natts; i++)
!         if (expected->attrs[i]->atttypid != returned->attrs[i]->atttypid)
!             ereport(ERROR,
!                     (errcode(ERRCODE_DATATYPE_MISMATCH),
!                      errmsg("%s", _(msg)),
!                      errdetail("Returned type %s does not match expected type %s in column %s.",
!                                OidIsValid(returned->attrs[i]->atttypid)
!                                ? format_type_be(returned->attrs[i]->atttypid)
!                                : dropped_column_type,
!                                OidIsValid(expected->attrs[i]->atttypid)
!                                ? format_type_be(expected->attrs[i]->atttypid)
!                                : dropped_column_type,
!                                NameStr(expected->attrs[i]->attname))));
  }

  /* ----------

Re: Verbosity of Function Return Type Checks

From
Alvaro Herrera
Date:
Volkan YAZICI wrote:
> On Mon, 8 Sep 2008, Alvaro Herrera <alvherre@commandprompt.com> writes:
> >> Modified as you suggested. BTW, will there be a similar i18n scenario
> >> for "dropped column" you mentioned below?
> >
> > Yes, you need _() around those too.
> 
> For this purpose, I introduced a dropped_column_type variable in
> validate_tupdesc_compat() function:
> 
>   const char dropped_column_type[] = _("n/a (dropped column)");
> 
> And used this in below fashion:
> 
>   OidIsValid(returned->attrs[i]->atttypid)
>   ? format_type_be(returned->attrs[i]->atttypid)
>   : dropped_column_type

I changed it to gettext_noop("the text") and _(dropped_column_type) in
errdetail, and committed it.

I'd still like to have a better way to word the message, and maybe have
this error appear in a regression test somewhere at least once ...

-- 
Alvaro Herrera                                http://www.CommandPrompt.com/
PostgreSQL Replication, Consulting, Custom Development, 24x7 support