partitionselection.c 18.0 KB
Newer Older
1 2 3 4 5
/*--------------------------------------------------------------------------
 *
 * partitionselection.c
 *	  Provides utility routines to support partition selection.
 *
6 7 8 9 10
 * Copyright (c) 2012-Present Pivotal Software, Inc.
 *
 *
 * IDENTIFICATION
 *	    src/backend/cdb/partitionselection.c
11 12 13 14 15 16 17 18 19 20
 *
 *--------------------------------------------------------------------------
 */

#include "postgres.h"
#include "miscadmin.h"

#include "cdb/partitionselection.h"
#include "cdb/cdbpartition.h"
#include "executor/executor.h"
21
#include "parser/parse_expr.h"
22 23
#include "utils/memutils.h"

24 25 26 27 28 29 30 31 32
/*
 * During attribute re-mapping for heterogeneous partitions, we use
 * this struct to identify which varno's attributes will be re-mapped.
 * Using this struct as a *context* during expression tree walking, we
 * can skip varattnos that do not belong to a given varno.
 */
typedef struct AttrMapContext
{
	const AttrNumber *newattno; /* The mapping table to remap the varattno */
A
Ashwin Agrawal 已提交
33
	Index		varno;			/* Which rte's varattno to re-map */
34 35 36 37
} AttrMapContext;

static bool change_varattnos_varno_walker(Node *node, const AttrMapContext *attrMapCxt);

38 39 40 41 42 43 44 45 46 47 48
/* ----------------------------------------------------------------
 *		eval_propagation_expression
 *
 *		Evaluate the propagation expression for the given leaf part Oid
 *		and return the result
 *
 * ----------------------------------------------------------------
 */
static int32
eval_propagation_expression(PartitionSelectorState *node, Oid part_oid)
{
A
Ashwin Agrawal 已提交
49
	ExprState  *propagationExprState = node->propagationExprState;
50 51

	ExprContext *econtext = node->ps.ps_ExprContext;
A
Ashwin Agrawal 已提交
52

53
	ResetExprContext(econtext);
A
Ashwin Agrawal 已提交
54
	bool		isNull = false;
55
	ExprDoneCond isDone = ExprSingleResult;
A
Ashwin Agrawal 已提交
56 57
	Datum		result = ExecEvalExpr(propagationExprState, econtext, &isNull, &isDone);

58 59 60 61 62 63 64
	return DatumGetInt32(result);
}

/* ----------------------------------------------------------------
 *		eval_part_qual
 *
 *		Evaluate a qualification expression that consists of
65 66
 *		PartDefaultExpr, PartBoundExpr, PartBoundInclusionExpr, PartBoundOpenExpr,
 *		PartListRuleExpr and PartListNullTestExpr.
67 68 69 70 71 72 73 74 75 76
 *
 *		Return true is passed, otherwise false.
 *
 * ----------------------------------------------------------------
 */
static bool
eval_part_qual(ExprState *exprstate, PartitionSelectorState *node, TupleTableSlot *inputTuple)
{
	/* evaluate generalPredicate */
	ExprContext *econtext = node->ps.ps_ExprContext;
A
Ashwin Agrawal 已提交
77

78 79 80 81
	ResetExprContext(econtext);
	econtext->ecxt_outertuple = inputTuple;
	econtext->ecxt_scantuple = inputTuple;

A
Ashwin Agrawal 已提交
82
	List	   *qualList = list_make1(exprstate);
83

A
Ashwin Agrawal 已提交
84
	return ExecQual(qualList, econtext, false /* result is not for null */ );
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
}

/* ----------------------------------------------------------------
 *		partition_selection
 *
 *		It finds a child PartitionRule for a given parent partitionNode, which
 *		satisfies with the given partition key value.
 *
 *		If no such a child partitionRule is found, return NULL.
 *
 *		Input parameters:
 *		pn: parent PartitionNode
 *		accessMethods: PartitionAccessMethods
 *		root_oid: root table Oid
 *		value: partition key value
 *		exprTypid: type of the expression
 *
 * ----------------------------------------------------------------
 */
A
Ashwin Agrawal 已提交
104
static PartitionRule *
105 106
partition_selection(PartitionNode *pn, PartitionAccessMethods *accessMethods, Oid root_oid, Datum value, Oid exprTypid, bool isNull)
{
A
Ashwin Agrawal 已提交
107 108 109 110 111 112
	Assert(NULL != pn);
	Assert(NULL != accessMethods);
	Partition  *part = pn->part;

	Assert(1 == part->parnatts);
	AttrNumber	partAttno = part->paratts[0];
113

A
Ashwin Agrawal 已提交
114 115 116 117
	Assert(0 < partAttno);

	Relation	rel = relation_open(root_oid, NoLock);
	TupleDesc	tupDesc = RelationGetDescr(rel);
118 119 120

	Assert(tupDesc->natts >= partAttno);

H
Heikki Linnakangas 已提交
121 122 123
	int			i;
	Datum	   *values = palloc0(partAttno * sizeof(Datum));
	bool	   *isnull = palloc(partAttno * sizeof(bool));
A
Ashwin Agrawal 已提交
124

H
Heikki Linnakangas 已提交
125 126
	for (i = 0; i < partAttno - 1; i++)
		isnull[i] = true;
127 128 129 130 131
	isnull[partAttno - 1] = isNull;
	values[partAttno - 1] = value;

	PartitionRule *result = get_next_level_matched_partition(pn, values, isnull, tupDesc, accessMethods, exprTypid);

H
Heikki Linnakangas 已提交
132 133
	pfree(values);
	pfree(isnull);
134 135 136 137 138 139
	relation_close(rel, NoLock);

	return result;
}

/* ----------------------------------------------------------------
140
 *		partition_rules_for_general_predicate
141
 *
142 143
 *		Returns a list of PartitionRule for the general predicate
 *		of current partition level
144 145 146 147
 *
 * ----------------------------------------------------------------
 */
static List *
148
partition_rules_for_general_predicate(PartitionSelectorState *node, int level,
A
Ashwin Agrawal 已提交
149
									  TupleTableSlot *inputTuple, PartitionNode *parentNode)
150
{
A
Ashwin Agrawal 已提交
151 152 153 154 155
	Assert(NULL != node);
	Assert(NULL != parentNode);

	List	   *result = NIL;
	ListCell   *lc = NULL;
156

A
Ashwin Agrawal 已提交
157
	foreach(lc, parentNode->rules)
158 159
	{
		PartitionRule *rule = (PartitionRule *) lfirst(lc);
A
Ashwin Agrawal 已提交
160 161 162 163 164

		/*
		 * We need to register it to allLevelParts to evaluate the current
		 * predicate
		 */
165
		node->levelPartRules[level] = rule;
166

167
		/* evaluate generalPredicate */
A
Ashwin Agrawal 已提交
168 169
		ExprState  *exprstate = (ExprState *) lfirst(list_nth_cell(node->levelExprStates, level));

170 171 172 173
		if (eval_part_qual(exprstate, node, inputTuple))
		{
			result = lappend(result, rule);
		}
174 175
	}

176
	if (parentNode->default_part)
177
	{
A
Ashwin Agrawal 已提交
178 179 180 181
		/*
		 * We need to register it to allLevelParts to evaluate the current
		 * predicate
		 */
182
		node->levelPartRules[level] = parentNode->default_part;
183 184

		/* evaluate generalPredicate */
A
Ashwin Agrawal 已提交
185 186
		ExprState  *exprstate = (ExprState *) lfirst(list_nth_cell(node->levelExprStates, level));

187 188
		if (eval_part_qual(exprstate, node, inputTuple))
		{
189
			result = lappend(result, parentNode->default_part);
190 191 192
		}
	}
	/* reset allLevelPartConstraints */
193
	node->levelPartRules[level] = NULL;
194 195 196 197 198

	return result;
}

/* ----------------------------------------------------------------
199
 *		partition_rules_for_equality_predicate
200
 *
201
 *		Return the PartitionRule for the equality predicate
202 203 204 205
 *		of current partition level
 *
 * ----------------------------------------------------------------
 */
206 207
static PartitionRule *
partition_rules_for_equality_predicate(PartitionSelectorState *node, int level,
A
Ashwin Agrawal 已提交
208
									   TupleTableSlot *inputTuple, PartitionNode *parentNode)
209
{
A
Ashwin Agrawal 已提交
210 211 212
	Assert(NULL != node);
	Assert(NULL != node->ps.plan);
	Assert(NULL != parentNode);
213
	PartitionSelector *ps = (PartitionSelector *) node->ps.plan;
A
Ashwin Agrawal 已提交
214 215

	Assert(level < ps->nLevels);
216 217

	/* evaluate equalityPredicate to get partition identifier value */
A
Ashwin Agrawal 已提交
218
	ExprState  *exprState = (ExprState *) lfirst(list_nth_cell(node->levelEqExprStates, level));
219 220

	ExprContext *econtext = node->ps.ps_ExprContext;
A
Ashwin Agrawal 已提交
221

222 223 224 225
	ResetExprContext(econtext);
	econtext->ecxt_outertuple = inputTuple;
	econtext->ecxt_scantuple = inputTuple;

A
Ashwin Agrawal 已提交
226
	bool		isNull = false;
227
	ExprDoneCond isDone = ExprSingleResult;
A
Ashwin Agrawal 已提交
228
	Datum		value = ExecEvalExpr(exprState, econtext, &isNull, &isDone);
229 230

	/*
A
Ashwin Agrawal 已提交
231 232 233
	 * Compute the type of the expression result. Sometimes this can be
	 * different than the type of the partition rules (MPP-25707), and we'll
	 * need this type to choose the correct comparator.
234
	 */
A
Ashwin Agrawal 已提交
235 236
	Oid			exprTypid = exprType((Node *) exprState->expr);

237
	return partition_selection(parentNode, node->accessMethods, ps->relid, value, exprTypid, isNull);
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
}

/* ----------------------------------------------------------------
 *		processLevel
 *
 *		find out satisfied PartOids for the given predicates in the
 *		given partition level
 *
 *		The function is recursively called:
 *		1. If we are in the intermediate level, we register the
 *		satisfied PartOids and continue with the next level
 *		2. If we are in the leaf level, we will propagate satisfied
 *		PartOids.
 *
 *		The return structure contains the leaf part oids and the ids of the scan
 *		operators to which they should be propagated
 *
 *		Input parameters:
 *		node: PartitionSelectorState
 *		level: the current partition level, starting with 0.
 *		inputTuple: input tuple from outer child for join partition
 *		elimination
 *
 * ----------------------------------------------------------------
 */
SelectedParts *
processLevel(PartitionSelectorState *node, int level, TupleTableSlot *inputTuple)
{
	SelectedParts *selparts = makeNode(SelectedParts);
A
Ashwin Agrawal 已提交
267

268 269 270
	selparts->partOids = NIL;
	selparts->scanIds = NIL;

A
Ashwin Agrawal 已提交
271
	Assert(NULL != node->ps.plan);
272
	PartitionSelector *ps = (PartitionSelector *) node->ps.plan;
A
Ashwin Agrawal 已提交
273 274

	Assert(level < ps->nLevels);
275 276

	/* get equality and general predicate for the current level */
A
Ashwin Agrawal 已提交
277 278
	Expr	   *equalityPredicate = (Expr *) lfirst(list_nth_cell(ps->levelEqExpressions, level));
	Expr	   *generalPredicate = (Expr *) lfirst(list_nth_cell(ps->levelExpressions, level));
279 280 281

	/* get parent PartitionNode if in level 0, it's the root PartitionNode */
	PartitionNode *parentNode = node->rootPartitionNode;
A
Ashwin Agrawal 已提交
282

283 284
	if (0 != level)
	{
A
Ashwin Agrawal 已提交
285
		Assert(NULL != node->levelPartRules[level - 1]);
286
		parentNode = node->levelPartRules[level - 1]->children;
287 288
	}

289
	/* list of PartitionRule that satisfied the predicates */
A
Ashwin Agrawal 已提交
290
	List	   *satisfiedRules = NIL;
291 292 293 294

	/* If equalityPredicate exists */
	if (NULL != equalityPredicate)
	{
A
Ashwin Agrawal 已提交
295
		Assert(NULL == generalPredicate);
296

297
		PartitionRule *chosenRule = partition_rules_for_equality_predicate(node, level, inputTuple, parentNode);
A
Ashwin Agrawal 已提交
298

299 300 301 302
		if (chosenRule != NULL)
		{
			satisfiedRules = lappend(satisfiedRules, chosenRule);
		}
303 304 305 306
	}
	/* If generalPredicate exists */
	else if (NULL != generalPredicate)
	{
A
Ashwin Agrawal 已提交
307 308
		List	   *chosenRules = partition_rules_for_general_predicate(node, level, inputTuple, parentNode);

309
		satisfiedRules = list_concat(satisfiedRules, chosenRules);
310 311 312 313 314
	}
	/* None of the predicate exists */
	else
	{
		/*
A
Ashwin Agrawal 已提交
315 316
		 * Neither equality predicate nor general predicate exists. Return all
		 * the next level PartitionRule.
317
		 *
A
Ashwin Agrawal 已提交
318 319 320 321 322 323
		 * WARNING: Do NOT use list_concat with satisfiedRules and
		 * parentNode->rules. list_concat will destructively modify
		 * satisfiedRules to point to parentNode->rules, which will then be
		 * freed when we free satisfiedRules. This does not apply when we
		 * execute partition_rules_for_general_predicate as it creates its own
		 * list.
324
		 */
A
Ashwin Agrawal 已提交
325 326 327
		ListCell   *lc = NULL;

		foreach(lc, parentNode->rules)
328 329
		{
			PartitionRule *rule = (PartitionRule *) lfirst(lc);
A
Ashwin Agrawal 已提交
330

331 332 333 334 335 336 337
			satisfiedRules = lappend(satisfiedRules, rule);
		}

		if (NULL != parentNode->default_part)
		{
			satisfiedRules = lappend(satisfiedRules, parentNode->default_part);
		}
338 339
	}

A
Ashwin Agrawal 已提交
340 341 342
	/*
	 * Based on the satisfied PartitionRules, go to next level or propagate
	 * PartOids if we are in the leaf level
343
	 */
A
Ashwin Agrawal 已提交
344 345 346
	ListCell   *lc = NULL;

	foreach(lc, satisfiedRules)
347
	{
348
		PartitionRule *rule = (PartitionRule *) lfirst(lc);
A
Ashwin Agrawal 已提交
349

350
		node->levelPartRules[level] = rule;
351 352 353 354

		/* If we already in the leaf level */
		if (level == ps->nLevels - 1)
		{
A
Ashwin Agrawal 已提交
355
			bool		shouldPropagate = true;
356 357 358 359 360

			/* if residual predicate exists */
			if (NULL != ps->residualPredicate)
			{
				/* evaluate residualPredicate */
A
Ashwin Agrawal 已提交
361 362
				ExprState  *exprstate = node->residualPredicateExprState;

363 364 365 366 367 368 369
				shouldPropagate = eval_part_qual(exprstate, node, inputTuple);
			}

			if (shouldPropagate)
			{
				if (NULL != ps->propagationExpression)
				{
370
					if (!list_member_oid(selparts->partOids, rule->parchildrelid))
371
					{
372
						selparts->partOids = lappend_oid(selparts->partOids, rule->parchildrelid);
A
Ashwin Agrawal 已提交
373 374
						int			scanId = eval_propagation_expression(node, rule->parchildrelid);

375 376 377 378 379
						selparts->scanIds = lappend_int(selparts->scanIds, scanId);
					}
				}
			}
		}
A
Ashwin Agrawal 已提交
380 381 382 383 384

		/*
		 * Recursively call this function for next level's partition
		 * elimination
		 */
385 386
		else
		{
A
Ashwin Agrawal 已提交
387 388
			SelectedParts *selpartsChild = processLevel(node, level + 1, inputTuple);

389 390 391 392 393 394
			selparts->partOids = list_concat(selparts->partOids, selpartsChild->partOids);
			selparts->scanIds = list_concat(selparts->scanIds, selpartsChild->scanIds);
			pfree(selpartsChild);
		}
	}

395
	list_free(satisfiedRules);
396

397 398
	/* After finish iteration, reset this level's PartitionRule */
	node->levelPartRules[level] = NULL;
399 400 401 402 403 404 405 406 407 408 409 410

	return selparts;
}

/* ----------------------------------------------------------------
 *		initPartitionSelection
 *
 *		Initialize partition selection state information
 *
 * ----------------------------------------------------------------
 */
PartitionSelectorState *
411
initPartitionSelection(PartitionSelector *node, EState *estate)
412 413
{
	/* create and initialize PartitionSelectorState structure */
414
	PartitionSelectorState *psstate;
A
Ashwin Agrawal 已提交
415
	ListCell   *lc;
416 417

	psstate = makeNode(PartitionSelectorState);
418 419
	psstate->ps.plan = (Plan *) node;
	psstate->ps.state = estate;
A
Ashwin Agrawal 已提交
420
	psstate->levelPartRules = (PartitionRule **) palloc0(node->nLevels * sizeof(PartitionRule *));
421

422 423
	/* ExprContext initialization */
	ExecAssignExprContext(estate, &psstate->ps);
424 425

	/* initialize ExprState for evaluating expressions */
A
Ashwin Agrawal 已提交
426
	foreach(lc, node->levelEqExpressions)
427
	{
A
Ashwin Agrawal 已提交
428 429
		Expr	   *eqExpr = (Expr *) lfirst(lc);

430
		psstate->levelEqExprStates = lappend(psstate->levelEqExprStates,
A
Ashwin Agrawal 已提交
431
											 ExecInitExpr(eqExpr, (PlanState *) psstate));
432 433
	}

A
Ashwin Agrawal 已提交
434
	foreach(lc, node->levelExpressions)
435
	{
A
Ashwin Agrawal 已提交
436 437
		Expr	   *generalExpr = (Expr *) lfirst(lc);

438
		psstate->levelExprStates = lappend(psstate->levelExprStates,
A
Ashwin Agrawal 已提交
439
										   ExecInitExpr(generalExpr, (PlanState *) psstate));
440 441 442
	}

	psstate->residualPredicateExprState = ExecInitExpr((Expr *) node->residualPredicate,
A
Ashwin Agrawal 已提交
443
													   (PlanState *) psstate);
444
	psstate->propagationExprState = ExecInitExpr((Expr *) node->propagationExpression,
A
Ashwin Agrawal 已提交
445
												 (PlanState *) psstate);
446 447

	psstate->ps.targetlist = (List *) ExecInitExpr((Expr *) node->plan.targetlist,
A
Ashwin Agrawal 已提交
448
												   (PlanState *) psstate);
449 450 451 452 453 454 455 456 457 458 459 460 461

	return psstate;
}

/* ----------------------------------------------------------------
 *		getPartitionNodeAndAccessMethod
 *
 * 		Retrieve PartitionNode and access method from root table
 *
 * ----------------------------------------------------------------
 */
void
getPartitionNodeAndAccessMethod(Oid rootOid, List *partsMetadata, MemoryContext memoryContext,
A
Ashwin Agrawal 已提交
462
								PartitionNode **partsAndRules, PartitionAccessMethods **accessMethods)
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
{
	Assert(NULL != partsMetadata);
	findPartitionMetadataEntry(partsMetadata, rootOid, partsAndRules, accessMethods);
	Assert(NULL != (*partsAndRules));
	Assert(NULL != (*accessMethods));
	(*accessMethods)->part_cxt = memoryContext;
}

/* ----------------------------------------------------------------
 *		static_part_selection
 *
 *		Statically select leaf part oids during optimization time
 *
 * ----------------------------------------------------------------
 */
SelectedParts *
static_part_selection(PartitionSelector *ps)
{
481 482 483 484 485 486 487 488 489 490 491 492
	List	   *partsMetadata;
	PartitionSelectorState *psstate;
	EState	   *estate;
	MemoryContext oldcxt;
	SelectedParts *selparts;

	estate = CreateExecutorState();

	oldcxt = MemoryContextSwitchTo(estate->es_query_cxt);

	partsMetadata = InitializePartsMetadata(ps->relid);
	psstate = initPartitionSelection(ps, estate);
493 494

	getPartitionNodeAndAccessMethod
A
Ashwin Agrawal 已提交
495 496 497 498 499 500 501
		(
		 ps->relid,
		 partsMetadata,
		 estate->es_query_cxt,
		 &psstate->rootPartitionNode,
		 &psstate->accessMethods
		);
502

503 504
	MemoryContextSwitchTo(oldcxt);

A
Ashwin Agrawal 已提交
505
	selparts = processLevel(psstate, 0 /* level */ , NULL /* inputSlot */ );
506 507

	/* cleanup */
508
	FreeExecutorState(estate);
509 510 511 512

	return selparts;
}

513 514 515 516 517 518 519 520
/*
 * Generate a map for change_varattnos_of_a_node from old and new TupleDesc's,
 * matching according to column name. This function returns a NULL pointer (i.e.
 * null map) if no mapping is necessary (i.e., old and new TupleDesc are already
 * aligned).
 *
 * This function, and change_varattnos_of_a_varno below, used to be in
 * tablecmds.c, but were removed in upstream commit 188a0a00. But we still need
D
Daniel Gustafsson 已提交
521
 * this for dynamic partition selection in GPDB, so copied them here.
522 523 524 525 526 527 528 529
 */
AttrNumber *
varattnos_map(TupleDesc old, TupleDesc new)
{
	AttrNumber *attmap;
	int			i,
				j;

A
Ashwin Agrawal 已提交
530
	bool		mapRequired = false;
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578

	attmap = (AttrNumber *) palloc0(sizeof(AttrNumber) * old->natts);
	for (i = 1; i <= old->natts; i++)
	{
		if (old->attrs[i - 1]->attisdropped)
			continue;			/* leave the entry as zero */

		for (j = 1; j <= new->natts; j++)
		{
			if (strcmp(NameStr(old->attrs[i - 1]->attname),
					   NameStr(new->attrs[j - 1]->attname)) == 0)
			{
				attmap[i - 1] = j;

				if (i != j)
				{
					mapRequired = true;
				}
				break;
			}
		}
	}

	if (!mapRequired)
	{
		pfree(attmap);

		/* No mapping required, so return NULL */
		attmap = NULL;
	}

	return attmap;
}

/*
 * Replace varattno values in an expression tree according to the given
 * map array, that is, varattno N is replaced by newattno[N-1].  It is
 * caller's responsibility to ensure that the array is long enough to
 * define values for all user varattnos present in the tree.  System column
 * attnos remain unchanged. For historical reason, we only map varattno of the first
 * range table entry from this method. So, we call the more general
 * change_varattnos_of_a_varno() with varno set to 1
 *
 * Note that the passed node tree is modified in-place!
 */
void
change_varattnos_of_a_node(Node *node, const AttrNumber *newattno)
{
A
Ashwin Agrawal 已提交
579 580 581 582
	/*
	 * Only attempt re-mapping if re-mapping is necessary (i.e., non-null
	 * newattno map)
	 */
583 584
	if (newattno)
	{
A
Ashwin Agrawal 已提交
585 586 587
		change_varattnos_of_a_varno(node, newattno, 1	/* varno is hard-coded
														 * to 1 (i.e., only
									  * first RTE) */ );
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
	}
}

/*
 * Replace varattno values for a given varno RTE index in an expression
 * tree according to the given map array, that is, varattno N is replaced
 * by newattno[N-1].  It is caller's responsibility to ensure that the array
 * is long enough to define values for all user varattnos present in the tree.
 * System column attnos remain unchanged.
 *
 * Note that the passed node tree is modified in-place!
 */
void
change_varattnos_of_a_varno(Node *node, const AttrNumber *newattno, Index varno)
{
	AttrMapContext attrMapCxt;

	attrMapCxt.newattno = newattno;
	attrMapCxt.varno = varno;

	(void) change_varattnos_varno_walker(node, &attrMapCxt);
}

/*
 * Remaps the varattno of a varattno in a Var node using an attribute map.
 */
static bool
change_varattnos_varno_walker(Node *node, const AttrMapContext *attrMapCxt)
{
	if (node == NULL)
		return false;
	if (IsA(node, Var))
	{
		Var		   *var = (Var *) node;

		if (var->varlevelsup == 0 && (var->varno == attrMapCxt->varno) &&
			var->varattno > 0)
		{
			/*
			 * ??? the following may be a problem when the node is multiply
			 * referenced though stringToNode() doesn't create such a node
			 * currently.
			 */
			Assert(attrMapCxt->newattno[var->varattno - 1] > 0);
			var->varattno = var->varoattno = attrMapCxt->newattno[var->varattno - 1];
		}
		return false;
	}
	return expression_tree_walker(node, change_varattnos_varno_walker,
								  (void *) attrMapCxt);
}