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

typeorm / typeorm / 15219332477

23 May 2025 09:13PM UTC coverage: 17.216% (-59.1%) from 76.346%
15219332477

Pull #11332

github

naorpeled
cr comments - move if block
Pull Request #11332: feat: add new undefined and null behavior flags

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

0 of 31 new or added lines in 3 files covered. (0.0%)

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

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

10
/**
11
 * Caches query result into current database, into separate table called "query-result-cache".
12
 */
13
export class DbQueryResultCache implements QueryResultCache {
1✔
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

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

UNCOV
37
        this.queryResultCacheDatabase = database
×
UNCOV
38
        this.queryResultCacheSchema = schema
×
UNCOV
39
        this.queryResultCacheTable = this.connection.driver.buildTableName(
×
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> {
UNCOV
64
        queryRunner = this.getQueryRunner(queryRunner)
×
UNCOV
65
        const driver = this.connection.driver
×
UNCOV
66
        const tableExist = await queryRunner.hasTable(
×
67
            this.queryResultCacheTable,
68
        ) // todo: table name should be configurable
UNCOV
69
        if (tableExist) return
×
70

UNCOV
71
        await queryRunner.createTable(
×
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"
×
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> {
UNCOV
142
        queryRunner = this.getQueryRunner(queryRunner)
×
UNCOV
143
        const qb = this.connection
×
144
            .createQueryBuilder(queryRunner)
145
            .select()
146
            .from(this.queryResultCacheTable, "cache")
147

UNCOV
148
        if (options.identifier) {
×
UNCOV
149
            return qb
×
150
                .where(
151
                    `${qb.escape("cache")}.${qb.escape(
152
                        "identifier",
153
                    )} = :identifier`,
154
                )
155
                .setParameters({
156
                    identifier:
157
                        this.connection.driver.options.type === "mssql"
×
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()
UNCOV
163
        } else if (options.query) {
×
UNCOV
164
            if (this.connection.driver.options.type === "oracle") {
×
UNCOV
165
                return qb
×
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

UNCOV
176
            return qb
×
177
                .where(`${qb.escape("cache")}.${qb.escape("query")} = :query`)
178
                .setParameters({
179
                    query:
180
                        this.connection.driver.options.type === "mssql"
×
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 =
UNCOV
196
            typeof savedCache.duration === "string"
×
197
                ? parseInt(savedCache.duration)
198
                : savedCache.duration
UNCOV
199
        return (
×
200
            (typeof savedCache.time === "string"
×
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 =
UNCOV
217
            queryRunner === undefined ||
×
218
            queryRunner?.getReplicationMode() === "slave"
219

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

UNCOV
224
        let insertedValues: ObjectLiteral = options
×
UNCOV
225
        if (this.connection.driver.options.type === "mssql") {
×
226
            // todo: bad abstraction, re-implement this part, probably better if we create an entity metadata for cache table
UNCOV
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

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

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

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

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

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

UNCOV
283
        if (shouldCreateQueryRunner) {
×
UNCOV
284
            await queryRunner.release()
×
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 {
UNCOV
331
        if (queryRunner) return queryRunner
×
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