164 lines
3.1 KiB
Go
164 lines
3.1 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"log"
|
||
|
"os"
|
||
|
|
||
|
"crawshaw.io/sqlite"
|
||
|
"crawshaw.io/sqlite/sqlitex"
|
||
|
)
|
||
|
|
||
|
var ErrNoConn = errors.New("could not get a database connection")
|
||
|
|
||
|
type Store struct {
|
||
|
pool *sqlitex.Pool
|
||
|
}
|
||
|
|
||
|
func NewStore(filename string) (*Store, error) {
|
||
|
var needCreate bool
|
||
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
||
|
needCreate = true
|
||
|
}
|
||
|
// If the file exists, then assume it was created properly.
|
||
|
|
||
|
pool, err := sqlitex.Open(filename, 0, 10)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
store := &Store{
|
||
|
pool: pool,
|
||
|
}
|
||
|
if needCreate {
|
||
|
log.Println("creating schema")
|
||
|
err = store.createSchema()
|
||
|
if err != nil {
|
||
|
defer os.Remove(filename)
|
||
|
defer pool.Close()
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return store, nil
|
||
|
}
|
||
|
|
||
|
func NewMemoryStore() (*Store, error) {
|
||
|
pool, err := sqlitex.Open("file::memory:?mode=memory&cache=shared", 0, 10)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
store := &Store{
|
||
|
pool: pool,
|
||
|
}
|
||
|
|
||
|
err = store.createSchema()
|
||
|
if err != nil {
|
||
|
defer pool.Close()
|
||
|
return nil, err
|
||
|
}
|
||
|
return store, nil
|
||
|
}
|
||
|
|
||
|
func (s *Store) Close() error {
|
||
|
return s.pool.Close()
|
||
|
}
|
||
|
|
||
|
func (s *Store) createSchema() error {
|
||
|
conn := s.pool.Get(context.Background())
|
||
|
defer s.pool.Put(conn)
|
||
|
return sqlitex.ExecScript(conn, dbSchema)
|
||
|
}
|
||
|
|
||
|
const dbSchema = `
|
||
|
CREATE TABLE post (
|
||
|
id INTEGER PRIMARY KEY,
|
||
|
author TEXT NOT NULL,
|
||
|
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
title TEXT NOT NULL,
|
||
|
body TEXT NOT NULL
|
||
|
);
|
||
|
|
||
|
CREATE TRIGGER update_post_updated_at
|
||
|
AFTER update ON post
|
||
|
BEGIN
|
||
|
UPDATE post SET updated_at = CURRENT_TIMESTAMP
|
||
|
WHERE id = NEW.id;
|
||
|
END;
|
||
|
`
|
||
|
|
||
|
type Post struct {
|
||
|
Id int64
|
||
|
Author string
|
||
|
CreatedAt string
|
||
|
UpdatedAt string
|
||
|
Title string
|
||
|
Body string
|
||
|
}
|
||
|
|
||
|
type GetPostsResult struct {
|
||
|
Posts []Post
|
||
|
}
|
||
|
|
||
|
func (s *Store) GetPosts(ctx context.Context) (GetPostsResult, error) {
|
||
|
conn := s.pool.Get(ctx)
|
||
|
if conn == nil {
|
||
|
return GetPostsResult{}, ErrNoConn
|
||
|
}
|
||
|
defer s.pool.Put(conn)
|
||
|
|
||
|
const dbQuery = `
|
||
|
SELECT id, author, created_at, updated_at, title, body
|
||
|
FROM post
|
||
|
ORDER BY created_at DESC`
|
||
|
var result GetPostsResult
|
||
|
err := sqlitex.Exec(conn, dbQuery,
|
||
|
func(stmt *sqlite.Stmt) error {
|
||
|
result.Posts = append(result.Posts, Post{
|
||
|
Id: stmt.ColumnInt64(0),
|
||
|
Author: stmt.ColumnText(1),
|
||
|
CreatedAt: stmt.ColumnText(2),
|
||
|
UpdatedAt: stmt.ColumnText(3),
|
||
|
Title: stmt.ColumnText(4),
|
||
|
Body: stmt.ColumnText(5),
|
||
|
})
|
||
|
return nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
return GetPostsResult{}, err
|
||
|
}
|
||
|
|
||
|
return result, nil
|
||
|
}
|
||
|
|
||
|
type CreatePostCommand struct {
|
||
|
Title string
|
||
|
Author string
|
||
|
Body string
|
||
|
}
|
||
|
|
||
|
type CreatePostResult struct {
|
||
|
PostId int64
|
||
|
}
|
||
|
|
||
|
func (s *Store) CreatePost(ctx context.Context, cmd CreatePostCommand) (CreatePostResult, error) {
|
||
|
conn := s.pool.Get(ctx)
|
||
|
if conn == nil {
|
||
|
return CreatePostResult{}, ErrNoConn
|
||
|
}
|
||
|
defer s.pool.Put(conn)
|
||
|
|
||
|
const dbQuery = `
|
||
|
INSERT INTO post(title, author, body)
|
||
|
VALUES (?, ?, ?)
|
||
|
RETURNING id;`
|
||
|
var result CreatePostResult
|
||
|
err := sqlitex.Exec(conn, dbQuery,
|
||
|
func(stmt *sqlite.Stmt) error {
|
||
|
result.PostId = stmt.ColumnInt64(0)
|
||
|
return nil
|
||
|
}, cmd.Title, cmd.Author, cmd.Body)
|
||
|
return result, err
|
||
|
}
|