delayed planning of unnamed statements - Mailing list pgsql-patches
From | Oliver Jowett |
---|---|
Subject | delayed planning of unnamed statements |
Date | |
Msg-id | 40B20EC1.9050702@opencloud.com Whole thread Raw |
Responses |
Re: delayed planning of unnamed statements
|
List | pgsql-patches |
Per discussion on -hackers (http://archives.postgresql.org/pgsql-hackers/2004-05/msg00990.php), here is a first cut at delaying query planning for unnamed statements until we have concrete parameters (i.e. at Bind). This passes 'make check' (not particularly informative since that doesn't exercise the extended query protocol path). It also passes the JDBC driver's regression tests when using a modified driver that uses the extended query protocol and parameterized queries. Also attached is a python script that talks the V3 protocol directly and does some simple tests of various bind/rebind cases with named and unnamed statements (beware, that code is very ad-hoc!). With these changes the JDBC driver gets performance similar to the unparameterized case when using the unnamed statement: > FE=> Parse(stmt=null,query="SELECT count(*) FROM test_big WHERE value = 2",oids={}) > FE=> Bind(stmt=null,portal=null) > FE=> Describe(portal=null) > FE=> Execute(portal=null,limit=0) > FE=> Sync > <=BE ParseComplete [null] > <=BE BindComplete [null] > <=BE RowDescription(1) > <=BE DataRow > <=BE CommandStatus(SELECT) > <=BE ReadyForQuery(I) > <<<<< END > Elapsed: 54ms > Result: 1 > FE=> Parse(stmt=S_1,query="SELECT count(*) FROM test_big WHERE value = $1",oids={23}) > FE=> Bind(stmt=S_1,portal=null,$1=<2>) > FE=> Describe(portal=null) > FE=> Execute(portal=null,limit=0) > FE=> Sync > <=BE ParseComplete [S_1] > <=BE BindComplete [null] > <=BE RowDescription(1) > <=BE DataRow > <=BE CommandStatus(SELECT) > <=BE ReadyForQuery(I) > <<<<< END > Elapsed: 1484ms > Result: 1 > FE=> Parse(stmt=null,query="SELECT count(*) FROM test_big WHERE value = $1",oids={23}) > FE=> Bind(stmt=null,portal=null,$1=<2>) > FE=> Describe(portal=null) > FE=> Execute(portal=null,limit=0) > FE=> Sync > <=BE ParseComplete [null] > <=BE BindComplete [null] > <=BE RowDescription(1) > <=BE DataRow > <=BE CommandStatus(SELECT) > <=BE ReadyForQuery(I) > <<<<< END > Elapsed: 65ms > Result: 1 -O Index: doc/src/sgml/protocol.sgml =================================================================== RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/protocol.sgml,v retrieving revision 1.51 diff -u -c -r1.51 protocol.sgml *** doc/src/sgml/protocol.sgml 21 Mar 2004 22:29:10 -0000 1.51 --- doc/src/sgml/protocol.sgml 24 May 2004 14:42:10 -0000 *************** *** 663,668 **** --- 663,688 ---- </para> <para> + Query planning of named prepared-statement objects occurs when the Parse + message is received. If a query will be repeatedly executed with + different parameters, it may be beneficial to send a single Parse message + containing a parameterized query, followed by multiple Bind + and Execute messages. This will avoid replanning the query on each + execution. + </para> + + <note> + <para> + Query plans generated from a parameterized query may be less + efficient than query plans generated from an equivalent query with actual + parameter values substituted. The query planner cannot make decisions + based on actual parameter values (for example, index selectivity) when + planning a parameterized query assigned to a named prepared-statement + object. + </para> + </note> + + <para> Once a prepared statement exists, it can be readied for execution using a Bind message. The Bind message gives the name of the source prepared statement (empty string denotes the unnamed prepared statement), the name *************** *** 674,679 **** --- 694,720 ---- by the query; the format can be specified overall, or per-column. The response is either BindComplete or ErrorResponse. </para> + + <para> + Query planning of the unnamed prepared-statement object occurs when the + first Bind message after a Parse message is received. The planner will + consider the actual values of any parameters provided in the Bind message + when planning the query. A Parse followed by Bind of the unnamed + prepared-statement object will produce the same query plan as for the + equivalent unparameterized query. + </para> + + <note> + <para> + When a second or subsequent Bind referencing the unnamed prepared- + statement object is received without an intervening Parse, the query is + not replanned. The parameter values used in the first Bind message may + produce a query plan that is only efficient for a subset of possible + parameter values. To force replanning of the query on each execution, send + a Parse message to replace the unnamed prepared-statement object before + each Bind. + </para> + </note> <note> <para> Index: src/backend/commands/explain.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/commands/explain.c,v retrieving revision 1.120 diff -u -c -r1.120 explain.c *** src/backend/commands/explain.c 1 Apr 2004 21:28:44 -0000 1.120 --- src/backend/commands/explain.c 24 May 2004 14:42:10 -0000 *************** *** 176,182 **** } /* plan the query */ ! plan = planner(query, isCursor, cursorOptions); /* Create a QueryDesc requesting no output */ queryDesc = CreateQueryDesc(query, plan, None_Receiver, NULL, --- 176,182 ---- } /* plan the query */ ! plan = planner(query, isCursor, cursorOptions, NULL); /* Create a QueryDesc requesting no output */ queryDesc = CreateQueryDesc(query, plan, None_Receiver, NULL, Index: src/backend/commands/portalcmds.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/commands/portalcmds.c,v retrieving revision 1.26 diff -u -c -r1.26 portalcmds.c *** src/backend/commands/portalcmds.c 21 Mar 2004 22:29:10 -0000 1.26 --- src/backend/commands/portalcmds.c 24 May 2004 14:42:10 -0000 *************** *** 84,90 **** errmsg("DECLARE CURSOR ... FOR UPDATE is not supported"), errdetail("Cursors must be READ ONLY."))); ! plan = planner(query, true, stmt->options); /* * Create a portal and copy the query and plan into its memory --- 84,90 ---- errmsg("DECLARE CURSOR ... FOR UPDATE is not supported"), errdetail("Cursors must be READ ONLY."))); ! plan = planner(query, true, stmt->options, NULL); /* * Create a portal and copy the query and plan into its memory Index: src/backend/commands/prepare.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/commands/prepare.c,v retrieving revision 1.26 diff -u -c -r1.26 prepare.c *** src/backend/commands/prepare.c 22 Apr 2004 02:58:20 -0000 1.26 --- src/backend/commands/prepare.c 24 May 2004 14:42:10 -0000 *************** *** 91,97 **** query_list = QueryRewrite(stmt->query); /* Generate plans for queries. Snapshot is already set. */ ! plan_list = pg_plan_queries(query_list, false); /* Save the results. */ StorePreparedStatement(stmt->name, --- 91,97 ---- query_list = QueryRewrite(stmt->query); /* Generate plans for queries. Snapshot is already set. */ ! plan_list = pg_plan_queries(query_list, false, NULL); /* Save the results. */ StorePreparedStatement(stmt->name, Index: src/backend/executor/functions.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/executor/functions.c,v retrieving revision 1.80 diff -u -c -r1.80 functions.c *** src/backend/executor/functions.c 2 Apr 2004 23:14:08 -0000 1.80 --- src/backend/executor/functions.c 24 May 2004 14:42:10 -0000 *************** *** 100,106 **** Plan *planTree; execution_state *newes; ! planTree = pg_plan_query(queryTree); newes = (execution_state *) palloc(sizeof(execution_state)); if (preves) --- 100,106 ---- Plan *planTree; execution_state *newes; ! planTree = pg_plan_query(queryTree, NULL); newes = (execution_state *) palloc(sizeof(execution_state)); if (preves) Index: src/backend/executor/spi.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/executor/spi.c,v retrieving revision 1.113 diff -u -c -r1.113 spi.c *** src/backend/executor/spi.c 1 Apr 2004 21:28:44 -0000 1.113 --- src/backend/executor/spi.c 24 May 2004 14:42:10 -0000 *************** *** 1130,1136 **** QueryDesc *qdesc; DestReceiver *dest; ! planTree = pg_plan_query(queryTree); plan_list = lappend(plan_list, planTree); dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL); --- 1130,1136 ---- QueryDesc *qdesc; DestReceiver *dest; ! planTree = pg_plan_query(queryTree, NULL); plan_list = lappend(plan_list, planTree); dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL); Index: src/backend/optimizer/path/clausesel.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/optimizer/path/clausesel.c,v retrieving revision 1.65 diff -u -c -r1.65 clausesel.c *** src/backend/optimizer/path/clausesel.c 10 May 2004 22:44:45 -0000 1.65 --- src/backend/optimizer/path/clausesel.c 24 May 2004 14:42:11 -0000 *************** *** 489,496 **** } else if (IsA(clause, Param)) { ! /* XXX any way to do better? */ ! s1 = 1.0; } else if (IsA(clause, Const)) { --- 489,504 ---- } else if (IsA(clause, Param)) { ! /* Try to collapse to Const. */ ! Node *collapsed_clause = collapse_parameters_to_const(clause); ! if (IsA(collapsed_clause, Const)) { ! /* bool constant is pretty easy... */ ! s1 = ((bool) ((Const *) collapsed_clause)->constvalue) ? 1.0 : 0.0; ! } else { ! /* Can't collapse to Const. */ ! /* XXX any way to do better? */ ! s1 = 1.0; ! } } else if (IsA(clause, Const)) { Index: src/backend/optimizer/path/indxpath.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/optimizer/path/indxpath.c,v retrieving revision 1.158 diff -u -c -r1.158 indxpath.c *** src/backend/optimizer/path/indxpath.c 27 Mar 2004 00:24:28 -0000 1.158 --- src/backend/optimizer/path/indxpath.c 24 May 2004 14:42:11 -0000 *************** *** 1068,1073 **** --- 1068,1076 ---- rightop = get_rightop(predicate); if (rightop == NULL) return false; /* not a binary opclause */ + + rightop = collapse_parameters_to_const(rightop); + leftop = collapse_parameters_to_const(leftop); if (IsA(rightop, Const)) { pred_var = leftop; *************** *** 1091,1096 **** --- 1094,1102 ---- rightop = get_rightop((Expr *) clause); if (rightop == NULL) return false; /* not a binary opclause */ + + rightop = collapse_parameters_to_const(rightop); + leftop = collapse_parameters_to_const(leftop); if (IsA(rightop, Const)) { clause_var = leftop; *************** *** 1873,1878 **** --- 1879,1885 ---- expr_op = ((OpExpr *) clause)->opno; /* again, required for all current special ops: */ + rightop = collapse_parameters_to_const(rightop); if (!IsA(rightop, Const) || ((Const *) rightop)->constisnull) return false; *************** *** 2056,2062 **** Node *leftop = get_leftop(clause); Node *rightop = get_rightop(clause); Oid expr_op = ((OpExpr *) clause)->opno; ! Const *patt = (Const *) rightop; Const *prefix = NULL; Const *rest = NULL; Pattern_Prefix_Status pstatus; --- 2063,2069 ---- Node *leftop = get_leftop(clause); Node *rightop = get_rightop(clause); Oid expr_op = ((OpExpr *) clause)->opno; ! Const *patt = (Const *) collapse_parameters_to_const(rightop); Const *prefix = NULL; Const *rest = NULL; Pattern_Prefix_Status pstatus; Index: src/backend/optimizer/plan/createplan.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/optimizer/plan/createplan.c,v retrieving revision 1.169 diff -u -c -r1.169 createplan.c *** src/backend/optimizer/plan/createplan.c 25 Apr 2004 18:23:56 -0000 1.169 --- src/backend/optimizer/plan/createplan.c 24 May 2004 14:42:11 -0000 *************** *** 2330,2335 **** --- 2330,2337 ---- * level, but if we are building a subquery then it's important to * report correct info to the outer planner. */ + if (limitOffset) + limitOffset = collapse_parameters_to_const(limitOffset); if (limitOffset && IsA(limitOffset, Const)) { Const *limito = (Const *) limitOffset; *************** *** 2348,2353 **** --- 2350,2357 ---- plan->plan_rows = 1; } } + if (limitCount) + limitCount = collapse_parameters_to_const(limitCount); if (limitCount && IsA(limitCount, Const)) { Const *limitc = (Const *) limitCount; Index: src/backend/optimizer/plan/planner.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/optimizer/plan/planner.c,v retrieving revision 1.169 diff -u -c -r1.169 planner.c *** src/backend/optimizer/plan/planner.c 11 May 2004 02:21:37 -0000 1.169 --- src/backend/optimizer/plan/planner.c 24 May 2004 14:42:11 -0000 *************** *** 71,82 **** * *****************************************************************************/ Plan * ! planner(Query *parse, bool isCursor, int cursorOptions) { double tuple_fraction; Plan *result_plan; Index save_PlannerQueryLevel; List *save_PlannerParamList; /* * The planner can be called recursively (an example is when --- 71,83 ---- * *****************************************************************************/ Plan * ! planner(Query *parse, bool isCursor, int cursorOptions, ParamListInfo boundParams) { double tuple_fraction; Plan *result_plan; Index save_PlannerQueryLevel; List *save_PlannerParamList; + ParamListInfo save_PlannerBoundParamList; /* * The planner can be called recursively (an example is when *************** *** 93,102 **** --- 94,105 ---- */ save_PlannerQueryLevel = PlannerQueryLevel; save_PlannerParamList = PlannerParamList; + save_PlannerBoundParamList = PlannerBoundParamList; /* Initialize state for handling outer-level references and params */ PlannerQueryLevel = 0; /* will be 1 in top-level subquery_planner */ PlannerParamList = NIL; + PlannerBoundParamList = boundParams; /* Determine what fraction of the plan is likely to be scanned */ if (isCursor) *************** *** 139,144 **** --- 142,148 ---- /* restore state for outer planner, if any */ PlannerQueryLevel = save_PlannerQueryLevel; PlannerParamList = save_PlannerParamList; + PlannerBoundParamList = save_PlannerBoundParamList; return result_plan; } *************** *** 401,407 **** /* * Simplify constant expressions. */ ! expr = eval_const_expressions(expr); /* Expand SubLinks to SubPlans */ if (parse->hasSubLinks) --- 405,411 ---- /* * Simplify constant expressions. */ ! expr = eval_const_expressions(expr, NULL); /* Expand SubLinks to SubPlans */ if (parse->hasSubLinks) *************** *** 762,770 **** */ double limit_fraction = 0.0; ! if (IsA(parse->limitCount, Const)) { ! Const *limitc = (Const *) parse->limitCount; int32 count = DatumGetInt32(limitc->constvalue); /* --- 766,775 ---- */ double limit_fraction = 0.0; ! Node *limitCount = collapse_parameters_to_const(parse->limitCount); ! if (IsA(limitCount, Const)) { ! Const *limitc = (Const *) limitCount; int32 count = DatumGetInt32(limitc->constvalue); /* *************** *** 778,788 **** /* We must also consider the OFFSET, if present */ if (parse->limitOffset != NULL) { ! if (IsA(parse->limitOffset, Const)) { int32 offset; ! limitc = (Const *) parse->limitOffset; offset = DatumGetInt32(limitc->constvalue); if (!limitc->constisnull && offset > 0) limit_fraction += (double) offset; --- 783,794 ---- /* We must also consider the OFFSET, if present */ if (parse->limitOffset != NULL) { ! Node *limitOffset = collapse_parameters_to_const(parse->limitCount); ! if (IsA(limitOffset, Const)) { int32 offset; ! limitc = (Const *) limitOffset; offset = DatumGetInt32(limitc->constvalue); if (!limitc->constisnull && offset > 0) limit_fraction += (double) offset; Index: src/backend/optimizer/prep/prepunion.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/optimizer/prep/prepunion.c,v retrieving revision 1.110 diff -u -c -r1.110 prepunion.c *** src/backend/optimizer/prep/prepunion.c 11 May 2004 22:43:55 -0000 1.110 --- src/backend/optimizer/prep/prepunion.c 24 May 2004 14:42:11 -0000 *************** *** 430,435 **** --- 430,436 ---- TargetEntry *inputtle = (TargetEntry *) lfirst(input_tlist); TargetEntry *reftle = (TargetEntry *) lfirst(refnames_tlist); int32 colTypmod; + Node *collapsed_expr; Assert(inputtle->resdom->resno == resno); Assert(reftle->resdom->resno == resno); *************** *** 449,456 **** * subquery-scan plans; we don't want phony constants appearing in * the output tlists of upper-level nodes! */ ! if (hack_constants && inputtle->expr && IsA(inputtle->expr, Const)) ! expr = (Node *) inputtle->expr; else expr = (Node *) makeVar(0, inputtle->resdom->resno, --- 450,459 ---- * subquery-scan plans; we don't want phony constants appearing in * the output tlists of upper-level nodes! */ ! if (hack_constants && inputtle->expr && ! NULL != (collapsed_expr = collapse_parameters_to_const((Node *) inputtle->expr)) && ! IsA(collapsed_expr, Const)) ! expr = collapsed_expr; else expr = (Node *) makeVar(0, inputtle->resdom->resno, Index: src/backend/optimizer/util/clauses.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/optimizer/util/clauses.c,v retrieving revision 1.170 diff -u -c -r1.170 clauses.c *** src/backend/optimizer/util/clauses.c 10 May 2004 22:44:45 -0000 1.170 --- src/backend/optimizer/util/clauses.c 24 May 2004 14:42:12 -0000 *************** *** 28,37 **** --- 28,39 ---- #include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/planmain.h" + #include "optimizer/planner.h" #include "optimizer/var.h" #include "parser/analyze.h" #include "parser/parse_clause.h" #include "parser/parse_expr.h" + #include "parser/parse_type.h" #include "tcop/tcopprot.h" #include "utils/acl.h" #include "utils/builtins.h" *************** *** 41,48 **** --- 43,61 ---- #include "utils/syscache.h" + ParamListInfo PlannerBoundParamList = NULL; /* to keep track of currently-bound parameter values */ + + + typedef struct + { + List *active_fns; + ParamListInfo params; + int nparams; + } eval_const_expressions_context; + typedef struct { + int nargs; List *args; int *usecounts; *************** *** 57,73 **** static bool contain_volatile_functions_walker(Node *node, void *context); static bool contain_nonstrict_functions_walker(Node *node, void *context); static bool set_coercionform_dontcare_walker(Node *node, void *context); ! static Node *eval_const_expressions_mutator(Node *node, List *active_fns); static List *simplify_or_arguments(List *args, bool *haveNull, bool *forceTrue); static List *simplify_and_arguments(List *args, bool *haveNull, bool *forceFalse); static Expr *simplify_function(Oid funcid, Oid result_type, List *args, ! bool allow_inline, List *active_fns); static Expr *evaluate_function(Oid funcid, Oid result_type, List *args, HeapTuple func_tuple); static Expr *inline_function(Oid funcid, Oid result_type, List *args, ! HeapTuple func_tuple, List *active_fns); static Node *substitute_actual_parameters(Node *expr, int nargs, List *args, int *usecounts); static Node *substitute_actual_parameters_mutator(Node *node, --- 70,86 ---- static bool contain_volatile_functions_walker(Node *node, void *context); static bool contain_nonstrict_functions_walker(Node *node, void *context); static bool set_coercionform_dontcare_walker(Node *node, void *context); ! static Node *eval_const_expressions_mutator(Node *node, eval_const_expressions_context *context); static List *simplify_or_arguments(List *args, bool *haveNull, bool *forceTrue); static List *simplify_and_arguments(List *args, bool *haveNull, bool *forceFalse); static Expr *simplify_function(Oid funcid, Oid result_type, List *args, ! bool allow_inline, eval_const_expressions_context *active_fns); static Expr *evaluate_function(Oid funcid, Oid result_type, List *args, HeapTuple func_tuple); static Expr *inline_function(Oid funcid, Oid result_type, List *args, ! HeapTuple func_tuple, eval_const_expressions_context *active_fns); static Node *substitute_actual_parameters(Node *expr, int nargs, List *args, int *usecounts); static Node *substitute_actual_parameters_mutator(Node *node, *************** *** 1062,1078 **** *-------------------- */ Node * ! eval_const_expressions(Node *node) { /* ! * The context for the mutator is a list of SQL functions being ! * recursively simplified, so we start with an empty list. */ ! return eval_const_expressions_mutator(node, NIL); } static Node * ! eval_const_expressions_mutator(Node *node, List *active_fns) { if (node == NULL) return NULL; --- 1075,1128 ---- *-------------------- */ Node * ! eval_const_expressions(Node *node, ParamListInfo params) { /* ! * The context for the mutator is the parameter list plus ! * a list of SQL functions being recursively simplified. ! * The function list is initially empty. */ ! int i; ! eval_const_expressions_context context; ! context.active_fns = NIL; ! context.params = params; ! ! if (params != NULL) { ! /* Count and check parameters. */ ! for (i = 0; params[i].kind != PARAM_INVALID; ++i) { ! if (params[i].id != (i + 1)) ! elog(ERROR, "param id mismatch: expected %d but was %d", i+1, params[i].id); ! } ! ! context.nparams = i; ! } else { ! context.nparams = 0; ! } ! ! return eval_const_expressions_mutator(node, &context); ! } ! ! /* ! * Helper for selectivity functions: apply a Param to Const replacement, ! * using parameters from PlannerBoundParamList, and return the new tree. Might ! * not copy the tree if it's obvious no change will happen. ! */ ! Node * ! collapse_parameters_to_const(Node *node) ! { ! /* Already a leaf? */ ! if (IsA(node, Const) || IsA(node, Var)) ! return node; ! ! /* No parameters? */ ! if (PlannerBoundParamList == NULL || PlannerBoundParamList[0].kind == PARAM_INVALID) ! return node; ! ! return eval_const_expressions(node, PlannerBoundParamList); } static Node * ! eval_const_expressions_mutator(Node *node, eval_const_expressions_context *context) { if (node == NULL) return NULL; *************** *** 1090,1103 **** */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) active_fns); /* * Code for op/func reduction is pretty bulky, so split it out as * a separate function. */ simple = simplify_function(expr->funcid, expr->funcresulttype, args, ! true, active_fns); if (simple) /* successfully simplified it */ return (Node *) simple; --- 1140,1153 ---- */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) context); /* * Code for op/func reduction is pretty bulky, so split it out as * a separate function. */ simple = simplify_function(expr->funcid, expr->funcresulttype, args, ! true, context); if (simple) /* successfully simplified it */ return (Node *) simple; *************** *** 1128,1134 **** */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) active_fns); /* * Need to get OID of underlying function. Okay to scribble on --- 1178,1184 ---- */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) context); /* * Need to get OID of underlying function. Okay to scribble on *************** *** 1141,1147 **** * a separate function. */ simple = simplify_function(expr->opfuncid, expr->opresulttype, args, ! true, active_fns); if (simple) /* successfully simplified it */ return (Node *) simple; --- 1191,1197 ---- * a separate function. */ simple = simplify_function(expr->opfuncid, expr->opresulttype, args, ! true, context); if (simple) /* successfully simplified it */ return (Node *) simple; *************** *** 1176,1182 **** */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) active_fns); /* * We must do our own check for NULLs because DistinctExpr has --- 1226,1232 ---- */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) context); /* * We must do our own check for NULLs because DistinctExpr has *************** *** 1220,1226 **** * as a separate function. */ simple = simplify_function(expr->opfuncid, expr->opresulttype, ! args, false, active_fns); if (simple) /* successfully simplified it */ { /* --- 1270,1276 ---- * as a separate function. */ simple = simplify_function(expr->opfuncid, expr->opresulttype, ! args, false, context); if (simple) /* successfully simplified it */ { /* *************** *** 1261,1267 **** */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) active_fns); switch (expr->boolop) { --- 1311,1317 ---- */ args = (List *) expression_tree_mutator((Node *) expr->args, eval_const_expressions_mutator, ! (void *) context); switch (expr->boolop) { *************** *** 1354,1360 **** Node *arg; arg = eval_const_expressions_mutator((Node *) relabel->arg, ! active_fns); /* * If we find stacked RelabelTypes (eg, from foo :: int :: oid) we --- 1404,1410 ---- Node *arg; arg = eval_const_expressions_mutator((Node *) relabel->arg, ! context); /* * If we find stacked RelabelTypes (eg, from foo :: int :: oid) we *************** *** 1418,1424 **** /* Simplify the test expression, if any */ newarg = eval_const_expressions_mutator((Node *) caseexpr->arg, ! active_fns); /* Simplify the WHEN clauses */ FastListInit(&newargs); --- 1468,1474 ---- /* Simplify the test expression, if any */ newarg = eval_const_expressions_mutator((Node *) caseexpr->arg, ! context); /* Simplify the WHEN clauses */ FastListInit(&newargs); *************** *** 1428,1434 **** CaseWhen *casewhen = (CaseWhen *) expression_tree_mutator((Node *) lfirst(arg), eval_const_expressions_mutator, ! (void *) active_fns); Assert(IsA(casewhen, CaseWhen)); if (casewhen->expr == NULL || --- 1478,1484 ---- CaseWhen *casewhen = (CaseWhen *) expression_tree_mutator((Node *) lfirst(arg), eval_const_expressions_mutator, ! (void *) context); Assert(IsA(casewhen, CaseWhen)); if (casewhen->expr == NULL || *************** *** 1458,1464 **** /* Simplify the default result */ defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult, ! active_fns); /* * If no non-FALSE alternatives, CASE reduces to the default --- 1508,1514 ---- /* Simplify the default result */ defresult = eval_const_expressions_mutator((Node *) caseexpr->defresult, ! context); /* * If no non-FALSE alternatives, CASE reduces to the default *************** *** 1488,1494 **** Node *e; e = eval_const_expressions_mutator((Node *) lfirst(element), ! active_fns); if (!IsA(e, Const)) all_const = false; FastAppend(&newelems, e); --- 1538,1544 ---- Node *e; e = eval_const_expressions_mutator((Node *) lfirst(element), ! context); if (!IsA(e, Const)) all_const = false; FastAppend(&newelems, e); *************** *** 1519,1525 **** Node *e; e = eval_const_expressions_mutator((Node *) lfirst(arg), ! active_fns); /* * We can remove null constants from the list. For a non-null --- 1569,1575 ---- Node *e; e = eval_const_expressions_mutator((Node *) lfirst(arg), ! context); /* * We can remove null constants from the list. For a non-null *************** *** 1555,1561 **** Node *arg; arg = eval_const_expressions_mutator((Node *) fselect->arg, ! active_fns); if (arg && IsA(arg, Var) && ((Var *) arg)->varattno == InvalidAttrNumber) { --- 1605,1611 ---- Node *arg; arg = eval_const_expressions_mutator((Node *) fselect->arg, ! context); if (arg && IsA(arg, Var) && ((Var *) arg)->varattno == InvalidAttrNumber) { *************** *** 1580,1585 **** --- 1630,1665 ---- newfselect->resulttypmod = fselect->resulttypmod; return (Node *) newfselect; } + if (IsA(node, Param)) + { + /* + * Attempt to substitute concrete parameter values in, if we have them. + */ + + Param *param = (Param *) node; + + if (param->paramkind == PARAM_NUM && context->params != NULL) { + /* + * Return a Constant in place of this Param. + */ + + Type type; + Const *replacement; + + if (param->paramid <= 0 || param->paramid > context->nparams) + elog(ERROR, "invalid paramid: %d", param->paramid); + + type = typeidType(param->paramtype); + replacement = makeConst(param->paramtype, + typeLen(type), + context->params[param->paramid - 1].value, + context->params[param->paramid - 1].isnull, + typeByVal(type)); + + ReleaseSysCache(type); + return (Node *) replacement; + } + } /* * For any node type not handled above, we recurse using *************** *** 1589,1595 **** * simplify constant expressions in its subscripts. */ return expression_tree_mutator(node, eval_const_expressions_mutator, ! (void *) active_fns); } /* --- 1669,1675 ---- * simplify constant expressions in its subscripts. */ return expression_tree_mutator(node, eval_const_expressions_mutator, ! (void *) context); } /* *************** *** 1727,1733 **** */ static Expr * simplify_function(Oid funcid, Oid result_type, List *args, ! bool allow_inline, List *active_fns) { HeapTuple func_tuple; Expr *newexpr; --- 1807,1813 ---- */ static Expr * simplify_function(Oid funcid, Oid result_type, List *args, ! bool allow_inline, eval_const_expressions_context *context) { HeapTuple func_tuple; Expr *newexpr; *************** *** 1750,1756 **** if (!newexpr && allow_inline) newexpr = inline_function(funcid, result_type, args, ! func_tuple, active_fns); ReleaseSysCache(func_tuple); --- 1830,1836 ---- if (!newexpr && allow_inline) newexpr = inline_function(funcid, result_type, args, ! func_tuple, context); ReleaseSysCache(func_tuple); *************** *** 1854,1860 **** */ static Expr * inline_function(Oid funcid, Oid result_type, List *args, ! HeapTuple func_tuple, List *active_fns) { Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); bool polymorphic = false; --- 1934,1940 ---- */ static Expr * inline_function(Oid funcid, Oid result_type, List *args, ! HeapTuple func_tuple, eval_const_expressions_context *context) { Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); bool polymorphic = false; *************** *** 1884,1890 **** return NULL; /* Check for recursive function, and give up trying to expand if so */ ! if (oidMember(funcid, active_fns)) return NULL; /* Check permission to call function (fail later, if not) */ --- 1964,1970 ---- return NULL; /* Check for recursive function, and give up trying to expand if so */ ! if (oidMember(funcid, context->active_fns)) return NULL; /* Check permission to call function (fail later, if not) */ *************** *** 2077,2084 **** * Recursively try to simplify the modified expression. Here we must * add the current function to the context list of active functions. */ ! newexpr = eval_const_expressions_mutator(newexpr, ! lconso(funcid, active_fns)); error_context_stack = sqlerrcontext.previous; --- 2157,2164 ---- * Recursively try to simplify the modified expression. Here we must * add the current function to the context list of active functions. */ ! context->active_fns = lconso(funcid, context->active_fns); ! newexpr = eval_const_expressions_mutator(newexpr, context); error_context_stack = sqlerrcontext.previous; Index: src/backend/tcop/postgres.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/tcop/postgres.c,v retrieving revision 1.414 diff -u -c -r1.414 postgres.c *** src/backend/tcop/postgres.c 23 May 2004 03:50:45 -0000 1.414 --- src/backend/tcop/postgres.c 24 May 2004 14:42:12 -0000 *************** *** 631,637 **** /* Generate a plan for a single already-rewritten query. */ Plan * ! pg_plan_query(Query *querytree) { Plan *plan; --- 631,637 ---- /* Generate a plan for a single already-rewritten query. */ Plan * ! pg_plan_query(Query *querytree, ParamListInfo params) { Plan *plan; *************** *** 643,649 **** ResetUsage(); /* call the optimizer */ ! plan = planner(querytree, false, 0); if (log_planner_stats) ShowUsage("PLANNER STATISTICS"); --- 643,649 ---- ResetUsage(); /* call the optimizer */ ! plan = planner(querytree, false, 0, params); if (log_planner_stats) ShowUsage("PLANNER STATISTICS"); *************** *** 688,694 **** * statements in the rewriter's output.) */ List * ! pg_plan_queries(List *querytrees, bool needSnapshot) { List *plan_list = NIL; List *query_list; --- 688,694 ---- * statements in the rewriter's output.) */ List * ! pg_plan_queries(List *querytrees, bool needSnapshot, ParamListInfo params) { List *plan_list = NIL; List *query_list; *************** *** 710,716 **** SetQuerySnapshot(); needSnapshot = false; } ! plan = pg_plan_query(query); } plan_list = lappend(plan_list, plan); --- 710,716 ---- SetQuerySnapshot(); needSnapshot = false; } ! plan = pg_plan_query(query, params); } plan_list = lappend(plan_list, plan); *************** *** 868,874 **** querytree_list = pg_analyze_and_rewrite(parsetree, NULL, 0); ! plantree_list = pg_plan_queries(querytree_list, true); /* If we got a cancel signal in analysis or planning, quit */ CHECK_FOR_INTERRUPTS(); --- 868,874 ---- querytree_list = pg_analyze_and_rewrite(parsetree, NULL, 0); ! plantree_list = pg_plan_queries(querytree_list, true, NULL); /* If we got a cancel signal in analysis or planning, quit */ CHECK_FOR_INTERRUPTS(); *************** *** 1206,1212 **** querytree_list = pg_rewrite_queries(querytree_list); ! plantree_list = pg_plan_queries(querytree_list, true); } else { --- 1206,1217 ---- querytree_list = pg_rewrite_queries(querytree_list); ! /* If this is the unnamed statement and we have parameters, defer query planning until Bind. */ ! if (!is_named && numParams > 0) { ! plantree_list = NIL; ! } else { ! plantree_list = pg_plan_queries(querytree_list, true, NULL); ! } } else { *************** *** 1357,1368 **** else portal = CreatePortal(portal_name, false, false); ! PortalDefineQuery(portal, ! pstmt->query_string, ! pstmt->commandTag, ! pstmt->query_list, ! pstmt->plan_list, ! pstmt->context); /* * Fetch parameters, if any, and store in the portal's memory context. --- 1362,1368 ---- else portal = CreatePortal(portal_name, false, false); ! /* Defer portal query definition until we're sure planning is done. */ /* * Fetch parameters, if any, and store in the portal's memory context. *************** *** 1517,1524 **** pq_getmsgend(input_message); /* ! * Start portal execution. */ PortalStart(portal, params); /* --- 1517,1542 ---- pq_getmsgend(input_message); /* ! * If this is the unnamed statement, we may not have planned the ! * query yet. In that case, do the planning (in the query's ! * memory context) now that we have concrete parameter values. ! */ ! if (pstmt->plan_list == NIL && pstmt->query_list != NIL) { ! MemoryContext oldContext = MemoryContextSwitchTo(pstmt->context); ! pstmt->plan_list = pg_plan_queries(pstmt->query_list, true, params); ! MemoryContextSwitchTo(oldContext); ! } ! ! /* ! * Define portal and start execution. */ + PortalDefineQuery(portal, + pstmt->query_string, + pstmt->commandTag, + pstmt->query_list, + pstmt->plan_list, + pstmt->context); + PortalStart(portal, params); /* Index: src/backend/utils/adt/selfuncs.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/selfuncs.c,v retrieving revision 1.158 diff -u -c -r1.158 selfuncs.c *** src/backend/utils/adt/selfuncs.c 27 Feb 2004 21:44:34 -0000 1.158 --- src/backend/utils/adt/selfuncs.c 24 May 2004 14:42:12 -0000 *************** *** 242,247 **** --- 242,249 ---- &vardata, &other, &varonleft)) PG_RETURN_FLOAT8(DEFAULT_EQ_SEL); + other = collapse_parameters_to_const(other); + /* * If the something is a NULL constant, assume operator is strict and * return zero, ie, operator will never return TRUE. *************** *** 711,716 **** --- 713,720 ---- &vardata, &other, &varonleft)) PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); + other = collapse_parameters_to_const(other); + /* * Can't do anything useful if the something is not a constant, * either. *************** *** 787,792 **** --- 791,798 ---- &vardata, &other, &varonleft)) PG_RETURN_FLOAT8(DEFAULT_INEQ_SEL); + other = collapse_parameters_to_const(other); + /* * Can't do anything useful if the something is not a constant, * either. *************** *** 870,876 **** if (!get_restriction_variable(root, args, varRelid, &vardata, &other, &varonleft)) return DEFAULT_MATCH_SEL; ! if (!varonleft || !IsA(other, Const)) { ReleaseVariableStats(vardata); return DEFAULT_MATCH_SEL; --- 876,891 ---- if (!get_restriction_variable(root, args, varRelid, &vardata, &other, &varonleft)) return DEFAULT_MATCH_SEL; ! ! if (!varonleft) ! { ! ReleaseVariableStats(vardata); ! return DEFAULT_MATCH_SEL; ! } ! ! other = collapse_parameters_to_const(other); ! ! if (!IsA(other, Const)) { ReleaseVariableStats(vardata); return DEFAULT_MATCH_SEL; Index: src/backend/utils/cache/relcache.c =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/cache/relcache.c,v retrieving revision 1.202 diff -u -c -r1.202 relcache.c *** src/backend/utils/cache/relcache.c 8 May 2004 19:09:25 -0000 1.202 --- src/backend/utils/cache/relcache.c 24 May 2004 14:42:13 -0000 *************** *** 2680,2686 **** */ result = (List *) flatten_andors((Node *) result); ! result = (List *) eval_const_expressions((Node *) result); /* * Also mark any coercion format fields as "don't care", so that the --- 2680,2686 ---- */ result = (List *) flatten_andors((Node *) result); ! result = (List *) eval_const_expressions((Node *) result, NULL); /* * Also mark any coercion format fields as "don't care", so that the *************** *** 2754,2760 **** */ result = (List *) canonicalize_qual((Expr *) result); ! result = (List *) eval_const_expressions((Node *) result); /* * Also mark any coercion format fields as "don't care", so that the --- 2754,2760 ---- */ result = (List *) canonicalize_qual((Expr *) result); ! result = (List *) eval_const_expressions((Node *) result, NULL); /* * Also mark any coercion format fields as "don't care", so that the Index: src/include/optimizer/clauses.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/optimizer/clauses.h,v retrieving revision 1.73 diff -u -c -r1.73 clauses.h *** src/include/optimizer/clauses.h 14 Mar 2004 23:41:27 -0000 1.73 --- src/include/optimizer/clauses.h 24 May 2004 14:42:13 -0000 *************** *** 15,22 **** #define CLAUSES_H #include "nodes/relation.h" ! #define is_opclause(clause) ((clause) != NULL && IsA(clause, OpExpr)) #define is_funcclause(clause) ((clause) != NULL && IsA(clause, FuncExpr)) --- 15,23 ---- #define CLAUSES_H #include "nodes/relation.h" + #include "nodes/params.h" ! extern ParamListInfo PlannerBoundParamList; /* to keep track of externally-specified parameter values */ #define is_opclause(clause) ((clause) != NULL && IsA(clause, OpExpr)) #define is_funcclause(clause) ((clause) != NULL && IsA(clause, FuncExpr)) *************** *** 65,71 **** extern void set_coercionform_dontcare(Node *node); ! extern Node *eval_const_expressions(Node *node); extern bool expression_tree_walker(Node *node, bool (*walker) (), void *context); --- 66,74 ---- extern void set_coercionform_dontcare(Node *node); ! extern Node *eval_const_expressions(Node *node, ParamListInfo params); ! ! extern Node *collapse_parameters_to_const(Node *node); extern bool expression_tree_walker(Node *node, bool (*walker) (), void *context); Index: src/include/optimizer/planner.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/optimizer/planner.h,v retrieving revision 1.28 diff -u -c -r1.28 planner.h *** src/include/optimizer/planner.h 29 Nov 2003 22:41:07 -0000 1.28 --- src/include/optimizer/planner.h 24 May 2004 14:42:13 -0000 *************** *** 16,24 **** #include "nodes/parsenodes.h" #include "nodes/plannodes.h" ! ! extern Plan *planner(Query *parse, bool isCursor, int cursorOptions); extern Plan *subquery_planner(Query *parse, double tuple_fraction); #endif /* PLANNER_H */ --- 16,24 ---- #include "nodes/parsenodes.h" #include "nodes/plannodes.h" + #include "nodes/params.h" ! extern Plan *planner(Query *parse, bool isCursor, int cursorOptions, ParamListInfo extParams); extern Plan *subquery_planner(Query *parse, double tuple_fraction); #endif /* PLANNER_H */ Index: src/include/tcop/tcopprot.h =================================================================== RCS file: /projects/cvsroot/pgsql-server/src/include/tcop/tcopprot.h,v retrieving revision 1.65 diff -u -c -r1.65 tcopprot.h *** src/include/tcop/tcopprot.h 11 Apr 2004 00:54:45 -0000 1.65 --- src/include/tcop/tcopprot.h 24 May 2004 14:42:13 -0000 *************** *** 24,29 **** --- 24,30 ---- #include "executor/execdesc.h" #include "tcop/dest.h" #include "utils/guc.h" + #include "nodes/params.h" extern DLLIMPORT sigjmp_buf Warn_restart; *************** *** 57,64 **** extern List *pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams); extern List *pg_rewrite_queries(List *querytree_list); ! extern Plan *pg_plan_query(Query *querytree); ! extern List *pg_plan_queries(List *querytrees, bool needSnapshot); extern bool assign_max_stack_depth(int newval, bool doit, GucSource source); --- 58,65 ---- extern List *pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams); extern List *pg_rewrite_queries(List *querytree_list); ! extern Plan *pg_plan_query(Query *querytree, ParamListInfo params); ! extern List *pg_plan_queries(List *querytrees, bool needSnapshot, ParamListInfo params); extern bool assign_max_stack_depth(int newval, bool doit, GucSource source); #!/usr/bin/env python import sys, socket, struct # V3 primitives def send_startup(conn, db, user): paramValues = 'user\0%s\0database\0%s\0\0' % (user, db) msgLen = 8 + len(paramValues) msg = struct.pack('>II', msgLen, 196608) + paramValues conn.send(msg) def send(conn, type, data): msg = type + struct.pack('>i', len(data)+4) + data conn.send(msg) def send_parse(conn, statement, query, oids): sys.stdout.write('<= Parse(%s,%s,%s)\n' % (repr(statement), repr(query), repr(oids))) msg = statement + '\0' msg += query + '\0' msg += struct.pack('>H', len(oids)) for oid in oids: msg += struct.pack('>I', oid) send(conn, 'P', msg) def send_bind(conn, portal, statement, params): sys.stdout.write('<= Bind(%s,%s,%s)\n' % (repr(portal), repr(statement), repr(params))) msg = portal + '\0' msg += statement + '\0' msg += struct.pack('>H', 0) # param format count msg += struct.pack('>H', len(params)) # param count for param in params: if param is None: msg += struct.pack('>i', -1) # NULL else: msg += struct.pack('>i', len(param) + 1) # param data length msg += param + '\0' msg += struct.pack('>H', 0) # result format count send(conn, 'B', msg) def send_execute(conn, portal, count): sys.stdout.write('<= Execute(%s,%d)\n' % (repr(portal), count)) msg = portal + '\0' msg += struct.pack('>i', 0) # max rows send(conn, 'E', msg) def send_sync(conn): sys.stdout.write('<= Sync\n') send(conn, 'S', '') # # Connection setup. # def connect(host, port, db, user): conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) conn.connect( (host, port) ) send_startup(conn, db, user) type = conn.recv(1) if type != 'R': raise RuntimeError('bad startup type: ' + type) length, = struct.unpack('>i', conn.recv(4)) data = conn.recv(length-4) res, = struct.unpack('>i', data[:4]) if res != 0: raise RuntimeError("auth was needed but we don't do it") process_results(conn) return conn # # Normal connection processing # def make_log(type): return lambda conn, data: sys.stdout.write(' => ' + type + '\n') def make_server_response(type): def server_response(conn, data, type=type): sys.stdout.write(' => ' + type + '\n') i = 0 while i != -1 and data[i] != '\0': type = data[i] end = data.find('\0', i+1) if end == -1: value = data[i+1:] i = -1 else: value = data[i+1:end] i = end + 1 sys.stdout.write(' => ' + type + ': ' + value + '\n') return server_response def command_complete(conn, data): sys.stdout.write(' => CommandComplete: ' + data + '\n') def data_row(conn, data): sys.stdout.write(' => DataRow ') count, = struct.unpack('>H', data[:2]) o = 2 for i in xrange(count): length, = struct.unpack('>i', data[o:o+4]) o += 4 if length == -1: sys.stdout.write('NULL,') else: sys.stdout.write(data[o:o+length] + ',') o += length sys.stdout.write('\n') handlers = { 'K': make_log('BackendKeyData'), '2': make_log('BindComplete'), '3': make_log('CloseComplete'), 'C': command_complete, 'D': data_row, 'I': make_log('EmptyQuery'), 'E': make_server_response('ErrorResponse'), 'N': make_server_response('NoticeResponse'), 'S': make_log('ParameterStatus'), '1': make_log('ParseComplete'), 'Z': make_log('ReadyForQuery') } def process_results(conn): seen_sync = False type = None while type != 'Z': type = conn.recv(1) if not type: raise RuntimeError('EOF seen') length, = struct.unpack('>i', conn.recv(4)) data = conn.recv(length-4) if not handlers.has_key(type): raise RuntimeError('Unhandled message type ' + type) handlers[type](conn,data) statement_number = 1 def combos(conn, query, oids, params, params_2): global statement_number send_parse(conn, '', query, oids) send_parse(conn, '', query, oids) send_bind(conn, '', '', params) send_execute(conn, '', 0) send_sync(conn) process_results(conn) send_bind(conn, '', '', params_2) send_execute(conn, '', 0) send_sync(conn) process_results(conn) stmt = 's_%d' % statement_number statement_number += 1 send_parse(conn, stmt, query, oids) send_bind(conn, '', stmt, params) send_execute(conn, '', 0) send_sync(conn) process_results(conn) send_bind(conn, '', stmt, params_2) send_execute(conn, '', 0) send_sync(conn) process_results(conn) def tests(conn): # Empty query combos(conn=conn, query='', oids=(), params=(), params_2=()) # Simple SELECT combos(conn=conn, query='SELECT 1', oids=(), params=(), params_2=()) # Simple parameterized SELECT combos(conn=conn, query='SELECT $1', oids=(23,), params=('42',), params_2=('24',)) # Parameterized SELECT that calls a function that can be preevaluated combos(conn=conn, query='SELECT abs($1)', oids=(23,), params=('42',), params_2=('-24',)) # Parameterized SELECT that calls a function that can't be preevaluated combos(conn=conn, query='SELECT abs($1 + random())', oids=(23,), params=('42',), params_2=('-24',)) if __name__ == '__main__': host, port, db, user = sys.argv[1:] conn = connect(host, int(port), db, user) tests(conn) send(conn, 'X', '') conn.close()
pgsql-patches by date: