支援的版本:目前 (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_' },

]

注意事項

  • 整體檔案佈局為:左方括號、一或多組花括號(每組花括號代表一個目錄列)、右方括號。在每個右花括號後寫一個逗號。

  • 在每個目錄列中,寫入逗號分隔的 key => value 對。允許的 key 是目錄欄位的名稱,加上中繼資料鍵 oidoid_symbolarray_type_oiddescr。(oidoid_symbol 的使用方式在Section 67.2.2 中說明,而 array_type_oidSection 67.2.4 中說明。descr 提供物件的描述字串,該字串將插入到 pg_descriptionpg_shdescription 中,視情況而定。)雖然中繼資料鍵是可選的,但目錄定義的欄位必須全部提供,除非目錄的 .h 檔案為該欄位指定了預設值。(在上面的範例中,datdba 欄位已被省略,因為 pg_database.h 為其提供了合適的預設值。)

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

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

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

  • 作為其他目錄條目的 OID 的欄位值應該由符號名稱而不是實際的數字 OID 表示。(在上面的範例中,dattablespace 包含這樣的參考。)這在 Section 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 => name 中繼資料欄位來建立該 OID 的 C 巨集。

如果預先載入的目錄列在其他預先載入的列中具有對它們的 OID 參考,則必須具有預先指派的 OID。如果必須從 C 程式碼參考該列的 OID,則也需要預先指派的 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 檔案,看看哪些 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) 的物件被視為 釘選,防止它們被刪除。(有少數例外情況,它們被硬編碼到 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。)

  • 運算子類別和運算子族的名稱僅在存取方法中是唯一的,因此它們由 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 來說過於複雜的外鍵關係(通常是多欄位外鍵)。

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 以再次移除多餘的欄位。

Ad-hoc 批量編輯:  可以修改 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://postgresql.dev.org.tw/message-id/CAJVSVGVX8gXnPm+Xa=DxR7kFYprcQ1tNcCT5D0O3ShfnM6jehA@mail.gmail.com

提交更正

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