]> granicus.if.org Git - postgresql/blob - src/backend/catalog/toasting.c
59838e305f6dbb85e2643debf1ade284bcfd6e40
[postgresql] / src / backend / catalog / toasting.c
1 /*-------------------------------------------------------------------------
2  *
3  * toasting.c
4  *        This file contains routines to support creation of toast tables
5  *
6  *
7  * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
8  * Portions Copyright (c) 1994, Regents of the University of California
9  *
10  * IDENTIFICATION
11  *        src/backend/catalog/toasting.c
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include "access/heapam.h"
18 #include "access/tuptoaster.h"
19 #include "access/xact.h"
20 #include "catalog/dependency.h"
21 #include "catalog/heap.h"
22 #include "catalog/index.h"
23 #include "catalog/indexing.h"
24 #include "catalog/namespace.h"
25 #include "catalog/pg_namespace.h"
26 #include "catalog/pg_opclass.h"
27 #include "catalog/pg_type.h"
28 #include "catalog/toasting.h"
29 #include "miscadmin.h"
30 #include "nodes/makefuncs.h"
31 #include "utils/builtins.h"
32 #include "utils/syscache.h"
33
34 /* Kluges for upgrade-in-place support */
35 extern Oid      binary_upgrade_next_toast_relfilenode;
36
37 Oid                     binary_upgrade_next_pg_type_toast_oid = InvalidOid;
38
39 static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
40                                    Datum reloptions);
41 static bool needs_toast_table(Relation rel);
42
43
44 /*
45  * AlterTableCreateToastTable
46  *              If the table needs a toast table, and doesn't already have one,
47  *              then create a toast table for it.
48  *
49  * reloptions for the toast table can be passed, too.  Pass (Datum) 0
50  * for default reloptions.
51  *
52  * We expect the caller to have verified that the relation is a table and have
53  * already done any necessary permission checks.  Callers expect this function
54  * to end with CommandCounterIncrement if it makes any changes.
55  */
56 void
57 AlterTableCreateToastTable(Oid relOid, Datum reloptions)
58 {
59         Relation        rel;
60
61         /*
62          * Grab an exclusive lock on the target table, which we will NOT release
63          * until end of transaction.  (This is probably redundant in all present
64          * uses...)
65          */
66         rel = heap_open(relOid, AccessExclusiveLock);
67
68         /* create_toast_table does all the work */
69         (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions);
70
71         heap_close(rel, NoLock);
72 }
73
74 /*
75  * Create a toast table during bootstrap
76  *
77  * Here we need to prespecify the OIDs of the toast table and its index
78  */
79 void
80 BootstrapToastTable(char *relName, Oid toastOid, Oid toastIndexOid)
81 {
82         Relation        rel;
83
84         rel = heap_openrv(makeRangeVar(NULL, relName, -1), AccessExclusiveLock);
85
86         /* Note: during bootstrap may see uncataloged relation */
87         if (rel->rd_rel->relkind != RELKIND_RELATION &&
88                 rel->rd_rel->relkind != RELKIND_UNCATALOGED)
89                 ereport(ERROR,
90                                 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
91                                  errmsg("\"%s\" is not a table",
92                                                 relName)));
93
94         /* create_toast_table does all the work */
95         if (!create_toast_table(rel, toastOid, toastIndexOid, (Datum) 0))
96                 elog(ERROR, "\"%s\" does not require a toast table",
97                          relName);
98
99         heap_close(rel, NoLock);
100 }
101
102
103 /*
104  * create_toast_table --- internal workhorse
105  *
106  * rel is already opened and exclusive-locked
107  * toastOid and toastIndexOid are normally InvalidOid, but during
108  * bootstrap they can be nonzero to specify hand-assigned OIDs
109  */
110 static bool
111 create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptions)
112 {
113         Oid                     relOid = RelationGetRelid(rel);
114         HeapTuple       reltup;
115         TupleDesc       tupdesc;
116         bool            shared_relation;
117         bool            mapped_relation;
118         Relation        class_rel;
119         Oid                     toast_relid;
120         Oid                     toast_idxid;
121         Oid                     toast_typid = InvalidOid;
122         Oid                     namespaceid;
123         char            toast_relname[NAMEDATALEN];
124         char            toast_idxname[NAMEDATALEN];
125         IndexInfo  *indexInfo;
126         Oid                     classObjectId[2];
127         int16           coloptions[2];
128         ObjectAddress baseobject,
129                                 toastobject;
130
131         /*
132          * Toast table is shared if and only if its parent is.
133          *
134          * We cannot allow toasting a shared relation after initdb (because
135          * there's no way to mark it toasted in other databases' pg_class).
136          */
137         shared_relation = rel->rd_rel->relisshared;
138         if (shared_relation && !IsBootstrapProcessingMode())
139                 ereport(ERROR,
140                                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
141                                  errmsg("shared tables cannot be toasted after initdb")));
142
143         /* It's mapped if and only if its parent is, too */
144         mapped_relation = RelationIsMapped(rel);
145
146         /*
147          * Is it already toasted?
148          */
149         if (rel->rd_rel->reltoastrelid != InvalidOid)
150                 return false;
151
152         /*
153          * Check to see whether the table actually needs a TOAST table.
154          *
155          * If an update-in-place toast relfilenode is specified, force toast file
156          * creation even if it seems not to need one.
157          */
158         if (!needs_toast_table(rel) &&
159                 !OidIsValid(binary_upgrade_next_toast_relfilenode))
160                 return false;
161
162         /*
163          * Create the toast table and its index
164          */
165         snprintf(toast_relname, sizeof(toast_relname),
166                          "pg_toast_%u", relOid);
167         snprintf(toast_idxname, sizeof(toast_idxname),
168                          "pg_toast_%u_index", relOid);
169
170         /* this is pretty painful...  need a tuple descriptor */
171         tupdesc = CreateTemplateTupleDesc(3, false);
172         TupleDescInitEntry(tupdesc, (AttrNumber) 1,
173                                            "chunk_id",
174                                            OIDOID,
175                                            -1, 0);
176         TupleDescInitEntry(tupdesc, (AttrNumber) 2,
177                                            "chunk_seq",
178                                            INT4OID,
179                                            -1, 0);
180         TupleDescInitEntry(tupdesc, (AttrNumber) 3,
181                                            "chunk_data",
182                                            BYTEAOID,
183                                            -1, 0);
184
185         /*
186          * Ensure that the toast table doesn't itself get toasted, or we'll be
187          * toast :-(.  This is essential for chunk_data because type bytea is
188          * toastable; hit the other two just to be sure.
189          */
190         tupdesc->attrs[0]->attstorage = 'p';
191         tupdesc->attrs[1]->attstorage = 'p';
192         tupdesc->attrs[2]->attstorage = 'p';
193
194         /*
195          * Toast tables for regular relations go in pg_toast; those for temp
196          * relations go into the per-backend temp-toast-table namespace.
197          */
198         if (RelationUsesTempNamespace(rel))
199                 namespaceid = GetTempToastNamespace();
200         else
201                 namespaceid = PG_TOAST_NAMESPACE;
202
203         if (OidIsValid(binary_upgrade_next_pg_type_toast_oid))
204         {
205                 toast_typid = binary_upgrade_next_pg_type_toast_oid;
206                 binary_upgrade_next_pg_type_toast_oid = InvalidOid;
207         }
208
209         toast_relid = heap_create_with_catalog(toast_relname,
210                                                                                    namespaceid,
211                                                                                    rel->rd_rel->reltablespace,
212                                                                                    toastOid,
213                                                                                    toast_typid,
214                                                                                    InvalidOid,
215                                                                                    rel->rd_rel->relowner,
216                                                                                    tupdesc,
217                                                                                    NIL,
218                                                                                    RELKIND_TOASTVALUE,
219                                                                                    rel->rd_rel->relpersistence,
220                                                                                    shared_relation,
221                                                                                    mapped_relation,
222                                                                                    true,
223                                                                                    0,
224                                                                                    ONCOMMIT_NOOP,
225                                                                                    reloptions,
226                                                                                    false,
227                                                                                    true,
228                                                                                    false);
229         Assert(toast_relid != InvalidOid);
230
231         /* make the toast relation visible, else index creation will fail */
232         CommandCounterIncrement();
233
234         /*
235          * Create unique index on chunk_id, chunk_seq.
236          *
237          * NOTE: the normal TOAST access routines could actually function with a
238          * single-column index on chunk_id only. However, the slice access
239          * routines use both columns for faster access to an individual chunk. In
240          * addition, we want it to be unique as a check against the possibility of
241          * duplicate TOAST chunk OIDs. The index might also be a little more
242          * efficient this way, since btree isn't all that happy with large numbers
243          * of equal keys.
244          */
245
246         indexInfo = makeNode(IndexInfo);
247         indexInfo->ii_NumIndexAttrs = 2;
248         indexInfo->ii_KeyAttrNumbers[0] = 1;
249         indexInfo->ii_KeyAttrNumbers[1] = 2;
250         indexInfo->ii_Expressions = NIL;
251         indexInfo->ii_ExpressionsState = NIL;
252         indexInfo->ii_Predicate = NIL;
253         indexInfo->ii_PredicateState = NIL;
254         indexInfo->ii_ExclusionOps = NULL;
255         indexInfo->ii_ExclusionProcs = NULL;
256         indexInfo->ii_ExclusionStrats = NULL;
257         indexInfo->ii_Unique = true;
258         indexInfo->ii_ReadyForInserts = true;
259         indexInfo->ii_Concurrent = false;
260         indexInfo->ii_BrokenHotChain = false;
261
262         classObjectId[0] = OID_BTREE_OPS_OID;
263         classObjectId[1] = INT4_BTREE_OPS_OID;
264
265         coloptions[0] = 0;
266         coloptions[1] = 0;
267
268         toast_idxid = index_create(toast_relid, toast_idxname, toastIndexOid,
269                                                            indexInfo,
270                                                            list_make2("chunk_id", "chunk_seq"),
271                                                            BTREE_AM_OID,
272                                                            rel->rd_rel->reltablespace,
273                                                            classObjectId, coloptions, (Datum) 0,
274                                                            true, false, false, false,
275                                                            true, false, false);
276
277         /*
278          * Store the toast table's OID in the parent relation's pg_class row
279          */
280         class_rel = heap_open(RelationRelationId, RowExclusiveLock);
281
282         reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relOid));
283         if (!HeapTupleIsValid(reltup))
284                 elog(ERROR, "cache lookup failed for relation %u", relOid);
285
286         ((Form_pg_class) GETSTRUCT(reltup))->reltoastrelid = toast_relid;
287
288         if (!IsBootstrapProcessingMode())
289         {
290                 /* normal case, use a transactional update */
291                 simple_heap_update(class_rel, &reltup->t_self, reltup);
292
293                 /* Keep catalog indexes current */
294                 CatalogUpdateIndexes(class_rel, reltup);
295         }
296         else
297         {
298                 /* While bootstrapping, we cannot UPDATE, so overwrite in-place */
299                 heap_inplace_update(class_rel, reltup);
300         }
301
302         heap_freetuple(reltup);
303
304         heap_close(class_rel, RowExclusiveLock);
305
306         /*
307          * Register dependency from the toast table to the master, so that the
308          * toast table will be deleted if the master is.  Skip this in bootstrap
309          * mode.
310          */
311         if (!IsBootstrapProcessingMode())
312         {
313                 baseobject.classId = RelationRelationId;
314                 baseobject.objectId = relOid;
315                 baseobject.objectSubId = 0;
316                 toastobject.classId = RelationRelationId;
317                 toastobject.objectId = toast_relid;
318                 toastobject.objectSubId = 0;
319
320                 recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
321         }
322
323         /*
324          * Make changes visible
325          */
326         CommandCounterIncrement();
327
328         return true;
329 }
330
331 /*
332  * Check to see whether the table needs a TOAST table.  It does only if
333  * (1) there are any toastable attributes, and (2) the maximum length
334  * of a tuple could exceed TOAST_TUPLE_THRESHOLD.  (We don't want to
335  * create a toast table for something like "f1 varchar(20)".)
336  */
337 static bool
338 needs_toast_table(Relation rel)
339 {
340         int32           data_length = 0;
341         bool            maxlength_unknown = false;
342         bool            has_toastable_attrs = false;
343         TupleDesc       tupdesc;
344         Form_pg_attribute *att;
345         int32           tuple_length;
346         int                     i;
347
348         tupdesc = rel->rd_att;
349         att = tupdesc->attrs;
350
351         for (i = 0; i < tupdesc->natts; i++)
352         {
353                 if (att[i]->attisdropped)
354                         continue;
355                 data_length = att_align_nominal(data_length, att[i]->attalign);
356                 if (att[i]->attlen > 0)
357                 {
358                         /* Fixed-length types are never toastable */
359                         data_length += att[i]->attlen;
360                 }
361                 else
362                 {
363                         int32           maxlen = type_maximum_size(att[i]->atttypid,
364                                                                                                    att[i]->atttypmod);
365
366                         if (maxlen < 0)
367                                 maxlength_unknown = true;
368                         else
369                                 data_length += maxlen;
370                         if (att[i]->attstorage != 'p')
371                                 has_toastable_attrs = true;
372                 }
373         }
374         if (!has_toastable_attrs)
375                 return false;                   /* nothing to toast? */
376         if (maxlength_unknown)
377                 return true;                    /* any unlimited-length attrs? */
378         tuple_length = MAXALIGN(offsetof(HeapTupleHeaderData, t_bits) +
379                                                         BITMAPLEN(tupdesc->natts)) +
380                 MAXALIGN(data_length);
381         return (tuple_length > TOAST_TUPLE_THRESHOLD);
382 }