diff --git a/doc/src/sgml/spgist.sgml b/doc/src/sgml/spgist.sgml new file mode 100644 index cd4a8d0..dcdc297 *** a/doc/src/sgml/spgist.sgml --- b/doc/src/sgml/spgist.sgml *************** *** 240,259 **** There are five user-defined methods that an index operator class for ! SP-GiST must provide. All five follow the convention ! of accepting two internal arguments, the first of which is a ! pointer to a C struct containing input values for the support method, ! while the second argument is a pointer to a C struct where output values ! must be placed. Four of the methods just return void, since ! all their results appear in the output struct; but leaf_consistent additionally returns a boolean result. The methods must not modify any fields of their input structs. In all cases, the output struct is initialized to zeroes before calling the ! user-defined method. ! The five user-defined methods are: --- 240,260 ---- There are five user-defined methods that an index operator class for ! SP-GiST must provide and one optional. All five mandatory ! methos follow the convention of accepting two internal arguments, ! the first of which is a pointer to a C struct containing input values for ! the support method, while the second argument is a pointer to a C struct ! where output values must be placed. Four of the methods just return ! void, since all their results appear in the output struct; but leaf_consistent additionally returns a boolean result. The methods must not modify any fields of their input structs. In all cases, the output struct is initialized to zeroes before calling the ! user-defined method. Optional method compress accepts ! datum to be indexed and returns values which actually will be indexed. ! The five mandatory user-defined methods are: *************** typedef struct spgConfigOut *** 283,288 **** --- 284,290 ---- { Oid prefixType; /* Data type of inner-tuple prefixes */ Oid labelType; /* Data type of inner-tuple node labels */ + Oid leafType; /* Data type of leaf */ bool canReturnData; /* Opclass can reconstruct original data */ bool longValuesOK; /* Opclass can cope with values > 1 page */ } spgConfigOut; *************** typedef struct spgConfigOut *** 303,309 **** longValuesOK should be set true only when the attType is of variable length and the operator class is capable of segmenting long values by repeated suffixing ! (see ). --- 305,319 ---- longValuesOK should be set true only when the attType is of variable length and the operator class is capable of segmenting long values by repeated suffixing ! (see ). leafType ! usually has the same value as attType but if ! it's different then optional method compress ! should be provided. Method compress is responsible ! for transformation from attType to ! leafType. In this case all other function should ! accept leafType values. Note: both consistent ! functions will get scankeys unchanged, without ! compress transformation. *************** typedef struct spgInnerConsistentOut *** 624,630 **** reconstructedValue is the value reconstructed for the parent tuple; it is (Datum) 0 at the root level or if the inner_consistent function did not provide a value at the ! parent level. traversalValue is a pointer to any traverse data passed down from the previous call of inner_consistent on the parent index tuple, or NULL at the root level. --- 634,641 ---- reconstructedValue is the value reconstructed for the parent tuple; it is (Datum) 0 at the root level or if the inner_consistent function did not provide a value at the ! parent level. reconstructedValue should be always a ! spgConfigOut.leafType type. traversalValue is a pointer to any traverse data passed down from the previous call of inner_consistent on the parent index tuple, or NULL at the root level. *************** typedef struct spgLeafConsistentOut *** 730,736 **** reconstructedValue is the value reconstructed for the parent tuple; it is (Datum) 0 at the root level or if the inner_consistent function did not provide a value at the ! parent level. traversalValue is a pointer to any traverse data passed down from the previous call of inner_consistent on the parent index tuple, or NULL at the root level. --- 741,748 ---- reconstructedValue is the value reconstructed for the parent tuple; it is (Datum) 0 at the root level or if the inner_consistent function did not provide a value at the ! parent level. reconstructedValue should be always a ! spgConfigOut.leafType type. traversalValue is a pointer to any traverse data passed down from the previous call of inner_consistent on the parent index tuple, or NULL at the root level. *************** typedef struct spgLeafConsistentOut *** 757,762 **** --- 769,792 ---- + + The optional user-defined method is: + + + + + Datum compress(Datum in) + + + Converts the data item into a format suitable for physical storage in + an index page. It accepts spgConfigIn.attType + value and return spgConfigOut.leafType + value. Output value should not be toasted. + + + + + All the SP-GiST support methods are normally called in a short-lived memory context; that is, CurrentMemoryContext will be reset diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c new file mode 100644 index b0702a7..68c3f45 *** a/src/backend/access/spgist/spgdoinsert.c --- b/src/backend/access/spgist/spgdoinsert.c *************** spgdoinsert(Relation index, SpGistState *** 1899,1919 **** FmgrInfo *procinfo = NULL; /* ! * Look up FmgrInfo of the user-defined choose function once, to save ! * cycles in the loop below. */ if (!isnull) ! procinfo = index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC); /* ! * Since we don't use index_form_tuple in this AM, we have to make sure ! * value to be inserted is not toasted; FormIndexDatum doesn't guarantee ! * that. */ ! if (!isnull && state->attType.attlen == -1) ! datum = PointerGetDatum(PG_DETOAST_DATUM(datum)); ! ! leafDatum = datum; /* * Compute space needed for a leaf tuple containing the given datum. --- 1899,1939 ---- FmgrInfo *procinfo = NULL; /* ! * Prepare the leaf datum to insert. ! * ! * If there is an optional "compress" method, call it to form the leaf ! * datum from the input datum. Otherwise we will store the input datum as ! * is. (We have to detoast it, though. We assume the "compress" method to ! * return an untoasted value.) */ if (!isnull) ! { ! if (OidIsValid(index_getprocid(index, 1, SPGIST_COMPRESS_PROC))) ! { ! procinfo = index_getprocinfo(index, 1, SPGIST_COMPRESS_PROC); ! leafDatum = FunctionCall1Coll(procinfo, ! index->rd_indcollation[0], ! datum); ! } ! else ! { ! Assert(state->attLeafType.type == state->attType.type); ! ! if (state->attType.attlen == -1) ! leafDatum = PointerGetDatum(PG_DETOAST_DATUM(datum)); ! else ! leafDatum = datum; ! } ! } ! else ! leafDatum = (Datum) 0; /* ! * Look up FmgrInfo of the user-defined choose function once, to save ! * cycles in the loop below. */ ! if (!isnull) ! procinfo = index_getprocinfo(index, 1, SPGIST_CHOOSE_PROC); /* * Compute space needed for a leaf tuple containing the given datum. *************** spgdoinsert(Relation index, SpGistState *** 1923,1929 **** */ if (!isnull) leafSize = SGLTHDRSZ + sizeof(ItemIdData) + ! SpGistGetTypeSize(&state->attType, leafDatum); else leafSize = SGDTSIZE + sizeof(ItemIdData); --- 1943,1949 ---- */ if (!isnull) leafSize = SGLTHDRSZ + sizeof(ItemIdData) + ! SpGistGetTypeSize(&state->attLeafType, leafDatum); else leafSize = SGDTSIZE + sizeof(ItemIdData); *************** spgdoinsert(Relation index, SpGistState *** 2138,2144 **** { leafDatum = out.result.matchNode.restDatum; leafSize = SGLTHDRSZ + sizeof(ItemIdData) + ! SpGistGetTypeSize(&state->attType, leafDatum); } /* --- 2158,2164 ---- { leafDatum = out.result.matchNode.restDatum; leafSize = SGLTHDRSZ + sizeof(ItemIdData) + ! SpGistGetTypeSize(&state->attLeafType, leafDatum); } /* diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c new file mode 100644 index 22f64b0..8a1311d *** a/src/backend/access/spgist/spgutils.c --- b/src/backend/access/spgist/spgutils.c *************** spgGetCache(Relation index) *** 124,130 **** PointerGetDatum(&cache->config)); /* Get the information we need about each relevant datatype */ ! fillTypeDesc(&cache->attType, atttype); fillTypeDesc(&cache->attPrefixType, cache->config.prefixType); fillTypeDesc(&cache->attLabelType, cache->config.labelType); --- 124,146 ---- PointerGetDatum(&cache->config)); /* Get the information we need about each relevant datatype */ ! if (OidIsValid(cache->config.leafType) && ! cache->config.leafType != atttype) ! { ! if (!OidIsValid(index_getprocid(index, 1, SPGIST_COMPRESS_PROC))) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_PARAMETER_VALUE), ! errmsg("compress method must not defined when leaf type is different from input type"))); ! ! fillTypeDesc(&cache->attType, atttype); ! fillTypeDesc(&cache->attLeafType, cache->config.leafType); ! } ! else ! { ! fillTypeDesc(&cache->attType, atttype); ! cache->attLeafType = cache->attType; ! } ! fillTypeDesc(&cache->attPrefixType, cache->config.prefixType); fillTypeDesc(&cache->attLabelType, cache->config.labelType); *************** initSpGistState(SpGistState *state, Rela *** 164,169 **** --- 180,186 ---- state->config = cache->config; state->attType = cache->attType; + state->attLeafType = cache->attLeafType; state->attPrefixType = cache->attPrefixType; state->attLabelType = cache->attLabelType; *************** spgFormLeafTuple(SpGistState *state, Ite *** 598,604 **** /* compute space needed (note result is already maxaligned) */ size = SGLTHDRSZ; if (!isnull) ! size += SpGistGetTypeSize(&state->attType, datum); /* * Ensure that we can replace the tuple with a dead tuple later. This --- 615,621 ---- /* compute space needed (note result is already maxaligned) */ size = SGLTHDRSZ; if (!isnull) ! size += SpGistGetTypeSize(&state->attLeafType, datum); /* * Ensure that we can replace the tuple with a dead tuple later. This *************** spgFormLeafTuple(SpGistState *state, Ite *** 614,620 **** tup->nextOffset = InvalidOffsetNumber; tup->heapPtr = *heapPtr; if (!isnull) ! memcpyDatum(SGLTDATAPTR(tup), &state->attType, datum); return tup; } --- 631,637 ---- tup->nextOffset = InvalidOffsetNumber; tup->heapPtr = *heapPtr; if (!isnull) ! memcpyDatum(SGLTDATAPTR(tup), &state->attLeafType, datum); return tup; } diff --git a/src/backend/access/spgist/spgvalidate.c b/src/backend/access/spgist/spgvalidate.c new file mode 100644 index 157cf2a..514da47 *** a/src/backend/access/spgist/spgvalidate.c --- b/src/backend/access/spgist/spgvalidate.c *************** spgvalidate(Oid opclassoid) *** 52,57 **** --- 52,61 ---- OpFamilyOpFuncGroup *opclassgroup; int i; ListCell *lc; + spgConfigIn configIn; + spgConfigOut configOut; + Oid configOutLefttype = InvalidOid; + Oid configOutRighttype = InvalidOid; /* Fetch opclass information */ classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); *************** spgvalidate(Oid opclassoid) *** 100,105 **** --- 104,118 ---- switch (procform->amprocnum) { case SPGIST_CONFIG_PROC: + ok = check_amproc_signature(procform->amproc, VOIDOID, true, + 2, 2, INTERNALOID, INTERNALOID); + configIn.attType = procform->amproclefttype; + OidFunctionCall2(procform->amproc, + PointerGetDatum(&configIn), + PointerGetDatum(&configOut)); + configOutLefttype = procform->amproclefttype; + configOutRighttype = procform->amprocrighttype; + break; case SPGIST_CHOOSE_PROC: case SPGIST_PICKSPLIT_PROC: case SPGIST_INNER_CONSISTENT_PROC: *************** spgvalidate(Oid opclassoid) *** 110,115 **** --- 123,137 ---- ok = check_amproc_signature(procform->amproc, BOOLOID, true, 2, 2, INTERNALOID, INTERNALOID); break; + case SPGIST_COMPRESS_PROC: + if (configOutLefttype != procform->amproclefttype || + configOutRighttype != procform->amprocrighttype) + ok = false; + else + ok = check_amproc_signature(procform->amproc, + configOut.leafType, true, + 1, 1, procform->amproclefttype); + break; default: ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), *************** spgvalidate(Oid opclassoid) *** 212,218 **** if (thisgroup->lefttype != thisgroup->righttype) continue; ! for (i = 1; i <= SPGISTNProc; i++) { if ((thisgroup->functionset & (((uint64) 1) << i)) != 0) continue; /* got it */ --- 234,240 ---- if (thisgroup->lefttype != thisgroup->righttype) continue; ! for (i = 1; i <= SPGISTNRequiredProc; i++) { if ((thisgroup->functionset & (((uint64) 1) << i)) != 0) continue; /* got it */ diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h new file mode 100644 index d1bc396..a477278 *** a/src/include/access/spgist.h --- b/src/include/access/spgist.h *************** *** 30,36 **** #define SPGIST_PICKSPLIT_PROC 3 #define SPGIST_INNER_CONSISTENT_PROC 4 #define SPGIST_LEAF_CONSISTENT_PROC 5 ! #define SPGISTNProc 5 /* * Argument structs for spg_config method --- 30,38 ---- #define SPGIST_PICKSPLIT_PROC 3 #define SPGIST_INNER_CONSISTENT_PROC 4 #define SPGIST_LEAF_CONSISTENT_PROC 5 ! #define SPGIST_COMPRESS_PROC 6 ! #define SPGISTNRequiredProc 5 ! #define SPGISTNProc 6 /* * Argument structs for spg_config method *************** typedef struct spgConfigOut *** 44,49 **** --- 46,52 ---- { Oid prefixType; /* Data type of inner-tuple prefixes */ Oid labelType; /* Data type of inner-tuple node labels */ + Oid leafType; /* Data type of leaf (type of SPGIST_COMPRESS_PROC output) */ bool canReturnData; /* Opclass can reconstruct original data */ bool longValuesOK; /* Opclass can cope with values > 1 page */ } spgConfigOut; diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h new file mode 100644 index 1c4b321..69dc2ba *** a/src/include/access/spgist_private.h --- b/src/include/access/spgist_private.h *************** typedef struct SpGistState *** 119,125 **** { spgConfigOut config; /* filled in by opclass config method */ ! SpGistTypeDesc attType; /* type of input data and leaf values */ SpGistTypeDesc attPrefixType; /* type of inner-tuple prefix values */ SpGistTypeDesc attLabelType; /* type of node label values */ --- 119,126 ---- { spgConfigOut config; /* filled in by opclass config method */ ! SpGistTypeDesc attType; /* type of values to be indexed/restored */ ! SpGistTypeDesc attLeafType; /* type of leaf values */ SpGistTypeDesc attPrefixType; /* type of inner-tuple prefix values */ SpGistTypeDesc attLabelType; /* type of node label values */ *************** typedef struct SpGistCache *** 178,184 **** { spgConfigOut config; /* filled in by opclass config method */ ! SpGistTypeDesc attType; /* type of input data and leaf values */ SpGistTypeDesc attPrefixType; /* type of inner-tuple prefix values */ SpGistTypeDesc attLabelType; /* type of node label values */ --- 179,186 ---- { spgConfigOut config; /* filled in by opclass config method */ ! SpGistTypeDesc attType; /* type of values to be indexed/restored */ ! SpGistTypeDesc attLeafType; /* type of leaf values */ SpGistTypeDesc attPrefixType; /* type of inner-tuple prefix values */ SpGistTypeDesc attLabelType; /* type of node label values */ *************** typedef SpGistLeafTupleData *SpGistLeafT *** 300,306 **** #define SGLTHDRSZ MAXALIGN(sizeof(SpGistLeafTupleData)) #define SGLTDATAPTR(x) (((char *) (x)) + SGLTHDRSZ) ! #define SGLTDATUM(x, s) ((s)->attType.attbyval ? \ *(Datum *) SGLTDATAPTR(x) : \ PointerGetDatum(SGLTDATAPTR(x))) --- 302,308 ---- #define SGLTHDRSZ MAXALIGN(sizeof(SpGistLeafTupleData)) #define SGLTDATAPTR(x) (((char *) (x)) + SGLTHDRSZ) ! #define SGLTDATUM(x, s) ((s)->attLeafType.attbyval ? \ *(Datum *) SGLTDATAPTR(x) : \ PointerGetDatum(SGLTDATAPTR(x)))