fix(redis): version detection logic (#11815)

Co-authored-by: AdolfodelSel <adolfo.selllano@gmail.com>
Co-authored-by: Giorgio Boa <35845425+gioboa@users.noreply.github.com>
Co-authored-by: Mike Guida <mike@mguida.com>
This commit is contained in:
ibrahim menem 2025-12-02 19:23:26 +01:00 committed by GitHub
parent 38715bbd41
commit 6f486e5a67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 196 additions and 16 deletions

View File

@ -300,26 +300,22 @@ export class RedisQueryResultCache implements QueryResultCache {
}
/**
* Detects the Redis version based on the connected client's API characteristics
* without creating test keys in the database
* Detects the Redis package version by reading the installed package.json
* and sets the appropriate API version (3 for callback-based, 5 for Promise-based).
*/
private detectRedisVersion(): void {
if (this.clientType !== "redis") return
try {
// Detect version by examining the client's method signatures
// This avoids creating test keys in the database
const setMethod = this.client.set
if (setMethod && setMethod.length <= 3) {
// Redis 5+ set method accepts fewer parameters (key, value, options)
this.redisMajorVersion = 5
} else {
// Redis 3/4 set method requires more parameters (key, value, flag, duration, callback)
this.redisMajorVersion = 3
}
} catch {
// Default to Redis 3/4 for maximum compatibility
const version = PlatformTools.readPackageVersion("redis")
const major = parseInt(version.split(".")[0], 10)
if (isNaN(major)) {
throw new TypeORMError(`Invalid Redis version format: ${version}`)
}
if (major <= 4) {
// Redis 3/4 uses callback-based API
this.redisMajorVersion = 3
} else {
// Redis 5+ uses Promise-based API
this.redisMajorVersion = 5
}
}

View File

@ -27,6 +27,20 @@ export class PlatformTools {
return global
}
/**
* Reads the version string from package.json of the given package.
* This operation is only supported in node.
*/
static readPackageVersion(name: string): string {
try {
return require(`${name}/package.json`).version
} catch (err) {
throw new TypeError(
`Failed to read package.json for "${name}": ${err.message}`,
)
}
}
/**
* Loads ("require"-s) given file or package.
* This operation only supports on node platform

View File

@ -0,0 +1,108 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect } from "chai"
import * as sinon from "sinon"
import { RedisQueryResultCache } from "../../../src/cache/RedisQueryResultCache"
import { PlatformTools } from "../../../src/platform/PlatformTools"
import { DataSource } from "../../../src/data-source/DataSource"
describe("RedisQueryResultCache", () => {
describe("detectRedisVersion", () => {
let sandbox: sinon.SinonSandbox
let mockDataSource: sinon.SinonStubbedInstance<DataSource>
let readPackageVersionStub: sinon.SinonStub
beforeEach(() => {
sandbox = sinon.createSandbox()
// Create a mock DataSource
mockDataSource = {
options: {},
logger: {
log: sandbox.stub(),
},
} as any
// Stub PlatformTools.readPackageVersion
readPackageVersionStub = sandbox.stub(
PlatformTools,
"readPackageVersion",
)
// Stub PlatformTools.load to prevent actual redis loading
sandbox.stub(PlatformTools, "load").returns({})
})
afterEach(() => {
sandbox.restore()
})
it("should detect Redis v3.x and set redisMajorVersion to 3", () => {
readPackageVersionStub.returns("3.1.2")
const cache = new RedisQueryResultCache(
mockDataSource as any,
"redis",
)
// Access the private method via any cast for testing
;(cache as any).detectRedisVersion()
expect((cache as any).redisMajorVersion).to.equal(3)
expect(readPackageVersionStub.calledOnceWith("redis")).to.be.true
})
it("should detect Redis v4.x and set redisMajorVersion to 3 (callback-based)", () => {
readPackageVersionStub.returns("4.6.13")
const cache = new RedisQueryResultCache(
mockDataSource as any,
"redis",
)
;(cache as any).detectRedisVersion()
expect((cache as any).redisMajorVersion).to.equal(3)
})
it("should detect Redis v5.x and set redisMajorVersion to 5 (Promise-based)", () => {
readPackageVersionStub.returns("5.0.0")
const cache = new RedisQueryResultCache(
mockDataSource as any,
"redis",
)
;(cache as any).detectRedisVersion()
expect((cache as any).redisMajorVersion).to.equal(5)
expect(readPackageVersionStub.calledOnceWith("redis")).to.be.true
})
it("should detect Redis v6.x and set redisMajorVersion to 5 (Promise-based)", () => {
readPackageVersionStub.returns("6.2.3")
const cache = new RedisQueryResultCache(
mockDataSource as any,
"redis",
)
;(cache as any).detectRedisVersion()
expect((cache as any).redisMajorVersion).to.equal(5)
})
it("should detect Redis v7.x and set redisMajorVersion to 5 (Promise-based)", () => {
readPackageVersionStub.returns("7.0.0")
const cache = new RedisQueryResultCache(
mockDataSource as any,
"redis",
)
;(cache as any).detectRedisVersion()
expect((cache as any).redisMajorVersion).to.equal(5)
})
})
})

View File

@ -0,0 +1,62 @@
import { expect } from "chai"
import * as sinon from "sinon"
import { PlatformTools } from "../../../src/platform/PlatformTools"
describe("PlatformTools", () => {
describe("readPackageVersion", () => {
let sandbox: sinon.SinonSandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
})
afterEach(() => {
sandbox.restore()
})
it("should successfully read version from an installed package", () => {
// Test with a package we know exists in node_modules (chai is in devDependencies)
const version = PlatformTools.readPackageVersion("chai")
expect(version).to.be.a("string")
expect(version).to.match(/^\d+\.\d+\.\d+/)
})
it("should return correct version format with major.minor.patch", () => {
const version = PlatformTools.readPackageVersion("chai")
expect(version).to.be.a("string")
expect(version.split(".").length).to.be.at.least(3)
})
it("should throw TypeError when package does not exist", () => {
expect(() => {
PlatformTools.readPackageVersion(
"this-package-definitely-does-not-exist-12345",
)
}).to.throw(
TypeError,
/Failed to read package\.json for "this-package-definitely-does-not-exist-12345"/,
)
})
it("should handle scoped package names", () => {
const version = PlatformTools.readPackageVersion("@types/node")
expect(version).to.be.a("string")
expect(version).to.match(/^\d+/)
})
it("should throw error for empty package name", () => {
expect(() => {
PlatformTools.readPackageVersion("")
}).to.throw(TypeError, /Failed to read package\.json/)
})
it("should throw error for whitespace-only package name", () => {
expect(() => {
PlatformTools.readPackageVersion(" ")
}).to.throw(TypeError, /Failed to read package\.json/)
})
})
})