Author: Kevin Schoon [me@kevinschoon.com]
Hash: 26131efa9e9a58e27d9e8f08d0d2a3479b178295
Timestamp: Thu, 02 Jun 2022 15:38:47 +0000 (2 years ago)

+136 -57 +/-6 browse
use recursive sqlite queries
1diff --git a/pkg/cmd/app.go b/pkg/cmd/app.go
2index ed5b5a7..e48869a 100644
3--- a/pkg/cmd/app.go
4+++ b/pkg/cmd/app.go
5 @@ -95,7 +95,11 @@ data structure. It uses SQLite to store note content and metadata.
6 },
7 },
8 Action: func(ctx *cli.Context) error {
9- return hierarchy.LoadNotes(*cfg)
10+ notes, err := hierarchy.LoadNotes(*cfg)
11+ if err != nil {
12+ return err
13+ }
14+ return json.NewEncoder(os.Stdout).Encode(notes)
15 },
16 },
17 {
18 diff --git a/pkg/hierarchy/database.go b/pkg/hierarchy/database.go
19index aa94544..53579c5 100644
20--- a/pkg/hierarchy/database.go
21+++ b/pkg/hierarchy/database.go
22 @@ -3,7 +3,6 @@ package hierarchy
23 import (
24 "database/sql"
25 _ "embed"
26- "encoding/json"
27 "io/ioutil"
28 "os"
29
30 @@ -50,6 +49,74 @@ type dbNote struct {
31 Parent int64
32 }
33
34+ func resolve(seed *Note, tx *sql.Tx) error {
35+ rows, err := tx.Query(`
36+ WITH RECURSIVE
37+ with_decendants(id, parent, name, content, level) AS (
38+ VALUES(?, ?, '', '', 0)
39+ UNION ALL
40+ SELECT
41+ notes.id,
42+ notes.parent,
43+ notes.name,
44+ notes.content,
45+ with_decendants.level+1
46+ FROM notes, with_decendants
47+ WHERE notes.parent=with_decendants.id
48+ )
49+ SELECT * FROM with_decendants;
50+ `, seed.ID, seed.Parent)
51+ if err != nil {
52+ return err
53+ }
54+ results := make(map[int64]*Note)
55+ results[seed.ID] = seed
56+ for rows.Next() {
57+ var (
58+ id = new(int64)
59+ parentId = new(int64)
60+ name = new(string)
61+ content = new(string)
62+ depth = new(int64)
63+ )
64+ err = rows.Scan(id, &parentId, name, content, depth)
65+ if err != nil {
66+ return err
67+ }
68+ if *depth > 0 {
69+ parent := results[*parentId]
70+ note := &Note{
71+ ID: *id,
72+ Parent: parent,
73+ Name: *name,
74+ Content: *content,
75+ }
76+ parent.Descendants = append(parent.Descendants, note)
77+ results[note.ID] = note
78+ }
79+ }
80+ return nil
81+ }
82+
83+ func Find(cfg config.Config, notePath string) (*Note, error) {
84+ np := ReadPath(notePath)
85+ note := new(Note)
86+ return note, With(cfg.Database, func(tx *sql.Tx) error {
87+ row := tx.QueryRow(`
88+ SELECT (
89+ id, name, content
90+ ) FROM notes
91+ WHERE
92+ name = ?
93+ `, np.String())
94+ err := row.Scan(&note.ID, &note.Name, &note.Content)
95+ if err != nil {
96+ return err
97+ }
98+ return resolve(note, tx)
99+ })
100+ }
101+
102 func Edit(cfg config.Config, notePath string) error {
103 np := ReadPath(notePath)
104 return With(cfg.Database, func(tx *sql.Tx) error {
105 @@ -77,69 +144,29 @@ VALUES (?, ?, ?)
106 })
107 }
108
109- // TODO: garbage
110- func linkNotes2(dbNotes []*dbNote, notes []*Note) []*Note {
111- remaining := []*dbNote{}
112- for _, note := range dbNotes {
113- if note.Parent == 0 {
114- notes = append(notes, &Note{
115- ID: note.ID,
116- Name: note.Name,
117- Content: note.Content,
118- Parent: nil,
119- })
120- continue
121- } else {
122- remaining = append(remaining, note)
123- }
124- }
125- nextRemaining := []*dbNote{}
126- loop:
127- for _, note := range remaining {
128- for _, other := range notes {
129- if note.Parent == other.ID {
130- n := &Note{
131- ID: note.ID,
132- Name: note.Name,
133- Content: note.Content,
134- Parent: other,
135- }
136- other.Children = append(other.Children, n)
137- continue loop
138- }
139- }
140- nextRemaining = append(nextRemaining, note)
141- }
142- if len(remaining) > 0 {
143- notes = linkNotes2(nextRemaining, notes)
144- }
145- return notes
146- }
147-
148 func LoadNotes(cfg config.Config) (notes []*Note, err error) {
149 return notes, With(cfg.Database, func(tx *sql.Tx) error {
150- var dbNotes []*dbNote
151 rows, err := tx.Query(`
152 SELECT
153- id, name, parent, content
154- FROM notes
155+ id, name, content
156+ FROM notes
157+ WHERE parent IS NULL
158 `)
159 if err != nil {
160 return err
161 }
162 for rows.Next() {
163- parentId := new(int64)
164- note := &dbNote{}
165- err = rows.Scan(&note.ID, &note.Name, &parentId, &note.Content)
166+ note := &Note{}
167+ err = rows.Scan(&note.ID, &note.Name, &note.Content)
168 if err != nil {
169 return err
170 }
171- if parentId != nil {
172- note.Parent = *parentId
173+ notes = append(notes, note)
174+ err = resolve(note, tx)
175+ if err != nil {
176+ return err
177 }
178- dbNotes = append(dbNotes, note)
179 }
180- notes = linkNotes2(dbNotes, nil)
181 return nil
182 })
183 }
184 diff --git a/pkg/hierarchy/editor.go b/pkg/hierarchy/editor.go
185index 87bc58e..aadda55 100644
186--- a/pkg/hierarchy/editor.go
187+++ b/pkg/hierarchy/editor.go
188 @@ -1,12 +1,15 @@
189 package hierarchy
190
191 import (
192+ "bytes"
193+ "io/ioutil"
194 "os"
195+ "os/exec"
196
197 "kevinschoon.com/hierarchy/pkg/config"
198 )
199
200- func OpenWithEditor(cfg config.Config, note *Note) error {
201+ func OpenWithEditor(cfg config.Config, note *Note, cb func() error) error {
202 editor := cfg.Editor
203 if editor == "" {
204 editor = os.Getenv("EDITOR")
205 @@ -14,4 +17,31 @@ func OpenWithEditor(cfg config.Config, note *Note) error {
206 if editor == "" {
207 editor = "vim"
208 }
209+ cacheDir, _ := os.UserCacheDir()
210+ buf := bytes.NewBufferString(note.Content)
211+ fp, err := os.CreateTemp(cacheDir, "hierarchy")
212+ if err != nil {
213+ return err
214+ }
215+ _, err = buf.WriteTo(fp)
216+ if err != nil {
217+ return err
218+ }
219+ err = fp.Close()
220+ if err != nil {
221+ return err
222+ }
223+ cmd := exec.Command(editor, fp.Name())
224+ err = cmd.Run()
225+ if err != nil {
226+ return err
227+ }
228+ raw, err := ioutil.ReadFile(fp.Name())
229+ if err != nil {
230+ return err
231+ }
232+ if string(raw) != note.Content {
233+ }
234+ // TODO
235+ return nil
236 }
237 diff --git a/pkg/hierarchy/helpers.go b/pkg/hierarchy/helpers.go
238index c74d083..4bca583 100644
239--- a/pkg/hierarchy/helpers.go
240+++ b/pkg/hierarchy/helpers.go
241 @@ -7,6 +7,10 @@ import (
242
243 type NotePath [2]string
244
245+ func (np NotePath) String() string {
246+ return path.Join(np[0], np[1])
247+ }
248+
249 func (np NotePath) Parent() string {
250 return np[0]
251 }
252 diff --git a/pkg/hierarchy/note.go b/pkg/hierarchy/note.go
253index 29223bb..6826368 100644
254--- a/pkg/hierarchy/note.go
255+++ b/pkg/hierarchy/note.go
256 @@ -1,9 +1,9 @@
257 package hierarchy
258
259 type Note struct {
260- ID int64 `json:"id"`
261- Name string `json:"name"`
262- Content string `json:"content"`
263- Parent *Note `json:"-"`
264- Children []*Note `json:"children"`
265+ ID int64 `json:"id"`
266+ Name string `json:"name"`
267+ Content string `json:"content"`
268+ Parent *Note `json:"-"`
269+ Descendants []*Note `json:"descendants"`
270 }
271 diff --git a/query.sql b/query.sql
272new file mode 100644
273index 0000000..6eda187
274--- /dev/null
275+++ b/query.sql
276 @@ -0,0 +1,14 @@
277+ WITH RECURSIVE
278+ with_decendants(id, parent, name, content, level) AS (
279+ VALUES(2, 1, '', '', 0)
280+ UNION ALL
281+ SELECT
282+ notes.id,
283+ notes.parent,
284+ notes.name,
285+ notes.content,
286+ with_decendants.level+1
287+ FROM notes, with_decendants
288+ WHERE notes.parent=with_decendants.id
289+ )
290+ SELECT * FROM with_decendants;