-
Notifications
You must be signed in to change notification settings - Fork 0
/
dbdata.c
3202 lines (2821 loc) · 89.3 KB
/
dbdata.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* $Id: $
* $Version: $
*
* Copyright (c) Tanel Tammet 2004,2005,2006,2007,2008,2009
* Copyright (c) Priit Järv 2009,2010,2011,2013,2014
*
* Contact: [email protected]
*
* This file is part of WhiteDB
*
* WhiteDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WhiteDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WhiteDB. If not, see <http://www.gnu.org/licenses/>.
*
*/
/** @file dbdata.c
* Procedures for handling actual data: strings, integers, records, etc
*
*/
/* ====== Includes =============== */
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
/* For Sleep() */
#include <windows.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <sys/timeb.h>
//#include <math.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _WIN32
#include "config-w32.h"
#else
#include "config-gcc.h"
#endif
#include "dballoc.h"
#include "dbdata.h"
#include "dbhash.h"
#include "dblog.h"
#include "dbindex.h"
#include "dbcompare.h"
#include "dblock.h"
/* ====== Private headers and defs ======== */
#ifdef _WIN32
//Thread-safe localtime_r appears not to be present on windows: emulate using win localtime, which is thread-safe.
static struct tm * localtime_r (const time_t *timer, struct tm *result);
#define sscanf sscanf_s // warning: needs extra buflen args for string etc params
#define snprintf sprintf_s
#endif
/* ======= Private protos ================ */
#ifdef USE_BACKLINKING
static gint remove_backlink_index_entries(void *db, gint *record,
gint value, gint depth);
static gint restore_backlink_index_entries(void *db, gint *record,
gint value, gint depth);
#endif
static int isleap(unsigned yr);
static unsigned months_to_days (unsigned month);
static long years_to_days (unsigned yr);
static long ymd_to_scalar (unsigned yr, unsigned mo, unsigned day);
static void scalar_to_ymd (long scalar, unsigned *yr, unsigned *mo, unsigned *day);
static gint free_field_encoffset(void* db,gint encoffset);
static gint find_create_longstr(void* db, char* data, char* extrastr, gint type, gint length);
#ifdef USE_CHILD_DB
static void *get_ptr_owner(void *db, gint encoded);
static int is_local_offset(void *db, gint offset);
#endif
#ifdef USE_RECPTR_BITMAP
static void recptr_setbit(void *db,void *ptr);
static void recptr_clearbit(void *db,void *ptr);
#endif
static gint show_data_error(void* db, char* errmsg);
static gint show_data_error_nr(void* db, char* errmsg, gint nr);
static gint show_data_error_double(void* db, char* errmsg, double nr);
static gint show_data_error_str(void* db, char* errmsg, char* str);
/* ====== Functions ============== */
/* ------------ full record handling ---------------- */
void* wg_create_record(void* db, wg_int length) {
void *rec = wg_create_raw_record(db, length);
/* Index all the created NULL fields to ensure index consistency */
if(rec) {
if(wg_index_add_rec(db, rec) < -1)
return NULL; /* index error */
}
return rec;
}
/*
* Creates the record and initializes the fields
* to NULL, but does not update indexes. This is useful in two
* scenarios: 1. fields are immediately initialized to something
* else, making indexing NULLs useless 2. record will have
* a RECORD_META_NOTDATA bit set, so the fields should not
* be indexed at all.
*
* In the first case, it is required that wg_set_new_field()
* is called on all the fields in the record. In the second case,
* the caller is responsible for setting the meta bits, however
* it is not mandatory to re-initialize all the fields.
*/
void* wg_create_raw_record(void* db, wg_int length) {
gint offset;
gint i;
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error_nr(db,"wrong database pointer given to wg_create_record with length ",length);
return 0;
}
if(length < 0) {
show_data_error_nr(db, "invalid record length:",length);
return 0;
}
#endif
#ifdef USE_DBLOG
/* Log first, modify shared memory next */
if(dbmemsegh(db)->logging.active) {
if(wg_log_create_record(db, length))
return 0;
}
#endif
offset=wg_alloc_gints(db,
&(dbmemsegh(db)->datarec_area_header),
length+RECORD_HEADER_GINTS);
if (!offset) {
show_data_error_nr(db,"cannot create a record of size ",length);
#ifdef USE_DBLOG
if(dbmemsegh(db)->logging.active) {
wg_log_encval(db, 0);
}
#endif
return 0;
}
/* Init header */
dbstore(db, offset+RECORD_META_POS*sizeof(gint), 0);
dbstore(db, offset+RECORD_BACKLINKS_POS*sizeof(gint), 0);
for(i=RECORD_HEADER_GINTS;i<length+RECORD_HEADER_GINTS;i++) {
dbstore(db,offset+(i*(sizeof(gint))),0);
}
#ifdef USE_DBLOG
/* Append the created offset to log */
if(dbmemsegh(db)->logging.active) {
if(wg_log_encval(db, offset))
return 0; /* journal error */
}
#endif
return offsettoptr(db,offset);
}
/** Delete record from database
* returns 0 on success
* returns -1 if the record is referenced by others and cannot be deleted.
* returns -2 on general error
* returns -3 on fatal error
*
* XXX: when USE_BACKLINKING is off, this function should be used
* with extreme care.
*/
gint wg_delete_record(void* db, void *rec) {
gint offset;
gint* dptr;
gint* dendptr;
gint data;
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error(db, "wrong database pointer given to wg_delete_record");
return -2;
}
#endif
#ifdef USE_BACKLINKING
if(*((gint *) rec + RECORD_BACKLINKS_POS))
return -1;
#endif
#ifdef USE_DBLOG
/* Log first, modify shared memory next */
if(dbmemsegh(db)->logging.active) {
if(wg_log_delete_record(db, ptrtooffset(db, rec)))
return -3;
}
#endif
/* Remove data from index */
if(!is_special_record(rec)) {
if(wg_index_del_rec(db, rec) < -1)
return -3; /* index error */
}
offset = ptrtooffset(db, rec);
#if defined(CHECK) && defined(USE_CHILD_DB)
/* Check if it's a local record */
if(!is_local_offset(db, offset)) {
show_data_error(db, "not deleting an external record");
return -2;
}
#endif
/* Loop over fields, freeing them */
dendptr = (gint *) (((char *) rec) + datarec_size_bytes(*((gint *)rec)));
for(dptr=(gint *)rec+RECORD_HEADER_GINTS; dptr<dendptr; dptr++) {
data = *dptr;
#ifdef USE_BACKLINKING
/* Is the field value a record pointer? If so, remove the backlink. */
#ifdef USE_CHILD_DB
if(wg_get_encoded_type(db, data) == WG_RECORDTYPE &&
is_local_offset(db, decode_datarec_offset(data))) {
#else
if(wg_get_encoded_type(db, data) == WG_RECORDTYPE) {
#endif
gint *child = (gint *) wg_decode_record(db, data);
gint *next_offset = child + RECORD_BACKLINKS_POS;
gcell *old = NULL;
while(*next_offset) {
old = (gcell *) offsettoptr(db, *next_offset);
if(old->car == offset) {
gint old_offset = *next_offset;
*next_offset = old->cdr; /* remove from list chain */
wg_free_listcell(db, old_offset); /* free storage */
goto recdel_backlink_removed;
}
next_offset = &(old->cdr);
}
show_data_error(db, "Corrupt backlink chain");
return -3; /* backlink error */
}
recdel_backlink_removed:
#endif
if(isptr(data)) free_field_encoffset(db,data);
}
/* Free the record storage */
wg_free_object(db,
&(dbmemsegh(db)->datarec_area_header),
offset);
return 0;
}
/** Get the first data record from the database
* Uses header meta bits to filter out special records
* (rules, system records etc)
*/
void* wg_get_first_record(void* db) {
void *res = wg_get_first_raw_record(db);
if(res && is_special_record(res))
return wg_get_next_record(db, res); /* find first data record */
return res;
}
/** Get the next data record from the database
* Uses header meta bits to filter out special records
*/
void* wg_get_next_record(void* db, void* record) {
void *res = record;
do {
res = wg_get_next_raw_record(db, res);
} while(res && is_special_record(res));
return res;
}
/** Get the first record from the database
*
*/
void* wg_get_first_raw_record(void* db) {
db_subarea_header* arrayadr;
gint firstoffset;
void* res;
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error(db,"wrong database pointer given to wg_get_first_record");
return NULL;
}
#endif
arrayadr=&((dbmemsegh(db)->datarec_area_header).subarea_array[0]);
firstoffset=((arrayadr[0]).alignedoffset); // do NOT skip initial "used" marker
//printf("arrayadr %x firstoffset %d \n",(uint)arrayadr,firstoffset);
res=wg_get_next_raw_record(db,offsettoptr(db,firstoffset));
return res;
}
/** Get the next record from the database
*
*/
void* wg_get_next_raw_record(void* db, void* record) {
gint curoffset;
gint head;
db_subarea_header* arrayadr;
gint last_subarea_index;
gint i;
gint found;
gint subareastart;
gint subareaend;
gint freemarker;
curoffset=ptrtooffset(db,record);
//printf("curroffset %d record %x\n",curoffset,(uint)record);
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error(db,"wrong database pointer given to wg_get_first_record");
return NULL;
}
head=dbfetch(db,curoffset);
if (isfreeobject(head)) {
show_data_error(db,"wrong record pointer (free) given to wg_get_next_record");
return NULL;
}
#endif
freemarker=0; //assume input pointer to used object
head=dbfetch(db,curoffset);
while(1) {
// increase offset to next memory block
curoffset=curoffset+(freemarker ? getfreeobjectsize(head) : getusedobjectsize(head));
head=dbfetch(db,curoffset);
//printf("new curoffset %d head %d isnormaluseobject %d isfreeobject %d \n",
// curoffset,head,isnormalusedobject(head),isfreeobject(head));
// check if found a normal used object
if (isnormalusedobject(head)) return offsettoptr(db,curoffset); //return ptr to normal used object
if (isfreeobject(head)) {
freemarker=1;
// loop start leads us to next object
} else {
// found a special object (dv or end marker)
freemarker=0;
if (dbfetch(db,curoffset+sizeof(gint))==SPECIALGINT1DV) {
// we have reached a dv object
continue; // loop start leads us to next object
} else {
// we have reached an end marker, have to find the next subarea
// first locate subarea for this offset
arrayadr=&((dbmemsegh(db)->datarec_area_header).subarea_array[0]);
last_subarea_index=(dbmemsegh(db)->datarec_area_header).last_subarea_index;
found=0;
for(i=0;(i<=last_subarea_index)&&(i<SUBAREA_ARRAY_SIZE);i++) {
subareastart=((arrayadr[i]).alignedoffset);
subareaend=((arrayadr[i]).offset)+((arrayadr[i]).size);
if (curoffset>=subareastart && curoffset<subareaend) {
found=1;
break;
}
}
if (!found) {
show_data_error(db,"wrong record pointer (out of area) given to wg_get_next_record");
return NULL;
}
// take next subarea, while possible
i++;
if (i>last_subarea_index || i>=SUBAREA_ARRAY_SIZE) {
//printf("next used object not found: i %d curoffset %d \n",i,curoffset);
return NULL;
}
//printf("taking next subarea i %d\n",i);
curoffset=((arrayadr[i]).alignedoffset); // curoffset is now the special start marker
head=dbfetch(db,curoffset);
// loop start will lead us to next object from special marker
}
}
}
}
/** Get the first data parent pointer from the backlink chain.
*
*/
void *wg_get_first_parent(void* db, void *record) {
#ifdef USE_BACKLINKING
gint backlink_list;
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error(db,"invalid database pointer given to wg_get_first_parent");
return NULL;
}
#endif
backlink_list = *((gint *) record + RECORD_BACKLINKS_POS);
if(backlink_list) {
gcell *cell = (gcell *) offsettoptr(db, backlink_list);
return (void *) offsettoptr(db, cell->car);
}
#endif /* USE_BACKLINKING */
return NULL; /* no parents or backlinking not enabled */
}
/** Get the next parent pointer from the backlink chain.
*
*/
void *wg_get_next_parent(void* db, void* record, void *parent) {
#ifdef USE_BACKLINKING
gint backlink_list;
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error(db,"invalid database pointer given to wg_get_next_parent");
return NULL;
}
#endif
backlink_list = *((gint *) record + RECORD_BACKLINKS_POS);
if(backlink_list) {
gcell *next = (gcell *) offsettoptr(db, backlink_list);
while(next->cdr) {
void *pp = (void *) offsettoptr(db, next->car);
next = (gcell *) offsettoptr(db, next->cdr);
if(pp == parent && next->car) {
return (void *) offsettoptr(db, next->car);
}
}
}
#endif /* USE_BACKLINKING */
return NULL; /* no more parents or backlinking not enabled */
}
/* ------------ backlink chain recursive functions ------------------- */
#ifdef USE_BACKLINKING
/** Remove index entries in backlink chain recursively.
* Needed for index maintenance when records are compared by their
* contens, as change in contents also changes the value of the entire
* record and thus affects it's placement in the index.
* Returns 0 for success
* Returns -1 in case of errors.
*/
static gint remove_backlink_index_entries(void *db, gint *record,
gint value, gint depth) {
gint col, length, err = 0;
db_memsegment_header *dbh = dbmemsegh(db);
if(!is_special_record(record)) {
/* Find all fields in the record that match value (which is actually
* a reference to a child record in encoded form) and remove it from
* indexes. It will be recreated in the indexes by wg_set_field() later.
*/
length = getusedobjectwantedgintsnr(*record) - RECORD_HEADER_GINTS;
if(length > MAX_INDEXED_FIELDNR)
length = MAX_INDEXED_FIELDNR + 1;
for(col=0; col<length; col++) {
if(*(record + RECORD_HEADER_GINTS + col) == value) {
/* Changed value is always a WG_RECORDDTYPE field, therefore
* we don't need to deal with index templates here
* (record links are not allowed in templates).
*/
if(dbh->index_control_area_header.index_table[col]) {
if(wg_index_del_field(db, record, col) < -1)
return -1;
}
}
}
}
/* If recursive depth is not exchausted, continue with the parents
* of this record.
*/
if(depth > 0) {
gint backlink_list = *(record + RECORD_BACKLINKS_POS);
if(backlink_list) {
gcell *next = (gcell *) offsettoptr(db, backlink_list);
for(;;) {
err = remove_backlink_index_entries(db,
(gint *) offsettoptr(db, next->car),
wg_encode_record(db, record), depth-1);
if(err)
return err;
if(!next->cdr)
break;
next = (gcell *) offsettoptr(db, next->cdr);
}
}
}
return 0;
}
/** Add index entries in backlink chain recursively.
* Called after doing remove_backling_index_entries() and updating
* data in the record that originated the call. This recreates the
* entries in the indexes for all the records that were affected.
* Returns 0 for success
* Returns -1 in case of errors.
*/
static gint restore_backlink_index_entries(void *db, gint *record,
gint value, gint depth) {
gint col, length, err = 0;
db_memsegment_header *dbh = dbmemsegh(db);
if(!is_special_record(record)) {
/* Find all fields in the record that match value (which is actually
* a reference to a child record in encoded form) and add it back to
* indexes.
*/
length = getusedobjectwantedgintsnr(*record) - RECORD_HEADER_GINTS;
if(length > MAX_INDEXED_FIELDNR)
length = MAX_INDEXED_FIELDNR + 1;
for(col=0; col<length; col++) {
if(*(record + RECORD_HEADER_GINTS + col) == value) {
if(dbh->index_control_area_header.index_table[col]) {
if(wg_index_add_field(db, record, col) < -1)
return -1;
}
}
}
}
/* Continue to the parents until depth==0 */
if(depth > 0) {
gint backlink_list = *(record + RECORD_BACKLINKS_POS);
if(backlink_list) {
gcell *next = (gcell *) offsettoptr(db, backlink_list);
for(;;) {
err = restore_backlink_index_entries(db,
(gint *) offsettoptr(db, next->car),
wg_encode_record(db, record), depth-1);
if(err)
return err;
if(!next->cdr)
break;
next = (gcell *) offsettoptr(db, next->cdr);
}
}
}
return 0;
}
#endif
/* ------------ field handling: data storage and fetching ---------------- */
wg_int wg_get_record_len(void* db, void* record) {
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error(db,"wrong database pointer given to wg_get_record_len");
return -1;
}
#endif
return ((gint)(getusedobjectwantedgintsnr(*((gint*)record))))-RECORD_HEADER_GINTS;
}
wg_int* wg_get_record_dataarray(void* db, void* record) {
#ifdef CHECK
if (!dbcheck(db)) {
show_data_error(db,"wrong database pointer given to wg_get_record_dataarray");
return NULL;
}
#endif
return (((gint*)record)+RECORD_HEADER_GINTS);
}
/** Update contents of one field
* returns 0 if successful
* returns -1 if invalid db pointer passed (by recordcheck macro)
* returns -2 if invalid record passed (by recordcheck macro)
* returns -3 for fatal index error
* returns -4 for backlink-related error
* returns -5 for invalid external data
* returns -6 for journal error
*/
wg_int wg_set_field(void* db, void* record, wg_int fieldnr, wg_int data) {
gint* fieldadr;
gint fielddata;
gint* strptr;
#ifdef USE_BACKLINKING
gint backlink_list; /** start of backlinks for this record */
gint rec_enc = WG_ILLEGAL; /** this record as encoded value. */
#endif
db_memsegment_header *dbh = dbmemsegh(db);
#ifdef USE_CHILD_DB
void *offset_owner = dbmemseg(db);
#endif
#ifdef CHECK
recordcheck(db,record,fieldnr,"wg_set_field");
#endif
#ifdef USE_DBLOG
/* Do not proceed before we've logged the operation */
if(dbh->logging.active) {
if(wg_log_set_field(db,record,fieldnr,data))
return -6; /* journal error, cannot write */
}
#endif
/* Read the old encoded value */
fieldadr=((gint*)record)+RECORD_HEADER_GINTS+fieldnr;
fielddata=*fieldadr;
/* Update index(es) while the old value is still in the db */
#ifdef USE_INDEX_TEMPLATE
if(!is_special_record(record) && fieldnr<=MAX_INDEXED_FIELDNR &&\
(dbh->index_control_area_header.index_table[fieldnr] ||\
dbh->index_control_area_header.index_template_table[fieldnr])) {
#else
if(!is_special_record(record) && fieldnr<=MAX_INDEXED_FIELDNR &&\
dbh->index_control_area_header.index_table[fieldnr]) {
#endif
if(wg_index_del_field(db, record, fieldnr) < -1)
return -3; /* index error */
}
/* If there are backlinks, go up the chain and remove the reference
* to this record from all indexes (updating a field in the record
* causes the value of the record to change). Note that we only go
* as far as the recursive comparison depth - records higher in the
* hierarchy are not affected.
*/
#if defined(USE_BACKLINKING) && (WG_COMPARE_REC_DEPTH > 0)
backlink_list = *((gint *) record + RECORD_BACKLINKS_POS);
if(backlink_list) {
gint err;
gcell *next = (gcell *) offsettoptr(db, backlink_list);
rec_enc = wg_encode_record(db, record);
for(;;) {
err = remove_backlink_index_entries(db,
(gint *) offsettoptr(db, next->car),
rec_enc, WG_COMPARE_REC_DEPTH-1);
if(err) {
return -4; /* override the error code, for now. */
}
if(!next->cdr)
break;
next = (gcell *) offsettoptr(db, next->cdr);
}
}
#endif
#ifdef USE_CHILD_DB
/* Get the offset owner */
if(isptr(data)) {
offset_owner = get_ptr_owner(db, data);
if(!offset_owner) {
show_data_error(db, "External reference not recognized");
return -5;
}
}
#endif
#ifdef USE_BACKLINKING
/* Is the old field value a record pointer? If so, remove the backlink.
* XXX: this can be optimized to use a custom macro instead of
* wg_get_encoded_type().
*/
#ifdef USE_CHILD_DB
/* Only touch local records */
if(wg_get_encoded_type(db, fielddata) == WG_RECORDTYPE &&
offset_owner == dbmemseg(db)) {
#else
if(wg_get_encoded_type(db, fielddata) == WG_RECORDTYPE) {
#endif
gint *rec = (gint *) wg_decode_record(db, fielddata);
gint *next_offset = rec + RECORD_BACKLINKS_POS;
gint parent_offset = ptrtooffset(db, record);
gcell *old = NULL;
while(*next_offset) {
old = (gcell *) offsettoptr(db, *next_offset);
if(old->car == parent_offset) {
gint old_offset = *next_offset;
*next_offset = old->cdr; /* remove from list chain */
wg_free_listcell(db, old_offset); /* free storage */
goto setfld_backlink_removed;
}
next_offset = &(old->cdr);
}
show_data_error(db, "Corrupt backlink chain");
return -4; /* backlink error */
}
setfld_backlink_removed:
#endif
//printf("wg_set_field adr %d offset %d\n",fieldadr,ptrtooffset(db,fieldadr));
if (isptr(fielddata)) {
//printf("wg_set_field freeing old data\n");
free_field_encoffset(db,fielddata);
}
(*fieldadr)=data; // store data to field
#ifdef USE_CHILD_DB
if (islongstr(data) && offset_owner == dbmemseg(db)) {
#else
if (islongstr(data)) {
#endif
// increase data refcount for longstr-s
strptr = (gint *) offsettoptr(db,decode_longstr_offset(data));
++(*(strptr+LONGSTR_REFCOUNT_POS));
}
/* Update index after new value is written */
#ifdef USE_INDEX_TEMPLATE
if(!is_special_record(record) && fieldnr<=MAX_INDEXED_FIELDNR &&\
(dbh->index_control_area_header.index_table[fieldnr] ||\
dbh->index_control_area_header.index_template_table[fieldnr])) {
#else
if(!is_special_record(record) && fieldnr<=MAX_INDEXED_FIELDNR &&\
dbh->index_control_area_header.index_table[fieldnr]) {
#endif
if(wg_index_add_field(db, record, fieldnr) < -1)
return -3;
}
#ifdef USE_BACKLINKING
/* Is the new field value a record pointer? If so, add a backlink */
#ifdef USE_CHILD_DB
if(wg_get_encoded_type(db, data) == WG_RECORDTYPE &&
offset_owner == dbmemseg(db)) {
#else
if(wg_get_encoded_type(db, data) == WG_RECORDTYPE) {
#endif
gint *rec = (gint *) wg_decode_record(db, data);
gint *next_offset = rec + RECORD_BACKLINKS_POS;
gint new_offset = wg_alloc_fixlen_object(db,
&(dbmemsegh(db)->listcell_area_header));
gcell *new_cell = (gcell *) offsettoptr(db, new_offset);
while(*next_offset)
next_offset = &(((gcell *) offsettoptr(db, *next_offset))->cdr);
new_cell->car = ptrtooffset(db, record);
new_cell->cdr = 0;
*next_offset = new_offset;
}
#endif
#if defined(USE_BACKLINKING) && (WG_COMPARE_REC_DEPTH > 0)
/* Create new entries in indexes in all referring records */
if(backlink_list) {
gint err;
gcell *next = (gcell *) offsettoptr(db, backlink_list);
for(;;) {
err = restore_backlink_index_entries(db,
(gint *) offsettoptr(db, next->car),
rec_enc, WG_COMPARE_REC_DEPTH-1);
if(err) {
return -4;
}
if(!next->cdr)
break;
next = (gcell *) offsettoptr(db, next->cdr);
}
}
#endif
return 0;
}
/** Write contents of one field.
*
* Used to initialize fields in records that have been created with
* wg_create_raw_record().
*
* This function ignores the previous contents of the field. The
* rationale is that newly created fields do not have any meaningful
* content and this allows faster writing. It is up to the programmer
* to ensure that this function is not called on fields that already
* contain data.
*
* returns 0 if successful
* returns -1 if invalid db pointer passed
* returns -2 if invalid record or field passed
* returns -3 for fatal index error
* returns -4 for backlink-related error
* returns -5 for invalid external data
* returns -6 for journal error
*/
wg_int wg_set_new_field(void* db, void* record, wg_int fieldnr, wg_int data) {
gint* fieldadr;
gint* strptr;
#ifdef USE_BACKLINKING
gint backlink_list; /** start of backlinks for this record */
#endif
db_memsegment_header *dbh = dbmemsegh(db);
#ifdef USE_CHILD_DB
void *offset_owner = dbmemseg(db);
#endif
#ifdef CHECK
recordcheck(db,record,fieldnr,"wg_set_field");
#endif
#ifdef USE_DBLOG
/* Do not proceed before we've logged the operation */
if(dbh->logging.active) {
if(wg_log_set_field(db,record,fieldnr,data))
return -6; /* journal error, cannot write */
}
#endif
#ifdef USE_CHILD_DB
/* Get the offset owner */
if(isptr(data)) {
offset_owner = get_ptr_owner(db, data);
if(!offset_owner) {
show_data_error(db, "External reference not recognized");
return -5;
}
}
#endif
/* Write new value */
fieldadr=((gint*)record)+RECORD_HEADER_GINTS+fieldnr;
#ifdef CHECK
if(*fieldadr) {
show_data_error(db,"wg_set_new_field called on field that contains data");
return -2;
}
#endif
(*fieldadr)=data;
#ifdef USE_CHILD_DB
if (islongstr(data) && offset_owner == dbmemseg(db)) {
#else
if (islongstr(data)) {
#endif
// increase data refcount for longstr-s
strptr = (gint *) offsettoptr(db,decode_longstr_offset(data));
++(*(strptr+LONGSTR_REFCOUNT_POS));
}
/* Update index after new value is written */
#ifdef USE_INDEX_TEMPLATE
if(!is_special_record(record) && fieldnr<=MAX_INDEXED_FIELDNR &&\
(dbh->index_control_area_header.index_table[fieldnr] ||\
dbh->index_control_area_header.index_template_table[fieldnr])) {
#else
if(!is_special_record(record) && fieldnr<=MAX_INDEXED_FIELDNR &&\
dbh->index_control_area_header.index_table[fieldnr]) {
#endif
if(wg_index_add_field(db, record, fieldnr) < -1)
return -3;
}
#ifdef USE_BACKLINKING
/* Is the new field value a record pointer? If so, add a backlink */
#ifdef USE_CHILD_DB
if(wg_get_encoded_type(db, data) == WG_RECORDTYPE &&
offset_owner == dbmemseg(db)) {
#else
if(wg_get_encoded_type(db, data) == WG_RECORDTYPE) {
#endif
gint *rec = (gint *) wg_decode_record(db, data);
gint *next_offset = rec + RECORD_BACKLINKS_POS;
gint new_offset = wg_alloc_fixlen_object(db,
&(dbmemsegh(db)->listcell_area_header));
gcell *new_cell = (gcell *) offsettoptr(db, new_offset);
while(*next_offset)
next_offset = &(((gcell *) offsettoptr(db, *next_offset))->cdr);
new_cell->car = ptrtooffset(db, record);
new_cell->cdr = 0;
*next_offset = new_offset;
}
#endif
#if defined(USE_BACKLINKING) && (WG_COMPARE_REC_DEPTH > 0)
/* Create new entries in indexes in all referring records. Normal
* usage scenario would be that the record is also new, so that
* there are no backlinks, however this is not guaranteed.
*/
backlink_list = *((gint *) record + RECORD_BACKLINKS_POS);
if(backlink_list) {
gint err;
gcell *next = (gcell *) offsettoptr(db, backlink_list);
gint rec_enc = wg_encode_record(db, record);
for(;;) {
err = restore_backlink_index_entries(db,
(gint *) offsettoptr(db, next->car),
rec_enc, WG_COMPARE_REC_DEPTH-1);
if(err) {
return -4;
}
if(!next->cdr)
break;
next = (gcell *) offsettoptr(db, next->cdr);
}
}
#endif
return 0;
}
wg_int wg_set_int_field(void* db, void* record, wg_int fieldnr, gint data) {
gint fielddata;
fielddata=wg_encode_int(db,data);
//printf("wg_set_int_field data %d encoded %d\n",data,fielddata);
if (fielddata==WG_ILLEGAL) return -1;
return wg_set_field(db,record,fieldnr,fielddata);
}
wg_int wg_set_double_field(void* db, void* record, wg_int fieldnr, double data) {
gint fielddata;
fielddata=wg_encode_double(db,data);
if (fielddata==WG_ILLEGAL) return -1;
return wg_set_field(db,record,fieldnr,fielddata);
}
wg_int wg_set_str_field(void* db, void* record, wg_int fieldnr, char* data) {
gint fielddata;
fielddata=wg_encode_str(db,data,NULL);
if (fielddata==WG_ILLEGAL) return -1;
return wg_set_field(db,record,fieldnr,fielddata);
}
wg_int wg_set_rec_field(void* db, void* record, wg_int fieldnr, void* data) {
gint fielddata;
fielddata=wg_encode_record(db,data);
if (fielddata==WG_ILLEGAL) return -1;
return wg_set_field(db,record,fieldnr,fielddata);
}
/** Special case of updating a field value without a write-lock.
*
* Operates like wg_set_field but takes a previous value in a field
* as an additional argument for atomicity check.
*
* This special case does not require a write lock: however,
* you MUST still get a read-lock before the operation while
* doing parallel processing, otherwise the operation
* may corrupt the database: no complex write operations should
* happen in parallel to this operation.
*
* NB! the operation may still confuse other parallel readers, changing
* the value in a record they have just read. Use only if this is
* known to not create problems for other processes.
*
* It can be only used to write an immediate value (NULL, short int,
* char, date, time) to a non-indexed field containing also an
* immediate field: checks whether these conditions hold.
*
* The operation will fail if the original value passed has been
* overwritten before we manage to store a new value: this is
* a guaranteed atomic check and enables correct operation of
* several parallel wg_set_atomic_field operations
* changing the same field.
*
* returns 0 if successful
* returns -1 if wrong db pointer
* returns -2 if wrong fieldnr
* returns -10 if new value non-immediate
* returns -11 if old value non-immediate
* returns -12 if cannot fetch old data
* returns -13 if the field has an index
* returns -14 if logging is active
* returns -15 if the field value has been changed from old_data
* may return other field-setting error codes from wg_set_new_field
*
*/
wg_int wg_update_atomic_field(void* db, void* record, wg_int fieldnr, wg_int data, wg_int old_data) {