Documentation: GiST extension implementation
Hi,
The following documentation page explains the GiST API to extensions authors:
http://www.postgresql.org/docs/current/static/gist-implementation.htm
I think we should be a little more verbose, and at least explains some more
the big picture: same/consistent/union are responsible for correctness of the
index while penalty and picksplit are responsible for performances of it,
which leaves compress/decompress, to use when leaf/nodes are not the same
datatype.
This leaf/node construct is explained in the last paragraph of following page,
but can exists directly into the C module too:
http://www.postgresql.org/docs/current/static/xindex.html
The consistent and union should get a lot of attention, and when exactly do
your operators need RECHECK is still unclear to me. It's hard to give precise
advices about consistent/union in a generic way, but I've been given the
following general rule (thanks RhodiumToad):
(z is consistent with x) implies (z is consistent with union(x,y))
What's unclear too is memory management: when to palloc() and when to reuse
arguments given by -core GiST support functions. I know it was a game of trial
and error to get it right, and while I know it's working now, I'd be in a bad
position to explain how and why. Maybe reading the source code is what to do
here, but a detailed API expectancies page in the documentation wouldn't
hurt...
Regards,
--
dim
I didn't propose a real doc patch mainly because english isn't my native
language, and while you'll have to reword the content...
On Wednesday 29 April 2009 16:43:44 Dimitri Fontaine wrote:
The following documentation page explains the GiST API to extensions
authors:
I think we should be a little more verbose,
I didn't propose a real doc patch mainly because english isn't my native
language, and while you'll have to reword the content...
There aren't a lot of people who have the experience to write that
documentation. So if you want to improve it, you will have to write it, or at
least organize the outline. Others can help cleaning it up.
Hi,
Le 4 mai 09 à 14:24, Peter Eisentraut a écrit :
There aren't a lot of people who have the experience to write that
documentation. So if you want to improve it, you will have to write
it, or at
least organize the outline. Others can help cleaning it up.
For the record, here's the current version of the documentation patch,
which still needs those items to be worked out:
- patch format
- proper English review
- consistent signature update for 8.4 (recheck arg)
- compress/decompress non void example
- multi column support in picksplit
I intend to try and work out those points, but it's all about areas I
don't (yet) know about. Of course I won't complete the second point
myself.
Regards,
--
dim
Attachments:
gist.sgml.patchapplication/octet-stream; name=gist.sgml.patch; x-unix-mode=0644Download
*** ../postgresql/doc/src/sgml/gist.sgml Thu Jan 29 23:50:18 2009
--- gist.sgml Wed May 20 17:38:00 2009
***************
*** 92,98 ****
<para>
There are seven methods that an index operator class for
! <acronym>GiST</acronym> must provide:
</para>
<variablelist>
--- 92,112 ----
<para>
There are seven methods that an index operator class for
! <acronym>GiST</acronym> must provide. Correctness of the index is ensured
! by proper implementation of the <term>same</>, <term>consistent</> and
! <term>union</> methods, while efficiency (speed) of the index will depend
! on the <term>penalty</> and <term>picksplit</> methods.
! </para>
!
! <para>
! The last two are <term>compress</> and <term>decompress</>, they allow to
! have internal tree data of a different type than the data indexed. The
! leaves are to be of the indexed data type type while the other tree nodes
! can be of any C struct (you still have to follow
! <productname>PostgreSQL</> rules here, see about <term>varlena</> for
! variable sized data). If the tree nodes internal data type exists at the
! SQL level, the <literal>STORAGE</> option of the <command>CREATE
! OPERATORS CLASS</> can be used.
</para>
<variablelist>
***************
*** 108,113 ****
--- 122,172 ----
the predicate implies the query (<literal>recheck</> = false) or
not (<literal>recheck</> = true).
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal)
+ RETURNS bool
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_consistent(PG_FUNCTION_ARGS);
+
+ PG_FUNCTION_INFO_V1(my_consistent);
+ Datum
+ my_consistent(PG_FUNCTION_ARGS)
+ {
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ data_type *query = PG_GETARG_DATA_TYPE_P(1);
+ StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+ data_type *key = DatumGetDataType(entry->key);
+ bool retval;
+
+ /*
+ * determine retval value as a function of strategy, key and query.
+ */
+ PG_RETURN_BOOL(retval);
+ }
+ </programlisting>
+
+ Here, <term>key</> is an element in the index and <term>query</> the
+ value being looked up in the index (which can be a <term>SELECT</> or
+ a <term>DML</>. The <term>StrategyNumber</> you get will be set to one
+ of the ones you declare in the corresponding <command>CREATE OPERATOR
+ CLASS</> command.
+ </para>
+
+ <para>
+ Of course the term <term>DATE_TYPE</> in the C code would have to
+ get replaced in a way to refer existing macros, such as
+ <literal>PG_GETARG_TEXT_P</> and <literal>DatumGetTextP</>.
+ </para>
</listitem>
</varlistentry>
***************
*** 119,124 ****
--- 178,246 ----
entries, this function generates a new predicate that is true for all
the entries.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_union(internal, internal)
+ RETURNS text
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_union(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_union);
+ Datum
+ my_union(PG_FUNCTION_ARGS)
+ {
+ GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
+ GISTENTRY *ent = entryvec->vector;
+ data_type *out, *tmp, *old;
+ int numranges, i = 0;
+
+ numranges = entryvec->n;
+ tmp = DatumGetDataType(ent[0].key);
+ out = tmp;
+
+ if( numranges == 1 )
+ {
+ out = data_type_deep_copy(tmp);
+
+ PG_RETURN_DATA_TYPE_P(out);
+ }
+
+ for (i = 1; i < numranges; i++)
+ {
+ old = out;
+ tmp = DatumGetDataType(ent[i].key);
+ out = my_union_implementation(out, tmp);
+ }
+
+ PG_RETURN_DATA_TYPE_P(out);
+ }
+ </programlisting>
+
+ <para>
+ As you can see, in this skeleton we're dealing with a data type
+ where <literal>union(X, Y, Z) = union(union(X, Y), Z)</>. It's easy
+ enough to support data types where this is not the case, by
+ implementing the proper union implemantation and usage from the
+ <term>GIST</> support method.
+ </para>
+
+ <para>
+ All your <term>union</> implementation functions should return
+ pointers to newly <literal>palloc()</>ed memory. You can't just
+ return whatever the input is as it's provided in a
+ <literal>MemoryContext</> from where
+ <productname>PostgreSQL</productname> wouldn't be able to store it
+ in the index, should your <term>union</> function be called in a
+ <command>CREATE INDEX</>.
+ </para>
</listitem>
</varlistentry>
***************
*** 129,134 ****
--- 251,284 ----
Converts the data item into a format suitable for physical storage in
an index page.
</para>
+
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_compress(internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_compress(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_compress);
+ Datum
+ my_compress(PG_FUNCTION_ARGS)
+ {
+ PG_RETURN_POINTER(PG_GETARG_POINTER(0));
+ }
+ </programlisting>
+
+ <para>
+ This skeleton is suitable only when you're storing the same data
+ type as the one you're indexing.
+ </para>
</listitem>
</varlistentry>
***************
*** 140,145 ****
--- 290,318 ----
index representation of the data item into a format that can be
manipulated by the database.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_decompress(internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_decompress(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_decompress);
+ Datum
+ my_decompress(PG_FUNCTION_ARGS)
+ {
+ PG_RETURN_POINTER(PG_GETARG_POINTER(0));
+ }
+ </programlisting>
+ </para>
</listitem>
</varlistentry>
***************
*** 151,156 ****
--- 324,368 ----
entry into a particular branch of the tree. items will be inserted
down the path of least <function>penalty</function> in the tree.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C' STRICT;
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_penalty(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_penalty);
+ Datum
+ my_penalty(PG_FUNCTION_ARGS)
+ {
+ GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
+ float *penalty = (float *) PG_GETARG_POINTER(2);
+
+ data_type *orig = DatumGetDataType(origentry->key);
+ data_type *new = DatumGetDataType(newentry->key);
+
+ *penalty = my_penalty_implementation(orig, new);
+ PG_RETURN_POINTER(penalty);
+ }
+
+ </programlisting>
+
+ <para>
+ The <term>penalty</> function is crucial to good performances of the
+ index building and usage. It'll get used at query time to determine
+ which branch to follow when choosing where to add the new entry in
+ the tree. At query time, the more balanced the index, the quicker
+ the lookup.
+ </para>
</listitem>
</varlistentry>
***************
*** 162,167 ****
--- 374,471 ----
the page are to stay on the old page, and which are to move to the new
page.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C' STRICT;
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_picksplit(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_picksplit_jordan);
+ Datum
+ my_picksplit(PG_FUNCTION_ARGS)
+ {
+ GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
+ OffsetNumber maxoff = entryvec->n - 1;
+ GISTENTRY *ent = entryvec->vector;
+ GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
+
+ int i, nbytes;
+ OffsetNumber *left, *right;
+ data_type *tmp_union;
+ data_type *unionL;
+ data_type *unionR;
+
+ GISTENTRY **raw_entryvec;
+
+ maxoff = entryvec->n - 1;
+ nbytes = (maxoff + 1) * sizeof(OffsetNumber);
+
+ v->spl_left = (OffsetNumber *) palloc(nbytes);
+ left = v->spl_left;
+ v->spl_nleft = 0;
+
+ v->spl_right = (OffsetNumber *) palloc(nbytes);
+ right = v->spl_right;
+ v->spl_nright = 0;
+
+ unionL = NULL;
+ unionR = NULL;
+
+ /* Initialize the raw entry vector. */
+ raw_entryvec = (GISTENTRY **) malloc(entryvec->n * sizeof(void *));
+ for (i=FirstOffsetNumber; i <= maxoff; i=OffsetNumberNext(i))
+ raw_entryvec[i] = &(entryvec->vector[i]);
+
+ for (i=FirstOffsetNumber; i <= maxoff; i=OffsetNumberNext(i)) {
+ int real_index = raw_entryvec[i] - entryvec->vector;
+ tmp_union = DatumGetDataType(entryvec->vector[real_index].key);
+ Assert(tmp_union != NULL);
+
+ /*
+ * Choose where to put the index entries and update unionL and unionR accordingly.
+ * Append the entries to either v_spl_left or v_spl_right, and care about the counters.
+ */
+
+ if( my_choice_is_left(unionL, curl, unionR, curr) )
+ {
+ if( unionL == NULL )
+ unionL = tmp_union;
+ else
+ unionL = my_union_implementation(unionL, tmp_union);
+
+ *left = real_index;
+ ++left;
+ ++(v->spl_nleft);
+ }
+ else
+ {
+ /*
+ * Same on the right
+ */
+ }
+ }
+
+ v->spl_ldatum = DataTypeGetDatum(unionL);
+ v->spl_rdatum = DataTypeGetDatum(unionR);
+ PG_RETURN_POINTER(v);
+ }
+ </programlisting>
+
+ <para>
+ The <term>picksplit</> implementation is crucial for optimized index
+ builds. Its implementation, combined with a proper <term>penalty</>
+ one, is where the challenge of implementing a performant
+ <term>GIST</> index lies.
+ </para>
</listitem>
</varlistentry>
***************
*** 171,176 ****
--- 475,512 ----
<para>
Returns true if two entries are identical, false otherwise.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_same(data_type, data_type, internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_same(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_same);
+ Datum
+ my_same(PG_FUNCTION_ARGS)
+ {
+ prefix_range *v1 = PG_GETARG_PREFIX_RANGE_P(0);
+ prefix_range *v2 = PG_GETARG_PREFIX_RANGE_P(1);
+ bool *result = (bool *) PG_GETARG_POINTER(2);
+
+ *result = my_eq(v1, v2);
+ PG_RETURN_POINTER( result );
+ }
+ </programlisting>
+
+ <para>
+ This is straightforward, even the memory place where to handle the
+ boolean return value is pre allocated.
+ </para>
</listitem>
</varlistentry>
Hi,
I've been working some more on this documentation:
Le 20 mai 09 à 18:10, Dimitri Fontaine a écrit :
- consistent signature update for 8.4 (recheck arg)
- compress/decompress non void example
This means the multi-column support part is still missing, as proper
english review too.
But I think it should not stop us to consider the patch either for 8.4
(documentation patch I believe are accepted this late in the game) or
for 8.5, in which case I'll put it on the CommitFest page. Even
without touching the multi-column aspect of GiST custom support, I
think it improves this part of the documentation a lot.
<why care?>
Please consider it: it's not coming from nowhere. All examples are
based on real working code, prefix.c (to be found on pgfoundry, served
more than 70 millions GiST lookups already, is used in several telco
companies) and gistproc.c, and I enjoyed Teodor's and Andrew Gierth's
advices and comments.
</>
Regards,
--
dim
Attachments:
gist.sgml.patchapplication/octet-stream; name=gist.sgml.patch; x-unix-mode=0644Download
*** ../postgresql/doc/src/sgml/gist.sgml Thu Jan 29 23:50:18 2009
--- gist.sgml Fri Jun 5 23:29:55 2009
***************
*** 92,98 ****
<para>
There are seven methods that an index operator class for
! <acronym>GiST</acronym> must provide:
</para>
<variablelist>
--- 92,112 ----
<para>
There are seven methods that an index operator class for
! <acronym>GiST</acronym> must provide. Correctness of the index is ensured
! by proper implementation of the <term>same</>, <term>consistent</> and
! <term>union</> methods, while efficiency (speed) of the index will depend
! on the <term>penalty</> and <term>picksplit</> methods.
! </para>
!
! <para>
! The last two are <term>compress</> and <term>decompress</>, they allow to
! have internal tree data of a different type than the data indexed. The
! leaves are to be of the indexed data type type while the other tree nodes
! can be of any C struct (you still have to follow
! <productname>PostgreSQL</> rules here, see about <term>varlena</> for
! variable sized data). If the tree nodes internal data type exists at the
! SQL level, the <literal>STORAGE</> option of the <command>CREATE
! OPERATORS CLASS</> can be used.
</para>
<variablelist>
***************
*** 108,113 ****
--- 122,195 ----
the predicate implies the query (<literal>recheck</> = false) or
not (<literal>recheck</> = true).
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_consistent(internal, data_type, smallint, oid, internal)
+ RETURNS bool
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_consistent(PG_FUNCTION_ARGS);
+
+ PG_FUNCTION_INFO_V1(my_consistent);
+ Datum
+ my_consistent(PG_FUNCTION_ARGS)
+ {
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ data_type *query = PG_GETARG_DATA_TYPE_P(1);
+ StrategyNumber strategy = (StrategyNumber) PG_GETARG_UINT16(2);
+ /* Oid subtype = PG_GETARG_OID(3); */
+ data_type *key = DatumGetDataType(entry->key);
+ bool *recheck;
+ bool retval;
+
+ Assert( PG_NARGS() == 4 || PG_NARGS() == 5);
+
+ if( PG_NARGS() == 5 ) {
+ recheck = (bool *) PG_GETARG_POINTER(4);
+ *recheck = true;
+ }
+ /*
+ * determine retval value as a function of strategy, key and query.
+ *
+ * Use GIST_LEAF(entry) to know where you're called in the index tree,
+ * which comes handy when supporting the = operator for example (you
+ * could check for non empty union() in non-leaf nodes and equality in
+ * leaf nodes).
+ */
+ PG_RETURN_BOOL(retval);
+ }
+ </programlisting>
+
+ Here, <term>key</> is an element in the index and <term>query</> the
+ value being looked up in the index (which can be a <term>SELECT</> or
+ a <term>DML</>. The <term>StrategyNumber</> you get will be set to one
+ of the ones you declare in the corresponding <command>CREATE OPERATOR
+ CLASS</> command.
+ </para>
+
+ <para>
+ Of course the term <term>DATE_TYPE</> in the C code would have to
+ get replaced in a way to refer existing macros, such as
+ <literal>PG_GETARG_TEXT_P</> and <literal>DatumGetTextP</>.
+ </para>
+
+ <para>
+ The <term>RECHECK</> information used to exists in the operator
+ class definition but is now supported in the <term>consistent</term>
+ support function. The <command>CREATE OPERATOR CLASS</> will do
+ nothing about the specific signature of the <term>consistent</>
+ function, which will get called through the <term>fmgr</> interface:
+ using <term>PG_NARGS()</> is a good way to support older versions of
+ <productname>PostgreSQL</>.
+ </para>
</listitem>
</varlistentry>
***************
*** 119,124 ****
--- 201,269 ----
entries, this function generates a new predicate that is true for all
the entries.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_union(internal, internal)
+ RETURNS text
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_union(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_union);
+ Datum
+ my_union(PG_FUNCTION_ARGS)
+ {
+ GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
+ GISTENTRY *ent = entryvec->vector;
+ data_type *out, *tmp, *old;
+ int numranges, i = 0;
+
+ numranges = entryvec->n;
+ tmp = DatumGetDataType(ent[0].key);
+ out = tmp;
+
+ if( numranges == 1 )
+ {
+ out = data_type_deep_copy(tmp);
+
+ PG_RETURN_DATA_TYPE_P(out);
+ }
+
+ for (i = 1; i < numranges; i++)
+ {
+ old = out;
+ tmp = DatumGetDataType(ent[i].key);
+ out = my_union_implementation(out, tmp);
+ }
+
+ PG_RETURN_DATA_TYPE_P(out);
+ }
+ </programlisting>
+
+ <para>
+ As you can see, in this skeleton we're dealing with a data type
+ where <literal>union(X, Y, Z) = union(union(X, Y), Z)</>. It's easy
+ enough to support data types where this is not the case, by
+ implementing the proper union implemantation and usage from the
+ <term>GIST</> support method.
+ </para>
+
+ <para>
+ All your <term>union</> implementation functions should return
+ pointers to newly <literal>palloc()</>ed memory. You can't just
+ return whatever the input is as it's provided in a
+ <literal>MemoryContext</> from where
+ <productname>PostgreSQL</productname> wouldn't be able to store it
+ in the index, should your <term>union</> function be called in a
+ <command>CREATE INDEX</>.
+ </para>
</listitem>
</varlistentry>
***************
*** 129,134 ****
--- 274,335 ----
Converts the data item into a format suitable for physical storage in
an index page.
</para>
+
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_compress(internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_compress(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_compress);
+ Datum
+ my_compress(PG_FUNCTION_ARGS)
+ {
+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ GISTENTRY *retval;
+
+ if (entry->leafkey)
+ {
+ retval = palloc(sizeof(GISTENTRY));
+ if (DatumGetPointer(entry->key) != NULL)
+ {
+ /*
+ * prepare the compressed representation of the data into some local varlena
+ */
+ compressed_data_type compressed_data = palloc(sizeof(compressed_data_type));
+
+ gistentryinit(*retval, compressed_data,
+ entry->rel, entry->page, entry->offset, FALSE);
+ }
+ }
+ else
+ retval = entry;
+
+ PG_RETURN_POINTER(retval);
+ }
+ </programlisting>
+
+ <para>
+ You have to adapt <term>compressed_data_type</> to the specific type
+ you're converting to in order to compress your leaf nodes, of
+ course.
+ </para>
+
+ <para>
+ Depending on your needs, you could also need to care about
+ compressing <term>NULL</> values in there, storing for example
+ <literal>(Datum) 0</> like <literal>gist_circle_compress</> is
+ doing.
+ </para>
</listitem>
</varlistentry>
***************
*** 140,145 ****
--- 341,369 ----
index representation of the data item into a format that can be
manipulated by the database.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_decompress(internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_decompress(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_decompress);
+ Datum
+ my_decompress(PG_FUNCTION_ARGS)
+ {
+ PG_RETURN_POINTER(PG_GETARG_POINTER(0));
+ }
+ </programlisting>
+ </para>
</listitem>
</varlistentry>
***************
*** 151,156 ****
--- 375,419 ----
entry into a particular branch of the tree. items will be inserted
down the path of least <function>penalty</function> in the tree.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C' STRICT;
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_penalty(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_penalty);
+ Datum
+ my_penalty(PG_FUNCTION_ARGS)
+ {
+ GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
+ float *penalty = (float *) PG_GETARG_POINTER(2);
+
+ data_type *orig = DatumGetDataType(origentry->key);
+ data_type *new = DatumGetDataType(newentry->key);
+
+ *penalty = my_penalty_implementation(orig, new);
+ PG_RETURN_POINTER(penalty);
+ }
+
+ </programlisting>
+
+ <para>
+ The <term>penalty</> function is crucial to good performances of the
+ index building and usage. It'll get used at query time to determine
+ which branch to follow when choosing where to add the new entry in
+ the tree. At query time, the more balanced the index, the quicker
+ the lookup.
+ </para>
</listitem>
</varlistentry>
***************
*** 162,167 ****
--- 425,522 ----
the page are to stay on the old page, and which are to move to the new
page.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_penalty(internal, internal, internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C' STRICT;
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_picksplit(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_picksplit_jordan);
+ Datum
+ my_picksplit(PG_FUNCTION_ARGS)
+ {
+ GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
+ OffsetNumber maxoff = entryvec->n - 1;
+ GISTENTRY *ent = entryvec->vector;
+ GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
+
+ int i, nbytes;
+ OffsetNumber *left, *right;
+ data_type *tmp_union;
+ data_type *unionL;
+ data_type *unionR;
+
+ GISTENTRY **raw_entryvec;
+
+ maxoff = entryvec->n - 1;
+ nbytes = (maxoff + 1) * sizeof(OffsetNumber);
+
+ v->spl_left = (OffsetNumber *) palloc(nbytes);
+ left = v->spl_left;
+ v->spl_nleft = 0;
+
+ v->spl_right = (OffsetNumber *) palloc(nbytes);
+ right = v->spl_right;
+ v->spl_nright = 0;
+
+ unionL = NULL;
+ unionR = NULL;
+
+ /* Initialize the raw entry vector. */
+ raw_entryvec = (GISTENTRY **) malloc(entryvec->n * sizeof(void *));
+ for (i=FirstOffsetNumber; i <= maxoff; i=OffsetNumberNext(i))
+ raw_entryvec[i] = &(entryvec->vector[i]);
+
+ for (i=FirstOffsetNumber; i <= maxoff; i=OffsetNumberNext(i)) {
+ int real_index = raw_entryvec[i] - entryvec->vector;
+ tmp_union = DatumGetDataType(entryvec->vector[real_index].key);
+ Assert(tmp_union != NULL);
+
+ /*
+ * Choose where to put the index entries and update unionL and unionR accordingly.
+ * Append the entries to either v_spl_left or v_spl_right, and care about the counters.
+ */
+
+ if( my_choice_is_left(unionL, curl, unionR, curr) )
+ {
+ if( unionL == NULL )
+ unionL = tmp_union;
+ else
+ unionL = my_union_implementation(unionL, tmp_union);
+
+ *left = real_index;
+ ++left;
+ ++(v->spl_nleft);
+ }
+ else
+ {
+ /*
+ * Same on the right
+ */
+ }
+ }
+
+ v->spl_ldatum = DataTypeGetDatum(unionL);
+ v->spl_rdatum = DataTypeGetDatum(unionR);
+ PG_RETURN_POINTER(v);
+ }
+ </programlisting>
+
+ <para>
+ The <term>picksplit</> implementation is crucial for optimized index
+ builds. Its implementation, combined with a proper <term>penalty</>
+ one, is where the challenge of implementing a performant
+ <term>GIST</> index lies.
+ </para>
</listitem>
</varlistentry>
***************
*** 171,176 ****
--- 526,563 ----
<para>
Returns true if two entries are identical, false otherwise.
</para>
+
+ <para>
+ The <term>SQL</> declaration of the function must look like this:
+
+ <programlisting>
+ CREATE OR REPLACE FUNCTION my_same(data_type, data_type, internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE 'C';
+ </programlisting>
+
+ And the matching code in the C module could then follow such a skeleton:
+
+ <programlisting>
+ Datum my_same(PG_FUNCTION_ARGS);
+ PG_FUNCTION_INFO_V1(my_same);
+ Datum
+ my_same(PG_FUNCTION_ARGS)
+ {
+ prefix_range *v1 = PG_GETARG_PREFIX_RANGE_P(0);
+ prefix_range *v2 = PG_GETARG_PREFIX_RANGE_P(1);
+ bool *result = (bool *) PG_GETARG_POINTER(2);
+
+ *result = my_eq(v1, v2);
+ PG_RETURN_POINTER( result );
+ }
+ </programlisting>
+
+ <para>
+ This is straightforward, even the memory place where to handle the
+ boolean return value is pre allocated.
+ </para>
</listitem>
</varlistentry>
Dimitri Fontaine <dfontaine@hi-media.com> writes:
I've been working some more on this documentation:
Applied with some editorialization. It seems to me it could still do
with a lot more detail to specify what API the functions are really
expected to implement.
regards, tom lane
Hi,
Le 12 juin 09 à 21:49, Tom Lane a écrit :
Applied with some editorialization.
Thanks a lot :)
It seems to me it could still do
with a lot more detail to specify what API the functions are really
expected to implement.
I'm sorry I'm not following... I guess you're talking about a better
high-level view of things? Like describing GiST itself, the way it's
done in the following link, but reduced in one or two paragraphs?
http://gist.cs.berkeley.edu/gist1.html
I'll be happy to work on improving this documentation some more (but
won't be there next week)...
Regards,
--
dim
Dimitri Fontaine <dfontaine@hi-media.com> writes:
Le 12 juin 09 � 21:49, Tom Lane a �crit :
It seems to me it could still do
with a lot more detail to specify what API the functions are really
expected to implement.
I'm sorry I'm not following... I guess you're talking about a better
high-level view of things? Like describing GiST itself, the way it's
done in the following link, but reduced in one or two paragraphs?
http://gist.cs.berkeley.edu/gist1.html
No, we already have that level of detail (some of it word for word in
fact); and it's not all that important for opclass authors to know how
GIST works anyway. What's bothering me is the fuzziness of the API
specifications for the support functions. It's not real clear for
example what you have to do to have an index storage type different from
the column datatype, and even less clear which type the same() function
is comparing. Having some skeletons that execute magic bits of
undocumented code is not a substitute for a specification.
regards, tom lane
Le 12 juin 09 à 23:20, Tom Lane a écrit :
Dimitri Fontaine <dfontaine@hi-media.com> writes:
Le 12 juin 09 à 21:49, Tom Lane a écrit :
It seems to me it could still do
with a lot more detail to specify what API the functions are really
expected to implement.What's bothering me is the fuzziness of the API
specifications for the support functions. It's not real clear for
example what you have to do to have an index storage type different
from
the column datatype, and even less clear which type the same()
function
is comparing. Having some skeletons that execute magic bits of
undocumented code is not a substitute for a specification.
Oh yes that wasn't easy to guess: I had to look at others
implementations then do some tests (trial&error) to determine this.
Andrew Gierth has been really helpful here, and his ip4r module a good
example (but without varlena).
I'll try to provide something here, what I'm trying to say is that I
need some help and research (and core code reading) to reverse
engineer the specs.
Regards,
--
dim