Wiki
c-orm / FlowPhonebook
Flow Phonebook
This is the source for a phonebook application that contains error handling with Flow macros.
First, the header file phonebook.h
, containing the structs mapped to the
database, that is pre-processed by the cgen script:
#ifndef PHONEBOOK_H_ #define PHONEBOOK_H_ typedef struct name { int id; // primary key in the database char *name; // a person's name } name; typedef struct number { int id; // primary key in the database char *number; // a phone number int name_id; // the (foreign) key for the person's name } number; #endif
And now the meat of the application:
#include "isti.h" #include "isti_sqlite.h" #include "isti_str.h" #include "isti_array.h" #include "isti_db.h" ISTI_SQLITE_AS(sqlite) ISTI_STR_AS(str) ALIAS_ARRAY_AS(isti_chars_array, chars); #include "phonebook.h" #include "phonebook.corm.h" CORM_NAME_AS(cname) CORM_NUMBER_AS(cnumber) ALIAS_ARRAY_AS(corm_number_array, cnumbers); static int command(const char *cmd) { if (strcmp(cmd, "\\help")) { printf("unknown command %s\n", cmd); } else { printf("\nUsage:\n\ \\help\n\ Show this message\n\ name\n\ Search for numbers\n\ name number\n\ Add a number\n\ name -number\n\ Remove a number\n\ ^D\n\ Quit\n\n"); } return 0; } static int find_name(isti_db *db, const char *text, name** name) { corm_name_select *select = NULL; *name = NULL; STATUS; CHECK(cname.select(&select, db)); CHECK(select->name(select, "like", text)->_go_one(select, name)); EXIT; if (status == ISTI_ERR_NO_RESULT) status = ISTI_OK; // see NULL name if (select) status = select->_free(select, status); RETURN; } static int add_name(isti_db *db, const char *text, name** name) { corm_name_insert *insert = NULL; STATUS; ASSERT_MEM(*name = calloc(1, sizeof(**name))); ASSERT_MEM((*name)->name = str.char_dup(text)); CHECK(cname.insert(&insert, db)); CHECK(insert->id_key(insert)->_go_one(insert, *name)); EXIT; if (insert) status = insert->_free(insert, status); RETURN; } static int search(isti_db *db, const char *text) { name *name = NULL; corm_number_select *select = NULL; corm_number_array *numbers = NULL; STATUS; CHECK(find_name(db, text, &name)); if (name) { CHECK(cnumber.select(&select, db)); CHECK(select->name_id(select, "=", name->id)->_go_any(select, &numbers)); for (int i = 0; i < numbers->n.used; ++i) printf("%s: %s\n", name->name, numbers->number[i]->number); } else { printf(" No entry found for %s\n", text); } EXIT; if (name) status = cname.free(name, status); if (select) status = select->_free(select, status); if (numbers) status = cnumbers.free(numbers, status); RETURN; } static int add_number(isti_db *db, const char *text1, const char *text2) { name *name = NULL; number *number = NULL; corm_number_insert *insert = NULL; STATUS; CHECK(find_name(db, text1, &name)); if (!name) CHECK(add_name(db, text1, &name)); ASSERT_MEM(number = calloc(1, sizeof(*number))); number->name_id = name->id; ASSERT_MEM(number->number = str.char_dup(text2)); CHECK(cnumber.insert(&insert, db)); CHECK(insert->id_key(insert)->_go_one(insert, number)); EXIT; if (name) status = cname.free(name, status); if (number) status = cnumber.free(number, status); if (insert) status = insert->_free(insert, status); RETURN; } static int remove_number(isti_db *db, const char *text1, const char *text2) { name *name = NULL; corm_number_delete *delete = NULL; STATUS; CHECK(find_name(db, text1, &name)); if (name) { CHECK(cnumber.delete(&delete, db)); CHECK(delete->name_id(delete, "=", name->id)->number(delete, "like", text2+1)->_go(delete)); } EXIT; if (name) status = cname.free(name, status); if (delete) status = delete->_free(delete, status); RETURN; } static int user_loop(isti_db *db) { char *line = NULL; isti_chars_array *words = NULL; size_t size; STATUS; printf("\n\\help for help\n\n"); for (;;) { printf("> "); if (getline(&line, &size, stdin) < 0) break; CHECK(str.split(&words, line, " \n", 2)); switch(words->n.used) { case 0: break; case 1: if (words->c[0][0] == '\\') {CHECK(command(words->c[0]));} else {CHECK(search(db, words->c[0]));} break; case 2: if (words->c[1][0] == '-') {CHECK(remove_number(db, words->c[0], words->c[1]));} else {CHECK(add_number(db, words->c[0], words->c[1]));} break; } CHECK(chars.free(words, status)); } EXIT; if (line) free(line); RETURN; } static int build_database(isti_db *db) { isti_str *ddl = NULL; STATUS; CHECK(str.alloc(&ddl)); CHECK(str.joinn(ddl, " ", "create table if not exists", cname.NAME, "(", cname.c.ID, cname.c.ID_type, "primary key autoincrement,", cname.c.NAME, cname.c.NAME_type, ");", NULL)); CHECK(str.joinn(ddl, " ", "create table if not exists", cnumber.NUMBER, "(", cnumber.c.ID, cnumber.c.ID_type, "primary key autoincrement,", cnumber.c.NUMBER, cnumber.c.NUMBER_type, ",", cnumber.c.NAME_ID, cnumber.c.NAME_ID_type, "references", cname.NAME, "(", cname.c.ID, ")", "on delete cascade", ");", NULL)); CHECK(db->str(db, ddl->c)); EXIT; if (ddl) status = str.free(ddl, status); RETURN; } int main() { isti_db *db = NULL; STATUS; CHECK(sqlite.open(&db, "/tmp/phonebook", 10)); CHECK(build_database(db)); CHECK(user_loop(db)); EXIT; if (db) status = db->close(db, status); if (status) printf("error: %d\n", status); RETURN; }
Updated