source: tags/ms_r17q2/ARBDB/adquery.cxx

Last change on this file was 15876, checked in by westram, 8 years ago
  • reintegrates 'fix' into 'trunk'
    • automatically deletes field 'group_name' at root of tree
      • done while loading tree
      • provides workaround for #753
  • adds: log:branches/fix@15870:15875
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 75.1 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : adquery.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "gb_aci.h"
12#include "gb_comm.h"
13#include "gb_index.h"
14#include "gb_key.h"
15#include "gb_localdata.h"
16#include "gb_ta.h"
17#include <algorithm>
18
19#include <arb_strbuf.h>
20#include <arb_match.h>
21
22#include <cctype>
23
24#define GB_PATH_MAX 1024
25
26static void build_GBDATA_path(GBDATA *gbd, char **buffer) {
27    GBCONTAINER *gbc = GB_FATHER(gbd);
28    if (gbc) {
29        build_GBDATA_path(gbc, buffer);
30
31        const char *key = GB_KEY(gbd);
32        char       *bp  = *buffer;
33
34        *bp++ = '/';
35        while (*key) *bp++ = *key++;
36        *bp = 0;
37
38        *buffer = bp;
39    }
40}
41
42#define BUFFERSIZE 1024
43
44static const char *GB_get_GBDATA_path(GBDATA *gbd) {
45    static char *orgbuffer = NULL;
46    char        *buffer;
47
48    if (!orgbuffer) ARB_alloc(orgbuffer, BUFFERSIZE);
49    buffer = orgbuffer;
50
51    build_GBDATA_path(gbd, &buffer);
52    assert_or_exit((buffer-orgbuffer) < BUFFERSIZE); // buffer overflow
53
54    return orgbuffer;
55}
56
57// ----------------
58//      QUERIES
59
60static bool gb_find_value_equal(GBDATA *gb, GB_TYPES type, const char *val, GB_CASE case_sens) {
61    bool equal = false;
62
63#if defined(DEBUG)
64    GB_TYPES realtype = gb->type();
65    gb_assert(val);
66    if (type == GB_STRING) {
67        gb_assert(gb->is_a_string()); // gb_find_internal called with wrong type
68    }
69    else {
70        gb_assert(realtype == type); // gb_find_internal called with wrong type
71    }
72#endif // DEBUG
73
74    switch (type) {
75        case GB_STRING:
76            equal = GBS_string_matches(GB_read_char_pntr(gb), val, case_sens);
77            break;
78
79        case GB_INT: {
80            int i                      = GB_read_int(gb);
81            if (i == *(int*)val) equal = true;
82            break;
83        }
84        default: {
85            const char *err = GBS_global_string("Value search not supported for data type %i (%i)", gb->type(), type);
86            GB_internal_error(err);
87            break;
88        }
89    }
90
91    return equal;
92}
93
94static GBDATA *find_sub_by_quark(GBCONTAINER *father, GBQUARK key_quark, GB_TYPES type, const char *val, GB_CASE case_sens, GBDATA *after, size_t skip_over) {
95    /* search an entry with a key 'key_quark' below a container 'father'
96       after position 'after'
97
98       if 'skip_over' > 0 search skips 'skip_over' entries
99
100       if (val != NULL) search for entry with value 'val':
101
102       GB_STRING/GB_LINK: compares string (case_sensitive or not)
103       GB_INT: compares values
104       GB_FLOAT: ditto (val MUST be a 'double*')
105       others: not implemented yet
106
107       Note: to search for non-char*-values use GB_find_int()
108             for other types write a new similar function
109
110             if key_quark<0 search everything
111    */
112
113    int             end    = father->d.nheader;
114    gb_header_list *header = GB_DATA_LIST_HEADER(father->d);
115
116    int index;
117    if (after) index = (int)after->index+1; else index = 0;
118
119    if (key_quark<0) { // unspecific key quark (i.e. search all)
120        gb_assert(!val);        // search for val not possible if searching all keys!
121        if (!val) {
122            for (; index < end; index++) {
123                if (header[index].flags.key_quark != 0) {
124                    if (header[index].flags.changed >= GB_DELETED) continue;
125                    GBDATA *gb = GB_HEADER_LIST_GBD(header[index]);
126                    if (!gb) {
127                        // @@@ DRY here versus below
128                        gb_unfold(father, 0, index);
129                        header = GB_DATA_LIST_HEADER(father->d);
130                        gb     = GB_HEADER_LIST_GBD(header[index]);
131                        if (!gb) {
132                            const char *err = GBS_global_string("Database entry #%u is missing (in '%s')", index, GB_get_GBDATA_path(father));
133                            GB_internal_error(err);
134                            continue;
135                        }
136                    }
137                    if (!skip_over--) return gb;
138                }
139            }
140        }
141    }
142    else { // specific key quark
143        for (; index < end; index++) {
144            if (key_quark == header[index].flags.key_quark) {
145                if (header[index].flags.changed >= GB_DELETED) continue;
146                GBDATA *gb = GB_HEADER_LIST_GBD(header[index]);
147                if (!gb) {
148                    // @@@ DRY here versus section above
149                    gb_unfold(father, 0, index);
150                    header = GB_DATA_LIST_HEADER(father->d);
151                    gb     = GB_HEADER_LIST_GBD(header[index]);
152                    if (!gb) {
153                        const char *err = GBS_global_string("Database entry #%u is missing (in '%s')", index, GB_get_GBDATA_path(father));
154                        GB_internal_error(err);
155                        continue;
156                    }
157                }
158                if (val) {
159                    if (!gb) {
160                        GB_internal_error("Cannot unfold data");
161                        continue;
162                    }
163                    else {
164                        if (!gb_find_value_equal(gb, type, val, case_sens)) continue;
165                    }
166                }
167                if (!skip_over--) return gb;
168            }
169        }
170    }
171    return NULL;
172}
173
174GBDATA *GB_find_sub_by_quark(GBDATA *father, GBQUARK key_quark, GBDATA *after, size_t skip_over) {
175    return find_sub_by_quark(father->expect_container(), key_quark, GB_NONE, NULL, GB_MIND_CASE, after, skip_over);
176}
177
178static GBDATA *GB_find_subcontent_by_quark(GBDATA *father, GBQUARK key_quark, GB_TYPES type, const char *val, GB_CASE case_sens, GBDATA *after, size_t skip_over) {
179    return find_sub_by_quark(father->expect_container(), key_quark, type, val, case_sens, after, skip_over);
180}
181
182static GBDATA *find_sub_sub_by_quark(GBCONTAINER *const father, const char *key, GBQUARK sub_key_quark, GB_TYPES type, const char *val, GB_CASE case_sens, GBDATA *after) {
183    gb_index_files *ifs    = NULL;
184    GB_MAIN_TYPE   *Main   = GBCONTAINER_MAIN(father);
185    int             end    = father->d.nheader;
186    gb_header_list *header = GB_DATA_LIST_HEADER(father->d);
187
188    int index;
189    if (after) index = (int)after->index+1; else index = 0;
190
191    GBDATA *res;
192    // ****** look for any hash index tables ********
193    // ****** no wildcards allowed       *******
194    if (Main->is_client()) {
195        if (father->flags2.folded_container) {
196            // do the query in the server
197            if (GB_ARRAY_FLAGS(father).changed) {
198                if (!father->flags2.update_in_server) {
199                    GB_ERROR error = Main->send_update_to_server(father);
200                    if (error) {
201                        GB_export_error(error);
202                        return NULL;
203                    }
204                }
205            }
206        }
207        if (father->d.size > GB_MAX_LOCAL_SEARCH && val) {
208            if (after) res = GBCMC_find(after,  key, type, val, case_sens, SEARCH_CHILD_OF_NEXT);
209            else       res = GBCMC_find(father, key, type, val, case_sens, SEARCH_GRANDCHILD);
210            return res;
211        }
212    }
213    if (val &&
214        (ifs=GBCONTAINER_IFS(father))!=NULL &&
215        (!strchr(val, '*')) &&
216        (!strchr(val, '?')))
217    {
218        for (; ifs; ifs = GB_INDEX_FILES_NEXT(ifs)) {
219            if (ifs->key != sub_key_quark) continue;
220            // ***** We found the index table *****
221            res = gb_index_find(father, ifs, sub_key_quark, val, case_sens, index);
222            return res;
223        }
224    }
225
226    GBDATA *gb = after ? after : NULL;
227    for (; index < end; index++) {
228        GBDATA *gbn = GB_HEADER_LIST_GBD(header[index]);
229
230        if (header[index].flags.changed >= GB_DELETED) continue;
231        if (!gbn) {
232            if (Main->is_client()) {
233                if (gb) res = GBCMC_find(gb,     key, type, val, case_sens, SEARCH_CHILD_OF_NEXT);
234                else    res = GBCMC_find(father, key, type, val, case_sens, SEARCH_GRANDCHILD);
235                return res;
236            }
237            GB_internal_error("Empty item in server");
238            continue;
239        }
240        gb = gbn;
241        if (gb->is_container()) {
242            res = GB_find_subcontent_by_quark(gb, sub_key_quark, type, val, case_sens, NULL, 0);
243            if (res) return res;
244        }
245    }
246    return NULL;
247}
248
249
250static GBDATA *gb_find_internal(GBDATA *gbd, const char *key, GB_TYPES type, const char *val, GB_CASE case_sens, GB_SEARCH_TYPE gbs) {
251    GBDATA *result = NULL;
252
253    if (gbd) {
254        GBDATA      *after = NULL;
255        GBCONTAINER *gbc   = NULL;
256
257        switch (gbs) {
258            case SEARCH_NEXT_BROTHER:
259                after = gbd;
260            case SEARCH_BROTHER:
261                gbs   = SEARCH_CHILD;
262                gbc   = GB_FATHER(gbd);
263                break;
264
265            case SEARCH_CHILD:
266            case SEARCH_GRANDCHILD:
267                if (gbd->is_container()) gbc = gbd->as_container();
268                break;
269
270            case SEARCH_CHILD_OF_NEXT:
271                after = gbd;
272                gbs   = SEARCH_GRANDCHILD;
273                gbc   = GB_FATHER(gbd);
274                break;
275        }
276
277        if (gbc) {
278            GBQUARK key_quark = GB_find_existing_quark(gbd, key);
279
280            if (key_quark) { // only search if 'key' is known to db
281                if (gbs == SEARCH_CHILD) {
282                    result = GB_find_subcontent_by_quark(gbc, key_quark, type, val, case_sens, after, 0);
283                }
284                else {
285                    gb_assert(gbs == SEARCH_GRANDCHILD);
286                    result = find_sub_sub_by_quark(gbc, key, key_quark, type, val, case_sens, after);
287                }
288            }
289        }
290    }
291    return result;
292}
293
294GBDATA *GB_find(GBDATA *gbd, const char *key, GB_SEARCH_TYPE gbs) {
295    // normally you should not need to use GB_find!
296    // better use one of the replacement functions
297    // (GB_find_string, GB_find_int, GB_child, GB_nextChild, GB_entry, GB_nextEntry, GB_brother)
298    return gb_find_internal(gbd, key, GB_NONE, NULL, GB_CASE_UNDEFINED, gbs);
299}
300
301GBDATA *GB_find_string(GBDATA *gbd, const char *key, const char *str, GB_CASE case_sens, GB_SEARCH_TYPE gbs) {
302    // search for a subentry of 'gbd' that has
303    // - fieldname 'key'
304    // - type GB_STRING and
305    // - content matching 'str'
306    // if 'case_sensitive' is true, content is matched case sensitive.
307    // GBS_string_matches is used to compare (supports wildcards)
308    return gb_find_internal(gbd, key, GB_STRING, str, case_sens, gbs);
309}
310NOT4PERL GBDATA *GB_find_int(GBDATA *gbd, const char *key, long val, GB_SEARCH_TYPE gbs) {
311    // search for a subentry of 'gbd' that has
312    // - fieldname 'key'
313    // - type GB_INT
314    // - and value 'val'
315    return gb_find_internal(gbd, key, GB_INT, (const char *)&val, GB_CASE_UNDEFINED, gbs);
316}
317
318// ----------------------------------------------------
319//      iterate over ALL subentries of a container
320
321GBDATA *GB_child(GBDATA *father) {
322    // return first child (or NULL if no children)
323    return GB_find(father, NULL, SEARCH_CHILD);
324}
325GBDATA *GB_nextChild(GBDATA *child) {
326    // return next child after 'child' (or NULL if no more children)
327    return GB_find(child, NULL, SEARCH_NEXT_BROTHER);
328}
329
330// ------------------------------------------------------------------------------
331//      iterate over all subentries of a container that have a specified key
332
333GBDATA *GB_entry(GBDATA *father, const char *key) { 
334    // return first child of 'father' that has fieldname 'key'
335    // (or NULL if none found)
336    return GB_find(father, key, SEARCH_CHILD);
337}
338GBDATA *GB_nextEntry(GBDATA *entry) {
339    // return next child after 'entry', that has the same fieldname
340    // (or NULL if 'entry' is last one)
341    return GB_find_sub_by_quark(GB_FATHER(entry), GB_get_quark(entry), entry, 0);
342}
343GBDATA *GB_followingEntry(GBDATA *entry, size_t skip_over) {
344    // return following child after 'entry', that has the same fieldname
345    // (or NULL if no such entry)
346    // skips 'skip_over' entries (skip_over == 0 behaves like GB_nextEntry)
347    return GB_find_sub_by_quark(GB_FATHER(entry), GB_get_quark(entry), entry, skip_over);
348}
349
350GBDATA *GB_brother(GBDATA *entry, const char *key) {
351    // searches (first) brother (before or after) of 'entry' which has field 'key'
352    // i.e. does same as GB_entry(GB_get_father(entry), key)
353    return GB_find(entry, key, SEARCH_BROTHER);
354}
355
356GBDATA *gb_find_by_nr(GBCONTAINER *father, int index) {
357    /* get a subentry by its internal number:
358       Warning: This subentry must exists, otherwise internal error */
359
360    gb_header_list *header = GB_DATA_LIST_HEADER(father->d);
361    if (index >= father->d.nheader || index <0) {
362        GB_internal_errorf("Index '%i' out of range [%i:%i[", index, 0, father->d.nheader);
363        return NULL;
364    }
365    if (header[index].flags.changed >= GB_DELETED || !header[index].flags.key_quark) {
366        GB_internal_error("Entry already deleted");
367        return NULL;
368    }
369
370    GBDATA *gb = GB_HEADER_LIST_GBD(header[index]);
371    if (!gb) {
372        gb_unfold(father, 0, index);
373        header = GB_DATA_LIST_HEADER(father->d);
374        gb = GB_HEADER_LIST_GBD(header[index]);
375        if (!gb) {
376            GB_internal_error("Could not unfold data");
377            return NULL;
378        }
379    }
380    return gb;
381}
382
383class keychar_table {
384    bool table[256];
385public:
386    keychar_table() {
387        for (int i=0; i<256; i++) {
388            table[i] = islower(i) || isupper(i) || isdigit(i) || i=='_' || i=='@';
389        }
390    }
391    const char *first_non_key_character(const char *str) const {
392        while (1) {
393            int c = *str;
394            if (!table[c]) {
395                if (c == 0) break;
396                return str;
397            }
398            str++;
399        }
400        return NULL;
401    }
402};
403static keychar_table keychars;
404
405const char *GB_first_non_key_char(const char *str) {
406    return keychars.first_non_key_character(str);
407}
408
409inline GBDATA *find_or_create(GBCONTAINER *gb_parent, const char *key, GB_TYPES create, bool internflag) {
410    gb_assert(!keychars.first_non_key_character(key));
411
412    GBDATA *gbd = GB_entry(gb_parent, key);
413    if (create) {
414        if (gbd) {
415            GB_TYPES oldType = gbd->type();
416            if (create != oldType) { // type mismatch
417                GB_export_errorf("Inconsistent type for field '%s' (existing=%i, expected=%i)", key, oldType, create);
418                gbd = NULL;
419            }
420        }
421        else {
422            if (create == GB_CREATE_CONTAINER) {
423                gbd = internflag ? gb_create_container(gb_parent, key) : GB_create_container(gb_parent, key);
424            }
425            else {
426                gbd = gb_create(gb_parent, key, create);
427            }
428            gb_assert(gbd || GB_have_error());
429        }
430    }
431    return gbd;
432}
433
434GBDATA *gb_search(GBCONTAINER *gbc, const char *key, GB_TYPES create, int internflag) {
435    /* finds a hierarchical key,
436     * if create != GB_FIND(==0), then create the key
437     * force types if ! internflag
438     */
439
440    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
441
442    GB_test_transaction(gbc);
443
444    if (!key) return NULL; // was allowed in the past (and returned the 1st child). now returns NULL
445   
446    if (key[0] == '/') {
447        gbc = gb_get_root(gbc);
448        key++;
449    }
450
451    if (!key[0]) {
452        return gbc;
453    }
454
455    GBDATA     *gb_result     = NULL;
456    const char *separator     = keychars.first_non_key_character(key);
457    if (!separator) gb_result = find_or_create(gbc, key, create, internflag);
458    else {
459        int  len = separator-key;
460        char firstKey[len+1];
461        memcpy(firstKey, key, len);
462        firstKey[len] = 0;
463
464        char invalid_char = 0;
465
466        switch (separator[0]) {
467            case '/': {
468                GBDATA *gb_sub = find_or_create(gbc, firstKey, create ? GB_CREATE_CONTAINER : GB_FIND, internflag);
469                if (gb_sub) {
470                    if (gb_sub->is_container()) {
471                        if (separator[1] == '/') {
472                            GB_export_errorf("Invalid '//' in key '%s'", key);
473                        }
474                        else {
475                            gb_result = gb_search(gb_sub->as_container(), separator+1, create, internflag);
476                        }
477                    }
478                    else {
479                        GB_export_errorf("terminal entry '%s' cannot be used as container", firstKey);
480                    }
481                }
482                break;
483            }
484            case '.': {
485                if (separator[1] != '.') invalid_char = separator[0];
486                else {
487                    GBCONTAINER *gb_parent = gbc->get_father();
488                    if (gb_parent) {
489                        switch (separator[2]) {
490                            case 0:   gb_result    = gb_parent; break;
491                            case '/': gb_result    = gb_search(gb_parent, separator+3, create, internflag); break;
492                            default:
493                                GB_export_errorf("Expected '/' after '..' in key '%s'", key);
494                                break;
495                        }
496                    }
497                    else { // ".." at root-node
498                        if (create) {
499                            GB_export_error("cannot use '..' at root node");
500                        }
501                    }
502                }
503                break;
504            }
505            default:
506                invalid_char = separator[0];
507                break;
508        }
509
510        if (invalid_char) {
511            gb_assert(!gb_result);
512            GB_export_errorf("Invalid char '%c' in key '%s'", invalid_char, key);
513        }
514    }
515    gb_assert(!(gb_result && GB_have_error()));
516    return gb_result;
517}
518
519
520GBDATA *GB_search(GBDATA *gbd, const char *fieldpath, GB_TYPES create) {
521    return gb_search(gbd->expect_container(), fieldpath, create, 0);
522}
523
524static GBDATA *gb_expect_type(GBDATA *gbd, GB_TYPES expected_type, const char *fieldname) {
525    gb_assert(expected_type != GB_FIND); // impossible
526
527    GB_TYPES type = gbd->type();
528    if (type != expected_type) {
529        GB_export_errorf("Field '%s' has wrong type (found=%i, expected=%i)", fieldname, type, expected_type);
530        gbd = 0;
531    }
532    return gbd;
533}
534
535GBDATA *GB_searchOrCreate_string(GBDATA *gb_container, const char *fieldpath, const char *default_value) {
536    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
537
538    GBDATA *gb_str = GB_search(gb_container, fieldpath, GB_FIND);
539    if (!gb_str) {
540        GB_clear_error();
541        gb_str = GB_search(gb_container, fieldpath, GB_STRING);
542        GB_ERROR error;
543
544        if (!gb_str) error = GB_await_error();
545        else error         = GB_write_string(gb_str, default_value);
546
547        if (error) {
548            gb_str = 0;
549            GB_export_error(error);
550        }
551    }
552    else {
553        gb_str = gb_expect_type(gb_str, GB_STRING, fieldpath);
554    }
555    return gb_str;
556}
557
558GBDATA *GB_searchOrCreate_int(GBDATA *gb_container, const char *fieldpath, long default_value) {
559    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
560
561    GBDATA *gb_int = GB_search(gb_container, fieldpath, GB_FIND);
562    if (!gb_int) {
563        gb_int = GB_search(gb_container, fieldpath, GB_INT);
564        GB_ERROR error;
565
566        if (!gb_int) error = GB_await_error();
567        else error         = GB_write_int(gb_int, default_value);
568
569        if (error) { // @@@ in case GB_search returned 0, gb_int already is 0 and error is exported. just assert error is exported
570            gb_int = 0;
571            GB_export_error(error);
572        }
573    }
574    else {
575        gb_int = gb_expect_type(gb_int, GB_INT, fieldpath);
576    }
577    return gb_int;
578}
579
580GBDATA *GB_searchOrCreate_float(GBDATA *gb_container, const char *fieldpath, float default_value) {
581    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
582
583    GBDATA *gb_float = GB_search(gb_container, fieldpath, GB_FIND);
584    if (!gb_float) {
585        gb_float = GB_search(gb_container, fieldpath, GB_FLOAT);
586        GB_ERROR error;
587
588        if (!gb_float) error = GB_await_error();
589        else error           = GB_write_float(gb_float, default_value);
590
591        if (error) {
592            gb_float = 0;
593            GB_export_error(error);
594        }
595    }
596    else {
597        gb_float = gb_expect_type(gb_float, GB_FLOAT, fieldpath);
598    }
599    return gb_float;
600}
601
602static GBDATA *gb_search_marked(GBCONTAINER *gbc, GBQUARK key_quark, int firstindex, size_t skip_over) {
603    int             userbit = GBCONTAINER_MAIN(gbc)->users[0]->userbit;
604    int             index;
605    int             end     = gbc->d.nheader;
606    gb_header_list *header  = GB_DATA_LIST_HEADER(gbc->d);
607
608    for (index = firstindex; index<end; index++) {
609        GBDATA *gb;
610
611        if (! (userbit & header[index].flags.flags)) continue;
612        if ((key_quark>=0) && (header[index].flags.key_quark  != key_quark)) continue;
613        if (header[index].flags.changed >= GB_DELETED) continue;
614        if ((gb=GB_HEADER_LIST_GBD(header[index]))==NULL) {
615            gb_unfold(gbc, 0, index);
616            header = GB_DATA_LIST_HEADER(gbc->d);
617            gb = GB_HEADER_LIST_GBD(header[index]);
618        }
619        if (!skip_over--) return gb;
620    }
621    return NULL;
622}
623
624long GB_number_of_marked_subentries(GBDATA *gbd) {
625    long count = 0;
626    if (gbd->is_container()) {
627        GBCONTAINER    *gbc    = gbd->as_container();
628        gb_header_list *header = GB_DATA_LIST_HEADER(gbc->d);
629
630        int userbit = GBCONTAINER_MAIN(gbc)->users[0]->userbit;
631        int end     = gbc->d.nheader;
632
633        for (int index = 0; index<end; index++) {
634            if (!(userbit & header[index].flags.flags)) continue;
635            if (header[index].flags.changed >= GB_DELETED) continue;
636            count++;
637        }
638    }
639    return count;
640}
641
642
643
644GBDATA *GB_first_marked(GBDATA *gbd, const char *keystring) {
645    GBCONTAINER *gbc       = gbd->expect_container();
646    GBQUARK      key_quark = GB_find_existing_quark(gbd, keystring);
647    GB_test_transaction(gbc);
648    return key_quark ? gb_search_marked(gbc, key_quark, 0, 0) : NULL;
649}
650
651
652GBDATA *GB_following_marked(GBDATA *gbd, const char *keystring, size_t skip_over) {
653    GBCONTAINER *gbc       = GB_FATHER(gbd);
654    GBQUARK      key_quark = GB_find_existing_quark(gbd, keystring);
655    GB_test_transaction(gbc);
656    return key_quark ? gb_search_marked(gbc, key_quark, (int)gbd->index+1, skip_over) : NULL;
657}
658
659GBDATA *GB_next_marked(GBDATA *gbd, const char *keystring) {
660    return GB_following_marked(gbd, keystring, 0);
661}
662
663// ----------------------------
664//      Command interpreter
665
666void gb_install_command_table(GBDATA *gb_main, struct GBL_command_table *table, size_t table_size) {
667    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
668    if (!Main->command_hash) Main->command_hash = GBS_create_hash(table_size, GB_IGNORE_CASE);
669
670    for (; table->command_identifier; table++) {
671        GBS_write_hash(Main->command_hash, table->command_identifier, (long)table->function);
672    }
673
674    gb_assert((GBS_hash_elements(Main->command_hash)+1) == table_size);
675}
676
677static char *gbs_search_second_x(const char *str) {
678    int c;
679    for (; (c=*str); str++) {
680        if (c=='\\') {      // escaped characters
681            str++;
682            if (!(c=*str)) return NULL;
683            continue;
684        }
685        if (c=='"') return (char *)str;
686    }
687    return NULL;
688}
689
690char *gbs_search_second_bracket(const char *source) {
691    int c;
692    int deep = 0;
693    if (*source != '(') deep --;    // first bracket
694    for (; (c=*source); source++) {
695        if (c=='\\') {      // escaped characters
696            source++;
697            if (!*source) break;
698            continue;
699        }
700        if (c=='(') deep--;
701        else if (c==')') deep++;
702        if (!deep) return (char *)source;
703        if (c=='"') {       // search the second "
704            source = gbs_search_second_x(source);
705            if (!source) return NULL;
706        }
707    }
708    if (!c) return NULL;
709    return (char *)source;
710}
711
712
713static char *gbs_search_next_separator(const char *source, const char *seps) {
714    // search the next separator
715    static char tab[256];
716    static int flag = 0;
717    int c;
718    const char *p;
719    if (!flag) {
720        flag = 1;
721        memset(tab, 0, 256);
722    }
723    for (p = seps; (c=*p); p++) tab[c] = 1; // tab[seps[x]] = 1
724    tab['('] = 1;               // exclude () pairs
725    tab['"'] = 1;               // exclude " pairs
726    tab['\\'] = 1;              // exclude \-escaped chars
727
728    for (; (c=*source); source++) {
729        if (tab[c]) {
730            if (c=='\\') {
731                source++;
732                continue;
733            }
734            if (c=='(') {
735                source = gbs_search_second_bracket(source);
736                if (!source) break;
737                continue;
738            }
739            if (c=='"') {
740                source = gbs_search_second_x(source+1);
741                if (!source) break;
742                continue;
743            }
744            for (p = seps; (c=*p); p++) tab[c] = 0;
745            return (char *)source;
746        }
747    }
748    for (p = seps; (c=*p); p++) tab[c] = 0; // clear tab
749    return NULL;
750}
751
752static void dumpStreams(const char *name, const GBL_streams& streams) {
753    int count = streams.size();
754    printf("%s=%i\n", name, count);
755    if (count > 0) {
756        for (int c = 0; c<count; c++) {
757            printf("  %02i='%s'\n", c, streams.get(c));
758        }
759    }
760}
761
762static const char *shortenLongString(const char *str, size_t wanted_len) {
763    // shortens the string 'str' to 'wanted_len' (appends '[..]' if string was shortened)
764
765    const char *result;
766    size_t      len = strlen(str);
767
768    gb_assert(wanted_len>4);
769
770    if (len>wanted_len) {
771        static char   *shortened_str;
772        static size_t  short_len = 0;
773
774        if (short_len >= wanted_len) {
775            memcpy(shortened_str, str, wanted_len-4);
776        }
777        else {
778            freeset(shortened_str, ARB_strpartdup(str, str+wanted_len));
779            short_len = wanted_len;
780        }
781        strcpy(shortened_str+wanted_len-4, "[..]");
782        result = shortened_str;
783    }
784    else {
785        result = str;
786    }
787    return result;
788}
789
790static char *apply_ACI(GBDATA *gb_main, const char *commands, const char *str, GBDATA *gbd, const char *default_tree_name) {
791    int trace = GB_get_ACISRT_trace();
792
793    GB_MAIN_TYPE *Main    = GB_MAIN(gb_main);
794    gb_local->gbl.gb_main = gb_main;
795
796    char *buffer = ARB_strdup(commands);
797
798    // ********************** remove all spaces and tabs *******************
799    {
800        const char *s1;
801        char *s2;
802        s1 = commands;
803        s2 = buffer;
804        {
805            int c;
806            for (; (c = *s1); s1++) {
807                if (c=='\\') {
808                    *(s2++) = c;
809                    if (!(c=*++s1)) { break; }
810                    *(s2++) = c;
811                    continue;
812                }
813
814                if (c=='"') {       // search the second "
815                    const char *hp = gbs_search_second_x(s1+1);
816                    if (!hp) {
817                        GB_export_errorf("unbalanced '\"' in '%s'", commands);
818                        free(buffer);
819                        return NULL;
820                    }
821                    while (s1 <= hp) *(s2++) = *(s1++);
822                    s1--;
823                    continue;
824                }
825                if (c!=' ' && c!='\t') *(s2++) = c;
826            }
827        }
828        *s2 = 0;
829    }
830
831    GBL_streams orig;
832
833    orig.insert(ARB_strdup(str));
834
835    GB_ERROR error = NULL;
836    GBL_streams out;
837    {
838        char *s1, *s2;
839        s1 = buffer;
840        if (*s1 == '|') s1++;
841
842        // ** loop over all commands **
843        for (; s1;  s1 = s2) {
844            int separator;
845            GBL_COMMAND command;
846            s2 = gbs_search_next_separator(s1, "|;,");
847            if (s2) {
848                separator = *(s2);
849                *(s2++) = 0;
850            }
851            else {
852                separator = 0;
853            }
854            // collect the parameters
855            GBL_streams in;
856            if (*s1 == '"') {           // copy "text" to out
857                char *end = gbs_search_second_x(s1+1);
858                if (!end) {
859                    UNCOVERED(); // seems unreachable (balancing is already ensured by gbs_search_next_separator)
860                    error = "Missing second '\"'";
861                    break;
862                }
863                *end = 0;
864
865                out.insert(ARB_strdup(s1+1));
866            }
867            else {
868                char *bracket   = strchr(s1, '(');
869
870                if (bracket) {      // I got the parameter list
871                    int slen;
872                    *(bracket++) = 0;
873                    slen  = strlen(bracket);
874                    if (bracket[slen-1] != ')') {
875                        error = "Missing ')'";
876                    }
877                    else {
878                        // go through the parameters
879                        char *p1, *p2;
880                        bracket[slen-1] = 0;
881                        for (p1 = bracket; p1;  p1 = p2) {
882                            p2 = gbs_search_next_separator(p1, ";,");
883                            if (p2) {
884                                *(p2++) = 0;
885                            }
886                            if (p1[0] == '"') { // remove "" pairs
887                                int len2;
888                                p1++;
889                                len2 = strlen(p1)-1;
890
891                                if (p1[len2] != '\"') {
892                                    error = "Missing '\"'";
893                                }
894                                else {
895                                    p1[len2] = 0;
896                                }
897                            }
898                            in.insert(ARB_strdup(p1));
899                        }
900                    }
901                }
902                if (!error && (bracket || *s1)) {
903                    char *p = s1;
904                    int c;
905                    while ((c = *p)) {          // command to lower case
906                        if (c>='A' && c<='Z') {
907                            c += 'a'-'A';
908                            *p = c;
909                        }
910                        p++;
911                    }
912
913                    command = (GBL_COMMAND)GBS_read_hash(Main->command_hash, s1);
914                    if (!command) {
915                        error = GBS_global_string("Unknown command '%s'", s1);
916                    }
917                    else {
918                        GBL_command_arguments args(gbd, default_tree_name, orig, in, out);
919
920                        args.command = s1;
921
922                        if (trace) {
923                            printf("-----------------------\nExecution of command '%s':\n", args.command);
924                            dumpStreams("Arguments", args.param);
925                            dumpStreams("InputStreams", args.input);
926                        }
927
928                        error = command(&args); // execute the command
929
930                        if (!error && trace) dumpStreams("OutputStreams", args.output);
931
932                        if (error) {
933                            char *dup_error = ARB_strdup(error);
934
935#define MAX_PRINT_LEN 200
936
937                            char *paramlist = 0;
938                            for (int j = 0; j<args.param.size(); ++j) {
939                                const char *param       = args.param.get(j);
940                                const char *param_short = shortenLongString(param, MAX_PRINT_LEN);
941
942                                if (!paramlist) paramlist = ARB_strdup(param_short);
943                                else freeset(paramlist, GBS_global_string_copy("%s,%s", paramlist, param_short));
944                            }
945                            char *inputstreams = 0;
946                            for (int j = 0; j<args.input.size(); ++j) {
947                                const char *input       = args.input.get(j);
948                                const char *input_short = shortenLongString(input, MAX_PRINT_LEN);
949
950                                if (!inputstreams) inputstreams = ARB_strdup(input_short);
951                                else freeset(inputstreams, GBS_global_string_copy("%s;%s", inputstreams, input_short));
952                            }
953#undef MAX_PRINT_LEN
954                            if (paramlist) {
955                                error = GBS_global_string("while applying '%s(%s)'\nto '%s':\n%s", s1, paramlist, inputstreams, dup_error);
956                            }
957                            else {
958                                error = GBS_global_string("while applying '%s'\nto '%s':\n%s", s1, inputstreams, dup_error);
959                            }
960
961                            free(inputstreams);
962                            free(paramlist);
963                            free(dup_error);
964                        }
965                    }
966                }
967            }
968
969            if (error) break;
970
971            if (separator == '|') { // out -> in pipe; clear in
972                out.swap(orig);
973                out.erase();
974            }
975        }
976    }
977
978    {
979        char *s1 = out.concatenated();
980        free(buffer);
981
982        if (!error) {
983            if (trace) printf("GB_command_interpreter: result='%s'\n", s1);
984            return s1;
985        }
986        free(s1);
987    }
988
989    GB_export_errorf("Command '%s' failed:\nReason: %s", commands, error);
990    return NULL;
991}
992// --------------------------------------------------------------------------------
993
994char *GBL_streams::concatenated() const {
995    int count = size();
996    if (!count) return ARB_strdup("");
997    if (count == 1) return ARB_strdup(get(0));
998
999    GBS_strstruct *strstruct = GBS_stropen(1000);
1000    for (int i=0; i<count; i++) {
1001        const char *s = get(i);
1002        if (s) GBS_strcat(strstruct, s);
1003    }
1004    return GBS_strclose(strstruct);
1005}
1006
1007char *GB_command_interpreter(GBDATA *gb_main, const char *str, const char *commands, GBDATA *gbd, const char *default_tree_name) {
1008    /* simple command interpreter returns NULL on error (which should be exported in that case)
1009     * if first character is == ':' run string parser
1010     * if first character is == '/' run regexpr
1011     * else run ACI
1012     */
1013
1014    int trace = GB_get_ACISRT_trace();
1015    SmartMallocPtr(char) heapstr;
1016
1017    if (!str) {
1018        if (!gbd) {
1019            GB_export_error("ACI: no input streams found");
1020            return NULL;
1021        }
1022
1023        if (GB_read_type(gbd) == GB_STRING) {
1024            str = GB_read_char_pntr(gbd);
1025        }
1026        else {
1027            char *asstr = GB_read_as_string(gbd);
1028            if (!asstr) {
1029                GB_export_error("Can't read this DB entry as string");
1030                return NULL;
1031            }
1032
1033            heapstr = asstr;
1034            str     = &*heapstr;
1035        }
1036    }
1037
1038    if (trace) {
1039        printf("GB_command_interpreter: str='%s'\n"
1040               "                        command='%s'\n", str, commands);
1041    }
1042
1043    if (!commands || !commands[0]) { // empty command -> do not modify string
1044        return ARB_strdup(str);
1045    }
1046
1047    if (commands[0] == ':') { // ':' -> string parser
1048        return GBS_string_eval(str, commands+1, gbd);
1049    }
1050
1051    if (commands[0] == '/') { // regular expression
1052        GB_ERROR  err    = 0;
1053        char     *result = GBS_regreplace(str, commands, &err);
1054
1055        if (!result) {
1056            if (strcmp(err, "Missing '/' between search and replace string") == 0) {
1057                // if GBS_regreplace didn't find a third '/' -> silently use GBS_regmatch:
1058                size_t matchlen;
1059                err    = 0;
1060                const char *matched = GBS_regmatch(str, commands, &matchlen, &err);
1061
1062                if (matched) result   = ARB_strndup(matched, matchlen);
1063                else if (!err) result = ARB_strdup("");
1064            }
1065
1066            if (!result && err) result = GBS_global_string_copy("<Error: %s>", err);
1067        }
1068        return result;
1069    }
1070
1071    return apply_ACI(gb_main, commands, str, gbd, default_tree_name);
1072}
1073
1074// --------------------------------------------------------------------------------
1075
1076#ifdef UNIT_TESTS
1077#include <test_unit.h>
1078
1079#define TEST_CI__INTERNAL(input,cmd,expected,got,TEST_RESULT) do {                                                      \
1080        char *result;                                                                                                   \
1081        TEST_EXPECT_RESULT__NOERROREXPORTED(result = GB_command_interpreter(gb_main, input, cmd, gb_data, NULL));       \
1082        TEST_RESULT(result,expected,got);                                                                               \
1083        free(result);                                                                                                   \
1084    } while(0)
1085
1086#define TEST_CI(input,cmd,expected)              TEST_CI__INTERNAL(input,cmd,expected,narg, TEST_EXPECT_EQUAL__IGNARG)
1087#define TEST_CI__BROKEN(input,cmd,expected,regr) TEST_CI__INTERNAL(input,cmd,expected,regr, TEST_EXPECT_EQUAL__BROKEN)
1088#define TEST_CI_NOOP(inandout,cmd)               TEST_CI__INTERNAL(inandout,cmd,inandout,narg,TEST_EXPECT_EQUAL__IGNARG)
1089#define TEST_CI_NOOP__BROKEN(inandout,regr,cmd)  TEST_CI__INTERNAL(inandout,cmd,inandout,regr,TEST_EXPECT_EQUAL__BROKEN)
1090
1091#define TEST_CI_INVERSE(in,cmd,inv_cmd,out) do {        \
1092        TEST_CI(in,  cmd,     out);                     \
1093        TEST_CI(out, inv_cmd, in);                      \
1094    } while(0)
1095
1096#define TEST_CI_ERROR_CONTAINS(input,cmd,errorpart_expected) \
1097    TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_command_interpreter(gb_main, input, cmd, gb_data, NULL), errorpart_expected)
1098
1099#define ACI_SPLIT          "|split(\",\",0)"
1100#define ACI_MERGE          "|merge(\",\")"
1101#define WITH_SPLITTED(aci) ACI_SPLIT aci ACI_MERGE
1102
1103void TEST_GB_command_interpreter() {
1104    GB_shell  shell;
1105    GBDATA   *gb_main = GB_open("TEST_nuc.arb", "rw"); // ../UNIT_TESTER/run/TEST_nuc.arb
1106
1107    GBT_set_default_alignment(gb_main, "ali_16s"); // for sequence ACI command (@@@ really needed ? )
1108
1109    int old_trace = GB_get_ACISRT_trace();
1110#if 0
1111    GB_set_ACISRT_trace(1); // used to detect coverage and for debugging purposes
1112#endif
1113    {
1114        GB_transaction  ta(gb_main);
1115        GBDATA         *gb_data = GBT_find_species(gb_main, "LcbReu40");
1116
1117        TEST_CI_NOOP("bla", "");
1118
1119        TEST_CI("bla", ":a=u", "blu"); // simple SRT
1120
1121        TEST_CI("bla",    "/a/u/",   "blu"); // simple regExp replace
1122        TEST_CI("blabla", "/l.*b/",  "lab"); // simple regExp match
1123        TEST_CI("blabla", "/b.b/",   "");    // simple regExp match (failing)
1124
1125        // escape / quote
1126        TEST_CI_INVERSE("ac", "|quote",        "|unquote",          "\"ac\"");
1127        TEST_CI_INVERSE("ac", "|escape",       "|unescape",         "ac");
1128        TEST_CI_INVERSE("ac", "|escape|quote", "|unquote|unescape", "\"ac\"");
1129        TEST_CI_INVERSE("ac", "|quote|escape", "|unescape|unquote", "\\\"ac\\\"");
1130
1131        TEST_CI_INVERSE("a\"b\\c", "|quote",        "|unquote",          "\"a\"b\\c\"");
1132        TEST_CI_INVERSE("a\"b\\c", "|escape",       "|unescape",         "a\\\"b\\\\c");
1133        TEST_CI_INVERSE("a\"b\\c", "|escape|quote", "|unquote|unescape", "\"a\\\"b\\\\c\"");
1134        TEST_CI_INVERSE("a\"b\\c", "|quote|escape", "|unescape|unquote", "\\\"a\\\"b\\\\c\\\"");
1135
1136        TEST_CI_NOOP("ac", "|unquote");
1137        TEST_CI_NOOP("\"ac", "|unquote");
1138        TEST_CI_NOOP("ac\"", "|unquote");
1139
1140        TEST_CI("blabla", "|coUNT(ab)",         "4");   // simple ACI
1141        TEST_CI("l",      "|\"b\";dd;\"a\"|dd", "bla"); // ACI with muliple streams
1142        TEST_CI("bla",    "|count()",           "0");   // one empty parameter
1143        TEST_CI("bla",    "|count(\"\")",       "0");   // empty parameter
1144        TEST_CI("b a",    "|count(\" \")",      "1");   // space in quotes
1145        TEST_CI("b\\a",   "|count(\\a)",        "2");   // count '\\' and 'a' (ok)
1146        TEST_CI__BROKEN("b\\a",   "|count(\"\\a\")",    "1", "2"); // should only count 'a' (which is escaped in param)
1147        TEST_CI("b\\a",   "|count(\"\a\")",     "0");   // does not contain '\a'
1148        TEST_CI("b\a",    "|count(\"\a\")",     "1");   // counts '\a'
1149
1150        // escaping (@@@ wrong behavior?)
1151        TEST_CI("b\\a",   "|count(\\a)",         "2"); // i would expect '1' as result (the 'a'), but it counts '\\' and 'a'
1152        TEST_CI("b\\a",   "|contains(\"\\\\\")", "0"); // searches for 2 backslashes, finds none
1153        TEST_CI__BROKEN("b\\a",   "|contains(\"\")",     "0", "1"); // search for nothing,              but reports 1 hit
1154        TEST_CI__BROKEN("b\\a",   "|contains(\\)",       "1", "2"); // searches for 1 backslash (ok),   but reports two hits instead of one
1155        TEST_CI__BROKEN("b\\\\a", "|contains(\"\\\\\")", "1", "2"); // searches for 2 backslashes (ok), but reports two hits instead of one
1156        TEST_CI_ERROR_CONTAINS("b\\a", "|contains(\"\\\")", "ARB ERROR: unbalanced '\"' in '|contains(\"\\\")'"); // raises error (should search for 1 backslash)
1157
1158        // test binary ops
1159        TEST_CI("", "\"5\";\"7\"|minus",            "-2");
1160        TEST_CI("", "\"5\"|minus(\"7\")",           "-2");
1161        TEST_CI("", "|minus(\"\"5\"\", \"\"7\"\")", "-2"); // @@@ syntax needed here 'minus(""5"", ""7"")' is broken - need to unescape correctly after parsing parameters!
1162
1163
1164        TEST_CI_NOOP("ab,bcb,abac", WITH_SPLITTED(""));
1165        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|len"),                       "2,3,4");
1166        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|count(a)"),                  "1,0,2");
1167        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(len,count(a))"),       "1,3,2");
1168        TEST_CI     ("ab,bcb,abac", WITH_SPLITTED("|minus(\"\"5\"\",count(a))"), "4,5,3"); // @@@ escaping broken here as well
1169
1170        // test other recursive uses of GB_command_interpreter
1171        TEST_CI("one",   "|dd;\"two\";dd|command(\"dd;\"_\";dd;\"-\"\")",                          "one_one-two_two-one_one-");
1172        TEST_CI("",      "|sequence|command(\"/^([\\\\.-]*)[A-Z].*/\\\\1/\")|len",                 "9"); // count gaps at start of sequence
1173        TEST_CI("one",   "|dd;dd|eval(\"\"up\";\"per\"|merge\")",                                  "ONEONE");
1174        TEST_CI("1,2,3", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "one,two,three");
1175        TEST_CI_ERROR_CONTAINS("1,4", WITH_SPLITTED("|select(\"\"\"\"one\"\", \"\"two\"\", \"\"three\"\")"), "Illegal param number '4' (allowed [0..3])");
1176
1177        // test define and do (@@@ SLOW)
1178        TEST_CI("ignored", "define(d4, \"dd;dd;dd;dd\")",        "");
1179        TEST_CI("ignored", "define(d16, \"do(d4)|do(d4)\")",     "");
1180        TEST_CI("ignored", "define(d64, \"do(d4)|do(d16)\")",    "");
1181        TEST_CI("ignored", "define(d4096, \"do(d64)|do(d64)\")", "");
1182
1183        TEST_CI("x",  "do(d4)",        "xxxx");
1184        TEST_CI("xy", "do(d4)",        "xyxyxyxy");
1185        TEST_CI("x",  "do(d16)",       "xxxxxxxxxxxxxxxx");
1186        TEST_CI("x",  "do(d64)|len",   "64");
1187        TEST_CI("xy", "do(d4)|len",    "8");
1188        TEST_CI("xy", "do(d4)|len()",  "8");
1189        TEST_CI("xy", "do(d4)|len(x)", "4");
1190        TEST_CI("x",  "do(d4096)|len", "4096");
1191
1192        {
1193            int prev_trace = GB_get_ACISRT_trace();
1194            GB_set_ACISRT_trace(0); // do not trace here
1195            // create 4096 streams:
1196            TEST_CI("x",  "dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|dd;dd|streams", "4096");
1197            GB_set_ACISRT_trace(prev_trace);
1198        }
1199
1200        // other commands
1201
1202        // streams
1203        TEST_CI("x", "dd;dd|streams",             "2");
1204        TEST_CI("x", "dd;dd|dd;dd|streams",       "4");
1205        TEST_CI("x", "dd;dd|dd;dd|dd;dd|streams", "8");
1206        TEST_CI("x", "do(d4)|streams",            "1"); // stream is merged when do() returns
1207
1208        TEST_CI("", "ali_name", "ali_16s");  // ask for default-alignment name
1209        TEST_CI("", "sequence_type", "rna"); // ask for sequence_type of default-alignment
1210
1211        // format
1212        TEST_CI("acgt", "format", "          acgt");
1213        TEST_CI("acgt", "format(firsttab=1)", " acgt");
1214        TEST_CI("acgt", "format(firsttab=1, width=2)",
1215                " ac\n"
1216                "          gt");
1217        TEST_CI("acgt", "format(firsttab=1,tab=1,width=2)",
1218                " ac\n"
1219                " gt");
1220        TEST_CI("acgt", "format(firsttab=0,tab=0,width=2)",
1221                "ac\n"
1222                "gt");
1223        TEST_CI("acgt", "format(firsttab=0,tab=0,width=1)",
1224                "a\n"
1225                "c\n"
1226                "g\n"
1227                "t");
1228
1229        TEST_CI_ERROR_CONTAINS("acgt", "format(gap=0)",   "Unknown Parameter 'gap=0' in command 'format'");
1230        TEST_CI_ERROR_CONTAINS("acgt", "format(numleft)", "Unknown Parameter 'numleft' in command 'format'");
1231
1232        // format_sequence
1233        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(numright=5, numleft)", "You may only specify 'numleft' OR 'numright',  not both");
1234
1235        TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numleft=1)",
1236                "1    acgt\n"
1237                "5    acgt\n"
1238                "9    acgt\n"
1239                "13   acg");
1240
1241        TEST_CI("acgtacgtacgtacg", "format_sequence(firsttab=5,tab=5,width=4,numright=9)", // test EMBL sequence formatting
1242                "     acgt         4\n"
1243                "     acgt         8\n"
1244                "     acgt        12\n"
1245                "     acg         15");
1246
1247        TEST_CI("acgtacgtacgtac", "format_sequence(firsttab=5,tab=5,width=4,gap=2,numright=-1)", // autodetect width for 'numright'
1248                "     ac gt  4\n"
1249                "     ac gt  8\n"
1250                "     ac gt 12\n"
1251                "     ac    14");
1252
1253        TEST_CI("acgt", "format_sequence(firsttab=0,tab=0,width=2,gap=1)",
1254                "a c\n"
1255                "g t");
1256        TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=1)", "a c g t");
1257        TEST_CI("acgt",     "format_sequence(firsttab=0,tab=0,width=4,gap=2)", "ac gt");
1258        TEST_CI("acgtacgt", "format_sequence(firsttab=0,width=10,gap=4)",      "acgt acgt");
1259        TEST_CI("acgtacgt", "format_sequence(firsttab=1,width=10,gap=4)",      " acgt acgt");
1260
1261        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(nl=c)",     "Unknown Parameter 'nl=c' in command 'format_sequence'");
1262        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(forcenl=)", "Unknown Parameter 'forcenl=' in command 'format_sequence'");
1263
1264        TEST_CI_ERROR_CONTAINS("acgt", "format(width=0)",          "Illegal zero width");
1265        TEST_CI_ERROR_CONTAINS("acgt", "format_sequence(width=0)", "Illegal zero width");
1266
1267        // remove + keep
1268        TEST_CI_NOOP("acgtacgt",         "remove(-.)");
1269        TEST_CI     ("..acg--ta-cgt...", "remove(-.)", "acgtacgt");
1270        TEST_CI     ("..acg--ta-cgt...", "remove(acgt)", "..---...");
1271
1272        TEST_CI_NOOP("acgtacgt",         "keep(acgt)");
1273        TEST_CI     ("..acg--ta-cgt...", "keep(-.)", "..---...");
1274        TEST_CI     ("..acg--ta-cgt...", "keep(acgt)", "acgtacgt");
1275
1276        // compare + icompare
1277        TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|compare"),  "-1,0,1,1,1,-1");
1278        TEST_CI("x,z,y,y,z,x,x,Z,y,Y,Z,x", WITH_SPLITTED("|icompare"), "-1,0,1,-1,0,1");
1279
1280        TEST_CI("x,y,z", WITH_SPLITTED("|compare(\"y\")"), "-1,0,1");
1281
1282        // equals + iequals
1283        TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|equals"),  "0,1,0");
1284        TEST_CI("a,b,a,a,a,A", WITH_SPLITTED("|iequals"), "0,1,1");
1285
1286        // contains + icontains
1287        TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|contains(\"bc\")"),   "2,1,0");
1288        TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"bc\")"),  "2,1,1");
1289        TEST_CI("abc,bcd,BCD", WITH_SPLITTED("|icontains(\"d\")"),   "0,3,3");
1290
1291        // partof + ipartof
1292        TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|partof(\"abcdefg\")"),   "1,0,4,0");
1293        TEST_CI("abc,BCD,def,deg", WITH_SPLITTED("|ipartof(\"abcdefg\")"),  "1,2,4,0");
1294
1295        // translate
1296        TEST_CI("abcdefgh", "translate(abc,cba)",     "cbadefgh");
1297        TEST_CI("abcdefgh", "translate(cba,abc)",     "cbadefgh");
1298        TEST_CI("abcdefgh", "translate(hcba,abch,-)", "hcb----a");
1299        TEST_CI("abcdefgh", "translate(aceg,aceg,-)", "a-c-e-g-");
1300        TEST_CI("abbaabba", "translate(ab,ba,-)",     "baabbaab");
1301        TEST_CI("abbaabba", "translate(a,x,-)",       "x--xx--x");
1302        TEST_CI("abbaabba", "translate(,,-)",         "--------");
1303
1304        // echo
1305        TEST_CI("", "echo", "");
1306        TEST_CI("", "echo(x,y,z)", "xyz");
1307        TEST_CI("", "echo(x;y,z)", "xyz"); // check ';' as param-separator
1308        TEST_CI("", "echo(x;y;z)", "xyz");
1309        TEST_CI("", "echo(x,y,z)|streams", "3");
1310
1311        // upper, lower + caps
1312        TEST_CI("the QUICK brOwn Fox", "lower", "the quick brown fox");
1313        TEST_CI("the QUICK brOwn Fox", "upper", "THE QUICK BROWN FOX");
1314        TEST_CI("the QUICK brOwn FoX", "caps",  "The Quick Brown Fox");
1315
1316        // head, tail + mid/mid0
1317        TEST_CI     ("1234567890", "head(3)", "123");
1318        TEST_CI     ("1234567890", "head(9)", "123456789");
1319        TEST_CI_NOOP("1234567890", "head(10)");
1320        TEST_CI_NOOP("1234567890", "head(20)");
1321
1322        TEST_CI     ("1234567890", "tail(4)", "7890");
1323        TEST_CI     ("1234567890", "tail(9)", "234567890");
1324        TEST_CI_NOOP("1234567890", "tail(10)");
1325        TEST_CI_NOOP("1234567890", "tail(20)");
1326
1327        TEST_CI("1234567890", "tail(0)", "");
1328        TEST_CI("1234567890", "head(0)", "");
1329        TEST_CI("1234567890", "tail(-2)", "");
1330        TEST_CI("1234567890", "head(-2)", "");
1331
1332        TEST_CI("1234567890", "mid(3,5)", "345");
1333        TEST_CI("1234567890", "mid(2,2)", "2");
1334
1335        TEST_CI("1234567890", "mid0(3,5)", "456");
1336
1337        TEST_CI("1234567890", "mid(9,20)", "90");
1338        TEST_CI("1234567890", "mid(20,20)", "");
1339
1340        TEST_CI("1234567890", "tail(3)",     "890"); // example from ../HELP_SOURCE/oldhelp/commands.hlp@mid0
1341        TEST_CI("1234567890", "mid(-2,0)",   "890");
1342        TEST_CI("1234567890", "mid0(-3,-1)", "890");
1343
1344        // tab + pretab
1345        TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(2)"),    "x ,xx,xxx");
1346        TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(3)"),    "x  ,xx ,xxx");
1347        TEST_CI("x,xx,xxx", WITH_SPLITTED("|tab(4)"),    "x   ,xx  ,xxx ");
1348        TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(2)"), " x,xx,xxx");
1349        TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(3)"), "  x, xx,xxx");
1350        TEST_CI("x,xx,xxx", WITH_SPLITTED("|pretab(4)"), "   x,  xx, xxx");
1351
1352        // crop
1353        TEST_CI("   x  x  ",         "crop(\" \")",     "x  x");
1354        TEST_CI("\n \t  x  x \n \t", "crop(\"\t\n \")", "x  x");
1355
1356        // cut, drop, dropempty and dropzero
1357        TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|cut(2,3,5)"),        "two,three,five");
1358        TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|drop(2,3,5)"),       "one,four,six");
1359
1360        TEST_CI_ERROR_CONTAINS("a", "drop(2)", "Illegal stream number '2' (allowed [1..1])");
1361        TEST_CI_ERROR_CONTAINS("a", "drop(0)", "Illegal stream number '0' (allowed [1..1])");
1362        TEST_CI_ERROR_CONTAINS("a", "cut(2)",  "Illegal stream number '2' (allowed [1..1])");
1363        TEST_CI_ERROR_CONTAINS("a", "cut(0)",  "Illegal stream number '0' (allowed [1..1])");
1364
1365        TEST_CI("one,two,three,four,five,six", WITH_SPLITTED("|dropempty|streams"), "6");
1366        TEST_CI("one,two,,,five,six",          WITH_SPLITTED("|dropempty|streams"), "4");
1367        TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty"),         "");
1368        TEST_CI(",,,,,",                       WITH_SPLITTED("|dropempty|streams"), "0");
1369
1370        TEST_CI("1,0,0,2,3,0",                 WITH_SPLITTED("|dropzero"),          "1,2,3");
1371        TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero"),          "");
1372        TEST_CI("0,0,0,0,0,0",                 WITH_SPLITTED("|dropzero|streams"),  "0");
1373
1374        // swap
1375        TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap"),                "1,2,3,four,six,five");
1376        TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)"),           "1,3,2,four,five,six");
1377        TEST_CI("1,2,3,four,five,six", WITH_SPLITTED("|swap(2,3)|swap(4,3)"), "1,3,four,2,five,six");
1378        TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,3)"));
1379        TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(2,3)"));
1380        TEST_CI_NOOP("1,2,3,four,five,six", WITH_SPLITTED("|swap(3,2)|swap(3,1)|swap(2,1)|swap(1,3)"));
1381
1382        TEST_CI_ERROR_CONTAINS("a",   "swap",                      "need at least two input streams");
1383        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(2,3)"), "Illegal stream number '3' (allowed [1..2])");
1384        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|swap(3,2)"), "Illegal stream number '3' (allowed [1..2])");
1385
1386        // toback + tofront
1387        TEST_CI     ("front,mid,back", WITH_SPLITTED("|toback(2)"),  "front,back,mid");
1388        TEST_CI     ("front,mid,back", WITH_SPLITTED("|tofront(2)"), "mid,front,back");
1389        TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|toback(3)"));
1390        TEST_CI_NOOP("front,mid,back", WITH_SPLITTED("|tofront(1)"));
1391        TEST_CI_NOOP("a",              WITH_SPLITTED("|tofront(1)"));
1392        TEST_CI_NOOP("a",              WITH_SPLITTED("|toback(1)"));
1393
1394        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|tofront(3)"), "Illegal stream number '3' (allowed [1..2])");
1395        TEST_CI_ERROR_CONTAINS("a,b", WITH_SPLITTED("|toback(3)"),  "Illegal stream number '3' (allowed [1..2])");
1396
1397        // split (default)
1398        TEST_CI("a\nb", "|split" ACI_MERGE, "a,b");
1399
1400        // extract_words + extract_sequence
1401        TEST_CI("1,2,3,four,five,six", "extract_words(\"0123456789\",1)",                  "1 2 3");
1402        TEST_CI("1,2,3,four,five,six", "extract_words(\"abcdefghijklmnopqrstuvwxyz\", 3)", "five four six");
1403        TEST_CI("1,2,3,four,five,six", "extract_words(\"abcdefghijklmnopqrstuvwxyz\", 4)", "five four");
1404        TEST_CI("1,2,3,four,five,six", "extract_words(\"abcdefghijklmnopqrstuvwxyz\", 5)", "");
1405
1406        TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 1.0)",   "");
1407        TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.5)",   "");
1408        TEST_CI     ("1,2,3,four,five,six",    "extract_sequence(\"acgtu\", 0.0)",   "four five six");
1409        TEST_CI     ("..acg--ta-cgt...",       "extract_sequence(\"acgtu\", 1.0)",   "");
1410        TEST_CI_NOOP("..acg--ta-cgt...",       "extract_sequence(\"acgtu-.\", 1.0)");
1411        TEST_CI_NOOP("..acg--ta-ygt...",       "extract_sequence(\"acgtu-.\", 0.7)");
1412        TEST_CI     ("70 ..acg--ta-cgt... 70", "extract_sequence(\"acgtu-.\", 1.0)", "..acg--ta-cgt...");
1413
1414        // checksum + gcgchecksum
1415        TEST_CI("", "sequence|checksum",      "4C549A5F");
1416        TEST_CI("", "sequence | gcgchecksum", "4308");
1417
1418        // SRT
1419        TEST_CI(        "The quick brown fox", "srt(\"quick=lazy:brown fox=dog\")", "The lazy dog");
1420        TEST_CI__BROKEN("The quick brown fox", "srt(quick=lazy:brown fox=dog)",
1421                        "The lazy dog",        // @@@ parsing problem ?
1422                        "The lazy brown fox"); // document current (unwanted behavior)
1423        TEST_CI_ERROR_CONTAINS("x", "srt(x=y,z)", "SRT ERROR: no '=' found in command");
1424
1425        // REG
1426        TEST_CI("stars*to*stripes", "/\\*/--/", "stars--to--stripes");
1427
1428        TEST_CI("sImILaRWIllBE,GonEEASIly", WITH_SPLITTED("|command(/[A-Z]//)"),   "small,only");
1429        TEST_CI("sthBIGinside,FATnotCAP",   WITH_SPLITTED("|command(/([A-Z])+/)"), "BIG,FAT");
1430
1431        // calculator
1432        TEST_CI("", "echo(9,3)|plus",     "12");
1433        TEST_CI("", "echo(9,3)|minus",    "6");
1434        TEST_CI("", "echo(9,3)|mult",     "27");
1435        TEST_CI("", "echo(9,3)|div",      "3");
1436        TEST_CI("", "echo(9,3)|rest",     "0");
1437        TEST_CI("", "echo(9,3)|per_cent", "300");
1438
1439        TEST_CI("", "echo(1,2,3)|plus(1)" ACI_MERGE,     "2,3,4");
1440        TEST_CI("", "echo(1,2,3)|minus(2)" ACI_MERGE,    "-1,0,1");
1441        TEST_CI("", "echo(1,2,3)|mult(42)" ACI_MERGE,    "42,84,126");
1442        TEST_CI("", "echo(1,2,3)|div(2)" ACI_MERGE,      "0,1,1");
1443        TEST_CI("", "echo(1,2,3)|rest(2)" ACI_MERGE,     "1,0,1");
1444        TEST_CI("", "echo(1,2,3)|per_cent(3)" ACI_MERGE, "33,66,100");
1445
1446        // readdb
1447        TEST_CI("", "readdb(name)", "LcbReu40");
1448        TEST_CI("", "readdb(acc)",  "X76328");
1449
1450        // taxonomy
1451        TEST_CI("", "taxonomy(1)",           "No default tree");
1452        TEST_CI("", "taxonomy(tree_nuc, 1)", "group1");
1453        TEST_CI("", "taxonomy(tree_nuc, 5)", "lower-red/group1");
1454        TEST_CI_ERROR_CONTAINS("", "taxonomy", "syntax: taxonomy([tree_name,]count)");
1455
1456        // diff, filter + change
1457        TEST_CI("..acg--ta-cgt..." ","
1458                "..acg--ta-cgt...", WITH_SPLITTED("|diff(pairwise=1)"),
1459                "................");
1460        TEST_CI("..acg--ta-cgt..." ","
1461                "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,equal==)"),
1462                "==cgt=====acg===");
1463        TEST_CI("..acg--ta-cgt..." ","
1464                "..cgt--ta-acg...", WITH_SPLITTED("|diff(pairwise=1,differ=X)"),
1465                "..XXX.....XXX...");
1466        TEST_CI("", "sequence|diff(species=LcbFruct)|checksum", "645E3107");
1467
1468        TEST_CI("..XXX.....XXX..." ","
1469                "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,exclude=X)"),
1470                "..--ta-...");
1471        TEST_CI("..XXX.....XXX..." ","
1472                "..acg--ta-cgt...", WITH_SPLITTED("|filter(pairwise=1,include=X)"),
1473                "acgcgt");
1474        TEST_CI("", "sequence|filter(species=LcbFruct,include=.-)", "-----------T----T-------G----------C-----T----T...");
1475
1476        TEST_CI("...XXX....XXX..." ","
1477                "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=C,change=100)"),
1478                "..aCC--ta-CCC...");
1479        TEST_CI("...XXXXXXXXX...." ","
1480                "..acg--ta-cgt...", WITH_SPLITTED("|change(pairwise=1,include=X,to=-,change=100)"),
1481                "..a---------t...");
1482
1483
1484        // exec
1485        TEST_CI("c,b,c,b,a,a", WITH_SPLITTED("|exec(\"(sort|uniq)\")|split|dropempty"),              "a,b,c");
1486        TEST_CI("a,aba,cac",   WITH_SPLITTED("|exec(\"perl\",-pe,s/([bc])/$1$1/g)|split|dropempty"), "a,abba,ccacc");
1487
1488         // error cases
1489        TEST_CI_ERROR_CONTAINS("", "nocmd",          "Unknown command 'nocmd'");
1490        TEST_CI_ERROR_CONTAINS("", "|nocmd",         "Unknown command 'nocmd'");
1491        TEST_CI_ERROR_CONTAINS("", "caps(x)",        "syntax: caps (no parameters)");
1492        TEST_CI_ERROR_CONTAINS("", "trace",          "syntax: trace(0|1)");
1493        TEST_CI_ERROR_CONTAINS("", "count",          "syntax: count(\"characters to count\")");
1494        TEST_CI_ERROR_CONTAINS("", "count(a,b)",     "syntax: count(\"characters to count\")");
1495        TEST_CI_ERROR_CONTAINS("", "len(a,b)",       "syntax: len[(\"characters not to count\")]");
1496        TEST_CI_ERROR_CONTAINS("", "plus(a,b,c)",    "syntax: plus[(Expr1[,Expr2])]");
1497        TEST_CI_ERROR_CONTAINS("", "count(a,b",      "Reason: Missing ')'");
1498        TEST_CI_ERROR_CONTAINS("", "count(a,\"b)",   "unbalanced '\"' in 'count(a,\"b)'");
1499        TEST_CI_ERROR_CONTAINS("", "count(a,\"b)\"", "Reason: Missing ')'");
1500        TEST_CI_ERROR_CONTAINS("", "dd;dd|count",    "syntax: count(\"characters to count\")");
1501        TEST_CI_ERROR_CONTAINS("", "|count(\"a\"x)", "Reason: Missing '\"'");
1502        TEST_CI_ERROR_CONTAINS("", "|count(\"a)",    "unbalanced '\"' in '|count(\"a)'");
1503
1504        TEST_CI_ERROR_CONTAINS("", "translate(a)",       "syntax: translate(old,new[,other])");
1505        TEST_CI_ERROR_CONTAINS("", "translate(a,b,c,d)", "syntax: translate(old,new[,other])");
1506
1507        TEST_CI_ERROR_CONTAINS(NULL, "whatever", "ARB ERROR: Can't read this DB entry as string"); // here gb_data is the species container
1508
1509        gb_data = GB_entry(gb_data, "full_name"); // execute ACI on 'full_name' from here on ------------------------------
1510
1511        TEST_CI(NULL, "", "Lactobacillus reuteri"); // noop
1512        TEST_CI(NULL, "|len", "21");
1513        TEST_CI(NULL, ":tobac=", "Lacillus reuteri");
1514        TEST_CI(NULL, "/ba.*us/B/", "LactoB reuteri");
1515
1516        TEST_CI(NULL, "|taxonomy(1)", "No default tree");
1517        TEST_CI_ERROR_CONTAINS(NULL, "|taxonomy(tree_nuc,2)", "Container has neither 'name' nor 'group_name' entry - can't detect container type");
1518
1519        gb_data = NULL;
1520        TEST_CI_ERROR_CONTAINS(NULL, "", "no input streams found");
1521    }
1522
1523    GB_set_ACISRT_trace(old_trace); // avoid side effect of TEST_GB_command_interpreter
1524    GB_close(gb_main);
1525}
1526
1527const char *GBT_get_name(GBDATA *gb_item); 
1528
1529struct TestDB : virtual Noncopyable {
1530    GB_shell  shell;
1531    GBDATA   *gb_main;
1532    GB_ERROR  error;
1533
1534    GBDATA *gb_cont1;
1535    GBDATA *gb_cont2;
1536    GBDATA *gb_cont_empty;
1537    GBDATA *gb_cont_misc;
1538
1539    GB_ERROR create_many_items(GBDATA *gb_parent, const char **item_key_list, int item_count) {
1540        int k = 0;
1541        for (int i = 0; i<item_count && !error; i++) {
1542            const char *item_key    = item_key_list[k++];
1543            if (!item_key) { item_key = item_key_list[0]; k = 1; }
1544
1545            GBDATA *gb_child = GB_create_container(gb_parent, item_key);
1546            if (!gb_child) {
1547                error = GB_await_error();
1548            }
1549            else {
1550                if ((i%7) == 0) GB_write_flag(gb_child, 1); // mark some
1551               
1552                GBDATA *gb_name = GB_create(gb_child, "name", GB_STRING);
1553                error           = GB_write_string(gb_name, GBS_global_string("%s %i", item_key, i));
1554            }
1555        }
1556        return error;
1557    }
1558
1559    TestDB() {
1560        gb_main = GB_open("nosuch.arb", "c");
1561        error   = gb_main ? NULL : GB_await_error();
1562
1563        if (!error) {
1564            GB_transaction ta(gb_main);
1565            gb_cont1      = GB_create_container(gb_main, "container1");
1566            gb_cont2      = GB_create_container(gb_main, "container2");
1567            gb_cont_empty = GB_create_container(gb_main, "empty");
1568            gb_cont_misc  = GB_create_container(gb_main, "misc");
1569
1570            if (!gb_cont1 || !gb_cont2) error = GB_await_error();
1571
1572            const char *single_key[] = { "entry", NULL };
1573            const char *mixed_keys[] = { "item", "other", NULL };
1574
1575            if (!error) error = create_many_items(gb_cont1, single_key, 100);
1576            if (!error) error = create_many_items(gb_cont2, mixed_keys, 20);
1577        }
1578        TEST_EXPECT_NO_ERROR(error);
1579    }
1580    ~TestDB() {
1581        GB_close(gb_main);
1582    }
1583};
1584
1585void TEST_DB_search() {
1586    TestDB db;
1587    TEST_EXPECT_NO_ERROR(db.error);
1588
1589    {
1590        GB_transaction ta(db.gb_main);
1591
1592        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont1), 100);
1593        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont2), 20);
1594
1595        {
1596            GBDATA *gb_any_child = GB_child(db.gb_cont1);
1597            TEST_REJECT_NULL(gb_any_child);
1598            TEST_EXPECT_EQUAL(gb_any_child, GB_entry(db.gb_cont1, "entry"));
1599            TEST_EXPECT_EQUAL(gb_any_child, GB_search(db.gb_main, "container1/entry", GB_FIND));
1600
1601            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "zic-zac", GB_FIND), "Invalid char '-' in key 'zic-zac'");
1602
1603            // check (obsolete) link-syntax (@@@ remove together with GB_OBSOLETE)
1604            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "->entry",                         GB_FIND),   "Invalid char '-' in key '->entry'");
1605            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "entry->bla",                      GB_FIND),   "Invalid char '-' in key 'entry->bla'");
1606            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/entry->nowhere",       GB_FIND),   "Invalid char '-' in key 'entry->nowhere'");
1607            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/nosuchentry->nowhere", GB_STRING), "Invalid char '-' in key 'nosuchentry->nowhere");
1608            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "entry->",                         GB_FIND),   "Invalid char '-' in key 'entry->'");
1609
1610            // check ..
1611            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "..", GB_FIND), db.gb_cont1);
1612            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "../..", GB_FIND), db.gb_main);
1613            // above main entry
1614            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "../../..", GB_FIND));
1615            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(gb_any_child, "../../../impossible", GB_STRING), "cannot use '..' at root node");
1616
1617            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "", GB_FIND), gb_any_child); // return self
1618            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/", GB_FIND), db.gb_cont1); // accept trailing slash for container ..
1619            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(gb_any_child, "/container1/entry/name/", GB_FIND), "terminal entry 'name' cannot be used as container"); // .. but not for normal entries
1620
1621            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/", GB_FIND), db.gb_main);
1622            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/..", GB_FIND), db.gb_main);
1623            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "/..", GB_FIND)); // main has no parent
1624        }
1625
1626        {
1627            GBDATA *gb_child1 = GB_child(db.gb_cont2);   TEST_REJECT_NULL(gb_child1);
1628            GBDATA *gb_child2 = GB_nextChild(gb_child1); TEST_REJECT_NULL(gb_child2);
1629            GBDATA *gb_child3 = GB_nextChild(gb_child2); TEST_REJECT_NULL(gb_child3);
1630            GBDATA *gb_child4 = GB_nextChild(gb_child3); TEST_REJECT_NULL(gb_child4);
1631
1632            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child1), "item");
1633            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child2), "other");
1634            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child3), "item");
1635            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child4), "other");
1636
1637            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_entry(db.gb_cont2, "entry"));
1638            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "item"),  gb_child1);
1639            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "other"), gb_child2);
1640
1641            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child1), gb_child3);
1642            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child2), gb_child4);
1643
1644            // check ..
1645            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../item", GB_FIND), gb_child1);
1646            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other", GB_FIND), gb_child2);
1647            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other/../item", GB_FIND), gb_child1);
1648        }
1649
1650        // ------------------------
1651        //      single entries
1652
1653        {
1654            GBDATA *gb_str      = GB_searchOrCreate_string(db.gb_cont_misc, "str", "bla");   TEST_REJECT_NULL(gb_str);
1655            GBDATA *gb_str_same = GB_searchOrCreate_string(db.gb_cont_misc, "str", "blub");
1656
1657            TEST_EXPECT_EQUAL(gb_str, gb_str_same);
1658            TEST_EXPECT_EQUAL(GB_read_char_pntr(gb_str), "bla");
1659
1660            GBDATA *gb_int      = GB_searchOrCreate_int(db.gb_cont_misc, "int", 4711);   TEST_REJECT_NULL(gb_int);
1661            GBDATA *gb_int_same = GB_searchOrCreate_int(db.gb_cont_misc, "int", 2012);
1662
1663            TEST_EXPECT_EQUAL(gb_int, gb_int_same);
1664            TEST_EXPECT_EQUAL(GB_read_int(gb_int), 4711);
1665
1666            GBDATA *gb_float      = GB_searchOrCreate_float(db.gb_cont_misc, "float", 0.815);   TEST_REJECT_NULL(gb_float);
1667            GBDATA *gb_float_same = GB_searchOrCreate_float(db.gb_cont_misc, "float", 3.1415);
1668
1669            TEST_EXPECT_EQUAL(gb_float, gb_float_same);
1670            TEST_EXPECT_SIMILAR(GB_read_float(gb_float), 0.815, 0.0001);
1671
1672            TEST_EXPECT_EQUAL  (GB_read_char_pntr(GB_searchOrCreate_string(db.gb_cont_misc, "sub1/str",    "blub")), "blub");
1673            TEST_EXPECT_EQUAL  (GB_read_int      (GB_searchOrCreate_int   (db.gb_cont_misc, "sub2/int",    2012)),   2012);
1674            TEST_EXPECT_SIMILAR(GB_read_float    (GB_searchOrCreate_float (db.gb_cont_misc, "sub3/float", 3.1415)), 3.1415, 0.00001);
1675
1676            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "int",   0.815), "has wrong type");
1677            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "str",   0.815), "has wrong type");
1678            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "float", 4711),  "has wrong type");
1679            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "str",   4711),  "has wrong type");
1680            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "float", "bla"), "has wrong type");
1681            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int",   "bla"), "has wrong type");
1682
1683            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "*", "bla"), "Invalid char '*' in key '*'");
1684            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int*", "bla"), "Invalid char '*' in key 'int*'");
1685            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "sth_else*", "bla"), "Invalid char '*' in key 'sth_else*'");
1686
1687            GBDATA *gb_entry;
1688            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, "subcont/entry", GB_FIND));
1689            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_entry = GB_search(db.gb_cont_misc, "subcont/entry", GB_INT));
1690            TEST_EXPECT_EQUAL(GB_read_int(gb_entry), 0);
1691
1692            GBDATA *gb_cont1;
1693            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont1 = GB_search(db.gb_cont_misc, "subcont", GB_CREATE_CONTAINER));
1694            TEST_EXPECT_EQUAL(GB_child(gb_cont1), gb_entry); // test GB_search found the container created implicitely above
1695
1696            GBDATA *gb_cont2;
1697            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2 = GB_search(db.gb_cont_misc, "subcont2", GB_CREATE_CONTAINER)); // create new container
1698
1699            // -----------------------
1700            //      search values
1701
1702            GBDATA *gb_4711;
1703            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_4711 = GB_find_int(db.gb_cont_misc, "int", 4711, SEARCH_CHILD));
1704            TEST_EXPECT_EQUAL(gb_4711, gb_int);
1705
1706            TEST_EXPECT_NULL(GB_find_int(db.gb_cont_misc, "int", 2012, SEARCH_CHILD));
1707
1708            GBDATA *gb_bla;
1709            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(db.gb_cont_misc, "str", "bla", GB_MIND_CASE, SEARCH_CHILD));
1710            TEST_EXPECT_EQUAL(gb_bla, gb_str);
1711            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(gb_4711, "str", "bla", GB_MIND_CASE, SEARCH_BROTHER));
1712            TEST_EXPECT_EQUAL(gb_bla, gb_str);
1713
1714            TEST_EXPECT_NULL(GB_find_string(db.gb_cont_misc, "str", "blub", GB_MIND_CASE, SEARCH_CHILD));
1715
1716            GBDATA *gb_name;
1717            TEST_REJECT_NULL                  (GB_find_string          (db.gb_cont1, "name", "entry 77",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
1718            TEST_REJECT_NULL                  (GB_find_string          (db.gb_cont1, "name", "entry 99",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
1719            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "entry 100", GB_MIND_CASE,   SEARCH_GRANDCHILD));
1720            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "ENTRY 13",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
1721            TEST_REJECT_NULL                  (gb_name = GB_find_string(db.gb_cont1, "name", "ENTRY 13",  GB_IGNORE_CASE, SEARCH_GRANDCHILD));
1722
1723            GBDATA *gb_sub;
1724            TEST_REJECT_NULL(gb_sub = GB_get_father(gb_name));        TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 13");
1725            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 0));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 14");
1726            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 1));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 16");
1727            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 10)); TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 27");
1728            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_followingEntry(gb_sub, -1U));
1729            TEST_REJECT_NULL(gb_sub = GB_brother(gb_sub, "entry"));   TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 0");
1730
1731            TEST_EXPECT_EQUAL(gb_bla = GB_search(gb_cont1, "/misc/str", GB_FIND), gb_str); // fullpath (ignores passed GBDATA)
1732
1733            // keyless search
1734            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, NULL, GB_FIND));
1735
1736            // ----------------------------
1737            //      GB_get_GBDATA_path
1738
1739            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_int),   "/main/misc/int");
1740            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_str),   "/main/misc/str");
1741            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_entry), "/main/misc/subcont/entry");
1742            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_cont2),  "/main/misc/subcont2");
1743
1744            // -----------------------------------------
1745            //      search/create with changed type
1746
1747            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "str", GB_INT), "Inconsistent type for field");
1748            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont", GB_STRING), "Inconsistent type for field");
1749
1750            // ---------------------------------------------
1751            //      search containers with trailing '/'
1752
1753            GBDATA *gb_cont2_slash;
1754            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2_slash = GB_search(db.gb_cont_misc, "subcont2/", GB_FIND));
1755            TEST_EXPECT_EQUAL(gb_cont2_slash, gb_cont2);
1756
1757            GBDATA *gb_rootcont;
1758            GBDATA *gb_rootcont_slash;
1759            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont = GB_search(db.gb_main, "/container1", GB_FIND));
1760            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont_slash = GB_search(db.gb_main, "/container1/", GB_FIND));
1761            TEST_EXPECT_EQUAL(gb_rootcont_slash, gb_rootcont);
1762        }
1763
1764        {
1765            // check invalid searches
1766            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/inva*lid",   GB_INT),  "Invalid char '*' in key 'inva*lid'");
1767            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/1 3",        GB_INT),  "Invalid char ' ' in key '1 3'");
1768            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub//sub",       GB_INT),  "Invalid '//' in key 'sub//sub'");
1769            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont2//",     GB_FIND), "Invalid '//' in key 'subcont2//'");
1770            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/..sub",      GB_INT),  "Expected '/' after '..' in key '..sub'");
1771            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont/entry/", GB_FIND), "terminal entry 'entry' cannot be used as container");
1772        }
1773
1774        // ---------------
1775        //      marks
1776
1777        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont1), 15);
1778        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont2), 3);
1779
1780        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_first_marked(db.gb_cont2, "entry"));
1781
1782        GBDATA *gb_marked;
1783        TEST_REJECT_NULL(gb_marked = GB_first_marked(db.gb_cont2, "item"));
1784        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 0");
1785
1786        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_following_marked(gb_marked, "item", 1)); // skip over last
1787       
1788        TEST_REJECT_NULL(gb_marked = GB_next_marked(gb_marked, "item")); // find last
1789        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 14");
1790    }
1791
1792    // @@@ delete some species, then search again
1793
1794    TEST_EXPECT_NO_ERROR(db.error);
1795}
1796
1797
1798#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.