1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 2002-2012 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 
22 static const char stats_usage[] =
23 "[+PLUGIN?\findex\f]"
24 "[+DESCRIPTION?The stats query lists the sum, average, unbiased standard"
25 "	deviation, and minimum and maximum range values of the numeric"
26 "	\afield\a operands. If no operands are specified then all numeric"
27 "	fields are assumed. If \b-\b is specified then only records or"
28 "	groups are counted. If any of \b--average\b, \b--count\b,"
29 "	\b--deviation\b, \b--range\b or \b--sum\b are specified then only"
30 "	those values are listed.]"
31 "[a:average?List the average.]"
32 "[c:count?List the record count.]"
33 "[d:deviation?List the unbiased standard deviation.]"
34 "[g:group?Group by values in \afield\a. More than one \b--group\b may be "
35     "specified; grouping is done by the tuple of all \afield\a "
36     "values.]:[field]"
37 "[l:label?Label the output with \alabel\a.]:[label]"
38 "[m:maxgroups?Maximum number of groupings. Values beyond \amax\a are"
39 "	listed as \bOVERFLOW\b.]#[max:=100]"
40 "[p:print?Print summary data according to \aformat\a. The format fields"
41 "	are:]:[format]{\ffields\f}"
42 "[r:range?List the minimum and maximum values.]"
43 "[s:sum?List the sum.]"
44 "\n"
45 "\n[ field ... ]\n"
46 "\n"
47 ;
48 
49 #include <dsslib.h>
50 #include <ast_float.h>
51 
52 #define FW		15
53 
54 #define STATS_AVERAGE	0x0001
55 #define STATS_COUNT	0x0002
56 #define STATS_DEVIATION	0x0004
57 #define STATS_FIELD	0x0008
58 #define STATS_GROUP	0x0010
59 #define STATS_MAX	0x0020
60 #define STATS_MIN	0x0040
61 #define STATS_SUM	0x0080
62 
63 #define STATS_RANGE	(STATS_MAX|STATS_MIN)
64 
65 struct Bucket_s; typedef struct Bucket_s Bucket_t;
66 struct Field_s; typedef struct Field_s Field_t;
67 struct Group_s; typedef struct Group_s Group_t;
68 struct Print_s; typedef struct Print_s Print_t;
69 struct State_s; typedef struct State_s State_t;
70 struct Total_s; typedef struct Total_s Total_t;
71 
72 struct Total_s
73 {
74 	Cxnumber_t	value;
75 	Cxnumber_t	square;
76 	Cxnumber_t	min;
77 	Cxnumber_t	max;
78 	Cxunsigned_t	count;
79 };
80 
81 struct Bucket_s
82 {
83 	Dtlink_t	link;
84 	Cxoperand_t*	key;
85 	Total_t		total[1];
86 };
87 
88 struct Group_s
89 {
90 	Group_t*	next;
91 	Cxvariable_t*	variable;
92 	int		string;
93 	int		width;
94 };
95 
96 struct Field_s
97 {
98 	Field_t*	next;
99 	Cxvariable_t*	variable;
100 };
101 
102 struct Print_s
103 {
104 	Field_t*	field;
105 	Group_t*	group;
106 	const char*	label;
107 	Total_t*	total;
108 	Cxoperand_t*	key;
109 };
110 
111 struct State_s
112 {
113 	Dtdisc_t	bucketdisc;
114 	Field_t*	field;
115 	char*		format;
116 	Cx_t*		print;
117 	Group_t*	group;
118 	char*		label;
119 	Cxoperand_t*	key;
120 	Dt_t*		buckets;
121 	Total_t*	total;
122 	Vmalloc_t*	vm;
123 	int		fields;
124 	int		fw;
125 	int		groups;
126 	int		maxgroups;
127 	int		op;
128 };
129 
130 static Cxvariable_t variables[] =
131 {
132 CXV("AVERAGE",  "number", STATS_AVERAGE,   "Average value.")
133 CXV("COUNT",    "number", STATS_COUNT,     "Number of values.")
134 CXV("DEVIATION","number", STATS_DEVIATION, "Unbiased standard deviation.")
135 CXV("FIELD",    "string", STATS_FIELD,     "Field name.")
136 CXV("GROUP",    "string", STATS_GROUP,     "Group (key) values.")
137 CXV("MAX",      "number", STATS_MAX,       "Maximum value.")
138 CXV("MIN",      "number", STATS_MIN,       "Minimum value.")
139 CXV("SUM",      "number", STATS_SUM,       "Sum of values.")
140 };
141 
142 extern Dsslib_t	dss_lib_stats;
143 
144 static int
getop(Cx_t * cx,Cxinstruction_t * pc,Cxoperand_t * r,Cxoperand_t * a,Cxoperand_t * b,void * data,Cxdisc_t * disc)145 getop(Cx_t* cx, Cxinstruction_t* pc, Cxoperand_t* r, Cxoperand_t* a, Cxoperand_t* b, void* data, Cxdisc_t* disc)
146 {
147 	register State_t*	state = (State_t*)((Dssrecord_t*)data)->data;
148 	Cxvariable_t*		variable = pc->data.variable;
149 	Group_t*		group;
150 	char*			s;
151 	char*			v;
152 
153 	if (variable->data == (void*)&variables[0])
154 	{
155 		state->op |= variable->index;
156 		if (variable->format.delimiter <= 0)
157 			variable->format.delimiter = ':';
158 		if (a && (s = a->value.string.data))
159 			do
160 			{
161 				while (*s == ':')
162 					s++;
163 				for (v = s; *s && *s != ':'; s++);
164 				if (strneq(v, "delimiter=", 10))
165 					variable->format.delimiter = v[10];
166 				else if (strneq(v, "nodelimiter", 11))
167 					variable->format.delimiter = 0;
168 			} while (*s);
169 	}
170 	else
171 	{
172 		group = state->group;
173 		for (;;)
174 		{
175 			if (!group)
176 			{
177 				if (disc->errorf)
178 					(*disc->errorf)(cx, disc, 2, "%s: unknown print variable", variable->name);
179 				return -1;
180 			}
181 			if (group->variable == variable)
182 				break;
183 			group = group->next;
184 		}
185 	}
186 	r->value.string.data = "";
187 	r->value.string.size = 0;
188 	return 0;
189 }
190 
191 static int
getvalue(Cx_t * cx,Cxinstruction_t * pc,Cxoperand_t * r,Cxoperand_t * a,Cxoperand_t * b,void * data,Cxdisc_t * disc)192 getvalue(Cx_t* cx, Cxinstruction_t* pc, Cxoperand_t* r, Cxoperand_t* a, Cxoperand_t* b, void* data, Cxdisc_t* disc)
193 {
194 	register Print_t*	print = (Print_t*)((Dssrecord_t*)data)->data;
195 	Cxvariable_t*		variable = pc->data.variable;
196 	char*			s;
197 	Group_t*		group;
198 	Cxoperand_t*		key;
199 	Cxoperand_t		arg;
200 	Cxnumber_t		u;
201 	int			sep;
202 
203 	if (variable->data == (void*)&variables[0])
204 		switch (variable->index)
205 		{
206 		case STATS_AVERAGE:
207 			r->value.number = print->total->count ? print->total->value / (Cxinteger_t)print->total->count : 0;
208 			break;
209 		case STATS_COUNT:
210 			r->value.number = (Cxinteger_t)print->total->count;
211 			break;
212 		case STATS_DEVIATION:
213 			if (print->total->count)
214 			{
215 				u = print->total->value / (Cxinteger_t)print->total->count;
216 				if ((u = print->total->square + u * (u - 2 * print->total->value)) < 0)
217 					u = -u;
218 				if (print->total->count > 1)
219 					u /= (Cxinteger_t)(print->total->count - 1);
220 				r->value.number = sqrt(u);
221 			}
222 			else
223 				r->value.number = 0;
224 			break;
225 		case STATS_FIELD:
226 			if (!print->field || !(r->value.string.data = (char*)print->field->variable->name))
227 				r->value.string.data = "-";
228 			r->value.string.size = strlen(r->value.string.data);
229 			break;
230 		case STATS_GROUP:
231 			sfstrseek(cx->buf, SEEK_SET, 0);
232 			if (key = print->key)
233 				for (sep = 0, group = print->group; group; group = group->next, key++)
234 				{
235 					if (sep)
236 						sfputc(cx->buf, sep);
237 					else
238 						sep = variable->format.delimiter;
239 					if (group->string)
240 						s = key->value.string.data;
241 					else
242 					{
243 						arg = *key;
244 						if (cxcast(cx, &arg, 0, cx->state->type_string, NiL, group->variable->format.details))
245 							return -1;
246 						s = arg.value.string.data;
247 					}
248 					sfprintf(cx->buf, "%s", s);
249 				}
250 			else if (print->label)
251 				sfprintf(cx->buf, "%s", print->label);
252 			r->value.string.size = sfstrtell(cx->buf);
253 			r->value.string.data = sfstruse(cx->buf);
254 			break;
255 		case STATS_MAX:
256 			r->value.number = print->total->max;
257 			break;
258 		case STATS_MIN:
259 			r->value.number = print->total->min;
260 			break;
261 		case STATS_SUM:
262 			r->value.number = print->total->value;
263 			break;
264 		}
265 	else if (key = print->key)
266 	{
267 		for (group = print->group; group->variable != variable; group = group->next, key++);
268 		r->value = key->value;
269 	}
270 	else
271 		memset(&r->value, 0, sizeof(r->value));
272 	return 0;
273 }
274 
275 static int
bucketcmp(Dt_t * dt,void * a,void * b,Dtdisc_t * disc)276 bucketcmp(Dt_t* dt, void* a, void* b, Dtdisc_t* disc)
277 {
278 	register State_t*	state = (State_t*)disc;
279 	register Cxoperand_t*	ka = (Cxoperand_t*)a;
280 	register Cxoperand_t*	kb = (Cxoperand_t*)b;
281 	register Group_t*	group;
282 	register int		n;
283 
284 	for (group = state->group; group; group = group->next, ka++, kb++)
285 	{
286 		if (group->string)
287 		{
288 			n = ka->value.string.size < kb->value.string.size ? ka->value.string.size : kb->value.string.size;
289 			if (n = memcmp(ka->value.string.data, kb->value.string.data, n))
290 				return n;
291 			if (ka->value.string.size < kb->value.string.size)
292 				return -1;
293 			if (ka->value.string.size > kb->value.string.size)
294 				return 1;
295 		}
296 		else if (ka->value.number < kb->value.number)
297 			return -1;
298 		else if (ka->value.number > kb->value.number)
299 			return 1;
300 	}
301 	return 0;
302 }
303 
304 static int
stats_beg(Cx_t * cx,Cxexpr_t * expr,void * data,Cxdisc_t * disc)305 stats_beg(Cx_t* cx, Cxexpr_t* expr, void* data, Cxdisc_t* disc)
306 {
307 	char**		argv = (char**)data;
308 	int		errors = error_info.errors;
309 	int		all;
310 	int		i;
311 	char*		s;
312 	State_t*	state;
313 	Cxvariable_t*	variable;
314 	Field_t*	field;
315 	Field_t*	lastfield;
316 	Group_t*	group;
317 	Group_t*	lastgroup;
318 	Vmalloc_t*	vm;
319 	Dssrecord_t	record;
320 	Sfio_t*		tmp;
321 
322 	if (!(vm = vmopen(Vmdcheap, Vmlast, 0)) || !(state = vmnewof(vm, 0, State_t, 1, 0)))
323 	{
324 		if (vm)
325 			vmclose(vm);
326 		if (disc->errorf)
327 			(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
328 		return -1;
329 	}
330 	state->vm = vm;
331 	state->maxgroups = 100;
332 	if (!(state->print = cxopen(0, 0, disc)))
333 	{
334 		if (disc->errorf)
335 			(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
336 		goto bad;
337 	}
338 	if (!cxscope(state->print, cx, 0, 0, disc))
339 	{
340 		cxclose(state->print);
341 		goto bad;
342 	}
343 	for (i = 0; i < elementsof(variables); i++)
344 	{
345 		if (cxaddvariable(state->print, &variables[i], disc))
346 			goto bad;
347 		variables[i].data = &variables[0];
348 	}
349 	sfprintf(cx->buf, "%s%s", strchr(dss_lib_stats.description, '['), stats_usage);
350 	s = sfstruse(cx->buf);
351 	for (;;)
352 	{
353 		switch (optget(argv, s))
354 		{
355 		case 'a':
356 			state->op |= STATS_AVERAGE;
357 			continue;
358 		case 'c':
359 			state->op |= STATS_COUNT;
360 			continue;
361 		case 'd':
362 			state->op |= STATS_DEVIATION;
363 			continue;
364 		case 'g':
365 			if (!(variable = cxvariable(cx, opt_info.arg, NiL, disc)))
366 				goto bad;
367 			if (!(group = vmnewof(vm, 0, Group_t, 1, 0)))
368 			{
369 				if (disc->errorf)
370 					(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
371 				goto bad;
372 			}
373 			group->variable = variable;
374 			group->string = cxisstring(variable->type) || cxisbuffer(variable->type);
375 			if (state->group)
376 				lastgroup = lastgroup->next = group;
377 			else
378 				lastgroup = state->group = group;
379 			state->groups++;
380 			continue;
381 		case 'l':
382 			if (!(state->label = vmstrdup(vm, opt_info.arg)))
383 			{
384 				if (disc->errorf)
385 					(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
386 				goto bad;
387 			}
388 			continue;
389 		case 'm':
390 			state->maxgroups = opt_info.num;
391 			continue;
392 		case 'p':
393 			if (!(state->format = vmstrdup(vm, opt_info.arg)))
394 			{
395 				if (disc->errorf)
396 					(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
397 				goto bad;
398 			}
399 			continue;
400 		case 'r':
401 			state->op |= STATS_RANGE;
402 			continue;
403 		case 's':
404 			state->op |= STATS_SUM;
405 			continue;
406 		case '?':
407 			if (disc->errorf)
408 			{
409 				(*disc->errorf)(cx, disc, ERROR_USAGE|4, "%s", opt_info.arg);
410 			}
411 			else
412 				goto bad;
413 			continue;
414 		case ':':
415 			if (disc->errorf)
416 				(*disc->errorf)(cx, disc, 2, "%s", opt_info.arg);
417 			else
418 				goto bad;
419 			continue;
420 		}
421 		break;
422 	}
423 	if (error_info.errors > errors)
424 		goto bad;
425 	argv += opt_info.index;
426 	if (!state->op)
427 		state->op = ~0;
428 	if (all = !*argv)
429 		variable = 0;
430 	state->fw = 5;
431 	do
432 	{
433 		if (all)
434 		{
435 			do
436 			{
437 				variable = (Cxvariable_t*)(variable ? dtnext(cx->fields, variable) : dtfirst(cx->fields));
438 			} while (variable && (variable->data == (void*)&variables[0] || !cxisnumber(variable->type)));
439 			if (!variable)
440 				break;
441 		}
442 		else if (streq(*argv, "-"))
443 			continue;
444 		else if (!(variable = cxvariable(cx, *argv, NiL, disc)))
445 			goto bad;
446 		else if (!cxisnumber(variable->type))
447 		{
448 			if (disc->errorf)
449 				(*disc->errorf)(cx, disc, 2, "%s: not a numeric field", variable->name);
450 			goto bad;
451 		}
452 		if (!(field = vmnewof(vm, 0, Field_t, 1, 0)))
453 		{
454 			if (disc->errorf)
455 				(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
456 			goto bad;
457 		}
458 		field->variable = variable;
459 		if (state->field)
460 			lastfield = lastfield->next = field;
461 		else
462 			lastfield = state->field = field;
463 		state->fields++;
464 		if (state->fw < (i = (int)strlen(variable->name)))
465 			state->fw = i;
466 	} while (all || *++argv);
467 	if (!state->fields)
468 	{
469 		if (all)
470 		{
471 			if (disc->errorf)
472 				(*disc->errorf)(cx, disc, 2, "no numeric fields");
473 			goto bad;
474 		}
475 		state->fields = 1;
476 	}
477 	if (!(state->total = vmnewof(vm, 0, Total_t, state->fields, 0)))
478 	{
479 		if (disc->errorf)
480 			(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
481 		goto bad;
482 	}
483 	if (state->group)
484 	{
485 		state->bucketdisc.comparf = bucketcmp;
486 		state->bucketdisc.link = offsetof(Bucket_t, link);
487 		state->bucketdisc.size = -1;
488 		state->bucketdisc.key = offsetof(Bucket_t, key);
489 		if (!(state->buckets = dtnew(vm, &state->bucketdisc, Dtoset)))
490 		{
491 			if (disc->errorf)
492 				(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
493 			goto bad;
494 		}
495 	}
496 	if (state->format)
497 	{
498 		if (!(tmp = sfstropen()))
499 		{
500 			if (disc->errorf)
501 				(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
502 			goto bad;
503 		}
504 		state->print->getf = getop;
505 		record.data = state;
506 		i = dssprintf(DSS(cx), state->print, tmp, state->format, &record);
507 		state->print->getf = getvalue;
508 		sfclose(tmp);
509 		if (i < 0)
510 			goto bad;
511 	}
512 	expr->data = state;
513 	return 0;
514  bad:
515 	if (state->print)
516 		cxscope(state->print, NiL, 0, 0, disc);
517 	vmclose(vm);
518 	return -1;
519 }
520 
521 static int
stats_act(Cx_t * cx,Cxexpr_t * expr,void * data,Cxdisc_t * disc)522 stats_act(Cx_t* cx, Cxexpr_t* expr, void* data, Cxdisc_t* disc)
523 {
524 	register State_t*	state = (State_t*)expr->data;
525 	register Cxoperand_t*	key;
526 	register Field_t*	field;
527 	register Group_t*	group;
528 	register Total_t*	total;
529 	Bucket_t*		bucket;
530 	Cxoperand_t		val;
531 	char*			s;
532 	int			range;
533 	int			square;
534 
535 	total = state->total;
536 	if (state->group)
537 	{
538 		if (!state->key && !(state->key = vmnewof(state->vm, 0, Cxoperand_t, state->groups, 0)))
539 		{
540 			if (disc->errorf)
541 				(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
542 			return -1;
543 		}
544 		for (group = state->group, key = state->key; group; group = group->next, key++)
545 			if (cxcast(cx, key, group->variable, group->variable->type, data, NiL))
546 				return -1;
547 		if (bucket = (Bucket_t*)dtmatch(state->buckets, state->key))
548 			total = bucket->total;
549 		else if (dtsize(state->buckets) < state->maxgroups)
550 		{
551 			if (!(bucket = vmnewof(state->vm, 0, Bucket_t, 1, (state->fields - 1) * sizeof(Total_t))))
552 			{
553 				if (disc->errorf)
554 					(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
555 				return -1;
556 			}
557 			for (group = state->group, key = state->key; group; group = group->next, key++)
558 				if (group->string)
559 				{
560 					s = key->value.string.data;
561 					if (!(key->value.string.data = vmnewof(state->vm, 0, char, key->value.string.size, 1)))
562 					{
563 						if (disc->errorf)
564 							(*disc->errorf)(cx, disc, ERROR_SYSTEM|2, "out of space");
565 						return -1;
566 					}
567 					memcpy(key->value.string.data, s, key->value.string.size);
568 				}
569 			bucket->key = state->key;
570 			state->key = 0;
571 			dtinsert(state->buckets, bucket);
572 			total = bucket->total;
573 		}
574 	}
575 	if (!state->field)
576 		total->count++;
577 	else
578 	{
579 		range = !!(state->op & STATS_RANGE);
580 		square = !!(state->op & STATS_DEVIATION);
581 		for (field = state->field; field; field = field->next)
582 		{
583 			if (cxcast(cx, &val, field->variable, cx->state->type_number, data, NiL))
584 				return -1;
585 			total->count++;
586 			total->value += val.value.number;
587 			if (range)
588 			{
589 				if (total->min > val.value.number || total->count == 1)
590 					total->min = val.value.number;
591 				if (total->max < val.value.number || total->count == 1)
592 					total->max = val.value.number;
593 			}
594 			if (square)
595 				total->square += val.value.number * val.value.number;
596 			total++;
597 		}
598 	}
599 	return 0;
600 }
601 
602 static void
number(Sfio_t * op,Cxnumber_t n,int fw)603 number(Sfio_t* op, Cxnumber_t n, int fw)
604 {
605 	if (n == 0 || ((n >= 0) ? n : -n) >= 1 && n >= FLTMAX_INTMAX_MIN && n <= FLTMAX_UINTMAX_MAX && n == (Cxinteger_t)n)
606 		sfprintf(op, " %*I*u", fw, sizeof(Cxinteger_t), (Cxinteger_t)n);
607 	else
608 	{
609 		if (n >= 0)
610 			sfputc(op, ' ');
611 		sfprintf(op, " %1.*I*e", fw - 7, sizeof(n), n);
612 	}
613 }
614 
615 static int
list(Cx_t * cx,register State_t * state,Sfio_t * op,const char * label,register Field_t * field,register Total_t * total,Cxoperand_t * key)616 list(Cx_t* cx, register State_t* state, Sfio_t* op, const char* label, register Field_t* field, register Total_t* total, Cxoperand_t* key)
617 {
618 	Dssrecord_t	record;
619 	Print_t		pr;
620 	Cxnumber_t	u;
621 	Cxoperand_t	arg;
622 	Group_t*	group;
623 	char*		s;
624 
625 	do
626 	{
627 		if (state->format)
628 		{
629 			record.data = &pr;
630 			pr.field = field;
631 			pr.group = state->group;
632 			pr.label = label;
633 			pr.total = total;
634 			pr.key = key;
635 			if (dssprintf(DSS(cx), state->print, op, state->format, &record) < 0)
636 				return -1;
637 		}
638 		else
639 		{
640 			if (field)
641 				sfprintf(op, "%*s", state->fw, field->variable->name);
642 			if (state->op & STATS_COUNT)
643 				number(op, (Cxinteger_t)total->count, FW);
644 			u = total->value / (Cxinteger_t)total->count;
645 			if (state->op & STATS_SUM)
646 				number(op, total->value, FW);
647 			if (state->op & STATS_AVERAGE)
648 				number(op, u, FW);
649 			if (state->op & STATS_DEVIATION)
650 			{
651 				if ((u = total->square + u * (u - 2 * total->value)) < 0)
652 					u = -u;
653 				if (total->count > 1)
654 					u /= (Cxinteger_t)(total->count - 1);
655 				number(op, (Cxnumber_t)sqrt(u), FW);
656 			}
657 			if (state->op & STATS_RANGE)
658 			{
659 				number(op, total->min, FW);
660 				number(op, total->max, FW);
661 			}
662 			if (label)
663 			{
664 				sfprintf(op, " %s", label);
665 				label = 0;
666 			}
667 			else if (key)
668 			{
669 				for (group = state->group; group; group = group->next, key++)
670 				{
671 					sfputc(op, ' ');
672 					if (group->string)
673 						s = key->value.string.data;
674 					else
675 					{
676 						arg = *key;
677 						if (cxcast(cx, &arg, 0, cx->state->type_string, NiL, group->variable->format.details))
678 							return -1;
679 						s = arg.value.string.data;
680 					}
681 					sfprintf(op, "%*s", group->width, s);
682 				}
683 				key = 0;
684 			}
685 			sfprintf(op, "\n");
686 		}
687 		total++;
688 	} while (field && (field = field->next));
689 	return 0;
690 }
691 
692 static int
stats_end(Cx_t * cx,Cxexpr_t * expr,void * data,Cxdisc_t * disc)693 stats_end(Cx_t* cx, Cxexpr_t* expr, void* data, Cxdisc_t* disc)
694 {
695 	register State_t*	state = (State_t*)expr->data;
696 	register Bucket_t*	bucket;
697 	register Group_t*	group;
698 
699 	if (state->label)
700 		sfprintf(expr->op, "%s\n", state->label);
701 	if (!state->format)
702 	{
703 		if (state->field)
704 			sfprintf(expr->op, "%*s", state->fw, "FIELD");
705 		if (state->op & STATS_COUNT)
706 			sfprintf(expr->op, " %*s", FW, "COUNT");
707 		if (state->op & STATS_SUM)
708 			sfprintf(expr->op, " %*s", FW, "SUM");
709 		if (state->op & STATS_AVERAGE)
710 			sfprintf(expr->op, " %*s", FW, "AVERAGE");
711 		if (state->op & STATS_DEVIATION)
712 			sfprintf(expr->op, " %*s", FW, "DEVIATION");
713 		if (state->op & STATS_RANGE)
714 			sfprintf(expr->op, " %*s %*s", FW, "MIN", FW, "MAX");
715 		if (state->buckets)
716 			for (group = state->group; group; group = group->next)
717 			{
718 				group->width = group->variable->format.print ? group->variable->format.print : group->variable->type->format.print ? group->variable->type->format.print : FW;
719 				sfprintf(expr->op, " %*s", group->width, group->variable->name);
720 			}
721 		sfprintf(expr->op, "\n");
722 	}
723 	if (state->buckets)
724 	{
725 		for (bucket = (Bucket_t*)dtfirst(state->buckets); bucket; bucket = (Bucket_t*)dtnext(state->buckets, bucket))
726 			if (list(cx, state, expr->op, NiL, state->field, bucket->total, bucket->key))
727 				goto bad;
728 		if (state->total->count && list(cx, state, expr->op, "OVERFLOW", state->field, state->total, NiL))
729 			goto bad;
730 	}
731 	else if (list(cx, state, expr->op, NiL, state->field, state->total, NiL))
732 		goto bad;
733 	vmclose(state->vm);
734 	return 0;
735  bad:
736 	vmclose(state->vm);
737 	return -1;
738 }
739 
740 static Cxquery_t	queries[] =
741 {
742 	{
743 		"stats",
744 		"collect numeric field value statistics",
745 		CXH,
746 		stats_beg,
747 		0,
748 		stats_act,
749 		stats_end
750 	},
751 	{0}
752 };
753 
754 Dsslib_t		dss_lib_stats =
755 {
756 	"stats",
757 	"stats query"
758 	"[-1lms5P?\n@(#)$Id: dss stats query (AT&T Research) 2011-09-11 $\n]"
759 	USAGE_LICENSE,
760 	CXH,
761 	0,
762 	0,
763 	0,
764 	0,
765 	0,
766 	0,
767 	&queries[0]
768 };
769