Use BaseRestrictInfo for finding equality columns

Baseinfo also has pushed down filters etc, so it makes more sense to use
BaseRestrictInfo to determine what columns have constant equality
filters.

Also RteIdentity is used for removing conversion candidates instead of
rteIndex.
pull/4358/head
Sait Talha Nisanci 2020-12-07 16:55:15 +03:00
parent 28c5b6a425
commit 5618f3a3fc
24 changed files with 589 additions and 333 deletions

View File

@ -249,6 +249,7 @@ CreateIndexStmtGetSchemaId(IndexStmt *createIndexStatement)
return namespaceId; return namespaceId;
} }
/* /*
* ExecuteFunctionOnEachTableIndex executes the given pgIndexProcessor function on each * ExecuteFunctionOnEachTableIndex executes the given pgIndexProcessor function on each
* index of the given relation. * index of the given relation.

View File

@ -368,11 +368,10 @@ CitusBeginModifyScan(CustomScanState *node, EState *estate, int eflags)
RebuildQueryStrings(workerJob); RebuildQueryStrings(workerJob);
} }
if (workerJob->onDummyPlacement) {
/* if this job is on a dummy placement, then it doesn't operate on /* We skip shard related things if the job contains only local tables */
an actual shard placement */ if (!OnlyLocalTableJob(workerJob))
return; {
}
/* /*
* Now that we know the shard ID(s) we can acquire the necessary shard metadata * Now that we know the shard ID(s) we can acquire the necessary shard metadata
* locks. Once we have the locks it's safe to load the placement metadata. * locks. Once we have the locks it's safe to load the placement metadata.
@ -381,9 +380,10 @@ CitusBeginModifyScan(CustomScanState *node, EState *estate, int eflags)
/* prevent concurrent placement changes */ /* prevent concurrent placement changes */
AcquireMetadataLocks(workerJob->taskList); AcquireMetadataLocks(workerJob->taskList);
/* modify tasks are always assigned using first-replica policy */ /* modify tasks are always assigned using first-replica policy */
workerJob->taskList = FirstReplicaAssignTaskList(workerJob->taskList); workerJob->taskList = FirstReplicaAssignTaskList(workerJob->taskList);
}
/* /*
* Now that we have populated the task placements we can determine whether * Now that we have populated the task placements we can determine whether
@ -544,10 +544,12 @@ RegenerateTaskForFasthPathQuery(Job *workerJob)
shardId = GetAnchorShardId(shardIntervalList); shardId = GetAnchorShardId(shardIntervalList);
} }
bool containsOnlyLocalTable = false;
GenerateSingleShardRouterTaskList(workerJob, GenerateSingleShardRouterTaskList(workerJob,
relationShardList, relationShardList,
placementList, placementList,
shardId); shardId,
containsOnlyLocalTable);
} }

View File

@ -410,7 +410,7 @@ AdjustPartitioningForDistributedPlanning(List *rangeTableList,
/* /*
* We want Postgres to behave partitioned tables as regular relations * We want Postgres to behave partitioned tables as regular relations
* (i.e. we do not want to expand them to their partitions). To do this * (i.e. we do not want to expand them to their partitions). To do this
* we set each distributed partitioned table's inh flag to appropriate * we set each partitioned table's inh flag to appropriate
* value before and after dropping to the standart_planner. * value before and after dropping to the standart_planner.
*/ */
if (rangeTableEntry->rtekind == RTE_RELATION && if (rangeTableEntry->rtekind == RTE_RELATION &&

View File

@ -789,7 +789,8 @@ RouterModifyTaskForShardInterval(Query *originalQuery,
&relationShardList, &relationShardList,
&prunedShardIntervalListList, &prunedShardIntervalListList,
replacePrunedQueryWithDummy, replacePrunedQueryWithDummy,
&multiShardModifyQuery, NULL); &multiShardModifyQuery, NULL,
false);
Assert(!multiShardModifyQuery); Assert(!multiShardModifyQuery);

View File

@ -63,8 +63,7 @@ int LocalTableJoinPolicy = LOCAL_JOIN_POLICY_AUTO;
typedef struct RangeTableEntryDetails typedef struct RangeTableEntryDetails
{ {
RangeTblEntry *rangeTableEntry; RangeTblEntry *rangeTableEntry;
Index rteIndex; int rteIdentity;
List *restrictionList;
List *requiredAttributeNumbers; List *requiredAttributeNumbers;
bool hasConstantFilterOnUniqueColumn; bool hasConstantFilterOnUniqueColumn;
} RangeTableEntryDetails; } RangeTableEntryDetails;
@ -75,9 +74,8 @@ typedef struct ConversionCandidates
List *localTableList; /* local or citus local table */ List *localTableList; /* local or citus local table */
}ConversionCandidates; }ConversionCandidates;
static bool HasConstantFilterOnUniqueColumn(FromExpr *joinTree, static bool HasConstantFilterOnUniqueColumn(RangeTblEntry *rangeTableEntry,
RangeTblEntry *rangeTableEntry, Index RelationRestriction *relationRestriction);
rteIndex);
static List * RequiredAttrNumbersForRelation(RangeTblEntry *relationRte, static List * RequiredAttrNumbersForRelation(RangeTblEntry *relationRte,
PlannerRestrictionContext * PlannerRestrictionContext *
plannerRestrictionContext); plannerRestrictionContext);
@ -92,8 +90,8 @@ static RangeTableEntryDetails * GetNextRTEToConvertToSubquery(FromExpr *joinTree
conversionCandidates, conversionCandidates,
PlannerRestrictionContext * PlannerRestrictionContext *
plannerRestrictionContext); plannerRestrictionContext);
static void RemoveFromConversionCandidates(ConversionCandidates *conversionCandidates, Oid static void RemoveFromConversionCandidates(ConversionCandidates *conversionCandidates,
relationId); int rteIdentity);
static bool AllRangeTableEntriesHaveUniqueIndex(List *rangeTableEntryDetailsList); static bool AllRangeTableEntriesHaveUniqueIndex(List *rangeTableEntryDetailsList);
/* /*
@ -105,9 +103,8 @@ void
RecursivelyPlanLocalTableJoins(Query *query, RecursivelyPlanLocalTableJoins(Query *query,
RecursivePlanningContext *context, List *rangeTableList) RecursivePlanningContext *context, List *rangeTableList)
{ {
PlannerRestrictionContext *plannerRestrictionContext = PlannerRestrictionContext *plannerRestrictionContext =
context->plannerRestrictionContext; GetPlannerRestrictionContext(context);
Oid resultRelationId = InvalidOid; Oid resultRelationId = InvalidOid;
if (IsModifyCommand(query)) if (IsModifyCommand(query))
@ -131,15 +128,15 @@ RecursivelyPlanLocalTableJoins(Query *query,
} }
RangeTblEntry *rangeTableEntry = rangeTableEntryDetails->rangeTableEntry; RangeTblEntry *rangeTableEntry = rangeTableEntryDetails->rangeTableEntry;
Oid relId = rangeTableEntryDetails->rangeTableEntry->relid;
List *restrictionList = rangeTableEntryDetails->restrictionList;
List *requiredAttributeNumbers = rangeTableEntryDetails->requiredAttributeNumbers; List *requiredAttributeNumbers = rangeTableEntryDetails->requiredAttributeNumbers;
ReplaceRTERelationWithRteSubquery(rangeTableEntry, restrictionList, ReplaceRTERelationWithRteSubquery(rangeTableEntry,
requiredAttributeNumbers, context); requiredAttributeNumbers, context);
RemoveFromConversionCandidates(conversionCandidates, relId); int rteIdentity = rangeTableEntryDetails->rteIdentity;
RemoveFromConversionCandidates(conversionCandidates, rteIdentity);
} }
} }
/* /*
* GetNextRTEToConvertToSubquery returns the range table entry * GetNextRTEToConvertToSubquery returns the range table entry
* which should be converted to a subquery. It considers the local join policy * which should be converted to a subquery. It considers the local join policy
@ -211,20 +208,35 @@ AllRangeTableEntriesHaveUniqueIndex(List *rangeTableEntryDetailsList)
* the relevant list based on the relation id. * the relevant list based on the relation id.
*/ */
static void static void
RemoveFromConversionCandidates(ConversionCandidates *conversionCandidates, Oid relationId) RemoveFromConversionCandidates(ConversionCandidates *conversionCandidates, int
rteIdentity)
{ {
if (IsRelationLocalTableOrMatView(relationId)) RangeTableEntryDetails *rangeTableEntryDetails = NULL;
foreach_ptr(rangeTableEntryDetails, conversionCandidates->localTableList)
{
if (rangeTableEntryDetails->rteIdentity == rteIdentity)
{ {
conversionCandidates->localTableList = conversionCandidates->localTableList =
list_delete_first(conversionCandidates->localTableList); list_delete_ptr(conversionCandidates->localTableList,
rangeTableEntryDetails);
return;
} }
else }
foreach_ptr(rangeTableEntryDetails, conversionCandidates->distributedTableList)
{
if (rangeTableEntryDetails->rteIdentity == rteIdentity)
{ {
conversionCandidates->distributedTableList = conversionCandidates->distributedTableList =
list_delete_first(conversionCandidates->distributedTableList); list_delete_ptr(conversionCandidates->distributedTableList,
rangeTableEntryDetails);
return;
} }
} }
ereport(ERROR, (errmsg("invalid rte index is given :%d", rteIdentity)));
}
/* /*
* ShouldConvertLocalTableJoinsToSubqueries returns true if we should * ShouldConvertLocalTableJoinsToSubqueries returns true if we should
@ -263,32 +275,22 @@ ShouldConvertLocalTableJoinsToSubqueries(Query *query, List *rangeTableList,
* filter on a unique column. * filter on a unique column.
*/ */
static bool static bool
HasConstantFilterOnUniqueColumn(FromExpr *joinTree, RangeTblEntry *rangeTableEntry, Index HasConstantFilterOnUniqueColumn(RangeTblEntry *rangeTableEntry,
rteIndex) RelationRestriction *relationRestriction)
{ {
if (rangeTableEntry == NULL) if (rangeTableEntry == NULL)
{ {
return false; return false;
} }
List *baseRestrictionList = relationRestriction->relOptInfo->baserestrictinfo;
List *restrictClauseList = get_all_actual_clauses(baseRestrictionList);
List *rteEqualityQuals = List *rteEqualityQuals =
FetchEqualityAttrNumsForRTEFromQuals(joinTree->quals, rteIndex); FetchEqualityAttrNumsForRTE((Node *) restrictClauseList);
Node *join = NULL; List *uniqueIndexAttrNumbers = ExecuteFunctionOnEachTableIndex(rangeTableEntry->relid,
foreach_ptr(join, joinTree->fromlist)
{
if (IsA(join, JoinExpr))
{
JoinExpr *joinExpr = (JoinExpr *) join;
List *joinExprEqualityQuals =
FetchEqualityAttrNumsForRTEFromQuals(joinExpr->quals, rteIndex);
rteEqualityQuals = list_concat(rteEqualityQuals, joinExprEqualityQuals);
}
}
List *uniqueIndexes = ExecuteFunctionOnEachTableIndex(rangeTableEntry->relid,
GetAllUniqueIndexes); GetAllUniqueIndexes);
int columnNumber = 0; int columnNumber = 0;
foreach_int(columnNumber, uniqueIndexes) foreach_int(columnNumber, uniqueIndexAttrNumbers)
{ {
if (list_member_int(rteEqualityQuals, columnNumber)) if (list_member_int(rteEqualityQuals, columnNumber))
{ {
@ -327,38 +329,28 @@ GetAllUniqueIndexes(Form_pg_index indexForm, List **uniqueIndexes)
* WHERE clause as a filter (e.g., not a join clause). * WHERE clause as a filter (e.g., not a join clause).
*/ */
static List * static List *
RequiredAttrNumbersForRelation(RangeTblEntry *relationRte, RequiredAttrNumbersForRelation(RangeTblEntry *rangeTableEntry,
PlannerRestrictionContext *plannerRestrictionContext) PlannerRestrictionContext *plannerRestrictionContext)
{ {
int rteIdentity = GetRTEIdentity(relationRte); RelationRestriction *relationRestriction =
RelationRestrictionContext *relationRestrictionContext = RelationRestrictionForRelation(rangeTableEntry, plannerRestrictionContext);
plannerRestrictionContext->relationRestrictionContext;
Relids queryRteIdentities = bms_make_singleton(rteIdentity);
RelationRestrictionContext *filteredRelationRestrictionContext =
FilterRelationRestrictionContext(relationRestrictionContext, queryRteIdentities);
List *filteredRelationRestrictionList =
filteredRelationRestrictionContext->relationRestrictionList;
if (list_length(filteredRelationRestrictionList) != 1) if (relationRestriction == NULL)
{ {
return NIL; return NIL;
} }
RelationRestriction *relationRestriction =
(RelationRestriction *) linitial(filteredRelationRestrictionList);
PlannerInfo *plannerInfo = relationRestriction->plannerInfo; PlannerInfo *plannerInfo = relationRestriction->plannerInfo;
Query *queryToProcess = plannerInfo->parse; Query *queryToProcess = plannerInfo->parse;
int rteIndex = relationRestriction->index; int rteIndex = relationRestriction->index;
List *allVarsInQuery = pull_vars_of_level((Node *) queryToProcess, 0); List *allVarsInQuery = pull_vars_of_level((Node *) queryToProcess, 0);
ListCell *varCell = NULL;
List *requiredAttrNumbers = NIL; List *requiredAttrNumbers = NIL;
foreach(varCell, allVarsInQuery) Var *var = NULL;
foreach_ptr(var, allVarsInQuery)
{ {
Var *var = (Var *) lfirst(varCell);
if (var->varno == rteIndex) if (var->varno == rteIndex)
{ {
requiredAttrNumbers = list_append_unique_int(requiredAttrNumbers, requiredAttrNumbers = list_append_unique_int(requiredAttrNumbers,
@ -379,15 +371,12 @@ CreateConversionCandidates(FromExpr *joinTree,
PlannerRestrictionContext *plannerRestrictionContext, PlannerRestrictionContext *plannerRestrictionContext,
List *rangeTableList, Oid resultRelationId) List *rangeTableList, Oid resultRelationId)
{ {
ConversionCandidates *conversionCandidates = palloc0( ConversionCandidates *conversionCandidates =
sizeof(ConversionCandidates)); palloc0(sizeof(ConversionCandidates));
int rteIndex = 0;
RangeTblEntry *rangeTableEntry = NULL; RangeTblEntry *rangeTableEntry = NULL;
foreach_ptr(rangeTableEntry, rangeTableList) foreach_ptr(rangeTableEntry, rangeTableList)
{ {
rteIndex++;
/* we're only interested in tables */ /* we're only interested in tables */
if (!IsRecursivelyPlannableRelation(rangeTableEntry)) if (!IsRecursivelyPlannableRelation(rangeTableEntry))
{ {
@ -400,23 +389,27 @@ CreateConversionCandidates(FromExpr *joinTree,
continue; continue;
} }
RelationRestriction *relationRestriction =
RelationRestrictionForRelation(rangeTableEntry, plannerRestrictionContext);
if (relationRestriction == NULL)
{
continue;
}
int rteIdentity = GetRTEIdentity(rangeTableEntry);
RangeTableEntryDetails *rangeTableEntryDetails = RangeTableEntryDetails *rangeTableEntryDetails =
palloc0(sizeof(RangeTableEntryDetails)); palloc0(sizeof(RangeTableEntryDetails));
rangeTableEntryDetails->rangeTableEntry = rangeTableEntry;
rangeTableEntryDetails->rteIndex = rteIndex;
rangeTableEntryDetails->restrictionList = GetRestrictInfoListForRelation(
rangeTableEntry, plannerRestrictionContext);
rangeTableEntryDetails->requiredAttributeNumbers = RequiredAttrNumbersForRelation(
rangeTableEntry, plannerRestrictionContext);
rangeTableEntryDetails->hasConstantFilterOnUniqueColumn =
HasConstantFilterOnUniqueColumn(joinTree,
rangeTableEntry,
rteIndex);
bool referenceOrDistributedTable = IsCitusTableType(rangeTableEntry->relid, rangeTableEntryDetails->rangeTableEntry = rangeTableEntry;
REFERENCE_TABLE) || rangeTableEntryDetails->rteIdentity = rteIdentity;
IsCitusTableType(rangeTableEntry->relid, rangeTableEntryDetails->requiredAttributeNumbers =
DISTRIBUTED_TABLE); RequiredAttrNumbersForRelation(rangeTableEntry, plannerRestrictionContext);
rangeTableEntryDetails->hasConstantFilterOnUniqueColumn =
HasConstantFilterOnUniqueColumn(rangeTableEntry, relationRestriction);
bool referenceOrDistributedTable =
IsCitusTableType(rangeTableEntry->relid, REFERENCE_TABLE) ||
IsCitusTableType(rangeTableEntry->relid, DISTRIBUTED_TABLE);
if (referenceOrDistributedTable) if (referenceOrDistributedTable)
{ {
conversionCandidates->distributedTableList = conversionCandidates->distributedTableList =

View File

@ -340,7 +340,13 @@ IsCitusTableRTE(Node *node)
bool bool
IsDistributedOrReferenceTableRTE(Node *node) IsDistributedOrReferenceTableRTE(Node *node)
{ {
return IsDistributedTableRTE(node) || IsReferenceTableRTE(node); Oid relationId = NodeTryGetRteRelid(node);
if (!OidIsValid(relationId))
{
return false;
}
return IsCitusTableType(relationId, DISTRIBUTED_TABLE) ||
IsCitusTableType(relationId, REFERENCE_TABLE);
} }

View File

@ -234,9 +234,9 @@ static StringInfo ColumnTypeArrayString(List *targetEntryList);
static bool CoPlacedShardIntervals(ShardInterval *firstInterval, static bool CoPlacedShardIntervals(ShardInterval *firstInterval,
ShardInterval *secondInterval); ShardInterval *secondInterval);
static List * FetchEqualityAttrNumsForRTEOpExpr(OpExpr *opExpr, Index rteIndex); static List * FetchEqualityAttrNumsForRTEOpExpr(OpExpr *opExpr);
static List * FetchEqualityAttrNumsForRTEBoolExpr(BoolExpr *boolExpr, Index rteIndex); static List * FetchEqualityAttrNumsForRTEBoolExpr(BoolExpr *boolExpr);
static List * FetchEqualityAttrNumsForList(List *nodeList);
#if PG_VERSION_NUM >= PG_VERSION_13 #if PG_VERSION_NUM >= PG_VERSION_13
static List * GetColumnOriginalIndexes(Oid relationId); static List * GetColumnOriginalIndexes(Oid relationId);
#endif #endif
@ -271,6 +271,27 @@ CreatePhysicalDistributedPlan(MultiTreeRoot *multiTree,
} }
/*
* OnlyLocalTableJob true if the given task contains
* only postgres tables
*/
bool
OnlyLocalTableJob(Job *job)
{
if (job == NULL)
{
return false;
}
List *taskList = job->taskList;
if (list_length(taskList) != 1)
{
return false;
}
Task *singleTask = (Task *) linitial(taskList);
return singleTask->containsOnlyLocalTable;
}
/* /*
* BuildJobTree builds the physical job tree from the given logical plan tree. * BuildJobTree builds the physical job tree from the given logical plan tree.
* The function walks over the logical plan from the bottom up, finds boundaries * The function walks over the logical plan from the bottom up, finds boundaries
@ -2113,7 +2134,8 @@ BuildJobTreeTaskList(Job *jobTree, PlannerRestrictionContext *plannerRestriction
prunedRelationShardList, READ_TASK, prunedRelationShardList, READ_TASK,
false, false,
&deferredErrorMessage); &deferredErrorMessage);
if (deferredErrorMessage != NULL) { if (deferredErrorMessage != NULL)
{
RaiseDeferredErrorInternal(deferredErrorMessage, ERROR); RaiseDeferredErrorInternal(deferredErrorMessage, ERROR);
} }
} }
@ -2306,7 +2328,8 @@ QueryPushdownSqlTaskList(Query *query, uint64 jobId,
taskType, taskType,
modifyRequiresCoordinatorEvaluation, modifyRequiresCoordinatorEvaluation,
planningError); planningError);
if (*planningError != NULL) { if (*planningError != NULL)
{
return NIL; return NIL;
} }
subqueryTask->jobId = jobId; subqueryTask->jobId = jobId;
@ -3615,26 +3638,58 @@ NodeIsRangeTblRefReferenceTable(Node *node, List *rangeTableList)
/* /*
* FetchEqualityAttrNumsForRTEFromQuals fetches the attribute numbers from quals * FetchEqualityAttrNumsForRTE fetches the attribute numbers from quals
* which: * which:
* - has equality operator * - has equality operator
* - belongs to rangeTableEntry with rteIndex * - belongs to rangeTableEntry with rteIndex
*/ */
List * List *
FetchEqualityAttrNumsForRTEFromQuals(Node *quals, Index rteIndex) FetchEqualityAttrNumsForRTE(Node *node)
{ {
if (quals == NULL) if (node == NULL)
{ {
return NIL; return NIL;
} }
if (IsA(node, List))
if (IsA(quals, OpExpr))
{ {
return FetchEqualityAttrNumsForRTEOpExpr((OpExpr *) quals, rteIndex); return FetchEqualityAttrNumsForList((List *) node);
} }
else if (IsA(quals, BoolExpr)) else if (IsA(node, OpExpr))
{ {
return FetchEqualityAttrNumsForRTEBoolExpr((BoolExpr *) quals, rteIndex); return FetchEqualityAttrNumsForRTEOpExpr((OpExpr *) node);
}
else if (IsA(node, BoolExpr))
{
return FetchEqualityAttrNumsForRTEBoolExpr((BoolExpr *) node);
}
return NIL;
}
/*
* FetchEqualityAttrNumsForList fetches the constant equality numbers
* from the given node list.
*/
static List *FetchEqualityAttrNumsForList(List *nodeList)
{
List *attributeNums = NIL;
Node *node = NULL;
bool hasAtLeastOneEquality = false;
foreach_ptr(node, nodeList)
{
List *fetchedEqualityAttrNums =
FetchEqualityAttrNumsForRTE(node);
hasAtLeastOneEquality |= list_length(fetchedEqualityAttrNums) > 0;
attributeNums = list_concat(attributeNums, fetchedEqualityAttrNums);
}
/*
* the given list is in the form of AND'ed expressions
* hence if we have one equality then it is enough.
* E.g: dist.a = 5 AND dist.a > 10
*/
if (hasAtLeastOneEquality)
{
return attributeNums;
} }
return NIL; return NIL;
} }
@ -3647,7 +3702,7 @@ FetchEqualityAttrNumsForRTEFromQuals(Node *quals, Index rteIndex)
* - belongs to rangeTableEntry with rteIndex * - belongs to rangeTableEntry with rteIndex
*/ */
static List * static List *
FetchEqualityAttrNumsForRTEOpExpr(OpExpr *opExpr, Index rteIndex) FetchEqualityAttrNumsForRTEOpExpr(OpExpr *opExpr)
{ {
if (!OperatorImplementsEquality(opExpr->opno)) if (!OperatorImplementsEquality(opExpr->opno))
{ {
@ -3656,7 +3711,7 @@ FetchEqualityAttrNumsForRTEOpExpr(OpExpr *opExpr, Index rteIndex)
List *attributeNums = NIL; List *attributeNums = NIL;
Var *var = NULL; Var *var = NULL;
if (VarConstOpExprClause(opExpr, &var, NULL) && var->varno == rteIndex) if (VarConstOpExprClause(opExpr, &var, NULL))
{ {
attributeNums = lappend_int(attributeNums, var->varattno); attributeNums = lappend_int(attributeNums, var->varattno);
} }
@ -3671,7 +3726,7 @@ FetchEqualityAttrNumsForRTEOpExpr(OpExpr *opExpr, Index rteIndex)
* - belongs to rangeTableEntry with rteIndex * - belongs to rangeTableEntry with rteIndex
*/ */
static List * static List *
FetchEqualityAttrNumsForRTEBoolExpr(BoolExpr *boolExpr, Index rteIndex) FetchEqualityAttrNumsForRTEBoolExpr(BoolExpr *boolExpr)
{ {
if (boolExpr->boolop != AND_EXPR && boolExpr->boolop != OR_EXPR) if (boolExpr->boolop != AND_EXPR && boolExpr->boolop != OR_EXPR)
{ {
@ -3683,8 +3738,7 @@ FetchEqualityAttrNumsForRTEBoolExpr(BoolExpr *boolExpr, Index rteIndex)
Node *arg = NULL; Node *arg = NULL;
foreach_ptr(arg, boolExpr->args) foreach_ptr(arg, boolExpr->args)
{ {
List *attributeNumsInSubExpression = FetchEqualityAttrNumsForRTEFromQuals(arg, List *attributeNumsInSubExpression = FetchEqualityAttrNumsForRTE(arg);
rteIndex);
if (boolExpr->boolop == AND_EXPR) if (boolExpr->boolop == AND_EXPR)
{ {
hasEquality |= list_length(attributeNumsInSubExpression) > 0; hasEquality |= list_length(attributeNumsInSubExpression) > 0;
@ -5466,7 +5520,6 @@ ActiveShardPlacementLists(List *taskList)
{ {
Task *task = (Task *) lfirst(taskCell); Task *task = (Task *) lfirst(taskCell);
uint64 anchorShardId = task->anchorShardId; uint64 anchorShardId = task->anchorShardId;
List *shardPlacementList = ActiveShardPlacementList(anchorShardId); List *shardPlacementList = ActiveShardPlacementList(anchorShardId);
/* filter out shard placements that reside in inactive nodes */ /* filter out shard placements that reside in inactive nodes */

View File

@ -170,7 +170,8 @@ static int CompareInsertValuesByShardId(const void *leftElement,
const void *rightElement); const void *rightElement);
static List * SingleShardTaskList(Query *query, uint64 jobId, static List * SingleShardTaskList(Query *query, uint64 jobId,
List *relationShardList, List *placementList, List *relationShardList, List *placementList,
uint64 shardId, bool parametersInQueryResolved); uint64 shardId, bool parametersInQueryResolved,
bool containsOnlyLocalTable);
static bool RowLocksOnRelations(Node *node, List **rtiLockList); static bool RowLocksOnRelations(Node *node, List **rtiLockList);
static void ReorderTaskPlacementsByTaskAssignmentPolicy(Job *job, static void ReorderTaskPlacementsByTaskAssignmentPolicy(Job *job,
TaskAssignmentPolicyType TaskAssignmentPolicyType
@ -1717,6 +1718,8 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
/* router planner should create task even if it doesn't hit a shard at all */ /* router planner should create task even if it doesn't hit a shard at all */
bool replacePrunedQueryWithDummy = true; bool replacePrunedQueryWithDummy = true;
bool containsOnlyLocalTable = false;
/* check if this query requires coordinator evaluation */ /* check if this query requires coordinator evaluation */
bool requiresCoordinatorEvaluation = RequiresCoordinatorEvaluation(originalQuery); bool requiresCoordinatorEvaluation = RequiresCoordinatorEvaluation(originalQuery);
FastPathRestrictionContext *fastPathRestrictionContext = FastPathRestrictionContext *fastPathRestrictionContext =
@ -1744,7 +1747,8 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
&prunedShardIntervalListList, &prunedShardIntervalListList,
replacePrunedQueryWithDummy, replacePrunedQueryWithDummy,
&isMultiShardModifyQuery, &isMultiShardModifyQuery,
&partitionKeyValue); &partitionKeyValue,
&containsOnlyLocalTable);
} }
if (*planningError) if (*planningError)
@ -1754,7 +1758,6 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
Job *job = CreateJob(originalQuery); Job *job = CreateJob(originalQuery);
job->partitionKeyValue = partitionKeyValue; job->partitionKeyValue = partitionKeyValue;
job->onDummyPlacement = replacePrunedQueryWithDummy;
if (originalQuery->resultRelation > 0) if (originalQuery->resultRelation > 0)
{ {
@ -1783,14 +1786,15 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
MODIFY_TASK, MODIFY_TASK,
requiresCoordinatorEvaluation, requiresCoordinatorEvaluation,
planningError); planningError);
if (*planningError) { if (*planningError)
{
return NULL; return NULL;
} }
} }
else else
{ {
GenerateSingleShardRouterTaskList(job, relationShardList, GenerateSingleShardRouterTaskList(job, relationShardList,
placementList, shardId); placementList, shardId, containsOnlyLocalTable);
} }
job->requiresCoordinatorEvaluation = requiresCoordinatorEvaluation; job->requiresCoordinatorEvaluation = requiresCoordinatorEvaluation;
@ -1806,7 +1810,8 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
*/ */
void void
GenerateSingleShardRouterTaskList(Job *job, List *relationShardList, GenerateSingleShardRouterTaskList(Job *job, List *relationShardList,
List *placementList, uint64 shardId) List *placementList, uint64 shardId, bool
containsOnlyLocalTable)
{ {
Query *originalQuery = job->jobQuery; Query *originalQuery = job->jobQuery;
@ -1815,7 +1820,9 @@ GenerateSingleShardRouterTaskList(Job *job, List *relationShardList,
job->taskList = SingleShardTaskList(originalQuery, job->jobId, job->taskList = SingleShardTaskList(originalQuery, job->jobId,
relationShardList, placementList, relationShardList, placementList,
shardId, shardId,
job->parametersInJobQueryResolved); job->parametersInJobQueryResolved,
containsOnlyLocalTable);
/* /*
* Queries to reference tables, or distributed tables with multiple replica's have * Queries to reference tables, or distributed tables with multiple replica's have
* their task placements reordered according to the configured * their task placements reordered according to the configured
@ -1831,7 +1838,7 @@ GenerateSingleShardRouterTaskList(Job *job, List *relationShardList,
placementList); placementList);
} }
} }
else if (shardId == INVALID_SHARD_ID && !job->onDummyPlacement) else if (shardId == INVALID_SHARD_ID && !containsOnlyLocalTable)
{ {
/* modification that prunes to 0 shards */ /* modification that prunes to 0 shards */
job->taskList = NIL; job->taskList = NIL;
@ -1841,7 +1848,8 @@ GenerateSingleShardRouterTaskList(Job *job, List *relationShardList,
job->taskList = SingleShardTaskList(originalQuery, job->jobId, job->taskList = SingleShardTaskList(originalQuery, job->jobId,
relationShardList, placementList, relationShardList, placementList,
shardId, shardId,
job->parametersInJobQueryResolved); job->parametersInJobQueryResolved,
containsOnlyLocalTable);
} }
} }
@ -1934,7 +1942,8 @@ RemoveCoordinatorPlacementIfNotSingleNode(List *placementList)
static List * static List *
SingleShardTaskList(Query *query, uint64 jobId, List *relationShardList, SingleShardTaskList(Query *query, uint64 jobId, List *relationShardList,
List *placementList, uint64 shardId, List *placementList, uint64 shardId,
bool parametersInQueryResolved) bool parametersInQueryResolved,
bool containsOnlyLocalTable)
{ {
TaskType taskType = READ_TASK; TaskType taskType = READ_TASK;
char replicationModel = 0; char replicationModel = 0;
@ -1993,6 +2002,7 @@ SingleShardTaskList(Query *query, uint64 jobId, List *relationShardList,
} }
Task *task = CreateTask(taskType); Task *task = CreateTask(taskType);
task->containsOnlyLocalTable = containsOnlyLocalTable;
List *relationRowLockList = NIL; List *relationRowLockList = NIL;
RowLocksOnRelations((Node *) query, &relationRowLockList); RowLocksOnRelations((Node *) query, &relationRowLockList);
@ -2104,6 +2114,8 @@ SelectsFromDistributedTable(List *rangeTableList, Query *query)
} }
static bool ContainsOnlyLocalTables(RTEListProperties *rteProperties);
/* /*
* RouterQuery runs router pruning logic for SELECT, UPDATE and DELETE queries. * RouterQuery runs router pruning logic for SELECT, UPDATE and DELETE queries.
* If there are shards present and query is routable, all RTEs have been updated * If there are shards present and query is routable, all RTEs have been updated
@ -2131,7 +2143,8 @@ PlanRouterQuery(Query *originalQuery,
List **placementList, uint64 *anchorShardId, List **relationShardList, List **placementList, uint64 *anchorShardId, List **relationShardList,
List **prunedShardIntervalListList, List **prunedShardIntervalListList,
bool replacePrunedQueryWithDummy, bool *multiShardModifyQuery, bool replacePrunedQueryWithDummy, bool *multiShardModifyQuery,
Const **partitionValueConst) Const **partitionValueConst,
bool *containsOnlyLocalTable)
{ {
bool isMultiShardQuery = false; bool isMultiShardQuery = false;
DeferredErrorMessage *planningError = NULL; DeferredErrorMessage *planningError = NULL;
@ -2247,6 +2260,10 @@ PlanRouterQuery(Query *originalQuery,
/* both Postgres tables and materialized tables are locally avaliable */ /* both Postgres tables and materialized tables are locally avaliable */
RTEListProperties *rteProperties = GetRTEListPropertiesForQuery(originalQuery); RTEListProperties *rteProperties = GetRTEListPropertiesForQuery(originalQuery);
if (shardId == INVALID_SHARD_ID && ContainsOnlyLocalTables(rteProperties))
{
*containsOnlyLocalTable = true;
}
bool hasPostgresLocalRelation = bool hasPostgresLocalRelation =
rteProperties->hasPostgresLocalTable || rteProperties->hasMaterializedView; rteProperties->hasPostgresLocalTable || rteProperties->hasMaterializedView;
List *taskPlacementList = List *taskPlacementList =
@ -2280,6 +2297,13 @@ PlanRouterQuery(Query *originalQuery,
} }
static bool
ContainsOnlyLocalTables(RTEListProperties *rteProperties)
{
return !rteProperties->hasDistributedTable && !rteProperties->hasReferenceTable;
}
/* /*
* CreateTaskPlacementListForShardIntervals returns a list of shard placements * CreateTaskPlacementListForShardIntervals returns a list of shard placements
* on which it can access all shards in shardIntervalListList, which contains * on which it can access all shards in shardIntervalListList, which contains

View File

@ -100,6 +100,20 @@
#include "utils/guc.h" #include "utils/guc.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
/*
* RecursivePlanningContext is used to recursively plan subqueries
* and CTEs, pull results to the coordinator, and push it back into
* the workers.
*/
struct RecursivePlanningContextInternal
{
int level;
uint64 planId;
bool allDistributionKeysInQueryAreEqual; /* used for some optimizations */
List *subPlanList;
PlannerRestrictionContext *plannerRestrictionContext;
};
/* track depth of current recursive planner query */ /* track depth of current recursive planner query */
static int recursivePlanningDepth = 0; static int recursivePlanningDepth = 0;
@ -159,7 +173,7 @@ static bool AllDistributionKeysInSubqueryAreEqual(Query *subquery,
static bool IsTableLocallyAccessible(Oid relationId); static bool IsTableLocallyAccessible(Oid relationId);
static bool ShouldRecursivelyPlanSetOperation(Query *query, static bool ShouldRecursivelyPlanSetOperation(Query *query,
RecursivePlanningContext *context); RecursivePlanningContext *context);
static void RecursivelyPlanSubquery(Query *subquery, static bool RecursivelyPlanSubquery(Query *subquery,
RecursivePlanningContext *planningContext); RecursivePlanningContext *planningContext);
static void RecursivelyPlanSetOperations(Query *query, Node *node, static void RecursivelyPlanSetOperations(Query *query, Node *node,
RecursivePlanningContext *context); RecursivePlanningContext *context);
@ -178,8 +192,7 @@ static Query * BuildReadIntermediateResultsQuery(List *targetEntryList,
List *columnAliasList, List *columnAliasList,
Const *resultIdConst, Oid functionOid, Const *resultIdConst, Oid functionOid,
bool useBinaryCopyFormat); bool useBinaryCopyFormat);
static void static void UpdateVarNosInNode(Query *query, Index newVarNo);
UpdateVarNosInNode(Query *query, Index newVarNo);
static bool ModifiesLocalTableWithRemoteCitusLocalTable(List *rangeTableList); static bool ModifiesLocalTableWithRemoteCitusLocalTable(List *rangeTableList);
static void GetRangeTableEntriesFromJoinTree(Node *joinNode, List *rangeTableList, static void GetRangeTableEntriesFromJoinTree(Node *joinNode, List *rangeTableList,
List **joinRangeTableEntries); List **joinRangeTableEntries);
@ -347,7 +360,8 @@ RecursivelyPlanSubqueriesAndCTEs(Query *query, RecursivePlanningContext *context
&rangeTableList); &rangeTableList);
if (ShouldConvertLocalTableJoinsToSubqueries(query, rangeTableList, if (ShouldConvertLocalTableJoinsToSubqueries(query, rangeTableList,
plannerRestrictionContext)) { plannerRestrictionContext))
{
/* /*
* Logical planner cannot handle "local_table" [OUTER] JOIN "dist_table", or * Logical planner cannot handle "local_table" [OUTER] JOIN "dist_table", or
* a query with local table/citus local table and subquery. We convert local/citus local * a query with local table/citus local table and subquery. We convert local/citus local
@ -356,13 +370,24 @@ RecursivelyPlanSubqueriesAndCTEs(Query *query, RecursivePlanningContext *context
* so that we can check if the current plan is router plannable at any step within this function. * so that we can check if the current plan is router plannable at any step within this function.
*/ */
RecursivelyPlanLocalTableJoins(query, context, rangeTableList); RecursivelyPlanLocalTableJoins(query, context, rangeTableList);
} }
return NULL; return NULL;
} }
/*
* GetPlannerRestrictionContext returns the planner restriction context
* from the given context.
*/
PlannerRestrictionContext *
GetPlannerRestrictionContext(RecursivePlanningContext *recursivePlanningContext)
{
return recursivePlanningContext->plannerRestrictionContext;
}
/* /*
* GetRangeTableEntriesFromJoinTree gets the range table entries that are * GetRangeTableEntriesFromJoinTree gets the range table entries that are
* on the given join tree. * on the given join tree.
@ -407,7 +432,6 @@ GetRangeTableEntriesFromJoinTree(Node *joinNode, List *rangeTableList,
} }
/* /*
* ShouldRecursivelyPlanNonColocatedSubqueries returns true if the input query contains joins * ShouldRecursivelyPlanNonColocatedSubqueries returns true if the input query contains joins
* that are not on the distribution key. * that are not on the distribution key.
@ -1180,7 +1204,7 @@ IsRelationLocalTableOrMatView(Oid relationId)
* and immediately returns. Later, the planner decides on what to do * and immediately returns. Later, the planner decides on what to do
* with the query. * with the query.
*/ */
static void static bool
RecursivelyPlanSubquery(Query *subquery, RecursivePlanningContext *planningContext) RecursivelyPlanSubquery(Query *subquery, RecursivePlanningContext *planningContext)
{ {
uint64 planId = planningContext->planId; uint64 planId = planningContext->planId;
@ -1191,7 +1215,7 @@ RecursivelyPlanSubquery(Query *subquery, RecursivePlanningContext *planningConte
elog(DEBUG2, "skipping recursive planning for the subquery since it " elog(DEBUG2, "skipping recursive planning for the subquery since it "
"contains references to outer queries"); "contains references to outer queries");
return; return false;
} }
/* /*
@ -1234,6 +1258,7 @@ RecursivelyPlanSubquery(Query *subquery, RecursivePlanningContext *planningConte
/* finally update the input subquery to point the result query */ /* finally update the input subquery to point the result query */
*subquery = *resultQuery; *subquery = *resultQuery;
return true;
} }
@ -1423,12 +1448,16 @@ NodeContainsSubqueryReferencingOuterQuery(Node *node)
* It then recursively plans the subquery. * It then recursively plans the subquery.
*/ */
void void
ReplaceRTERelationWithRteSubquery(RangeTblEntry *rangeTableEntry, List *restrictionList, ReplaceRTERelationWithRteSubquery(RangeTblEntry *rangeTableEntry,
List *requiredAttrNumbers, List *requiredAttrNumbers,
RecursivePlanningContext *context) RecursivePlanningContext *context)
{ {
Query *subquery = WrapRteRelationIntoSubquery(rangeTableEntry, requiredAttrNumbers); Query *subquery = WrapRteRelationIntoSubquery(rangeTableEntry, requiredAttrNumbers);
Expr *andedBoundExpressions = make_ands_explicit(restrictionList); List *restrictionList =
GetRestrictInfoListForRelation(rangeTableEntry,
context->plannerRestrictionContext);
List *copyRestrictionList = copyObject(restrictionList);
Expr *andedBoundExpressions = make_ands_explicit(copyRestrictionList);
subquery->jointree->quals = (Node *) andedBoundExpressions; subquery->jointree->quals = (Node *) andedBoundExpressions;
UpdateVarNosInNode(subquery, SINGLE_RTE_INDEX); UpdateVarNosInNode(subquery, SINGLE_RTE_INDEX);
@ -1456,7 +1485,13 @@ ReplaceRTERelationWithRteSubquery(RangeTblEntry *rangeTableEntry, List *restrict
} }
/* as we created the subquery, now forcefully recursively plan it */ /* as we created the subquery, now forcefully recursively plan it */
RecursivelyPlanSubquery(rangeTableEntry->subquery, context); bool recursivellyPlanned = RecursivelyPlanSubquery(rangeTableEntry->subquery,
context);
if (!recursivellyPlanned)
{
ereport(ERROR, (errmsg(
"unexpected state: query should have been recursively planned")));
}
} }
@ -1610,6 +1645,7 @@ ContainsLocalTableDistributedTableJoin(List *rangeTableList)
return containsLocalTable && containsDistributedTable; return containsLocalTable && containsDistributedTable;
} }
/* /*
* WrapFunctionsInSubqueries iterates over all the immediate Range Table Entries * WrapFunctionsInSubqueries iterates over all the immediate Range Table Entries
* of a query and wraps the functions inside (SELECT * FROM fnc() f) * of a query and wraps the functions inside (SELECT * FROM fnc() f)
@ -1733,7 +1769,6 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
subquery->targetList = lappend(subquery->targetList, targetEntry); subquery->targetList = lappend(subquery->targetList, targetEntry);
} }
} }
/* /*
* If tupleDesc is NULL we have 2 different cases: * If tupleDesc is NULL we have 2 different cases:
* *
@ -1783,7 +1818,6 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry)
columnType = list_nth_oid(rangeTblFunction->funccoltypes, columnType = list_nth_oid(rangeTblFunction->funccoltypes,
targetColumnIndex); targetColumnIndex);
} }
/* use the types in the function definition otherwise */ /* use the types in the function definition otherwise */
else else
{ {

View File

@ -1848,34 +1848,25 @@ List *
GetRestrictInfoListForRelation(RangeTblEntry *rangeTblEntry, GetRestrictInfoListForRelation(RangeTblEntry *rangeTblEntry,
PlannerRestrictionContext *plannerRestrictionContext) PlannerRestrictionContext *plannerRestrictionContext)
{ {
int rteIdentity = GetRTEIdentity(rangeTblEntry); RelationRestriction *relationRestriction =
RelationRestrictionContext *relationRestrictionContext = RelationRestrictionForRelation(rangeTblEntry, plannerRestrictionContext);
plannerRestrictionContext->relationRestrictionContext; if (relationRestriction == NULL)
Relids queryRteIdentities = bms_make_singleton(rteIdentity);
RelationRestrictionContext *filteredRelationRestrictionContext =
FilterRelationRestrictionContext(relationRestrictionContext, queryRteIdentities);
List *filteredRelationRestrictionList =
filteredRelationRestrictionContext->relationRestrictionList;
if (list_length(filteredRelationRestrictionList) != 1)
{ {
return NIL; return NIL;
} }
RelationRestriction *relationRestriction =
(RelationRestriction *) linitial(filteredRelationRestrictionList);
RelOptInfo *relOptInfo = relationRestriction->relOptInfo; RelOptInfo *relOptInfo = relationRestriction->relOptInfo;
List *baseRestrictInfo = relOptInfo->baserestrictinfo;
List *joinRestrictInfo = relOptInfo->joininfo; List *joinRestrictInfo = relOptInfo->joininfo;
List *baseRestrictInfo = relOptInfo->baserestrictinfo;
List *joinRrestrictClauseList = get_all_actual_clauses(joinRestrictInfo); List *joinRestrictClauseList = get_all_actual_clauses(joinRestrictInfo);
if (ContainsFalseClause(joinRrestrictClauseList)) if (ContainsFalseClause(joinRestrictClauseList))
{ {
/* found WHERE false, no need to continue */ /* found WHERE false, no need to continue */
return copyObject((List *) joinRrestrictClauseList); return copyObject((List *) joinRestrictClauseList);
} }
List *restrictExprList = NIL; List *restrictExprList = NIL;
RestrictInfo *restrictInfo = NULL; RestrictInfo *restrictInfo = NULL;
foreach_ptr(restrictInfo, baseRestrictInfo) foreach_ptr(restrictInfo, baseRestrictInfo)
@ -1919,6 +1910,34 @@ GetRestrictInfoListForRelation(RangeTblEntry *rangeTblEntry,
} }
/*
* RelationRestrictionForRelation gets the relation restriction for the given
* range table entry.
*/
RelationRestriction *
RelationRestrictionForRelation(RangeTblEntry *rangeTableEntry,
PlannerRestrictionContext *plannerRestrictionContext)
{
int rteIdentity = GetRTEIdentity(rangeTableEntry);
RelationRestrictionContext *relationRestrictionContext =
plannerRestrictionContext->relationRestrictionContext;
Relids queryRteIdentities = bms_make_singleton(rteIdentity);
RelationRestrictionContext *filteredRelationRestrictionContext =
FilterRelationRestrictionContext(relationRestrictionContext, queryRteIdentities);
List *filteredRelationRestrictionList =
filteredRelationRestrictionContext->relationRestrictionList;
if (list_length(filteredRelationRestrictionList) != 1)
{
return NULL;
}
RelationRestriction *relationRestriction =
(RelationRestriction *) linitial(filteredRelationRestrictionList);
return relationRestriction;
}
/* /*
* IsParam determines whether the given node is a param. * IsParam determines whether the given node is a param.
*/ */

View File

@ -101,7 +101,6 @@ copyJobInfo(Job *newnode, Job *from)
COPY_NODE_FIELD(partitionKeyValue); COPY_NODE_FIELD(partitionKeyValue);
COPY_NODE_FIELD(localPlannedStatements); COPY_NODE_FIELD(localPlannedStatements);
COPY_SCALAR_FIELD(parametersInJobQueryResolved); COPY_SCALAR_FIELD(parametersInJobQueryResolved);
COPY_SCALAR_FIELD(onDummyPlacement);
} }
@ -328,6 +327,7 @@ CopyNodeTask(COPYFUNC_ARGS)
COPY_SCALAR_FIELD(fetchedExplainAnalyzePlacementIndex); COPY_SCALAR_FIELD(fetchedExplainAnalyzePlacementIndex);
COPY_STRING_FIELD(fetchedExplainAnalyzePlan); COPY_STRING_FIELD(fetchedExplainAnalyzePlan);
COPY_SCALAR_FIELD(fetchedExplainAnalyzeExecutionDuration); COPY_SCALAR_FIELD(fetchedExplainAnalyzeExecutionDuration);
COPY_SCALAR_FIELD(containsOnlyLocalTable);
} }

View File

@ -540,6 +540,7 @@ OutTask(OUTFUNC_ARGS)
WRITE_INT_FIELD(fetchedExplainAnalyzePlacementIndex); WRITE_INT_FIELD(fetchedExplainAnalyzePlacementIndex);
WRITE_STRING_FIELD(fetchedExplainAnalyzePlan); WRITE_STRING_FIELD(fetchedExplainAnalyzePlan);
WRITE_FLOAT_FIELD(fetchedExplainAnalyzeExecutionDuration, "%.2f"); WRITE_FLOAT_FIELD(fetchedExplainAnalyzeExecutionDuration, "%.2f");
WRITE_BOOL_FIELD(containsOnlyLocalTable);
} }

View File

@ -27,11 +27,12 @@ typedef enum
extern int LocalTableJoinPolicy; extern int LocalTableJoinPolicy;
extern bool extern bool ShouldConvertLocalTableJoinsToSubqueries(Query *query,
ShouldConvertLocalTableJoinsToSubqueries(Query *query,
List *rangeTableList, List *rangeTableList,
PlannerRestrictionContext *plannerRestrictionContext); PlannerRestrictionContext *
plannerRestrictionContext);
extern void RecursivelyPlanLocalTableJoins(Query *query, extern void RecursivelyPlanLocalTableJoins(Query *query,
RecursivePlanningContext *context, List *rangeTableList); RecursivePlanningContext *context,
List *rangeTableList);
#endif /* LOCAL_DISTRIBUTED_JOIN_PLANNER_H */ #endif /* LOCAL_DISTRIBUTED_JOIN_PLANNER_H */

View File

@ -163,7 +163,6 @@ typedef struct Job
* query. * query.
*/ */
bool parametersInJobQueryResolved; bool parametersInJobQueryResolved;
bool onDummyPlacement;
} Job; } Job;
@ -329,6 +328,11 @@ typedef struct Task
* ExplainTaskList(). * ExplainTaskList().
*/ */
double fetchedExplainAnalyzeExecutionDuration; double fetchedExplainAnalyzeExecutionDuration;
/*
* containsOnlyLocalTable is true if the task contains only postgres table/MV.
*/
bool containsOnlyLocalTable;
} Task; } Task;
@ -579,6 +583,8 @@ extern List * QueryPushdownSqlTaskList(Query *query, uint64 jobId,
bool modifyRequiresCoordinatorEvaluation, bool modifyRequiresCoordinatorEvaluation,
DeferredErrorMessage **planningError); DeferredErrorMessage **planningError);
extern bool OnlyLocalTableJob(Job *job);
/* function declarations for managing jobs */ /* function declarations for managing jobs */
extern uint64 UniqueJobId(void); extern uint64 UniqueJobId(void);
@ -591,6 +597,6 @@ extern RangeTblEntry * DerivedRangeTableEntry(MultiNode *multiNode, List *column
List *funcColumnTypeMods, List *funcColumnTypeMods,
List *funcCollations); List *funcCollations);
extern List * FetchEqualityAttrNumsForRTEFromQuals(Node *quals, Index rteIndex); extern List * FetchEqualityAttrNumsForRTE(Node *quals);
#endif /* MULTI_PHYSICAL_PLANNER_H */ #endif /* MULTI_PHYSICAL_PLANNER_H */

View File

@ -42,7 +42,8 @@ extern DeferredErrorMessage * PlanRouterQuery(Query *originalQuery,
List **prunedShardIntervalListList, List **prunedShardIntervalListList,
bool replacePrunedQueryWithDummy, bool replacePrunedQueryWithDummy,
bool *multiShardModifyQuery, bool *multiShardModifyQuery,
Const **partitionValueConst); Const **partitionValueConst,
bool *containOnlyLocalTable);
extern List * RelationShardListForShardIntervalList(List *shardIntervalList, extern List * RelationShardListForShardIntervalList(List *shardIntervalList,
bool *shardsPresent); bool *shardsPresent);
extern List * CreateTaskPlacementListForShardIntervals(List *shardIntervalList, extern List * CreateTaskPlacementListForShardIntervals(List *shardIntervalList,
@ -85,7 +86,8 @@ extern List * TargetShardIntervalForFastPathQuery(Query *query,
extern void GenerateSingleShardRouterTaskList(Job *job, extern void GenerateSingleShardRouterTaskList(Job *job,
List *relationShardList, List *relationShardList,
List *placementList, List *placementList,
uint64 shardId); uint64 shardId,
bool containsOnlyLocalTable);
extern bool IsRouterPlannable(Query *query, extern bool IsRouterPlannable(Query *query,
PlannerRestrictionContext *plannerRestrictionContext); PlannerRestrictionContext *plannerRestrictionContext);

View File

@ -22,21 +22,16 @@
#include "nodes/relation.h" #include "nodes/relation.h"
#endif #endif
/* typedef struct RecursivePlanningContextInternal RecursivePlanningContext;
* RecursivePlanningContext is used to recursively plan subqueries
* and CTEs, pull results to the coordinator, and push it back into typedef struct RangeTblEntryIndex
* the workers.
*/
typedef struct RecursivePlanningContext
{ {
int level; RangeTblEntry *rangeTableEntry;
uint64 planId; Index rteIndex;
bool allDistributionKeysInQueryAreEqual; /* used for some optimizations */ }RangeTblEntryIndex;
List *subPlanList;
PlannerRestrictionContext *plannerRestrictionContext;
} RecursivePlanningContext;
extern PlannerRestrictionContext * GetPlannerRestrictionContext(
RecursivePlanningContext *recursivePlanningContext);
extern List * GenerateSubplansForSubqueriesAndCTEs(uint64 planId, Query *originalQuery, extern List * GenerateSubplansForSubqueriesAndCTEs(uint64 planId, Query *originalQuery,
PlannerRestrictionContext * PlannerRestrictionContext *
plannerRestrictionContext); plannerRestrictionContext);
@ -50,7 +45,6 @@ extern Query * BuildReadIntermediateResultsArrayQuery(List *targetEntryList,
extern bool GeneratingSubplans(void); extern bool GeneratingSubplans(void);
extern bool ContainsLocalTableDistributedTableJoin(List *rangeTableList); extern bool ContainsLocalTableDistributedTableJoin(List *rangeTableList);
extern void ReplaceRTERelationWithRteSubquery(RangeTblEntry *rangeTableEntry, extern void ReplaceRTERelationWithRteSubquery(RangeTblEntry *rangeTableEntry,
List *restrictionList,
List *requiredAttrNumbers, List *requiredAttrNumbers,
RecursivePlanningContext *context); RecursivePlanningContext *context);
extern bool ContainsTableToBeConvertedToSubquery(List *rangeTableList); extern bool ContainsTableToBeConvertedToSubquery(List *rangeTableList);

View File

@ -41,13 +41,18 @@ extern PlannerRestrictionContext * FilterPlannerRestrictionForQuery(
extern List * GetRestrictInfoListForRelation(RangeTblEntry *rangeTblEntry, extern List * GetRestrictInfoListForRelation(RangeTblEntry *rangeTblEntry,
PlannerRestrictionContext * PlannerRestrictionContext *
plannerRestrictionContext); plannerRestrictionContext);
extern RelationRestriction * RelationRestrictionForRelation(
RangeTblEntry *rangeTableEntry,
PlannerRestrictionContext *
plannerRestrictionContext);
extern JoinRestrictionContext * RemoveDuplicateJoinRestrictions(JoinRestrictionContext * extern JoinRestrictionContext * RemoveDuplicateJoinRestrictions(JoinRestrictionContext *
joinRestrictionContext); joinRestrictionContext);
extern bool EquivalenceListContainsRelationsEquality(List *attributeEquivalenceList, extern bool EquivalenceListContainsRelationsEquality(List *attributeEquivalenceList,
RelationRestrictionContext * RelationRestrictionContext *
restrictionContext); restrictionContext);
extern RelationRestrictionContext * extern RelationRestrictionContext * FilterRelationRestrictionContext(
FilterRelationRestrictionContext(RelationRestrictionContext *relationRestrictionContext, RelationRestrictionContext *relationRestrictionContext,
Relids queryRteIdentities); Relids
queryRteIdentities);
#endif /* RELATION_RESTRICTION_EQUIVALENCE_H */ #endif /* RELATION_RESTRICTION_EQUIVALENCE_H */

View File

@ -446,9 +446,11 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT sum((d1.id OP
(1 row) (1 row)
SELECT count(*) FROM distributed d1 JOIN local USING (id) LEFT JOIN distributed d2 USING (id) WHERE d2.id = 1 ORDER BY 1 DESC LIMIT 4; SELECT count(*) FROM distributed d1 JOIN local USING (id) LEFT JOIN distributed d2 USING (id) WHERE d2.id = 1 ORDER BY 1 DESC LIMIT 4;
DEBUG: Wrapping relation "local" to a subquery: SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE (id OPERATOR(pg_catalog.=) 1) DEBUG: Wrapping relation "distributed" to a subquery: SELECT id, NULL::text AS name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed d1 WHERE (id OPERATOR(pg_catalog.=) 1)
DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE (id OPERATOR(pg_catalog.=) 1) DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed d1 WHERE (id OPERATOR(pg_catalog.=) 1)
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((local_dist_join_mixed.distributed d1 JOIN (SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local USING (id)) LEFT JOIN local_dist_join_mixed.distributed d2 USING (id)) WHERE (d2.id OPERATOR(pg_catalog.=) 1) ORDER BY (count(*)) DESC LIMIT 4 DEBUG: Wrapping relation "distributed" to a subquery: SELECT id, NULL::text AS name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed d2 WHERE (id OPERATOR(pg_catalog.=) 1)
DEBUG: generating subplan XXX_2 for subquery SELECT id, NULL::text AS name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed d2 WHERE (id OPERATOR(pg_catalog.=) 1)
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (((SELECT intermediate_result.id, intermediate_result.name, intermediate_result.created_at FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, name text, created_at timestamp with time zone)) d1 JOIN local_dist_join_mixed.local USING (id)) LEFT JOIN (SELECT intermediate_result.id, intermediate_result.name, intermediate_result.created_at FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, name text, created_at timestamp with time zone)) d2 USING (id)) WHERE (d2.id OPERATOR(pg_catalog.=) 1) ORDER BY (count(*)) DESC LIMIT 4
count count
--------------------------------------------------------------------- ---------------------------------------------------------------------
1 1
@ -1171,9 +1173,9 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS c
-- w count(*) it works fine as PG ignores the inner tables -- w count(*) it works fine as PG ignores the inner tables
SELECT count(*) FROM distributed LEFT JOIN local USING (id); SELECT count(*) FROM distributed LEFT JOIN local USING (id);
DEBUG: Wrapping relation "local" to a subquery: SELECT NULL::bigint AS id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true DEBUG: Wrapping relation "distributed" to a subquery: SELECT id, NULL::text AS name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT NULL::bigint AS id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (local_dist_join_mixed.distributed LEFT JOIN (SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local USING (id)) DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM ((SELECT intermediate_result.id, intermediate_result.name, intermediate_result.created_at FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, name text, created_at timestamp with time zone)) distributed LEFT JOIN local_dist_join_mixed.local USING (id))
count count
--------------------------------------------------------------------- ---------------------------------------------------------------------
101 101
@ -1188,20 +1190,19 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS c
101 101
(1 row) (1 row)
SELECT id, name FROM distributed LEFT JOIN local USING (id) LIMIT 1; SELECT id, name FROM distributed LEFT JOIN local USING (id) ORDER BY 1 LIMIT 1;
DEBUG: Wrapping relation "local" to a subquery: SELECT NULL::bigint AS id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true DEBUG: Wrapping relation "distributed" to a subquery: SELECT id, name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT NULL::bigint AS id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true DEBUG: generating subplan XXX_1 for subquery SELECT id, name, NULL::timestamp with time zone AS created_at FROM local_dist_join_mixed.distributed WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT distributed.id, distributed.name FROM (local_dist_join_mixed.distributed LEFT JOIN (SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local USING (id)) LIMIT 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT distributed.id, distributed.name FROM ((SELECT intermediate_result.id, intermediate_result.name, intermediate_result.created_at FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, name text, created_at timestamp with time zone)) distributed LEFT JOIN local_dist_join_mixed.local USING (id)) ORDER BY distributed.id LIMIT 1
DEBUG: push down of limit count: 1
id | name id | name
--------------------------------------------------------------------- ---------------------------------------------------------------------
1 | 1 0 | 0
(1 row) (1 row)
SELECT id, name FROM local LEFT JOIN distributed USING (id) LIMIT 1; SELECT id, name FROM local LEFT JOIN distributed USING (id) ORDER BY 1 LIMIT 1;
DEBUG: Wrapping relation "local" to a subquery: SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true DEBUG: Wrapping relation "local" to a subquery: SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT local.id, distributed.name FROM ((SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local LEFT JOIN local_dist_join_mixed.distributed USING (id)) LIMIT 1 DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT local.id, distributed.name FROM ((SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local LEFT JOIN local_dist_join_mixed.distributed USING (id)) ORDER BY local.id LIMIT 1
ERROR: cannot pushdown the subquery ERROR: cannot pushdown the subquery
DETAIL: Complex subqueries and CTEs cannot be in the outer part of the outer join DETAIL: Complex subqueries and CTEs cannot be in the outer part of the outer join
SELECT SELECT
@ -1521,6 +1522,78 @@ DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT foo1.id FROM
--------------------------------------------------------------------- ---------------------------------------------------------------------
(0 rows) (0 rows)
SELECT
count(*)
FROM
distributed
JOIN LATERAL
(SELECT
*
FROM
local
JOIN
distributed d2
ON(true)
WHERE local.id = distributed.id AND d2.id = local.id) as foo
ON (true);
DEBUG: Wrapping relation "local" to a subquery: SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (local_dist_join_mixed.distributed JOIN LATERAL (SELECT local.id, local.title, d2.id, d2.name, d2.created_at FROM ((SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local JOIN local_dist_join_mixed.distributed d2 ON (true)) WHERE ((local.id OPERATOR(pg_catalog.=) distributed.id) AND (d2.id OPERATOR(pg_catalog.=) local.id))) foo(id, title, id_1, name, created_at) ON (true))
count
---------------------------------------------------------------------
101
(1 row)
SELECT local.title, local.title FROM local JOIN distributed USING(id) ORDER BY 1,2 LIMIt 1;
DEBUG: Wrapping relation "local" to a subquery: SELECT id, title FROM local_dist_join_mixed.local WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT id, title FROM local_dist_join_mixed.local WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT local.title, local.title FROM ((SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local JOIN local_dist_join_mixed.distributed USING (id)) ORDER BY local.title, local.title LIMIT 1
DEBUG: push down of limit count: 1
title | title
---------------------------------------------------------------------
0 | 0
(1 row)
SELECT NULL FROM local JOIN distributed USING(id) ORDER BY 1 LIMIt 1;
DEBUG: Wrapping relation "local" to a subquery: SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT NULL::text FROM ((SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local JOIN local_dist_join_mixed.distributed USING (id)) ORDER BY NULL::text LIMIT 1
DEBUG: push down of limit count: 1
?column?
---------------------------------------------------------------------
(1 row)
SELECT distributed.name, distributed.name, local.title, local.title FROM local JOIN distributed USING(id) ORDER BY 1,2,3,4 LIMIT 1;
DEBUG: Wrapping relation "local" to a subquery: SELECT id, title FROM local_dist_join_mixed.local WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT id, title FROM local_dist_join_mixed.local WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT distributed.name, distributed.name, local.title, local.title FROM ((SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local JOIN local_dist_join_mixed.distributed USING (id)) ORDER BY distributed.name, distributed.name, local.title, local.title LIMIT 1
DEBUG: push down of limit count: 1
name | name | title | title
---------------------------------------------------------------------
0 | 0 | 0 | 0
(1 row)
SELECT
COUNT(*)
FROM
local
JOIN
distributed
USING
(id)
JOIN
(SELECT id, NULL, NULL FROM distributed) foo
USING
(id);
DEBUG: Wrapping relation "local" to a subquery: SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: generating subplan XXX_1 for subquery SELECT id, NULL::text AS title FROM local_dist_join_mixed.local WHERE true
DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (((SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title text)) local JOIN local_dist_join_mixed.distributed USING (id)) JOIN (SELECT distributed_1.id, NULL::text, NULL::text FROM local_dist_join_mixed.distributed distributed_1) foo(id, "?column?", "?column?_1") USING (id))
count
---------------------------------------------------------------------
101
(1 row)
DROP SCHEMA local_dist_join_mixed CASCADE; DROP SCHEMA local_dist_join_mixed CASCADE;
NOTICE: drop cascades to 7 other objects NOTICE: drop cascades to 7 other objects
DETAIL: drop cascades to table distributed DETAIL: drop cascades to table distributed

View File

@ -1473,15 +1473,27 @@ UPDATE partitioning_locks_2009 SET time = '2009-03-01';
SELECT * FROM lockinfo; SELECT * FROM lockinfo;
logicalrelid | locktype | mode logicalrelid | locktype | mode
--------------------------------------------------------------------- ---------------------------------------------------------------------
partitioning_locks | colocated_shards_metadata | ShareLock
partitioning_locks | colocated_shards_metadata | ShareLock
partitioning_locks | colocated_shards_metadata | ShareLock
partitioning_locks | colocated_shards_metadata | ShareLock
partitioning_locks | shard | ShareUpdateExclusiveLock partitioning_locks | shard | ShareUpdateExclusiveLock
partitioning_locks | shard | ShareUpdateExclusiveLock partitioning_locks | shard | ShareUpdateExclusiveLock
partitioning_locks | shard | ShareUpdateExclusiveLock partitioning_locks | shard | ShareUpdateExclusiveLock
partitioning_locks | shard | ShareUpdateExclusiveLock partitioning_locks | shard | ShareUpdateExclusiveLock
partitioning_locks_2009 | colocated_shards_metadata | ShareLock
partitioning_locks_2009 | colocated_shards_metadata | ShareLock
partitioning_locks_2009 | colocated_shards_metadata | ShareLock
partitioning_locks_2009 | colocated_shards_metadata | ShareLock
partitioning_locks_2009 | shard | ShareUpdateExclusiveLock partitioning_locks_2009 | shard | ShareUpdateExclusiveLock
partitioning_locks_2009 | shard | ShareUpdateExclusiveLock partitioning_locks_2009 | shard | ShareUpdateExclusiveLock
partitioning_locks_2009 | shard | ShareUpdateExclusiveLock partitioning_locks_2009 | shard | ShareUpdateExclusiveLock
partitioning_locks_2009 | shard | ShareUpdateExclusiveLock partitioning_locks_2009 | shard | ShareUpdateExclusiveLock
(8 rows) partitioning_locks_2010 | colocated_shards_metadata | ShareLock
partitioning_locks_2010 | colocated_shards_metadata | ShareLock
partitioning_locks_2010 | colocated_shards_metadata | ShareLock
partitioning_locks_2010 | colocated_shards_metadata | ShareLock
(20 rows)
COMMIT; COMMIT;
-- test shard resource locks with TRUNCATE -- test shard resource locks with TRUNCATE

View File

@ -309,8 +309,8 @@ SELECT count(*) FROM distributed CROSS JOIN local WHERE distributed.id = 1;
SELECT count(*) FROM distributed LEFT JOIN local USING (id); SELECT count(*) FROM distributed LEFT JOIN local USING (id);
SELECT count(*) FROM local LEFT JOIN distributed USING (id); SELECT count(*) FROM local LEFT JOIN distributed USING (id);
SELECT id, name FROM distributed LEFT JOIN local USING (id) LIMIT 1; SELECT id, name FROM distributed LEFT JOIN local USING (id) ORDER BY 1 LIMIT 1;
SELECT id, name FROM local LEFT JOIN distributed USING (id) LIMIT 1; SELECT id, name FROM local LEFT JOIN distributed USING (id) ORDER BY 1 LIMIT 1;
SELECT SELECT
foo1.id foo1.id
@ -370,6 +370,35 @@ WHERE
foo1.id = foo5.id foo1.id = foo5.id
ORDER BY 1; ORDER BY 1;
SELECT
count(*)
FROM
distributed
JOIN LATERAL
(SELECT
*
FROM
local
JOIN
distributed d2
ON(true)
WHERE local.id = distributed.id AND d2.id = local.id) as foo
ON (true);
SELECT local.title, local.title FROM local JOIN distributed USING(id) ORDER BY 1,2 LIMIt 1;
SELECT NULL FROM local JOIN distributed USING(id) ORDER BY 1 LIMIt 1;
SELECT distributed.name, distributed.name, local.title, local.title FROM local JOIN distributed USING(id) ORDER BY 1,2,3,4 LIMIT 1;
SELECT
COUNT(*)
FROM
local
JOIN
distributed
USING
(id)
JOIN
(SELECT id, NULL, NULL FROM distributed) foo
USING
(id);
DROP SCHEMA local_dist_join_mixed CASCADE; DROP SCHEMA local_dist_join_mixed CASCADE;