source: tags/ms_r17q3/ARBDB/adquery.cxx

Last change on this file was 16374, checked in by westram, 7 years ago
  • reintegrates 'aci' into 'trunk'
    • refactored wide parts of ACI code (incl. SRT+REG)
      • added more test
      • ACI tracing
        • more complete and readable
        • automatically turned off when done with expression
      • improved error messages (esp. diagnostics)
      • documentation (updated, added missing)
      • fixed a bunch of bugs (incl. SEGV and deadlock)
      • ACI now runs inside execution environment
    • ACI language may be extended with custom commands (implements #756)
      • added ACI extension for group-batch-rename
  • adds:
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.8 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#ifdef UNIT_TESTS
665#include <test_unit.h>
666
667const char *GBT_get_name(GBDATA *gb_item); 
668
669struct TestDB : virtual Noncopyable {
670    GB_shell  shell;
671    GBDATA   *gb_main;
672    GB_ERROR  error;
673
674    GBDATA *gb_cont1;
675    GBDATA *gb_cont2;
676    GBDATA *gb_cont_empty;
677    GBDATA *gb_cont_misc;
678
679    GB_ERROR create_many_items(GBDATA *gb_parent, const char **item_key_list, int item_count) {
680        int k = 0;
681        for (int i = 0; i<item_count && !error; i++) {
682            const char *item_key    = item_key_list[k++];
683            if (!item_key) { item_key = item_key_list[0]; k = 1; }
684
685            GBDATA *gb_child = GB_create_container(gb_parent, item_key);
686            if (!gb_child) {
687                error = GB_await_error();
688            }
689            else {
690                if ((i%7) == 0) GB_write_flag(gb_child, 1); // mark some
691               
692                GBDATA *gb_name = GB_create(gb_child, "name", GB_STRING);
693                error           = GB_write_string(gb_name, GBS_global_string("%s %i", item_key, i));
694            }
695        }
696        return error;
697    }
698
699    TestDB() {
700        gb_main = GB_open("nosuch.arb", "c");
701        error   = gb_main ? NULL : GB_await_error();
702
703        if (!error) {
704            GB_transaction ta(gb_main);
705            gb_cont1      = GB_create_container(gb_main, "container1");
706            gb_cont2      = GB_create_container(gb_main, "container2");
707            gb_cont_empty = GB_create_container(gb_main, "empty");
708            gb_cont_misc  = GB_create_container(gb_main, "misc");
709
710            if (!gb_cont1 || !gb_cont2) error = GB_await_error();
711
712            const char *single_key[] = { "entry", NULL };
713            const char *mixed_keys[] = { "item", "other", NULL };
714
715            if (!error) error = create_many_items(gb_cont1, single_key, 100);
716            if (!error) error = create_many_items(gb_cont2, mixed_keys, 20);
717        }
718        TEST_EXPECT_NO_ERROR(error);
719    }
720    ~TestDB() {
721        GB_close(gb_main);
722    }
723};
724
725void TEST_DB_search() {
726    TestDB db;
727    TEST_EXPECT_NO_ERROR(db.error);
728
729    {
730        GB_transaction ta(db.gb_main);
731
732        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont1), 100);
733        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont2), 20);
734
735        {
736            GBDATA *gb_any_child = GB_child(db.gb_cont1);
737            TEST_REJECT_NULL(gb_any_child);
738            TEST_EXPECT_EQUAL(gb_any_child, GB_entry(db.gb_cont1, "entry"));
739            TEST_EXPECT_EQUAL(gb_any_child, GB_search(db.gb_main, "container1/entry", GB_FIND));
740
741            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "zic-zac", GB_FIND), "Invalid char '-' in key 'zic-zac'");
742
743            // check (obsolete) link-syntax (@@@ remove together with GB_OBSOLETE)
744            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "->entry",                         GB_FIND),   "Invalid char '-' in key '->entry'");
745            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "entry->bla",                      GB_FIND),   "Invalid char '-' in key 'entry->bla'");
746            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/entry->nowhere",       GB_FIND),   "Invalid char '-' in key 'entry->nowhere'");
747            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/nosuchentry->nowhere", GB_STRING), "Invalid char '-' in key 'nosuchentry->nowhere");
748            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "entry->",                         GB_FIND),   "Invalid char '-' in key 'entry->'");
749
750            // check ..
751            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "..", GB_FIND), db.gb_cont1);
752            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "../..", GB_FIND), db.gb_main);
753            // above main entry
754            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "../../..", GB_FIND));
755            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(gb_any_child, "../../../impossible", GB_STRING), "cannot use '..' at root node");
756
757            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "", GB_FIND), gb_any_child); // return self
758            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/", GB_FIND), db.gb_cont1); // accept trailing slash for container ..
759            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
760
761            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/", GB_FIND), db.gb_main);
762            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/..", GB_FIND), db.gb_main);
763            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "/..", GB_FIND)); // main has no parent
764        }
765
766        {
767            GBDATA *gb_child1 = GB_child(db.gb_cont2);   TEST_REJECT_NULL(gb_child1);
768            GBDATA *gb_child2 = GB_nextChild(gb_child1); TEST_REJECT_NULL(gb_child2);
769            GBDATA *gb_child3 = GB_nextChild(gb_child2); TEST_REJECT_NULL(gb_child3);
770            GBDATA *gb_child4 = GB_nextChild(gb_child3); TEST_REJECT_NULL(gb_child4);
771
772            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child1), "item");
773            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child2), "other");
774            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child3), "item");
775            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child4), "other");
776
777            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_entry(db.gb_cont2, "entry"));
778            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "item"),  gb_child1);
779            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "other"), gb_child2);
780
781            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child1), gb_child3);
782            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child2), gb_child4);
783
784            // check ..
785            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../item", GB_FIND), gb_child1);
786            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other", GB_FIND), gb_child2);
787            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other/../item", GB_FIND), gb_child1);
788        }
789
790        // ------------------------
791        //      single entries
792
793        {
794            GBDATA *gb_str      = GB_searchOrCreate_string(db.gb_cont_misc, "str", "bla");   TEST_REJECT_NULL(gb_str);
795            GBDATA *gb_str_same = GB_searchOrCreate_string(db.gb_cont_misc, "str", "blub");
796
797            TEST_EXPECT_EQUAL(gb_str, gb_str_same);
798            TEST_EXPECT_EQUAL(GB_read_char_pntr(gb_str), "bla");
799
800            GBDATA *gb_int      = GB_searchOrCreate_int(db.gb_cont_misc, "int", 4711);   TEST_REJECT_NULL(gb_int);
801            GBDATA *gb_int_same = GB_searchOrCreate_int(db.gb_cont_misc, "int", 2012);
802
803            TEST_EXPECT_EQUAL(gb_int, gb_int_same);
804            TEST_EXPECT_EQUAL(GB_read_int(gb_int), 4711);
805
806            GBDATA *gb_float      = GB_searchOrCreate_float(db.gb_cont_misc, "float", 0.815);   TEST_REJECT_NULL(gb_float);
807            GBDATA *gb_float_same = GB_searchOrCreate_float(db.gb_cont_misc, "float", 3.1415);
808
809            TEST_EXPECT_EQUAL(gb_float, gb_float_same);
810            TEST_EXPECT_SIMILAR(GB_read_float(gb_float), 0.815, 0.0001);
811
812            TEST_EXPECT_EQUAL  (GB_read_char_pntr(GB_searchOrCreate_string(db.gb_cont_misc, "sub1/str",    "blub")), "blub");
813            TEST_EXPECT_EQUAL  (GB_read_int      (GB_searchOrCreate_int   (db.gb_cont_misc, "sub2/int",    2012)),   2012);
814            TEST_EXPECT_SIMILAR(GB_read_float    (GB_searchOrCreate_float (db.gb_cont_misc, "sub3/float", 3.1415)), 3.1415, 0.00001);
815
816            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "int",   0.815), "has wrong type");
817            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "str",   0.815), "has wrong type");
818            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "float", 4711),  "has wrong type");
819            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "str",   4711),  "has wrong type");
820            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "float", "bla"), "has wrong type");
821            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int",   "bla"), "has wrong type");
822
823            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "*", "bla"), "Invalid char '*' in key '*'");
824            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int*", "bla"), "Invalid char '*' in key 'int*'");
825            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "sth_else*", "bla"), "Invalid char '*' in key 'sth_else*'");
826
827            GBDATA *gb_entry;
828            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, "subcont/entry", GB_FIND));
829            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_entry = GB_search(db.gb_cont_misc, "subcont/entry", GB_INT));
830            TEST_EXPECT_EQUAL(GB_read_int(gb_entry), 0);
831
832            GBDATA *gb_cont1;
833            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont1 = GB_search(db.gb_cont_misc, "subcont", GB_CREATE_CONTAINER));
834            TEST_EXPECT_EQUAL(GB_child(gb_cont1), gb_entry); // test GB_search found the container created implicitely above
835
836            GBDATA *gb_cont2;
837            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2 = GB_search(db.gb_cont_misc, "subcont2", GB_CREATE_CONTAINER)); // create new container
838
839            // -----------------------
840            //      search values
841
842            GBDATA *gb_4711;
843            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_4711 = GB_find_int(db.gb_cont_misc, "int", 4711, SEARCH_CHILD));
844            TEST_EXPECT_EQUAL(gb_4711, gb_int);
845
846            TEST_EXPECT_NULL(GB_find_int(db.gb_cont_misc, "int", 2012, SEARCH_CHILD));
847
848            GBDATA *gb_bla;
849            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(db.gb_cont_misc, "str", "bla", GB_MIND_CASE, SEARCH_CHILD));
850            TEST_EXPECT_EQUAL(gb_bla, gb_str);
851            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(gb_4711, "str", "bla", GB_MIND_CASE, SEARCH_BROTHER));
852            TEST_EXPECT_EQUAL(gb_bla, gb_str);
853
854            TEST_EXPECT_NULL(GB_find_string(db.gb_cont_misc, "str", "blub", GB_MIND_CASE, SEARCH_CHILD));
855
856            GBDATA *gb_name;
857            TEST_REJECT_NULL                  (GB_find_string          (db.gb_cont1, "name", "entry 77",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
858            TEST_REJECT_NULL                  (GB_find_string          (db.gb_cont1, "name", "entry 99",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
859            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "entry 100", GB_MIND_CASE,   SEARCH_GRANDCHILD));
860            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "ENTRY 13",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
861            TEST_REJECT_NULL                  (gb_name = GB_find_string(db.gb_cont1, "name", "ENTRY 13",  GB_IGNORE_CASE, SEARCH_GRANDCHILD));
862
863            GBDATA *gb_sub;
864            TEST_REJECT_NULL(gb_sub = GB_get_father(gb_name));        TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 13");
865            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 0));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 14");
866            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 1));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 16");
867            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 10)); TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 27");
868            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_followingEntry(gb_sub, -1U));
869            TEST_REJECT_NULL(gb_sub = GB_brother(gb_sub, "entry"));   TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 0");
870
871            TEST_EXPECT_EQUAL(gb_bla = GB_search(gb_cont1, "/misc/str", GB_FIND), gb_str); // fullpath (ignores passed GBDATA)
872
873            // keyless search
874            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, NULL, GB_FIND));
875
876            // ----------------------------
877            //      GB_get_GBDATA_path
878
879            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_int),   "/main/misc/int");
880            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_str),   "/main/misc/str");
881            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_entry), "/main/misc/subcont/entry");
882            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_cont2),  "/main/misc/subcont2");
883
884            // -----------------------------------------
885            //      search/create with changed type
886
887            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "str", GB_INT), "Inconsistent type for field");
888            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont", GB_STRING), "Inconsistent type for field");
889
890            // ---------------------------------------------
891            //      search containers with trailing '/'
892
893            GBDATA *gb_cont2_slash;
894            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2_slash = GB_search(db.gb_cont_misc, "subcont2/", GB_FIND));
895            TEST_EXPECT_EQUAL(gb_cont2_slash, gb_cont2);
896
897            GBDATA *gb_rootcont;
898            GBDATA *gb_rootcont_slash;
899            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont = GB_search(db.gb_main, "/container1", GB_FIND));
900            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont_slash = GB_search(db.gb_main, "/container1/", GB_FIND));
901            TEST_EXPECT_EQUAL(gb_rootcont_slash, gb_rootcont);
902        }
903
904        {
905            // check invalid searches
906            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/inva*lid",   GB_INT),  "Invalid char '*' in key 'inva*lid'");
907            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/1 3",        GB_INT),  "Invalid char ' ' in key '1 3'");
908            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub//sub",       GB_INT),  "Invalid '//' in key 'sub//sub'");
909            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont2//",     GB_FIND), "Invalid '//' in key 'subcont2//'");
910            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/..sub",      GB_INT),  "Expected '/' after '..' in key '..sub'");
911            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont/entry/", GB_FIND), "terminal entry 'entry' cannot be used as container");
912        }
913
914        // ---------------
915        //      marks
916
917        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont1), 15);
918        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont2), 3);
919
920        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_first_marked(db.gb_cont2, "entry"));
921
922        GBDATA *gb_marked;
923        TEST_REJECT_NULL(gb_marked = GB_first_marked(db.gb_cont2, "item"));
924        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 0");
925
926        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_following_marked(gb_marked, "item", 1)); // skip over last
927       
928        TEST_REJECT_NULL(gb_marked = GB_next_marked(gb_marked, "item")); // find last
929        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 14");
930    }
931
932    // @@@ delete some species, then search again
933
934    TEST_EXPECT_NO_ERROR(db.error);
935}
936
937
938#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.