source: trunk/ARBDB/adindex.cxx

Last change on this file was 19339, checked in by westram, 2 years ago
  • reintegrates 'refactor' into 'trunk'
    • eliminates old interface of GBS_strstruct
    • add a few new unittests (editor-config string + some PT-SERVER-functions)
  • adds: log:branches/refactor@19300:19338
  • 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    char          *info   = NULp;
528    g_b_undo_list *u      = uh->stack;
529    if (!u) {
530        info = ARB_strdup("No more undos available");
531    }
532    else {
533        GBS_strstruct res(1024);
534        for (g_b_undo_entry *ue = u->entries; ue; ue = ue->next) {
535            switch (ue->type) {
536                case GB_UNDO_ENTRY_TYPE_CREATED:
537                    res.cat("Delete new entry: ");
538                    res.cat(gb_read_key_pntr(ue->source));
539                    break;
540
541                case GB_UNDO_ENTRY_TYPE_DELETED:
542                    res.cat("Rebuild deleted entry: ");
543                    res.cat(g_b_read_undo_key_pntr(Main, ue));
544                    break;
545
546                case GB_UNDO_ENTRY_TYPE_MODIFY_ARRAY:
547                case GB_UNDO_ENTRY_TYPE_MODIFY:
548                    res.cat("Undo modified entry: ");
549                    res.cat(gb_read_key_pntr(ue->source));
550                    break;
551            }
552            res.put('\n');
553        }
554        info = res.release();
555    }
556    return info;
557}
558
559static char *gb_free_all_undos(GBDATA *gb_main) {
560    // Remove all existing undos/redos
561    GB_MAIN_TYPE  *Main = GB_MAIN(gb_main);
562    g_b_undo_list *a, *next;
563
564    for (a = Main->undo->r->stack; a; a = next) {
565        next = a->next;
566        delete_g_b_undo_list(a);
567    }
568    Main->undo->r->stack       = NULp;
569    Main->undo->r->sizeof_this = 0;
570
571    for (a = Main->undo->u->stack; a; a = next) {
572        next = a->next;
573        delete_g_b_undo_list(a);
574    }
575    Main->undo->u->stack       = NULp;
576    Main->undo->u->sizeof_this = 0;
577
578    return NULp;
579}
580
581
582char *gb_set_undo_sync(GBDATA *gb_main) {
583    // start a new undoable transaction
584    GB_MAIN_TYPE    *Main  = GB_MAIN(gb_main);
585    char            *error = g_b_check_undo_size(Main);
586    g_b_undo_header *uhs;
587
588    if (error) return error;
589    switch (Main->requested_undo_type) {    // init the target undo stack
590        case GB_UNDO_UNDO:      // that will undo but delete all redos
591            uhs         = Main->undo->u;
592            break;
593        case GB_UNDO_UNDO_REDO: uhs = Main->undo->u; break;
594        case GB_UNDO_REDO:      uhs = Main->undo->r; break;
595        case GB_UNDO_KILL:      gb_free_all_undos(gb_main);
596                                FALLTHROUGH;
597        default:                uhs = NULp;
598    }
599    if (uhs) {
600        g_b_undo_list *u = ARB_calloc<g_b_undo_list>(1);
601        u->next = uhs->stack;
602        u->father = uhs;
603        uhs->stack = u;
604        Main->undo->valid_u = u;
605    }
606
607    return gb_set_undo_type(gb_main, Main->requested_undo_type);
608}
609
610char *gb_disable_undo(GBDATA *gb_main) {
611    // called to finish an undoable section, called at end of gb_commit_transaction
612    GB_MAIN_TYPE  *Main = GB_MAIN(gb_main);
613    g_b_undo_list *u    = Main->undo->valid_u;
614
615    if (!u) return NULp;
616    if (!u->entries) {      // nothing to undo, just a read transaction
617        u->father->stack = u->next;
618        delete_g_b_undo_list(u);
619    }
620    else {
621        if (Main->requested_undo_type == GB_UNDO_UNDO) {    // remove all redos
622            g_b_undo_list *a, *next;
623
624            for (a = Main->undo->r->stack; a; a = next) {
625                next = a->next;
626                delete_g_b_undo_list(a);
627            }
628            Main->undo->r->stack       = NULp;
629            Main->undo->r->sizeof_this = 0;
630        }
631    }
632    Main->undo->valid_u = NULp;
633    return gb_set_undo_type(gb_main, GB_UNDO_NONE);
634}
635
636void gb_check_in_undo_create(GB_MAIN_TYPE *Main, GBDATA *gbd) {
637    if (Main->undo->valid_u) {
638        g_b_undo_entry *ue = new_g_b_undo_entry(Main->undo->valid_u);
639
640        ue->type      = GB_UNDO_ENTRY_TYPE_CREATED;
641        ue->source    = gbd;
642        ue->gbm_index = GB_GBM_INDEX(gbd);
643        ue->flag      = 0;
644    }
645}
646
647void gb_check_in_undo_modify(GB_MAIN_TYPE *Main, GBDATA *gbd) {
648    if (!Main->undo->valid_u) {
649        GB_FREE_TRANSACTION_SAVE(gbd);
650    }
651    else {
652        gb_transaction_save *old = gbd->get_oldData();
653        g_b_undo_entry      *ue  = new_g_b_undo_entry(Main->undo->valid_u);
654
655        ue->source    = gbd;
656        ue->gbm_index = GB_GBM_INDEX(gbd);
657        ue->type      = GB_UNDO_ENTRY_TYPE_MODIFY;
658        ue->flag      = gbd->flags.saved_flags;
659
660        if (gbd->is_entry()) {
661            ue->d.ts = old;
662            if (old) {
663                gb_add_ref_gb_transaction_save(old);
664                if (gbd->type() >= GB_BITS && old->stored_external() && old->info.ex.data) {
665                    ue->type = GB_UNDO_ENTRY_TYPE_MODIFY_ARRAY;
666                    // move external array from ts to undo entry struct
667                    g_b_add_size_to_undo_entry(ue, old->info.ex.memsize);
668                }
669            }
670        }
671    }
672}
673
674void gb_check_in_undo_delete(GB_MAIN_TYPE *Main, GBDATA*& gbd) {
675    if (!Main->undo->valid_u) {
676        gb_delete_entry(gbd);
677        return;
678    }
679
680    if (gbd->is_container()) {
681        GBCONTAINER *gbc = gbd->as_container();
682        for (int index = 0; (index < gbc->d.nheader); index++) {
683            GBDATA *gbd2 = GBCONTAINER_ELEM(gbc, index);
684            if (gbd2) gb_check_in_undo_delete(Main, gbd2);
685        }
686    }
687    else {
688        gbd->as_entry()->index_check_out();
689        gbd->flags2.should_be_indexed = 0; // do not re-checkin
690    }
691    gb_abort_entry(gbd);            // get old version
692
693    g_b_undo_entry *ue = new_g_b_undo_entry(Main->undo->valid_u);
694
695    ue->type      = GB_UNDO_ENTRY_TYPE_DELETED;
696    ue->source    = GB_FATHER(gbd);
697    ue->gbm_index = GB_GBM_INDEX(gbd);
698    ue->flag      = GB_ARRAY_FLAGS(gbd).flags;
699
700    ue->d.gs.gbd = gbd;
701    ue->d.gs.key = GB_KEY_QUARK(gbd);
702
703    gb_pre_delete_entry(gbd);       // get the core of the entry
704
705    if (gbd->is_container()) {
706        g_b_add_size_to_undo_entry(ue, sizeof(GBCONTAINER));
707    }
708    else {
709        if (gbd->type() >= GB_BITS && gbd->as_entry()->stored_external()) {
710            /* we have copied the data structures, now
711               mark the old as deleted !!! */
712            g_b_add_size_to_undo_entry(ue, gbd->as_entry()->memsize());
713        }
714        g_b_add_size_to_undo_entry(ue, sizeof(GBENTRY));
715    }
716}
717
718// ----------------------------------------
719//      UNDO functions exported to USER
720
721GB_ERROR GB_request_undo_type(GBDATA *gb_main, GB_UNDO_TYPE type) { // goes to header: __ATTR__USERESULT_TODO
722    /*! Define how to undo DB changes.
723     *
724     * This function should be called just before opening a transaction,
725     * otherwise its effect will be delayed.
726     *
727     * Possible types are:
728     *      GB_UNDO_UNDO        enable undo
729     *      GB_UNDO_NONE        disable undo
730     *      GB_UNDO_KILL        disable undo and remove old undos !!
731     *
732     * Note: if GB_request_undo_type returns an error, local undo type remains unchanged
733     */
734
735    GB_MAIN_TYPE *Main  = GB_MAIN(gb_main);
736    GB_ERROR      error = NULp;
737
738    if (Main->is_client()) {
739        enum gb_undo_commands cmd = (type == GB_UNDO_NONE || type == GB_UNDO_KILL)
740            ? _GBCMC_UNDOCOM_REQUEST_NOUNDO
741            : _GBCMC_UNDOCOM_REQUEST_UNDO;
742        error = gbcmc_send_undo_commands(gb_main, cmd);
743    }
744    if (!error) Main->requested_undo_type = type;
745
746    return error;
747}
748
749GB_UNDO_TYPE GB_get_requested_undo_type(GBDATA *gb_main) {
750    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
751    return Main->requested_undo_type;
752}
753
754
755GB_ERROR GB_undo(GBDATA *gb_main, GB_UNDO_TYPE type) { // goes to header: __ATTR__USERESULT
756    // undo/redo the last transaction
757
758    GB_MAIN_TYPE *Main  = GB_MAIN(gb_main);
759    GB_ERROR      error = NULp;
760
761    if (Main->is_client()) {
762        switch (type) {
763            case GB_UNDO_UNDO:
764                error = gbcmc_send_undo_commands(gb_main, _GBCMC_UNDOCOM_UNDO);
765                break;
766
767            case GB_UNDO_REDO:
768                error = gbcmc_send_undo_commands(gb_main, _GBCMC_UNDOCOM_REDO);
769                break;
770
771            default:
772                GB_internal_error("unknown undo type in GB_undo");
773                error = "Internal UNDO error";
774                break;
775        }
776    }
777    else {
778        GB_UNDO_TYPE old_type = GB_get_requested_undo_type(gb_main);
779        switch (type) {
780            case GB_UNDO_UNDO:
781                error = GB_request_undo_type(gb_main, GB_UNDO_REDO);
782                if (!error) {
783                    error = g_b_undo(gb_main, Main->undo->u);
784                    ASSERT_NO_ERROR(GB_request_undo_type(gb_main, old_type));
785                }
786                break;
787
788            case GB_UNDO_REDO:
789                error = GB_request_undo_type(gb_main, GB_UNDO_UNDO_REDO);
790                if (!error) {
791                    error = g_b_undo(gb_main, Main->undo->r);
792                    ASSERT_NO_ERROR(GB_request_undo_type(gb_main, old_type));
793                }
794                break;
795
796            default:
797                error = "GB_undo: unknown undo type specified";
798                break;
799        }
800    }
801
802    return error;
803}
804
805
806char *GB_undo_info(GBDATA *gb_main, GB_UNDO_TYPE type) {
807    // get some information about the next undo
808    // returns NULp in case of exported error
809
810    GB_MAIN_TYPE *Main = GB_MAIN(gb_main);
811    if (Main->is_client()) {
812        switch (type) {
813            case GB_UNDO_UNDO:
814                return gbcmc_send_undo_info_commands(gb_main, _GBCMC_UNDOCOM_INFO_UNDO);
815            case GB_UNDO_REDO:
816                return gbcmc_send_undo_info_commands(gb_main, _GBCMC_UNDOCOM_INFO_REDO);
817            default:
818                GB_export_error("GB_undo_info: unknown undo type specified");
819                return NULp;
820        }
821    }
822    switch (type) {
823        case GB_UNDO_UNDO:
824            return g_b_undo_info(Main, Main->undo->u);
825        case GB_UNDO_REDO:
826            return g_b_undo_info(Main, Main->undo->r);
827        default:
828            GB_export_error("GB_undo_info: unknown undo type specified");
829            return NULp;
830    }
831}
832
833GB_ERROR GB_set_undo_mem(GBDATA *gbd, long memsize) {
834    // set the maximum memory used for undoing
835
836    GB_MAIN_TYPE *Main = GB_MAIN(gbd);
837    if (memsize < _GBCMC_UNDOCOM_SET_MEM) {
838        return GB_export_errorf("Not enough UNDO memory specified: should be more than %i",
839                                _GBCMC_UNDOCOM_SET_MEM);
840    }
841    Main->undo->max_size_of_all_undos = memsize;
842    if (Main->is_client()) {
843        return gbcmc_send_undo_commands(gbd, (enum gb_undo_commands)memsize);
844    }
845    g_b_check_undo_size(Main);
846    return NULp;
847}
848
849
850#ifdef UNIT_TESTS
851#include <test_unit.h>
852#include <map>
853#include <ad_cb_prot.h>
854
855class cb_counter : virtual Noncopyable {
856    GBDATA *gbd;
857    int deletes, changes, creates;
858    bool do_trace;
859public:
860    static void count_swapped(GBDATA* gbd, cb_counter* counter, GB_CB_TYPE t ) {
861        // CB system cannot swap parameters, we need to wrap
862        // And yes... fixed parameters are added *in the middle*, not at the end.
863        counter->count(gbd, t);
864    }
865
866    cb_counter(GBDATA* gbd_) : gbd(gbd_), deletes(0), changes(0), creates(0), do_trace(false) {
867        GB_add_callback(gbd, GB_CB_ALL, makeDatabaseCallback(cb_counter::count_swapped, this));
868    }
869
870    ~cb_counter() {
871        // CB system cannot auto-destroy callbacks, nor are there CB handles
872        if (deletes == 0) { // (GB_delete destroys CBs I think)
873            GB_remove_callback(gbd, GB_CB_ALL, makeDatabaseCallback(cb_counter::count_swapped, this));
874            // above must be exact copy of GB_add_callback copy
875        }
876    }
877
878    void count(GBDATA*, GB_CB_TYPE t) {
879        if (t & GB_CB_DELETE) deletes ++;
880        if (t & GB_CB_SON_CREATED) creates ++;
881        if (t & GB_CB_CHANGED) changes ++;
882        if (do_trace) printf("counts: %p d=%i c=%i n=%i\n", gbd, deletes, changes, creates);
883    }
884
885    void trace(bool t) {
886        do_trace = t;
887    }
888
889    int get_deletes() {
890        int res = deletes;
891        deletes = 0;
892        return res;
893    }
894
895    int get_creates() {
896        int res = creates;
897        creates = 0;
898        return res;
899    }
900
901    int get_changes() {
902        int res = changes;
903        changes = 0;
904        return res;
905    }
906};
907
908
909
910void TEST_GB_undo__basic() {
911    GB_shell  shell;
912    // GB_ERROR err;
913
914    GBDATA   *main = GB_open("nosuch.arb", "c");
915    GB_begin_transaction(main);
916    cb_counter main_counter(main);
917    GB_commit_transaction(main);
918
919    GBDATA   *gbd;
920    cb_counter *gbd_counter;
921
922    GB_set_undo_mem(main, 10000);
923    GB_request_undo_type(main, GB_UNDO_UNDO);
924
925    // Notes:
926    // - CB_CHANGED == CB_SON_CREATED
927    //   Both are called on creating sons as well as just writing strings.
928    // - GB_SON_CREATED also called if "SON_DELETED"
929    // - It's possible to get CB_SON_CREATED on GBENTRY if the CB is
930    //   registered within the running transaction.
931    // - CBs are triggered at the end of the transaction.
932
933
934    // test undo create empty string entry
935    GB_begin_transaction(main);
936    gbd = GB_create(main, "test", GB_STRING);
937    gbd_counter = new cb_counter(gbd);
938    GB_commit_transaction(main);
939
940    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1);
941    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
942    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
943    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1);
944    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
945    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
946    // string initialises as empty
947    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "");
948    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                 );
949    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1); // BROKEN -- should be 0
950    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
951    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0); // BROKEN -- should be 1
952    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 0);
953    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 0);
954    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 1);
955    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)         );
956    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                 );
957    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1);
958    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
959    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
960    TEST_REJECT_NULL(  gbd = GB_find(main, "test", SEARCH_CHILD) );
961    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "");
962
963    // re-establish counter
964    GB_begin_transaction(main);
965    delete gbd_counter;
966    gbd_counter = new cb_counter(gbd);
967    GB_commit_transaction(main);
968
969    // test undo delete empty string
970    GB_begin_transaction(main);
971    GB_delete(gbd);
972    GB_commit_transaction(main);
973    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1); // BROKEN -- should be 0
974    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
975    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
976    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 0);
977    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 0);
978    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 1);
979    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
980    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
981    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1);
982    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
983    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
984    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "" );
985    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
986    TEST_EXPECT_EQUAL( main_counter.get_creates(), 1); // BROKEN -- should be 0
987    TEST_EXPECT_EQUAL( main_counter.get_changes(), 1);
988    TEST_EXPECT_EQUAL( main_counter.get_deletes(), 0);
989    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
990    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
991    TEST_REJECT_NULL( gbd = GB_find(main, "test", SEARCH_CHILD) );
992    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 0);
993    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 0);
994    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
995
996    // re-establish counter
997    GB_begin_transaction(main);
998    delete gbd_counter;
999    gbd_counter = new cb_counter(gbd);
1000    GB_commit_transaction(main);
1001
1002    // test undo write short string
1003    const char* str = "testtest9012345";
1004    GB_begin_transaction(main);
1005    GB_write_string(gbd, str);
1006    GB_commit_transaction(main);
1007    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1); // BROKEN?
1008    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
1009    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
1010    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str);
1011    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1012    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1); // BROKEN?
1013    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
1014    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
1015    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), "");
1016    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
1017    TEST_EXPECT_EQUAL( gbd_counter->get_creates(), 1); // BROKEN?
1018    TEST_EXPECT_EQUAL( gbd_counter->get_changes(), 1);
1019    TEST_EXPECT_EQUAL( gbd_counter->get_deletes(), 0);
1020    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str);
1021
1022    delete gbd_counter;
1023
1024    // test undo delete short string
1025    GB_begin_transaction(main);
1026    GB_delete(gbd);
1027    GB_commit_transaction(main);
1028    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1029    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1030
1031    //////////// THIS IS WHERE UNDO FAILS //////////////////
1032    TEST_EXPECT_EQUAL__BROKEN( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str, (char*)NULp);
1033    GB_close(main);
1034    return; // remainder will fail now
1035
1036
1037    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
1038    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1039    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1040    TEST_REJECT_NULL( gbd = GB_find(main, "test", SEARCH_CHILD) );
1041
1042    // test undo write "" and delete short string
1043    GB_begin_transaction(main);
1044    GB_write_string(gbd, str);
1045    GB_delete(gbd);
1046    GB_commit_transaction(main);
1047    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1048    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1049    TEST_EXPECT_EQUAL( GB_read_pntr(GB_find(main, "test", SEARCH_CHILD)), str);
1050    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_REDO)                           );
1051    TEST_EXPECT_NULL(  GB_find(main, "test", SEARCH_CHILD)                   );
1052    TEST_EXPECT_NULL(  GB_undo(main, GB_UNDO_UNDO)                           );
1053    TEST_REJECT_NULL( gbd = GB_find(main, "test", SEARCH_CHILD) );
1054
1055    //err = GB_write_string(gbd, "testtest9012345");
1056
1057    GB_close(main);
1058}
1059TEST_PUBLISH(TEST_GB_undo__basic);
1060
1061
1062#endif // UNIT_TESTS
Note: See TracBrowser for help on using the repository browser.