source: tags/arb-6.0.5/ARBDB/adquery.cxx

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