source: branches/help/ARBDB/adindex.cxx

Last change on this file was 18730, checked in by westram, 3 years ago
  • remove trailing whitespace from c source.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.9 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : adindex.cxx                                       //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "gb_key.h"
12#include "gb_undo.h"
13#include "gb_index.h"
14#include "gb_hashindex.h"
15#include "gb_ts.h"
16
17#include <arb_strbuf.h>
18
19#include <cctype>
20
21#define GB_INDEX_FIND(gbf, ifs, quark)                                  \
22    for (ifs = GBCONTAINER_IFS(gbf);                                    \
23         ifs;                                                           \
24         ifs = GB_INDEX_FILES_NEXT(ifs))                                \
25    {                                                                   \
26        if (ifs->key == quark) break;                                   \
27    }
28
29void GBENTRY::index_check_in() {
30    // write field in index table
31
32    GBCONTAINER *gfather = GB_GRANDPA(this);
33    if (gfather) {
34        GBQUARK quark = GB_KEY_QUARK(this);
35        gb_index_files *ifs;
36        GB_INDEX_FIND(gfather, ifs, quark);
37
38        if (ifs) { // if key is indexed
39            if (is_indexable()) {
40                if (flags2.is_indexed) {
41                    GB_internal_error("Double checked in");
42                }
43                else {
44                    GB_CSTR       content = GB_read_char_pntr(this);
45                    unsigned long idx;
46                    GB_CALC_HASH_INDEX(content, idx, ifs->hash_table_size, ifs->case_sens);
47                    ifs->nr_of_elements++;
48
49                    {
50                        GB_REL_IFES   *entries = GB_INDEX_FILES_ENTRIES(ifs);
51                        gb_if_entries *ifes    = (gb_if_entries *)gbm_get_mem(sizeof(gb_if_entries), GB_GBM_INDEX(this));
52
53                        SET_GB_IF_ENTRIES_NEXT(ifes, GB_ENTRIES_ENTRY(entries, idx));
54                        SET_GB_IF_ENTRIES_GBD(ifes, this);
55                        SET_GB_ENTRIES_ENTRY(entries, idx, ifes);
56                    }
57                    flags2.should_be_indexed = 1;
58                    flags2.is_indexed        = 1;
59                }
60            }
61        }
62    }
63}
64
65void GBENTRY::index_check_out() {
66    // remove entry from index table
67    if (flags2.is_indexed) {
68        GBCONTAINER *gfather = GB_GRANDPA(this);
69        GBQUARK      quark   = GB_KEY_QUARK(this);
70
71        flags2.is_indexed = 0;
72
73        gb_index_files *ifs;
74        GB_INDEX_FIND(gfather, ifs, quark);
75
76        GB_ERROR     error;
77        if (!ifs) error = "key is not indexed";
78        else {
79            error = GB_push_transaction(this);
80            if (!error) {
81                GB_CSTR content = GB_read_char_pntr(this);
82
83                if (!content) {
84                    error = GBS_global_string("can't read key value (%s)", GB_await_error());
85                }
86                else {
87                    unsigned long idx;
88                    GB_CALC_HASH_INDEX(content, idx, ifs->hash_table_size, ifs->case_sens);
89
90                    gb_if_entries *ifes2   = NULp;
91                    GB_REL_IFES   *entries = GB_INDEX_FILES_ENTRIES(ifs);
92                    gb_if_entries *ifes;
93
94                    for (ifes = GB_ENTRIES_ENTRY(entries, idx); ifes; ifes = GB_IF_ENTRIES_NEXT(ifes)) {
95                        if (this == GB_IF_ENTRIES_GBD(ifes)) { // entry found
96                            if (ifes2) SET_GB_IF_ENTRIES_NEXT(ifes2, GB_IF_ENTRIES_NEXT(ifes));
97                            else SET_GB_ENTRIES_ENTRY(entries, idx, GB_IF_ENTRIES_NEXT(ifes));
98
99                            ifs->nr_of_elements--;
100                            gbm_free_mem(ifes, sizeof(gb_if_entries), GB_GBM_INDEX(this));
101                            break;
102                        }
103                        ifes2 = ifes;
104                    }
105                }
106            }
107            error = GB_end_transaction(this, error);
108        }
109
110        if (error) {
111            error = GBS_global_string("GBENTRY::index_check_out failed for key '%s' (%s)\n", GB_KEY(this), error);
112            GB_internal_error(error);
113        }
114    }
115}
116
117GB_ERROR GB_create_index(GBDATA *gbd, const char *key, GB_CASE case_sens, long estimated_size) { // goes to header: __ATTR__USERESULT
118    /* Create an index for a database.
119     * Uses hash tables - collisions are avoided by using linked lists.
120     */
121    GB_ERROR error = NULp;
122
123    if (gbd->is_entry()) {
124        error = "GB_create_index used on non CONTAINER Type";
125    }
126    else if (GB_read_clients(gbd)<0) {
127        error = "No index tables in DB clients allowed";
128    }
129    else {
130        GBCONTAINER *gbc       = gbd->as_container();
131        GBQUARK      key_quark = GB_find_or_create_quark(gbd, key);
132
133        gb_index_files *ifs;
134        GB_INDEX_FIND(gbc, ifs, key_quark);
135
136        if (!ifs) { // if not already have index (e.g. if fast-loaded)
137            ifs = (gb_index_files *)gbm_get_mem(sizeof(gb_index_files), GB_GBM_INDEX(gbc));
138            SET_GB_INDEX_FILES_NEXT(ifs, GBCONTAINER_IFS(gbc));
139            SET_GBCONTAINER_IFS(gbc, ifs);
140
141            ifs->key             = key_quark;
142            ifs->hash_table_size = gbs_get_a_prime(estimated_size);
143            ifs->nr_of_elements  = 0;
144            ifs->case_sens       = case_sens;
145
146            SET_GB_INDEX_FILES_ENTRIES(ifs, (gb_if_entries **)gbm_get_mem(sizeof(void *)*(int)ifs->hash_table_size, GB_GBM_INDEX(gbc)));
147
148            for (GBDATA *gbf = GB_find_sub_by_quark(gbd, -1, NULp, 0);
149                 gbf;
150                 gbf = GB_find_sub_by_quark(gbd, -1, gbf, 0))
151            {
152                if (gbf->is_container()) {
153                    for (GBDATA *gb2 = GB_find_sub_by_quark(gbf, key_quark, NULp, 0);
154                         gb2;
155                         gb2 = GB_find_sub_by_quark(gbf, key_quark, gb2, 0))
156                    {
157                        if (gb2->is_indexable()) gb2->as_entry()->index_check_in();
158                    }
159                }
160            }
161        }
162    }
163    RETURN_ERROR(error);
164}
165
166void gb_destroy_indices(GBCONTAINER *gbc) {
167    gb_index_files *ifs = GBCONTAINER_IFS(gbc);
168
169    while (ifs) {
170        GB_REL_IFES *if_entries = GB_INDEX_FILES_ENTRIES(ifs);
171
172        for (int index = 0; index<ifs->hash_table_size; index++) {
173            gb_if_entries *ifes = GB_ENTRIES_ENTRY(if_entries, index);
174
175            while (ifes) {
176                gb_if_entries *ifes_next = GB_IF_ENTRIES_NEXT(ifes);
177
178                gbm_free_mem(ifes, sizeof(*ifes), GB_GBM_INDEX(gbc));
179                ifes = ifes_next;
180            }
181        }
182        gbm_free_mem(if_entries, sizeof(void *)*(int)ifs->hash_table_size, GB_GBM_INDEX(gbc));
183
184        gb_index_files *ifs_next = GB_INDEX_FILES_NEXT(ifs);
185        gbm_free_mem(ifs, sizeof(gb_index_files), GB_GBM_INDEX(gbc));
186        ifs = ifs_next;
187    }
188}
189
190#if defined(DEBUG)
191
192NOT4PERL void GB_dump_indices(GBDATA *gbd) { // used for debugging
193    // dump indices of container
194
195    char *db_path = ARB_strdup(GB_get_db_path(gbd));
196    if (gbd->is_entry()) {
197        fprintf(stderr, "'%s' (%s) is no container.\n", db_path, GB_get_type_name(gbd));
198    }
199    else {
200        gb_index_files *ifs;
201        int             index_count = 0;
202
203        GBCONTAINER  *gbc  = gbd->as_container();
204        GB_MAIN_TYPE *Main = GBCONTAINER_MAIN(gbc);
205
206        for (ifs = GBCONTAINER_IFS(gbc); ifs; ifs = GB_INDEX_FILES_NEXT(ifs)) {
207            index_count++;
208        }
209
210        if (index_count == 0) {
211            fprintf(stderr, "Container '%s' has no index.\n", db_path);
212        }
213        else {
214            int pass;
215
216            fprintf(stderr, "Indices for '%s':\n", db_path);
217            for (pass = 1; pass <= 2; pass++) {
218                if (pass == 2) {
219                    fprintf(stderr, "\nDetailed index contents:\n\n");
220                }
221                index_count = 0;
222                for (ifs = GBCONTAINER_IFS(gbc); ifs; ifs = GB_INDEX_FILES_NEXT(ifs)) {
223                    fprintf(stderr,
224                            "* Index %i for key=%s (%i), entries=%li, %s\n",
225                            index_count,
226                            quark2key(Main, ifs->key),
227                            ifs->key,
228                            ifs->nr_of_elements,
229                            ifs->case_sens == GB_MIND_CASE
230                            ? "Case sensitive"
231                            : (ifs->case_sens == GB_IGNORE_CASE
232                               ? "Case insensitive"
233                               : "<Error in case_sens>")
234                            );
235
236                    if (pass == 2) {
237                        gb_if_entries *ifes;
238                        int            index;
239
240                        fprintf(stderr, "\n");
241                        for (index = 0; index<ifs->hash_table_size; index++) {
242                            for (ifes = GB_ENTRIES_ENTRY(GB_INDEX_FILES_ENTRIES(ifs), index);
243                                 ifes;
244                                 ifes = GB_IF_ENTRIES_NEXT(ifes))
245                            {
246                                GBDATA     *igbd = GB_IF_ENTRIES_GBD(ifes);
247                                const char *data = GB_read_char_pntr(igbd);
248
249                                fprintf(stderr, "  - '%s' (@idx=%i)\n", data, index);
250                            }
251                        }
252                        fprintf(stderr, "\n");
253                    }
254                    index_count++;
255                }
256            }
257        }
258    }
259
260    free(db_path);
261}
262
263#endif // DEBUG
264
265
266// find an entry in an hash table
267GBDATA *gb_index_find(GBCONTAINER *gbf, gb_index_files *ifs, GBQUARK quark, const char *val, GB_CASE case_sens, int after_index) {
268    unsigned long  index;
269    GB_CSTR        data;
270    gb_if_entries *ifes;
271    GBDATA        *result = NULp;
272    long           min_index;
273
274    if (!ifs) {
275        GB_INDEX_FIND(gbf, ifs, quark);
276        if (!ifs) {
277            GB_internal_error("gb_index_find called, but no index table found");
278            return NULp;
279        }
280    }
281
282    if (ifs->case_sens != case_sens) {
283        GB_internal_error("case mismatch between index and search");
284        return NULp;
285    }
286
287    GB_CALC_HASH_INDEX(val, index, ifs->hash_table_size, ifs->case_sens);
288    min_index = gbf->d.nheader;
289
290    for (ifes = GB_ENTRIES_ENTRY(GB_INDEX_FILES_ENTRIES(ifs), index);
291            ifes;
292            ifes = GB_IF_ENTRIES_NEXT(ifes))
293    {
294        GBDATA *igbd = GB_IF_ENTRIES_GBD(ifes);
295        GBCONTAINER *ifather = GB_FATHER(igbd);
296
297        if (ifather->index < after_index) continue;
298        if (ifather->index >= min_index) continue;
299        data = GB_read_char_pntr(igbd);
300        if (GBS_string_matches(data, val, case_sens)) { // entry found
301            result    = igbd;
302            min_index = ifather->index;
303        }
304    }
305    return result;
306}
307
308
309/* UNDO functions
310 *
311 * There are three undo stacks:
312 *
313 * GB_UNDO_NONE    no undo
314 * GB_UNDO_UNDO    normal undo stack
315 * GB_UNDO_REDO    redo stack
316 */
317
318static char *gb_set_undo_type(GBDATA *gb_main, GB_UNDO_TYPE type) {
319    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
320    Main->undo_type = type;
321    return NULp;
322}
323
324static void g_b_add_size_to_undo_entry(g_b_undo_entry *ue, long size) {
325    ue->sizeof_this                 += size;        // undo entry
326    ue->father->sizeof_this         += size;        // one undo
327    ue->father->father->sizeof_this += size;        // all undos
328}
329
330static g_b_undo_entry *new_g_b_undo_entry(g_b_undo_list *u) {
331    g_b_undo_entry *ue = (g_b_undo_entry *)gbm_get_mem(sizeof(g_b_undo_entry), GBM_UNDO);
332
333    ue->next   = u->entries;
334    ue->father = u;
335    u->entries = ue;
336
337    g_b_add_size_to_undo_entry(ue, sizeof(g_b_undo_entry));
338
339    return ue;
340}
341
342
343
344void gb_init_undo_stack(GB_MAIN_TYPE *Main) { // @@@ move into GB_MAIN_TYPE-ctor
345    ARB_calloc(Main->undo, 1);
346
347    Main->undo->max_size_of_all_undos = GB_MAX_UNDO_SIZE;
348
349    ARB_calloc(Main->undo->u, 1);
350    ARB_calloc(Main->undo->r, 1);
351}
352
353static void delete_g_b_undo_entry(g_b_undo_entry *entry) {
354    switch (entry->type) {
355        case GB_UNDO_ENTRY_TYPE_MODIFY:
356        case GB_UNDO_ENTRY_TYPE_MODIFY_ARRAY: {
357            if (entry->d.ts) {
358                gb_del_ref_gb_transaction_save(entry->d.ts);
359            }
360        }
361        default:
362            break;
363    }
364    gbm_free_mem(entry, sizeof(g_b_undo_entry), GBM_UNDO);
365}
366
367static void delete_g_b_undo_list(g_b_undo_list *u) {
368    g_b_undo_entry *a, *next;
369    for (a = u->entries; a; a = next) {
370        next = a->next;
371        delete_g_b_undo_entry(a);
372    }
373    free(u);
374}
375
376static void delete_g_b_undo_header(g_b_undo_header *uh) {
377    g_b_undo_list *next = NULp;
378    for (g_b_undo_list *a = uh->stack; a; a = next) {
379        next = a->next;
380        delete_g_b_undo_list(a);
381    }
382    free(uh);
383}
384
385static char *g_b_check_undo_size2(g_b_undo_header *uhs, long size, long max_cnt) {
386    long           csize = 0;
387    long           ccnt  = 0;
388    g_b_undo_list *us;
389
390    for (us = uhs->stack; us && us->next;  us = us->next) {
391        csize += us->sizeof_this;
392        ccnt ++;
393        if (((csize + us->next->sizeof_this) > size) ||
394            (ccnt >= max_cnt)) {  // delete the rest
395            g_b_undo_list *next = NULp;
396
397            for (g_b_undo_list *a = us->next; a; a = next) {
398                next = a->next;
399                delete_g_b_undo_list(a);
400            }
401            us->next = NULp;
402            uhs->sizeof_this = csize;
403            break;
404        }
405    }
406    return NULp;
407}
408
409static char *g_b_check_undo_size(GB_MAIN_TYPE *Main) {
410    long  maxsize     = Main->undo->max_size_of_all_undos;
411    char *error       = g_b_check_undo_size2(Main->undo->u, maxsize/2, GB_MAX_UNDO_CNT);
412    if (!error) error = g_b_check_undo_size2(Main->undo->r, maxsize/2, GB_MAX_REDO_CNT);
413    return error;
414}
415
416
417void gb_free_undo_stack(GB_MAIN_TYPE *Main) {
418    delete_g_b_undo_header(Main->undo->u);
419    delete_g_b_undo_header(Main->undo->r);
420    free(Main->undo);
421}
422
423// -------------------------
424//      real undo (redo)
425
426static GB_ERROR undo_entry(g_b_undo_entry *ue) {
427    GB_ERROR error = NULp;
428    switch (ue->type) {
429        case GB_UNDO_ENTRY_TYPE_CREATED:
430            error = GB_delete(ue->source);
431            break;
432
433        case GB_UNDO_ENTRY_TYPE_DELETED: {
434            GBDATA *gbd = ue->d.gs.gbd;
435            if (gbd->is_container()) {
436                gbd = gb_make_pre_defined_container(ue->source->as_container(), gbd->as_container(), -1, ue->d.gs.key);
437            }
438            else {
439                gbd = gb_make_pre_defined_entry(ue->source->as_container(), gbd, -1, ue->d.gs.key);
440            }
441            GB_ARRAY_FLAGS(gbd).flags = ue->flag;
442            gb_touch_header(GB_FATHER(gbd));
443            gb_touch_entry(gbd, GB_CREATED);
444            break;
445        }
446        case GB_UNDO_ENTRY_TYPE_MODIFY_ARRAY:
447        case GB_UNDO_ENTRY_TYPE_MODIFY: {
448            GBDATA *gbd = ue->source;
449            if (gbd->is_entry()) {
450                GBENTRY *gbe = gbd->as_entry();
451                gb_save_extern_data_in_ts(gbe); // check out and free string
452
453                if (ue->d.ts) { // nothing to undo (e.g. if undoing GB_touch)
454                    gbe->flags              = ue->d.ts->flags;
455                    gbe->flags2.extern_data = ue->d.ts->flags2.extern_data;
456
457                    memcpy(&gbe->info, &ue->d.ts->info, sizeof(gbe->info)); // restore old information
458                    if (gbe->type() >= GB_BITS) {
459                        if (gbe->stored_external()) {
460                            gbe->info.ex.set_data(ue->d.ts->info.ex.data);
461                        }
462
463                        gb_del_ref_and_extern_gb_transaction_save(ue->d.ts);
464                        ue->d.ts = NULp;
465
466                        gbe->index_re_check_in();
467                    }
468                }
469            }
470            {
471                gb_header_flags *pflags = &GB_ARRAY_FLAGS(gbd);
472                if (pflags->flags != (unsigned)ue->flag) {
473                    GBCONTAINER *gb_father = GB_FATHER(gbd);
474                    gbd->flags.saved_flags = pflags->flags;
475                    pflags->flags = ue->flag;
476                    if (GB_FATHER(gb_father)) {
477                        gb_touch_header(gb_father); // don't touch father of main
478                    }
479                }
480            }
481            gb_touch_entry(gbd, GB_NORMAL_CHANGE);
482            break;
483        }
484        default:
485            GB_internal_error("Undo stack corrupt:!!!");
486            error = GB_export_error("shit 34345");
487            break;
488    }
489
490    return error;
491}
492
493
494
495static GB_ERROR g_b_undo(GBDATA *gb_main, g_b_undo_header *uh) { // goes to header: __ATTR__USERESULT
496    GB_ERROR error = NULp;
497
498    if (!uh->stack) {
499        error = "Sorry no more undos/redos available";
500    }
501    else {
502        g_b_undo_list  *u = uh->stack;
503        g_b_undo_entry *ue, *next;
504
505        error = GB_begin_transaction(gb_main);
506
507        for (ue=u->entries; ue && !error; ue = next) {
508            next = ue->next;
509            error = undo_entry(ue);
510            delete_g_b_undo_entry(ue);
511            u->entries = next;
512        }
513        uh->sizeof_this -= u->sizeof_this;          // remove undo from list
514        uh->stack        = u->next;
515
516        delete_g_b_undo_list(u);
517        error = GB_end_transaction(gb_main, error);
518    }
519    return error;
520}
521
522static GB_CSTR g_b_read_undo_key_pntr(GB_MAIN_TYPE *Main, g_b_undo_entry *ue) {
523    return quark2key(Main, ue->d.gs.key);
524}
525
526static char *g_b_undo_info(GB_MAIN_TYPE *Main, g_b_undo_header *uh) {
527    GBS_strstruct  *res = GBS_stropen(1024);
528    g_b_undo_list  *u;
529    g_b_undo_entry *ue;
530
531    u = uh->stack;
532    if (!u) return ARB_strdup("No more undos available");
533    for (ue=u->entries; ue; ue = ue->next) {
534        switch (ue->type) {
535            case GB_UNDO_ENTRY_TYPE_CREATED:
536                GBS_strcat(res, "Delete new entry: ");
537                GBS_strcat(res, gb_read_key_pntr(ue->source));
538                break;
539            case GB_UNDO_ENTRY_TYPE_DELETED:
540                GBS_strcat(res, "Rebuild deleted entry: ");
541                GBS_strcat(res, g_b_read_undo_key_pntr(Main, ue));
542                break;
543            case GB_UNDO_ENTRY_TYPE_MODIFY_ARRAY:
544            case GB_UNDO_ENTRY_TYPE_MODIFY:
545                GBS_strcat(res, "Undo modified entry: ");
546                GBS_strcat(res, gb_read_key_pntr(ue->source));
547                break;
548            default:
549                break;
550        }
551        GBS_chrcat(res, '\n');
552    }
553    return GBS_strclose(res);
554}
555
556static char *gb_free_all_undos(GBDATA *gb_main) {
557    // Remove all existing undos/redos
558    GB_MAIN_TYPE  *Main = GB_MAIN(gb_main);
559    g_b_undo_list *a, *next;
560
561    for (a = Main->undo->r->stack; a; a = next) {
562        next = a->next;
563        delete_g_b_undo_list(a);
564    }
565    Main->undo->r->stack       = NULp;
566    Main->undo->r->sizeof_this = 0;
567
568    for (a = Main->undo->u->stack; a; a = next) {
569        next = a->next;
570        delete_g_b_undo_list(a);
571    }
572    Main->undo->u->stack       = NULp;
573    Main->undo->u->sizeof_this = 0;
574
575    return NULp;
576}
577
578
579char *gb_set_undo_sync(GBDATA *gb_main) {
580    // start a new undoable transaction
581    GB_MAIN_TYPE    *Main  = GB_MAIN(gb_main);
582    char            *error = g_b_check_undo_size(Main);
583    g_b_undo_header *uhs;
584
585    if (error) return error;
586    switch (Main->requested_undo_type) {    // init the target undo stack
587        case GB_UNDO_UNDO:      // that will undo but delete all redos
588            uhs         = Main->undo->u;
589            break;
590        case GB_UNDO_UNDO_REDO: uhs = Main->undo->u; break;
591        case GB_UNDO_REDO:      uhs = Main->undo->r; break;
592        case GB_UNDO_KILL:      gb_free_all_undos(gb_main);
593                                FALLTHROUGH;
594        default:                uhs = NULp;
595    }
596    if (uhs) {
597        g_b_undo_list *u = ARB_calloc<g_b_undo_list>(1);
598        u->next = uhs->stack;
599        u->father = uhs;
600        uhs->stack = u;
601        Main->undo->valid_u = u;
602    }
603
604    return gb_set_undo_type(gb_main, Main->requested_undo_type);
605}
606
607char *gb_disable_undo(GBDATA *gb_main) {
608    // called to finish an undoable section, called at end of gb_commit_transaction
609    GB_MAIN_TYPE  *Main = GB_MAIN(gb_main);
610    g_b_undo_list *u    = Main->undo->valid_u;
611
612    if (!u) return NULp;
613    if (!u->entries) {      // nothing to undo, just a read transaction
614        u->father->stack = u->next;
615        delete_g_b_undo_list(u);
616    }
617    else {
618        if (Main->requested_undo_type == GB_UNDO_UNDO) {    // remove all redos
619            g_b_undo_list *a, *next;
620
621            for (a = Main->undo->r->stack; a; a = next) {
622                next = a->next;
623                delete_g_b_undo_list(a);
624            }
625            Main->undo->r->stack       = NULp;
626            Main->undo->r->sizeof_this = 0;
627        }
628    }
629    Main->undo->valid_u = NULp;
630    return gb_set_undo_type(gb_main, GB_UNDO_NONE);
631}
632
633void gb_check_in_undo_create(GB_MAIN_TYPE *Main, GBDATA *gbd) {
634    if (Main->undo->valid_u) {
635        g_b_undo_entry *ue = new_g_b_undo_entry(Main->undo->valid_u);
636
637        ue->type      = GB_UNDO_ENTRY_TYPE_CREATED;
638        ue->source    = gbd;
639        ue->gbm_index = GB_GBM_INDEX(gbd);
640        ue->flag      = 0;
641    }
642}
643
644void gb_check_in_undo_modify(GB_MAIN_TYPE *Main, GBDATA *gbd) {
645    if (!Main->undo->valid_u) {
646        GB_FREE_TRANSACTION_SAVE(gbd);
647    }
648    else {
649        gb_transaction_save *old = gbd->get_oldData();
650        g_b_undo_entry      *ue  = new_g_b_undo_entry(Main->undo->valid_u);
651
652        ue->source    = gbd;
653        ue->gbm_index = GB_GBM_INDEX(gbd);
654        ue->type      = GB_UNDO_ENTRY_TYPE_MODIFY;
655        ue->flag      = gbd->flags.saved_flags;
656
657        if (gbd->is_entry()) {
658            ue->d.ts = old;
659            if (old) {
660                gb_add_ref_gb_transaction_save(old);
661                if (gbd->type() >= GB_BITS && old->stored_external() && old->info.ex.data) {
662                    ue->type = GB_UNDO_ENTRY_TYPE_MODIFY_ARRAY;
663                    // move external array from ts to undo entry struct
664                    g_b_add_size_to_undo_entry(ue, old->info.ex.memsize);
665                }
666            }
667        }
668    }
669}
670
671void gb_check_in_undo_delete(GB_MAIN_TYPE *Main, GBDATA*& gbd) {
672    if (!Main->undo->valid_u) {
673        gb_delete_entry(gbd);
674        return;
675    }
676
677    if (gbd->is_container()) {
678        GBCONTAINER *gbc = gbd->as_container();
679        for (int index = 0; (index < gbc->d.nheader); index++) {
680            GBDATA *gbd2 = GBCONTAINER_ELEM(gbc, index);
681            if (gbd2) gb_check_in_undo_delete(Main, gbd2);
682        }
683    }
684    else {
685        gbd->as_entry()->index_check_out();
686        gbd->flags2.should_be_indexed = 0; // do not re-checkin
687    }
688    gb_abort_entry(gbd);            // get old version
689
690    g_b_undo_entry *ue = new_g_b_undo_entry(Main->undo->valid_u);
691
692    ue->type      = GB_UNDO_ENTRY_TYPE_DELETED;
693    ue->source    = GB_FATHER(gbd);
694    ue->gbm_index = GB_GBM_INDEX(gbd);
695    ue->flag      = GB_ARRAY_FLAGS(gbd).flags;
696
697    ue->d.gs.gbd = gbd;
698    ue->d.gs.key = GB_KEY_QUARK(gbd);
699
700    gb_pre_delete_entry(gbd);       // get the core of the entry
701
702    if (gbd->is_container()) {
703        g_b_add_size_to_undo_entry(ue, sizeof(GBCONTAINER));
704    }
705    else {
706        if (gbd->type() >= GB_BITS && gbd->as_entry()->stored_external()) {
707            /* we have copied the data structures, now
708               mark the old as deleted !!! */
709            g_b_add_size_to_undo_entry(ue, gbd->as_entry()->memsize());
710        }
711        g_b_add_size_to_undo_entry(ue, sizeof(GBENTRY));
712    }
713}
714
715// ----------------------------------------
716//      UNDO functions exported to USER
717
718GB_ERROR GB_request_undo_type(GBDATA *gb_main, GB_UNDO_TYPE type) { // goes to header: __ATTR__USERESULT_TODO
719    /*! Define how to undo DB changes.
720     *
721     * This function should be called just before opening a transaction,
722     * otherwise its effect will be delayed.
723     *
724     * Possible types are:
725     *      GB_UNDO_UNDO        enable undo
726     *      GB_UNDO_NONE        disable undo
727     *      GB_UNDO_KILL        disable undo and remove old undos !!
728     *
729     * Note: if GB_request_undo_type returns an error, local undo type remains unchanged
730     */
731
732    GB_MAIN_TYPE *Main  = GB_MAIN(gb_main);
733    GB_ERROR      error = NULp;
734
735    if (Main->is_client()) {
736        enum gb_undo_commands cmd = (type == GB_UNDO_NONE || type == GB_UNDO_KILL)
737            ? _GBCMC_UNDOCOM_REQUEST_NOUNDO
738            : _GBCMC_UNDOCOM_REQUEST_UNDO;
739        error = gbcmc_send_undo_commands(gb_main, cmd);
740    }
741    if (!error) Main->requested_undo_type = type;
742
743    return error;
744}
745
746GB_UNDO_TYPE GB_get_requested_undo_type(GBDATA *gb_main) {
747    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
748    return Main->requested_undo_type;
749}
750
751
752GB_ERROR GB_undo(GBDATA *gb_main, GB_UNDO_TYPE type) { // goes to header: __ATTR__USERESULT
753    // undo/redo the last transaction
754
755    GB_MAIN_TYPE *Main  = GB_MAIN(gb_main);
756    GB_ERROR      error = NULp;
757
758    if (Main->is_client()) {
759        switch (type) {
760            case GB_UNDO_UNDO:
761                error = gbcmc_send_undo_commands(gb_main, _GBCMC_UNDOCOM_UNDO);
762                break;
763
764            case GB_UNDO_REDO:
765                error = gbcmc_send_undo_commands(gb_main, _GBCMC_UNDOCOM_REDO);
766                break;
767
768            default:
769                GB_internal_error("unknown undo type in GB_undo");
770                error = "Internal UNDO error";
771                break;
772        }
773    }
774    else {
775        GB_UNDO_TYPE old_type = GB_get_requested_undo_type(gb_main);
776        switch (type) {
777            case GB_UNDO_UNDO:
778                error = GB_request_undo_type(gb_main, GB_UNDO_REDO);
779                if (!error) {
780                    error = g_b_undo(gb_main, Main->undo->u);
781                    ASSERT_NO_ERROR(GB_request_undo_type(gb_main, old_type));
782                }
783                break;
784
785            case GB_UNDO_REDO:
786                error = GB_request_undo_type(gb_main, GB_UNDO_UNDO_REDO);
787                if (!error) {
788                    error = g_b_undo(gb_main, Main->undo->r);
789                    ASSERT_NO_ERROR(GB_request_undo_type(gb_main, old_type));
790                }
791                break;
792
793            default:
794                error = "GB_undo: unknown undo type specified";
795                break;
796        }
797    }
798
799    return error;
800}
801
802
803char *GB_undo_info(GBDATA *gb_main, GB_UNDO_TYPE type) {
804    // get some information about the next undo
805    // returns NULp in case of exported error
806
807    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
808    if (Main->is_client()) {
809        switch (type) {
810            case GB_UNDO_UNDO:
811                return gbcmc_send_undo_info_commands(gb_main, _GBCMC_UNDOCOM_INFO_UNDO);
812            case GB_UNDO_REDO:
813                return gbcmc_send_undo_info_commands(gb_main, _GBCMC_UNDOCOM_INFO_REDO);
814            default:
815                GB_export_error("GB_undo_info: unknown undo type specified");
816                return NULp;
817        }
818    }
819    switch (type) {
820        case GB_UNDO_UNDO:
821            return g_b_undo_info(Main, Main->undo->u);
822        case GB_UNDO_REDO:
823            return g_b_undo_info(Main, Main->undo->r);
824        default:
825            GB_export_error("GB_undo_info: unknown undo type specified");
826            return NULp;
827    }
828}
829
830GB_ERROR GB_set_undo_mem(GBDATA *gbd, long memsize) {
831    // set the maximum memory used for undoing
832
833    GB_MAIN_TYPE *Main = GB_MAIN(gbd);
834    if (memsize < _GBCMC_UNDOCOM_SET_MEM) {
835        return GB_export_errorf("Not enough UNDO memory specified: should be more than %i",
836                                _GBCMC_UNDOCOM_SET_MEM);
837    }
838    Main->undo->max_size_of_all_undos = memsize;
839    if (Main->is_client()) {
840        return gbcmc_send_undo_commands(gbd, (enum gb_undo_commands)memsize);
841    }
842    g_b_check_undo_size(Main);
843    return NULp;
844}
845
846
847#ifdef UNIT_TESTS
848#include <test_unit.h>
849#include <map>
850#include <ad_cb_prot.h>
851
852class cb_counter : virtual Noncopyable {
853    GBDATA *gbd;
854    int deletes, changes, creates;
855    bool do_trace;
856public:
857    static void count_swapped(GBDATA* gbd, cb_counter* counter, GB_CB_TYPE t ) {
858        // CB system cannot swap parameters, we need to wrap
859        // And yes... fixed parameters are added *in the middle*, not at the end.
860        counter->count(gbd, t);
861    }
862
863    cb_counter(GBDATA* gbd_) : gbd(gbd_), deletes(0), changes(0), creates(0), do_trace(false) {
864        GB_add_callback(gbd, GB_CB_ALL, makeDatabaseCallback(cb_counter::count_swapped, this));
865    }
866
867    ~cb_counter() {
868        // CB system cannot auto-destroy callbacks, nor are there CB handles
869        if (deletes == 0) { // (GB_delete destroys CBs I think)
870            GB_remove_callback(gbd, GB_CB_ALL, makeDatabaseCallback(cb_counter::count_swapped, this));
871            // above must be exact copy of GB_add_callback copy
872        }
873    }
874
875    void count(GBDATA*, GB_CB_TYPE t) {
876        if (t & GB_CB_DELETE) deletes ++;
877        if (t & GB_CB_SON_CREATED) creates ++;
878        if (t & GB_CB_CHANGED) changes ++;
879        if (do_trace) printf("counts: %p d=%i c=%i n=%i\n", gbd, deletes, changes, creates);
880    }
881
882    void trace(bool t) {
883        do_trace = t;
884    }
885
886    int get_deletes() {
887        int res = deletes;
888        deletes = 0;
889        return res;
890    }
891
892    int get_creates() {
893        int res = creates;
894        creates = 0;
895        return res;
896    }
897
898    int get_changes() {
899        int res = changes;
900        changes = 0;
901        return res;
902    }
903};
904
905
906
907void TEST_GB_undo__basic() {
908    GB_shell  shell;
909    // GB_ERROR err;
910
911    GBDATA   *main = GB_open("nosuch.arb", "c");
912    GB_begin_transaction(main);
913    cb_counter main_counter(main);
914    GB_commit_transaction(main);
915
916    GBDATA   *gbd;
917    cb_counter *gbd_counter;
918
919    GB_set_undo_mem(main, 10000);
920    GB_request_undo_type(main, GB_UNDO_UNDO);
921
922    // Notes:
923    // - CB_CHANGED == CB_SON_CREATED
924    //   Both are called on creating sons as well as just writing strings.
925    // - GB_SON_CREATED also called if "SON_DELETED"
926    // - It's possible to get CB_SON_CREATED on GBENTRY if the CB is
927    //   registered within the running transaction.
928    // - CBs are triggered at the end of the transaction.
929
930
931    // test undo create empty string entry
932    GB_begin_transaction(main);
933    gbd = GB_create(main, "test", GB_STRING);
934    gbd_counter = new cb_counter(gbd);
935    GB_commit_transaction(main);
936
937    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1);
938    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
939    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
940    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1);
941    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
942    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
943    // string initialises as empty
944    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "");
945    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                 );
946    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1); // BROKEN -- should be 0
947    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
948    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0); // BROKEN -- should be 1
949    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 0);
950    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 0);
951    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 1);
952    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)         );
953    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                 );
954    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1);
955    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
956    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
957    TEST_REJECT_NULL(  gbd = GB_find(main, "test", SEARCH_CHILD) );
958    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "");
959
960    // re-establish counter
961    GB_begin_transaction(main);
962    delete gbd_counter;
963    gbd_counter = new cb_counter(gbd);
964    GB_commit_transaction(main);
965
966    // test undo delete empty string
967    GB_begin_transaction(main);
968    GB_delete(gbd);
969    GB_commit_transaction(main);
970    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1); // BROKEN -- should be 0
971    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
972    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
973    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 0);
974    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 0);
975    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 1);
976    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
977    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
978    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1);
979    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
980    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
981    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "" );
982    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
983    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1); // BROKEN -- should be 0
984    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
985    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
986    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
987    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
988    TEST_REJECT_NULL( gbd = GB_find(main, "test", SEARCH_CHILD) );
989    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 0);
990    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 0);
991    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
992
993    // re-establish counter
994    GB_begin_transaction(main);
995    delete gbd_counter;
996    gbd_counter = new cb_counter(gbd);
997    GB_commit_transaction(main);
998
999    // test undo write short string
1000    const char* str = "testtest9012345";
1001    GB_begin_transaction(main);
1002    GB_write_string(gbd, str);
1003    GB_commit_transaction(main);
1004    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1); // BROKEN?
1005    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
1006    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
1007    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str);
1008    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1009    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1); // BROKEN?
1010    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
1011    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
1012    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "");
1013    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
1014    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1); // BROKEN?
1015    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
1016    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
1017    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str);
1018
1019    delete gbd_counter;
1020
1021    // test undo delete short string
1022    GB_begin_transaction(main);
1023    GB_delete(gbd);
1024    GB_commit_transaction(main);
1025    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1026    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1027
1028    //////////// THIS IS WHERE UNDO FAILS //////////////////
1029    TEST_EXPECT_EQUAL__BROKEN( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str, (char*)NULp);
1030    GB_close(main);
1031    return; // remainder will fail now
1032
1033
1034    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
1035    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1036    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1037    TEST_REJECT_NULL( gbd = GB_find(main, "test", SEARCH_CHILD) );
1038
1039    // test undo write "" and delete short string
1040    GB_begin_transaction(main);
1041    GB_write_string(gbd, str);
1042    GB_delete(gbd);
1043    GB_commit_transaction(main);
1044    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1045    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1046    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str);
1047    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
1048    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1049    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1050    TEST_REJECT_NULL( gbd = GB_find(main, "test", SEARCH_CHILD) );
1051
1052    //err = GB_write_string(gbd, "testtest9012345");
1053
1054    GB_close(main);
1055}
1056TEST_PUBLISH(TEST_GB_undo__basic);
1057
1058
1059#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.