0002_explain_analyze_and_detoast.patch
text/x-patch
Filename: 0002_explain_analyze_and_detoast.patch
Type: text/x-patch
Part: 0
Patch
Format: unified
| File | + | − |
|---|---|---|
| src/backend/commands/explain.c | 130 | 1 |
| src/include/commands/explain.h | 2 | 0 |
| src/test/regress/expected/explain.out | 20 | 0 |
| src/test/regress/sql/explain.sql | 15 | 0 |
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 8570b14f62..95a09dd4cb 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -47,7 +47,6 @@ ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
/* Hook for plugins to get control in explain_get_index_name() */
explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
-
/* OR-able flags for ExplainXMLTag() */
#define X_OPENING 0
#define X_CLOSING 1
@@ -155,6 +154,8 @@ static void ExplainYAMLLineStarting(ExplainState *es);
static void escape_yaml(StringInfo buf, const char *str);
+static DestReceiver *CreateDetoastDestReceiver(void);
+
/*
* ExplainQuery -
@@ -192,6 +193,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
es->settings = defGetBoolean(opt);
else if (strcmp(opt->defname, "generic_plan") == 0)
es->generic = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "detoast") == 0)
+ es->detoast = defGetBoolean(opt);
else if (strcmp(opt->defname, "timing") == 0)
{
timing_set = true;
@@ -244,6 +247,12 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("EXPLAIN option TIMING requires ANALYZE")));
+ /* check that detoast is used with EXPLAIN ANALYZE */
+ if (es->detoast && !es->analyze)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("EXPLAIN option DETOAST requires ANALYZE")));
+
/* check that GENERIC_PLAN is not used with EXPLAIN ANALYZE */
if (es->generic && es->analyze)
ereport(ERROR,
@@ -565,9 +574,13 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
/*
* Normally we discard the query's output, but if explaining CREATE TABLE
* AS, we'd better use the appropriate tuple receiver.
+ * Also if we choose to detoast during explain, we need to send the
+ * tuples to a receiver that performs the detoasting
*/
if (into)
dest = CreateIntoRelDestReceiver(into);
+ else if (es->analyze && es->detoast)
+ dest = CreateDetoastDestReceiver();
else
dest = None_Receiver;
@@ -606,6 +619,9 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
/* run cleanup too */
ExecutorFinish(queryDesc);
+ /* call the dest receiver's destroy method even during explain */
+ dest->rDestroy(dest);
+
/* We can't run ExecutorEnd 'till we're done printing the stats... */
totaltime += elapsed_time(&starttime);
}
@@ -5052,3 +5068,116 @@ escape_yaml(StringInfo buf, const char *str)
{
escape_json(buf, str);
}
+
+
+/* ----------------
+ * detoasting DestReceiver functions
+ * ----------------
+ */
+/* the idea is that running EXPLAIN (ANALYZE) sometimes gives results that
+ * are way unrealistic, since during explain there is no detoasting of
+ * values. That means you get no idea what your actual query time is going
+ * to be, nor what the actual overhead of detoasting is.
+ *
+ * if you run EXPLAIN (ANAYLZE, DETOAST) then you get an impression of
+ * what is actually happening when you use postgresql as a document-database
+ * or have large values for some other reason.
+ */
+
+typedef struct
+{
+ DestReceiver destRecevier; /* receiver for the tuples, that just detoasts */
+ MemoryContext memoryContext; /* used throughout the detoasting analysis run */
+ MemoryContext oldMemoryContext; /* restore to old when done */
+} DetoastDestReceiver;
+
+
+
+/* this method receives the tuples/records during explain (analyze, detoast)
+ * and detoasts them.
+ * this method is otherwise not called and its purpose is to detoast to
+ * make analyze optionally more realistic
+ */
+static bool
+detoastAnalyzeReceive(TupleTableSlot *slot, DestReceiver *self)
+{
+ DetoastDestReceiver *receiver = (DetoastDestReceiver*) self;
+ int i;
+ bool shouldFree = true;
+ TupleDesc tupdesc;
+ HeapTuple tuple;
+
+ receiver->oldMemoryContext = MemoryContextSwitchTo(receiver->memoryContext);
+ tupdesc = slot->tts_tupleDescriptor;
+ tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+
+ /* fill all of the slot's attributes, we can now use slot->tts_values
+ * and its tts_isnull array which should be long enough even if added
+ * a null-column to the table */
+ slot_getallattrs(slot);
+
+ /* iterate over all attributes of the tuple and potentially detoast them */
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr;
+ char storage;
+
+ attr = TupleDescAttr(tupdesc, i);
+ storage = attr->attstorage;
+ if (!slot->tts_isnull[i] && (storage == 'e' || storage == 'x' || storage == 'm')) {
+ Datum datum = slot->tts_values[i];
+ /* here simple detoast the attribute's value, making
+ * the explain (analyze) slower, but also more realistic
+ * the above storage makes sure, that datum is actually
+ * a struct varlena pointer */
+ pg_detoast_datum_packed((struct varlena *)datum);
+ }
+ }
+ if (shouldFree)
+ pfree(tuple);
+ MemoryContextSwitchTo(receiver->oldMemoryContext);
+ return true;
+}
+
+static void
+detoastAnalyzeStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ DetoastDestReceiver *receiver = (DetoastDestReceiver*) self;
+ /* memory context for our work */
+ receiver->memoryContext = AllocSetContextCreate(CurrentMemoryContext,
+ "DetoastTupleReceive", ALLOCSET_DEFAULT_SIZES);
+}
+
+static void
+detoastAnalyzeCleanup(DestReceiver *self)
+{
+ DetoastDestReceiver *receiver = (DetoastDestReceiver*) self;
+ MemoryContextDelete(receiver->memoryContext);
+}
+
+static void
+detoastAnalyzeDestroy(DestReceiver *self)
+{
+ pfree(self);
+}
+
+static DestReceiver *CreateDetoastDestReceiver(void)
+{
+ DetoastDestReceiver *self = (DetoastDestReceiver*) palloc0(sizeof(DetoastDestReceiver));
+
+ self->destRecevier.receiveSlot = detoastAnalyzeReceive;
+ self->destRecevier.rStartup = detoastAnalyzeStartup;
+ self->destRecevier.rShutdown = detoastAnalyzeCleanup;
+ self->destRecevier.rDestroy = detoastAnalyzeDestroy;
+ self->destRecevier.mydest = DestNone;
+ return (DestReceiver *) self;
+}
+
+
+/*
+static const DestReceiver detoastAnalyzeDR = {
+ detoastAnalyzeReceive, detoastAnalyzeStartup, detoastAnalyzeCleanup, detoastAnalyzeDestroy,
+ DestNone
+};
+
+*/
\ No newline at end of file
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 3d3e632a0c..011dffb890 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -47,6 +47,8 @@ typedef struct ExplainState
bool summary; /* print total planning and execution timing */
bool settings; /* print modified settings */
bool generic; /* generate a generic plan */
+ bool detoast; /* detoast all toasted values, only applicable
+ when "analyze" is active. */
ExplainFormat format; /* output format */
/* state for output formatting --- not reset for each new plan tree */
int indent; /* current indentation level */
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 1aca77491b..eef68d1eca 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -559,3 +559,23 @@ select explain_filter('explain (verbose) select * from int8_tbl i8');
Query Identifier: N
(3 rows)
+-- Test detoast is accepted as a parameter to explain
+-- seems hard to test the expected longer runtime of detoasting
+-- since one needs to setup enough data to make such a test stable
+-- also timing are filtered out by explain_filter anyway
+create table test_detoast(id bigserial, val text);
+select explain_filter('explain (analyze,detoast) select * from test_detoast');
+ explain_filter
+------------------------------------------------------------------------------------------------
+ Seq Scan on test_detoast (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(3 rows)
+
+drop table test_detoast;
+-- Test detoast is _not_ accepted as a parameter to explain unless analyze is specified
+create table test_detoast(id bigserial, val text);
+select explain_filter('explain (detoast) select * from test_detoast');
+ERROR: EXPLAIN option DETOAST requires ANALYZE
+CONTEXT: PL/pgSQL function explain_filter(text) line 5 at FOR over EXECUTE statement
+drop table test_detoast;
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
index b6b7beab27..43047f9cce 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -154,3 +154,18 @@ select explain_filter('explain (verbose) select * from t1 where pg_temp.mysin(f1
-- Test compute_query_id
set compute_query_id = on;
select explain_filter('explain (verbose) select * from int8_tbl i8');
+
+-- Test detoast is accepted as a parameter to explain
+-- seems hard to test the expected longer runtime of detoasting
+-- since one needs to setup enough data to make such a test stable
+-- also timing are filtered out by explain_filter anyway
+create table test_detoast(id bigserial, val text);
+select explain_filter('explain (analyze,detoast) select * from test_detoast');
+drop table test_detoast;
+
+-- Test detoast is _not_ accepted as a parameter to explain unless analyze is specified
+create table test_detoast(id bigserial, val text);
+select explain_filter('explain (detoast) select * from test_detoast');
+drop table test_detoast;
+
+