Improve database usage

This commit is contained in:
Jan-Lukas Else 2021-05-29 13:32:00 +02:00
parent 0219a6302b
commit 29dba59574
15 changed files with 152 additions and 167 deletions

View File

@ -278,7 +278,7 @@ func apGetRemoteActor(iri string) (*asPerson, int, error) {
} }
func apGetAllInboxes(blog string) ([]string, error) { func apGetAllInboxes(blog string) ([]string, error) {
rows, err := appDbQuery("select distinct inbox from activitypub_followers where blog = @blog", sql.Named("blog", blog)) rows, err := appDb.query("select distinct inbox from activitypub_followers where blog = @blog", sql.Named("blog", blog))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -295,17 +295,17 @@ func apGetAllInboxes(blog string) ([]string, error) {
} }
func apAddFollower(blog, follower, inbox string) error { func apAddFollower(blog, follower, inbox string) error {
_, err := appDbExec("insert or replace into activitypub_followers (blog, follower, inbox) values (@blog, @follower, @inbox)", sql.Named("blog", blog), sql.Named("follower", follower), sql.Named("inbox", inbox)) _, err := appDb.exec("insert or replace into activitypub_followers (blog, follower, inbox) values (@blog, @follower, @inbox)", sql.Named("blog", blog), sql.Named("follower", follower), sql.Named("inbox", inbox))
return err return err
} }
func apRemoveFollower(blog, follower string) error { func apRemoveFollower(blog, follower string) error {
_, err := appDbExec("delete from activitypub_followers where blog = @blog and follower = @follower", sql.Named("blog", blog), sql.Named("follower", follower)) _, err := appDb.exec("delete from activitypub_followers where blog = @blog and follower = @follower", sql.Named("blog", blog), sql.Named("follower", follower))
return err return err
} }
func apRemoveInbox(inbox string) error { func apRemoveInbox(inbox string) error {
_, err := appDbExec("delete from activitypub_followers where inbox = @inbox", sql.Named("inbox", inbox)) _, err := appDb.exec("delete from activitypub_followers where inbox = @inbox", sql.Named("inbox", inbox))
return err return err
} }

View File

@ -67,7 +67,7 @@ func getBlogStats(blog string) (data map[string]interface{}, err error) {
Name, Posts, Chars, Words, WordsPerPost string Name, Posts, Chars, Words, WordsPerPost string
} }
// Count total posts // Count total posts
row, err := appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+"))", params...) row, err := appDb.queryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+"))", params...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -76,7 +76,7 @@ func getBlogStats(blog string) (data map[string]interface{}, err error) {
return nil, err return nil, err
} }
// Count posts per year // Count posts per year
rows, err := appDbQuery("select *, "+wordsPerPost+" from (select year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...) rows, err := appDb.query("select *, "+wordsPerPost+" from (select year, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' group by year order by year desc)", params...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,7 +90,7 @@ func getBlogStats(blog string) (data map[string]interface{}, err error) {
} }
} }
// Count posts without date // Count posts without date
row, err = appDbQueryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published = '')", params...) row, err = appDb.queryRow("select *, "+wordsPerPost+" from (select "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published = '')", params...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -102,7 +102,7 @@ func getBlogStats(blog string) (data map[string]interface{}, err error) {
months := map[string][]statsTableType{} months := map[string][]statsTableType{}
month := statsTableType{} month := statsTableType{}
for _, year := range years { for _, year := range years {
rows, err = appDbQuery("select *, "+wordsPerPost+" from (select month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and year = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...) rows, err = appDb.query("select *, "+wordsPerPost+" from (select month, "+postCount+", "+charCount+", "+wordCount+" from ("+query+") where published != '' and year = @year group by month order by month desc)", append(params, sql.Named("year", year.Name))...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -26,7 +26,7 @@ func serveComment(w http.ResponseWriter, r *http.Request) {
serveError(w, r, err.Error(), http.StatusBadRequest) serveError(w, r, err.Error(), http.StatusBadRequest)
return return
} }
row, err := appDbQueryRow("select id, target, name, website, comment from comments where id = @id", sql.Named("id", id)) row, err := appDb.queryRow("select id, target, name, website, comment from comments where id = @id", sql.Named("id", id))
if err != nil { if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError) serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
@ -66,7 +66,7 @@ func createComment(w http.ResponseWriter, r *http.Request) {
} }
website := strings.TrimSpace(strict.Sanitize(r.FormValue("website"))) website := strings.TrimSpace(strict.Sanitize(r.FormValue("website")))
// Insert // Insert
result, err := appDbExec("insert into comments (target, comment, name, website) values (@target, @comment, @name, @website)", sql.Named("target", target), sql.Named("comment", comment), sql.Named("name", name), sql.Named("website", website)) result, err := appDb.exec("insert into comments (target, comment, name, website) values (@target, @comment, @name, @website)", sql.Named("target", target), sql.Named("comment", comment), sql.Named("name", name), sql.Named("website", website))
if err != nil { if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError) serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
@ -117,7 +117,7 @@ func buildCommentsQuery(config *commentsRequestConfig) (query string, args []int
func getComments(config *commentsRequestConfig) ([]*comment, error) { func getComments(config *commentsRequestConfig) ([]*comment, error) {
comments := []*comment{} comments := []*comment{}
query, args := buildCommentsQuery(config) query, args := buildCommentsQuery(config)
rows, err := appDbQuery(query, args...) rows, err := appDb.query(query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -135,7 +135,7 @@ func getComments(config *commentsRequestConfig) ([]*comment, error) {
func countComments(config *commentsRequestConfig) (count int, err error) { func countComments(config *commentsRequestConfig) (count int, err error) {
query, params := buildCommentsQuery(config) query, params := buildCommentsQuery(config)
query = "select count(*) from (" + query + ")" query = "select count(*) from (" + query + ")"
row, err := appDbQueryRow(query, params...) row, err := appDb.queryRow(query, params...)
if err != nil { if err != nil {
return return
} }
@ -144,6 +144,6 @@ func countComments(config *commentsRequestConfig) (count int, err error) {
} }
func deleteComment(id int) error { func deleteComment(id int) error {
_, err := appDbExec("delete from comments where id = @id", sql.Named("id", id)) _, err := appDb.exec("delete from comments where id = @id", sql.Named("id", id))
return err return err
} }

View File

@ -2,21 +2,23 @@ package main
import ( import (
"database/sql" "database/sql"
"errors"
"log" "log"
"os" "os"
"sync"
sqlite "github.com/mattn/go-sqlite3" sqlite "github.com/mattn/go-sqlite3"
"github.com/schollz/sqlite3dump" "github.com/schollz/sqlite3dump"
) )
var ( var appDb *goblogDb
appDb *sql.DB
appDbWriteMutex = &sync.Mutex{} type goblogDb struct {
dbStatementCache = map[string]*sql.Stmt{} db *sql.DB
) statementCache map[string]*sql.Stmt
}
func initDatabase() (err error) { func initDatabase() (err error) {
// Setup db
sql.Register("goblog_db", &sqlite.SQLiteDriver{ sql.Register("goblog_db", &sqlite.SQLiteDriver{
ConnectHook: func(c *sqlite.SQLiteConn) error { ConnectHook: func(c *sqlite.SQLiteConn) error {
if err := c.RegisterFunc("tolocal", toLocalSafe, true); err != nil { if err := c.RegisterFunc("tolocal", toLocalSafe, true); err != nil {
@ -31,71 +33,87 @@ func initDatabase() (err error) {
return nil return nil
}, },
}) })
appDb, err = sql.Open("goblog_db", appConfig.Db.File+"?cache=shared&mode=rwc&_journal_mode=WAL") db, err := sql.Open("goblog_db", appConfig.Db.File+"?cache=shared&mode=rwc&_journal_mode=WAL")
if err != nil { if err != nil {
return err return err
} }
err = appDb.Ping() db.SetMaxOpenConns(1)
err = db.Ping()
if err != nil { if err != nil {
return err return err
} }
// Check available SQLite features
rows, err := db.Query("pragma compile_options")
if err != nil {
return err
}
cos := map[string]bool{}
var co string
for rows.Next() {
err = rows.Scan(&co)
if err != nil {
return err
}
cos[co] = true
}
if _, ok := cos["ENABLE_FTS5"]; !ok {
return errors.New("sqlite not compiled with FTS5")
}
// Migrate DB
err = migrateDb(db)
if err != nil {
return err
}
// Create appDB
appDb = &goblogDb{
db: db,
statementCache: map[string]*sql.Stmt{},
}
appDb.vacuum()
addShutdownFunc(func() { addShutdownFunc(func() {
_ = closeDb() _ = appDb.close()
log.Println("Closed database") log.Println("Closed database")
}) })
vacuumDb()
err = migrateDb()
if err != nil {
return err
}
if appConfig.Db.DumpFile != "" { if appConfig.Db.DumpFile != "" {
hourlyHooks = append(hourlyHooks, dumpDb) hourlyHooks = append(hourlyHooks, func() {
dumpDb() appDb.dump()
})
appDb.dump()
} }
return nil return nil
} }
func dumpDb() { func (db *goblogDb) dump() {
f, err := os.Create(appConfig.Db.DumpFile) f, err := os.Create(appConfig.Db.DumpFile)
if err != nil { if err != nil {
log.Println("Error while dump db:", err.Error()) log.Println("Error while dump db:", err.Error())
return return
} }
startWritingToDb() if err = sqlite3dump.DumpDB(db.db, f); err != nil {
defer finishWritingToDb()
if err = sqlite3dump.DumpDB(appDb, f); err != nil {
log.Println("Error while dump db:", err.Error()) log.Println("Error while dump db:", err.Error())
} }
} }
func startWritingToDb() { func (db *goblogDb) close() error {
appDbWriteMutex.Lock() db.vacuum()
return db.db.Close()
} }
func finishWritingToDb() { func (db *goblogDb) vacuum() {
appDbWriteMutex.Unlock() _, _ = db.exec("VACUUM")
} }
func closeDb() error { func (db *goblogDb) prepare(query string) (*sql.Stmt, error) {
vacuumDb()
return appDb.Close()
}
func vacuumDb() {
_, _ = appDbExec("VACUUM")
}
func prepareAppDbStatement(query string) (*sql.Stmt, error) {
stmt, err, _ := cacheGroup.Do(query, func() (interface{}, error) { stmt, err, _ := cacheGroup.Do(query, func() (interface{}, error) {
stmt, ok := dbStatementCache[query] stmt, ok := db.statementCache[query]
if ok && stmt != nil { if ok && stmt != nil {
return stmt, nil return stmt, nil
} }
stmt, err := appDb.Prepare(query) stmt, err := db.db.Prepare(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbStatementCache[query] = stmt db.statementCache[query] = stmt
return stmt, nil return stmt, nil
}) })
if err != nil { if err != nil {
@ -104,26 +122,29 @@ func prepareAppDbStatement(query string) (*sql.Stmt, error) {
return stmt.(*sql.Stmt), nil return stmt.(*sql.Stmt), nil
} }
func appDbExec(query string, args ...interface{}) (sql.Result, error) { func (db *goblogDb) exec(query string, args ...interface{}) (sql.Result, error) {
stmt, err := prepareAppDbStatement(query) stmt, err := db.prepare(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
startWritingToDb()
defer finishWritingToDb()
return stmt.Exec(args...) return stmt.Exec(args...)
} }
func appDbQuery(query string, args ...interface{}) (*sql.Rows, error) { func (db *goblogDb) execMulti(query string, args ...interface{}) (sql.Result, error) {
stmt, err := prepareAppDbStatement(query) // Can't prepare the statement
return db.db.Exec(query, args...)
}
func (db *goblogDb) query(query string, args ...interface{}) (*sql.Rows, error) {
stmt, err := db.prepare(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return stmt.Query(args...) return stmt.Query(args...)
} }
func appDbQueryRow(query string, args ...interface{}) (*sql.Row, error) { func (db *goblogDb) queryRow(query string, args ...interface{}) (*sql.Row, error) {
stmt, err := prepareAppDbStatement(query) stmt, err := db.prepare(query)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,9 +6,7 @@ import (
"github.com/lopezator/migrator" "github.com/lopezator/migrator"
) )
func migrateDb() error { func migrateDb(db *sql.DB) error {
startWritingToDb()
defer finishWritingToDb()
m, err := migrator.New( m, err := migrator.New(
migrator.Migrations( migrator.Migrations(
&migrator.Migration{ &migrator.Migration{
@ -171,8 +169,5 @@ func migrateDb() error {
if err != nil { if err != nil {
return err return err
} }
if err := m.Migrate(appDb); err != nil { return m.Migrate(db)
return err
}
return nil
} }

View File

@ -218,13 +218,13 @@ func indieAuthToken(w http.ResponseWriter, r *http.Request) {
} }
func (data *indieAuthData) saveAuthorization() (err error) { func (data *indieAuthData) saveAuthorization() (err error) {
_, err = appDbExec("insert into indieauthauth (time, code, client, redirect, scope) values (?, ?, ?, ?, ?)", data.time.Unix(), data.code, data.ClientID, data.RedirectURI, strings.Join(data.Scopes, " ")) _, err = appDb.exec("insert into indieauthauth (time, code, client, redirect, scope) values (?, ?, ?, ?, ?)", data.time.Unix(), data.code, data.ClientID, data.RedirectURI, strings.Join(data.Scopes, " "))
return return
} }
func (data *indieAuthData) verifyAuthorization() (valid bool, err error) { func (data *indieAuthData) verifyAuthorization() (valid bool, err error) {
// code valid for 600 seconds // code valid for 600 seconds
row, err := appDbQueryRow("select code, client, redirect, scope from indieauthauth where time >= ? and code = ? and client = ? and redirect = ?", time.Now().Unix()-600, data.code, data.ClientID, data.RedirectURI) row, err := appDb.queryRow("select code, client, redirect, scope from indieauthauth where time >= ? and code = ? and client = ? and redirect = ?", time.Now().Unix()-600, data.code, data.ClientID, data.RedirectURI)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -239,13 +239,13 @@ func (data *indieAuthData) verifyAuthorization() (valid bool, err error) {
data.Scopes = strings.Split(scope, " ") data.Scopes = strings.Split(scope, " ")
} }
valid = true valid = true
_, err = appDbExec("delete from indieauthauth where code = ? or time < ?", data.code, time.Now().Unix()-600) _, err = appDb.exec("delete from indieauthauth where code = ? or time < ?", data.code, time.Now().Unix()-600)
data.code = "" data.code = ""
return return
} }
func (data *indieAuthData) saveToken() (err error) { func (data *indieAuthData) saveToken() (err error) {
_, err = appDbExec("insert into indieauthtoken (time, token, client, scope) values (?, ?, ?, ?)", data.time.Unix(), data.token, data.ClientID, strings.Join(data.Scopes, " ")) _, err = appDb.exec("insert into indieauthtoken (time, token, client, scope) values (?, ?, ?, ?)", data.time.Unix(), data.token, data.ClientID, strings.Join(data.Scopes, " "))
return return
} }
@ -254,7 +254,7 @@ func verifyIndieAuthToken(token string) (data *indieAuthData, err error) {
data = &indieAuthData{ data = &indieAuthData{
Scopes: []string{}, Scopes: []string{},
} }
row, err := appDbQueryRow("select time, token, client, scope from indieauthtoken where token = @token", sql.Named("token", token)) row, err := appDb.queryRow("select time, token, client, scope from indieauthtoken where token = @token", sql.Named("token", token))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -275,6 +275,6 @@ func verifyIndieAuthToken(token string) (data *indieAuthData, err error) {
func revokeIndieAuthToken(token string) { func revokeIndieAuthToken(token string) {
if token != "" { if token != "" {
_, _ = appDbExec("delete from indieauthtoken where token=?", token) _, _ = appDb.exec("delete from indieauthtoken where token=?", token)
} }
} }

View File

@ -40,14 +40,14 @@ func sendNotification(text string) {
} }
func saveNotification(n *notification) error { func saveNotification(n *notification) error {
if _, err := appDbExec("insert into notifications (time, text) values (@time, @text)", sql.Named("time", n.Time), sql.Named("text", n.Text)); err != nil { if _, err := appDb.exec("insert into notifications (time, text) values (@time, @text)", sql.Named("time", n.Time), sql.Named("text", n.Text)); err != nil {
return err return err
} }
return nil return nil
} }
func deleteNotification(id int) error { func deleteNotification(id int) error {
_, err := appDbExec("delete from notifications where id = @id", sql.Named("id", id)) _, err := appDb.exec("delete from notifications where id = @id", sql.Named("id", id))
return err return err
} }
@ -68,7 +68,7 @@ func buildNotificationsQuery(config *notificationsRequestConfig) (query string,
func getNotifications(config *notificationsRequestConfig) ([]*notification, error) { func getNotifications(config *notificationsRequestConfig) ([]*notification, error) {
notifications := []*notification{} notifications := []*notification{}
query, args := buildNotificationsQuery(config) query, args := buildNotificationsQuery(config)
rows, err := appDbQuery(query, args...) rows, err := appDb.query(query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -86,7 +86,7 @@ func getNotifications(config *notificationsRequestConfig) ([]*notification, erro
func countNotifications(config *notificationsRequestConfig) (count int, err error) { func countNotifications(config *notificationsRequestConfig) (count int, err error) {
query, params := buildNotificationsQuery(config) query, params := buildNotificationsQuery(config)
query = "select count(*) from (" + query + ")" query = "select count(*) from (" + query + ")"
row, err := appDbQueryRow(query, params...) row, err := appDb.queryRow(query, params...)
if err != nil { if err != nil {
return return
} }

View File

@ -9,7 +9,7 @@ import (
func cachePersistently(key string, data []byte) error { func cachePersistently(key string, data []byte) error {
date, _ := toLocal(time.Now().String()) date, _ := toLocal(time.Now().String())
_, err := appDbExec("insert or replace into persistent_cache(key, data, date) values(@key, @data, @date)", sql.Named("key", key), sql.Named("data", data), sql.Named("date", date)) _, err := appDb.exec("insert or replace into persistent_cache(key, data, date) values(@key, @data, @date)", sql.Named("key", key), sql.Named("data", data), sql.Named("date", date))
return err return err
} }
@ -17,7 +17,7 @@ var persistentCacheGroup singleflight.Group
func retrievePersistentCache(key string) (data []byte, err error) { func retrievePersistentCache(key string) (data []byte, err error) {
d, err, _ := persistentCacheGroup.Do(key, func() (interface{}, error) { d, err, _ := persistentCacheGroup.Do(key, func() (interface{}, error) {
if row, err := appDbQueryRow("select data from persistent_cache where key = @key", sql.Named("key", key)); err == sql.ErrNoRows { if row, err := appDb.queryRow("select data from persistent_cache where key = @key", sql.Named("key", key)); err == sql.ErrNoRows {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
return nil, err return nil, err
@ -33,6 +33,6 @@ func retrievePersistentCache(key string) (data []byte, err error) {
} }
func clearPersistentCache(pattern string) error { func clearPersistentCache(pattern string) error {
_, err := appDbExec("delete from persistent_cache where key like @pattern", sql.Named("pattern", pattern)) _, err := appDb.exec("delete from persistent_cache where key like @pattern", sql.Named("pattern", pattern))
return err return err
} }

View File

@ -7,7 +7,7 @@ import (
func allPostAliases() ([]string, error) { func allPostAliases() ([]string, error) {
var aliases []string var aliases []string
rows, err := appDbQuery("select distinct value from post_parameters where parameter = 'aliases' and value != path") rows, err := appDb.query("select distinct value from post_parameters where parameter = 'aliases' and value != path")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -22,7 +22,7 @@ func allPostAliases() ([]string, error) {
} }
func servePostAlias(w http.ResponseWriter, r *http.Request) { func servePostAlias(w http.ResponseWriter, r *http.Request) {
row, err := appDbQueryRow("select path from post_parameters where parameter = 'aliases' and value = @alias", sql.Named("alias", r.URL.Path)) row, err := appDb.queryRow("select path from post_parameters where parameter = 'aliases' and value = @alias", sql.Named("alias", r.URL.Path))
if err != nil { if err != nil {
serveError(w, r, err.Error(), http.StatusInternalServerError) serveError(w, r, err.Error(), http.StatusInternalServerError)
return return

View File

@ -125,80 +125,49 @@ func (p *post) createOrReplace(o *postCreationOptions) error {
if err != nil { if err != nil {
return err return err
} }
startWritingToDb() // Check if path is already in use
// Create transaction if o.new || (p.Path != o.oldPath) {
tx, err := appDb.Begin() // Post is new or post path was changed
if err != nil { newPathExists := false
finishWritingToDb() row, err := appDb.queryRow("select exists(select 1 from posts where path = @path)", sql.Named("path", p.Path))
return err
}
if !o.new {
// Remove old post
path := p.Path
if o.oldPath != "" {
path = o.oldPath
}
_, err := tx.Exec("delete from posts where path = @path", sql.Named("path", path))
if err != nil { if err != nil {
_ = tx.Rollback()
finishWritingToDb()
return err return err
} }
} err = row.Scan(&newPathExists)
// Check if new path exists if err != nil {
postExists := func(path string) (bool, error) { return err
result := 0 }
row := tx.QueryRow("select exists(select 1 from posts where path = @path)", sql.Named("path", path)) if newPathExists {
if err = row.Scan(&result); err != nil { // New path already exists
return false, err return errors.New("post already exists at given path")
} }
return result == 1, nil
} }
if exists, err := postExists(p.Path); err != nil { // Build SQL
_ = tx.Rollback() var sqlBuilder strings.Builder
finishWritingToDb() var sqlArgs []interface{}
return err // Delete old post
} else if exists { if !o.new {
_ = tx.Rollback() sqlBuilder.WriteString("delete from posts where path = ?;")
finishWritingToDb() sqlArgs = append(sqlArgs, o.oldPath)
return errors.New("post already exists at given path")
}
// Create new post
_, err = tx.Exec(
`insert into posts (path, content, published, updated, blog, section, status)
values (@path, @content, @published, @updated, @blog, @section, @status)`,
sql.Named("path", p.Path), sql.Named("content", p.Content), sql.Named("published", p.Published),
sql.Named("updated", p.Updated), sql.Named("blog", p.Blog), sql.Named("section", p.Section),
sql.Named("status", p.Status))
if err != nil {
_ = tx.Rollback()
finishWritingToDb()
return err
}
// Create parameters
ppStmt, err := tx.Prepare("insert into post_parameters (path, parameter, value) values (@path, @parameter, @value)")
if err != nil {
_ = tx.Rollback()
finishWritingToDb()
return err
} }
// Insert new post
sqlBuilder.WriteString("insert into posts (path, content, published, updated, blog, section, status) values (?, ?, ?, ?, ?, ?, ?);")
sqlArgs = append(sqlArgs, p.Path, p.Content, p.Published, p.Updated, p.Blog, p.Section, p.Status)
// Insert post parameters
for param, value := range p.Parameters { for param, value := range p.Parameters {
for _, value := range value { for _, value := range value {
if value != "" { if value != "" {
_, err := ppStmt.Exec(sql.Named("path", p.Path), sql.Named("parameter", param), sql.Named("value", value)) sqlBuilder.WriteString("insert into post_parameters (path, parameter, value) values (?, ?, ?);")
if err != nil { sqlArgs = append(sqlArgs, p.Path, param, value)
_ = tx.Rollback()
finishWritingToDb()
return err
}
} }
} }
} }
if tx.Commit() != nil { // Execute
finishWritingToDb() _, err = appDb.execMulti(sqlBuilder.String(), sqlArgs...)
if err != nil {
return err return err
} }
finishWritingToDb() // Update FTS index, trigger hooks and reload router
rebuildFTSIndex() rebuildFTSIndex()
if p.Status == statusPublished { if p.Status == statusPublished {
if o.new || o.oldStatus == statusDraft { if o.new || o.oldStatus == statusDraft {
@ -218,7 +187,7 @@ func deletePost(path string) error {
if err != nil { if err != nil {
return err return err
} }
_, err = appDbExec("delete from posts where path = @path", sql.Named("path", p.Path)) _, err = appDb.exec("delete from posts where path = @path", sql.Named("path", p.Path))
if err != nil { if err != nil {
return err return err
} }
@ -228,7 +197,7 @@ func deletePost(path string) error {
} }
func rebuildFTSIndex() { func rebuildFTSIndex() {
_, _ = appDbExec("insert into posts_fts(posts_fts) values ('rebuild')") _, _ = appDb.exec("insert into posts_fts(posts_fts) values ('rebuild')")
} }
func getPost(path string) (*post, error) { func getPost(path string) (*post, error) {
@ -344,7 +313,7 @@ func buildPostsQuery(config *postsRequestConfig) (query string, args []interface
func getPosts(config *postsRequestConfig) (posts []*post, err error) { func getPosts(config *postsRequestConfig) (posts []*post, err error) {
query, queryParams := buildPostsQuery(config) query, queryParams := buildPostsQuery(config)
rows, err := appDbQuery(query, queryParams...) rows, err := appDb.query(query, queryParams...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -379,7 +348,7 @@ func getPosts(config *postsRequestConfig) (posts []*post, err error) {
func countPosts(config *postsRequestConfig) (count int, err error) { func countPosts(config *postsRequestConfig) (count int, err error) {
query, params := buildPostsQuery(config) query, params := buildPostsQuery(config)
query = "select count(distinct path) from (" + query + ")" query = "select count(distinct path) from (" + query + ")"
row, err := appDbQueryRow(query, params...) row, err := appDb.queryRow(query, params...)
if err != nil { if err != nil {
return return
} }
@ -389,7 +358,7 @@ func countPosts(config *postsRequestConfig) (count int, err error) {
func allPostPaths(status postStatus) ([]string, error) { func allPostPaths(status postStatus) ([]string, error) {
var postPaths []string var postPaths []string
rows, err := appDbQuery("select path from posts where status = @status", sql.Named("status", status)) rows, err := appDb.query("select path from posts where status = @status", sql.Named("status", status))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -405,7 +374,7 @@ func allPostPaths(status postStatus) ([]string, error) {
func allTaxonomyValues(blog string, taxonomy string) ([]string, error) { func allTaxonomyValues(blog string, taxonomy string) ([]string, error) {
var values []string var values []string
rows, err := appDbQuery("select distinct pp.value from posts p left outer join post_parameters pp on p.path = pp.path where pp.parameter = @tax and length(coalesce(pp.value, '')) > 1 and blog = @blog and status = @status", sql.Named("tax", taxonomy), sql.Named("blog", blog), sql.Named("status", statusPublished)) rows, err := appDb.query("select distinct pp.value from posts p left outer join post_parameters pp on p.path = pp.path where pp.parameter = @tax and length(coalesce(pp.value, '')) > 1 and blog = @blog and status = @status", sql.Named("tax", taxonomy), sql.Named("blog", blog), sql.Named("status", statusPublished))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -422,7 +391,7 @@ type publishedDate struct {
} }
func allPublishedDates(blog string) (dates []publishedDate, err error) { func allPublishedDates(blog string) (dates []publishedDate, err error) {
rows, err := appDbQuery("select distinct substr(published, 1, 4) as year, substr(published, 6, 2) as month, substr(published, 9, 2) as day from posts where blog = @blog and status = @status and year != '' and month != '' and day != ''", sql.Named("blog", blog), sql.Named("status", statusPublished)) rows, err := appDb.query("select distinct substr(published, 1, 4) as year, substr(published, 6, 2) as month, substr(published, 9, 2) as day from posts where blog = @blog and status = @status and year != '' and month != '' and day != ''", sql.Named("blog", blog), sql.Named("status", statusPublished))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -12,7 +12,7 @@ func enqueue(name string, content []byte, schedule time.Time) error {
if len(content) == 0 { if len(content) == 0 {
return errors.New("empty content") return errors.New("empty content")
} }
_, err := appDbExec("insert into queue (name, content, schedule) values (@name, @content, @schedule)", _, err := appDb.exec("insert into queue (name, content, schedule) values (@name, @content, @schedule)",
sql.Named("name", name), sql.Named("content", content), sql.Named("schedule", schedule.UTC().String())) sql.Named("name", name), sql.Named("content", content), sql.Named("schedule", schedule.UTC().String()))
return err return err
} }
@ -25,17 +25,17 @@ type queueItem struct {
} }
func (qi *queueItem) reschedule(dur time.Duration) error { func (qi *queueItem) reschedule(dur time.Duration) error {
_, err := appDbExec("update queue set schedule = @schedule, content = @content where id = @id", sql.Named("schedule", qi.schedule.Add(dur).UTC().String()), sql.Named("content", qi.content), sql.Named("id", qi.id)) _, err := appDb.exec("update queue set schedule = @schedule, content = @content where id = @id", sql.Named("schedule", qi.schedule.Add(dur).UTC().String()), sql.Named("content", qi.content), sql.Named("id", qi.id))
return err return err
} }
func (qi *queueItem) dequeue() error { func (qi *queueItem) dequeue() error {
_, err := appDbExec("delete from queue where id = @id", sql.Named("id", qi.id)) _, err := appDb.exec("delete from queue where id = @id", sql.Named("id", qi.id))
return err return err
} }
func peekQueue(name string) (*queueItem, error) { func peekQueue(name string) (*queueItem, error) {
row, err := appDbQueryRow("select id, name, content, schedule from queue where schedule <= @schedule and name = @name order by schedule asc limit 1", sql.Named("name", name), sql.Named("schedule", time.Now().UTC().String())) row, err := appDb.queryRow("select id, name, content, schedule from queue where schedule <= @schedule and name = @name order by schedule asc limit 1", sql.Named("name", name), sql.Named("schedule", time.Now().UTC().String()))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -23,7 +23,7 @@ const (
func initSessions() { func initSessions() {
deleteExpiredSessions := func() { deleteExpiredSessions := func() {
if _, err := appDbExec("delete from sessions where expires < @now", if _, err := appDb.exec("delete from sessions where expires < @now",
sql.Named("now", time.Now().Local().String())); err != nil { sql.Named("now", time.Now().Local().String())); err != nil {
log.Println("Failed to delete expired sessions:", err.Error()) log.Println("Failed to delete expired sessions:", err.Error())
} }
@ -101,14 +101,14 @@ func (s *dbSessionStore) Delete(r *http.Request, w http.ResponseWriter, session
for k := range session.Values { for k := range session.Values {
delete(session.Values, k) delete(session.Values, k)
} }
if _, err := appDbExec("delete from sessions where id = @id", sql.Named("id", session.ID)); err != nil { if _, err := appDb.exec("delete from sessions where id = @id", sql.Named("id", session.ID)); err != nil {
return err return err
} }
return nil return nil
} }
func (s *dbSessionStore) load(session *sessions.Session) (err error) { func (s *dbSessionStore) load(session *sessions.Session) (err error) {
row, err := appDbQueryRow("select data, created, modified, expires from sessions where id = @id", sql.Named("id", session.ID)) row, err := appDb.queryRow("select data, created, modified, expires from sessions where id = @id", sql.Named("id", session.ID))
if err != nil { if err != nil {
return err return err
} }
@ -144,7 +144,7 @@ func (s *dbSessionStore) insert(session *sessions.Session) (err error) {
if err != nil { if err != nil {
return err return err
} }
res, err := appDbExec("insert into sessions(data, created, modified, expires) values(@data, @created, @modified, @expires)", res, err := appDb.exec("insert into sessions(data, created, modified, expires) values(@data, @created, @modified, @expires)",
sql.Named("data", encoded), sql.Named("created", created.Local().String()), sql.Named("modified", modified.Local().String()), sql.Named("expires", expires.Local().String())) sql.Named("data", encoded), sql.Named("created", created.Local().String()), sql.Named("modified", modified.Local().String()), sql.Named("expires", expires.Local().String()))
if err != nil { if err != nil {
return err return err
@ -168,7 +168,7 @@ func (s *dbSessionStore) save(session *sessions.Session) (err error) {
if err != nil { if err != nil {
return err return err
} }
_, err = appDbExec("update sessions set data = @data, modified = @modified where id = @id", _, err = appDb.exec("update sessions set data = @data, modified = @modified where id = @id",
sql.Named("data", encoded), sql.Named("modified", time.Now().Local().String()), sql.Named("id", session.ID)) sql.Named("data", encoded), sql.Named("modified", time.Now().Local().String()), sql.Named("id", session.ID))
if err != nil { if err != nil {
return err return err

View File

@ -16,7 +16,7 @@ func shortenPath(p string) (string, error) {
} }
id := getShortPathID(p) id := getShortPathID(p)
if id == -1 { if id == -1 {
_, err := appDbExec("insert or ignore into shortpath (path) values (@path)", sql.Named("path", p)) _, err := appDb.exec("insert or ignore into shortpath (path) values (@path)", sql.Named("path", p))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -32,7 +32,7 @@ func getShortPathID(p string) (id int) {
if p == "" { if p == "" {
return -1 return -1
} }
row, err := appDbQueryRow("select id from shortpath where path = @path", sql.Named("path", p)) row, err := appDb.queryRow("select id from shortpath where path = @path", sql.Named("path", p))
if err != nil { if err != nil {
return -1 return -1
} }
@ -49,7 +49,7 @@ func redirectToLongPath(rw http.ResponseWriter, r *http.Request) {
serve404(rw, r) serve404(rw, r)
return return
} }
row, err := appDbQueryRow("select path from shortpath where id = @id", sql.Named("id", id)) row, err := appDb.queryRow("select path from shortpath where id = @id", sql.Named("id", id))
if err != nil { if err != nil {
serve404(rw, r) serve404(rw, r)
return return

View File

@ -84,7 +84,7 @@ func extractMention(r *http.Request) (*mention, error) {
func webmentionExists(source, target string) bool { func webmentionExists(source, target string) bool {
result := 0 result := 0
row, err := appDbQueryRow("select exists(select 1 from webmentions where source = ? and target = ?)", source, target) row, err := appDb.queryRow("select exists(select 1 from webmentions where source = ? and target = ?)", source, target)
if err != nil { if err != nil {
return false return false
} }
@ -103,12 +103,12 @@ func createWebmention(source, target string) (err error) {
} }
func deleteWebmention(id int) error { func deleteWebmention(id int) error {
_, err := appDbExec("delete from webmentions where id = @id", sql.Named("id", id)) _, err := appDb.exec("delete from webmentions where id = @id", sql.Named("id", id))
return err return err
} }
func approveWebmention(id int) error { func approveWebmention(id int) error {
_, err := appDbExec("update webmentions set status = ? where id = ?", webmentionStatusApproved, id) _, err := appDb.exec("update webmentions set status = ? where id = ?", webmentionStatusApproved, id)
return err return err
} }
@ -172,7 +172,7 @@ func buildWebmentionsQuery(config *webmentionsRequestConfig) (query string, args
func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) { func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
mentions := []*mention{} mentions := []*mention{}
query, args := buildWebmentionsQuery(config) query, args := buildWebmentionsQuery(config)
rows, err := appDbQuery(query, args...) rows, err := appDb.query(query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -190,7 +190,7 @@ func getWebmentions(config *webmentionsRequestConfig) ([]*mention, error) {
func countWebmentions(config *webmentionsRequestConfig) (count int, err error) { func countWebmentions(config *webmentionsRequestConfig) (count int, err error) {
query, params := buildWebmentionsQuery(config) query, params := buildWebmentionsQuery(config)
query = "select count(*) from (" + query + ")" query = "select count(*) from (" + query + ")"
row, err := appDbQueryRow(query, params...) row, err := appDb.queryRow(query, params...)
if err != nil { if err != nil {
return return
} }

View File

@ -82,7 +82,7 @@ func (m *mention) verifyMention() error {
err = m.verifyReader(resp.Body) err = m.verifyReader(resp.Body)
_ = resp.Body.Close() _ = resp.Body.Close()
if err != nil { if err != nil {
_, err := appDbExec("delete from webmentions where source = @source and target = @target", sql.Named("source", m.Source), sql.Named("target", m.Target)) _, err := appDb.exec("delete from webmentions where source = @source and target = @target", sql.Named("source", m.Source), sql.Named("target", m.Target))
return err return err
} }
if len(m.Content) > 500 { if len(m.Content) > 500 {
@ -93,10 +93,10 @@ func (m *mention) verifyMention() error {
} }
newStatus := webmentionStatusVerified newStatus := webmentionStatusVerified
if webmentionExists(m.Source, m.Target) { if webmentionExists(m.Source, m.Target) {
_, err = appDbExec("update webmentions set status = @status, title = @title, content = @content, author = @author where source = @source and target = @target", _, err = appDb.exec("update webmentions set status = @status, title = @title, content = @content, author = @author where source = @source and target = @target",
sql.Named("status", newStatus), sql.Named("title", m.Title), sql.Named("content", m.Content), sql.Named("author", m.Author), sql.Named("source", m.Source), sql.Named("target", m.Target)) sql.Named("status", newStatus), sql.Named("title", m.Title), sql.Named("content", m.Content), sql.Named("author", m.Author), sql.Named("source", m.Source), sql.Named("target", m.Target))
} else { } else {
_, err = appDbExec("insert into webmentions (source, target, created, status, title, content, author) values (@source, @target, @created, @status, @title, @content, @author)", _, err = appDb.exec("insert into webmentions (source, target, created, status, title, content, author) values (@source, @target, @created, @status, @title, @content, @author)",
sql.Named("source", m.Source), sql.Named("target", m.Target), sql.Named("created", m.Created), sql.Named("status", newStatus), sql.Named("title", m.Title), sql.Named("content", m.Content), sql.Named("author", m.Author)) sql.Named("source", m.Source), sql.Named("target", m.Target), sql.Named("created", m.Created), sql.Named("status", newStatus), sql.Named("title", m.Title), sql.Named("content", m.Content), sql.Named("author", m.Author))
sendNotification(fmt.Sprintf("New webmention from %s to %s", m.Source, m.Target)) sendNotification(fmt.Sprintf("New webmention from %s to %s", m.Source, m.Target))
} }