Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/providers/redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,10 @@ on a leading `{` as §3.4 does, and asks before running any command in its Redis
vocabulary - key, expiry, string, hash, list, set, sorted-set and stream writes, the scripting
entry points, and the server and access commands, with container commands such as `CONFIG SET`
matched on their two-token spelling - while a body it cannot read (broken JSON, a JSON body whose
`command` is not a string) asks rather than staying silent.
`command` is not a string) asks rather than staying silent. The vocabulary names only commands the
provider runs: the blocking list and sorted-set pops are refused before they reach the server
([§5.2b](#52b-commands-that-would-change-the-shared-connection-1107)), so their non-blocking
forms are the ones the gate asks about.

### 3.5 Reply normalisation into the shared grid

Expand Down
17 changes: 8 additions & 9 deletions src/lib/db/destructive-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@ const MONGODB_DESTRUCTIVE_OPERATIONS: ReadonlySet<string> = new Set([
* `CLUSTER SHARDS` are the reads); every form of it reassigns a slot, which is what
* puts it beside `CLUSTER RESET` and `CLUSTER FORGET`.
*
* Every name here can reach the server: `runCommand` calls
* Every name here is a command the provider runs: `query()` refuses some commands
* before they reach the server (`sharedConnectionRefusal` in
* `src/lib/db/providers/keyvalue/redis.ts`, documented in section 5.2b of
* `docs/providers/redis.md`), and a confirmation followed by that refusal would be
* the double take this gate exists to avoid. The blocking list and sorted-set pops
* (`BLPOP`, `BZPOPMIN` and the rest of the `B` forms) are refused that way, so only their
* non-blocking forms (`LPOP`, `RPOP`, `LMPOP`, `LMOVE`, `RPOPLPUSH`, `ZPOPMIN`,
* `ZPOPMAX`, `ZMPOP`) are here. `runCommand` itself calls
* `client.call(command, ...args)` with no allow-list of any kind, so the vocabulary
* is bounded by what Redis accepts rather than by what this provider implements.
* Container commands are spelled with their subcommand (`CONFIG SET`), which the
Expand Down Expand Up @@ -133,16 +140,11 @@ const REDIS_DESTRUCTIVE_COMMANDS: ReadonlySet<string> = new Set([
"LPOP",
"RPOP",
"LMPOP",
"BLPOP",
"BRPOP",
"BLMPOP",
"LSET",
"LREM",
"LTRIM",
"LMOVE",
"BLMOVE",
"RPOPLPUSH",
"BRPOPLPUSH",
// Sets
"SPOP",
"SREM",
Expand All @@ -157,10 +159,7 @@ const REDIS_DESTRUCTIVE_COMMANDS: ReadonlySet<string> = new Set([
"ZREMRANGEBYLEX",
"ZPOPMIN",
"ZPOPMAX",
"BZPOPMIN",
"BZPOPMAX",
"ZMPOP",
"BZMPOP",
"ZUNIONSTORE",
"ZINTERSTORE",
"ZDIFFSTORE",
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/db/destructive-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,36 @@ describe("isDestructiveNonSqlQuery", () => {
expect(isDestructiveNonSqlQuery(query, "redis")).toBe(true);
});

test.each<[string]>([
["BLPOP queue 0"],
["BRPOP queue 0"],
["BLMPOP 2 2 queue LEFT COUNT 1"],
["BLMOVE src dst LEFT RIGHT 0"],
["BRPOPLPUSH src dst 0"],
["BZPOPMIN z 0"],
["BZPOPMAX z 0"],
["BZMPOP 2 1 z MIN COUNT 1"],
])("does not ask before the Redis command %s, which the provider refuses before it reaches the server", (query) => {
// Since #1121 `RedisProvider.query()` refuses the blocking commands through
// `sharedConnectionRefusal`, so a confirmation followed by a refusal is the
// double take this gate exists to avoid. The dialog is for commands that run.
expect(isDestructiveNonSqlQuery(query, "redis")).toBe(false);
});

test.each<[string]>([
["LPOP queue"],
["RPOP queue"],
["LMPOP 1 queue LEFT COUNT 1"],
["LMOVE src dst LEFT RIGHT"],
["RPOPLPUSH src dst"],
["ZPOPMIN z"],
["ZPOPMAX z"],
["ZMPOP 1 z MIN COUNT 1"],
])("still asks before %s, the non-blocking form of a refused command", (query) => {
// The provider runs these, and each one removes what it returns.
expect(isDestructiveNonSqlQuery(query, "redis")).toBe(true);
});

test.each<[string]>([
["GET k"],
["HGETALL user:1"],
Expand Down
Loading