• 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

9.87
/src/driver/mysql/MysqlDriver.ts
1
import { Driver, ReturningType } from "../Driver"
2
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
1✔
3
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
1✔
4
import { DriverUtils } from "../DriverUtils"
1✔
5
import { CteCapabilities } from "../types/CteCapabilities"
6
import { MysqlQueryRunner } from "./MysqlQueryRunner"
1✔
7
import { ObjectLiteral } from "../../common/ObjectLiteral"
8
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
9
import { DateUtils } from "../../util/DateUtils"
1✔
10
import { PlatformTools } from "../../platform/PlatformTools"
1✔
11
import { DataSource } from "../../data-source/DataSource"
12
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
1✔
13
import { MysqlConnectionOptions } from "./MysqlConnectionOptions"
14
import { MappedColumnTypes } from "../types/MappedColumnTypes"
15
import { ColumnType } from "../types/ColumnTypes"
16
import { DataTypeDefaults } from "../types/DataTypeDefaults"
17
import { TableColumn } from "../../schema-builder/table/TableColumn"
18
import { MysqlConnectionCredentialsOptions } from "./MysqlConnectionCredentialsOptions"
19
import { EntityMetadata } from "../../metadata/EntityMetadata"
20
import { OrmUtils } from "../../util/OrmUtils"
1✔
21
import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
1✔
22
import { ReplicationMode } from "../types/ReplicationMode"
23
import { TypeORMError } from "../../error"
1✔
24
import { Table } from "../../schema-builder/table/Table"
25
import { View } from "../../schema-builder/view/View"
26
import { TableForeignKey } from "../../schema-builder/table/TableForeignKey"
27
import { VersionUtils } from "../../util/VersionUtils"
1✔
28
import { InstanceChecker } from "../../util/InstanceChecker"
1✔
29
import { UpsertType } from "../types/UpsertType"
30

31
/**
32
 * Organizes communication with MySQL DBMS.
33
 */
34
export class MysqlDriver implements Driver {
1✔
35
    // -------------------------------------------------------------------------
36
    // Public Properties
37
    // -------------------------------------------------------------------------
38

39
    /**
40
     * Connection used by driver.
41
     */
42
    connection: DataSource
43

44
    /**
45
     * Mysql underlying library.
46
     */
47
    mysql: any
48

49
    /**
50
     * Connection pool.
51
     * Used in non-replication mode.
52
     */
53
    pool: any
54

55
    /**
56
     * Pool cluster used in replication mode.
57
     */
58
    poolCluster: any
59

60
    // -------------------------------------------------------------------------
61
    // Public Implemented Properties
62
    // -------------------------------------------------------------------------
63

64
    /**
65
     * Connection options.
66
     */
67
    options: MysqlConnectionOptions
68

69
    /**
70
     * Version of MySQL. Requires a SQL query to the DB, so it is not always set
71
     */
72
    version?: string
73

74
    /**
75
     * Master database used to perform all write queries.
76
     */
77
    database?: string
78

79
    /**
80
     * Indicates if replication is enabled.
81
     */
82
    isReplicated: boolean = false
13✔
83

84
    /**
85
     * Indicates if tree tables are supported by this driver.
86
     */
87
    treeSupport = true
13✔
88

89
    /**
90
     * Represent transaction support by this driver
91
     */
92
    transactionSupport = "nested" as const
13✔
93

94
    /**
95
     * Gets list of supported column data types by a driver.
96
     *
97
     * @see https://www.tutorialspoint.com/mysql/mysql-data-types.htm
98
     * @see https://dev.mysql.com/doc/refman/8.0/en/data-types.html
99
     */
100
    supportedDataTypes: ColumnType[] = [
13✔
101
        // numeric types
102
        "bit",
103
        "int",
104
        "integer", // synonym for int
105
        "tinyint",
106
        "smallint",
107
        "mediumint",
108
        "bigint",
109
        "float",
110
        "double",
111
        "double precision", // synonym for double
112
        "real", // synonym for double
113
        "decimal",
114
        "dec", // synonym for decimal
115
        "numeric", // synonym for decimal
116
        "fixed", // synonym for decimal
117
        "bool", // synonym for tinyint
118
        "boolean", // synonym for tinyint
119
        // date and time types
120
        "date",
121
        "datetime",
122
        "timestamp",
123
        "time",
124
        "year",
125
        // string types
126
        "char",
127
        "nchar", // synonym for national char
128
        "national char",
129
        "varchar",
130
        "nvarchar", // synonym for national varchar
131
        "national varchar",
132
        "blob",
133
        "text",
134
        "tinyblob",
135
        "tinytext",
136
        "mediumblob",
137
        "mediumtext",
138
        "longblob",
139
        "longtext",
140
        "enum",
141
        "set",
142
        "binary",
143
        "varbinary",
144
        // json data type
145
        "json",
146
        // spatial data types
147
        "geometry",
148
        "point",
149
        "linestring",
150
        "polygon",
151
        "multipoint",
152
        "multilinestring",
153
        "multipolygon",
154
        "geometrycollection",
155
        // additional data types for mariadb
156
        "uuid",
157
        "inet4",
158
        "inet6",
159
    ]
160

161
    /**
162
     * Returns type of upsert supported by driver if any
163
     */
164
    supportedUpsertTypes: UpsertType[] = ["on-duplicate-key-update"]
13✔
165

166
    /**
167
     * Gets list of spatial column data types.
168
     */
169
    spatialTypes: ColumnType[] = [
13✔
170
        "geometry",
171
        "point",
172
        "linestring",
173
        "polygon",
174
        "multipoint",
175
        "multilinestring",
176
        "multipolygon",
177
        "geometrycollection",
178
    ]
179

180
    /**
181
     * Gets list of column data types that support length by a driver.
182
     */
183
    withLengthColumnTypes: ColumnType[] = [
13✔
184
        "char",
185
        "varchar",
186
        "nvarchar",
187
        "binary",
188
        "varbinary",
189
    ]
190

191
    /**
192
     * Gets list of column data types that support length by a driver.
193
     */
194
    withWidthColumnTypes: ColumnType[] = [
13✔
195
        "bit",
196
        "tinyint",
197
        "smallint",
198
        "mediumint",
199
        "int",
200
        "integer",
201
        "bigint",
202
    ]
203

204
    /**
205
     * Gets list of column data types that support precision by a driver.
206
     */
207
    withPrecisionColumnTypes: ColumnType[] = [
13✔
208
        "decimal",
209
        "dec",
210
        "numeric",
211
        "fixed",
212
        "float",
213
        "double",
214
        "double precision",
215
        "real",
216
        "time",
217
        "datetime",
218
        "timestamp",
219
    ]
220

221
    /**
222
     * Gets list of column data types that supports scale by a driver.
223
     */
224
    withScaleColumnTypes: ColumnType[] = [
13✔
225
        "decimal",
226
        "dec",
227
        "numeric",
228
        "fixed",
229
        "float",
230
        "double",
231
        "double precision",
232
        "real",
233
    ]
234

235
    /**
236
     * Gets list of column data types that supports UNSIGNED and ZEROFILL attributes.
237
     */
238
    unsignedAndZerofillTypes: ColumnType[] = [
13✔
239
        "int",
240
        "integer",
241
        "smallint",
242
        "tinyint",
243
        "mediumint",
244
        "bigint",
245
        "decimal",
246
        "dec",
247
        "numeric",
248
        "fixed",
249
        "float",
250
        "double",
251
        "double precision",
252
        "real",
253
    ]
254

255
    /**
256
     * ORM has special columns and we need to know what database column types should be for those columns.
257
     * Column types are driver dependant.
258
     */
259
    mappedDataTypes: MappedColumnTypes = {
13✔
260
        createDate: "datetime",
261
        createDatePrecision: 6,
262
        createDateDefault: "CURRENT_TIMESTAMP(6)",
263
        updateDate: "datetime",
264
        updateDatePrecision: 6,
265
        updateDateDefault: "CURRENT_TIMESTAMP(6)",
266
        deleteDate: "datetime",
267
        deleteDatePrecision: 6,
268
        deleteDateNullable: true,
269
        version: "int",
270
        treeLevel: "int",
271
        migrationId: "int",
272
        migrationName: "varchar",
273
        migrationTimestamp: "bigint",
274
        cacheId: "int",
275
        cacheIdentifier: "varchar",
276
        cacheTime: "bigint",
277
        cacheDuration: "int",
278
        cacheQuery: "text",
279
        cacheResult: "text",
280
        metadataType: "varchar",
281
        metadataDatabase: "varchar",
282
        metadataSchema: "varchar",
283
        metadataTable: "varchar",
284
        metadataName: "varchar",
285
        metadataValue: "text",
286
    }
287

288
    /**
289
     * Default values of length, precision and scale depends on column data type.
290
     * Used in the cases when length/precision/scale is not specified by user.
291
     */
292
    dataTypeDefaults: DataTypeDefaults = {
13✔
293
        varchar: { length: 255 },
294
        nvarchar: { length: 255 },
295
        "national varchar": { length: 255 },
296
        char: { length: 1 },
297
        binary: { length: 1 },
298
        varbinary: { length: 255 },
299
        decimal: { precision: 10, scale: 0 },
300
        dec: { precision: 10, scale: 0 },
301
        numeric: { precision: 10, scale: 0 },
302
        fixed: { precision: 10, scale: 0 },
303
        float: { precision: 12 },
304
        double: { precision: 22 },
305
        time: { precision: 0 },
306
        datetime: { precision: 0 },
307
        timestamp: { precision: 0 },
308
        bit: { width: 1 },
309
        int: { width: 11 },
310
        integer: { width: 11 },
311
        tinyint: { width: 4 },
312
        smallint: { width: 6 },
313
        mediumint: { width: 9 },
314
        bigint: { width: 20 },
315
    }
316

317
    /**
318
     * Max length allowed by MySQL for aliases.
319
     * @see https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
320
     */
321
    maxAliasLength = 63
13✔
322

323
    cteCapabilities: CteCapabilities = {
13✔
324
        enabled: false,
325
        requiresRecursiveHint: true,
326
    }
327

328
    /**
329
     * Supported returning types
330
     */
331
    private readonly _isReturningSqlSupported: Record<ReturningType, boolean> =
13✔
332
        {
333
            delete: false,
334
            insert: false,
335
            update: false,
336
        }
337

338
    /** MariaDB supports uuid type for version 10.7.0 and up */
339
    private uuidColumnTypeSuported = false
13✔
340

341
    // -------------------------------------------------------------------------
342
    // Constructor
343
    // -------------------------------------------------------------------------
344

345
    constructor(connection: DataSource) {
346
        this.connection = connection
13✔
347
        this.options = {
13✔
348
            legacySpatialSupport: true,
349
            ...connection.options,
350
        } as MysqlConnectionOptions
351
        this.isReplicated = this.options.replication ? true : false
13!
352

353
        // load mysql package
354
        this.loadDependencies()
13✔
355

356
        this.database = DriverUtils.buildDriverOptions(
13✔
357
            this.options.replication
13!
358
                ? this.options.replication.master
359
                : this.options,
360
        ).database
361

362
        // validate options to make sure everything is set
363
        // todo: revisit validation with replication in mind
364
        // if (!(this.options.host || (this.options.extra && this.options.extra.socketPath)) && !this.options.socketPath)
365
        //     throw new DriverOptionNotSetError("socketPath and host");
366
        // if (!this.options.username)
367
        //     throw new DriverOptionNotSetError("username");
368
        // if (!this.options.database)
369
        //     throw new DriverOptionNotSetError("database");
370
        // todo: check what is going on when connection is setup without database and how to connect to a database then?
371
        // todo: provide options to auto-create a database if it does not exist yet
372
    }
373

374
    // -------------------------------------------------------------------------
375
    // Public Methods
376
    // -------------------------------------------------------------------------
377

378
    /**
379
     * Performs connection to the database.
380
     */
381
    async connect(): Promise<void> {
UNCOV
382
        if (this.options.replication) {
×
UNCOV
383
            this.poolCluster = this.mysql.createPoolCluster(
×
384
                this.options.replication,
385
            )
UNCOV
386
            this.options.replication.slaves.forEach((slave, index) => {
×
UNCOV
387
                this.poolCluster.add(
×
388
                    "SLAVE" + index,
389
                    this.createConnectionOptions(this.options, slave),
390
                )
391
            })
UNCOV
392
            this.poolCluster.add(
×
393
                "MASTER",
394
                this.createConnectionOptions(
395
                    this.options,
396
                    this.options.replication.master,
397
                ),
398
            )
399
        } else {
UNCOV
400
            this.pool = await this.createPool(
×
401
                this.createConnectionOptions(this.options, this.options),
402
            )
403
        }
404

UNCOV
405
        if (!this.database) {
×
406
            const queryRunner = this.createQueryRunner("master")
×
407

408
            this.database = await queryRunner.getCurrentDatabase()
×
409

410
            await queryRunner.release()
×
411
        }
412

UNCOV
413
        const queryRunner = this.createQueryRunner("master")
×
UNCOV
414
        this.version = await queryRunner.getVersion()
×
UNCOV
415
        await queryRunner.release()
×
416

UNCOV
417
        if (this.options.type === "mariadb") {
×
UNCOV
418
            if (VersionUtils.isGreaterOrEqual(this.version, "10.0.5")) {
×
UNCOV
419
                this._isReturningSqlSupported.delete = true
×
420
            }
UNCOV
421
            if (VersionUtils.isGreaterOrEqual(this.version, "10.5.0")) {
×
UNCOV
422
                this._isReturningSqlSupported.insert = true
×
423
            }
UNCOV
424
            if (VersionUtils.isGreaterOrEqual(this.version, "10.2.0")) {
×
UNCOV
425
                this.cteCapabilities.enabled = true
×
426
            }
UNCOV
427
            if (VersionUtils.isGreaterOrEqual(this.version, "10.7.0")) {
×
UNCOV
428
                this.uuidColumnTypeSuported = true
×
429
            }
UNCOV
430
        } else if (this.options.type === "mysql") {
×
UNCOV
431
            if (VersionUtils.isGreaterOrEqual(this.version, "8.0.0")) {
×
432
                this.cteCapabilities.enabled = true
×
433
            }
434
        }
435
    }
436

437
    /**
438
     * Makes any action after connection (e.g. create extensions in Postgres driver).
439
     */
440
    afterConnect(): Promise<void> {
UNCOV
441
        return Promise.resolve()
×
442
    }
443

444
    /**
445
     * Closes connection with the database.
446
     */
447
    async disconnect(): Promise<void> {
UNCOV
448
        if (!this.poolCluster && !this.pool)
×
449
            return Promise.reject(new ConnectionIsNotSetError("mysql"))
×
450

UNCOV
451
        if (this.poolCluster) {
×
UNCOV
452
            return new Promise<void>((ok, fail) => {
×
UNCOV
453
                this.poolCluster.end((err: any) => (err ? fail(err) : ok()))
×
UNCOV
454
                this.poolCluster = undefined
×
455
            })
456
        }
UNCOV
457
        if (this.pool) {
×
UNCOV
458
            return new Promise<void>((ok, fail) => {
×
UNCOV
459
                this.pool.end((err: any) => {
×
UNCOV
460
                    if (err) return fail(err)
×
UNCOV
461
                    this.pool = undefined
×
UNCOV
462
                    ok()
×
463
                })
464
            })
465
        }
466
    }
467

468
    /**
469
     * Creates a schema builder used to build and sync a schema.
470
     */
471
    createSchemaBuilder() {
UNCOV
472
        return new RdbmsSchemaBuilder(this.connection)
×
473
    }
474

475
    /**
476
     * Creates a query runner used to execute database queries.
477
     */
478
    createQueryRunner(mode: ReplicationMode) {
UNCOV
479
        return new MysqlQueryRunner(this, mode)
×
480
    }
481

482
    /**
483
     * Replaces parameters in the given sql with special escaping character
484
     * and an array of parameter names to be passed to a query.
485
     */
486
    escapeQueryWithParameters(
487
        sql: string,
488
        parameters: ObjectLiteral,
489
        nativeParameters: ObjectLiteral,
490
    ): [string, any[]] {
UNCOV
491
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
×
492
            (key) => nativeParameters[key],
×
493
        )
UNCOV
494
        if (!parameters || !Object.keys(parameters).length)
×
UNCOV
495
            return [sql, escapedParameters]
×
496

UNCOV
497
        sql = sql.replace(
×
498
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
499
            (full, isArray: string, key: string): string => {
UNCOV
500
                if (!parameters.hasOwnProperty(key)) {
×
501
                    return full
×
502
                }
503

UNCOV
504
                const value: any = parameters[key]
×
505

UNCOV
506
                if (isArray) {
×
UNCOV
507
                    return value
×
508
                        .map((v: any) => {
UNCOV
509
                            escapedParameters.push(v)
×
UNCOV
510
                            return this.createParameter(
×
511
                                key,
512
                                escapedParameters.length - 1,
513
                            )
514
                        })
515
                        .join(", ")
516
                }
517

UNCOV
518
                if (typeof value === "function") {
×
519
                    return value()
×
520
                }
521

UNCOV
522
                escapedParameters.push(value)
×
UNCOV
523
                return this.createParameter(key, escapedParameters.length - 1)
×
524
            },
525
        ) // todo: make replace only in value statements, otherwise problems
UNCOV
526
        return [sql, escapedParameters]
×
527
    }
528

529
    /**
530
     * Escapes a column name.
531
     */
532
    escape(columnName: string): string {
UNCOV
533
        return "`" + columnName + "`"
×
534
    }
535

536
    /**
537
     * Build full table name with database name, schema name and table name.
538
     * E.g. myDB.mySchema.myTable
539
     */
540
    buildTableName(
541
        tableName: string,
542
        schema?: string,
543
        database?: string,
544
    ): string {
545
        const tablePath = [tableName]
18✔
546

547
        if (database) {
18!
UNCOV
548
            tablePath.unshift(database)
×
549
        }
550

551
        return tablePath.join(".")
18✔
552
    }
553

554
    /**
555
     * Parse a target table name or other types and return a normalized table definition.
556
     */
557
    parseTableName(
558
        target: EntityMetadata | Table | View | TableForeignKey | string,
559
    ): { database?: string; schema?: string; tableName: string } {
UNCOV
560
        const driverDatabase = this.database
×
UNCOV
561
        const driverSchema = undefined
×
562

UNCOV
563
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
×
UNCOV
564
            const parsed = this.parseTableName(target.name)
×
565

UNCOV
566
            return {
×
567
                database: target.database || parsed.database || driverDatabase,
×
568
                schema: target.schema || parsed.schema || driverSchema,
×
569
                tableName: parsed.tableName,
570
            }
571
        }
572

UNCOV
573
        if (InstanceChecker.isTableForeignKey(target)) {
×
UNCOV
574
            const parsed = this.parseTableName(target.referencedTableName)
×
575

UNCOV
576
            return {
×
577
                database:
578
                    target.referencedDatabase ||
×
579
                    parsed.database ||
580
                    driverDatabase,
581
                schema:
582
                    target.referencedSchema || parsed.schema || driverSchema,
×
583
                tableName: parsed.tableName,
584
            }
585
        }
586

UNCOV
587
        if (InstanceChecker.isEntityMetadata(target)) {
×
588
            // EntityMetadata tableName is never a path
589

UNCOV
590
            return {
×
591
                database: target.database || driverDatabase,
×
592
                schema: target.schema || driverSchema,
×
593
                tableName: target.tableName,
594
            }
595
        }
596

UNCOV
597
        const parts = target.split(".")
×
598

UNCOV
599
        return {
×
600
            database:
601
                (parts.length > 1 ? parts[0] : undefined) || driverDatabase,
×
602
            schema: driverSchema,
603
            tableName: parts.length > 1 ? parts[1] : parts[0],
×
604
        }
605
    }
606

607
    /**
608
     * Prepares given value to a value to be persisted, based on its column type and metadata.
609
     */
610
    preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
UNCOV
611
        if (columnMetadata.transformer)
×
UNCOV
612
            value = ApplyValueTransformers.transformTo(
×
613
                columnMetadata.transformer,
614
                value,
615
            )
616

UNCOV
617
        if (value === null || value === undefined) return value
×
618

UNCOV
619
        if (columnMetadata.type === Boolean) {
×
UNCOV
620
            return value === true ? 1 : 0
×
UNCOV
621
        } else if (columnMetadata.type === "date") {
×
UNCOV
622
            return DateUtils.mixedDateToDateString(value)
×
UNCOV
623
        } else if (columnMetadata.type === "time") {
×
UNCOV
624
            return DateUtils.mixedDateToTimeString(value)
×
UNCOV
625
        } else if (columnMetadata.type === "json") {
×
UNCOV
626
            return JSON.stringify(value)
×
UNCOV
627
        } else if (
×
628
            columnMetadata.type === "timestamp" ||
×
629
            columnMetadata.type === "datetime" ||
630
            columnMetadata.type === Date
631
        ) {
UNCOV
632
            return DateUtils.mixedDateToDate(value)
×
UNCOV
633
        } else if (columnMetadata.type === "simple-array") {
×
UNCOV
634
            return DateUtils.simpleArrayToString(value)
×
UNCOV
635
        } else if (columnMetadata.type === "simple-json") {
×
UNCOV
636
            return DateUtils.simpleJsonToString(value)
×
UNCOV
637
        } else if (
×
638
            columnMetadata.type === "enum" ||
×
639
            columnMetadata.type === "simple-enum"
640
        ) {
UNCOV
641
            return "" + value
×
UNCOV
642
        } else if (columnMetadata.type === "set") {
×
UNCOV
643
            return DateUtils.simpleArrayToString(value)
×
UNCOV
644
        } else if (columnMetadata.type === Number) {
×
645
            // convert to number if number
UNCOV
646
            value = !isNaN(+value) ? parseInt(value) : value
×
647
        }
648

UNCOV
649
        return value
×
650
    }
651

652
    /**
653
     * Prepares given value to a value to be persisted, based on its column type or metadata.
654
     */
655
    prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
UNCOV
656
        if (value === null || value === undefined)
×
UNCOV
657
            return columnMetadata.transformer
×
658
                ? ApplyValueTransformers.transformFrom(
659
                      columnMetadata.transformer,
660
                      value,
661
                  )
662
                : value
663

UNCOV
664
        if (
×
665
            columnMetadata.type === Boolean ||
×
666
            columnMetadata.type === "bool" ||
667
            columnMetadata.type === "boolean"
668
        ) {
UNCOV
669
            value = value ? true : false
×
UNCOV
670
        } else if (
×
671
            columnMetadata.type === "datetime" ||
×
672
            columnMetadata.type === Date
673
        ) {
UNCOV
674
            value = DateUtils.normalizeHydratedDate(value)
×
UNCOV
675
        } else if (columnMetadata.type === "date") {
×
UNCOV
676
            value = DateUtils.mixedDateToDateString(value)
×
UNCOV
677
        } else if (columnMetadata.type === "json") {
×
UNCOV
678
            value = typeof value === "string" ? JSON.parse(value) : value
×
UNCOV
679
        } else if (columnMetadata.type === "time") {
×
UNCOV
680
            value = DateUtils.mixedTimeToString(value)
×
UNCOV
681
        } else if (columnMetadata.type === "simple-array") {
×
UNCOV
682
            value = DateUtils.stringToSimpleArray(value)
×
UNCOV
683
        } else if (columnMetadata.type === "simple-json") {
×
UNCOV
684
            value = DateUtils.stringToSimpleJson(value)
×
UNCOV
685
        } else if (
×
686
            (columnMetadata.type === "enum" ||
×
687
                columnMetadata.type === "simple-enum") &&
688
            columnMetadata.enum &&
689
            !isNaN(value) &&
690
            columnMetadata.enum.indexOf(parseInt(value)) >= 0
691
        ) {
692
            // convert to number if that exists in possible enum options
UNCOV
693
            value = parseInt(value)
×
UNCOV
694
        } else if (columnMetadata.type === "set") {
×
UNCOV
695
            value = DateUtils.stringToSimpleArray(value)
×
UNCOV
696
        } else if (columnMetadata.type === Number) {
×
697
            // convert to number if number
UNCOV
698
            value = !isNaN(+value) ? parseInt(value) : value
×
699
        }
700

UNCOV
701
        if (columnMetadata.transformer)
×
UNCOV
702
            value = ApplyValueTransformers.transformFrom(
×
703
                columnMetadata.transformer,
704
                value,
705
            )
706

UNCOV
707
        return value
×
708
    }
709

710
    /**
711
     * Creates a database type from a given column metadata.
712
     */
713
    normalizeType(column: {
714
        type: ColumnType
715
        length?: number | string
716
        precision?: number | null
717
        scale?: number
718
    }): string {
719
        if (column.type === Number || column.type === "integer") {
55✔
720
            return "int"
39✔
721
        } else if (column.type === String) {
16!
722
            return "varchar"
16✔
UNCOV
723
        } else if (column.type === Date) {
×
UNCOV
724
            return "datetime"
×
UNCOV
725
        } else if ((column.type as any) === Buffer) {
×
UNCOV
726
            return "blob"
×
UNCOV
727
        } else if (column.type === Boolean) {
×
UNCOV
728
            return "tinyint"
×
UNCOV
729
        } else if (column.type === "uuid" && !this.uuidColumnTypeSuported) {
×
UNCOV
730
            return "varchar"
×
UNCOV
731
        } else if (
×
732
            column.type === "json" &&
×
733
            this.options.type === "mariadb" &&
734
            !VersionUtils.isGreaterOrEqual(this.version, "10.4.3")
735
        ) {
736
            /*
737
             * MariaDB implements this as a LONGTEXT rather, as the JSON data type contradicts the SQL standard,
738
             * and MariaDB's benchmarks indicate that performance is at least equivalent.
739
             *
740
             * @see https://mariadb.com/kb/en/json-data-type/
741
             * if Version is 10.4.3 or greater, JSON is an alias for longtext and an automatic check_json(column) constraint is added
742
             */
743
            return "longtext"
×
UNCOV
744
        } else if (
×
745
            column.type === "simple-array" ||
×
746
            column.type === "simple-json"
747
        ) {
UNCOV
748
            return "text"
×
UNCOV
749
        } else if (column.type === "simple-enum") {
×
UNCOV
750
            return "enum"
×
UNCOV
751
        } else if (
×
752
            column.type === "double precision" ||
×
753
            column.type === "real"
754
        ) {
UNCOV
755
            return "double"
×
UNCOV
756
        } else if (
×
757
            column.type === "dec" ||
×
758
            column.type === "numeric" ||
759
            column.type === "fixed"
760
        ) {
UNCOV
761
            return "decimal"
×
UNCOV
762
        } else if (column.type === "bool" || column.type === "boolean") {
×
UNCOV
763
            return "tinyint"
×
UNCOV
764
        } else if (
×
765
            column.type === "nvarchar" ||
×
766
            column.type === "national varchar"
767
        ) {
UNCOV
768
            return "varchar"
×
UNCOV
769
        } else if (column.type === "nchar" || column.type === "national char") {
×
UNCOV
770
            return "char"
×
771
        } else {
UNCOV
772
            return (column.type as string) || ""
×
773
        }
774
    }
775

776
    /**
777
     * Normalizes "default" value of the column.
778
     */
779
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
UNCOV
780
        const defaultValue = columnMetadata.default
×
781

UNCOV
782
        if (defaultValue === null) {
×
UNCOV
783
            return undefined
×
784
        }
785

UNCOV
786
        if (
×
787
            (columnMetadata.type === "enum" ||
×
788
                columnMetadata.type === "simple-enum" ||
789
                typeof defaultValue === "string") &&
790
            defaultValue !== undefined
791
        ) {
UNCOV
792
            return `'${defaultValue}'`
×
793
        }
794

UNCOV
795
        if (columnMetadata.type === "set" && defaultValue !== undefined) {
×
UNCOV
796
            return `'${DateUtils.simpleArrayToString(defaultValue)}'`
×
797
        }
798

UNCOV
799
        if (typeof defaultValue === "number") {
×
UNCOV
800
            return `'${defaultValue.toFixed(columnMetadata.scale)}'`
×
801
        }
802

UNCOV
803
        if (typeof defaultValue === "boolean") {
×
UNCOV
804
            return defaultValue ? "1" : "0"
×
805
        }
806

UNCOV
807
        if (typeof defaultValue === "function") {
×
UNCOV
808
            const value = defaultValue()
×
UNCOV
809
            return this.normalizeDatetimeFunction(value)
×
810
        }
811

UNCOV
812
        if (defaultValue === undefined) {
×
UNCOV
813
            return undefined
×
814
        }
815

816
        return `${defaultValue}`
×
817
    }
818

819
    /**
820
     * Normalizes "isUnique" value of the column.
821
     */
822
    normalizeIsUnique(column: ColumnMetadata): boolean {
UNCOV
823
        return column.entityMetadata.indices.some(
×
824
            (idx) =>
UNCOV
825
                idx.isUnique &&
×
826
                idx.columns.length === 1 &&
827
                idx.columns[0] === column,
828
        )
829
    }
830

831
    /**
832
     * Returns default column lengths, which is required on column creation.
833
     */
834
    getColumnLength(column: ColumnMetadata | TableColumn): string {
UNCOV
835
        if (column.length) return column.length.toString()
×
836

837
        /**
838
         * fix https://github.com/typeorm/typeorm/issues/1139
839
         * note that if the db did support uuid column type it wouldn't have been defaulted to varchar
840
         */
UNCOV
841
        if (
×
842
            column.generationStrategy === "uuid" &&
×
843
            !this.uuidColumnTypeSuported
844
        )
UNCOV
845
            return "36"
×
846

UNCOV
847
        switch (column.type) {
×
848
            case String:
849
            case "varchar":
850
            case "nvarchar":
851
            case "national varchar":
UNCOV
852
                return "255"
×
853
            case "varbinary":
UNCOV
854
                return "255"
×
855
            default:
UNCOV
856
                return ""
×
857
        }
858
    }
859

860
    /**
861
     * Creates column type definition including length, precision and scale
862
     */
863
    createFullType(column: TableColumn): string {
UNCOV
864
        let type = column.type
×
865

866
        // used 'getColumnLength()' method, because MySQL requires column length for `varchar`, `nvarchar` and `varbinary` data types
UNCOV
867
        if (this.getColumnLength(column)) {
×
UNCOV
868
            type += `(${this.getColumnLength(column)})`
×
UNCOV
869
        } else if (column.width) {
×
UNCOV
870
            type += `(${column.width})`
×
UNCOV
871
        } else if (
×
872
            column.precision !== null &&
×
873
            column.precision !== undefined &&
874
            column.scale !== null &&
875
            column.scale !== undefined
876
        ) {
UNCOV
877
            type += `(${column.precision},${column.scale})`
×
UNCOV
878
        } else if (
×
879
            column.precision !== null &&
×
880
            column.precision !== undefined
881
        ) {
UNCOV
882
            type += `(${column.precision})`
×
883
        }
884

UNCOV
885
        if (column.isArray) type += " array"
×
886

UNCOV
887
        return type
×
888
    }
889

890
    /**
891
     * Obtains a new database connection to a master server.
892
     * Used for replication.
893
     * If replication is not setup then returns default connection's database connection.
894
     */
895
    obtainMasterConnection(): Promise<any> {
UNCOV
896
        return new Promise<any>((ok, fail) => {
×
UNCOV
897
            if (this.poolCluster) {
×
UNCOV
898
                this.poolCluster.getConnection(
×
899
                    "MASTER",
900
                    (err: any, dbConnection: any) => {
UNCOV
901
                        err
×
902
                            ? fail(err)
903
                            : ok(this.prepareDbConnection(dbConnection))
904
                    },
905
                )
UNCOV
906
            } else if (this.pool) {
×
UNCOV
907
                this.pool.getConnection((err: any, dbConnection: any) => {
×
UNCOV
908
                    err ? fail(err) : ok(this.prepareDbConnection(dbConnection))
×
909
                })
910
            } else {
911
                fail(
×
912
                    new TypeORMError(
913
                        `Connection is not established with mysql database`,
914
                    ),
915
                )
916
            }
917
        })
918
    }
919

920
    /**
921
     * Obtains a new database connection to a slave server.
922
     * Used for replication.
923
     * If replication is not setup then returns master (default) connection's database connection.
924
     */
925
    obtainSlaveConnection(): Promise<any> {
926
        if (!this.poolCluster) return this.obtainMasterConnection()
×
927

928
        return new Promise<any>((ok, fail) => {
×
929
            this.poolCluster.getConnection(
×
930
                "SLAVE*",
931
                (err: any, dbConnection: any) => {
932
                    err ? fail(err) : ok(this.prepareDbConnection(dbConnection))
×
933
                },
934
            )
935
        })
936
    }
937

938
    /**
939
     * Creates generated map of values generated or returned by database after INSERT query.
940
     */
941
    createGeneratedMap(
942
        metadata: EntityMetadata,
943
        insertResult: any,
944
        entityIndex: number,
945
    ) {
UNCOV
946
        if (!insertResult) {
×
UNCOV
947
            return undefined
×
948
        }
949

UNCOV
950
        if (insertResult.insertId === undefined) {
×
UNCOV
951
            return Object.keys(insertResult).reduce((map, key) => {
×
UNCOV
952
                const column = metadata.findColumnWithDatabaseName(key)
×
UNCOV
953
                if (column) {
×
UNCOV
954
                    OrmUtils.mergeDeep(
×
955
                        map,
956
                        column.createValueMap(insertResult[key]),
957
                    )
958
                    // OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column))); // TODO: probably should be like there, but fails on enums, fix later
959
                }
UNCOV
960
                return map
×
961
            }, {} as ObjectLiteral)
962
        }
963

UNCOV
964
        const generatedMap = metadata.generatedColumns.reduce(
×
965
            (map, generatedColumn) => {
966
                let value: any
UNCOV
967
                if (
×
968
                    generatedColumn.generationStrategy === "increment" &&
×
969
                    insertResult.insertId
970
                ) {
971
                    // NOTE: When multiple rows is inserted by a single INSERT statement,
972
                    // `insertId` is the value generated for the first inserted row only.
UNCOV
973
                    value = insertResult.insertId + entityIndex
×
974
                    // } else if (generatedColumn.generationStrategy === "uuid") {
975
                    //     console.log("getting db value:", generatedColumn.databaseName);
976
                    //     value = generatedColumn.getEntityValue(uuidMap);
977
                }
978

UNCOV
979
                return OrmUtils.mergeDeep(
×
980
                    map,
981
                    generatedColumn.createValueMap(value),
982
                )
983
            },
984
            {} as ObjectLiteral,
985
        )
986

UNCOV
987
        return Object.keys(generatedMap).length > 0 ? generatedMap : undefined
×
988
    }
989

990
    /**
991
     * Differentiate columns of this table and columns from the given column metadatas columns
992
     * and returns only changed.
993
     */
994
    findChangedColumns(
995
        tableColumns: TableColumn[],
996
        columnMetadatas: ColumnMetadata[],
997
    ): ColumnMetadata[] {
UNCOV
998
        return columnMetadatas.filter((columnMetadata) => {
×
UNCOV
999
            const tableColumn = tableColumns.find(
×
UNCOV
1000
                (c) => c.name === columnMetadata.databaseName,
×
1001
            )
UNCOV
1002
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
×
1003

1004
            const isColumnChanged =
UNCOV
1005
                tableColumn.name !== columnMetadata.databaseName ||
×
1006
                this.isColumnDataTypeChanged(tableColumn, columnMetadata) ||
1007
                tableColumn.length !== this.getColumnLength(columnMetadata) ||
1008
                tableColumn.width !== columnMetadata.width ||
1009
                (columnMetadata.precision !== undefined &&
1010
                    tableColumn.precision !== columnMetadata.precision) ||
1011
                (columnMetadata.scale !== undefined &&
1012
                    tableColumn.scale !== columnMetadata.scale) ||
1013
                tableColumn.zerofill !== columnMetadata.zerofill ||
1014
                tableColumn.unsigned !== columnMetadata.unsigned ||
1015
                tableColumn.asExpression !== columnMetadata.asExpression ||
1016
                tableColumn.generatedType !== columnMetadata.generatedType ||
1017
                tableColumn.comment !==
1018
                    this.escapeComment(columnMetadata.comment) ||
1019
                !this.compareDefaultValues(
1020
                    this.normalizeDefault(columnMetadata),
1021
                    tableColumn.default,
1022
                ) ||
1023
                (tableColumn.enum &&
1024
                    columnMetadata.enum &&
1025
                    !OrmUtils.isArraysEqual(
1026
                        tableColumn.enum,
UNCOV
1027
                        columnMetadata.enum.map((val) => val + ""),
×
1028
                    )) ||
1029
                tableColumn.onUpdate !==
1030
                    this.normalizeDatetimeFunction(columnMetadata.onUpdate) ||
1031
                tableColumn.isPrimary !== columnMetadata.isPrimary ||
1032
                !this.compareNullableValues(columnMetadata, tableColumn) ||
1033
                tableColumn.isUnique !==
1034
                    this.normalizeIsUnique(columnMetadata) ||
1035
                (columnMetadata.generationStrategy !== "uuid" &&
1036
                    tableColumn.isGenerated !== columnMetadata.isGenerated)
1037

1038
            // DEBUG SECTION
1039
            // if (isColumnChanged) {
1040
            //     console.log("table:", columnMetadata.entityMetadata.tableName)
1041
            //     console.log(
1042
            //         "name:",
1043
            //         tableColumn.name,
1044
            //         columnMetadata.databaseName,
1045
            //     )
1046
            //     console.log(
1047
            //         "type:",
1048
            //         tableColumn.type,
1049
            //         this.normalizeType(columnMetadata),
1050
            //     )
1051
            //     console.log(
1052
            //         "length:",
1053
            //         tableColumn.length,
1054
            //         columnMetadata.length,
1055
            //     )
1056
            //     console.log("width:", tableColumn.width, columnMetadata.width)
1057
            //     console.log(
1058
            //         "precision:",
1059
            //         tableColumn.precision,
1060
            //         columnMetadata.precision,
1061
            //     )
1062
            //     console.log("scale:", tableColumn.scale, columnMetadata.scale)
1063
            //     console.log(
1064
            //         "zerofill:",
1065
            //         tableColumn.zerofill,
1066
            //         columnMetadata.zerofill,
1067
            //     )
1068
            //     console.log(
1069
            //         "unsigned:",
1070
            //         tableColumn.unsigned,
1071
            //         columnMetadata.unsigned,
1072
            //     )
1073
            //     console.log(
1074
            //         "asExpression:",
1075
            //         tableColumn.asExpression,
1076
            //         columnMetadata.asExpression,
1077
            //     )
1078
            //     console.log(
1079
            //         "generatedType:",
1080
            //         tableColumn.generatedType,
1081
            //         columnMetadata.generatedType,
1082
            //     )
1083
            //     console.log(
1084
            //         "comment:",
1085
            //         tableColumn.comment,
1086
            //         this.escapeComment(columnMetadata.comment),
1087
            //     )
1088
            //     console.log(
1089
            //         "default:",
1090
            //         tableColumn.default,
1091
            //         this.normalizeDefault(columnMetadata),
1092
            //     )
1093
            //     console.log("enum:", tableColumn.enum, columnMetadata.enum)
1094
            //     console.log(
1095
            //         "default changed:",
1096
            //         !this.compareDefaultValues(
1097
            //             this.normalizeDefault(columnMetadata),
1098
            //             tableColumn.default,
1099
            //         ),
1100
            //     )
1101
            //     console.log(
1102
            //         "isPrimary:",
1103
            //         tableColumn.isPrimary,
1104
            //         columnMetadata.isPrimary,
1105
            //     )
1106
            //     console.log(
1107
            //         "isNullable changed:",
1108
            //         !this.compareNullableValues(columnMetadata, tableColumn),
1109
            //     )
1110
            //     console.log(
1111
            //         "isUnique:",
1112
            //         tableColumn.isUnique,
1113
            //         this.normalizeIsUnique(columnMetadata),
1114
            //     )
1115
            //     console.log(
1116
            //         "isGenerated:",
1117
            //         tableColumn.isGenerated,
1118
            //         columnMetadata.isGenerated,
1119
            //     )
1120
            //     console.log(
1121
            //         columnMetadata.generationStrategy !== "uuid" &&
1122
            //             tableColumn.isGenerated !== columnMetadata.isGenerated,
1123
            //     )
1124
            //     console.log("==========================================")
1125
            // }
1126

UNCOV
1127
            return isColumnChanged
×
1128
        })
1129
    }
1130

1131
    /**
1132
     * Returns true if driver supports RETURNING / OUTPUT statement.
1133
     */
1134
    isReturningSqlSupported(returningType: ReturningType): boolean {
UNCOV
1135
        return this._isReturningSqlSupported[returningType]
×
1136
    }
1137

1138
    /**
1139
     * Returns true if driver supports uuid values generation on its own.
1140
     */
1141
    isUUIDGenerationSupported(): boolean {
UNCOV
1142
        return false
×
1143
    }
1144

1145
    /**
1146
     * Returns true if driver supports fulltext indices.
1147
     */
1148
    isFullTextColumnTypeSupported(): boolean {
UNCOV
1149
        return true
×
1150
    }
1151

1152
    /**
1153
     * Creates an escaped parameter.
1154
     */
1155
    createParameter(parameterName: string, index: number): string {
UNCOV
1156
        return "?"
×
1157
    }
1158

1159
    // -------------------------------------------------------------------------
1160
    // Protected Methods
1161
    // -------------------------------------------------------------------------
1162

1163
    /**
1164
     * Loads all driver dependencies.
1165
     */
1166
    protected loadDependencies(): void {
1167
        const connectorPackage = this.options.connectorPackage ?? "mysql"
13✔
1168
        const fallbackConnectorPackage =
1169
            connectorPackage === "mysql"
13!
1170
                ? ("mysql2" as const)
1171
                : ("mysql" as const)
1172
        try {
13✔
1173
            // try to load first supported package
1174
            const mysql =
1175
                this.options.driver || PlatformTools.load(connectorPackage)
13✔
1176
            this.mysql = mysql
13✔
1177
            /*
1178
             * Some frameworks (such as Jest) may mess up Node's require cache and provide garbage for the 'mysql' module
1179
             * if it was not installed. We check that the object we got actually contains something otherwise we treat
1180
             * it as if the `require` call failed.
1181
             *
1182
             * @see https://github.com/typeorm/typeorm/issues/1373
1183
             */
1184
            if (Object.keys(this.mysql).length === 0) {
13!
1185
                throw new TypeORMError(
×
1186
                    `'${connectorPackage}' was found but it is empty. Falling back to '${fallbackConnectorPackage}'.`,
1187
                )
1188
            }
1189
        } catch (e) {
1190
            try {
×
1191
                this.mysql = PlatformTools.load(fallbackConnectorPackage) // try to load second supported package
×
1192
            } catch (e) {
1193
                throw new DriverPackageNotInstalledError(
×
1194
                    "Mysql",
1195
                    connectorPackage,
1196
                )
1197
            }
1198
        }
1199
    }
1200

1201
    /**
1202
     * Creates a new connection pool for a given database credentials.
1203
     */
1204
    protected createConnectionOptions(
1205
        options: MysqlConnectionOptions,
1206
        credentials: MysqlConnectionCredentialsOptions,
1207
    ): Promise<any> {
UNCOV
1208
        credentials = Object.assign(
×
1209
            {},
1210
            credentials,
1211
            DriverUtils.buildDriverOptions(credentials),
1212
        ) // todo: do it better way
1213

1214
        // build connection options for the driver
UNCOV
1215
        return Object.assign(
×
1216
            {},
1217
            {
1218
                charset: options.charset,
1219
                timezone: options.timezone,
1220
                connectTimeout: options.connectTimeout,
1221
                insecureAuth: options.insecureAuth,
1222
                supportBigNumbers:
1223
                    options.supportBigNumbers !== undefined
×
1224
                        ? options.supportBigNumbers
1225
                        : true,
1226
                bigNumberStrings:
1227
                    options.bigNumberStrings !== undefined
×
1228
                        ? options.bigNumberStrings
1229
                        : true,
1230
                dateStrings: options.dateStrings,
1231
                debug: options.debug,
1232
                trace: options.trace,
1233
                multipleStatements: options.multipleStatements,
1234
                flags: options.flags,
1235
            },
1236
            {
1237
                host: credentials.host,
1238
                user: credentials.username,
1239
                password: credentials.password,
1240
                database: credentials.database,
1241
                port: credentials.port,
1242
                ssl: options.ssl,
1243
                socketPath: credentials.socketPath,
1244
            },
1245
            options.acquireTimeout === undefined
×
1246
                ? {}
1247
                : { acquireTimeout: options.acquireTimeout },
1248
            { connectionLimit: options.poolSize },
1249
            options.extra || {},
×
1250
        )
1251
    }
1252

1253
    /**
1254
     * Creates a new connection pool for a given database credentials.
1255
     */
1256
    protected createPool(connectionOptions: any): Promise<any> {
1257
        // create a connection pool
UNCOV
1258
        const pool = this.mysql.createPool(connectionOptions)
×
1259

1260
        // make sure connection is working fine
UNCOV
1261
        return new Promise<void>((ok, fail) => {
×
1262
            // (issue #610) we make first connection to database to make sure if connection credentials are wrong
1263
            // we give error before calling any other method that creates actual query runner
UNCOV
1264
            pool.getConnection((err: any, connection: any) => {
×
UNCOV
1265
                if (err) return pool.end(() => fail(err))
×
1266

UNCOV
1267
                connection.release()
×
UNCOV
1268
                ok(pool)
×
1269
            })
1270
        })
1271
    }
1272

1273
    /**
1274
     * Attaches all required base handlers to a database connection, such as the unhandled error handler.
1275
     */
1276
    private prepareDbConnection(connection: any): any {
UNCOV
1277
        const { logger } = this.connection
×
1278
        /*
1279
         * Attaching an error handler to connection errors is essential, as, otherwise, errors raised will go unhandled and
1280
         * cause the hosting app to crash.
1281
         */
UNCOV
1282
        if (connection.listeners("error").length === 0) {
×
1283
            connection.on("error", (error: any) =>
×
1284
                logger.log(
×
1285
                    "warn",
1286
                    `MySQL connection raised an error. ${error}`,
1287
                ),
1288
            )
1289
        }
UNCOV
1290
        return connection
×
1291
    }
1292

1293
    /**
1294
     * Checks if "DEFAULT" values in the column metadata and in the database are equal.
1295
     */
1296
    protected compareDefaultValues(
1297
        columnMetadataValue: string | undefined,
1298
        databaseValue: string | undefined,
1299
    ): boolean {
UNCOV
1300
        if (
×
1301
            typeof columnMetadataValue === "string" &&
×
1302
            typeof databaseValue === "string"
1303
        ) {
1304
            // we need to cut out "'" because in mysql we can understand returned value is a string or a function
1305
            // as result compare cannot understand if default is really changed or not
UNCOV
1306
            columnMetadataValue = columnMetadataValue.replace(/^'+|'+$/g, "")
×
UNCOV
1307
            databaseValue = databaseValue.replace(/^'+|'+$/g, "")
×
1308
        }
1309

UNCOV
1310
        return columnMetadataValue === databaseValue
×
1311
    }
1312

1313
    compareNullableValues(
1314
        columnMetadata: ColumnMetadata,
1315
        tableColumn: TableColumn,
1316
    ): boolean {
1317
        // MariaDB does not support NULL/NOT NULL expressions for generated columns
UNCOV
1318
        const isMariaDb = this.options.type === "mariadb"
×
UNCOV
1319
        if (isMariaDb && columnMetadata.generatedType) {
×
UNCOV
1320
            return true
×
1321
        }
1322

UNCOV
1323
        return columnMetadata.isNullable === tableColumn.isNullable
×
1324
    }
1325

1326
    /**
1327
     * If parameter is a datetime function, e.g. "CURRENT_TIMESTAMP", normalizes it.
1328
     * Otherwise returns original input.
1329
     */
1330
    protected normalizeDatetimeFunction(value?: string) {
UNCOV
1331
        if (!value) return value
×
1332

1333
        // check if input is datetime function
1334
        const isDatetimeFunction =
UNCOV
1335
            value.toUpperCase().indexOf("CURRENT_TIMESTAMP") !== -1 ||
×
1336
            value.toUpperCase().indexOf("NOW") !== -1
1337

UNCOV
1338
        if (isDatetimeFunction) {
×
1339
            // extract precision, e.g. "(3)"
UNCOV
1340
            const precision = value.match(/\(\d+\)/)
×
UNCOV
1341
            if (this.options.type === "mariadb") {
×
UNCOV
1342
                return precision
×
1343
                    ? `CURRENT_TIMESTAMP${precision[0]}`
1344
                    : "CURRENT_TIMESTAMP()"
1345
            } else {
UNCOV
1346
                return precision
×
1347
                    ? `CURRENT_TIMESTAMP${precision[0]}`
1348
                    : "CURRENT_TIMESTAMP"
1349
            }
1350
        } else {
UNCOV
1351
            return value
×
1352
        }
1353
    }
1354

1355
    /**
1356
     * Escapes a given comment.
1357
     */
1358
    protected escapeComment(comment?: string) {
UNCOV
1359
        if (!comment) return comment
×
1360

UNCOV
1361
        comment = comment.replace(/\u0000/g, "") // Null bytes aren't allowed in comments
×
1362

UNCOV
1363
        return comment
×
1364
    }
1365

1366
    /**
1367
     * A helper to check if column data types have changed
1368
     * This can be used to manage checking any types the
1369
     * database may alias
1370
     */
1371
    private isColumnDataTypeChanged(
1372
        tableColumn: TableColumn,
1373
        columnMetadata: ColumnMetadata,
1374
    ) {
1375
        // this is an exception for mariadb versions where json is an alias for longtext
UNCOV
1376
        if (
×
1377
            this.normalizeType(columnMetadata) === "json" &&
×
1378
            tableColumn.type.toLowerCase() === "longtext"
1379
        )
UNCOV
1380
            return false
×
UNCOV
1381
        return tableColumn.type !== this.normalizeType(columnMetadata)
×
1382
    }
1383
}
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