|
82 | 82 | #include "utils/lsyscache.h"
|
83 | 83 | #include "utils/memutils.h"
|
84 | 84 | #include "utils/rel.h"
|
| 85 | +#include "utils/spccache.h" |
85 | 86 | #include "utils/varlena.h"
|
86 | 87 |
|
87 | 88 | /* GUC variables */
|
@@ -222,6 +223,54 @@ TablespaceCreateDbspace(Oid spcNode, Oid dbNode, bool isRedo)
|
222 | 223 | pfree(dir);
|
223 | 224 | }
|
224 | 225 |
|
| 226 | +static int |
| 227 | +getIntOption(List *options, char const *name, int defaultValue) |
| 228 | +{ |
| 229 | + ListCell *cell; |
| 230 | + |
| 231 | + foreach(cell, options) |
| 232 | + { |
| 233 | + DefElem *def = (DefElem *) lfirst(cell); |
| 234 | + if (strcmp(def->defname, name) == 0) |
| 235 | + { |
| 236 | + if (def->arg != NULL) |
| 237 | + { |
| 238 | + switch (nodeTag(def->arg)) |
| 239 | + { |
| 240 | + case T_Integer: |
| 241 | + return (int32) intVal(def->arg); |
| 242 | + case T_String: |
| 243 | + return atoi(strVal(def->arg)); |
| 244 | + default: |
| 245 | + ereport(ERROR, |
| 246 | + (errcode(ERRCODE_SYNTAX_ERROR), |
| 247 | + errmsg("%s requires an integer value", |
| 248 | + name))); |
| 249 | + } |
| 250 | + break; |
| 251 | + } |
| 252 | + } |
| 253 | + } |
| 254 | + return defaultValue; |
| 255 | +} |
| 256 | + |
| 257 | +static void |
| 258 | +AtomicChangeLink(char const* link_path, char const* new_location) |
| 259 | +{ |
| 260 | + char* tmp_link_path = psprintf("%s.tmp", link_path); |
| 261 | + (void)unlink(tmp_link_path); /* remove temporary link if exists */ |
| 262 | + if (symlink(new_location, tmp_link_path) < 0) |
| 263 | + ereport(ERROR, |
| 264 | + (errcode_for_file_access(), |
| 265 | + errmsg("could not create symbolic link \"%s\": %m", |
| 266 | + tmp_link_path))); |
| 267 | + if (rename(tmp_link_path, link_path) < 0) |
| 268 | + ereport(ERROR, |
| 269 | + (errcode_for_file_access(), |
| 270 | + errmsg("could not rename symbolic link \"%s\": %m", |
| 271 | + link_path))); |
| 272 | +} |
| 273 | + |
225 | 274 | /*
|
226 | 275 | * Create a table space
|
227 | 276 | *
|
@@ -366,6 +415,30 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
|
366 | 415 | /* Post creation hook for new tablespace */
|
367 | 416 | InvokeObjectPostCreateHook(TableSpaceRelationId, tablespaceoid, 0);
|
368 | 417 |
|
| 418 | + if (getIntOption(stmt->options, "max_files", 0) != 0) |
| 419 | + { |
| 420 | + /* Limited tablespace */ |
| 421 | + char* segment = psprintf("%s/%u", location, tablespaceoid); |
| 422 | + char* link_path = psprintf("%s/current_segment", location); |
| 423 | + |
| 424 | + pfree(location); |
| 425 | + location = segment; |
| 426 | + if (MakePGDirectory(location) < 0) |
| 427 | + { |
| 428 | + if (errno == EEXIST) |
| 429 | + ereport(ERROR, |
| 430 | + (errcode(ERRCODE_OBJECT_IN_USE), |
| 431 | + errmsg("directory \"%s\" already in use as a tablespace", |
| 432 | + location))); |
| 433 | + else |
| 434 | + ereport(ERROR, |
| 435 | + (errcode_for_file_access(), |
| 436 | + errmsg("could not create directory \"%s\": %m", |
| 437 | + location))); |
| 438 | + } |
| 439 | + AtomicChangeLink(link_path, location); |
| 440 | + } |
| 441 | + |
369 | 442 | create_tablespace_directories(location, tablespaceoid);
|
370 | 443 |
|
371 | 444 | /* Record the filesystem change in XLOG */
|
@@ -1554,3 +1627,161 @@ tblspc_redo(XLogReaderState *record)
|
1554 | 1627 | else
|
1555 | 1628 | elog(PANIC, "tblspc_redo: unknown op code %u", info);
|
1556 | 1629 | }
|
| 1630 | + |
| 1631 | + |
| 1632 | +static int |
| 1633 | +RecursiveCountFiles(char const* linkloc_with_version_dir, int level) |
| 1634 | +{ |
| 1635 | + char *subfile; |
| 1636 | + int count = 0; |
| 1637 | + DIR *dirdesc = AllocateDir(linkloc_with_version_dir); |
| 1638 | + struct dirent *de; |
| 1639 | + |
| 1640 | + if (dirdesc == NULL) |
| 1641 | + { |
| 1642 | + return 0; |
| 1643 | + } |
| 1644 | + while ((de = ReadDir(dirdesc, linkloc_with_version_dir)) != NULL) |
| 1645 | + { |
| 1646 | + if (strcmp(de->d_name, ".") == 0 || |
| 1647 | + strcmp(de->d_name, "..") == 0) |
| 1648 | + continue; |
| 1649 | + |
| 1650 | + subfile = psprintf("%s/%s", linkloc_with_version_dir, de->d_name); |
| 1651 | + /* Assume that relations files are always at second level */ |
| 1652 | + count += level > 0 ? RecursiveCountFiles(subfile, level - 1) : 1; |
| 1653 | + } |
| 1654 | + FreeDir(dirdesc); |
| 1655 | + return count; |
| 1656 | +} |
| 1657 | + |
| 1658 | +static int |
| 1659 | +CountTablespaceFiles(char const* curr_segment) |
| 1660 | +{ |
| 1661 | + char* linkloc_with_version_dir = psprintf("%s/%s", curr_segment, TABLESPACE_VERSION_DIRECTORY); |
| 1662 | + return RecursiveCountFiles(linkloc_with_version_dir, 1); |
| 1663 | +} |
| 1664 | + |
| 1665 | +static Oid |
| 1666 | +GetCurrentTablespaceSegmentId(char* segment_path) |
| 1667 | +{ |
| 1668 | + char* tblspc = strrchr(segment_path, '/'); |
| 1669 | + if (tblspc == NULL) |
| 1670 | + ereport(ERROR, |
| 1671 | + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| 1672 | + errmsg("Invalid current tablespace segment \"%s\"", |
| 1673 | + segment_path))); |
| 1674 | + *tblspc++ = '\0'; |
| 1675 | + return atoi(tblspc); |
| 1676 | +} |
| 1677 | + |
| 1678 | +static char* |
| 1679 | +GetCurrentTablespaceSegmentLocation(Oid tablespaceId) |
| 1680 | +{ |
| 1681 | + char link_target[MAXPGPATH]; |
| 1682 | + char* link_path = psprintf("pg_tblspc/%u", tablespaceId); |
| 1683 | + int len = readlink(link_path, link_target, sizeof(link_target)); |
| 1684 | + if (len < 0) |
| 1685 | + ereport(ERROR, |
| 1686 | + (errcode_for_file_access(), |
| 1687 | + errmsg("could not read symbolic link \"%s\": %m", |
| 1688 | + link_path))); |
| 1689 | + link_target[len] = '\0'; |
| 1690 | + pfree(link_path); |
| 1691 | + GetCurrentTablespaceSegmentId(link_target); |
| 1692 | + link_path = psprintf("%s/current_segment", link_target); |
| 1693 | + len = readlink(link_path, link_target, sizeof(link_target)); |
| 1694 | + if (len < 0) |
| 1695 | + ereport(ERROR, |
| 1696 | + (errcode_for_file_access(), |
| 1697 | + errmsg("could not read symbolic link \"%s\": %m", |
| 1698 | + link_path))); |
| 1699 | + link_target[len] = '\0'; |
| 1700 | + pfree(link_path); |
| 1701 | + return pstrdup(link_target); |
| 1702 | +} |
| 1703 | + |
| 1704 | +Oid |
| 1705 | +GetCurrentTablespaceSegment(Oid tablespaceId) |
| 1706 | +{ |
| 1707 | + CreateTableSpaceStmt* stmt; |
| 1708 | + Relation rel; |
| 1709 | + ScanKeyData entry[1]; |
| 1710 | + TableScanDesc scan; |
| 1711 | + HeapTuple tup; |
| 1712 | + Oid spcowner; |
| 1713 | + bool isnull; |
| 1714 | + List* relopt; |
| 1715 | + int num_files; |
| 1716 | + char* orig_spcname; |
| 1717 | + char* new_spcname; |
| 1718 | + char* curr_segment; |
| 1719 | + bool segment_is_used; |
| 1720 | + int limit; |
| 1721 | + Oid segmentId; |
| 1722 | + |
| 1723 | + /* If there is limit for maximal files in tablespace, then count files in the current segment and create new one if needed */ |
| 1724 | + limit = get_tablespace_max_files(tablespaceId); |
| 1725 | + if (limit == 0) |
| 1726 | + return tablespaceId; |
| 1727 | + |
| 1728 | + curr_segment = GetCurrentTablespaceSegmentLocation(tablespaceId); |
| 1729 | + num_files = CountTablespaceFiles(curr_segment); |
| 1730 | + segmentId = GetCurrentTablespaceSegmentId(curr_segment); |
| 1731 | + |
| 1732 | + if (num_files >= limit) /* Check for tablespace overflow */ |
| 1733 | + { |
| 1734 | + /* Search pg_tablespace */ |
| 1735 | + rel = table_open(TableSpaceRelationId, RowExclusiveLock); |
| 1736 | + |
| 1737 | + /* Extract information about original tablespace */ |
| 1738 | + ScanKeyInit(&entry[0], |
| 1739 | + Anum_pg_tablespace_oid, |
| 1740 | + BTEqualStrategyNumber, F_OIDEQ, |
| 1741 | + ObjectIdGetDatum(tablespaceId)); |
| 1742 | + scan = table_beginscan_catalog(rel, 1, entry); |
| 1743 | + tup = heap_getnext(scan, ForwardScanDirection); |
| 1744 | + if (!HeapTupleIsValid(tup)) /* If tablespace with specified OID was not found, then most likely it was removed */ |
| 1745 | + ereport(ERROR, |
| 1746 | + (errcode(ERRCODE_UNDEFINED_OBJECT), |
| 1747 | + errmsg("tablespace with OID %d was removed", |
| 1748 | + tablespaceId))); |
| 1749 | + /* Extract tablespace options */ |
| 1750 | + relopt = untransformRelOptions(heap_getattr(tup, Anum_pg_tablespace_spcoptions, |
| 1751 | + RelationGetDescr(rel), &isnull)); |
| 1752 | + /* Original tablespace name */ |
| 1753 | + orig_spcname = pstrdup(NameStr(((Form_pg_tablespace) GETSTRUCT(tup))->spcname)); |
| 1754 | + spcowner = ((Form_pg_tablespace) GETSTRUCT(tup))->spcowner; |
| 1755 | + |
| 1756 | + table_endscan(scan); |
| 1757 | + |
| 1758 | + /* Choose new unused tablespace name by concatanating segno suffix */ |
| 1759 | + do |
| 1760 | + { |
| 1761 | + new_spcname = psprintf("%s.%d", orig_spcname, segmentId++); |
| 1762 | + ScanKeyInit(&entry[0], |
| 1763 | + Anum_pg_tablespace_spcname, |
| 1764 | + BTEqualStrategyNumber, F_NAMEEQ, |
| 1765 | + CStringGetDatum(new_spcname)); |
| 1766 | + scan = table_beginscan_catalog(rel, 1, entry); |
| 1767 | + tup = heap_getnext(scan, ForwardScanDirection); |
| 1768 | + segment_is_used = HeapTupleIsValid(tup); |
| 1769 | + table_endscan(scan); |
| 1770 | + } while (segment_is_used); |
| 1771 | + |
| 1772 | + table_close(rel, RowExclusiveLock); |
| 1773 | + |
| 1774 | + stmt = makeNode(CreateTableSpaceStmt); |
| 1775 | + stmt->tablespacename = new_spcname; |
| 1776 | + stmt->owner = makeNode(RoleSpec); |
| 1777 | + stmt->owner->roletype = ROLESPEC_CSTRING; |
| 1778 | + stmt->owner->rolename = GetUserNameFromId(spcowner, false); |
| 1779 | + stmt->location = curr_segment; |
| 1780 | + stmt->options = relopt; |
| 1781 | + |
| 1782 | + /* Create new tablespace */ |
| 1783 | + segmentId = CreateTableSpace(stmt); |
| 1784 | + Assert(OidIsValid(segmentId)); |
| 1785 | + } |
| 1786 | + return segmentId; |
| 1787 | +} |
0 commit comments