Skip to content
This repository was archived by the owner on May 25, 2025. It is now read-only.

Commit d85ff72

Browse files
authored
feat: add incrementBatch support (#17)
1 parent 54add95 commit d85ff72

File tree

7 files changed

+131
-43
lines changed

7 files changed

+131
-43
lines changed

src/redisClient.manual.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@ afterAll(async () => {
1616
await client.disconnect()
1717
})
1818

19+
test('incrBatch should increase multiple keys', async () => {
20+
await client.set('test:one', 1)
21+
await client.set('test:two', 2)
22+
23+
const result = await client.incrBatch([
24+
['test:one', 1],
25+
['test:two', 2],
26+
])
27+
28+
expect(result).toEqual([
29+
['test:one', 2],
30+
['test:two', 4],
31+
])
32+
})
33+
1934
describe('hashmap functions', () => {
2035
test('hset should save a map', async () => {
2136
await client.hset('test:key', { foo: 'bar' })
@@ -95,6 +110,20 @@ describe('hashmap functions', () => {
95110
expect(result).toBe(1)
96111
})
97112

113+
test('hincrBatch should increase multiple keys', async () => {
114+
await client.hset('test:key', { one: 1, two: 2 })
115+
116+
const result = await client.hincrBatch('test:key', [
117+
['one', 1],
118+
['two', 2],
119+
])
120+
121+
expect(result).toEqual([
122+
['one', 2],
123+
['two', 4],
124+
])
125+
})
126+
98127
test('hscanCount should return the number of keys in the hash', async () => {
99128
await client.hset('test:key', { one: 1, two: 2, three: 3 })
100129

@@ -120,7 +149,7 @@ describe('hashmap functions', () => {
120149
expect(result).toEqual({ one: '1' })
121150
})
122151

123-
test('hsetWithTTL should set the fields with expiry', async () => {
152+
test.skip('hsetWithTTL should set the fields with expiry', async () => {
124153
const now = localTime.now().unix
125154

126155
await client.hsetWithTTL('test:key', { foo1: 'bar' }, now + 1000)

src/redisClient.ts

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {
2+
_stringMapEntries,
23
AnyObject,
34
CommonLogger,
45
NullableBuffer,
56
NullableString,
67
Promisable,
8+
StringMap,
79
UnixTimestampNumber,
810
} from '@naturalcycles/js-lib'
911
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
@@ -157,6 +159,25 @@ export class RedisClient implements CommonClient {
157159
return await this.redis().hincrby(key, field, increment)
158160
}
159161

162+
async hincrBatch(key: string, incrementTuples: [string, number][]): Promise<[string, number][]> {
163+
const results: StringMap<number | undefined> = {}
164+
165+
await this.withPipeline(async pipeline => {
166+
for (const [field, increment] of incrementTuples) {
167+
pipeline.hincrby(key, field, increment, (_err, newValue) => {
168+
results[field] = newValue
169+
})
170+
}
171+
})
172+
173+
const validResults = _stringMapEntries(results).filter(([_, v]) => v !== undefined) as [
174+
string,
175+
number,
176+
][]
177+
178+
return validResults
179+
}
180+
160181
async setWithTTL(
161182
key: string,
162183
value: string | number | Buffer,
@@ -165,14 +186,19 @@ export class RedisClient implements CommonClient {
165186
await this.redis().set(key, value, 'EXAT', expireAt)
166187
}
167188

168-
async hsetWithTTL(key: string, value: AnyObject, expireAt: UnixTimestampNumber): Promise<void> {
169-
const valueKeys = Object.keys(value)
170-
const numberOfKeys = valueKeys.length
171-
const keyList = valueKeys.join(' ')
172-
const commandString = `HEXPIREAT ${key} ${expireAt} FIELDS ${numberOfKeys} ${keyList}`
173-
const [command, ...args] = commandString.split(' ')
174-
await this.redis().hset(key, value)
175-
await this.redis().call(command!, args)
189+
async hsetWithTTL(
190+
_key: string,
191+
_value: AnyObject,
192+
_expireAt: UnixTimestampNumber,
193+
): Promise<void> {
194+
throw new Error('Not supported until Redis 7.4.0')
195+
// const valueKeys = Object.keys(value)
196+
// const numberOfKeys = valueKeys.length
197+
// const keyList = valueKeys.join(' ')
198+
// const commandString = `HEXPIREAT ${key} ${expireAt} FIELDS ${numberOfKeys} ${keyList}`
199+
// const [command, ...args] = commandString.split(' ')
200+
// await this.redis().hset(key, value)
201+
// await this.redis().call(command!, args)
176202
}
177203

178204
async mset(obj: Record<string, string | number>): Promise<void> {
@@ -187,6 +213,25 @@ export class RedisClient implements CommonClient {
187213
return await this.redis().incrby(key, by)
188214
}
189215

216+
async incrBatch(incrementTuples: [string, number][]): Promise<[string, number][]> {
217+
const results: StringMap<number | undefined> = {}
218+
219+
await this.withPipeline(async pipeline => {
220+
for (const [key, increment] of incrementTuples) {
221+
pipeline.incrby(key, increment, (_err, newValue) => {
222+
results[key] = newValue
223+
})
224+
}
225+
})
226+
227+
const validResults = _stringMapEntries(results).filter(([_, v]) => v !== undefined) as [
228+
string,
229+
number,
230+
][]
231+
232+
return validResults
233+
}
234+
190235
async ttl(key: string): Promise<number> {
191236
return await this.redis().ttl(key)
192237
}

src/redisHashKeyValueDB.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
CommonKeyValueDB,
44
commonKeyValueDBFullSupport,
55
CommonKeyValueDBSaveBatchOptions,
6+
IncrementTuple,
67
KeyValueDBTuple,
78
} from '@naturalcycles/db-lib'
89
import { _chunk, StringMap } from '@naturalcycles/js-lib'
@@ -121,15 +122,18 @@ export class RedisHashKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
121122
})
122123
}
123124

124-
async increment(table: string, id: string, by = 1): Promise<number> {
125-
return await this.client.hincr(this.keyOfHashField, this.idToKey(table, id), by)
126-
}
127-
128-
async incrementBatch(
129-
_table: string,
130-
_incrementMap: StringMap<number>,
131-
): Promise<StringMap<number>> {
132-
throw new Error('Not implemented')
125+
async incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]> {
126+
const incrementTuplesWithInternalKeys = increments.map(
127+
([id, v]) => [this.idToKey(table, id), v] as [string, number],
128+
)
129+
const resultsWithInternalKeys = await this.client.hincrBatch(
130+
this.keyOfHashField,
131+
incrementTuplesWithInternalKeys,
132+
)
133+
const results = resultsWithInternalKeys.map(
134+
([k, v]) => [this.keyToId(table, k), v] as IncrementTuple,
135+
)
136+
return results
133137
}
134138

135139
async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {

src/redisKeyValueDB.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import {
33
CommonKeyValueDB,
44
commonKeyValueDBFullSupport,
55
CommonKeyValueDBSaveBatchOptions,
6+
IncrementTuple,
67
KeyValueDBTuple,
78
} from '@naturalcycles/db-lib'
8-
import { _isTruthy, _zip, StringMap } from '@naturalcycles/js-lib'
9+
import { _isTruthy, _zip } from '@naturalcycles/js-lib'
910
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
1011
import { RedisClient } from './redisClient'
1112

@@ -123,15 +124,15 @@ export class RedisKeyValueDB implements CommonKeyValueDB, AsyncDisposable {
123124
})
124125
}
125126

126-
async increment(table: string, id: string, by = 1): Promise<number> {
127-
return await this.client.incr(this.idToKey(table, id), by)
128-
}
129-
130-
async incrementBatch(
131-
_table: string,
132-
_incrementMap: StringMap<number>,
133-
): Promise<StringMap<number>> {
134-
throw new Error('Not implemented')
127+
async incrementBatch(table: string, increments: IncrementTuple[]): Promise<IncrementTuple[]> {
128+
const incrementTuplesWithInternalKeys = increments.map(
129+
([id, v]) => [this.idToKey(table, id), v] as [string, number],
130+
)
131+
const resultsWithInternalKeys = await this.client.incrBatch(incrementTuplesWithInternalKeys)
132+
const results = resultsWithInternalKeys.map(
133+
([k, v]) => [this.keyToId(table, k), v] as IncrementTuple,
134+
)
135+
return results
135136
}
136137

137138
async createTable(table: string, opt?: CommonDBCreateOptions): Promise<void> {

src/test/redis.hash.manual.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { CommonKeyValueDao } from '@naturalcycles/db-lib'
21
import {
32
runCommonKeyValueDaoTest,
43
runCommonKeyValueDBTest,
@@ -12,7 +11,6 @@ import { RedisHashKeyValueDB } from '../redisHashKeyValueDB'
1211
const client = new RedisClient()
1312
const hashKey = 'hashField'
1413
const db = new RedisHashKeyValueDB({ client, hashKey })
15-
const dao = new CommonKeyValueDao<Buffer>({ db, table: TEST_TABLE })
1614

1715
afterAll(async () => {
1816
await client.disconnect()
@@ -23,9 +21,9 @@ test('connect', async () => {
2321
})
2422

2523
describe('runCommonHashKeyValueDBTest', () => runCommonKeyValueDBTest(db))
26-
describe('runCommonKeyValueDaoTest', () => runCommonKeyValueDaoTest(dao))
24+
describe('runCommonKeyValueDaoTest', () => runCommonKeyValueDaoTest(db))
2725

28-
test('saveBatch with EXAT', async () => {
26+
test.skip('saveBatch with EXAT', async () => {
2927
const testIds = _range(1, 4).map(n => `id${n}`)
3028
const testEntries: KeyValueDBTuple[] = testIds.map(id => [id, Buffer.from(`${id}value`)])
3129

src/test/redis.manual.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { RedisKeyValueDB } from '../redisKeyValueDB'
99
const client = new RedisClient()
1010
const db = new RedisKeyValueDB({ client })
1111

12-
const dao = new CommonKeyValueDao<Buffer>({
12+
const dao = new CommonKeyValueDao<string, Buffer>({
1313
db,
1414
table: TEST_TABLE,
1515
})
@@ -23,8 +23,7 @@ test('connect', async () => {
2323
})
2424

2525
describe('runCommonKeyValueDBTest', () => runCommonKeyValueDBTest(db))
26-
27-
describe('runCommonKeyValueDaoTest', () => runCommonKeyValueDaoTest(dao))
26+
describe('runCommonKeyValueDaoTest', () => runCommonKeyValueDaoTest(db))
2827

2928
test('saveBatch with EXAT', async () => {
3029
const testIds = _range(1, 4).map(n => `id${n}`)

yarn.lock

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,9 +1007,9 @@
10071007
typescript "^5.0.2"
10081008

10091009
"@naturalcycles/db-lib@^9.9.2":
1010-
version "9.22.0"
1011-
resolved "https://registry.yarnpkg.com/@naturalcycles/db-lib/-/db-lib-9.22.0.tgz#d01153b033e22155ade705aa049fe089978c5798"
1012-
integrity sha512-989fWQqlfMrtoaKxzqWN2Eh7Y7MrJcqoq5wl7Uldm84eUe3OUY87H0BYgGr/1kO309l2EuzhEkkEzcuO9QyKJw==
1010+
version "9.23.1"
1011+
resolved "https://registry.yarnpkg.com/@naturalcycles/db-lib/-/db-lib-9.23.1.tgz#0bdcb67032762755c75c75d07529dea4f1c20225"
1012+
integrity sha512-yQQwC8g21eQTstDSuiob1sKyJ6d6pgkP8tZ3NPqX/Sk91LXX0MZb3dqq7djh1o0nqV6etjBG1stM+ZszHBd/0w==
10131013
dependencies:
10141014
"@naturalcycles/js-lib" "^14.116.0"
10151015
"@naturalcycles/nodejs-lib" "^13.1.1"
@@ -1261,7 +1261,14 @@
12611261
dependencies:
12621262
"@types/node" "*"
12631263

1264-
"@types/node@*", "@types/node@^22.0.0", "@types/node@^22.7.5":
1264+
"@types/node@*":
1265+
version "22.7.6"
1266+
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.6.tgz#3ec3e2b071e136cd11093c19128405e1d1f92f33"
1267+
integrity sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==
1268+
dependencies:
1269+
undici-types "~6.19.2"
1270+
1271+
"@types/node@^22.0.0", "@types/node@^22.7.5":
12651272
version "22.7.5"
12661273
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b"
12671274
integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==
@@ -2391,9 +2398,9 @@ fast-levenshtein@^2.0.6:
23912398
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
23922399

23932400
fast-uri@^3.0.1:
2394-
version "3.0.2"
2395-
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.2.tgz#d78b298cf70fd3b752fd951175a3da6a7b48f024"
2396-
integrity sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==
2401+
version "3.0.3"
2402+
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241"
2403+
integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==
23972404

23982405
fastq@^1.6.0:
23992406
version "1.17.1"
@@ -4480,7 +4487,12 @@ ts-node@^10.0.0:
44804487
v8-compile-cache-lib "^3.0.1"
44814488
yn "3.1.1"
44824489

4483-
tslib@^2.0.0, tslib@^2.6.2, tslib@^2.6.3:
4490+
tslib@^2.0.0:
4491+
version "2.8.0"
4492+
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.0.tgz#d124c86c3c05a40a91e6fdea4021bd31d377971b"
4493+
integrity sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==
4494+
4495+
tslib@^2.6.2, tslib@^2.6.3:
44844496
version "2.7.0"
44854497
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
44864498
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==

0 commit comments

Comments
 (0)