306 lines
6.0 KiB
Go
306 lines
6.0 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
_ "embed"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.arav.su/Arav/justguestbook/guestbook"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
//go:embed queries/schema.sql
|
|
querySchema string
|
|
|
|
//go:embed queries/entryGetAll.sql
|
|
queryGetAll string
|
|
//go:embed queries/entryCount.sql
|
|
queryCount string
|
|
|
|
//go:embed queries/entryNew.sql
|
|
queryNewEntry string
|
|
//go:embed queries/entryUpdate.sql
|
|
queryUpdateEntry string
|
|
//go:embed queries/entryDelete.sql
|
|
queryDeleteEntry string
|
|
//go:embed queries/replyNew.sql
|
|
queryNewReply string
|
|
//go:embed queries/replyUpdate.sql
|
|
queryUpdateReply string
|
|
//go:embed queries/replyDelete.sql
|
|
queryDeleteReply string
|
|
)
|
|
|
|
var (
|
|
stmtGetAll *sql.Stmt
|
|
stmtCount *sql.Stmt
|
|
stmtNewEntry *sql.Stmt
|
|
stmtUpdateEntry *sql.Stmt
|
|
stmtDeleteEntry *sql.Stmt
|
|
stmtNewReply *sql.Stmt
|
|
stmtUpdateReply *sql.Stmt
|
|
stmtDeleteReply *sql.Stmt
|
|
)
|
|
|
|
func initDBStatements(db *sql.DB) error {
|
|
db.Exec("PRAGMA foreign_keys = ON;")
|
|
|
|
_, err := db.Exec(querySchema)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to init schema")
|
|
}
|
|
|
|
stmtGetAll, err = db.Prepare(queryGetAll)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryGetAll")
|
|
}
|
|
|
|
stmtCount, err = db.Prepare(queryCount)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryCount")
|
|
}
|
|
|
|
stmtNewEntry, err = db.Prepare(queryNewEntry)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryNewEntry")
|
|
}
|
|
|
|
stmtUpdateEntry, err = db.Prepare(queryUpdateEntry)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryUpdateEntry")
|
|
}
|
|
|
|
stmtDeleteEntry, err = db.Prepare(queryDeleteEntry)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryDeleteEntry")
|
|
}
|
|
|
|
stmtNewReply, err = db.Prepare(queryNewReply)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryNewReply")
|
|
}
|
|
|
|
stmtUpdateReply, err = db.Prepare(queryUpdateReply)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryUpdateReply")
|
|
}
|
|
|
|
stmtDeleteReply, err = db.Prepare(queryDeleteReply)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to prepare queryDeleteReply")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type SQLiteDatabase struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
func New(filePath string) (guestbook.Guestbook, error) {
|
|
db, err := sql.Open("sqlite3", dsn(filePath))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := initDBStatements(db); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &SQLiteDatabase{db: db}, nil
|
|
}
|
|
|
|
func (d *SQLiteDatabase) Entries(page, pageSize int64) (entries []*guestbook.Entry, err error) {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
rows, err := tx.Stmt(stmtGetAll).Query(pageSize, (page-1)*pageSize)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var entry guestbook.Entry
|
|
var entry_created string
|
|
var reply_created sql.NullString
|
|
var reply_message sql.NullString
|
|
if err = rows.Scan(
|
|
&entry.ID, &entry_created, &entry.Name,
|
|
&entry.Message, &entry.Website,
|
|
&reply_created, &reply_message); err != nil {
|
|
return
|
|
}
|
|
|
|
entry.Created, err = time.Parse(guestbook.DateFormat, entry_created)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if reply_message.Valid /* reply_created is also valid if reply is */ {
|
|
date, err := time.Parse(guestbook.DateFormat, reply_created.String)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
entry.Reply = &guestbook.Reply{
|
|
ID: entry.ID,
|
|
Created: date,
|
|
Message: reply_message.String}
|
|
}
|
|
|
|
entries = append(entries, &entry)
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
return
|
|
}
|
|
|
|
// Count returns how much entries are in an `entry` table.
|
|
func (d *SQLiteDatabase) Count() (count int64, err error) {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
err = tx.Stmt(stmtCount).QueryRow().Scan(&count)
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
return count, nil
|
|
}
|
|
|
|
// NewEntry inserts a passed Entry struct and fills its ID field if successful.
|
|
func (d *SQLiteDatabase) NewEntry(entry *guestbook.Entry) error {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
r, err := tx.Stmt(stmtNewEntry).Exec(entry.Created.Format(guestbook.DateFormat), entry.Name, entry.Message,
|
|
entry.Website, entry.HideWebsite)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
entry.ID, err = r.LastInsertId()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *SQLiteDatabase) EditEntry(entry *guestbook.Entry) error {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
row := tx.Stmt(stmtUpdateEntry).QueryRow(entry.Name, entry.Message, entry.Website, entry.HideWebsite, entry.ID)
|
|
if row.Err() != nil {
|
|
return row.Err()
|
|
}
|
|
tx.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *SQLiteDatabase) DeleteEntry(entryID int64) error {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if _, err = tx.Stmt(stmtDeleteEntry).Exec(entryID); err != nil {
|
|
return err
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *SQLiteDatabase) NewReply(reply *guestbook.Reply) error {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
_, err = tx.Stmt(stmtNewReply).Exec(reply.ID, reply.Created.Format(guestbook.DateFormat), reply.Message)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *SQLiteDatabase) EditReply(reply *guestbook.Reply) error {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
row := tx.Stmt(stmtUpdateReply).QueryRow(reply.Message, reply.ID)
|
|
if row.Err() != nil {
|
|
return row.Err()
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *SQLiteDatabase) DeleteReply(entryID int64) error {
|
|
tx, err := d.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
if _, err = tx.Stmt(stmtDeleteReply).Exec(entryID); err != nil {
|
|
return err
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *SQLiteDatabase) Close() error {
|
|
stmtCount.Close()
|
|
stmtDeleteEntry.Close()
|
|
stmtDeleteReply.Close()
|
|
stmtGetAll.Close()
|
|
stmtNewEntry.Close()
|
|
stmtNewReply.Close()
|
|
stmtUpdateEntry.Close()
|
|
stmtUpdateReply.Close()
|
|
|
|
return d.db.Close()
|
|
}
|
|
|
|
func dsn(filePath string) string {
|
|
return fmt.Sprintf("file:%s?_journal=WAL&_mutex=full", filePath)
|
|
}
|