3 * Implementation of inclusion opclasses for BRIN
5 * This module provides framework BRIN support functions for the "inclusion"
6 * operator classes. A few SQL-level support functions are also required for
9 * The "inclusion" BRIN strategy is useful for types that support R-Tree
10 * operations. This implementation is a straight mapping of those operations
11 * to the block-range nature of BRIN, with two exceptions: (a) we explicitly
12 * support "empty" elements: at least with range types, we need to consider
13 * emptiness separately from regular R-Tree strategies; and (b) we need to
14 * consider "unmergeable" elements, that is, a set of elements for whose union
15 * no representation exists. The only case where that happens as of this
16 * writing is the INET type, where IPv6 values cannot be merged with IPv4
19 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
20 * Portions Copyright (c) 1994, Regents of the University of California
23 * src/backend/access/brin/brin_inclusion.c
27 #include "access/brin_internal.h"
28 #include "access/brin_tuple.h"
29 #include "access/genam.h"
30 #include "access/skey.h"
31 #include "catalog/pg_amop.h"
32 #include "catalog/pg_type.h"
33 #include "utils/datum.h"
34 #include "utils/fmgrprotos.h"
35 #include "utils/lsyscache.h"
36 #include "utils/rel.h"
37 #include "utils/syscache.h"
41 * Additional SQL level support functions
43 * Procedure numbers must not use values reserved for BRIN itself; see
46 #define INCLUSION_MAX_PROCNUMS 4 /* maximum support procs we need */
47 #define PROCNUM_MERGE 11 /* required */
48 #define PROCNUM_MERGEABLE 12 /* optional */
49 #define PROCNUM_CONTAINS 13 /* optional */
50 #define PROCNUM_EMPTY 14 /* optional */
54 * Subtract this from procnum to obtain index in InclusionOpaque arrays
55 * (Must be equal to minimum of private procnums).
57 #define PROCNUM_BASE 11
60 * The values stored in the bv_values arrays correspond to:
63 * the union of the values in the block range
64 * INCLUSION_UNMERGEABLE
65 * whether the values in the block range cannot be merged
66 * (e.g. an IPv6 address amidst IPv4 addresses)
67 * INCLUSION_CONTAINS_EMPTY
68 * whether an empty value is present in any tuple
71 #define INCLUSION_UNION 0
72 #define INCLUSION_UNMERGEABLE 1
73 #define INCLUSION_CONTAINS_EMPTY 2
76 typedef struct InclusionOpaque
78 FmgrInfo extra_procinfos
[INCLUSION_MAX_PROCNUMS
];
79 bool extra_proc_missing
[INCLUSION_MAX_PROCNUMS
];
81 FmgrInfo strategy_procinfos
[RTMaxStrategyNumber
];
84 static FmgrInfo
*inclusion_get_procinfo(BrinDesc
*bdesc
, uint16 attno
,
86 static FmgrInfo
*inclusion_get_strategy_procinfo(BrinDesc
*bdesc
, uint16 attno
,
87 Oid subtype
, uint16 strategynum
);
91 * BRIN inclusion OpcInfo function
94 brin_inclusion_opcinfo(PG_FUNCTION_ARGS
)
96 Oid typoid
= PG_GETARG_OID(0);
98 TypeCacheEntry
*bool_typcache
= lookup_type_cache(BOOLOID
, 0);
101 * All members of opaque are initialized lazily; both procinfo arrays
102 * start out as non-initialized by having fn_oid be InvalidOid, and
103 * "missing" to false, by zeroing here. strategy_procinfos elements can
104 * be invalidated when cached_subtype changes by zeroing fn_oid.
105 * extra_procinfo entries are never invalidated, but if a lookup fails
106 * (which is expected), extra_proc_missing is set to true, indicating not
107 * to look it up again.
109 result
= palloc0(MAXALIGN(SizeofBrinOpcInfo(3)) + sizeof(InclusionOpaque
));
110 result
->oi_nstored
= 3;
111 result
->oi_regular_nulls
= true;
112 result
->oi_opaque
= (InclusionOpaque
*)
113 MAXALIGN((char *) result
+ SizeofBrinOpcInfo(3));
116 result
->oi_typcache
[INCLUSION_UNION
] =
117 lookup_type_cache(typoid
, 0);
119 /* includes elements that are not mergeable */
120 result
->oi_typcache
[INCLUSION_UNMERGEABLE
] = bool_typcache
;
122 /* includes the empty element */
123 result
->oi_typcache
[INCLUSION_CONTAINS_EMPTY
] = bool_typcache
;
125 PG_RETURN_POINTER(result
);
129 * BRIN inclusion add value function
131 * Examine the given index tuple (which contains partial status of a certain
132 * page range) by comparing it to the given value that comes from another heap
133 * tuple. If the new value is outside the union specified by the existing
134 * tuple values, update the index tuple and return true. Otherwise, return
135 * false and do not modify in this case.
138 brin_inclusion_add_value(PG_FUNCTION_ARGS
)
140 BrinDesc
*bdesc
= (BrinDesc
*) PG_GETARG_POINTER(0);
141 BrinValues
*column
= (BrinValues
*) PG_GETARG_POINTER(1);
142 Datum newval
= PG_GETARG_DATUM(2);
143 bool isnull PG_USED_FOR_ASSERTS_ONLY
= PG_GETARG_BOOL(3);
144 Oid colloid
= PG_GET_COLLATION();
149 CompactAttribute
*attr
;
153 attno
= column
->bv_attno
;
154 attr
= TupleDescCompactAttr(bdesc
->bd_tupdesc
, attno
- 1);
157 * If the recorded value is null, copy the new value (which we know to be
158 * not null), and we're almost done.
160 if (column
->bv_allnulls
)
162 column
->bv_values
[INCLUSION_UNION
] =
163 datumCopy(newval
, attr
->attbyval
, attr
->attlen
);
164 column
->bv_values
[INCLUSION_UNMERGEABLE
] = BoolGetDatum(false);
165 column
->bv_values
[INCLUSION_CONTAINS_EMPTY
] = BoolGetDatum(false);
166 column
->bv_allnulls
= false;
171 * No need for further processing if the block range is marked as
172 * containing unmergeable values.
174 if (DatumGetBool(column
->bv_values
[INCLUSION_UNMERGEABLE
]))
175 PG_RETURN_BOOL(false);
178 * If the opclass supports the concept of empty values, test the passed
179 * new value for emptiness; if it returns true, we need to set the
180 * "contains empty" flag in the element (unless already set).
182 finfo
= inclusion_get_procinfo(bdesc
, attno
, PROCNUM_EMPTY
);
183 if (finfo
!= NULL
&& DatumGetBool(FunctionCall1Coll(finfo
, colloid
, newval
)))
185 if (!DatumGetBool(column
->bv_values
[INCLUSION_CONTAINS_EMPTY
]))
187 column
->bv_values
[INCLUSION_CONTAINS_EMPTY
] = BoolGetDatum(true);
188 PG_RETURN_BOOL(true);
191 PG_RETURN_BOOL(false);
195 PG_RETURN_BOOL(true);
197 /* Check if the new value is already contained. */
198 finfo
= inclusion_get_procinfo(bdesc
, attno
, PROCNUM_CONTAINS
);
200 DatumGetBool(FunctionCall2Coll(finfo
, colloid
,
201 column
->bv_values
[INCLUSION_UNION
],
203 PG_RETURN_BOOL(false);
206 * Check if the new value is mergeable to the existing union. If it is
207 * not, mark the value as containing unmergeable elements and get out.
209 * Note: at this point we could remove the value from the union, since
210 * it's not going to be used any longer. However, the BRIN framework
211 * doesn't allow for the value not being present. Improve someday.
213 finfo
= inclusion_get_procinfo(bdesc
, attno
, PROCNUM_MERGEABLE
);
215 !DatumGetBool(FunctionCall2Coll(finfo
, colloid
,
216 column
->bv_values
[INCLUSION_UNION
],
219 column
->bv_values
[INCLUSION_UNMERGEABLE
] = BoolGetDatum(true);
220 PG_RETURN_BOOL(true);
223 /* Finally, merge the new value to the existing union. */
224 finfo
= inclusion_get_procinfo(bdesc
, attno
, PROCNUM_MERGE
);
225 Assert(finfo
!= NULL
);
226 result
= FunctionCall2Coll(finfo
, colloid
,
227 column
->bv_values
[INCLUSION_UNION
], newval
);
228 if (!attr
->attbyval
&&
229 DatumGetPointer(result
) != DatumGetPointer(column
->bv_values
[INCLUSION_UNION
]))
231 pfree(DatumGetPointer(column
->bv_values
[INCLUSION_UNION
]));
233 if (result
== newval
)
234 result
= datumCopy(result
, attr
->attbyval
, attr
->attlen
);
236 column
->bv_values
[INCLUSION_UNION
] = result
;
238 PG_RETURN_BOOL(true);
242 * BRIN inclusion consistent function
244 * We're no longer dealing with NULL keys in the consistent function, that is
245 * now handled by the AM code. That means we should not get any all-NULL ranges
246 * either, because those can't be consistent with regular (not [IS] NULL) keys.
248 * All of the strategies are optional.
251 brin_inclusion_consistent(PG_FUNCTION_ARGS
)
253 BrinDesc
*bdesc
= (BrinDesc
*) PG_GETARG_POINTER(0);
254 BrinValues
*column
= (BrinValues
*) PG_GETARG_POINTER(1);
255 ScanKey key
= (ScanKey
) PG_GETARG_POINTER(2);
256 Oid colloid
= PG_GET_COLLATION(),
264 /* This opclass uses the old signature with only three arguments. */
265 Assert(PG_NARGS() == 3);
267 /* Should not be dealing with all-NULL ranges. */
268 Assert(!column
->bv_allnulls
);
270 /* It has to be checked, if it contains elements that are not mergeable. */
271 if (DatumGetBool(column
->bv_values
[INCLUSION_UNMERGEABLE
]))
272 PG_RETURN_BOOL(true);
274 attno
= key
->sk_attno
;
275 subtype
= key
->sk_subtype
;
276 query
= key
->sk_argument
;
277 unionval
= column
->bv_values
[INCLUSION_UNION
];
278 switch (key
->sk_strategy
)
281 * Placement strategies
283 * These are implemented by logically negating the result of the
284 * converse placement operator; for this to work, the converse
285 * operator must be part of the opclass. An error will be thrown
286 * by inclusion_get_strategy_procinfo() if the required strategy
287 * is not part of the opclass.
289 * These all return false if either argument is empty, so there is
290 * no need to check for empty elements.
293 case RTLeftStrategyNumber
:
294 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
295 RTOverRightStrategyNumber
);
296 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
297 PG_RETURN_BOOL(!DatumGetBool(result
));
299 case RTOverLeftStrategyNumber
:
300 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
301 RTRightStrategyNumber
);
302 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
303 PG_RETURN_BOOL(!DatumGetBool(result
));
305 case RTOverRightStrategyNumber
:
306 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
307 RTLeftStrategyNumber
);
308 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
309 PG_RETURN_BOOL(!DatumGetBool(result
));
311 case RTRightStrategyNumber
:
312 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
313 RTOverLeftStrategyNumber
);
314 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
315 PG_RETURN_BOOL(!DatumGetBool(result
));
317 case RTBelowStrategyNumber
:
318 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
319 RTOverAboveStrategyNumber
);
320 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
321 PG_RETURN_BOOL(!DatumGetBool(result
));
323 case RTOverBelowStrategyNumber
:
324 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
325 RTAboveStrategyNumber
);
326 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
327 PG_RETURN_BOOL(!DatumGetBool(result
));
329 case RTOverAboveStrategyNumber
:
330 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
331 RTBelowStrategyNumber
);
332 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
333 PG_RETURN_BOOL(!DatumGetBool(result
));
335 case RTAboveStrategyNumber
:
336 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
337 RTOverBelowStrategyNumber
);
338 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
339 PG_RETURN_BOOL(!DatumGetBool(result
));
342 * Overlap and contains strategies
344 * These strategies are simple enough that we can simply call the
345 * operator and return its result. Empty elements don't change
349 case RTOverlapStrategyNumber
:
350 case RTContainsStrategyNumber
:
351 case RTContainsElemStrategyNumber
:
352 case RTSubStrategyNumber
:
353 case RTSubEqualStrategyNumber
:
354 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
356 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
357 PG_RETURN_DATUM(result
);
360 * Contained by strategies
362 * We cannot just call the original operator for the contained by
363 * strategies because some elements can be contained even though
364 * the union is not; instead we use the overlap operator.
366 * We check for empty elements separately as they are not merged
367 * to the union but contained by everything.
370 case RTContainedByStrategyNumber
:
371 case RTSuperStrategyNumber
:
372 case RTSuperEqualStrategyNumber
:
373 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
374 RTOverlapStrategyNumber
);
375 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
376 if (DatumGetBool(result
))
377 PG_RETURN_BOOL(true);
379 PG_RETURN_DATUM(column
->bv_values
[INCLUSION_CONTAINS_EMPTY
]);
384 * We test for overlap first but to be safe we need to call the
385 * actual adjacent operator also.
387 * An empty element cannot be adjacent to any other, so there is
388 * no need to check for it.
391 case RTAdjacentStrategyNumber
:
392 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
393 RTOverlapStrategyNumber
);
394 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
395 if (DatumGetBool(result
))
396 PG_RETURN_BOOL(true);
398 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
399 RTAdjacentStrategyNumber
);
400 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
401 PG_RETURN_DATUM(result
);
404 * Basic comparison strategies
406 * It is straightforward to support the equality strategies with
407 * the contains operator. Generally, inequality strategies do not
408 * make much sense for the types which will be used with the
409 * inclusion BRIN family of opclasses, but it is possible to
410 * implement them with logical negation of the left-of and
411 * right-of operators.
413 * NB: These strategies cannot be used with geometric datatypes
414 * that use comparison of areas! The only exception is the "same"
417 * Empty elements are considered to be less than the others. We
418 * cannot use the empty support function to check the query is an
419 * empty element, because the query can be another data type than
420 * the empty support function argument. So we will return true,
421 * if there is a possibility that empty elements will change the
425 case RTLessStrategyNumber
:
426 case RTLessEqualStrategyNumber
:
427 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
428 RTRightStrategyNumber
);
429 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
430 if (!DatumGetBool(result
))
431 PG_RETURN_BOOL(true);
433 PG_RETURN_DATUM(column
->bv_values
[INCLUSION_CONTAINS_EMPTY
]);
435 case RTSameStrategyNumber
:
436 case RTEqualStrategyNumber
:
437 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
438 RTContainsStrategyNumber
);
439 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
440 if (DatumGetBool(result
))
441 PG_RETURN_BOOL(true);
443 PG_RETURN_DATUM(column
->bv_values
[INCLUSION_CONTAINS_EMPTY
]);
445 case RTGreaterEqualStrategyNumber
:
446 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
447 RTLeftStrategyNumber
);
448 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
449 if (!DatumGetBool(result
))
450 PG_RETURN_BOOL(true);
452 PG_RETURN_DATUM(column
->bv_values
[INCLUSION_CONTAINS_EMPTY
]);
454 case RTGreaterStrategyNumber
:
455 /* no need to check for empty elements */
456 finfo
= inclusion_get_strategy_procinfo(bdesc
, attno
, subtype
,
457 RTLeftStrategyNumber
);
458 result
= FunctionCall2Coll(finfo
, colloid
, unionval
, query
);
459 PG_RETURN_BOOL(!DatumGetBool(result
));
462 /* shouldn't happen */
463 elog(ERROR
, "invalid strategy number %d", key
->sk_strategy
);
464 PG_RETURN_BOOL(false);
469 * BRIN inclusion union function
471 * Given two BrinValues, update the first of them as a union of the summary
472 * values contained in both. The second one is untouched.
475 brin_inclusion_union(PG_FUNCTION_ARGS
)
477 BrinDesc
*bdesc
= (BrinDesc
*) PG_GETARG_POINTER(0);
478 BrinValues
*col_a
= (BrinValues
*) PG_GETARG_POINTER(1);
479 BrinValues
*col_b
= (BrinValues
*) PG_GETARG_POINTER(2);
480 Oid colloid
= PG_GET_COLLATION();
482 CompactAttribute
*attr
;
486 Assert(col_a
->bv_attno
== col_b
->bv_attno
);
487 Assert(!col_a
->bv_allnulls
&& !col_b
->bv_allnulls
);
489 attno
= col_a
->bv_attno
;
490 attr
= TupleDescCompactAttr(bdesc
->bd_tupdesc
, attno
- 1);
492 /* If B includes empty elements, mark A similarly, if needed. */
493 if (!DatumGetBool(col_a
->bv_values
[INCLUSION_CONTAINS_EMPTY
]) &&
494 DatumGetBool(col_b
->bv_values
[INCLUSION_CONTAINS_EMPTY
]))
495 col_a
->bv_values
[INCLUSION_CONTAINS_EMPTY
] = BoolGetDatum(true);
497 /* Check if A includes elements that are not mergeable. */
498 if (DatumGetBool(col_a
->bv_values
[INCLUSION_UNMERGEABLE
]))
501 /* If B includes elements that are not mergeable, mark A similarly. */
502 if (DatumGetBool(col_b
->bv_values
[INCLUSION_UNMERGEABLE
]))
504 col_a
->bv_values
[INCLUSION_UNMERGEABLE
] = BoolGetDatum(true);
508 /* Check if A and B are mergeable; if not, mark A unmergeable. */
509 finfo
= inclusion_get_procinfo(bdesc
, attno
, PROCNUM_MERGEABLE
);
511 !DatumGetBool(FunctionCall2Coll(finfo
, colloid
,
512 col_a
->bv_values
[INCLUSION_UNION
],
513 col_b
->bv_values
[INCLUSION_UNION
])))
515 col_a
->bv_values
[INCLUSION_UNMERGEABLE
] = BoolGetDatum(true);
519 /* Finally, merge B to A. */
520 finfo
= inclusion_get_procinfo(bdesc
, attno
, PROCNUM_MERGE
);
521 Assert(finfo
!= NULL
);
522 result
= FunctionCall2Coll(finfo
, colloid
,
523 col_a
->bv_values
[INCLUSION_UNION
],
524 col_b
->bv_values
[INCLUSION_UNION
]);
525 if (!attr
->attbyval
&&
526 DatumGetPointer(result
) != DatumGetPointer(col_a
->bv_values
[INCLUSION_UNION
]))
528 pfree(DatumGetPointer(col_a
->bv_values
[INCLUSION_UNION
]));
530 if (result
== col_b
->bv_values
[INCLUSION_UNION
])
531 result
= datumCopy(result
, attr
->attbyval
, attr
->attlen
);
533 col_a
->bv_values
[INCLUSION_UNION
] = result
;
539 * Cache and return inclusion opclass support procedure
541 * Return the procedure corresponding to the given function support number
542 * or null if it is not exists.
545 inclusion_get_procinfo(BrinDesc
*bdesc
, uint16 attno
, uint16 procnum
)
547 InclusionOpaque
*opaque
;
548 uint16 basenum
= procnum
- PROCNUM_BASE
;
551 * We cache these in the opaque struct, to avoid repetitive syscache
554 opaque
= (InclusionOpaque
*) bdesc
->bd_info
[attno
- 1]->oi_opaque
;
557 * If we already searched for this proc and didn't find it, don't bother
560 if (opaque
->extra_proc_missing
[basenum
])
563 if (opaque
->extra_procinfos
[basenum
].fn_oid
== InvalidOid
)
565 if (RegProcedureIsValid(index_getprocid(bdesc
->bd_index
, attno
,
568 fmgr_info_copy(&opaque
->extra_procinfos
[basenum
],
569 index_getprocinfo(bdesc
->bd_index
, attno
, procnum
),
574 opaque
->extra_proc_missing
[basenum
] = true;
579 return &opaque
->extra_procinfos
[basenum
];
583 * Cache and return the procedure of the given strategy
585 * Return the procedure corresponding to the given sub-type and strategy
586 * number. The data type of the index will be used as the left hand side of
587 * the operator and the given sub-type will be used as the right hand side.
588 * Throws an error if the pg_amop row does not exist, but that should not
589 * happen with a properly configured opclass.
591 * It always throws an error when the data type of the opclass is different
592 * from the data type of the column or the expression. That happens when the
593 * column data type has implicit cast to the opclass data type. We don't
594 * bother casting types, because this situation can easily be avoided by
595 * setting storage data type to that of the opclass. The same problem does not
596 * apply to the data type of the right hand side, because the type in the
597 * ScanKey always matches the opclass' one.
599 * Note: this function mirrors minmax_get_strategy_procinfo; if changes are
600 * made here, see that function too.
603 inclusion_get_strategy_procinfo(BrinDesc
*bdesc
, uint16 attno
, Oid subtype
,
606 InclusionOpaque
*opaque
;
608 Assert(strategynum
>= 1 &&
609 strategynum
<= RTMaxStrategyNumber
);
611 opaque
= (InclusionOpaque
*) bdesc
->bd_info
[attno
- 1]->oi_opaque
;
614 * We cache the procedures for the last sub-type in the opaque struct, to
615 * avoid repetitive syscache lookups. If the sub-type is changed,
616 * invalidate all the cached entries.
618 if (opaque
->cached_subtype
!= subtype
)
622 for (i
= 1; i
<= RTMaxStrategyNumber
; i
++)
623 opaque
->strategy_procinfos
[i
- 1].fn_oid
= InvalidOid
;
624 opaque
->cached_subtype
= subtype
;
627 if (opaque
->strategy_procinfos
[strategynum
- 1].fn_oid
== InvalidOid
)
629 Form_pg_attribute attr
;
634 opfamily
= bdesc
->bd_index
->rd_opfamily
[attno
- 1];
635 attr
= TupleDescAttr(bdesc
->bd_tupdesc
, attno
- 1);
636 tuple
= SearchSysCache4(AMOPSTRATEGY
, ObjectIdGetDatum(opfamily
),
637 ObjectIdGetDatum(attr
->atttypid
),
638 ObjectIdGetDatum(subtype
),
639 Int16GetDatum(strategynum
));
641 if (!HeapTupleIsValid(tuple
))
642 elog(ERROR
, "missing operator %d(%u,%u) in opfamily %u",
643 strategynum
, attr
->atttypid
, subtype
, opfamily
);
645 oprid
= DatumGetObjectId(SysCacheGetAttrNotNull(AMOPSTRATEGY
, tuple
,
646 Anum_pg_amop_amopopr
));
647 ReleaseSysCache(tuple
);
648 Assert(RegProcedureIsValid(oprid
));
650 fmgr_info_cxt(get_opcode(oprid
),
651 &opaque
->strategy_procinfos
[strategynum
- 1],
655 return &opaque
->strategy_procinfos
[strategynum
- 1];