Improving contrib/tablefunc's error reporting - Mailing list pgsql-hackers

From Tom Lane
Subject Improving contrib/tablefunc's error reporting
Date
Msg-id 18937.1709676295@sss.pgh.pa.us
Whole thread Raw
Responses Re: Improving contrib/tablefunc's error reporting
Re: Improving contrib/tablefunc's error reporting
List pgsql-hackers
After reading the thread at [1], I could not escape the feeling
that contrib/tablefunc's error reporting is very confusing.
Looking into the source code, I soon found that it is also
very inconsistent, with similar error reports being phrased
quite differently.  The terminology for column names doesn't
match the SGML docs either.  And there are some places that are
so confused about whether they are complaining about the calling
query or the called query that the output is flat-out backwards.
So at that point my nascent OCD wouldn't let me rest without
fixing it.  Here's a quick patch series to do that.

For review purposes, I split this into two patches.  0001 simply
adds some more test cases to reach currently-unexercised error
reports.  Then 0002 makes my proposed code changes and shows
how the existing error messages change.

I'm not necessarily wedded to the phrasings I used here,
in case anyone has better ideas.

BTW, while I didn't touch it here, it seems fairly bogus that
connectby() checks both type OID and typmod for its output
columns while crosstab() only checks type OID.  I think
crosstab() is in the wrong and needs to be checking typmod.
That might be fit material for a separate patch though.

            regards, tom lane

[1]
https://www.postgresql.org/message-id/flat/DM4PR19MB597886696589C5CE33F5D58AD3222%40DM4PR19MB5978.namprd19.prod.outlook.com

From 7fba23c28e9e7558f54c52dd9407ce50f75b0e1d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 5 Mar 2024 16:37:35 -0500
Subject: [PATCH v1 1/2] Add more test coverage for contrib/tablefunc.

Add test cases that exercise all the error reports in the module,
excluding those that are for failures elsewhere such as SPI errors.
---
 contrib/tablefunc/expected/tablefunc.out | 63 ++++++++++++++++++++++++
 contrib/tablefunc/sql/tablefunc.sql      | 42 ++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/contrib/tablefunc/expected/tablefunc.out b/contrib/tablefunc/expected/tablefunc.out
index 464c210f42..0c4e114aba 100644
--- a/contrib/tablefunc/expected/tablefunc.out
+++ b/contrib/tablefunc/expected/tablefunc.out
@@ -145,6 +145,21 @@ 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 source data SQL statement
+DETAIL:  The provided SQL must return 3 columns: rowid, category, and values.
+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:  return and sql tuple descriptions are incompatible
+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 return type
+DETAIL:  SQL rowid datatype does not match return rowid datatype.
+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:  return and sql tuple descriptions are incompatible
 --
 -- hash based crosstab
 --
@@ -223,6 +238,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);
 ERROR:  provided "categories" SQL must return 1 column of at least one row
+-- 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:  provided "categories" SQL must not return NULL values
 -- 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 +262,25 @@ 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 source data SQL statement
+DETAIL:  The provided SQL must return 3  columns; rowid, category, and values.
+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 return type
+DETAIL:  Query-specified return tuple has 6 columns but crosstab returns 7.
+-- 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:  query-specified return tuple and crosstab function are not compatible
 -- check it works with a named result rowtype
 create type my_crosstab_result as (
   rowid text, rowdt timestamp,
@@ -387,6 +427,29 @@ DETAIL:  First two columns must be the same type.
 SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid float8, parent_keyid float8,
levelint, branch text); 
 ERROR:  invalid return type
 DETAIL:  SQL key field type double precision does not match return key field type integer.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid float8,
levelint, branch text); 
+ERROR:  invalid return type
+DETAIL:  First two columns must be the same type.
+-- check other rowtype mismatch cases
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int,
branchtext); 
+ERROR:  invalid return type
+DETAIL:  Query-specified return tuple has wrong number of columns.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level
int);
+ERROR:  invalid return type
+DETAIL:  Query-specified return tuple has wrong number of columns.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid text, level
int);
+ERROR:  invalid return type
+DETAIL:  First two columns must be the same type.
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid int, level
float,branch float); 
+ERROR:  invalid return type
+DETAIL:  Third column 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 return type
+DETAIL:  Fourth column must be type text.
+SELECT * FROM connectby('connectby_text', 'keyid', 'parent_keyid', 'pos', 'row2', 0, '~') AS t(keyid text,
parent_keyidtext, level int, branch text, pos text); 
+ERROR:  query-specified return tuple not valid for Connectby: fifth column 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:  query-specified return tuple not valid for Connectby: fourth column 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);
diff --git a/contrib/tablefunc/sql/tablefunc.sql b/contrib/tablefunc/sql/tablefunc.sql
index 02e8a98c73..0fb8e40de2 100644
--- a/contrib/tablefunc/sql/tablefunc.sql
+++ b/contrib/tablefunc/sql/tablefunc.sql
@@ -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,
levelint, branch text); 
+SELECT * FROM connectby('connectby_int', 'keyid', 'parent_keyid', '2', 0, '~') AS t(keyid int, parent_keyid float8,
levelint, 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,
branchtext); 
+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_keyidtext, 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
--
2.39.3

From 93440770c38c14b926cd1e93e192549ad95fb52d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 5 Mar 2024 16:49:23 -0500
Subject: [PATCH v1 2/2] Improve contrib/tablefunc's error reporting.

Try to make these messages less confusing, ambiguous, and
inconsistent.  Notably, avoid saying "query" where it's not clear
whether that means the calling query's AS clause or the query
that crosstab/connectby is invoking.  Also, mention in each
error message that it's from crosstab or connectby, because
otherwise even that much is far from clear.
---
 contrib/tablefunc/expected/tablefunc.out |  74 +++++-----
 contrib/tablefunc/tablefunc.c            | 169 ++++++++++-------------
 2 files changed, 116 insertions(+), 127 deletions(-)

diff --git a/contrib/tablefunc/expected/tablefunc.out b/contrib/tablefunc/expected/tablefunc.out
index 0c4e114aba..ddece79029 100644
--- a/contrib/tablefunc/expected/tablefunc.out
+++ b/contrib/tablefunc/expected/tablefunc.out
@@ -148,18 +148,20 @@ SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass
 -- 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 source data SQL statement
-DETAIL:  The provided SQL must return 3 columns: rowid, category, and values.
+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:  return and sql tuple descriptions are incompatible
+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 return type
-DETAIL:  SQL rowid datatype does not match return rowid datatype.
+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:  return and sql tuple descriptions are incompatible
+ERROR:  invalid crosstab return type
+DETAIL:  Source value datatype text does not match return value datatype integer in column 3.
 --
 -- hash based crosstab
 --
@@ -231,19 +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:  provided "categories" SQL must not return NULL values
+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',
@@ -267,20 +270,21 @@ 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 source data SQL statement
-DETAIL:  The provided SQL must return 3  columns; rowid, category, and values.
+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 return type
-DETAIL:  Query-specified return tuple has 6 columns but crosstab returns 7.
+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:  query-specified return tuple and crosstab function are not compatible
+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,
@@ -421,40 +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,
levelint, 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,
levelint, 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,
levelint, branch text); 
-ERROR:  invalid return type
-DETAIL:  First two columns must be the same type.
+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,
branchtext); 
-ERROR:  invalid return type
-DETAIL:  Query-specified return tuple has wrong number of columns.
+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 return type
-DETAIL:  Query-specified return tuple has wrong number of columns.
+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 return type
-DETAIL:  First two columns must be the same type.
+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 return type
-DETAIL:  Third column must be type integer.
+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 return type
-DETAIL:  Fourth column must be type text.
+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_keyidtext, level int, branch text, pos text); 
-ERROR:  query-specified return tuple not valid for Connectby: fifth column must be type integer
+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:  query-specified return tuple not valid for Connectby: fourth column must be type integer
+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
diff --git a/contrib/tablefunc/tablefunc.c b/contrib/tablefunc/tablefunc.c
index ba17c9ba8e..8009becefe 100644
--- a/contrib/tablefunc/tablefunc.c
+++ b/contrib/tablefunc/tablefunc.c
@@ -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;
 }
--
2.39.3


pgsql-hackers by date:

Previous
From: David Rowley
Date:
Subject: Re: Get rid of the excess semicolon in planner.c
Next
From: Joe Conway
Date:
Subject: Re: Improving contrib/tablefunc's error reporting