Query samples in docs and better errors

pull/2572/head
Hanefi Onaldi 2019-01-23 14:32:25 +03:00
parent 574b071113
commit 825666f912
No known key found for this signature in database
GPG Key ID: 95177DABDC09D1F5
4 changed files with 77 additions and 42 deletions

View File

@ -164,7 +164,7 @@ static bool CteReferenceListWalker(Node *node, CteReferenceWalkerContext *contex
static bool ContainsReferencesToOuterQuery(Query *query); static bool ContainsReferencesToOuterQuery(Query *query);
static bool ContainsReferencesToOuterQueryWalker(Node *node, static bool ContainsReferencesToOuterQueryWalker(Node *node,
VarLevelsUpWalkerContext *context); VarLevelsUpWalkerContext *context);
static void WrapFunctionsInQuery(Query *query); static void WrapFunctionsInSubqueries(Query *query);
static void TransformFunctionRTE(RangeTblEntry *rangeTblEntry); static void TransformFunctionRTE(RangeTblEntry *rangeTblEntry);
static bool ShouldTransformRTE(RangeTblEntry *rangeTableEntry); static bool ShouldTransformRTE(RangeTblEntry *rangeTableEntry);
@ -268,7 +268,7 @@ RecursivelyPlanSubqueriesAndCTEs(Query *query, RecursivePlanningContext *context
} }
/* make sure function calls in joins are executed in the coordinator */ /* make sure function calls in joins are executed in the coordinator */
WrapFunctionsInQuery(query); WrapFunctionsInSubqueries(query);
/* descend into subqueries */ /* descend into subqueries */
query_tree_walker(query, RecursivelyPlanSubqueryWalker, context, 0); query_tree_walker(query, RecursivelyPlanSubqueryWalker, context, 0);
@ -1313,14 +1313,14 @@ ContainsReferencesToOuterQueryWalker(Node *node, VarLevelsUpWalkerContext *conte
/* /*
* WrapFunctionsInQuery iterates over all the immediate Range Table Entries * WrapFunctionsInSubqueries iterates over all the immediate Range Table Entries
* of a query and wraps the functions inside (SELECT * FROM fnc() f) subqueries. * of a query and wraps the functions inside (SELECT * FROM fnc() f) subqueries.
* *
* We currently wrap only those functions that return a single value. If a * We currently wrap only those functions that return a single value. If a
* function returns records or a table we leave it as it is * function returns records or a table we leave it as it is
* */ * */
static void static void
WrapFunctionsInQuery(Query *query) WrapFunctionsInSubqueries(Query *query)
{ {
List *rangeTableList = query->rtable; List *rangeTableList = query->rtable;
ListCell *rangeTableCell = NULL; ListCell *rangeTableCell = NULL;
@ -1358,7 +1358,7 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
Var *targetColumn = NULL; Var *targetColumn = NULL;
TargetEntry *targetEntry = NULL; TargetEntry *targetEntry = NULL;
RangeTblFunction *rangeTblFunction = NULL; RangeTblFunction *rangeTblFunction = NULL;
int targetColumnIndex = 0; AttrNumber targetColumnIndex = 0;
TupleDesc tupleDesc = NULL; TupleDesc tupleDesc = NULL;
rangeTblFunction = linitial(rangeTblEntry->functions); rangeTblFunction = linitial(rangeTblEntry->functions);
@ -1381,10 +1381,20 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
tupleDesc = (TupleDesc) get_expr_result_tupdesc(rangeTblFunction->funcexpr, tupleDesc = (TupleDesc) get_expr_result_tupdesc(rangeTblFunction->funcexpr,
true); true);
/* if tupleDesc is not null, we iterate over all the attributes and /*
* create targetEntries*/ * If tupleDesc is not null, we iterate over all the attributes and
* create targetEntries
* */
if (tupleDesc) if (tupleDesc)
{ {
/*
* A sample function join that end up here:
*
* CREATE FUNCTION f(..) RETURNS TABLE(c1 int, c2 text) AS .. ;
* SELECT .. FROM table JOIN f(..) ON ( .. ) ;
*
* We will iterate over Tuple Description attributes. i.e (c1 int, c2 text)
*/
for (targetColumnIndex = 0; targetColumnIndex < tupleDesc->natts; for (targetColumnIndex = 0; targetColumnIndex < tupleDesc->natts;
targetColumnIndex++) targetColumnIndex++)
{ {
@ -1397,7 +1407,7 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
* The indexing of attributes and TupleDesc and varattno differ * The indexing of attributes and TupleDesc and varattno differ
* *
* varattno=0 corresponds to whole row * varattno=0 corresponds to whole row
* varattno=1 corrensponds to first column that is stored in tupDesc->attrs[0] */ * varattno=1 corresponds to first column that is stored in tupDesc->attrs[0] */
targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, InvalidOid, targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, InvalidOid,
0); 0);
targetEntry = makeTargetEntry((Expr *) targetColumn, targetColumnIndex + 1, targetEntry = makeTargetEntry((Expr *) targetColumn, targetColumnIndex + 1,
@ -1407,13 +1417,13 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
} }
} }
/* /*
* If tupleDesc is NULL we have several cases: * If tupleDesc is NULL we have 2 different cases:
* *
* 1. The function returns a record but the attributes can not be * 1. The function returns a record but the attributes can not be
* determined before running the query. In this case the column names and * determined just by looking at the function definition. In this case the
* types must be defined explicitly in the query * column names and types must be defined explicitly in the query
* *
* 2. The function returns a non-composite type * 2. The function returns a non-composite type (e.g. int, text, jsonb ..)
* */ * */
else else
{ {
@ -1427,24 +1437,56 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
char *columnName = strVal(lfirst(functionColumnName)); char *columnName = strVal(lfirst(functionColumnName));
Oid columnType = InvalidOid; Oid columnType = InvalidOid;
/* use explicitly defined types in the query if they are available */ /*
* If the function returns a set of records, the query needs
* to explicitly name column names and types
*
* Use explicitly defined types in the query if they are
* available
* */
if (list_length(rangeTblFunction->funccoltypes) > 0) if (list_length(rangeTblFunction->funccoltypes) > 0)
{ {
/*
* A sample function join that end up here:
*
* CREATE FUNCTION get_set_of_records() RETURNS SETOF RECORD AS
* $cmd$
* SELECT x, x+1 FROM generate_series(0,4) f(x)
* $cmd$
* LANGUAGE SQL;
*
* SELECT *
* FROM table1 JOIN get_set_of_records() AS t2(x int, y int)
* ON (id = x);
*
* Note that the function definition does not have column
* names and types. Therefore the user needs to explicitly
* state them in the query
* */
columnType = list_nth_oid(rangeTblFunction->funccoltypes, columnType = list_nth_oid(rangeTblFunction->funccoltypes,
targetColumnIndex); targetColumnIndex);
} }
/* use the types in the function definition otherwise */ /* use the types in the function definition otherwise */
else else
{ {
/*
* Only functions returning simple types end up here.
* A sample function:
*
* CREATE FUNCTION add(integer, integer) RETURNS integer AS
* 'SELECT $1 + $2;'
* LANGUAGE SQL;
* SELECT * FROM table JOIN add(3,5) sum ON ( .. ) ;
* */
FuncExpr *funcExpr = (FuncExpr *) rangeTblFunction->funcexpr; FuncExpr *funcExpr = (FuncExpr *) rangeTblFunction->funcexpr;
columnType = funcExpr->funcresulttype; columnType = funcExpr->funcresulttype;
} }
/* Note that the column k is associated with varattno/resno of k+1 */
targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1,
InvalidOid, 0); InvalidOid, 0);
targetEntry = makeTargetEntry((Expr *) targetColumn, targetEntry = makeTargetEntry((Expr *) targetColumn,
targetColumnIndex + 1, columnName, targetColumnIndex + 1, columnName, false);
false);
subquery->targetList = lappend(subquery->targetList, targetEntry); subquery->targetList = lappend(subquery->targetList, targetEntry);
targetColumnIndex++; targetColumnIndex++;
@ -1461,33 +1503,22 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
* ShouldTransformRTE determines whether a given RTE should bne wrapped in a * ShouldTransformRTE determines whether a given RTE should bne wrapped in a
* subquery. * subquery.
* *
* Not all functions can be wrapped in a subquery for now. As we support more * Not all functions should be wrapped in a subquery for now. As we support more
* functions to be used in joins, the constraints here will be relaxed. * functions to be used in joins, the constraints here will be relaxed.
* */ * */
static bool static bool
ShouldTransformRTE(RangeTblEntry *rangeTableEntry) ShouldTransformRTE(RangeTblEntry *rangeTableEntry)
{ {
/* wrap only function rtes */
if (rangeTableEntry->rtekind != RTE_FUNCTION)
{
return false;
}
/* /*
* TODO: remove this check once lateral joins are supported * We should wrap only function rtes that are not LATERAL and
* We do not support lateral joins on functions for now, as referencing * without WITH CARDINALITY clause
* columns of an outer query is quite tricky */ * */
if (rangeTableEntry->lateral) if (rangeTableEntry->rtekind != RTE_FUNCTION ||
rangeTableEntry->lateral ||
rangeTableEntry->funcordinality)
{ {
return false; return false;
} }
/* We do not want to wrap read-intermediate-result function calls */
if (ContainsReadIntermediateResultFunction(linitial(rangeTableEntry->functions)))
{
return false;
}
return true; return true;
} }

View File

@ -139,7 +139,7 @@ canonicalize_qual_compat(Expr *qual, bool is_check)
* A convenient wrapper around get_expr_result_type() that is added on PG11 * A convenient wrapper around get_expr_result_type() that is added on PG11
* *
* Note that this function ignores the second parameter and behaves * Note that this function ignores the second parameter and behaves
* slightly differently. * slightly differently than the PG11 version.
* *
* 1. The original function throws errors if noError flag is not set, we ignore * 1. The original function throws errors if noError flag is not set, we ignore
* this flag here and return NULL in that case * this flag here and return NULL in that case

View File

@ -6,8 +6,8 @@
-- that we wrap those functions inside (SELECT * FROM fnc()) sub queries. -- that we wrap those functions inside (SELECT * FROM fnc()) sub queries.
-- --
-- We do not yet support those functions that: -- We do not yet support those functions that:
-- - return records -- - have lateral joins
-- - return tables -- - have WITH ORDINALITY clause
-- - are user-defined and immutable -- - are user-defined and immutable
CREATE SCHEMA functions_in_joins; CREATE SCHEMA functions_in_joins;
SET search_path TO 'functions_in_joins'; SET search_path TO 'functions_in_joins';
@ -202,14 +202,16 @@ SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer)
$cmd$); $cmd$);
ERROR: Task failed to execute ERROR: Task failed to execute
CONTEXT: PL/pgSQL function public.raise_failed_execution(text) line 6 at RAISE CONTEXT: PL/pgSQL function public.raise_failed_execution(text) line 6 at RAISE
-- WITH ORDINALITY clause forcing the result type to be RECORD/RECORDs -- WITH ORDINALITY clause
SELECT public.raise_failed_execution($cmd$
SELECT * SELECT *
FROM table1 FROM table1
JOIN next_k_integers(10,5) WITH ORDINALITY next_integers JOIN next_k_integers(10,5) WITH ORDINALITY next_integers
ON (id = next_integers.result) ON (id = next_integers.result)
ORDER BY id ASC; ORDER BY id ASC;
ERROR: attribute 2 of type record has wrong type $cmd$);
DETAIL: Table has type bigint, but query expects integer. ERROR: Task failed to execute
CONTEXT: PL/pgSQL function public.raise_failed_execution(text) line 6 at RAISE
RESET client_min_messages; RESET client_min_messages;
DROP SCHEMA functions_in_joins CASCADE; DROP SCHEMA functions_in_joins CASCADE;
NOTICE: drop cascades to 11 other objects NOTICE: drop cascades to 11 other objects

View File

@ -6,8 +6,8 @@
-- that we wrap those functions inside (SELECT * FROM fnc()) sub queries. -- that we wrap those functions inside (SELECT * FROM fnc()) sub queries.
-- --
-- We do not yet support those functions that: -- We do not yet support those functions that:
-- - return records -- - have lateral joins
-- - return tables -- - have WITH ORDINALITY clause
-- - are user-defined and immutable -- - are user-defined and immutable
CREATE SCHEMA functions_in_joins; CREATE SCHEMA functions_in_joins;
@ -132,12 +132,14 @@ SELECT public.raise_failed_execution($cmd$
SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer) SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer)
$cmd$); $cmd$);
-- WITH ORDINALITY clause forcing the result type to be RECORD/RECORDs -- WITH ORDINALITY clause
SELECT public.raise_failed_execution($cmd$
SELECT * SELECT *
FROM table1 FROM table1
JOIN next_k_integers(10,5) WITH ORDINALITY next_integers JOIN next_k_integers(10,5) WITH ORDINALITY next_integers
ON (id = next_integers.result) ON (id = next_integers.result)
ORDER BY id ASC; ORDER BY id ASC;
$cmd$);
RESET client_min_messages; RESET client_min_messages;
DROP SCHEMA functions_in_joins CASCADE; DROP SCHEMA functions_in_joins CASCADE;