第四十三章. 擴展索引接口
到目前為止我們描述的過程可以讓你定義一個新類型,新函數和新操作符.但是,我們還不能在一個新類型或它的操作符上面定義一個從屬索引(象一個 B-tree,R-tree 或哈希(hash)訪問方式).
回過頭來看看 主要的 Postgres 系統表。右半部分顯示了那些我們如果要告訴 Postgres 如何在索引上(例如,pg_am,pg_amop,pg_amproc 和 pg_opclass)去使用用戶定義類型和/或用戶定義操作符時必須要修改的表.不幸的是,沒有簡單的命令可以做這些事情.我們將通過實例來演示如何通過修改這些表來實現上面需求,此例子是:一個新的用于 B-tree 訪問模式的操作符表,它把復數以絕對值升序的順序排列.
pg_am 表為每個用戶定義的訪問模式都保留一條記錄.對堆的訪問模式的支持內建于 Postgres,但其他所有訪問模式在這里都有描述.表結構是
表 43-1. 索引表結構
字段/屬性 描述
amname 訪問模式名稱
amowner 所有這在 pg_user 中的對象標識(object id )
amstrategies 此訪問模式的(訪問)策略數(見下面)
amsupport 此訪問模式支持的過程數(見下面)
amorderstrategy 如果該索引沒有提供排序順序,為零,否則是描述了排序順序的策略操作符的策略數
amgettuple
aminsert
... 該訪問模式的接口過程的過程標識.例如,regproc 表示打開,關閉,和從這里出現的訪問模式中獲取記錄.
pg_am 里的記錄的 對象標識(object ID) 用于其他很多表的外部鍵值.你不需要向這個表里面增加一條新記錄;你要關心的是你想要擴展的訪問模式記錄的 對象標識(object ID):
SELECT oid FROM pg_am WHERE amname = 'btree';
oid
-----
403
(1 row)
我們將在稍后在一個 WHERE 子句中使用這個 SELECT。
amstrategies 字段的存在用于使數據類型之間的比較標準化.例如,B-tree 對鍵字,小于號到大于號施加了很嚴格的順序.因為 Postgres 允許用戶定義操作符,所以 Postgres不能只是看到操作符的名稱(如,">" 或 "<")就認為是什么樣的比較.實際上,一些訪問模式并不強加任何順序要求.例如,R-tree的表達式是長方形包含關系,而一個散列(hash)數據結構表達式只是與散列(hash)函數的結果有一些位(bitwise)相似.Postgres 需要某種連貫的方法從你的查詢里取來一個資格(條件),查看一下操作符然后馬上決定是否有一個可用的索引存在.這意味著Postgres 需要知道,比如說,象 "<=" 和">" 操作符分割一個 B-tree.Postgres 使用策略來表達這些操作符之間的關系以及它們可以用于掃描索引的方法.
定義一套新的策略超出了這個討論的范疇,但是我們將 B-tree 策略如何工作,因為你將需要知道了解這些來增加一個新的操作符表.在 pg_am 表里,amstrategies 字段是為這個訪問模式定義的策略數量.對于 B-tree,這個數量是 5.這些策略對應于
表 43-2. B-tree 策略
操作 索引
less than(小于) 1
less than or equal(小于或等于) 2
equal(等于) 3
greater than or equal(大于或等于) 4
greater than(大于) 5
方法是你需要增加與上面對應的比較過程到 pg_amop 關系里去(見下面).訪問模式代碼可以使用這些策略數(不管數據類型是什么),來確定如何分割 B-tree,計算選擇性等.先不必關心增加過程的細節問題;只要明白我們必須有一套這樣的過程用于 int2,int4,oid,和所有其他 B-tree 可以操作的數據類型.
有時候,策略的信息還不足以讓系統決定如何使用某個索引. 一些訪問模式就需要其他的一些過程來保證能夠工作.例如,B-tree 訪問模式必須能夠比較兩個鍵字以決定其中一個是大于,等于,還是小于另外一個.類似的,R-tree 訪問模式必須能夠計算長方形的相交,聯合,和大小等.這些操作不能在 SQL 查詢里與用戶的資格(條件)對應;它們是被訪問模式的管理性質的過程內部調用的過程.
為了管理所有 Postgres 的訪問模式支持的各種各樣的過程,pg_am 包含一個字段叫 amsupport.這個字段記錄被某個訪問模式支持的過程的個數.對于 B-tree,這個數字是一-一個接受兩個鍵字并且根據第一個鍵字是否小于,等于或大于第二個鍵字而返回 -1,0,或 +1的過程.
注意:嚴格的說,這個過程可以返回一個負數(< 0),0,或一個非零正數(> 0).
pg_am 里的 amstrategies 條目只是正在討論的訪問模式定義的策略數.用于小于,小于等于等的過程不在 pg_am 里出現.類似的,amsupport 只是訪問模式需要的支持過程個數.實際的過程在其他地方列出.
順便說一句,amorderstrategy 字段報告此訪問模式是否支持排序的掃描。零意味著它不支持;如果該訪問模式支持排序的掃描,amorderstrategy 就是對應排序操作符的策略過程的數目。例如,btree 的 amorderstrategy = 1,就是它的"小于"策略數目。
下一個讓人感興趣的表是 pg_opclass.這個表的存在只是用于把一個名稱和一個缺省類型與一個對象標識(oid)聯結起來.在 pg_amop,每個 B-tree 操作符表有一套過程,象上面那樣從一到五.一些現有的 opclasses 是 int2_ops,int4_ops,和 oid_ops。你需要用你的 opclass 名稱(例如,complex_abs_ops)增加一條記錄到 pg_opclass 里面.這條記錄的 oid 是其他表的外部鍵字,尤其是 pg_amop.
INSERT INTO pg_opclass (opcname, opcdeftype)
SELECT 'complex_abs_ops', oid FROM pg_type WHERE typname = 'complex';
SELECT oid, opcname, opcdeftype
FROM pg_opclass
WHERE opcname = 'complex_abs_ops';
oid | opcname | opcdeftype
--------+-----------------+------------
277975 | complex_abs_ops | 277946
(1 row)
注意你的 pg_opclass 記錄的對象標識(oid)將會不一樣!先不考慮這些。我們將在稍后從系統獲取這些數字,就象我們在這里獲取該類型的 oid 一樣。
上面的例子假設你想把這個新的 opclass 做為 complex 數據類型的缺省索引.如果不是這樣,只需要向 opcdeftype 插入零,而不是插入該數據類型的 oid:
INSERT INTO pg_opclass (opcname, opcdeftype) VALUES ('complex_abs_ops', 0);
因此,現在我們有了一個訪問方式和一個操作符表.我們還需要一套操作符;用于定義操作符的過程已經在這份手冊的早先部分討論過了.對這個用于 Btrees 的 complex_abs_ops 操作符表,我們需要的操作符是:
absolute value less-than
absolute value less-than-or-equal
absolute value equal
absolute value greater-than-or-equal
absolute value greater-than
假設實現函數冊代碼放在文件 PGROOT/src/tutorial/complex.c 里
有一部分代碼看起來象:(注意我們在余下的例子中將只展示等號操作符.其他四種操作符都非常相似,請參考 complex.c 或 complex.sql 獲取詳細信息.)
#define Mag(c) ((c)->x*(c)->x + (c)->y*(c)->y)
bool
complex_abs_eq(Complex *a, Complex *b)
{
double amag = Mag(a), bmag = Mag(b);
return (amag==bmag);
}
我們用下面語句讓 Postgres 這樣識別這個函數:
CREATE FUNCTION complex_abs_eq(complex, complex)
RETURNS bool
AS 'PGROOT/tutorial/obj/complex.so'
LANGUAGE 'c';
有幾個很重要的問題要在這里指出.
首先,請注意定義了用于 complex 的小于,小于或等于,等于,大于或等于和大于操作符.我們可以只有一個命名了的操作符名,(比如 =)并把類型 complex 做為其兩個操作數.這種情況下我們沒有其它用于 complex 的 = 操作符,但是如果我們要制作一個實用的數據類型,我們可能需要 = 做為用于復數的通用等于操作的操作符.這種情況,我們可能需要使用一些其他操作符名稱來命名 complex_abs_eq.
第二,盡管 Postgres 可以處理同名操作符,只要它們的輸入數據類型不同.C 只能處理一個具有給出名稱的全局過程.因此我們不能把 C 函數命名為象 abs_eq 這樣簡單的名字.通常在 C 函數名里面包含數據類型名稱是一個好習慣,這樣就不會和用于其它數據類型的函數沖突.
第三,我們可以制作函數 abs_eq 的 Postgres 名稱,依靠 Postgres 通過輸入數據類型來與任何其他同名 Postgres 函數區分開.為了令例子簡單,我們做的函數在 C 層次和 Postgres 層次都有相同的名稱,
最后,請注意這些操作符函數返回布爾值.訪問模式依靠這個特性.(令一方面,支持函數返回特定的訪問模式的希望值--在這種情況下是一個符號整數.)文件里最終的過程是我們在討論表 pg_am 里amsupport 字段時提到過的"支持過程".我們稍后將用到這個東西.目前我們暫時忽略它.
現在定義使用它們的操作符:
CREATE OPERATOR = (
leftarg = complex, rightarg = complex,
procedure = complex_abs_eq,
restrict = eqsel, join = eqjoinsel
)
這里的重要問題是過程名稱(就是上面定義的 C 函數)和這個關系和聯合選擇性函數.你應該只使用例子里(參閱 complex.source)的選擇性函數.請注意還要有這樣的用于小于,等于和大于情況的函數.必須提供這些(函數),否則優化器將無法有效地使用該索引.
下一步是為這些操作符向 pg_amop 關系里面增加條目.要做這些,我們將需要我們剛定義的這些操作符的 oid.我們將從所有操作符中找出接受兩個復數的操作符名稱,并把它們選出來:
SELECT o.oid AS opoid, o.oprname
INTO TABLE complex_ops_tmp
FROM pg_operator o, pg_type t
WHERE o.oprleft = t.oid and o.oprright = t.oid
and t.typname = 'complex';
opoid | oprname
--------+---------
277963 | +
277970 | <
277971 | <=
277972 | =
277973 | >=
277974 | >
(6 rows)
(同樣,一些你的 oid (對象標識)將可以肯定是不同的.)我們感興趣的操作符是那些 oid(對象標識)在 277970 到 277974 之間的.你得到的值將可能不同,你應該用你得到的值代替上面的數值.我們通過一條 select 語句實現這個目的。
現在我們已經準備好用我們新的操作符表更新 pg_amop 表.在整個討論中最重要的是在 pg_amop 里操作符是順序排列的,從小于等于到大于等于.我們用下面方法增加我們需要的實例:
INSERT INTO pg_amop (amopid, amopclaid, amopopr, amopstrategy)
SELECT am.oid, opcl.oid, c.opoid, 1
FROM pg_am am, pg_opclass opcl, complex_ops_tmp c
WHERE amname = 'btree' AND
opcname = 'complex_abs_ops' AND
c.oprname = '<';
然后添加其他操作符,相應地替換上面第三行的 "1" 和最后一行的 "<"。注意順序:"小于" 是 1,"小于或等于" 是 2,"等于" 是 3,"大于或等于" 是 4,而 "大于" 是 5。
下一步是注冊我們前面在討論 pg_am 時描述過的"支持過程".支持過程的 oid(對象標識)存放在表 pg_amproc 里,用訪問模式的 oid(對象標識)和操作符表 oid(對象標識)做關鍵字.首先,我們需要在 Postgres 注冊函數(還記得我們把實現這個過程的C 代碼放在了我們實現操作符過程的文件的底部嗎?):
CREATE FUNCTION complex_abs_cmp(complex, complex)
RETURNS int4
AS 'PGROOT/tutorial/obj/complex.so'
LANGUAGE 'c';
SELECT oid, proname FROM pg_proc
WHERE proname = 'complex_abs_cmp';
oid | proname
--------+-----------------
277997 | complex_abs_cmp
(1 row)
(同樣,你的 oid (對象標識)的數字將可能.)我們可以用下面方法增加新的記錄:
INSERT INTO pg_amproc (amid, amopclaid, amproc, amprocnum)
SELECT a.oid, b.oid, c.oid, 1
FROM pg_am a, pg_opclass b, pg_proc c
WHERE a.amname = 'btree' AND
b.opcname = 'complex_abs_ops' AND
c.proname = 'complex_abs_cmp';
這樣我們就完成了!(烏拉.)現在我們可以在一個復數列上創建和使用 btree 索引了.
--------------------------------------------------------------------------------