• 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

4.26
/src/driver/postgres/PostgresDriver.ts
1
import { ObjectLiteral } from "../../common/ObjectLiteral"
2
import { DataSource } from "../../data-source/DataSource"
3
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
1✔
4
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
1✔
5
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
6
import { EntityMetadata } from "../../metadata/EntityMetadata"
7
import { PlatformTools } from "../../platform/PlatformTools"
1✔
8
import { QueryRunner } from "../../query-runner/QueryRunner"
9
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
1✔
10
import { TableColumn } from "../../schema-builder/table/TableColumn"
11
import { ApplyValueTransformers } from "../../util/ApplyValueTransformers"
1✔
12
import { DateUtils } from "../../util/DateUtils"
1✔
13
import { OrmUtils } from "../../util/OrmUtils"
1✔
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"
1✔
21
import { PostgresConnectionCredentialsOptions } from "./PostgresConnectionCredentialsOptions"
22
import { PostgresConnectionOptions } from "./PostgresConnectionOptions"
23
import { PostgresQueryRunner } from "./PostgresQueryRunner"
1✔
24
import { DriverUtils } from "../DriverUtils"
1✔
25
import { TypeORMError } from "../../error"
1✔
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"
1✔
30
import { UpsertType } from "../types/UpsertType"
31

32
/**
33
 * Organizes communication with PostgreSQL DBMS.
34
 */
35
export class PostgresDriver implements Driver {
1✔
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[] = []
11✔
60

61
    /**
62
     * We store all created query runners because we need to release them.
63
     */
64
    connectedQueryRunners: QueryRunner[] = []
11✔
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
11✔
104

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

110
    /**
111
     * Represent transaction support by this driver
112
     */
113
    transactionSupport = "nested" as const
11✔
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[] = [
11✔
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"]
11✔
200

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

206
    /**
207
     * Gets list of column data types that support length by a driver.
208
     */
209
    withLengthColumnTypes: ColumnType[] = [
11✔
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[] = [
11✔
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"]
11✔
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 = {
11✔
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 = "$"
11✔
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 = {
11✔
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
11✔
291

292
    isGeneratedColumnsSupported: boolean = false
11✔
293

294
    cteCapabilities: CteCapabilities = {
11✔
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) {
11✔
307
            return
11✔
308
        }
309

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

UNCOV
319
        this.database = DriverUtils.buildDriverOptions(
×
320
            this.options.replication
×
321
                ? this.options.replication.master
322
                : this.options,
323
        ).database
UNCOV
324
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
×
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> {
UNCOV
347
        if (this.options.replication) {
×
UNCOV
348
            this.slaves = await Promise.all(
×
349
                this.options.replication.slaves.map((slave) => {
UNCOV
350
                    return this.createPool(this.options, slave)
×
351
                }),
352
            )
UNCOV
353
            this.master = await this.createPool(
×
354
                this.options,
355
                this.options.replication.master,
356
            )
357
        } else {
UNCOV
358
            this.master = await this.createPool(this.options, this.options)
×
359
        }
360

UNCOV
361
        const queryRunner = this.createQueryRunner("master")
×
362

UNCOV
363
        this.version = await queryRunner.getVersion()
×
364

UNCOV
365
        if (!this.database) {
×
366
            this.database = await queryRunner.getCurrentDatabase()
×
367
        }
368

UNCOV
369
        if (!this.searchSchema) {
×
UNCOV
370
            this.searchSchema = await queryRunner.getCurrentSchema()
×
371
        }
372

UNCOV
373
        await queryRunner.release()
×
374

UNCOV
375
        if (!this.schema) {
×
UNCOV
376
            this.schema = this.searchSchema
×
377
        }
378
    }
379

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

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

UNCOV
394
        this.isGeneratedColumnsSupported = VersionUtils.isGreaterOrEqual(
×
395
            this.version,
396
            "12.0",
397
        )
398

UNCOV
399
        await release()
×
400
    }
401

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

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

UNCOV
415
        if (hasUuidColumns)
×
UNCOV
416
            try {
×
UNCOV
417
                await this.executeQuery(
×
418
                    connection,
419
                    `CREATE EXTENSION IF NOT EXISTS "${
420
                        this.options.uuidExtension || "uuid-ossp"
×
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
            }
UNCOV
431
        if (hasCitextColumns)
×
UNCOV
432
            try {
×
UNCOV
433
                await this.executeQuery(
×
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
            }
UNCOV
443
        if (hasHstoreColumns)
×
UNCOV
444
            try {
×
UNCOV
445
                await this.executeQuery(
×
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
            }
UNCOV
455
        if (hasGeometryColumns)
×
UNCOV
456
            try {
×
UNCOV
457
                await this.executeQuery(
×
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
            }
UNCOV
467
        if (hasCubeColumns)
×
UNCOV
468
            try {
×
UNCOV
469
                await this.executeQuery(
×
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
            }
UNCOV
479
        if (hasLtreeColumns)
×
UNCOV
480
            try {
×
UNCOV
481
                await this.executeQuery(
×
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
            }
UNCOV
491
        if (hasExclusionConstraints)
×
UNCOV
492
            try {
×
493
                // The btree_gist extension provides operator support in PostgreSQL exclusion constraints
UNCOV
494
                await this.executeQuery(
×
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() {
UNCOV
507
        const hasUuidColumns = this.connection.entityMetadatas.some(
×
508
            (metadata) => {
UNCOV
509
                return (
×
510
                    metadata.generatedColumns.filter(
UNCOV
511
                        (column) => column.generationStrategy === "uuid",
×
512
                    ).length > 0
513
                )
514
            },
515
        )
UNCOV
516
        const hasCitextColumns = this.connection.entityMetadatas.some(
×
517
            (metadata) => {
UNCOV
518
                return (
×
519
                    metadata.columns.filter(
UNCOV
520
                        (column) => column.type === "citext",
×
521
                    ).length > 0
522
                )
523
            },
524
        )
UNCOV
525
        const hasHstoreColumns = this.connection.entityMetadatas.some(
×
526
            (metadata) => {
UNCOV
527
                return (
×
528
                    metadata.columns.filter(
UNCOV
529
                        (column) => column.type === "hstore",
×
530
                    ).length > 0
531
                )
532
            },
533
        )
UNCOV
534
        const hasCubeColumns = this.connection.entityMetadatas.some(
×
535
            (metadata) => {
UNCOV
536
                return (
×
UNCOV
537
                    metadata.columns.filter((column) => column.type === "cube")
×
538
                        .length > 0
539
                )
540
            },
541
        )
UNCOV
542
        const hasGeometryColumns = this.connection.entityMetadatas.some(
×
543
            (metadata) => {
UNCOV
544
                return (
×
545
                    metadata.columns.filter(
UNCOV
546
                        (column) => this.spatialTypes.indexOf(column.type) >= 0,
×
547
                    ).length > 0
548
                )
549
            },
550
        )
UNCOV
551
        const hasLtreeColumns = this.connection.entityMetadatas.some(
×
552
            (metadata) => {
UNCOV
553
                return (
×
UNCOV
554
                    metadata.columns.filter((column) => column.type === "ltree")
×
555
                        .length > 0
556
                )
557
            },
558
        )
UNCOV
559
        const hasExclusionConstraints = this.connection.entityMetadatas.some(
×
560
            (metadata) => {
UNCOV
561
                return metadata.exclusions.length > 0
×
562
            },
563
        )
564

UNCOV
565
        return {
×
566
            hasUuidColumns,
567
            hasCitextColumns,
568
            hasHstoreColumns,
569
            hasCubeColumns,
570
            hasGeometryColumns,
571
            hasLtreeColumns,
572
            hasExclusionConstraints,
573
            hasExtensions:
574
                hasUuidColumns ||
×
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> {
UNCOV
588
        if (!this.master)
×
589
            return Promise.reject(new ConnectionIsNotSetError("postgres"))
×
590

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

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

604
    /**
605
     * Creates a query runner used to execute database queries.
606
     */
607
    createQueryRunner(mode: ReplicationMode): PostgresQueryRunner {
UNCOV
608
        return new PostgresQueryRunner(this, mode)
×
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 {
UNCOV
615
        if (columnMetadata.transformer)
×
UNCOV
616
            value = ApplyValueTransformers.transformTo(
×
617
                columnMetadata.transformer,
618
                value,
619
            )
620

UNCOV
621
        if (value === null || value === undefined) return value
×
622

UNCOV
623
        if (columnMetadata.type === Boolean) {
×
UNCOV
624
            return value === true ? 1 : 0
×
UNCOV
625
        } else if (columnMetadata.type === "date") {
×
UNCOV
626
            return DateUtils.mixedDateToDateString(value)
×
UNCOV
627
        } else if (columnMetadata.type === "time") {
×
UNCOV
628
            return DateUtils.mixedDateToTimeString(value)
×
UNCOV
629
        } else if (
×
630
            columnMetadata.type === "datetime" ||
×
631
            columnMetadata.type === Date ||
632
            columnMetadata.type === "timestamp" ||
633
            columnMetadata.type === "timestamp with time zone" ||
634
            columnMetadata.type === "timestamp without time zone"
635
        ) {
UNCOV
636
            return DateUtils.mixedDateToDate(value)
×
UNCOV
637
        } else if (
×
638
            ["json", "jsonb", ...this.spatialTypes].indexOf(
639
                columnMetadata.type,
640
            ) >= 0
641
        ) {
UNCOV
642
            return JSON.stringify(value)
×
UNCOV
643
        } else if (columnMetadata.type === "hstore") {
×
UNCOV
644
            if (typeof value === "string") {
×
UNCOV
645
                return value
×
646
            } else {
647
                // https://www.postgresql.org/docs/9.0/hstore.html
UNCOV
648
                const quoteString = (value: unknown) => {
×
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.
UNCOV
651
                    if (value === null || typeof value === "undefined") {
×
UNCOV
652
                        return "NULL"
×
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.
UNCOV
656
                    return `"${`${value}`.replace(/(?=["\\])/g, "\\")}"`
×
657
                }
UNCOV
658
                return Object.keys(value)
×
659
                    .map(
660
                        (key) =>
UNCOV
661
                            quoteString(key) + "=>" + quoteString(value[key]),
×
662
                    )
663
                    .join(",")
664
            }
UNCOV
665
        } else if (columnMetadata.type === "simple-array") {
×
UNCOV
666
            return DateUtils.simpleArrayToString(value)
×
UNCOV
667
        } else if (columnMetadata.type === "simple-json") {
×
UNCOV
668
            return DateUtils.simpleJsonToString(value)
×
UNCOV
669
        } else if (columnMetadata.type === "cube") {
×
UNCOV
670
            if (columnMetadata.isArray) {
×
UNCOV
671
                return `{${value
×
UNCOV
672
                    .map((cube: number[]) => `"(${cube.join(",")})"`)
×
673
                    .join(",")}}`
674
            }
UNCOV
675
            return `(${value.join(",")})`
×
UNCOV
676
        } else if (columnMetadata.type === "ltree") {
×
UNCOV
677
            return value
×
678
                .split(".")
679
                .filter(Boolean)
680
                .join(".")
681
                .replace(/[\s]+/g, "_")
UNCOV
682
        } else if (
×
683
            (columnMetadata.type === "enum" ||
×
684
                columnMetadata.type === "simple-enum") &&
685
            !columnMetadata.isArray
686
        ) {
UNCOV
687
            return "" + value
×
688
        }
689

UNCOV
690
        return value
×
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 {
UNCOV
697
        if (value === null || value === undefined)
×
UNCOV
698
            return columnMetadata.transformer
×
699
                ? ApplyValueTransformers.transformFrom(
700
                      columnMetadata.transformer,
701
                      value,
702
                  )
703
                : value
704

UNCOV
705
        if (columnMetadata.type === Boolean) {
×
UNCOV
706
            value = value ? true : false
×
UNCOV
707
        } else if (
×
708
            columnMetadata.type === "datetime" ||
×
709
            columnMetadata.type === Date ||
710
            columnMetadata.type === "timestamp" ||
711
            columnMetadata.type === "timestamp with time zone" ||
712
            columnMetadata.type === "timestamp without time zone"
713
        ) {
UNCOV
714
            value = DateUtils.normalizeHydratedDate(value)
×
UNCOV
715
        } else if (columnMetadata.type === "date") {
×
UNCOV
716
            value = DateUtils.mixedDateToDateString(value)
×
UNCOV
717
        } else if (columnMetadata.type === "time") {
×
UNCOV
718
            value = DateUtils.mixedTimeToString(value)
×
UNCOV
719
        } else if (columnMetadata.type === "hstore") {
×
UNCOV
720
            if (columnMetadata.hstoreType === "object") {
×
UNCOV
721
                const unescapeString = (str: string) =>
×
UNCOV
722
                    str.replace(/\\./g, (m) => m[1])
×
723
                const regexp =
UNCOV
724
                    /"([^"\\]*(?:\\.[^"\\]*)*)"=>(?:(NULL)|"([^"\\]*(?:\\.[^"\\]*)*)")(?:,|$)/g
×
UNCOV
725
                const object: ObjectLiteral = {}
×
UNCOV
726
                ;`${value}`.replace(
×
727
                    regexp,
728
                    (_, key, nullValue, stringValue) => {
UNCOV
729
                        object[unescapeString(key)] = nullValue
×
730
                            ? null
731
                            : unescapeString(stringValue)
UNCOV
732
                        return ""
×
733
                    },
734
                )
UNCOV
735
                value = object
×
736
            }
UNCOV
737
        } else if (columnMetadata.type === "simple-array") {
×
UNCOV
738
            value = DateUtils.stringToSimpleArray(value)
×
UNCOV
739
        } else if (columnMetadata.type === "simple-json") {
×
UNCOV
740
            value = DateUtils.stringToSimpleJson(value)
×
UNCOV
741
        } else if (columnMetadata.type === "cube") {
×
UNCOV
742
            value = value.replace(/[()\s]+/g, "") // remove whitespace
×
UNCOV
743
            if (columnMetadata.isArray) {
×
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
                 */
UNCOV
750
                const regexp = /(?:"((?:[\d\s.,])*)")|(?:(NULL))/g
×
UNCOV
751
                const unparsedArrayString = value
×
752

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

775
                // manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
UNCOV
776
                value = (value as string)
×
777
                    .slice(1, -1)
778
                    .split(",")
779
                    .map((val) => {
780
                        // replace double quotes from the beginning and from the end
UNCOV
781
                        if (val.startsWith(`"`) && val.endsWith(`"`))
×
UNCOV
782
                            val = val.slice(1, -1)
×
783
                        // replace escaped backslash and double quotes
UNCOV
784
                        return val.replace(/\\(\\|")/g, "$1")
×
785
                    })
786

787
                // convert to number if that exists in possible enum options
UNCOV
788
                value = value.map((val: string) => {
×
UNCOV
789
                    return !isNaN(+val) &&
×
790
                        columnMetadata.enum!.indexOf(parseInt(val)) >= 0
791
                        ? parseInt(val)
792
                        : val
793
                })
794
            } else {
795
                // convert to number if that exists in possible enum options
UNCOV
796
                value =
×
797
                    !isNaN(+value) &&
×
798
                    columnMetadata.enum!.indexOf(parseInt(value)) >= 0
799
                        ? parseInt(value)
800
                        : value
801
            }
UNCOV
802
        } else if (columnMetadata.type === Number) {
×
803
            // convert to number if number
UNCOV
804
            value = !isNaN(+value) ? parseInt(value) : value
×
805
        }
806

UNCOV
807
        if (columnMetadata.transformer)
×
UNCOV
808
            value = ApplyValueTransformers.transformFrom(
×
809
                columnMetadata.transformer,
810
                value,
811
            )
UNCOV
812
        return value
×
813
    }
814

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

UNCOV
830
        const parameterIndexMap = new Map<string, number>()
×
UNCOV
831
        sql = sql.replace(
×
832
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
833
            (full, isArray: string, key: string): string => {
UNCOV
834
                if (!parameters.hasOwnProperty(key)) {
×
UNCOV
835
                    return full
×
836
                }
837

UNCOV
838
                if (parameterIndexMap.has(key)) {
×
UNCOV
839
                    return this.parametersPrefix + parameterIndexMap.get(key)
×
840
                }
841

UNCOV
842
                const value: any = parameters[key]
×
843

UNCOV
844
                if (isArray) {
×
UNCOV
845
                    return value
×
846
                        .map((v: any) => {
UNCOV
847
                            escapedParameters.push(v)
×
UNCOV
848
                            return this.createParameter(
×
849
                                key,
850
                                escapedParameters.length - 1,
851
                            )
852
                        })
853
                        .join(", ")
854
                }
855

UNCOV
856
                if (typeof value === "function") {
×
857
                    return value()
×
858
                }
859

UNCOV
860
                escapedParameters.push(value)
×
UNCOV
861
                parameterIndexMap.set(key, escapedParameters.length)
×
UNCOV
862
                return this.createParameter(key, escapedParameters.length - 1)
×
863
            },
864
        ) // todo: make replace only in value statements, otherwise problems
UNCOV
865
        return [sql, escapedParameters]
×
866
    }
867

868
    /**
869
     * Escapes a column name.
870
     */
871
    escape(columnName: string): string {
UNCOV
872
        return '"' + columnName + '"'
×
873
    }
874

875
    /**
876
     * Build full table name with schema name and table name.
877
     * E.g. myDB.mySchema.myTable
878
     */
879
    buildTableName(tableName: string, schema?: string): string {
UNCOV
880
        const tablePath = [tableName]
×
881

UNCOV
882
        if (schema) {
×
UNCOV
883
            tablePath.unshift(schema)
×
884
        }
885

UNCOV
886
        return tablePath.join(".")
×
887
    }
888

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

UNCOV
898
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
×
UNCOV
899
            const parsed = this.parseTableName(target.name)
×
900

UNCOV
901
            return {
×
902
                database: target.database || parsed.database || driverDatabase,
×
903
                schema: target.schema || parsed.schema || driverSchema,
×
904
                tableName: parsed.tableName,
905
            }
906
        }
907

UNCOV
908
        if (InstanceChecker.isTableForeignKey(target)) {
×
UNCOV
909
            const parsed = this.parseTableName(target.referencedTableName)
×
910

UNCOV
911
            return {
×
912
                database:
913
                    target.referencedDatabase ||
×
914
                    parsed.database ||
915
                    driverDatabase,
916
                schema:
917
                    target.referencedSchema || parsed.schema || driverSchema,
×
918
                tableName: parsed.tableName,
919
            }
920
        }
921

UNCOV
922
        if (InstanceChecker.isEntityMetadata(target)) {
×
923
            // EntityMetadata tableName is never a path
924

UNCOV
925
            return {
×
926
                database: target.database || driverDatabase,
×
927
                schema: target.schema || driverSchema,
×
928
                tableName: target.tableName,
929
            }
930
        }
931

UNCOV
932
        const parts = target.split(".")
×
933

UNCOV
934
        return {
×
935
            database: driverDatabase,
936
            schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
×
937
            tableName: parts.length > 1 ? parts[1] : parts[0],
×
938
        }
939
    }
940

941
    /**
942
     * Creates a database type from a given column metadata.
943
     */
944
    normalizeType(column: {
945
        type?: ColumnType
946
        length?: number | string
947
        precision?: number | null
948
        scale?: number
949
        isArray?: boolean
950
    }): string {
UNCOV
951
        if (
×
952
            column.type === Number ||
×
953
            column.type === "int" ||
954
            column.type === "int4"
955
        ) {
UNCOV
956
            return "integer"
×
UNCOV
957
        } else if (column.type === String || column.type === "varchar") {
×
UNCOV
958
            return "character varying"
×
UNCOV
959
        } else if (column.type === Date || column.type === "timestamp") {
×
UNCOV
960
            return "timestamp without time zone"
×
UNCOV
961
        } else if (column.type === "timestamptz") {
×
UNCOV
962
            return "timestamp with time zone"
×
UNCOV
963
        } else if (column.type === "time") {
×
UNCOV
964
            return "time without time zone"
×
UNCOV
965
        } else if (column.type === "timetz") {
×
UNCOV
966
            return "time with time zone"
×
UNCOV
967
        } else if (column.type === Boolean || column.type === "bool") {
×
UNCOV
968
            return "boolean"
×
UNCOV
969
        } else if (column.type === "simple-array") {
×
UNCOV
970
            return "text"
×
UNCOV
971
        } else if (column.type === "simple-json") {
×
UNCOV
972
            return "text"
×
UNCOV
973
        } else if (column.type === "simple-enum") {
×
UNCOV
974
            return "enum"
×
UNCOV
975
        } else if (column.type === "int2") {
×
UNCOV
976
            return "smallint"
×
UNCOV
977
        } else if (column.type === "int8") {
×
UNCOV
978
            return "bigint"
×
UNCOV
979
        } else if (column.type === "decimal") {
×
UNCOV
980
            return "numeric"
×
UNCOV
981
        } else if (column.type === "float8" || column.type === "float") {
×
UNCOV
982
            return "double precision"
×
UNCOV
983
        } else if (column.type === "float4") {
×
UNCOV
984
            return "real"
×
UNCOV
985
        } else if (column.type === "char") {
×
UNCOV
986
            return "character"
×
UNCOV
987
        } else if (column.type === "varbit") {
×
UNCOV
988
            return "bit varying"
×
989
        } else {
UNCOV
990
            return (column.type as string) || ""
×
991
        }
992
    }
993

994
    /**
995
     * Normalizes "default" value of the column.
996
     */
997
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
UNCOV
998
        const defaultValue = columnMetadata.default
×
999

UNCOV
1000
        if (defaultValue === null || defaultValue === undefined) {
×
UNCOV
1001
            return undefined
×
1002
        }
1003

UNCOV
1004
        if (columnMetadata.isArray && Array.isArray(defaultValue)) {
×
UNCOV
1005
            return `'{${defaultValue
×
UNCOV
1006
                .map((val: string) => `${val}`)
×
1007
                .join(",")}}'`
1008
        }
1009

UNCOV
1010
        if (
×
1011
            (columnMetadata.type === "enum" ||
×
1012
                columnMetadata.type === "simple-enum" ||
1013
                typeof defaultValue === "number" ||
1014
                typeof defaultValue === "string") &&
1015
            defaultValue !== undefined
1016
        ) {
UNCOV
1017
            return `'${defaultValue}'`
×
1018
        }
1019

UNCOV
1020
        if (typeof defaultValue === "boolean") {
×
UNCOV
1021
            return defaultValue ? "true" : "false"
×
1022
        }
1023

UNCOV
1024
        if (typeof defaultValue === "function") {
×
UNCOV
1025
            const value = defaultValue()
×
1026

UNCOV
1027
            return this.normalizeDatetimeFunction(value)
×
1028
        }
1029

UNCOV
1030
        if (typeof defaultValue === "object") {
×
UNCOV
1031
            return `'${JSON.stringify(defaultValue)}'`
×
1032
        }
1033

1034
        return `${defaultValue}`
×
1035
    }
1036

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

UNCOV
1059
            return OrmUtils.deepCompare(
×
1060
                columnMetadata.default,
1061
                tableColumnDefault,
1062
            )
1063
        }
1064

UNCOV
1065
        const columnDefault = this.lowerDefaultValueIfNecessary(
×
1066
            this.normalizeDefault(columnMetadata),
1067
        )
UNCOV
1068
        return columnDefault === tableColumn.default
×
1069
    }
1070

1071
    /**
1072
     * Normalizes "isUnique" value of the column.
1073
     */
1074
    normalizeIsUnique(column: ColumnMetadata): boolean {
UNCOV
1075
        return column.entityMetadata.uniques.some(
×
UNCOV
1076
            (uq) => uq.columns.length === 1 && uq.columns[0] === column,
×
1077
        )
1078
    }
1079

1080
    /**
1081
     * Returns default column lengths, which is required on column creation.
1082
     */
1083
    getColumnLength(column: ColumnMetadata): string {
UNCOV
1084
        return column.length ? column.length.toString() : ""
×
1085
    }
1086

1087
    /**
1088
     * Creates column type definition including length, precision and scale
1089
     */
1090
    createFullType(column: TableColumn): string {
UNCOV
1091
        let type = column.type
×
1092

UNCOV
1093
        if (column.length) {
×
UNCOV
1094
            type += "(" + column.length + ")"
×
UNCOV
1095
        } else if (
×
1096
            column.precision !== null &&
×
1097
            column.precision !== undefined &&
1098
            column.scale !== null &&
1099
            column.scale !== undefined
1100
        ) {
UNCOV
1101
            type += "(" + column.precision + "," + column.scale + ")"
×
UNCOV
1102
        } else if (
×
1103
            column.precision !== null &&
×
1104
            column.precision !== undefined
1105
        ) {
UNCOV
1106
            type += "(" + column.precision + ")"
×
1107
        }
1108

UNCOV
1109
        if (column.type === "time without time zone") {
×
UNCOV
1110
            type =
×
1111
                "TIME" +
1112
                (column.precision !== null && column.precision !== undefined
×
1113
                    ? "(" + column.precision + ")"
1114
                    : "")
UNCOV
1115
        } else if (column.type === "time with time zone") {
×
UNCOV
1116
            type =
×
1117
                "TIME" +
1118
                (column.precision !== null && column.precision !== undefined
×
1119
                    ? "(" + column.precision + ")"
1120
                    : "") +
1121
                " WITH TIME ZONE"
UNCOV
1122
        } else if (column.type === "timestamp without time zone") {
×
UNCOV
1123
            type =
×
1124
                "TIMESTAMP" +
1125
                (column.precision !== null && column.precision !== undefined
×
1126
                    ? "(" + column.precision + ")"
1127
                    : "")
UNCOV
1128
        } else if (column.type === "timestamp with time zone") {
×
UNCOV
1129
            type =
×
1130
                "TIMESTAMP" +
1131
                (column.precision !== null && column.precision !== undefined
×
1132
                    ? "(" + column.precision + ")"
1133
                    : "") +
1134
                " WITH TIME ZONE"
UNCOV
1135
        } else if (this.spatialTypes.indexOf(column.type as ColumnType) >= 0) {
×
UNCOV
1136
            if (column.spatialFeatureType != null && column.srid != null) {
×
UNCOV
1137
                type = `${column.type}(${column.spatialFeatureType},${column.srid})`
×
UNCOV
1138
            } else if (column.spatialFeatureType != null) {
×
UNCOV
1139
                type = `${column.type}(${column.spatialFeatureType})`
×
1140
            } else {
UNCOV
1141
                type = column.type
×
1142
            }
1143
        }
1144

UNCOV
1145
        if (column.isArray) type += " array"
×
1146

UNCOV
1147
        return type
×
1148
    }
1149

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

UNCOV
1160
        return new Promise((ok, fail) => {
×
UNCOV
1161
            this.master.connect((err: any, connection: any, release: any) => {
×
UNCOV
1162
                err ? fail(err) : ok([connection, release])
×
1163
            })
1164
        })
1165
    }
1166

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

UNCOV
1177
        const random = Math.floor(Math.random() * this.slaves.length)
×
1178

UNCOV
1179
        return new Promise((ok, fail) => {
×
UNCOV
1180
            this.slaves[random].connect(
×
1181
                (err: any, connection: any, release: any) => {
UNCOV
1182
                    err ? fail(err) : ok([connection, release])
×
1183
                },
1184
            )
1185
        })
1186
    }
1187

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

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

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

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

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

UNCOV
1353
            return isColumnChanged
×
1354
        })
1355
    }
1356

1357
    private lowerDefaultValueIfNecessary(value: string | undefined) {
1358
        // Postgres saves function calls in default value as lowercase #2733
UNCOV
1359
        if (!value) {
×
UNCOV
1360
            return value
×
1361
        }
UNCOV
1362
        return value
×
1363
            .split(`'`)
1364
            .map((v, i) => {
UNCOV
1365
                return i % 2 === 1 ? v : v.toLowerCase()
×
1366
            })
1367
            .join(`'`)
1368
    }
1369

1370
    /**
1371
     * Returns true if driver supports RETURNING / OUTPUT statement.
1372
     */
1373
    isReturningSqlSupported(): boolean {
UNCOV
1374
        return true
×
1375
    }
1376

1377
    /**
1378
     * Returns true if driver supports uuid values generation on its own.
1379
     */
1380
    isUUIDGenerationSupported(): boolean {
UNCOV
1381
        return true
×
1382
    }
1383

1384
    /**
1385
     * Returns true if driver supports fulltext indices.
1386
     */
1387
    isFullTextColumnTypeSupported(): boolean {
UNCOV
1388
        return false
×
1389
    }
1390

1391
    get uuidGenerator(): string {
UNCOV
1392
        return this.options.uuidExtension === "pgcrypto"
×
1393
            ? "gen_random_uuid()"
1394
            : "uuid_generate_v4()"
1395
    }
1396

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

1404
    // -------------------------------------------------------------------------
1405
    // Public Methods
1406
    // -------------------------------------------------------------------------
1407

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

1422
    // -------------------------------------------------------------------------
1423
    // Protected Methods
1424
    // -------------------------------------------------------------------------
1425

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

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

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

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

1492
        // create a connection pool
UNCOV
1493
        const pool = new this.postgres.Pool(connectionOptions)
×
1494

1495
        const poolErrorHandler =
UNCOV
1496
            options.poolErrorHandler ||
×
1497
            ((error: any) =>
1498
                logger.log("warn", `Postgres pool raised an error. ${error}`))
×
1499

1500
        /*
1501
          Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
1502
          cause the hosting app to crash.
1503
         */
UNCOV
1504
        pool.on("error", poolErrorHandler)
×
1505

UNCOV
1506
        return new Promise((ok, fail) => {
×
UNCOV
1507
            pool.connect((err: any, connection: any, release: Function) => {
×
UNCOV
1508
                if (err) return fail(err)
×
1509

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

1528
    /**
1529
     * Closes connection pool.
1530
     */
1531
    protected async closePool(pool: any): Promise<void> {
UNCOV
1532
        while (this.connectedQueryRunners.length) {
×
UNCOV
1533
            await this.connectedQueryRunners[0].release()
×
1534
        }
1535

UNCOV
1536
        return new Promise<void>((ok, fail) => {
×
UNCOV
1537
            pool.end((err: any) => (err ? fail(err) : ok()))
×
1538
        })
1539
    }
1540

1541
    /**
1542
     * Executes given query.
1543
     */
1544
    protected executeQuery(connection: any, query: string) {
UNCOV
1545
        this.connection.logger.logQuery(query)
×
1546

UNCOV
1547
        return new Promise((ok, fail) => {
×
UNCOV
1548
            connection.query(query, (err: any, result: any) =>
×
UNCOV
1549
                err ? fail(err) : ok(result),
×
1550
            )
1551
        })
1552
    }
1553

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

UNCOV
1568
        if (isDatetimeFunction) {
×
1569
            // extract precision, e.g. "(3)"
UNCOV
1570
            const precision = value.match(/\(\d+\)/)
×
1571

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

UNCOV
1593
        return value
×
1594
    }
1595

1596
    /**
1597
     * Escapes a given comment.
1598
     */
1599
    protected escapeComment(comment?: string) {
UNCOV
1600
        if (!comment) return comment
×
1601

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

UNCOV
1604
        return comment
×
1605
    }
1606
}
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