From d9d5fe173027bcb9139b4439f33b85e2e8d31c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Sun, 31 Jan 2021 17:07:28 +0100 Subject: [PATCH] Add --max-distance for recursive link filtering options --- adapter/sqlite/note_dao.go | 17 +++++++++++------ adapter/sqlite/note_dao_test.go | 28 ++++++++++++++++++++++++++++ cmd/finder_opts.go | 15 +++++++++------ core/note/find.go | 14 ++++++++------ 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/adapter/sqlite/note_dao.go b/adapter/sqlite/note_dao.go index 68b2acc..49ffcae 100644 --- a/adapter/sqlite/note_dao.go +++ b/adapter/sqlite/note_dao.go @@ -342,7 +342,7 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { groupById := false args := make([]interface{}, 0) - setupLinkFilter := func(paths []string, forward, negate bool, recursive bool) error { + setupLinkFilter := func(paths []string, forward, negate, recursive bool, maxDistance int) error { ids, err := d.findIdsByPathPrefixes(paths) if err != nil { return err @@ -371,7 +371,12 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { // Credit to https://inviqa.com/blog/storing-graphs-database-sql-meets-social-network links_src = "links_transitive_closure" orderTerms = append(orderTerms, alias+".distance") - cteClauses = append(cteClauses, `WITH RECURSIVE links_transitive_closure(source_id, target_id, title, snippet, distance, path) AS ( + + if maxDistance == 0 { + maxDistance = 1000 + } + + cteClauses = append(cteClauses, fmt.Sprintf(`WITH RECURSIVE links_transitive_closure(source_id, target_id, title, snippet, distance, path) AS ( SELECT source_id, target_id, title, snippet, 1 AS distance, source_id || '.' || target_id || '.' AS path @@ -385,8 +390,8 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { FROM links AS l JOIN links_transitive_closure AS tc ON l.source_id = tc.target_id - WHERE tc.path NOT LIKE '%' || l.target_id || '.%' -)`) + WHERE tc.distance < %d AND tc.path NOT LIKE '%%' || l.target_id || '.%%' +)`, maxDistance)) } if !negate { @@ -439,13 +444,13 @@ func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) { whereExprs = append(whereExprs, strings.Join(globs, " AND ")) case note.LinkedByFilter: - err := setupLinkFilter(filter.Paths, false, filter.Negate, filter.Recursive) + err := setupLinkFilter(filter.Paths, false, filter.Negate, filter.Recursive, filter.MaxDistance) if err != nil { return nil, err } case note.LinkingToFilter: - err := setupLinkFilter(filter.Paths, true, filter.Negate, filter.Recursive) + err := setupLinkFilter(filter.Paths, true, filter.Negate, filter.Recursive, filter.MaxDistance) if err != nil { return nil, err } diff --git a/adapter/sqlite/note_dao_test.go b/adapter/sqlite/note_dao_test.go index 85b954a..a263901 100644 --- a/adapter/sqlite/note_dao_test.go +++ b/adapter/sqlite/note_dao_test.go @@ -528,6 +528,20 @@ func TestNoteDAOFindLinkedByRecursive(t *testing.T) { ) } +func TestNoteDAOFindLinkedByRecursiveWithMaxDistance(t *testing.T) { + testNoteDAOFindPaths(t, + note.FinderOpts{ + Filters: []note.Filter{note.LinkedByFilter{ + Paths: []string{"log/2021-01-04.md"}, + Negate: false, + Recursive: true, + MaxDistance: 2, + }}, + }, + []string{"index.md", "f39c8.md"}, + ) +} + func TestNoteDAOFindLinkedByWithSnippets(t *testing.T) { testNoteDAOFind(t, note.FinderOpts{ @@ -612,6 +626,20 @@ func TestNoteDAOFindLinkingToRecursive(t *testing.T) { ) } +func TestNoteDAOFindLinkingToRecursiveWithMaxDistance(t *testing.T) { + testNoteDAOFindPaths(t, + note.FinderOpts{ + Filters: []note.Filter{note.LinkingToFilter{ + Paths: []string{"log/2021-01-04.md"}, + Negate: false, + Recursive: true, + MaxDistance: 2, + }}, + }, + []string{"log/2021-01-03.md", "f39c8.md"}, + ) +} + func TestNoteDAOFindNotLinkingTo(t *testing.T) { testNoteDAOFindPaths(t, note.FinderOpts{ diff --git a/cmd/finder_opts.go b/cmd/finder_opts.go index 68e26f2..dc551c2 100644 --- a/cmd/finder_opts.go +++ b/cmd/finder_opts.go @@ -24,6 +24,7 @@ type Filtering struct { LinkingTo []string `help:"Only the notes linking to the given notes" placeholder:"" short:"L"` NotLinkedBy []string `help:"Only the notes not linked by the given notes" placeholder:""` NotLinkingTo []string `help:"Only the notes not linking to the given notes" placeholder:""` + MaxDistance int `help:"Maximum distance between two linked notes"` Orphan bool `help:"Only the notes which don't have any other note linking to them"` Exclude []string `help:"Excludes notes matching the given file path pattern from the list" short:"x" placeholder:""` Recursive bool `help:"Follow links recursively" short:"r"` @@ -128,18 +129,20 @@ func NewFinderOpts(zk *zk.Zk, filtering Filtering, sorting Sorting) (*note.Finde linkedByPaths, ok := relPaths(zk, filtering.LinkedBy) if ok { filters = append(filters, note.LinkedByFilter{ - Paths: linkedByPaths, - Negate: false, - Recursive: filtering.Recursive, + Paths: linkedByPaths, + Negate: false, + Recursive: filtering.Recursive, + MaxDistance: filtering.MaxDistance, }) } linkingToPaths, ok := relPaths(zk, filtering.LinkingTo) if ok { filters = append(filters, note.LinkingToFilter{ - Paths: linkingToPaths, - Negate: false, - Recursive: filtering.Recursive, + Paths: linkingToPaths, + Negate: false, + Recursive: filtering.Recursive, + MaxDistance: filtering.MaxDistance, }) } diff --git a/core/note/find.go b/core/note/find.go index 9314ec4..e74b41e 100644 --- a/core/note/find.go +++ b/core/note/find.go @@ -42,16 +42,18 @@ type ExcludePathFilter []string // LinkedByFilter is a note filter used to select notes being linked by another one. type LinkedByFilter struct { - Paths []string - Negate bool - Recursive bool + Paths []string + Negate bool + Recursive bool + MaxDistance int } // LinkingToFilter is a note filter used to select notes being linked by another one. type LinkingToFilter struct { - Paths []string - Negate bool - Recursive bool + Paths []string + Negate bool + Recursive bool + MaxDistance int } // OrphanFilter is a note filter used to select notes having no other notes linking to them.