Line data Source code
1 : /*------------------------------------------------------------------------- 2 : * 3 : * toast_helper.c 4 : * Helper functions for table AMs implementing compressed or 5 : * out-of-line storage of varlena attributes. 6 : * 7 : * Copyright (c) 2000-2024, PostgreSQL Global Development Group 8 : * 9 : * IDENTIFICATION 10 : * src/backend/access/table/toast_helper.c 11 : * 12 : *------------------------------------------------------------------------- 13 : */ 14 : 15 : #include "postgres.h" 16 : 17 : #include "access/detoast.h" 18 : #include "access/toast_helper.h" 19 : #include "access/toast_internals.h" 20 : #include "catalog/pg_type_d.h" 21 : #include "varatt.h" 22 : 23 : 24 : /* 25 : * Prepare to TOAST a tuple. 26 : * 27 : * tupleDesc, toast_values, and toast_isnull are required parameters; they 28 : * provide the necessary details about the tuple to be toasted. 29 : * 30 : * toast_oldvalues and toast_oldisnull should be NULL for a newly-inserted 31 : * tuple; for an update, they should describe the existing tuple. 32 : * 33 : * All of these arrays should have a length equal to tupleDesc->natts. 34 : * 35 : * On return, toast_flags and toast_attr will have been initialized. 36 : * toast_flags is just a single uint8, but toast_attr is a caller-provided 37 : * array with a length equal to tupleDesc->natts. The caller need not 38 : * perform any initialization of the array before calling this function. 39 : */ 40 : void 41 36382 : toast_tuple_init(ToastTupleContext *ttc) 42 : { 43 36382 : TupleDesc tupleDesc = ttc->ttc_rel->rd_att; 44 36382 : int numAttrs = tupleDesc->natts; 45 : int i; 46 : 47 36382 : ttc->ttc_flags = 0; 48 : 49 336134 : for (i = 0; i < numAttrs; i++) 50 : { 51 299752 : Form_pg_attribute att = TupleDescAttr(tupleDesc, i); 52 : struct varlena *old_value; 53 : struct varlena *new_value; 54 : 55 299752 : ttc->ttc_attr[i].tai_colflags = 0; 56 299752 : ttc->ttc_attr[i].tai_oldexternal = NULL; 57 299752 : ttc->ttc_attr[i].tai_compression = att->attcompression; 58 : 59 299752 : if (ttc->ttc_oldvalues != NULL) 60 : { 61 : /* 62 : * For UPDATE get the old and new values of this attribute 63 : */ 64 : old_value = 65 56770 : (struct varlena *) DatumGetPointer(ttc->ttc_oldvalues[i]); 66 : new_value = 67 56770 : (struct varlena *) DatumGetPointer(ttc->ttc_values[i]); 68 : 69 : /* 70 : * If the old value is stored on disk, check if it has changed so 71 : * we have to delete it later. 72 : */ 73 56770 : if (att->attlen == -1 && !ttc->ttc_oldisnull[i] && 74 7002 : VARATT_IS_EXTERNAL_ONDISK(old_value)) 75 : { 76 648 : if (ttc->ttc_isnull[i] || 77 630 : !VARATT_IS_EXTERNAL_ONDISK(new_value) || 78 96 : memcmp((char *) old_value, (char *) new_value, 79 96 : VARSIZE_EXTERNAL(old_value)) != 0) 80 : { 81 : /* 82 : * The old external stored value isn't needed any more 83 : * after the update 84 : */ 85 554 : ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_DELETE_OLD; 86 554 : ttc->ttc_flags |= TOAST_NEEDS_DELETE_OLD; 87 : } 88 : else 89 : { 90 : /* 91 : * This attribute isn't changed by this update so we reuse 92 : * the original reference to the old value in the new 93 : * tuple. 94 : */ 95 94 : ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; 96 94 : continue; 97 : } 98 : } 99 : } 100 : else 101 : { 102 : /* 103 : * For INSERT simply get the new value 104 : */ 105 242982 : new_value = (struct varlena *) DatumGetPointer(ttc->ttc_values[i]); 106 : } 107 : 108 : /* 109 : * Handle NULL attributes 110 : */ 111 299658 : if (ttc->ttc_isnull[i]) 112 : { 113 28564 : ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; 114 28564 : ttc->ttc_flags |= TOAST_HAS_NULLS; 115 28564 : continue; 116 : } 117 : 118 : /* 119 : * Now look at varlena attributes 120 : */ 121 271094 : if (att->attlen == -1) 122 : { 123 : /* 124 : * If the table's attribute says PLAIN always, force it so. 125 : */ 126 65578 : if (att->attstorage == TYPSTORAGE_PLAIN) 127 1872 : ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; 128 : 129 : /* 130 : * We took care of UPDATE above, so any external value we find 131 : * still in the tuple must be someone else's that we cannot reuse 132 : * (this includes the case of an out-of-line in-memory datum). 133 : * Fetch it back (without decompression, unless we are forcing 134 : * PLAIN storage). If necessary, we'll push it out as a new 135 : * external value below. 136 : */ 137 65578 : if (VARATT_IS_EXTERNAL(new_value)) 138 : { 139 938 : ttc->ttc_attr[i].tai_oldexternal = new_value; 140 938 : if (att->attstorage == TYPSTORAGE_PLAIN) 141 0 : new_value = detoast_attr(new_value); 142 : else 143 938 : new_value = detoast_external_attr(new_value); 144 938 : ttc->ttc_values[i] = PointerGetDatum(new_value); 145 938 : ttc->ttc_attr[i].tai_colflags |= TOASTCOL_NEEDS_FREE; 146 938 : ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); 147 : } 148 : 149 : /* 150 : * Remember the size of this attribute 151 : */ 152 65578 : ttc->ttc_attr[i].tai_size = VARSIZE_ANY(new_value); 153 : } 154 : else 155 : { 156 : /* 157 : * Not a varlena attribute, plain storage always 158 : */ 159 205516 : ttc->ttc_attr[i].tai_colflags |= TOASTCOL_IGNORE; 160 : } 161 : } 162 36382 : } 163 : 164 : /* 165 : * Find the largest varlena attribute that satisfies certain criteria. 166 : * 167 : * The relevant column must not be marked TOASTCOL_IGNORE, and if the 168 : * for_compression flag is passed as true, it must also not be marked 169 : * TOASTCOL_INCOMPRESSIBLE. 170 : * 171 : * The column must have attstorage EXTERNAL or EXTENDED if check_main is 172 : * false, and must have attstorage MAIN if check_main is true. 173 : * 174 : * The column must have a minimum size of MAXALIGN(TOAST_POINTER_SIZE); 175 : * if not, no benefit is to be expected by compressing it. 176 : * 177 : * The return value is the index of the biggest suitable column, or 178 : * -1 if there is none. 179 : */ 180 : int 181 42092 : toast_tuple_find_biggest_attribute(ToastTupleContext *ttc, 182 : bool for_compression, bool check_main) 183 : { 184 42092 : TupleDesc tupleDesc = ttc->ttc_rel->rd_att; 185 42092 : int numAttrs = tupleDesc->natts; 186 42092 : int biggest_attno = -1; 187 42092 : int32 biggest_size = MAXALIGN(TOAST_POINTER_SIZE); 188 42092 : int32 skip_colflags = TOASTCOL_IGNORE; 189 : int i; 190 : 191 42092 : if (for_compression) 192 40608 : skip_colflags |= TOASTCOL_INCOMPRESSIBLE; 193 : 194 488630 : for (i = 0; i < numAttrs; i++) 195 : { 196 446538 : Form_pg_attribute att = TupleDescAttr(tupleDesc, i); 197 : 198 446538 : if ((ttc->ttc_attr[i].tai_colflags & skip_colflags) != 0) 199 362952 : continue; 200 83586 : if (VARATT_IS_EXTERNAL(DatumGetPointer(ttc->ttc_values[i]))) 201 0 : continue; /* can't happen, toast_action would be PLAIN */ 202 83586 : if (for_compression && 203 79356 : VARATT_IS_COMPRESSED(DatumGetPointer(ttc->ttc_values[i]))) 204 6694 : continue; 205 76892 : if (check_main && att->attstorage != TYPSTORAGE_MAIN) 206 0 : continue; 207 76892 : if (!check_main && att->attstorage != TYPSTORAGE_EXTENDED && 208 5922 : att->attstorage != TYPSTORAGE_EXTERNAL) 209 112 : continue; 210 : 211 76780 : if (ttc->ttc_attr[i].tai_size > biggest_size) 212 : { 213 51232 : biggest_attno = i; 214 51232 : biggest_size = ttc->ttc_attr[i].tai_size; 215 : } 216 : } 217 : 218 42092 : return biggest_attno; 219 : } 220 : 221 : /* 222 : * Try compression for an attribute. 223 : * 224 : * If we find that the attribute is not compressible, mark it so. 225 : */ 226 : void 227 33332 : toast_tuple_try_compression(ToastTupleContext *ttc, int attribute) 228 : { 229 33332 : Datum *value = &ttc->ttc_values[attribute]; 230 : Datum new_value; 231 33332 : ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; 232 : 233 33332 : new_value = toast_compress_datum(*value, attr->tai_compression); 234 : 235 33332 : if (DatumGetPointer(new_value) != NULL) 236 : { 237 : /* successful compression */ 238 31896 : if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) 239 122 : pfree(DatumGetPointer(*value)); 240 31896 : *value = new_value; 241 31896 : attr->tai_colflags |= TOASTCOL_NEEDS_FREE; 242 31896 : attr->tai_size = VARSIZE(DatumGetPointer(*value)); 243 31896 : ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); 244 : } 245 : else 246 : { 247 : /* incompressible, ignore on subsequent compression passes */ 248 1436 : attr->tai_colflags |= TOASTCOL_INCOMPRESSIBLE; 249 : } 250 33332 : } 251 : 252 : /* 253 : * Move an attribute to external storage. 254 : */ 255 : void 256 15264 : toast_tuple_externalize(ToastTupleContext *ttc, int attribute, int options) 257 : { 258 15264 : Datum *value = &ttc->ttc_values[attribute]; 259 15264 : Datum old_value = *value; 260 15264 : ToastAttrInfo *attr = &ttc->ttc_attr[attribute]; 261 : 262 15264 : attr->tai_colflags |= TOASTCOL_IGNORE; 263 15264 : *value = toast_save_datum(ttc->ttc_rel, old_value, attr->tai_oldexternal, 264 : options); 265 15264 : if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) 266 9372 : pfree(DatumGetPointer(old_value)); 267 15264 : attr->tai_colflags |= TOASTCOL_NEEDS_FREE; 268 15264 : ttc->ttc_flags |= (TOAST_NEEDS_CHANGE | TOAST_NEEDS_FREE); 269 15264 : } 270 : 271 : /* 272 : * Perform appropriate cleanup after one tuple has been subjected to TOAST. 273 : */ 274 : void 275 36382 : toast_tuple_cleanup(ToastTupleContext *ttc) 276 : { 277 36382 : TupleDesc tupleDesc = ttc->ttc_rel->rd_att; 278 36382 : int numAttrs = tupleDesc->natts; 279 : 280 : /* 281 : * Free allocated temp values 282 : */ 283 36382 : if ((ttc->ttc_flags & TOAST_NEEDS_FREE) != 0) 284 : { 285 : int i; 286 : 287 334438 : for (i = 0; i < numAttrs; i++) 288 : { 289 298198 : ToastAttrInfo *attr = &ttc->ttc_attr[i]; 290 : 291 298198 : if ((attr->tai_colflags & TOASTCOL_NEEDS_FREE) != 0) 292 38604 : pfree(DatumGetPointer(ttc->ttc_values[i])); 293 : } 294 : } 295 : 296 : /* 297 : * Delete external values from the old tuple 298 : */ 299 36382 : if ((ttc->ttc_flags & TOAST_NEEDS_DELETE_OLD) != 0) 300 : { 301 : int i; 302 : 303 12262 : for (i = 0; i < numAttrs; i++) 304 : { 305 11770 : ToastAttrInfo *attr = &ttc->ttc_attr[i]; 306 : 307 11770 : if ((attr->tai_colflags & TOASTCOL_NEEDS_DELETE_OLD) != 0) 308 554 : toast_delete_datum(ttc->ttc_rel, ttc->ttc_oldvalues[i], false); 309 : } 310 : } 311 36382 : } 312 : 313 : /* 314 : * Check for external stored attributes and delete them from the secondary 315 : * relation. 316 : */ 317 : void 318 544 : toast_delete_external(Relation rel, const Datum *values, const bool *isnull, 319 : bool is_speculative) 320 : { 321 544 : TupleDesc tupleDesc = rel->rd_att; 322 544 : int numAttrs = tupleDesc->natts; 323 : int i; 324 : 325 4236 : for (i = 0; i < numAttrs; i++) 326 : { 327 3692 : if (TupleDescAttr(tupleDesc, i)->attlen == -1) 328 : { 329 1256 : Datum value = values[i]; 330 : 331 1256 : if (isnull[i]) 332 336 : continue; 333 920 : else if (VARATT_IS_EXTERNAL_ONDISK(value)) 334 564 : toast_delete_datum(rel, value, is_speculative); 335 : } 336 : } 337 544 : }