Commit d0cfc3d6 authored by Tom Lane's avatar Tom Lane

Add a debugging option to stress-test outfuncs.c and readfuncs.c.

In the normal course of operation, query trees will be serialized only if
they are stored as views or rules; and plan trees will be serialized only
if they get passed to parallel-query workers.  This leaves an awful lot of
opportunity for bugs/oversights to not get detected, as indeed we've just
been reminded of the hard way.

To improve matters, this patch adds a new compile option
WRITE_READ_PARSE_PLAN_TREES, which is modeled on the longstanding option
COPY_PARSE_PLAN_TREES; but instead of passing all parse and plan trees
through copyObject, it passes them through nodeToString + stringToNode.
Enabling this option in a buildfarm animal or two will catch problems
at least for cases that are exercised by the regression tests.

A small problem with this idea is that readfuncs.c historically has
discarded location fields, on the reasonable grounds that parse
locations in a retrieved view are not relevant to the current query.
But doing that in WRITE_READ_PARSE_PLAN_TREES breaks pg_stat_statements,
and it could cause problems for future improvements that might try to
report error locations at runtime.  To fix that, provide a variant
behavior in readfuncs.c that makes it restore location fields when
told to.

In passing, const-ify the string arguments of stringToNode and its
subsidiary functions, just because it annoyed me that they weren't
const already.

Discussion: https://postgr.es/m/17114.1537138992@sss.pgh.pa.us
parent db1071d4
...@@ -28,18 +28,30 @@ ...@@ -28,18 +28,30 @@
/* Static state for pg_strtok */ /* Static state for pg_strtok */
static char *pg_strtok_ptr = NULL; static const char *pg_strtok_ptr = NULL;
/* State flag that determines how readfuncs.c should treat location fields */
#ifdef WRITE_READ_PARSE_PLAN_TREES
bool restore_location_fields = false;
#endif
/* /*
* stringToNode - * stringToNode -
* returns a Node with a given legal ASCII representation * builds a Node tree from its string representation (assumed valid)
*
* restore_loc_fields instructs readfuncs.c whether to restore location
* fields rather than set them to -1. This is currently only supported
* in builds with the WRITE_READ_PARSE_PLAN_TREES debugging flag set.
*/ */
void * static void *
stringToNode(char *str) stringToNodeInternal(const char *str, bool restore_loc_fields)
{ {
char *save_strtok;
void *retval; void *retval;
const char *save_strtok;
#ifdef WRITE_READ_PARSE_PLAN_TREES
bool save_restore_location_fields;
#endif
/* /*
* We save and restore the pre-existing state of pg_strtok. This makes the * We save and restore the pre-existing state of pg_strtok. This makes the
...@@ -51,13 +63,45 @@ stringToNode(char *str) ...@@ -51,13 +63,45 @@ stringToNode(char *str)
pg_strtok_ptr = str; /* point pg_strtok at the string to read */ pg_strtok_ptr = str; /* point pg_strtok at the string to read */
/*
* If enabled, likewise save/restore the location field handling flag.
*/
#ifdef WRITE_READ_PARSE_PLAN_TREES
save_restore_location_fields = restore_location_fields;
restore_location_fields = restore_loc_fields;
#endif
retval = nodeRead(NULL, 0); /* do the reading */ retval = nodeRead(NULL, 0); /* do the reading */
pg_strtok_ptr = save_strtok; pg_strtok_ptr = save_strtok;
#ifdef WRITE_READ_PARSE_PLAN_TREES
restore_location_fields = save_restore_location_fields;
#endif
return retval; return retval;
} }
/*
* Externally visible entry points
*/
void *
stringToNode(const char *str)
{
return stringToNodeInternal(str, false);
}
#ifdef WRITE_READ_PARSE_PLAN_TREES
void *
stringToNodeWithLocations(const char *str)
{
return stringToNodeInternal(str, true);
}
#endif
/***************************************************************************** /*****************************************************************************
* *
* the lisp token parser * the lisp token parser
...@@ -104,11 +148,11 @@ stringToNode(char *str) ...@@ -104,11 +148,11 @@ stringToNode(char *str)
* code should add backslashes to a string constant to ensure it is treated * code should add backslashes to a string constant to ensure it is treated
* as a single token. * as a single token.
*/ */
char * const char *
pg_strtok(int *length) pg_strtok(int *length)
{ {
char *local_str; /* working pointer to string */ const char *local_str; /* working pointer to string */
char *ret_str; /* start of token to return */ const char *ret_str; /* start of token to return */
local_str = pg_strtok_ptr; local_str = pg_strtok_ptr;
...@@ -166,7 +210,7 @@ pg_strtok(int *length) ...@@ -166,7 +210,7 @@ pg_strtok(int *length)
* any protective backslashes in the token are removed. * any protective backslashes in the token are removed.
*/ */
char * char *
debackslash(char *token, int length) debackslash(const char *token, int length)
{ {
char *result = palloc(length + 1); char *result = palloc(length + 1);
char *ptr = result; char *ptr = result;
...@@ -198,10 +242,10 @@ debackslash(char *token, int length) ...@@ -198,10 +242,10 @@ debackslash(char *token, int length)
* Assumption: the ascii representation is legal * Assumption: the ascii representation is legal
*/ */
static NodeTag static NodeTag
nodeTokenType(char *token, int length) nodeTokenType(const char *token, int length)
{ {
NodeTag retval; NodeTag retval;
char *numptr; const char *numptr;
int numlen; int numlen;
/* /*
...@@ -269,7 +313,7 @@ nodeTokenType(char *token, int length) ...@@ -269,7 +313,7 @@ nodeTokenType(char *token, int length)
* this should only be invoked from within a stringToNode operation). * this should only be invoked from within a stringToNode operation).
*/ */
void * void *
nodeRead(char *token, int tok_len) nodeRead(const char *token, int tok_len)
{ {
Node *result; Node *result;
NodeTag type; NodeTag type;
......
...@@ -17,10 +17,14 @@ ...@@ -17,10 +17,14 @@
* never read executor state trees, either. * never read executor state trees, either.
* *
* Parse location fields are written out by outfuncs.c, but only for * Parse location fields are written out by outfuncs.c, but only for
* possible debugging use. When reading a location field, we discard * debugging use. When reading a location field, we normally discard
* the stored value and set the location field to -1 (ie, "unknown"). * the stored value and set the location field to -1 (ie, "unknown").
* This is because nodes coming from a stored rule should not be thought * This is because nodes coming from a stored rule should not be thought
* to have a known location in the current query's text. * to have a known location in the current query's text.
* However, if restore_location_fields is true, we do restore location
* fields from the string. This is currently intended only for use by the
* WRITE_READ_PARSE_PLAN_TREES test code, which doesn't want to cause
* any change in the node contents.
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -51,7 +55,7 @@ ...@@ -51,7 +55,7 @@
/* And a few guys need only the pg_strtok support fields */ /* And a few guys need only the pg_strtok support fields */
#define READ_TEMP_LOCALS() \ #define READ_TEMP_LOCALS() \
char *token; \ const char *token; \
int length int length
/* ... but most need both */ /* ... but most need both */
...@@ -120,12 +124,19 @@ ...@@ -120,12 +124,19 @@
token = pg_strtok(&length); /* get field value */ \ token = pg_strtok(&length); /* get field value */ \
local_node->fldname = nullable_string(token, length) local_node->fldname = nullable_string(token, length)
/* Read a parse location field (and throw away the value, per notes above) */ /* Read a parse location field (and possibly throw away the value) */
#ifdef WRITE_READ_PARSE_PLAN_TREES
#define READ_LOCATION_FIELD(fldname) \
token = pg_strtok(&length); /* skip :fldname */ \
token = pg_strtok(&length); /* get field value */ \
local_node->fldname = restore_location_fields ? atoi(token) : -1
#else
#define READ_LOCATION_FIELD(fldname) \ #define READ_LOCATION_FIELD(fldname) \
token = pg_strtok(&length); /* skip :fldname */ \ token = pg_strtok(&length); /* skip :fldname */ \
token = pg_strtok(&length); /* get field value */ \ token = pg_strtok(&length); /* get field value */ \
(void) token; /* in case not used elsewhere */ \ (void) token; /* in case not used elsewhere */ \
local_node->fldname = -1 /* set field to "unknown" */ local_node->fldname = -1 /* set field to "unknown" */
#endif
/* Read a Node field */ /* Read a Node field */
#define READ_NODE_FIELD(fldname) \ #define READ_NODE_FIELD(fldname) \
...@@ -2804,7 +2815,7 @@ readDatum(bool typbyval) ...@@ -2804,7 +2815,7 @@ readDatum(bool typbyval)
Size length, Size length,
i; i;
int tokenLength; int tokenLength;
char *token; const char *token;
Datum res; Datum res;
char *s; char *s;
...@@ -2817,7 +2828,7 @@ readDatum(bool typbyval) ...@@ -2817,7 +2828,7 @@ readDatum(bool typbyval)
token = pg_strtok(&tokenLength); /* read the '[' */ token = pg_strtok(&tokenLength); /* read the '[' */
if (token == NULL || token[0] != '[') if (token == NULL || token[0] != '[')
elog(ERROR, "expected \"[\" to start datum, but got \"%s\"; length = %zu", elog(ERROR, "expected \"[\" to start datum, but got \"%s\"; length = %zu",
token ? (const char *) token : "[NULL]", length); token ? token : "[NULL]", length);
if (typbyval) if (typbyval)
{ {
...@@ -2847,7 +2858,7 @@ readDatum(bool typbyval) ...@@ -2847,7 +2858,7 @@ readDatum(bool typbyval)
token = pg_strtok(&tokenLength); /* read the ']' */ token = pg_strtok(&tokenLength); /* read the ']' */
if (token == NULL || token[0] != ']') if (token == NULL || token[0] != ']')
elog(ERROR, "expected \"]\" to end datum, but got \"%s\"; length = %zu", elog(ERROR, "expected \"]\" to end datum, but got \"%s\"; length = %zu",
token ? (const char *) token : "[NULL]", length); token ? token : "[NULL]", length);
return res; return res;
} }
...@@ -2860,7 +2871,7 @@ readAttrNumberCols(int numCols) ...@@ -2860,7 +2871,7 @@ readAttrNumberCols(int numCols)
{ {
int tokenLength, int tokenLength,
i; i;
char *token; const char *token;
AttrNumber *attr_vals; AttrNumber *attr_vals;
if (numCols <= 0) if (numCols <= 0)
...@@ -2884,7 +2895,7 @@ readOidCols(int numCols) ...@@ -2884,7 +2895,7 @@ readOidCols(int numCols)
{ {
int tokenLength, int tokenLength,
i; i;
char *token; const char *token;
Oid *oid_vals; Oid *oid_vals;
if (numCols <= 0) if (numCols <= 0)
...@@ -2908,7 +2919,7 @@ readIntCols(int numCols) ...@@ -2908,7 +2919,7 @@ readIntCols(int numCols)
{ {
int tokenLength, int tokenLength,
i; i;
char *token; const char *token;
int *int_vals; int *int_vals;
if (numCols <= 0) if (numCols <= 0)
...@@ -2932,7 +2943,7 @@ readBoolCols(int numCols) ...@@ -2932,7 +2943,7 @@ readBoolCols(int numCols)
{ {
int tokenLength, int tokenLength,
i; i;
char *token; const char *token;
bool *bool_vals; bool *bool_vals;
if (numCols <= 0) if (numCols <= 0)
......
...@@ -1335,7 +1335,6 @@ addRangeTableEntryForSubquery(ParseState *pstate, ...@@ -1335,7 +1335,6 @@ addRangeTableEntryForSubquery(ParseState *pstate,
Assert(pstate != NULL); Assert(pstate != NULL);
rte->rtekind = RTE_SUBQUERY; rte->rtekind = RTE_SUBQUERY;
rte->relid = InvalidOid;
rte->subquery = subquery; rte->subquery = subquery;
rte->alias = alias; rte->alias = alias;
......
...@@ -633,6 +633,12 @@ pg_parse_query(const char *query_string) ...@@ -633,6 +633,12 @@ pg_parse_query(const char *query_string)
} }
#endif #endif
/*
* Currently, outfuncs/readfuncs support is missing for many raw parse
* tree nodes, so we don't try to implement WRITE_READ_PARSE_PLAN_TREES
* here.
*/
TRACE_POSTGRESQL_QUERY_PARSE_DONE(query_string); TRACE_POSTGRESQL_QUERY_PARSE_DONE(query_string);
return raw_parsetree_list; return raw_parsetree_list;
...@@ -763,7 +769,7 @@ pg_rewrite_query(Query *query) ...@@ -763,7 +769,7 @@ pg_rewrite_query(Query *query)
ShowUsage("REWRITER STATISTICS"); ShowUsage("REWRITER STATISTICS");
#ifdef COPY_PARSE_PLAN_TREES #ifdef COPY_PARSE_PLAN_TREES
/* Optional debugging check: pass querytree output through copyObject() */ /* Optional debugging check: pass querytree through copyObject() */
{ {
List *new_list; List *new_list;
...@@ -776,6 +782,46 @@ pg_rewrite_query(Query *query) ...@@ -776,6 +782,46 @@ pg_rewrite_query(Query *query)
} }
#endif #endif
#ifdef WRITE_READ_PARSE_PLAN_TREES
/* Optional debugging check: pass querytree through outfuncs/readfuncs */
{
List *new_list = NIL;
ListCell *lc;
/*
* We currently lack outfuncs/readfuncs support for most utility
* statement types, so only attempt to write/read non-utility queries.
*/
foreach(lc, querytree_list)
{
Query *query = castNode(Query, lfirst(lc));
if (query->commandType != CMD_UTILITY)
{
char *str = nodeToString(query);
Query *new_query = stringToNodeWithLocations(str);
/*
* queryId is not saved in stored rules, but we must preserve
* it here to avoid breaking pg_stat_statements.
*/
new_query->queryId = query->queryId;
new_list = lappend(new_list, new_query);
pfree(str);
}
else
new_list = lappend(new_list, query);
}
/* This checks both outfuncs/readfuncs and the equal() routines... */
if (!equal(new_list, querytree_list))
elog(WARNING, "outfuncs/readfuncs failed to produce equal parse tree");
else
querytree_list = new_list;
}
#endif
if (Debug_print_rewritten) if (Debug_print_rewritten)
elog_node_display(LOG, "rewritten parse tree", querytree_list, elog_node_display(LOG, "rewritten parse tree", querytree_list,
Debug_pretty_print); Debug_pretty_print);
...@@ -812,7 +858,7 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams) ...@@ -812,7 +858,7 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
ShowUsage("PLANNER STATISTICS"); ShowUsage("PLANNER STATISTICS");
#ifdef COPY_PARSE_PLAN_TREES #ifdef COPY_PARSE_PLAN_TREES
/* Optional debugging check: pass plan output through copyObject() */ /* Optional debugging check: pass plan tree through copyObject() */
{ {
PlannedStmt *new_plan = copyObject(plan); PlannedStmt *new_plan = copyObject(plan);
...@@ -830,6 +876,30 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams) ...@@ -830,6 +876,30 @@ pg_plan_query(Query *querytree, int cursorOptions, ParamListInfo boundParams)
} }
#endif #endif
#ifdef WRITE_READ_PARSE_PLAN_TREES
/* Optional debugging check: pass plan tree through outfuncs/readfuncs */
{
char *str;
PlannedStmt *new_plan;
str = nodeToString(plan);
new_plan = stringToNodeWithLocations(str);
pfree(str);
/*
* equal() currently does not have routines to compare Plan nodes, so
* don't try to test equality here. Perhaps fix someday?
*/
#ifdef NOT_USED
/* This checks both outfuncs/readfuncs and the equal() routines... */
if (!equal(new_plan, plan))
elog(WARNING, "outfuncs/readfuncs failed to produce an equal plan tree");
else
#endif
plan = new_plan;
}
#endif
/* /*
* Print plan if debugging. * Print plan if debugging.
*/ */
......
...@@ -610,7 +610,10 @@ extern char *bmsToString(const struct Bitmapset *bms); ...@@ -610,7 +610,10 @@ extern char *bmsToString(const struct Bitmapset *bms);
/* /*
* nodes/{readfuncs.c,read.c} * nodes/{readfuncs.c,read.c}
*/ */
extern void *stringToNode(char *str); extern void *stringToNode(const char *str);
#ifdef WRITE_READ_PARSE_PLAN_TREES
extern void *stringToNodeWithLocations(const char *str);
#endif
extern struct Bitmapset *readBitmapset(void); extern struct Bitmapset *readBitmapset(void);
extern uintptr_t readDatum(bool typbyval); extern uintptr_t readDatum(bool typbyval);
extern bool *readBoolCols(int numCols); extern bool *readBoolCols(int numCols);
......
...@@ -16,12 +16,19 @@ ...@@ -16,12 +16,19 @@
#include "nodes/nodes.h" #include "nodes/nodes.h"
/*
* variable in read.c that needs to be accessible to readfuncs.c
*/
#ifdef WRITE_READ_PARSE_PLAN_TREES
extern bool restore_location_fields;
#endif
/* /*
* prototypes for functions in read.c (the lisp token parser) * prototypes for functions in read.c (the lisp token parser)
*/ */
extern char *pg_strtok(int *length); extern const char *pg_strtok(int *length);
extern char *debackslash(char *token, int length); extern char *debackslash(const char *token, int length);
extern void *nodeRead(char *token, int tok_len); extern void *nodeRead(const char *token, int tok_len);
/* /*
* prototypes for functions in readfuncs.c * prototypes for functions in readfuncs.c
......
...@@ -286,6 +286,13 @@ ...@@ -286,6 +286,13 @@
*/ */
/* #define COPY_PARSE_PLAN_TREES */ /* #define COPY_PARSE_PLAN_TREES */
/*
* Define this to force all parse and plan trees to be passed through
* outfuncs.c/readfuncs.c, to facilitate catching errors and omissions in
* those modules.
*/
/* #define WRITE_READ_PARSE_PLAN_TREES */
/* /*
* Define this to force all raw parse trees for DML statements to be scanned * Define this to force all raw parse trees for DML statements to be scanned
* by raw_expression_tree_walker(), to facilitate catching errors and * by raw_expression_tree_walker(), to facilitate catching errors and
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment