2025年9月25日: PostgreSQL 18 釋出!
支援的版本: 當前 (18) / 17 / 16 / 15 / 14 / 13
開發版本: devel
不支援的版本: 12 / 11

68.2. 系統目錄初始資料 #

每個包含手動建立的初始資料(有些不包含)的目錄都有一個對應的 .dat 檔案,該檔案以可編輯的格式包含其初始資料。

68.2.1. 資料檔案格式 #

每個 .dat 檔案都包含 Perl 資料結構字面量,這些字面量會被簡單地 `eval` 來生成一個記憶體中的資料結構,該結構由一個雜湊引用陣列組成,每個雜湊引用代表一行目錄。對 `pg_database.dat` 的一個稍微修改過的摘錄將演示關鍵特性。

[

# A comment could appear here.
{ oid => '1', oid_symbol => 'Template1DbOid',
  descr => 'database\'s default template',
  datname => 'template1', encoding => 'ENCODING',
  datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't',
  datallowconn => 't', dathasloginevt => 'f', datconnlimit => '-1', datfrozenxid => '0',
  datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
  datctype => 'LC_CTYPE', datlocale => 'DATLOCALE', datacl => '_null_' },

]

需要注意的點

  • 整體檔案佈局是:左方括號,一個或多個大括號集合(每個代表一行目錄),右方括號。在每個閉合的大括號後寫一個逗號。

  • 在每個目錄行內,寫入逗號分隔的 => 對。允許的 是目錄列的名稱,加上元資料鍵 oidoid_symbolarray_type_oiddescr。(oidoid_symbol 的使用在下面 第 68.2.2 節 中描述,而 array_type_oid 在下面 第 68.2.4 節 中描述。 descr 為物件提供一個描述字串,該字串將被插入到 pg_descriptionpg_shdescription 中。)雖然元資料鍵是可選的,但必須提供目錄定義的所有列,除非目錄的 .h 檔案為該列指定了預設值。(在上例中,datdba 欄位已被省略,因為 pg_database.h 為其提供了合適的預設值。)

  • 所有值都必須用單引號括起來。使用反斜槓轉義值內的單引號。用作資料的反斜槓可以(但不必)加倍;這遵循 Perl 對簡單引號字面量的規則。請注意,用作資料的反斜槓將被載入程式掃描器視為跳脫字元,規則與跳脫字元串常量相同(參見 第 4.1.2.2 節);例如,\t 會轉換為製表符。如果您確實想要一個反斜槓作為資料,您將需要寫四個反斜槓:Perl 會剝離兩個,留下 \\ 讓載入程式掃描器看到。

  • 空值用 _null_ 表示。(請注意,沒有辦法建立僅由該字串組成的值。)

  • 註釋以 # 開頭,並且必須獨佔一行。

  • 欄位值如果是其他目錄條目的 OID,則應使用符號名稱而不是實際的數字 OID 來表示。(在上例中,dattablespace 包含此類引用。)這在下面 第 68.2.3 節 中進行了描述。

  • 由於雜湊是無序的資料結構,欄位順序和行佈局在語義上並不重要。但是,為了保持一致的外觀,我們設定了一些由格式化指令碼 reformat_dat_file.pl 應用的規則。

    • 在大括號對之間,元資料欄位 oidoid_symbolarray_type_oiddescr(如果存在)先出現,按此順序,然後是目錄自身的欄位,按其定義的順序出現。

    • 根據需要插入換行符以將行長度限制在 80 個字元(如果可能)。元資料欄位和常規欄位之間也會插入換行符。

    • 如果目錄的 .h 檔案為某個列指定了預設值,並且某個資料條目具有相同的值,則 reformat_dat_file.pl 將在資料檔案中省略它。這保持了資料表示的緊湊性。

    • reformat_dat_file.pl 按原樣保留空行和註釋行。

    建議在提交目錄資料補丁之前執行 reformat_dat_file.pl。為了方便起見,您可以簡單地切換到 src/include/catalog/ 目錄並執行 make reformat-dat-files

  • 如果您想新增一種新的資料表示壓縮方法,您必須在 reformat_dat_file.pl 中實現它,並教 Catalog::ParseData() 如何將資料擴充套件回完整表示。

68.2.2. OID 分配 #

初始資料中出現的目錄行可以透過寫入 oid => nnnn 元資料欄位來賦予手動分配的 OID。此外,如果分配了 OID,可以透過寫入 oid_symbol => name 元資料欄位來為此 OID 建立一個 C 宏。

預載入的目錄行必須具有預先分配的 OID,如果其他預載入行中存在對它們的 OID 引用。如果行的 OID 需要從 C 程式碼中引用,也需要預先分配的 OID。如果以上兩種情況都不適用,則可以省略 oid 元資料欄位,在這種情況下,引導程式碼會自動分配一個 OID。實際上,我們通常會為給定目錄中所有或所有預載入行預先分配 OID,即使只有其中一些實際上被交叉引用。

在 C 程式碼中寫入任何 OID 的實際數值被認為是非常不好的做法;始終使用宏。對 pg_proc OID 的直接引用足夠常見,以至於有一個特殊的機制來自動建立必要的宏;請參閱 src/backend/utils/Gen_fmgrtab.pl。類似地——但由於歷史原因,做法不同——有一個自動方法用於為 pg_type OID 建立宏。oid_symbol 條目因此在這兩個目錄中不是必需的。同樣,系統目錄和索引的 pg_class OID 的宏也是自動設定的。對於所有其他系統目錄,您必須透過 oid_symbol 條目手動指定您需要的任何宏。

要查詢新預載入行的可用 OID,請執行指令碼 src/include/catalog/unused_oids。它會列印未使用的 OID 的範圍(例如,輸出行 45-900 表示 OID 45 到 900 尚未分配)。目前,OID 1-9999 保留用於手動分配;unused_oids 指令碼只是檢視目錄標頭檔案和 .dat 檔案,檢視哪些 OID 未出現。您還可以使用 duplicate_oids 指令碼檢查錯誤。(genbki.pl 會為任何沒有手動分配 OID 的行分配 OID,並且還會在編譯時檢測到重複的 OID。)

當為預計不會立即提交的補丁選擇 OID 時,最佳實踐是使用一組大致連續的 OID,以 8000-9999 範圍內的某個隨機選擇開始。這最大限度地降低了與其他正在並行開發的補丁發生 OID 衝突的風險。為了將 8000-9999 範圍保留用於開發目的,在補丁已提交到主 git 儲存庫後,其 OID 應重新編號到該範圍以下的可用空間。通常,這將在每個開發週期結束時完成,同時將該週期中提交的所有補丁消耗的 OID 移動。可以使用 renumber_oids.pl 指令碼來實現此目的。如果發現未提交的補丁與最近提交的補丁存在 OID 衝突,renumber_oids.pl 也可用於從該情況中恢復。

由於這種可能重新編號補丁分配的 OID 的約定,因此補丁分配的 OID 在補丁被包含在官方發行版中之前不應被視為穩定。但是,一旦釋出,我們不會更改手動分配的物件 OID,因為這會產生各種相容性問題。

如果 genbki.pl 需要為沒有手動分配 OID 的目錄條目分配 OID,它將使用 10000-11999 範圍內的值。伺服器的 OID 計數器在引導執行開始時設定為 10000,以便在引導處理過程中即時建立的任何物件也獲得此範圍內的 OID。(通常的 OID 分配機制負責防止任何衝突。)

OID 小於 FirstUnpinnedObjectId (12000) 的物件被視為“固定”(“pinned”),阻止其被刪除。(有一些小的例外,這些例外已硬編碼在 IsPinnedObject() 中。)initdb 在準備建立未固定物件時,會盡快將 OID 計數器強制提高到 FirstUnpinnedObjectId。因此,在 initdb 的後期階段建立的物件(例如,在執行 information_schema.sql 指令碼時建立的物件)將不會被固定,而 genbki.pl 所知的所有物件都將被固定。

在正常資料庫操作期間分配的 OID 被限制為 16384 或更高。這確保了 10000-16383 範圍可用於由 genbki.pl 或在 initdb 期間自動分配的 OID。這些自動分配的 OID 不被認為是穩定的,並且可能因一個安裝到另一個安裝而不同。

68.2.3. OID 引用查詢 #

原則上,一個初始目錄行到另一個的交叉引用可以透過在引用欄位中寫入被引用行的預分配 OID 來實現。然而,這違反了專案政策,因為容易出錯、難以閱讀,並且在重新編號新分配的 OID 時容易損壞。因此,genbki.pl 提供了使用符號引用代替的機制。規則如下:

  • 透過將 BKI_LOOKUP(lookuprule) 附加到列的定義來啟用特定目錄列中的符號引用,其中 lookuprule 是被引用目錄的名稱,例如 pg_procBKI_LOOKUP 可以附加到 OidregprocoidvectorOid[] 型別的列;在後兩種情況下,它意味著對陣列的每個元素執行查詢。

  • BKI_LOOKUP(encoding) 附加到整數列以引用字元集編碼也是允許的,這些字元集編碼目前不表示為目錄 OID,但它們的值集是 genbki.pl 已知的。

  • 在某些目錄列中,條目可以為零而不是有效的引用。如果允許,請寫 BKI_LOOKUP_OPT 而不是 BKI_LOOKUP。然後您可以為條目寫 0。(如果列宣告為 regproc,您可以選擇寫 - 而不是 0。)除了這個特殊情況,BKI_LOOKUP 列中的所有條目都必須是符號引用。genbki.pl 會警告未識別的名稱。

  • 大多數型別的目錄物件僅透過其名稱來引用。請注意,型別名稱必須與被引用的 pg_type 條目的 typname 完全匹配;您不能使用任何別名,例如 integer 代替 int4

  • 函式可以由其 proname 表示,如果它在 pg_proc.dat 條目中是唯一的(這類似於 regproc 輸入)。否則,寫成 proname(argtypename,argtypename,...),就像 regprocedure 一樣。引數型別名稱必須與 pg_proc.dat 條目的 proargtypes 欄位中的名稱完全一致。不要插入任何空格。

  • 運算子表示為 oprname(lefttype,righttype),型別名稱必須與 pg_operator.dat 條目的 oprleftoprright 欄位中的名稱完全一致。(對於一元運算子的省略運算元,寫 0。)

  • 運算子類和運算子族的名稱僅在訪問方法內是唯一的,因此它們表示為 access_method_name/object_name

  • 在以上任何情況下,都沒有提供模式限定的機制;在引導期間建立的所有物件都應位於 pg_catalog 模式下。

genbki.pl 在執行時解析所有符號引用,並將簡單的數字 OID 放入生成的 BKI 檔案中。因此,引導後端無需處理符號引用。

即使某個目錄當前不需要查詢,也建議使用 BKI_LOOKUPBKI_LOOKUP_OPT 來標記 OID 引用列。這允許 genbki.pl 記錄系統目錄中存在的外部索引鍵關係。這些資訊用於迴歸測試以檢查不正確的條目。另請參閱宏 DECLARE_FOREIGN_KEYDECLARE_FOREIGN_KEY_OPTDECLARE_ARRAY_FOREIGN_KEYDECLARE_ARRAY_FOREIGN_KEY_OPT,它們用於宣告 BKI_LOOKUP 無法處理的(通常是多列外部索引鍵)複雜外部索引鍵關係。

68.2.4. 自動建立陣列型別 #

大多數標量資料型別應該有一個對應的陣列型別(即,一個元素型別為標量型別、並透過標量型別的 pg_type 條目的 typarray 欄位引用的標準 varlena 陣列型別)。在大多數情況下,genbki.pl 能夠自動生成陣列型別的 pg_type 條目。

要使用此功能,只需在標量型別的 pg_type 條目中寫入 array_type_oid => nnnn 元資料欄位,指定用於陣列型別的 OID。然後您可以省略 typarray 欄位,因為它將自動填充為該 OID。

生成的陣列型別的名稱是標量型別的名稱前面加上下劃線。陣列條目的其他欄位從 pg_type.h 中的 BKI_ARRAY_DEFAULT(value) 註釋中填充,如果沒有,則從標量型別中複製。(對於 typalign 也有一個特殊情況。)然後,兩個條目的 typelemtyparray 欄位被設定為相互引用。

68.2.5. 編輯資料檔案的規範 #

以下是一些關於在更新目錄資料檔案時執行常見任務的最簡單方法的建議。

為目錄新增帶有預設值的列:  在標頭檔案中新增列,並帶有 BKI_DEFAULT(value) 註釋。資料檔案只需在需要非預設值的地方調整,透過新增欄位到現有行。

為沒有預設值的現有列新增預設值:  在標頭檔案中新增 BKI_DEFAULT 註釋,然後執行 make reformat-dat-files 以刪除現在多餘的欄位條目。

刪除列,無論是否有預設值:  從標頭檔案中刪除列,然後執行 make reformat-dat-files 以刪除現在無用的欄位條目。

更改或刪除現有預設值:  您不能僅更改標頭檔案,因為這會導致當前資料被錯誤解釋。首先執行 make expand-dat-files 將所有預設值顯式插入到資料檔案中,然後更改或刪除 BKI_DEFAULT 註釋,然後再次執行 make reformat-dat-files 以刪除多餘的欄位。

臨時批次編輯:  reformat_dat_file.pl 可用於執行多種批次更改。查詢其塊註釋,顯示可以插入一次性程式碼的位置。在下面的示例中,我們將 pg_proc 中的兩個布林欄位合併為一個 char 欄位。

  1. pg_proc.h 中新增新列(帶預設值)。

    +    /* see PROKIND_ categories below */
    +    char        prokind BKI_DEFAULT(f);
    
  2. 建立一個基於 reformat_dat_file.pl 的新指令碼,以即時插入適當的值。

    -           # At this point we have the full row in memory as a hash
    -           # and can do any operations we want. As written, it only
    -           # removes default values, but this script can be adapted to
    -           # do one-off bulk-editing.
    +           # One-off change to migrate to prokind
    +           # Default has already been filled in by now, so change to other
    +           # values as appropriate
    +           if ($values{proisagg} eq 't')
    +           {
    +               $values{prokind} = 'a';
    +           }
    +           elsif ($values{proiswindow} eq 't')
    +           {
    +               $values{prokind} = 'w';
    +           }
    
  3. 執行新指令碼。

    $ cd src/include/catalog
    $ perl  rewrite_dat_with_prokind.pl  pg_proc.dat
    

    此時 pg_proc.dat 包含所有三個列:prokindproisaggproiswindow,儘管它們只出現在具有非預設值的行中。

  4. pg_proc.h 中刪除舊列。

    -    /* is it an aggregate? */
    -    bool        proisagg BKI_DEFAULT(f);
    -
    -    /* is it a window function? */
    -    bool        proiswindow BKI_DEFAULT(f);
    
  5. 最後,執行 make reformat-dat-files 以從 pg_proc.dat 中刪除無用的舊條目。

有關用於批次編輯的指令碼的更多示例,請參閱此訊息附帶的 convert_oid2name.plremove_pg_type_oid_symbols.plhttps://postgres.tw/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com

提交更正

如果您在文件中看到任何不正確、與您對特定功能的使用經驗不符或需要進一步說明的內容,請使用 此表單 報告文件問題。