Skip to content

Commit 49a9500

Browse files
authored
Merge branch 'main' into ihalatci-persist-last-upgrade-version
2 parents 8ea3e20 + 787c130 commit 49a9500

29 files changed

Lines changed: 1207 additions & 246 deletions

.devcontainer/src/test/regress/Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ url = "https://pypi.python.org/simple"
44
verify_ssl = true
55

66
[packages]
7+
pyasn1 = ">=0.6.4"
78
mitmproxy = {git = "https://github.com/citusdata/mitmproxy.git", ref = "321e6d203cf31e36d59ba8e4f9c6a3c4a5d6ddf0"}
89
"aioquic" = ">=1.2.0,<1.3.0"
910
"mitmproxy-rs" = ">=0.12.6,<0.13.0"

.devcontainer/src/test/regress/Pipfile.lock

Lines changed: 126 additions & 109 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/build_and_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
style_checker_image_name: "ghcr.io/citusdata/stylechecker"
3737
style_checker_tools_version: "0.8.33"
3838
sql_snapshot_pg_version: "18.3"
39-
image_suffix: "-va56062b"
39+
image_suffix: "-v081d17c"
4040
pg16_version: '{ "major": "16", "full": "16.13" }'
4141
pg17_version: '{ "major": "17", "full": "17.9" }'
4242
pg18_version: '{ "major": "18", "full": "18.3" }'

src/backend/distributed/commands/alter_table.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1358,7 +1358,7 @@ CreateTableConversion(TableConversionParameters *params)
13581358
relation_close(relation, NoLock);
13591359
con->distributionKey =
13601360
BuildDistributionKeyFromColumnName(con->relationId, con->distributionColumn,
1361-
NoLock);
1361+
NoLock, false);
13621362

13631363
con->originalAccessMethod = NULL;
13641364
if (!PartitionedTable(con->relationId) && !IsForeignTable(con->relationId))

src/backend/distributed/commands/create_distributed_table.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ CreateDistributedTableConcurrently(Oid relationId, char *distributionColumnName,
535535
*/
536536
Var *distributionColumn = BuildDistributionKeyFromColumnName(relationId,
537537
distributionColumnName,
538-
NoLock);
538+
NoLock, false);
539539

540540
/* get an advisory lock to serialize concurrent default group creations */
541541
if (IsColocateWithDefault(colocateWithTableName))
@@ -704,7 +704,7 @@ EnsureColocateWithTableIsValid(Oid relationId, char distributionMethod,
704704

705705
Var *distributionColumn = BuildDistributionKeyFromColumnName(relationId,
706706
distributionColumnName,
707-
NoLock);
707+
AccessShareLock, true);
708708
EnsureTableCanBeColocatedWith(relationId, replicationModel,
709709
distributionColumn, colocateWithTableId);
710710
}
@@ -1221,7 +1221,7 @@ CreateCitusTable(Oid relationId, CitusTableType tableType,
12211221
distributionColumn = BuildDistributionKeyFromColumnName(relationId,
12221222
distributedTableParams->
12231223
distributionColumnName,
1224-
NoLock);
1224+
NoLock, false);
12251225
}
12261226

12271227
CitusTableParams citusTableParams = DecideCitusTableParams(tableType,

src/backend/distributed/metadata/metadata_sync.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3472,7 +3472,7 @@ citus_internal_add_partition_metadata(PG_FUNCTION_ARGS)
34723472

34733473
distributionColumnVar =
34743474
BuildDistributionKeyFromColumnName(relationId, distributionColumnString,
3475-
AccessShareLock);
3475+
AccessShareLock, false);
34763476
Assert(distributionColumnVar != NULL);
34773477
}
34783478

src/backend/distributed/operations/worker_split_copy_udf.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ CreatePartitionedSplitCopyDestReceiver(EState *estate,
317317
Oid shardRelationId = LookupShardRelationFromCatalog(shardId, missingOK);
318318
Var *partitionColumn = BuildDistributionKeyFromColumnName(shardRelationId,
319319
partitionColumnName,
320-
AccessShareLock);
320+
AccessShareLock, false);
321321

322322
CitusTableCacheEntry *shardSearchInfo =
323323
QueryTupleShardSearchInfo(minValuesArray, maxValuesArray,

src/backend/distributed/operations/worker_split_shard_replication_setup_udf.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ CreateShardSplitInfo(uint64 sourceShardIdToSplit,
222222
/* determine the partition column in the tuple descriptor */
223223
Var *partitionColumn = BuildDistributionKeyFromColumnName(sourceShardToSplitOid,
224224
partitionColumnName,
225-
AccessShareLock);
225+
AccessShareLock, false);
226226
if (partitionColumn == NULL)
227227
{
228228
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),

src/backend/distributed/planner/multi_router_planner.c

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ static bool ModifiesLocalTableWithRemoteCitusLocalTable(List *rangeTableList);
178178
static DeferredErrorMessage * DeferErrorIfUnsupportedLocalTableJoin(List *rangeTableList);
179179
static bool IsLocallyAccessibleCitusLocalTable(Oid relationId);
180180
static bool ConvertToQueryOnShard(Query *query, Oid relationID, Oid shardRelationId);
181+
static void ReplaceModifyingCteWithEmptyResult(Query *outerQuery);
181182

182183
/*
183184
* CreateRouterPlan attempts to create a router executor plan for the given
@@ -284,6 +285,16 @@ CreateSingleTaskRouterSelectPlan(DistributedPlan *distributedPlan, Query *origin
284285
return;
285286
}
286287

288+
/*
289+
* RouterJob may rewrite a zero-shard modifying CTE inside a router SELECT
290+
* into a plain empty-result CTE (see ReplaceModifyingCteWithEmptyResult).
291+
* That clears originalQuery->hasModifyingCTE, so recompute modLevel from
292+
* the (possibly-transformed) job query -- otherwise the executor would
293+
* treat the plan as ROW_MODIFY_NONCOMMUTATIVE and attempt to acquire
294+
* shard-locks on the pruned-away shard.
295+
*/
296+
distributedPlan->modLevel = RowModifyLevelForQuery(job->jobQuery);
297+
287298
ereport(DEBUG2, (errmsg("Creating router plan")));
288299

289300
distributedPlan->workerJob = job;
@@ -2004,6 +2015,39 @@ RouterJob(Query *originalQuery, PlannerRestrictionContext *plannerRestrictionCon
20042015
}
20052016
}
20062017
}
2018+
else if (shardId == INVALID_SHARD_ID && originalQuery->hasModifyingCTE)
2019+
{
2020+
/*
2021+
* Router SELECT that wraps a modifying CTE whose pruning
2022+
* yielded zero shards (e.g. WITH u AS (UPDATE t ... WHERE
2023+
* dist_key = X AND FALSE RETURNING ...) SELECT ... FROM u).
2024+
*
2025+
* PlanRouterQuery returned INVALID_SHARD_ID with a dummy
2026+
* placement, and UpdateRelationToShardNames has already
2027+
* flipped the CTE's target relation RTE to an empty-result
2028+
* RTE_SUBQUERY. The direct UPDATE/DELETE escape above only
2029+
* inspects the OUTER query's resultRelation (which is 0 for
2030+
* a SELECT), so absent this branch the query would fall
2031+
* through to SingleShardTaskList and be promoted to a
2032+
* MODIFY_TASK with anchorShardId = 0 -- which then fails at
2033+
* execution time in AcquireMetadataLocks ->
2034+
* LookupShardIdCacheEntry(0) with "could not find valid
2035+
* entry for shard 0".
2036+
*
2037+
* We cannot use the taskList = NIL contract that the direct
2038+
* UPDATE/DELETE escape above uses, because the outer SELECT
2039+
* has a consumer (e.g. SELECT count(*) FROM u must return
2040+
* one row containing 0, not the empty resultset that an empty
2041+
* task list would yield). Instead, rewrite each modifying CTE
2042+
* in-place to a plain SELECT that returns no rows but retains
2043+
* the CTE's output column shape, and clear hasModifyingCTE.
2044+
* The rewritten query then flows through the normal
2045+
* zero-shard router-SELECT machinery (READ_TASK on the dummy
2046+
* placement), producing the correct empty CTE and letting the
2047+
* outer aggregate emit the required single row.
2048+
*/
2049+
ReplaceModifyingCteWithEmptyResult(originalQuery);
2050+
}
20072051

20082052
if (isMultiShardModifyQuery)
20092053
{
@@ -2134,6 +2178,27 @@ CheckAndBuildDelayedFastPathPlan(DistributedPlanningContext *planContext,
21342178
return;
21352179
}
21362180

2181+
if (list_length(job->taskList) == 0)
2182+
{
2183+
/*
2184+
* Planner-time shard pruning legitimately produced zero shards for a
2185+
* delayed fast-path modification (e.g. WHERE dist_key = X AND FALSE,
2186+
* or a fast-path fallback where the distribution-key type does not
2187+
* match the literal type and PruneShards short-circuits on
2188+
* ContainsFalseClause). GenerateSingleShardRouterTaskList already
2189+
* established the terminal state job->taskList = NIL for this case
2190+
* in the non-delayed fast-path route; mirror the deferred-pruning
2191+
* branch above so we build a placeholder plan (no single-task local
2192+
* shortcut, no deparse) and let the executor treat the empty task
2193+
* list as a silent no-op, matching the non-fast-path direct
2194+
* UPDATE/DELETE contract.
2195+
*/
2196+
planContext->plan = FastPathPlanner(planContext->originalQuery,
2197+
planContext->query,
2198+
planContext->boundParams);
2199+
return;
2200+
}
2201+
21372202
List *tasks = job->taskList;
21382203
Assert(list_length(tasks) == 1);
21392204
Task *task = (Task *) linitial(tasks);
@@ -2290,6 +2355,94 @@ ConvertToQueryOnShard(Query *query, Oid citusTableOid, Oid shardId)
22902355
}
22912356

22922357

2358+
/*
2359+
* ReplaceModifyingCteWithEmptyResult rewrites each modifying (UPDATE/DELETE)
2360+
* CTE in outerQuery->cteList so that its body becomes a plain SELECT whose
2361+
* jointree quals are constant FALSE and whose target list is a matching-shape
2362+
* list of NULL constants. This preserves the outer query's structural reference
2363+
* to the CTE (name, output column names/types/typmods/collations) while
2364+
* guaranteeing the CTE produces zero rows and requires no shard metadata.
2365+
*
2366+
* hasModifyingCTE is cleared only if no other modifying CTE (e.g. CMD_INSERT,
2367+
* CMD_MERGE) remains in the cteList after the rewrite; otherwise the flag is
2368+
* preserved so downstream routing (READ_TASK vs MODIFY_TASK selection,
2369+
* modLevel computation, lock acquisition) still treats the query as
2370+
* modifying. When the flag does end up cleared, the outer SELECT flows
2371+
* through the standard zero-shard router-SELECT machinery (READ_TASK on the
2372+
* dummy placement, no shard-metadata lookups, no MODIFY_TASK promotion in
2373+
* SingleShardTaskList).
2374+
*
2375+
* This helper mutates outerQuery in place. Callers must have already
2376+
* established that pruning yielded zero shards (shardId == INVALID_SHARD_ID);
2377+
* pruning-relevant fields (placementList, relationShardList, prunedShardIntervalListList)
2378+
* are unaffected.
2379+
*/
2380+
static void
2381+
ReplaceModifyingCteWithEmptyResult(Query *outerQuery)
2382+
{
2383+
CommonTableExpr *cte = NULL;
2384+
bool anyModifyingCteLeft = false;
2385+
2386+
foreach_declared_ptr(cte, outerQuery->cteList)
2387+
{
2388+
Query *cteQuery = (Query *) cte->ctequery;
2389+
2390+
if (cteQuery->commandType != CMD_UPDATE &&
2391+
cteQuery->commandType != CMD_DELETE)
2392+
{
2393+
/*
2394+
* Non-UPDATE/DELETE CTE: leave it alone. If it is still a
2395+
* modification (e.g. CMD_INSERT, CMD_MERGE) note that so we do
2396+
* not clear the outer query's hasModifyingCTE flag below.
2397+
*/
2398+
if (cteQuery->commandType != CMD_SELECT)
2399+
{
2400+
anyModifyingCteLeft = true;
2401+
}
2402+
continue;
2403+
}
2404+
2405+
int columnCount = list_length(cte->ctecoltypes);
2406+
List *targetList = NIL;
2407+
2408+
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++)
2409+
{
2410+
Oid coltype = list_nth_oid(cte->ctecoltypes, columnIndex);
2411+
int32 coltypmod = list_nth_int(cte->ctecoltypmods, columnIndex);
2412+
Oid colcoll = list_nth_oid(cte->ctecolcollations, columnIndex);
2413+
Node *colname = (Node *) list_nth(cte->ctecolnames, columnIndex);
2414+
2415+
Const *nullConst = makeNullConst(coltype, coltypmod, colcoll);
2416+
2417+
TargetEntry *targetEntry = makeNode(TargetEntry);
2418+
targetEntry->expr = (Expr *) nullConst;
2419+
targetEntry->resno = columnIndex + 1;
2420+
targetEntry->resname = pstrdup(strVal(colname));
2421+
targetEntry->resorigtbl = InvalidOid;
2422+
targetEntry->resorigcol = 0;
2423+
targetEntry->resjunk = false;
2424+
2425+
targetList = lappend(targetList, targetEntry);
2426+
}
2427+
2428+
FromExpr *joinTree = makeNode(FromExpr);
2429+
joinTree->fromlist = NIL;
2430+
joinTree->quals = (Node *) makeBoolConst(false, false);
2431+
2432+
Query *emptyCteQuery = makeNode(Query);
2433+
emptyCteQuery->commandType = CMD_SELECT;
2434+
emptyCteQuery->querySource = cteQuery->querySource;
2435+
emptyCteQuery->canSetTag = cteQuery->canSetTag;
2436+
emptyCteQuery->targetList = targetList;
2437+
emptyCteQuery->jointree = joinTree;
2438+
2439+
cte->ctequery = (Node *) emptyCteQuery;
2440+
}
2441+
2442+
outerQuery->hasModifyingCTE = anyModifyingCteLeft;
2443+
}
2444+
2445+
22932446
/*
22942447
* GenerateSingleShardRouterTaskList is a wrapper around other corresponding task
22952448
* list generation functions specific to single shard selects and modifications.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- citus--13.3-1--13.4-1
2+
-- bump version to 13.4-1
3+
4+
#include "udfs/citus_internal_distribute_object/13.4-1.sql"

0 commit comments

Comments
 (0)