source: branches/help/ARBDB/adquery.cxx

Last change on this file was 18159, checked in by westram, 5 years ago
  • full update from child 'fix' into 'trunk'
    • fix item name accessors (GBT_get_name + GBT_get_name_or_description)
    • add null2empty
  • adds: log:branches/fix@18140:18158
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.7 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 = NULp;
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' specified -> 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 NULp;
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, NULp, 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    = NULp;
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 NULp;
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)) &&
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 : NULp;
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, NULp, 0);
243            if (res) return res;
244        }
245    }
246    return NULp;
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 = NULp;
252
253    if (gbd) {
254        GBDATA      *after = NULp;
255        GBCONTAINER *gbc   = NULp;
256
257        switch (gbs) {
258            case SEARCH_NEXT_BROTHER:
259                after = gbd;
260                FALLTHROUGH;
261            case SEARCH_BROTHER:
262                gbs = SEARCH_CHILD;
263                gbc = GB_FATHER(gbd);
264                break;
265
266            case SEARCH_CHILD:
267            case SEARCH_GRANDCHILD:
268                if (gbd->is_container()) gbc = gbd->as_container();
269                break;
270
271            case SEARCH_CHILD_OF_NEXT:
272                after = gbd;
273                gbs   = SEARCH_GRANDCHILD;
274                gbc   = GB_FATHER(gbd);
275                break;
276        }
277
278        if (gbc) {
279            GBQUARK key_quark = GB_find_existing_quark(gbd, key);
280
281            if (key_quark) { // only search if 'key' is known to db
282                if (gbs == SEARCH_CHILD) {
283                    result = GB_find_subcontent_by_quark(gbc, key_quark, type, val, case_sens, after, 0);
284                }
285                else {
286                    gb_assert(gbs == SEARCH_GRANDCHILD);
287                    result = find_sub_sub_by_quark(gbc, key, key_quark, type, val, case_sens, after);
288                }
289            }
290        }
291    }
292    return result;
293}
294
295GBDATA *GB_find(GBDATA *gbd, const char *key, GB_SEARCH_TYPE gbs) {
296    // normally you should not need to use GB_find!
297    // better use one of the replacement functions
298    // (GB_find_string, GB_find_int, GB_child, GB_nextChild, GB_entry, GB_nextEntry, GB_brother)
299    return gb_find_internal(gbd, key, GB_NONE, NULp, GB_CASE_UNDEFINED, gbs);
300}
301
302GBDATA *GB_find_string(GBDATA *gbd, const char *key, const char *str, GB_CASE case_sens, GB_SEARCH_TYPE gbs) {
303    // search for a subentry of 'gbd' that has
304    // - fieldname 'key'
305    // - type GB_STRING and
306    // - content matching 'str'
307    // if 'case_sens' is GB_MIND_CASE, content is matched case sensitive.
308    // GBS_string_matches is used to compare (supports wildcards)
309    return gb_find_internal(gbd, key, GB_STRING, str, case_sens, gbs);
310}
311NOT4PERL GBDATA *GB_find_int(GBDATA *gbd, const char *key, long val, GB_SEARCH_TYPE gbs) {
312    // search for a subentry of 'gbd' that has
313    // - fieldname 'key'
314    // - type GB_INT
315    // - and value 'val'
316    return gb_find_internal(gbd, key, GB_INT, (const char *)&val, GB_CASE_UNDEFINED, gbs);
317}
318
319// ----------------------------------------------------
320//      iterate over ALL subentries of a container
321
322GBDATA *GB_child(GBDATA *father) {
323    // return first child (or NULp if no children)
324    return GB_find(father, NULp, SEARCH_CHILD);
325}
326GBDATA *GB_nextChild(GBDATA *child) {
327    // return next child after 'child' (or NULp if no more children)
328    return GB_find(child, NULp, SEARCH_NEXT_BROTHER);
329}
330
331// ------------------------------------------------------------------------------
332//      iterate over all subentries of a container that have a specified key
333
334GBDATA *GB_entry(GBDATA *father, const char *key) {
335    // return first child of 'father' that has fieldname 'key'
336    // (or NULp if none found)
337    return GB_find(father, key, SEARCH_CHILD);
338}
339GBDATA *GB_nextEntry(GBDATA *entry) {
340    // return next child after 'entry', that has the same fieldname
341    // (or NULp if 'entry' is last one)
342    return GB_find_sub_by_quark(GB_FATHER(entry), GB_get_quark(entry), entry, 0);
343}
344GBDATA *GB_followingEntry(GBDATA *entry, size_t skip_over) {
345    // return following child after 'entry', that has the same fieldname
346    // (or NULp if no such entry)
347    // skips 'skip_over' entries (skip_over == 0 behaves like GB_nextEntry)
348    return GB_find_sub_by_quark(GB_FATHER(entry), GB_get_quark(entry), entry, skip_over);
349}
350
351size_t GB_countEntries(GBDATA *father, const char *key) {
352    size_t  count    = 0;
353    GBDATA *gb_entry = GB_entry(father, key);
354    while (gb_entry) {
355        ++count;
356        gb_entry = GB_nextEntry(gb_entry);
357    }
358    return count;
359}
360
361GBDATA *GB_brother(GBDATA *entry, const char *key) {
362    // searches (first) brother (before or after) of 'entry' which has field 'key'
363    // i.e. does same as GB_entry(GB_get_father(entry), key)
364    return GB_find(entry, key, SEARCH_BROTHER);
365}
366
367GBDATA *gb_find_by_nr(GBCONTAINER *father, int index) {
368    /* get a subentry by its internal number:
369       Warning: This subentry must exists, otherwise internal error */
370
371    gb_header_list *header = GB_DATA_LIST_HEADER(father->d);
372    if (index >= father->d.nheader || index <0) {
373        GB_internal_errorf("Index '%i' out of range [%i:%i[", index, 0, father->d.nheader);
374        return NULp;
375    }
376    if (header[index].flags.changed >= GB_DELETED || !header[index].flags.key_quark) {
377        GB_internal_error("Entry already deleted");
378        return NULp;
379    }
380
381    GBDATA *gb = GB_HEADER_LIST_GBD(header[index]);
382    if (!gb) {
383        gb_unfold(father, 0, index);
384        header = GB_DATA_LIST_HEADER(father->d);
385        gb = GB_HEADER_LIST_GBD(header[index]);
386        if (!gb) {
387            GB_internal_error("Could not unfold data");
388            return NULp;
389        }
390    }
391    return gb;
392}
393
394class keychar_table {
395    bool table[256];
396public:
397    keychar_table() {
398        for (int i=0; i<256; i++) {
399            table[i] = islower(i) || isupper(i) || isdigit(i) || i=='_' || i=='@';
400        }
401    }
402    const char *first_non_key_character(const char *str) const {
403        while (1) {
404            int c = *str;
405            if (!table[c]) {
406                if (c == 0) break;
407                return str;
408            }
409            str++;
410        }
411        return NULp;
412    }
413};
414static keychar_table keychars;
415
416const char *GB_first_non_key_char(const char *str) {
417    return keychars.first_non_key_character(str);
418}
419
420inline GBDATA *find_or_create(GBCONTAINER *gb_parent, const char *key, GB_TYPES create, bool internflag) {
421    gb_assert(!keychars.first_non_key_character(key));
422
423    GBDATA *gbd = GB_entry(gb_parent, key);
424    if (create) {
425        if (gbd) {
426            GB_TYPES oldType = gbd->type();
427            if (create != oldType) { // type mismatch
428                GB_export_errorf("Inconsistent type for field '%s' (existing=%i, expected=%i)", key, oldType, create);
429                gbd = NULp;
430            }
431        }
432        else {
433            if (create == GB_CREATE_CONTAINER) {
434                gbd = internflag ? gb_create_container(gb_parent, key) : GB_create_container(gb_parent, key);
435            }
436            else {
437                gbd = gb_create(gb_parent, key, create);
438            }
439            gb_assert(gbd || GB_have_error());
440        }
441    }
442    return gbd;
443}
444
445GBDATA *gb_search(GBCONTAINER *gbc, const char *key, GB_TYPES create, int internflag) {
446    /* finds a hierarchical key,
447     * if create != GB_FIND(==0), then create the key
448     * force types if ! internflag
449     */
450
451    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
452
453    GB_test_transaction(gbc);
454
455    if (!key) return NULp; // was allowed in the past (and returned the 1st child). now returns NULp
456
457    if (key[0] == '/') {
458        gbc = gb_get_root(gbc);
459        key++;
460    }
461
462    if (!key[0]) {
463        return gbc;
464    }
465
466    GBDATA     *gb_result     = NULp;
467    const char *separator     = keychars.first_non_key_character(key);
468    if (!separator) gb_result = find_or_create(gbc, key, create, internflag);
469    else {
470        int  len = separator-key;
471        char firstKey[len+1];
472        memcpy(firstKey, key, len);
473        firstKey[len] = 0;
474
475        char invalid_char = 0;
476
477        switch (separator[0]) {
478            case '/': {
479                GBDATA *gb_sub = find_or_create(gbc, firstKey, create ? GB_CREATE_CONTAINER : GB_FIND, internflag);
480                if (gb_sub) {
481                    if (gb_sub->is_container()) {
482                        if (separator[1] == '/') {
483                            GB_export_errorf("Invalid '//' in key '%s'", key);
484                        }
485                        else {
486                            gb_result = gb_search(gb_sub->as_container(), separator+1, create, internflag);
487                        }
488                    }
489                    else {
490                        GB_export_errorf("terminal entry '%s' cannot be used as container", firstKey);
491                    }
492                }
493                break;
494            }
495            case '.': {
496                if (separator[1] != '.') invalid_char = separator[0];
497                else {
498                    GBCONTAINER *gb_parent = gbc->get_father();
499                    if (gb_parent) {
500                        switch (separator[2]) {
501                            case 0:   gb_result    = gb_parent; break;
502                            case '/': gb_result    = gb_search(gb_parent, separator+3, create, internflag); break;
503                            default:
504                                GB_export_errorf("Expected '/' after '..' in key '%s'", key);
505                                break;
506                        }
507                    }
508                    else { // ".." at root-node
509                        if (create) {
510                            GB_export_error("cannot use '..' at root node");
511                        }
512                    }
513                }
514                break;
515            }
516            default:
517                invalid_char = separator[0];
518                break;
519        }
520
521        if (invalid_char) {
522            gb_assert(!gb_result);
523            GB_export_errorf("Invalid char '%c' in key '%s'", invalid_char, key);
524        }
525    }
526    gb_assert(!(gb_result && GB_have_error()));
527    return gb_result;
528}
529
530
531GBDATA *GB_search(GBDATA *gbd, const char *fieldpath, GB_TYPES create) {
532    return gb_search(gbd->expect_container(), fieldpath, create, 0);
533}
534
535static GBDATA *gb_expect_type(GBDATA *gbd, GB_TYPES expected_type, const char *fieldname) {
536    gb_assert(expected_type != GB_FIND); // impossible
537
538    GB_TYPES type = gbd->type();
539    if (type != expected_type) {
540        GB_export_errorf("Field '%s' has wrong type (found=%i, expected=%i)", fieldname, type, expected_type);
541        gbd = NULp;
542    }
543    return gbd;
544}
545
546GBDATA *GB_searchOrCreate_string(GBDATA *gb_container, const char *fieldpath, const char *default_value) {
547    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
548
549    GBDATA *gb_str = GB_search(gb_container, fieldpath, GB_FIND);
550    if (!gb_str) {
551        GB_clear_error();
552        gb_str = GB_search(gb_container, fieldpath, GB_STRING);
553        GB_ERROR error;
554
555        if (!gb_str) error = GB_await_error();
556        else error         = GB_write_string(gb_str, default_value);
557
558        if (error) {
559            gb_str = NULp;
560            GB_export_error(error);
561        }
562    }
563    else {
564        gb_str = gb_expect_type(gb_str, GB_STRING, fieldpath);
565    }
566    return gb_str;
567}
568
569GBDATA *GB_searchOrCreate_int(GBDATA *gb_container, const char *fieldpath, long default_value) {
570    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
571
572    GBDATA *gb_int = GB_search(gb_container, fieldpath, GB_FIND);
573    if (!gb_int) {
574        gb_int = GB_search(gb_container, fieldpath, GB_INT);
575        GB_ERROR error;
576
577        if (!gb_int) error = GB_await_error();
578        else error         = GB_write_int(gb_int, default_value);
579
580        if (error) { // @@@ in case GB_search returned 0, gb_int already is 0 and error is exported. just assert error is exported
581            gb_int = NULp;
582            GB_export_error(error);
583        }
584    }
585    else {
586        gb_int = gb_expect_type(gb_int, GB_INT, fieldpath);
587    }
588    return gb_int;
589}
590
591GBDATA *GB_searchOrCreate_float(GBDATA *gb_container, const char *fieldpath, float default_value) {
592    gb_assert(!GB_have_error()); // illegal to enter this function when an error is exported!
593
594    GBDATA *gb_float = GB_search(gb_container, fieldpath, GB_FIND);
595    if (!gb_float) {
596        gb_float = GB_search(gb_container, fieldpath, GB_FLOAT);
597        GB_ERROR error;
598
599        if (!gb_float) error = GB_await_error();
600        else error           = GB_write_float(gb_float, default_value);
601
602        if (error) {
603            gb_float = NULp;
604            GB_export_error(error);
605        }
606    }
607    else {
608        gb_float = gb_expect_type(gb_float, GB_FLOAT, fieldpath);
609    }
610    return gb_float;
611}
612
613static GBDATA *gb_search_marked(GBCONTAINER *gbc, GBQUARK key_quark, int firstindex, size_t skip_over) {
614    int             userbit = GBCONTAINER_MAIN(gbc)->users[0]->userbit;
615    int             index;
616    int             end     = gbc->d.nheader;
617    gb_header_list *header  = GB_DATA_LIST_HEADER(gbc->d);
618
619    for (index = firstindex; index<end; index++) {
620        GBDATA *gb;
621
622        if (! (userbit & header[index].flags.flags)) continue;
623        if ((key_quark>=0) && (header[index].flags.key_quark  != key_quark)) continue;
624        if (header[index].flags.changed >= GB_DELETED) continue;
625        if (!(gb=GB_HEADER_LIST_GBD(header[index]))) {
626            gb_unfold(gbc, 0, index);
627            header = GB_DATA_LIST_HEADER(gbc->d);
628            gb = GB_HEADER_LIST_GBD(header[index]);
629        }
630        if (!skip_over--) return gb;
631    }
632    return NULp;
633}
634
635long GB_number_of_marked_subentries(GBDATA *gbd) {
636    long count = 0;
637    if (gbd->is_container()) {
638        GBCONTAINER    *gbc    = gbd->as_container();
639        gb_header_list *header = GB_DATA_LIST_HEADER(gbc->d);
640
641        int userbit = GBCONTAINER_MAIN(gbc)->users[0]->userbit;
642        int end     = gbc->d.nheader;
643
644        for (int index = 0; index<end; index++) {
645            if (!(userbit & header[index].flags.flags)) continue;
646            if (header[index].flags.changed >= GB_DELETED) continue;
647            count++;
648        }
649    }
650    return count;
651}
652
653
654
655GBDATA *GB_first_marked(GBDATA *gbd, const char *keystring) {
656    GBCONTAINER *gbc       = gbd->expect_container();
657    GBQUARK      key_quark = GB_find_existing_quark(gbd, keystring);
658    GB_test_transaction(gbc);
659    return key_quark ? gb_search_marked(gbc, key_quark, 0, 0) : NULp;
660}
661
662
663GBDATA *GB_following_marked(GBDATA *gbd, const char *keystring, size_t skip_over) {
664    GBCONTAINER *gbc       = GB_FATHER(gbd);
665    GBQUARK      key_quark = GB_find_existing_quark(gbd, keystring);
666    GB_test_transaction(gbc);
667    return key_quark ? gb_search_marked(gbc, key_quark, (int)gbd->index+1, skip_over) : NULp;
668}
669
670GBDATA *GB_next_marked(GBDATA *gbd, const char *keystring) {
671    return GB_following_marked(gbd, keystring, 0);
672}
673
674
675#ifdef UNIT_TESTS
676#include <test_unit.h>
677
678struct TestDB : virtual Noncopyable {
679    GB_shell  shell;
680    GBDATA   *gb_main;
681    GB_ERROR  error;
682
683    GBDATA *gb_cont1;
684    GBDATA *gb_cont2;
685    GBDATA *gb_cont_empty;
686    GBDATA *gb_cont_misc;
687
688    GB_ERROR create_many_items(GBDATA *gb_parent, const char **item_key_list, int item_count) {
689        int k = 0;
690        for (int i = 0; i<item_count && !error; i++) {
691            const char *item_key    = item_key_list[k++];
692            if (!item_key) { item_key = item_key_list[0]; k = 1; }
693
694            GBDATA *gb_child = GB_create_container(gb_parent, item_key);
695            if (!gb_child) {
696                error = GB_await_error();
697            }
698            else {
699                if ((i%7) == 0) GB_write_flag(gb_child, 1); // mark some
700
701                GBDATA *gb_name = GB_create(gb_child, "name", GB_STRING);
702                error           = GB_write_string(gb_name, GBS_global_string("%s %i", item_key, i));
703            }
704        }
705        return error;
706    }
707
708    TestDB() {
709        gb_main = GB_open("nosuch.arb", "c");
710        error   = gb_main ? NULp : GB_await_error();
711
712        if (!error) {
713            GB_transaction ta(gb_main);
714            gb_cont1      = GB_create_container(gb_main, "container1");
715            gb_cont2      = GB_create_container(gb_main, "container2");
716            gb_cont_empty = GB_create_container(gb_main, "empty");
717            gb_cont_misc  = GB_create_container(gb_main, "misc");
718
719            if (!gb_cont1 || !gb_cont2) error = GB_await_error();
720
721            const char *single_key[] = { "entry", NULp };
722            const char *mixed_keys[] = { "item", "other", NULp };
723
724            if (!error) error = create_many_items(gb_cont1, single_key, 100);
725            if (!error) error = create_many_items(gb_cont2, mixed_keys, 20);
726        }
727        TEST_EXPECT_NO_ERROR(error);
728    }
729    ~TestDB() {
730        GB_close(gb_main);
731    }
732};
733
734__ATTR__REDUCED_OPTIMIZE void TEST_DB_search() {
735    TestDB db;
736    TEST_EXPECT_NO_ERROR(db.error);
737
738    {
739        GB_transaction ta(db.gb_main);
740
741        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont1), 100);
742        TEST_EXPECT_EQUAL(GB_number_of_subentries(db.gb_cont2), 20);
743
744        {
745            GBDATA *gb_any_child = GB_child(db.gb_cont1);
746            TEST_REJECT_NULL(gb_any_child);
747            TEST_EXPECT_EQUAL(gb_any_child, GB_entry(db.gb_cont1, "entry"));
748            TEST_EXPECT_EQUAL(gb_any_child, GB_search(db.gb_main, "container1/entry", GB_FIND));
749
750            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "zic-zac", GB_FIND), "Invalid char '-' in key 'zic-zac'");
751
752            // check (obsolete) link-syntax is invalid (@@@ remove these tests together with GB_OBSOLETE)
753            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "->entry",                         GB_FIND),   "Invalid char '-' in key '->entry'");
754            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "entry->bla",                      GB_FIND),   "Invalid char '-' in key 'entry->bla'");
755            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/entry->nowhere",       GB_FIND),   "Invalid char '-' in key 'entry->nowhere'");
756            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "container1/nosuchentry->nowhere", GB_STRING), "Invalid char '-' in key 'nosuchentry->nowhere");
757            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_main, "entry->",                         GB_FIND),   "Invalid char '-' in key 'entry->'");
758            TEST_EXPECT_ERROR_CONTAINS(GB_check_hkey("->entry"),                         "Invalid character '-' in '->entry'");
759            TEST_EXPECT_ERROR_CONTAINS(GB_check_hkey("entry->"),                         "Invalid character '-' in 'entry->'");
760            TEST_EXPECT_ERROR_CONTAINS(GB_check_hkey("entry->bla"),                      "Invalid character '-' in 'entry->bla'");
761            TEST_EXPECT_ERROR_CONTAINS(GB_check_hkey("container1/entry->nowhere"),       "Invalid character '-' in 'entry->nowhere'");
762            TEST_EXPECT_ERROR_CONTAINS(GB_check_hkey("container1/nosuchentry->nowhere"), "Invalid character '-' in 'nosuchentry->nowhere'");
763
764            // check ..
765            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "..", GB_FIND), db.gb_cont1);
766            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "../..", GB_FIND), db.gb_main);
767            // above main entry
768            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "../../..", GB_FIND));
769            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(gb_any_child, "../../../impossible", GB_STRING), "cannot use '..' at root node");
770
771            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "", GB_FIND), gb_any_child); // return self
772            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/", GB_FIND), db.gb_cont1); // accept trailing slash for container ..
773            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
774
775            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/", GB_FIND), db.gb_main);
776            TEST_EXPECT_EQUAL(GB_search(gb_any_child, "/container1/..", GB_FIND), db.gb_main);
777            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(gb_any_child, "/..", GB_FIND)); // main has no parent
778        }
779
780        {
781            GBDATA *gb_child1 = GB_child(db.gb_cont2);   TEST_REJECT_NULL(gb_child1);
782            GBDATA *gb_child2 = GB_nextChild(gb_child1); TEST_REJECT_NULL(gb_child2);
783            GBDATA *gb_child3 = GB_nextChild(gb_child2); TEST_REJECT_NULL(gb_child3);
784            GBDATA *gb_child4 = GB_nextChild(gb_child3); TEST_REJECT_NULL(gb_child4);
785
786            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child1), "item");
787            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child2), "other");
788            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child3), "item");
789            TEST_EXPECT_EQUAL(GB_read_key_pntr(gb_child4), "other");
790
791            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_entry(db.gb_cont2, "entry"));
792            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "item"),  gb_child1);
793            TEST_EXPECT_EQUAL(GB_entry(db.gb_cont2, "other"), gb_child2);
794
795            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child1), gb_child3);
796            TEST_EXPECT_EQUAL(GB_nextEntry(gb_child2), gb_child4);
797
798            // check ..
799            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../item", GB_FIND), gb_child1);
800            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other", GB_FIND), gb_child2);
801            TEST_EXPECT_EQUAL(GB_search(gb_child3, "../other/../item", GB_FIND), gb_child1);
802        }
803
804        // ------------------------
805        //      single entries
806
807        {
808            GBDATA *gb_str      = GB_searchOrCreate_string(db.gb_cont_misc, "str", "bla");   TEST_REJECT_NULL(gb_str);
809            GBDATA *gb_str_same = GB_searchOrCreate_string(db.gb_cont_misc, "str", "blub");
810
811            TEST_EXPECT_EQUAL(gb_str, gb_str_same);
812            TEST_EXPECT_EQUAL(GB_read_char_pntr(gb_str), "bla");
813
814            GBDATA *gb_int      = GB_searchOrCreate_int(db.gb_cont_misc, "int", 4711);   TEST_REJECT_NULL(gb_int);
815            GBDATA *gb_int_same = GB_searchOrCreate_int(db.gb_cont_misc, "int", 2012);
816
817            TEST_EXPECT_EQUAL(gb_int, gb_int_same);
818            TEST_EXPECT_EQUAL(GB_read_int(gb_int), 4711);
819
820            GBDATA *gb_float      = GB_searchOrCreate_float(db.gb_cont_misc, "float", 0.815);   TEST_REJECT_NULL(gb_float);
821            GBDATA *gb_float_same = GB_searchOrCreate_float(db.gb_cont_misc, "float", 3.1415);
822
823            TEST_EXPECT_EQUAL(gb_float, gb_float_same);
824            TEST_EXPECT_SIMILAR(GB_read_float(gb_float), 0.815, 0.0001);
825
826            TEST_EXPECT_EQUAL  (GB_read_char_pntr(GB_searchOrCreate_string(db.gb_cont_misc, "sub1/str",    "blub")), "blub");
827            TEST_EXPECT_EQUAL  (GB_read_int      (GB_searchOrCreate_int   (db.gb_cont_misc, "sub2/int",    2012)),   2012);
828            TEST_EXPECT_SIMILAR(GB_read_float    (GB_searchOrCreate_float (db.gb_cont_misc, "sub3/float", 3.1415)), 3.1415, 0.00001);
829
830            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "int",   0.815), "has wrong type");
831            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_float (db.gb_cont_misc, "str",   0.815), "has wrong type");
832            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "float", 4711),  "has wrong type");
833            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_int   (db.gb_cont_misc, "str",   4711),  "has wrong type");
834            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "float", "bla"), "has wrong type");
835            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int",   "bla"), "has wrong type");
836
837            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "*", "bla"), "Invalid char '*' in key '*'");
838            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "int*", "bla"), "Invalid char '*' in key 'int*'");
839            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_searchOrCreate_string(db.gb_cont_misc, "sth_else*", "bla"), "Invalid char '*' in key 'sth_else*'");
840
841            GBDATA *gb_entry;
842            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, "subcont/entry", GB_FIND));
843            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_entry = GB_search(db.gb_cont_misc, "subcont/entry", GB_INT));
844            TEST_EXPECT_EQUAL(GB_read_int(gb_entry), 0);
845
846            GBDATA *gb_cont1;
847            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont1 = GB_search(db.gb_cont_misc, "subcont", GB_CREATE_CONTAINER));
848            TEST_EXPECT_EQUAL(GB_child(gb_cont1), gb_entry); // test GB_search found the container created implicitely above
849
850            GBDATA *gb_cont2;
851            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2 = GB_search(db.gb_cont_misc, "subcont2", GB_CREATE_CONTAINER)); // create new container
852
853            // -----------------------
854            //      search values
855
856            GBDATA *gb_4711;
857            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_4711 = GB_find_int(db.gb_cont_misc, "int", 4711, SEARCH_CHILD));
858            TEST_EXPECT_EQUAL(gb_4711, gb_int);
859
860            TEST_EXPECT_NULL(GB_find_int(db.gb_cont_misc, "int", 2012, SEARCH_CHILD));
861
862            GBDATA *gb_bla;
863            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(db.gb_cont_misc, "str", "bla", GB_MIND_CASE, SEARCH_CHILD));
864            TEST_EXPECT_EQUAL(gb_bla, gb_str);
865            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_bla = GB_find_string(gb_4711, "str", "bla", GB_MIND_CASE, SEARCH_BROTHER));
866            TEST_EXPECT_EQUAL(gb_bla, gb_str);
867
868            TEST_EXPECT_NULL(GB_find_string(db.gb_cont_misc, "str", "blub", GB_MIND_CASE, SEARCH_CHILD));
869
870            GBDATA *gb_name;
871            TEST_REJECT_NULL                     (GB_find_string          (db.gb_cont1, "name", "entry 77",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
872            TEST_REJECT_NULL                     (GB_find_string          (db.gb_cont1, "name", "entry 99",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
873            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "entry 100", GB_MIND_CASE,   SEARCH_GRANDCHILD));
874            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_find_string          (db.gb_cont1, "name", "ENTRY 13",  GB_MIND_CASE,   SEARCH_GRANDCHILD));
875            TEST_REJECT_NULL                     (gb_name = GB_find_string(db.gb_cont1, "name", "ENTRY 13",  GB_IGNORE_CASE, SEARCH_GRANDCHILD));
876
877            GBDATA *gb_sub;
878            TEST_REJECT_NULL(gb_sub = GB_get_father(gb_name));        TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 13");
879            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 0));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 14");
880            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 1));  TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 16");
881            TEST_REJECT_NULL(gb_sub = GB_followingEntry(gb_sub, 10)); TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 27");
882            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_followingEntry(gb_sub, -1U));
883            TEST_REJECT_NULL(gb_sub = GB_brother(gb_sub, "entry"));   TEST_EXPECT_EQUAL(GBT_get_name(gb_sub), "entry 0");
884
885            TEST_EXPECT_EQUAL(gb_bla = GB_search(gb_cont1, "/misc/str", GB_FIND), gb_str); // fullpath (ignores passed GBDATA)
886
887            // keyless search
888            TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_search(db.gb_cont_misc, NULp, GB_FIND));
889
890            // ----------------------------
891            //      GB_get_GBDATA_path
892
893            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_int),   "/main/misc/int");
894            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_str),   "/main/misc/str");
895            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_entry), "/main/misc/subcont/entry");
896            TEST_EXPECT_EQUAL(GB_get_GBDATA_path(gb_cont2), "/main/misc/subcont2");
897
898            // -----------------------------------------
899            //      search/create with changed type
900
901            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "str", GB_INT), "Inconsistent type for field");
902            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont", GB_STRING), "Inconsistent type for field");
903
904            // ---------------------------------------------
905            //      search containers with trailing '/'
906
907            GBDATA *gb_cont2_slash;
908            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_cont2_slash = GB_search(db.gb_cont_misc, "subcont2/", GB_FIND));
909            TEST_EXPECT_EQUAL(gb_cont2_slash, gb_cont2);
910
911            GBDATA *gb_rootcont;
912            GBDATA *gb_rootcont_slash;
913            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont = GB_search(db.gb_main, "/container1", GB_FIND));
914            TEST_EXPECT_RESULT__NOERROREXPORTED(gb_rootcont_slash = GB_search(db.gb_main, "/container1/", GB_FIND));
915            TEST_EXPECT_EQUAL(gb_rootcont_slash, gb_rootcont);
916        }
917
918        {
919            // check invalid searches
920            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/inva*lid",   GB_INT),  "Invalid char '*' in key 'inva*lid'");
921            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/1 3",        GB_INT),  "Invalid char ' ' in key '1 3'");
922            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub//sub",       GB_INT),  "Invalid '//' in key 'sub//sub'");
923            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont2//",     GB_FIND), "Invalid '//' in key 'subcont2//'");
924            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "sub/..sub",      GB_INT),  "Expected '/' after '..' in key '..sub'");
925            TEST_EXPECT_NORESULT__ERROREXPORTED_CONTAINS(GB_search(db.gb_cont_misc, "subcont/entry/", GB_FIND), "terminal entry 'entry' cannot be used as container");
926        }
927
928        // ---------------
929        //      marks
930
931        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont1), 15);
932        TEST_EXPECT_EQUAL(GB_number_of_marked_subentries(db.gb_cont2), 3);
933
934        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_first_marked(db.gb_cont2, "entry"));
935
936        GBDATA *gb_marked;
937        TEST_REJECT_NULL(gb_marked = GB_first_marked(db.gb_cont2, "item"));
938        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 0");
939
940        TEST_EXPECT_NORESULT__NOERROREXPORTED(GB_following_marked(gb_marked, "item", 1)); // skip over last
941
942        TEST_REJECT_NULL(gb_marked = GB_next_marked(gb_marked, "item")); // find last
943        TEST_EXPECT_EQUAL(GBT_get_name(gb_marked), "item 14");
944    }
945
946    // @@@ delete some species, then search again
947
948    TEST_EXPECT_NO_ERROR(db.error);
949}
950
951
952#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.