Line data Source code
1 : /* FreeTDS - Library of routines accessing Sybase and Microsoft databases
2 : * Copyright (C) 2011 James K. Lowden
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Library General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Library General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Library General Public
15 : * License along with this library; if not, write to the
16 : * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 : * Boston, MA 02111-1307, USA.
18 : */
19 :
20 : #include <config.h>
21 :
22 : #include <stdarg.h>
23 :
24 : #include <freetds/time.h>
25 :
26 : #include <assert.h>
27 : #include <stdio.h>
28 :
29 : #if HAVE_STDLIB_H
30 : #include <stdlib.h>
31 : #endif /* HAVE_STDLIB_H */
32 :
33 : #if HAVE_STRING_H
34 : #include <string.h>
35 : #endif /* HAVE_STRING_H */
36 :
37 : #if HAVE_UNISTD_H
38 : #include <unistd.h>
39 : #endif /* HAVE_UNISTD_H */
40 :
41 : #if HAVE_ERRNO_H
42 : # include <errno.h>
43 : #endif /* HAVE_ERRNO_H */
44 :
45 :
46 : #include <freetds/tds.h>
47 : #include <freetds/thread.h>
48 : #include <freetds/convert.h>
49 : #include <freetds/utils/string.h>
50 : #include <freetds/replacements.h>
51 : #include <sybfront.h>
52 : #include <sybdb.h>
53 : #include <syberror.h>
54 : #include <dblib.h>
55 :
56 : #define TDS_FIND(k,b,c) tds_find(k, b, TDS_VECTOR_SIZE(b), sizeof(b[0]), c)
57 :
58 : typedef bool (*compare_func)(const void *, const void *);
59 :
60 : static void *
61 : tds_find(const void *key, const void *base, size_t nelem, size_t width,
62 : compare_func compar)
63 : {
64 : size_t n;
65 175551 : char *p = (char *) base;
66 :
67 175551 : for (n = nelem; n != 0; --n) {
68 0 : if (compar(key, p))
69 : return p;
70 0 : p += width;
71 : }
72 : return NULL;
73 : }
74 :
75 :
76 : struct col_t
77 : {
78 : size_t len;
79 : TDS_SERVER_TYPE type;
80 : int null_indicator;
81 : char *s;
82 : union {
83 : DBTINYINT ti;
84 : DBSMALLINT si;
85 : DBINT i;
86 : DBREAL r;
87 : DBFLT8 f;
88 : } data;
89 : };
90 :
91 : static TDS_SERVER_TYPE infer_col_type(int sybtype);
92 :
93 : static struct col_t *
94 0 : col_init(struct col_t *pcol, int sybtype, int collen)
95 : {
96 0 : assert(pcol);
97 :
98 0 : pcol->type = infer_col_type(sybtype);
99 0 : if (pcol->type == TDS_INVALID_TYPE)
100 : return NULL;
101 0 : pcol->len = collen;
102 0 : pcol->s = NULL;
103 :
104 0 : switch (sybtype) {
105 0 : case 0:
106 0 : pcol->len = 0;
107 0 : return NULL;
108 0 : case SYBDATETIME:
109 : case SYBDATETIME4:
110 : case SYBDATETIMN:
111 0 : collen = 30;
112 : /* fall through */
113 0 : case SYBCHAR:
114 : case SYBVARCHAR:
115 : case SYBTEXT:
116 : case SYBNTEXT:
117 0 : pcol->len = collen;
118 0 : if ((pcol->s = tds_new(char, 1+collen)) == NULL) {
119 : return NULL;
120 : }
121 : break;
122 : }
123 : return pcol;
124 : }
125 :
126 : static void
127 0 : col_free(struct col_t *p)
128 : {
129 0 : free(p->s);
130 0 : memset(p, 0, sizeof(*p));
131 0 : }
132 :
133 : static bool
134 0 : col_equal(const struct col_t *pc1, const struct col_t *pc2)
135 : {
136 0 : assert( pc1 && pc2 );
137 0 : assert( pc1->type == pc2->type );
138 :
139 0 : switch (pc1->type) {
140 :
141 0 : case SYBCHAR:
142 : case SYBVARCHAR:
143 0 : if( pc1->len != pc2->len)
144 : return false;
145 0 : return strncmp(pc1->s, pc2->s, pc1->len) == 0;
146 0 : case SYBINT1:
147 : case SYBUINT1:
148 : case SYBSINT1:
149 0 : return pc1->data.ti == pc2->data.ti;
150 0 : case SYBINT2:
151 : case SYBUINT2:
152 0 : return pc1->data.si == pc2->data.si;
153 0 : case SYBINT4:
154 : case SYBUINT4:
155 0 : return pc1->data.i == pc2->data.i;
156 0 : case SYBFLT8:
157 0 : return pc1->data.f == pc2->data.f;
158 0 : case SYBREAL:
159 0 : return pc1->data.r == pc2->data.r;
160 :
161 : case SYBINTN:
162 : case SYBDATETIME:
163 : case SYBBIT:
164 : case SYBTEXT:
165 : case SYBNTEXT:
166 : case SYBIMAGE:
167 : case SYBMONEY4:
168 : case SYBMONEY:
169 : case SYBDATETIME4:
170 : case SYBBINARY:
171 : case SYBVOID:
172 : case SYBVARBINARY:
173 : case SYBBITN:
174 : case SYBNUMERIC:
175 : case SYBDECIMAL:
176 : case SYBFLTN:
177 : case SYBMONEYN:
178 : case SYBDATETIMN:
179 : case SYBMSTABLE:
180 : case SYBNVARCHAR:
181 : case SYBINT8:
182 : case XSYBCHAR:
183 : case XSYBVARCHAR:
184 : case XSYBNVARCHAR:
185 : case XSYBNCHAR:
186 : case XSYBVARBINARY:
187 : case XSYBBINARY:
188 : case SYBUNIQUE:
189 : case SYBVARIANT:
190 : case SYBMSUDT:
191 : case SYBMSXML:
192 : case SYBMSDATE:
193 : case SYBMSTIME:
194 : case SYBMSDATETIME2:
195 : case SYBMSDATETIMEOFFSET:
196 : case SYBLONGBINARY:
197 : case SYBUINT8:
198 : case SYBDATE:
199 : case SYBDATEN:
200 : case SYB5INT8:
201 : case SYBINTERVAL:
202 : case SYBTIME:
203 : case SYBTIMEN:
204 : case SYBUINTN:
205 : case SYBUNITEXT:
206 : case SYBXML:
207 : case SYB5BIGDATETIME:
208 : case SYB5BIGTIME:
209 :
210 0 : assert( false && pc1->type );
211 : break;
212 : }
213 : return false;
214 : }
215 :
216 : static void *
217 0 : col_buffer(struct col_t *pcol)
218 : {
219 0 : switch (pcol->type) {
220 :
221 0 : case SYBCHAR:
222 : case SYBVARCHAR:
223 0 : return pcol->s;
224 0 : case SYBINT1:
225 : case SYBUINT1:
226 : case SYBSINT1:
227 0 : return &pcol->data.ti;
228 0 : case SYBINT2:
229 : case SYBUINT2:
230 0 : return &pcol->data.si;
231 0 : case SYBINT4:
232 : case SYBUINT4:
233 0 : return &pcol->data.i;
234 0 : case SYBFLT8:
235 0 : return &pcol->data.f;
236 0 : case SYBREAL:
237 0 : return &pcol->data.r;
238 :
239 : case SYBINTN:
240 : case SYBDATETIME:
241 : case SYBBIT:
242 : case SYBTEXT:
243 : case SYBNTEXT:
244 : case SYBIMAGE:
245 : case SYBMONEY4:
246 : case SYBMONEY:
247 : case SYBDATETIME4:
248 : case SYBBINARY:
249 : case SYBVOID:
250 : case SYBVARBINARY:
251 : case SYBBITN:
252 : case SYBNUMERIC:
253 : case SYBDECIMAL:
254 : case SYBFLTN:
255 : case SYBMONEYN:
256 : case SYBDATETIMN:
257 : case SYBMSTABLE:
258 : case SYBNVARCHAR:
259 : case SYBINT8:
260 : case XSYBCHAR:
261 : case XSYBVARCHAR:
262 : case XSYBNVARCHAR:
263 : case XSYBNCHAR:
264 : case XSYBVARBINARY:
265 : case XSYBBINARY:
266 : case SYBUNIQUE:
267 : case SYBVARIANT:
268 : case SYBMSUDT:
269 : case SYBMSXML:
270 : case SYBMSDATE:
271 : case SYBMSTIME:
272 : case SYBMSDATETIME2:
273 : case SYBMSDATETIMEOFFSET:
274 : case SYBLONGBINARY:
275 : case SYBUINT8:
276 : case SYBDATE:
277 : case SYBDATEN:
278 : case SYB5INT8:
279 : case SYBINTERVAL:
280 : case SYBTIME:
281 : case SYBTIMEN:
282 : case SYBUINTN:
283 : case SYBUNITEXT:
284 : case SYBXML:
285 : case SYB5BIGDATETIME:
286 : case SYB5BIGTIME:
287 0 : assert( false && pcol->type );
288 : break;
289 : }
290 : return NULL;
291 :
292 : }
293 :
294 : #if 0
295 : static int
296 : col_print(FILE* out, const struct col_t *pcol)
297 : {
298 : char *fmt;
299 :
300 : switch (pcol->type) {
301 :
302 : case SYBCHAR:
303 : case SYBVARCHAR:
304 : return (int) fwrite(pcol->s, pcol->len, 1, out);
305 : case SYBINT1:
306 : return fprintf(out, "%d", (int)pcol->ti);
307 : case SYBINT2:
308 : return fprintf(out, "%d", (int)pcol->si);
309 : case SYBINT4:
310 : return fprintf(out, "%d", (int)pcol->i);
311 : case SYBFLT8:
312 : return fprintf(out, "%f", pcol->f);
313 : case SYBREAL:
314 : return fprintf(out, "%f", (double)pcol->r);
315 :
316 : case SYBINTN:
317 : case SYBDATETIME:
318 : case SYBBIT:
319 : case SYBTEXT:
320 : case SYBNTEXT:
321 : case SYBIMAGE:
322 : case SYBMONEY4:
323 : case SYBMONEY:
324 : case SYBDATETIME4:
325 : case SYBBINARY:
326 : case SYBVOID:
327 : case SYBVARBINARY:
328 : case SYBBITN:
329 : case SYBNUMERIC:
330 : case SYBDECIMAL:
331 : case SYBFLTN:
332 : case SYBMONEYN:
333 : case SYBDATETIMN:
334 : assert( false && pcol->type );
335 : break;
336 : }
337 : return false;
338 : }
339 : #endif
340 : static struct col_t *
341 0 : col_cpy(struct col_t *pdest, const struct col_t *psrc)
342 : {
343 0 : assert( pdest && psrc );
344 0 : assert( psrc->len > 0 || psrc->null_indicator == -1);
345 :
346 0 : memcpy(pdest, psrc, sizeof(*pdest));
347 :
348 0 : if (psrc->s) {
349 : assert(psrc->len >= 0);
350 0 : if ((pdest->s = tds_new(char, psrc->len)) == NULL)
351 : return NULL;
352 0 : memcpy(pdest->s, psrc->s, psrc->len);
353 : }
354 :
355 0 : assert( pdest->len > 0 || pdest->null_indicator == -1);
356 : return pdest;
357 : }
358 :
359 : static bool
360 : col_null( const struct col_t *pcol )
361 : {
362 : assert(pcol);
363 0 : return pcol->null_indicator == -1;
364 : }
365 :
366 : static char *
367 0 : string_value(const struct col_t *pcol)
368 : {
369 0 : char *output = NULL;
370 0 : int len = -1;
371 :
372 0 : switch (pcol->type) {
373 0 : case SYBCHAR:
374 : case SYBVARCHAR:
375 0 : if ((output = tds_new0(char, 1 + pcol->len)) == NULL)
376 : return NULL;
377 0 : strncpy(output, pcol->s, pcol->len);
378 0 : return output;
379 : break;
380 0 : case SYBINT1:
381 0 : len = asprintf(&output, "%d", (int)pcol->data.ti);
382 0 : break;
383 0 : case SYBINT2:
384 0 : len = asprintf(&output, "%d", (int)pcol->data.si);
385 0 : break;
386 0 : case SYBINT4:
387 0 : len = asprintf(&output, "%d", (int)pcol->data.i);
388 0 : break;
389 0 : case SYBFLT8:
390 0 : len = asprintf(&output, "%f", pcol->data.f);
391 0 : break;
392 0 : case SYBREAL:
393 0 : len = asprintf(&output, "%f", (double)pcol->data.r);
394 0 : break;
395 :
396 : default:
397 : case SYBINTN:
398 : case SYBDATETIME:
399 : case SYBBIT:
400 : case SYBTEXT:
401 : case SYBNTEXT:
402 : case SYBIMAGE:
403 : case SYBMONEY4:
404 : case SYBMONEY:
405 : case SYBDATETIME4:
406 : case SYBBINARY:
407 : case SYBVOID:
408 : case SYBVARBINARY:
409 : case SYBBITN:
410 : case SYBNUMERIC:
411 : case SYBDECIMAL:
412 : case SYBFLTN:
413 : case SYBMONEYN:
414 : case SYBDATETIMN:
415 0 : assert( false && pcol->type );
416 : return NULL;
417 : break;
418 : }
419 :
420 0 : return len >= 0? output : NULL;
421 : }
422 :
423 : static char *
424 0 : join(int argc, char *argv[], const char sep[])
425 : {
426 0 : size_t len = 0;
427 : char **p, *output;
428 :
429 0 : for (p=argv; p < argv + argc; p++) {
430 0 : len += strlen(*p);
431 : }
432 :
433 0 : len += 1 + argc * strlen(sep); /* allows one too many */
434 :
435 0 : output = tds_new0(char, len);
436 0 : if (!output)
437 : return NULL;
438 :
439 0 : for (p=argv; p < argv + argc; p++) {
440 0 : if (p != argv)
441 0 : strcat(output, sep);
442 0 : strcat(output, *p);
443 : }
444 : return output;
445 : }
446 :
447 : static TDS_SERVER_TYPE
448 0 : infer_col_type(int sybtype)
449 : {
450 0 : switch (sybtype) {
451 : case SYBCHAR:
452 : case SYBVARCHAR:
453 : case SYBTEXT:
454 : case SYBNTEXT:
455 : return SYBCHAR;
456 : case SYBDATETIME:
457 : case SYBDATETIME4:
458 : case SYBDATETIMN:
459 : return SYBCHAR;
460 0 : case SYBINT1:
461 : case SYBBIT:
462 : case SYBBITN:
463 0 : return SYBINT1;
464 0 : case SYBINT2:
465 0 : return SYBINT2;
466 0 : case SYBINT4:
467 : case SYBINTN:
468 0 : return SYBINT4;
469 0 : case SYBFLT8:
470 : case SYBMONEY4:
471 : case SYBMONEY:
472 : case SYBFLTN:
473 : case SYBMONEYN:
474 : case SYBNUMERIC:
475 : case SYBDECIMAL:
476 0 : return SYBFLT8;
477 0 : case SYBREAL:
478 0 : return SYBREAL;
479 :
480 : case SYBIMAGE:
481 : case SYBBINARY:
482 : case SYBVOID:
483 : case SYBVARBINARY:
484 0 : assert( false && sybtype );
485 : break;
486 : }
487 0 : return TDS_INVALID_TYPE;
488 : }
489 :
490 : static int
491 0 : bind_type(int sybtype)
492 : {
493 0 : switch (sybtype) {
494 : case SYBCHAR:
495 : case SYBVARCHAR:
496 : case SYBTEXT:
497 : case SYBNTEXT:
498 : case SYBDATETIME:
499 : case SYBDATETIME4:
500 : case SYBDATETIMN:
501 : return NTBSTRINGBIND;
502 0 : case SYBINT1:
503 : case SYBBIT:
504 : case SYBBITN:
505 0 : return TINYBIND;
506 0 : case SYBINT2:
507 0 : return SMALLBIND;
508 0 : case SYBINT4:
509 : case SYBINTN:
510 0 : return INTBIND;
511 0 : case SYBFLT8:
512 : case SYBMONEY4:
513 : case SYBMONEY:
514 : case SYBFLTN:
515 : case SYBMONEYN:
516 : case SYBNUMERIC:
517 : case SYBDECIMAL:
518 0 : return FLT8BIND;
519 0 : case SYBREAL:
520 0 : return REALBIND;
521 :
522 : case SYBIMAGE:
523 : case SYBBINARY:
524 : case SYBVOID:
525 : case SYBVARBINARY:
526 0 : assert( false && sybtype );
527 : break;
528 : }
529 0 : return 0;
530 : }
531 :
532 : typedef struct KEY_T
533 : {
534 : int nkeys;
535 : struct col_t *keys;
536 : } KEY_T;
537 :
538 : static bool
539 0 : key_equal(const KEY_T *a, const KEY_T *b)
540 : {
541 : int i;
542 :
543 0 : assert(a && b);
544 0 : assert(a->keys && b->keys);
545 0 : assert(a->nkeys == b->nkeys);
546 :
547 0 : for (i=0; i < a->nkeys; i++) {
548 0 : if (! col_equal(a->keys+i, b->keys+i))
549 : return false;
550 : }
551 : return true;
552 : }
553 :
554 :
555 : static void
556 0 : key_free(KEY_T *p)
557 : {
558 0 : col_free(p->keys);
559 0 : free(p->keys);
560 0 : memset(p, 0, sizeof(*p));
561 0 : }
562 :
563 : static KEY_T *
564 0 : key_cpy(KEY_T *pdest, const KEY_T *psrc)
565 : {
566 : int i;
567 :
568 0 : assert( pdest && psrc );
569 :
570 0 : if ((pdest->keys = tds_new0(struct col_t, psrc->nkeys)) == NULL)
571 : return NULL;
572 :
573 0 : pdest->nkeys = psrc->nkeys;
574 :
575 0 : for( i=0; i < psrc->nkeys; i++) {
576 0 : if (NULL == col_cpy(pdest->keys+i, psrc->keys+i))
577 : return NULL;
578 : }
579 :
580 : return pdest;
581 : }
582 :
583 :
584 : static char *
585 0 : make_col_name(DBPROCESS *dbproc, const KEY_T *k)
586 : {
587 : const struct col_t *pc;
588 : char **names, **s, *output;
589 :
590 0 : assert(k);
591 0 : assert(k->nkeys);
592 0 : assert(k->keys);
593 :
594 0 : s = names = tds_new0(char *, k->nkeys);
595 0 : if (!s) {
596 0 : dbperror(dbproc, SYBEMEM, errno);
597 0 : return NULL;
598 : }
599 0 : for(pc=k->keys; pc < k->keys + k->nkeys; pc++) {
600 0 : *s++ = string_value(pc);
601 : }
602 :
603 0 : output = join(k->nkeys, names, "/");
604 :
605 0 : for(s=names; s < names + k->nkeys; s++) {
606 0 : free(*s);
607 : }
608 0 : free(names);
609 :
610 0 : return output;
611 : }
612 :
613 :
614 : typedef struct agg_t
615 : {
616 : KEY_T row_key, col_key;
617 : struct col_t value;
618 : } AGG_T;
619 :
620 : #if 0
621 : static bool
622 : agg_key_equal(const void *a, const void *b)
623 : {
624 : int i;
625 : const AGG_T *p1 = a, *p2 = b;
626 :
627 : assert(p1 && p2);
628 : assert(p1->row_key.keys && p2->row_key.keys);
629 : assert(p1->row_key.nkeys == p2->row_key.nkeys);
630 :
631 : for( i=0; i < p1->row_key.nkeys; i++ ) {
632 : if (! col_equal(p1->row_key.keys+i, p2->row_key.keys+i))
633 : return false;
634 : }
635 :
636 : return true;
637 : }
638 : #endif
639 :
640 : static bool
641 0 : agg_next(const AGG_T *p1, const AGG_T *p2)
642 : {
643 : int i;
644 :
645 0 : assert(p1 && p2);
646 :
647 0 : if (p1->row_key.keys == NULL || p2->row_key.keys == NULL)
648 : return false;
649 :
650 : assert(p1->row_key.keys && p2->row_key.keys);
651 0 : assert(p1->row_key.nkeys == p2->row_key.nkeys);
652 :
653 0 : assert(p1->col_key.keys && p2->col_key.keys);
654 0 : assert(p1->col_key.nkeys == p2->col_key.nkeys);
655 :
656 0 : for( i=0; i < p1->row_key.nkeys; i++ ) {
657 0 : assert(p1->row_key.keys[i].type);
658 0 : assert(p2->row_key.keys[i].type);
659 0 : if (p1->row_key.keys[i].type != p2->row_key.keys[i].type)
660 : return false;
661 : }
662 :
663 0 : for( i=0; i < p1->row_key.nkeys; i++ ) {
664 0 : if (! col_equal(p1->row_key.keys+i, p2->row_key.keys+i))
665 : return false;
666 : }
667 :
668 0 : for( i=0; i < p1->col_key.nkeys; i++ ) {
669 0 : if (p1->col_key.keys[i].type != p2->col_key.keys[i].type)
670 : return false;
671 : }
672 :
673 0 : for( i=0; i < p1->col_key.nkeys; i++ ) {
674 0 : if (! col_equal(p1->col_key.keys+i, p2->col_key.keys+i))
675 : return false;
676 : }
677 :
678 : return true;
679 : }
680 :
681 : static void
682 0 : agg_free(AGG_T *p)
683 : {
684 0 : key_free(&p->row_key);
685 0 : key_free(&p->col_key);
686 0 : col_free(&p->value);
687 0 : }
688 :
689 : static bool
690 0 : agg_equal(const AGG_T *p1, const AGG_T *p2)
691 : {
692 : int i;
693 :
694 0 : assert(p1 && p2);
695 0 : assert(p1->row_key.keys && p1->col_key.keys);
696 0 : assert(p2->row_key.keys && p2->col_key.keys);
697 :
698 0 : assert(p1->row_key.nkeys == p2->row_key.nkeys);
699 0 : assert(p1->col_key.nkeys == p2->col_key.nkeys);
700 :
701 : /* todo: use key_equal */
702 0 : for( i=0; i < p1->row_key.nkeys; i++ ) {
703 0 : if (! col_equal(p1->row_key.keys+i, p2->row_key.keys+i))
704 : return false;
705 : }
706 0 : for( i=0; i < p1->col_key.nkeys; i++ ) {
707 0 : if (! col_equal(p1->col_key.keys+i, p2->col_key.keys+i))
708 : return false;
709 : }
710 : return true;
711 : }
712 :
713 : #undef TEST_MALLOC
714 : #define TEST_MALLOC(dest,type) \
715 : {if (!(dest = (type*)calloc(1, sizeof(type)))) goto Cleanup;}
716 :
717 : #undef TEST_CALLOC
718 : #define TEST_CALLOC(dest,type,n) \
719 : {if (!(dest = (type*)calloc((n), sizeof(type)))) goto Cleanup;}
720 :
721 : #define tds_alloc_column() ((TDSCOLUMN*) calloc(1, sizeof(TDSCOLUMN)))
722 :
723 : static TDSRESULTINFO *
724 0 : alloc_results(size_t num_cols)
725 : {
726 : TDSRESULTINFO *res_info;
727 : TDSCOLUMN **ppcol;
728 :
729 0 : TEST_MALLOC(res_info, TDSRESULTINFO);
730 0 : res_info->ref_count = 1;
731 0 : TEST_CALLOC(res_info->columns, TDSCOLUMN *, num_cols);
732 :
733 0 : for (ppcol = res_info->columns; ppcol < res_info->columns + num_cols; ppcol++)
734 0 : if ((*ppcol = tds_alloc_column()) == NULL)
735 : goto Cleanup;
736 0 : res_info->num_cols = num_cols;
737 0 : res_info->row_size = 0;
738 0 : return res_info;
739 :
740 0 : Cleanup:
741 0 : tds_free_results(res_info);
742 0 : return NULL;
743 : }
744 :
745 : static TDSRET
746 0 : set_result_column(TDSSOCKET * tds, TDSCOLUMN * curcol, const char name[], const struct col_t *pvalue)
747 : {
748 0 : assert(curcol && pvalue);
749 0 : assert(name);
750 :
751 0 : curcol->column_usertype = pvalue->type;
752 0 : curcol->column_nullable = true;
753 0 : curcol->column_writeable = false;
754 0 : curcol->column_identity = false;
755 :
756 0 : tds_set_column_type(tds->conn, curcol, pvalue->type); /* sets "cardinal" type */
757 :
758 0 : curcol->column_timestamp = (curcol->column_type == SYBBINARY && curcol->column_usertype == TDS_UT_TIMESTAMP);
759 :
760 : #if 0
761 : curcol->funcs->get_info(tds, curcol);
762 : #endif
763 0 : curcol->on_server.column_size = curcol->column_size;
764 :
765 0 : if (!tds_dstr_copy(&curcol->column_name, name))
766 : return TDS_FAIL;
767 :
768 0 : tdsdump_log(TDS_DBG_INFO1, "tds7_get_data_info: \n"
769 : "\tcolname = %s\n"
770 : "\ttype = %d (%s)\n"
771 : "\tserver's type = %d (%s)\n"
772 : "\tcolumn_varint_size = %d\n"
773 : "\tcolumn_size = %d (%d on server)\n",
774 0 : tds_dstr_cstr(&curcol->column_name),
775 0 : curcol->column_type, tds_prtype(curcol->column_type),
776 0 : curcol->on_server.column_type, tds_prtype(curcol->on_server.column_type),
777 0 : curcol->column_varint_size,
778 : curcol->column_size, curcol->on_server.column_size);
779 :
780 : return TDS_SUCCESS;
781 : }
782 :
783 : struct metadata_t { KEY_T *pacross; char *name; struct col_t col; };
784 :
785 :
786 : static bool
787 0 : reinit_results(TDSSOCKET * tds, size_t num_cols, const struct metadata_t meta[])
788 : {
789 : TDSRESULTINFO *info;
790 : int i;
791 :
792 0 : assert(tds);
793 0 : assert(num_cols);
794 0 : assert(meta);
795 :
796 0 : tds_free_all_results(tds);
797 0 : tds->rows_affected = TDS_NO_COUNT;
798 :
799 0 : if ((info = alloc_results(num_cols)) == NULL)
800 : return false;
801 :
802 0 : tds_set_current_results(tds, info);
803 0 : if (tds->cur_cursor) {
804 0 : tds_free_results(tds->cur_cursor->res_info);
805 0 : tds->cur_cursor->res_info = info;
806 0 : tdsdump_log(TDS_DBG_INFO1, "set current_results to cursor->res_info\n");
807 : } else {
808 0 : tds->res_info = info;
809 0 : tdsdump_log(TDS_DBG_INFO1, "set current_results (%u column%s) to tds->res_info\n", (unsigned) num_cols, (num_cols==1? "":"s"));
810 : }
811 :
812 0 : tdsdump_log(TDS_DBG_INFO1, "setting up %u columns\n", (unsigned) num_cols);
813 :
814 0 : for (i = 0; i < num_cols; i++) {
815 0 : set_result_column(tds, info->columns[i], meta[i].name, &meta[i].col);
816 0 : info->columns[i]->bcp_terminator = (char*) meta[i].pacross; /* overload available pointer */
817 : }
818 :
819 : if (num_cols > 0) {
820 : static const char dashes[31] = "------------------------------";
821 0 : tdsdump_log(TDS_DBG_INFO1, " %-20s %-15s %-15s %-7s\n", "name", "size/wsize", "type/wtype", "utype");
822 0 : tdsdump_log(TDS_DBG_INFO1, " %-20s %15s %15s %7s\n", dashes+10, dashes+30-15, dashes+30-15, dashes+30-7);
823 : }
824 0 : for (i = 0; i < num_cols; i++) {
825 0 : TDSCOLUMN *curcol = info->columns[i];
826 :
827 0 : tdsdump_log(TDS_DBG_INFO1, " %-20s %7d/%-7d %7d/%-7d %7d\n",
828 0 : tds_dstr_cstr(&curcol->column_name),
829 : curcol->column_size, curcol->on_server.column_size,
830 0 : curcol->column_type, curcol->on_server.column_type,
831 : curcol->column_usertype);
832 : }
833 :
834 : #if 1
835 : /* all done now allocate a row for tds_process_row to use */
836 0 : if (TDS_FAILED(tds_alloc_row(info))) return false;
837 : #endif
838 0 : return true;
839 : }
840 :
841 : typedef struct pivot_t
842 : {
843 : DBPROCESS *dbproc;
844 : STATUS status;
845 : DB_RESULT_STATE dbresults_state;
846 :
847 : AGG_T *output;
848 : KEY_T *across;
849 : size_t nout, nacross;
850 : } PIVOT_T;
851 :
852 : static bool
853 0 : pivot_key_equal(const PIVOT_T *a, const PIVOT_T *b)
854 : {
855 0 : assert(a && b);
856 :
857 0 : return a->dbproc == b->dbproc;
858 : }
859 :
860 : static PIVOT_T *pivots = NULL;
861 : static size_t npivots = 0;
862 :
863 : PIVOT_T *
864 175551 : dbrows_pivoted(DBPROCESS *dbproc)
865 : {
866 : PIVOT_T P;
867 :
868 175551 : assert(dbproc);
869 175551 : P.dbproc = dbproc;
870 :
871 351102 : return (PIVOT_T *) tds_find(&P, pivots, npivots, sizeof(*pivots), (compare_func) pivot_key_equal);
872 : }
873 :
874 : STATUS
875 0 : dbnextrow_pivoted(DBPROCESS *dbproc, PIVOT_T *pp)
876 : {
877 : int i;
878 : AGG_T candidate, *pout;
879 :
880 0 : assert(pp);
881 0 : assert(dbproc && dbproc->tds_socket);
882 0 : assert(dbproc->tds_socket->res_info);
883 0 : assert(dbproc->tds_socket->res_info->columns || 0 == dbproc->tds_socket->res_info->num_cols);
884 :
885 0 : for (pout = pp->output; pout < pp->output + pp->nout; pout++) {
886 0 : if (pout->row_key.keys != NULL)
887 : break;
888 : }
889 :
890 0 : if (pout == pp->output + pp->nout) {
891 0 : dbproc->dbresults_state = _DB_RES_NEXT_RESULT;
892 0 : return NO_MORE_ROWS;
893 : }
894 :
895 0 : memset(&candidate, 0, sizeof(candidate));
896 0 : key_cpy(&candidate.row_key, &pout->row_key);
897 :
898 : /* "buffer_transfer_bound_data" */
899 0 : for (i = 0; i < dbproc->tds_socket->res_info->num_cols; i++) {
900 0 : struct col_t *pval = NULL;
901 0 : TDSCOLUMN *pcol = dbproc->tds_socket->res_info->columns[i];
902 0 : assert(pcol);
903 :
904 0 : if (pcol->column_nullbind) {
905 0 : if (pcol->column_cur_size < 0) {
906 0 : *(DBINT *)(pcol->column_nullbind) = -1;
907 : } else {
908 0 : *(DBINT *)(pcol->column_nullbind) = 0;
909 : }
910 : }
911 0 : if (!pcol->column_varaddr) {
912 0 : tdsdump_log(TDS_DBG_ERROR, "no pcol->column_varaddr in col %d\n", i);
913 0 : continue;
914 : }
915 :
916 : /* find column in output */
917 0 : if (pcol->bcp_terminator == NULL) { /* not a cross-tab column */
918 0 : pval = &candidate.row_key.keys[i];
919 : } else {
920 : AGG_T *pcan;
921 0 : key_cpy(&candidate.col_key, (KEY_T *) pcol->bcp_terminator);
922 0 : if ((pcan = tds_find(&candidate, pout, pp->output + pp->nout - pout,
923 : sizeof(*pp->output), (compare_func) agg_next)) != NULL) {
924 : /* flag this output as used */
925 0 : pout->row_key.keys = NULL;
926 0 : pval = &pcan->value;
927 : }
928 : }
929 :
930 0 : if (!pval || col_null(pval)) { /* nothing in output for this x,y location */
931 0 : dbgetnull(dbproc, pcol->column_bindtype, pcol->column_bindlen, (BYTE *) pcol->column_varaddr);
932 0 : continue;
933 : }
934 :
935 : assert(pval);
936 :
937 0 : pcol->column_size = pval->len;
938 0 : pcol->column_data = col_buffer(pval);
939 :
940 0 : copy_data_to_host_var( dbproc,
941 : pval->type,
942 0 : col_buffer(pval),
943 : pval->len,
944 0 : (BYTE *) pcol->column_varaddr,
945 : pcol->column_bindlen,
946 0 : pcol->column_bindtype,
947 0 : (DBINT*) pcol->column_nullbind
948 : );
949 : }
950 :
951 : return REG_ROW;
952 : }
953 :
954 : /**
955 : * Pivot the rows, creating a new resultset
956 : *
957 : * Call dbpivot() immediately after dbresults(). It calls dbnextrow() as long as
958 : * it returns REG_ROW, transforming the results into a cross-tab report.
959 : * dbpivot() modifies the metadata such that DB-Library can be used tranparently:
960 : * retrieve the rows as usual with dbnumcols(), dbnextrow(), etc.
961 : *
962 : * @dbproc, our old friend
963 : * @nkeys the number of left-edge columns to group by
964 : * @keys an array of left-edge columns to group by
965 : * @ncols the number of top-edge columns to group by
966 : * @cols an array of top-edge columns to group by
967 : * @func the aggregation function to use
968 : * @val the number of the column to which @func is applied
969 : *
970 : * @returns the return code from the final call to dbnextrow().
971 : * Success is normally indicated by NO_MORE_ROWS.
972 : */
973 : RETCODE
974 0 : dbpivot(DBPROCESS *dbproc, int nkeys, int *keys, int ncols, int *cols, DBPIVOT_FUNC func, int val)
975 : {
976 : enum { logalot = 1 };
977 : PIVOT_T P, *pp;
978 0 : AGG_T input, *pout = NULL;
979 : struct metadata_t *metadata, *pmeta;
980 0 : size_t i, nmeta = 0;
981 :
982 0 : tdsdump_log(TDS_DBG_FUNC, "dbpivot(%p, %d,%p, %d,%p, %p, %d)\n", dbproc, nkeys, keys, ncols, cols, func, val);
983 : if (logalot) {
984 0 : char buffer[1024] = {'\0'}, *s = buffer;
985 : static const char *const names[2] = { "\tkeys (down)", "\n\tcols (across)" };
986 0 : int *p = keys, *pend = p + nkeys;
987 :
988 0 : for (i=0; i < 2; i++) {
989 0 : const char *sep = "";
990 0 : s += sprintf(s, "%s: ", names[i]);
991 0 : for ( ; p < pend; p++) {
992 0 : s += sprintf(s, "%s%d", sep, *p);
993 0 : sep = ", ";
994 : }
995 0 : p = cols;
996 0 : pend = p + ncols;
997 0 : assert(s < buffer + sizeof(buffer));
998 : }
999 0 : tdsdump_log(TDS_DBG_FUNC, "%s\n", buffer);
1000 : }
1001 :
1002 0 : memset(&input, 0, sizeof(input));
1003 :
1004 0 : P.dbproc = dbproc;
1005 0 : if ((pp = tds_find(&P, pivots, npivots, sizeof(*pivots), (compare_func) pivot_key_equal)) == NULL ) {
1006 0 : pp = TDS_RESIZE(pivots, 1 + npivots);
1007 0 : if (!pp)
1008 : return FAIL;
1009 0 : pp += npivots++;
1010 : } else {
1011 0 : agg_free(pp->output);
1012 0 : key_free(pp->across);
1013 : }
1014 0 : memset(pp, 0, sizeof(*pp));
1015 :
1016 0 : if ((input.row_key.keys = tds_new0(struct col_t, nkeys)) == NULL)
1017 : return FAIL;
1018 0 : input.row_key.nkeys = nkeys;
1019 0 : for (i=0; i < nkeys; i++) {
1020 0 : int type = dbcoltype(dbproc, keys[i]);
1021 0 : int len = dbcollen(dbproc, keys[i]);
1022 0 : assert(type && len);
1023 :
1024 0 : if (!col_init(input.row_key.keys+i, type, len))
1025 : return FAIL;
1026 0 : if (FAIL == dbbind(dbproc, keys[i], bind_type(type), input.row_key.keys[i].len, col_buffer(input.row_key.keys+i)))
1027 : return FAIL;
1028 0 : if (FAIL == dbnullbind(dbproc, keys[i], &input.row_key.keys[i].null_indicator))
1029 : return FAIL;
1030 : }
1031 :
1032 0 : if ((input.col_key.keys = tds_new0(struct col_t, ncols)) == NULL)
1033 : return FAIL;
1034 0 : input.col_key.nkeys = ncols;
1035 0 : for (i=0; i < ncols; i++) {
1036 0 : int type = dbcoltype(dbproc, cols[i]);
1037 0 : int len = dbcollen(dbproc, cols[i]);
1038 0 : assert(type && len);
1039 :
1040 0 : if (!col_init(input.col_key.keys+i, type, len))
1041 : return FAIL;
1042 0 : if (FAIL == dbbind(dbproc, cols[i], bind_type(type), input.col_key.keys[i].len, col_buffer(input.col_key.keys+i)))
1043 : return FAIL;
1044 0 : if (FAIL == dbnullbind(dbproc, cols[i], &input.col_key.keys[i].null_indicator))
1045 : return FAIL;
1046 : }
1047 :
1048 : /* value */ {
1049 0 : int type = dbcoltype(dbproc, val);
1050 0 : int len = dbcollen(dbproc, val);
1051 0 : assert(type && len);
1052 :
1053 0 : if (!col_init(&input.value, type, len))
1054 : return FAIL;
1055 0 : if (FAIL == dbbind(dbproc, val, bind_type(type), input.value.len, col_buffer(&input.value)))
1056 : return FAIL;
1057 0 : if (FAIL == dbnullbind(dbproc, val, &input.value.null_indicator))
1058 : return FAIL;
1059 : }
1060 :
1061 0 : while ((pp->status = dbnextrow(dbproc)) == REG_ROW) {
1062 : /* add to unique list of crosstab columns */
1063 0 : if (tds_find(&input.col_key, pp->across, pp->nacross, sizeof(*pp->across), (compare_func) key_equal) == NULL) {
1064 0 : if (!TDS_RESIZE(pp->across, 1 + pp->nacross))
1065 : return FAIL;
1066 0 : key_cpy(pp->across + pp->nacross, &input.col_key);
1067 : }
1068 0 : assert(pp->across);
1069 :
1070 0 : if ((pout = tds_find(&input, pp->output, pp->nout, sizeof(*pp->output), (compare_func) agg_equal)) == NULL ) {
1071 0 : if (!TDS_RESIZE(pp->output, 1 + pp->nout))
1072 : return FAIL;
1073 0 : pout = pp->output + pp->nout++;
1074 :
1075 :
1076 0 : if ((pout->row_key.keys = tds_new0(struct col_t, input.row_key.nkeys)) == NULL)
1077 : return FAIL;
1078 0 : key_cpy(&pout->row_key, &input.row_key);
1079 :
1080 0 : if ((pout->col_key.keys = tds_new0(struct col_t, input.col_key.nkeys)) == NULL)
1081 : return FAIL;
1082 0 : key_cpy(&pout->col_key, &input.col_key);
1083 :
1084 0 : if (!col_init(&pout->value, input.value.type, input.value.len))
1085 : return FAIL;
1086 : }
1087 :
1088 0 : func(&pout->value, &input.value);
1089 :
1090 : }
1091 :
1092 : /* Mark this proc as pivoted, so that dbnextrow() sees it when the application calls it */
1093 0 : pp->dbproc = dbproc;
1094 0 : pp->dbresults_state = dbproc->dbresults_state;
1095 0 : dbproc->dbresults_state = pp->output < pout? _DB_RES_RESULTSET_ROWS : _DB_RES_RESULTSET_EMPTY;
1096 :
1097 : /*
1098 : * Initialize new metadata
1099 : */
1100 0 : nmeta = input.row_key.nkeys + pp->nacross;
1101 0 : metadata = tds_new0(struct metadata_t, nmeta);
1102 0 : if (!metadata) {
1103 0 : dbperror(dbproc, SYBEMEM, errno);
1104 0 : return FAIL;
1105 : }
1106 0 : assert(pp->across || pp->nacross == 0);
1107 :
1108 : /* key columns are passed through as-is, verbatim */
1109 0 : for (i=0; i < input.row_key.nkeys; i++) {
1110 0 : assert(i < nkeys);
1111 0 : metadata[i].name = strdup(dbcolname(dbproc, keys[i]));
1112 0 : metadata[i].pacross = NULL;
1113 0 : col_cpy(&metadata[i].col, input.row_key.keys+i);
1114 : }
1115 :
1116 : /* pivoted columms are found in the "across" data */
1117 0 : for (i=0, pmeta = metadata + input.row_key.nkeys; i < pp->nacross; i++) {
1118 : struct col_t col;
1119 0 : if (!col_init(&col, SYBFLT8, sizeof(double)))
1120 0 : return FAIL;
1121 0 : assert(pmeta + i < metadata + nmeta);
1122 0 : pmeta[i].name = make_col_name(dbproc, pp->across+i);
1123 0 : if (!pmeta[i].name)
1124 : return FAIL;
1125 0 : assert(pp->across);
1126 0 : pmeta[i].pacross = pp->across + i;
1127 0 : col_cpy(&pmeta[i].col, pp->nout? &pp->output[0].value : &col);
1128 : }
1129 :
1130 0 : if (!reinit_results(dbproc->tds_socket, nmeta, metadata)) {
1131 : return FAIL;
1132 : }
1133 :
1134 0 : return SUCCEED;
1135 : }
1136 :
1137 : /*
1138 : * Aggregation functions
1139 : */
1140 :
1141 : void
1142 0 : dbpivot_count (struct col_t *tgt, const struct col_t *src)
1143 : {
1144 0 : assert( tgt && src);
1145 0 : assert (src->type);
1146 :
1147 0 : tgt->type = SYBINT4;
1148 :
1149 0 : if (! col_null(src))
1150 0 : tgt->data.i++;
1151 0 : }
1152 :
1153 : void
1154 0 : dbpivot_sum (struct col_t *tgt, const struct col_t *src)
1155 : {
1156 0 : assert( tgt && src);
1157 0 : assert (src->type);
1158 :
1159 0 : tgt->type = src->type;
1160 :
1161 0 : if (col_null(src))
1162 : return;
1163 :
1164 0 : switch (src->type) {
1165 0 : case SYBINT1:
1166 0 : tgt->data.ti += src->data.ti;
1167 0 : break;
1168 0 : case SYBINT2:
1169 0 : tgt->data.si += src->data.si;
1170 0 : break;
1171 0 : case SYBINT4:
1172 0 : tgt->data.i += src->data.i;
1173 0 : break;
1174 0 : case SYBFLT8:
1175 0 : tgt->data.f += src->data.f;
1176 0 : break;
1177 0 : case SYBREAL:
1178 0 : tgt->data.r += src->data.r;
1179 0 : break;
1180 :
1181 0 : case SYBCHAR:
1182 : case SYBVARCHAR:
1183 : case SYBINTN:
1184 : case SYBDATETIME:
1185 : case SYBBIT:
1186 : case SYBTEXT:
1187 : case SYBNTEXT:
1188 : case SYBIMAGE:
1189 : case SYBMONEY4:
1190 : case SYBMONEY:
1191 : case SYBDATETIME4:
1192 : case SYBBINARY:
1193 : case SYBVOID:
1194 : case SYBVARBINARY:
1195 : case SYBBITN:
1196 : case SYBNUMERIC:
1197 : case SYBDECIMAL:
1198 : case SYBFLTN:
1199 : case SYBMONEYN:
1200 : case SYBDATETIMN:
1201 : default:
1202 0 : tdsdump_log(TDS_DBG_INFO1, "dbpivot_sum(): invalid operand %d\n", src->type);
1203 0 : tgt->type = SYBINT4;
1204 0 : tgt->data.i = 0;
1205 0 : break;
1206 : }
1207 : }
1208 :
1209 : void
1210 0 : dbpivot_min (struct col_t *tgt, const struct col_t *src)
1211 : {
1212 0 : assert( tgt && src);
1213 0 : assert (src->type);
1214 :
1215 0 : tgt->type = src->type;
1216 :
1217 0 : if (col_null(src))
1218 : return;
1219 :
1220 0 : switch (src->type) {
1221 0 : case SYBINT1:
1222 0 : tgt->data.ti = tgt->data.ti < src->data.ti? tgt->data.ti : src->data.ti;
1223 0 : break;
1224 0 : case SYBINT2:
1225 0 : tgt->data.si = tgt->data.si < src->data.si? tgt->data.si : src->data.si;
1226 0 : break;
1227 0 : case SYBINT4:
1228 0 : tgt->data.i = tgt->data.i < src->data.i? tgt->data.i : src->data.i;
1229 0 : break;
1230 0 : case SYBFLT8:
1231 0 : tgt->data.f = tgt->data.f < src->data.f? tgt->data.f : src->data.f;
1232 0 : break;
1233 0 : case SYBREAL:
1234 0 : tgt->data.r = tgt->data.r < src->data.r? tgt->data.r : src->data.r;
1235 0 : break;
1236 :
1237 0 : case SYBCHAR:
1238 : case SYBVARCHAR:
1239 : case SYBINTN:
1240 : case SYBDATETIME:
1241 : case SYBBIT:
1242 : case SYBTEXT:
1243 : case SYBNTEXT:
1244 : case SYBIMAGE:
1245 : case SYBMONEY4:
1246 : case SYBMONEY:
1247 : case SYBDATETIME4:
1248 : case SYBBINARY:
1249 : case SYBVOID:
1250 : case SYBVARBINARY:
1251 : case SYBBITN:
1252 : case SYBNUMERIC:
1253 : case SYBDECIMAL:
1254 : case SYBFLTN:
1255 : case SYBMONEYN:
1256 : case SYBDATETIMN:
1257 : default:
1258 0 : tdsdump_log(TDS_DBG_INFO1, "dbpivot_sum(): invalid operand %d\n", src->type);
1259 0 : tgt->type = SYBINT4;
1260 0 : tgt->data.i = 0;
1261 0 : break;
1262 : }
1263 : }
1264 :
1265 : void
1266 0 : dbpivot_max (struct col_t *tgt, const struct col_t *src)
1267 : {
1268 0 : assert( tgt && src);
1269 0 : assert (src->type);
1270 :
1271 0 : tgt->type = src->type;
1272 :
1273 0 : if (col_null(src))
1274 : return;
1275 :
1276 0 : switch (src->type) {
1277 0 : case SYBINT1:
1278 0 : tgt->data.ti = tgt->data.ti > src->data.ti? tgt->data.ti : src->data.ti;
1279 0 : break;
1280 0 : case SYBINT2:
1281 0 : tgt->data.si = tgt->data.si > src->data.si? tgt->data.si : src->data.si;
1282 0 : break;
1283 0 : case SYBINT4:
1284 0 : tgt->data.i = tgt->data.i > src->data.i? tgt->data.i : src->data.i;
1285 0 : break;
1286 0 : case SYBFLT8:
1287 0 : tgt->data.f = tgt->data.f > src->data.f? tgt->data.f : src->data.f;
1288 0 : break;
1289 0 : case SYBREAL:
1290 0 : tgt->data.r = tgt->data.r > src->data.r? tgt->data.r : src->data.r;
1291 0 : break;
1292 :
1293 0 : case SYBCHAR:
1294 : case SYBVARCHAR:
1295 : case SYBINTN:
1296 : case SYBDATETIME:
1297 : case SYBBIT:
1298 : case SYBTEXT:
1299 : case SYBNTEXT:
1300 : case SYBIMAGE:
1301 : case SYBMONEY4:
1302 : case SYBMONEY:
1303 : case SYBDATETIME4:
1304 : case SYBBINARY:
1305 : case SYBVOID:
1306 : case SYBVARBINARY:
1307 : case SYBBITN:
1308 : case SYBNUMERIC:
1309 : case SYBDECIMAL:
1310 : case SYBFLTN:
1311 : case SYBMONEYN:
1312 : case SYBDATETIMN:
1313 : default:
1314 0 : tdsdump_log(TDS_DBG_INFO1, "dbpivot_sum(): invalid operand %d\n", src->type);
1315 0 : tgt->type = SYBINT4;
1316 0 : tgt->data.i = 0;
1317 0 : break;
1318 : }
1319 : }
1320 :
1321 : static const struct name_t {
1322 : char name[14];
1323 : DBPIVOT_FUNC func;
1324 : } names[] =
1325 : { { "count", dbpivot_count }
1326 : , { "sum", dbpivot_sum }
1327 : , { "min", dbpivot_min }
1328 : , { "max", dbpivot_max }
1329 : };
1330 :
1331 : static bool
1332 0 : name_equal( const struct name_t *n1, const struct name_t *n2 )
1333 : {
1334 0 : assert(n1 && n2);
1335 0 : return strcmp(n1->name, n2->name) == 0;
1336 : }
1337 :
1338 : DBPIVOT_FUNC
1339 0 : dbpivot_lookup_name( const char name[] )
1340 : {
1341 0 : struct name_t *n = TDS_FIND(name, names, (compare_func) name_equal);
1342 :
1343 0 : return n ? n->func : NULL;
1344 : }
|