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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

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

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

18
    private queryResultCacheTable: string
19

20
    private queryResultCacheDatabase?: string
21

22
    private queryResultCacheSchema?: string
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

283
        if (shouldCreateQueryRunner) {
68✔
284
            await queryRunner.release()
17✔
285
        }
286
    }
287

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

297
    /**
298
     * Removes all cached results by given identifiers from cache.
299
     */
300
    async remove(
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

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

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

333
        return this.connection.createQueryRunner()
×
334
    }
335
}
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

© 2025 Coveralls, Inc