File view with blame information shown in the left gutter beside each line.

barerepo / server / internal/store/search.go
157 lines · 4.6kb · 133728eaa05486504991b230f9d4c7e986b7defc
log files threads runs releases config jump to file t
133728e barerepo 1mo
1
package store
133728e barerepo 1mo
2
133728e barerepo 1mo
3
import (
133728e barerepo 1mo
4
"context"
133728e barerepo 1mo
5
"strings"
133728e barerepo 1mo
6
)
133728e barerepo 1mo
7
133728e barerepo 1mo
8
// Doc is one indexed thing: a file at the tip, a thread, or a repository. Chapter 17.
133728e barerepo 1mo
9
type Doc struct {
133728e barerepo 1mo
10
Repo string
133728e barerepo 1mo
11
Kind string
133728e barerepo 1mo
12
Path string
133728e barerepo 1mo
13
Title string
133728e barerepo 1mo
14
Body string
133728e barerepo 1mo
15
}
133728e barerepo 1mo
16
133728e barerepo 1mo
17
// Doc kinds, ranked in this order, because a barerepo is usually searched for a symbol.
133728e barerepo 1mo
18
const (
133728e barerepo 1mo
19
Code = "code"
133728e barerepo 1mo
20
Thread = "thread"
133728e barerepo 1mo
21
Repository = "repo"
133728e barerepo 1mo
22
)
133728e barerepo 1mo
23
133728e barerepo 1mo
24
// reader marks a name inside the readers column, so john never matches johnson.
133728e barerepo 1mo
25
func reader(name string) string { return "|" + name + "|" }
133728e barerepo 1mo
26
133728e barerepo 1mo
27
// Readers renders the read set of a repository for the index, per chapter 18's binary read rule.
133728e barerepo 1mo
28
func Readers(owner string, push []string) string {
133728e barerepo 1mo
29
var b strings.Builder
133728e barerepo 1mo
30
b.WriteString(reader(owner))
133728e barerepo 1mo
31
for _, name := range push {
133728e barerepo 1mo
32
if name != owner {
133728e barerepo 1mo
33
b.WriteString(reader(name))
133728e barerepo 1mo
34
}
133728e barerepo 1mo
35
}
133728e barerepo 1mo
36
return b.String()
133728e barerepo 1mo
37
}
133728e barerepo 1mo
38
133728e barerepo 1mo
39
// PutDocs swaps one repository's documents of one kind, in a transaction, so a query sees one set.
133728e barerepo 1mo
40
func (db *DB) PutDocs(ctx context.Context, repo, kind string, docs []Doc, public bool, readers string) error {
133728e barerepo 1mo
41
tx, err := db.Begin(ctx)
133728e barerepo 1mo
42
if err != nil {
133728e barerepo 1mo
43
return err
133728e barerepo 1mo
44
}
133728e barerepo 1mo
45
defer tx.Rollback()
133728e barerepo 1mo
46
if _, err := tx.ExecContext(ctx,
133728e barerepo 1mo
47
`DELETE FROM search_docs WHERE repo = ? AND kind = ?`, repo, kind); err != nil {
133728e barerepo 1mo
48
return err
133728e barerepo 1mo
49
}
133728e barerepo 1mo
50
flag := 0
133728e barerepo 1mo
51
if public {
133728e barerepo 1mo
52
flag = 1
133728e barerepo 1mo
53
}
133728e barerepo 1mo
54
for _, d := range docs {
133728e barerepo 1mo
55
if _, err := tx.ExecContext(ctx,
133728e barerepo 1mo
56
`INSERT INTO search_docs (repo, kind, path, title, body, public, readers)
133728e barerepo 1mo
57
VALUES (?, ?, ?, ?, ?, ?, ?)`,
133728e barerepo 1mo
58
repo, kind, d.Path, d.Title, d.Body, flag, readers); err != nil {
133728e barerepo 1mo
59
return err
133728e barerepo 1mo
60
}
133728e barerepo 1mo
61
}
133728e barerepo 1mo
62
return tx.Commit()
133728e barerepo 1mo
63
}
133728e barerepo 1mo
64
133728e barerepo 1mo
65
// SetReadable rewrites who may read a repository's documents, since visibility arrives by push.
133728e barerepo 1mo
66
func (db *DB) SetReadable(ctx context.Context, repo string, public bool, readers string) error {
133728e barerepo 1mo
67
flag := 0
133728e barerepo 1mo
68
if public {
133728e barerepo 1mo
69
flag = 1
133728e barerepo 1mo
70
}
133728e barerepo 1mo
71
_, err := db.ExecContext(ctx,
133728e barerepo 1mo
72
`UPDATE search_docs SET public = ?, readers = ? WHERE repo = ?`, flag, readers, repo)
133728e barerepo 1mo
73
return err
133728e barerepo 1mo
74
}
133728e barerepo 1mo
75
133728e barerepo 1mo
76
// EmptyIndex drops every document, so a rebuild produces exactly the index git says it should.
133728e barerepo 1mo
77
func (db *DB) EmptyIndex(ctx context.Context) error {
133728e barerepo 1mo
78
_, err := db.ExecContext(ctx, `DELETE FROM search_docs`)
133728e barerepo 1mo
79
return err
133728e barerepo 1mo
80
}
133728e barerepo 1mo
81
133728e barerepo 1mo
82
// likePattern escapes what LIKE reads as syntax, so a query of 100% finds the string and not everything.
133728e barerepo 1mo
83
func likePattern(query string) string {
133728e barerepo 1mo
84
var b strings.Builder
133728e barerepo 1mo
85
b.WriteByte('%')
133728e barerepo 1mo
86
for _, c := range query {
133728e barerepo 1mo
87
switch c {
133728e barerepo 1mo
88
case '\\', '%', '_':
133728e barerepo 1mo
89
b.WriteByte('\\')
133728e barerepo 1mo
90
}
133728e barerepo 1mo
91
b.WriteRune(c)
133728e barerepo 1mo
92
}
133728e barerepo 1mo
93
b.WriteByte('%')
133728e barerepo 1mo
94
return b.String()
133728e barerepo 1mo
95
}
133728e barerepo 1mo
96
133728e barerepo 1mo
97
// FindDocs is one query, filtered by read access inside it, because filtering after ranking leaks. 17.
133728e barerepo 1mo
98
func (db *DB) FindDocs(ctx context.Context, viewer, query string, limit int) ([]Doc, error) {
133728e barerepo 1mo
99
pattern := likePattern(strings.ToLower(query))
133728e barerepo 1mo
100
args := []any{pattern, pattern}
133728e barerepo 1mo
101
access := `public = 1`
133728e barerepo 1mo
102
if viewer != "" {
133728e barerepo 1mo
103
access = `(public = 1 OR readers LIKE ?)`
133728e barerepo 1mo
104
args = append(args, "%"+reader(viewer)+"%")
133728e barerepo 1mo
105
}
133728e barerepo 1mo
106
args = append(args, limit)
133728e barerepo 1mo
107
rows, err := db.QueryContext(ctx,
133728e barerepo 1mo
108
`SELECT repo, kind, path, title, body FROM search_docs
133728e barerepo 1mo
109
WHERE (LOWER(body) LIKE ? ESCAPE '\' OR LOWER(title) LIKE ? ESCAPE '\')
133728e barerepo 1mo
110
AND `+access+`
133728e barerepo 1mo
111
ORDER BY CASE kind WHEN 'code' THEN 0 WHEN 'thread' THEN 1 ELSE 2 END, repo, path
133728e barerepo 1mo
112
LIMIT ?`, args...)
133728e barerepo 1mo
113
if err != nil {
133728e barerepo 1mo
114
return nil, err
133728e barerepo 1mo
115
}
133728e barerepo 1mo
116
defer rows.Close()
133728e barerepo 1mo
117
var out []Doc
133728e barerepo 1mo
118
for rows.Next() {
133728e barerepo 1mo
119
var d Doc
133728e barerepo 1mo
120
if err := rows.Scan(&d.Repo, &d.Kind, &d.Path, &d.Title, &d.Body); err != nil {
133728e barerepo 1mo
121
return nil, err
133728e barerepo 1mo
122
}
133728e barerepo 1mo
123
out = append(out, d)
133728e barerepo 1mo
124
}
133728e barerepo 1mo
125
return out, rows.Err()
133728e barerepo 1mo
126
}
133728e barerepo 1mo
127
133728e barerepo 1mo
128
// PutDoc writes or replaces one document, which is what an incremental push update needs.
133728e barerepo 1mo
129
func (db *DB) PutDoc(ctx context.Context, d Doc, public bool, readers string) error {
133728e barerepo 1mo
130
flag := 0
133728e barerepo 1mo
131
if public {
133728e barerepo 1mo
132
flag = 1
133728e barerepo 1mo
133
}
133728e barerepo 1mo
134
_, err := db.ExecContext(ctx,
133728e barerepo 1mo
135
`INSERT INTO search_docs (repo, kind, path, title, body, public, readers)
133728e barerepo 1mo
136
VALUES (?, ?, ?, ?, ?, ?, ?)
133728e barerepo 1mo
137
ON CONFLICT (repo, kind, path) DO UPDATE SET
133728e barerepo 1mo
138
title = excluded.title, body = excluded.body,
133728e barerepo 1mo
139
public = excluded.public, readers = excluded.readers`,
133728e barerepo 1mo
140
d.Repo, d.Kind, d.Path, d.Title, d.Body, flag, readers)
133728e barerepo 1mo
141
return err
133728e barerepo 1mo
142
}
133728e barerepo 1mo
143
133728e barerepo 1mo
144
// DeleteDoc drops one document, for a file a push removed.
133728e barerepo 1mo
145
func (db *DB) DeleteDoc(ctx context.Context, repo, kind, path string) error {
133728e barerepo 1mo
146
_, err := db.ExecContext(ctx,
133728e barerepo 1mo
147
`DELETE FROM search_docs WHERE repo = ? AND kind = ? AND path = ?`, repo, kind, path)
133728e barerepo 1mo
148
return err
133728e barerepo 1mo
149
}
133728e barerepo 1mo
150
133728e barerepo 1mo
151
// HasDocs reports whether a repository has been indexed, so a first push does a full pass.
133728e barerepo 1mo
152
func (db *DB) HasDocs(ctx context.Context, repo string) (bool, error) {
133728e barerepo 1mo
153
var n int
133728e barerepo 1mo
154
err := db.QueryRowContext(ctx,
133728e barerepo 1mo
155
`SELECT COUNT(*) FROM search_docs WHERE repo = ?`, repo).Scan(&n)
133728e barerepo 1mo
156
return n > 0, err
133728e barerepo 1mo
157
}
history · rawbarerepo 0.1.0