Conversation
…y, decrBy, incrByFloat Co-authored-by: sj817 <74231782+sj817@users.noreply.github.com>
… implementations Co-authored-by: sj817 <74231782+sj817@users.noreply.github.com>
|
你可以通过以下命令安装该版本: |
There was a problem hiding this comment.
Pull Request Overview
补充并完善 mock Redis 客户端缺失的常用命令,实现与真实 Redis API 更高的兼容性,并修复过期管理方法错误仅限 HyperLogLog 的问题。
- 新增字符串、过期控制、键管理、数值操作等 18 个常用方法
- 修复 pExpire/pTTL/pExpireAt 仅适用于 HyperLogLog 的错误实现
- 增强键重命名与过期处理、随机键、数据库统计等辅助能力
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| async setNX (key: string, value: string | Buffer): Promise<boolean> { | ||
| if (this.store[key] && !this.checkExpire(key)) { | ||
| return false | ||
| } | ||
| await this.set(key, value, { NX: true }) | ||
| return true |
There was a problem hiding this comment.
The return type deviates from Redis semantics; SETNX returns an integer reply (1 if a key was set, 0 otherwise) rather than a boolean. To maintain API compatibility, change the return type to Promise and return 1/0.
| async setNX (key: string, value: string | Buffer): Promise<boolean> { | |
| if (this.store[key] && !this.checkExpire(key)) { | |
| return false | |
| } | |
| await this.set(key, value, { NX: true }) | |
| return true | |
| async setNX (key: string, value: string | Buffer): Promise<number> { | |
| if (this.store[key] && !this.checkExpire(key)) { | |
| return 0 | |
| } | |
| await this.set(key, value, { NX: true }) | |
| return 1 |
| async rename (key: string, newKey: string): Promise<string> { | ||
| if (!this.store[key]) throw new Error('no such key') | ||
|
|
||
| // 获取原键的数据 | ||
| const { type, expire } = this.store[key] | ||
| const value = this.getValueStringByKey(key) | ||
|
|
||
| // 删除旧键 | ||
| this.#del(key) | ||
|
|
||
| // 创建新键 | ||
| this.store[newKey] = { type, expire } | ||
|
|
||
| // 根据类型设置值 | ||
| switch (type) { | ||
| case Key.STR: | ||
| this.#str[newKey] = value | ||
| break | ||
| case Key.NUM: | ||
| this.#num[newKey] = Number(value) | ||
| break | ||
| case Key.HASH: | ||
| this.#hash[newKey] = JSON.parse(value) | ||
| break | ||
| case Key.LIST: | ||
| this.#list[newKey] = JSON.parse(value) | ||
| break | ||
| case Key.SET: | ||
| this.#set[newKey] = new Set(JSON.parse(value)) | ||
| break | ||
| case Key.ZSET: | ||
| this.#zset[newKey] = JSON.parse(value) | ||
| break | ||
| case Key.PF: | ||
| this.#pf[newKey] = new Set(JSON.parse(value)) | ||
| break | ||
| case Key.BIT: | ||
| this.#bit[newKey] = Buffer.from(value, 'base64') | ||
| break | ||
| } | ||
|
|
||
| this.#sqlite.set(newKey, value, type, expire) | ||
| return 'OK' |
There was a problem hiding this comment.
When newKey already exists, Redis RENAME overwrites the destination, but this implementation does not remove existing type-specific data for newKey before assignment, potentially leaving orphaned data (e.g., stale this.#hash[newKey]). Add a pre-step to delete newKey (if different from key) via this.#del(newKey) to avoid inconsistent internal state, and optionally guard key === newKey to mimic Redis error semantics.
| async renameNX (key: string, newKey: string): Promise<number> { | ||
| if (!this.store[key]) return 0 | ||
| if (this.store[newKey] && !this.checkExpire(newKey)) return 0 | ||
|
|
||
| await this.rename(key, newKey) | ||
| return 1 | ||
| } |
There was a problem hiding this comment.
renameNX should fail (return 0) if key === newKey per Redis semantics (identical source/destination not allowed), but current logic delegates to rename and returns 1. Add an early check if (key === newKey) return 0 (or throw) for fidelity.
| async incrByFloat (key: string, increment: number): Promise<number> { | ||
| if (!this.#num[key]) { | ||
| this.#num[key] = 0 | ||
| this.store[key] = { type: Key.NUM, expire: -1 } | ||
| } else if (this.checkExpire(key, false)) { | ||
| this.store[key].expire = -1 | ||
| this.#num[key] = 0 | ||
| } | ||
|
|
||
| this.#num[key] += increment | ||
| this.#sqlite.set(key, String(this.#num[key]), Key.NUM, this.store[key].expire) | ||
| return this.#num[key] |
There was a problem hiding this comment.
INCRBYFLOAT in Redis returns the new value as a bulk string; returning a number here introduces an incompatibility for consumers expecting string output. Consider returning String(this.#num[key]) (and adjusting the return type) to align with Redis behavior.
问题描述
使用模拟的 Redis 客户端时遇到错误:
TypeError: redis.setEx is not a function这是因为 mock Redis 客户端实现不完整,缺少
setEx及其他常用的 Redis 命令。解决方案
本 PR 为 mock Redis 客户端补充了 18 个常用的 Redis 方法,使其更接近真实 Redis 客户端的 API。
主要新增方法
字符串操作与过期时间设置:
setEx(key, seconds, value)- 设置键值对并指定过期时间(秒)[修复原问题]pSetEx(key, milliseconds, value)- 设置键值对并指定过期时间(毫秒)setNX(key, value)- 仅当键不存在时设置getEx(key, options)- 获取值并可选更新过期时间getDel(key)- 原子性地获取并删除键strLen(key)- 获取字符串长度数值操作:
incrBy(key, increment)- 按指定值自增decrBy(key, decrement)- 按指定值自减incrByFloat(key, increment)- 按浮点数自增过期时间管理:
expireAt(key, timestamp)- 设置到期时间戳(秒)pExpire(key, milliseconds)- 设置过期时间(毫秒)pExpireAt(key, timestamp)- 设置到期时间戳(毫秒)pTTL(key)- 获取剩余生存时间(毫秒)persist(key)- 移除键的过期时间键管理工具:
rename(key, newKey)- 重命名键renameNX(key, newKey)- 仅当新键不存在时重命名dbSize()- 返回数据库中键的数量randomKey()- 随机返回一个键Bug 修复
修复了以下三个方法的错误实现,它们之前被错误地实现为仅支持 HyperLogLog 类型:
pExpire- 现在支持所有键类型pTTL- 现在支持所有键类型pExpireAt- 现在支持所有键类型技术细节
所有新增方法:
测试验证
已验证:
影响范围
此更改仅影响 mock Redis 客户端 (
packages/core/src/core/db/redis/mock/index.ts),不影响真实 Redis 连接的行为。现有代码完全向后兼容。Warning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
registry.npmmirror.comnode /home/REDACTED/.npm/_npx/4876f32048baf8ac/node_modules/.bin/prebuild-install -r napi --pkg_version=5.1.7 --pkg_name=sqlite3(dns block)node /home/REDACTED/.npm/_npx/c65dbefa27d408e0/node_modules/.bin/prebuild-install -r node --pkg_version=0.12.0 --pkg_name=node-pty-prebuilt-multiarch(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.