diff --git a/database.go b/database.go index 26a072e..25f705a 100644 --- a/database.go +++ b/database.go @@ -20,7 +20,7 @@ type database struct { // Other things pc singleflight.Group // persistant cache pcm sync.Mutex // post creation - sp singleflight.Group // singleflight group for short path requests + sp sync.Mutex // short path creation debug bool } diff --git a/postsFuncs.go b/postsFuncs.go index 792c29e..7bb82a9 100644 --- a/postsFuncs.go +++ b/postsFuncs.go @@ -19,8 +19,8 @@ func (a *goBlog) fullPostURL(p *post) string { func (a *goBlog) shortPostURL(p *post) string { s, err := a.db.shortenPath(p.Path) - if err != nil { - return p.Path + if err != nil || s == "" { + return a.getFullAddress(p.Path) } if a.cfg.Server.ShortPublicAddress != "" { return a.cfg.Server.ShortPublicAddress + s diff --git a/shortPath.go b/shortPath.go index 3f1479f..22c82a7 100644 --- a/shortPath.go +++ b/shortPath.go @@ -11,16 +11,15 @@ func (db *database) shortenPath(p string) (string, error) { return "", errors.New("empty path") } - // Use singleflight to deduplicate concurrent requests and handle caching - v, err, _ := db.sp.Do(p, func() (interface{}, error) { - return db.shortenPathTransaction(p) - }) + db.sp.Lock() + defer db.sp.Unlock() + result, err := db.shortenPathTransaction(p) if err != nil { return "", err } - return v.(string), nil + return result, nil } func (db *database) shortenPathTransaction(p string) (string, error) { @@ -33,14 +32,24 @@ func (db *database) shortenPathTransaction(p string) (string, error) { var id int64 err = tx.QueryRow("select id from shortpath where path = @path", sql.Named("path", p)).Scan(&id) if err == sql.ErrNoRows { - // Path doesn't exist, insert new entry + // Path doesn't exist, insert new entry with the lowest available id result, err := tx.Exec(` - insert into shortpath (id, path) - values ( - (select min(id) + 1 from (select id from shortpath union all select 0) where id + 1 not in (select id from shortpath)), - @path - ) - `, sql.Named("path", p)) + INSERT INTO shortpath (id, path) + VALUES ( + (WITH RECURSIVE ids(n) AS ( + SELECT 1 + UNION ALL + SELECT n + 1 FROM ids + WHERE n < (SELECT COALESCE(MAX(id), 0) + 1 FROM shortpath) + ) + SELECT n FROM ids + LEFT JOIN shortpath s ON ids.n = s.id + WHERE s.id IS NULL + ORDER BY n + LIMIT 1), + ? + ) + `, p) if err != nil { return "", err } diff --git a/shortPath_test.go b/shortPath_test.go index ec3c065..9466468 100644 --- a/shortPath_test.go +++ b/shortPath_test.go @@ -17,28 +17,35 @@ func Test_shortenPath(t *testing.T) { res1, err := db.shortenPath("/a") require.NoError(t, err) + assert.Equal(t, "/s/1", res1) res2, err := db.shortenPath("/a") require.NoError(t, err) + assert.Equal(t, "/s/1", res2) res3, err := db.shortenPath("/b") require.NoError(t, err) + assert.Equal(t, "/s/2", res3) res4, err := db.shortenPath("/a") require.NoError(t, err) + assert.Equal(t, "/s/1", res4) - assert.Equal(t, res1, res2) - assert.Equal(t, "/s/1", res1) + res5, err := db.shortenPath("/c") + require.NoError(t, err) + assert.Equal(t, "/s/3", res5) - assert.NotEqual(t, res1, res3) - assert.Equal(t, "/s/2", res3) + _, err = db.Exec("delete from shortpath where id = 2") + require.NoError(t, err) - assert.Equal(t, res2, res4) - assert.Equal(t, "/s/1", res4) + res6, err := db.shortenPath("/d") + require.NoError(t, err) + assert.Equal(t, "/s/2", res6) - _, _ = db.Exec("delete from shortpath where id = 1") + _, err = db.Exec("delete from shortpath where id = 1") + require.NoError(t, err) - res5, err := db.shortenPath("/c") + res7, err := db.shortenPath("/e") require.NoError(t, err) - assert.Equal(t, "/s/1", res5) + assert.Equal(t, "/s/1", res7) }