支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11

67.2. 系統目錄初始資料 #

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

67.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 的使用在下方 第 67.2.2 節中描述,而 array_type_oid第 67.2.4 節中描述。descr 提供物件的描述字串,該字串將根據情況插入 pg_descriptionpg_shdescription 中。)雖然元資料鍵是可選的,但目錄的已定義欄位必須全部提供,除非目錄的 .h 檔案指定了欄位的預設值。(在上面的例子中,datdba 欄位已被省略,因為 pg_database.h 為它提供了一個合適的預設值。)

  • 所有值都必須用單引號引起來。用反斜線跳脫值中使用的單引號。可以(但不一定)將作為資料的反斜線加倍;這遵循 Perl 的簡單引號文字規則。請注意,根據與跳脫字串常數相同的規則(請參閱 第 4.1.2.2 節),作為資料出現的反斜線將被 bootstrap 掃描器視為跳脫;例如,\t 會轉換為 tab 字元。如果您實際上想要最終值中的反斜線,則需要寫四個:Perl 會去除兩個,留下 \\ 給 bootstrap 掃描器查看。

  • 空值以 _null_ 表示。(請注意,沒有辦法建立一個只是該字串的值。)

  • 註解以 # 開頭,並且必須位於它們自己的行上。

  • 作為其他目錄條目的 OID 的欄位值應該用符號名稱而不是實際的數字 OID 表示。(在上面的例子中,dattablespace 包含這樣一個參考。)這在下方 第 67.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() 如何將資料擴展回完整的表示形式。

67.2.2. OID 指派 #

可以通過編寫一個 oid => nnnn 元資料欄位,為初始資料中出現的目錄列指定手動分配的 OID。此外,如果分配了 OID,則可以通過編寫一個 oid_symbol => 名稱 元資料欄位,來建立該 OID 的 C 巨集。

如果其他預先載入的列中有對預先載入的目錄列的 OID 參考,則這些預先載入的目錄列必須具有預先分配的 OID。如果列的 OID 必須從 C 程式碼中參考,則也需要預先分配的 OID。如果兩種情況都不適用,則可以省略 oid 元資料欄位,在這種情況下,bootstrap 程式碼會自動分配一個 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 檔案,以查看哪些未出現。您也可以使用 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) 的物件被認為是釘選,防止它們被刪除。(有一些例外情況,它們被硬編碼到 IsPinnedObject() 中。)initdb 會強制 OID 計數器上升到 FirstUnpinnedObjectId,一旦它準備好建立未釘選的物件。因此,在 initdb 的後期階段建立的物件,例如在執行 information_schema.sql 腳本時建立的物件,將不會被釘選,而 genbki.pl 所知的所有物件將會被釘選。

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

67.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

  • 如果函數的 pronamepg_proc.dat 條目中是唯一的(這就像 regproc 輸入),則可以使用其 proname 來表示該函數。否則,將其寫為 proname(argtypename,argtypename,...),就像 regprocedure 一樣。參數類型名稱必須完全按照它們在 pg_proc.dat 條目的 proargtypes 欄位中的拼寫方式拼寫。不要插入任何空格。

  • 運算子由 oprname(lefttype,righttype) 表示,完全按照它們在 pg_operator.dat 條目的 oprleftoprright 欄位中的拼寫方式寫入類型名稱。(對於一元運算子的省略運算元,寫入 0。)

  • 運算子類別 (opclass) 和運算子家族 (opfamily) 的名稱僅在存取方法中是唯一的,因此它們以 access_method_name/object_name 的形式表示。

  • 在這些情況下,都沒有提供結構描述限定 (schema-qualification);所有在啟動過程中建立的物件都預期位於 pg_catalog 結構描述中。

genbki.pl 會在其執行時解析所有符號參考,並將簡單的數字 OID 放入發出的 BKI 檔案中。因此,啟動後端 (bootstrap backend) 不需要處理符號參考。

即使目錄沒有需要查詢的初始資料,最好還是用 BKI_LOOKUPBKI_LOOKUP_OPT 標記 OID 參考欄位。這允許 genbki.pl 記錄系統目錄中存在的外鍵關係。該資訊用於迴歸測試中,以檢查不正確的條目。另請參閱巨集 DECLARE_FOREIGN_KEYDECLARE_FOREIGN_KEY_OPTDECLARE_ARRAY_FOREIGN_KEYDECLARE_ARRAY_FOREIGN_KEY_OPT,它們用於宣告對 BKI_LOOKUP 而言太複雜的外鍵關係(通常是多欄位外鍵)。

67.2.4. 自動建立陣列型別 #

大多數純量資料型別都應該有一個對應的陣列型別(也就是說,一個標準的 varlena 陣列型別,其元素型別是純量型別,並且由純量型別的 pg_type 條目的 typarray 欄位引用)。在大多數情況下,genbki.pl 能夠自動產生陣列型別的 pg_type 條目。

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

產生的陣列型別的名稱是純量型別的名稱,並在前面加上一個底線。陣列條目的其他欄位從 pg_type.h 中的 BKI_ARRAY_DEFAULT(value) 註解中填寫,如果沒有,則從純量型別複製。(typalign 也有一個特殊情況。)然後,兩個條目的 typelemtyparray 欄位設定為互相交叉引用。

67.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 中的兩個布林欄位合併為一個字元欄位。

  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://postgresql.dev.org.tw/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用此表格回報文件問題。