Skip to content

mget does not return the correct number of returns if values are undefined or the keys are not in alphabetical order #2

@eligundry

Description

@eligundry

Hi, huge fan of your library. I went ahead and forked it to meet the following needs:

  • Make it work with cache-manager@5, which simplified the storage contract to be less variadic and more predictable. This doesn't appear to be backwards compatible.
  • Converted it to Typescript so that I can get code completion when passing it to cacheManager.caching.
  • Swapped in better-sqlite3 for sqlite3 as the former is "faster" and the latter uses a node-gyp fork that has bunch of testing required into code that is shipped with the library which breaks builds in an Astro / Vite setup that I'm working in.

Feel free to port/steal whatever you would like for this library! Open source!

In doing the porting, I noticed a bug in your implementation of mget. The SELECT statement for this is just an IN query which will return unpredictably if keys do not exist OR the keys are not provided in alphabetical order. In practice, the following have issues from what I can tell:

await cache.set('a', 'a')
await cache.set('b', 'b')
await cache.set('z', 'z')

// This mget has a gap at position 'c' and it should return ['a', 'b', undefined, 'z'] but it returns ['a', 'b', 'z']
let [a, b, c, z] = await cache.mget('a', 'b', 'c', 'z')
// this will actually equal 'z'
assert(c === undefined)
// this is undefined as the array is only 3 elements long
assert(z === 'z')

// This mget returns items in reverse order and should be ['z', 'b', 'a'] but it returns ['a', 'b', 'z']
let [z, b, a] = await cache.mget('z', 'b', 'a')
// this will actually equal 'a'
assert(z === 'z')
// this will actually equal 'z'
assert(a === 'a')

The way I got around this is by using a varadic CTE that correctly orders and fills in the gaps in the query with almost equal complexity to an IN statement you have.

> explain query plan WITH getKeys(key) AS (VALUES ("a"), ("b"), ("c"), ("z"))
SELECT
  getKeys.key,
  val,
  created_at,
  expire_at
FROM getKeys
LEFT JOIN kv ON kv.key = getKeys.key;

QUERY PLAN
|--CO-ROUTINE getKeys
|  `--SCAN 3 CONSTANT ROWS
|--SCAN getKeys
`--SEARCH kv USING INDEX sqlite_autoindex_kv_1 (key=?) LEFT-JOIN

> explain query plan select * from kv where key in ("a", "b", "c", "z");

QUERY PLAN
`--SEARCH kv USING INDEX sqlite_autoindex_kv_1 (key=?)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions