1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Improve wrong-tuple-type error reports in contrib/tablefunc.

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
This commit is contained in:
Tom Lane
2024-03-09 15:48:21 -05:00
parent b0289574bd
commit 76904eda25
3 changed files with 195 additions and 101 deletions

View File

@ -145,6 +145,23 @@ SELECT * FROM crosstab_out('SELECT rowid, attribute, val FROM ct where rowclass
| val9 | val10 | val11 | val9 | val10 | val11
(3 rows) (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 -- hash based crosstab
-- --
@ -216,13 +233,20 @@ SELECT * FROM crosstab(
'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
'SELECT DISTINCT attribute FROM cth WHERE attribute = ''a'' 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); 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 -- if category query generates more than one column, get expected error
SELECT * FROM crosstab( SELECT * FROM crosstab(
'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1', 'SELECT rowid, rowdt, attribute, val FROM cth ORDER BY 1',
'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2') '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); 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 -- if source query returns zero rows, get zero rows returned
SELECT * FROM crosstab( SELECT * FROM crosstab(
'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', '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) (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 -- check it works with a named result rowtype
create type my_crosstab_result as ( create type my_crosstab_result as (
rowid text, rowdt timestamp, 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 -- 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); 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 ERROR: invalid connectby return type
DETAIL: First two columns must be the same type. DETAIL: Source key type integer does not match return key type text.
-- should fail as key field datatype should match return datatype -- 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 float8, parent_keyid float8, level int, branch text);
ERROR: invalid return type ERROR: invalid connectby return type
DETAIL: SQL key field type double precision does not match return key field type integer. 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 -- tests for values using custom queries
-- query with one column - failed -- query with one column - failed
SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int); SELECT * FROM connectby('connectby_int', '1; --', 'parent_keyid', '2', 0) AS t(keyid int, parent_keyid int, level int);
ERROR: invalid return type ERROR: invalid connectby source data query
DETAIL: Query must return at least two columns. DETAIL: The query must return at least two columns.
-- query with two columns first value as NULL -- 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); 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 keyid | parent_keyid | level

View 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;'); 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 -- hash based crosstab
-- --
@ -99,6 +109,12 @@ SELECT * FROM crosstab(
'SELECT DISTINCT rowdt, attribute FROM cth ORDER BY 2') '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); 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 -- if source query returns zero rows, get zero rows returned
SELECT * FROM crosstab( SELECT * FROM crosstab(
'SELECT rowid, rowdt, attribute, val FROM cth WHERE false ORDER BY 1', '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') '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); 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 -- check it works with a named result rowtype
create type my_crosstab_result as ( 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 -- 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 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 -- tests for values using custom queries
-- query with one column - failed -- query with one column - failed

View File

@ -52,7 +52,7 @@ static Tuplestorestate *get_crosstab_tuplestore(char *sql,
TupleDesc tupdesc, TupleDesc tupdesc,
bool randomAccess); bool randomAccess);
static void validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial); 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 compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc);
static void get_normal_pair(float8 *x1, float8 *x2); static void get_normal_pair(float8 *x1, float8 *x2);
static Tuplestorestate *connectby(char *relname, static Tuplestorestate *connectby(char *relname,
@ -418,9 +418,8 @@ crosstab(PG_FUNCTION_ARGS)
if (spi_tupdesc->natts != 3) if (spi_tupdesc->natts != 3)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid source data SQL statement"), errmsg("invalid crosstab source data query"),
errdetail("The provided SQL must return 3 " errdetail("The query must return 3 columns: row_name, category, and value.")));
"columns: rowid, category, and values.")));
/* get a tuple descriptor for our result type */ /* get a tuple descriptor for our result type */
switch (get_call_result_type(fcinfo, NULL, &tupdesc)) 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, * Check that return tupdesc is compatible with the data we got from SPI,
* at least based on number and type of attributes * at least based on number and type of attributes
*/ */
if (!compatCrosstabTupleDescs(tupdesc, spi_tupdesc)) compatCrosstabTupleDescs(tupdesc, spi_tupdesc);
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("return and sql tuple descriptions are " \
"incompatible")));
/* /*
* switch to long-lived memory context * switch to long-lived memory context
@ -673,9 +668,9 @@ crosstab_hash(PG_FUNCTION_ARGS)
*/ */
if (tupdesc->natts < 2) if (tupdesc->natts < 2)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("query-specified return tuple and " \ errmsg("invalid crosstab return type"),
"crosstab function are not compatible"))); errdetail("Return row must have at least two columns.")));
/* load up the categories hash table */ /* load up the categories hash table */
crosstab_hash = load_categories_hash(cats_sql, per_query_ctx); 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) if (spi_tupdesc->natts != 1)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("provided \"categories\" SQL must " \ errmsg("invalid crosstab categories query"),
"return 1 column of at least one row"))); errdetail("The query must return one column.")));
for (i = 0; i < proc; i++) 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); catname = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
if (catname == NULL) if (catname == NULL)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("provided \"categories\" SQL must " \ errmsg("crosstab category value must not be null")));
"not return NULL values")));
SPIcontext = MemoryContextSwitchTo(per_query_ctx); SPIcontext = MemoryContextSwitchTo(per_query_ctx);
@ -837,9 +831,8 @@ get_crosstab_tuplestore(char *sql,
{ {
/* no qualifying category tuples */ /* no qualifying category tuples */
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("provided \"categories\" SQL must " \ errmsg("crosstab categories query must return at least one row")));
"return 1 column of at least one row")));
} }
/* /*
@ -858,20 +851,18 @@ get_crosstab_tuplestore(char *sql,
if (ncols < 3) if (ncols < 3)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid source data SQL statement"), errmsg("invalid crosstab source data query"),
errdetail("The provided SQL must return 3 " \ errdetail("The query must return at least 3 columns: row_name, category, and value.")));
" columns; rowid, category, and values.")));
result_ncols = (ncols - 2) + num_categories; 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) if (tupdesc->natts != result_ncols)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"), errmsg("invalid crosstab return type"),
errdetail("Query-specified return " \ errdetail("Return row must have %d columns, not %d.",
"tuple has %d columns but crosstab " \ result_ncols, tupdesc->natts)));
"returns %d.", tupdesc->natts, result_ncols)));
/* allocate space and make sure it's clear */ /* allocate space and make sure it's clear */
values = (char **) palloc0(result_ncols * sizeof(char *)); values = (char **) palloc0(result_ncols * sizeof(char *));
@ -1422,77 +1413,62 @@ build_tuplestore_recursively(char *key_fld,
static void static void
validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial) validateConnectbyTupleDesc(TupleDesc td, bool show_branch, bool show_serial)
{ {
int serial_column = 0; int expected_cols;
if (show_serial)
serial_column = 1;
/* are there the correct number of columns */ /* are there the correct number of columns */
if (show_branch) if (show_branch)
{ expected_cols = CONNECTBY_NCOLS;
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.")));
}
else else
{ expected_cols = CONNECTBY_NCOLS_NOBRANCH;
if (td->natts != CONNECTBY_NCOLS_NOBRANCH + serial_column) if (show_serial)
ereport(ERROR, expected_cols++;
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"),
errdetail("Query-specified return tuple has " \
"wrong number of columns.")));
}
/* check that the types of the first two columns match */ if (td->natts != expected_cols)
if (TupleDescAttr(td, 0)->atttypid != TupleDescAttr(td, 1)->atttypid)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"), errmsg("invalid connectby return type"),
errdetail("First two columns must be the same 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 */ /* check that the type of the third column is INT4 */
if (TupleDescAttr(td, 2)->atttypid != INT4OID) if (TupleDescAttr(td, 2)->atttypid != INT4OID)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"), errmsg("invalid connectby return type"),
errdetail("Third column must be type %s.", errdetail("Third return column (depth) must be type %s.",
format_type_be(INT4OID)))); 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) if (show_branch && TupleDescAttr(td, 3)->atttypid != TEXTOID)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"), errmsg("invalid connectby return type"),
errdetail("Fourth column must be type %s.", errdetail("Fourth return column (branch) must be type %s.",
format_type_be(TEXTOID)))); 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 && if (show_branch && show_serial &&
TupleDescAttr(td, 4)->atttypid != INT4OID) TupleDescAttr(td, 4)->atttypid != INT4OID)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("query-specified return tuple not valid for Connectby: " errmsg("invalid connectby return type"),
"fifth column must be type %s", errdetail("Fifth return column (serial) must be type %s.",
format_type_be(INT4OID)))); format_type_be(INT4OID))));
/* check that the type of the fourth column is INT4 */
if (!show_branch && show_serial && if (!show_branch && show_serial &&
TupleDescAttr(td, 3)->atttypid != INT4OID) TupleDescAttr(td, 3)->atttypid != INT4OID)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("query-specified return tuple not valid for Connectby: " errmsg("invalid connectby return type"),
"fourth column must be type %s", errdetail("Fourth return column (serial) must be type %s.",
format_type_be(INT4OID)))); format_type_be(INT4OID))));
/* OK, the tupdesc is valid for our purposes */ /* 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 static void
compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc) compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
@ -1503,13 +1479,13 @@ compatConnectbyTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
int32 sql_atttypmod; int32 sql_atttypmod;
/* /*
* Result must have at least 2 columns. * Query result must have at least 2 columns.
*/ */
if (sql_tupdesc->natts < 2) if (sql_tupdesc->natts < 2)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid return type"), errmsg("invalid connectby source data query"),
errdetail("Query must return at least two columns."))); errdetail("The query must return at least two columns.")));
/* /*
* These columns must match the result type indicated by the calling * 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)) (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"), errmsg("invalid connectby return type"),
errdetail("SQL key field type %s does " \ errdetail("Source key type %s does not match return key type %s.",
"not match return key field type %s.", format_type_with_typemod(sql_atttypid, sql_atttypmod),
format_type_with_typemod(ret_atttypid, ret_atttypmod), format_type_with_typemod(ret_atttypid, ret_atttypmod))));
format_type_with_typemod(sql_atttypid, sql_atttypmod))));
ret_atttypid = TupleDescAttr(ret_tupdesc, 1)->atttypid; ret_atttypid = TupleDescAttr(ret_tupdesc, 1)->atttypid;
sql_atttypid = TupleDescAttr(sql_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)) (ret_atttypmod >= 0 && ret_atttypmod != sql_atttypmod))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"), errmsg("invalid connectby return type"),
errdetail("SQL parent key field type %s does " \ errdetail("Source parent key type %s does not match return parent key type %s.",
"not match return parent key field type %s.", format_type_with_typemod(sql_atttypid, sql_atttypmod),
format_type_with_typemod(ret_atttypid, ret_atttypmod), format_type_with_typemod(ret_atttypid, ret_atttypmod))));
format_type_with_typemod(sql_atttypid, sql_atttypmod))));
/* OK, the two tupdescs are compatible for our purposes */ /* 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) compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
{ {
int i; int i;
@ -1558,9 +1532,12 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
Form_pg_attribute sql_attr; Form_pg_attribute sql_attr;
Oid sql_atttypid; Oid sql_atttypid;
if (ret_tupdesc->natts < 2 || if (ret_tupdesc->natts < 2)
sql_tupdesc->natts < 3) ereport(ERROR,
return false; (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 */ /* check the rowid types match */
ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid; ret_atttypid = TupleDescAttr(ret_tupdesc, 0)->atttypid;
@ -1568,9 +1545,10 @@ compatCrosstabTupleDescs(TupleDesc ret_tupdesc, TupleDesc sql_tupdesc)
if (ret_atttypid != sql_atttypid) if (ret_atttypid != sql_atttypid)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH), (errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("invalid return type"), errmsg("invalid crosstab return type"),
errdetail("SQL rowid datatype does not match " \ errdetail("Source row_name datatype %s does not match return row_name datatype %s.",
"return rowid datatype."))); format_type_be(sql_atttypid),
format_type_be(ret_atttypid))));
/* /*
* - attribute [1] of the sql tuple is the category; no need to check it - * - 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); ret_attr = TupleDescAttr(ret_tupdesc, i);
if (ret_attr->atttypid != sql_attr->atttypid) 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 */ /* OK, the two tupdescs are compatible for our purposes */
return true;
} }