| 1 | /* |
| 2 | * brin_minmax.c |
| 3 | * Implementation of Min/Max opclass for BRIN |
| 4 | * |
| 5 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
| 6 | * Portions Copyright (c) 1994, Regents of the University of California |
| 7 | * |
| 8 | * IDENTIFICATION |
| 9 | * src/backend/access/brin/brin_minmax.c |
| 10 | */ |
| 11 | #include "postgres.h" |
| 12 | |
| 13 | #include "access/genam.h" |
| 14 | #include "access/brin_internal.h" |
| 15 | #include "access/brin_tuple.h" |
| 16 | #include "access/stratnum.h" |
| 17 | #include "catalog/pg_type.h" |
| 18 | #include "catalog/pg_amop.h" |
| 19 | #include "utils/builtins.h" |
| 20 | #include "utils/datum.h" |
| 21 | #include "utils/lsyscache.h" |
| 22 | #include "utils/rel.h" |
| 23 | #include "utils/syscache.h" |
| 24 | |
| 25 | |
| 26 | typedef struct MinmaxOpaque |
| 27 | { |
| 28 | Oid cached_subtype; |
| 29 | FmgrInfo strategy_procinfos[BTMaxStrategyNumber]; |
| 30 | } MinmaxOpaque; |
| 31 | |
| 32 | static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, |
| 33 | Oid subtype, uint16 strategynum); |
| 34 | |
| 35 | |
| 36 | Datum |
| 37 | brin_minmax_opcinfo(PG_FUNCTION_ARGS) |
| 38 | { |
| 39 | Oid typoid = PG_GETARG_OID(0); |
| 40 | BrinOpcInfo *result; |
| 41 | |
| 42 | /* |
| 43 | * opaque->strategy_procinfos is initialized lazily; here it is set to |
| 44 | * all-uninitialized by palloc0 which sets fn_oid to InvalidOid. |
| 45 | */ |
| 46 | |
| 47 | result = palloc0(MAXALIGN(SizeofBrinOpcInfo(2)) + |
| 48 | sizeof(MinmaxOpaque)); |
| 49 | result->oi_nstored = 2; |
| 50 | result->oi_opaque = (MinmaxOpaque *) |
| 51 | MAXALIGN((char *) result + SizeofBrinOpcInfo(2)); |
| 52 | result->oi_typcache[0] = result->oi_typcache[1] = |
| 53 | lookup_type_cache(typoid, 0); |
| 54 | |
| 55 | PG_RETURN_POINTER(result); |
| 56 | } |
| 57 | |
| 58 | /* |
| 59 | * Examine the given index tuple (which contains partial status of a certain |
| 60 | * page range) by comparing it to the given value that comes from another heap |
| 61 | * tuple. If the new value is outside the min/max range specified by the |
| 62 | * existing tuple values, update the index tuple and return true. Otherwise, |
| 63 | * return false and do not modify in this case. |
| 64 | */ |
| 65 | Datum |
| 66 | brin_minmax_add_value(PG_FUNCTION_ARGS) |
| 67 | { |
| 68 | BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); |
| 69 | BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); |
| 70 | Datum newval = PG_GETARG_DATUM(2); |
| 71 | bool isnull = PG_GETARG_DATUM(3); |
| 72 | Oid colloid = PG_GET_COLLATION(); |
| 73 | FmgrInfo *cmpFn; |
| 74 | Datum compar; |
| 75 | bool updated = false; |
| 76 | Form_pg_attribute attr; |
| 77 | AttrNumber attno; |
| 78 | |
| 79 | /* |
| 80 | * If the new value is null, we record that we saw it if it's the first |
| 81 | * one; otherwise, there's nothing to do. |
| 82 | */ |
| 83 | if (isnull) |
| 84 | { |
| 85 | if (column->bv_hasnulls) |
| 86 | PG_RETURN_BOOL(false); |
| 87 | |
| 88 | column->bv_hasnulls = true; |
| 89 | PG_RETURN_BOOL(true); |
| 90 | } |
| 91 | |
| 92 | attno = column->bv_attno; |
| 93 | attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); |
| 94 | |
| 95 | /* |
| 96 | * If the recorded value is null, store the new value (which we know to be |
| 97 | * not null) as both minimum and maximum, and we're done. |
| 98 | */ |
| 99 | if (column->bv_allnulls) |
| 100 | { |
| 101 | column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen); |
| 102 | column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen); |
| 103 | column->bv_allnulls = false; |
| 104 | PG_RETURN_BOOL(true); |
| 105 | } |
| 106 | |
| 107 | /* |
| 108 | * Otherwise, need to compare the new value with the existing boundaries |
| 109 | * and update them accordingly. First check if it's less than the |
| 110 | * existing minimum. |
| 111 | */ |
| 112 | cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid, |
| 113 | BTLessStrategyNumber); |
| 114 | compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[0]); |
| 115 | if (DatumGetBool(compar)) |
| 116 | { |
| 117 | if (!attr->attbyval) |
| 118 | pfree(DatumGetPointer(column->bv_values[0])); |
| 119 | column->bv_values[0] = datumCopy(newval, attr->attbyval, attr->attlen); |
| 120 | updated = true; |
| 121 | } |
| 122 | |
| 123 | /* |
| 124 | * And now compare it to the existing maximum. |
| 125 | */ |
| 126 | cmpFn = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid, |
| 127 | BTGreaterStrategyNumber); |
| 128 | compar = FunctionCall2Coll(cmpFn, colloid, newval, column->bv_values[1]); |
| 129 | if (DatumGetBool(compar)) |
| 130 | { |
| 131 | if (!attr->attbyval) |
| 132 | pfree(DatumGetPointer(column->bv_values[1])); |
| 133 | column->bv_values[1] = datumCopy(newval, attr->attbyval, attr->attlen); |
| 134 | updated = true; |
| 135 | } |
| 136 | |
| 137 | PG_RETURN_BOOL(updated); |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | * Given an index tuple corresponding to a certain page range and a scan key, |
| 142 | * return whether the scan key is consistent with the index tuple's min/max |
| 143 | * values. Return true if so, false otherwise. |
| 144 | */ |
| 145 | Datum |
| 146 | brin_minmax_consistent(PG_FUNCTION_ARGS) |
| 147 | { |
| 148 | BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); |
| 149 | BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); |
| 150 | ScanKey key = (ScanKey) PG_GETARG_POINTER(2); |
| 151 | Oid colloid = PG_GET_COLLATION(), |
| 152 | subtype; |
| 153 | AttrNumber attno; |
| 154 | Datum value; |
| 155 | Datum matches; |
| 156 | FmgrInfo *finfo; |
| 157 | |
| 158 | Assert(key->sk_attno == column->bv_attno); |
| 159 | |
| 160 | /* handle IS NULL/IS NOT NULL tests */ |
| 161 | if (key->sk_flags & SK_ISNULL) |
| 162 | { |
| 163 | if (key->sk_flags & SK_SEARCHNULL) |
| 164 | { |
| 165 | if (column->bv_allnulls || column->bv_hasnulls) |
| 166 | PG_RETURN_BOOL(true); |
| 167 | PG_RETURN_BOOL(false); |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | * For IS NOT NULL, we can only skip ranges that are known to have |
| 172 | * only nulls. |
| 173 | */ |
| 174 | if (key->sk_flags & SK_SEARCHNOTNULL) |
| 175 | PG_RETURN_BOOL(!column->bv_allnulls); |
| 176 | |
| 177 | /* |
| 178 | * Neither IS NULL nor IS NOT NULL was used; assume all indexable |
| 179 | * operators are strict and return false. |
| 180 | */ |
| 181 | PG_RETURN_BOOL(false); |
| 182 | } |
| 183 | |
| 184 | /* if the range is all empty, it cannot possibly be consistent */ |
| 185 | if (column->bv_allnulls) |
| 186 | PG_RETURN_BOOL(false); |
| 187 | |
| 188 | attno = key->sk_attno; |
| 189 | subtype = key->sk_subtype; |
| 190 | value = key->sk_argument; |
| 191 | switch (key->sk_strategy) |
| 192 | { |
| 193 | case BTLessStrategyNumber: |
| 194 | case BTLessEqualStrategyNumber: |
| 195 | finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, |
| 196 | key->sk_strategy); |
| 197 | matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0], |
| 198 | value); |
| 199 | break; |
| 200 | case BTEqualStrategyNumber: |
| 201 | |
| 202 | /* |
| 203 | * In the equality case (WHERE col = someval), we want to return |
| 204 | * the current page range if the minimum value in the range <= |
| 205 | * scan key, and the maximum value >= scan key. |
| 206 | */ |
| 207 | finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, |
| 208 | BTLessEqualStrategyNumber); |
| 209 | matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0], |
| 210 | value); |
| 211 | if (!DatumGetBool(matches)) |
| 212 | break; |
| 213 | /* max() >= scankey */ |
| 214 | finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, |
| 215 | BTGreaterEqualStrategyNumber); |
| 216 | matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1], |
| 217 | value); |
| 218 | break; |
| 219 | case BTGreaterEqualStrategyNumber: |
| 220 | case BTGreaterStrategyNumber: |
| 221 | finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, |
| 222 | key->sk_strategy); |
| 223 | matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1], |
| 224 | value); |
| 225 | break; |
| 226 | default: |
| 227 | /* shouldn't happen */ |
| 228 | elog(ERROR, "invalid strategy number %d" , key->sk_strategy); |
| 229 | matches = 0; |
| 230 | break; |
| 231 | } |
| 232 | |
| 233 | PG_RETURN_DATUM(matches); |
| 234 | } |
| 235 | |
| 236 | /* |
| 237 | * Given two BrinValues, update the first of them as a union of the summary |
| 238 | * values contained in both. The second one is untouched. |
| 239 | */ |
| 240 | Datum |
| 241 | brin_minmax_union(PG_FUNCTION_ARGS) |
| 242 | { |
| 243 | BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); |
| 244 | BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1); |
| 245 | BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2); |
| 246 | Oid colloid = PG_GET_COLLATION(); |
| 247 | AttrNumber attno; |
| 248 | Form_pg_attribute attr; |
| 249 | FmgrInfo *finfo; |
| 250 | bool needsadj; |
| 251 | |
| 252 | Assert(col_a->bv_attno == col_b->bv_attno); |
| 253 | |
| 254 | /* Adjust "hasnulls" */ |
| 255 | if (!col_a->bv_hasnulls && col_b->bv_hasnulls) |
| 256 | col_a->bv_hasnulls = true; |
| 257 | |
| 258 | /* If there are no values in B, there's nothing left to do */ |
| 259 | if (col_b->bv_allnulls) |
| 260 | PG_RETURN_VOID(); |
| 261 | |
| 262 | attno = col_a->bv_attno; |
| 263 | attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); |
| 264 | |
| 265 | /* |
| 266 | * Adjust "allnulls". If A doesn't have values, just copy the values from |
| 267 | * B into A, and we're done. We cannot run the operators in this case, |
| 268 | * because values in A might contain garbage. Note we already established |
| 269 | * that B contains values. |
| 270 | */ |
| 271 | if (col_a->bv_allnulls) |
| 272 | { |
| 273 | col_a->bv_allnulls = false; |
| 274 | col_a->bv_values[0] = datumCopy(col_b->bv_values[0], |
| 275 | attr->attbyval, attr->attlen); |
| 276 | col_a->bv_values[1] = datumCopy(col_b->bv_values[1], |
| 277 | attr->attbyval, attr->attlen); |
| 278 | PG_RETURN_VOID(); |
| 279 | } |
| 280 | |
| 281 | /* Adjust minimum, if B's min is less than A's min */ |
| 282 | finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid, |
| 283 | BTLessStrategyNumber); |
| 284 | needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[0], |
| 285 | col_a->bv_values[0]); |
| 286 | if (needsadj) |
| 287 | { |
| 288 | if (!attr->attbyval) |
| 289 | pfree(DatumGetPointer(col_a->bv_values[0])); |
| 290 | col_a->bv_values[0] = datumCopy(col_b->bv_values[0], |
| 291 | attr->attbyval, attr->attlen); |
| 292 | } |
| 293 | |
| 294 | /* Adjust maximum, if B's max is greater than A's max */ |
| 295 | finfo = minmax_get_strategy_procinfo(bdesc, attno, attr->atttypid, |
| 296 | BTGreaterStrategyNumber); |
| 297 | needsadj = FunctionCall2Coll(finfo, colloid, col_b->bv_values[1], |
| 298 | col_a->bv_values[1]); |
| 299 | if (needsadj) |
| 300 | { |
| 301 | if (!attr->attbyval) |
| 302 | pfree(DatumGetPointer(col_a->bv_values[1])); |
| 303 | col_a->bv_values[1] = datumCopy(col_b->bv_values[1], |
| 304 | attr->attbyval, attr->attlen); |
| 305 | } |
| 306 | |
| 307 | PG_RETURN_VOID(); |
| 308 | } |
| 309 | |
| 310 | /* |
| 311 | * Cache and return the procedure for the given strategy. |
| 312 | * |
| 313 | * Note: this function mirrors inclusion_get_strategy_procinfo; see notes |
| 314 | * there. If changes are made here, see that function too. |
| 315 | */ |
| 316 | static FmgrInfo * |
| 317 | minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, |
| 318 | uint16 strategynum) |
| 319 | { |
| 320 | MinmaxOpaque *opaque; |
| 321 | |
| 322 | Assert(strategynum >= 1 && |
| 323 | strategynum <= BTMaxStrategyNumber); |
| 324 | |
| 325 | opaque = (MinmaxOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; |
| 326 | |
| 327 | /* |
| 328 | * We cache the procedures for the previous subtype in the opaque struct, |
| 329 | * to avoid repetitive syscache lookups. If the subtype changed, |
| 330 | * invalidate all the cached entries. |
| 331 | */ |
| 332 | if (opaque->cached_subtype != subtype) |
| 333 | { |
| 334 | uint16 i; |
| 335 | |
| 336 | for (i = 1; i <= BTMaxStrategyNumber; i++) |
| 337 | opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid; |
| 338 | opaque->cached_subtype = subtype; |
| 339 | } |
| 340 | |
| 341 | if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid) |
| 342 | { |
| 343 | Form_pg_attribute attr; |
| 344 | HeapTuple tuple; |
| 345 | Oid opfamily, |
| 346 | oprid; |
| 347 | bool isNull; |
| 348 | |
| 349 | opfamily = bdesc->bd_index->rd_opfamily[attno - 1]; |
| 350 | attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); |
| 351 | tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily), |
| 352 | ObjectIdGetDatum(attr->atttypid), |
| 353 | ObjectIdGetDatum(subtype), |
| 354 | Int16GetDatum(strategynum)); |
| 355 | |
| 356 | if (!HeapTupleIsValid(tuple)) |
| 357 | elog(ERROR, "missing operator %d(%u,%u) in opfamily %u" , |
| 358 | strategynum, attr->atttypid, subtype, opfamily); |
| 359 | |
| 360 | oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple, |
| 361 | Anum_pg_amop_amopopr, &isNull)); |
| 362 | ReleaseSysCache(tuple); |
| 363 | Assert(!isNull && RegProcedureIsValid(oprid)); |
| 364 | |
| 365 | fmgr_info_cxt(get_opcode(oprid), |
| 366 | &opaque->strategy_procinfos[strategynum - 1], |
| 367 | bdesc->bd_context); |
| 368 | } |
| 369 | |
| 370 | return &opaque->strategy_procinfos[strategynum - 1]; |
| 371 | } |
| 372 | |