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

typeorm / typeorm / 14081280213

26 Mar 2025 10:41AM UTC coverage: 72.384% (+0.004%) from 72.38%
14081280213

push

github

web-flow
refactor: database server version fetching & comparison (#11357)

8658 of 12643 branches covered (68.48%)

Branch coverage included in aggregate %.

37 of 59 new or added lines in 13 files covered. (62.71%)

3 existing lines in 3 files now uncovered.

17889 of 24032 relevant lines covered (74.44%)

159593.23 hits per line

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

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

32
/**
33
 * Organizes communication with PostgreSQL DBMS.
34
 */
35
export class PostgresDriver implements Driver {
35✔
36
    // -------------------------------------------------------------------------
37
    // Public Properties
38
    // -------------------------------------------------------------------------
39

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

45
    /**
46
     * Postgres underlying library.
47
     */
48
    postgres: any
49

50
    /**
51
     * Pool for master database.
52
     */
53
    master: any
54

55
    /**
56
     * Pool for slave databases.
57
     * Used in replication.
58
     */
59
    slaves: any[] = []
1,932✔
60

61
    /**
62
     * We store all created query runners because we need to release them.
63
     */
64
    connectedQueryRunners: QueryRunner[] = []
1,932✔
65

66
    // -------------------------------------------------------------------------
67
    // Public Implemented Properties
68
    // -------------------------------------------------------------------------
69

70
    /**
71
     * Connection options.
72
     */
73
    options: PostgresConnectionOptions
74

75
    /**
76
     * Version of Postgres. Requires a SQL query to the DB, so it is not always set
77
     */
78
    version?: string
79

80
    /**
81
     * Database name used to perform all write queries.
82
     */
83
    database?: string
84

85
    /**
86
     * Schema name used to perform all write queries.
87
     */
88
    schema?: string
89

90
    /**
91
     * Schema that's used internally by Postgres for object resolution.
92
     *
93
     * Because we never set this we have to track it in separately from the `schema` so
94
     * we know when we have to specify the full schema or not.
95
     *
96
     * In most cases this will be `public`.
97
     */
98
    searchSchema?: string
99

100
    /**
101
     * Indicates if replication is enabled.
102
     */
103
    isReplicated: boolean = false
1,932✔
104

105
    /**
106
     * Indicates if tree tables are supported by this driver.
107
     */
108
    treeSupport = true
1,932✔
109

110
    /**
111
     * Represent transaction support by this driver
112
     */
113
    transactionSupport = "nested" as const
1,932✔
114

115
    /**
116
     * Gets list of supported column data types by a driver.
117
     *
118
     * @see https://www.tutorialspoint.com/postgresql/postgresql_data_types.htm
119
     * @see https://www.postgresql.org/docs/9.2/static/datatype.html
120
     */
121
    supportedDataTypes: ColumnType[] = [
1,932✔
122
        "int",
123
        "int2",
124
        "int4",
125
        "int8",
126
        "smallint",
127
        "integer",
128
        "bigint",
129
        "decimal",
130
        "numeric",
131
        "real",
132
        "float",
133
        "float4",
134
        "float8",
135
        "double precision",
136
        "money",
137
        "character varying",
138
        "varchar",
139
        "character",
140
        "char",
141
        "text",
142
        "citext",
143
        "hstore",
144
        "bytea",
145
        "bit",
146
        "varbit",
147
        "bit varying",
148
        "timetz",
149
        "timestamptz",
150
        "timestamp",
151
        "timestamp without time zone",
152
        "timestamp with time zone",
153
        "date",
154
        "time",
155
        "time without time zone",
156
        "time with time zone",
157
        "interval",
158
        "bool",
159
        "boolean",
160
        "enum",
161
        "point",
162
        "line",
163
        "lseg",
164
        "box",
165
        "path",
166
        "polygon",
167
        "circle",
168
        "cidr",
169
        "inet",
170
        "macaddr",
171
        "macaddr8",
172
        "tsvector",
173
        "tsquery",
174
        "uuid",
175
        "xml",
176
        "json",
177
        "jsonb",
178
        "int4range",
179
        "int8range",
180
        "numrange",
181
        "tsrange",
182
        "tstzrange",
183
        "daterange",
184
        "int4multirange",
185
        "int8multirange",
186
        "nummultirange",
187
        "tsmultirange",
188
        "tstzmultirange",
189
        "datemultirange",
190
        "geometry",
191
        "geography",
192
        "cube",
193
        "ltree",
194
    ]
195

196
    /**
197
     * Returns type of upsert supported by driver if any
198
     */
199
    supportedUpsertTypes: UpsertType[] = ["on-conflict-do-update"]
1,932✔
200

201
    /**
202
     * Gets list of spatial column data types.
203
     */
204
    spatialTypes: ColumnType[] = ["geometry", "geography"]
1,932✔
205

206
    /**
207
     * Gets list of column data types that support length by a driver.
208
     */
209
    withLengthColumnTypes: ColumnType[] = [
1,932✔
210
        "character varying",
211
        "varchar",
212
        "character",
213
        "char",
214
        "bit",
215
        "varbit",
216
        "bit varying",
217
    ]
218

219
    /**
220
     * Gets list of column data types that support precision by a driver.
221
     */
222
    withPrecisionColumnTypes: ColumnType[] = [
1,932✔
223
        "numeric",
224
        "decimal",
225
        "interval",
226
        "time without time zone",
227
        "time with time zone",
228
        "timestamp without time zone",
229
        "timestamp with time zone",
230
    ]
231

232
    /**
233
     * Gets list of column data types that support scale by a driver.
234
     */
235
    withScaleColumnTypes: ColumnType[] = ["numeric", "decimal"]
1,932✔
236

237
    /**
238
     * Orm has special columns and we need to know what database column types should be for those types.
239
     * Column types are driver dependant.
240
     */
241
    mappedDataTypes: MappedColumnTypes = {
1,932✔
242
        createDate: "timestamp",
243
        createDateDefault: "now()",
244
        updateDate: "timestamp",
245
        updateDateDefault: "now()",
246
        deleteDate: "timestamp",
247
        deleteDateNullable: true,
248
        version: "int4",
249
        treeLevel: "int4",
250
        migrationId: "int4",
251
        migrationName: "varchar",
252
        migrationTimestamp: "int8",
253
        cacheId: "int4",
254
        cacheIdentifier: "varchar",
255
        cacheTime: "int8",
256
        cacheDuration: "int4",
257
        cacheQuery: "text",
258
        cacheResult: "text",
259
        metadataType: "varchar",
260
        metadataDatabase: "varchar",
261
        metadataSchema: "varchar",
262
        metadataTable: "varchar",
263
        metadataName: "varchar",
264
        metadataValue: "text",
265
    }
266

267
    /**
268
     * The prefix used for the parameters
269
     */
270
    parametersPrefix: string = "$"
1,932✔
271

272
    /**
273
     * Default values of length, precision and scale depends on column data type.
274
     * Used in the cases when length/precision/scale is not specified by user.
275
     */
276
    dataTypeDefaults: DataTypeDefaults = {
1,932✔
277
        character: { length: 1 },
278
        bit: { length: 1 },
279
        interval: { precision: 6 },
280
        "time without time zone": { precision: 6 },
281
        "time with time zone": { precision: 6 },
282
        "timestamp without time zone": { precision: 6 },
283
        "timestamp with time zone": { precision: 6 },
284
    }
285

286
    /**
287
     * Max length allowed by Postgres for aliases.
288
     * @see https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
289
     */
290
    maxAliasLength = 63
1,932✔
291

292
    isGeneratedColumnsSupported: boolean = false
1,932✔
293

294
    cteCapabilities: CteCapabilities = {
1,932✔
295
        enabled: true,
296
        writable: true,
297
        requiresRecursiveHint: true,
298
        materializedHint: true,
299
    }
300

301
    // -------------------------------------------------------------------------
302
    // Constructor
303
    // -------------------------------------------------------------------------
304

305
    constructor(connection?: DataSource) {
306
        if (!connection) {
1,932!
307
            return
×
308
        }
309

310
        this.connection = connection
1,932✔
311
        this.options = connection.options as PostgresConnectionOptions
1,932✔
312
        this.isReplicated = this.options.replication ? true : false
1,932✔
313
        if (this.options.useUTC) {
1,932!
314
            process.env.PGTZ = "UTC"
×
315
        }
316
        // load postgres package
317
        this.loadDependencies()
1,932✔
318

319
        this.database = DriverUtils.buildDriverOptions(
1,932✔
320
            this.options.replication
1,932✔
321
                ? this.options.replication.master
322
                : this.options,
323
        ).database
324
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
1,932✔
325

326
        // ObjectUtils.assign(this.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
327
        // validate options to make sure everything is set
328
        // todo: revisit validation with replication in mind
329
        // if (!this.options.host)
330
        //     throw new DriverOptionNotSetError("host");
331
        // if (!this.options.username)
332
        //     throw new DriverOptionNotSetError("username");
333
        // if (!this.options.database)
334
        //     throw new DriverOptionNotSetError("database");
335
    }
336

337
    // -------------------------------------------------------------------------
338
    // Public Implemented Methods
339
    // -------------------------------------------------------------------------
340

341
    /**
342
     * Performs connection to the database.
343
     * Based on pooling options, it can either create connection immediately,
344
     * either create a pool and create connection when needed.
345
     */
346
    async connect(): Promise<void> {
347
        if (this.options.replication) {
1,932✔
348
            this.slaves = await Promise.all(
24✔
349
                this.options.replication.slaves.map((slave) => {
350
                    return this.createPool(this.options, slave)
24✔
351
                }),
352
            )
353
            this.master = await this.createPool(
24✔
354
                this.options,
355
                this.options.replication.master,
356
            )
357
        } else {
358
            this.master = await this.createPool(this.options, this.options)
1,908✔
359
        }
360

361
        const queryRunner = this.createQueryRunner("master")
1,932✔
362

363
        this.version = await queryRunner.getVersion()
1,932✔
364

365
        if (!this.database) {
1,932!
NEW
366
            this.database = await queryRunner.getCurrentDatabase()
×
367
        }
368

369
        if (!this.searchSchema) {
1,932✔
370
            this.searchSchema = await queryRunner.getCurrentSchema()
1,932✔
371
        }
372

373
        await queryRunner.release()
1,932✔
374

375
        if (!this.schema) {
1,932✔
376
            this.schema = this.searchSchema
1,902✔
377
        }
378
    }
379

380
    /**
381
     * Makes any action after connection (e.g. create extensions in Postgres driver).
382
     */
383
    async afterConnect(): Promise<void> {
384
        const extensionsMetadata = await this.checkMetadataForExtensions()
1,926✔
385
        const [connection, release] = await this.obtainMasterConnection()
1,926✔
386

387
        const installExtensions =
388
            this.options.installExtensions === undefined ||
1,926✔
389
            this.options.installExtensions
390
        if (installExtensions && extensionsMetadata.hasExtensions) {
1,926✔
391
            await this.enableExtensions(extensionsMetadata, connection)
219✔
392
        }
393

394
        this.isGeneratedColumnsSupported = VersionUtils.isGreaterOrEqual(
1,926✔
395
            this.version,
396
            "12.0",
397
        )
398

399
        await release()
1,926✔
400
    }
401

402
    protected async enableExtensions(extensionsMetadata: any, connection: any) {
403
        const { logger } = this.connection
219✔
404

405
        const {
406
            hasUuidColumns,
407
            hasCitextColumns,
408
            hasHstoreColumns,
409
            hasCubeColumns,
410
            hasGeometryColumns,
411
            hasLtreeColumns,
412
            hasExclusionConstraints,
413
        } = extensionsMetadata
219✔
414

415
        if (hasUuidColumns)
219✔
416
            try {
96✔
417
                await this.executeQuery(
96✔
418
                    connection,
419
                    `CREATE EXTENSION IF NOT EXISTS "${
420
                        this.options.uuidExtension || "uuid-ossp"
186✔
421
                    }"`,
422
                )
423
            } catch (_) {
424
                logger.log(
×
425
                    "warn",
426
                    `At least one of the entities has uuid column, but the '${
427
                        this.options.uuidExtension || "uuid-ossp"
×
428
                    }' extension cannot be installed automatically. Please install it manually using superuser rights, or select another uuid extension.`,
429
                )
430
            }
431
        if (hasCitextColumns)
219✔
432
            try {
12✔
433
                await this.executeQuery(
12✔
434
                    connection,
435
                    `CREATE EXTENSION IF NOT EXISTS "citext"`,
436
                )
437
            } catch (_) {
438
                logger.log(
×
439
                    "warn",
440
                    "At least one of the entities has citext column, but the 'citext' extension cannot be installed automatically. Please install it manually using superuser rights",
441
                )
442
            }
443
        if (hasHstoreColumns)
219✔
444
            try {
12✔
445
                await this.executeQuery(
12✔
446
                    connection,
447
                    `CREATE EXTENSION IF NOT EXISTS "hstore"`,
448
                )
449
            } catch (_) {
450
                logger.log(
×
451
                    "warn",
452
                    "At least one of the entities has hstore column, but the 'hstore' extension cannot be installed automatically. Please install it manually using superuser rights",
453
                )
454
            }
455
        if (hasGeometryColumns)
219✔
456
            try {
6✔
457
                await this.executeQuery(
6✔
458
                    connection,
459
                    `CREATE EXTENSION IF NOT EXISTS "postgis"`,
460
                )
461
            } catch (_) {
462
                logger.log(
×
463
                    "warn",
464
                    "At least one of the entities has a geometry column, but the 'postgis' extension cannot be installed automatically. Please install it manually using superuser rights",
465
                )
466
            }
467
        if (hasCubeColumns)
219✔
468
            try {
3✔
469
                await this.executeQuery(
3✔
470
                    connection,
471
                    `CREATE EXTENSION IF NOT EXISTS "cube"`,
472
                )
473
            } catch (_) {
474
                logger.log(
×
475
                    "warn",
476
                    "At least one of the entities has a cube column, but the 'cube' extension cannot be installed automatically. Please install it manually using superuser rights",
477
                )
478
            }
479
        if (hasLtreeColumns)
219✔
480
            try {
3✔
481
                await this.executeQuery(
3✔
482
                    connection,
483
                    `CREATE EXTENSION IF NOT EXISTS "ltree"`,
484
                )
485
            } catch (_) {
486
                logger.log(
×
487
                    "warn",
488
                    "At least one of the entities has a ltree column, but the 'ltree' extension cannot be installed automatically. Please install it manually using superuser rights",
489
                )
490
            }
491
        if (hasExclusionConstraints)
219✔
492
            try {
99✔
493
                // The btree_gist extension provides operator support in PostgreSQL exclusion constraints
494
                await this.executeQuery(
99✔
495
                    connection,
496
                    `CREATE EXTENSION IF NOT EXISTS "btree_gist"`,
497
                )
498
            } catch (_) {
499
                logger.log(
×
500
                    "warn",
501
                    "At least one of the entities has an exclusion constraint, but the 'btree_gist' extension cannot be installed automatically. Please install it manually using superuser rights",
502
                )
503
            }
504
    }
505

506
    protected async checkMetadataForExtensions() {
507
        const hasUuidColumns = this.connection.entityMetadatas.some(
1,926✔
508
            (metadata) => {
509
                return (
4,500✔
510
                    metadata.generatedColumns.filter(
511
                        (column) => column.generationStrategy === "uuid",
2,763✔
512
                    ).length > 0
513
                )
514
            },
515
        )
516
        const hasCitextColumns = this.connection.entityMetadatas.some(
1,926✔
517
            (metadata) => {
518
                return (
4,617✔
519
                    metadata.columns.filter(
520
                        (column) => column.type === "citext",
14,787✔
521
                    ).length > 0
522
                )
523
            },
524
        )
525
        const hasHstoreColumns = this.connection.entityMetadatas.some(
1,926✔
526
            (metadata) => {
527
                return (
4,617✔
528
                    metadata.columns.filter(
529
                        (column) => column.type === "hstore",
14,787✔
530
                    ).length > 0
531
                )
532
            },
533
        )
534
        const hasCubeColumns = this.connection.entityMetadatas.some(
1,926✔
535
            (metadata) => {
536
                return (
4,617✔
537
                    metadata.columns.filter((column) => column.type === "cube")
14,787✔
538
                        .length > 0
539
                )
540
            },
541
        )
542
        const hasGeometryColumns = this.connection.entityMetadatas.some(
1,926✔
543
            (metadata) => {
544
                return (
4,617✔
545
                    metadata.columns.filter(
546
                        (column) => this.spatialTypes.indexOf(column.type) >= 0,
14,787✔
547
                    ).length > 0
548
                )
549
            },
550
        )
551
        const hasLtreeColumns = this.connection.entityMetadatas.some(
1,926✔
552
            (metadata) => {
553
                return (
4,617✔
554
                    metadata.columns.filter((column) => column.type === "ltree")
14,787✔
555
                        .length > 0
556
                )
557
            },
558
        )
559
        const hasExclusionConstraints = this.connection.entityMetadatas.some(
1,926✔
560
            (metadata) => {
561
                return metadata.exclusions.length > 0
3,981✔
562
            },
563
        )
564

565
        return {
1,926✔
566
            hasUuidColumns,
567
            hasCitextColumns,
568
            hasHstoreColumns,
569
            hasCubeColumns,
570
            hasGeometryColumns,
571
            hasLtreeColumns,
572
            hasExclusionConstraints,
573
            hasExtensions:
574
                hasUuidColumns ||
12,804✔
575
                hasCitextColumns ||
576
                hasHstoreColumns ||
577
                hasGeometryColumns ||
578
                hasCubeColumns ||
579
                hasLtreeColumns ||
580
                hasExclusionConstraints,
581
        }
582
    }
583

584
    /**
585
     * Closes connection with database.
586
     */
587
    async disconnect(): Promise<void> {
588
        if (!this.master)
1,932!
589
            return Promise.reject(new ConnectionIsNotSetError("postgres"))
×
590

591
        await this.closePool(this.master)
1,932✔
592
        await Promise.all(this.slaves.map((slave) => this.closePool(slave)))
1,932✔
593
        this.master = undefined
1,932✔
594
        this.slaves = []
1,932✔
595
    }
596

597
    /**
598
     * Creates a schema builder used to build and sync a schema.
599
     */
600
    createSchemaBuilder() {
601
        return new RdbmsSchemaBuilder(this.connection)
5,436✔
602
    }
603

604
    /**
605
     * Creates a query runner used to execute database queries.
606
     */
607
    createQueryRunner(mode: ReplicationMode): PostgresQueryRunner {
608
        return new PostgresQueryRunner(this, mode)
41,484✔
609
    }
610

611
    /**
612
     * Prepares given value to a value to be persisted, based on its column type and metadata.
613
     */
614
    preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
615
        if (columnMetadata.transformer)
572,544✔
616
            value = ApplyValueTransformers.transformTo(
144✔
617
                columnMetadata.transformer,
618
                value,
619
            )
620

621
        if (value === null || value === undefined) return value
572,544✔
622

623
        if (columnMetadata.type === Boolean) {
560,619✔
624
            return value === true ? 1 : 0
6,009✔
625
        } else if (columnMetadata.type === "date") {
554,610✔
626
            return DateUtils.mixedDateToDateString(value)
6✔
627
        } else if (columnMetadata.type === "time") {
554,604✔
628
            return DateUtils.mixedDateToTimeString(value)
15✔
629
        } else if (
554,589✔
630
            columnMetadata.type === "datetime" ||
2,742,492✔
631
            columnMetadata.type === Date ||
632
            columnMetadata.type === "timestamp" ||
633
            columnMetadata.type === "timestamp with time zone" ||
634
            columnMetadata.type === "timestamp without time zone"
635
        ) {
636
            return DateUtils.mixedDateToDate(value)
30,180✔
637
        } else if (
524,409✔
638
            ["json", "jsonb", ...this.spatialTypes].indexOf(
639
                columnMetadata.type,
640
            ) >= 0
641
        ) {
642
            return JSON.stringify(value)
30,150✔
643
        } else if (columnMetadata.type === "hstore") {
494,259✔
644
            if (typeof value === "string") {
18✔
645
                return value
6✔
646
            } else {
647
                // https://www.postgresql.org/docs/9.0/hstore.html
648
                const quoteString = (value: unknown) => {
12✔
649
                    // If a string to be quoted is `null` or `undefined`, we return a literal unquoted NULL.
650
                    // This way, NULL values can be stored in the hstore object.
651
                    if (value === null || typeof value === "undefined") {
84✔
652
                        return "NULL"
3✔
653
                    }
654
                    // Convert non-null values to string since HStore only stores strings anyway.
655
                    // To include a double quote or a backslash in a key or value, escape it with a backslash.
656
                    return `"${`${value}`.replace(/(?=["\\])/g, "\\")}"`
81✔
657
                }
658
                return Object.keys(value)
12✔
659
                    .map(
660
                        (key) =>
661
                            quoteString(key) + "=>" + quoteString(value[key]),
42✔
662
                    )
663
                    .join(",")
664
            }
665
        } else if (columnMetadata.type === "simple-array") {
494,241✔
666
            return DateUtils.simpleArrayToString(value)
6✔
667
        } else if (columnMetadata.type === "simple-json") {
494,235✔
668
            return DateUtils.simpleJsonToString(value)
15✔
669
        } else if (columnMetadata.type === "cube") {
494,220✔
670
            if (columnMetadata.isArray) {
30✔
671
                return `{${value
3✔
672
                    .map((cube: number[]) => `"(${cube.join(",")})"`)
6✔
673
                    .join(",")}}`
674
            }
675
            return `(${value.join(",")})`
27✔
676
        } else if (columnMetadata.type === "ltree") {
494,190✔
677
            return value
24✔
678
                .split(".")
679
                .filter(Boolean)
680
                .join(".")
681
                .replace(/[\s]+/g, "_")
682
        } else if (
494,166✔
683
            (columnMetadata.type === "enum" ||
988,380✔
684
                columnMetadata.type === "simple-enum") &&
685
            !columnMetadata.isArray
686
        ) {
687
            return "" + value
81✔
688
        }
689

690
        return value
494,085✔
691
    }
692

693
    /**
694
     * Prepares given value to a value to be persisted, based on its column type or metadata.
695
     */
696
    prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
697
        if (value === null || value === undefined)
301,332✔
698
            return columnMetadata.transformer
12,030✔
699
                ? ApplyValueTransformers.transformFrom(
700
                      columnMetadata.transformer,
701
                      value,
702
                  )
703
                : value
704

705
        if (columnMetadata.type === Boolean) {
289,302✔
706
            value = value ? true : false
2,115✔
707
        } else if (
287,187✔
708
            columnMetadata.type === "datetime" ||
1,433,769✔
709
            columnMetadata.type === Date ||
710
            columnMetadata.type === "timestamp" ||
711
            columnMetadata.type === "timestamp with time zone" ||
712
            columnMetadata.type === "timestamp without time zone"
713
        ) {
714
            value = DateUtils.normalizeHydratedDate(value)
1,026✔
715
        } else if (columnMetadata.type === "date") {
286,161✔
716
            value = DateUtils.mixedDateToDateString(value)
6✔
717
        } else if (columnMetadata.type === "time") {
286,155✔
718
            value = DateUtils.mixedTimeToString(value)
15✔
719
        } else if (columnMetadata.type === "hstore") {
286,140✔
720
            if (columnMetadata.hstoreType === "object") {
18✔
721
                const unescapeString = (str: string) =>
12✔
722
                    str.replace(/\\./g, (m) => m[1])
81✔
723
                const regexp =
724
                    /"([^"\\]*(?:\\.[^"\\]*)*)"=>(?:(NULL)|"([^"\\]*(?:\\.[^"\\]*)*)")(?:,|$)/g
12✔
725
                const object: ObjectLiteral = {}
12✔
726
                ;`${value}`.replace(
12✔
727
                    regexp,
728
                    (_, key, nullValue, stringValue) => {
729
                        object[unescapeString(key)] = nullValue
42✔
730
                            ? null
731
                            : unescapeString(stringValue)
732
                        return ""
42✔
733
                    },
734
                )
735
                value = object
12✔
736
            }
737
        } else if (columnMetadata.type === "simple-array") {
286,122✔
738
            value = DateUtils.stringToSimpleArray(value)
6✔
739
        } else if (columnMetadata.type === "simple-json") {
286,116✔
740
            value = DateUtils.stringToSimpleJson(value)
15✔
741
        } else if (columnMetadata.type === "cube") {
286,101✔
742
            value = value.replace(/[()\s]+/g, "") // remove whitespace
27✔
743
            if (columnMetadata.isArray) {
27✔
744
                /**
745
                 * Strips these groups from `{"1,2,3","",NULL}`:
746
                 * 1. ["1,2,3", undefined]  <- cube of arity 3
747
                 * 2. ["", undefined]         <- cube of arity 0
748
                 * 3. [undefined, "NULL"]     <- NULL
749
                 */
750
                const regexp = /(?:"((?:[\d\s.,])*)")|(?:(NULL))/g
3✔
751
                const unparsedArrayString = value
3✔
752

753
                value = []
3✔
754
                let cube: RegExpExecArray | null = null
3✔
755
                // Iterate through all regexp matches for cubes/null in array
756
                while ((cube = regexp.exec(unparsedArrayString)) !== null) {
3✔
757
                    if (cube[1] !== undefined) {
6!
758
                        value.push(
6✔
759
                            cube[1].split(",").filter(Boolean).map(Number),
760
                        )
761
                    } else {
762
                        value.push(undefined)
×
763
                    }
764
                }
765
            } else {
766
                value = value.split(",").filter(Boolean).map(Number)
24✔
767
            }
768
        } else if (
286,074✔
769
            columnMetadata.type === "enum" ||
571,803✔
770
            columnMetadata.type === "simple-enum"
771
        ) {
772
            if (columnMetadata.isArray) {
513✔
773
                if (value === "{}") return []
297✔
774

775
                // manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
776
                value = (value as string)
246✔
777
                    .substr(1, (value as string).length - 2)
778
                    .split(",")
779
                    .map((val) => {
780
                        // replace double quotes from the beginning and from the end
781
                        if (val.startsWith(`"`) && val.endsWith(`"`))
396✔
782
                            val = val.slice(1, -1)
42✔
783
                        // replace double escaped backslash to single escaped e.g. \\\\ -> \\
784
                        val = val.replace(/(\\\\)/g, "\\")
396✔
785
                        // replace escaped double quotes to non-escaped e.g. \"asd\" -> "asd"
786
                        return val.replace(/(\\")/g, '"')
396✔
787
                    })
788

789
                // convert to number if that exists in possible enum options
790
                value = value.map((val: string) => {
246✔
791
                    return !isNaN(+val) &&
396✔
792
                        columnMetadata.enum!.indexOf(parseInt(val)) >= 0
793
                        ? parseInt(val)
794
                        : val
795
                })
796
            } else {
797
                // convert to number if that exists in possible enum options
798
                value =
216✔
799
                    !isNaN(+value) &&
516✔
800
                    columnMetadata.enum!.indexOf(parseInt(value)) >= 0
801
                        ? parseInt(value)
802
                        : value
803
            }
804
        } else if (columnMetadata.type === Number) {
285,561✔
805
            // convert to number if number
806
            value = !isNaN(+value) ? parseInt(value) : value
148,074!
807
        }
808

809
        if (columnMetadata.transformer)
289,251✔
810
            value = ApplyValueTransformers.transformFrom(
225✔
811
                columnMetadata.transformer,
812
                value,
813
            )
814
        return value
289,251✔
815
    }
816

817
    /**
818
     * Replaces parameters in the given sql with special escaping character
819
     * and an array of parameter names to be passed to a query.
820
     */
821
    escapeQueryWithParameters(
822
        sql: string,
823
        parameters: ObjectLiteral,
824
        nativeParameters: ObjectLiteral,
825
    ): [string, any[]] {
826
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
49,242✔
827
            (key) => nativeParameters[key],
×
828
        )
829
        if (!parameters || !Object.keys(parameters).length)
49,242✔
830
            return [sql, escapedParameters]
2,385✔
831

832
        const parameterIndexMap = new Map<string, number>()
46,857✔
833
        sql = sql.replace(
46,857✔
834
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
835
            (full, isArray: string, key: string): string => {
836
                if (!parameters.hasOwnProperty(key)) {
773,076✔
837
                    return full
129✔
838
                }
839

840
                if (parameterIndexMap.has(key)) {
772,947✔
841
                    return this.parametersPrefix + parameterIndexMap.get(key)
42✔
842
                }
843

844
                const value: any = parameters[key]
772,905✔
845

846
                if (isArray) {
772,905✔
847
                    return value
459✔
848
                        .map((v: any) => {
849
                            escapedParameters.push(v)
612✔
850
                            return this.createParameter(
612✔
851
                                key,
852
                                escapedParameters.length - 1,
853
                            )
854
                        })
855
                        .join(", ")
856
                }
857

858
                if (typeof value === "function") {
772,446!
859
                    return value()
×
860
                }
861

862
                escapedParameters.push(value)
772,446✔
863
                parameterIndexMap.set(key, escapedParameters.length)
772,446✔
864
                return this.createParameter(key, escapedParameters.length - 1)
772,446✔
865
            },
866
        ) // todo: make replace only in value statements, otherwise problems
867
        return [sql, escapedParameters]
46,857✔
868
    }
869

870
    /**
871
     * Escapes a column name.
872
     */
873
    escape(columnName: string): string {
874
        return '"' + columnName + '"'
12,354,948✔
875
    }
876

877
    /**
878
     * Build full table name with schema name and table name.
879
     * E.g. myDB.mySchema.myTable
880
     */
881
    buildTableName(tableName: string, schema?: string): string {
882
        const tablePath = [tableName]
1,190,016✔
883

884
        if (schema) {
1,190,016✔
885
            tablePath.unshift(schema)
1,160,811✔
886
        }
887

888
        return tablePath.join(".")
1,190,016✔
889
    }
890

891
    /**
892
     * Parse a target table name or other types and return a normalized table definition.
893
     */
894
    parseTableName(
895
        target: EntityMetadata | Table | View | TableForeignKey | string,
896
    ): { database?: string; schema?: string; tableName: string } {
897
        const driverDatabase = this.database
1,924,377✔
898
        const driverSchema = this.schema
1,924,377✔
899

900
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
1,924,377✔
901
            const parsed = this.parseTableName(target.name)
638,151✔
902

903
            return {
638,151✔
904
                database: target.database || parsed.database || driverDatabase,
1,213,026!
905
                schema: target.schema || parsed.schema || driverSchema,
1,210,107!
906
                tableName: parsed.tableName,
907
            }
908
        }
909

910
        if (InstanceChecker.isTableForeignKey(target)) {
1,286,226✔
911
            const parsed = this.parseTableName(target.referencedTableName)
11,676✔
912

913
            return {
11,676✔
914
                database:
915
                    target.referencedDatabase ||
23,265!
916
                    parsed.database ||
917
                    driverDatabase,
918
                schema:
919
                    target.referencedSchema || parsed.schema || driverSchema,
22,431!
920
                tableName: parsed.tableName,
921
            }
922
        }
923

924
        if (InstanceChecker.isEntityMetadata(target)) {
1,274,550✔
925
            // EntityMetadata tableName is never a path
926

927
            return {
589,608✔
928
                database: target.database || driverDatabase,
1,179,183✔
929
                schema: target.schema || driverSchema,
1,175,217✔
930
                tableName: target.tableName,
931
            }
932
        }
933

934
        const parts = target.split(".")
684,942✔
935

936
        return {
684,942✔
937
            database: driverDatabase,
938
            schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
2,022,165✔
939
            tableName: parts.length > 1 ? parts[1] : parts[0],
684,942✔
940
        }
941
    }
942

943
    /**
944
     * Creates a database type from a given column metadata.
945
     */
946
    normalizeType(column: {
947
        type?: ColumnType
948
        length?: number | string
949
        precision?: number | null
950
        scale?: number
951
        isArray?: boolean
952
    }): string {
953
        if (
124,998✔
954
            column.type === Number ||
251,835✔
955
            column.type === "int" ||
956
            column.type === "int4"
957
        ) {
958
            return "integer"
63,546✔
959
        } else if (column.type === String || column.type === "varchar") {
61,452✔
960
            return "character varying"
46,218✔
961
        } else if (column.type === Date || column.type === "timestamp") {
15,234✔
962
            return "timestamp without time zone"
3,390✔
963
        } else if (column.type === "timestamptz") {
11,844✔
964
            return "timestamp with time zone"
66✔
965
        } else if (column.type === "time") {
11,778✔
966
            return "time without time zone"
444✔
967
        } else if (column.type === "timetz") {
11,334✔
968
            return "time with time zone"
21✔
969
        } else if (column.type === Boolean || column.type === "bool") {
11,313✔
970
            return "boolean"
2,754✔
971
        } else if (column.type === "simple-array") {
8,559✔
972
            return "text"
30✔
973
        } else if (column.type === "simple-json") {
8,529✔
974
            return "text"
75✔
975
        } else if (column.type === "simple-enum") {
8,454✔
976
            return "enum"
333✔
977
        } else if (column.type === "int2") {
8,121✔
978
            return "smallint"
39✔
979
        } else if (column.type === "int8") {
8,082✔
980
            return "bigint"
129✔
981
        } else if (column.type === "decimal") {
7,953✔
982
            return "numeric"
168✔
983
        } else if (column.type === "float8" || column.type === "float") {
7,785✔
984
            return "double precision"
87✔
985
        } else if (column.type === "float4") {
7,698✔
986
            return "real"
21✔
987
        } else if (column.type === "char") {
7,677✔
988
            return "character"
66✔
989
        } else if (column.type === "varbit") {
7,611✔
990
            return "bit varying"
21✔
991
        } else {
992
            return (column.type as string) || ""
7,590!
993
        }
994
    }
995

996
    /**
997
     * Normalizes "default" value of the column.
998
     */
999
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
1000
        const defaultValue = columnMetadata.default
95,448✔
1001

1002
        if (defaultValue === null || defaultValue === undefined) {
95,448✔
1003
            return undefined
87,792✔
1004
        }
1005

1006
        if (columnMetadata.isArray && Array.isArray(defaultValue)) {
7,656✔
1007
            return `'{${defaultValue
231✔
1008
                .map((val: string) => `${val}`)
294✔
1009
                .join(",")}}'`
1010
        }
1011

1012
        if (
7,425✔
1013
            (columnMetadata.type === "enum" ||
30,954✔
1014
                columnMetadata.type === "simple-enum" ||
1015
                typeof defaultValue === "number" ||
1016
                typeof defaultValue === "string") &&
1017
            defaultValue !== undefined
1018
        ) {
1019
            return `'${defaultValue}'`
4,593✔
1020
        }
1021

1022
        if (typeof defaultValue === "boolean") {
2,832✔
1023
            return defaultValue ? "true" : "false"
54✔
1024
        }
1025

1026
        if (typeof defaultValue === "function") {
2,778✔
1027
            const value = defaultValue()
2,730✔
1028

1029
            return this.normalizeDatetimeFunction(value)
2,730✔
1030
        }
1031

1032
        if (typeof defaultValue === "object") {
48✔
1033
            return `'${JSON.stringify(defaultValue)}'`
48✔
1034
        }
1035

1036
        return `${defaultValue}`
×
1037
    }
1038

1039
    /**
1040
     * Compares "default" value of the column.
1041
     * Postgres sorts json values before it is saved, so in that case a deep comparison has to be performed to see if has changed.
1042
     */
1043
    private defaultEqual(
1044
        columnMetadata: ColumnMetadata,
1045
        tableColumn: TableColumn,
1046
    ): boolean {
1047
        if (
44,040✔
1048
            ["json", "jsonb"].includes(columnMetadata.type as string) &&
44,301✔
1049
            !["function", "undefined"].includes(typeof columnMetadata.default)
1050
        ) {
1051
            const tableColumnDefault =
1052
                typeof tableColumn.default === "string"
93✔
1053
                    ? JSON.parse(
1054
                          tableColumn.default.substring(
1055
                              1,
1056
                              tableColumn.default.length - 1,
1057
                          ),
1058
                      )
1059
                    : tableColumn.default
1060

1061
            return OrmUtils.deepCompare(
93✔
1062
                columnMetadata.default,
1063
                tableColumnDefault,
1064
            )
1065
        }
1066

1067
        const columnDefault = this.lowerDefaultValueIfNecessary(
43,947✔
1068
            this.normalizeDefault(columnMetadata),
1069
        )
1070
        return columnDefault === tableColumn.default
43,947✔
1071
    }
1072

1073
    /**
1074
     * Normalizes "isUnique" value of the column.
1075
     */
1076
    normalizeIsUnique(column: ColumnMetadata): boolean {
1077
        return column.entityMetadata.uniques.some(
108,912✔
1078
            (uq) => uq.columns.length === 1 && uq.columns[0] === column,
31,989✔
1079
        )
1080
    }
1081

1082
    /**
1083
     * Returns default column lengths, which is required on column creation.
1084
     */
1085
    getColumnLength(column: ColumnMetadata): string {
1086
        return column.length ? column.length.toString() : ""
51,774✔
1087
    }
1088

1089
    /**
1090
     * Creates column type definition including length, precision and scale
1091
     */
1092
    createFullType(column: TableColumn): string {
1093
        let type = column.type
42,693✔
1094

1095
        if (column.length) {
42,693✔
1096
            type += "(" + column.length + ")"
1,200✔
1097
        } else if (
41,493✔
1098
            column.precision !== null &&
83,166✔
1099
            column.precision !== undefined &&
1100
            column.scale !== null &&
1101
            column.scale !== undefined
1102
        ) {
1103
            type += "(" + column.precision + "," + column.scale + ")"
36✔
1104
        } else if (
41,457✔
1105
            column.precision !== null &&
82,914✔
1106
            column.precision !== undefined
1107
        ) {
1108
            type += "(" + column.precision + ")"
54✔
1109
        }
1110

1111
        if (column.type === "time without time zone") {
42,693✔
1112
            type =
141✔
1113
                "TIME" +
1114
                (column.precision !== null && column.precision !== undefined
423✔
1115
                    ? "(" + column.precision + ")"
1116
                    : "")
1117
        } else if (column.type === "time with time zone") {
42,552✔
1118
            type =
33✔
1119
                "TIME" +
1120
                (column.precision !== null && column.precision !== undefined
99✔
1121
                    ? "(" + column.precision + ")"
1122
                    : "") +
1123
                " WITH TIME ZONE"
1124
        } else if (column.type === "timestamp without time zone") {
42,519✔
1125
            type =
1,401✔
1126
                "TIMESTAMP" +
1127
                (column.precision !== null && column.precision !== undefined
4,203✔
1128
                    ? "(" + column.precision + ")"
1129
                    : "")
1130
        } else if (column.type === "timestamp with time zone") {
41,118✔
1131
            type =
63✔
1132
                "TIMESTAMP" +
1133
                (column.precision !== null && column.precision !== undefined
189✔
1134
                    ? "(" + column.precision + ")"
1135
                    : "") +
1136
                " WITH TIME ZONE"
1137
        } else if (this.spatialTypes.indexOf(column.type as ColumnType) >= 0) {
41,055✔
1138
            if (column.spatialFeatureType != null && column.srid != null) {
102✔
1139
                type = `${column.type}(${column.spatialFeatureType},${column.srid})`
30✔
1140
            } else if (column.spatialFeatureType != null) {
72✔
1141
                type = `${column.type}(${column.spatialFeatureType})`
24✔
1142
            } else {
1143
                type = column.type
48✔
1144
            }
1145
        }
1146

1147
        if (column.isArray) type += " array"
42,693✔
1148

1149
        return type
42,693✔
1150
    }
1151

1152
    /**
1153
     * Obtains a new database connection to a master server.
1154
     * Used for replication.
1155
     * If replication is not setup then returns default connection's database connection.
1156
     */
1157
    async obtainMasterConnection(): Promise<[any, Function]> {
1158
        if (!this.master) {
41,466!
1159
            throw new TypeORMError("Driver not Connected")
×
1160
        }
1161

1162
        return new Promise((ok, fail) => {
41,466✔
1163
            this.master.connect((err: any, connection: any, release: any) => {
41,466✔
1164
                err ? fail(err) : ok([connection, release])
41,466!
1165
            })
1166
        })
1167
    }
1168

1169
    /**
1170
     * Obtains a new database connection to a slave server.
1171
     * Used for replication.
1172
     * If replication is not setup then returns master (default) connection's database connection.
1173
     */
1174
    async obtainSlaveConnection(): Promise<[any, Function]> {
1175
        if (!this.slaves.length) {
9!
1176
            return this.obtainMasterConnection()
×
1177
        }
1178

1179
        const random = Math.floor(Math.random() * this.slaves.length)
9✔
1180

1181
        return new Promise((ok, fail) => {
9✔
1182
            this.slaves[random].connect(
9✔
1183
                (err: any, connection: any, release: any) => {
1184
                    err ? fail(err) : ok([connection, release])
9!
1185
                },
1186
            )
1187
        })
1188
    }
1189

1190
    /**
1191
     * Creates generated map of values generated or returned by database after INSERT query.
1192
     *
1193
     * todo: slow. optimize Object.keys(), OrmUtils.mergeDeep and column.createValueMap parts
1194
     */
1195
    createGeneratedMap(metadata: EntityMetadata, insertResult: ObjectLiteral) {
1196
        if (!insertResult) return undefined
167,361✔
1197

1198
        return Object.keys(insertResult).reduce((map, key) => {
119,412✔
1199
            const column = metadata.findColumnWithDatabaseName(key)
122,100✔
1200
            if (column) {
122,100✔
1201
                OrmUtils.mergeDeep(
122,100✔
1202
                    map,
1203
                    column.createValueMap(insertResult[key]),
1204
                )
1205
                // OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column))); // TODO: probably should be like there, but fails on enums, fix later
1206
            }
1207
            return map
122,100✔
1208
        }, {} as ObjectLiteral)
1209
    }
1210

1211
    /**
1212
     * Differentiate columns of this table and columns from the given column metadatas columns
1213
     * and returns only changed.
1214
     */
1215
    findChangedColumns(
1216
        tableColumns: TableColumn[],
1217
        columnMetadatas: ColumnMetadata[],
1218
    ): ColumnMetadata[] {
1219
        return columnMetadatas.filter((columnMetadata) => {
16,839✔
1220
            const tableColumn = tableColumns.find(
54,456✔
1221
                (c) => c.name === columnMetadata.databaseName,
169,554✔
1222
            )
1223
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
54,456✔
1224

1225
            const isColumnChanged =
1226
                tableColumn.name !== columnMetadata.databaseName ||
54,447✔
1227
                tableColumn.type !== this.normalizeType(columnMetadata) ||
1228
                tableColumn.length !== columnMetadata.length ||
1229
                tableColumn.isArray !== columnMetadata.isArray ||
1230
                tableColumn.precision !== columnMetadata.precision ||
1231
                (columnMetadata.scale !== undefined &&
1232
                    tableColumn.scale !== columnMetadata.scale) ||
1233
                tableColumn.comment !==
1234
                    this.escapeComment(columnMetadata.comment) ||
1235
                (!tableColumn.isGenerated &&
1236
                    !this.defaultEqual(columnMetadata, tableColumn)) || // we included check for generated here, because generated columns already can have default values
1237
                tableColumn.isPrimary !== columnMetadata.isPrimary ||
1238
                tableColumn.isNullable !== columnMetadata.isNullable ||
1239
                tableColumn.isUnique !==
1240
                    this.normalizeIsUnique(columnMetadata) ||
1241
                tableColumn.enumName !== columnMetadata.enumName ||
1242
                (tableColumn.enum &&
1243
                    columnMetadata.enum &&
1244
                    !OrmUtils.isArraysEqual(
1245
                        tableColumn.enum,
1246
                        columnMetadata.enum.map((val) => val + ""),
2,457✔
1247
                    )) || // enums in postgres are always strings
1248
                tableColumn.isGenerated !== columnMetadata.isGenerated ||
1249
                (tableColumn.spatialFeatureType || "").toLowerCase() !==
108,483✔
1250
                    (columnMetadata.spatialFeatureType || "").toLowerCase() ||
108,483✔
1251
                tableColumn.srid !== columnMetadata.srid ||
1252
                tableColumn.generatedType !== columnMetadata.generatedType ||
1253
                (tableColumn.asExpression || "").trim() !==
108,474✔
1254
                    (columnMetadata.asExpression || "").trim()
108,474✔
1255

1256
            // DEBUG SECTION
1257
            // if (isColumnChanged) {
1258
            //     console.log("table:", columnMetadata.entityMetadata.tableName)
1259
            //     console.log(
1260
            //         "name:",
1261
            //         tableColumn.name,
1262
            //         columnMetadata.databaseName,
1263
            //     )
1264
            //     console.log(
1265
            //         "type:",
1266
            //         tableColumn.type,
1267
            //         this.normalizeType(columnMetadata),
1268
            //     )
1269
            //     console.log(
1270
            //         "length:",
1271
            //         tableColumn.length,
1272
            //         columnMetadata.length,
1273
            //     )
1274
            //     console.log(
1275
            //         "isArray:",
1276
            //         tableColumn.isArray,
1277
            //         columnMetadata.isArray,
1278
            //     )
1279
            //     console.log(
1280
            //         "precision:",
1281
            //         tableColumn.precision,
1282
            //         columnMetadata.precision,
1283
            //     )
1284
            //     console.log("scale:", tableColumn.scale, columnMetadata.scale)
1285
            //     console.log(
1286
            //         "comment:",
1287
            //         tableColumn.comment,
1288
            //         this.escapeComment(columnMetadata.comment),
1289
            //     )
1290
            //     console.log(
1291
            //         "enumName:",
1292
            //         tableColumn.enumName,
1293
            //         columnMetadata.enumName,
1294
            //     )
1295
            //     console.log(
1296
            //         "enum:",
1297
            //         tableColumn.enum &&
1298
            //             columnMetadata.enum &&
1299
            //             !OrmUtils.isArraysEqual(
1300
            //                 tableColumn.enum,
1301
            //                 columnMetadata.enum.map((val) => val + ""),
1302
            //             ),
1303
            //     )
1304
            //     console.log(
1305
            //         "isPrimary:",
1306
            //         tableColumn.isPrimary,
1307
            //         columnMetadata.isPrimary,
1308
            //     )
1309
            //     console.log(
1310
            //         "isNullable:",
1311
            //         tableColumn.isNullable,
1312
            //         columnMetadata.isNullable,
1313
            //     )
1314
            //     console.log(
1315
            //         "isUnique:",
1316
            //         tableColumn.isUnique,
1317
            //         this.normalizeIsUnique(columnMetadata),
1318
            //     )
1319
            //     console.log(
1320
            //         "isGenerated:",
1321
            //         tableColumn.isGenerated,
1322
            //         columnMetadata.isGenerated,
1323
            //     )
1324
            //     console.log(
1325
            //         "generatedType:",
1326
            //         tableColumn.generatedType,
1327
            //         columnMetadata.generatedType,
1328
            //     )
1329
            //     console.log(
1330
            //         "asExpression:",
1331
            //         (tableColumn.asExpression || "").trim(),
1332
            //         (columnMetadata.asExpression || "").trim(),
1333
            //     )
1334
            //     console.log(
1335
            //         "collation:",
1336
            //         tableColumn.collation,
1337
            //         columnMetadata.collation,
1338
            //     )
1339
            //     console.log(
1340
            //         "isGenerated 2:",
1341
            //         !tableColumn.isGenerated &&
1342
            //             this.lowerDefaultValueIfNecessary(
1343
            //                 this.normalizeDefault(columnMetadata),
1344
            //             ) !== tableColumn.default,
1345
            //     )
1346
            //     console.log(
1347
            //         "spatialFeatureType:",
1348
            //         (tableColumn.spatialFeatureType || "").toLowerCase(),
1349
            //         (columnMetadata.spatialFeatureType || "").toLowerCase(),
1350
            //     )
1351
            //     console.log("srid", tableColumn.srid, columnMetadata.srid)
1352
            //     console.log("==========================================")
1353
            // }
1354

1355
            return isColumnChanged
54,447✔
1356
        })
1357
    }
1358

1359
    private lowerDefaultValueIfNecessary(value: string | undefined) {
1360
        // Postgres saves function calls in default value as lowercase #2733
1361
        if (!value) {
43,947✔
1362
            return value
39,993✔
1363
        }
1364
        return value
3,954✔
1365
            .split(`'`)
1366
            .map((v, i) => {
1367
                return i % 2 === 1 ? v : v.toLowerCase()
9,636✔
1368
            })
1369
            .join(`'`)
1370
    }
1371

1372
    /**
1373
     * Returns true if driver supports RETURNING / OUTPUT statement.
1374
     */
1375
    isReturningSqlSupported(): boolean {
1376
        return true
61,320✔
1377
    }
1378

1379
    /**
1380
     * Returns true if driver supports uuid values generation on its own.
1381
     */
1382
    isUUIDGenerationSupported(): boolean {
1383
        return true
423✔
1384
    }
1385

1386
    /**
1387
     * Returns true if driver supports fulltext indices.
1388
     */
1389
    isFullTextColumnTypeSupported(): boolean {
1390
        return false
153✔
1391
    }
1392

1393
    get uuidGenerator(): string {
1394
        return this.options.uuidExtension === "pgcrypto"
552✔
1395
            ? "gen_random_uuid()"
1396
            : "uuid_generate_v4()"
1397
    }
1398

1399
    /**
1400
     * Creates an escaped parameter.
1401
     */
1402
    createParameter(parameterName: string, index: number): string {
1403
        return this.parametersPrefix + (index + 1)
773,772✔
1404
    }
1405

1406
    // -------------------------------------------------------------------------
1407
    // Public Methods
1408
    // -------------------------------------------------------------------------
1409

1410
    /**
1411
     * Loads postgres query stream package.
1412
     */
1413
    loadStreamDependency() {
1414
        try {
6✔
1415
            return PlatformTools.load("pg-query-stream")
6✔
1416
        } catch (e) {
1417
            // todo: better error for browser env
1418
            throw new TypeORMError(
×
1419
                `To use streams you should install pg-query-stream package. Please run npm i pg-query-stream --save command.`,
1420
            )
1421
        }
1422
    }
1423

1424
    // -------------------------------------------------------------------------
1425
    // Protected Methods
1426
    // -------------------------------------------------------------------------
1427

1428
    /**
1429
     * If driver dependency is not given explicitly, then try to load it via "require".
1430
     */
1431
    protected loadDependencies(): void {
1432
        try {
1,932✔
1433
            const postgres = this.options.driver || PlatformTools.load("pg")
1,932✔
1434
            this.postgres = postgres
1,932✔
1435
            try {
1,932✔
1436
                const pgNative =
1437
                    this.options.nativeDriver || PlatformTools.load("pg-native")
1,932✔
1438
                if (pgNative && this.postgres.native)
×
1439
                    this.postgres = this.postgres.native
×
1440
            } catch (e) {}
1441
        } catch (e) {
1442
            // todo: better error for browser env
1443
            throw new DriverPackageNotInstalledError("Postgres", "pg")
×
1444
        }
1445
    }
1446

1447
    /**
1448
     * Creates a new connection pool for a given database credentials.
1449
     */
1450
    protected async createPool(
1451
        options: PostgresConnectionOptions,
1452
        credentials: PostgresConnectionCredentialsOptions,
1453
    ): Promise<any> {
1454
        const { logger } = this.connection
1,956✔
1455
        credentials = Object.assign({}, credentials)
1,956✔
1456

1457
        // build connection options for the driver
1458
        // See: https://github.com/brianc/node-postgres/tree/master/packages/pg-pool#create
1459
        const connectionOptions = Object.assign(
1,956✔
1460
            {},
1461
            {
1462
                connectionString: credentials.url,
1463
                host: credentials.host,
1464
                user: credentials.username,
1465
                password: credentials.password,
1466
                database: credentials.database,
1467
                port: credentials.port,
1468
                ssl: credentials.ssl,
1469
                connectionTimeoutMillis: options.connectTimeoutMS,
1470
                application_name:
1471
                    options.applicationName ?? credentials.applicationName,
3,909✔
1472
                max: options.poolSize,
1473
            },
1474
            options.extra || {},
3,912✔
1475
        )
1476

1477
        if (options.parseInt8 !== undefined) {
1,956✔
1478
            if (
3!
1479
                this.postgres.defaults &&
6✔
1480
                Object.getOwnPropertyDescriptor(
1481
                    this.postgres.defaults,
1482
                    "parseInt8",
1483
                )?.set
1484
            ) {
1485
                this.postgres.defaults.parseInt8 = options.parseInt8
3✔
1486
            } else {
1487
                logger.log(
×
1488
                    "warn",
1489
                    "Attempted to set parseInt8 option, but the postgres driver does not support setting defaults.parseInt8. This option will be ignored.",
1490
                )
1491
            }
1492
        }
1493

1494
        // create a connection pool
1495
        const pool = new this.postgres.Pool(connectionOptions)
1,956✔
1496

1497
        const poolErrorHandler =
1498
            options.poolErrorHandler ||
1,956✔
1499
            ((error: any) =>
1500
                logger.log("warn", `Postgres pool raised an error. ${error}`))
×
1501

1502
        /*
1503
          Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
1504
          cause the hosting app to crash.
1505
         */
1506
        pool.on("error", poolErrorHandler)
1,956✔
1507

1508
        return new Promise((ok, fail) => {
1,956✔
1509
            pool.connect((err: any, connection: any, release: Function) => {
1,956✔
1510
                if (err) return fail(err)
1,956!
1511

1512
                if (options.logNotifications) {
1,956✔
1513
                    connection.on("notice", (msg: any) => {
3✔
1514
                        msg && this.connection.logger.log("info", msg.message)
9✔
1515
                    })
1516
                    connection.on("notification", (msg: any) => {
3✔
1517
                        msg &&
3✔
1518
                            this.connection.logger.log(
1519
                                "info",
1520
                                `Received NOTIFY on channel ${msg.channel}: ${msg.payload}.`,
1521
                            )
1522
                    })
1523
                }
1524
                release()
1,956✔
1525
                ok(pool)
1,956✔
1526
            })
1527
        })
1528
    }
1529

1530
    /**
1531
     * Closes connection pool.
1532
     */
1533
    protected async closePool(pool: any): Promise<void> {
1534
        while (this.connectedQueryRunners.length) {
1,956✔
1535
            await this.connectedQueryRunners[0].release()
48✔
1536
        }
1537

1538
        return new Promise<void>((ok, fail) => {
1,956✔
1539
            pool.end((err: any) => (err ? fail(err) : ok()))
1,956!
1540
        })
1541
    }
1542

1543
    /**
1544
     * Executes given query.
1545
     */
1546
    protected executeQuery(connection: any, query: string) {
1547
        this.connection.logger.logQuery(query)
231✔
1548

1549
        return new Promise((ok, fail) => {
231✔
1550
            connection.query(query, (err: any, result: any) =>
231✔
1551
                err ? fail(err) : ok(result),
231!
1552
            )
1553
        })
1554
    }
1555

1556
    /**
1557
     * If parameter is a datetime function, e.g. "CURRENT_TIMESTAMP", normalizes it.
1558
     * Otherwise returns original input.
1559
     */
1560
    protected normalizeDatetimeFunction(value: string) {
1561
        // check if input is datetime function
1562
        const upperCaseValue = value.toUpperCase()
2,730✔
1563
        const isDatetimeFunction =
1564
            upperCaseValue.indexOf("CURRENT_TIMESTAMP") !== -1 ||
2,730✔
1565
            upperCaseValue.indexOf("CURRENT_DATE") !== -1 ||
1566
            upperCaseValue.indexOf("CURRENT_TIME") !== -1 ||
1567
            upperCaseValue.indexOf("LOCALTIMESTAMP") !== -1 ||
1568
            upperCaseValue.indexOf("LOCALTIME") !== -1
1569

1570
        if (isDatetimeFunction) {
2,730✔
1571
            // extract precision, e.g. "(3)"
1572
            const precision = value.match(/\(\d+\)/)
666✔
1573

1574
            if (upperCaseValue.indexOf("CURRENT_TIMESTAMP") !== -1) {
666✔
1575
                return precision
171✔
1576
                    ? `('now'::text)::timestamp${precision[0]} with time zone`
1577
                    : "now()"
1578
            } else if (upperCaseValue === "CURRENT_DATE") {
495✔
1579
                return "('now'::text)::date"
90✔
1580
            } else if (upperCaseValue.indexOf("CURRENT_TIME") !== -1) {
405✔
1581
                return precision
135✔
1582
                    ? `('now'::text)::time${precision[0]} with time zone`
1583
                    : "('now'::text)::time with time zone"
1584
            } else if (upperCaseValue.indexOf("LOCALTIMESTAMP") !== -1) {
270✔
1585
                return precision
135✔
1586
                    ? `('now'::text)::timestamp${precision[0]} without time zone`
1587
                    : "('now'::text)::timestamp without time zone"
1588
            } else if (upperCaseValue.indexOf("LOCALTIME") !== -1) {
135✔
1589
                return precision
135✔
1590
                    ? `('now'::text)::time${precision[0]} without time zone`
1591
                    : "('now'::text)::time without time zone"
1592
            }
1593
        }
1594

1595
        return value
2,064✔
1596
    }
1597

1598
    /**
1599
     * Escapes a given comment.
1600
     */
1601
    protected escapeComment(comment?: string) {
1602
        if (!comment) return comment
54,399✔
1603

1604
        comment = comment.replace(/\u0000/g, "") // Null bytes aren't allowed in comments
300✔
1605

1606
        return comment
300✔
1607
    }
1608
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc