1 /*
2 ** Copyright (c) 2010 Michael Dvorkin
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 */
12 
13 /*
14 ** Sqlite probably is a great backend alternative for pit. I just
15 ** figured I would have more fun writing it myself.
16 */
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include "pit.h"
21 
22 #define TABLE_INCREMENT 10
23 /*
24 ** Initialize the table by alloocating necessary memory chunks.
25 */
pit_table_initialize(int record_size,int flags)26 PTable pit_table_initialize(int record_size, int flags)
27 {
28     PTable pt = calloc(1, sizeof(Table));
29 
30     pt->flags             = flags;
31     pt->record_size       = record_size;
32     pt->number_of_slots   = TABLE_INCREMENT;
33     pt->number_of_records = 0;
34     pt->auto_increment    = 0;
35     pt->current           = 0;
36     pt->index_size        = TABLE_INCREMENT;
37     pt->slots             = calloc(TABLE_INCREMENT, pt->record_size);
38     pt->index             = calloc(TABLE_INCREMENT, sizeof(char *));
39 
40     return pt;
41 }
42 
43 /*
44 ** Return the address of next avaiable slot within pt->slots chunk.
45 */
table_available_slot(PTable pt)46 static char *table_available_slot(PTable pt)
47 {
48     return pt->slots + pt->number_of_records * pt->record_size;
49 }
50 
51 /*
52 ** Return the address of last stored record.
53 */
table_last_record(PTable pt)54 static char *table_last_record(PTable pt)
55 {
56     if (pt->number_of_records == 0) {
57         return pt->slots;
58     } else {
59         return table_available_slot(pt) - pt->record_size;
60     }
61 }
62 
63 /*
64 ** Return the address of next available pointer within pt->index chunk.
65 */
table_available_index(PTable pt)66 static char **table_available_index(PTable pt)
67 {
68     return HAS_ID(pt) ? (pt->index + pt->auto_increment) : NULL;
69 }
70 
71 /*
72 ** Return the address of last pointer within pt->index chunk.
73 */
table_last_index(PTable pt)74 static char **table_last_index(PTable pt)
75 {
76     if (HAS_ID(pt)) {
77         if (pt->auto_increment == 0) {
78             return pt->index;
79         } else {
80             return table_last_index(pt) - sizeof(char *);
81         }
82     }
83     return NULL;
84 }
85 
86 /*
87 ** Re-allocate the index and set newly added memory chunk to 0.
88 */
table_extend_index(PTable pt)89 static char **table_extend_index(PTable pt)
90 {
91     if (HAS_ID(pt)) {
92         pt->index_size += TABLE_INCREMENT;
93         pt->index = realloc(pt->index, pt->index_size * sizeof(char *));
94         memset(table_available_index(pt), 0, TABLE_INCREMENT * sizeof(char *));
95         return pt->index;
96     }
97 
98     return NULL;
99 }
100 
101 /*
102 ** Extend the table involves three steps. First, reallocate pt->slots
103 ** chunk adding TABLE_INCREMENT empty slots. Then extend the index.
104 ** Finally, adjust existing indices to point to reallocated record slots.
105 */
table_extend(PTable pt)106 static PTable table_extend(PTable pt)
107 {
108     register int i;
109     register char **pi;
110 
111     pt->number_of_slots += TABLE_INCREMENT;
112     /*
113     ** Re-allocate the records and set newly added memory chunk to 0.
114     */
115     pt->slots = realloc(pt->slots, pt->number_of_slots * pt->record_size);
116     memset(table_available_slot(pt), 0, TABLE_INCREMENT * pt->record_size);
117 
118     if (HAS_ID(pt)) {
119         /*
120         ** Extend the index and make its entries point to reallocatted records.
121         */
122         table_extend_index(pt);
123         for (i = 0, pi = pt->index;  i < pt->auto_increment;  i++, pi++) {
124             if (*pi != NULL) {
125                 *pi = pt->slots + i * pt->record_size;
126             }
127         }
128     }
129     return pt;
130 }
131 
132 /*
133 ** Find a record by id and return its address.
134 */
pit_table_find(PTable pt,int id)135 char *pit_table_find(PTable pt, int id)
136 {
137     if (HAS_ID(pt)) {
138         if (pt->number_of_records == 0 || id <= 0 || id > pt->auto_increment) {
139             return NULL;
140         } else {
141             return *(pt->index + id - 1);
142         }
143     }
144     return NULL;
145 }
146 
147 /*
148 ** Delete a record by its id. Return the address of deleted record or NULL
149 ** if the record was not found.
150 */
pit_table_delete(PTable pt,int id)151 char *pit_table_delete(PTable pt, int id)
152 {
153     register char *pr = (char *)pit_table_find(pt, id);
154 
155     if (pr) {
156         register char *last = table_last_record(pt);
157         /*
158         ** Overwrite current record by shifting over remaining slots.
159         */
160         if (pr != last) {
161             memmove(pr, pr + pt->record_size, last - pr);
162         }
163         /*
164         ** Set the slot occupied by the last record to zero.
165         */
166         memset(last, 0, pt->record_size);
167 
168         if (HAS_ID(pt)) {
169             register int i;
170             register char **pi;
171             /*
172             ** Set current record pointer to NULL, then update the rest of
173             ** the index to point to the shifted records.
174             */
175             pi = pt->index + id - 1;
176            *pi++ = NULL;
177 
178             for (i = id;  i < pt->auto_increment; i++) {
179                 *pi++ -= pt->record_size;
180             }
181         }
182         pt->number_of_records--;
183     }
184 
185     return pr;
186 }
187 
188 /*
189 ** Insert a record and return its address. The table gets extended
190 ** as necessary.
191 */
pit_table_insert(PTable pt,char * record)192 char *pit_table_insert(PTable pt, char *record)
193 {
194     register char *pr;
195     register time_t now = time(NULL);
196 
197     if (pt->number_of_records >= pt->number_of_slots) {
198         pt = table_extend(pt);
199     } else {
200         if (HAS_ID(pt) && pt->auto_increment >= pt->index_size) {
201             table_extend_index(pt);
202         }
203     }
204 
205     pr = table_available_slot(pt);
206     memmove(pr, record, pt->record_size);
207     pt->number_of_records++;
208 
209     if (HAS_ID(pt)) {
210         /*
211         ** Save current slot address in the index.
212         */
213         register char **pi = table_available_index(pt);
214         *pi = pr;
215         /*
216         ** Update record id if the table has primary key. The id must
217         ** be the first record field of type "unsigned long".
218         */
219         pt->auto_increment++;
220         *(int *)*pi = pt->auto_increment;
221     }
222     /*
223     ** Update created_at and/or updated_at which must be last one or
224     ** two record fields of type "time_t".
225     */
226     if (HAS_CREATED_AT(pt) || HAS_UPDATED_AT(pt)) {
227         *(time_t *)(pr + pt->record_size - sizeof(time_t)) = now;
228     }
229     if (HAS_CREATED_AT(pt) && HAS_UPDATED_AT(pt)) {
230         *(time_t *)(pr + pt->record_size - sizeof(time_t) * 2) = now;
231     }
232     return pr;
233 }
234 
235 /*
236 ** Find current record.
237 */
pit_table_current(PTable pt)238 char *pit_table_current(PTable pt)
239 {
240     return pit_table_find(pt, pt->current);
241 }
242 
243 /*
244 ** Set current record as indicated by the id, then find and return it.
245 */
pit_table_mark(PTable pt,int id)246 char *pit_table_mark(PTable pt, int id)
247 {
248     return pit_table_find(pt, pt->current = id); /* <-- Assign and pass as parameter */
249 }
250 
251 /*
252 ** Release pt->slots and pt->index memory chunks, then free the table itself.
253 */
pit_table_free(PTable pt)254 void pit_table_free(PTable pt)
255 {
256     if (pt) {
257         if (pt->index) {
258             free(pt->index);
259         }
260         if (pt->slots) {
261             free(pt->slots);
262         }
263         free(pt);
264     }
265 }
266 
267 /*
268 ** Save the contents of the table to file. The file handle should be
269 ** open with for writing.
270 */
pit_table_save(FILE * file,PTable pt)271 int pit_table_save(FILE *file, PTable pt)
272 {
273     register int written = 0;
274     /*
275     ** Save table header data: flags, record_size, number_of_slots,
276     ** number_of_records, auto_increment, current, and index_size.
277     */
278     written += fwrite(pt, sizeof(int), 7, file);
279     /*
280     ** Save the records. Note that we save the actual (not allocated) data.
281     */
282     written += fwrite(pt->slots, pt->record_size, pt->number_of_records, file);
283     return written;
284 }
285 
286 /*
287 ** Load the contents of the table from file. The file handle should be
288 ** open with for reading.
289 */
pit_table_load(FILE * file)290 PTable pit_table_load(FILE *file)
291 {
292     PTable pt;
293     register int read = 0;
294     register int i;
295     char *pr, **pi = NULL;
296 
297     pt = calloc(1, sizeof(Table));
298     /*
299     ** First read the header.
300     */
301     read += fread(pt, sizeof(int), 7, file);
302     /*
303     ** Now allocate slots and index based on the original number of slots.
304     */
305     /*** printf("Allocating %d slots\n", pt->number_of_slots); ***/
306     pt->slots = pr = calloc(pt->number_of_slots, pt->record_size);
307     if (HAS_ID(pt)) {
308         pt->index = pi = calloc(pt->index_size, sizeof(char *));
309     }
310     /*
311     ** Now read the records into the slots and rebuild the index if the
312     ** table has primary key.
313     */
314     read += fread(pt->slots, pt->record_size, pt->number_of_records, file);
315     if (HAS_ID(pt)) {
316         for(i = 1;  i <= pt->number_of_slots;  i++, pi++) {
317             if (*(int *)pr == i) {
318                 *pi = pr;
319                 pr += pt->record_size;
320             }
321         }
322     }
323     return pt;
324 }
325 
326 /* #define TEST */
327 #if defined(TEST)
328 
329 typedef struct {
330     int id;
331     int value;
332     char name[30];
333     time_t created_at;
334     time_t updated_at;
335 } Record;
336 
dump(Record * prec)337 void dump(Record *prec) {
338     if (prec) {
339         printf("(%08lX) id: %08d, value: %d, created_at: %ld, updated_at: %ld\n",
340         (unsigned long)prec, prec->id, prec->value, (time_t)prec->created_at, (time_t)prec->updated_at);
341     } else {
342         printf("(NULL)\n");
343     }
344 }
345 
dump_all(PTable pt)346 void dump_all(PTable pt) {
347     int i;
348     Record *prec;
349 
350     for(i = 1;  i <= pt->number_of_slots;  i++) {
351         prec = (Record *)pit_table_find(pt, i);
352         if (prec) {
353             dump(prec);
354         } else {
355             printf("%d not found\n", i);
356         }
357     }
358 }
359 
main()360 int main() {
361     PTable pt;
362 
363     Record rec, *prec;
364     int i, total = 3;
365 
366     pt = pit_table_initialize(sizeof(Record), TABLE_HAS_ID | TABLE_HAS_TIMESTAMPS);
367     for(i = 0;  i < total;  i++) {
368         rec.id = 0;
369         rec.value = 0x11223344 + i + 1;
370         strcpy(rec.name, "test");
371         rec.created_at = rec.updated_at = (time_t)0;
372 
373         prec = (Record *)pit_table_insert(pt, (char *)&rec);
374         dump(prec);
375     }
376     prec = (Record *)pit_table_find(pt, total - 1);
377     pit_table_mark(pt, prec->id);
378     printf("current: %d\n", pt->current);
379 
380     printf("Deleting %d\n", 1);
381     prec = (Record *)pit_table_delete(pt, 1);
382     printf("current: %d\n", pt->current);
383     dump_all(pt);
384 
385     FILE *file = fopen("/tmp/.pit", "w");
386     pit_table_save(file, pt);
387     fclose(file);
388 
389     file = fopen("/tmp/.pit", "r");
390     pt = pit_table_load(file);
391     dump_all(pt);
392     printf("current: %d\n", pt->current);
393     fclose(file);
394 
395     pit_table_free(pt);
396     printf("OK\n");
397     return 0;
398 }
399 #endif
400