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

typeorm / typeorm / 12896205915

21 Jan 2025 09:28PM CUT coverage: 71.565% (-0.8%) from 72.364%
12896205915

Pull #10927

github

web-flow
Merge f6e528020 into 6c0c2bab6
Pull Request #10927: fix: empty objects being hydrated when eager loading relations that have a `@VirtualColumn`

8569 of 12651 branches covered (67.73%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 2 files covered. (100.0%)

176 existing lines in 20 files now uncovered.

17611 of 23931 relevant lines covered (73.59%)

136086.4 hits per line

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

8.89
/src/driver/sqlite/SqliteDriver.ts
1
import fs from "fs/promises"
29✔
2
import path from "path"
29✔
3
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
29✔
4
import { SqliteQueryRunner } from "./SqliteQueryRunner"
29✔
5
import { PlatformTools } from "../../platform/PlatformTools"
29✔
6
import { DataSource } from "../../data-source/DataSource"
7
import { SqliteConnectionOptions } from "./SqliteConnectionOptions"
8
import { ColumnType } from "../types/ColumnTypes"
9
import { QueryRunner } from "../../query-runner/QueryRunner"
10
import { AbstractSqliteDriver } from "../sqlite-abstract/AbstractSqliteDriver"
29✔
11
import { ReplicationMode } from "../types/ReplicationMode"
12
import { filepathToName, isAbsolute } from "../../util/PathUtils"
29✔
13

14
/**
15
 * Organizes communication with sqlite DBMS.
16
 */
17
export class SqliteDriver extends AbstractSqliteDriver {
29✔
18
    // -------------------------------------------------------------------------
19
    // Public Properties
20
    // -------------------------------------------------------------------------
21

22
    /**
23
     * Connection options.
24
     */
25
    options: SqliteConnectionOptions
26

27
    /**
28
     * SQLite underlying library.
29
     */
30
    sqlite: any
31

32
    // -------------------------------------------------------------------------
33
    // Constructor
34
    // -------------------------------------------------------------------------
35

36
    constructor(connection: DataSource) {
UNCOV
37
        super(connection)
×
UNCOV
38
        this.connection = connection
×
UNCOV
39
        this.options = connection.options as SqliteConnectionOptions
×
UNCOV
40
        this.database = this.options.database
×
41

42
        // load sqlite package
UNCOV
43
        this.loadDependencies()
×
44
    }
45

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

50
    /**
51
     * Closes connection with database.
52
     */
53
    async disconnect(): Promise<void> {
UNCOV
54
        return new Promise<void>((ok, fail) => {
×
UNCOV
55
            this.queryRunner = undefined
×
UNCOV
56
            this.databaseConnection.close((err: any) =>
×
UNCOV
57
                err ? fail(err) : ok(),
×
58
            )
59
        })
60
    }
61

62
    /**
63
     * Creates a query runner used to execute database queries.
64
     */
65
    createQueryRunner(mode: ReplicationMode): QueryRunner {
UNCOV
66
        if (!this.queryRunner) this.queryRunner = new SqliteQueryRunner(this)
×
67

UNCOV
68
        return this.queryRunner
×
69
    }
70

71
    normalizeType(column: {
72
        type?: ColumnType
73
        length?: number | string
74
        precision?: number | null
75
        scale?: number
76
    }): string {
UNCOV
77
        if ((column.type as any) === Buffer) {
×
UNCOV
78
            return "blob"
×
79
        }
80

UNCOV
81
        return super.normalizeType(column)
×
82
    }
83

84
    async afterConnect(): Promise<void> {
UNCOV
85
        return this.attachDatabases()
×
86
    }
87

88
    /**
89
     * For SQLite, the database may be added in the decorator metadata. It will be a filepath to a database file.
90
     */
91
    buildTableName(
92
        tableName: string,
93
        _schema?: string,
94
        database?: string,
95
    ): string {
UNCOV
96
        if (!database) return tableName
×
UNCOV
97
        if (this.getAttachedDatabaseHandleByRelativePath(database))
×
UNCOV
98
            return `${this.getAttachedDatabaseHandleByRelativePath(
×
99
                database,
100
            )}.${tableName}`
101

UNCOV
102
        if (database === this.options.database) return tableName
×
103

104
        // we use the decorated name as supplied when deriving attach handle (ideally without non-portable absolute path)
UNCOV
105
        const identifierHash = filepathToName(database)
×
106
        // decorated name will be assumed relative to main database file when non absolute. Paths supplied as absolute won't be portable
UNCOV
107
        const absFilepath = isAbsolute(database)
×
108
            ? database
109
            : path.join(this.getMainDatabasePath(), database)
110

UNCOV
111
        this.attachedDatabases[database] = {
×
112
            attachFilepathAbsolute: absFilepath,
113
            attachFilepathRelative: database,
114
            attachHandle: identifierHash,
115
        }
116

UNCOV
117
        return `${identifierHash}.${tableName}`
×
118
    }
119

120
    // -------------------------------------------------------------------------
121
    // Protected Methods
122
    // -------------------------------------------------------------------------
123

124
    /**
125
     * Creates connection with the database.
126
     */
127
    protected async createDatabaseConnection() {
UNCOV
128
        if (
×
129
            this.options.flags === undefined ||
×
130
            !(this.options.flags & this.sqlite.OPEN_URI)
131
        ) {
UNCOV
132
            await this.createDatabaseDirectory(this.options.database)
×
133
        }
134

UNCOV
135
        const databaseConnection: any = await new Promise((ok, fail) => {
×
UNCOV
136
            if (this.options.flags === undefined) {
×
UNCOV
137
                const connection = new this.sqlite.Database(
×
138
                    this.options.database,
139
                    (err: any) => {
UNCOV
140
                        if (err) return fail(err)
×
UNCOV
141
                        ok(connection)
×
142
                    },
143
                )
144
            } else {
UNCOV
145
                const connection = new this.sqlite.Database(
×
146
                    this.options.database,
147
                    this.options.flags,
148
                    (err: any) => {
UNCOV
149
                        if (err) return fail(err)
×
UNCOV
150
                        ok(connection)
×
151
                    },
152
                )
153
            }
154
        })
155

156
        // Internal function to run a command on the connection and fail if an error occured.
157
        function run(line: string): Promise<void> {
UNCOV
158
            return new Promise((ok, fail) => {
×
UNCOV
159
                databaseConnection.run(line, (err: any) => {
×
UNCOV
160
                    if (err) return fail(err)
×
UNCOV
161
                    ok()
×
162
                })
163
            })
164
        }
165
        // in the options, if encryption key for SQLCipher is setted.
166
        // Must invoke key pragma before trying to do any other interaction with the database.
UNCOV
167
        if (this.options.key) {
×
168
            await run(`PRAGMA key = ${JSON.stringify(this.options.key)}`)
×
169
        }
170

UNCOV
171
        if (this.options.enableWAL) {
×
UNCOV
172
            await run(`PRAGMA journal_mode = WAL`)
×
173
        }
174

UNCOV
175
        if (
×
176
            this.options.busyTimeout &&
×
177
            typeof this.options.busyTimeout === "number" &&
178
            this.options.busyTimeout > 0
179
        ) {
UNCOV
180
            await run(`PRAGMA busy_timeout = ${this.options.busyTimeout}`)
×
181
        }
182

183
        // we need to enable foreign keys in sqlite to make sure all foreign key related features
184
        // working properly. this also makes onDelete to work with sqlite.
UNCOV
185
        await run(`PRAGMA foreign_keys = ON`)
×
186

UNCOV
187
        return databaseConnection
×
188
    }
189

190
    /**
191
     * If driver dependency is not given explicitly, then try to load it via "require".
192
     */
193
    protected loadDependencies(): void {
UNCOV
194
        try {
×
UNCOV
195
            const sqlite = this.options.driver || PlatformTools.load("sqlite3")
×
UNCOV
196
            this.sqlite = sqlite.verbose()
×
197
        } catch (e) {
198
            throw new DriverPackageNotInstalledError("SQLite", "sqlite3")
×
199
        }
200
    }
201

202
    /**
203
     * Auto creates database directory if it does not exist.
204
     */
205
    protected async createDatabaseDirectory(fullPath: string): Promise<void> {
UNCOV
206
        await fs.mkdir(path.dirname(fullPath), { recursive: true })
×
207
    }
208

209
    /**
210
     * Performs the attaching of the database files. The attachedDatabase should have been populated during calls to #buildTableName
211
     * during EntityMetadata production (see EntityMetadata#buildTablePath)
212
     *
213
     * https://sqlite.org/lang_attach.html
214
     */
215
    protected async attachDatabases() {
216
        // @todo - possibly check number of databases (but unqueriable at runtime sadly) - https://www.sqlite.org/limits.html#max_attached
UNCOV
217
        for await (const {
×
218
            attachHandle,
219
            attachFilepathAbsolute,
220
        } of Object.values(this.attachedDatabases)) {
UNCOV
221
            await this.createDatabaseDirectory(attachFilepathAbsolute)
×
UNCOV
222
            await this.connection.query(
×
223
                `ATTACH "${attachFilepathAbsolute}" AS "${attachHandle}"`,
224
            )
225
        }
226
    }
227

228
    protected getMainDatabasePath(): string {
UNCOV
229
        const optionsDb = this.options.database
×
UNCOV
230
        return path.dirname(
×
231
            isAbsolute(optionsDb)
×
232
                ? optionsDb
233
                : path.join(process.cwd(), optionsDb),
234
        )
235
    }
236
}
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