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