Skip to content

Commit dc77be0

Browse files
committed
Fix executor to work correctly with mergejoins where left and
right sides have different data types.
1 parent 98f7394 commit dc77be0

File tree

2 files changed

+80
-123
lines changed

2 files changed

+80
-123
lines changed

src/backend/executor/nodeMergejoin.c

Lines changed: 75 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*
88
*
99
* IDENTIFICATION
10-
* $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.24 1999/02/24 10:20:07 momjian Exp $
10+
* $Header: /cvsroot/pgsql/src/backend/executor/nodeMergejoin.c,v 1.25 1999/02/28 00:36:05 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -67,6 +67,7 @@
6767
#include "postgres.h"
6868

6969
#include "access/heapam.h"
70+
#include "catalog/pg_operator.h"
7071
#include "executor/executor.h"
7172
#include "executor/execdefs.h"
7273
#include "executor/nodeMergejoin.h"
@@ -87,28 +88,31 @@ static bool MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
8788

8889

8990
/* ----------------------------------------------------------------
90-
* MJFormOSortopI
91+
* MJFormSkipQual
9192
*
9293
* This takes the mergeclause which is a qualification of the
9394
* form ((= expr expr) (= expr expr) ...) and forms a new
9495
* qualification like ((> expr expr) (> expr expr) ...) which
9596
* is used by ExecMergeJoin() in order to determine if we should
96-
* skip tuples.
97-
*
98-
* old comments
99-
* The 'qual' must be of the form:
100-
* {(= outerkey1 innerkey1)(= outerkey2 innerkey2) ...}
101-
* The "sortOp outerkey innerkey" is formed by substituting the "="
102-
* by "sortOp".
97+
* skip tuples. The replacement operators are named either ">"
98+
* or "<" according to the replaceopname parameter, and have the
99+
* same operand data types as the "=" operators they replace.
100+
* (We expect there to be such operators because the "=" operators
101+
* were marked mergejoinable; however, there might be a different
102+
* one needed in each qual clause.)
103103
* ----------------------------------------------------------------
104104
*/
105105
static List *
106-
MJFormOSortopI(List *qualList, Oid sortOp)
106+
MJFormSkipQual(List *qualList, char * replaceopname)
107107
{
108108
List *qualCopy;
109109
List *qualcdr;
110110
Expr *qual;
111111
Oper *op;
112+
HeapTuple optup;
113+
Form_pg_operator opform;
114+
Oid oprleft,
115+
oprright;
112116

113117
/* ----------------
114118
* qualList is a list: ((op .. ..) ...)
@@ -132,73 +136,50 @@ MJFormOSortopI(List *qualList, Oid sortOp)
132136
*/
133137
op = (Oper *) qual->oper;
134138
if (!IsA(op, Oper))
135-
{
136-
elog(DEBUG, "MJFormOSortopI: op not an Oper!");
137-
return NIL;
138-
}
139+
elog(ERROR, "MJFormSkipQual: op not an Oper!");
139140

140141
/* ----------------
141-
* change it's opid and since Op nodes now carry around a
142-
* cached pointer to the associated op function, we have
143-
* to make sure we invalidate this. Otherwise you get bizarre
144-
* behavior when someone runs a mergejoin with _exec_repeat_ > 1
145-
* -cim 4/23/91
142+
* Get the declared left and right operand types of the operator.
143+
* Note we do *not* use the actual operand types, since those might
144+
* be different in scenarios with binary-compatible data types.
145+
* There should be "<" and ">" operators matching a mergejoinable
146+
* "=" operator's declared operand types, but we might not find them
147+
* if we search with the actual operand types.
146148
* ----------------
147149
*/
148-
op->opid = sortOp;
149-
op->op_fcache = NULL;
150-
}
151-
152-
return qualCopy;
153-
}
154-
155-
/* ----------------------------------------------------------------
156-
* MJFormISortopO
157-
*
158-
* This does the same thing as MJFormOSortopI() except that
159-
* it also reverses the expressions in the qualifications.
160-
* For example: ((= expr1 expr2)) produces ((> expr2 expr1))
161-
*
162-
* old comments
163-
* The 'qual' must be of the form:
164-
* {(= outerkey1 innerkey1) (= outerkey2 innerkey2) ...}
165-
* The 'sortOp innerkey1 outerkey" is formed by substituting the "="
166-
* by "sortOp" and reversing the positions of the keys.
167-
* ----------------------------------------------------------------
168-
*/
169-
static List *
170-
MJFormISortopO(List *qualList, Oid sortOp)
171-
{
172-
List *ISortopO;
173-
List *qualcdr;
174-
175-
/* ----------------
176-
* first generate OSortopI, a list of the form
177-
* ((op outer inner) (op outer inner) ... )
178-
* ----------------
179-
*/
180-
ISortopO = MJFormOSortopI(qualList, sortOp);
181-
182-
/* ----------------
183-
* now swap the cadr and caddr of each qual to form ISortopO,
184-
* ((op inner outer) (op inner outer) ... )
185-
* ----------------
186-
*/
187-
foreach(qualcdr, ISortopO)
188-
{
189-
Expr *qual;
190-
List *inner;
191-
List *outer;
150+
optup = get_operator_tuple(op->opno);
151+
if (!HeapTupleIsValid(optup)) /* shouldn't happen */
152+
elog(ERROR, "MJFormSkipQual: operator %d not found", op->opno);
153+
opform = (Form_pg_operator) GETSTRUCT(optup);
154+
oprleft = opform->oprleft;
155+
oprright = opform->oprright;
192156

193-
qual = lfirst(qualcdr);
157+
/* ----------------
158+
* Now look up the matching "<" or ">" operator. If there isn't one,
159+
* whoever marked the "=" operator mergejoinable was a loser.
160+
* ----------------
161+
*/
162+
optup = SearchSysCacheTuple(OPRNAME,
163+
PointerGetDatum(replaceopname),
164+
ObjectIdGetDatum(oprleft),
165+
ObjectIdGetDatum(oprright),
166+
CharGetDatum('b'));
167+
if (!HeapTupleIsValid(optup))
168+
elog(ERROR,
169+
"MJFormSkipQual: mergejoin operator %d has no matching %s op",
170+
op->opno, replaceopname);
171+
opform = (Form_pg_operator) GETSTRUCT(optup);
194172

195-
inner = lfirst(qual->args);
196-
outer = lfirst(lnext(qual->args));
197-
lfirst(qual->args) = outer;
198-
lfirst(lnext(qual->args)) = inner;
173+
/* ----------------
174+
* And replace the data in the copied operator node.
175+
* ----------------
176+
*/
177+
op->opno = optup->t_data->t_oid;
178+
op->opid = opform->oprcode;
179+
op->op_fcache = NULL;
199180
}
200181

201-
return ISortopO;
182+
return qualCopy;
202183
}
203184

204185
/* ----------------------------------------------------------------
@@ -215,6 +196,7 @@ MJFormISortopO(List *qualList, Oid sortOp)
215196
* the first keys being most significant. Therefore, the clauses
216197
* are evaluated in order and the 'compareQual' is satisfied
217198
* if (key1i > key2i) is true and (key1j = key2j) for 0 < j < i.
199+
* We use the original mergeclause items to detect equality.
218200
* ----------------------------------------------------------------
219201
*/
220202
static bool
@@ -386,12 +368,16 @@ ExecMergeTupleDump(ExprContext *econtext, MergeJoinState *mergestate)
386368
*
387369
* Therefore, when initializing the merge-join node, the executor
388370
* creates the "greater/smaller" clause by substituting the "="
389-
* operator in the join clauses with the sort operator used to
390-
* sort the outer and inner relation forming (outerKey sortOp innerKey).
391-
* The sort operator is "<" if the relations are in ascending order
392-
* otherwise, it is ">" if the relations are in descending order.
393-
* The opposite "smaller/greater" clause is formed by reversing the
394-
* outer and inner keys forming (innerKey sortOp outerKey).
371+
* operator in the join clauses with the corresponding ">" operator.
372+
* The opposite "smaller/greater" clause is formed by substituting "<".
373+
*
374+
* Note: prior to v6.5, the relational clauses were formed using the
375+
* sort op used to sort the inner relation, which of course would fail
376+
* if the outer and inner keys were of different data types.
377+
* In the current code, we instead assume that operators named "<" and ">"
378+
* will do the right thing. This should be true since the mergejoin "="
379+
* operator's pg_operator entry will have told the planner to sort by
380+
* "<" for each of the left and right sides.
395381
*
396382
* (2) repositioning inner "cursor"
397383
*
@@ -452,13 +438,13 @@ ExecMergeJoin(MergeJoin *node)
452438

453439
if (ScanDirectionIsForward(direction))
454440
{
455-
outerSkipQual = mergestate->mj_OSortopI;
456-
innerSkipQual = mergestate->mj_ISortopO;
441+
outerSkipQual = mergestate->mj_OuterSkipQual;
442+
innerSkipQual = mergestate->mj_InnerSkipQual;
457443
}
458444
else
459445
{
460-
outerSkipQual = mergestate->mj_ISortopO;
461-
innerSkipQual = mergestate->mj_OSortopI;
446+
outerSkipQual = mergestate->mj_InnerSkipQual;
447+
innerSkipQual = mergestate->mj_OuterSkipQual;
462448
}
463449

464450
/* ----------------
@@ -1130,14 +1116,8 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
11301116
{
11311117
MergeJoinState *mergestate;
11321118
List *joinclauses;
1133-
RegProcedure rightsortop;
1134-
RegProcedure leftsortop;
1135-
RegProcedure sortop;
11361119
TupleTableSlot *mjSlot;
11371120

1138-
List *OSortopI;
1139-
List *ISortopO;
1140-
11411121
MJ1_printf("ExecInitMergeJoin: %s\n",
11421122
"initializing node");
11431123

@@ -1153,14 +1133,14 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
11531133
* ----------------
11541134
*/
11551135
mergestate = makeNode(MergeJoinState);
1156-
mergestate->mj_OSortopI = NIL;
1157-
mergestate->mj_ISortopO = NIL;
1136+
mergestate->mj_OuterSkipQual = NIL;
1137+
mergestate->mj_InnerSkipQual = NIL;
11581138
mergestate->mj_JoinState = 0;
11591139
mergestate->mj_MarkedTupleSlot = NULL;
11601140
node->mergestate = mergestate;
11611141

11621142
/* ----------------
1163-
* Miscellanious initialization
1143+
* Miscellaneous initialization
11641144
*
11651145
* + assign node's base_id
11661146
* + assign debugging hooks and
@@ -1185,40 +1165,17 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, Plan *parent)
11851165
mergestate->mj_MarkedTupleSlot = mjSlot;
11861166

11871167
/* ----------------
1188-
* get merge sort operators.
1189-
*
1190-
* XXX for now we assume all quals in the joinclauses were
1191-
* sorted with the same operator in both the inner and
1192-
* outer relations. -cim 11/2/89
1168+
* form merge skip qualifications
11931169
* ----------------
11941170
*/
11951171
joinclauses = node->mergeclauses;
1172+
mergestate->mj_OuterSkipQual = MJFormSkipQual(joinclauses, "<");
1173+
mergestate->mj_InnerSkipQual = MJFormSkipQual(joinclauses, ">");
11961174

1197-
rightsortop = get_opcode(node->mergerightorder[0]);
1198-
leftsortop = get_opcode(node->mergeleftorder[0]);
1199-
1200-
if (leftsortop != rightsortop)
1201-
elog(NOTICE, "ExecInitMergeJoin: %s",
1202-
"left and right sortop's are unequal!");
1203-
1204-
sortop = rightsortop;
1205-
1206-
/* ----------------
1207-
* form merge skip qualifications
1208-
*
1209-
* XXX MJform routines need to be extended
1210-
* to take a list of sortops.. -cim 11/2/89
1211-
* ----------------
1212-
*/
1213-
OSortopI = MJFormOSortopI(joinclauses, sortop);
1214-
ISortopO = MJFormISortopO(joinclauses, sortop);
1215-
mergestate->mj_OSortopI = OSortopI;
1216-
mergestate->mj_ISortopO = ISortopO;
1217-
1218-
MJ_printf("\nExecInitMergeJoin: OSortopI is ");
1219-
MJ_nodeDisplay(OSortopI);
1220-
MJ_printf("\nExecInitMergeJoin: ISortopO is ");
1221-
MJ_nodeDisplay(ISortopO);
1175+
MJ_printf("\nExecInitMergeJoin: OuterSkipQual is ");
1176+
MJ_nodeDisplay(mergestate->mj_OuterSkipQual);
1177+
MJ_printf("\nExecInitMergeJoin: InnerSkipQual is ");
1178+
MJ_nodeDisplay(mergestate->mj_InnerSkipQual);
12221179
MJ_printf("\n");
12231180

12241181
/* ----------------

src/include/nodes/execnodes.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*
77
* Copyright (c) 1994, Regents of the University of California
88
*
9-
* $Id: execnodes.h,v 1.25 1999/02/23 07:55:23 thomas Exp $
9+
* $Id: execnodes.h,v 1.26 1999/02/28 00:36:04 tgl Exp $
1010
*
1111
*-------------------------------------------------------------------------
1212
*/
@@ -463,8 +463,8 @@ typedef struct NestLoopState
463463
/* ----------------
464464
* MergeJoinState information
465465
*
466-
* OSortopI outerKey1 sortOp innerKey1 ...
467-
* ISortopO innerkey1 sortOp outerkey1 ...
466+
* OuterSkipQual outerKey1 < innerKey1 ...
467+
* InnerSkipQual outerKey1 > innerKey1 ...
468468
* JoinState current "state" of join. see executor.h
469469
* MarkedTupleSlot pointer to slot in tuple table for marked tuple
470470
*
@@ -483,8 +483,8 @@ typedef struct NestLoopState
483483
typedef struct MergeJoinState
484484
{
485485
JoinState jstate; /* its first field is NodeTag */
486-
List *mj_OSortopI;
487-
List *mj_ISortopO;
486+
List *mj_OuterSkipQual;
487+
List *mj_InnerSkipQual;
488488
int mj_JoinState;
489489
TupleTableSlot *mj_MarkedTupleSlot;
490490
} MergeJoinState;

0 commit comments

Comments
 (0)