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

typeorm / typeorm / 23390157208

21 Mar 2026 10:26PM UTC coverage: 56.678% (-16.6%) from 73.277%
23390157208

Pull #12252

github

web-flow
Merge 5b60ba41c into 7038fa166
Pull Request #12252: fix: unskip cascade soft remove test

17767 of 26580 branches covered (66.84%)

Branch coverage included in aggregate %.

64033 of 117744 relevant lines covered (54.38%)

1514.83 hits per line

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

79.03
/src/cache/DbQueryResultCache.ts
1
import type { ObjectLiteral } from "../common/ObjectLiteral"
28✔
2
import type { DataSource } from "../data-source/DataSource"
28✔
3
import { MssqlParameter } from "../driver/sqlserver/MssqlParameter"
28✔
4
import type { QueryRunner } from "../query-runner/QueryRunner"
28✔
5
import { Table } from "../schema-builder/table/Table"
28✔
6
import { RandomGenerator } from "../util/RandomGenerator"
28✔
7
import type { QueryResultCache } from "./QueryResultCache"
28✔
8
import type { QueryResultCacheOptions } from "./QueryResultCacheOptions"
28✔
9

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

28✔
18
    private queryResultCacheTable: string
28✔
19

28✔
20
    private queryResultCacheDatabase?: string
28✔
21

28✔
22
    private queryResultCacheSchema?: string
28✔
23

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

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

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

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

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

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

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

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

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

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

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

×
191
        return Promise.resolve(undefined)
×
192
    }
×
193

28✔
194
    /**
28✔
195
     * Checks if cache is expired or not.
28✔
196
     * @param savedCache
28✔
197
     */
28✔
198
    isExpired(savedCache: QueryResultCacheOptions): boolean {
28✔
199
        const duration =
15✔
200
            typeof savedCache.duration === "string"
15✔
201
                ? parseInt(savedCache.duration)
15✔
202
                : savedCache.duration
15!
203
        return (
15✔
204
            (typeof savedCache.time === "string"
15✔
205
                ? parseInt(savedCache.time as any)
15✔
206
                : savedCache.time)! +
15!
207
                duration <
15✔
208
            Date.now()
15✔
209
        )
15✔
210
    }
15✔
211

28✔
212
    /**
28✔
213
     * Stores given query result in the cache.
28✔
214
     * @param options
28✔
215
     * @param savedCache
28✔
216
     * @param queryRunner
28✔
217
     */
28✔
218
    async storeInCache(
28✔
219
        options: QueryResultCacheOptions,
13✔
220
        savedCache: QueryResultCacheOptions | undefined,
13✔
221
        queryRunner?: QueryRunner,
13✔
222
    ): Promise<void> {
13✔
223
        const shouldCreateQueryRunner =
13✔
224
            queryRunner === undefined ||
13✔
225
            queryRunner?.getReplicationMode() === "slave"
13✔
226

13✔
227
        if (queryRunner === undefined || shouldCreateQueryRunner) {
13✔
228
            queryRunner = this.dataSource.createQueryRunner("master")
13✔
229
        }
13✔
230

13✔
231
        let insertedValues: ObjectLiteral = options
13✔
232
        if (this.dataSource.driver.options.type === "mssql") {
13!
233
            // todo: bad abstraction, re-implement this part, probably better if we create an entity metadata for cache table
×
234
            insertedValues = {
×
235
                identifier: new MssqlParameter(options.identifier, "nvarchar"),
×
236
                time: new MssqlParameter(options.time, "bigint"),
×
237
                duration: new MssqlParameter(options.duration, "int"),
×
238
                query: new MssqlParameter(options.query, "nvarchar"),
×
239
                result: new MssqlParameter(options.result, "nvarchar"),
×
240
            }
×
241
        }
×
242

13✔
243
        if (savedCache && savedCache.identifier) {
13!
244
            // if exist then update
1✔
245
            const qb = queryRunner.manager
1✔
246
                .createQueryBuilder()
1✔
247
                .update(this.queryResultCacheTable)
1✔
248
                .set(insertedValues)
1✔
249

1✔
250
            qb.where(`${qb.escape("identifier")} = :condition`, {
1✔
251
                condition: insertedValues.identifier,
1✔
252
            })
1✔
253
            await qb.execute()
1✔
254
        } else if (savedCache && savedCache.query) {
13!
255
            // if exist then update
4✔
256
            const qb = queryRunner.manager
4✔
257
                .createQueryBuilder()
4✔
258
                .update(this.queryResultCacheTable)
4✔
259
                .set(insertedValues)
4✔
260

4✔
261
            if (this.dataSource.driver.options.type === "oracle") {
4!
262
                qb.where(`dbms_lob.compare("query", :condition) = 0`, {
×
263
                    condition: insertedValues.query,
×
264
                })
×
265
            } else {
4✔
266
                qb.where(`${qb.escape("query")} = :condition`, {
4✔
267
                    condition: insertedValues.query,
4✔
268
                })
4✔
269
            }
4✔
270

4✔
271
            await qb.execute()
4✔
272
        } else {
12✔
273
            // Spanner does not support auto-generated columns
8✔
274
            if (
8✔
275
                this.dataSource.driver.options.type === "spanner" &&
8!
276
                !insertedValues.id
×
277
            ) {
8!
278
                insertedValues.id = RandomGenerator.uuidv4()
×
279
            }
×
280

8✔
281
            // otherwise insert
8✔
282
            await queryRunner.manager
8✔
283
                .createQueryBuilder()
8✔
284
                .insert()
8✔
285
                .into(this.queryResultCacheTable)
8✔
286
                .values(insertedValues)
8✔
287
                .execute()
8✔
288
        }
8✔
289

13✔
290
        if (shouldCreateQueryRunner) {
13✔
291
            await queryRunner.release()
13✔
292
        }
13✔
293
    }
13✔
294

28✔
295
    /**
28✔
296
     * Clears everything stored in the cache.
28✔
297
     * @param queryRunner
28✔
298
     */
28✔
299
    async clear(queryRunner: QueryRunner): Promise<void> {
28✔
300
        return this.getQueryRunner(queryRunner).clearTable(
×
301
            this.queryResultCacheTable,
×
302
        )
×
303
    }
×
304

28✔
305
    /**
28✔
306
     * Removes all cached results by given identifiers from cache.
28✔
307
     * @param identifiers
28✔
308
     * @param queryRunner
28✔
309
     */
28✔
310
    async remove(
28✔
311
        identifiers: string[],
×
312
        queryRunner?: QueryRunner,
×
313
    ): Promise<void> {
×
314
        const _queryRunner: QueryRunner = queryRunner || this.getQueryRunner()
×
315
        await Promise.all(
×
316
            identifiers.map((identifier) => {
×
317
                const qb = _queryRunner.manager.createQueryBuilder()
×
318
                return qb
×
319
                    .delete()
×
320
                    .from(this.queryResultCacheTable)
×
321
                    .where(`${qb.escape("identifier")} = :identifier`, {
×
322
                        identifier,
×
323
                    })
×
324
                    .execute()
×
325
            }),
×
326
        )
×
327

×
328
        if (!queryRunner) {
×
329
            await _queryRunner.release()
×
330
        }
×
331
    }
×
332

28✔
333
    // -------------------------------------------------------------------------
28✔
334
    // Protected Methods
28✔
335
    // -------------------------------------------------------------------------
28✔
336

28✔
337
    /**
28✔
338
     * Gets a query runner to work with.
28✔
339
     * @param queryRunner
28✔
340
     */
28✔
341
    protected getQueryRunner(queryRunner?: QueryRunner): QueryRunner {
28✔
342
        if (queryRunner) return queryRunner
32✔
343

×
344
        return this.dataSource.createQueryRunner()
×
345
    }
×
346
}
28✔
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