/* $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_TAGS, STMT_BOOK_AUTHORS, STMT_BOOK_DATA, STMT_BOOKS, STMT_BOOKS_COUNT, STMT_BOOKS_TAGS, STMT_BOOKS_AUTHORS, STMT_AUTHORS, STMT_AUTHOR_BOOKS, STMT_AUTHORS_AUTHORS, STMT_AUTHOR, 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_TAGS */ "SELECT name FROM tags, books_tags_link WHERE tags.id = books_tags_link.tag AND books_tags_link.book = ?", /* 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_TAGS */ "SELECT name, books_tags_link.book as book FROM tags, books_tags_link \ WHERE tags.id = books_tags_link.tag AND books_tags_link.book \ IN ( SELECT id FROM books ORDER BY id LIMIT ? OFFSET ?)", /* 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 ?)", /* STMT_AUTHORS */ "SELECT authors.id, authors.name, count(book) as count FROM authors, books_authors_link \ WHERE authors.id = books_authors_link.author GROUP BY author \ ORDER BY authors.id LIMIT ? OFFSET ?", /* STMT_AUTHOR_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 \ LEFT OUTER JOIN books_authors_link ON books.id = books_authors_link.book \ WHERE books_authors_link.author = ? ORDER BY id", /* STMT_AUTHORS_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 books.id FROM books LEFT OUTER JOIN books_authors_link \ ON books.id = books_authors_link.book WHERE books_authors_link.author = ? ORDER BY books.id)", /* STMT_AUTHOR */ "SELECT name FROM authors WHERE id = ?", }; /* * Linked list authors of book. */ struct bookauth { Author *a; BookAdv *b; struct bookauth *p; }; /* * Linked list tags of book. */ struct booktag { char *tag; BookAdv *b; struct booktag *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); } void db_author_adv_free(AuthorAdv *p) { db_author_unfill(&p->a); free(p); } void db_author_free(Author *p) { db_author_unfill(p); free(p); } 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; size_t i; db_book_unfill(&p->b); for (i = 0; i < p->asize; i++) { db_author_unfill(p->a[i]); } for (i = 0; i < p->tsize; i++) { free(p->tags[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_tags(struct booktag *list) { struct booktag *item = NULL; struct booktag *p = NULL; p = list; while (NULL != p) { if (NULL != p->b && NULL == p->b->tags && p->b->tsize > 0) { p->b->tags = kcalloc(p->b->tsize, sizeof(char*)); p->b->tsize = 0; // use as counter for insert } p->b->tags[p->b->tsize] = p->tag; p->b->tsize++; item = p; p = p->p; free(item); } } 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); } } static struct booktag * db_load_books_tags(struct kreq *r, BookAdv **books, int count, int limit, int offset) { struct ksqlstmt *stmt; struct booktag *p = NULL; struct booktag *item = NULL; int i; int64_t bid; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOKS_TAGS], STMT_BOOKS_TAGS); ksql_bind_int(stmt, 0, limit); ksql_bind_int(stmt, 1, offset); while (KSQL_ROW == ksql_stmt_step(stmt)) { item = kcalloc(1, sizeof(struct booktag)); item->tag = kstrdup(ksql_stmt_str(stmt, 0)); bid = ksql_stmt_int(stmt, 1); for (i = 0; i < count && NULL == item->b; i++) { if (books[i]->b.id == bid) { item->b = books[i]; item->b->tsize++; } } item->p = p; p = item; } ksql_stmt_free(stmt); return p; } static struct bookauth * db_load_books_authors(BookAdv **books, int count, struct ksqlstmt *stmt) { struct bookauth *p = NULL; struct bookauth *item = NULL; int i; int64_t bid; 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; } return p; } 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_a; struct booktag *p_t; int count = 0; 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; books[count]->tsize = 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); p_a = db_load_books_authors(books, count, stmt); ksql_stmt_free(stmt); db_assign_book_authors(p_a); p_t = db_load_books_tags(r, books, count, limit, offset); db_assign_book_tags(p_t); return count; } static void db_book_full_tags(BookAdv *b, struct kreq *r, int64_t id) { struct ksqlstmt *stmt; struct booktag *p = NULL; struct booktag *item = NULL; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_BOOK_TAGS], STMT_BOOK_TAGS); ksql_bind_int(stmt, 0, id); while (KSQL_ROW == ksql_stmt_step(stmt)) { item = kcalloc(1, sizeof(struct booktag)); item->tag = kstrdup(ksql_stmt_str(stmt, 0)); item->b = b; item->b->tsize++; item->p = p; p = item; } ksql_stmt_free(stmt); db_assign_book_tags(p); } 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; } ksql_stmt_free(stmt); // assign authors to book db_assign_book_authors(p); } 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_tags(&book->ba, r, id); db_book_full_authors(&book->ba, r, id); db_book_full_data(book, r, id); return book; } AuthorFull * db_author_load(struct kreq *r, int64_t id) { struct ksqlstmt *stmt; struct bookauth *item = NULL; struct bookauth *p = NULL; int books = 0; BookAdv *b; AuthorFull *a = kcalloc(1, sizeof(AuthorFull)); a->a.id = id; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_AUTHOR_BOOKS], STMT_AUTHOR_BOOKS); ksql_bind_int(stmt, 0, id); while (KSQL_ROW == ksql_stmt_step(stmt)) { books++; b = kcalloc(1, sizeof(BookAdv)); b->asize = 0; b->tsize = 0; db_book_fill(&b->b, stmt); item = kcalloc(1, sizeof(struct bookauth)); item->b = b; item->a = &a->a; item->p = p; p = item; } ksql_stmt_free(stmt); a->bsize = books; if (books>0) { a->books = kcalloc(books, sizeof(BookAdv)); while (NULL != p && books > 0) { a->books[--books] = p->b; item = p; p = p->p; free(item); } ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_AUTHORS_AUTHORS], STMT_AUTHORS_AUTHORS); ksql_bind_int(stmt, 0, id); p = db_load_books_authors(a->books, a->bsize, stmt); ksql_stmt_free(stmt); db_assign_book_authors(p); // get name from 1st book for (size_t i=0; ibooks[0]->asize; i++) { if (a->a.id == a->books[0]->a[i]->id) a->a.name = a->books[0]->a[i]->name; } } else { // no books, get name ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_AUTHOR], STMT_AUTHOR); ksql_bind_int(stmt, 0, id); if (KSQL_ROW == ksql_stmt_step(stmt)) a->a.name = kstrdup(ksql_stmt_str(stmt, 0)); else a = NULL; ksql_stmt_free(stmt); } return a; } int db_authors_load(struct kreq *r, AuthorAdv **authors, int limit, int offset) { if (limit < 0 || offset < 0) { return 0; } struct ksqlstmt *stmt; int count = 0; ksql_stmt_alloc(r->arg, &stmt, stmts[STMT_AUTHORS], STMT_AUTHORS); ksql_bind_int(stmt, 0, limit); ksql_bind_int(stmt, 1, offset); while (KSQL_ROW == ksql_stmt_step(stmt)) { authors[count] = kcalloc(1, sizeof(AuthorAdv)); authors[count]->a.id = ksql_stmt_int(stmt, 0); authors[count]->a.name = kstrdup(ksql_stmt_str(stmt, 1)); authors[count]->count = ksql_stmt_int(stmt, 2); count++; } ksql_stmt_free(stmt); return count; } /* * 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; }