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

typeorm / typeorm / 14796576772

02 May 2025 01:52PM UTC coverage: 45.367% (-30.9%) from 76.309%
14796576772

Pull #11434

github

web-flow
Merge ec4ce2d00 into fadad1a74
Pull Request #11434: feat: release PR releases using pkg.pr.new

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

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

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

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

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

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

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

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

292
    isGeneratedColumnsSupported: boolean = false
×
293

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

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

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

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

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

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

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

373
        await queryRunner.release()
×
374

375
        if (!this.schema) {
×
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> {
384
        const extensionsMetadata = await this.checkMetadataForExtensions()
×
385
        const [connection, release] = await this.obtainMasterConnection()
×
386

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

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

399
        await release()
×
400
    }
401

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

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

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

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> {
588
        if (!this.master)
×
589
            return Promise.reject(new ConnectionIsNotSetError("postgres"))
×
590

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

597
    /**
598
     * Creates a schema builder used to build and sync a schema.
599
     */
600
    createSchemaBuilder() {
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 {
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 {
615
        if (columnMetadata.transformer)
×
616
            value = ApplyValueTransformers.transformTo(
×
617
                columnMetadata.transformer,
618
                value,
619
            )
620

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

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

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

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

753
                value = []
×
754
                let cube: RegExpExecArray | null = null
×
755
                // Iterate through all regexp matches for cubes/null in array
756
                while ((cube = regexp.exec(unparsedArrayString)) !== null) {
×
757
                    if (cube[1] !== undefined) {
×
758
                        value.push(
×
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)
×
767
            }
768
        } else if (
×
769
            columnMetadata.type === "enum" ||
×
770
            columnMetadata.type === "simple-enum"
771
        ) {
772
            if (columnMetadata.isArray) {
×
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)
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
781
                        if (val.startsWith(`"`) && val.endsWith(`"`))
×
782
                            val = val.slice(1, -1)
×
783
                        // replace escaped backslash and double quotes
784
                        return val.replace(/\\(\\|")/g, "$1")
×
785
                    })
786

787
                // convert to number if that exists in possible enum options
788
                value = value.map((val: string) => {
×
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
796
                value =
×
797
                    !isNaN(+value) &&
×
798
                    columnMetadata.enum!.indexOf(parseInt(value)) >= 0
799
                        ? parseInt(value)
800
                        : value
801
            }
802
        } else if (columnMetadata.type === Number) {
×
803
            // convert to number if number
804
            value = !isNaN(+value) ? parseInt(value) : value
×
805
        }
806

807
        if (columnMetadata.transformer)
×
808
            value = ApplyValueTransformers.transformFrom(
×
809
                columnMetadata.transformer,
810
                value,
811
            )
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[]] {
824
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
×
825
            (key) => nativeParameters[key],
×
826
        )
827
        if (!parameters || !Object.keys(parameters).length)
×
828
            return [sql, escapedParameters]
×
829

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

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

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

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

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

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

868
    /**
869
     * Escapes a column name.
870
     */
871
    escape(columnName: string): string {
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 {
880
        const tablePath = [tableName]
×
881

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

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 } {
895
        const driverDatabase = this.database
×
896
        const driverSchema = this.schema
×
897

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

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

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

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

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

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

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

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 {
951
        if (
×
952
            column.type === Number ||
×
953
            column.type === "int" ||
954
            column.type === "int4"
955
        ) {
956
            return "integer"
×
957
        } else if (column.type === String || column.type === "varchar") {
×
958
            return "character varying"
×
959
        } else if (column.type === Date || column.type === "timestamp") {
×
960
            return "timestamp without time zone"
×
961
        } else if (column.type === "timestamptz") {
×
962
            return "timestamp with time zone"
×
963
        } else if (column.type === "time") {
×
964
            return "time without time zone"
×
965
        } else if (column.type === "timetz") {
×
966
            return "time with time zone"
×
967
        } else if (column.type === Boolean || column.type === "bool") {
×
968
            return "boolean"
×
969
        } else if (column.type === "simple-array") {
×
970
            return "text"
×
971
        } else if (column.type === "simple-json") {
×
972
            return "text"
×
973
        } else if (column.type === "simple-enum") {
×
974
            return "enum"
×
975
        } else if (column.type === "int2") {
×
976
            return "smallint"
×
977
        } else if (column.type === "int8") {
×
978
            return "bigint"
×
979
        } else if (column.type === "decimal") {
×
980
            return "numeric"
×
981
        } else if (column.type === "float8" || column.type === "float") {
×
982
            return "double precision"
×
983
        } else if (column.type === "float4") {
×
984
            return "real"
×
985
        } else if (column.type === "char") {
×
986
            return "character"
×
987
        } else if (column.type === "varbit") {
×
988
            return "bit varying"
×
989
        } else {
990
            return (column.type as string) || ""
×
991
        }
992
    }
993

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

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

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

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

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

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

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

1030
        if (typeof defaultValue === "object") {
×
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 {
1045
        if (
×
1046
            ["json", "jsonb"].includes(columnMetadata.type as string) &&
×
1047
            !["function", "undefined"].includes(typeof columnMetadata.default)
1048
        ) {
1049
            const tableColumnDefault =
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

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

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

1071
    /**
1072
     * Normalizes "isUnique" value of the column.
1073
     */
1074
    normalizeIsUnique(column: ColumnMetadata): boolean {
1075
        return column.entityMetadata.uniques.some(
×
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 {
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 {
1091
        let type = column.type
×
1092

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

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

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

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]> {
1156
        if (!this.master) {
×
1157
            throw new TypeORMError("Driver not Connected")
×
1158
        }
1159

1160
        return new Promise((ok, fail) => {
×
1161
            this.master.connect((err: any, connection: any, release: any) => {
×
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]> {
1173
        if (!this.slaves.length) {
×
1174
            return this.obtainMasterConnection()
×
1175
        }
1176

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

1179
        return new Promise((ok, fail) => {
×
1180
            this.slaves[random].connect(
×
1181
                (err: any, connection: any, release: any) => {
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) {
1194
        if (!insertResult) return undefined
×
1195

1196
        return Object.keys(insertResult).reduce((map, key) => {
×
1197
            const column = metadata.findColumnWithDatabaseName(key)
×
1198
            if (column) {
×
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
            }
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[] {
1217
        return columnMetadatas.filter((columnMetadata) => {
×
1218
            const tableColumn = tableColumns.find(
×
1219
                (c) => c.name === columnMetadata.databaseName,
×
1220
            )
1221
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
×
1222

1223
            const isColumnChanged =
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,
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

1353
            return isColumnChanged
×
1354
        })
1355
    }
1356

1357
    private lowerDefaultValueIfNecessary(value: string | undefined) {
1358
        // Postgres saves function calls in default value as lowercase #2733
1359
        if (!value) {
×
1360
            return value
×
1361
        }
1362
        return value
×
1363
            .split(`'`)
1364
            .map((v, i) => {
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 {
1374
        return true
×
1375
    }
1376

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

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

1391
    get uuidGenerator(): string {
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)
×
1402
    }
1403

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

1408
    /**
1409
     * Loads postgres query stream package.
1410
     */
1411
    loadStreamDependency() {
1412
        try {
×
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 {
1430
        try {
×
1431
            const postgres = this.options.driver || PlatformTools.load("pg")
×
1432
            this.postgres = postgres
×
1433
            try {
×
1434
                const pgNative =
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> {
1452
        const { logger } = this.connection
×
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
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

1475
        if (options.parseInt8 !== undefined) {
×
1476
            if (
×
1477
                this.postgres.defaults &&
×
1478
                Object.getOwnPropertyDescriptor(
1479
                    this.postgres.defaults,
1480
                    "parseInt8",
1481
                )?.set
1482
            ) {
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
1493
        const pool = new this.postgres.Pool(connectionOptions)
×
1494

1495
        const poolErrorHandler =
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
         */
1504
        pool.on("error", poolErrorHandler)
×
1505

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

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

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

1536
        return new Promise<void>((ok, fail) => {
×
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) {
1545
        this.connection.logger.logQuery(query)
×
1546

1547
        return new Promise((ok, fail) => {
×
1548
            connection.query(query, (err: any, result: any) =>
×
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
1560
        const upperCaseValue = value.toUpperCase()
×
1561
        const isDatetimeFunction =
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

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

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

1593
        return value
×
1594
    }
1595

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

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

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