• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

typeorm / typeorm / 19549987525

20 Nov 2025 08:11PM UTC coverage: 80.769% (+4.3%) from 76.433%
19549987525

push

github

web-flow
ci: run tests on commits to master and next (#11783)

Co-authored-by: Oleg "OSA413" Sokolov <OSA413@users.noreply.github.com>

26500 of 32174 branches covered (82.36%)

Branch coverage included in aggregate %.

91252 of 113615 relevant lines covered (80.32%)

88980.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.17
/src/cache/DbQueryResultCache.ts
1
import { ObjectLiteral } from "../common/ObjectLiteral"
26✔
2
import { DataSource } from "../data-source/DataSource"
26✔
3
import { MssqlParameter } from "../driver/sqlserver/MssqlParameter"
26✔
4
import { QueryRunner } from "../query-runner/QueryRunner"
26✔
5
import { Table } from "../schema-builder/table/Table"
26✔
6
import { QueryResultCache } from "./QueryResultCache"
26✔
7
import { QueryResultCacheOptions } from "./QueryResultCacheOptions"
26✔
8
import { v4 as uuidv4 } from "uuid"
26✔
9

26✔
10
/**
26✔
11
 * Caches query result into current database, into separate table called "query-result-cache".
26✔
12
 */
26✔
13
export class DbQueryResultCache implements QueryResultCache {
26✔
14
    // -------------------------------------------------------------------------
26✔
15
    // Private properties
26✔
16
    // -------------------------------------------------------------------------
26✔
17

26✔
18
    private queryResultCacheTable: string
26✔
19

26✔
20
    private queryResultCacheDatabase?: string
26✔
21

26✔
22
    private queryResultCacheSchema?: string
26✔
23

26✔
24
    // -------------------------------------------------------------------------
26✔
25
    // Constructor
26✔
26
    // -------------------------------------------------------------------------
26✔
27

26✔
28
    constructor(protected connection: DataSource) {
26✔
29
        const { schema } = this.connection.driver.options as any
120✔
30
        const database = this.connection.driver.database
120✔
31
        const cacheOptions =
120✔
32
            typeof this.connection.options.cache === "object"
120✔
33
                ? this.connection.options.cache
120✔
34
                : {}
120✔
35
        const cacheTableName = cacheOptions.tableName || "query-result-cache"
120✔
36

120✔
37
        this.queryResultCacheDatabase = database
120✔
38
        this.queryResultCacheSchema = schema
120✔
39
        this.queryResultCacheTable = this.connection.driver.buildTableName(
120✔
40
            cacheTableName,
120✔
41
            schema,
120✔
42
            database,
120✔
43
        )
120✔
44
    }
120✔
45

26✔
46
    // -------------------------------------------------------------------------
26✔
47
    // Public Methods
26✔
48
    // -------------------------------------------------------------------------
26✔
49

26✔
50
    /**
26✔
51
     * Creates a connection with given cache provider.
26✔
52
     */
26✔
53
    async connect(): Promise<void> {}
26✔
54

26✔
55
    /**
26✔
56
     * Disconnects with given cache provider.
26✔
57
     */
26✔
58
    async disconnect(): Promise<void> {}
26✔
59

26✔
60
    /**
26✔
61
     * Creates table for storing cache if it does not exist yet.
26✔
62
     */
26✔
63
    async synchronize(queryRunner?: QueryRunner): Promise<void> {
26✔
64
        queryRunner = this.getQueryRunner(queryRunner)
296✔
65
        const driver = this.connection.driver
296✔
66
        const tableExist = await queryRunner.hasTable(
296✔
67
            this.queryResultCacheTable,
296✔
68
        ) // todo: table name should be configurable
296✔
69
        if (tableExist) return
296!
70

296✔
71
        await queryRunner.createTable(
296✔
72
            new Table({
296✔
73
                database: this.queryResultCacheDatabase,
296✔
74
                schema: this.queryResultCacheSchema,
296✔
75
                name: this.queryResultCacheTable,
296✔
76
                columns: [
296✔
77
                    {
296✔
78
                        name: "id",
296✔
79
                        isPrimary: true,
296✔
80
                        isNullable: false,
296✔
81
                        type: driver.normalizeType({
296✔
82
                            type: driver.mappedDataTypes.cacheId,
296✔
83
                        }),
296✔
84
                        generationStrategy:
296✔
85
                            driver.options.type === "spanner"
296✔
86
                                ? "uuid"
296!
87
                                : "increment",
296✔
88
                        isGenerated: true,
296✔
89
                    },
296✔
90
                    {
296✔
91
                        name: "identifier",
296✔
92
                        type: driver.normalizeType({
296✔
93
                            type: driver.mappedDataTypes.cacheIdentifier,
296✔
94
                        }),
296✔
95
                        isNullable: true,
296✔
96
                    },
296✔
97
                    {
296✔
98
                        name: "time",
296✔
99
                        type: driver.normalizeType({
296✔
100
                            type: driver.mappedDataTypes.cacheTime,
296✔
101
                        }),
296✔
102
                        isPrimary: false,
296✔
103
                        isNullable: false,
296✔
104
                    },
296✔
105
                    {
296✔
106
                        name: "duration",
296✔
107
                        type: driver.normalizeType({
296✔
108
                            type: driver.mappedDataTypes.cacheDuration,
296✔
109
                        }),
296✔
110
                        isPrimary: false,
296✔
111
                        isNullable: false,
296✔
112
                    },
296✔
113
                    {
296✔
114
                        name: "query",
296✔
115
                        type: driver.normalizeType({
296✔
116
                            type: driver.mappedDataTypes.cacheQuery,
296✔
117
                        }),
296✔
118
                        isPrimary: false,
296✔
119
                        isNullable: false,
296✔
120
                    },
296✔
121
                    {
296✔
122
                        name: "result",
296✔
123
                        type: driver.normalizeType({
296✔
124
                            type: driver.mappedDataTypes.cacheResult,
296✔
125
                        }),
296✔
126
                        isNullable: false,
296✔
127
                    },
296✔
128
                ],
296✔
129
            }),
296✔
130
        )
296✔
131
    }
296✔
132

26✔
133
    /**
26✔
134
     * Get data from cache.
26✔
135
     * Returns cache result if found.
26✔
136
     * Returns undefined if result is not cached.
26✔
137
     */
26✔
138
    getFromCache(
26✔
139
        options: QueryResultCacheOptions,
716✔
140
        queryRunner?: QueryRunner,
716✔
141
    ): Promise<QueryResultCacheOptions | undefined> {
716✔
142
        queryRunner = this.getQueryRunner(queryRunner)
716✔
143
        const qb = this.connection
716✔
144
            .createQueryBuilder(queryRunner)
716✔
145
            .select()
716✔
146
            .from(this.queryResultCacheTable, "cache")
716✔
147

716✔
148
        if (options.identifier) {
716✔
149
            return qb
196✔
150
                .where(
196✔
151
                    `${qb.escape("cache")}.${qb.escape(
196✔
152
                        "identifier",
196✔
153
                    )} = :identifier`,
196✔
154
                )
196✔
155
                .setParameters({
196✔
156
                    identifier:
196✔
157
                        this.connection.driver.options.type === "mssql"
196✔
158
                            ? new MssqlParameter(options.identifier, "nvarchar")
196!
159
                            : options.identifier,
196!
160
                })
196✔
161
                .cache(false) // disable cache to avoid infinite loops when cache is alwaysEnable
196✔
162
                .getRawOne()
196✔
163
        } else if (options.query) {
716✔
164
            if (this.connection.driver.options.type === "oracle") {
520!
165
                return qb
36✔
166
                    .where(
36✔
167
                        `dbms_lob.compare(${qb.escape("cache")}.${qb.escape(
36✔
168
                            "query",
36✔
169
                        )}, :query) = 0`,
36✔
170
                        { query: options.query },
36✔
171
                    )
36✔
172
                    .cache(false) // disable cache to avoid infinite loops when cache is alwaysEnable
36✔
173
                    .getRawOne()
36✔
174
            }
36✔
175

484!
176
            return qb
484✔
177
                .where(`${qb.escape("cache")}.${qb.escape("query")} = :query`)
484✔
178
                .setParameters({
484✔
179
                    query:
484✔
180
                        this.connection.driver.options.type === "mssql"
484✔
181
                            ? new MssqlParameter(options.query, "nvarchar")
484!
182
                            : options.query,
520!
183
                })
520✔
184
                .cache(false) // disable cache to avoid infinite loops when cache is alwaysEnable
520✔
185
                .getRawOne()
520✔
186
        }
520✔
187

×
188
        return Promise.resolve(undefined)
×
189
    }
×
190

26✔
191
    /**
26✔
192
     * Checks if cache is expired or not.
26✔
193
     */
26✔
194
    isExpired(savedCache: QueryResultCacheOptions): boolean {
26✔
195
        const duration =
456✔
196
            typeof savedCache.duration === "string"
456✔
197
                ? parseInt(savedCache.duration)
456!
198
                : savedCache.duration
456!
199
        return (
456✔
200
            (typeof savedCache.time === "string"
456✔
201
                ? parseInt(savedCache.time as any)
456✔
202
                : savedCache.time)! +
456!
203
                duration <
456✔
204
            Date.now()
456✔
205
        )
456✔
206
    }
456✔
207

26✔
208
    /**
26✔
209
     * Stores given query result in the cache.
26✔
210
     */
26✔
211
    async storeInCache(
26✔
212
        options: QueryResultCacheOptions,
400✔
213
        savedCache: QueryResultCacheOptions | undefined,
400✔
214
        queryRunner?: QueryRunner,
400✔
215
    ): Promise<void> {
400✔
216
        const shouldCreateQueryRunner =
400✔
217
            queryRunner === undefined ||
400✔
218
            queryRunner?.getReplicationMode() === "slave"
400✔
219

400✔
220
        if (queryRunner === undefined || shouldCreateQueryRunner) {
400!
221
            queryRunner = this.connection.createQueryRunner("master")
270✔
222
        }
270✔
223

400✔
224
        let insertedValues: ObjectLiteral = options
400✔
225
        if (this.connection.driver.options.type === "mssql") {
400!
226
            // todo: bad abstraction, re-implement this part, probably better if we create an entity metadata for cache table
28✔
227
            insertedValues = {
28✔
228
                identifier: new MssqlParameter(options.identifier, "nvarchar"),
28✔
229
                time: new MssqlParameter(options.time, "bigint"),
28✔
230
                duration: new MssqlParameter(options.duration, "int"),
28✔
231
                query: new MssqlParameter(options.query, "nvarchar"),
28✔
232
                result: new MssqlParameter(options.result, "nvarchar"),
28✔
233
            }
28✔
234
        }
28✔
235

400✔
236
        if (savedCache && savedCache.identifier) {
400✔
237
            // if exist then update
28✔
238
            const qb = queryRunner.manager
28✔
239
                .createQueryBuilder()
28✔
240
                .update(this.queryResultCacheTable)
28✔
241
                .set(insertedValues)
28✔
242

28✔
243
            qb.where(`${qb.escape("identifier")} = :condition`, {
28✔
244
                condition: insertedValues.identifier,
28✔
245
            })
28✔
246
            await qb.execute()
28✔
247
        } else if (savedCache && savedCache.query) {
400✔
248
            // if exist then update
112✔
249
            const qb = queryRunner.manager
112✔
250
                .createQueryBuilder()
112✔
251
                .update(this.queryResultCacheTable)
112✔
252
                .set(insertedValues)
112✔
253

112✔
254
            if (this.connection.driver.options.type === "oracle") {
112!
255
                qb.where(`dbms_lob.compare("query", :condition) = 0`, {
8✔
256
                    condition: insertedValues.query,
8✔
257
                })
8✔
258
            } else {
112!
259
                qb.where(`${qb.escape("query")} = :condition`, {
104✔
260
                    condition: insertedValues.query,
104✔
261
                })
104✔
262
            }
104✔
263

112✔
264
            await qb.execute()
112✔
265
        } else {
372✔
266
            // Spanner does not support auto-generated columns
260✔
267
            if (
260✔
268
                this.connection.driver.options.type === "spanner" &&
260!
269
                !insertedValues.id
×
270
            ) {
260!
271
                insertedValues.id = uuidv4()
×
272
            }
×
273

260✔
274
            // otherwise insert
260✔
275
            await queryRunner.manager
260✔
276
                .createQueryBuilder()
260✔
277
                .insert()
260✔
278
                .into(this.queryResultCacheTable)
260✔
279
                .values(insertedValues)
260✔
280
                .execute()
260✔
281
        }
260✔
282

400✔
283
        if (shouldCreateQueryRunner) {
400!
284
            await queryRunner.release()
270✔
285
        }
270✔
286
    }
400✔
287

26✔
288
    /**
26✔
289
     * Clears everything stored in the cache.
26✔
290
     */
26✔
291
    async clear(queryRunner: QueryRunner): Promise<void> {
26✔
292
        return this.getQueryRunner(queryRunner).clearTable(
×
293
            this.queryResultCacheTable,
×
294
        )
×
295
    }
×
296

26✔
297
    /**
26✔
298
     * Removes all cached results by given identifiers from cache.
26✔
299
     */
26✔
300
    async remove(
26✔
301
        identifiers: string[],
×
302
        queryRunner?: QueryRunner,
×
303
    ): Promise<void> {
×
304
        const _queryRunner: QueryRunner = queryRunner || this.getQueryRunner()
×
305
        await Promise.all(
×
306
            identifiers.map((identifier) => {
×
307
                const qb = _queryRunner.manager.createQueryBuilder()
×
308
                return qb
×
309
                    .delete()
×
310
                    .from(this.queryResultCacheTable)
×
311
                    .where(`${qb.escape("identifier")} = :identifier`, {
×
312
                        identifier,
×
313
                    })
×
314
                    .execute()
×
315
            }),
×
316
        )
×
317

×
318
        if (!queryRunner) {
×
319
            await _queryRunner.release()
×
320
        }
×
321
    }
×
322

26✔
323
    // -------------------------------------------------------------------------
26✔
324
    // Protected Methods
26✔
325
    // -------------------------------------------------------------------------
26✔
326

26✔
327
    /**
26✔
328
     * Gets a query runner to work with.
26✔
329
     */
26✔
330
    protected getQueryRunner(queryRunner?: QueryRunner): QueryRunner {
26✔
331
        if (queryRunner) return queryRunner
1,012✔
332

×
333
        return this.connection.createQueryRunner()
×
334
    }
×
335
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc