19
19
#include "postgres.h"
20
20
21
21
#include "access/htup_details.h"
22
+ #include "access/tuptoaster.h"
22
23
#include "catalog/heap.h"
23
24
#include "catalog/pg_type.h"
24
25
#include "utils/builtins.h"
@@ -41,7 +42,7 @@ static const ExpandedObjectMethods ER_methods =
41
42
42
43
/* Other local functions */
43
44
static void ER_mc_callback (void * arg );
44
- static MemoryContext get_domain_check_cxt (ExpandedRecordHeader * erh );
45
+ static MemoryContext get_short_term_cxt (ExpandedRecordHeader * erh );
45
46
static void build_dummy_expanded_header (ExpandedRecordHeader * main_erh );
46
47
static pg_noinline void check_domain_for_new_field (ExpandedRecordHeader * erh ,
47
48
int fnumber ,
@@ -57,8 +58,9 @@ static pg_noinline void check_domain_for_new_tuple(ExpandedRecordHeader *erh,
57
58
*
58
59
* The expanded record is initially "empty", having a state logically
59
60
* equivalent to a NULL composite value (not ROW(NULL, NULL, ...)).
60
- * Note that this might not be a valid state for a domain type; if the
61
- * caller needs to check that, call expanded_record_set_tuple(erh, NULL).
61
+ * Note that this might not be a valid state for a domain type;
62
+ * if the caller needs to check that, call
63
+ * expanded_record_set_tuple(erh, NULL, false, false).
62
64
*
63
65
* The expanded object will be a child of parentcontext.
64
66
*/
@@ -424,16 +426,20 @@ make_expanded_record_from_exprecord(ExpandedRecordHeader *olderh,
424
426
*
425
427
* The tuple is physically copied into the expanded record's local storage
426
428
* if "copy" is true, otherwise it's caller's responsibility that the tuple
427
- * will live as long as the expanded record does. In any case, out-of-line
428
- * fields in the tuple are not automatically inlined.
429
+ * will live as long as the expanded record does.
430
+ *
431
+ * Out-of-line field values in the tuple are automatically inlined if
432
+ * "expand_external" is true, otherwise not. (The combination copy = false,
433
+ * expand_external = true is not sensible and not supported.)
429
434
*
430
435
* Alternatively, tuple can be NULL, in which case we just set the expanded
431
436
* record to be empty.
432
437
*/
433
438
void
434
439
expanded_record_set_tuple (ExpandedRecordHeader * erh ,
435
440
HeapTuple tuple ,
436
- bool copy )
441
+ bool copy ,
442
+ bool expand_external )
437
443
{
438
444
int oldflags ;
439
445
HeapTuple oldtuple ;
@@ -452,6 +458,25 @@ expanded_record_set_tuple(ExpandedRecordHeader *erh,
452
458
if (erh -> flags & ER_FLAG_IS_DOMAIN )
453
459
check_domain_for_new_tuple (erh , tuple );
454
460
461
+ /*
462
+ * If we need to get rid of out-of-line field values, do so, using the
463
+ * short-term context to avoid leaking whatever cruft the toast fetch
464
+ * might generate.
465
+ */
466
+ if (expand_external && tuple )
467
+ {
468
+ /* Assert caller didn't ask for unsupported case */
469
+ Assert (copy );
470
+ if (HeapTupleHasExternal (tuple ))
471
+ {
472
+ oldcxt = MemoryContextSwitchTo (get_short_term_cxt (erh ));
473
+ tuple = toast_flatten_tuple (tuple , erh -> er_tupdesc );
474
+ MemoryContextSwitchTo (oldcxt );
475
+ }
476
+ else
477
+ expand_external = false; /* need not clean up below */
478
+ }
479
+
455
480
/*
456
481
* Initialize new flags, keeping only non-data status bits.
457
482
*/
@@ -468,6 +493,10 @@ expanded_record_set_tuple(ExpandedRecordHeader *erh,
468
493
newtuple = heap_copytuple (tuple );
469
494
newflags |= ER_FLAG_FVALUE_ALLOCED ;
470
495
MemoryContextSwitchTo (oldcxt );
496
+
497
+ /* We can now flush anything that detoasting might have leaked. */
498
+ if (expand_external )
499
+ MemoryContextReset (erh -> er_short_term_cxt );
471
500
}
472
501
else
473
502
newtuple = tuple ;
@@ -676,23 +705,13 @@ ER_get_flat_size(ExpandedObjectHeader *eohptr)
676
705
VARATT_IS_EXTERNAL (DatumGetPointer (erh -> dvalues [i ])))
677
706
{
678
707
/*
679
- * It's an external toasted value, so we need to dereference
680
- * it so that the flat representation will be self-contained.
681
- * Do this step in the caller's context because the TOAST
682
- * fetch might leak memory. That means making an extra copy,
683
- * which is a tad annoying, but repetitive leaks in the
684
- * record's context would be worse.
708
+ * expanded_record_set_field_internal can do the actual work
709
+ * of detoasting. It needn't recheck domain constraints.
685
710
*/
686
- Datum newValue ;
687
-
688
- newValue = PointerGetDatum (PG_DETOAST_DATUM (erh -> dvalues [i ]));
689
- /* expanded_record_set_field can do the rest */
690
- /* ... and we don't need it to recheck domain constraints */
691
711
expanded_record_set_field_internal (erh , i + 1 ,
692
- newValue , false,
712
+ erh -> dvalues [i ], false,
713
+ true,
693
714
false);
694
- /* Might as well free the detoasted value */
695
- pfree (DatumGetPointer (newValue ));
696
715
}
697
716
}
698
717
@@ -1087,12 +1106,16 @@ expanded_record_fetch_field(ExpandedRecordHeader *erh, int fnumber,
1087
1106
* (without changing the record's state) if the domain's constraints would
1088
1107
* be violated.
1089
1108
*
1109
+ * If expand_external is true and newValue is an out-of-line value, we'll
1110
+ * forcibly detoast it so that the record does not depend on external storage.
1111
+ *
1090
1112
* Internal callers can pass check_constraints = false to skip application
1091
1113
* of domain constraints. External callers should never do that.
1092
1114
*/
1093
1115
void
1094
1116
expanded_record_set_field_internal (ExpandedRecordHeader * erh , int fnumber ,
1095
1117
Datum newValue , bool isnull ,
1118
+ bool expand_external ,
1096
1119
bool check_constraints )
1097
1120
{
1098
1121
TupleDesc tupdesc ;
@@ -1124,23 +1147,46 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
1124
1147
elog (ERROR , "cannot assign to field %d of expanded record" , fnumber );
1125
1148
1126
1149
/*
1127
- * Copy new field value into record's context, if needed.
1150
+ * Copy new field value into record's context, and deal with detoasting,
1151
+ * if needed.
1128
1152
*/
1129
1153
attr = TupleDescAttr (tupdesc , fnumber - 1 );
1130
1154
if (!isnull && !attr -> attbyval )
1131
1155
{
1132
1156
MemoryContext oldcxt ;
1133
1157
1158
+ /* If requested, detoast any external value */
1159
+ if (expand_external )
1160
+ {
1161
+ if (attr -> attlen == -1 &&
1162
+ VARATT_IS_EXTERNAL (DatumGetPointer (newValue )))
1163
+ {
1164
+ /* Detoasting should be done in short-lived context. */
1165
+ oldcxt = MemoryContextSwitchTo (get_short_term_cxt (erh ));
1166
+ newValue = PointerGetDatum (heap_tuple_fetch_attr ((struct varlena * ) DatumGetPointer (newValue )));
1167
+ MemoryContextSwitchTo (oldcxt );
1168
+ }
1169
+ else
1170
+ expand_external = false; /* need not clean up below */
1171
+ }
1172
+
1173
+ /* Copy value into record's context */
1134
1174
oldcxt = MemoryContextSwitchTo (erh -> hdr .eoh_context );
1135
1175
newValue = datumCopy (newValue , false, attr -> attlen );
1136
1176
MemoryContextSwitchTo (oldcxt );
1137
1177
1178
+ /* We can now flush anything that detoasting might have leaked */
1179
+ if (expand_external )
1180
+ MemoryContextReset (erh -> er_short_term_cxt );
1181
+
1138
1182
/* Remember that we have field(s) that may need to be pfree'd */
1139
1183
erh -> flags |= ER_FLAG_DVALUES_ALLOCED ;
1140
1184
1141
1185
/*
1142
1186
* While we're here, note whether it's an external toasted value,
1143
- * because that could mean we need to inline it later.
1187
+ * because that could mean we need to inline it later. (Think not to
1188
+ * merge this into the previous expand_external logic: datumCopy could
1189
+ * by itself have made the value non-external.)
1144
1190
*/
1145
1191
if (attr -> attlen == -1 &&
1146
1192
VARATT_IS_EXTERNAL (DatumGetPointer (newValue )))
@@ -1193,14 +1239,20 @@ expanded_record_set_field_internal(ExpandedRecordHeader *erh, int fnumber,
1193
1239
* Caller must ensure that the provided datums are of the right types
1194
1240
* to match the record's previously assigned rowtype.
1195
1241
*
1242
+ * If expand_external is true, we'll forcibly detoast out-of-line field values
1243
+ * so that the record does not depend on external storage.
1244
+ *
1196
1245
* Unlike repeated application of expanded_record_set_field(), this does not
1197
1246
* guarantee to leave the expanded record in a non-corrupt state in event
1198
1247
* of an error. Typically it would only be used for initializing a new
1199
- * expanded record.
1248
+ * expanded record. Also, because we expect this to be applied at most once
1249
+ * in the lifespan of an expanded record, we do not worry about any cruft
1250
+ * that detoasting might leak.
1200
1251
*/
1201
1252
void
1202
1253
expanded_record_set_fields (ExpandedRecordHeader * erh ,
1203
- const Datum * newValues , const bool * isnulls )
1254
+ const Datum * newValues , const bool * isnulls ,
1255
+ bool expand_external )
1204
1256
{
1205
1257
TupleDesc tupdesc ;
1206
1258
Datum * dvalues ;
@@ -1245,22 +1297,37 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
1245
1297
if (!attr -> attbyval )
1246
1298
{
1247
1299
/*
1248
- * Copy new field value into record's context, if needed.
1300
+ * Copy new field value into record's context, and deal with
1301
+ * detoasting, if needed.
1249
1302
*/
1250
1303
if (!isnull )
1251
1304
{
1252
- newValue = datumCopy (newValue , false, attr -> attlen );
1305
+ /* Is it an external toasted value? */
1306
+ if (attr -> attlen == -1 &&
1307
+ VARATT_IS_EXTERNAL (DatumGetPointer (newValue )))
1308
+ {
1309
+ if (expand_external )
1310
+ {
1311
+ /* Detoast as requested while copying the value */
1312
+ newValue = PointerGetDatum (heap_tuple_fetch_attr ((struct varlena * ) DatumGetPointer (newValue )));
1313
+ }
1314
+ else
1315
+ {
1316
+ /* Just copy the value */
1317
+ newValue = datumCopy (newValue , false, -1 );
1318
+ /* If it's still external, remember that */
1319
+ if (VARATT_IS_EXTERNAL (DatumGetPointer (newValue )))
1320
+ erh -> flags |= ER_FLAG_HAVE_EXTERNAL ;
1321
+ }
1322
+ }
1323
+ else
1324
+ {
1325
+ /* Not an external value, just copy it */
1326
+ newValue = datumCopy (newValue , false, attr -> attlen );
1327
+ }
1253
1328
1254
1329
/* Remember that we have field(s) that need to be pfree'd */
1255
1330
erh -> flags |= ER_FLAG_DVALUES_ALLOCED ;
1256
-
1257
- /*
1258
- * While we're here, note whether it's an external toasted
1259
- * value, because that could mean we need to inline it later.
1260
- */
1261
- if (attr -> attlen == -1 &&
1262
- VARATT_IS_EXTERNAL (DatumGetPointer (newValue )))
1263
- erh -> flags |= ER_FLAG_HAVE_EXTERNAL ;
1264
1331
}
1265
1332
1266
1333
/*
@@ -1291,7 +1358,7 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
1291
1358
if (erh -> flags & ER_FLAG_IS_DOMAIN )
1292
1359
{
1293
1360
/* We run domain_check in a short-lived context to limit cruft */
1294
- MemoryContextSwitchTo (get_domain_check_cxt (erh ));
1361
+ MemoryContextSwitchTo (get_short_term_cxt (erh ));
1295
1362
1296
1363
domain_check (ExpandedRecordGetRODatum (erh ), false,
1297
1364
erh -> er_decltypeid ,
@@ -1303,25 +1370,26 @@ expanded_record_set_fields(ExpandedRecordHeader *erh,
1303
1370
}
1304
1371
1305
1372
/*
1306
- * Construct (or reset) working memory context for domain checks.
1373
+ * Construct (or reset) working memory context for short-term operations.
1374
+ *
1375
+ * This context is used for domain check evaluation and for detoasting.
1307
1376
*
1308
- * If we don't have a working memory context for domain checking, make one;
1309
- * if we have one, reset it to get rid of any leftover cruft. (It is a tad
1310
- * annoying to need a whole context for this, since it will often go unused
1311
- * --- but it's hard to avoid memory leaks otherwise. We can make the
1312
- * context small, at least.)
1377
+ * If we don't have a short-lived memory context, make one; if we have one,
1378
+ * reset it to get rid of any leftover cruft. (It is a tad annoying to need a
1379
+ * whole context for this, since it will often go unused --- but it's hard to
1380
+ * avoid memory leaks otherwise. We can make the context small, at least.)
1313
1381
*/
1314
1382
static MemoryContext
1315
- get_domain_check_cxt (ExpandedRecordHeader * erh )
1383
+ get_short_term_cxt (ExpandedRecordHeader * erh )
1316
1384
{
1317
- if (erh -> er_domain_check_cxt == NULL )
1318
- erh -> er_domain_check_cxt =
1385
+ if (erh -> er_short_term_cxt == NULL )
1386
+ erh -> er_short_term_cxt =
1319
1387
AllocSetContextCreate (erh -> hdr .eoh_context ,
1320
- "expanded record domain checks " ,
1388
+ "expanded record short-term context " ,
1321
1389
ALLOCSET_SMALL_SIZES );
1322
1390
else
1323
- MemoryContextReset (erh -> er_domain_check_cxt );
1324
- return erh -> er_domain_check_cxt ;
1391
+ MemoryContextReset (erh -> er_short_term_cxt );
1392
+ return erh -> er_short_term_cxt ;
1325
1393
}
1326
1394
1327
1395
/*
@@ -1340,8 +1408,8 @@ build_dummy_expanded_header(ExpandedRecordHeader *main_erh)
1340
1408
ExpandedRecordHeader * erh ;
1341
1409
TupleDesc tupdesc = expanded_record_get_tupdesc (main_erh );
1342
1410
1343
- /* Ensure we have a domain_check_cxt */
1344
- (void ) get_domain_check_cxt (main_erh );
1411
+ /* Ensure we have a short-lived context */
1412
+ (void ) get_short_term_cxt (main_erh );
1345
1413
1346
1414
/*
1347
1415
* Allocate dummy header on first time through, or in the unlikely event
@@ -1372,7 +1440,7 @@ build_dummy_expanded_header(ExpandedRecordHeader *main_erh)
1372
1440
* nothing else is authorized to delete or transfer ownership of the
1373
1441
* object's context, so it should be safe enough.
1374
1442
*/
1375
- EOH_init_header (& erh -> hdr , & ER_methods , main_erh -> er_domain_check_cxt );
1443
+ EOH_init_header (& erh -> hdr , & ER_methods , main_erh -> er_short_term_cxt );
1376
1444
erh -> er_magic = ER_MAGIC ;
1377
1445
1378
1446
/* Set up dvalues/dnulls, with no valid contents as yet */
@@ -1488,7 +1556,7 @@ check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber,
1488
1556
* We call domain_check in the short-lived context, so that any cruft
1489
1557
* leaked by expression evaluation can be reclaimed.
1490
1558
*/
1491
- oldcxt = MemoryContextSwitchTo (erh -> er_domain_check_cxt );
1559
+ oldcxt = MemoryContextSwitchTo (erh -> er_short_term_cxt );
1492
1560
1493
1561
/*
1494
1562
* And now we can apply the check. Note we use main header's domain cache
@@ -1502,7 +1570,7 @@ check_domain_for_new_field(ExpandedRecordHeader *erh, int fnumber,
1502
1570
MemoryContextSwitchTo (oldcxt );
1503
1571
1504
1572
/* We might as well clean up cruft immediately. */
1505
- MemoryContextReset (erh -> er_domain_check_cxt );
1573
+ MemoryContextReset (erh -> er_short_term_cxt );
1506
1574
}
1507
1575
1508
1576
/*
@@ -1518,7 +1586,7 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
1518
1586
if (tuple == NULL )
1519
1587
{
1520
1588
/* We run domain_check in a short-lived context to limit cruft */
1521
- oldcxt = MemoryContextSwitchTo (get_domain_check_cxt (erh ));
1589
+ oldcxt = MemoryContextSwitchTo (get_short_term_cxt (erh ));
1522
1590
1523
1591
domain_check ((Datum ) 0 , true,
1524
1592
erh -> er_decltypeid ,
@@ -1528,7 +1596,7 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
1528
1596
MemoryContextSwitchTo (oldcxt );
1529
1597
1530
1598
/* We might as well clean up cruft immediately. */
1531
- MemoryContextReset (erh -> er_domain_check_cxt );
1599
+ MemoryContextReset (erh -> er_short_term_cxt );
1532
1600
1533
1601
return ;
1534
1602
}
@@ -1551,7 +1619,7 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
1551
1619
* We call domain_check in the short-lived context, so that any cruft
1552
1620
* leaked by expression evaluation can be reclaimed.
1553
1621
*/
1554
- oldcxt = MemoryContextSwitchTo (erh -> er_domain_check_cxt );
1622
+ oldcxt = MemoryContextSwitchTo (erh -> er_short_term_cxt );
1555
1623
1556
1624
/*
1557
1625
* And now we can apply the check. Note we use main header's domain cache
@@ -1565,5 +1633,5 @@ check_domain_for_new_tuple(ExpandedRecordHeader *erh, HeapTuple tuple)
1565
1633
MemoryContextSwitchTo (oldcxt );
1566
1634
1567
1635
/* We might as well clean up cruft immediately. */
1568
- MemoryContextReset (erh -> er_domain_check_cxt );
1636
+ MemoryContextReset (erh -> er_short_term_cxt );
1569
1637
}
0 commit comments