/*------------------------------------------------------------------------- * * toast_helper.c * Helper functions for table AMs implementing compressed or * out-of-line storage of varlena attributes. * * Copyright (c) 2000-2022, PostgreSQL Global Development Group * * IDENTIFICATION * src/backend/access/table/toast_helper.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/detoast.h" #include "access/table.h" #include "access/toast_helper.h" #include "access/toast_internals.h" #include "catalog/pg_type_d.h" /* * Prepare to TOAST a tuple. * * tupleDesc, toast_values, and toast_isnull are required parameters; they * provide the necessary details about the tuple to be toasted. * * toast_oldvalues and toast_oldisnull should be NULL for a newly-inserted * tuple; for an update, they should describe the existing tuple. * * All of these arrays should have a length equal to tupleDesc->natts. * * On return, toast_flags and toast_attr will have been initialized. * toast_flags is just a single uint8, but toast_attr is a caller-provided * array with a length equal to tupleDesc->natts. The caller need not * perform any initialization of the array before calling this function. */ void toast_tuple_init(ToastTupleContext *ttc) { TupleDesc tupleDesc = ttc->ttc_rel->rd_att; int numAttrs = tupleDesc->natts; int i; ttc->ttc_flags = 0; for (i = 0; i < numAttrs; i++) { Form_pg_attribute att = TupleDescAttr(tupleDesc, i); struct varlena *old_value; struct varlena *new_value; ttc->ttc_attr[i].tai_colflags = 0; ttc->ttc_attr[i].tai_oldexternal = NULL; ttc->ttc_attr[i].tai_compression = att->attcompression; if (ttc->ttc_oldvalues != NULL) { /* * For UPDATE get the old and new values of this attribute */ old_value = (struct varlena *) DatumGetPointer(ttc->ttc_oldvalues[i]); new_value = (struct varlena *) DatumGetPointer(ttc->ttc_values[i]); /* * If the old value is stored on disk, check if it has changed so * we have to delete it later. */ if (att->attlen == -1 && !ttc->ttc_oldisnull[i] && VARATT_IS_EXTERNAL_ONDISK(old_value)) { if (ttc->ttc_isnull[i] || !VARATT_IS_EXTERNAL_ONDISK(new_value) || memcmp((char *) old_value, (char *) new_value, VARSIZE_EXTERNAL(old_value)) != 0) { /* * The old external stored value isn't needed any more * after the update */ ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_DELETE_OLD; ttc->ttc_flags |= TOAST_NEEDS_DELETE_OLD; } else { /* * This attribute isn't changed by this update so we reuse * the original reference to the old value in the new * tuple. */ ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; continue; } } } else { /* * For INSERT simply get the new value */ new_value = (struct varlena *) DatumGetPointer(ttc->ttc_values[i]); } /* * Handle NULL attributes */ if (ttc->ttc_isnull[i]) { ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; ttc->ttc_flags |= TOAST_HAS_NULLS; continue; } /* * Now look at varlena attributes */ if (att->attlen == -1) { /* * If the table's attribute says PLAIN always, force it so. */ if (att->attstorage == TYPSTORAGE_PLAIN) ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; /* * We took care of UPDATE above, so any external value we find * still in the tuple must be someone else's that we cannot reuse * (this includes the case of an out-of-line in-memory datum). * Fetch it back (without decompression, unless we are forcing * PLAIN storage). If necessary, we'll push it out as a new * external value below. */ if (VARATT_IS_EXTERNAL(new_value)) { ttc->ttc_attr[i].tai_oldexternal = new_value; if (att->attstorage == TYPSTORAGE_PLAIN) new_value = detoast_attr(new_value); else new_value = detoast_external_attr(new_value); ttc->ttc_values[i] = PointerGetDatum(new_value); ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_FREE; ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); } /* * Remember the size of this attribute */ ttc->ttc_attr[i].tai_size = VARSIZE_ANY(new_value); } else { /* * Not a varlena attribute, plain storage always */ ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; } } } /* * Find the largest varlena attribute that satisfies certain criteria. * * The relevant column must not be marked TOASTCOL_IGNORE, and if the * for_compression flag is passed as true, it must also not be marked * TOASTCOL_INCOMPRESSIBLE. * * The column must have attstorage EXTERNAL or EXTENDED if check_main is * false, and must have attstorage MAIN if check_main is true. * * The column must have a minimum size of MAXALIGN(TOAST_POINTER_SIZE); * if not, no benefit is to be expected by compressing it. * * The return value is the index of the biggest suitable column, or * -1 if there is none. */ int toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, bool for_compression, bool check_main) { TupleDesc tupleDesc = ttc->ttc_rel->rd_att; int numAttrs = tupleDesc->natts; int biggest_attno = -1; int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); int32 skip_colflags = TOASTCOL_IGNORE; int i; if (for_compression) skip_colflags |= TOASTCOL_INCOMPRESSIBLE; for (i = 0; i < numAttrs; i++) { Form_pg_attribute att = TupleDescAttr(tupleDesc, i); if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0) continue; if (VARATT_IS_EXTERNAL(DatumGetPointer(ttc->ttc_values[i]))) continue; /* can't happen, toast_action would be PLAIN */ if (for_compression && VARATT_IS_COMPRESSED(DatumGetPointer(ttc->ttc_values[i]))) continue; if (check_main && att->attstorage != TYPSTORAGE_MAIN) continue; if (!check_main && att->attstorage != TYPSTORAGE_EXTENDED && att->attstorage != TYPSTORAGE_EXTERNAL) continue; if (ttc->ttc_attr[i].tai_size > biggest_size) { biggest_attno = i; biggest_size = ttc->ttc_attr[i].tai_size; } } return biggest_attno; } /* * Try compression for an attribute. * * If we find that the attribute is not compressible, mark it so. */ void toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) { Datum *value = &ttc->ttc_values[attribute]; Datum new_value; ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; new_value = toast_compress_datum(*value, attr->tai_compression); if (DatumGetPointer(new_value) != NULL) { /* successful compression */ if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) pfree(DatumGetPointer(*value)); *value = new_value; attr->tai_colflags |= TOASTCOL_NEEDS_FREE; attr->tai_size = VARSIZE(DatumGetPointer(*value)); ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); } else { /* incompressible, ignore on subsequent compression passes */ attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE; } } /* * Move an attribute to external storage. */ void toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) { Datum *value = &ttc->ttc_values[attribute]; Datum old_value = *value; ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; attr->tai_colflags |= TOASTCOL_IGNORE; *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal, options); if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) pfree(DatumGetPointer(old_value)); attr->tai_colflags |= TOASTCOL_NEEDS_FREE; ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); } /* * Perform appropriate cleanup after one tuple has been subjected to TOAST. */ void toast_tuple_cleanup(ToastTupleContext *ttc) { TupleDesc tupleDesc = ttc->ttc_rel->rd_att; int numAttrs = tupleDesc->natts; /* * Free allocated temp values */ if ((ttc->ttc_flags & TOAST_NEEDS_FREE) != 0) { int i; for (i = 0; i < numAttrs; i++) { ToastAttrInfo *attr = &ttc->ttc_attr[i]; if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) pfree(DatumGetPointer(ttc->ttc_values[i])); } } /* * Delete external values from the old tuple */ if ((ttc->ttc_flags & TOAST_NEEDS_DELETE_OLD) != 0) { int i; for (i = 0; i < numAttrs; i++) { ToastAttrInfo *attr = &ttc->ttc_attr[i]; if ((attr->tai_colflags & TOASTCOL_NEEDS_DELETE_OLD) != 0) toast_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false); } } } /* * Check for external stored attributes and delete them from the secondary * relation. */ void toast_delete_external(Relation rel, Datum *values, bool *isnull, bool is_speculative) { TupleDesc tupleDesc = rel->rd_att; int numAttrs = tupleDesc->natts; int i; for (i = 0; i < numAttrs; i++) { if (TupleDescAttr(tupleDesc, i)->attlen == -1) { Datum value = values[i]; if (isnull[i]) continue; else if (VARATT_IS_EXTERNAL_ONDISK(PointerGetDatum(value))) toast_delete_datum(rel, value, is_speculative); } } }