diff --git a/doc/src/sgml/spgist.sgml b/doc/src/sgml/spgist.sgml
index f40c7906124022a3fd2a4101acff98430d76e951..dfa62adb5571f718de397313a15605d7d9e13267 100644
--- a/doc/src/sgml/spgist.sgml
+++ b/doc/src/sgml/spgist.sgml
@@ -114,7 +114,7 @@
      </row>
      <row>
       <entry><literal>box_ops</></entry>
-      <entry>box</entry>
+      <entry><type>box</></entry>
       <entry>
        <literal>&lt;&lt;</>
        <literal>&amp;&lt;</>
@@ -183,11 +183,14 @@
   Inner tuples are more complex, since they are branching points in the
   search tree.  Each inner tuple contains a set of one or more
   <firstterm>nodes</>, which represent groups of similar leaf values.
-  A node contains a downlink that leads to either another, lower-level inner
-  tuple, or a short list of leaf tuples that all lie on the same index page.
-  Each node has a <firstterm>label</> that describes it; for example,
+  A node contains a downlink that leads either to another, lower-level inner
+  tuple, or to a short list of leaf tuples that all lie on the same index page.
+  Each node normally has a <firstterm>label</> that describes it; for example,
   in a radix tree the node label could be the next character of the string
-  value.  Optionally, an inner tuple can have a <firstterm>prefix</> value
+  value.  (Alternatively, an operator class can omit the node labels, if it
+  works with a fixed set of nodes for all inner tuples;
+  see <xref linkend="spgist-null-labels">.)
+  Optionally, an inner tuple can have a <firstterm>prefix</> value
   that describes all its members.  In a radix tree this could be the common
   prefix of the represented strings.  The prefix value is not necessarily
   really a prefix, but can be any data needed by the operator class;
@@ -202,7 +205,8 @@
   tuple, so the <acronym>SP-GiST</acronym> core provides the possibility for
   operator classes to manage level counting while descending the tree.
   There is also support for incrementally reconstructing the represented
-  value when that is needed.
+  value when that is needed, and for passing down additional data (called
+  <firstterm>traverse values</>) during a tree descent.
  </para>
 
  <note>
@@ -343,10 +347,13 @@ typedef struct spgChooseOut
         }           addNode;
         struct                  /* results for spgSplitTuple */
         {
-            /* Info to form new inner tuple with one node */
+            /* Info to form new upper-level inner tuple with one child tuple */
             bool        prefixHasPrefix;    /* tuple should have a prefix? */
             Datum       prefixPrefixDatum;  /* if so, its value */
-            Datum       nodeLabel;          /* node's label */
+            int         prefixNNodes;       /* number of nodes */
+            Datum      *prefixNodeLabels;   /* their labels (or NULL for
+                                             * no labels) */
+            int         childNodeN;         /* which node gets child tuple */
 
             /* Info to form new lower-level inner tuple with all old nodes */
             bool        postfixHasPrefix;   /* tuple should have a prefix? */
@@ -416,29 +423,33 @@ typedef struct spgChooseOut
        set <structfield>resultType</> to <literal>spgSplitTuple</>.
        This action moves all the existing nodes into a new lower-level
        inner tuple, and replaces the existing inner tuple with a tuple
-       having a single node that links to the new lower-level inner tuple.
+       having a single downlink pointing to the new lower-level inner tuple.
        Set <structfield>prefixHasPrefix</> to indicate whether the new
        upper tuple should have a prefix, and if so set
        <structfield>prefixPrefixDatum</> to the prefix value.  This new
        prefix value must be sufficiently less restrictive than the original
-       to accept the new value to be indexed, and it should be no longer
-       than the original prefix.
-       Set <structfield>nodeLabel</> to the label to be used for the
-       node that will point to the new lower-level inner tuple.
+       to accept the new value to be indexed.
+       Set <structfield>prefixNNodes</> to the number of nodes needed in the
+       new tuple, and set <structfield>prefixNodeLabels</> to a palloc'd array
+       holding their labels, or to NULL if node labels are not required.
+       Note that the total size of the new upper tuple must be no more
+       than the total size of the tuple it is replacing; this constrains
+       the lengths of the new prefix and new labels.
+       Set <structfield>childNodeN</> to the index (from zero) of the node
+       that will downlink to the new lower-level inner tuple.
        Set <structfield>postfixHasPrefix</> to indicate whether the new
        lower-level inner tuple should have a prefix, and if so set
        <structfield>postfixPrefixDatum</> to the prefix value.  The
-       combination of these two prefixes and the additional label must
-       have the same meaning as the original prefix, because there is
-       no opportunity to alter the node labels that are moved to the new
-       lower-level tuple, nor to change any child index entries.
+       combination of these two prefixes and the downlink node's label
+       (if any) must have the same meaning as the original prefix, because
+       there is no opportunity to alter the node labels that are moved to
+       the new lower-level tuple, nor to change any child index entries.
        After the node has been split, the <function>choose</function>
        function will be called again with the replacement inner tuple.
-       That call will usually result in an <literal>spgAddNode</> result,
-       since presumably the node label added in the split step will not
-       match the new value; so after that, there will be a third call
-       that finally returns <literal>spgMatchNode</> and allows the
-       insertion to descend to the leaf level.
+       That call may return an <literal>spgAddNode</> result, if no suitable
+       node was created by the <literal>spgSplitTuple</> action.  Eventually
+       <function>choose</function> must return <literal>spgMatchNode</> to
+       allow the insertion to descend to the next level.
       </para>
      </listitem>
     </varlistentry>
@@ -492,9 +503,8 @@ typedef struct spgPickSplitOut
        <structfield>prefixDatum</> to the prefix value.
        Set <structfield>nNodes</> to indicate the number of nodes that
        the new inner tuple will contain, and
-       set <structfield>nodeLabels</> to an array of their label values.
-       (If the nodes do not require labels, set <structfield>nodeLabels</>
-       to NULL; see <xref linkend="spgist-null-labels"> for details.)
+       set <structfield>nodeLabels</> to an array of their label values,
+       or to NULL if node labels are not required.
        Set <structfield>mapTuplesToNodes</> to an array that gives the index
        (from zero) of the node that each leaf tuple should be assigned to.
        Set <structfield>leafTupleDatums</> to an array of the values to
@@ -561,7 +571,7 @@ typedef struct spgInnerConsistentIn
 
     Datum       reconstructedValue;     /* value reconstructed at parent */
     void       *traversalValue; /* opclass-specific traverse value */
-    MemoryContext traversalMemoryContext;
+    MemoryContext traversalMemoryContext;   /* put new traverse values here */
     int         level;          /* current level (counting from zero) */
     bool        returnData;     /* original data must be returned? */
 
@@ -580,7 +590,6 @@ typedef struct spgInnerConsistentOut
     int        *levelAdds;      /* increment level by this much for each */
     Datum      *reconstructedValues;    /* associated reconstructed values */
     void      **traversalValues;        /* opclass-specific traverse values */
-
 } spgInnerConsistentOut;
 </programlisting>
 
@@ -599,6 +608,11 @@ typedef struct spgInnerConsistentOut
        parent tuple; it is <literal>(Datum) 0</> at the root level or if the
        <function>inner_consistent</> function did not provide a value at the
        parent level.
+       <structfield>traversalValue</> is a pointer to any traverse data
+       passed down from the previous call of <function>inner_consistent</>
+       on the parent index tuple, or NULL at the root level.
+       <structfield>traversalMemoryContext</> is the memory context in which
+       to store output traverse values (see below).
        <structfield>level</> is the current inner tuple's level, starting at
        zero for the root level.
        <structfield>returnData</> is <literal>true</> if reconstructed data is
@@ -615,9 +629,6 @@ typedef struct spgInnerConsistentOut
        inner tuple, and
        <structfield>nodeLabels</> is an array of their label values, or
        NULL if the nodes do not have labels.
-       <structfield>traversalValue</> is a pointer to data that
-       <function>inner_consistent</> gets when called on child nodes from an
-       outer call of <function>inner_consistent</> on parent nodes.
       </para>
 
       <para>
@@ -633,17 +644,19 @@ typedef struct spgInnerConsistentOut
        <structfield>reconstructedValues</> to an array of the values
        reconstructed for each child node to be visited; otherwise, leave
        <structfield>reconstructedValues</> as NULL.
+       If it is desired to pass down additional out-of-band information
+       (<quote>traverse values</>) to lower levels of the tree search,
+       set <structfield>traversalValues</> to an array of the appropriate
+       traverse values, one for each child node to be visited; otherwise,
+       leave <structfield>traversalValues</> as NULL.
        Note that the <function>inner_consistent</> function is
        responsible for palloc'ing the
-       <structfield>nodeNumbers</>, <structfield>levelAdds</> and
-       <structfield>reconstructedValues</> arrays.
-       Sometimes accumulating some information is needed, while
-       descending from parent to child node was happened. In this case
-       <structfield>traversalValues</> array keeps pointers to
-       specific data you need to accumulate for every child node.
-       Memory for <structfield>traversalValues</> should be allocated in
-       the default context, but each element of it should be allocated in
-       <structfield>traversalMemoryContext</>.
+       <structfield>nodeNumbers</>, <structfield>levelAdds</>,
+       <structfield>reconstructedValues</>, and
+       <structfield>traversalValues</> arrays in the current memory context.
+       However, any output traverse values pointed to by
+       the <structfield>traversalValues</> array should be allocated
+       in <structfield>traversalMemoryContext</>.
       </para>
      </listitem>
     </varlistentry>
@@ -670,8 +683,8 @@ typedef struct spgLeafConsistentIn
     ScanKey     scankeys;       /* array of operators and comparison values */
     int         nkeys;          /* length of array */
 
-    void       *traversalValue; /* opclass-specific traverse value */
     Datum       reconstructedValue;     /* value reconstructed at parent */
+    void       *traversalValue; /* opclass-specific traverse value */
     int         level;          /* current level (counting from zero) */
     bool        returnData;     /* original data must be returned? */
 
@@ -700,6 +713,9 @@ typedef struct spgLeafConsistentOut
        parent tuple; it is <literal>(Datum) 0</> at the root level or if the
        <function>inner_consistent</> function did not provide a value at the
        parent level.
+       <structfield>traversalValue</> is a pointer to any traverse data
+       passed down from the previous call of <function>inner_consistent</>
+       on the parent index tuple, or NULL at the root level.
        <structfield>level</> is the current leaf tuple's level, starting at
        zero for the root level.
        <structfield>returnData</> is <literal>true</> if reconstructed data is
@@ -797,7 +813,10 @@ typedef struct spgLeafConsistentOut
    point.  In such a case the code typically works with the nodes by
    number, and there is no need for explicit node labels.  To suppress
    node labels (and thereby save some space), the <function>picksplit</>
-   function can return NULL for the <structfield>nodeLabels</> array.
+   function can return NULL for the <structfield>nodeLabels</> array,
+   and likewise the <function>choose</> function can return NULL for
+   the <structfield>prefixNodeLabels</> array during
+   a <literal>spgSplitTuple</> action.
    This will in turn result in <structfield>nodeLabels</> being NULL during
    subsequent calls to <function>choose</> and <function>inner_consistent</>.
    In principle, node labels could be used for some inner tuples and omitted
@@ -807,10 +826,7 @@ typedef struct spgLeafConsistentOut
   <para>
    When working with an inner tuple having unlabeled nodes, it is an error
    for <function>choose</> to return <literal>spgAddNode</>, since the set
-   of nodes is supposed to be fixed in such cases.  Also, there is no
-   provision for generating an unlabeled node in <literal>spgSplitTuple</>
-   actions, since it is expected that an <literal>spgAddNode</> action will
-   be needed as well.
+   of nodes is supposed to be fixed in such cases.
   </para>
  </sect2>
 
@@ -859,11 +875,10 @@ typedef struct spgLeafConsistentOut
 
  <para>
   The <productname>PostgreSQL</productname> source distribution includes
-  several examples of index operator classes for
-  <acronym>SP-GiST</acronym>.  The core system currently provides radix
-  trees over text columns and two types of trees over points: quad-tree and
-  k-d tree.  Look into <filename>src/backend/access/spgist/</> to see the
-  code.
+  several examples of index operator classes for <acronym>SP-GiST</acronym>,
+  as described in <xref linkend="spgist-builtin-opclasses-table">.  Look
+  into <filename>src/backend/access/spgist/</>
+  and <filename>src/backend/utils/adt/</> to see the code.
  </para>
 
 </sect1>
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index f090ca528b1be0220a692d0a12db5ec3b8eef838..6fc04b224dfd70009e6fc7e4c75b49b0df8f5e28 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -1705,17 +1705,40 @@ spgSplitNodeAction(Relation index, SpGistState *state,
 	/* Should not be applied to nulls */
 	Assert(!SpGistPageStoresNulls(current->page));
 
+	/* Check opclass gave us sane values */
+	if (out->result.splitTuple.prefixNNodes <= 0 ||
+		out->result.splitTuple.prefixNNodes > SGITMAXNNODES)
+		elog(ERROR, "invalid number of prefix nodes: %d",
+			 out->result.splitTuple.prefixNNodes);
+	if (out->result.splitTuple.childNodeN < 0 ||
+		out->result.splitTuple.childNodeN >=
+		out->result.splitTuple.prefixNNodes)
+		elog(ERROR, "invalid child node number: %d",
+			 out->result.splitTuple.childNodeN);
+
 	/*
-	 * Construct new prefix tuple, containing a single node with the specified
-	 * label.  (We'll update the node's downlink to point to the new postfix
-	 * tuple, below.)
+	 * Construct new prefix tuple with requested number of nodes.  We'll fill
+	 * in the childNodeN'th node's downlink below.
 	 */
-	node = spgFormNodeTuple(state, out->result.splitTuple.nodeLabel, false);
+	nodes = (SpGistNodeTuple *) palloc(sizeof(SpGistNodeTuple) *
+									   out->result.splitTuple.prefixNNodes);
+
+	for (i = 0; i < out->result.splitTuple.prefixNNodes; i++)
+	{
+		Datum		label = (Datum) 0;
+		bool		labelisnull;
+
+		labelisnull = (out->result.splitTuple.prefixNodeLabels == NULL);
+		if (!labelisnull)
+			label = out->result.splitTuple.prefixNodeLabels[i];
+		nodes[i] = spgFormNodeTuple(state, label, labelisnull);
+	}
 
 	prefixTuple = spgFormInnerTuple(state,
 									out->result.splitTuple.prefixHasPrefix,
 									out->result.splitTuple.prefixPrefixDatum,
-									1, &node);
+									out->result.splitTuple.prefixNNodes,
+									nodes);
 
 	/* it must fit in the space that innerTuple now occupies */
 	if (prefixTuple->size > innerTuple->size)
@@ -1807,10 +1830,12 @@ spgSplitNodeAction(Relation index, SpGistState *state,
 	 * the postfix tuple first.)  We have to update the local copy of the
 	 * prefixTuple too, because that's what will be written to WAL.
 	 */
-	spgUpdateNodeLink(prefixTuple, 0, postfixBlkno, postfixOffset);
+	spgUpdateNodeLink(prefixTuple, out->result.splitTuple.childNodeN,
+					  postfixBlkno, postfixOffset);
 	prefixTuple = (SpGistInnerTuple) PageGetItem(current->page,
 							  PageGetItemId(current->page, current->offnum));
-	spgUpdateNodeLink(prefixTuple, 0, postfixBlkno, postfixOffset);
+	spgUpdateNodeLink(prefixTuple, out->result.splitTuple.childNodeN,
+					  postfixBlkno, postfixOffset);
 
 	MarkBufferDirty(current->buffer);
 
diff --git a/src/backend/access/spgist/spgtextproc.c b/src/backend/access/spgist/spgtextproc.c
index e0d8f30ef16c1e44f2861875ec948f6143a7559f..852a9b00fa5784662240fc59eb565c89ae4b71be 100644
--- a/src/backend/access/spgist/spgtextproc.c
+++ b/src/backend/access/spgist/spgtextproc.c
@@ -212,9 +212,14 @@ spg_text_choose(PG_FUNCTION_ARGS)
 				out->result.splitTuple.prefixPrefixDatum =
 					formTextDatum(prefixStr, commonLen);
 			}
-			out->result.splitTuple.nodeLabel =
+			out->result.splitTuple.prefixNNodes = 1;
+			out->result.splitTuple.prefixNodeLabels =
+				(Datum *) palloc(sizeof(Datum));
+			out->result.splitTuple.prefixNodeLabels[0] =
 				Int16GetDatum(*(unsigned char *) (prefixStr + commonLen));
 
+			out->result.splitTuple.childNodeN = 0;
+
 			if (prefixSize - commonLen == 1)
 			{
 				out->result.splitTuple.postfixHasPrefix = false;
@@ -280,7 +285,10 @@ spg_text_choose(PG_FUNCTION_ARGS)
 		out->resultType = spgSplitTuple;
 		out->result.splitTuple.prefixHasPrefix = in->hasPrefix;
 		out->result.splitTuple.prefixPrefixDatum = in->prefixDatum;
-		out->result.splitTuple.nodeLabel = Int16GetDatum(-2);
+		out->result.splitTuple.prefixNNodes = 1;
+		out->result.splitTuple.prefixNodeLabels = (Datum *) palloc(sizeof(Datum));
+		out->result.splitTuple.prefixNodeLabels[0] = Int16GetDatum(-2);
+		out->result.splitTuple.childNodeN = 0;
 		out->result.splitTuple.postfixHasPrefix = false;
 	}
 	else
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index f39a2d6938c69a745ddcae4be58348acebe6ecfb..a953a5a401e0b488208ab871d7547f3d96410875 100644
--- a/src/include/access/spgist.h
+++ b/src/include/access/spgist.h
@@ -90,10 +90,13 @@ typedef struct spgChooseOut
 		}			addNode;
 		struct					/* results for spgSplitTuple */
 		{
-			/* Info to form new inner tuple with one node */
+			/* Info to form new upper-level inner tuple with one child tuple */
 			bool		prefixHasPrefix;		/* tuple should have a prefix? */
 			Datum		prefixPrefixDatum;		/* if so, its value */
-			Datum		nodeLabel;		/* node's label */
+			int			prefixNNodes;	/* number of nodes */
+			Datum	   *prefixNodeLabels;		/* their labels (or NULL for
+												 * no labels) */
+			int			childNodeN;		/* which node gets child tuple */
 
 			/* Info to form new lower-level inner tuple with all old nodes */
 			bool		postfixHasPrefix;		/* tuple should have a prefix? */
@@ -134,7 +137,8 @@ typedef struct spgInnerConsistentIn
 
 	Datum		reconstructedValue;		/* value reconstructed at parent */
 	void	   *traversalValue; /* opclass-specific traverse value */
-	MemoryContext traversalMemoryContext;
+	MemoryContext traversalMemoryContext;		/* put new traverse values
+												 * here */
 	int			level;			/* current level (counting from zero) */
 	bool		returnData;		/* original data must be returned? */
 
@@ -163,8 +167,8 @@ typedef struct spgLeafConsistentIn
 	ScanKey		scankeys;		/* array of operators and comparison values */
 	int			nkeys;			/* length of array */
 
-	void	   *traversalValue; /* opclass-specific traverse value */
 	Datum		reconstructedValue;		/* value reconstructed at parent */
+	void	   *traversalValue; /* opclass-specific traverse value */
 	int			level;			/* current level (counting from zero) */
 	bool		returnData;		/* original data must be returned? */