Skip to content

Commit

Permalink
Merge pull request #1002 from go-kivik/moreCacheTests
Browse files Browse the repository at this point in the history
A couple more tests for caching logic
  • Loading branch information
flimzy committed Jun 19, 2024
2 parents d30346c + 935d478 commit 64884f9
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 7 deletions.
23 changes: 18 additions & 5 deletions x/sqlite/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,11 @@ func (d *db) performQuery(
FROM (
SELECT
"", -- id
first_key,
value,
first_pk,
last_pk,
last_key,
cache.first_key,
cache.value,
cache.first_pk,
cache.last_pk,
cache.last_key,
0, -- attachment_count
NULL, -- filename
NULL, -- content_type
Expand All @@ -188,6 +188,19 @@ func (d *db) performQuery(
NULL, -- rev_pos
NULL -- data
FROM cache
LEFT JOIN (
SELECT
c1.first_key,
c1.first_pk,
c1.last_key,
c1.last_pk
FROM cache AS c1
INNER JOIN cache AS c2
-- todo: consider pk columns
ON NOT (c2.first_key BETWEEN c1.first_key AND c1.last_key
AND c2.last_key BETWEEN c1.first_key AND c1.last_key)
) AS winning ON cache.first_key = winning.first_key AND cache.last_key = winning.last_key
WHERE winning.first_key IS NULL
)
UNION ALL
Expand Down
134 changes: 132 additions & 2 deletions x/sqlite/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2781,13 +2781,143 @@ func TestDBQuery(t *testing.T) {
},
}
})
tests.Add("cache entry created with key, used for range", func(t *testing.T) interface{} {
d := newDB(t)
_ = d.tPut("_design/foo", map[string]interface{}{
"views": map[string]interface{}{
"bar": map[string]string{
"map": `function(doc) {
emit(doc.key, [1]);
}`,
"reduce": `_count`,
},
},
})
_ = d.tPut("a", map[string]interface{}{"key": "a"})
_ = d.tPut("b", map[string]interface{}{"key": "a"})
_ = d.tPut("c", map[string]interface{}{"key": "a"})
_ = d.tPut("d", map[string]interface{}{"key": "b"})

db := d.underlying()
var table string
if err := db.QueryRow(`
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name LIKE '%_%_reduce_%'
`).Scan(&table); err != nil {
t.Fatalf("Failed to find reduced table: %s", err)
}
if _, err := db.Exec(fmt.Sprintf(`
INSERT INTO %q (seq, depth, first_key, first_pk, last_key, last_pk, value)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`, table), 5, 0, `"a"`, 1, `"a"`, 3, "3"); err != nil {
t.Fatalf("Failed to insert reduced value: %s", err)
}

return test{
db: d,
ddoc: "_design/foo",
view: "_view/bar",
want: []rowResult{{Key: `null`, Value: `4`}},
wantCache: []reduced{
{Seq: 5, Depth: 0, FirstKey: `"a"`, FirstPK: 1, LastKey: `"a"`, LastPK: 3, Value: "3"},
{Seq: 5, Depth: 0, FirstKey: `"a"`, FirstPK: 1, LastKey: `"b"`, LastPK: 4, Value: "4"},
{Seq: 5, Depth: 0, FirstKey: `"b"`, FirstPK: 4, LastKey: `"b"`, LastPK: 4, Value: "1"},
},
}
})
tests.Add("non-inclusive end caches properly", func(t *testing.T) interface{} {
d := newDB(t)
_ = d.tPut("_design/foo", map[string]interface{}{
"views": map[string]interface{}{
"bar": map[string]string{
"map": `function(doc) {
emit(doc.key, [1]);
}`,
"reduce": `_count`,
},
},
})
_ = d.tPut("a", map[string]interface{}{"key": "a"})
_ = d.tPut("b", map[string]interface{}{"key": "a"})
_ = d.tPut("c", map[string]interface{}{"key": "a"})
_ = d.tPut("d", map[string]interface{}{"key": "b"})

return test{
db: d,
ddoc: "_design/foo",
view: "_view/bar",
options: kivik.Params(map[string]interface{}{
"endkey": "b",
"inclusive_end": false,
}),
want: []rowResult{{Key: `null`, Value: `3`}},
wantCache: []reduced{
{Seq: 5, Depth: 0, FirstKey: `"a"`, FirstPK: 1, LastKey: `"a"`, LastPK: 3, Value: "3"},
},
}
})
tests.Add("one cache entry contained within other", func(t *testing.T) interface{} {
d := newDB(t)
_ = d.tPut("_design/foo", map[string]interface{}{
"views": map[string]interface{}{
"bar": map[string]string{
"map": `function(doc) {
emit(doc._id, [1]);
}`,
"reduce": `_count`,
},
},
})
_ = d.tPut("a", map[string]interface{}{})
_ = d.tPut("b", map[string]interface{}{})
_ = d.tPut("c", map[string]interface{}{})
_ = d.tPut("d", map[string]interface{}{})

db := d.underlying()
var table string
if err := db.QueryRow(`
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name LIKE '%_%_reduce_%'
`).Scan(&table); err != nil {
t.Fatalf("Failed to find reduced table: %s", err)
}
if _, err := db.Exec(fmt.Sprintf(`
INSERT INTO %q (seq, depth, first_key, first_pk, last_key, last_pk, value)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`, table), 5, 0, `"a"`, 1, `"d"`, 4, "4"); err != nil {
t.Fatalf("Failed to insert reduced value: %s", err)
}
if _, err := db.Exec(fmt.Sprintf(`
INSERT INTO %q (seq, depth, first_key, first_pk, last_key, last_pk, value)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`, table), 5, 0, `"b"`, 2, `"b"`, 2, "1"); err != nil {
t.Fatalf("Failed to insert reduced value: %s", err)
}

return test{
db: d,
ddoc: "_design/foo",
view: "_view/bar",
want: []rowResult{{Key: `null`, Value: `4`}},
wantCache: []reduced{
{Seq: 5, Depth: 0, FirstKey: `"a"`, FirstPK: 1, LastKey: `"d"`, LastPK: 4, Value: "4"},
{Seq: 5, Depth: 0, FirstKey: `"b"`, FirstPK: 2, LastKey: `"b"`, LastPK: 2, Value: "1"},
},
}
})

/*
TODO:
- add support for ASCII collation to facilitate testing
- overlapping, not contained, cache entries
- cache invalidated by new document or deleted document
- cache invidual keys with keys=[...] + reduce/group
- test sub-key cache entry boundaries
- reduce cache
- caches created with key, used for range
- inclusive vs non-inclusive end
- different depths
- competing cache depths
- gaps in cache results
Expand Down

0 comments on commit 64884f9

Please sign in to comment.