1 | /***************************************
2 | $Revision: 1.37 $
3 |
4 |
5 | Sql module (sq). This is a mysql implementation of an sql module.
6 |
7 | Status: NOT REVUED, NOT TESTED
8 |
9 | Note: this code has been heavily coupled to MySQL, and may need to be changed
10 | (to improve performance) if a new RDBMS is used.
11 |
12 | ******************/ /******************
13 | Filename : query_instructions.c
14 | Author : ottrey@ripe.net
15 | OSs Tested : Solaris
16 | Problems : Moderately linked to MySQL. Not sure which inverse
17 | attributes each option has. Would like to modify this
18 | after re-designing the objects module.
19 | Comments : Not sure about the different keytypes.
20 | ******************/ /******************
21 | Copyright (c) 1999 RIPE NCC
22 |
23 | All Rights Reserved
24 |
25 | Permission to use, copy, modify, and distribute this software and its
26 | documentation for any purpose and without fee is hereby granted,
27 | provided that the above copyright notice appear in all copies and that
28 | both that copyright notice and this permission notice appear in
29 | supporting documentation, and that the name of the author not be
30 | used in advertising or publicity pertaining to distribution of the
31 | software without specific, written prior permission.
32 |
33 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
34 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
35 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
36 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
37 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
38 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
39 | ***************************************/
40 | #include <stdio.h>
41 | #include <string.h>
42 | #include <glib.h>
43 |
44 | #include "which_keytypes.h"
45 | #include "query_instructions.h"
46 | #include "mysql_driver.h"
47 | #include "rp.h"
48 | #include "stubs.h"
49 | #include "constants.h"
50 | #include "memwrap.h"
51 | #include "wh_queries.h"
52 |
53 | /*+ String sizes +*/
54 | #define STR_S 63
55 | #define STR_M 255
56 | #define STR_L 1023
57 | #define STR_XL 4095
58 | #define STR_XXL 16383
59 |
60 | /* XXX this must be removed from here!!! a .h file must be
61 | generated from xml */
62 |
63 | #include "defs.h"
64 |
65 | /* create_name_query() */
66 | /*++++++++++++++++++++++++++++++++++++++
67 | Create an sql query for the names table.
68 |
69 | char *query_str
70 |
71 | const char *sql_query
72 |
73 | const char *keys
74 |
75 | More:
76 | +html+ <PRE>
77 | Authors:
78 | ottrey
79 | +html+ </PRE><DL COMPACT>
80 | +html+ <DT>Online References:
81 | +html+ <DD><UL>
82 | +html+ </UL></DL>
83 |
84 | ++++++++++++++++++++++++++++++++++++++*/
85 | static void create_name_query(char *query_str, const char *sql_query, const char *keys) {
86 | int i;
87 | /* Allocate stuff */
88 | GString *from_clause = g_string_sized_new(STR_XL);
89 | GString *where_clause = g_string_sized_new(STR_XL);
90 | gchar **words = g_strsplit(keys, " ", 0);
91 |
92 | /* double quotes " are used in queries to allow querying for
93 | names like O'Hara */
94 |
95 | g_string_sprintfa(from_clause, "names N%.2d", 0);
96 | g_string_sprintfa(where_clause, "N%.2d.name=\"%s\"", 0, words[0]);
97 |
98 | for (i=1; words[i] != NULL; i++) {
99 | g_string_sprintfa(from_clause, ", names N%.2d", i);
100 | g_string_sprintfa(where_clause, " AND N%.2d.name=\"%s\" AND N00.object_id = N%.2d.object_id", i, words[i], i);
101 | }
102 |
103 | sprintf(query_str, sql_query, from_clause->str, where_clause->str);
104 |
105 | /* Free up stuff */
106 | g_strfreev(words);
107 | g_string_free(where_clause,/* CONSTCOND */ TRUE);
108 | g_string_free(from_clause, /* CONSTCOND */ TRUE);
109 |
110 | } /* create_name_query() */
111 |
112 |
113 |
114 |
115 | static void add_filter(char *query_str, const Query_command *qc) {
116 | int i;
117 | int qlen;
118 | char filter_atom[STR_M];
119 |
120 | /*
121 | if (MA_bitcount(qc->object_type_bitmap) > 0) {
122 | g_string_sprintfa(query_str, " AND (");
123 | for (i=0; i < C_END; i++) {
124 | if (MA_isset(qc->object_type_bitmap, i)) {
125 | g_string_sprintfa(query_str, "i.object_type = %d OR ", DF_get_class_dbase_code(i));
126 | }
127 | }
128 | g_string_truncate(query_str, query_str->len-3);
129 | g_string_append_c(query_str, ')');
130 | }
131 | */
132 | if (MA_bitcount(qc->object_type_bitmap) > 0) {
133 | strcat(query_str, " AND (");
134 | for (i=0; i < C_END; i++) {
135 | if (MA_isset(qc->object_type_bitmap, i)) {
136 | strcpy(filter_atom, "");
137 | sprintf(filter_atom, "i.object_type = %d OR ", i);
138 | /* XXX class codes should be used instead:
139 | DF_get_class_dbase_code(i))
140 | but currently the tables contain values of enums
141 | (C_IN, etc) and not codes
142 | */
143 | strcat(query_str, filter_atom);
144 | }
145 | }
146 | qlen = strlen(query_str);
147 | query_str[qlen-3] = ')';
148 | query_str[qlen-2] = '\0';
149 | query_str[qlen-1] = '\0';
150 | }
151 |
152 | } /* add_filter() */
153 |
154 | /* create_query() */
155 | /*++++++++++++++++++++++++++++++++++++++
156 | Create an sql query from the query_command and the matching keytype and the
157 | selected inverse attributes.
158 | Note this clears the first inv_attribute it sees, so is called sequentially
159 | until there are no inv_attributes left.
160 |
161 | WK_Type keytype The matching keytype.
162 |
163 | const Query_command *qc The query command.
164 |
165 | mask_t *inv_attrs_bitmap The selected inverse attributes.
166 |
167 | More:
168 | +html+ <PRE>
169 | Authors:
170 | ottrey
171 | +html+ </PRE><DL COMPACT>
172 | +html+ <DT>Online References:
173 | +html+ <DD><UL>
174 | +html+ </UL></DL>
175 |
176 | ++++++++++++++++++++++++++++++++++++++*/
177 | static char *create_query(const Query_t q, const Query_command *qc) {
178 | char *result=NULL;
179 | char result_buff[STR_XL];
180 | Q_Type_t querytype;
181 | int conduct_test = 0;
182 |
183 | if (MA_bitcount(qc->inv_attrs_bitmap) > 0) {
184 | querytype = Q_INVERSE;
185 | }
186 | else {
187 | querytype = Q_LOOKUP;
188 | }
189 |
190 | if ( (q.query != NULL)
191 | && (q.querytype == querytype) ) {
192 | conduct_test=1;
193 | }
194 |
195 | if (conduct_test == 1) {
196 |
197 | if (q.keytype == WK_NAME) {
198 | /* Name queries require special treatment. */
199 | create_name_query(result_buff, q.query, qc->keys);
200 | }
201 | else if( q.keytype == WK_IPADDRESS ) { /* ifaddr sql lookups */
202 | ip_range_t myrang;
203 | unsigned begin, end;
204 | ip_keytype_t key_type;
205 |
206 | if (NOERR(IP_smart_range(qc->keys, &myrang, IP_EXPN, &key_type))) {
207 | if(IP_rang_b2_space(&myrang) == IP_V4 ) {
208 | IP_rang_b2v4(&myrang, &begin, &end);
209 | sprintf(result_buff, q.query, begin, end);
210 | }
211 | else {
212 | die;
213 | }
214 | }
215 | }
216 | else {
217 | sprintf(result_buff, q.query, qc->keys);
218 | }
219 |
220 | if (q.class == -1) {
221 | /* It is class type ANY so add the object filtering */
222 | add_filter(result_buff, qc);
223 | }
224 |
225 | dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);
226 | strcpy(result, result_buff);
227 | }
228 |
229 | return result;
230 | } /* create_query() */
231 |
232 | /* fast_output() */
233 | /*++++++++++++++++++++++++++++++++++++++
234 | This is for the '-F' flag.
235 | It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.
236 |
237 | Fast isn't fast anymore - it's just there for compatibility reasons.
238 | This could be speed up if there were breaks out of the loops, once it matched something.
239 | (Wanna add a goto Marek? :-) ).
240 |
241 | const char *string The string to be "fast outputed".
242 |
243 | More:
244 | +html+ <PRE>
245 | Authors:
246 | ottrey
247 | +html+ </PRE><DL COMPACT>
248 | +html+ <DT>Online References:
249 | +html+ <DD><UL>
250 | +html+ </UL></DL>
251 |
252 | ++++++++++++++++++++++++++++++++++++++*/
253 |
254 | char *fast_output(const char *str)
255 | {
256 | int i,j;
257 | char *result;
258 | char result_bit[STR_L];
259 | char result_buff[STR_XL];
260 | gchar **lines = g_strsplit(str, "\n", 0);
261 | char * const *attribute_names;
262 | gboolean filtering_an_attribute = FALSE;
263 | char *value;
264 |
265 | attribute_names = DF_get_attribute_names();
266 |
267 | strcpy(result_buff, "");
268 |
269 | for(i=0; attribute_names[i] != NULL; i++) {
270 | for (j=0; lines[j] != NULL; j++) {
271 | if (strncmp(attribute_names[i], lines[j], strlen(attribute_names[i])) == 0) {
272 | strcpy(result_bit, "");
273 | /* This is the juicy bit that converts the likes of; "source: RIPE" to "*so: RIPE" */
274 | value = strchr(lines[j], ':');
275 | value++;
276 | /* Now get rid of whitespace. */
277 | while (*value == ' ' || *value == '\t') {
278 | value++;
279 | }
280 | sprintf(result_bit, "*%s: %s\n", DF_get_attribute_code(i), value);
281 | strcat(result_buff, result_bit);
282 | }
283 | /* CONSTCOND */
284 | else if (filtering_an_attribute == TRUE) {
285 | switch (lines[j][0]) {
286 | case ' ':
287 | case '\t':
288 | case '+':
289 | strcpy(result_bit, "");
290 | sprintf(result_bit, "%s\n", lines[j]);
291 | strcat(result_buff, result_bit);
292 | break;
293 |
294 | default:
295 | filtering_an_attribute = FALSE;
296 | }
297 | }
298 | }
299 | }
300 |
301 |
302 | dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);
303 |
304 | strcpy(result, result_buff);
305 |
306 | return result;
307 | } /* fast_output() */
308 |
309 | /* filter() */
310 | /*++++++++++++++++++++++++++++++++++++++
311 | Basically it's for the '-K' flag for non-set (and non-radix) objects.
312 | It assumes lines starting with ' ', '\t' or '+' belong to the prior attribute.
313 |
314 | This could be speed up if there were breaks out of the loops, once it matched something.
315 | (Wanna add a goto Marek? :-) ).
316 |
317 | const char *string The string to be filtered.
318 |
319 | More:
320 | +html+ <PRE>
321 | Authors:
322 | ottrey
323 | +html+ </PRE><DL COMPACT>
324 | +html+ <DT>Online References:
325 | +html+ <DD><UL>
326 | +html+ </UL></DL>
327 |
328 | ++++++++++++++++++++++++++++++++++++++*/
329 | char *filter(const char *str) {
330 | int i,j, passed=0;
331 | char *result;
332 | char result_bit[STR_L];
333 | char result_buff[STR_XL];
334 | gchar **lines = g_strsplit(str, "\n", 0);
335 | char * const *filter_names;
336 | gboolean filtering_an_attribute = FALSE;
337 |
338 | filter_names = DF_get_filter_names();
339 |
340 | strcpy(result_buff, "");
341 | for (i=0; filter_names[i] != NULL; i++) {
342 | for (j=0; lines[j] != NULL; j++) {
343 | if (strncmp(filter_names[i], lines[j], strlen(filter_names[i])) == 0) {
344 | strcpy(result_bit, "");
345 | sprintf(result_bit, "%s\n", lines[j]);
346 | strcat(result_buff, result_bit);
347 | passed++;
348 |
349 | /* can someone explain where %^&()! lint sees the condition here ? */
350 | /* CONSTCOND */
351 | filtering_an_attribute = TRUE;
352 | }
353 | /* CONSTCOND */
354 | else if (filtering_an_attribute == TRUE) {
355 | switch (lines[j][0]) {
356 | case ' ':
357 | case '\t':
358 | case '+':
359 | strcpy(result_bit, "");
360 | sprintf(result_bit, "%s\n", lines[j]);
361 | strcat(result_buff, result_bit);
362 | break;
363 |
364 | default:
365 | filtering_an_attribute = FALSE;
366 | }
367 | }
368 | }
369 | }
370 |
371 | if(passed) {
372 | strcat(result_buff, "\n");
373 | }
374 |
375 | dieif( wr_malloc((void **)&result, strlen(result_buff)+1) != UT_OK);
376 | strcpy(result, result_buff);
377 |
378 | return result;
379 | } /* filter() */
380 |
381 | /* write_results() */
382 | /*++++++++++++++++++++++++++++++++++++++
383 | Write the results to the client socket.
384 |
385 | SQ_result_set_t *result The result set returned from the sql query.
386 | unsigned filtered if the objects should go through a filter (-K)
387 | sk_conn_st *condat Connection data for the client
388 | int maxobjects max # of objects to write
389 |
390 | XXX NB. this is very dependendant on what rows are returned in the result!!!
391 |
392 | More:
393 | +html+ <PRE>
394 | Authors:
395 | ottrey
396 | +html+ </PRE><DL COMPACT>
397 | +html+ <DT>Online References:
398 | +html+ <DD><UL>
399 | +html+ </UL></DL>
400 |
401 | ++++++++++++++++++++++++++++++++++++++*/
402 | static int write_results(SQ_result_set_t *result,
403 | unsigned filtered,
404 | unsigned fast,
405 | sk_conn_st *condat,
406 | acc_st *acc_credit,
407 | acl_st *acl
408 | ) {
409 | SQ_row_t *row;
410 | char *str;
411 | char *filtrate;
412 | char *fasted;
413 | int retrieved_objects=0;
414 | char *objt;
415 | int type;
416 |
417 | /* Get all the results - one at a time */
418 | if (result != NULL) {
419 | /* here we are making use of the mysql_store_result capability
420 | of interrupting the cycle of reading rows. mysql_use_result
421 | does not allow that, must be read until end */
422 |
423 | while ( (row = SQ_row_next(result)) != NULL && acc_credit->denials == 0 ) {
424 | if ( (str = SQ_get_column_string(result, row, 0)) == NULL
425 | || (objt = SQ_get_column_string(result, row, 3)) == NULL ) {
426 | /* handle it somehow ? */
427 | die;
428 | }
429 | else {
430 | /* get + add object type */
431 | type = atoi(objt);
432 |
433 | /* ASP_QI_LAST_DET */
434 | ER_dbg_va(FAC_QI, ASP_QI_LAST_DET,
435 | "Retrieved serial id = %d , type = %s", atoi(str), objt);
436 |
437 | wr_free(str);
438 | wr_free(objt);
439 | }
440 |
441 | /* decrement credit for accounting purposes */
442 |
443 | /* XXX the definition of private/public should go into the defs (xml) */
444 | switch( type ) {
445 | case C_PN:
446 | case C_RO:
447 | if( acc_credit->private_objects <= 0 && acl->maxbonus != -1 ) {
448 | /* must be negative, will be subtracted */
449 | acc_credit->denials = -1;
450 | continue; /* go to the head of the loop */
451 | }
452 | acc_credit->private_objects --;
453 | break;
454 | default:
455 | if( acc_credit->public_objects <= 0 && acl->maxpublic != -1 ) {
456 | acc_credit->denials = -1;
457 | continue; /* go to the head of the loop */
458 | }
459 | acc_credit->public_objects --;
460 | }
461 |
462 | if ((str = SQ_get_column_string(result, row, 2)) == NULL) { die; }
463 | else {
464 |
465 | /* The fast output stage */
466 | if (fast == 1) {
467 | fasted = fast_output(str);
468 | wr_free(str);
469 | str = fasted;
470 | }
471 |
472 | /* The filtering stage */
473 | if (filtered == 0) {
474 | SK_cd_puts(condat, str);
475 | SK_cd_puts(condat, "\n");
476 | }
477 | else {
478 |
479 | /* XXX accounting should be done AFTER that, and not for objects
480 | filtered out */
481 |
482 | filtrate = filter(str);
483 | SK_cd_puts(condat, filtrate);
484 | wr_free(filtrate);
485 | }
486 | retrieved_objects++;
487 | }
488 | wr_free(str);
489 | }
490 | }
491 |
492 | return retrieved_objects;
493 | } /* write_results() */
494 |
495 | /* write_objects() */
496 | /*++++++++++++++++++++++++++++++++++++++
497 | This is linked into MySQL by the fact that MySQL doesn't have sub selects
498 | (yet). The queries are done in two stages. Make some temporary tables and
499 | insert into them. Then use them in the next select.
500 |
501 | SQ_connection_t *sql_connection The connection to the database.
502 |
503 | char *id_table The id of the temporary table (This is a result of the hacky
504 | way we've tried to get MySQL to do sub-selects.)
505 |
506 | sk_conn_st *condat Connection data for the client
507 |
508 | More:
509 | +html+ <PRE>
510 | Authors:
511 | ottrey
512 | +html+ </PRE><DL COMPACT>
513 | ++++++++++++++++++++++++++++++++++++++*/
514 | static void write_objects(SQ_connection_t *sql_connection,
515 | char *id_table,
516 | unsigned int filtered,
517 | unsigned int fast,
518 | sk_conn_st *condat,
519 | acc_st *acc_credit,
520 | acl_st *acl
521 | )
522 | {
523 | /* XXX This should really return a linked list of the objects */
524 |
525 | SQ_result_set_t *result;
526 | int retrieved_objects=0;
527 | char sql_command[STR_XL];
528 |
529 | /* XXX These may and should change a lot. */
530 | sprintf(sql_command, Q_OBJECTS, id_table);
531 | dieif(SQ_execute_query(sql_connection, sql_command, &result) == -1 );
532 |
533 | retrieved_objects = write_results(result, filtered, fast, condat,
534 | acc_credit, acl);
535 | SQ_free_result(result);
536 |
537 | } /* write_objects() */
538 |
539 | /* insert_radix_serials() */
540 | /*++++++++++++++++++++++++++++++++++++++
541 | Insert the radix serial numbers into a temporary table in the database.
542 |
543 | mask_t bitmap The bitmap of attribute to be converted.
544 |
545 | SQ_connection_t *sql_connection The connection to the database.
546 |
547 | char *id_table The id of the temporary table (This is a result of the hacky
548 | way we've tried to get MySQL to do sub-selects.)
549 |
550 | GList *datlist The list of data from the radix tree.
551 |
552 | XXX Hmmmmm this isn't really a good place to free things... infact it's quite nasty. :-(
553 |
554 | More:
555 | +html+ <PRE>
556 | Authors:
557 | ottrey
558 | +html+ </PRE><DL COMPACT>
559 | +html+ <DT>Online References:
560 | +html+ <DD><UL>
561 | <LI><A HREF="http://www.gtk.org/rdp/glib/glib-doubly-linked-lists.html">Glist</A>
562 | +html+ </UL></DL>
563 |
564 | ++++++++++++++++++++++++++++++++++++++*/
565 | static void insert_radix_serials(SQ_connection_t *sql_connection, char *id_table, GList *datlist) {
566 | GList *qitem;
567 | char sql_command[STR_XL];
568 | int serial;
569 |
570 | for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
571 | rx_datcpy_t *datcpy = qitem->data;
572 |
573 | serial = datcpy->leafcpy.data_key;
574 |
575 | sprintf(sql_command, "INSERT INTO %s values (%d)", id_table, serial);
576 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1);
577 |
578 | wr_free(datcpy->leafcpy.data_ptr);
579 | }
580 |
581 | wr_clear_list( &datlist );
582 |
583 | } /* insert_radix_serials() */
584 |
585 |
586 | /* write_radix_immediate() */
587 | /*++++++++++++++++++++++++++++++++++++++
588 | Display the immediate data carried with the objects returned by the
589 | radix tree.
590 |
591 | GList *datlist The linked list of dataleaf copies
592 | sk_conn_st *condat Connection data for the client
593 | acc_st *acc_credit Accounting struct
594 |
595 | More:
596 | +html+ <PRE>
597 | Authors:
598 | marek
599 | +html+ </PRE><DL COMPACT>
600 | +html+ <DT>Online References:
601 | +html+ <DD><UL>
602 | +html+ </UL></DL>
603 |
604 |
605 | Also free the list of answers.
606 | */
607 | static void write_radix_immediate(GList *datlist,
608 | sk_conn_st *condat,
609 | acc_st *acc_credit)
610 | {
611 | GList *qitem;
612 |
613 | for( qitem = g_list_first(datlist); qitem != NULL; qitem = g_list_next(qitem)) {
614 | rx_datcpy_t *datcpy = qitem->data;
615 |
616 | SK_cd_puts(condat, datcpy->leafcpy.data_ptr );
617 | SK_cd_puts(condat, "\n");
618 |
619 | wr_free(datcpy->leafcpy.data_ptr);
620 |
621 | acc_credit->public_objects --;
622 | }
623 |
624 | wr_clear_list( &datlist );
625 | } /* write_radix_immediate() */
626 |
627 |
628 | /* map_qc2rx() */
629 | /*++++++++++++++++++++++++++++++++++++++
630 | The mapping between a query_command and a radix query.
631 |
632 | Query_instruction *qi The Query Instruction to be created from the mapping
633 | of the query command.
634 |
635 | const Query_command *qc The query command to be mapped.
636 |
637 | More:
638 | +html+ <PRE>
639 | Authors:
640 | ottrey
641 | +html+ </PRE><DL COMPACT>
642 | +html+ <DT>Online References:
643 | +html+ <DD><UL>
644 | +html+ </UL></DL>
645 |
646 | ++++++++++++++++++++++++++++++++++++++*/
647 | static int map_qc2rx(Query_instruction *qi, const Query_command *qc) {
648 | int result=1;
649 |
650 | qi->rx_keys = qc->keys;
651 |
652 | if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
653 | qi->rx_srch_mode = RX_SRCH_EXLESS;
654 | qi->rx_par_a = 0;
655 | }
656 | else if ( (qc->L == 1) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
657 | qi->rx_srch_mode = RX_SRCH_LESS;
658 | qi->rx_par_a = RX_ALL_DEPTHS;
659 | }
660 | else if ( (qc->L == 0) && (qc->M == 1) && (qc->l == 0) && (qc->m == 0) && (qc->x == 0) ) {
661 | qi->rx_srch_mode = RX_SRCH_MORE;
662 | qi->rx_par_a = RX_ALL_DEPTHS;
663 | }
664 | else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 1) && (qc->m == 0) && (qc->x == 0) ) {
665 | qi->rx_srch_mode = RX_SRCH_LESS;
666 | qi->rx_par_a = 1;
667 | }
668 | else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 1) && (qc->x == 0) ) {
669 | qi->rx_srch_mode = RX_SRCH_MORE;
670 | qi->rx_par_a = 1;
671 | }
672 | else if ( (qc->L == 0) && (qc->M == 0) && (qc->l == 0) && (qc->m == 0) && (qc->x == 1) ) {
673 | qi->rx_srch_mode = RX_SRCH_EXACT;
674 | qi->rx_par_a = 0;
675 | }
676 | else {
677 | /* user error ( XXX : this should have been checked before) */
678 |
679 | ER_dbg_va(FAC_QI, ASP_QI_SKIP,
680 | "ERROR in qc2rx mapping: bad combination of flags");
681 | result = 0;
682 | }
683 |
684 | return result;
685 |
686 | } /* map_qc2rx() */
687 |
688 | /* run_referral() */
689 | /*
690 | invoked when no such domain found. Goes through the domain table
691 | and searches for shorter domains, then if it finds one with referral
692 | it performs it, otherwise it just returns nothing.
693 |
694 | to perform referral, it actually composes the referral query
695 | for a given host/port/type and calls the whois query function.
696 |
697 | Well, it returns nothing anyway (void). It just prints to the socket.
698 |
699 | */
700 | void run_referral(SQ_connection_t *sql_connection, Query_instructions *qis, Query_environ *qe, int qi_index) {
701 | char *dot = qis->qc->keys;
702 | char querystr[STR_L];
703 | SQ_row_t *row;
704 | SQ_result_set_t *result;
705 | char sql_command[STR_XL];
706 | int stop_loop=0;
707 | char *ref_host;
708 | char *ref_type;
709 | char *ref_port;
710 | int ref_port_int;
711 |
712 | strcpy(querystr,"");
713 |
714 | while( !stop_loop && (dot=index(dot,'.')) != NULL ) {
715 | dot++;
716 |
717 | ER_dbg_va(FAC_QI, ASP_QI_REF_DET, "run_referral: checking %s", dot);
718 |
719 | sprintf(sql_command, "SELECT domain.object_id, domain, type, port, host FROM domain, refer WHERE domain.object_id = refer.object_id AND domain = '%s'", dot);
720 | dieif( SQ_execute_query(sql_connection, sql_command, &result) == -1);
721 |
722 | switch( SQ_num_rows(result) ) {
723 | case 0: /* no such domain -> no action, will try next chunk */
724 | break;
725 |
726 | case 1: /* check for referral host and perform query if present
727 | in any case end the loop */
728 | stop_loop=1;
729 | assert( (row = SQ_row_next(result)) != NULL);
730 |
731 | ref_host = SQ_get_column_string(result, row, 4);
732 |
733 | ER_dbg_va(FAC_QI, ASP_QI_REF_GEN, "referral host is %s", ref_host);
734 |
735 | if( ref_host != NULL && strlen(ref_host) > 0 ) {
736 | ref_type = SQ_get_column_string(result, row, 2);
737 | ref_port = SQ_get_column_string(result, row, 3);
738 |
739 | /* get the integer value, it should be correct */
740 | if( sscanf( ref_port, "%d",&ref_port_int) < 1 ) {
741 | die;
742 | }
743 |
744 | /* compose the query: */
745 |
746 | /* put -r if the reftype is RIPE and -r or -i were used */
747 | if( strcmp(ref_type,"RIPE") == 0
748 | && ( Query[qis->instruction[qi_index]->queryindex]
749 | .querytype == Q_INVERSE
750 | || qis->recursive > 0 ) ) {
751 | strcat(querystr," -r ");
752 | }
753 |
754 | /* prepend with -Vversion,IP for type CLIENTADDRESS */
755 | if( strcmp(ref_type,"CLIENTADDRESS") == 0 ) {
756 | char optv[STR_M];
757 |
758 | snprintf(optv,STR_M," -V%s,%s ","RIP0.88", qe->condat.ip);
759 | strcat(querystr,optv);
760 | }
761 |
762 | /* now set the search term - set to the stripped down version
763 | for inverse query, full-length otherwise */
764 | if( Query[qis->instruction[qi_index]->queryindex].querytype == Q_INVERSE ) {
765 | strcat(querystr,dot);
766 | }
767 | else {
768 | strcat(querystr,qis->qc->keys);
769 | }
770 |
771 | SK_cd_puts(&(qe->condat), "% Please note: this information is not stored in the RIPE database\n%\n% connecting to the remote referral site ");
772 | SK_cd_puts(&(qe->condat), ref_host);
773 | SK_cd_puts(&(qe->condat), "\n\n");
774 |
775 | /* WH_sock(sock, host, port, query, maxlines, timeout)) */
776 | switch( WH_sock(qe->condat.sock, ref_host, ref_port_int, querystr, 25, 5) ) {
777 | case WH_TIMEOUT:
778 | SK_cd_puts(&(qe->condat),"referral timeout\n");
779 | break;
780 |
781 | case WH_MAXLINES:
782 | SK_cd_puts(&(qe->condat),"referral maxlines exceeded\n");
783 | break;
784 |
785 | case WH_BADHOST:
786 | SK_cd_puts(&(qe->condat),"referral host not found\n");
787 | break;
788 |
789 | case WH_CONNECT:
790 | SK_cd_puts(&(qe->condat),"referral host not responding\n");
791 | break;
792 |
793 | case WH_BIND:
794 | case WH_SOCKET:
795 | /* XXX internal server problem... what to do - wait ? */
796 | default:
797 | ;
798 | } /*switch WH_sock */
799 | }
800 | break;
801 |
802 | default: /* more than one domain in this file: something broken */
803 | die;
804 | }
805 | SQ_free_result(result);
806 | }
807 | } /*run_referral*/
808 |
809 | static
810 | void
811 | add_ref_name(SQ_connection_t *sql_connection,
812 | char *rectable,
813 | char *allnames
814 | )
815 | {
816 | /* construct the query, allow zero-length list */
817 | if( strlen(allnames) > 0 ) {
818 | char final_query[STR_XL];
819 | char select_query[STR_XL];
820 |
821 | create_name_query(select_query, "SELECT N00.object_id FROM %s WHERE %s "
822 | "AND N00.object_type != 100 AND N00.thread_id = 0",
823 | allnames);
824 |
825 | sprintf(final_query, "INSERT INTO %s %s",
826 | rectable,
827 | select_query);
828 |
829 | dieif(SQ_execute_query(sql_connection, final_query, NULL) == -1 );
830 |
831 | allnames[0]=0;
832 | }
833 | }
834 |
835 |
836 | /* QI_execute() */
837 | /*++++++++++++++++++++++++++++++++++++++
838 | Execute the query instructions. This is called by a g_list_foreach
839 | function, so each of the sources in the "database source" list can be passed
840 | into this function.
841 |
842 | This function has bloated itself. Can we split it up Marek? (ottrey 13/12/99)
843 |
844 | void *database_voidptr Pointer to the database.
845 |
846 | void *qis_voidptr Pointer to the query_instructions.
847 |
848 | More:
849 | +html+ <PRE>
850 | Authors:
851 | ottrey
852 | +html+ </PRE><DL COMPACT>
853 | +html+ <DT>Online References:
854 | +html+ <DD><UL>
855 | <LI><A
856 | HREF="http://www.gtk.org/rdp/glib/glib-singly-linked-lists.html#G-SLIST-FOREACH">g_list_foreach</A>
857 | +html+ </UL></DL>
858 |
859 | ++++++++++++++++++++++++++++++++++++++*/
860 | void QI_execute(void *database_voidptr,
861 | Query_instructions *qis,
862 | Query_environ *qe,
863 | acc_st *acc_credit,
864 | acl_st *acl
865 | ) {
866 | char *database = (char *)database_voidptr;
867 | Query_instruction **ins=NULL;
868 | char id_table[STR_S];
869 | char sql_command[STR_XL];
870 | GList *datlist=NULL;
871 | int i;
872 | SQ_row_t *row;
873 | SQ_result_set_t *result;
874 | SQ_connection_t *sql_connection=NULL;
875 | char *countstr;
876 | int count,afcount;
877 |
878 | sql_connection = SQ_get_connection(CO_get_host(), CO_get_database_port(), database, CO_get_user(), CO_get_password() );
879 |
880 | if (sql_connection == NULL) {
881 | SK_cd_puts(&(qe->condat), "% WARNING: Failed to make connection to ");
882 | SK_cd_puts(&(qe->condat), database);
883 | SK_cd_puts(&(qe->condat), " database mirror.\n\n");
884 |
885 | /* XXX void prevents us from sending any error code back. It is OK ? */
886 | return;
887 | }
888 |
889 | /* XXX This is a really bad thing to do.
890 | It should'nt _have_ to be called here.
891 | But unfortunately it does. -- Sigh. */
892 | sprintf(id_table, "ID_%ld", mysql_thread_id(sql_connection) );
893 |
894 | /* create a table for id's of all objects found NOT NULL , UNIQUE(id) */
895 | sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", id_table);
896 | dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
897 |
898 | /* create a table for special subqueries (domain only for now) */
899 | sprintf(sql_command, "CREATE TABLE %s_S ( id int ) TYPE=HEAP", id_table);
900 | dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
901 |
902 | /* Iterate through query instructions */
903 | ins = qis->instruction;
904 | for (i=0; ins[i] != NULL; i++) {
905 | Query_instruction *qi = ins[i];
906 |
907 | switch ( qi->search_type ) {
908 | case R_SQL:
909 | if ( qi->query_str != NULL ) {
910 |
911 | /* handle special cases first */
912 | if( Query[qi->queryindex].class == C_DN ) {
913 |
914 | /* XXX if any more cases than just domain appear, we will be
915 | cleaning the _S table from the previous query here
916 |
917 | "DELETE FROM %s_S"
918 | */
919 |
920 | /* now query into the _S table */
921 | sprintf(sql_command, "INSERT INTO %s_S %s", id_table, qi->query_str);
922 | dieif( SQ_execute_query(sql_connection, sql_command, NULL) == -1);
923 |
924 | /* if any results - copy to the id's table.
925 | Otherwise, run referral */
926 |
927 | sprintf(sql_command, "SELECT COUNT(*) FROM %s_S", id_table);
928 | dieif(SQ_execute_query(sql_connection, sql_command, &result) == -1 );
929 | row = SQ_row_next(result);
930 | countstr = SQ_get_column_string(result, row, 0);
931 | sscanf(countstr, "%d", &count);
932 | SQ_free_result(result);
933 |
934 | ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
935 | "DN lookup for %s found %d entries",
936 | qis->qc->keys, count);
937 |
938 | if( count ) {
939 | sprintf(sql_command, "INSERT INTO %s SELECT id FROM %s_S",
940 | id_table, id_table);
941 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1);
942 | }
943 |
944 | if( count == 0
945 | || Query[qi->queryindex].querytype == Q_INVERSE ) {
946 | /* now: if the domain was not found, we run referral.
947 | unless prohibited by a flag
948 |
949 | But for inverse queries we return the things that were
950 | or were not found AND also do the referral unless prohibited.
951 | */
952 | if (qis->qc->R == 0) {
953 | run_referral(sql_connection, qis, qe, i);
954 | }
955 | }
956 |
957 | }
958 | else {
959 | sprintf(sql_command, "INSERT INTO %s %s", id_table, qi->query_str);
960 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
961 | }
962 | }
963 |
964 | /* debug */
965 | afcount = SQ_get_affected_rows(sql_connection);
966 |
967 | ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
968 | "%d entries added in %s query for %s",
969 | afcount, Query[qi->queryindex].descr, qis->qc->keys
970 | );
971 | break;
972 |
973 | #define RIPE_REG 17
974 | case R_RADIX:
975 | if ( RP_asc_search(qi->rx_srch_mode, qi->rx_par_a, 0, qi->rx_keys,
976 | RIPE_REG, Query[qi->queryindex].attribute,
977 | &datlist, RX_ANS_ALL) == RX_OK ) {
978 | if( ER_is_traced(FAC_QI, ASP_QI_COLL_DET ) ) {
979 | /* prevent unnecessary g_list_length call */
980 |
981 | ER_dbg_va(FAC_QI, ASP_QI_COLL_DET,
982 | "%d entries after %s (mode %d par %d reg %d) query for %s",
983 | g_list_length(datlist),
984 | Query[qi->queryindex].descr,
985 | qi->rx_srch_mode, qi->rx_par_a,
986 | RIPE_REG,
987 | qi->rx_keys);
988 | }
989 | }
990 | else {
991 | die;
992 | }
993 | break;
994 |
995 | default: die;
996 | } /* switch */
997 | } /* for every instruction */
998 |
999 | /* post-processing */
1000 |
1001 |
1002 |
1003 | /* display */
1004 |
1005 | if( qis->filtered == 0 ) {
1006 | /* add radix results to the end */
1007 | insert_radix_serials(sql_connection, id_table, datlist);
1008 |
1009 | /* fetch recursive objects (ac,tc,zc,ah) */
1010 | if ( qis->recursive ) {
1011 | char rec_table[32];
1012 | SQ_result_set_t *result;
1013 | SQ_row_t *row;
1014 | int thisid = 0;
1015 | int oldid = 0;
1016 | char allnames[STR_M];
1017 |
1018 | sprintf(rec_table, "%s_R", id_table);
1019 |
1020 | /* a temporary table for recursive data must be created, because
1021 | a query using the same table as a source and target is illegal
1022 | ( like: INSERT into ID_123 SELECT * FROM ID_123,admin_c WHERE ... )
1023 | */
1024 | sprintf(sql_command, "CREATE TABLE %s ( id int PRIMARY KEY NOT NULL ) TYPE=HEAP", rec_table);
1025 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1026 |
1027 | /* find the contacts */
1028 | sprintf(sql_command, Q_REC, rec_table, id_table, "author");
1029 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1030 |
1031 | sprintf(sql_command, Q_REC, rec_table, id_table, "admin_c");
1032 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1033 |
1034 | sprintf(sql_command, Q_REC, rec_table, id_table, "tech_c" );
1035 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1036 |
1037 | sprintf(sql_command, Q_REC, rec_table, id_table, "zone_c" );
1038 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1039 |
1040 |
1041 | /* replace references to dummies by references by name */
1042 | sprintf(sql_command,
1043 | " SELECT id, name FROM %s IDS STRAIGHT_JOIN names "
1044 | " WHERE IDS.id = names.object_id "
1045 | " AND names.object_type = 100"
1046 | " ORDER BY id",
1047 | rec_table);
1048 |
1049 | dieif(SQ_execute_query(sql_connection, sql_command, &result) == -1 );
1050 |
1051 | allnames[0]=0;
1052 | /* now go through the results and collect names */
1053 | while ( (row = SQ_row_next(result)) != NULL ) {
1054 | char *id = SQ_get_column_string(result, row, 0);
1055 | char *name = SQ_get_column_string(result, row, 1);
1056 |
1057 | thisid = atoi(id);
1058 |
1059 | /* when the id changes, the name is complete */
1060 | if( thisid != oldid && oldid != 0 ) {
1061 | add_ref_name( sql_connection, rec_table, allnames);
1062 | }
1063 |
1064 | strcat(allnames, name);
1065 | strcat(allnames, " ");
1066 | oldid = thisid;
1067 | wr_free(id);
1068 | wr_free(name);
1069 | }
1070 | /* also do the last name */
1071 | add_ref_name( sql_connection, rec_table, allnames);
1072 |
1073 | SQ_free_result(result);
1074 |
1075 | /* now copy things back to the main temporary table */
1076 | sprintf(sql_command, "INSERT INTO %s SELECT * FROM %s_R",
1077 | id_table, id_table);
1078 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1079 |
1080 | /* Now drop the IDS recursive table */
1081 | sprintf(sql_command, "DROP TABLE %s_R", id_table);
1082 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1083 | } /* if recursive */
1084 | }
1085 | else {
1086 | /* -K filtering */
1087 | /* right now only filtering, no expanding sets */
1088 | /* implies no recursion */
1089 |
1090 | /* XXX write_set_objects() ?? */
1091 |
1092 | /* display the immediate data from the radix tree */
1093 | /* XXX pass+decrease credit here */
1094 | write_radix_immediate(datlist, &(qe->condat), acc_credit );
1095 | }
1096 |
1097 | /* display objects */
1098 | write_objects(sql_connection, id_table, qis->filtered,
1099 | qis->fast, &(qe->condat), acc_credit, acl);
1100 |
1101 |
1102 | /* Now drop the _S table */
1103 | sprintf(sql_command, "DROP TABLE %s_S", id_table);
1104 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1105 |
1106 | /* Now drop the IDS table */
1107 | sprintf(sql_command, "DROP TABLE %s", id_table);
1108 | dieif(SQ_execute_query(sql_connection, sql_command, NULL) == -1 );
1109 | SQ_close_connection(sql_connection);
1110 |
1111 |
1112 | } /* QI_execute() */
1113 |
1114 |
1115 |
1116 | /* instruction_free() */
1117 | /*++++++++++++++++++++++++++++++++++++++
1118 | Free the instruction.
1119 |
1120 | Query_instruction *qi query_instruction to be freed.
1121 |
1122 | More:
1123 | +html+ <PRE>
1124 | Authors:
1125 | ottrey
1126 | +html+ </PRE><DL COMPACT>
1127 | +html+ <DT>Online References:
1128 | +html+ <DD><UL>
1129 | +html+ </UL></DL>
1130 |
1131 | ++++++++++++++++++++++++++++++++++++++*/
1132 | static void instruction_free(Query_instruction *qi) {
1133 | if (qi != NULL) {
1134 | if (qi->query_str != NULL) {
1135 | wr_free(qi->query_str);
1136 | }
1137 | wr_free(qi);
1138 | }
1139 | } /* instruction_free() */
1140 |
1141 | /* QI_free() */
1142 | /*++++++++++++++++++++++++++++++++++++++
1143 | Free the query_instructions.
1144 |
1145 | Query_instructions *qis Query_instructions to be freed.
1146 |
1147 | XXX This isn't working too well at the moment.
1148 |
1149 | More:
1150 | +html+ <PRE>
1151 | Authors:
1152 | ottrey
1153 | +html+ </PRE><DL COMPACT>
1154 | +html+ <DT>Online References:
1155 | +html+ <DD><UL>
1156 | +html+ </UL></DL>
1157 |
1158 | ++++++++++++++++++++++++++++++++++++++*/
1159 | void QI_free(Query_instructions *qis) {
1160 | int i;
1161 |
1162 | for (i=0; qis->instruction[i] != NULL; i++) {
1163 | instruction_free(qis->instruction[i]);
1164 | }
1165 |
1166 | if (qis != NULL) {
1167 | wr_free(qis);
1168 | }
1169 |
1170 | } /* QI_free() */
1171 |
1172 | /*++++++++++++++++++++++++++++++++++++++
1173 | Determine if this query should be conducted or not.
1174 |
1175 | If it was an inverse query - it the attribute appears in the query command's bitmap.
1176 | If it was a lookup query - if the attribute appears in the object type bitmap or
1177 | disregard if there is no object_type bitmap (Ie object filter).
1178 |
1179 | mask_t bitmap The bitmap of attribute to be converted.
1180 |
1181 | const Query_command *qc The query_command that the instructions are created
1182 | from.
1183 |
1184 | const Query_t q The query being investigated.
1185 |
1186 | ++++++++++++++++++++++++++++++++++++++*/
1187 | static int valid_query(const Query_command *qc, const Query_t q) {
1188 | int result=0;
1189 |
1190 | if (MA_isset(qc->keytypes_bitmap, q.keytype) == 1) {
1191 | if (q.query != NULL) {
1192 | switch (q.querytype) {
1193 | case Q_INVERSE:
1194 | if (MA_isset(qc->inv_attrs_bitmap, q.attribute) ) {
1195 | result = 1;
1196 | }
1197 | break;
1198 |
1199 | case Q_LOOKUP:
1200 | if (MA_bitcount(qc->object_type_bitmap) == 0) {
1201 | result=1;
1202 | }
1203 | else if (q.class<0 || MA_isset(qc->object_type_bitmap, q.class)) {
1204 | result=1;
1205 | }
1206 | break;
1207 |
1208 | default:
1209 | fprintf(stderr, "qi:valid_query() -> Bad querytype\n");
1210 | }
1211 | }
1212 | }
1213 |
1214 | return result;
1215 | } /* valid_query() */
1216 |
1217 | /* QI_new() */
1218 | /*++++++++++++++++++++++++++++++++++++++
1219 | Create a new set of query_instructions.
1220 |
1221 | const Query_command *qc The query_command that the instructions are created
1222 | from.
1223 |
1224 | const Query_environ *qe The environmental variables that they query is being
1225 | performed under.
1226 | More:
1227 | +html+ <PRE>
1228 | Authors:
1229 | ottrey
1230 | +html+ </PRE><DL COMPACT>
1231 | +html+ <DT>Online References:
1232 | +html+ <DD><UL>
1233 | +html+ </UL></DL>
1234 |
1235 | ++++++++++++++++++++++++++++++++++++++*/
1236 | Query_instructions *QI_new(const Query_command *qc, const Query_environ *qe) {
1237 | Query_instructions *qis=NULL;
1238 | Query_instruction *qi=NULL;
1239 | int i_no=0;
1240 | int i;
1241 | char *query_str;
1242 |
1243 | dieif(wr_calloc( (void **) & qis, 1, sizeof(Query_instructions)) != UT_OK);
1244 |
1245 | qis->filtered = qc->filtered;
1246 | qis->fast = qc->fast;
1247 | qis->recursive = qc->recursive;
1248 | qis->qc = (qc);
1249 |
1250 |
1251 | for (i=0; Query[i].query != NULL; i++) {
1252 |
1253 | /* If a valid query. */
1254 | if ( valid_query(qc, Query[i]) == 1) {
1255 |
1256 | dieif( wr_calloc((void **) &qi, 1, sizeof(Query_instruction)) != UT_OK);
1257 |
1258 | qi->queryindex = i;
1259 |
1260 | /* SQL Query */
1261 | if ( Query[i].refer == R_SQL) {
1262 | qi->search_type = R_SQL;
1263 | query_str = create_query(Query[i], qc);
1264 |
1265 | if (query_str!= NULL) {
1266 | qi->query_str = query_str;
1267 | qis->instruction[i_no++] = qi;
1268 | }
1269 | }
1270 | /* Radix Query */
1271 | else if (Query[i].refer == R_RADIX) {
1272 | qi->search_type = R_RADIX;
1273 |
1274 | if (map_qc2rx(qi, qc) == 1) {
1275 | int j;
1276 | int found=0;
1277 |
1278 | /* check that there is no such query yet, for example if
1279 | more than one keytype (wk) matched */
1280 | for (j=0; j<i_no; j++) {
1281 | Query_instruction *qij = qis->instruction[j];
1282 |
1283 | if( qij->search_type == R_RADIX
1284 | && Query[qij->queryindex].attribute
1285 | == Query[qi ->queryindex].attribute) {
1286 |
1287 | found=1;
1288 | break;
1289 | }
1290 | }
1291 |
1292 | if ( found ) {
1293 | /* Discard the Query Instruction */
1294 | wr_free(qi);
1295 | }
1296 | else {
1297 | /* Add the query_instruction to the array */
1298 | qis->instruction[i_no++] = qi;
1299 | }
1300 | }
1301 | }
1302 | else {
1303 | /* ERROR: bad search_type */
1304 | die;
1305 | }
1306 | }
1307 | }
1308 | qis->instruction[i_no++] = NULL;
1309 |
1310 |
1311 | { /* tracing */
1312 | char *descrstr = QI_queries_to_string(qis);
1313 |
1314 | ER_dbg_va(FAC_QI, ASP_QI_COLL_GEN, "Queries: %s", descrstr );
1315 | wr_free( descrstr );
1316 | }
1317 |
1318 | return qis;
1319 |
1320 | } /* QI_new() */
1321 |
1322 | /* QI_queries_to_string()
1323 |
1324 | returns a list of descriptions for queries that will be performed.
1325 | */
1326 |
1327 | char *QI_queries_to_string(Query_instructions *qis)
1328 | {
1329 | Query_instruction *qi;
1330 | int i;
1331 | char *resstr = NULL;
1332 |
1333 | dieif( wr_realloc((void **)&resstr, resstr, 2 ) != UT_OK);
1334 | strcpy(resstr, "{");
1335 |
1336 | for( i = 0; ( qi=qis->instruction[i] ) != NULL; i++ ) {
1337 | char *descr = Query[qi->queryindex].descr;
1338 | int oldres = strlen( resstr );
1339 |
1340 | dieif( wr_realloc((void **)&resstr, resstr, oldres+strlen(descr)+2) != UT_OK);
1341 | strcat(resstr, descr);
1342 | strcat(resstr, ",");
1343 | }
1344 | if( i>0 ) {
1345 | /* cancel the last comma */
1346 | resstr[strlen(resstr)-1] = 0;
1347 | }
1348 |
1349 | dieif( wr_realloc((void **)&resstr, resstr, strlen( resstr ) + 2 )
1350 | != UT_OK);
1351 | strcat(resstr, "}");
1352 |
1353 | return resstr;
1354 | }