This repository has been archived on 2025-03-01. You can view files and clone it, but cannot push or open issues or pull requests.
trantor/lib/database/books.go
2017-05-21 11:02:35 +00:00

219 lines
5.6 KiB
Go

package database
import (
"strings"
"time"
)
// Book metadata
type Book struct {
ID string
Title string
Authors []string `sql:"authors" pg:",array"`
Contributor string
Publisher string
Description string
Tags []string `sql:"tags" pg:",array"`
Date string
Lang string
Isbn string
FileSize int
Cover bool
Active bool
UploadDate time.Time
Tsv string
}
// AddBook to the database
func (db *pgDB) AddBook(book Book) error {
emptyTime := time.Time{}
if book.UploadDate == emptyTime {
book.UploadDate = time.Now()
}
return db.sql.Create(&book)
}
// GetBooks matching query
func (db *pgDB) GetBooks(query string, length int, start int) (books []Book, num int, err error) {
return db.getBooks(true, query, length, start)
}
// TODO: func (db *pgDB) GetBooksIter() Iter {
// GetNewBooks returns a list of books in the incoming queue and the number of books
// in the queue
func (db *pgDB) GetNewBooks(query string, length int, start int) (books []Book, num int, err error) {
return db.getBooks(false, query, length, start)
}
func (db *pgDB) getBooks(active bool, query string, length int, start int) (books []Book, num int, err error) {
column := []string{}
columnParams := []interface{}{}
searchCondition := "active = "
if active {
searchCondition = "true"
} else {
searchCondition = "false"
}
searchParams := []interface{}{}
textQuery, columnQuerys, trigramQuerys := buildQuery(query)
for _, c := range columnQuerys {
searchCondition = searchCondition + " AND " + c.column + " = ?"
searchParams = append(searchParams, c.value)
}
for _, c := range trigramQuerys {
column = append(column, "word_similarity(?, "+c.column+")")
columnParams = append(columnParams, c.value)
searchCondition = searchCondition + " AND " + c.column + " %> ?"
searchParams = append(searchParams, c.value)
}
if textQuery != "" {
column = append(column, "ts_rank(tsv, to_tsquery(?))")
columnParams = append(columnParams, textQuery)
searchCondition = searchCondition + " AND to_tsquery(?) @@ tsv"
searchParams = append(searchParams, textQuery)
}
columnStr := "*"
order := "upload_date DESC"
if len(column) > 0 {
columnStr = "*, " + strings.Join(column, "+") + " AS rank"
order = "rank DESC, upload_date DESC"
}
num, err = db.sql.Model(&books).
ColumnExpr(columnStr, columnParams...).
Where(searchCondition, searchParams...).
Order(order).
Offset(start).
Limit(length).
SelectAndCountEstimate(100)
return books, num, err
}
// GetBookID returns a the book with the specified id
func (db *pgDB) GetBookID(id string) (Book, error) {
var book Book
err := db.sql.Model(&book).
Where("id = ?", id).
Select()
return book, err
}
// DeleteBook removes the book with id from the database
func (db *pgDB) DeleteBook(id string) error {
_, err := db.sql.Model(&Book{}).
Where("id = ?", id).
Delete()
return err
}
// UpdateBook metadata
func (db *pgDB) UpdateBook(id string, data map[string]interface{}) error {
setCondition := ""
params := []interface{}{}
for col, val := range data {
colValid := false
for _, name := range []string{"title", "authors", "contributor", "publisher",
"description", "tags", "date", "lang", "isbn"} {
if col == name {
colValid = true
break
}
}
if !colValid {
continue
}
if len(setCondition) != 0 {
setCondition += ", "
}
setCondition += col + " = ?"
params = append(params, val)
}
_, err := db.sql.Model(&Book{}).
Set(setCondition, params...).
Where("id = ?", id).
Update()
return err
}
// ActiveBook activates the book
func (db *pgDB) ActiveBook(id string) error {
uploadDate := time.Now()
_, err := db.sql.Model(&Book{}).
Set("active = true, upload_date = ? ", uploadDate).
Where("id = ?", id).
Update()
return err
}
// IsBookActive checks if the book is active
func (db *pgDB) IsBookActive(id string) bool {
var active []bool
err := db.sql.Model(&Book{}).
Column("active").
Where("id = ?", id).
Select(&active)
if err != nil || len(active) != 1 {
return false
}
return active[0]
}
type columnq struct {
column string
value string
}
func buildQuery(query string) (textQuery string, columnQuerys []columnq, trigramQuerys []columnq) {
// FIXME: does *Querys need initialization??
words := strings.Split(query, " ")
for _, w := range words {
if w == "" {
continue
}
tag := strings.SplitN(w, ":", 2)
if len(tag) > 1 && tag[1] != "" {
value := strings.Replace(tag[1], "%", "\\%", 0)
value = strings.Replace(value, "_", "\\_", 0)
switch tag[0] {
case "lang":
columnQuerys = append(columnQuerys, columnq{"lang", value})
case "isbn":
columnQuerys = append(columnQuerys, columnq{"isbn", value})
case "author":
// TODO how do we do trigram on arrays??
trigramQuerys = append(trigramQuerys, columnq{"array_to_string(authors, ' ')", value})
case "title":
trigramQuerys = append(trigramQuerys, columnq{"title", value})
case "contributor":
trigramQuerys = append(trigramQuerys, columnq{"contributor", value})
case "publisher":
trigramQuerys = append(trigramQuerys, columnq{"publisher", value})
case "subject":
trigramQuerys = append(trigramQuerys, columnq{"array_to_string(tags, ' ')", value})
case "tag":
trigramQuerys = append(trigramQuerys, columnq{"array_to_string(tags, ' ')", value})
default:
if len(textQuery) != 0 {
textQuery += " | "
}
textQuery += w
}
} else {
if len(textQuery) != 0 {
lastChar := textQuery[len(textQuery)-1:]
if w != "&" && w != "|" && lastChar != "&" && lastChar != "|" {
textQuery += " | "
} else {
textQuery += " "
}
}
textQuery += w
}
}
return
}