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