1 /*------------------------------------------------------------------------- 2 * 3 * expandedrecord.c 4 * Functions for manipulating composite expanded objects. 5 * 6 * This module supports "expanded objects" (cf. expandeddatum.h) that can 7 * store values of named composite types, domains over named composite types, 8 * and record types (registered or anonymous). 9 * 10 * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group 11 * Portions Copyright (c) 1994, Regents of the University of California 12 * 13 * 14 * IDENTIFICATION 15 * src/backend/utils/adt/expandedrecord.c 16 * 17 *------------------------------------------------------------------------- 18 */ 19 #include "postgres.h" 20 21 #include "access/htup_details.h" 22 #include "access/tuptoaster.h" 23 #include "catalog/heap.h" 24 #include "catalog/pg_type.h" 25 #include "utils/builtins.h" 26 #include "utils/datum.h" 27 #include "utils/expandedrecord.h" 28 #include "utils/memutils.h" 29 #include "utils/typcache.h" 30 31 32 /* "Methods" required for an expanded object */ 33 static Size ER_get_flat_size(ExpandedObjectHeader *eohptr); 34 static void ER_flatten_into(ExpandedObjectHeader *eohptr, 35 void *result, Size allocated_size); 36 37 static const ExpandedObjectMethods ER_methods = 38 { 39 ER_get_flat_size, 40 ER_flatten_into 41 }; 42 43 /* Other local functions */ 44 static void ER_mc_callback(void *arg); 45 static MemoryContext get_short_term_cxt(ExpandedRecordHeader *erh); 46 static void build_dummy_expanded_header(ExpandedRecordHeader *main_erh); 47 static pg_noinline void check_domain_for_new_field(ExpandedRecordHeader *erh, 48 int fnumber, 49 Datum newValue, bool isnull); 50 static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh, 51 HeapTuple tuple); 52 53 54 /* 55 * Build an expanded record of the specified composite type 56 * 57 * type_id can be RECORDOID, but only if a positive typmod is given. 58 * 59 * The expanded record is initially "empty", having a state logically 60 * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)). 61 * Note that this might not be a valid state for a domain type; 62 * if the caller needs to check that, call 63 * expanded_record_set_tuple(erh, NULL, false, false). 64 * 65 * The expanded object will be a child of parentcontext. 66 */ 67 ExpandedRecordHeader * 68 make_expanded_record_from_typeid(Oid type_id, int32 typmod, 69 MemoryContext parentcontext) 70 { 71 ExpandedRecordHeader *erh; 72 int flags = 0; 73 TupleDesc tupdesc; 74 uint64 tupdesc_id; 75 MemoryContext objcxt; 76 char *chunk; 77 78 if (type_id != RECORDOID) 79 { 80 /* 81 * Consult the typcache to see if it's a domain over composite, and in 82 * any case to get the tupdesc and tupdesc identifier. 83 */ 84 TypeCacheEntry *typentry; 85 86 typentry = lookup_type_cache(type_id, 87 TYPECACHE_TUPDESC | 88 TYPECACHE_DOMAIN_BASE_INFO); 89 if (typentry->typtype == TYPTYPE_DOMAIN) 90 { 91 flags |= ER_FLAG_IS_DOMAIN; 92 typentry = lookup_type_cache(typentry->domainBaseType, 93 TYPECACHE_TUPDESC); 94 } 95 if (typentry->tupDesc == NULL) 96 ereport(ERROR, 97 (errcode(ERRCODE_WRONG_OBJECT_TYPE), 98 errmsg("type %s is not composite", 99 format_type_be(type_id)))); 100 tupdesc = typentry->tupDesc; 101 tupdesc_id = typentry->tupDesc_identifier; 102 } 103 else 104 { 105 /* 106 * For RECORD types, get the tupdesc and identifier from typcache. 107 */ 108 tupdesc = lookup_rowtype_tupdesc(type_id, typmod); 109 tupdesc_id = assign_record_type_identifier(type_id, typmod); 110 } 111 112 /* 113 * Allocate private context for expanded object. We use a regular-size 114 * context, not a small one, to improve the odds that we can fit a tupdesc 115 * into it without needing an extra malloc block. (This code path doesn't 116 * ever need to copy a tupdesc into the expanded record, but let's be 117 * consistent with the other ways of making an expanded record.) 118 */ 119 objcxt = AllocSetContextCreate(parentcontext, 120 "expanded record", 121 ALLOCSET_DEFAULT_SIZES); 122 123 /* 124 * Since we already know the number of fields in the tupdesc, we can 125 * allocate the dvalues/dnulls arrays along with the record header. This 126 * is useless if we never need those arrays, but it costs almost nothing, 127 * and it will save a palloc cycle if we do need them. 128 */ 129 erh = (ExpandedRecordHeader *) 130 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) 131 + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); 132 133 /* Ensure all header fields are initialized to 0/null */ 134 memset(erh, 0, sizeof(ExpandedRecordHeader)); 135 136 EOH_init_header(&erh->hdr, &ER_methods, objcxt); 137 erh->er_magic = ER_MAGIC; 138 139 /* Set up dvalues/dnulls, with no valid contents as yet */ 140 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); 141 erh->dvalues = (Datum *) chunk; 142 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); 143 erh->nfields = tupdesc->natts; 144 145 /* Fill in composite-type identification info */ 146 erh->er_decltypeid = type_id; 147 erh->er_typeid = tupdesc->tdtypeid; 148 erh->er_typmod = tupdesc->tdtypmod; 149 erh->er_tupdesc_id = tupdesc_id; 150 151 erh->flags = flags; 152 153 /* 154 * If what we got from the typcache is a refcounted tupdesc, we need to 155 * acquire our own refcount on it. We manage the refcount with a memory 156 * context callback rather than assuming that the CurrentResourceOwner is 157 * longer-lived than this expanded object. 158 */ 159 if (tupdesc->tdrefcount >= 0) 160 { 161 /* Register callback to release the refcount */ 162 erh->er_mcb.func = ER_mc_callback; 163 erh->er_mcb.arg = (void *) erh; 164 MemoryContextRegisterResetCallback(erh->hdr.eoh_context, 165 &erh->er_mcb); 166 167 /* And save the pointer */ 168 erh->er_tupdesc = tupdesc; 169 tupdesc->tdrefcount++; 170 171 /* If we called lookup_rowtype_tupdesc, release the pin it took */ 172 if (type_id == RECORDOID) 173 DecrTupleDescRefCount(tupdesc); 174 } 175 else 176 { 177 /* 178 * If it's not refcounted, just assume it will outlive the expanded 179 * object. (This can happen for shared record types, for instance.) 180 */ 181 erh->er_tupdesc = tupdesc; 182 } 183 184 /* 185 * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the 186 * record remains logically empty. 187 */ 188 189 return erh; 190 } 191 192 /* 193 * Build an expanded record of the rowtype defined by the tupdesc 194 * 195 * The tupdesc is copied if necessary (i.e., if we can't just bump its 196 * reference count instead). 197 * 198 * The expanded record is initially "empty", having a state logically 199 * equivalent to a NULL composite value (not ROW(NULL, NULL, ...)). 200 * 201 * The expanded object will be a child of parentcontext. 202 */ 203 ExpandedRecordHeader * 204 make_expanded_record_from_tupdesc(TupleDesc tupdesc, 205 MemoryContext parentcontext) 206 { 207 ExpandedRecordHeader *erh; 208 uint64 tupdesc_id; 209 MemoryContext objcxt; 210 MemoryContext oldcxt; 211 char *chunk; 212 213 if (tupdesc->tdtypeid != RECORDOID) 214 { 215 /* 216 * If it's a named composite type (not RECORD), we prefer to reference 217 * the typcache's copy of the tupdesc, which is guaranteed to be 218 * refcounted (the given tupdesc might not be). In any case, we need 219 * to consult the typcache to get the correct tupdesc identifier. 220 * 221 * Note that tdtypeid couldn't be a domain type, so we need not 222 * consider that case here. 223 */ 224 TypeCacheEntry *typentry; 225 226 typentry = lookup_type_cache(tupdesc->tdtypeid, TYPECACHE_TUPDESC); 227 if (typentry->tupDesc == NULL) 228 ereport(ERROR, 229 (errcode(ERRCODE_WRONG_OBJECT_TYPE), 230 errmsg("type %s is not composite", 231 format_type_be(tupdesc->tdtypeid)))); 232 tupdesc = typentry->tupDesc; 233 tupdesc_id = typentry->tupDesc_identifier; 234 } 235 else 236 { 237 /* 238 * For RECORD types, get the appropriate unique identifier (possibly 239 * freshly assigned). 240 */ 241 tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid, 242 tupdesc->tdtypmod); 243 } 244 245 /* 246 * Allocate private context for expanded object. We use a regular-size 247 * context, not a small one, to improve the odds that we can fit a tupdesc 248 * into it without needing an extra malloc block. 249 */ 250 objcxt = AllocSetContextCreate(parentcontext, 251 "expanded record", 252 ALLOCSET_DEFAULT_SIZES); 253 254 /* 255 * Since we already know the number of fields in the tupdesc, we can 256 * allocate the dvalues/dnulls arrays along with the record header. This 257 * is useless if we never need those arrays, but it costs almost nothing, 258 * and it will save a palloc cycle if we do need them. 259 */ 260 erh = (ExpandedRecordHeader *) 261 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) 262 + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); 263 264 /* Ensure all header fields are initialized to 0/null */ 265 memset(erh, 0, sizeof(ExpandedRecordHeader)); 266 267 EOH_init_header(&erh->hdr, &ER_methods, objcxt); 268 erh->er_magic = ER_MAGIC; 269 270 /* Set up dvalues/dnulls, with no valid contents as yet */ 271 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); 272 erh->dvalues = (Datum *) chunk; 273 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); 274 erh->nfields = tupdesc->natts; 275 276 /* Fill in composite-type identification info */ 277 erh->er_decltypeid = erh->er_typeid = tupdesc->tdtypeid; 278 erh->er_typmod = tupdesc->tdtypmod; 279 erh->er_tupdesc_id = tupdesc_id; 280 281 /* 282 * Copy tupdesc if needed, but we prefer to bump its refcount if possible. 283 * We manage the refcount with a memory context callback rather than 284 * assuming that the CurrentResourceOwner is longer-lived than this 285 * expanded object. 286 */ 287 if (tupdesc->tdrefcount >= 0) 288 { 289 /* Register callback to release the refcount */ 290 erh->er_mcb.func = ER_mc_callback; 291 erh->er_mcb.arg = (void *) erh; 292 MemoryContextRegisterResetCallback(erh->hdr.eoh_context, 293 &erh->er_mcb); 294 295 /* And save the pointer */ 296 erh->er_tupdesc = tupdesc; 297 tupdesc->tdrefcount++; 298 } 299 else 300 { 301 /* Just copy it */ 302 oldcxt = MemoryContextSwitchTo(objcxt); 303 erh->er_tupdesc = CreateTupleDescCopy(tupdesc); 304 erh->flags |= ER_FLAG_TUPDESC_ALLOCED; 305 MemoryContextSwitchTo(oldcxt); 306 } 307 308 /* 309 * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the 310 * record remains logically empty. 311 */ 312 313 return erh; 314 } 315 316 /* 317 * Build an expanded record of the same rowtype as the given expanded record 318 * 319 * This is faster than either of the above routines because we can bypass 320 * typcache lookup(s). 321 * 322 * The expanded record is initially "empty" --- we do not copy whatever 323 * tuple might be in the source expanded record. 324 * 325 * The expanded object will be a child of parentcontext. 326 */ 327 ExpandedRecordHeader * 328 make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh, 329 MemoryContext parentcontext) 330 { 331 ExpandedRecordHeader *erh; 332 TupleDesc tupdesc = expanded_record_get_tupdesc(olderh); 333 MemoryContext objcxt; 334 MemoryContext oldcxt; 335 char *chunk; 336 337 /* 338 * Allocate private context for expanded object. We use a regular-size 339 * context, not a small one, to improve the odds that we can fit a tupdesc 340 * into it without needing an extra malloc block. 341 */ 342 objcxt = AllocSetContextCreate(parentcontext, 343 "expanded record", 344 ALLOCSET_DEFAULT_SIZES); 345 346 /* 347 * Since we already know the number of fields in the tupdesc, we can 348 * allocate the dvalues/dnulls arrays along with the record header. This 349 * is useless if we never need those arrays, but it costs almost nothing, 350 * and it will save a palloc cycle if we do need them. 351 */ 352 erh = (ExpandedRecordHeader *) 353 MemoryContextAlloc(objcxt, MAXALIGN(sizeof(ExpandedRecordHeader)) 354 + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); 355 356 /* Ensure all header fields are initialized to 0/null */ 357 memset(erh, 0, sizeof(ExpandedRecordHeader)); 358 359 EOH_init_header(&erh->hdr, &ER_methods, objcxt); 360 erh->er_magic = ER_MAGIC; 361 362 /* Set up dvalues/dnulls, with no valid contents as yet */ 363 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); 364 erh->dvalues = (Datum *) chunk; 365 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); 366 erh->nfields = tupdesc->natts; 367 368 /* Fill in composite-type identification info */ 369 erh->er_decltypeid = olderh->er_decltypeid; 370 erh->er_typeid = olderh->er_typeid; 371 erh->er_typmod = olderh->er_typmod; 372 erh->er_tupdesc_id = olderh->er_tupdesc_id; 373 374 /* The only flag bit that transfers over is IS_DOMAIN */ 375 erh->flags = olderh->flags & ER_FLAG_IS_DOMAIN; 376 377 /* 378 * Copy tupdesc if needed, but we prefer to bump its refcount if possible. 379 * We manage the refcount with a memory context callback rather than 380 * assuming that the CurrentResourceOwner is longer-lived than this 381 * expanded object. 382 */ 383 if (tupdesc->tdrefcount >= 0) 384 { 385 /* Register callback to release the refcount */ 386 erh->er_mcb.func = ER_mc_callback; 387 erh->er_mcb.arg = (void *) erh; 388 MemoryContextRegisterResetCallback(erh->hdr.eoh_context, 389 &erh->er_mcb); 390 391 /* And save the pointer */ 392 erh->er_tupdesc = tupdesc; 393 tupdesc->tdrefcount++; 394 } 395 else if (olderh->flags & ER_FLAG_TUPDESC_ALLOCED) 396 { 397 /* We need to make our own copy of the tupdesc */ 398 oldcxt = MemoryContextSwitchTo(objcxt); 399 erh->er_tupdesc = CreateTupleDescCopy(tupdesc); 400 erh->flags |= ER_FLAG_TUPDESC_ALLOCED; 401 MemoryContextSwitchTo(oldcxt); 402 } 403 else 404 { 405 /* 406 * Assume the tupdesc will outlive this expanded object, just like 407 * we're assuming it will outlive the source object. 408 */ 409 erh->er_tupdesc = tupdesc; 410 } 411 412 /* 413 * We don't set ER_FLAG_DVALUES_VALID or ER_FLAG_FVALUE_VALID, so the 414 * record remains logically empty. 415 */ 416 417 return erh; 418 } 419 420 /* 421 * Insert given tuple as the value of the expanded record 422 * 423 * It is caller's responsibility that the tuple matches the record's 424 * previously-assigned rowtype. (However domain constraints, if any, 425 * will be checked here.) 426 * 427 * The tuple is physically copied into the expanded record's local storage 428 * if "copy" is true, otherwise it's caller's responsibility that the tuple 429 * will live as long as the expanded record does. 430 * 431 * Out-of-line field values in the tuple are automatically inlined if 432 * "expand_external" is true, otherwise not. (The combination copy = false, 433 * expand_external = true is not sensible and not supported.) 434 * 435 * Alternatively, tuple can be NULL, in which case we just set the expanded 436 * record to be empty. 437 */ 438 void 439 expanded_record_set_tuple(ExpandedRecordHeader *erh, 440 HeapTuple tuple, 441 bool copy, 442 bool expand_external) 443 { 444 int oldflags; 445 HeapTuple oldtuple; 446 char *oldfstartptr; 447 char *oldfendptr; 448 int newflags; 449 HeapTuple newtuple; 450 MemoryContext oldcxt; 451 452 /* Shouldn't ever be trying to assign new data to a dummy header */ 453 Assert(!(erh->flags & ER_FLAG_IS_DUMMY)); 454 455 /* 456 * Before performing the assignment, see if result will satisfy domain. 457 */ 458 if (erh->flags & ER_FLAG_IS_DOMAIN) 459 check_domain_for_new_tuple(erh, tuple); 460 461 /* 462 * If we need to get rid of out-of-line field values, do so, using the 463 * short-term context to avoid leaking whatever cruft the toast fetch 464 * might generate. 465 */ 466 if (expand_external && tuple) 467 { 468 /* Assert caller didn't ask for unsupported case */ 469 Assert(copy); 470 if (HeapTupleHasExternal(tuple)) 471 { 472 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); 473 tuple = toast_flatten_tuple(tuple, erh->er_tupdesc); 474 MemoryContextSwitchTo(oldcxt); 475 } 476 else 477 expand_external = false; /* need not clean up below */ 478 } 479 480 /* 481 * Initialize new flags, keeping only non-data status bits. 482 */ 483 oldflags = erh->flags; 484 newflags = oldflags & ER_FLAGS_NON_DATA; 485 486 /* 487 * Copy tuple into local storage if needed. We must be sure this succeeds 488 * before we start to modify the expanded record's state. 489 */ 490 if (copy && tuple) 491 { 492 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); 493 newtuple = heap_copytuple(tuple); 494 newflags |= ER_FLAG_FVALUE_ALLOCED; 495 MemoryContextSwitchTo(oldcxt); 496 497 /* We can now flush anything that detoasting might have leaked. */ 498 if (expand_external) 499 MemoryContextReset(erh->er_short_term_cxt); 500 } 501 else 502 newtuple = tuple; 503 504 /* Make copies of fields we're about to overwrite */ 505 oldtuple = erh->fvalue; 506 oldfstartptr = erh->fstartptr; 507 oldfendptr = erh->fendptr; 508 509 /* 510 * It's now safe to update the expanded record's state. 511 */ 512 if (newtuple) 513 { 514 /* Save flat representation */ 515 erh->fvalue = newtuple; 516 erh->fstartptr = (char *) newtuple->t_data; 517 erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len; 518 newflags |= ER_FLAG_FVALUE_VALID; 519 520 /* Remember if we have any out-of-line field values */ 521 if (HeapTupleHasExternal(newtuple)) 522 newflags |= ER_FLAG_HAVE_EXTERNAL; 523 } 524 else 525 { 526 erh->fvalue = NULL; 527 erh->fstartptr = erh->fendptr = NULL; 528 } 529 530 erh->flags = newflags; 531 532 /* Reset flat-size info; we don't bother to make it valid now */ 533 erh->flat_size = 0; 534 535 /* 536 * Now, release any storage belonging to old field values. It's safe to 537 * do this because ER_FLAG_DVALUES_VALID is no longer set in erh->flags; 538 * even if we fail partway through, the record is valid, and at worst 539 * we've failed to reclaim some space. 540 */ 541 if (oldflags & ER_FLAG_DVALUES_ALLOCED) 542 { 543 TupleDesc tupdesc = erh->er_tupdesc; 544 int i; 545 546 for (i = 0; i < erh->nfields; i++) 547 { 548 if (!erh->dnulls[i] && 549 !(TupleDescAttr(tupdesc, i)->attbyval)) 550 { 551 char *oldValue = (char *) DatumGetPointer(erh->dvalues[i]); 552 553 if (oldValue < oldfstartptr || oldValue >= oldfendptr) 554 pfree(oldValue); 555 } 556 } 557 } 558 559 /* Likewise free the old tuple, if it was locally allocated */ 560 if (oldflags & ER_FLAG_FVALUE_ALLOCED) 561 heap_freetuple(oldtuple); 562 563 /* We won't make a new deconstructed representation until/unless needed */ 564 } 565 566 /* 567 * make_expanded_record_from_datum: build expanded record from composite Datum 568 * 569 * This combines the functions of make_expanded_record_from_typeid and 570 * expanded_record_set_tuple. However, we do not force a lookup of the 571 * tupdesc immediately, reasoning that it might never be needed. 572 * 573 * The expanded object will be a child of parentcontext. 574 * 575 * Note: a composite datum cannot self-identify as being of a domain type, 576 * so we need not consider domain cases here. 577 */ 578 Datum 579 make_expanded_record_from_datum(Datum recorddatum, MemoryContext parentcontext) 580 { 581 ExpandedRecordHeader *erh; 582 HeapTupleHeader tuphdr; 583 HeapTupleData tmptup; 584 HeapTuple newtuple; 585 MemoryContext objcxt; 586 MemoryContext oldcxt; 587 588 /* 589 * Allocate private context for expanded object. We use a regular-size 590 * context, not a small one, to improve the odds that we can fit a tupdesc 591 * into it without needing an extra malloc block. 592 */ 593 objcxt = AllocSetContextCreate(parentcontext, 594 "expanded record", 595 ALLOCSET_DEFAULT_SIZES); 596 597 /* Set up expanded record header, initializing fields to 0/null */ 598 erh = (ExpandedRecordHeader *) 599 MemoryContextAllocZero(objcxt, sizeof(ExpandedRecordHeader)); 600 601 EOH_init_header(&erh->hdr, &ER_methods, objcxt); 602 erh->er_magic = ER_MAGIC; 603 604 /* 605 * Detoast and copy source record into private context, as a HeapTuple. 606 * (If we actually have to detoast the source, we'll leak some memory in 607 * the caller's context, but it doesn't seem worth worrying about.) 608 */ 609 tuphdr = DatumGetHeapTupleHeader(recorddatum); 610 611 tmptup.t_len = HeapTupleHeaderGetDatumLength(tuphdr); 612 ItemPointerSetInvalid(&(tmptup.t_self)); 613 tmptup.t_tableOid = InvalidOid; 614 tmptup.t_data = tuphdr; 615 616 oldcxt = MemoryContextSwitchTo(objcxt); 617 newtuple = heap_copytuple(&tmptup); 618 erh->flags |= ER_FLAG_FVALUE_ALLOCED; 619 MemoryContextSwitchTo(oldcxt); 620 621 /* Fill in composite-type identification info */ 622 erh->er_decltypeid = erh->er_typeid = HeapTupleHeaderGetTypeId(tuphdr); 623 erh->er_typmod = HeapTupleHeaderGetTypMod(tuphdr); 624 625 /* remember we have a flat representation */ 626 erh->fvalue = newtuple; 627 erh->fstartptr = (char *) newtuple->t_data; 628 erh->fendptr = ((char *) newtuple->t_data) + newtuple->t_len; 629 erh->flags |= ER_FLAG_FVALUE_VALID; 630 631 /* Shouldn't need to set ER_FLAG_HAVE_EXTERNAL */ 632 Assert(!HeapTupleHeaderHasExternal(tuphdr)); 633 634 /* 635 * We won't look up the tupdesc till we have to, nor make a deconstructed 636 * representation. We don't have enough info to fill flat_size and 637 * friends, either. 638 */ 639 640 /* return a R/W pointer to the expanded record */ 641 return EOHPGetRWDatum(&erh->hdr); 642 } 643 644 /* 645 * get_flat_size method for expanded records 646 * 647 * Note: call this in a reasonably short-lived memory context, in case of 648 * memory leaks from activities such as detoasting. 649 */ 650 static Size 651 ER_get_flat_size(ExpandedObjectHeader *eohptr) 652 { 653 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr; 654 TupleDesc tupdesc; 655 Size len; 656 Size data_len; 657 int hoff; 658 bool hasnull; 659 int i; 660 661 Assert(erh->er_magic == ER_MAGIC); 662 663 /* 664 * The flat representation has to be a valid composite datum. Make sure 665 * that we have a registered, not anonymous, RECORD type. 666 */ 667 if (erh->er_typeid == RECORDOID && 668 erh->er_typmod < 0) 669 { 670 tupdesc = expanded_record_get_tupdesc(erh); 671 assign_record_type_typmod(tupdesc); 672 erh->er_typmod = tupdesc->tdtypmod; 673 } 674 675 /* 676 * If we have a valid flattened value without out-of-line fields, we can 677 * just use it as-is. 678 */ 679 if (erh->flags & ER_FLAG_FVALUE_VALID && 680 !(erh->flags & ER_FLAG_HAVE_EXTERNAL)) 681 return erh->fvalue->t_len; 682 683 /* If we have a cached size value, believe that */ 684 if (erh->flat_size) 685 return erh->flat_size; 686 687 /* If we haven't yet deconstructed the tuple, do that */ 688 if (!(erh->flags & ER_FLAG_DVALUES_VALID)) 689 deconstruct_expanded_record(erh); 690 691 /* Tuple descriptor must be valid by now */ 692 tupdesc = erh->er_tupdesc; 693 694 /* 695 * Composite datums mustn't contain any out-of-line values. 696 */ 697 if (erh->flags & ER_FLAG_HAVE_EXTERNAL) 698 { 699 for (i = 0; i < erh->nfields; i++) 700 { 701 Form_pg_attribute attr = TupleDescAttr(tupdesc, i); 702 703 if (!erh->dnulls[i] && 704 !attr->attbyval && attr->attlen == -1 && 705 VARATT_IS_EXTERNAL(DatumGetPointer(erh->dvalues[i]))) 706 { 707 /* 708 * expanded_record_set_field_internal can do the actual work 709 * of detoasting. It needn't recheck domain constraints. 710 */ 711 expanded_record_set_field_internal(erh, i + 1, 712 erh->dvalues[i], false, 713 true, 714 false); 715 } 716 } 717 718 /* 719 * We have now removed all external field values, so we can clear the 720 * flag about them. This won't cause ER_flatten_into() to mistakenly 721 * take the fast path, since expanded_record_set_field() will have 722 * cleared ER_FLAG_FVALUE_VALID. 723 */ 724 erh->flags &= ~ER_FLAG_HAVE_EXTERNAL; 725 } 726 727 /* Test if we currently have any null values */ 728 hasnull = false; 729 for (i = 0; i < erh->nfields; i++) 730 { 731 if (erh->dnulls[i]) 732 { 733 hasnull = true; 734 break; 735 } 736 } 737 738 /* Determine total space needed */ 739 len = offsetof(HeapTupleHeaderData, t_bits); 740 741 if (hasnull) 742 len += BITMAPLEN(tupdesc->natts); 743 744 if (tupdesc->tdhasoid) 745 len += sizeof(Oid); 746 747 hoff = len = MAXALIGN(len); /* align user data safely */ 748 749 data_len = heap_compute_data_size(tupdesc, erh->dvalues, erh->dnulls); 750 751 len += data_len; 752 753 /* Cache for next time */ 754 erh->flat_size = len; 755 erh->data_len = data_len; 756 erh->hoff = hoff; 757 erh->hasnull = hasnull; 758 759 return len; 760 } 761 762 /* 763 * flatten_into method for expanded records 764 */ 765 static void 766 ER_flatten_into(ExpandedObjectHeader *eohptr, 767 void *result, Size allocated_size) 768 { 769 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) eohptr; 770 HeapTupleHeader tuphdr = (HeapTupleHeader) result; 771 TupleDesc tupdesc; 772 773 Assert(erh->er_magic == ER_MAGIC); 774 775 /* Easy if we have a valid flattened value without out-of-line fields */ 776 if (erh->flags & ER_FLAG_FVALUE_VALID && 777 !(erh->flags & ER_FLAG_HAVE_EXTERNAL)) 778 { 779 Assert(allocated_size == erh->fvalue->t_len); 780 memcpy(tuphdr, erh->fvalue->t_data, allocated_size); 781 /* The original flattened value might not have datum header fields */ 782 HeapTupleHeaderSetDatumLength(tuphdr, allocated_size); 783 HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid); 784 HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod); 785 return; 786 } 787 788 /* Else allocation should match previous get_flat_size result */ 789 Assert(allocated_size == erh->flat_size); 790 791 /* We'll need the tuple descriptor */ 792 tupdesc = expanded_record_get_tupdesc(erh); 793 794 /* We must ensure that any pad space is zero-filled */ 795 memset(tuphdr, 0, allocated_size); 796 797 /* Set up header fields of composite Datum */ 798 HeapTupleHeaderSetDatumLength(tuphdr, allocated_size); 799 HeapTupleHeaderSetTypeId(tuphdr, erh->er_typeid); 800 HeapTupleHeaderSetTypMod(tuphdr, erh->er_typmod); 801 /* We also make sure that t_ctid is invalid unless explicitly set */ 802 ItemPointerSetInvalid(&(tuphdr->t_ctid)); 803 804 HeapTupleHeaderSetNatts(tuphdr, tupdesc->natts); 805 tuphdr->t_hoff = erh->hoff; 806 807 if (tupdesc->tdhasoid) /* else leave infomask = 0 */ 808 tuphdr->t_infomask = HEAP_HASOID; 809 810 /* And fill the data area from dvalues/dnulls */ 811 heap_fill_tuple(tupdesc, 812 erh->dvalues, 813 erh->dnulls, 814 (char *) tuphdr + erh->hoff, 815 erh->data_len, 816 &tuphdr->t_infomask, 817 (erh->hasnull ? tuphdr->t_bits : NULL)); 818 } 819 820 /* 821 * Look up the tupdesc for the expanded record's actual type 822 * 823 * Note: code internal to this module is allowed to just fetch 824 * erh->er_tupdesc if ER_FLAG_DVALUES_VALID is set; otherwise it should call 825 * expanded_record_get_tupdesc. This function is the out-of-line portion 826 * of expanded_record_get_tupdesc. 827 */ 828 TupleDesc 829 expanded_record_fetch_tupdesc(ExpandedRecordHeader *erh) 830 { 831 TupleDesc tupdesc; 832 833 /* Easy if we already have it (but caller should have checked already) */ 834 if (erh->er_tupdesc) 835 return erh->er_tupdesc; 836 837 /* Lookup the composite type's tupdesc using the typcache */ 838 tupdesc = lookup_rowtype_tupdesc(erh->er_typeid, erh->er_typmod); 839 840 /* 841 * If it's a refcounted tupdesc rather than a statically allocated one, we 842 * want to manage the refcount with a memory context callback rather than 843 * assuming that the CurrentResourceOwner is longer-lived than this 844 * expanded object. 845 */ 846 if (tupdesc->tdrefcount >= 0) 847 { 848 /* Register callback if we didn't already */ 849 if (erh->er_mcb.arg == NULL) 850 { 851 erh->er_mcb.func = ER_mc_callback; 852 erh->er_mcb.arg = (void *) erh; 853 MemoryContextRegisterResetCallback(erh->hdr.eoh_context, 854 &erh->er_mcb); 855 } 856 857 /* Remember our own pointer */ 858 erh->er_tupdesc = tupdesc; 859 tupdesc->tdrefcount++; 860 861 /* Release the pin lookup_rowtype_tupdesc acquired */ 862 DecrTupleDescRefCount(tupdesc); 863 } 864 else 865 { 866 /* Just remember the pointer */ 867 erh->er_tupdesc = tupdesc; 868 } 869 870 /* In either case, fetch the process-global ID for this tupdesc */ 871 erh->er_tupdesc_id = assign_record_type_identifier(tupdesc->tdtypeid, 872 tupdesc->tdtypmod); 873 874 return tupdesc; 875 } 876 877 /* 878 * Get a HeapTuple representing the current value of the expanded record 879 * 880 * If valid, the originally stored tuple is returned, so caller must not 881 * scribble on it. Otherwise, we return a HeapTuple created in the current 882 * memory context. In either case, no attempt has been made to inline 883 * out-of-line toasted values, so the tuple isn't usable as a composite 884 * datum. 885 * 886 * Returns NULL if expanded record is empty. 887 */ 888 HeapTuple 889 expanded_record_get_tuple(ExpandedRecordHeader *erh) 890 { 891 /* Easy case if we still have original tuple */ 892 if (erh->flags & ER_FLAG_FVALUE_VALID) 893 return erh->fvalue; 894 895 /* Else just build a tuple from datums */ 896 if (erh->flags & ER_FLAG_DVALUES_VALID) 897 return heap_form_tuple(erh->er_tupdesc, erh->dvalues, erh->dnulls); 898 899 /* Expanded record is empty */ 900 return NULL; 901 } 902 903 /* 904 * Memory context reset callback for cleaning up external resources 905 */ 906 static void 907 ER_mc_callback(void *arg) 908 { 909 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) arg; 910 TupleDesc tupdesc = erh->er_tupdesc; 911 912 /* Release our privately-managed tupdesc refcount, if any */ 913 if (tupdesc) 914 { 915 erh->er_tupdesc = NULL; /* just for luck */ 916 if (tupdesc->tdrefcount > 0) 917 { 918 if (--tupdesc->tdrefcount == 0) 919 FreeTupleDesc(tupdesc); 920 } 921 } 922 } 923 924 /* 925 * DatumGetExpandedRecord: get a writable expanded record from an input argument 926 * 927 * Caution: if the input is a read/write pointer, this returns the input 928 * argument; so callers must be sure that their changes are "safe", that is 929 * they cannot leave the record in a corrupt state. 930 */ 931 ExpandedRecordHeader * 932 DatumGetExpandedRecord(Datum d) 933 { 934 /* If it's a writable expanded record already, just return it */ 935 if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) 936 { 937 ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(d); 938 939 Assert(erh->er_magic == ER_MAGIC); 940 return erh; 941 } 942 943 /* Else expand the hard way */ 944 d = make_expanded_record_from_datum(d, CurrentMemoryContext); 945 return (ExpandedRecordHeader *) DatumGetEOHP(d); 946 } 947 948 /* 949 * Create the Datum/isnull representation of an expanded record object 950 * if we didn't do so already. After calling this, it's OK to read the 951 * dvalues/dnulls arrays directly, rather than going through get_field. 952 * 953 * Note that if the object is currently empty ("null"), this will change 954 * it to represent a row of nulls. 955 */ 956 void 957 deconstruct_expanded_record(ExpandedRecordHeader *erh) 958 { 959 TupleDesc tupdesc; 960 Datum *dvalues; 961 bool *dnulls; 962 int nfields; 963 964 if (erh->flags & ER_FLAG_DVALUES_VALID) 965 return; /* already valid, nothing to do */ 966 967 /* We'll need the tuple descriptor */ 968 tupdesc = expanded_record_get_tupdesc(erh); 969 970 /* 971 * Allocate arrays in private context, if we don't have them already. We 972 * don't expect to see a change in nfields here, so while we cope if it 973 * happens, we don't bother avoiding a leak of the old arrays (which might 974 * not be separately palloc'd, anyway). 975 */ 976 nfields = tupdesc->natts; 977 if (erh->dvalues == NULL || erh->nfields != nfields) 978 { 979 char *chunk; 980 981 /* 982 * To save a palloc cycle, we allocate both the Datum and isnull 983 * arrays in one palloc chunk. 984 */ 985 chunk = MemoryContextAlloc(erh->hdr.eoh_context, 986 nfields * (sizeof(Datum) + sizeof(bool))); 987 dvalues = (Datum *) chunk; 988 dnulls = (bool *) (chunk + nfields * sizeof(Datum)); 989 erh->dvalues = dvalues; 990 erh->dnulls = dnulls; 991 erh->nfields = nfields; 992 } 993 else 994 { 995 dvalues = erh->dvalues; 996 dnulls = erh->dnulls; 997 } 998 999 if (erh->flags & ER_FLAG_FVALUE_VALID) 1000 { 1001 /* Deconstruct tuple */ 1002 heap_deform_tuple(erh->fvalue, tupdesc, dvalues, dnulls); 1003 } 1004 else 1005 { 1006 /* If record was empty, instantiate it as a row of nulls */ 1007 memset(dvalues, 0, nfields * sizeof(Datum)); 1008 memset(dnulls, true, nfields * sizeof(bool)); 1009 } 1010 1011 /* Mark the dvalues as valid */ 1012 erh->flags |= ER_FLAG_DVALUES_VALID; 1013 } 1014 1015 /* 1016 * Look up a record field by name 1017 * 1018 * If there is a field named "fieldname", fill in the contents of finfo 1019 * and return "true". Else return "false" without changing *finfo. 1020 */ 1021 bool 1022 expanded_record_lookup_field(ExpandedRecordHeader *erh, const char *fieldname, 1023 ExpandedRecordFieldInfo *finfo) 1024 { 1025 TupleDesc tupdesc; 1026 int fno; 1027 Form_pg_attribute attr; 1028 1029 tupdesc = expanded_record_get_tupdesc(erh); 1030 1031 /* First, check user-defined attributes */ 1032 for (fno = 0; fno < tupdesc->natts; fno++) 1033 { 1034 attr = TupleDescAttr(tupdesc, fno); 1035 if (namestrcmp(&attr->attname, fieldname) == 0 && 1036 !attr->attisdropped) 1037 { 1038 finfo->fnumber = attr->attnum; 1039 finfo->ftypeid = attr->atttypid; 1040 finfo->ftypmod = attr->atttypmod; 1041 finfo->fcollation = attr->attcollation; 1042 return true; 1043 } 1044 } 1045 1046 /* How about system attributes? */ 1047 attr = SystemAttributeByName(fieldname, tupdesc->tdhasoid); 1048 if (attr != NULL) 1049 { 1050 finfo->fnumber = attr->attnum; 1051 finfo->ftypeid = attr->atttypid; 1052 finfo->ftypmod = attr->atttypmod; 1053 finfo->fcollation = attr->attcollation; 1054 return true; 1055 } 1056 1057 return false; 1058 } 1059 1060 /* 1061 * Fetch value of record field 1062 * 1063 * expanded_record_get_field is the frontend for this; it handles the 1064 * easy inline-able cases. 1065 */ 1066 Datum 1067 expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber, 1068 bool *isnull) 1069 { 1070 if (fnumber > 0) 1071 { 1072 /* Empty record has null fields */ 1073 if (ExpandedRecordIsEmpty(erh)) 1074 { 1075 *isnull = true; 1076 return (Datum) 0; 1077 } 1078 /* Make sure we have deconstructed form */ 1079 deconstruct_expanded_record(erh); 1080 /* Out-of-range field number reads as null */ 1081 if (unlikely(fnumber > erh->nfields)) 1082 { 1083 *isnull = true; 1084 return (Datum) 0; 1085 } 1086 *isnull = erh->dnulls[fnumber - 1]; 1087 return erh->dvalues[fnumber - 1]; 1088 } 1089 else 1090 { 1091 /* System columns read as null if we haven't got flat tuple */ 1092 if (erh->fvalue == NULL) 1093 { 1094 *isnull = true; 1095 return (Datum) 0; 1096 } 1097 /* heap_getsysattr doesn't actually use tupdesc, so just pass null */ 1098 return heap_getsysattr(erh->fvalue, fnumber, NULL, isnull); 1099 } 1100 } 1101 1102 /* 1103 * Set value of record field 1104 * 1105 * If the expanded record is of domain type, the assignment will be rejected 1106 * (without changing the record's state) if the domain's constraints would 1107 * be violated. 1108 * 1109 * If expand_external is true and newValue is an out-of-line value, we'll 1110 * forcibly detoast it so that the record does not depend on external storage. 1111 * 1112 * Internal callers can pass check_constraints = false to skip application 1113 * of domain constraints. External callers should never do that. 1114 */ 1115 void 1116 expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber, 1117 Datum newValue, bool isnull, 1118 bool expand_external, 1119 bool check_constraints) 1120 { 1121 TupleDesc tupdesc; 1122 Form_pg_attribute attr; 1123 Datum *dvalues; 1124 bool *dnulls; 1125 char *oldValue; 1126 1127 /* 1128 * Shouldn't ever be trying to assign new data to a dummy header, except 1129 * in the case of an internal call for field inlining. 1130 */ 1131 Assert(!(erh->flags & ER_FLAG_IS_DUMMY) || !check_constraints); 1132 1133 /* Before performing the assignment, see if result will satisfy domain */ 1134 if ((erh->flags & ER_FLAG_IS_DOMAIN) && check_constraints) 1135 check_domain_for_new_field(erh, fnumber, newValue, isnull); 1136 1137 /* If we haven't yet deconstructed the tuple, do that */ 1138 if (!(erh->flags & ER_FLAG_DVALUES_VALID)) 1139 deconstruct_expanded_record(erh); 1140 1141 /* Tuple descriptor must be valid by now */ 1142 tupdesc = erh->er_tupdesc; 1143 Assert(erh->nfields == tupdesc->natts); 1144 1145 /* Caller error if fnumber is system column or nonexistent column */ 1146 if (unlikely(fnumber <= 0 || fnumber > erh->nfields)) 1147 elog(ERROR, "cannot assign to field %d of expanded record", fnumber); 1148 1149 /* 1150 * Copy new field value into record's context, and deal with detoasting, 1151 * if needed. 1152 */ 1153 attr = TupleDescAttr(tupdesc, fnumber - 1); 1154 if (!isnull && !attr->attbyval) 1155 { 1156 MemoryContext oldcxt; 1157 1158 /* If requested, detoast any external value */ 1159 if (expand_external) 1160 { 1161 if (attr->attlen == -1 && 1162 VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) 1163 { 1164 /* Detoasting should be done in short-lived context. */ 1165 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); 1166 newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue))); 1167 MemoryContextSwitchTo(oldcxt); 1168 } 1169 else 1170 expand_external = false; /* need not clean up below */ 1171 } 1172 1173 /* Copy value into record's context */ 1174 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); 1175 newValue = datumCopy(newValue, false, attr->attlen); 1176 MemoryContextSwitchTo(oldcxt); 1177 1178 /* We can now flush anything that detoasting might have leaked */ 1179 if (expand_external) 1180 MemoryContextReset(erh->er_short_term_cxt); 1181 1182 /* Remember that we have field(s) that may need to be pfree'd */ 1183 erh->flags |= ER_FLAG_DVALUES_ALLOCED; 1184 1185 /* 1186 * While we're here, note whether it's an external toasted value, 1187 * because that could mean we need to inline it later. (Think not to 1188 * merge this into the previous expand_external logic: datumCopy could 1189 * by itself have made the value non-external.) 1190 */ 1191 if (attr->attlen == -1 && 1192 VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) 1193 erh->flags |= ER_FLAG_HAVE_EXTERNAL; 1194 } 1195 1196 /* 1197 * We're ready to make irreversible changes. 1198 */ 1199 dvalues = erh->dvalues; 1200 dnulls = erh->dnulls; 1201 1202 /* Flattened value will no longer represent record accurately */ 1203 erh->flags &= ~ER_FLAG_FVALUE_VALID; 1204 /* And we don't know the flattened size either */ 1205 erh->flat_size = 0; 1206 1207 /* Grab old field value for pfree'ing, if needed. */ 1208 if (!attr->attbyval && !dnulls[fnumber - 1]) 1209 oldValue = (char *) DatumGetPointer(dvalues[fnumber - 1]); 1210 else 1211 oldValue = NULL; 1212 1213 /* And finally we can insert the new field. */ 1214 dvalues[fnumber - 1] = newValue; 1215 dnulls[fnumber - 1] = isnull; 1216 1217 /* 1218 * Free old field if needed; this keeps repeated field replacements from 1219 * bloating the record's storage. If the pfree somehow fails, it won't 1220 * corrupt the record. 1221 * 1222 * If we're updating a dummy header, we can't risk pfree'ing the old 1223 * value, because most likely the expanded record's main header still has 1224 * a pointer to it. This won't result in any sustained memory leak, since 1225 * whatever we just allocated here is in the short-lived domain check 1226 * context. 1227 */ 1228 if (oldValue && !(erh->flags & ER_FLAG_IS_DUMMY)) 1229 { 1230 /* Don't try to pfree a part of the original flat record */ 1231 if (oldValue < erh->fstartptr || oldValue >= erh->fendptr) 1232 pfree(oldValue); 1233 } 1234 } 1235 1236 /* 1237 * Set all record field(s) 1238 * 1239 * Caller must ensure that the provided datums are of the right types 1240 * to match the record's previously assigned rowtype. 1241 * 1242 * If expand_external is true, we'll forcibly detoast out-of-line field values 1243 * so that the record does not depend on external storage. 1244 * 1245 * Unlike repeated application of expanded_record_set_field(), this does not 1246 * guarantee to leave the expanded record in a non-corrupt state in event 1247 * of an error. Typically it would only be used for initializing a new 1248 * expanded record. Also, because we expect this to be applied at most once 1249 * in the lifespan of an expanded record, we do not worry about any cruft 1250 * that detoasting might leak. 1251 */ 1252 void 1253 expanded_record_set_fields(ExpandedRecordHeader *erh, 1254 const Datum *newValues, const bool *isnulls, 1255 bool expand_external) 1256 { 1257 TupleDesc tupdesc; 1258 Datum *dvalues; 1259 bool *dnulls; 1260 int fnumber; 1261 MemoryContext oldcxt; 1262 1263 /* Shouldn't ever be trying to assign new data to a dummy header */ 1264 Assert(!(erh->flags & ER_FLAG_IS_DUMMY)); 1265 1266 /* If we haven't yet deconstructed the tuple, do that */ 1267 if (!(erh->flags & ER_FLAG_DVALUES_VALID)) 1268 deconstruct_expanded_record(erh); 1269 1270 /* Tuple descriptor must be valid by now */ 1271 tupdesc = erh->er_tupdesc; 1272 Assert(erh->nfields == tupdesc->natts); 1273 1274 /* Flattened value will no longer represent record accurately */ 1275 erh->flags &= ~ER_FLAG_FVALUE_VALID; 1276 /* And we don't know the flattened size either */ 1277 erh->flat_size = 0; 1278 1279 oldcxt = MemoryContextSwitchTo(erh->hdr.eoh_context); 1280 1281 dvalues = erh->dvalues; 1282 dnulls = erh->dnulls; 1283 1284 for (fnumber = 0; fnumber < erh->nfields; fnumber++) 1285 { 1286 Form_pg_attribute attr = TupleDescAttr(tupdesc, fnumber); 1287 Datum newValue; 1288 bool isnull; 1289 1290 /* Ignore dropped columns */ 1291 if (attr->attisdropped) 1292 continue; 1293 1294 newValue = newValues[fnumber]; 1295 isnull = isnulls[fnumber]; 1296 1297 if (!attr->attbyval) 1298 { 1299 /* 1300 * Copy new field value into record's context, and deal with 1301 * detoasting, if needed. 1302 */ 1303 if (!isnull) 1304 { 1305 /* Is it an external toasted value? */ 1306 if (attr->attlen == -1 && 1307 VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) 1308 { 1309 if (expand_external) 1310 { 1311 /* Detoast as requested while copying the value */ 1312 newValue = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) DatumGetPointer(newValue))); 1313 } 1314 else 1315 { 1316 /* Just copy the value */ 1317 newValue = datumCopy(newValue, false, -1); 1318 /* If it's still external, remember that */ 1319 if (VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) 1320 erh->flags |= ER_FLAG_HAVE_EXTERNAL; 1321 } 1322 } 1323 else 1324 { 1325 /* Not an external value, just copy it */ 1326 newValue = datumCopy(newValue, false, attr->attlen); 1327 } 1328 1329 /* Remember that we have field(s) that need to be pfree'd */ 1330 erh->flags |= ER_FLAG_DVALUES_ALLOCED; 1331 } 1332 1333 /* 1334 * Free old field value, if any (not likely, since really we ought 1335 * to be inserting into an empty record). 1336 */ 1337 if (unlikely(!dnulls[fnumber])) 1338 { 1339 char *oldValue; 1340 1341 oldValue = (char *) DatumGetPointer(dvalues[fnumber]); 1342 /* Don't try to pfree a part of the original flat record */ 1343 if (oldValue < erh->fstartptr || oldValue >= erh->fendptr) 1344 pfree(oldValue); 1345 } 1346 } 1347 1348 /* And finally we can insert the new field. */ 1349 dvalues[fnumber] = newValue; 1350 dnulls[fnumber] = isnull; 1351 } 1352 1353 /* 1354 * Because we don't guarantee atomicity of set_fields(), we can just leave 1355 * checking of domain constraints to occur as the final step; if it throws 1356 * an error, too bad. 1357 */ 1358 if (erh->flags & ER_FLAG_IS_DOMAIN) 1359 { 1360 /* We run domain_check in a short-lived context to limit cruft */ 1361 MemoryContextSwitchTo(get_short_term_cxt(erh)); 1362 1363 domain_check(ExpandedRecordGetRODatum(erh), false, 1364 erh->er_decltypeid, 1365 &erh->er_domaininfo, 1366 erh->hdr.eoh_context); 1367 } 1368 1369 MemoryContextSwitchTo(oldcxt); 1370 } 1371 1372 /* 1373 * Construct (or reset) working memory context for short-term operations. 1374 * 1375 * This context is used for domain check evaluation and for detoasting. 1376 * 1377 * If we don't have a short-lived memory context, make one; if we have one, 1378 * reset it to get rid of any leftover cruft. (It is a tad annoying to need a 1379 * whole context for this, since it will often go unused --- but it's hard to 1380 * avoid memory leaks otherwise. We can make the context small, at least.) 1381 */ 1382 static MemoryContext 1383 get_short_term_cxt(ExpandedRecordHeader *erh) 1384 { 1385 if (erh->er_short_term_cxt == NULL) 1386 erh->er_short_term_cxt = 1387 AllocSetContextCreate(erh->hdr.eoh_context, 1388 "expanded record short-term context", 1389 ALLOCSET_SMALL_SIZES); 1390 else 1391 MemoryContextReset(erh->er_short_term_cxt); 1392 return erh->er_short_term_cxt; 1393 } 1394 1395 /* 1396 * Construct "dummy header" for checking domain constraints. 1397 * 1398 * Since we don't want to modify the state of the expanded record until 1399 * we've validated the constraints, our approach is to set up a dummy 1400 * record header containing the new field value(s) and then pass that to 1401 * domain_check. We retain the dummy header as part of the expanded 1402 * record's state to save palloc cycles, but reinitialize (most of) 1403 * its contents on each use. 1404 */ 1405 static void 1406 build_dummy_expanded_header(ExpandedRecordHeader *main_erh) 1407 { 1408 ExpandedRecordHeader *erh; 1409 TupleDesc tupdesc = expanded_record_get_tupdesc(main_erh); 1410 1411 /* Ensure we have a short-lived context */ 1412 (void) get_short_term_cxt(main_erh); 1413 1414 /* 1415 * Allocate dummy header on first time through, or in the unlikely event 1416 * that the number of fields changes (in which case we just leak the old 1417 * one). Include space for its field values in the request. 1418 */ 1419 erh = main_erh->er_dummy_header; 1420 if (erh == NULL || erh->nfields != tupdesc->natts) 1421 { 1422 char *chunk; 1423 1424 erh = (ExpandedRecordHeader *) 1425 MemoryContextAlloc(main_erh->hdr.eoh_context, 1426 MAXALIGN(sizeof(ExpandedRecordHeader)) 1427 + tupdesc->natts * (sizeof(Datum) + sizeof(bool))); 1428 1429 /* Ensure all header fields are initialized to 0/null */ 1430 memset(erh, 0, sizeof(ExpandedRecordHeader)); 1431 1432 /* 1433 * We set up the dummy header with an indication that its memory 1434 * context is the short-lived context. This is so that, if any 1435 * detoasting of out-of-line values happens due to an attempt to 1436 * extract a composite datum from the dummy header, the detoasted 1437 * stuff will end up in the short-lived context and not cause a leak. 1438 * This is cheating a bit on the expanded-object protocol; but since 1439 * we never pass a R/W pointer to the dummy object to any other code, 1440 * nothing else is authorized to delete or transfer ownership of the 1441 * object's context, so it should be safe enough. 1442 */ 1443 EOH_init_header(&erh->hdr, &ER_methods, main_erh->er_short_term_cxt); 1444 erh->er_magic = ER_MAGIC; 1445 1446 /* Set up dvalues/dnulls, with no valid contents as yet */ 1447 chunk = (char *) erh + MAXALIGN(sizeof(ExpandedRecordHeader)); 1448 erh->dvalues = (Datum *) chunk; 1449 erh->dnulls = (bool *) (chunk + tupdesc->natts * sizeof(Datum)); 1450 erh->nfields = tupdesc->natts; 1451 1452 /* 1453 * The fields we just set are assumed to remain constant through 1454 * multiple uses of the dummy header to check domain constraints. All 1455 * other dummy header fields should be explicitly reset below, to 1456 * ensure there's not accidental effects of one check on the next one. 1457 */ 1458 1459 main_erh->er_dummy_header = erh; 1460 } 1461 1462 /* 1463 * If anything inquires about the dummy header's declared type, it should 1464 * report the composite base type, not the domain type (since the VALUE in 1465 * a domain check constraint is of the base type not the domain). Hence 1466 * we do not transfer over the IS_DOMAIN flag, nor indeed any of the main 1467 * header's flags, since the dummy header is empty of data at this point. 1468 * But don't forget to mark header as dummy. 1469 */ 1470 erh->flags = ER_FLAG_IS_DUMMY; 1471 1472 /* Copy composite-type identification info */ 1473 erh->er_decltypeid = erh->er_typeid = main_erh->er_typeid; 1474 erh->er_typmod = main_erh->er_typmod; 1475 1476 /* Dummy header does not need its own tupdesc refcount */ 1477 erh->er_tupdesc = tupdesc; 1478 erh->er_tupdesc_id = main_erh->er_tupdesc_id; 1479 1480 /* 1481 * It's tempting to copy over whatever we know about the flat size, but 1482 * there's no point since we're surely about to modify the dummy record's 1483 * field(s). Instead just clear anything left over from a previous usage 1484 * cycle. 1485 */ 1486 erh->flat_size = 0; 1487 1488 /* Copy over fvalue if we have it, so that system columns are available */ 1489 erh->fvalue = main_erh->fvalue; 1490 erh->fstartptr = main_erh->fstartptr; 1491 erh->fendptr = main_erh->fendptr; 1492 } 1493 1494 /* 1495 * Precheck domain constraints for a set_field operation 1496 */ 1497 static pg_noinline void 1498 check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber, 1499 Datum newValue, bool isnull) 1500 { 1501 ExpandedRecordHeader *dummy_erh; 1502 MemoryContext oldcxt; 1503 1504 /* Construct dummy header to contain proposed new field set */ 1505 build_dummy_expanded_header(erh); 1506 dummy_erh = erh->er_dummy_header; 1507 1508 /* 1509 * If record isn't empty, just deconstruct it (if needed) and copy over 1510 * the existing field values. If it is empty, just fill fields with nulls 1511 * manually --- don't call deconstruct_expanded_record prematurely. 1512 */ 1513 if (!ExpandedRecordIsEmpty(erh)) 1514 { 1515 deconstruct_expanded_record(erh); 1516 memcpy(dummy_erh->dvalues, erh->dvalues, 1517 dummy_erh->nfields * sizeof(Datum)); 1518 memcpy(dummy_erh->dnulls, erh->dnulls, 1519 dummy_erh->nfields * sizeof(bool)); 1520 /* There might be some external values in there... */ 1521 dummy_erh->flags |= erh->flags & ER_FLAG_HAVE_EXTERNAL; 1522 } 1523 else 1524 { 1525 memset(dummy_erh->dvalues, 0, dummy_erh->nfields * sizeof(Datum)); 1526 memset(dummy_erh->dnulls, true, dummy_erh->nfields * sizeof(bool)); 1527 } 1528 1529 /* Either way, we now have valid dvalues */ 1530 dummy_erh->flags |= ER_FLAG_DVALUES_VALID; 1531 1532 /* Caller error if fnumber is system column or nonexistent column */ 1533 if (unlikely(fnumber <= 0 || fnumber > dummy_erh->nfields)) 1534 elog(ERROR, "cannot assign to field %d of expanded record", fnumber); 1535 1536 /* Insert proposed new value into dummy field array */ 1537 dummy_erh->dvalues[fnumber - 1] = newValue; 1538 dummy_erh->dnulls[fnumber - 1] = isnull; 1539 1540 /* 1541 * The proposed new value might be external, in which case we'd better set 1542 * the flag for that in dummy_erh. (This matters in case something in the 1543 * domain check expressions tries to extract a flat value from the dummy 1544 * header.) 1545 */ 1546 if (!isnull) 1547 { 1548 Form_pg_attribute attr = TupleDescAttr(erh->er_tupdesc, fnumber - 1); 1549 1550 if (!attr->attbyval && attr->attlen == -1 && 1551 VARATT_IS_EXTERNAL(DatumGetPointer(newValue))) 1552 dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL; 1553 } 1554 1555 /* 1556 * We call domain_check in the short-lived context, so that any cruft 1557 * leaked by expression evaluation can be reclaimed. 1558 */ 1559 oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt); 1560 1561 /* 1562 * And now we can apply the check. Note we use main header's domain cache 1563 * space, so that caching carries across repeated uses. 1564 */ 1565 domain_check(ExpandedRecordGetRODatum(dummy_erh), false, 1566 erh->er_decltypeid, 1567 &erh->er_domaininfo, 1568 erh->hdr.eoh_context); 1569 1570 MemoryContextSwitchTo(oldcxt); 1571 1572 /* We might as well clean up cruft immediately. */ 1573 MemoryContextReset(erh->er_short_term_cxt); 1574 } 1575 1576 /* 1577 * Precheck domain constraints for a set_tuple operation 1578 */ 1579 static pg_noinline void 1580 check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple) 1581 { 1582 ExpandedRecordHeader *dummy_erh; 1583 MemoryContext oldcxt; 1584 1585 /* If we're being told to set record to empty, just see if NULL is OK */ 1586 if (tuple == NULL) 1587 { 1588 /* We run domain_check in a short-lived context to limit cruft */ 1589 oldcxt = MemoryContextSwitchTo(get_short_term_cxt(erh)); 1590 1591 domain_check((Datum) 0, true, 1592 erh->er_decltypeid, 1593 &erh->er_domaininfo, 1594 erh->hdr.eoh_context); 1595 1596 MemoryContextSwitchTo(oldcxt); 1597 1598 /* We might as well clean up cruft immediately. */ 1599 MemoryContextReset(erh->er_short_term_cxt); 1600 1601 return; 1602 } 1603 1604 /* Construct dummy header to contain replacement tuple */ 1605 build_dummy_expanded_header(erh); 1606 dummy_erh = erh->er_dummy_header; 1607 1608 /* Insert tuple, but don't bother to deconstruct its fields for now */ 1609 dummy_erh->fvalue = tuple; 1610 dummy_erh->fstartptr = (char *) tuple->t_data; 1611 dummy_erh->fendptr = ((char *) tuple->t_data) + tuple->t_len; 1612 dummy_erh->flags |= ER_FLAG_FVALUE_VALID; 1613 1614 /* Remember if we have any out-of-line field values */ 1615 if (HeapTupleHasExternal(tuple)) 1616 dummy_erh->flags |= ER_FLAG_HAVE_EXTERNAL; 1617 1618 /* 1619 * We call domain_check in the short-lived context, so that any cruft 1620 * leaked by expression evaluation can be reclaimed. 1621 */ 1622 oldcxt = MemoryContextSwitchTo(erh->er_short_term_cxt); 1623 1624 /* 1625 * And now we can apply the check. Note we use main header's domain cache 1626 * space, so that caching carries across repeated uses. 1627 */ 1628 domain_check(ExpandedRecordGetRODatum(dummy_erh), false, 1629 erh->er_decltypeid, 1630 &erh->er_domaininfo, 1631 erh->hdr.eoh_context); 1632 1633 MemoryContextSwitchTo(oldcxt); 1634 1635 /* We might as well clean up cruft immediately. */ 1636 MemoryContextReset(erh->er_short_term_cxt); 1637 } 1638