Get rid of -Wshadow warning - I guess we're including something that
[metze/wireshark/wip.git] / epan / stats_tree.c
1 /* stats_tree.c
2  * API for a counter tree for Wireshark
3  * 2004, Luis E. G. Ontanon
4  *
5  * $Id$
6  *
7  * Wireshark - Network traffic analyzer
8  * By Gerald Combs <gerald@wireshark.org>
9  * Copyright 1998 Gerald Combs
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25
26 #include "config.h"
27
28 #include <glib.h>
29 #include <epan/stats_tree_priv.h>
30 #include <string.h>
31
32 #include "stats_tree.h"
33
34 /*
35 TODO:
36    - sort out the sorting issue
37
38  */
39
40 /* used to contain the registered stat trees */
41 static GHashTable *registry = NULL;
42
43 /* writes into the buffers pointed by value, rate and percent
44    the string representations of a node*/
45 extern void
46 stats_tree_get_strs_from_node(const stat_node *node, gchar *value, gchar *rate, gchar *percent)
47 {
48         float f;
49
50         if (value) g_snprintf(value,NUM_BUF_SIZE,"%u",node->counter);
51
52         if (rate) {
53                 *rate = '\0';
54                 if (node->st->elapsed > 0.0) {
55                         f = ((float)node->counter) / (float)node->st->elapsed;
56                         g_snprintf(rate,NUM_BUF_SIZE,"%f",f);
57                 }
58         }
59
60         if (percent) {
61                 *percent = '\0';
62                 if (node->parent->counter > 0) {
63                         f = (float)(((float)node->counter * 100.0) / node->parent->counter);
64                         g_snprintf(percent,NUM_BUF_SIZE,"%.2f%%",f);
65                 }
66         }
67 }
68
69
70 /* a text representation of a node
71 if buffer is NULL returns a newly allocated string */
72 extern gchar*
73 stats_tree_node_to_str(const stat_node *node, gchar *buffer, guint len)
74 {
75         if (buffer) {
76                 g_snprintf(buffer,len,"%s: %i",node->name, node->counter);
77                 return buffer;
78         } else {
79                 return g_strdup_printf("%s: %i",node->name, node->counter);
80         }
81 }
82
83 extern guint
84 stats_tree_branch_max_namelen(const stat_node *node, guint indent)
85 {
86         stat_node *child;
87         guint maxlen = 0;
88         guint len;
89
90         indent = indent > INDENT_MAX ? INDENT_MAX : indent;
91
92         if (node->children) {
93                 for (child = node->children; child; child = child->next ) {
94                         len = stats_tree_branch_max_namelen(child,indent+1);
95                         maxlen = len > maxlen ? len : maxlen;
96                 }
97         }
98
99         len = (guint) strlen(node->name) + indent;
100         maxlen = len > maxlen ? len : maxlen;
101
102         return maxlen;
103 }
104
105 static gchar *format;
106
107 /* populates the given GString with a tree representation of a branch given by node,
108 using indent spaces as initial indentation */
109 extern void
110 stats_tree_branch_to_str(const stat_node *node, GString *s, guint indent)
111 {
112         stat_node *child;
113         static gchar indentation[INDENT_MAX+1];
114         static gchar value[NUM_BUF_SIZE];
115         static gchar rate[NUM_BUF_SIZE];
116         static gchar percent[NUM_BUF_SIZE];
117
118         guint i = 0;
119
120         if (indent == 0) {
121                 format = g_strdup_printf(" %%s%%-%us%%12s   %%12s    %%12s\n",stats_tree_branch_max_namelen(node,0));
122         }
123
124         stats_tree_get_strs_from_node(node, value, rate, percent);
125
126         indent = indent > INDENT_MAX ? INDENT_MAX : indent;
127
128         /* fill indentation with indent spaces */
129         if (indent > 0) {
130                 while(i<indent)
131                         indentation[i++] = ' ';
132         }
133
134         indentation[i] = '\0';
135
136         g_string_append_printf(s,format,
137                                           indentation,node->name,value,rate,percent);
138
139         if (node->children) {
140                 for (child = node->children; child; child = child->next ) {
141                         stats_tree_branch_to_str(child,s,indent+1);
142                 }
143         }
144
145         if (indent == 0) {
146                 g_free(format);
147         }
148 }
149
150
151 /* frees the resources allocated by a stat_tree node */
152 static void
153 free_stat_node(stat_node *node)
154 {
155         stat_node *child;
156         stat_node *next;
157
158         if (node->children) {
159         for (child = node->children; child; child = next ) {
160             /* child->next will be gone after free_stat_node, so cache it here */
161             next = child->next;
162                         free_stat_node(child);
163         }
164         }
165
166         if(node->st->cfg->free_node_pr) node->st->cfg->free_node_pr(node);
167
168         if (node->hash) g_hash_table_destroy(node->hash);
169
170         g_free(node->rng);
171         g_free(node->name);
172         g_free(node);
173 }
174
175 /* destroys the whole tree instance */
176 extern void
177 stats_tree_free(stats_tree *st)
178 {
179         stat_node *child;
180         stat_node *next;
181
182         g_free(st->filter);
183         g_hash_table_destroy(st->names);
184         g_ptr_array_free(st->parents,TRUE);
185
186         for (child = st->root.children; child; child = next ) {
187                 /* child->next will be gone after free_stat_node, so cache it here */
188                 next = child->next;
189                 free_stat_node(child);
190         }
191
192         if (st->cfg->free_tree_pr)
193                 st->cfg->free_tree_pr(st);
194
195         if (st->cfg->cleanup)
196                 st->cfg->cleanup(st);
197
198         g_free(st);
199 }
200
201
202 /* reset a node to its original state */
203 static void
204 reset_stat_node(stat_node *node)
205 {
206         stat_node *child;
207
208         if (node->children) {
209                 for (child = node->children; child; child = child->next )
210                         reset_stat_node(child);
211         }
212
213         node->counter = 0;
214
215         if(node->st->cfg->reset_node) {
216                 node->st->cfg->reset_node(node);
217         }
218
219 }
220
221 /* reset the whole stats_tree */
222 extern void
223 stats_tree_reset(void *p)
224 {
225         stats_tree *st = (stats_tree *)p;
226
227         st->start = -1.0;
228         st->elapsed = 0.0;
229
230         reset_stat_node(&st->root);
231
232         if (st->cfg->reset_tree) {
233                 st->cfg->reset_tree(st);
234         }
235 }
236
237 extern void
238 stats_tree_reinit(void *p)
239 {
240         stats_tree *st = (stats_tree *)p;
241         stat_node *child;
242         stat_node *next;
243
244         for (child = st->root.children; child; child = next) {
245         /* child->next will be gone after free_stat_node, so cache it here */
246         next = child->next;
247                 free_stat_node(child);
248         }
249
250         st->root.children = NULL;
251         st->root.counter = 0;
252
253         if (st->cfg->init) {
254                 st->cfg->init(st);
255         }
256 }
257
258 /* register a new stats_tree */
259 extern void
260 stats_tree_register_with_group(const char *tapname, const char *abbr, const char *name,
261                     guint flags,
262                     stat_tree_packet_cb packet, stat_tree_init_cb init,
263                     stat_tree_cleanup_cb cleanup, register_stat_group_t stat_group)
264 {
265
266         stats_tree_cfg *cfg = (stats_tree_cfg *)g_malloc( sizeof(stats_tree_cfg) );
267
268         /* at the very least the abbrev and the packet function should be given */
269         g_assert( tapname && abbr && packet );
270
271         cfg->plugin = FALSE;
272         cfg->tapname = g_strdup(tapname);
273         cfg->abbr = g_strdup(abbr);
274         cfg->name = name ? g_strdup(name) : g_strdup(abbr);
275         cfg->stat_group = stat_group;
276
277         cfg->packet = packet;
278         cfg->init = init;
279         cfg->cleanup = cleanup;
280
281         cfg->flags = flags;
282
283         /* these have to be filled in by implementations */
284         cfg->setup_node_pr = NULL;
285         cfg->new_tree_pr = NULL;
286         cfg->free_node_pr = NULL;
287         cfg->free_tree_pr = NULL;
288         cfg->draw_node = NULL;
289         cfg->draw_tree = NULL;
290         cfg->reset_node = NULL;
291         cfg->reset_tree = NULL;
292
293         if (!registry) registry = g_hash_table_new(g_str_hash,g_str_equal);
294
295         g_hash_table_insert(registry,cfg->abbr,cfg);
296 }
297
298 /* register a new stats_tree with default group REGISTER_STAT_GROUP_UNSORTED */
299 extern void
300 stats_tree_register(const char *tapname, const char *abbr, const char *name,
301                     guint flags,
302                     stat_tree_packet_cb packet, stat_tree_init_cb init,
303                     stat_tree_cleanup_cb cleanup)
304 {
305         stats_tree_register_with_group(tapname, abbr, name,
306                     flags,
307                     packet, init,
308                     cleanup, REGISTER_STAT_GROUP_UNSORTED);
309 }
310
311 /* register a new stat_tree with default group REGISTER_STAT_GROUP_UNSORTED from a plugin */
312 extern void
313 stats_tree_register_plugin(const char *tapname, const char *abbr, const char *name,
314                     guint flags,
315                     stat_tree_packet_cb packet, stat_tree_init_cb init,
316                     stat_tree_cleanup_cb cleanup)
317 {
318         stats_tree_cfg *cfg;
319
320         stats_tree_register(tapname, abbr, name,
321                     flags,
322                     packet, init,
323                     cleanup);
324         cfg = stats_tree_get_cfg_by_abbr((char*)abbr);
325         cfg->plugin = TRUE;
326 }
327
328 extern stats_tree*
329 stats_tree_new(stats_tree_cfg *cfg, tree_pres *pr, const char *filter)
330 {
331         stats_tree *st = (stats_tree *)g_malloc(sizeof(stats_tree));
332
333         st->cfg = cfg;
334         st->pr = pr;
335
336         st->names = g_hash_table_new(g_str_hash,g_str_equal);
337         st->parents = g_ptr_array_new();
338         st->filter = g_strdup(filter);
339
340         st->start = -1.0;
341         st->elapsed = 0.0;
342
343         st->root.counter = 0;
344         st->root.name = g_strdup(cfg->name);
345         st->root.st = st;
346         st->root.parent = NULL;
347         st->root.children = NULL;
348         st->root.next = NULL;
349         st->root.hash = NULL;
350         st->root.pr = NULL;
351
352         g_ptr_array_add(st->parents,&st->root);
353
354         return st;
355 }
356
357 /* will be the tap packet cb */
358 extern int
359 stats_tree_packet(void *p, packet_info *pinfo, epan_dissect_t *edt, const void *pri)
360 {
361         stats_tree *st = (stats_tree *)p;
362         double now = nstime_to_msec(&pinfo->rel_ts);
363
364         if (st->start < 0.0) st->start = now;
365
366         st->elapsed = now - st->start;
367
368         if (st->cfg->packet)
369                 return st->cfg->packet(st,pinfo,edt,pri);
370         else
371                 return 0;
372 }
373
374 extern stats_tree_cfg*
375 stats_tree_get_cfg_by_abbr(char *abbr)
376 {
377         return (stats_tree_cfg *)g_hash_table_lookup(registry,abbr);
378 }
379
380 extern GList*
381 stats_tree_get_cfg_list(void)
382 {
383         return g_hash_table_get_values(registry);
384 }
385
386 struct _stats_tree_pres_cbs {
387         void (*setup_node_pr)(stat_node*);
388         void (*free_node_pr)(stat_node*);
389         void (*draw_node)(stat_node*);
390         void (*reset_node)(stat_node*);
391         tree_pres *(*new_tree_pr)(stats_tree*);
392         void (*free_tree_pr)(stats_tree*);
393         void (*draw_tree)(stats_tree*);
394         void (*reset_tree)(stats_tree*);
395 };
396
397 static void
398 setup_tree_presentation(gpointer k _U_, gpointer v, gpointer p)
399 {
400         stats_tree_cfg *cfg = (stats_tree_cfg *)v;
401         struct _stats_tree_pres_cbs *d = (struct _stats_tree_pres_cbs *)p;
402
403         cfg->in_use = FALSE;
404         cfg->setup_node_pr = d->setup_node_pr;
405         cfg->new_tree_pr = d->new_tree_pr;
406         cfg->free_node_pr = d->free_node_pr;
407         cfg->free_tree_pr = d->free_tree_pr;
408         cfg->draw_node = d->draw_node;
409         cfg->draw_tree = d->draw_tree;
410         cfg->reset_node = d->reset_node;
411         cfg->reset_tree = d->reset_tree;
412
413 }
414
415 extern void
416 stats_tree_presentation(void (*registry_iterator)(gpointer,gpointer,gpointer),
417                         void (*setup_node_pr)(stat_node*),
418                         void (*free_node_pr)(stat_node*),
419                         void (*draw_node)(stat_node*),
420                         void (*reset_node)(stat_node*),
421                         tree_pres *(*new_tree_pr)(stats_tree*),
422                         void (*free_tree_pr)(stats_tree*),
423                         void (*draw_tree)(stats_tree*),
424                         void (*reset_tree)(stats_tree*),
425                         void *data)
426 {
427         static struct _stats_tree_pres_cbs d;
428
429         d.setup_node_pr = setup_node_pr;
430         d.new_tree_pr = new_tree_pr;
431         d.free_node_pr = free_node_pr;
432         d.free_tree_pr = free_tree_pr;
433         d.draw_node = draw_node;
434         d.draw_tree = draw_tree;
435         d.reset_node = reset_node;
436         d.reset_tree = reset_tree;
437
438         if (registry) g_hash_table_foreach(registry,setup_tree_presentation,&d);
439
440         if (registry_iterator && registry)
441                 g_hash_table_foreach(registry,registry_iterator,data);
442
443 }
444
445
446 /* creates a stat_tree node
447 *    name: the name of the stats_tree node
448 *    parent_name: the name of the ALREADY REGISTERED parent
449 *    with_hash: whether or not it should keep a hash with its children names
450 *    as_named_node: whether or not it has to be registered in the root namespace
451 */
452 static stat_node*
453 new_stat_node(stats_tree *st, const gchar *name, int parent_id,
454               gboolean with_hash, gboolean as_parent_node)
455 {
456
457         stat_node *node = (stat_node *)g_malloc (sizeof(stat_node));
458         stat_node *last_chld = NULL;
459
460         node->counter = 0;
461         node->name = g_strdup(name);
462         node->children = NULL;
463         node->next = NULL;
464         node->st = (stats_tree*) st;
465         node->hash = with_hash ? g_hash_table_new(g_str_hash,g_str_equal) : NULL;
466         node->parent = NULL;
467         node->rng  =  NULL;
468
469         if (as_parent_node) {
470                 g_hash_table_insert(st->names,
471                                                         node->name,
472                                                         node);
473
474                 g_ptr_array_add(st->parents,node);
475
476                 node->id = st->parents->len - 1;
477         } else {
478                 node->id = -1;
479         }
480
481         if (parent_id >= 0 && parent_id < (int) st->parents->len ) {
482                 node->parent = (stat_node *)g_ptr_array_index(st->parents,parent_id);
483         } else {
484                 /* ??? should we set the parent to be root ??? */
485                 g_assert_not_reached();
486         }
487
488         if (node->parent->children) {
489                 /* insert as last child */
490
491                 for (last_chld = node->parent->children;
492                          last_chld->next;
493                          last_chld = last_chld->next ) ;
494
495                 last_chld->next = node;
496
497         } else {
498                 /* insert as first child */
499                 node->parent->children = node;
500         }
501
502         if(node->parent->hash) {
503                 g_hash_table_insert(node->parent->hash,node->name,node);
504         }
505
506         if (st->cfg->setup_node_pr) {
507                 st->cfg->setup_node_pr(node);
508         } else {
509                 node->pr = NULL;
510         }
511
512         return node;
513 }
514 /***/
515
516 extern int
517 stats_tree_create_node(stats_tree *st, const gchar *name, int parent_id, gboolean with_hash)
518 {
519         stat_node *node = new_stat_node(st,name,parent_id,with_hash,TRUE);
520
521         if (node)
522                 return node->id;
523         else
524                 return 0;
525 }
526
527 /* XXX: should this be a macro? */
528 extern int
529 stats_tree_create_node_by_pname(stats_tree *st, const gchar *name,
530                                 const gchar *parent_name, gboolean with_children)
531 {
532         return stats_tree_create_node(st,name,stats_tree_parent_id_by_name(st,parent_name),with_children);
533 }
534
535
536
537 /*
538  * Increases by delta the counter of the node whose name is given
539  * if the node does not exist yet it's created (with counter=1)
540  * using parent_name as parent node.
541  * with_hash=TRUE to indicate that the created node will have a parent
542  */
543 extern int
544 stats_tree_manip_node(manip_node_mode mode, stats_tree *st, const char *name,
545                       int parent_id, gboolean with_hash, gint value)
546 {
547         stat_node *node = NULL;
548         stat_node *parent = NULL;
549
550         g_assert( parent_id >= 0 && parent_id < (int) st->parents->len );
551
552         parent = (stat_node *)g_ptr_array_index(st->parents,parent_id);
553
554         if( parent->hash ) {
555                 node = (stat_node *)g_hash_table_lookup(parent->hash,name);
556         } else {
557                 node = (stat_node *)g_hash_table_lookup(st->names,name);
558         }
559
560         if ( node == NULL )
561                 node = new_stat_node(st,name,parent_id,with_hash,with_hash);
562
563         switch (mode) {
564                 case MN_INCREASE: node->counter += value; break;
565                 case MN_SET: node->counter = value; break;
566         }
567
568         if (node)
569                 return node->id;
570         else
571                 return -1;
572 }
573
574
575 extern char*
576 stats_tree_get_abbr(const char *opt_arg)
577 {
578         guint i;
579
580         /* XXX: this fails when tshark is given any options
581            after the -z */
582         g_assert(opt_arg != NULL);
583
584         for (i=0; opt_arg[i] && opt_arg[i] != ','; i++);
585
586         if (opt_arg[i] == ',') {
587                 return g_strndup(opt_arg,i);
588         } else {
589                 return NULL;
590         }
591 }
592
593
594 /*
595  * This function accepts an input string which should define a long integer range.
596  * The normal result is a struct containing the floor and ceil value of this
597  * range.
598  *
599  * It is allowed to define a range string in the following ways :
600  *
601  * "0-10" -> { 0, 10 }
602  * "-0" -> { G_MININT, 0 }
603  * "0-" -> { 0, G_MAXINT }
604  * "-" -> { G_MININT, G_MAXINT }
605  *
606  * Note that this function is robust to buggy input string. If in some cases it
607  * returns NULL, it but may also return a pair with undefined values.
608  *
609  */
610 static range_pair_t*
611 get_range(char *rngstr)
612 {
613         gchar **split;
614         range_pair_t *rng;
615
616         split = g_strsplit((gchar*)rngstr,"-",2);
617
618         /* empty string */
619         if (split[0] == NULL) {
620           g_strfreev(split);
621           return NULL;
622         }
623
624         /* means we have a non empty string
625          * which does not contain a delimiter */
626         if (split[1] == NULL) {
627           g_strfreev(split);
628           return NULL;
629         }
630
631         rng = (range_pair_t *)g_malloc(sizeof(range_pair_t));
632
633         /* string == "X-?" */
634         if (*(split[0]) != '\0') {
635             rng->floor = (gint)strtol(split[0],NULL,10);
636         } else
637           /* string == "-?" */
638           rng->floor = G_MININT;
639
640         /* string != "?-" */
641         if (*(split[1]) != '\0') {
642           rng->ceil  = (gint)strtol(split[1],NULL,10);
643         } else
644           /* string == "?-" */
645           rng->ceil = G_MAXINT;
646
647         g_strfreev(split);
648
649         return rng;
650 }
651
652
653 extern int
654 stats_tree_create_range_node(stats_tree *st, const gchar *name, int parent_id, ...)
655 {
656         va_list list;
657         gchar *curr_range;
658         stat_node *rng_root = new_stat_node(st, name, parent_id, FALSE, TRUE);
659         stat_node *range_node = NULL;
660
661         va_start( list, parent_id );
662         while (( curr_range = va_arg(list, gchar*) )) {
663                 range_node = new_stat_node(st, curr_range, rng_root->id, FALSE, FALSE);
664                 range_node->rng = get_range(curr_range);
665         }
666         va_end( list );
667
668         return rng_root->id;
669 }
670
671 extern int 
672 stats_tree_create_range_node_string(stats_tree *st, const gchar *name,
673                                     int parent_id, int num_str_ranges,
674                                     gchar** str_ranges)
675 {
676         int i;
677         stat_node *rng_root = new_stat_node(st, name, parent_id, FALSE, TRUE);
678         stat_node *range_node = NULL;
679
680         for (i = 0; i < num_str_ranges; i++) {
681                 range_node = new_stat_node(st, str_ranges[i], rng_root->id, FALSE, FALSE);
682                 range_node->rng = get_range(str_ranges[i]);
683         }
684
685         return rng_root->id;
686 }
687
688 /****/
689 extern int
690 stats_tree_parent_id_by_name(stats_tree *st, const gchar *parent_name)
691 {
692         stat_node *node = (stat_node *)g_hash_table_lookup(st->names,parent_name);
693
694         if (node)
695                 return node->id;
696         else
697                 return 0; /* XXX: this is the root shoud we return -1 instead?*/
698 }
699
700
701 extern int
702 stats_tree_range_node_with_pname(stats_tree *st, const gchar *name,
703                                  const gchar *parent_name, ...)
704 {
705         va_list list;
706         gchar *curr_range;
707         stat_node *range_node = NULL;
708         int parent_id = stats_tree_parent_id_by_name(st,parent_name);
709         stat_node *rng_root = new_stat_node(st, name, parent_id, FALSE, TRUE);
710
711         va_start( list, parent_name );
712         while (( curr_range = va_arg(list, gchar*) )) {
713                 range_node = new_stat_node(st, curr_range, rng_root->id, FALSE, FALSE);
714                 range_node->rng = get_range(curr_range);
715         }
716         va_end( list );
717
718         return rng_root->id;
719 }
720
721
722 extern int
723 stats_tree_tick_range(stats_tree *st, const gchar *name, int parent_id,
724                       int value_in_range)
725 {
726
727         stat_node *node = NULL;
728         stat_node *parent = NULL;
729         stat_node *child = NULL;
730         gint floor, ceil;
731
732         if (parent_id >= 0 && parent_id < (int) st->parents->len) {
733                 parent = (stat_node *)g_ptr_array_index(st->parents,parent_id);
734         } else {
735                 g_assert_not_reached();
736         }
737
738         if( parent->hash ) {
739                 node = (stat_node *)g_hash_table_lookup(parent->hash,name);
740         } else {
741                 node = (stat_node *)g_hash_table_lookup(st->names,name);
742         }
743
744         if ( node == NULL )
745                 g_assert_not_reached();
746
747         for ( child = node->children; child; child = child->next) {
748                 floor =  child->rng->floor;
749                 ceil = child->rng->ceil;
750
751                 if ( value_in_range >= floor && value_in_range <= ceil ) {
752                         child->counter++;
753                         return node->id;
754                 }
755         }
756
757         return node->id;
758 }
759
760 extern int
761 stats_tree_create_pivot(stats_tree *st, const gchar *name, int parent_id)
762 {
763         stat_node *node = new_stat_node(st,name,parent_id,TRUE,TRUE);
764
765         if (node)
766                 return node->id;
767         else
768                 return 0;
769 }
770
771 extern int
772 stats_tree_create_pivot_by_pname(stats_tree *st, const gchar *name,
773                                  const gchar *parent_name)
774 {
775         int parent_id = stats_tree_parent_id_by_name(st,parent_name);
776         stat_node *node;
777
778         node = new_stat_node(st,name,parent_id,TRUE,TRUE);
779
780         if (node)
781                 return node->id;
782         else
783                 return 0;
784 }
785
786 extern int
787 stats_tree_tick_pivot(stats_tree *st, int pivot_id, const gchar *pivot_value)
788 {
789
790         stat_node *parent = (stat_node *)g_ptr_array_index(st->parents,pivot_id);
791
792         parent->counter++;
793         stats_tree_manip_node( MN_INCREASE, st, pivot_value, pivot_id, FALSE, 1);
794
795         return pivot_id;
796 }
797