]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
tableam: New callback relation_fetch_toast_slice.
authorRobert Haas <rhaas@postgresql.org>
Tue, 7 Jan 2020 19:35:48 +0000 (14:35 -0500)
committerRobert Haas <rhaas@postgresql.org>
Tue, 7 Jan 2020 19:36:38 +0000 (14:36 -0500)
Instead of always calling heap_fetch_toast_slice during detoasting,
invoke a table AM callback which, when the toast table is a heap
table, will be heap_fetch_toast_slice.

This makes it possible for a table AM other than heap to be used
as a TOAST table. It also completes the series of commits intended
to improve the interaction of tableam with TOAST that began with
commit 8b94dab06617ef80a0901ab103ebd8754427ef5a; detoast.c is
now, hopefully, fully AM-independent.

Patch by me, reviewed by Andres Freund and Peter Eisentraut.

Discussion: http://postgr.es/m/CA+TgmoZv-=2iWM4jcw5ZhJeL18HF96+W1yJeYrnGMYdkFFnEpQ@mail.gmail.com

src/backend/access/common/detoast.c
src/backend/access/heap/heapam_handler.c
src/backend/access/heap/heaptoast.c
src/include/access/heaptoast.h
src/include/access/tableam.h

index 9b4682f2ad44d4d854476d7d24a74549b863eb2a..496240c755180920072c54d29028a70edf7932e3 100644 (file)
 #include "postgres.h"
 
 #include "access/detoast.h"
-#include "access/genam.h"
-#include "access/heaptoast.h"
 #include "access/table.h"
+#include "access/tableam.h"
 #include "access/toast_internals.h"
 #include "common/pg_lzcompress.h"
 #include "utils/expandeddatum.h"
-#include "utils/fmgroids.h"
 #include "utils/rel.h"
 
 static struct varlena *toast_fetch_datum(struct varlena *attr);
 static struct varlena *toast_fetch_datum_slice(struct varlena *attr,
                                                                                           int32 sliceoffset,
                                                                                           int32 slicelength);
-static void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
-                                                                  int32 attrsize, int32 sliceoffset,
-                                                                  int32 slicelength, struct varlena *result);
 static struct varlena *toast_decompress_datum(struct varlena *attr);
 static struct varlena *toast_decompress_datum_slice(struct varlena *attr, int32 slicelength);
 
@@ -356,8 +351,8 @@ toast_fetch_datum(struct varlena *attr)
        toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
        /* Fetch all chunks */
-       heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize, 0,
-                                                  attrsize, result);
+       table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+                                                                        attrsize, 0, attrsize, result);
 
        /* Close toast table */
        table_close(toastrel, AccessShareLock);
@@ -431,8 +426,9 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
        toastrel = table_open(toast_pointer.va_toastrelid, AccessShareLock);
 
        /* Fetch all chunks */
-       heap_fetch_toast_slice(toastrel, toast_pointer.va_valueid, attrsize,
-                                                  sliceoffset, slicelength, result);
+       table_relation_fetch_toast_slice(toastrel, toast_pointer.va_valueid,
+                                                                        attrsize, sliceoffset, slicelength,
+                                                                        result);
 
        /* Close toast table */
        table_close(toastrel, AccessShareLock);
@@ -440,189 +436,6 @@ toast_fetch_datum_slice(struct varlena *attr, int32 sliceoffset,
        return result;
 }
 
-/*
- * Fetch a TOAST slice from a heap table.
- *
- * toastrel is the relation from which chunks are to be fetched.
- * valueid identifies the TOAST value from which chunks are being fetched.
- * attrsize is the total size of the TOAST value.
- * sliceoffset is the byte offset within the TOAST value from which to fetch.
- * slicelength is the number of bytes to be fetched from the TOAST value.
- * result is the varlena into which the results should be written.
- */
-static void
-heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
-                                          int32 sliceoffset, int32 slicelength,
-                                          struct varlena *result)
-{
-       Relation   *toastidxs;
-       ScanKeyData toastkey[3];
-       TupleDesc       toasttupDesc = toastrel->rd_att;
-       int                     nscankeys;
-       SysScanDesc toastscan;
-       HeapTuple       ttup;
-       int32           expectedchunk;
-       int32           totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
-       int                     startchunk;
-       int                     endchunk;
-       int                     num_indexes;
-       int                     validIndex;
-       SnapshotData SnapshotToast;
-
-       /* Look for the valid index of toast relation */
-       validIndex = toast_open_indexes(toastrel,
-                                                                       AccessShareLock,
-                                                                       &toastidxs,
-                                                                       &num_indexes);
-
-       startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
-       endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
-       Assert(endchunk <= totalchunks);
-
-       /*
-        * Setup a scan key to fetch from the index. This is either two keys or
-        * three depending on the number of chunks.
-        */
-       ScanKeyInit(&toastkey[0],
-                               (AttrNumber) 1,
-                               BTEqualStrategyNumber, F_OIDEQ,
-                               ObjectIdGetDatum(valueid));
-
-       /*
-        * No additional condition if fetching all chunks. Otherwise, use an
-        * equality condition for one chunk, and a range condition otherwise.
-        */
-       if (startchunk == 0 && endchunk == totalchunks - 1)
-               nscankeys = 1;
-       else if (startchunk == endchunk)
-       {
-               ScanKeyInit(&toastkey[1],
-                                       (AttrNumber) 2,
-                                       BTEqualStrategyNumber, F_INT4EQ,
-                                       Int32GetDatum(startchunk));
-               nscankeys = 2;
-       }
-       else
-       {
-               ScanKeyInit(&toastkey[1],
-                                       (AttrNumber) 2,
-                                       BTGreaterEqualStrategyNumber, F_INT4GE,
-                                       Int32GetDatum(startchunk));
-               ScanKeyInit(&toastkey[2],
-                                       (AttrNumber) 2,
-                                       BTLessEqualStrategyNumber, F_INT4LE,
-                                       Int32GetDatum(endchunk));
-               nscankeys = 3;
-       }
-
-       /* Prepare for scan */
-       init_toast_snapshot(&SnapshotToast);
-       toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
-                                                                                  &SnapshotToast, nscankeys, toastkey);
-
-       /*
-        * Read the chunks by index
-        *
-        * The index is on (valueid, chunkidx) so they will come in order
-        */
-       expectedchunk = startchunk;
-       while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
-       {
-               int32           curchunk;
-               Pointer         chunk;
-               bool            isnull;
-               char       *chunkdata;
-               int32           chunksize;
-               int32           expected_size;
-               int32           chcpystrt;
-               int32           chcpyend;
-
-               /*
-                * Have a chunk, extract the sequence number and the data
-                */
-               curchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
-               Assert(!isnull);
-               chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
-               Assert(!isnull);
-               if (!VARATT_IS_EXTENDED(chunk))
-               {
-                       chunksize = VARSIZE(chunk) - VARHDRSZ;
-                       chunkdata = VARDATA(chunk);
-               }
-               else if (VARATT_IS_SHORT(chunk))
-               {
-                       /* could happen due to heap_form_tuple doing its thing */
-                       chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
-                       chunkdata = VARDATA_SHORT(chunk);
-               }
-               else
-               {
-                       /* should never happen */
-                       elog(ERROR, "found toasted toast chunk for toast value %u in %s",
-                                valueid, RelationGetRelationName(toastrel));
-                       chunksize = 0;          /* keep compiler quiet */
-                       chunkdata = NULL;
-               }
-
-               /*
-                * Some checks on the data we've found
-                */
-               if (curchunk != expectedchunk)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATA_CORRUPTED),
-                                        errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
-                                                                        curchunk, expectedchunk, valueid,
-                                                                        RelationGetRelationName(toastrel))));
-               if (curchunk > endchunk)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATA_CORRUPTED),
-                                        errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
-                                                                        curchunk,
-                                                                        startchunk, endchunk, valueid,
-                                                                        RelationGetRelationName(toastrel))));
-               expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
-                       : attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
-               if (chunksize != expected_size)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATA_CORRUPTED),
-                                        errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
-                                                                        chunksize, expected_size,
-                                                                        curchunk, totalchunks, valueid,
-                                                                        RelationGetRelationName(toastrel))));
-
-               /*
-                * Copy the data into proper place in our result
-                */
-               chcpystrt = 0;
-               chcpyend = chunksize - 1;
-               if (curchunk == startchunk)
-                       chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
-               if (curchunk == endchunk)
-                       chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
-
-               memcpy(VARDATA(result) +
-                          (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
-                          chunkdata + chcpystrt,
-                          (chcpyend - chcpystrt) + 1);
-
-               expectedchunk++;
-       }
-
-       /*
-        * Final checks that we successfully fetched the datum
-        */
-       if (expectedchunk != (endchunk + 1))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATA_CORRUPTED),
-                                errmsg_internal("missing chunk number %d for toast value %u in %s",
-                                                                expectedchunk, valueid,
-                                                                RelationGetRelationName(toastrel))));
-
-       /* End scan and close indexes. */
-       systable_endscan_ordered(toastscan);
-       toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
-}
-
 /* ----------
  * toast_decompress_datum -
  *
index 1ed60f4f4e544daa9124e105eaed3b9fb7b5f5ad..1f6f6d0ea9eea253c8b6f2040d2b3b8351d26f1f 100644 (file)
@@ -2545,6 +2545,7 @@ static const TableAmRoutine heapam_methods = {
        .relation_size = table_block_relation_size,
        .relation_needs_toast_table = heapam_relation_needs_toast_table,
        .relation_toast_am = heapam_relation_toast_am,
+       .relation_fetch_toast_slice = heap_fetch_toast_slice,
 
        .relation_estimate_size = heapam_estimate_rel_size,
 
index e5d0fcd9764320f54c90626c12d1a0e8eb78d081..a6631f93d34eea8328ef9de508de401233afcafe 100644 (file)
 #include "postgres.h"
 
 #include "access/detoast.h"
+#include "access/genam.h"
 #include "access/heapam.h"
 #include "access/heaptoast.h"
 #include "access/toast_helper.h"
 #include "access/toast_internals.h"
+#include "utils/fmgroids.h"
 
 
 /* ----------
@@ -604,3 +606,183 @@ toast_build_flattened_tuple(TupleDesc tupleDesc,
 
        return new_tuple;
 }
+
+/*
+ * Fetch a TOAST slice from a heap table.
+ *
+ * toastrel is the relation from which chunks are to be fetched.
+ * valueid identifies the TOAST value from which chunks are being fetched.
+ * attrsize is the total size of the TOAST value.
+ * sliceoffset is the byte offset within the TOAST value from which to fetch.
+ * slicelength is the number of bytes to be fetched from the TOAST value.
+ * result is the varlena into which the results should be written.
+ */
+void
+heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize,
+                                          int32 sliceoffset, int32 slicelength,
+                                          struct varlena *result)
+{
+       Relation   *toastidxs;
+       ScanKeyData toastkey[3];
+       TupleDesc       toasttupDesc = toastrel->rd_att;
+       int                     nscankeys;
+       SysScanDesc toastscan;
+       HeapTuple       ttup;
+       int32           expectedchunk;
+       int32           totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1;
+       int                     startchunk;
+       int                     endchunk;
+       int                     num_indexes;
+       int                     validIndex;
+       SnapshotData SnapshotToast;
+
+       /* Look for the valid index of toast relation */
+       validIndex = toast_open_indexes(toastrel,
+                                                                       AccessShareLock,
+                                                                       &toastidxs,
+                                                                       &num_indexes);
+
+       startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE;
+       endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE;
+       Assert(endchunk <= totalchunks);
+
+       /* Set up a scan key to fetch from the index. */
+       ScanKeyInit(&toastkey[0],
+                               (AttrNumber) 1,
+                               BTEqualStrategyNumber, F_OIDEQ,
+                               ObjectIdGetDatum(valueid));
+
+       /*
+        * No additional condition if fetching all chunks. Otherwise, use an
+        * equality condition for one chunk, and a range condition otherwise.
+        */
+       if (startchunk == 0 && endchunk == totalchunks - 1)
+               nscankeys = 1;
+       else if (startchunk == endchunk)
+       {
+               ScanKeyInit(&toastkey[1],
+                                       (AttrNumber) 2,
+                                       BTEqualStrategyNumber, F_INT4EQ,
+                                       Int32GetDatum(startchunk));
+               nscankeys = 2;
+       }
+       else
+       {
+               ScanKeyInit(&toastkey[1],
+                                       (AttrNumber) 2,
+                                       BTGreaterEqualStrategyNumber, F_INT4GE,
+                                       Int32GetDatum(startchunk));
+               ScanKeyInit(&toastkey[2],
+                                       (AttrNumber) 2,
+                                       BTLessEqualStrategyNumber, F_INT4LE,
+                                       Int32GetDatum(endchunk));
+               nscankeys = 3;
+       }
+
+       /* Prepare for scan */
+       init_toast_snapshot(&SnapshotToast);
+       toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex],
+                                                                                  &SnapshotToast, nscankeys, toastkey);
+
+       /*
+        * Read the chunks by index
+        *
+        * The index is on (valueid, chunkidx) so they will come in order
+        */
+       expectedchunk = startchunk;
+       while ((ttup = systable_getnext_ordered(toastscan, ForwardScanDirection)) != NULL)
+       {
+               int32           curchunk;
+               Pointer         chunk;
+               bool            isnull;
+               char       *chunkdata;
+               int32           chunksize;
+               int32           expected_size;
+               int32           chcpystrt;
+               int32           chcpyend;
+
+               /*
+                * Have a chunk, extract the sequence number and the data
+                */
+               curchunk = DatumGetInt32(fastgetattr(ttup, 2, toasttupDesc, &isnull));
+               Assert(!isnull);
+               chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull));
+               Assert(!isnull);
+               if (!VARATT_IS_EXTENDED(chunk))
+               {
+                       chunksize = VARSIZE(chunk) - VARHDRSZ;
+                       chunkdata = VARDATA(chunk);
+               }
+               else if (VARATT_IS_SHORT(chunk))
+               {
+                       /* could happen due to heap_form_tuple doing its thing */
+                       chunksize = VARSIZE_SHORT(chunk) - VARHDRSZ_SHORT;
+                       chunkdata = VARDATA_SHORT(chunk);
+               }
+               else
+               {
+                       /* should never happen */
+                       elog(ERROR, "found toasted toast chunk for toast value %u in %s",
+                                valueid, RelationGetRelationName(toastrel));
+                       chunksize = 0;          /* keep compiler quiet */
+                       chunkdata = NULL;
+               }
+
+               /*
+                * Some checks on the data we've found
+                */
+               if (curchunk != expectedchunk)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATA_CORRUPTED),
+                                        errmsg_internal("unexpected chunk number %d (expected %d) for toast value %u in %s",
+                                                                        curchunk, expectedchunk, valueid,
+                                                                        RelationGetRelationName(toastrel))));
+               if (curchunk > endchunk)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATA_CORRUPTED),
+                                        errmsg_internal("unexpected chunk number %d (out of range %d..%d) for toast value %u in %s",
+                                                                        curchunk,
+                                                                        startchunk, endchunk, valueid,
+                                                                        RelationGetRelationName(toastrel))));
+               expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE
+                       : attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE);
+               if (chunksize != expected_size)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATA_CORRUPTED),
+                                        errmsg_internal("unexpected chunk size %d (expected %d) in chunk %d of %d for toast value %u in %s",
+                                                                        chunksize, expected_size,
+                                                                        curchunk, totalchunks, valueid,
+                                                                        RelationGetRelationName(toastrel))));
+
+               /*
+                * Copy the data into proper place in our result
+                */
+               chcpystrt = 0;
+               chcpyend = chunksize - 1;
+               if (curchunk == startchunk)
+                       chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE;
+               if (curchunk == endchunk)
+                       chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE;
+
+               memcpy(VARDATA(result) +
+                          (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt,
+                          chunkdata + chcpystrt,
+                          (chcpyend - chcpystrt) + 1);
+
+               expectedchunk++;
+       }
+
+       /*
+        * Final checks that we successfully fetched the datum
+        */
+       if (expectedchunk != (endchunk + 1))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATA_CORRUPTED),
+                                errmsg_internal("missing chunk number %d for toast value %u in %s",
+                                                                expectedchunk, valueid,
+                                                                RelationGetRelationName(toastrel))));
+
+       /* End scan and close indexes. */
+       systable_endscan_ordered(toastscan);
+       toast_close_indexes(toastidxs, num_indexes, AccessShareLock);
+}
index 269648b18043cf020a67185aad4d726dba86a9cb..26358319102c8541d3f8fc1fe4864f726f428328 100644 (file)
@@ -136,4 +136,14 @@ extern HeapTuple toast_build_flattened_tuple(TupleDesc tupleDesc,
                                                                                         Datum *values,
                                                                                         bool *isnull);
 
+/* ----------
+ * heap_fetch_toast_slice
+ *
+ *     Fetch a slice from a toast value stored in a heap table.
+ * ----------
+ */
+extern void heap_fetch_toast_slice(Relation toastrel, Oid valueid,
+                                                                  int32 attrsize, int32 sliceoffset,
+                                                                  int32 slicelength, struct varlena *result);
+
 #endif                                                 /* HEAPTOAST_H */
index c30a435b72c850c8a8690d3e678872d9943aea17..60dbd74a57463cfa76c769f2a3b8ee1b1cc4f922 100644 (file)
@@ -588,6 +588,17 @@ typedef struct TableAmRoutine
         */
        Oid                 (*relation_toast_am) (Relation rel);
 
+       /*
+        * This callback is invoked when detoasting a value stored in a toast
+        * table implemented by this AM.  See table_relation_fetch_toast_slice()
+        * for more details.
+        */
+       void            (*relation_fetch_toast_slice) (Relation toastrel, Oid valueid,
+                                                                                          int32 attrsize,
+                                                                                          int32 sliceoffset,
+                                                                                          int32 slicelength,
+                                                                                          struct varlena *result);
+
 
        /* ------------------------------------------------------------------------
         * Planner related functions.
@@ -1620,6 +1631,41 @@ table_relation_toast_am(Relation rel)
        return rel->rd_tableam->relation_toast_am(rel);
 }
 
+/*
+ * Fetch all or part of a TOAST value from a TOAST table.
+ *
+ * If this AM is never used to implement a TOAST table, then this callback
+ * is not needed. But, if toasted values are ever stored in a table of this
+ * type, then you will need this callback.
+ *
+ * toastrel is the relation in which the toasted value is stored.
+ *
+ * valueid identifes which toast value is to be fetched. For the heap,
+ * this corresponds to the values stored in the chunk_id column.
+ *
+ * attrsize is the total size of the toast value to be fetched.
+ *
+ * sliceoffset is the offset within the toast value of the first byte that
+ * should be fetched.
+ *
+ * slicelength is the number of bytes from the toast value that should be
+ * fetched.
+ *
+ * result is caller-allocated space into which the fetched bytes should be
+ * stored.
+ */
+static inline void
+table_relation_fetch_toast_slice(Relation toastrel, Oid valueid,
+                                                                int32 attrsize, int32 sliceoffset,
+                                                                int32 slicelength, struct varlena *result)
+{
+       return toastrel->rd_tableam->relation_fetch_toast_slice(toastrel, valueid,
+                                                                                                                       attrsize,
+                                                                                                                       sliceoffset,
+                                                                                                                       slicelength,
+                                                                                                                       result);
+}
+
 
 /* ----------------------------------------------------------------------------
  * Planner related functionality