summaryrefslogtreecommitdiff
path: root/src/backend/commands/explain.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/explain.c')
-rw-r--r--src/backend/commands/explain.c914
1 files changed, 0 insertions, 914 deletions
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
deleted file mode 100644
index b14ae4274f6..00000000000
--- a/src/backend/commands/explain.c
+++ /dev/null
@@ -1,914 +0,0 @@
-/*
- * explain.c
- * Explain the query execution plan
- *
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994-5, Regents of the University of California
- *
- * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.80 2002/06/20 20:29:27 momjian Exp $
- *
- */
-
-#include "postgres.h"
-
-#include "access/genam.h"
-#include "access/heapam.h"
-#include "catalog/pg_type.h"
-#include "commands/explain.h"
-#include "executor/instrument.h"
-#include "lib/stringinfo.h"
-#include "nodes/print.h"
-#include "optimizer/clauses.h"
-#include "optimizer/planner.h"
-#include "optimizer/var.h"
-#include "parser/parsetree.h"
-#include "rewrite/rewriteHandler.h"
-#include "tcop/pquery.h"
-#include "utils/builtins.h"
-#include "utils/guc.h"
-#include "utils/lsyscache.h"
-
-
-typedef struct ExplainState
-{
- /* options */
- bool printCost; /* print cost */
- bool printNodes; /* do nodeToString() instead */
- /* other states */
- List *rtable; /* range table */
-} ExplainState;
-
-typedef struct TextOutputState
-{
- TupleDesc tupdesc;
- DestReceiver *destfunc;
-} TextOutputState;
-
-static StringInfo Explain_PlanToString(Plan *plan, ExplainState *es);
-static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
- TextOutputState *tstate);
-static void explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan,
- int indent, ExplainState *es);
-static void show_scan_qual(List *qual, bool is_or_qual, const char *qlabel,
- int scanrelid, Plan *outer_plan,
- StringInfo str, int indent, ExplainState *es);
-static void show_upper_qual(List *qual, const char *qlabel,
- const char *outer_name, int outer_varno, Plan *outer_plan,
- const char *inner_name, int inner_varno, Plan *inner_plan,
- StringInfo str, int indent, ExplainState *es);
-static void show_sort_keys(List *tlist, int nkeys, const char *qlabel,
- StringInfo str, int indent, ExplainState *es);
-static Node *make_ors_ands_explicit(List *orclauses);
-static TextOutputState *begin_text_output(CommandDest dest, char *title);
-static void do_text_output(TextOutputState *tstate, char *aline);
-static void do_text_output_multiline(TextOutputState *tstate, char *text);
-static void end_text_output(TextOutputState *tstate);
-
-
-/*
- * ExplainQuery -
- * execute an EXPLAIN command
- */
-void
-ExplainQuery(ExplainStmt *stmt, CommandDest dest)
-{
- Query *query = stmt->query;
- TextOutputState *tstate;
- List *rewritten;
- List *l;
-
- tstate = begin_text_output(dest, "QUERY PLAN");
-
- if (query->commandType == CMD_UTILITY)
- {
- /* rewriter will not cope with utility statements */
- do_text_output(tstate, "Utility statements have no plan structure");
- }
- else
- {
- /* Rewrite through rule system */
- rewritten = QueryRewrite(query);
-
- if (rewritten == NIL)
- {
- /* In the case of an INSTEAD NOTHING, tell at least that */
- do_text_output(tstate, "Query rewrites to nothing");
- }
- else
- {
- /* Explain every plan */
- foreach(l, rewritten)
- {
- ExplainOneQuery(lfirst(l), stmt, tstate);
- /* put a blank line between plans */
- if (lnext(l) != NIL)
- do_text_output(tstate, "");
- }
- }
- }
-
- end_text_output(tstate);
-}
-
-/*
- * ExplainOneQuery -
- * print out the execution plan for one query
- */
-static void
-ExplainOneQuery(Query *query, ExplainStmt *stmt, TextOutputState *tstate)
-{
- Plan *plan;
- ExplainState *es;
- double totaltime = 0;
-
- /* planner will not cope with utility statements */
- if (query->commandType == CMD_UTILITY)
- {
- if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
- do_text_output(tstate, "NOTIFY");
- else
- do_text_output(tstate, "UTILITY");
- return;
- }
-
- /* plan the query */
- plan = planner(query);
-
- /* pg_plan could have failed */
- if (plan == NULL)
- return;
-
- /* Execute the plan for statistics if asked for */
- if (stmt->analyze)
- {
- struct timeval starttime;
- struct timeval endtime;
-
- /*
- * Set up the instrumentation for the top node. This will cascade
- * during plan initialisation
- */
- plan->instrument = InstrAlloc();
-
- gettimeofday(&starttime, NULL);
- ProcessQuery(query, plan, None, NULL);
- CommandCounterIncrement();
- gettimeofday(&endtime, NULL);
-
- endtime.tv_sec -= starttime.tv_sec;
- endtime.tv_usec -= starttime.tv_usec;
- while (endtime.tv_usec < 0)
- {
- endtime.tv_usec += 1000000;
- endtime.tv_sec--;
- }
- totaltime = (double) endtime.tv_sec +
- (double) endtime.tv_usec / 1000000.0;
- }
-
- es = (ExplainState *) palloc(sizeof(ExplainState));
- MemSet(es, 0, sizeof(ExplainState));
-
- es->printCost = true; /* default */
-
- if (stmt->verbose)
- es->printNodes = true;
-
- es->rtable = query->rtable;
-
- if (es->printNodes)
- {
- char *s;
- char *f;
-
- s = nodeToString(plan);
- if (s)
- {
- if (Explain_pretty_print)
- f = pretty_format_node_dump(s);
- else
- f = format_node_dump(s);
- pfree(s);
- do_text_output_multiline(tstate, f);
- pfree(f);
- if (es->printCost)
- do_text_output(tstate, ""); /* separator line */
- }
- }
-
- if (es->printCost)
- {
- StringInfo str;
-
- str = Explain_PlanToString(plan, es);
- if (stmt->analyze)
- appendStringInfo(str, "Total runtime: %.2f msec\n",
- 1000.0 * totaltime);
- do_text_output_multiline(tstate, str->data);
- pfree(str->data);
- pfree(str);
- }
-
- pfree(es);
-}
-
-
-/*
- * explain_outNode -
- * converts a Plan node into ascii string and appends it to 'str'
- *
- * outer_plan, if not null, references another plan node that is the outer
- * side of a join with the current node. This is only interesting for
- * deciphering runtime keys of an inner indexscan.
- */
-static void
-explain_outNode(StringInfo str, Plan *plan, Plan *outer_plan,
- int indent, ExplainState *es)
-{
- List *l;
- char *pname;
- int i;
-
- if (plan == NULL)
- {
- appendStringInfo(str, "\n");
- return;
- }
-
- switch (nodeTag(plan))
- {
- case T_Result:
- pname = "Result";
- break;
- case T_Append:
- pname = "Append";
- break;
- case T_NestLoop:
- pname = "Nested Loop";
- break;
- case T_MergeJoin:
- pname = "Merge Join";
- break;
- case T_HashJoin:
- pname = "Hash Join";
- break;
- case T_SeqScan:
- pname = "Seq Scan";
- break;
- case T_IndexScan:
- pname = "Index Scan";
- break;
- case T_TidScan:
- pname = "Tid Scan";
- break;
- case T_SubqueryScan:
- pname = "Subquery Scan";
- break;
- case T_FunctionScan:
- pname = "Function Scan";
- break;
- case T_Material:
- pname = "Materialize";
- break;
- case T_Sort:
- pname = "Sort";
- break;
- case T_Group:
- pname = "Group";
- break;
- case T_Agg:
- pname = "Aggregate";
- break;
- case T_Unique:
- pname = "Unique";
- break;
- case T_SetOp:
- switch (((SetOp *) plan)->cmd)
- {
- case SETOPCMD_INTERSECT:
- pname = "SetOp Intersect";
- break;
- case SETOPCMD_INTERSECT_ALL:
- pname = "SetOp Intersect All";
- break;
- case SETOPCMD_EXCEPT:
- pname = "SetOp Except";
- break;
- case SETOPCMD_EXCEPT_ALL:
- pname = "SetOp Except All";
- break;
- default:
- pname = "SetOp ???";
- break;
- }
- break;
- case T_Limit:
- pname = "Limit";
- break;
- case T_Hash:
- pname = "Hash";
- break;
- default:
- pname = "???";
- break;
- }
-
- appendStringInfo(str, pname);
- switch (nodeTag(plan))
- {
- case T_IndexScan:
- if (ScanDirectionIsBackward(((IndexScan *) plan)->indxorderdir))
- appendStringInfo(str, " Backward");
- appendStringInfo(str, " using ");
- i = 0;
- foreach(l, ((IndexScan *) plan)->indxid)
- {
- Relation relation;
-
- relation = index_open(lfirsti(l));
- appendStringInfo(str, "%s%s",
- (++i > 1) ? ", " : "",
- quote_identifier(RelationGetRelationName(relation)));
- index_close(relation);
- }
- /* FALL THRU */
- case T_SeqScan:
- case T_TidScan:
- if (((Scan *) plan)->scanrelid > 0)
- {
- RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- es->rtable);
- char *relname;
-
- /* Assume it's on a real relation */
- Assert(rte->rtekind == RTE_RELATION);
-
- /* We only show the rel name, not schema name */
- relname = get_rel_name(rte->relid);
-
- appendStringInfo(str, " on %s",
- quote_identifier(relname));
- if (strcmp(rte->eref->aliasname, relname) != 0)
- appendStringInfo(str, " %s",
- quote_identifier(rte->eref->aliasname));
- }
- break;
- case T_SubqueryScan:
- if (((Scan *) plan)->scanrelid > 0)
- {
- RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- es->rtable);
-
- appendStringInfo(str, " %s",
- quote_identifier(rte->eref->aliasname));
- }
- break;
- case T_FunctionScan:
- if (((Scan *) plan)->scanrelid > 0)
- {
- RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid,
- es->rtable);
- Expr *expr;
- Func *funcnode;
- Oid funcid;
- char *proname;
-
- /* Assert it's on a RangeFunction */
- Assert(rte->rtekind == RTE_FUNCTION);
-
- expr = (Expr *) rte->funcexpr;
- funcnode = (Func *) expr->oper;
- funcid = funcnode->funcid;
-
- /* We only show the func name, not schema name */
- proname = get_func_name(funcid);
-
- appendStringInfo(str, " on %s",
- quote_identifier(proname));
- if (strcmp(rte->eref->aliasname, proname) != 0)
- appendStringInfo(str, " %s",
- quote_identifier(rte->eref->aliasname));
- }
- break;
- default:
- break;
- }
- if (es->printCost)
- {
- appendStringInfo(str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
- plan->startup_cost, plan->total_cost,
- plan->plan_rows, plan->plan_width);
-
- if (plan->instrument && plan->instrument->nloops > 0)
- {
- double nloops = plan->instrument->nloops;
-
- appendStringInfo(str, " (actual time=%.2f..%.2f rows=%.0f loops=%.0f)",
- 1000.0 * plan->instrument->startup / nloops,
- 1000.0 * plan->instrument->total / nloops,
- plan->instrument->ntuples / nloops,
- plan->instrument->nloops);
- }
- }
- appendStringInfo(str, "\n");
-
- /* quals, sort keys, etc */
- switch (nodeTag(plan))
- {
- case T_IndexScan:
- show_scan_qual(((IndexScan *) plan)->indxqualorig, true,
- "Index Cond",
- ((Scan *) plan)->scanrelid,
- outer_plan,
- str, indent, es);
- show_scan_qual(plan->qual, false,
- "Filter",
- ((Scan *) plan)->scanrelid,
- outer_plan,
- str, indent, es);
- break;
- case T_SeqScan:
- case T_TidScan:
- case T_FunctionScan:
- show_scan_qual(plan->qual, false,
- "Filter",
- ((Scan *) plan)->scanrelid,
- outer_plan,
- str, indent, es);
- break;
- case T_NestLoop:
- show_upper_qual(((NestLoop *) plan)->join.joinqual,
- "Join Filter",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- show_upper_qual(plan->qual,
- "Filter",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- break;
- case T_MergeJoin:
- show_upper_qual(((MergeJoin *) plan)->mergeclauses,
- "Merge Cond",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- show_upper_qual(((MergeJoin *) plan)->join.joinqual,
- "Join Filter",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- show_upper_qual(plan->qual,
- "Filter",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- break;
- case T_HashJoin:
- show_upper_qual(((HashJoin *) plan)->hashclauses,
- "Hash Cond",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- show_upper_qual(((HashJoin *) plan)->join.joinqual,
- "Join Filter",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- show_upper_qual(plan->qual,
- "Filter",
- "outer", OUTER, outerPlan(plan),
- "inner", INNER, innerPlan(plan),
- str, indent, es);
- break;
- case T_SubqueryScan:
- show_upper_qual(plan->qual,
- "Filter",
- "subplan", 1, ((SubqueryScan *) plan)->subplan,
- "", 0, NULL,
- str, indent, es);
- break;
- case T_Agg:
- case T_Group:
- show_upper_qual(plan->qual,
- "Filter",
- "subplan", 0, outerPlan(plan),
- "", 0, NULL,
- str, indent, es);
- break;
- case T_Sort:
- show_sort_keys(plan->targetlist, ((Sort *) plan)->keycount,
- "Sort Key",
- str, indent, es);
- break;
- case T_Result:
- show_upper_qual((List *) ((Result *) plan)->resconstantqual,
- "One-Time Filter",
- "subplan", OUTER, outerPlan(plan),
- "", 0, NULL,
- str, indent, es);
- show_upper_qual(plan->qual,
- "Filter",
- "subplan", OUTER, outerPlan(plan),
- "", 0, NULL,
- str, indent, es);
- break;
- default:
- break;
- }
-
- /* initPlan-s */
- if (plan->initPlan)
- {
- List *saved_rtable = es->rtable;
- List *lst;
-
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " InitPlan\n");
- foreach(lst, plan->initPlan)
- {
- es->rtable = ((SubPlan *) lfirst(lst))->rtable;
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " -> ");
- explain_outNode(str, ((SubPlan *) lfirst(lst))->plan, NULL,
- indent + 4, es);
- }
- es->rtable = saved_rtable;
- }
-
- /* lefttree */
- if (outerPlan(plan))
- {
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " -> ");
- explain_outNode(str, outerPlan(plan), NULL, indent + 3, es);
- }
-
- /* righttree */
- if (innerPlan(plan))
- {
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " -> ");
- explain_outNode(str, innerPlan(plan), outerPlan(plan),
- indent + 3, es);
- }
-
- if (IsA(plan, Append))
- {
- Append *appendplan = (Append *) plan;
- List *lst;
-
- foreach(lst, appendplan->appendplans)
- {
- Plan *subnode = (Plan *) lfirst(lst);
-
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " -> ");
-
- explain_outNode(str, subnode, NULL, indent + 3, es);
- }
- }
-
- if (IsA(plan, SubqueryScan))
- {
- SubqueryScan *subqueryscan = (SubqueryScan *) plan;
- Plan *subnode = subqueryscan->subplan;
- RangeTblEntry *rte = rt_fetch(subqueryscan->scan.scanrelid,
- es->rtable);
- List *saved_rtable = es->rtable;
-
- Assert(rte->rtekind == RTE_SUBQUERY);
- es->rtable = rte->subquery->rtable;
-
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " -> ");
-
- explain_outNode(str, subnode, NULL, indent + 3, es);
-
- es->rtable = saved_rtable;
- }
-
- /* subPlan-s */
- if (plan->subPlan)
- {
- List *saved_rtable = es->rtable;
- List *lst;
-
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " SubPlan\n");
- foreach(lst, plan->subPlan)
- {
- es->rtable = ((SubPlan *) lfirst(lst))->rtable;
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " -> ");
- explain_outNode(str, ((SubPlan *) lfirst(lst))->plan, NULL,
- indent + 4, es);
- }
- es->rtable = saved_rtable;
- }
-}
-
-static StringInfo
-Explain_PlanToString(Plan *plan, ExplainState *es)
-{
- StringInfo str = makeStringInfo();
-
- if (plan != NULL)
- explain_outNode(str, plan, NULL, 0, es);
- return str;
-}
-
-/*
- * Show a qualifier expression for a scan plan node
- */
-static void
-show_scan_qual(List *qual, bool is_or_qual, const char *qlabel,
- int scanrelid, Plan *outer_plan,
- StringInfo str, int indent, ExplainState *es)
-{
- RangeTblEntry *rte;
- Node *scancontext;
- Node *outercontext;
- List *context;
- Node *node;
- char *exprstr;
- int i;
-
- /* No work if empty qual */
- if (qual == NIL)
- return;
- if (is_or_qual)
- {
- if (lfirst(qual) == NIL && lnext(qual) == NIL)
- return;
- }
-
- /* Fix qual --- indexqual requires different processing */
- if (is_or_qual)
- node = make_ors_ands_explicit(qual);
- else
- node = (Node *) make_ands_explicit(qual);
-
- /* Generate deparse context */
- Assert(scanrelid > 0 && scanrelid <= length(es->rtable));
- rte = rt_fetch(scanrelid, es->rtable);
- scancontext = deparse_context_for_rte(rte);
-
- /*
- * If we have an outer plan that is referenced by the qual, add it to
- * the deparse context. If not, don't (so that we don't force prefixes
- * unnecessarily).
- */
- if (outer_plan)
- {
- if (intMember(OUTER, pull_varnos(node)))
- outercontext = deparse_context_for_subplan("outer",
- outer_plan->targetlist,
- es->rtable);
- else
- outercontext = NULL;
- }
- else
- outercontext = NULL;
-
- context = deparse_context_for_plan(scanrelid, scancontext,
- OUTER, outercontext,
- NIL);
-
- /* Deparse the expression */
- exprstr = deparse_expression(node, context, (outercontext != NULL));
-
- /* And add to str */
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " %s: %s\n", qlabel, exprstr);
-}
-
-/*
- * Show a qualifier expression for an upper-level plan node
- */
-static void
-show_upper_qual(List *qual, const char *qlabel,
- const char *outer_name, int outer_varno, Plan *outer_plan,
- const char *inner_name, int inner_varno, Plan *inner_plan,
- StringInfo str, int indent, ExplainState *es)
-{
- List *context;
- Node *outercontext;
- Node *innercontext;
- Node *node;
- char *exprstr;
- int i;
-
- /* No work if empty qual */
- if (qual == NIL)
- return;
-
- /* Generate deparse context */
- if (outer_plan)
- outercontext = deparse_context_for_subplan(outer_name,
- outer_plan->targetlist,
- es->rtable);
- else
- outercontext = NULL;
- if (inner_plan)
- innercontext = deparse_context_for_subplan(inner_name,
- inner_plan->targetlist,
- es->rtable);
- else
- innercontext = NULL;
- context = deparse_context_for_plan(outer_varno, outercontext,
- inner_varno, innercontext,
- NIL);
-
- /* Deparse the expression */
- node = (Node *) make_ands_explicit(qual);
- exprstr = deparse_expression(node, context, (inner_plan != NULL));
-
- /* And add to str */
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " %s: %s\n", qlabel, exprstr);
-}
-
-/*
- * Show the sort keys for a Sort node.
- */
-static void
-show_sort_keys(List *tlist, int nkeys, const char *qlabel,
- StringInfo str, int indent, ExplainState *es)
-{
- List *context;
- bool useprefix;
- int keyno;
- List *tl;
- char *exprstr;
- int i;
-
- if (nkeys <= 0)
- return;
-
- for (i = 0; i < indent; i++)
- appendStringInfo(str, " ");
- appendStringInfo(str, " %s: ", qlabel);
-
- /*
- * In this routine we expect that the plan node's tlist has not been
- * processed by set_plan_references(). Normally, any Vars will contain
- * valid varnos referencing the actual rtable. But we might instead be
- * looking at a dummy tlist generated by prepunion.c; if there are
- * Vars with zero varno, use the tlist itself to determine their names.
- */
- if (intMember(0, pull_varnos((Node *) tlist)))
- {
- Node *outercontext;
-
- outercontext = deparse_context_for_subplan("sort",
- tlist,
- es->rtable);
- context = deparse_context_for_plan(0, outercontext,
- 0, NULL,
- NIL);
- useprefix = false;
- }
- else
- {
- context = deparse_context_for_plan(0, NULL,
- 0, NULL,
- es->rtable);
- useprefix = length(es->rtable) > 1;
- }
-
- for (keyno = 1; keyno <= nkeys; keyno++)
- {
- /* find key expression in tlist */
- foreach(tl, tlist)
- {
- TargetEntry *target = (TargetEntry *) lfirst(tl);
-
- if (target->resdom->reskey == keyno)
- {
- /* Deparse the expression */
- exprstr = deparse_expression(target->expr, context, useprefix);
- /* And add to str */
- if (keyno > 1)
- appendStringInfo(str, ", ");
- appendStringInfo(str, "%s", exprstr);
- break;
- }
- }
- if (tl == NIL)
- elog(ERROR, "show_sort_keys: no tlist entry for key %d", keyno);
- }
-
- appendStringInfo(str, "\n");
-}
-
-/*
- * Indexscan qual lists have an implicit OR-of-ANDs structure. Make it
- * explicit so deparsing works properly.
- */
-static Node *
-make_ors_ands_explicit(List *orclauses)
-{
- if (orclauses == NIL)
- return NULL; /* probably can't happen */
- else if (lnext(orclauses) == NIL)
- return (Node *) make_ands_explicit(lfirst(orclauses));
- else
- {
- List *args = NIL;
- List *orptr;
-
- foreach(orptr, orclauses)
- {
- args = lappend(args, make_ands_explicit(lfirst(orptr)));
- }
-
- return (Node *) make_orclause(args);
- }
-}
-
-
-/*
- * Functions for sending text to the frontend (or other specified destination)
- * as though it is a SELECT result.
- *
- * We tell the frontend that the table structure is a single TEXT column.
- */
-
-static TextOutputState *
-begin_text_output(CommandDest dest, char *title)
-{
- TextOutputState *tstate;
- TupleDesc tupdesc;
-
- tstate = (TextOutputState *) palloc(sizeof(TextOutputState));
-
- /* need a tuple descriptor representing a single TEXT column */
- tupdesc = CreateTemplateTupleDesc(1);
- TupleDescInitEntry(tupdesc, (AttrNumber) 1, title,
- TEXTOID, -1, 0, false);
-
- tstate->tupdesc = tupdesc;
- tstate->destfunc = DestToFunction(dest);
-
- (*tstate->destfunc->setup) (tstate->destfunc, (int) CMD_SELECT,
- NULL, tupdesc);
-
- return tstate;
-}
-
-/* write a single line of text */
-static void
-do_text_output(TextOutputState *tstate, char *aline)
-{
- HeapTuple tuple;
- Datum values[1];
- char nulls[1];
-
- /* form a tuple and send it to the receiver */
- values[0] = DirectFunctionCall1(textin, CStringGetDatum(aline));
- nulls[0] = ' ';
- tuple = heap_formtuple(tstate->tupdesc, values, nulls);
- (*tstate->destfunc->receiveTuple) (tuple,
- tstate->tupdesc,
- tstate->destfunc);
- pfree(DatumGetPointer(values[0]));
- heap_freetuple(tuple);
-}
-
-/* write a chunk of text, breaking at newline characters */
-/* NB: scribbles on its input! */
-static void
-do_text_output_multiline(TextOutputState *tstate, char *text)
-{
- while (*text)
- {
- char *eol;
-
- eol = strchr(text, '\n');
- if (eol)
- *eol++ = '\0';
- else
- eol = text + strlen(text);
- do_text_output(tstate, text);
- text = eol;
- }
-}
-
-static void
-end_text_output(TextOutputState *tstate)
-{
- (*tstate->destfunc->cleanup) (tstate->destfunc);
- pfree(tstate);
-}