Avoid updating inactive_since for invalid replication slots.
[pgsql.git] / src / backend / access / brin / brin_inclusion.c
blobef2247347f912e31f2f2d1e15caa1f40d15816ab
1 /*
2 * brin_inclusion.c
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
7 * each opclass.
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
17 * values.
19 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
20 * Portions Copyright (c) 1994, Regents of the University of California
22 * IDENTIFICATION
23 * src/backend/access/brin/brin_inclusion.c
25 #include "postgres.h"
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
44 * brin_internal.h.
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
59 /*-
60 * The values stored in the bv_values arrays correspond to:
62 * INCLUSION_UNION
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
69 * in the block range
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];
80 Oid cached_subtype;
81 FmgrInfo strategy_procinfos[RTMaxStrategyNumber];
82 } InclusionOpaque;
84 static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno,
85 uint16 procnum);
86 static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
87 Oid subtype, uint16 strategynum);
91 * BRIN inclusion OpcInfo function
93 Datum
94 brin_inclusion_opcinfo(PG_FUNCTION_ARGS)
96 Oid typoid = PG_GETARG_OID(0);
97 BrinOpcInfo *result;
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));
115 /* the union */
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.
137 Datum
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();
145 FmgrInfo *finfo;
146 Datum result;
147 bool new = false;
148 AttrNumber attno;
149 CompactAttribute *attr;
151 Assert(!isnull);
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;
167 new = true;
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);
194 if (new)
195 PG_RETURN_BOOL(true);
197 /* Check if the new value is already contained. */
198 finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_CONTAINS);
199 if (finfo != NULL &&
200 DatumGetBool(FunctionCall2Coll(finfo, colloid,
201 column->bv_values[INCLUSION_UNION],
202 newval)))
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);
214 if (finfo != NULL &&
215 !DatumGetBool(FunctionCall2Coll(finfo, colloid,
216 column->bv_values[INCLUSION_UNION],
217 newval)))
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.
250 Datum
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(),
257 subtype;
258 Datum unionval;
259 AttrNumber attno;
260 Datum query;
261 FmgrInfo *finfo;
262 Datum result;
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
346 * the result.
349 case RTOverlapStrategyNumber:
350 case RTContainsStrategyNumber:
351 case RTContainsElemStrategyNumber:
352 case RTSubStrategyNumber:
353 case RTSubEqualStrategyNumber:
354 finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
355 key->sk_strategy);
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]);
382 * Adjacent strategy
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"
415 * strategy.
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
422 * result.
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));
461 default:
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.
474 Datum
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();
481 AttrNumber attno;
482 CompactAttribute *attr;
483 FmgrInfo *finfo;
484 Datum result;
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]))
499 PG_RETURN_VOID();
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);
505 PG_RETURN_VOID();
508 /* Check if A and B are mergeable; if not, mark A unmergeable. */
509 finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE);
510 if (finfo != NULL &&
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);
516 PG_RETURN_VOID();
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;
535 PG_RETURN_VOID();
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.
544 static FmgrInfo *
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
552 * lookups.
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
558 * searching again.
560 if (opaque->extra_proc_missing[basenum])
561 return NULL;
563 if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid)
565 if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno,
566 procnum)))
568 fmgr_info_copy(&opaque->extra_procinfos[basenum],
569 index_getprocinfo(bdesc->bd_index, attno, procnum),
570 bdesc->bd_context);
572 else
574 opaque->extra_proc_missing[basenum] = true;
575 return NULL;
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.
602 static FmgrInfo *
603 inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype,
604 uint16 strategynum)
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)
620 uint16 i;
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;
630 HeapTuple tuple;
631 Oid opfamily,
632 oprid;
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],
652 bdesc->bd_context);
655 return &opaque->strategy_procinfos[strategynum - 1];