/* $Id$ */ /* * Copyright (c) 2016 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "ksql.h" #include "extern.h" enum stmt { STMT_BOOK, STMT_BOOK_AUTHORS, STMT_BOOK_DATA, STMT_BOOKS, STMT_BOOKS_COUNT, STMT_BOOKS_AUTHORS, STMT__MAX }; static const char *const stmts[STMT__MAX] = { /* STMT_BOOK */ "SELECT books.id AS id,title, series_index, series.name AS series_name, series.id AS series_id, \ strftime('%s', timestamp), strftime('%Y', pubdate), isbn,lccn,path,uuid,has_cover, \ languages.lang_code, publishers.name AS pubname FROM books \ LEFT OUTER JOIN books_languages_link ON books_languages_link.book = books.id \ LEFT OUTER JOIN languages ON languages.id = books_languages_link.lang_code \ LEFT OUTER JOIN data ON data.book = books.id \ LEFT OUTER JOIN books_series_link ON books.id = books_series_link.book \ LEFT OUTER JOIN series ON series.id = books_series_link.series \ LEFT OUTER JOIN books_publishers_link ON books.id = books_publishers_link.book \ LEFT OUTER JOIN publishers ON publishers.id = books_publishers_link.publisher \ WHERE books.id = ?", /* STMT_BOOK_AUTHORS */ "SELECT authors.id, authors.name, books_authors_link.book as book \ FROM authors, books_authors_link WHERE books_authors_link.author = authors.id \ AND books_authors_link.book = ?", /* STMT_BOOK_DATA */ "SELECT data.name, data.format, data.uncompressed_size \ FROM data WHERE data.book = ?", /* STMT_BOOKS */ "SELECT books.id AS id,title,series_index,name as series_name,series.id AS series_id \ FROM books LEFT OUTER JOIN books_series_link ON books.id = books_series_link.book \ LEFT OUTER JOIN series ON series.id = books_series_link.series ORDER BY id LIMIT ? OFFSET ?", /* STMT_BOOKS_COUNT */ "SELECT count(id) FROM books", /* STMT_BOOKS_AUTHORS */ "SELECT authors.id, authors.name, books_authors_link.book as book \ FROM authors, books_authors_link WHERE books_authors_link.author = authors.id \ AND books_authors_link.book IN ( SELECT id FROM books ORDER BY id LIMIT ? OFFSET ?)", }; /* * Linked list authors of book. */ struct bookauth { Author *a; BookAdv *b; struct bookauth *p; }; /* * Linked list book data. */ struct bookdata { BookData *d; BookFull *b; struct bookdata *p; }; static char * strornull(struct ksqlstmt *stmt, int col) { if (ksql_stmt_isnull(stmt, col)) return(NULL); return(kstrdup(ksql_stmt_str(stmt, col))); } static void db_series_unfill(Series *p) { if (NULL == p) return; free(p->name); } void db_series_free(Series *p) { db_series_unfill(p); free(p); } static void db_book_unfill(Book *p) { if (NULL == p) return; free(p->title); db_series_unfill(&p->s); } static void db_author_unfill(Author *p) { if (NULL == p) return; free(p->name); } static void db_book_data_unfill(BookData *p) { if (NULL == p) return; free(p->name); free(p->format); } void db_book_free(Book *p) { db_book_unfill(p); free(p); } void db_book_adv_free(BookAdv *p) { if (NULL == p) return; db_book_unfill(&p->b); for (size_t i = 0; i < p->asize; i++) { db_author_unfill(p->a[i]); } free(p); } void db_book_full_unfill(BookFull *p) { if (NULL == p) return; for (size_t i = 0; i < p->dsize; i++) { db_book_data_unfill(p->data[i]); } free(p->isbn); free(p->lccn); free(p->path); free(p->uuid); free(p->lang); free(p->publisher); } void db_book_full_free(BookFull *p) { if (NULL == p) return; db_book_unfill(&p->ba.b); for (size_t i = 0; i < p->ba.asize; i++) { db_author_unfill(p->ba.a[i]); } db_book_full_unfill(p); free(p); } static void db_book_fill(Book *book, struct ksqlstmt *stmt) { book->id = ksql_stmt_int(stmt, 0); book->title = kstrdup(ksql_stmt_str(stmt, 1)); if ( ksql_stmt_isnull(stmt, 4) ) { book->s.id = -1; } else { book->s_idx = ksql_stmt_double(stmt, 2); book->s.name = kstrdup(ksql_stmt_str(stmt, 3)); book->s.id = ksql_stmt_int(stmt, 4); } } int db_books_count(struct kreq *r) { int count; struct ksqlstmt *stmt; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOKS_COUNT], STMT_BOOKS_COUNT); if (KSQL_ROW != ksql_stmt_step(stmt)) { ksql_stmt_free(stmt); return(0); } count = ksql_stmt_int(stmt, 0); ksql_stmt_free(stmt); return(count); } static void db_assign_book_authors(struct bookauth *list) { struct bookauth *item = NULL; struct bookauth *p = NULL; p = list; while (NULL != p) { if (NULL != p->b && NULL == p->b->a && p->b->asize > 0) { p->b->a = kcalloc(p->b->asize, sizeof(Author)); p->b->asize = 0; // use as counter for insert } p->b->a[p->b->asize] = p->a;// add author p->b->asize++; item = p; p = p->p; free(item); } } int db_books_load(struct kreq *r, BookAdv **books, int limit, int offset) { if (limit < 0 || offset < 0) { return 0; } struct ksqlstmt *stmt; struct bookauth *p = NULL; struct bookauth *item = NULL; int i, count = 0; int64_t bid; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOKS], STMT_BOOKS); ksql_bind_int(stmt, 0, limit); ksql_bind_int(stmt, 1, offset); while (KSQL_ROW == ksql_stmt_step(stmt)) { books[count] = kcalloc(1, sizeof(BookAdv)); db_book_fill(&books[count]->b,stmt); books[count]->asize = 0; count++; } ksql_stmt_free(stmt); ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOKS_AUTHORS], STMT_BOOKS_AUTHORS); ksql_bind_int(stmt, 0, limit); ksql_bind_int(stmt, 1, offset); while (KSQL_ROW == ksql_stmt_step(stmt)) { // add author to list, link related book and increment asize item = kcalloc(1, sizeof(struct bookauth)); item->a = kcalloc(1, sizeof(Author)); item->a->id = ksql_stmt_int(stmt, 0); item->a->name = kstrdup(ksql_stmt_str(stmt, 1)); bid = ksql_stmt_int(stmt, 2); for (i = 0; i < count && NULL == item->b; i++) { if (books[i]->b.id == bid) { item->b = books[i]; item->b->asize++; } } item->p = p; p = item; } // assign authors to books db_assign_book_authors(p); ksql_stmt_free(stmt); return count; } static void db_book_full_authors(BookAdv *b, struct kreq *r, int64_t id) { struct ksqlstmt *stmt; struct bookauth *p = NULL; struct bookauth *item = NULL; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOK_AUTHORS], STMT_BOOK_AUTHORS); ksql_bind_int(stmt, 0, id); while (KSQL_ROW == ksql_stmt_step(stmt)) { item = kcalloc(1, sizeof(struct bookauth)); item->a = kcalloc(1, sizeof(Author)); item->a->id = ksql_stmt_int(stmt, 0); item->a->name = kstrdup(ksql_stmt_str(stmt, 1)); item->b = b; item->b->asize++; item->p = p; p = item; } // assign authors to book db_assign_book_authors(p); ksql_stmt_free(stmt); } static void db_book_full_data(BookFull *b, struct kreq *r, int64_t id) { struct ksqlstmt *stmt; struct bookdata *p = NULL; struct bookdata *item = NULL; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOK_DATA], STMT_BOOK_DATA); ksql_bind_int(stmt, 0, id); while (KSQL_ROW == ksql_stmt_step(stmt)) { item = kcalloc(1, sizeof(struct bookdata)); item->d = kcalloc(1, sizeof(BookData)); item->d->name = kstrdup(ksql_stmt_str(stmt, 0)); item->d->format = kstrdup(ksql_stmt_str(stmt, 1)); item->d->size = ksql_stmt_int(stmt, 2); item->b = b; item->b->dsize++; item->p = p; p = item; } ksql_stmt_free(stmt); while (NULL != p) { if (NULL != p->b && NULL == p->b->data && p->b->dsize > 0) { p->b->data = kcalloc(p->b->dsize, sizeof(BookData)); p->b->dsize = 0; // use as counter for insert } p->b->data[p->b->dsize] = p->d; p->b->dsize++; item = p; p = p->p; free(item); } } BookFull * db_book_load(struct kreq *r, int64_t id) { struct ksqlstmt *stmt; BookFull *book; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOK], STMT_BOOK); ksql_bind_int(stmt, 0, id); if (KSQL_ROW != ksql_stmt_step(stmt)) { ksql_stmt_free(stmt); return(NULL); } book = kcalloc(1, sizeof(BookFull)); db_book_fill(&book->ba.b, stmt); book->timestamp = ksql_stmt_int(stmt, 5); book->pubdate = ksql_stmt_int(stmt, 6); book->isbn = strornull(stmt, 7); book->lccn = strornull(stmt, 8); book->path = strornull(stmt, 9); book->uuid = strornull(stmt, 10); book->has_cover = ksql_stmt_int(stmt, 11); book->lang = strornull(stmt, 12); book->publisher = strornull(stmt, 13); ksql_stmt_free(stmt); db_book_full_authors(&book->ba, r, id); db_book_full_data(book, r, id); return book; } /* * Open the database and stash the resulting handle in the d */ int db_open(struct kreq *r, const char *file) { struct ksqlcfg cfg; struct ksql *sql; /* Configure normal database except with foreign keys. */ memset(&cfg, 0, sizeof(struct ksqlcfg)); cfg.flags = KSQL_EXIT_ON_ERR | KSQL_FOREIGN_KEYS | KSQL_SAFE_EXIT; cfg.err = ksqlitemsg; cfg.dberr = ksqlitedbmsg; /* Allocate database. */ if (NULL == (sql = ksql_alloc(&cfg))) return(0); ksql_open(sql, file); r->arg = sql; return(1); } /* * Close the database stashed in the kreq's argument. */ void db_close(struct kreq *r) { ksql_free(r->arg); r->arg = NULL; }