1 /*
2  * This file and its contents are licensed under the Apache License 2.0.
3  * Please see the included NOTICE for copyright information and
4  * LICENSE-APACHE for a copy of the license.
5  */
6 
7 #include <postgres.h>
8 #include <catalog/pg_collation.h>
9 #include <commands/explain.h>
10 #include <nodes/execnodes.h>
11 #include <nodes/nodeFuncs.h>
12 #include <nodes/parsenodes.h>
13 #include <parser/parsetree.h>
14 #include <utils/builtins.h>
15 #include <utils/lsyscache.h>
16 #include <utils/ruleutils.h>
17 #include <utils/typcache.h>
18 
19 #include "nodes/chunk_append/exec.h"
20 #include "nodes/chunk_append/explain.h"
21 #include "compat/compat.h"
22 
23 static void show_sort_group_keys(ChunkAppendState *planstate, List *ancestors, ExplainState *es);
24 static void show_sortorder_options(StringInfo buf, Node *sortexpr, Oid sortOperator, Oid collation,
25 								   bool nullsFirst);
26 
27 /*
28  * Output additional information for EXPLAIN of a custom-scan plan node.
29  * This callback is optional. Common data stored in the ScanState,
30  * such as the target list and scan relation, will be shown even without
31  * this callback, but the callback allows the display of additional,
32  * private state.
33  */
34 void
ts_chunk_append_explain(CustomScanState * node,List * ancestors,ExplainState * es)35 ts_chunk_append_explain(CustomScanState *node, List *ancestors, ExplainState *es)
36 {
37 	ChunkAppendState *state = (ChunkAppendState *) node;
38 
39 	if (state->sort_options != NIL)
40 		show_sort_group_keys(state, ancestors, es);
41 
42 	if (es->verbose || es->format != EXPLAIN_FORMAT_TEXT)
43 		ExplainPropertyBool("Startup Exclusion", state->startup_exclusion, es);
44 
45 	if (es->verbose || es->format != EXPLAIN_FORMAT_TEXT)
46 		ExplainPropertyBool("Runtime Exclusion", state->runtime_exclusion, es);
47 
48 	if (state->startup_exclusion)
49 		ExplainPropertyInteger("Chunks excluded during startup",
50 							   NULL,
51 							   list_length(state->initial_subplans) - list_length(node->custom_ps),
52 							   es);
53 
54 	if (state->runtime_exclusion && state->runtime_number_loops > 0)
55 	{
56 		int avg_excluded = state->runtime_number_exclusions / state->runtime_number_loops;
57 		ExplainPropertyInteger("Chunks excluded during runtime", NULL, avg_excluded, es);
58 	}
59 }
60 
61 /*
62  * adjusted from postgresql explain.c
63  * since we have to keep the state in custom_private our sort state
64  * is in lists instead of arrays
65  */
66 static void
show_sort_group_keys(ChunkAppendState * state,List * ancestors,ExplainState * es)67 show_sort_group_keys(ChunkAppendState *state, List *ancestors, ExplainState *es)
68 {
69 	Plan *plan = state->csstate.ss.ps.plan;
70 	List *context;
71 	List *result = NIL;
72 	StringInfoData sortkeybuf;
73 	bool useprefix;
74 	int keyno;
75 	int nkeys = list_length(linitial(state->sort_options));
76 	List *sort_indexes = linitial(state->sort_options);
77 	List *sort_ops = lsecond(state->sort_options);
78 	List *sort_collations = lthird(state->sort_options);
79 	List *sort_nulls = lfourth(state->sort_options);
80 
81 	if (nkeys <= 0)
82 		return;
83 
84 	initStringInfo(&sortkeybuf);
85 
86 	/* Set up deparsing context */
87 #if PG13_GE
88 	context = set_deparse_context_plan(es->deparse_cxt, plan, ancestors);
89 #else
90 	context = set_deparse_context_planstate(es->deparse_cxt, (Node *) state, ancestors);
91 #endif
92 	useprefix = (list_length(es->rtable) > 1 || es->verbose);
93 
94 	for (keyno = 0; keyno < nkeys; keyno++)
95 	{
96 		/* find key expression in tlist */
97 		AttrNumber keyresno = list_nth_oid(sort_indexes, keyno);
98 		TargetEntry *target =
99 			get_tle_by_resno(castNode(CustomScan, plan)->custom_scan_tlist, keyresno);
100 		char *exprstr;
101 
102 		if (!target)
103 			elog(ERROR, "no tlist entry for key %d", keyresno);
104 		/* Deparse the expression, showing any top-level cast */
105 		exprstr = deparse_expression((Node *) target->expr, context, useprefix, true);
106 		resetStringInfo(&sortkeybuf);
107 		appendStringInfoString(&sortkeybuf, exprstr);
108 		/* Append sort order information, if relevant */
109 		if (sort_ops != NIL)
110 			show_sortorder_options(&sortkeybuf,
111 								   (Node *) target->expr,
112 								   list_nth_oid(sort_ops, keyno),
113 								   list_nth_oid(sort_collations, keyno),
114 								   list_nth_oid(sort_nulls, keyno));
115 		/* Emit one property-list item per sort key */
116 		result = lappend(result, pstrdup(sortkeybuf.data));
117 	}
118 
119 	ExplainPropertyList("Order", result, es);
120 }
121 
122 /* copied verbatim from postgresql explain.c */
123 static void
show_sortorder_options(StringInfo buf,Node * sortexpr,Oid sortOperator,Oid collation,bool nullsFirst)124 show_sortorder_options(StringInfo buf, Node *sortexpr, Oid sortOperator, Oid collation,
125 					   bool nullsFirst)
126 {
127 	Oid sortcoltype = exprType(sortexpr);
128 	bool reverse = false;
129 	TypeCacheEntry *typentry;
130 
131 	typentry = lookup_type_cache(sortcoltype, TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
132 
133 	/*
134 	 * Print COLLATE if it's not default.  There are some cases where this is
135 	 * redundant, eg if expression is a column whose declared collation is
136 	 * that collation, but it's hard to distinguish that here.
137 	 */
138 	if (OidIsValid(collation) && collation != DEFAULT_COLLATION_OID)
139 	{
140 		char *collname = get_collation_name(collation);
141 
142 		if (collname == NULL)
143 			elog(ERROR, "cache lookup failed for collation %u", collation);
144 		appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
145 	}
146 
147 	/* Print direction if not ASC, or USING if non-default sort operator */
148 	if (sortOperator == typentry->gt_opr)
149 	{
150 		appendStringInfoString(buf, " DESC");
151 		reverse = true;
152 	}
153 	else if (sortOperator != typentry->lt_opr)
154 	{
155 		char *opname = get_opname(sortOperator);
156 
157 		if (opname == NULL)
158 			elog(ERROR, "cache lookup failed for operator %u", sortOperator);
159 		appendStringInfo(buf, " USING %s", opname);
160 		/* Determine whether operator would be considered ASC or DESC */
161 		(void) get_equality_op_for_ordering_op(sortOperator, &reverse);
162 	}
163 
164 	/* Add NULLS FIRST/LAST only if it wouldn't be default */
165 	if (nullsFirst && !reverse)
166 	{
167 		appendStringInfoString(buf, " NULLS FIRST");
168 	}
169 	else if (!nullsFirst && reverse)
170 	{
171 		appendStringInfoString(buf, " NULLS LAST");
172 	}
173 }
174