|
27 | 27 | #include "catalog/pg_am.h" |
28 | 28 | #include "catalog/pg_namespace.h" |
29 | 29 | #include "catalog/pg_opclass.h" |
| 30 | +#include "catalog/pg_type.h" |
30 | 31 | #include "catalog/toasting.h" |
31 | 32 | #include "miscadmin.h" |
32 | 33 | #include "nodes/makefuncs.h" |
| 34 | +#include "nodes/nodeFuncs.h" |
| 35 | +#include "utils/builtins.h" |
33 | 36 | #include "utils/fmgroids.h" |
| 37 | +#include "utils/inval.h" |
| 38 | +#include "utils/lsyscache.h" |
34 | 39 | #include "utils/rel.h" |
35 | 40 | #include "utils/lsyscache.h" |
36 | 41 | #include "utils/syscache.h" |
@@ -514,3 +519,176 @@ needs_toast_table(Relation rel) |
514 | 519 | /* Otherwise, let the AM decide. */ |
515 | 520 | return table_relation_needs_toast_table(rel); |
516 | 521 | } |
| 522 | + |
| 523 | +/* |
| 524 | + * ensure_direct_toast |
| 525 | + * |
| 526 | + * Upgrades a legacy (3-column, full index) TOAST table in-place to support |
| 527 | + * the Direct TOAST format. |
| 528 | + * Accepts either the parent table's OID or the TOAST table's OID. |
| 529 | + */ |
| 530 | +void |
| 531 | +ensure_direct_toast(Oid relid) |
| 532 | +{ |
| 533 | + Relation targetrel; |
| 534 | + Relation toastrel; |
| 535 | + Oid toastrelid; |
| 536 | + Relation pg_class_rel; |
| 537 | + Relation pg_attribute_rel; |
| 538 | + Relation pg_index_rel; |
| 539 | + HeapTuple tuple; |
| 540 | + HeapTuple newtuple; |
| 541 | + TupleDesc td; |
| 542 | + Oid toastIndexOid = InvalidOid; |
| 543 | + List *indexoids; |
| 544 | + |
| 545 | + targetrel = table_open(relid, AccessShareLock); |
| 546 | + |
| 547 | + if (targetrel->rd_rel->relkind == RELKIND_TOASTVALUE) |
| 548 | + { |
| 549 | + toastrelid = relid; |
| 550 | + table_close(targetrel, AccessShareLock); |
| 551 | + } |
| 552 | + else if (targetrel->rd_rel->relkind == RELKIND_RELATION || |
| 553 | + targetrel->rd_rel->relkind == RELKIND_MATVIEW) |
| 554 | + { |
| 555 | + toastrelid = targetrel->rd_rel->reltoastrelid; |
| 556 | + table_close(targetrel, AccessShareLock); |
| 557 | + |
| 558 | + if (!OidIsValid(toastrelid)) |
| 559 | + ereport(ERROR, |
| 560 | + (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 561 | + errmsg("table \"%s\" does not have a TOAST table", |
| 562 | + get_rel_name(relid)))); |
| 563 | + } |
| 564 | + else |
| 565 | + { |
| 566 | + table_close(targetrel, AccessShareLock); |
| 567 | + ereport(ERROR, |
| 568 | + (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| 569 | + errmsg("relation \"%s\" is not a table or TOAST table", |
| 570 | + get_rel_name(relid)))); |
| 571 | + } |
| 572 | + |
| 573 | + /* Open the toast relation with exclusive lock */ |
| 574 | + toastrel = table_open(toastrelid, AccessExclusiveLock); |
| 575 | + |
| 576 | + if (toastrel->rd_rel->relkind != RELKIND_TOASTVALUE) |
| 577 | + elog(ERROR, "relation %u is not a TOAST table", toastrelid); |
| 578 | + |
| 579 | + /* |
| 580 | + * Step 1: Add missing columns chunk_tids and chunk_tid_offsets if needed. |
| 581 | + */ |
| 582 | + if (toastrel->rd_att->natts < 5) |
| 583 | + { |
| 584 | + td = CreateTemplateTupleDesc(2); |
| 585 | + TupleDescInitEntry(td, (AttrNumber) 1, |
| 586 | + "chunk_tids", |
| 587 | + TIDARRAYOID, |
| 588 | + -1, 0); |
| 589 | + TupleDescInitEntry(td, (AttrNumber) 2, |
| 590 | + "chunk_tid_offsets", |
| 591 | + INT8ARRAYOID, |
| 592 | + -1, 0); |
| 593 | + TupleDescAttr(td, 0)->attnum = 4; |
| 594 | + TupleDescAttr(td, 0)->attstorage = TYPSTORAGE_PLAIN; |
| 595 | + TupleDescAttr(td, 0)->attcompression = InvalidCompressionMethod; |
| 596 | + TupleDescAttr(td, 1)->attnum = 5; |
| 597 | + TupleDescAttr(td, 1)->attstorage = TYPSTORAGE_PLAIN; |
| 598 | + TupleDescAttr(td, 1)->attcompression = InvalidCompressionMethod; |
| 599 | + |
| 600 | + populate_compact_attribute(td, 0); |
| 601 | + populate_compact_attribute(td, 1); |
| 602 | + TupleDescFinalize(td); |
| 603 | + |
| 604 | + pg_attribute_rel = table_open(AttributeRelationId, RowExclusiveLock); |
| 605 | + InsertPgAttributeTuples(pg_attribute_rel, td, toastrelid, NULL, NULL); |
| 606 | + table_close(pg_attribute_rel, RowExclusiveLock); |
| 607 | + FreeTupleDesc(td); |
| 608 | + |
| 609 | + /* Update pg_class.relnatts = 5 */ |
| 610 | + pg_class_rel = table_open(RelationRelationId, RowExclusiveLock); |
| 611 | + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(toastrelid)); |
| 612 | + if (!HeapTupleIsValid(tuple)) |
| 613 | + elog(ERROR, "cache lookup failed for relation %u", toastrelid); |
| 614 | + ((Form_pg_class) GETSTRUCT(tuple))->relnatts = 5; |
| 615 | + CatalogTupleUpdate(pg_class_rel, &tuple->t_self, tuple); |
| 616 | + heap_freetuple(tuple); |
| 617 | + table_close(pg_class_rel, RowExclusiveLock); |
| 618 | + } |
| 619 | + |
| 620 | + /* |
| 621 | + * Step 2: Ensure the TOAST unique index is partial (WHERE chunk_id IS NOT NULL). |
| 622 | + */ |
| 623 | + indexoids = RelationGetIndexList(toastrel); |
| 624 | + if (indexoids != NIL) |
| 625 | + toastIndexOid = linitial_oid(indexoids); |
| 626 | + list_free(indexoids); |
| 627 | + |
| 628 | + if (OidIsValid(toastIndexOid)) |
| 629 | + { |
| 630 | + pg_index_rel = table_open(IndexRelationId, RowExclusiveLock); |
| 631 | + tuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(toastIndexOid)); |
| 632 | + if (HeapTupleIsValid(tuple)) |
| 633 | + { |
| 634 | + bool isnull; |
| 635 | + |
| 636 | + (void) SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indpred, &isnull); |
| 637 | + |
| 638 | + if (isnull) |
| 639 | + { |
| 640 | + Datum values[Natts_pg_index]; |
| 641 | + bool nulls[Natts_pg_index]; |
| 642 | + bool replaces[Natts_pg_index]; |
| 643 | + NullTest *ntest; |
| 644 | + char *pred_str; |
| 645 | + |
| 646 | + memset(values, 0, sizeof(values)); |
| 647 | + memset(nulls, 0, sizeof(nulls)); |
| 648 | + memset(replaces, 0, sizeof(replaces)); |
| 649 | + |
| 650 | + ntest = makeNode(NullTest); |
| 651 | + ntest->arg = (Expr *) makeVar(1, 1, OIDOID, -1, InvalidOid, 0); |
| 652 | + ntest->nulltesttype = IS_NOT_NULL; |
| 653 | + ntest->argisrow = false; |
| 654 | + ntest->location = -1; |
| 655 | + |
| 656 | + pred_str = nodeToString(list_make1(ntest)); |
| 657 | + |
| 658 | + values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(false); |
| 659 | + replaces[Anum_pg_index_indisprimary - 1] = true; |
| 660 | + |
| 661 | + values[Anum_pg_index_indpred - 1] = CStringGetTextDatum(pred_str); |
| 662 | + replaces[Anum_pg_index_indpred - 1] = true; |
| 663 | + nulls[Anum_pg_index_indpred - 1] = false; |
| 664 | + |
| 665 | + newtuple = heap_modify_tuple(tuple, RelationGetDescr(pg_index_rel), |
| 666 | + values, nulls, replaces); |
| 667 | + CatalogTupleUpdate(pg_index_rel, &tuple->t_self, newtuple); |
| 668 | + heap_freetuple(newtuple); |
| 669 | + pfree(pred_str); |
| 670 | + |
| 671 | + CacheInvalidateRelcacheByRelid(toastIndexOid); |
| 672 | + } |
| 673 | + heap_freetuple(tuple); |
| 674 | + } |
| 675 | + table_close(pg_index_rel, RowExclusiveLock); |
| 676 | + } |
| 677 | + |
| 678 | + CacheInvalidateRelcacheByRelid(toastrelid); |
| 679 | + CommandCounterIncrement(); |
| 680 | + |
| 681 | + table_close(toastrel, AccessExclusiveLock); |
| 682 | +} |
| 683 | + |
| 684 | +/* |
| 685 | + * SQL-callable function |
| 686 | + */ |
| 687 | +Datum |
| 688 | +pg_ensure_direct_toast(PG_FUNCTION_ARGS) |
| 689 | +{ |
| 690 | + Oid relid = PG_GETARG_OID(0); |
| 691 | + |
| 692 | + ensure_direct_toast(relid); |
| 693 | + PG_RETURN_VOID(); |
| 694 | +} |
0 commit comments