mirror of https://github.com/citusdata/citus.git
Does the EXPLAIN ANALYZE at the same time as execution, so avoids executing twice.
We wrap worker tasks in worker_save_query_explain_analyze() so we can fetch their explain output later by a call worker_last_saved_explain_analyze(). Fixes #3519 Fixes #2347 Fixes #2613 Fixes #621pull/3890/head
parent
8551affc1e
commit
bb96ef5047
|
@ -143,6 +143,7 @@
|
||||||
#include "distributed/local_executor.h"
|
#include "distributed/local_executor.h"
|
||||||
#include "distributed/multi_client_executor.h"
|
#include "distributed/multi_client_executor.h"
|
||||||
#include "distributed/multi_executor.h"
|
#include "distributed/multi_executor.h"
|
||||||
|
#include "distributed/multi_explain.h"
|
||||||
#include "distributed/multi_partitioning_utils.h"
|
#include "distributed/multi_partitioning_utils.h"
|
||||||
#include "distributed/multi_physical_planner.h"
|
#include "distributed/multi_physical_planner.h"
|
||||||
#include "distributed/multi_resowner.h"
|
#include "distributed/multi_resowner.h"
|
||||||
|
@ -188,12 +189,6 @@ typedef struct DistributedExecution
|
||||||
List *remoteTaskList;
|
List *remoteTaskList;
|
||||||
List *localTaskList;
|
List *localTaskList;
|
||||||
|
|
||||||
/*
|
|
||||||
* Corresponding distributed plan returns results,
|
|
||||||
* either because it is a SELECT or has RETURNING.
|
|
||||||
*/
|
|
||||||
bool expectResults;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If a task specific destination is not provided for a task, then use
|
* If a task specific destination is not provided for a task, then use
|
||||||
* defaultTupleDest.
|
* defaultTupleDest.
|
||||||
|
@ -490,9 +485,6 @@ typedef struct ShardCommandExecution
|
||||||
struct TaskPlacementExecution **placementExecutions;
|
struct TaskPlacementExecution **placementExecutions;
|
||||||
int placementExecutionCount;
|
int placementExecutionCount;
|
||||||
|
|
||||||
/* whether we expect results to come back */
|
|
||||||
bool expectResults;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* RETURNING results from other shard placements can be ignored
|
* RETURNING results from other shard placements can be ignored
|
||||||
* after we got results from the first placements.
|
* after we got results from the first placements.
|
||||||
|
@ -562,7 +554,6 @@ typedef struct TaskPlacementExecution
|
||||||
/* local functions */
|
/* local functions */
|
||||||
static DistributedExecution * CreateDistributedExecution(RowModifyLevel modLevel,
|
static DistributedExecution * CreateDistributedExecution(RowModifyLevel modLevel,
|
||||||
List *taskList,
|
List *taskList,
|
||||||
bool expectResults,
|
|
||||||
ParamListInfo paramListInfo,
|
ParamListInfo paramListInfo,
|
||||||
int targetPoolSize,
|
int targetPoolSize,
|
||||||
TupleDestination *
|
TupleDestination *
|
||||||
|
@ -694,6 +685,12 @@ AdaptiveExecutor(CitusScanState *scanState)
|
||||||
TupleDestination *defaultTupleDest =
|
TupleDestination *defaultTupleDest =
|
||||||
CreateTupleStoreTupleDest(scanState->tuplestorestate, tupleDescriptor);
|
CreateTupleStoreTupleDest(scanState->tuplestorestate, tupleDescriptor);
|
||||||
|
|
||||||
|
if (RequestedForExplainAnalyze(scanState))
|
||||||
|
{
|
||||||
|
taskList = ExplainAnalyzeTaskList(taskList, defaultTupleDest, tupleDescriptor,
|
||||||
|
paramListInfo);
|
||||||
|
}
|
||||||
|
|
||||||
bool hasDependentJobs = HasDependentJobs(job);
|
bool hasDependentJobs = HasDependentJobs(job);
|
||||||
if (hasDependentJobs)
|
if (hasDependentJobs)
|
||||||
{
|
{
|
||||||
|
@ -714,7 +711,6 @@ AdaptiveExecutor(CitusScanState *scanState)
|
||||||
DistributedExecution *execution = CreateDistributedExecution(
|
DistributedExecution *execution = CreateDistributedExecution(
|
||||||
distributedPlan->modLevel,
|
distributedPlan->modLevel,
|
||||||
taskList,
|
taskList,
|
||||||
distributedPlan->expectResults,
|
|
||||||
paramListInfo,
|
paramListInfo,
|
||||||
targetPoolSize,
|
targetPoolSize,
|
||||||
defaultTupleDest,
|
defaultTupleDest,
|
||||||
|
@ -1010,9 +1006,8 @@ ExecuteTaskListExtended(ExecutionParams *executionParams)
|
||||||
DistributedExecution *execution =
|
DistributedExecution *execution =
|
||||||
CreateDistributedExecution(
|
CreateDistributedExecution(
|
||||||
executionParams->modLevel, remoteTaskList,
|
executionParams->modLevel, remoteTaskList,
|
||||||
executionParams->expectResults, paramListInfo,
|
paramListInfo, executionParams->targetPoolSize,
|
||||||
executionParams->targetPoolSize, defaultTupleDest,
|
defaultTupleDest, &executionParams->xactProperties,
|
||||||
&executionParams->xactProperties,
|
|
||||||
executionParams->jobIdList);
|
executionParams->jobIdList);
|
||||||
|
|
||||||
StartDistributedExecution(execution);
|
StartDistributedExecution(execution);
|
||||||
|
@ -1055,7 +1050,7 @@ CreateBasicExecutionParams(RowModifyLevel modLevel,
|
||||||
*/
|
*/
|
||||||
static DistributedExecution *
|
static DistributedExecution *
|
||||||
CreateDistributedExecution(RowModifyLevel modLevel, List *taskList,
|
CreateDistributedExecution(RowModifyLevel modLevel, List *taskList,
|
||||||
bool expectResults, ParamListInfo paramListInfo,
|
ParamListInfo paramListInfo,
|
||||||
int targetPoolSize, TupleDestination *defaultTupleDest,
|
int targetPoolSize, TupleDestination *defaultTupleDest,
|
||||||
TransactionProperties *xactProperties,
|
TransactionProperties *xactProperties,
|
||||||
List *jobIdList)
|
List *jobIdList)
|
||||||
|
@ -1065,7 +1060,6 @@ CreateDistributedExecution(RowModifyLevel modLevel, List *taskList,
|
||||||
|
|
||||||
execution->modLevel = modLevel;
|
execution->modLevel = modLevel;
|
||||||
execution->tasksToExecute = taskList;
|
execution->tasksToExecute = taskList;
|
||||||
execution->expectResults = expectResults;
|
|
||||||
execution->transactionProperties = xactProperties;
|
execution->transactionProperties = xactProperties;
|
||||||
|
|
||||||
execution->localTaskList = NIL;
|
execution->localTaskList = NIL;
|
||||||
|
@ -1723,7 +1717,6 @@ AssignTasksToConnectionsOrWorkerPool(DistributedExecution *execution)
|
||||||
{
|
{
|
||||||
RowModifyLevel modLevel = execution->modLevel;
|
RowModifyLevel modLevel = execution->modLevel;
|
||||||
List *taskList = execution->tasksToExecute;
|
List *taskList = execution->tasksToExecute;
|
||||||
bool expectResults = execution->expectResults;
|
|
||||||
|
|
||||||
int32 localGroupId = GetLocalGroupId();
|
int32 localGroupId = GetLocalGroupId();
|
||||||
|
|
||||||
|
@ -1764,9 +1757,6 @@ AssignTasksToConnectionsOrWorkerPool(DistributedExecution *execution)
|
||||||
NULL;
|
NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
shardCommandExecution->expectResults =
|
|
||||||
expectResults && !task->partiallyLocalOrRemote;
|
|
||||||
|
|
||||||
ShardPlacement *taskPlacement = NULL;
|
ShardPlacement *taskPlacement = NULL;
|
||||||
foreach_ptr(taskPlacement, task->taskPlacementList)
|
foreach_ptr(taskPlacement, task->taskPlacementList)
|
||||||
{
|
{
|
||||||
|
@ -3178,13 +3168,25 @@ TransactionStateMachine(WorkerSession *session)
|
||||||
TaskPlacementExecution *placementExecution = session->currentTask;
|
TaskPlacementExecution *placementExecution = session->currentTask;
|
||||||
ShardCommandExecution *shardCommandExecution =
|
ShardCommandExecution *shardCommandExecution =
|
||||||
placementExecution->shardCommandExecution;
|
placementExecution->shardCommandExecution;
|
||||||
bool storeRows = shardCommandExecution->expectResults;
|
Task *task = shardCommandExecution->task;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In EXPLAIN ANALYZE we need to store results except for multiple placements,
|
||||||
|
* regardless of query type. In other cases, doing the same doesn't seem to have
|
||||||
|
* a drawback.
|
||||||
|
*/
|
||||||
|
bool storeRows = true;
|
||||||
|
|
||||||
if (shardCommandExecution->gotResults)
|
if (shardCommandExecution->gotResults)
|
||||||
{
|
{
|
||||||
/* already received results from another replica */
|
/* already received results from another replica */
|
||||||
storeRows = false;
|
storeRows = false;
|
||||||
}
|
}
|
||||||
|
else if (task->partiallyLocalOrRemote)
|
||||||
|
{
|
||||||
|
/* already received results from local execution */
|
||||||
|
storeRows = false;
|
||||||
|
}
|
||||||
|
|
||||||
bool fetchDone = ReceiveResults(session, storeRows);
|
bool fetchDone = ReceiveResults(session, storeRows);
|
||||||
if (!fetchDone)
|
if (!fetchDone)
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "libpq-fe.h"
|
#include "libpq-fe.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
|
||||||
|
#include "access/htup_details.h"
|
||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/namespace.h"
|
#include "catalog/namespace.h"
|
||||||
#include "catalog/pg_class.h"
|
#include "catalog/pg_class.h"
|
||||||
|
@ -41,6 +42,7 @@
|
||||||
#include "distributed/remote_commands.h"
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/recursive_planning.h"
|
#include "distributed/recursive_planning.h"
|
||||||
#include "distributed/placement_connection.h"
|
#include "distributed/placement_connection.h"
|
||||||
|
#include "distributed/tuple_destination.h"
|
||||||
#include "distributed/tuplestore.h"
|
#include "distributed/tuplestore.h"
|
||||||
#include "distributed/listutils.h"
|
#include "distributed/listutils.h"
|
||||||
#include "distributed/worker_protocol.h"
|
#include "distributed/worker_protocol.h"
|
||||||
|
@ -73,6 +75,22 @@ bool ExplainAllTasks = false;
|
||||||
*/
|
*/
|
||||||
static char *SavedExplainPlan = NULL;
|
static char *SavedExplainPlan = NULL;
|
||||||
|
|
||||||
|
/* struct to save explain flags */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
bool verbose;
|
||||||
|
bool costs;
|
||||||
|
bool buffers;
|
||||||
|
bool timing;
|
||||||
|
bool summary;
|
||||||
|
ExplainFormat format;
|
||||||
|
} ExplainOptions;
|
||||||
|
|
||||||
|
|
||||||
|
/* EXPLAIN flags of current distributed explain */
|
||||||
|
static ExplainOptions CurrentDistributedQueryExplainOptions = {
|
||||||
|
0, 0, 0, 0, 0, EXPLAIN_FORMAT_TEXT
|
||||||
|
};
|
||||||
|
|
||||||
/* Result for a single remote EXPLAIN command */
|
/* Result for a single remote EXPLAIN command */
|
||||||
typedef struct RemoteExplainPlan
|
typedef struct RemoteExplainPlan
|
||||||
|
@ -82,17 +100,33 @@ typedef struct RemoteExplainPlan
|
||||||
} RemoteExplainPlan;
|
} RemoteExplainPlan;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ExplainAnalyzeDestination is internal representation of a TupleDestination
|
||||||
|
* which collects EXPLAIN ANALYZE output after the main query is run.
|
||||||
|
*/
|
||||||
|
typedef struct ExplainAnalyzeDestination
|
||||||
|
{
|
||||||
|
TupleDestination pub;
|
||||||
|
Task *originalTask;
|
||||||
|
TupleDestination *originalTaskDestination;
|
||||||
|
TupleDesc lastSavedExplainAnalyzeTupDesc;
|
||||||
|
} ExplainAnalyzeDestination;
|
||||||
|
|
||||||
|
|
||||||
/* Explain functions for distributed queries */
|
/* Explain functions for distributed queries */
|
||||||
static void ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es);
|
static void ExplainSubPlans(DistributedPlan *distributedPlan, ExplainState *es);
|
||||||
static void ExplainJob(Job *job, ExplainState *es);
|
static void ExplainJob(Job *job, ExplainState *es);
|
||||||
static void ExplainMapMergeJob(MapMergeJob *mapMergeJob, ExplainState *es);
|
static void ExplainMapMergeJob(MapMergeJob *mapMergeJob, ExplainState *es);
|
||||||
static void ExplainTaskList(List *taskList, ExplainState *es);
|
static void ExplainTaskList(List *taskList, ExplainState *es);
|
||||||
static RemoteExplainPlan * RemoteExplain(Task *task, ExplainState *es);
|
static RemoteExplainPlan * RemoteExplain(Task *task, ExplainState *es);
|
||||||
|
static RemoteExplainPlan * GetSavedRemoteExplain(Task *task, ExplainState *es);
|
||||||
|
static RemoteExplainPlan * FetchRemoteExplainFromWorkers(Task *task, ExplainState *es);
|
||||||
static void ExplainTask(Task *task, int placementIndex, List *explainOutputList,
|
static void ExplainTask(Task *task, int placementIndex, List *explainOutputList,
|
||||||
ExplainState *es);
|
ExplainState *es);
|
||||||
static void ExplainTaskPlacement(ShardPlacement *taskPlacement, List *explainOutputList,
|
static void ExplainTaskPlacement(ShardPlacement *taskPlacement, List *explainOutputList,
|
||||||
ExplainState *es);
|
ExplainState *es);
|
||||||
static StringInfo BuildRemoteExplainQuery(char *queryString, ExplainState *es);
|
static StringInfo BuildRemoteExplainQuery(char *queryString, ExplainState *es);
|
||||||
|
static const char * ExplainFormatStr(ExplainFormat format);
|
||||||
static void ExplainWorkerPlan(PlannedStmt *plannedStmt, DestReceiver *dest,
|
static void ExplainWorkerPlan(PlannedStmt *plannedStmt, DestReceiver *dest,
|
||||||
ExplainState *es,
|
ExplainState *es,
|
||||||
const char *queryString, ParamListInfo params,
|
const char *queryString, ParamListInfo params,
|
||||||
|
@ -102,6 +136,15 @@ static bool ExtractFieldBoolean(Datum jsonbDoc, const char *fieldName, bool defa
|
||||||
static ExplainFormat ExtractFieldExplainFormat(Datum jsonbDoc, const char *fieldName,
|
static ExplainFormat ExtractFieldExplainFormat(Datum jsonbDoc, const char *fieldName,
|
||||||
ExplainFormat defaultValue);
|
ExplainFormat defaultValue);
|
||||||
static bool ExtractFieldJsonbDatum(Datum jsonbDoc, const char *fieldName, Datum *result);
|
static bool ExtractFieldJsonbDatum(Datum jsonbDoc, const char *fieldName, Datum *result);
|
||||||
|
static TupleDestination * CreateExplainAnlyzeDestination(Task *task,
|
||||||
|
TupleDestination *taskDest);
|
||||||
|
static void ExplainAnalyzeDestPutTuple(TupleDestination *self, Task *task,
|
||||||
|
int placementIndex, int queryNumber,
|
||||||
|
HeapTuple heapTuple);
|
||||||
|
static TupleDesc ExplainAnalyzeDestTupleDescForQuery(TupleDestination *self, int
|
||||||
|
queryNumber);
|
||||||
|
static char * WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc);
|
||||||
|
static List * SplitString(const char *str, char delimiter);
|
||||||
|
|
||||||
/* Static Explain functions copied from explain.c */
|
/* Static Explain functions copied from explain.c */
|
||||||
static void ExplainOneQuery(Query *query, int cursorOptions,
|
static void ExplainOneQuery(Query *query, int cursorOptions,
|
||||||
|
@ -409,18 +452,72 @@ ExplainTaskList(List *taskList, ExplainState *es)
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* RemoteExplain fetches the remote EXPLAIN output for a single
|
* RemoteExplain fetches the remote EXPLAIN output for a single task.
|
||||||
* task. It tries each shard placement until one succeeds or all
|
|
||||||
* failed.
|
|
||||||
*/
|
*/
|
||||||
static RemoteExplainPlan *
|
static RemoteExplainPlan *
|
||||||
RemoteExplain(Task *task, ExplainState *es)
|
RemoteExplain(Task *task, ExplainState *es)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* For EXPLAIN EXECUTE we still use the old method, so task->fetchedExplainAnalyzePlan
|
||||||
|
* can be NULL for some cases of es->analyze == true.
|
||||||
|
*/
|
||||||
|
if (es->analyze && task->fetchedExplainAnalyzePlan)
|
||||||
|
{
|
||||||
|
return GetSavedRemoteExplain(task, es);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return FetchRemoteExplainFromWorkers(task, es);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* GetSavedRemoteExplain creates a remote EXPLAIN output from information saved
|
||||||
|
* in task.
|
||||||
|
*/
|
||||||
|
static RemoteExplainPlan *
|
||||||
|
GetSavedRemoteExplain(Task *task, ExplainState *es)
|
||||||
|
{
|
||||||
|
RemoteExplainPlan *remotePlan = (RemoteExplainPlan *) palloc0(
|
||||||
|
sizeof(RemoteExplainPlan));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similar to postgres' ExplainQuery(), we split by newline only for
|
||||||
|
* text format.
|
||||||
|
*/
|
||||||
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
||||||
|
{
|
||||||
|
remotePlan->explainOutputList = SplitString(task->fetchedExplainAnalyzePlan,
|
||||||
|
'\n');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StringInfo explainAnalyzeString = makeStringInfo();
|
||||||
|
appendStringInfoString(explainAnalyzeString, task->fetchedExplainAnalyzePlan);
|
||||||
|
remotePlan->explainOutputList = list_make1(explainAnalyzeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
remotePlan->placementIndex = task->fetchedExplainAnalyzePlacementIndex;
|
||||||
|
|
||||||
|
return remotePlan;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FetchRemoteExplainFromWorkers fetches the remote EXPLAIN output for a single
|
||||||
|
* task by querying it from worker nodes. It tries each shard placement until
|
||||||
|
* one succeeds or all failed.
|
||||||
|
*/
|
||||||
|
static RemoteExplainPlan *
|
||||||
|
FetchRemoteExplainFromWorkers(Task *task, ExplainState *es)
|
||||||
{
|
{
|
||||||
List *taskPlacementList = task->taskPlacementList;
|
List *taskPlacementList = task->taskPlacementList;
|
||||||
int placementCount = list_length(taskPlacementList);
|
int placementCount = list_length(taskPlacementList);
|
||||||
|
|
||||||
RemoteExplainPlan *remotePlan = (RemoteExplainPlan *) palloc0(
|
RemoteExplainPlan *remotePlan = (RemoteExplainPlan *) palloc0(
|
||||||
sizeof(RemoteExplainPlan));
|
sizeof(RemoteExplainPlan));
|
||||||
|
|
||||||
StringInfo explainQuery = BuildRemoteExplainQuery(TaskQueryStringForAllPlacements(
|
StringInfo explainQuery = BuildRemoteExplainQuery(TaskQueryStringForAllPlacements(
|
||||||
task),
|
task),
|
||||||
es);
|
es);
|
||||||
|
@ -615,34 +712,7 @@ static StringInfo
|
||||||
BuildRemoteExplainQuery(char *queryString, ExplainState *es)
|
BuildRemoteExplainQuery(char *queryString, ExplainState *es)
|
||||||
{
|
{
|
||||||
StringInfo explainQuery = makeStringInfo();
|
StringInfo explainQuery = makeStringInfo();
|
||||||
char *formatStr = NULL;
|
const char *formatStr = ExplainFormatStr(es->format);
|
||||||
|
|
||||||
switch (es->format)
|
|
||||||
{
|
|
||||||
case EXPLAIN_FORMAT_XML:
|
|
||||||
{
|
|
||||||
formatStr = "XML";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPLAIN_FORMAT_JSON:
|
|
||||||
{
|
|
||||||
formatStr = "JSON";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case EXPLAIN_FORMAT_YAML:
|
|
||||||
{
|
|
||||||
formatStr = "YAML";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
formatStr = "TEXT";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
appendStringInfo(explainQuery,
|
appendStringInfo(explainQuery,
|
||||||
"EXPLAIN (ANALYZE %s, VERBOSE %s, "
|
"EXPLAIN (ANALYZE %s, VERBOSE %s, "
|
||||||
|
@ -661,6 +731,37 @@ BuildRemoteExplainQuery(char *queryString, ExplainState *es)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ExplainFormatStr converts the given explain format to string.
|
||||||
|
*/
|
||||||
|
static const char *
|
||||||
|
ExplainFormatStr(ExplainFormat format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case EXPLAIN_FORMAT_XML:
|
||||||
|
{
|
||||||
|
return "XML";
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPLAIN_FORMAT_JSON:
|
||||||
|
{
|
||||||
|
return "JSON";
|
||||||
|
}
|
||||||
|
|
||||||
|
case EXPLAIN_FORMAT_YAML:
|
||||||
|
{
|
||||||
|
return "YAML";
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return "TEXT";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* worker_last_saved_explain_analyze returns the last saved EXPLAIN ANALYZE output of
|
* worker_last_saved_explain_analyze returns the last saved EXPLAIN ANALYZE output of
|
||||||
* a worker task query. It returns NULL if nothing has been saved yet.
|
* a worker task query. It returns NULL if nothing has been saved yet.
|
||||||
|
@ -875,6 +976,289 @@ ExtractFieldJsonbDatum(Datum jsonbDoc, const char *fieldName, Datum *result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CitusExplainOneQuery is the executor hook that is called when
|
||||||
|
* postgres wants to explain a query.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
CitusExplainOneQuery(Query *query, int cursorOptions, IntoClause *into,
|
||||||
|
ExplainState *es, const char *queryString, ParamListInfo params,
|
||||||
|
QueryEnvironment *queryEnv)
|
||||||
|
{
|
||||||
|
/* save the flags of current EXPLAIN command */
|
||||||
|
CurrentDistributedQueryExplainOptions.costs = es->costs;
|
||||||
|
CurrentDistributedQueryExplainOptions.buffers = es->buffers;
|
||||||
|
CurrentDistributedQueryExplainOptions.verbose = es->verbose;
|
||||||
|
CurrentDistributedQueryExplainOptions.summary = es->summary;
|
||||||
|
CurrentDistributedQueryExplainOptions.timing = es->timing;
|
||||||
|
CurrentDistributedQueryExplainOptions.format = es->format;
|
||||||
|
|
||||||
|
/* rest is copied from ExplainOneQuery() */
|
||||||
|
instr_time planstart,
|
||||||
|
planduration;
|
||||||
|
|
||||||
|
INSTR_TIME_SET_CURRENT(planstart);
|
||||||
|
|
||||||
|
/* plan the query */
|
||||||
|
PlannedStmt *plan = pg_plan_query(query, cursorOptions, params);
|
||||||
|
|
||||||
|
INSTR_TIME_SET_CURRENT(planduration);
|
||||||
|
INSTR_TIME_SUBTRACT(planduration, planstart);
|
||||||
|
|
||||||
|
/* run it (if needed) and produce output */
|
||||||
|
ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
|
||||||
|
&planduration);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CreateExplainAnlyzeDestination creates a destination suitable for collecting
|
||||||
|
* explain analyze output from workers.
|
||||||
|
*/
|
||||||
|
static TupleDestination *
|
||||||
|
CreateExplainAnlyzeDestination(Task *task, TupleDestination *taskDest)
|
||||||
|
{
|
||||||
|
ExplainAnalyzeDestination *tupleDestination = palloc0(
|
||||||
|
sizeof(ExplainAnalyzeDestination));
|
||||||
|
tupleDestination->originalTask = task;
|
||||||
|
tupleDestination->originalTaskDestination = taskDest;
|
||||||
|
|
||||||
|
#if PG_VERSION_NUM >= PG_VERSION_12
|
||||||
|
TupleDesc lastSavedExplainAnalyzeTupDesc = CreateTemplateTupleDesc(1);
|
||||||
|
#else
|
||||||
|
TupleDesc lastSavedExplainAnalyzeTupDesc = CreateTemplateTupleDesc(1, false);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TupleDescInitEntry(lastSavedExplainAnalyzeTupDesc, 1, "explain analyze", TEXTOID, 0,
|
||||||
|
0);
|
||||||
|
tupleDestination->lastSavedExplainAnalyzeTupDesc = lastSavedExplainAnalyzeTupDesc;
|
||||||
|
|
||||||
|
tupleDestination->pub.putTuple = ExplainAnalyzeDestPutTuple;
|
||||||
|
tupleDestination->pub.tupleDescForQuery = ExplainAnalyzeDestTupleDescForQuery;
|
||||||
|
|
||||||
|
return (TupleDestination *) tupleDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ExplainAnalyzeDestPutTuple implements TupleDestination->putTuple
|
||||||
|
* for ExplainAnalyzeDestination.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
ExplainAnalyzeDestPutTuple(TupleDestination *self, Task *task,
|
||||||
|
int placementIndex, int queryNumber,
|
||||||
|
HeapTuple heapTuple)
|
||||||
|
{
|
||||||
|
ExplainAnalyzeDestination *tupleDestination = (ExplainAnalyzeDestination *) self;
|
||||||
|
if (queryNumber == 0)
|
||||||
|
{
|
||||||
|
TupleDestination *originalTupDest = tupleDestination->originalTaskDestination;
|
||||||
|
originalTupDest->putTuple(originalTupDest, task, placementIndex, 0, heapTuple);
|
||||||
|
}
|
||||||
|
else if (queryNumber == 1)
|
||||||
|
{
|
||||||
|
bool isNull = false;
|
||||||
|
TupleDesc tupDesc = tupleDestination->lastSavedExplainAnalyzeTupDesc;
|
||||||
|
Datum explainAnalyze = heap_getattr(heapTuple, 1, tupDesc, &isNull);
|
||||||
|
|
||||||
|
if (isNull)
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg(
|
||||||
|
"received null explain analyze output from worker")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *fetchedExplainAnalyzePlan = TextDatumGetCString(explainAnalyze);
|
||||||
|
|
||||||
|
tupleDestination->originalTask->fetchedExplainAnalyzePlan =
|
||||||
|
pstrdup(fetchedExplainAnalyzePlan);
|
||||||
|
tupleDestination->originalTask->fetchedExplainAnalyzePlacementIndex =
|
||||||
|
placementIndex;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("cannot get EXPLAIN ANALYZE of multiple queries"),
|
||||||
|
errdetail("while receiving tuples for query %d", queryNumber)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ExplainAnalyzeDestTupleDescForQuery implements TupleDestination->tupleDescForQuery
|
||||||
|
* for ExplainAnalyzeDestination.
|
||||||
|
*/
|
||||||
|
static TupleDesc
|
||||||
|
ExplainAnalyzeDestTupleDescForQuery(TupleDestination *self, int queryNumber)
|
||||||
|
{
|
||||||
|
ExplainAnalyzeDestination *tupleDestination = (ExplainAnalyzeDestination *) self;
|
||||||
|
if (queryNumber == 0)
|
||||||
|
{
|
||||||
|
TupleDestination *originalTupDest = tupleDestination->originalTaskDestination;
|
||||||
|
return originalTupDest->tupleDescForQuery(originalTupDest, 0);
|
||||||
|
}
|
||||||
|
else if (queryNumber == 1)
|
||||||
|
{
|
||||||
|
return tupleDestination->lastSavedExplainAnalyzeTupDesc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ereport(ERROR, (errmsg("cannot get EXPLAIN ANALYZE of multiple queries"),
|
||||||
|
errdetail("while requesting for tuple descriptor of query %d",
|
||||||
|
queryNumber)));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RequestedForExplainAnalyze returns true if we should get the EXPLAIN ANALYZE
|
||||||
|
* output for the given custom scan node.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
RequestedForExplainAnalyze(CitusScanState *node)
|
||||||
|
{
|
||||||
|
return (node->customScanState.ss.ps.state->es_instrument != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ExplainAnalyzeTaskList returns a task list suitable for explain analyze. After executing
|
||||||
|
* these tasks, fetchedExplainAnalyzePlan of originalTaskList should be populated.
|
||||||
|
*/
|
||||||
|
List *
|
||||||
|
ExplainAnalyzeTaskList(List *originalTaskList,
|
||||||
|
TupleDestination *defaultTupleDest,
|
||||||
|
TupleDesc tupleDesc,
|
||||||
|
ParamListInfo params)
|
||||||
|
{
|
||||||
|
List *explainAnalyzeTaskList = NIL;
|
||||||
|
Task *originalTask = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We cannot use multiple commands in a prepared statement, so use the old
|
||||||
|
* EXPLAIN ANALYZE method for this case.
|
||||||
|
*/
|
||||||
|
if (params != NULL)
|
||||||
|
{
|
||||||
|
return originalTaskList;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach_ptr(originalTask, originalTaskList)
|
||||||
|
{
|
||||||
|
if (originalTask->queryCount != 1)
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("cannot get EXPLAIN ANALYZE of multiple queries")));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task *explainAnalyzeTask = copyObject(originalTask);
|
||||||
|
const char *queryString = TaskQueryStringForAllPlacements(explainAnalyzeTask);
|
||||||
|
char *wrappedQuery = WrapQueryForExplainAnalyze(queryString, tupleDesc);
|
||||||
|
char *fetchQuery = "SELECT worker_last_saved_explain_analyze()";
|
||||||
|
SetTaskQueryStringList(explainAnalyzeTask, list_make2(wrappedQuery, fetchQuery));
|
||||||
|
|
||||||
|
TupleDestination *originalTaskDest = originalTask->tupleDest ?
|
||||||
|
originalTask->tupleDest :
|
||||||
|
defaultTupleDest;
|
||||||
|
|
||||||
|
explainAnalyzeTask->tupleDest =
|
||||||
|
CreateExplainAnlyzeDestination(originalTask, originalTaskDest);
|
||||||
|
|
||||||
|
explainAnalyzeTaskList = lappend(explainAnalyzeTaskList, explainAnalyzeTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
return explainAnalyzeTaskList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WrapQueryForExplainAnalyze wraps a query into a worker_save_query_explain_analyze()
|
||||||
|
* call so we can fetch its explain analyze after its execution.
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
WrapQueryForExplainAnalyze(const char *queryString, TupleDesc tupleDesc)
|
||||||
|
{
|
||||||
|
StringInfo columnDef = makeStringInfo();
|
||||||
|
for (int columnIndex = 0; columnIndex < tupleDesc->natts; columnIndex++)
|
||||||
|
{
|
||||||
|
if (columnIndex != 0)
|
||||||
|
{
|
||||||
|
appendStringInfoString(columnDef, ", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
Form_pg_attribute attr = &tupleDesc->attrs[columnIndex];
|
||||||
|
char *attrType = format_type_with_typemod(attr->atttypid, attr->atttypmod);
|
||||||
|
|
||||||
|
appendStringInfo(columnDef, "field_%d %s", columnIndex, attrType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* column definition cannot be empty, so create a dummy column definition for
|
||||||
|
* queries with no results.
|
||||||
|
*/
|
||||||
|
if (tupleDesc->natts == 0)
|
||||||
|
{
|
||||||
|
appendStringInfo(columnDef, "dummy_field int");
|
||||||
|
}
|
||||||
|
|
||||||
|
StringInfo explainOptions = makeStringInfo();
|
||||||
|
appendStringInfo(explainOptions, "{\"verbose\": %s, \"costs\": %s, \"buffers\": %s, "
|
||||||
|
"\"timing\": %s, \"summary\": %s, \"format\": \"%s\"}",
|
||||||
|
CurrentDistributedQueryExplainOptions.verbose ? "true" : "false",
|
||||||
|
CurrentDistributedQueryExplainOptions.costs ? "true" : "false",
|
||||||
|
CurrentDistributedQueryExplainOptions.buffers ? "true" : "false",
|
||||||
|
CurrentDistributedQueryExplainOptions.timing ? "true" : "false",
|
||||||
|
CurrentDistributedQueryExplainOptions.summary ? "true" : "false",
|
||||||
|
ExplainFormatStr(CurrentDistributedQueryExplainOptions.format));
|
||||||
|
|
||||||
|
StringInfo wrappedQuery = makeStringInfo();
|
||||||
|
appendStringInfo(wrappedQuery,
|
||||||
|
"SELECT * FROM worker_save_query_explain_analyze(%s, %s) AS (%s)",
|
||||||
|
quote_literal_cstr(queryString),
|
||||||
|
quote_literal_cstr(explainOptions->data),
|
||||||
|
columnDef->data);
|
||||||
|
|
||||||
|
return wrappedQuery->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SplitString splits the given string by the given delimiter.
|
||||||
|
*
|
||||||
|
* Why not use strtok_s()? Its signature and semantics are difficult to understand.
|
||||||
|
*
|
||||||
|
* Why not use strchr() (similar to do_text_output_multiline)? Although not banned,
|
||||||
|
* it isn't safe if by any chance str is not null-terminated.
|
||||||
|
*/
|
||||||
|
static List *
|
||||||
|
SplitString(const char *str, char delimiter)
|
||||||
|
{
|
||||||
|
size_t len = strnlen_s(str, RSIZE_MAX_STR);
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
List *tokenList = NIL;
|
||||||
|
StringInfo token = makeStringInfo();
|
||||||
|
|
||||||
|
for (size_t index = 0; index < len; index++)
|
||||||
|
{
|
||||||
|
if (str[index] == delimiter)
|
||||||
|
{
|
||||||
|
tokenList = lappend(tokenList, token);
|
||||||
|
token = makeStringInfo();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appendStringInfoChar(token, str[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* append last token */
|
||||||
|
tokenList = lappend(tokenList, token);
|
||||||
|
|
||||||
|
return tokenList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* below are private functions copied from explain.c */
|
/* below are private functions copied from explain.c */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -221,7 +221,8 @@ _PG_init(void)
|
||||||
* duties. For simplicity insist that all hooks are previously unused.
|
* duties. For simplicity insist that all hooks are previously unused.
|
||||||
*/
|
*/
|
||||||
if (planner_hook != NULL || ProcessUtility_hook != NULL ||
|
if (planner_hook != NULL || ProcessUtility_hook != NULL ||
|
||||||
ExecutorStart_hook != NULL || ExecutorRun_hook != NULL)
|
ExecutorStart_hook != NULL || ExecutorRun_hook != NULL ||
|
||||||
|
ExplainOneQuery_hook != NULL)
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errmsg("Citus has to be loaded first"),
|
ereport(ERROR, (errmsg("Citus has to be loaded first"),
|
||||||
errhint("Place citus at the beginning of "
|
errhint("Place citus at the beginning of "
|
||||||
|
@ -267,6 +268,7 @@ _PG_init(void)
|
||||||
set_join_pathlist_hook = multi_join_restriction_hook;
|
set_join_pathlist_hook = multi_join_restriction_hook;
|
||||||
ExecutorStart_hook = CitusExecutorStart;
|
ExecutorStart_hook = CitusExecutorStart;
|
||||||
ExecutorRun_hook = CitusExecutorRun;
|
ExecutorRun_hook = CitusExecutorRun;
|
||||||
|
ExplainOneQuery_hook = CitusExplainOneQuery;
|
||||||
|
|
||||||
/* register hook for error messages */
|
/* register hook for error messages */
|
||||||
emit_log_hook = multi_log_hook;
|
emit_log_hook = multi_log_hook;
|
||||||
|
|
|
@ -329,6 +329,8 @@ CopyNodeTask(COPYFUNC_ARGS)
|
||||||
COPY_SCALAR_FIELD(parametersInQueryStringResolved);
|
COPY_SCALAR_FIELD(parametersInQueryStringResolved);
|
||||||
COPY_SCALAR_FIELD(tupleDest);
|
COPY_SCALAR_FIELD(tupleDest);
|
||||||
COPY_SCALAR_FIELD(queryCount);
|
COPY_SCALAR_FIELD(queryCount);
|
||||||
|
COPY_SCALAR_FIELD(fetchedExplainAnalyzePlacementIndex);
|
||||||
|
COPY_STRING_FIELD(fetchedExplainAnalyzePlan);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,20 @@
|
||||||
#define MULTI_EXPLAIN_H
|
#define MULTI_EXPLAIN_H
|
||||||
|
|
||||||
#include "executor/executor.h"
|
#include "executor/executor.h"
|
||||||
|
#include "tuple_destination.h"
|
||||||
|
|
||||||
/* Config variables managed via guc.c to explain distributed query plans */
|
/* Config variables managed via guc.c to explain distributed query plans */
|
||||||
extern bool ExplainDistributedQueries;
|
extern bool ExplainDistributedQueries;
|
||||||
extern bool ExplainAllTasks;
|
extern bool ExplainAllTasks;
|
||||||
|
|
||||||
extern void FreeSavedExplainPlan(void);
|
extern void FreeSavedExplainPlan(void);
|
||||||
|
extern void CitusExplainOneQuery(Query *query, int cursorOptions, IntoClause *into,
|
||||||
|
ExplainState *es, const char *queryString, ParamListInfo
|
||||||
|
params,
|
||||||
|
QueryEnvironment *queryEnv);
|
||||||
|
extern List * ExplainAnalyzeTaskList(List *originalTaskList,
|
||||||
|
TupleDestination *defaultTupleDest, TupleDesc
|
||||||
|
tupleDesc, ParamListInfo params);
|
||||||
|
extern bool RequestedForExplainAnalyze(CitusScanState *node);
|
||||||
|
|
||||||
#endif /* MULTI_EXPLAIN_H */
|
#endif /* MULTI_EXPLAIN_H */
|
||||||
|
|
|
@ -337,6 +337,13 @@ typedef struct Task
|
||||||
* NULL, in which case executor might use a default destination.
|
* NULL, in which case executor might use a default destination.
|
||||||
*/
|
*/
|
||||||
struct TupleDestination *tupleDest;
|
struct TupleDestination *tupleDest;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* EXPLAIN ANALYZE output fetched from worker. This is saved to be used later
|
||||||
|
* by RemoteExplain().
|
||||||
|
*/
|
||||||
|
char *fetchedExplainAnalyzePlan;
|
||||||
|
int fetchedExplainAnalyzePlacementIndex;
|
||||||
} Task;
|
} Task;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -327,10 +327,11 @@ EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF) DELETE FROM distributed_ta
|
||||||
-> Task
|
-> Task
|
||||||
Node: host=localhost port=xxxxx dbname=regression
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
-> Delete on distributed_table_1470001 distributed_table (actual rows=0 loops=1)
|
-> Delete on distributed_table_1470001 distributed_table (actual rows=0 loops=1)
|
||||||
-> Index Scan using distributed_table_pkey_1470001 on distributed_table_1470001 distributed_table (actual rows=0 loops=1)
|
-> Index Scan using distributed_table_pkey_1470001 on distributed_table_1470001 distributed_table (actual rows=1 loops=1)
|
||||||
Index Cond: (key = 1)
|
Index Cond: (key = 1)
|
||||||
Filter: (age = 20)
|
Filter: (age = 20)
|
||||||
(9 rows)
|
Trigger for constraint second_distributed_table_key_fkey_1470005: calls=1
|
||||||
|
(10 rows)
|
||||||
|
|
||||||
-- show that EXPLAIN ANALYZE deleted the row and cascades deletes
|
-- show that EXPLAIN ANALYZE deleted the row and cascades deletes
|
||||||
SELECT * FROM distributed_table WHERE key = 1 AND age = 20 ORDER BY 1,2,3;
|
SELECT * FROM distributed_table WHERE key = 1 AND age = 20 ORDER BY 1,2,3;
|
||||||
|
|
|
@ -1377,7 +1377,7 @@ t
|
||||||
--
|
--
|
||||||
\a\t
|
\a\t
|
||||||
\set default_opts '''{"costs": false, "timing": false, "summary": false}'''::jsonb
|
\set default_opts '''{"costs": false, "timing": false, "summary": false}'''::jsonb
|
||||||
CREATE TABLE explain_analyze_test(a int, b text);;
|
CREATE TABLE explain_analyze_test(a int, b text);
|
||||||
INSERT INTO explain_analyze_test VALUES (1, 'value 1'), (2, 'value 2'), (3, 'value 3'), (4, 'value 4');
|
INSERT INTO explain_analyze_test VALUES (1, 'value 1'), (2, 'value 2'), (3, 'value 3'), (4, 'value 4');
|
||||||
-- simple select
|
-- simple select
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
@ -1690,7 +1690,7 @@ SELECT worker_last_saved_explain_analyze() IS NULL;
|
||||||
|
|
||||||
-- should be deleted at the end of prepare commit
|
-- should be deleted at the end of prepare commit
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT * FROM worker_save_query_explain_analyze('UPDATE explain_analyze_test SET a=1', '{}') as (a int);
|
SELECT * FROM worker_save_query_explain_analyze('UPDATE explain_analyze_test SET a=6 WHERE a=4', '{}') as (a int);
|
||||||
a
|
a
|
||||||
---------------------------------------------------------------------
|
---------------------------------------------------------------------
|
||||||
(0 rows)
|
(0 rows)
|
||||||
|
@ -1709,3 +1709,367 @@ SELECT worker_last_saved_explain_analyze() IS NULL;
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
COMMIT PREPARED 'citus_0_1496350_7_0';
|
COMMIT PREPARED 'citus_0_1496350_7_0';
|
||||||
|
SELECT * FROM explain_analyze_test ORDER BY a;
|
||||||
|
a | b
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
1 | value 1
|
||||||
|
2 | value 2
|
||||||
|
3 | value 3
|
||||||
|
6 | value 4
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
|
\a\t
|
||||||
|
--
|
||||||
|
-- Test different cases of EXPLAIN ANALYZE
|
||||||
|
--
|
||||||
|
SET citus.shard_count TO 4;
|
||||||
|
SET client_min_messages TO WARNING;
|
||||||
|
SELECT create_distributed_table('explain_analyze_test', 'a');
|
||||||
|
|
||||||
|
\set default_analyze_flags '(ANALYZE on, COSTS off, TIMING off, SUMMARY off)'
|
||||||
|
-- router SELECT
|
||||||
|
EXPLAIN :default_analyze_flags SELECT * FROM explain_analyze_test WHERE a = 1;
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=1 loops=1)
|
||||||
|
Task Count: 1
|
||||||
|
Tasks Shown: All
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Seq Scan on explain_analyze_test_570009 explain_analyze_test (actual rows=1 loops=1)
|
||||||
|
Filter: (a = 1)
|
||||||
|
-- multi-shard SELECT
|
||||||
|
EXPLAIN :default_analyze_flags SELECT count(*) FROM explain_analyze_test;
|
||||||
|
Aggregate (actual rows=1 loops=1)
|
||||||
|
-> Custom Scan (Citus Adaptive) (actual rows=4 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Aggregate (actual rows=1 loops=1)
|
||||||
|
-> Seq Scan on explain_analyze_test_570009 explain_analyze_test (actual rows=1 loops=1)
|
||||||
|
-- router DML
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags DELETE FROM explain_analyze_test WHERE a = 1;
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 1
|
||||||
|
Tasks Shown: All
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Delete on explain_analyze_test_570009 explain_analyze_test (actual rows=0 loops=1)
|
||||||
|
-> Seq Scan on explain_analyze_test_570009 explain_analyze_test (actual rows=1 loops=1)
|
||||||
|
Filter: (a = 1)
|
||||||
|
EXPLAIN :default_analyze_flags UPDATE explain_analyze_test SET b = 'b' WHERE a = 2;
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 1
|
||||||
|
Tasks Shown: All
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Update on explain_analyze_test_570012 explain_analyze_test (actual rows=0 loops=1)
|
||||||
|
-> Seq Scan on explain_analyze_test_570012 explain_analyze_test (actual rows=1 loops=1)
|
||||||
|
Filter: (a = 2)
|
||||||
|
SELECT * FROM explain_analyze_test ORDER BY a;
|
||||||
|
2|b
|
||||||
|
3|value 3
|
||||||
|
6|value 4
|
||||||
|
ROLLBACK;
|
||||||
|
-- multi-shard DML
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags UPDATE explain_analyze_test SET b = 'b' WHERE a IN (1, 2);
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 2
|
||||||
|
Tasks Shown: One of 2
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Update on explain_analyze_test_570009 explain_analyze_test (actual rows=0 loops=1)
|
||||||
|
-> Seq Scan on explain_analyze_test_570009 explain_analyze_test (actual rows=1 loops=1)
|
||||||
|
Filter: (a = ANY ('{1,2}'::integer[]))
|
||||||
|
EXPLAIN :default_analyze_flags DELETE FROM explain_analyze_test;
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Delete on explain_analyze_test_570009 explain_analyze_test (actual rows=0 loops=1)
|
||||||
|
-> Seq Scan on explain_analyze_test_570009 explain_analyze_test (actual rows=1 loops=1)
|
||||||
|
SELECT * FROM explain_analyze_test ORDER BY a;
|
||||||
|
ROLLBACK;
|
||||||
|
-- single-row insert
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_analyze_test VALUES (5, 'value 5');
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 1
|
||||||
|
Tasks Shown: All
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Insert on explain_analyze_test_570009 (actual rows=0 loops=1)
|
||||||
|
-> Result (actual rows=1 loops=1)
|
||||||
|
ROLLBACK;
|
||||||
|
-- multi-row insert
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_analyze_test VALUES (5, 'value 5'), (6, 'value 6');
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 2
|
||||||
|
Tasks Shown: One of 2
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Insert on explain_analyze_test_570009 citus_table_alias (actual rows=0 loops=1)
|
||||||
|
-> Result (actual rows=1 loops=1)
|
||||||
|
ROLLBACK;
|
||||||
|
-- distributed insert/select
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_analyze_test SELECT * FROM explain_analyze_test;
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Insert on explain_analyze_test_570009 citus_table_alias (actual rows=0 loops=1)
|
||||||
|
-> Seq Scan on explain_analyze_test_570009 explain_analyze_test (actual rows=1 loops=1)
|
||||||
|
Filter: ((worker_hash(a) >= '-2147483648'::integer) AND (worker_hash(a) <= '-1073741825'::integer))
|
||||||
|
ROLLBACK;
|
||||||
|
DROP TABLE explain_analyze_test;
|
||||||
|
-- test EXPLAIN ANALYZE works fine with primary keys
|
||||||
|
CREATE TABLE explain_pk(a int primary key, b int);
|
||||||
|
SELECT create_distributed_table('explain_pk', 'a');
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_pk VALUES (1, 2), (2, 3);
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=0 loops=1)
|
||||||
|
Task Count: 2
|
||||||
|
Tasks Shown: One of 2
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Insert on explain_pk_570013 citus_table_alias (actual rows=0 loops=1)
|
||||||
|
-> Result (actual rows=1 loops=1)
|
||||||
|
SELECT * FROM explain_pk ORDER BY 1;
|
||||||
|
1|2
|
||||||
|
2|3
|
||||||
|
ROLLBACK;
|
||||||
|
-- test EXPLAIN ANALYZE with non-text output formats
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT JSON) INSERT INTO explain_pk VALUES (1, 2), (2, 3);
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Plan": {
|
||||||
|
"Node Type": "Custom Scan",
|
||||||
|
"Custom Plan Provider": "Citus Adaptive",
|
||||||
|
"Parallel Aware": false,
|
||||||
|
"Actual Rows": 0,
|
||||||
|
"Actual Loops": 1,
|
||||||
|
"Distributed Query": {
|
||||||
|
"Job": {
|
||||||
|
"Task Count": 2,
|
||||||
|
"Tasks Shown": "One of 2",
|
||||||
|
"Tasks": [
|
||||||
|
{
|
||||||
|
"Node": "host=localhost port=xxxxx dbname=regression",
|
||||||
|
"Remote Plan": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Plan": {
|
||||||
|
"Node Type": "ModifyTable",
|
||||||
|
"Operation": "Insert",
|
||||||
|
"Parallel Aware": false,
|
||||||
|
"Relation Name": "explain_pk_570013",
|
||||||
|
"Alias": "citus_table_alias",
|
||||||
|
"Actual Rows": 0,
|
||||||
|
"Actual Loops": 1,
|
||||||
|
"Plans": [
|
||||||
|
{
|
||||||
|
"Node Type": "Result",
|
||||||
|
"Parent Relationship": "Member",
|
||||||
|
"Parallel Aware": false,
|
||||||
|
"Actual Rows": 1,
|
||||||
|
"Actual Loops": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Triggers": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Triggers": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
ROLLBACK;
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT XML) INSERT INTO explain_pk VALUES (1, 2), (2, 3);
|
||||||
|
<explain xmlns="http://www.postgresql.org/2009/explain">
|
||||||
|
<Query>
|
||||||
|
<Plan>
|
||||||
|
<Node-Type>Custom Scan</Node-Type>
|
||||||
|
<Custom-Plan-Provider>Citus Adaptive</Custom-Plan-Provider>
|
||||||
|
<Parallel-Aware>false</Parallel-Aware>
|
||||||
|
<Actual-Rows>0</Actual-Rows>
|
||||||
|
<Actual-Loops>1</Actual-Loops>
|
||||||
|
<Distributed-Query>
|
||||||
|
<Job>
|
||||||
|
<Task-Count>2</Task-Count>
|
||||||
|
<Tasks-Shown>One of 2</Tasks-Shown>
|
||||||
|
<Tasks>
|
||||||
|
<Task>
|
||||||
|
<Node>host=localhost port=xxxxx dbname=regression</Node>
|
||||||
|
<Remote-Plan>
|
||||||
|
<explain xmlns="http://www.postgresql.org/2009/explain">
|
||||||
|
<Query>
|
||||||
|
<Plan>
|
||||||
|
<Node-Type>ModifyTable</Node-Type>
|
||||||
|
<Operation>Insert</Operation>
|
||||||
|
<Parallel-Aware>false</Parallel-Aware>
|
||||||
|
<Relation-Name>explain_pk_570013</Relation-Name>
|
||||||
|
<Alias>citus_table_alias</Alias>
|
||||||
|
<Actual-Rows>0</Actual-Rows>
|
||||||
|
<Actual-Loops>1</Actual-Loops>
|
||||||
|
<Plans>
|
||||||
|
<Plan>
|
||||||
|
<Node-Type>Result</Node-Type>
|
||||||
|
<Parent-Relationship>Member</Parent-Relationship>
|
||||||
|
<Parallel-Aware>false</Parallel-Aware>
|
||||||
|
<Actual-Rows>1</Actual-Rows>
|
||||||
|
<Actual-Loops>1</Actual-Loops>
|
||||||
|
</Plan>
|
||||||
|
</Plans>
|
||||||
|
</Plan>
|
||||||
|
<Triggers>
|
||||||
|
</Triggers>
|
||||||
|
</Query>
|
||||||
|
</explain>
|
||||||
|
</Remote-Plan>
|
||||||
|
</Task>
|
||||||
|
</Tasks>
|
||||||
|
</Job>
|
||||||
|
</Distributed-Query>
|
||||||
|
</Plan>
|
||||||
|
<Triggers>
|
||||||
|
</Triggers>
|
||||||
|
</Query>
|
||||||
|
</explain>
|
||||||
|
ROLLBACK;
|
||||||
|
DROP TABLE explain_pk;
|
||||||
|
-- test EXPLAIN ANALYZE with CTEs and subqueries
|
||||||
|
CREATE TABLE dist_table(a int, b int);
|
||||||
|
SELECT create_distributed_table('dist_table', 'a');
|
||||||
|
|
||||||
|
CREATE TABLE ref_table(a int);
|
||||||
|
SELECT create_reference_table('ref_table');
|
||||||
|
|
||||||
|
INSERT INTO dist_table SELECT i, i*i FROM generate_series(1, 10) i;
|
||||||
|
INSERT INTO ref_table SELECT i FROM generate_series(1, 10) i;
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
WITH r AS (
|
||||||
|
SELECT random() r, a FROM dist_table
|
||||||
|
)
|
||||||
|
SELECT count(distinct a) from r NATURAL JOIN ref_table;
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=1 loops=1)
|
||||||
|
-> Distributed Subplan XXX_1
|
||||||
|
-> Custom Scan (Citus Adaptive) (actual rows=10 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Seq Scan on dist_table_570017 dist_table (actual rows=4 loops=1)
|
||||||
|
Task Count: 1
|
||||||
|
Tasks Shown: All
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Aggregate (actual rows=1 loops=1)
|
||||||
|
-> Hash Join (actual rows=10 loops=1)
|
||||||
|
Hash Cond: (ref_table.a = intermediate_result.a)
|
||||||
|
-> Seq Scan on ref_table_570021 ref_table (actual rows=10 loops=1)
|
||||||
|
-> Hash (actual rows=10 loops=1)
|
||||||
|
Buckets: 1024 Batches: 1 Memory Usage: 9kB
|
||||||
|
-> Function Scan on read_intermediate_result intermediate_result (actual rows=10 loops=1)
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
SELECT count(distinct a) FROM (SELECT random() r, a FROM dist_table) t NATURAL JOIN ref_table;
|
||||||
|
Aggregate (actual rows=1 loops=1)
|
||||||
|
-> Custom Scan (Citus Adaptive) (actual rows=10 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Group (actual rows=4 loops=1)
|
||||||
|
Group Key: t.a
|
||||||
|
-> Merge Join (actual rows=4 loops=1)
|
||||||
|
Merge Cond: (t.a = ref_table.a)
|
||||||
|
-> Sort (actual rows=4 loops=1)
|
||||||
|
Sort Key: t.a
|
||||||
|
Sort Method: quicksort Memory: 25kB
|
||||||
|
-> Subquery Scan on t (actual rows=4 loops=1)
|
||||||
|
-> Seq Scan on dist_table_570017 dist_table (actual rows=4 loops=1)
|
||||||
|
-> Sort (actual rows=10 loops=1)
|
||||||
|
Sort Key: ref_table.a
|
||||||
|
Sort Method: quicksort Memory: 25kB
|
||||||
|
-> Seq Scan on ref_table_570021 ref_table (actual rows=10 loops=1)
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
SELECT count(distinct a) FROM dist_table
|
||||||
|
WHERE EXISTS(SELECT random() FROM dist_table NATURAL JOIN ref_table);
|
||||||
|
Aggregate (actual rows=1 loops=1)
|
||||||
|
-> Custom Scan (Citus Adaptive) (actual rows=10 loops=1)
|
||||||
|
-> Distributed Subplan XXX_1
|
||||||
|
-> Custom Scan (Citus Adaptive) (actual rows=10 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Merge Join (actual rows=4 loops=1)
|
||||||
|
Merge Cond: (dist_table.a = ref_table.a)
|
||||||
|
-> Sort (actual rows=4 loops=1)
|
||||||
|
Sort Key: dist_table.a
|
||||||
|
Sort Method: quicksort Memory: 25kB
|
||||||
|
-> Seq Scan on dist_table_570017 dist_table (actual rows=4 loops=1)
|
||||||
|
-> Sort (actual rows=10 loops=1)
|
||||||
|
Sort Key: ref_table.a
|
||||||
|
Sort Method: quicksort Memory: 25kB
|
||||||
|
-> Seq Scan on ref_table_570021 ref_table (actual rows=10 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> HashAggregate (actual rows=4 loops=1)
|
||||||
|
Group Key: dist_table.a
|
||||||
|
InitPlan 1 (returns $0)
|
||||||
|
-> Function Scan on read_intermediate_result intermediate_result (actual rows=1 loops=1)
|
||||||
|
-> Result (actual rows=4 loops=1)
|
||||||
|
One-Time Filter: $0
|
||||||
|
-> Seq Scan on dist_table_570017 dist_table (actual rows=4 loops=1)
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
WITH r AS (
|
||||||
|
INSERT INTO dist_table SELECT a, a * a FROM dist_table
|
||||||
|
RETURNING a
|
||||||
|
), s AS (
|
||||||
|
SELECT random(), a * a a2 FROM r
|
||||||
|
)
|
||||||
|
SELECT count(distinct a2) FROM s;
|
||||||
|
Custom Scan (Citus Adaptive) (actual rows=1 loops=1)
|
||||||
|
-> Distributed Subplan XXX_1
|
||||||
|
-> Custom Scan (Citus Adaptive) (actual rows=20 loops=1)
|
||||||
|
Task Count: 4
|
||||||
|
Tasks Shown: One of 4
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Insert on dist_table_570017 citus_table_alias (actual rows=8 loops=1)
|
||||||
|
-> Seq Scan on dist_table_570017 dist_table (actual rows=8 loops=1)
|
||||||
|
Filter: ((worker_hash(a) >= '-2147483648'::integer) AND (worker_hash(a) <= '-1073741825'::integer))
|
||||||
|
-> Distributed Subplan XXX_2
|
||||||
|
-> Custom Scan (Citus Adaptive) (actual rows=10 loops=1)
|
||||||
|
Task Count: 1
|
||||||
|
Tasks Shown: All
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Function Scan on read_intermediate_result intermediate_result (actual rows=10 loops=1)
|
||||||
|
Task Count: 1
|
||||||
|
Tasks Shown: All
|
||||||
|
-> Task
|
||||||
|
Node: host=localhost port=xxxxx dbname=regression
|
||||||
|
-> Aggregate (actual rows=1 loops=1)
|
||||||
|
-> Function Scan on read_intermediate_result intermediate_result (actual rows=10 loops=1)
|
||||||
|
ROLLBACK;
|
||||||
|
DROP TABLE ref_table, dist_table;
|
||||||
|
|
|
@ -620,7 +620,7 @@ $$);
|
||||||
|
|
||||||
\set default_opts '''{"costs": false, "timing": false, "summary": false}'''::jsonb
|
\set default_opts '''{"costs": false, "timing": false, "summary": false}'''::jsonb
|
||||||
|
|
||||||
CREATE TABLE explain_analyze_test(a int, b text);;
|
CREATE TABLE explain_analyze_test(a int, b text);
|
||||||
INSERT INTO explain_analyze_test VALUES (1, 'value 1'), (2, 'value 2'), (3, 'value 3'), (4, 'value 4');
|
INSERT INTO explain_analyze_test VALUES (1, 'value 1'), (2, 'value 2'), (3, 'value 3'), (4, 'value 4');
|
||||||
|
|
||||||
-- simple select
|
-- simple select
|
||||||
|
@ -727,8 +727,114 @@ SELECT worker_last_saved_explain_analyze() IS NULL;
|
||||||
|
|
||||||
-- should be deleted at the end of prepare commit
|
-- should be deleted at the end of prepare commit
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT * FROM worker_save_query_explain_analyze('UPDATE explain_analyze_test SET a=1', '{}') as (a int);
|
SELECT * FROM worker_save_query_explain_analyze('UPDATE explain_analyze_test SET a=6 WHERE a=4', '{}') as (a int);
|
||||||
SELECT worker_last_saved_explain_analyze() IS NOT NULL;
|
SELECT worker_last_saved_explain_analyze() IS NOT NULL;
|
||||||
PREPARE TRANSACTION 'citus_0_1496350_7_0';
|
PREPARE TRANSACTION 'citus_0_1496350_7_0';
|
||||||
SELECT worker_last_saved_explain_analyze() IS NULL;
|
SELECT worker_last_saved_explain_analyze() IS NULL;
|
||||||
COMMIT PREPARED 'citus_0_1496350_7_0';
|
COMMIT PREPARED 'citus_0_1496350_7_0';
|
||||||
|
|
||||||
|
SELECT * FROM explain_analyze_test ORDER BY a;
|
||||||
|
|
||||||
|
\a\t
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test different cases of EXPLAIN ANALYZE
|
||||||
|
--
|
||||||
|
|
||||||
|
SET citus.shard_count TO 4;
|
||||||
|
SET client_min_messages TO WARNING;
|
||||||
|
SELECT create_distributed_table('explain_analyze_test', 'a');
|
||||||
|
|
||||||
|
\set default_analyze_flags '(ANALYZE on, COSTS off, TIMING off, SUMMARY off)'
|
||||||
|
|
||||||
|
-- router SELECT
|
||||||
|
EXPLAIN :default_analyze_flags SELECT * FROM explain_analyze_test WHERE a = 1;
|
||||||
|
|
||||||
|
-- multi-shard SELECT
|
||||||
|
EXPLAIN :default_analyze_flags SELECT count(*) FROM explain_analyze_test;
|
||||||
|
|
||||||
|
-- router DML
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags DELETE FROM explain_analyze_test WHERE a = 1;
|
||||||
|
EXPLAIN :default_analyze_flags UPDATE explain_analyze_test SET b = 'b' WHERE a = 2;
|
||||||
|
SELECT * FROM explain_analyze_test ORDER BY a;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- multi-shard DML
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags UPDATE explain_analyze_test SET b = 'b' WHERE a IN (1, 2);
|
||||||
|
EXPLAIN :default_analyze_flags DELETE FROM explain_analyze_test;
|
||||||
|
SELECT * FROM explain_analyze_test ORDER BY a;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- single-row insert
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_analyze_test VALUES (5, 'value 5');
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- multi-row insert
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_analyze_test VALUES (5, 'value 5'), (6, 'value 6');
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- distributed insert/select
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_analyze_test SELECT * FROM explain_analyze_test;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
DROP TABLE explain_analyze_test;
|
||||||
|
|
||||||
|
-- test EXPLAIN ANALYZE works fine with primary keys
|
||||||
|
CREATE TABLE explain_pk(a int primary key, b int);
|
||||||
|
SELECT create_distributed_table('explain_pk', 'a');
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags INSERT INTO explain_pk VALUES (1, 2), (2, 3);
|
||||||
|
SELECT * FROM explain_pk ORDER BY 1;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
-- test EXPLAIN ANALYZE with non-text output formats
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT JSON) INSERT INTO explain_pk VALUES (1, 2), (2, 3);
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN (COSTS off, ANALYZE on, TIMING off, SUMMARY off, FORMAT XML) INSERT INTO explain_pk VALUES (1, 2), (2, 3);
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
DROP TABLE explain_pk;
|
||||||
|
|
||||||
|
-- test EXPLAIN ANALYZE with CTEs and subqueries
|
||||||
|
CREATE TABLE dist_table(a int, b int);
|
||||||
|
SELECT create_distributed_table('dist_table', 'a');
|
||||||
|
CREATE TABLE ref_table(a int);
|
||||||
|
SELECT create_reference_table('ref_table');
|
||||||
|
|
||||||
|
INSERT INTO dist_table SELECT i, i*i FROM generate_series(1, 10) i;
|
||||||
|
INSERT INTO ref_table SELECT i FROM generate_series(1, 10) i;
|
||||||
|
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
WITH r AS (
|
||||||
|
SELECT random() r, a FROM dist_table
|
||||||
|
)
|
||||||
|
SELECT count(distinct a) from r NATURAL JOIN ref_table;
|
||||||
|
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
SELECT count(distinct a) FROM (SELECT random() r, a FROM dist_table) t NATURAL JOIN ref_table;
|
||||||
|
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
SELECT count(distinct a) FROM dist_table
|
||||||
|
WHERE EXISTS(SELECT random() FROM dist_table NATURAL JOIN ref_table);
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
EXPLAIN :default_analyze_flags
|
||||||
|
WITH r AS (
|
||||||
|
INSERT INTO dist_table SELECT a, a * a FROM dist_table
|
||||||
|
RETURNING a
|
||||||
|
), s AS (
|
||||||
|
SELECT random(), a * a a2 FROM r
|
||||||
|
)
|
||||||
|
SELECT count(distinct a2) FROM s;
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
DROP TABLE ref_table, dist_table;
|
||||||
|
|
Loading…
Reference in New Issue