mirror of https://github.com/citusdata/citus.git
307 lines
8.6 KiB
C
307 lines
8.6 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* deparse_shard_query.c
|
|
*
|
|
* This file contains functions for deparsing shard queries.
|
|
*
|
|
* Copyright (c) 2014-2016, Citus Data, Inc.
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "c.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "distributed/citus_nodefuncs.h"
|
|
#include "distributed/citus_ruleutils.h"
|
|
#include "distributed/deparse_shard_query.h"
|
|
#include "distributed/insert_select_planner.h"
|
|
#include "distributed/metadata_cache.h"
|
|
#include "distributed/multi_physical_planner.h"
|
|
#include "distributed/multi_router_planner.h"
|
|
#include "distributed/version_compat.h"
|
|
#include "lib/stringinfo.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "nodes/nodes.h"
|
|
#include "nodes/parsenodes.h"
|
|
#include "nodes/pg_list.h"
|
|
#include "parser/parsetree.h"
|
|
#include "storage/lock.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
|
|
|
|
static void UpdateTaskQueryString(Query *query, Oid distributedTableId,
|
|
RangeTblEntry *valuesRTE, Task *task);
|
|
static void ConvertRteToSubqueryWithEmptyResult(RangeTblEntry *rte);
|
|
|
|
|
|
/*
|
|
* RebuildQueryStrings deparses the job query for each task to
|
|
* include execution-time changes such as function evaluation.
|
|
*/
|
|
void
|
|
RebuildQueryStrings(Query *originalQuery, List *taskList)
|
|
{
|
|
ListCell *taskCell = NULL;
|
|
Oid relationId = ((RangeTblEntry *) linitial(originalQuery->rtable))->relid;
|
|
RangeTblEntry *valuesRTE = ExtractDistributedInsertValuesRTE(originalQuery);
|
|
|
|
foreach(taskCell, taskList)
|
|
{
|
|
Task *task = (Task *) lfirst(taskCell);
|
|
Query *query = originalQuery;
|
|
|
|
if (task->insertSelectQuery)
|
|
{
|
|
/* for INSERT..SELECT, adjust shard names in SELECT part */
|
|
RangeTblEntry *copiedInsertRte = NULL;
|
|
RangeTblEntry *copiedSubqueryRte = NULL;
|
|
Query *copiedSubquery = NULL;
|
|
List *relationShardList = task->relationShardList;
|
|
ShardInterval *shardInterval = LoadShardInterval(task->anchorShardId);
|
|
|
|
query = copyObject(originalQuery);
|
|
|
|
copiedInsertRte = ExtractInsertRangeTableEntry(query);
|
|
copiedSubqueryRte = ExtractSelectRangeTableEntry(query);
|
|
copiedSubquery = copiedSubqueryRte->subquery;
|
|
|
|
AddShardIntervalRestrictionToSelect(copiedSubquery, shardInterval);
|
|
ReorderInsertSelectTargetLists(query, copiedInsertRte, copiedSubqueryRte);
|
|
|
|
/* setting an alias simplifies deparsing of RETURNING */
|
|
if (copiedInsertRte->alias == NULL)
|
|
{
|
|
Alias *alias = makeAlias(CITUS_TABLE_ALIAS, NIL);
|
|
copiedInsertRte->alias = alias;
|
|
}
|
|
|
|
UpdateRelationToShardNames((Node *) copiedSubquery, relationShardList);
|
|
}
|
|
else if (task->upsertQuery || valuesRTE != NULL)
|
|
{
|
|
RangeTblEntry *rangeTableEntry = NULL;
|
|
|
|
/*
|
|
* Always an alias in UPSERTs and multi-row INSERTs to avoid
|
|
* deparsing issues (e.g. RETURNING might reference the original
|
|
* table name, which has been replaced by a shard name).
|
|
*/
|
|
rangeTableEntry = linitial(query->rtable);
|
|
if (rangeTableEntry->alias == NULL)
|
|
{
|
|
Alias *alias = makeAlias(CITUS_TABLE_ALIAS, NIL);
|
|
rangeTableEntry->alias = alias;
|
|
}
|
|
}
|
|
|
|
ereport(DEBUG4, (errmsg("query before rebuilding: %s", task->queryString)));
|
|
|
|
UpdateTaskQueryString(query, relationId, valuesRTE, task);
|
|
|
|
ereport(DEBUG4, (errmsg("query after rebuilding: %s", task->queryString)));
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* UpdateTaskQueryString updates the query string stored within the provided
|
|
* Task. If the Task has row values from a multi-row INSERT, those are injected
|
|
* into the provided query (using the provided valuesRTE, which must belong to
|
|
* the query) before deparse occurs (the query's full VALUES list will be
|
|
* restored before this function returns).
|
|
*/
|
|
static void
|
|
UpdateTaskQueryString(Query *query, Oid distributedTableId, RangeTblEntry *valuesRTE,
|
|
Task *task)
|
|
{
|
|
StringInfo queryString = makeStringInfo();
|
|
List *oldValuesLists = NIL;
|
|
|
|
if (valuesRTE != NULL)
|
|
{
|
|
Assert(valuesRTE->rtekind == RTE_VALUES);
|
|
Assert(task->rowValuesLists != NULL);
|
|
|
|
oldValuesLists = valuesRTE->values_lists;
|
|
valuesRTE->values_lists = task->rowValuesLists;
|
|
}
|
|
|
|
/*
|
|
* For INSERT queries, we only have one relation to update, so we can
|
|
* use deparse_shard_query(). For UPDATE and DELETE queries, we may have
|
|
* subqueries and joins, so we use relation shard list to update shard
|
|
* names and call pg_get_query_def() directly.
|
|
*/
|
|
if (query->commandType == CMD_INSERT)
|
|
{
|
|
deparse_shard_query(query, distributedTableId, task->anchorShardId, queryString);
|
|
}
|
|
else
|
|
{
|
|
List *relationShardList = task->relationShardList;
|
|
UpdateRelationToShardNames((Node *) query, relationShardList);
|
|
|
|
pg_get_query_def(query, queryString);
|
|
}
|
|
|
|
if (valuesRTE != NULL)
|
|
{
|
|
valuesRTE->values_lists = oldValuesLists;
|
|
}
|
|
|
|
task->queryString = queryString->data;
|
|
}
|
|
|
|
|
|
/*
|
|
* UpdateRelationToShardNames walks over the query tree and appends shard ids to
|
|
* relations. It uses unique identity value to establish connection between a
|
|
* shard and the range table entry. If the range table id is not given a
|
|
* identity, than the relation is not referenced from the query, no connection
|
|
* could be found between a shard and this relation. Therefore relation is replaced
|
|
* by set of NULL values so that the query would work at worker without any problems.
|
|
*
|
|
*/
|
|
bool
|
|
UpdateRelationToShardNames(Node *node, List *relationShardList)
|
|
{
|
|
RangeTblEntry *newRte = NULL;
|
|
uint64 shardId = INVALID_SHARD_ID;
|
|
Oid relationId = InvalidOid;
|
|
Oid schemaId = InvalidOid;
|
|
char *relationName = NULL;
|
|
char *schemaName = NULL;
|
|
bool replaceRteWithNullValues = false;
|
|
ListCell *relationShardCell = NULL;
|
|
RelationShard *relationShard = NULL;
|
|
|
|
if (node == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* want to look at all RTEs, even in subqueries, CTEs and such */
|
|
if (IsA(node, Query))
|
|
{
|
|
return query_tree_walker((Query *) node, UpdateRelationToShardNames,
|
|
relationShardList, QTW_EXAMINE_RTES);
|
|
}
|
|
|
|
if (!IsA(node, RangeTblEntry))
|
|
{
|
|
return expression_tree_walker(node, UpdateRelationToShardNames,
|
|
relationShardList);
|
|
}
|
|
|
|
newRte = (RangeTblEntry *) node;
|
|
|
|
if (newRte->rtekind != RTE_RELATION)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Search for the restrictions associated with the RTE. There better be
|
|
* some, otherwise this query wouldn't be elegible as a router query.
|
|
*
|
|
* FIXME: We should probably use a hashtable here, to do efficient
|
|
* lookup.
|
|
*/
|
|
foreach(relationShardCell, relationShardList)
|
|
{
|
|
relationShard = (RelationShard *) lfirst(relationShardCell);
|
|
|
|
if (newRte->relid == relationShard->relationId)
|
|
{
|
|
break;
|
|
}
|
|
|
|
relationShard = NULL;
|
|
}
|
|
|
|
replaceRteWithNullValues = relationShard == NULL ||
|
|
relationShard->shardId == INVALID_SHARD_ID;
|
|
if (replaceRteWithNullValues)
|
|
{
|
|
ConvertRteToSubqueryWithEmptyResult(newRte);
|
|
return false;
|
|
}
|
|
|
|
shardId = relationShard->shardId;
|
|
relationId = relationShard->relationId;
|
|
|
|
relationName = get_rel_name(relationId);
|
|
AppendShardIdToName(&relationName, shardId);
|
|
|
|
schemaId = get_rel_namespace(relationId);
|
|
schemaName = get_namespace_name(schemaId);
|
|
|
|
ModifyRangeTblExtraData(newRte, CITUS_RTE_SHARD, schemaName, relationName, NIL);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* ConvertRteToSubqueryWithEmptyResult converts given relation RTE into
|
|
* subquery RTE that returns no results.
|
|
*/
|
|
static void
|
|
ConvertRteToSubqueryWithEmptyResult(RangeTblEntry *rte)
|
|
{
|
|
Relation relation = heap_open(rte->relid, NoLock);
|
|
TupleDesc tupleDescriptor = RelationGetDescr(relation);
|
|
int columnCount = tupleDescriptor->natts;
|
|
int columnIndex = 0;
|
|
Query *subquery = NULL;
|
|
List *targetList = NIL;
|
|
FromExpr *joinTree = NULL;
|
|
|
|
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
|
|
{
|
|
FormData_pg_attribute *attributeForm = TupleDescAttr(tupleDescriptor,
|
|
columnIndex);
|
|
TargetEntry *targetEntry = NULL;
|
|
StringInfo resname = NULL;
|
|
Const *constValue = NULL;
|
|
|
|
if (attributeForm->attisdropped)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
resname = makeStringInfo();
|
|
constValue = makeNullConst(attributeForm->atttypid, attributeForm->atttypmod,
|
|
attributeForm->attcollation);
|
|
|
|
appendStringInfo(resname, "%s", attributeForm->attname.data);
|
|
|
|
targetEntry = makeNode(TargetEntry);
|
|
targetEntry->expr = (Expr *) constValue;
|
|
targetEntry->resno = columnIndex;
|
|
targetEntry->resname = resname->data;
|
|
|
|
targetList = lappend(targetList, targetEntry);
|
|
}
|
|
|
|
heap_close(relation, NoLock);
|
|
|
|
joinTree = makeNode(FromExpr);
|
|
joinTree->quals = makeBoolConst(false, false);
|
|
|
|
subquery = makeNode(Query);
|
|
subquery->commandType = CMD_SELECT;
|
|
subquery->querySource = QSRC_ORIGINAL;
|
|
subquery->canSetTag = true;
|
|
subquery->targetList = targetList;
|
|
subquery->jointree = joinTree;
|
|
|
|
rte->rtekind = RTE_SUBQUERY;
|
|
rte->subquery = subquery;
|
|
rte->alias = copyObject(rte->eref);
|
|
}
|