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 | |
---|
29 | void 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 | |
---|
65 | void 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 | |
---|
117 | GB_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 | |
---|
166 | void 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 | |
---|
192 | NOT4PERL 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 |
---|
267 | GBDATA *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 | |
---|
318 | static 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 | |
---|
324 | static 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 | |
---|
330 | static 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 | |
---|
344 | void 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 | |
---|
353 | static 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 | |
---|
367 | static 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 | |
---|
376 | static 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 | |
---|
385 | static 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 | |
---|
409 | static 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 | |
---|
417 | void 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 | |
---|
426 | static 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 | |
---|
495 | static 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 | |
---|
522 | static 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 | |
---|
526 | static 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 | |
---|
559 | static 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 | |
---|
582 | char *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 | |
---|
610 | char *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 | |
---|
636 | void 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 | |
---|
647 | void 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 | |
---|
674 | void 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 | |
---|
721 | GB_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 | |
---|
749 | GB_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 | |
---|
755 | GB_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 | |
---|
806 | char *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 | |
---|
833 | GB_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 | |
---|
855 | class cb_counter : virtual Noncopyable { |
---|
856 | GBDATA *gbd; |
---|
857 | int deletes, changes, creates; |
---|
858 | bool do_trace; |
---|
859 | public: |
---|
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 | |
---|
910 | void 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 | } |
---|
1059 | TEST_PUBLISH(TEST_GB_undo__basic); |
---|
1060 | |
---|
1061 | |
---|
1062 | #endif // UNIT_TESTS |
---|