Общие замечания по программированию на С под постгрес (Фукции должны использовать интерфейс версии 1, версия 0 deprecated, хотя пока и поддерживается):
int32 i = DatumGetInt32(datum);
Datum datum = BoolGetDatum( true );
text *sometext = DatumGetTextP( datum );
SOMETYPE *ptr = (SOMETYPE*)DatumGetPointer(datum);
Datum datum = PointerGetDatum( ptr );
SOMETYPE *ptr = (SOMETYPE*)PG_DETOAST_DATUM( DatumGetPointer(datum) );
VARSIZE( ptr ) - размер в байтах
VARDATA( ptr ) - возвращает указатель на данные после поля длины.
typedef struct {
int32 length;
char data[1];
} FOO;
FOO *foo = f();
то f->length == VARSIZE(f) и f->data == (char*)VARDATA(f) всегда. Заметим,
что длина поля не может превышать 1Gb. Два бита в поле длина используются постгресом в своих целях.
PG_FUNCTION_INFO_V1(function);
Datum function(PG_FUNCTION_ARGS);
Datum
function(PG_FUNCTION_ARGS) {
/* целое число, передается по значению */
int32 i = PG_GETARG_INT32(0);
/* указатель на прямоугольник */
BOX *b = PG_GETARG_BOX_P(1);
/* указатель на текст */
text *t = PG_GETARG_TEXT_P(2);
/* пользовательский тип с переменной длиной */
FOO *f = (FOO*)DatumGetPointer(PG_DETOAST_DATUM(PG_GETARG_DATUM(3));
....
/*
* после того, как работа произведена, не плохо бы очистить память
* "потостенных" значений, Аргументы:
* первый - имя переменной-параметра ф-ции,
* второй - порядковый номер
*/
PG_FREE_IF_COPY(t,2);
PG_FREE_IF_COPY(f,3);
PG_RETURN_INT32( i );
}
GiST предоставляет разработчикам новых типов данных основные методы для работы с ними: SEARCH, INSERT, DELETE. Управлять этими методами можно с помощью 7 интерфейсных функций, которые разработчик должен специфицировать.
Большинство функций интерфейса работают с ключами, передаваемыми в следующей структуре:
typedef struct GISTENTRY
{
Datum key; /* собственно ключ */
Relation rel; /* индекс */
Page page; /* страница индекса */
OffsetNumber offset; /* положение в индексе */
int bytes; /* длина ключа в байтах, может быть равной -1 */
bool leafkey; /* признак, указывающий, что в key находится не
ключ, а значение из таблицы */
} GISTENTRY;
Как правило, для обычных реализаций представляют интерес поля key и leafkey.
Общие замечания:
Для упрощения описания в объявлениях фукций используется псевдосинтаксис, реальные определения должны соответствовать правилам, приведенным выше. В последующей секции рассматривается пример построения.
GISTENTRY * compress( GISTENTRY * in )
GISTENTRY * decompress( GISTENTRY * in )
bool equal( Datum a, Datum b) float * penalty( GISTENTRY *origentry, GISTENTRY *newentry, float *result) Datum union(GistEntryVector *entryvec, int *size)
typedef struct
{
int32 n; /* количество элементов в поле vector*/
GISTENTRY vector[1];
} GistEntryVector;
Массив никогда не содержит NULL элементов.
bool consistent( GISTENTRY *entry, Datum query, StrategyNumber strategy )
Тестирует ключ (entry->key) на соответствие запроса (query) операцией с номером
strategy и возвращает TRUE в случае соответствия, иначе FALSE.
select
pg_amop.amopstrategy,
pg_operator.oprname,
pg_amop.amopreqcheck
from
pg_type,
pg_operator,
pg_opclass,
pg_am,
pg_amop
where
pg_type.typname = 'box' and
pg_am.amname = 'gist' and
pg_opclass.opcname = 'box_ops' and
pg_am.oid=pg_opclass.opcamid and
pg_opclass.oid = pg_amop.amopclaid and
pg_opclass.opcintype = pg_type.oid and
pg_amop.amopopr = pg_operator.oid;
Соответственно, при внесении нового opclass и/или операций надо
позаботиться об обновлении системных таблиц.
GIST_SPLITVEC * split(GistEntryVector *entryvec, GIST_SPLITVEC *v)
typedef struct GIST_SPLITVEC
{
/* Значения для левой страницы (массива) */
OffsetNumber *spl_left; /* массив номеров ключей из entryvec*/
int spl_nleft; /* количество ключей в spl_left */
Datum spl_ldatum; /* объединяющий ключ */
/* Аналогичные значения для правой страницы (массива) */
OffsetNumber *spl_right;
int spl_nright;
Datum spl_rdatum;
...
} GIST_SPLITVEC;
Структура содержит бОльшее количество полей, чем указано здесь, но
остальные поля не должны ею трогаться.
v->spl_left & v->spl_right должны аллоцироваться(palloc) самостоятельно, при возврате они должны содержать номера элементов массива entryvec (один номер НЕ может содержаться в spl_left и spl_right одновременно). Значения в массиве entryvec начинаются с 1, а не с 0!! Также функция обязана сформировать spl_ldatum & spl_rdatum (объединяющие ключи для левого и правого массива).
CREATE OPERATOR CLASS gist_box_ops
DEFAULT FOR TYPE box USING gist AS
OPERATOR 1 << ,
OPERATOR 2 &< ,
OPERATOR 3 &&,
OPERATOR 4 &> ,
OPERATOR 5 >> ,
OPERATOR 6 ~= ,
OPERATOR 7 ~ ,
OPERATOR 8 @ ,
FUNCTION 1 gbox_consistent (internal, box, int4),
FUNCTION 2 gbox_union (internal, internal),
FUNCTION 3 gbox_compress (internal),
FUNCTION 4 rtree_decompress (internal),
FUNCTION 5 gbox_penalty (internal, internal, internal),
FUNCTION 6 gbox_picksplit (internal, internal),
FUNCTION 7 gbox_same (box, box, internal);
замечания
compress, decompress
Datum
gist_box_compress(PG_FUNCTION_ARGS) {
PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}
Datum
gist_box_decompress(PG_FUNCTION_ARGS) {
PG_RETURN_POINTER(PG_GETARG_POINTER(0));
}
equal
Datum
gist_box_same(PG_FUNCTION_ARGS) {
BOX *b1 = PG_GETARG_BOX_P(0);
BOX *b2 = PG_GETARG_BOX_P(1);
bool *result = (bool *) PG_GETARG_POINTER(2);
if (b1 && b2)
*result = DatumGetBool(DirectFunctionCall2(box_same,
PointerGetDatum(b1),
PointerGetDatum(b2)));
else
*result = (b1 == NULL && b2 == NULL) ? TRUE : FALSE;
PG_RETURN_POINTER(result);
}
penalty
static double
size_box(Datum dbox) { /* Выисление площади прямоугольника */
BOX *box = DatumGetBoxP(dbox);
if (box == NULL || box->high.x <= box->low.x || box->high.y <= box->low.y)
return 0.0;
return (box->high.x - box->low.x) * (box->high.y - box->low.y);
}
Datum
gist_box_penalty(PG_FUNCTION_ARGS) {
/* исходный прямоугольник */
GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
/* добавляемый прямоугольник */
GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
float *result = (float *) PG_GETARG_POINTER(2);
Datum ud;
/* получаем объединяющий прямоугольник */
ud = DirectFunctionCall2(rt_box_union, origentry->key, newentry->key);
/* вычитаем площадь исходниго из полученного прямоугольника */
*result = (float) (size_box(ud) - size_box(origentry->key));
PG_RETURN_POINTER(result);
}
union
Datum
gist_box_union(PG_FUNCTION_ARGS) {
GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
int *sizep = (int *) PG_GETARG_POINTER(1);
int numranges,
i;
BOX *cur,
*pageunion;
numranges = entryvec->n;
/* возвращаемое значение должно быть palloc'ировано! */
pageunion = (BOX *) palloc(sizeof(BOX));
/* инициация объединяющего прямоугольника первым прямоугольником */
cur = DatumGetBoxP(entryvec->vector[0].key);
memcpy((void *) pageunion, (void *) cur, sizeof(BOX));
for (i = 1; i < numranges; i++)
{
cur = DatumGetBoxP(entryvec->vector[i].key);
if (pageunion->high.x < cur->high.x)
pageunion->high.x = cur->high.x;
if (pageunion->low.x > cur->low.x)
pageunion->low.x = cur->low.x;
if (pageunion->high.y < cur->high.y)
pageunion->high.y = cur->high.y;
if (pageunion->low.y > cur->low.y)
pageunion->low.y = cur->low.y;
}
/* размер возвращаемого значения в байтах */
*sizep = sizeof(BOX);
PG_RETURN_POINTER(pageunion);
}
consistent
Datum
gist_box_consistent(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
BOX *query = PG_GETARG_BOX_P(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
if (DatumGetBoxP(entry->key) == NULL || query == NULL)
PG_RETURN_BOOL(FALSE);
/* если значение находится на концевой странице, то выполняется точное сравнение,
на внутренней мы должны вернуть true если значения на последующих
страницах (детях) МОГУТ удовлетворять условию */
if (GIST_LEAF(entry))
PG_RETURN_BOOL(gist_box_leaf_consistent(DatumGetBoxP(entry->key),
query,
strategy));
else
PG_RETURN_BOOL(rtree_internal_consistent(DatumGetBoxP(entry->key),
query,
strategy));
}
Реальные функции gist_box_leaf_consistent и rtree_internal_consistent довольно объемны, ограничимся их вариантами толька для поиска на совпадение.
static bool
gist_box_leaf_consistent(BOX *key, BOX *query, StrategyNumber strategy) {
bool retval = FALSE;
switch (strategy) {
case RTSameStrategyNumber:
retval = DatumGetBool(DirectFunctionCall2(box_same,
PointerGetDatum(key),
PointerGetDatum(query)));
break;
default:
elog(NOTICE,"Unsupported StrategyNumber %d", strategy);
}
return retval;
}
static bool
rtree_internal_consistent(BOX *key, BOX *query, StrategyNumber strategy) {
bool retval=FALSE;
switch (strategy) {
case RTSameStrategyNumber:
retval = DatumGetBool(DirectFunctionCall2(box_contain,
PointerGetDatum(key),
PointerGetDatum(query)));
break;
default:
elog(NOTICE,"Unsupported StrategyNumber %d", strategy);
}
return retval;
}
Обратите внимание, что в функции gist_box_leaf_consistent поисковый прямоугольник тестируется на полное совпадение с испытуемым значением, а в rtree_internal_consistent поисковый прямоугольник должен полностью содержаться в испытуемом значении. Очевидно, что если поисковый прямоугольник не содержиться, то совпадающих с ним прямоугольников в страница-наследниках просто не может быть.
picksplit
Datum
gist_box_picksplit(PG_FUNCTION_ARGS) {
GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
OffsetNumber i, maxoff = entryvec->n - 1;
int nbytes;
nbytes = (maxoff + 2) * sizeof(OffsetNumber);
v->spl_left = (OffsetNumber *) palloc(nbytes);
v->spl_right = (OffsetNumber *) palloc(nbytes);
v->spl_ldatum = PointerGetDatum( palloc( sizeof(BOX) ) );
v->spl_rdatum = PointerGetDatum( palloc( sizeof(BOX) ) );
v->spl_nleft = 0;
v->spl_nright = 0;
for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) {
BOX *pageunion; /* указатель на объединяющий прямоугольник для страницы */
if ( i%2 == 0 ) {
v->spl_left[ v->spl_nleft ] = i;
v->spl_nleft++;
pageunion = DatumGetBoxP( v->spl_ldatum );
} else {
v->spl_right[ v->spl_nright ] = i;
v->spl_nright++;
pageunion = DatumGetBoxP( v->spl_rdatum );
}
if ( i<=OffsetNumberNext( FirstOffsetNumber ) ) {
/* первоначальная инициализация объединяющего прямоугольника */
memcpy( pageunion, DatumGetBoxP(entryvec->vector[i].key), sizeof(BOX) );
} else {
BOX *curbox = DatumGetBoxP(entryvec->vector[i].key);
if (pageunion->high.x < curbox->high.x)
pageunion->high.x = curbox->high.x;
if (pageunion->low.x > curbox->low.x)
pageunion->low.x = curbox->low.x;
if (pageunion->high.y < curbox->high.y)
pageunion->high.y = curbox->high.y;
if (pageunion->low.y > curbox->low.y)
pageunion->low.y = curbox->low.y;
}
}
PG_RETURN_POINTER(v);
}
Хранимыми ключами для полигонов являются те же прямоугольники. При внесении в индекс полигоны хранимый прямоугольник берется просто как описывающий прямоугольник. Таким образом, все отличия от вариaнта R-Tree для прямоугольников заключаются всего в двух функциях. Такой индекс называют with lossy compression. Не данные, которые он выдает при поиске необходимо сравнить с исходным значением, хранящимся в таблице.
compress
Datum
gist_poly_compress(PG_FUNCTION_ARGS) {
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
GISTENTRY *retval;
if (entry->leafkey) {
/* значение entry->key содержит полигон, т.е.
это новое значение для вставки в индекс */
retval = palloc(sizeof(GISTENTRY));
if (DatumGetPointer(entry->key) != NULL) {
POLYGON *in = DatumGetPolygonP(entry->key);
BOX *r;
r = (BOX *) palloc(sizeof(BOX));
memcpy((void *) r, (void *) &(in->boundbox), sizeof(BOX));
gistentryinit(*retval, PointerGetDatum(r),
entry->rel, entry->page,
entry->offset, sizeof(BOX), FALSE);
} else {
gistentryinit(*retval, (Datum) 0,
entry->rel, entry->page,
entry->offset, 0, FALSE);
}
} else
retval = entry;
PG_RETURN_POINTER(retval);
}
consistent
Datum
gist_poly_consistent(PG_FUNCTION_ARGS) {
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
POLYGON *query = PG_GETARG_POLYGON_P(1);
StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
bool result;
if (DatumGetBoxP(entry->key) == NULL || query == NULL)
PG_RETURN_BOOL(FALSE);
result = rtree_internal_consistent(DatumGetBoxP(entry->key),
&(query->boundbox), strategy);
PG_FREE_IF_COPY(query, 1);
PG_RETURN_BOOL(result);
}