Skip to content

Commit 3d100d1

Browse files
authored
Merge pull request #1034 from go-kivik/missingDB
x/sqlite: Handle DB not found errors
2 parents 958ade9 + 764e41e commit 3d100d1

17 files changed

+232
-19
lines changed

pouchdb/find_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func TestExplain(t *testing.T) {
8080
tests.Add("query error", test{
8181
db: &db{db: bindings.GlobalPouchDB().New("foo", nil)},
8282
query: nil,
83-
err: "TypeError: Cannot read properties",
83+
err: "TypeError: Cannot read propert",
8484
})
8585
tests.Add("simple selector", func(t *testing.T) interface{} {
8686
options := map[string]interface{}{

x/sqlite/changes.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ func (d *db) newNormalChanges(ctx context.Context, opts optsMap, since, lastSeq
189189

190190
c.rows, err = d.db.QueryContext(ctx, query, args...) //nolint:rowserrcheck,sqlclosecheck // Err checked in Next
191191
if err != nil {
192-
return nil, err
192+
return nil, d.errDatabaseNotFound(err)
193193
}
194194

195195
// The first row is used to calculate the ETag; it's done as part of the

x/sqlite/createdoc.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (d *db) CreateDoc(ctx context.Context, doc interface{}, _ driver.Options) (
5252
)
5353
`), data.ID).Scan(&exists)
5454
if err != nil && !errors.Is(err, sql.ErrNoRows) {
55-
return "", "", err
55+
return "", "", d.errDatabaseNotFound(err)
5656
}
5757
if exists {
5858
return "", "", &kerrors.Error{Status: http.StatusConflict, Message: "document update conflict"}

x/sqlite/createdoc_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ func TestDBCreateDoc(t *testing.T) {
148148
})
149149
/*
150150
TODO:
151+
- nil doc
151152
- different UUID configuration options????
152153
- retry in case of duplicate random uuid ???
153154
- support batch mode?

x/sqlite/db.go

+19
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ package sqlite
1515
import (
1616
"context"
1717
"database/sql"
18+
"fmt"
1819
"log"
20+
"net/http"
1921

2022
"github.com/go-kivik/kivik/v4/driver"
23+
internal "github.com/go-kivik/kivik/v4/int/errors"
2124
)
2225

2326
type db struct {
@@ -43,6 +46,7 @@ func (d *db) Close() error {
4346
return d.db.Close()
4447
}
4548

49+
// TODO: I think Ping belongs on *client, not *db
4650
func (d *db) Ping(ctx context.Context) error {
4751
return d.db.PingContext(ctx)
4852
}
@@ -88,3 +92,18 @@ func (db) DeleteIndex(context.Context, string, string, driver.Options) error {
8892
func (db) Explain(context.Context, interface{}, driver.Options) (*driver.QueryPlan, error) {
8993
return nil, nil
9094
}
95+
96+
// errDatabaseNotFound converts a sqlite "no such table" error into a kivik
97+
// database not found error
98+
func (d *db) errDatabaseNotFound(err error) error {
99+
if err == nil {
100+
return nil
101+
}
102+
if errIsNoSuchTable(err) {
103+
return &internal.Error{
104+
Status: http.StatusNotFound,
105+
Message: fmt.Sprintf("database not found: %s", d.name),
106+
}
107+
}
108+
return err
109+
}

x/sqlite/db_test.go

+193
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,17 @@
1616
package sqlite
1717

1818
import (
19+
"context"
1920
"database/sql"
21+
"encoding/json"
22+
"net/http"
2023
"testing"
24+
25+
"gitlab.com/flimzy/testy"
26+
27+
"github.com/go-kivik/kivik/v4"
28+
"github.com/go-kivik/kivik/v4/driver"
29+
"github.com/go-kivik/kivik/v4/int/mock"
2130
)
2231

2332
type leaf struct {
@@ -52,3 +61,187 @@ func readRevisions(t *testing.T, db *sql.DB) []leaf {
5261
}
5362
return leaves
5463
}
64+
65+
func Test_db_not_found(t *testing.T) {
66+
t.Parallel()
67+
const (
68+
wantStatus = http.StatusNotFound
69+
wantErr = "database not found: db_not_found"
70+
)
71+
72+
type test struct {
73+
call func(*db) error
74+
}
75+
76+
tests := testy.NewTable()
77+
tests.Add("Changes", test{
78+
call: func(d *db) error {
79+
_, err := d.Changes(context.Background(), mock.NilOption)
80+
return err
81+
},
82+
})
83+
tests.Add("Changes, since=now", test{
84+
call: func(d *db) error {
85+
_, err := d.Changes(context.Background(), kivik.Param("since", "now"))
86+
return err
87+
},
88+
})
89+
tests.Add("Changes, longpoll", test{
90+
call: func(d *db) error {
91+
_, err := d.Changes(context.Background(), kivik.Param("feed", "longpoll"))
92+
return err
93+
},
94+
})
95+
tests.Add("CreateDoc", test{
96+
call: func(d *db) error {
97+
_, _, err := d.CreateDoc(context.Background(), map[string]string{}, mock.NilOption)
98+
return err
99+
},
100+
})
101+
tests.Add("DeleteAttachment", test{
102+
call: func(d *db) error {
103+
_, err := d.DeleteAttachment(context.Background(), "doc", "att", kivik.Rev("1-x"))
104+
return err
105+
},
106+
})
107+
tests.Add("Delete", test{
108+
call: func(d *db) error {
109+
_, err := d.Delete(context.Background(), "doc", kivik.Rev("1-x"))
110+
return err
111+
},
112+
})
113+
tests.Add("Find", test{
114+
call: func(d *db) error {
115+
_, err := d.Find(context.Background(), json.RawMessage(`{"selector":{}}`), mock.NilOption)
116+
return err
117+
},
118+
})
119+
tests.Add("GetAttachment", test{
120+
call: func(d *db) error {
121+
_, err := d.GetAttachment(context.Background(), "doc", "att", mock.NilOption)
122+
return err
123+
},
124+
})
125+
tests.Add("GetAttachmentMeta", test{
126+
call: func(d *db) error {
127+
_, err := d.GetAttachmentMeta(context.Background(), "doc", "att", mock.NilOption)
128+
return err
129+
},
130+
})
131+
tests.Add("Get", test{
132+
call: func(d *db) error {
133+
_, err := d.Get(context.Background(), "doc", mock.NilOption)
134+
return err
135+
},
136+
})
137+
tests.Add("GetRev", test{
138+
call: func(d *db) error {
139+
_, err := d.GetRev(context.Background(), "doc", mock.NilOption)
140+
return err
141+
},
142+
})
143+
tests.Add("OpenRevs", test{
144+
call: func(d *db) error {
145+
_, err := d.OpenRevs(context.Background(), "doc", nil, mock.NilOption)
146+
return err
147+
},
148+
})
149+
tests.Add("Purge", test{
150+
call: func(d *db) error {
151+
_, err := d.Purge(context.Background(), map[string][]string{"doc": {"1-x"}})
152+
return err
153+
},
154+
})
155+
tests.Add("PutAttachment", test{
156+
call: func(d *db) error {
157+
_, err := d.PutAttachment(context.Background(), "doc", &driver.Attachment{}, mock.NilOption)
158+
return err
159+
},
160+
})
161+
tests.Add("Put", test{
162+
call: func(d *db) error {
163+
_, err := d.Put(context.Background(), "doc", map[string]string{}, mock.NilOption)
164+
return err
165+
},
166+
})
167+
tests.Add("Put, new_edits=false", test{
168+
call: func(d *db) error {
169+
_, err := d.Put(context.Background(), "doc", map[string]any{
170+
"_rev": "1-x",
171+
}, kivik.Param("new_edits", false))
172+
return err
173+
},
174+
})
175+
tests.Add("Put, new_edits=false + _revisions", test{
176+
call: func(d *db) error {
177+
_, err := d.Put(context.Background(), "doc", map[string]any{
178+
"_revisions": map[string]interface{}{
179+
"start": 1,
180+
"ids": []string{"x"},
181+
},
182+
}, kivik.Param("new_edits", false))
183+
return err
184+
},
185+
})
186+
tests.Add("Put, _revisoins + new_edits=true", test{
187+
call: func(d *db) error {
188+
_, err := d.Put(context.Background(), "doc", map[string]any{
189+
"_revisions": map[string]interface{}{
190+
"start": 1,
191+
"ids": []string{"x", "y", "z"},
192+
},
193+
}, mock.NilOption)
194+
return err
195+
},
196+
})
197+
tests.Add("AllDocs", test{
198+
call: func(d *db) error {
199+
_, err := d.AllDocs(context.Background(), mock.NilOption)
200+
return err
201+
},
202+
})
203+
tests.Add("Query", test{
204+
call: func(d *db) error {
205+
_, err := d.Query(context.Background(), "ddoc", "view", mock.NilOption)
206+
return err
207+
},
208+
})
209+
tests.Add("Query, group=true", test{
210+
call: func(d *db) error {
211+
_, err := d.Query(context.Background(), "ddoc", "view", kivik.Param("group", true))
212+
return err
213+
},
214+
})
215+
tests.Add("RevsDiff", test{
216+
call: func(d *db) error {
217+
_, err := d.RevsDiff(context.Background(), map[string][]string{"doc": {"1-x"}})
218+
return err
219+
},
220+
})
221+
/*
222+
TODO:
223+
*/
224+
225+
tests.Run(t, func(t *testing.T, tt test) {
226+
t.Parallel()
227+
client, err := (drv{}).NewClient(":memory:", mock.NilOption)
228+
if err != nil {
229+
t.Fatal(err)
230+
}
231+
d, err := client.DB("db_not_found", mock.NilOption)
232+
if err != nil {
233+
t.Fatal(err)
234+
}
235+
236+
err = tt.call(d.(*db))
237+
if err == nil {
238+
t.Fatal("Expected error")
239+
}
240+
if wantErr != err.Error() {
241+
t.Errorf("Unexpected error: %s", err)
242+
}
243+
if status := kivik.HTTPStatus(err); status != wantStatus {
244+
t.Errorf("Unexpected status: %d", status)
245+
}
246+
})
247+
}

x/sqlite/delete.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (d *db) Delete(ctx context.Context, docID string, options driver.Options) (
4848

4949
data.MD5sum, err = d.isLeafRev(ctx, tx, docID, curRev.rev, curRev.id)
5050
if err != nil {
51-
return "", err
51+
return "", d.errDatabaseNotFound(err)
5252
}
5353
data.Deleted = true
5454
data.Doc = []byte("{}")

x/sqlite/deleteattachment.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func (d *db) DeleteAttachment(ctx context.Context, docID, filename string, optio
4545

4646
data.MD5sum, err = d.isLeafRev(ctx, tx, docID, curRev.rev, curRev.id)
4747
if err != nil {
48-
return "", err
48+
return "", d.errDatabaseNotFound(err)
4949
}
5050

5151
// Read list of current attachments, then remove the requested one

x/sqlite/getrev.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (d *db) getCoreDoc(ctx context.Context, tx queryer, id string, rev revision
133133
case errors.Is(err, sql.ErrNoRows) || deleted && rev.IsZero():
134134
return nil, revision{}, &internal.Error{Status: http.StatusNotFound, Message: "not found"}
135135
case err != nil:
136-
return nil, revision{}, err
136+
return nil, revision{}, d.errDatabaseNotFound(err)
137137
}
138138

139139
return &fullDoc{

x/sqlite/openrevs.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ func (d *db) OpenRevs(ctx context.Context, docID string, revs []string, options
167167
`), strings.Join(values, ", "))
168168
rows, err := d.db.QueryContext(ctx, query, args...) //nolint:rowserrcheck // Err checked in Next
169169
if err != nil {
170-
return nil, err
170+
return nil, d.errDatabaseNotFound(err)
171171
}
172172

173173
// Call rows.Next() to see if we get any results at all. If zero results,

x/sqlite/purge.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (d *db) Purge(ctx context.Context, request map[string][]string) (*driver.Pu
4444
// Non-leaf rev, do nothing
4545
continue
4646
default:
47-
return nil, err
47+
return nil, d.errDatabaseNotFound(err)
4848
}
4949
}
5050

x/sqlite/put.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func (d *db) Put(ctx context.Context, docID string, doc interface{}, options dri
6262
for _, r := range revs[:len(revs)-1] {
6363
err := stmt.QueryRowContext(ctx, data.ID, r.rev, r.id).Scan(&exists)
6464
if err != nil {
65-
return "", err
65+
return "", d.errDatabaseNotFound(err)
6666
}
6767
if !exists {
6868
return "", &internal.Error{Status: http.StatusConflict, Message: "document update conflict"}
@@ -101,7 +101,7 @@ func (d *db) Put(ctx context.Context, docID string, doc interface{}, options dri
101101
r := r
102102
_, err := stmt.ExecContext(ctx, data.ID, r.rev, r.id, parentRev, parentRevID)
103103
if err != nil {
104-
return "", err
104+
return "", d.errDatabaseNotFound(err)
105105
}
106106
parentRev = &r.rev
107107
parentRevID = &r.id
@@ -121,7 +121,7 @@ func (d *db) Put(ctx context.Context, docID string, doc interface{}, options dri
121121
ON CONFLICT DO NOTHING
122122
`), docID, rev.rev, rev.id)
123123
if err != nil {
124-
return "", err
124+
return "", d.errDatabaseNotFound(err)
125125
}
126126
}
127127
var newRev string
@@ -163,7 +163,7 @@ func (d *db) Put(ctx context.Context, docID string, doc interface{}, options dri
163163
return "", &internal.Error{Status: http.StatusConflict, Message: "document update conflict"}
164164
}
165165
case err != nil:
166-
return "", err
166+
return "", d.errDatabaseNotFound(err)
167167
}
168168

169169
r, err := d.createRev(ctx, tx, data, curRev)

x/sqlite/putattachment.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func (d *db) PutAttachment(ctx context.Context, docID string, att *driver.Attach
5353
// This means the doc is being created, and is empty other than the attachment
5454
data.Doc = []byte("{}")
5555
case err != nil:
56-
return "", err
56+
return "", d.errDatabaseNotFound(err)
5757
}
5858

5959
content, err := io.ReadAll(att.Content)

x/sqlite/query.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func (d *db) performQuery(
107107
for {
108108
rev, err := d.updateIndex(ctx, ddoc, view, vopts.update)
109109
if err != nil {
110-
return nil, err
110+
return nil, d.errDatabaseNotFound(err)
111111
}
112112

113113
args := []interface{}{
@@ -304,7 +304,7 @@ func (d *db) performGroupQuery(ctx context.Context, ddoc, view string, vopts *vi
304304
for {
305305
rev, err = d.updateIndex(ctx, ddoc, view, vopts.update)
306306
if err != nil {
307-
return nil, err
307+
return nil, d.errDatabaseNotFound(err)
308308
}
309309

310310
args := []any{"_design/" + ddoc, rev.rev, rev.id, view, kivik.EndKeySuffix, true, vopts.updateSeq}

x/sqlite/revsdiff.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func (d *db) RevsDiff(ctx context.Context, revMap interface{}) (driver.Rows, err
6767

6868
rows, err := d.db.QueryContext(ctx, query, ids...) //nolint:rowserrcheck // Err checked in Next
6969
if err != nil {
70-
return nil, err
70+
return nil, d.errDatabaseNotFound(err)
7171
}
7272

7373
return &revsDiffResponse{

0 commit comments

Comments
 (0)