1
0
justguestbook/database/sqlite/database.go

320 lines
6.2 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) (*SQLiteDatabase, 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
}
// UpdateEntry
func (d *SQLiteDatabase) UpdateEntry(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) (int64, error) {
tx, err := d.db.Begin()
if err != nil {
return -1, err
}
defer tx.Rollback()
res, err := tx.Stmt(stmtDeleteEntry).Exec(entryID)
if err != nil {
return -1, err
}
c, err := res.RowsAffected()
if err != nil {
return -1, err
}
tx.Commit()
return c, 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) UpdateReply(reply *guestbook.Reply) error {
tx, err := d.db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
row := tx.Stmt(stmtUpdateReply).QueryRow(
reply.Created.Format(guestbook.DateFormat), 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()
res, err := tx.Stmt(stmtDeleteReply).Exec(entryID)
if err != nil {
return err
}
_, err = res.RowsAffected()
if 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)
}