| 1 | /* |
| 2 | * brin_inclusion.c |
| 3 | * Implementation of inclusion opclasses for BRIN |
| 4 | * |
| 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. |
| 8 | * |
| 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. |
| 18 | * |
| 19 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 20 | * Portions Copyright (c) 1994, Regents of the University of California |
| 21 | * |
| 22 | * IDENTIFICATION |
| 23 | * src/backend/access/brin/brin_inclusion.c |
| 24 | */ |
| 25 | #include "postgres.h" |
| 26 | |
| 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/builtins.h" |
| 34 | #include "utils/datum.h" |
| 35 | #include "utils/lsyscache.h" |
| 36 | #include "utils/rel.h" |
| 37 | #include "utils/syscache.h" |
| 38 | |
| 39 | |
| 40 | /* |
| 41 | * Additional SQL level support functions |
| 42 | * |
| 43 | * Procedure numbers must not use values reserved for BRIN itself; see |
| 44 | * brin_internal.h. |
| 45 | */ |
| 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 */ |
| 51 | |
| 52 | |
| 53 | /* |
| 54 | * Subtract this from procnum to obtain index in InclusionOpaque arrays |
| 55 | * (Must be equal to minimum of private procnums). |
| 56 | */ |
| 57 | #define PROCNUM_BASE 11 |
| 58 | |
| 59 | /*- |
| 60 | * The values stored in the bv_values arrays correspond to: |
| 61 | * |
| 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 |
| 70 | */ |
| 71 | #define INCLUSION_UNION 0 |
| 72 | #define INCLUSION_UNMERGEABLE 1 |
| 73 | #define INCLUSION_CONTAINS_EMPTY 2 |
| 74 | |
| 75 | |
| 76 | typedef struct InclusionOpaque |
| 77 | { |
| 78 | FmgrInfo [INCLUSION_MAX_PROCNUMS]; |
| 79 | bool [INCLUSION_MAX_PROCNUMS]; |
| 80 | Oid cached_subtype; |
| 81 | FmgrInfo strategy_procinfos[RTMaxStrategyNumber]; |
| 82 | } InclusionOpaque; |
| 83 | |
| 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); |
| 88 | |
| 89 | |
| 90 | /* |
| 91 | * BRIN inclusion OpcInfo function |
| 92 | */ |
| 93 | Datum |
| 94 | brin_inclusion_opcinfo(PG_FUNCTION_ARGS) |
| 95 | { |
| 96 | Oid typoid = PG_GETARG_OID(0); |
| 97 | BrinOpcInfo *result; |
| 98 | TypeCacheEntry *bool_typcache = lookup_type_cache(BOOLOID, 0); |
| 99 | |
| 100 | /* |
| 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. |
| 108 | */ |
| 109 | result = palloc0(MAXALIGN(SizeofBrinOpcInfo(3)) + sizeof(InclusionOpaque)); |
| 110 | result->oi_nstored = 3; |
| 111 | result->oi_opaque = (InclusionOpaque *) |
| 112 | MAXALIGN((char *) result + SizeofBrinOpcInfo(3)); |
| 113 | |
| 114 | /* the union */ |
| 115 | result->oi_typcache[INCLUSION_UNION] = |
| 116 | lookup_type_cache(typoid, 0); |
| 117 | |
| 118 | /* includes elements that are not mergeable */ |
| 119 | result->oi_typcache[INCLUSION_UNMERGEABLE] = bool_typcache; |
| 120 | |
| 121 | /* includes the empty element */ |
| 122 | result->oi_typcache[INCLUSION_CONTAINS_EMPTY] = bool_typcache; |
| 123 | |
| 124 | PG_RETURN_POINTER(result); |
| 125 | } |
| 126 | |
| 127 | /* |
| 128 | * BRIN inclusion add value function |
| 129 | * |
| 130 | * Examine the given index tuple (which contains partial status of a certain |
| 131 | * page range) by comparing it to the given value that comes from another heap |
| 132 | * tuple. If the new value is outside the union specified by the existing |
| 133 | * tuple values, update the index tuple and return true. Otherwise, return |
| 134 | * false and do not modify in this case. |
| 135 | */ |
| 136 | Datum |
| 137 | brin_inclusion_add_value(PG_FUNCTION_ARGS) |
| 138 | { |
| 139 | BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); |
| 140 | BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); |
| 141 | Datum newval = PG_GETARG_DATUM(2); |
| 142 | bool isnull = PG_GETARG_BOOL(3); |
| 143 | Oid colloid = PG_GET_COLLATION(); |
| 144 | FmgrInfo *finfo; |
| 145 | Datum result; |
| 146 | bool new = false; |
| 147 | AttrNumber attno; |
| 148 | Form_pg_attribute attr; |
| 149 | |
| 150 | /* |
| 151 | * If the new value is null, we record that we saw it if it's the first |
| 152 | * one; otherwise, there's nothing to do. |
| 153 | */ |
| 154 | if (isnull) |
| 155 | { |
| 156 | if (column->bv_hasnulls) |
| 157 | PG_RETURN_BOOL(false); |
| 158 | |
| 159 | column->bv_hasnulls = true; |
| 160 | PG_RETURN_BOOL(true); |
| 161 | } |
| 162 | |
| 163 | attno = column->bv_attno; |
| 164 | attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); |
| 165 | |
| 166 | /* |
| 167 | * If the recorded value is null, copy the new value (which we know to be |
| 168 | * not null), and we're almost done. |
| 169 | */ |
| 170 | if (column->bv_allnulls) |
| 171 | { |
| 172 | column->bv_values[INCLUSION_UNION] = |
| 173 | datumCopy(newval, attr->attbyval, attr->attlen); |
| 174 | column->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(false); |
| 175 | column->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(false); |
| 176 | column->bv_allnulls = false; |
| 177 | new = true; |
| 178 | } |
| 179 | |
| 180 | /* |
| 181 | * No need for further processing if the block range is marked as |
| 182 | * containing unmergeable values. |
| 183 | */ |
| 184 | if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) |
| 185 | PG_RETURN_BOOL(false); |
| 186 | |
| 187 | /* |
| 188 | * If the opclass supports the concept of empty values, test the passed |
| 189 | * new value for emptiness; if it returns true, we need to set the |
| 190 | * "contains empty" flag in the element (unless already set). |
| 191 | */ |
| 192 | finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_EMPTY); |
| 193 | if (finfo != NULL && DatumGetBool(FunctionCall1Coll(finfo, colloid, newval))) |
| 194 | { |
| 195 | if (!DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY])) |
| 196 | { |
| 197 | column->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(true); |
| 198 | PG_RETURN_BOOL(true); |
| 199 | } |
| 200 | |
| 201 | PG_RETURN_BOOL(false); |
| 202 | } |
| 203 | |
| 204 | if (new) |
| 205 | PG_RETURN_BOOL(true); |
| 206 | |
| 207 | /* Check if the new value is already contained. */ |
| 208 | finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_CONTAINS); |
| 209 | if (finfo != NULL && |
| 210 | DatumGetBool(FunctionCall2Coll(finfo, colloid, |
| 211 | column->bv_values[INCLUSION_UNION], |
| 212 | newval))) |
| 213 | PG_RETURN_BOOL(false); |
| 214 | |
| 215 | /* |
| 216 | * Check if the new value is mergeable to the existing union. If it is |
| 217 | * not, mark the value as containing unmergeable elements and get out. |
| 218 | * |
| 219 | * Note: at this point we could remove the value from the union, since |
| 220 | * it's not going to be used any longer. However, the BRIN framework |
| 221 | * doesn't allow for the value not being present. Improve someday. |
| 222 | */ |
| 223 | finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE); |
| 224 | if (finfo != NULL && |
| 225 | !DatumGetBool(FunctionCall2Coll(finfo, colloid, |
| 226 | column->bv_values[INCLUSION_UNION], |
| 227 | newval))) |
| 228 | { |
| 229 | column->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true); |
| 230 | PG_RETURN_BOOL(true); |
| 231 | } |
| 232 | |
| 233 | /* Finally, merge the new value to the existing union. */ |
| 234 | finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE); |
| 235 | Assert(finfo != NULL); |
| 236 | result = FunctionCall2Coll(finfo, colloid, |
| 237 | column->bv_values[INCLUSION_UNION], newval); |
| 238 | if (!attr->attbyval) |
| 239 | pfree(DatumGetPointer(column->bv_values[INCLUSION_UNION])); |
| 240 | column->bv_values[INCLUSION_UNION] = result; |
| 241 | |
| 242 | PG_RETURN_BOOL(true); |
| 243 | } |
| 244 | |
| 245 | /* |
| 246 | * BRIN inclusion consistent function |
| 247 | * |
| 248 | * All of the strategies are optional. |
| 249 | */ |
| 250 | Datum |
| 251 | brin_inclusion_consistent(PG_FUNCTION_ARGS) |
| 252 | { |
| 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; |
| 263 | |
| 264 | Assert(key->sk_attno == column->bv_attno); |
| 265 | |
| 266 | /* Handle IS NULL/IS NOT NULL tests. */ |
| 267 | if (key->sk_flags & SK_ISNULL) |
| 268 | { |
| 269 | if (key->sk_flags & SK_SEARCHNULL) |
| 270 | { |
| 271 | if (column->bv_allnulls || column->bv_hasnulls) |
| 272 | PG_RETURN_BOOL(true); |
| 273 | PG_RETURN_BOOL(false); |
| 274 | } |
| 275 | |
| 276 | /* |
| 277 | * For IS NOT NULL, we can only skip ranges that are known to have |
| 278 | * only nulls. |
| 279 | */ |
| 280 | if (key->sk_flags & SK_SEARCHNOTNULL) |
| 281 | PG_RETURN_BOOL(!column->bv_allnulls); |
| 282 | |
| 283 | /* |
| 284 | * Neither IS NULL nor IS NOT NULL was used; assume all indexable |
| 285 | * operators are strict and return false. |
| 286 | */ |
| 287 | PG_RETURN_BOOL(false); |
| 288 | } |
| 289 | |
| 290 | /* If it is all nulls, it cannot possibly be consistent. */ |
| 291 | if (column->bv_allnulls) |
| 292 | PG_RETURN_BOOL(false); |
| 293 | |
| 294 | /* It has to be checked, if it contains elements that are not mergeable. */ |
| 295 | if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) |
| 296 | PG_RETURN_BOOL(true); |
| 297 | |
| 298 | attno = key->sk_attno; |
| 299 | subtype = key->sk_subtype; |
| 300 | query = key->sk_argument; |
| 301 | unionval = column->bv_values[INCLUSION_UNION]; |
| 302 | switch (key->sk_strategy) |
| 303 | { |
| 304 | /* |
| 305 | * Placement strategies |
| 306 | * |
| 307 | * These are implemented by logically negating the result of the |
| 308 | * converse placement operator; for this to work, the converse |
| 309 | * operator must be part of the opclass. An error will be thrown |
| 310 | * by inclusion_get_strategy_procinfo() if the required strategy |
| 311 | * is not part of the opclass. |
| 312 | * |
| 313 | * These all return false if either argument is empty, so there is |
| 314 | * no need to check for empty elements. |
| 315 | */ |
| 316 | |
| 317 | case RTLeftStrategyNumber: |
| 318 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 319 | RTOverRightStrategyNumber); |
| 320 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 321 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 322 | |
| 323 | case RTOverLeftStrategyNumber: |
| 324 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 325 | RTRightStrategyNumber); |
| 326 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 327 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 328 | |
| 329 | case RTOverRightStrategyNumber: |
| 330 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 331 | RTLeftStrategyNumber); |
| 332 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 333 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 334 | |
| 335 | case RTRightStrategyNumber: |
| 336 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 337 | RTOverLeftStrategyNumber); |
| 338 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 339 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 340 | |
| 341 | case RTBelowStrategyNumber: |
| 342 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 343 | RTOverAboveStrategyNumber); |
| 344 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 345 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 346 | |
| 347 | case RTOverBelowStrategyNumber: |
| 348 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 349 | RTAboveStrategyNumber); |
| 350 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 351 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 352 | |
| 353 | case RTOverAboveStrategyNumber: |
| 354 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 355 | RTBelowStrategyNumber); |
| 356 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 357 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 358 | |
| 359 | case RTAboveStrategyNumber: |
| 360 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 361 | RTOverBelowStrategyNumber); |
| 362 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 363 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 364 | |
| 365 | /* |
| 366 | * Overlap and contains strategies |
| 367 | * |
| 368 | * These strategies are simple enough that we can simply call the |
| 369 | * operator and return its result. Empty elements don't change |
| 370 | * the result. |
| 371 | */ |
| 372 | |
| 373 | case RTOverlapStrategyNumber: |
| 374 | case RTContainsStrategyNumber: |
| 375 | case RTOldContainsStrategyNumber: |
| 376 | case RTContainsElemStrategyNumber: |
| 377 | case RTSubStrategyNumber: |
| 378 | case RTSubEqualStrategyNumber: |
| 379 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 380 | key->sk_strategy); |
| 381 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 382 | PG_RETURN_DATUM(result); |
| 383 | |
| 384 | /* |
| 385 | * Contained by strategies |
| 386 | * |
| 387 | * We cannot just call the original operator for the contained by |
| 388 | * strategies because some elements can be contained even though |
| 389 | * the union is not; instead we use the overlap operator. |
| 390 | * |
| 391 | * We check for empty elements separately as they are not merged |
| 392 | * to the union but contained by everything. |
| 393 | */ |
| 394 | |
| 395 | case RTContainedByStrategyNumber: |
| 396 | case RTOldContainedByStrategyNumber: |
| 397 | case RTSuperStrategyNumber: |
| 398 | case RTSuperEqualStrategyNumber: |
| 399 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 400 | RTOverlapStrategyNumber); |
| 401 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 402 | if (DatumGetBool(result)) |
| 403 | PG_RETURN_BOOL(true); |
| 404 | |
| 405 | PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); |
| 406 | |
| 407 | /* |
| 408 | * Adjacent strategy |
| 409 | * |
| 410 | * We test for overlap first but to be safe we need to call the |
| 411 | * actual adjacent operator also. |
| 412 | * |
| 413 | * An empty element cannot be adjacent to any other, so there is |
| 414 | * no need to check for it. |
| 415 | */ |
| 416 | |
| 417 | case RTAdjacentStrategyNumber: |
| 418 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 419 | RTOverlapStrategyNumber); |
| 420 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 421 | if (DatumGetBool(result)) |
| 422 | PG_RETURN_BOOL(true); |
| 423 | |
| 424 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 425 | RTAdjacentStrategyNumber); |
| 426 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 427 | PG_RETURN_DATUM(result); |
| 428 | |
| 429 | /* |
| 430 | * Basic comparison strategies |
| 431 | * |
| 432 | * It is straightforward to support the equality strategies with |
| 433 | * the contains operator. Generally, inequality strategies do not |
| 434 | * make much sense for the types which will be used with the |
| 435 | * inclusion BRIN family of opclasses, but it is possible to |
| 436 | * implement them with logical negation of the left-of and |
| 437 | * right-of operators. |
| 438 | * |
| 439 | * NB: These strategies cannot be used with geometric datatypes |
| 440 | * that use comparison of areas! The only exception is the "same" |
| 441 | * strategy. |
| 442 | * |
| 443 | * Empty elements are considered to be less than the others. We |
| 444 | * cannot use the empty support function to check the query is an |
| 445 | * empty element, because the query can be another data type than |
| 446 | * the empty support function argument. So we will return true, |
| 447 | * if there is a possibility that empty elements will change the |
| 448 | * result. |
| 449 | */ |
| 450 | |
| 451 | case RTLessStrategyNumber: |
| 452 | case RTLessEqualStrategyNumber: |
| 453 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 454 | RTRightStrategyNumber); |
| 455 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 456 | if (!DatumGetBool(result)) |
| 457 | PG_RETURN_BOOL(true); |
| 458 | |
| 459 | PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); |
| 460 | |
| 461 | case RTSameStrategyNumber: |
| 462 | case RTEqualStrategyNumber: |
| 463 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 464 | RTContainsStrategyNumber); |
| 465 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 466 | if (DatumGetBool(result)) |
| 467 | PG_RETURN_BOOL(true); |
| 468 | |
| 469 | PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); |
| 470 | |
| 471 | case RTGreaterEqualStrategyNumber: |
| 472 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 473 | RTLeftStrategyNumber); |
| 474 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 475 | if (!DatumGetBool(result)) |
| 476 | PG_RETURN_BOOL(true); |
| 477 | |
| 478 | PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); |
| 479 | |
| 480 | case RTGreaterStrategyNumber: |
| 481 | /* no need to check for empty elements */ |
| 482 | finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, |
| 483 | RTLeftStrategyNumber); |
| 484 | result = FunctionCall2Coll(finfo, colloid, unionval, query); |
| 485 | PG_RETURN_BOOL(!DatumGetBool(result)); |
| 486 | |
| 487 | default: |
| 488 | /* shouldn't happen */ |
| 489 | elog(ERROR, "invalid strategy number %d" , key->sk_strategy); |
| 490 | PG_RETURN_BOOL(false); |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | /* |
| 495 | * BRIN inclusion union function |
| 496 | * |
| 497 | * Given two BrinValues, update the first of them as a union of the summary |
| 498 | * values contained in both. The second one is untouched. |
| 499 | */ |
| 500 | Datum |
| 501 | brin_inclusion_union(PG_FUNCTION_ARGS) |
| 502 | { |
| 503 | BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); |
| 504 | BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1); |
| 505 | BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2); |
| 506 | Oid colloid = PG_GET_COLLATION(); |
| 507 | AttrNumber attno; |
| 508 | Form_pg_attribute attr; |
| 509 | FmgrInfo *finfo; |
| 510 | Datum result; |
| 511 | |
| 512 | Assert(col_a->bv_attno == col_b->bv_attno); |
| 513 | |
| 514 | /* Adjust "hasnulls". */ |
| 515 | if (!col_a->bv_hasnulls && col_b->bv_hasnulls) |
| 516 | col_a->bv_hasnulls = true; |
| 517 | |
| 518 | /* If there are no values in B, there's nothing left to do. */ |
| 519 | if (col_b->bv_allnulls) |
| 520 | PG_RETURN_VOID(); |
| 521 | |
| 522 | attno = col_a->bv_attno; |
| 523 | attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); |
| 524 | |
| 525 | /* |
| 526 | * Adjust "allnulls". If A doesn't have values, just copy the values from |
| 527 | * B into A, and we're done. We cannot run the operators in this case, |
| 528 | * because values in A might contain garbage. Note we already established |
| 529 | * that B contains values. |
| 530 | */ |
| 531 | if (col_a->bv_allnulls) |
| 532 | { |
| 533 | col_a->bv_allnulls = false; |
| 534 | col_a->bv_values[INCLUSION_UNION] = |
| 535 | datumCopy(col_b->bv_values[INCLUSION_UNION], |
| 536 | attr->attbyval, attr->attlen); |
| 537 | col_a->bv_values[INCLUSION_UNMERGEABLE] = |
| 538 | col_b->bv_values[INCLUSION_UNMERGEABLE]; |
| 539 | col_a->bv_values[INCLUSION_CONTAINS_EMPTY] = |
| 540 | col_b->bv_values[INCLUSION_CONTAINS_EMPTY]; |
| 541 | PG_RETURN_VOID(); |
| 542 | } |
| 543 | |
| 544 | /* If B includes empty elements, mark A similarly, if needed. */ |
| 545 | if (!DatumGetBool(col_a->bv_values[INCLUSION_CONTAINS_EMPTY]) && |
| 546 | DatumGetBool(col_b->bv_values[INCLUSION_CONTAINS_EMPTY])) |
| 547 | col_a->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(true); |
| 548 | |
| 549 | /* Check if A includes elements that are not mergeable. */ |
| 550 | if (DatumGetBool(col_a->bv_values[INCLUSION_UNMERGEABLE])) |
| 551 | PG_RETURN_VOID(); |
| 552 | |
| 553 | /* If B includes elements that are not mergeable, mark A similarly. */ |
| 554 | if (DatumGetBool(col_b->bv_values[INCLUSION_UNMERGEABLE])) |
| 555 | { |
| 556 | col_a->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true); |
| 557 | PG_RETURN_VOID(); |
| 558 | } |
| 559 | |
| 560 | /* Check if A and B are mergeable; if not, mark A unmergeable. */ |
| 561 | finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE); |
| 562 | if (finfo != NULL && |
| 563 | !DatumGetBool(FunctionCall2Coll(finfo, colloid, |
| 564 | col_a->bv_values[INCLUSION_UNION], |
| 565 | col_b->bv_values[INCLUSION_UNION]))) |
| 566 | { |
| 567 | col_a->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true); |
| 568 | PG_RETURN_VOID(); |
| 569 | } |
| 570 | |
| 571 | /* Finally, merge B to A. */ |
| 572 | finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE); |
| 573 | Assert(finfo != NULL); |
| 574 | result = FunctionCall2Coll(finfo, colloid, |
| 575 | col_a->bv_values[INCLUSION_UNION], |
| 576 | col_b->bv_values[INCLUSION_UNION]); |
| 577 | if (!attr->attbyval) |
| 578 | pfree(DatumGetPointer(col_a->bv_values[INCLUSION_UNION])); |
| 579 | col_a->bv_values[INCLUSION_UNION] = result; |
| 580 | |
| 581 | PG_RETURN_VOID(); |
| 582 | } |
| 583 | |
| 584 | /* |
| 585 | * Cache and return inclusion opclass support procedure |
| 586 | * |
| 587 | * Return the procedure corresponding to the given function support number |
| 588 | * or null if it is not exists. |
| 589 | */ |
| 590 | static FmgrInfo * |
| 591 | inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) |
| 592 | { |
| 593 | InclusionOpaque *opaque; |
| 594 | uint16 basenum = procnum - PROCNUM_BASE; |
| 595 | |
| 596 | /* |
| 597 | * We cache these in the opaque struct, to avoid repetitive syscache |
| 598 | * lookups. |
| 599 | */ |
| 600 | opaque = (InclusionOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; |
| 601 | |
| 602 | /* |
| 603 | * If we already searched for this proc and didn't find it, don't bother |
| 604 | * searching again. |
| 605 | */ |
| 606 | if (opaque->extra_proc_missing[basenum]) |
| 607 | return NULL; |
| 608 | |
| 609 | if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid) |
| 610 | { |
| 611 | if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno, |
| 612 | procnum))) |
| 613 | { |
| 614 | fmgr_info_copy(&opaque->extra_procinfos[basenum], |
| 615 | index_getprocinfo(bdesc->bd_index, attno, procnum), |
| 616 | bdesc->bd_context); |
| 617 | } |
| 618 | else |
| 619 | { |
| 620 | opaque->extra_proc_missing[basenum] = true; |
| 621 | return NULL; |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | return &opaque->extra_procinfos[basenum]; |
| 626 | } |
| 627 | |
| 628 | /* |
| 629 | * Cache and return the procedure of the given strategy |
| 630 | * |
| 631 | * Return the procedure corresponding to the given sub-type and strategy |
| 632 | * number. The data type of the index will be used as the left hand side of |
| 633 | * the operator and the given sub-type will be used as the right hand side. |
| 634 | * Throws an error if the pg_amop row does not exist, but that should not |
| 635 | * happen with a properly configured opclass. |
| 636 | * |
| 637 | * It always throws an error when the data type of the opclass is different |
| 638 | * from the data type of the column or the expression. That happens when the |
| 639 | * column data type has implicit cast to the opclass data type. We don't |
| 640 | * bother casting types, because this situation can easily be avoided by |
| 641 | * setting storage data type to that of the opclass. The same problem does not |
| 642 | * apply to the data type of the right hand side, because the type in the |
| 643 | * ScanKey always matches the opclass' one. |
| 644 | * |
| 645 | * Note: this function mirrors minmax_get_strategy_procinfo; if changes are |
| 646 | * made here, see that function too. |
| 647 | */ |
| 648 | static FmgrInfo * |
| 649 | inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, |
| 650 | uint16 strategynum) |
| 651 | { |
| 652 | InclusionOpaque *opaque; |
| 653 | |
| 654 | Assert(strategynum >= 1 && |
| 655 | strategynum <= RTMaxStrategyNumber); |
| 656 | |
| 657 | opaque = (InclusionOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; |
| 658 | |
| 659 | /* |
| 660 | * We cache the procedures for the last sub-type in the opaque struct, to |
| 661 | * avoid repetitive syscache lookups. If the sub-type is changed, |
| 662 | * invalidate all the cached entries. |
| 663 | */ |
| 664 | if (opaque->cached_subtype != subtype) |
| 665 | { |
| 666 | uint16 i; |
| 667 | |
| 668 | for (i = 1; i <= RTMaxStrategyNumber; i++) |
| 669 | opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid; |
| 670 | opaque->cached_subtype = subtype; |
| 671 | } |
| 672 | |
| 673 | if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid) |
| 674 | { |
| 675 | Form_pg_attribute attr; |
| 676 | HeapTuple tuple; |
| 677 | Oid opfamily, |
| 678 | oprid; |
| 679 | bool isNull; |
| 680 | |
| 681 | opfamily = bdesc->bd_index->rd_opfamily[attno - 1]; |
| 682 | attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); |
| 683 | tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily), |
| 684 | ObjectIdGetDatum(attr->atttypid), |
| 685 | ObjectIdGetDatum(subtype), |
| 686 | Int16GetDatum(strategynum)); |
| 687 | |
| 688 | if (!HeapTupleIsValid(tuple)) |
| 689 | elog(ERROR, "missing operator %d(%u,%u) in opfamily %u" , |
| 690 | strategynum, attr->atttypid, subtype, opfamily); |
| 691 | |
| 692 | oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple, |
| 693 | Anum_pg_amop_amopopr, &isNull)); |
| 694 | ReleaseSysCache(tuple); |
| 695 | Assert(!isNull && RegProcedureIsValid(oprid)); |
| 696 | |
| 697 | fmgr_info_cxt(get_opcode(oprid), |
| 698 | &opaque->strategy_procinfos[strategynum - 1], |
| 699 | bdesc->bd_context); |
| 700 | } |
| 701 | |
| 702 | return &opaque->strategy_procinfos[strategynum - 1]; |
| 703 | } |
| 704 | |