Improve wrong-tuple-type error reports in contrib/tablefunc.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 9 Mar 2024 20:48:21 +0000 (15:48 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 9 Mar 2024 20:48:21 +0000 (15:48 -0500)
These messages were fairly confusing, and didn't match the
column names used in the SGML docs.  Try to improve that.
Also use error codes more specific than ERRCODE_SYNTAX_ERROR.

Patch by me, reviewed by Joe Conway

Discussion: https://postgr.es/m/18937.1709676295@sss.pgh.pa.us

contrib/tablefunc/expected/tablefunc.out
contrib/tablefunc/sql/tablefunc.sql
contrib/tablefunc/tablefunc.c

index 464c210f42fd11b379c6e9adff5802df6de21830..ddece79029a3ad398cf41855b54f6b8d0cae851e 100644 (file)
@@ -145,6 +145,23 @@ SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass
        | val9 | val10 | val11
 (3 rows)
 
+-- check error reporting
+SELECT * FROM crosstab('SELECT rowid, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name text, category_1 text, category_2 text);
+ERROR:  invalid crosstab source data query
+DETAIL:  The query must return 3 columns: row_name, category, and value.
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name text);
+ERROR:  invalid crosstab return type
+DETAIL:  Return row must have at least two columns.
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name int, category_1 text, category_2 text);
+ERROR:  invalid crosstab return type
+DETAIL:  Source row_name datatype text does not match return row_name datatype integer.
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name text, category_1 text, category_2 int);
+ERROR:  invalid crosstab return type
+DETAIL:  Source value datatype text does not match return value datatype integer in column 3.
 --
 -- hash based crosstab
 --
@@ -216,13 +233,20 @@ SELECT * FROM crosstab(
   'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
   'SELECT DISTINCT attribute FROM cth WHERE attribute = ''a'' ORDER BY 1')
 AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
-ERROR:  provided "categories" SQL must return 1 column of at least one row
+ERROR:  crosstab categories query must return at least one row
 -- if category query generates more than one column, get expected error
 SELECT * FROM crosstab(
   'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
   'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2')
 AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
-ERROR:  provided "categories" SQL must return 1 column of at least one row
+ERROR:  invalid crosstab categories query
+DETAIL:  The query must return one column.
+-- if category query generates a NULL value, get expected error
+SELECT * FROM crosstab(
+  'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
+  'SELECT NULL::text')
+AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
+ERROR:  crosstab category value must not be null
 -- if source query returns zero rows, get zero rows returned
 SELECT * FROM crosstab(
   'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1',
@@ -241,6 +265,26 @@ AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_start
 -------+-------+-------------+-------------+----------------+-------
 (0 rows)
 
+-- check errors with inappropriate input rowtype
+SELECT * FROM crosstab(
+  'SELECT rowid, attribute FROM cth ORDER BY 1',
+  'SELECT DISTINCT attribute FROM cth ORDER BY 1')
+AS c(rowid text, temperature text, test_result text, test_startdate text, volts text);
+ERROR:  invalid crosstab source data query
+DETAIL:  The query must return at least 3 columns: row_name, category, and value.
+SELECT * FROM crosstab(
+  'SELECT rowid, rowdt, rowdt, attribute, val FROM cth ORDER BY 1',
+  'SELECT DISTINCT attribute FROM cth ORDER BY 1')
+AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
+ERROR:  invalid crosstab return type
+DETAIL:  Return row must have 7 columns, not 6.
+-- check errors with inappropriate result rowtype
+SELECT * FROM crosstab(
+  'SELECT rowid, attribute, val FROM cth ORDER BY 1',
+  'SELECT DISTINCT attribute FROM cth ORDER BY 1')
+AS c(rowid text);
+ERROR:  invalid crosstab return type
+DETAIL:  Return row must have at least two columns.
 -- check it works with a named result rowtype
 create type my_crosstab_result as (
   rowid text, rowdt timestamp,
@@ -381,17 +425,42 @@ SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 4, '~') A
 
 -- should fail as first two columns must have the same type
 SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid text, parent_keyid int, level int, branch text);
-ERROR:  invalid return type
-DETAIL:  First two columns must be the same type.
+ERROR:  invalid connectby return type
+DETAIL:  Source key type integer does not match return key type text.
 -- should fail as key field datatype should match return datatype
 SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8, level int, branch text);
-ERROR:  invalid return type
-DETAIL:  SQL key field type double precision does not match return key field type integer.
+ERROR:  invalid connectby return type
+DETAIL:  Source key type integer does not match return key type double precision.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid float8, level int, branch text);
+ERROR:  invalid connectby return type
+DETAIL:  Source parent key type integer does not match return parent key type double precision.
+-- check other rowtype mismatch cases
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int, branch text);
+ERROR:  invalid connectby return type
+DETAIL:  Return row must have 3 columns, not 4.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int);
+ERROR:  invalid connectby return type
+DETAIL:  Return row must have 4 columns, not 3.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid text, level int);
+ERROR:  invalid connectby return type
+DETAIL:  Source parent key type integer does not match return parent key type text.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level float, branch float);
+ERROR:  invalid connectby return type
+DETAIL:  Third return column (depth) must be type integer.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch float);
+ERROR:  invalid connectby return type
+DETAIL:  Fourth return column (branch) must be type text.
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos text);
+ERROR:  invalid connectby return type
+DETAIL:  Fifth return column (serial) must be type integer.
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos text);
+ERROR:  invalid connectby return type
+DETAIL:  Fourth return column (serial) must be type integer.
 -- tests for values using custom queries
 -- query with one column - failed
 SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
-ERROR:  invalid return type
-DETAIL:  Query must return at least two columns.
+ERROR:  invalid connectby source data query
+DETAIL:  The query must return at least two columns.
 -- query with two columns first value as NULL
 SELECT * FROM connectby('connectby_int', 'NULL::int, 1::int; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
  keyid | parent_keyid | level 
index 02e8a98c73e0b0f7acdb6e2c0e4301b584322908..0fb8e40de29fe974f93e553e96c9432f3773cd39 100644 (file)
@@ -44,6 +44,16 @@ LANGUAGE C STABLE STRICT;
 
 SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' ORDER BY 1,2;');
 
+-- check error reporting
+SELECT * FROM crosstab('SELECT rowid, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name text, category_1 text, category_2 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name int, category_1 text, category_2 text);
+SELECT * FROM crosstab('SELECT rowid, attribute, val FROM ct where rowclass = ''group1'' and (attribute = ''att2'' or attribute = ''att3'') ORDER BY 1,2;')
+  AS ct(row_name text, category_1 text, category_2 int);
+
 --
 -- hash based crosstab
 --
@@ -99,6 +109,12 @@ SELECT * FROM crosstab(
   'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2')
 AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
 
+-- if category query generates a NULL value, get expected error
+SELECT * FROM crosstab(
+  'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
+  'SELECT NULL::text')
+AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
+
 -- if source query returns zero rows, get zero rows returned
 SELECT * FROM crosstab(
   'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1',
@@ -111,6 +127,22 @@ SELECT * FROM crosstab(
   'SELECT DISTINCT attribute FROM cth WHERE false ORDER BY 1')
 AS c(rowid text, rowdt timestamp, temperature text, test_result text, test_startdate text, volts text);
 
+-- check errors with inappropriate input rowtype
+SELECT * FROM crosstab(
+  'SELECT rowid, attribute FROM cth ORDER BY 1',
+  'SELECT DISTINCT attribute FROM cth ORDER BY 1')
+AS c(rowid text, temperature text, test_result text, test_startdate text, volts text);
+SELECT * FROM crosstab(
+  'SELECT rowid, rowdt, rowdt, attribute, val FROM cth ORDER BY 1',
+  'SELECT DISTINCT attribute FROM cth ORDER BY 1')
+AS c(rowid text, rowdt timestamp, temperature int4, test_result text, test_startdate timestamp, volts float8);
+
+-- check errors with inappropriate result rowtype
+SELECT * FROM crosstab(
+  'SELECT rowid, attribute, val FROM cth ORDER BY 1',
+  'SELECT DISTINCT attribute FROM cth ORDER BY 1')
+AS c(rowid text);
+
 -- check it works with a named result rowtype
 
 create type my_crosstab_result as (
@@ -186,6 +218,16 @@ SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') A
 
 -- should fail as key field datatype should match return datatype
 SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8, level int, branch text);
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid float8, level int, branch text);
+
+-- check other rowtype mismatch cases
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int, branch text);
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int);
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid text, level int);
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level float, branch float);
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level int, branch float);
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text, parent_keyid text, level int, branch text, pos text);
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0) AS t(keyid text, parent_keyid text, level int, pos text);
 
 -- tests for values using custom queries
 -- query with one column - failed
index ba17c9ba8e35235a8bc9025b304dc1ae9d86fd55..8009becefec724bccf27a01506f1e38743f38697 100644 (file)
@@ -52,7 +52,7 @@ static Tuplestorestate *get_crosstab_tuplestore(char *sql,
                                                TupleDesc tupdesc,
                                                bool randomAccess);
 static void validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial);
-static bool compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc);
+static void compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc);
 static void compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc);
 static void get_normal_pair(float8 *x1, float8 *x2);
 static Tuplestorestate *connectby(char *relname,
@@ -418,9 +418,8 @@ crosstab(PG_FUNCTION_ARGS)
    if (spi_tupdesc->natts != 3)
        ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("invalid source data SQL statement"),
-                errdetail("The provided SQL must return 3 "
-                          "columns: rowid, category, and values.")));
+                errmsg("invalid crosstab source data query"),
+                errdetail("The query must return 3 columns: row_name, category, and value.")));
 
    /* get a tuple descriptor for our result type */
    switch (get_call_result_type(fcinfo, NULL, &tupdesc))
@@ -447,11 +446,7 @@ crosstab(PG_FUNCTION_ARGS)
     * Check that return tupdesc is compatible with the data we got from SPI,
     * at least based on number and type of attributes
     */
-   if (!compatCrosstabTupleDescs(tupdesc, spi_tupdesc))
-       ereport(ERROR,
-               (errcode(ERRCODE_SYNTAX_ERROR),
-                errmsg("return and sql tuple descriptions are " \
-                       "incompatible")));
+   compatCrosstabTupleDescs(tupdesc, spi_tupdesc);
 
    /*
     * switch to long-lived memory context
@@ -673,9 +668,9 @@ crosstab_hash(PG_FUNCTION_ARGS)
     */
    if (tupdesc->natts < 2)
        ereport(ERROR,
-               (errcode(ERRCODE_SYNTAX_ERROR),
-                errmsg("query-specified return tuple and " \
-                       "crosstab function are not compatible")));
+               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                errmsg("invalid crosstab return type"),
+                errdetail("Return row must have at least two columns.")));
 
    /* load up the categories hash table */
    crosstab_hash = load_categories_hash(cats_sql, per_query_ctx);
@@ -750,9 +745,9 @@ load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
         */
        if (spi_tupdesc->natts != 1)
            ereport(ERROR,
-                   (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("provided \"categories\" SQL must " \
-                           "return 1 column of at least one row")));
+                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("invalid crosstab categories query"),
+                    errdetail("The query must return one column.")));
 
        for (i = 0; i < proc; i++)
        {
@@ -767,9 +762,8 @@ load_categories_hash(char *cats_sql, MemoryContext per_query_ctx)
            catname = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
            if (catname == NULL)
                ereport(ERROR,
-                       (errcode(ERRCODE_SYNTAX_ERROR),
-                        errmsg("provided \"categories\" SQL must " \
-                               "not return NULL values")));
+                       (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+                        errmsg("crosstab category value must not be null")));
 
            SPIcontext = MemoryContextSwitchTo(per_query_ctx);
 
@@ -837,9 +831,8 @@ get_crosstab_tuplestore(char *sql,
        {
            /* no qualifying category tuples */
            ereport(ERROR,
-                   (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("provided \"categories\" SQL must " \
-                           "return 1 column of at least one row")));
+                   (errcode(ERRCODE_CARDINALITY_VIOLATION),
+                    errmsg("crosstab categories query must return at least one row")));
        }
 
        /*
@@ -858,20 +851,18 @@ get_crosstab_tuplestore(char *sql,
        if (ncols < 3)
            ereport(ERROR,
                    (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                    errmsg("invalid source data SQL statement"),
-                    errdetail("The provided SQL must return 3 " \
-                              " columns; rowid, category, and values.")));
+                    errmsg("invalid crosstab source data query"),
+                    errdetail("The query must return at least 3 columns: row_name, category, and value.")));
 
        result_ncols = (ncols - 2) + num_categories;
 
-       /* Recheck to make sure we tuple descriptor still looks reasonable */
+       /* Recheck to make sure output tuple descriptor looks reasonable */
        if (tupdesc->natts != result_ncols)
            ereport(ERROR,
-                   (errcode(ERRCODE_SYNTAX_ERROR),
-                    errmsg("invalid return type"),
-                    errdetail("Query-specified return " \
-                              "tuple has %d columns but crosstab " \
-                              "returns %d.", tupdesc->natts, result_ncols)));
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("invalid crosstab return type"),
+                    errdetail("Return row must have %d columns, not %d.",
+                              result_ncols, tupdesc->natts)));
 
        /* allocate space and make sure it's clear */
        values = (char **) palloc0(result_ncols * sizeof(char *));
@@ -1422,77 +1413,62 @@ build_tuplestore_recursively(char *key_fld,
 static void
 validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial)
 {
-   int         serial_column = 0;
-
-   if (show_serial)
-       serial_column = 1;
+   int         expected_cols;
 
    /* are there the correct number of columns */
    if (show_branch)
-   {
-       if (td->natts != (CONNECTBY_NCOLS + serial_column))
-           ereport(ERROR,
-                   (errcode(ERRCODE_DATATYPE_MISMATCH),
-                    errmsg("invalid return type"),
-                    errdetail("Query-specified return tuple has " \
-                              "wrong number of columns.")));
-   }
+       expected_cols = CONNECTBY_NCOLS;
    else
-   {
-       if (td->natts != CONNECTBY_NCOLS_NOBRANCH + serial_column)
-           ereport(ERROR,
-                   (errcode(ERRCODE_DATATYPE_MISMATCH),
-                    errmsg("invalid return type"),
-                    errdetail("Query-specified return tuple has " \
-                              "wrong number of columns.")));
-   }
+       expected_cols = CONNECTBY_NCOLS_NOBRANCH;
+   if (show_serial)
+       expected_cols++;
 
-   /* check that the types of the first two columns match */
-   if (TupleDescAttr(td, 0)->atttypid != TupleDescAttr(td, 1)->atttypid)
+   if (td->natts != expected_cols)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("invalid return type"),
-                errdetail("First two columns must be the same type.")));
+                errmsg("invalid connectby return type"),
+                errdetail("Return row must have %d columns, not %d.",
+                          expected_cols, td->natts)));
+
+   /* the first two columns will be checked against the input tuples later */
 
    /* check that the type of the third column is INT4 */
    if (TupleDescAttr(td, 2)->atttypid != INT4OID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("invalid return type"),
-                errdetail("Third column must be type %s.",
+                errmsg("invalid connectby return type"),
+                errdetail("Third return column (depth) must be type %s.",
                           format_type_be(INT4OID))));
 
-   /* check that the type of the fourth column is TEXT if applicable */
+   /* check that the type of the branch column is TEXT if applicable */
    if (show_branch && TupleDescAttr(td, 3)->atttypid != TEXTOID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("invalid return type"),
-                errdetail("Fourth column must be type %s.",
+                errmsg("invalid connectby return type"),
+                errdetail("Fourth return column (branch) must be type %s.",
                           format_type_be(TEXTOID))));
 
-   /* check that the type of the fifth column is INT4 */
+   /* check that the type of the serial column is INT4 if applicable */
    if (show_branch && show_serial &&
        TupleDescAttr(td, 4)->atttypid != INT4OID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("query-specified return tuple not valid for Connectby: "
-                       "fifth column must be type %s",
-                       format_type_be(INT4OID))));
-
-   /* check that the type of the fourth column is INT4 */
+                errmsg("invalid connectby return type"),
+                errdetail("Fifth return column (serial) must be type %s.",
+                          format_type_be(INT4OID))));
    if (!show_branch && show_serial &&
        TupleDescAttr(td, 3)->atttypid != INT4OID)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("query-specified return tuple not valid for Connectby: "
-                       "fourth column must be type %s",
-                       format_type_be(INT4OID))));
+                errmsg("invalid connectby return type"),
+                errdetail("Fourth return column (serial) must be type %s.",
+                          format_type_be(INT4OID))));
 
    /* OK, the tupdesc is valid for our purposes */
 }
 
 /*
- * Check if spi sql tupdesc and return tupdesc are compatible
+ * Check if output tupdesc and SQL query's tupdesc are compatible
  */
 static void
 compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
@@ -1503,13 +1479,13 @@ compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
    int32       sql_atttypmod;
 
    /*
-    * Result must have at least 2 columns.
+    * Query result must have at least 2 columns.
     */
    if (sql_tupdesc->natts < 2)
        ereport(ERROR,
-               (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("invalid return type"),
-                errdetail("Query must return at least two columns.")));
+               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                errmsg("invalid connectby source data query"),
+                errdetail("The query must return at least two columns.")));
 
    /*
     * These columns must match the result type indicated by the calling
@@ -1523,11 +1499,10 @@ compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
        (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("invalid return type"),
-                errdetail("SQL key field type %s does " \
-                          "not match return key field type %s.",
-                          format_type_with_typemod(ret_atttypid, ret_atttypmod),
-                          format_type_with_typemod(sql_atttypid, sql_atttypmod))));
+                errmsg("invalid connectby return type"),
+                errdetail("Source key type %s does not match return key type %s.",
+                          format_type_with_typemod(sql_atttypid, sql_atttypmod),
+                          format_type_with_typemod(ret_atttypid, ret_atttypmod))));
 
    ret_atttypid = TupleDescAttr(ret_tupdesc, 1)->atttypid;
    sql_atttypid = TupleDescAttr(sql_tupdesc, 1)->atttypid;
@@ -1537,19 +1512,18 @@ compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
        (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("invalid return type"),
-                errdetail("SQL parent key field type %s does " \
-                          "not match return parent key field type %s.",
-                          format_type_with_typemod(ret_atttypid, ret_atttypmod),
-                          format_type_with_typemod(sql_atttypid, sql_atttypmod))));
+                errmsg("invalid connectby return type"),
+                errdetail("Source parent key type %s does not match return parent key type %s.",
+                          format_type_with_typemod(sql_atttypid, sql_atttypmod),
+                          format_type_with_typemod(ret_atttypid, ret_atttypmod))));
 
    /* OK, the two tupdescs are compatible for our purposes */
 }
 
 /*
- * Check if two tupdescs match in type of attributes
+ * Check if crosstab output tupdesc agrees with input tupdesc
  */
-static bool
+static void
 compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
 {
    int         i;
@@ -1558,9 +1532,12 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
    Form_pg_attribute sql_attr;
    Oid         sql_atttypid;
 
-   if (ret_tupdesc->natts < 2 ||
-       sql_tupdesc->natts < 3)
-       return false;
+   if (ret_tupdesc->natts < 2)
+       ereport(ERROR,
+               (errcode(ERRCODE_DATATYPE_MISMATCH),
+                errmsg("invalid crosstab return type"),
+                errdetail("Return row must have at least two columns.")));
+   Assert(sql_tupdesc->natts == 3);    /* already checked by caller */
 
    /* check the rowid types match */
    ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid;
@@ -1568,9 +1545,10 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
    if (ret_atttypid != sql_atttypid)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
-                errmsg("invalid return type"),
-                errdetail("SQL rowid datatype does not match " \
-                          "return rowid datatype.")));
+                errmsg("invalid crosstab return type"),
+                errdetail("Source row_name datatype %s does not match return row_name datatype %s.",
+                          format_type_be(sql_atttypid),
+                          format_type_be(ret_atttypid))));
 
    /*
     * - attribute [1] of the sql tuple is the category; no need to check it -
@@ -1583,9 +1561,14 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
        ret_attr = TupleDescAttr(ret_tupdesc, i);
 
        if (ret_attr->atttypid != sql_attr->atttypid)
-           return false;
+           ereport(ERROR,
+                   (errcode(ERRCODE_DATATYPE_MISMATCH),
+                    errmsg("invalid crosstab return type"),
+                    errdetail("Source value datatype %s does not match return value datatype %s in column %d.",
+                              format_type_be(sql_attr->atttypid),
+                              format_type_be(ret_attr->atttypid),
+                              i + 1)));
    }
 
    /* OK, the two tupdescs are compatible for our purposes */
-   return true;
 }