mirror of
https://github.com/mickael-menu/zk
synced 2024-11-11 07:10:25 +00:00
172 lines
4.4 KiB
Go
172 lines
4.4 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/mickael-menu/zk/internal/core"
|
|
"github.com/mickael-menu/zk/internal/util"
|
|
)
|
|
|
|
// LinkDAO persists links in the SQLite database.
|
|
type LinkDAO struct {
|
|
tx Transaction
|
|
logger util.Logger
|
|
|
|
// Prepared SQL statements
|
|
addLinkStmt *LazyStmt
|
|
removeLinksStmt *LazyStmt
|
|
updateTargetIDStmt *LazyStmt
|
|
}
|
|
|
|
// NewLinkDAO creates a new instance of a DAO working on the given database
|
|
// transaction.
|
|
func NewLinkDAO(tx Transaction, logger util.Logger) *LinkDAO {
|
|
return &LinkDAO{
|
|
tx: tx,
|
|
logger: logger,
|
|
|
|
// Add a new link.
|
|
addLinkStmt: tx.PrepareLazy(`
|
|
INSERT INTO links (source_id, target_id, title, href, type, external, rels, snippet, snippet_start, snippet_end)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`),
|
|
|
|
// Remove all the outbound links of a note.
|
|
removeLinksStmt: tx.PrepareLazy(`
|
|
DELETE FROM links
|
|
WHERE source_id = ?
|
|
`),
|
|
|
|
updateTargetIDStmt: tx.PrepareLazy(`
|
|
UPDATE links
|
|
SET target_id = ?
|
|
WHERE id = ?
|
|
`),
|
|
}
|
|
}
|
|
|
|
// Add inserts all the outbound links of the given note.
|
|
func (d *LinkDAO) Add(links []core.ResolvedLink) error {
|
|
for _, link := range links {
|
|
sourceID := noteIDToSQL(link.SourceID)
|
|
targetID := noteIDToSQL(link.TargetID)
|
|
|
|
_, err := d.addLinkStmt.Exec(sourceID, targetID, link.Title, link.Href, link.Type, link.IsExternal, joinLinkRels(link.Rels), link.Snippet, link.SnippetStart, link.SnippetEnd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveAll removes all the outbound links of the given note.
|
|
func (d *LinkDAO) RemoveAll(id core.NoteID) error {
|
|
_, err := d.removeLinksStmt.Exec(noteIDToSQL(id))
|
|
return err
|
|
}
|
|
|
|
// SetTargetID updates the target note of a link.
|
|
func (d *LinkDAO) SetTargetID(id core.LinkID, targetID core.NoteID) error {
|
|
_, err := d.updateTargetIDStmt.Exec(noteIDToSQL(targetID), linkIDToSQL(id))
|
|
return err
|
|
}
|
|
|
|
// joinLinkRels will concatenate a list of rels into a SQLite ready string.
|
|
// Each rel is delimited by \x01 for easy matching in queries.
|
|
func joinLinkRels(rels []core.LinkRelation) string {
|
|
if len(rels) == 0 {
|
|
return ""
|
|
}
|
|
delimiter := "\x01"
|
|
res := delimiter
|
|
for _, rel := range rels {
|
|
res += string(rel) + delimiter
|
|
}
|
|
return res
|
|
}
|
|
|
|
// FindInternal returns all the links internal to the notebook.
|
|
func (d *LinkDAO) FindInternal() ([]core.ResolvedLink, error) {
|
|
return d.findWhere("external = 0")
|
|
}
|
|
|
|
// FindBetweenNotes returns all the links existing between the given notes.
|
|
func (d *LinkDAO) FindBetweenNotes(ids []core.NoteID) ([]core.ResolvedLink, error) {
|
|
idsString := joinNoteIDs(ids, ",")
|
|
return d.findWhere(fmt.Sprintf("source_id IN (%s) AND target_id IN (%s)", idsString, idsString))
|
|
}
|
|
|
|
// findWhere returns all the links, filtered by the given where query.
|
|
func (d *LinkDAO) findWhere(where string) ([]core.ResolvedLink, error) {
|
|
links := make([]core.ResolvedLink, 0)
|
|
|
|
query := `
|
|
SELECT id, source_id, source_path, target_id, target_path, title, href, type, external, rels, snippet, snippet_start, snippet_end
|
|
FROM resolved_links
|
|
`
|
|
|
|
if where != "" {
|
|
query += "\nWHERE " + where
|
|
}
|
|
|
|
rows, err := d.tx.Query(query)
|
|
if err != nil {
|
|
return links, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
link, err := d.scanLink(rows)
|
|
if err != nil {
|
|
d.logger.Err(err)
|
|
continue
|
|
}
|
|
if link != nil {
|
|
links = append(links, *link)
|
|
}
|
|
}
|
|
|
|
return links, nil
|
|
}
|
|
|
|
func (d *LinkDAO) scanLink(row RowScanner) (*core.ResolvedLink, error) {
|
|
var (
|
|
id, sourceID, snippetStart, snippetEnd int
|
|
targetID sql.NullInt64
|
|
sourcePath, title, href, linkType, snippet string
|
|
external bool
|
|
targetPath, rels sql.NullString
|
|
)
|
|
|
|
err := row.Scan(
|
|
&id, &sourceID, &sourcePath, &targetID, &targetPath, &title, &href,
|
|
&linkType, &external, &rels, &snippet, &snippetStart, &snippetEnd,
|
|
)
|
|
switch {
|
|
case err == sql.ErrNoRows:
|
|
return nil, nil
|
|
case err != nil:
|
|
return nil, err
|
|
default:
|
|
return &core.ResolvedLink{
|
|
ID: core.LinkID(id),
|
|
SourceID: core.NoteID(sourceID),
|
|
SourcePath: sourcePath,
|
|
TargetID: core.NoteID(targetID.Int64),
|
|
TargetPath: targetPath.String,
|
|
Link: core.Link{
|
|
Title: title,
|
|
Href: href,
|
|
Type: core.LinkType(linkType),
|
|
IsExternal: external,
|
|
Rels: core.LinkRels(parseListFromNullString(rels)...),
|
|
Snippet: snippet,
|
|
SnippetStart: snippetStart,
|
|
SnippetEnd: snippetEnd,
|
|
},
|
|
}, nil
|
|
}
|
|
}
|