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

typeorm / typeorm / 20883731116

10 Jan 2026 08:09PM UTC coverage: 80.886% (-0.005%) from 80.891%
20883731116

push

github

web-flow
feat: add support for installing additional postgres extensions (#11888)

27128 of 32972 branches covered (82.28%)

Branch coverage included in aggregate %.

52 of 72 new or added lines in 2 files covered. (72.22%)

1 existing line in 1 file now uncovered.

91609 of 113824 relevant lines covered (80.48%)

69531.05 hits per line

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

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

26✔
35
/**
26✔
36
 * Organizes communication with PostgreSQL DBMS.
26✔
37
 */
26✔
38
export class PostgresDriver implements Driver {
26✔
39
    // -------------------------------------------------------------------------
26✔
40
    // Public Properties
26✔
41
    // -------------------------------------------------------------------------
26✔
42

26✔
43
    /**
26✔
44
     * Connection used by driver.
26✔
45
     */
26✔
46
    connection: DataSource
26✔
47

26✔
48
    /**
26✔
49
     * Postgres underlying library.
26✔
50
     */
26✔
51
    postgres: any
26✔
52

26✔
53
    /**
26✔
54
     * Pool for master database.
26✔
55
     */
26✔
56
    master: any
26✔
57

26✔
58
    /**
26✔
59
     * Pool for slave databases.
26✔
60
     * Used in replication.
26✔
61
     */
26✔
62
    slaves: any[] = []
26✔
63

26✔
64
    /**
26✔
65
     * We store all created query runners because we need to release them.
26✔
66
     */
26✔
67
    connectedQueryRunners: QueryRunner[] = []
26✔
68

26✔
69
    // -------------------------------------------------------------------------
26✔
70
    // Public Implemented Properties
26✔
71
    // -------------------------------------------------------------------------
26✔
72

26✔
73
    /**
26✔
74
     * Connection options.
26✔
75
     */
26✔
76
    options: PostgresConnectionOptions
26✔
77

26✔
78
    /**
26✔
79
     * Version of Postgres. Requires a SQL query to the DB, so it is set on the first
26✔
80
     * connection attempt.
26✔
81
     */
26✔
82
    version?: string
26✔
83

26✔
84
    /**
26✔
85
     * Database name used to perform all write queries.
26✔
86
     */
26✔
87
    database?: string
26✔
88

26✔
89
    /**
26✔
90
     * Schema name used to perform all write queries.
26✔
91
     */
26✔
92
    schema?: string
26✔
93

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

26✔
104
    /**
26✔
105
     * Indicates if replication is enabled.
26✔
106
     */
26✔
107
    isReplicated: boolean = false
26✔
108

26✔
109
    /**
26✔
110
     * Indicates if tree tables are supported by this driver.
26✔
111
     */
26✔
112
    treeSupport = true
26✔
113

26✔
114
    /**
26✔
115
     * Represent transaction support by this driver
26✔
116
     */
26✔
117
    transactionSupport = "nested" as const
26✔
118

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

26✔
202
    /**
26✔
203
     * Returns type of upsert supported by driver if any
26✔
204
     */
26✔
205
    supportedUpsertTypes: UpsertType[] = ["on-conflict-do-update"]
26✔
206

26✔
207
    /**
26✔
208
     * Gets list of spatial column data types.
26✔
209
     */
26✔
210
    spatialTypes: ColumnType[] = ["geometry", "geography"]
26✔
211

26✔
212
    /**
26✔
213
     * Gets list of column data types that support length by a driver.
26✔
214
     */
26✔
215
    withLengthColumnTypes: ColumnType[] = [
26✔
216
        "character varying",
26✔
217
        "varchar",
26✔
218
        "character",
26✔
219
        "char",
26✔
220
        "bit",
26✔
221
        "varbit",
26✔
222
        "bit varying",
26✔
223
        "vector",
26✔
224
        "halfvec",
26✔
225
    ]
26✔
226

26✔
227
    /**
26✔
228
     * Gets list of column data types that support precision by a driver.
26✔
229
     */
26✔
230
    withPrecisionColumnTypes: ColumnType[] = [
26✔
231
        "numeric",
26✔
232
        "decimal",
26✔
233
        "interval",
26✔
234
        "time without time zone",
26✔
235
        "time with time zone",
26✔
236
        "timestamp without time zone",
26✔
237
        "timestamp with time zone",
26✔
238
    ]
26✔
239

26✔
240
    /**
26✔
241
     * Gets list of column data types that support scale by a driver.
26✔
242
     */
26✔
243
    withScaleColumnTypes: ColumnType[] = ["numeric", "decimal"]
26✔
244

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

26✔
275
    /**
26✔
276
     * Table indices supported
26✔
277
     */
26✔
278
    supportedIndexTypes: TableIndexTypes[] = [
26✔
279
        "brin",
26✔
280
        "btree",
26✔
281
        "gin",
26✔
282
        "gist",
26✔
283
        "hash",
26✔
284
        "spgist",
26✔
285
    ]
26✔
286

26✔
287
    /**
26✔
288
     * The prefix used for the parameters
26✔
289
     */
26✔
290
    parametersPrefix: string = "$"
26✔
291

26✔
292
    /**
26✔
293
     * Default values of length, precision and scale depends on column data type.
26✔
294
     * Used in the cases when length/precision/scale is not specified by user.
26✔
295
     */
26✔
296
    dataTypeDefaults: DataTypeDefaults = {
26✔
297
        character: { length: 1 },
26✔
298
        bit: { length: 1 },
26✔
299
        interval: { precision: 6 },
26✔
300
        "time without time zone": { precision: 6 },
26✔
301
        "time with time zone": { precision: 6 },
26✔
302
        "timestamp without time zone": { precision: 6 },
26✔
303
        "timestamp with time zone": { precision: 6 },
26✔
304
    }
26✔
305

26✔
306
    /**
26✔
307
     * Max length allowed by Postgres for aliases.
26✔
308
     * @see https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
26✔
309
     */
26✔
310
    maxAliasLength = 63
26✔
311

26✔
312
    isGeneratedColumnsSupported: boolean = false
26✔
313

26✔
314
    cteCapabilities: CteCapabilities = {
26✔
315
        enabled: true,
26✔
316
        writable: true,
26✔
317
        requiresRecursiveHint: true,
26✔
318
        materializedHint: true,
26✔
319
    }
26✔
320

26✔
321
    // -------------------------------------------------------------------------
26✔
322
    // Constructor
26✔
323
    // -------------------------------------------------------------------------
26✔
324

26✔
325
    constructor(connection?: DataSource) {
26✔
326
        if (!connection) {
3,002✔
327
            return
286✔
328
        }
286✔
329

2,716!
330
        this.connection = connection
2,716✔
331
        this.options = connection.options as PostgresConnectionOptions
2,716✔
332
        this.isReplicated = this.options.replication ? true : false
3,002!
333
        if (this.options.useUTC) {
3,002!
334
            process.env.PGTZ = "UTC"
×
335
        }
×
336
        // load postgres package
2,716✔
337
        this.loadDependencies()
2,716✔
338

2,716✔
339
        this.database = DriverUtils.buildDriverOptions(
2,716✔
340
            this.options.replication
2,716✔
341
                ? this.options.replication.master
2,760✔
342
                : this.options,
3,002!
343
        ).database
3,002✔
344
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
3,002✔
345

3,002✔
346
        // ObjectUtils.assign(this.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
3,002✔
347
        // validate options to make sure everything is set
3,002✔
348
        // todo: revisit validation with replication in mind
3,002✔
349
        // if (!this.options.host)
3,002✔
350
        //     throw new DriverOptionNotSetError("host");
3,002✔
351
        // if (!this.options.username)
3,002✔
352
        //     throw new DriverOptionNotSetError("username");
3,002✔
353
        // if (!this.options.database)
3,002✔
354
        //     throw new DriverOptionNotSetError("database");
3,002✔
355
    }
3,002✔
356

26✔
357
    // -------------------------------------------------------------------------
26✔
358
    // Public Implemented Methods
26✔
359
    // -------------------------------------------------------------------------
26✔
360

26✔
361
    /**
26✔
362
     * Performs connection to the database.
26✔
363
     * Based on pooling options, it can either create connection immediately,
26✔
364
     * either create a pool and create connection when needed.
26✔
365
     */
26✔
366
    async connect(): Promise<void> {
26✔
367
        if (this.options.replication) {
2,716✔
368
            this.slaves = await Promise.all(
32✔
369
                this.options.replication.slaves.map((slave) => {
32✔
370
                    return this.createPool(this.options, slave)
32✔
371
                }),
32✔
372
            )
32✔
373
            this.master = await this.createPool(
32✔
374
                this.options,
32✔
375
                this.options.replication.master,
32✔
376
            )
32✔
377
        } else {
2,716✔
378
            this.master = await this.createPool(this.options, this.options)
2,684✔
379
        }
2,684✔
380

2,716✔
381
        if (!this.version || !this.database || !this.searchSchema) {
2,716!
382
            const queryRunner = this.createQueryRunner("master")
2,716✔
383

2,716✔
384
            if (!this.version) {
2,716✔
385
                this.version = await queryRunner.getVersion()
2,716✔
386
            }
2,716✔
387

2,716✔
388
            if (!this.database) {
2,716!
389
                this.database = await queryRunner.getCurrentDatabase()
×
390
            }
×
391

2,716✔
392
            if (!this.searchSchema) {
2,716✔
393
                this.searchSchema = await queryRunner.getCurrentSchema()
2,716✔
394
            }
2,716✔
395

2,716✔
396
            await queryRunner.release()
2,716✔
397
        }
2,716✔
398

2,716✔
399
        if (!this.schema) {
2,716✔
400
            this.schema = this.searchSchema
2,676✔
401
        }
2,676✔
402
    }
2,716✔
403

26✔
404
    /**
26✔
405
     * Makes any action after connection (e.g. create extensions in Postgres driver).
26✔
406
     */
26✔
407
    async afterConnect(): Promise<void> {
26✔
408
        const [connection, release] = await this.obtainMasterConnection()
2,708✔
409

2,708✔
410
        const installExtensions =
2,708✔
411
            this.options.installExtensions === undefined ||
2,708✔
412
            this.options.installExtensions
8✔
413
        if (installExtensions) {
2,708✔
414
            const extensionsMetadata = await this.checkMetadataForExtensions()
2,704✔
415
            const extensionsToInstall = this.options.extensions
2,704✔
416
            if (extensionsMetadata.hasExtensions)
2,704✔
417
                await this.enableExtensions(extensionsMetadata, connection)
2,704✔
418

2,704✔
419
            if (extensionsToInstall) {
2,704✔
420
                const availableExtensions =
4✔
421
                    await this.getAvailableExtensions(connection)
4✔
422

4✔
423
                await this.enableCustomExtensions(
4✔
424
                    availableExtensions,
4✔
425
                    extensionsToInstall,
4✔
426
                    connection,
4✔
427
                )
4✔
428
            }
4✔
429
        }
2,704✔
430

2,708✔
431
        this.isGeneratedColumnsSupported = VersionUtils.isGreaterOrEqual(
2,708✔
432
            this.version,
2,708✔
433
            "12.0",
2,708✔
434
        )
2,708✔
435

2,708✔
436
        await release()
2,708✔
437
    }
2,708✔
438

26✔
439
    protected async getAvailableExtensions(connection: any) {
26✔
440
        const availableExtensions = new Set<string>()
4✔
441
        const { logger } = this.connection
4✔
442
        try {
4✔
443
            const result: any = await this.executeQuery(
4✔
444
                connection,
4✔
445
                `SELECT name FROM pg_available_extensions`,
4✔
446
            )
4✔
447
            if (result.rows && Array.isArray(result.rows)) {
4✔
448
                result.rows.forEach((row: any) => {
4✔
449
                    availableExtensions.add(row.name)
242✔
450
                })
4✔
451
            }
4✔
452
        } catch (_) {
4!
NEW
453
            logger.log(
×
NEW
454
                "warn",
×
NEW
455
                "Could not retrieve available extensions. Extension installation may fail if extensions are not available.",
×
NEW
456
            )
×
NEW
457
        }
×
458
        return availableExtensions
4✔
459
    }
4✔
460

26✔
461
    protected async enableExtensions(extensionsMetadata: any, connection: any) {
26✔
462
        const { logger } = this.connection
308✔
463

308✔
464
        const {
308✔
465
            hasUuidColumns,
308✔
466
            hasCitextColumns,
308✔
467
            hasHstoreColumns,
308✔
468
            hasCubeColumns,
308✔
469
            hasGeometryColumns,
308✔
470
            hasLtreeColumns,
308✔
471
            hasVectorColumns,
308✔
472
            hasExclusionConstraints,
308✔
473
        } = extensionsMetadata
308✔
474

308✔
475
        if (hasUuidColumns)
308✔
476
            try {
308✔
477
                await this.executeQuery(
132✔
478
                    connection,
132✔
479
                    `CREATE EXTENSION IF NOT EXISTS "${
132✔
480
                        this.options.uuidExtension || "uuid-ossp"
132✔
481
                    }"`,
132✔
482
                )
132✔
483
            } catch (_) {
132!
484
                logger.log(
×
485
                    "warn",
×
486
                    `At least one of the entities has uuid column, but the '${
×
487
                        this.options.uuidExtension || "uuid-ossp"
×
488
                    }' extension cannot be installed automatically. Please install it manually using superuser rights, or select another uuid extension.`,
×
489
                )
×
490
            }
×
491
        if (hasCitextColumns)
308✔
492
            try {
308✔
493
                await this.executeQuery(
20✔
494
                    connection,
20✔
495
                    `CREATE EXTENSION IF NOT EXISTS "citext"`,
20✔
496
                )
20✔
497
            } catch (_) {
20!
498
                logger.log(
×
499
                    "warn",
×
500
                    "At least one of the entities has citext column, but the 'citext' extension cannot be installed automatically. Please install it manually using superuser rights",
×
501
                )
×
502
            }
×
503
        if (hasHstoreColumns)
308✔
504
            try {
308✔
505
                await this.executeQuery(
20✔
506
                    connection,
20✔
507
                    `CREATE EXTENSION IF NOT EXISTS "hstore"`,
20✔
508
                )
20✔
509
            } catch (_) {
20!
510
                logger.log(
×
511
                    "warn",
×
512
                    "At least one of the entities has hstore column, but the 'hstore' extension cannot be installed automatically. Please install it manually using superuser rights",
×
513
                )
×
514
            }
×
515
        if (hasGeometryColumns)
308✔
516
            try {
308✔
517
                await this.executeQuery(
8✔
518
                    connection,
8✔
519
                    `CREATE EXTENSION IF NOT EXISTS "postgis"`,
8✔
520
                )
8✔
521
            } catch (_) {
8!
522
                logger.log(
×
523
                    "warn",
×
524
                    "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",
×
525
                )
×
526
            }
×
527
        if (hasCubeColumns)
308✔
528
            try {
308✔
529
                await this.executeQuery(
4✔
530
                    connection,
4✔
531
                    `CREATE EXTENSION IF NOT EXISTS "cube"`,
4✔
532
                )
4✔
533
            } catch (_) {
4!
534
                logger.log(
×
535
                    "warn",
×
536
                    "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",
×
537
                )
×
538
            }
×
539
        if (hasLtreeColumns)
308✔
540
            try {
308✔
541
                await this.executeQuery(
4✔
542
                    connection,
4✔
543
                    `CREATE EXTENSION IF NOT EXISTS "ltree"`,
4✔
544
                )
4✔
545
            } catch (_) {
4!
546
                logger.log(
×
547
                    "warn",
×
548
                    "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",
×
549
                )
×
550
            }
×
551
        if (hasVectorColumns)
308✔
552
            try {
308✔
553
                await this.executeQuery(
8✔
554
                    connection,
8✔
555
                    `CREATE EXTENSION IF NOT EXISTS "vector"`,
8✔
556
                )
8✔
557
            } catch (_) {
8!
558
                logger.log(
×
559
                    "warn",
×
560
                    "At least one of the entities has a vector column, but the 'vector' extension (pgvector) cannot be installed automatically. Please install it manually using superuser rights",
×
561
                )
×
562
            }
×
563
        if (hasExclusionConstraints)
308✔
564
            try {
308✔
565
                // The btree_gist extension provides operator support in PostgreSQL exclusion constraints
132✔
566
                await this.executeQuery(
132✔
567
                    connection,
132✔
568
                    `CREATE EXTENSION IF NOT EXISTS "btree_gist"`,
132✔
569
                )
132✔
570
            } catch (_) {
132!
571
                logger.log(
×
572
                    "warn",
×
573
                    "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",
×
574
                )
×
575
            }
×
576
    }
308✔
577

26✔
578
    protected async enableCustomExtensions(
26✔
579
        availableExtensions: Set<string>,
4✔
580
        extensionsToInstall: string[],
4✔
581
        connection: any,
4✔
582
    ) {
4✔
583
        if (!extensionsToInstall) return
4!
584
        const logger = this.connection.logger
4✔
585
        for (const extension of extensionsToInstall) {
4✔
586
            if (availableExtensions.has(extension)) {
8✔
587
                try {
8✔
588
                    await this.executeQuery(
8✔
589
                        connection,
8✔
590
                        `CREATE EXTENSION IF NOT EXISTS "${extension}"`,
8✔
591
                    )
8✔
592
                } catch (_) {
8!
NEW
593
                    logger.log(
×
NEW
594
                        "warn",
×
NEW
595
                        `The extension "${extension}" cannot be installed automatically. Please install it manually using superuser rights`,
×
NEW
596
                    )
×
NEW
597
                }
×
598
            } else {
8!
NEW
599
                logger.log(
×
NEW
600
                    "warn",
×
NEW
601
                    `The extension "${extension}" is not available on this database. Please install it manually using superuser rights`,
×
NEW
602
                )
×
NEW
603
            }
×
604
        }
8✔
605
    }
4✔
606

26✔
607
    protected async checkMetadataForExtensions() {
26✔
608
        const hasUuidColumns = this.connection.entityMetadatas.some(
2,704✔
609
            (metadata) => {
2,704✔
610
                return (
6,396✔
611
                    metadata.generatedColumns.filter(
6,396✔
612
                        (column) => column.generationStrategy === "uuid",
6,396✔
613
                    ).length > 0
6,396✔
614
                )
6,396✔
615
            },
2,704✔
616
        )
2,704✔
617
        const hasCitextColumns = this.connection.entityMetadatas.some(
2,704✔
618
            (metadata) => {
2,704✔
619
                return (
6,556✔
620
                    metadata.columns.filter(
6,556✔
621
                        (column) => column.type === "citext",
6,556✔
622
                    ).length > 0
6,556✔
623
                )
6,556✔
624
            },
2,704✔
625
        )
2,704✔
626
        const hasHstoreColumns = this.connection.entityMetadatas.some(
2,704✔
627
            (metadata) => {
2,704✔
628
                return (
6,556✔
629
                    metadata.columns.filter(
6,556✔
630
                        (column) => column.type === "hstore",
6,556✔
631
                    ).length > 0
6,556✔
632
                )
6,556✔
633
            },
2,704✔
634
        )
2,704✔
635
        const hasCubeColumns = this.connection.entityMetadatas.some(
2,704✔
636
            (metadata) => {
2,704✔
637
                return (
6,564✔
638
                    metadata.columns.filter((column) => column.type === "cube")
6,564✔
639
                        .length > 0
6,564✔
640
                )
6,564✔
641
            },
2,704✔
642
        )
2,704✔
643
        const hasGeometryColumns = this.connection.entityMetadatas.some(
2,704✔
644
            (metadata) => {
2,704✔
645
                return (
6,564✔
646
                    metadata.columns.filter(
6,564✔
647
                        (column) => this.spatialTypes.indexOf(column.type) >= 0,
6,564✔
648
                    ).length > 0
6,564✔
649
                )
6,564✔
650
            },
2,704✔
651
        )
2,704✔
652
        const hasLtreeColumns = this.connection.entityMetadatas.some(
2,704✔
653
            (metadata) => {
2,704✔
654
                return (
6,564✔
655
                    metadata.columns.filter((column) => column.type === "ltree")
6,564✔
656
                        .length > 0
6,564✔
657
                )
6,564✔
658
            },
2,704✔
659
        )
2,704✔
660
        const hasVectorColumns = this.connection.entityMetadatas.some(
2,704✔
661
            (metadata) => {
2,704✔
662
                return metadata.columns.some(
6,564✔
663
                    (column) =>
6,564✔
664
                        column.type === "vector" || column.type === "halfvec",
6,564✔
665
                )
6,564✔
666
            },
2,704✔
667
        )
2,704✔
668
        const hasExclusionConstraints = this.connection.entityMetadatas.some(
2,704✔
669
            (metadata) => {
2,704✔
670
                return metadata.exclusions.length > 0
5,716✔
671
            },
2,704✔
672
        )
2,704✔
673

2,704✔
674
        return {
2,704✔
675
            hasUuidColumns,
2,704✔
676
            hasCitextColumns,
2,704✔
677
            hasHstoreColumns,
2,704✔
678
            hasCubeColumns,
2,704✔
679
            hasGeometryColumns,
2,704✔
680
            hasLtreeColumns,
2,704✔
681
            hasVectorColumns,
2,704✔
682
            hasExclusionConstraints,
2,704✔
683
            hasExtensions:
2,704✔
684
                hasUuidColumns ||
2,704✔
685
                hasCitextColumns ||
2,704✔
686
                hasHstoreColumns ||
2,704✔
687
                hasGeometryColumns ||
2,704✔
688
                hasCubeColumns ||
2,704✔
689
                hasLtreeColumns ||
2,704✔
690
                hasVectorColumns ||
2,704✔
691
                hasExclusionConstraints,
2,704✔
692
        }
2,704✔
693
    }
2,704✔
694

26✔
695
    /**
26✔
696
     * Closes connection with database.
26✔
697
     */
26✔
698
    async disconnect(): Promise<void> {
26✔
699
        if (!this.master) {
2,704!
700
            throw new ConnectionIsNotSetError("postgres")
×
701
        }
×
702

2,704✔
703
        await this.closePool(this.master)
2,704✔
704
        await Promise.all(this.slaves.map((slave) => this.closePool(slave)))
2,704✔
705
        this.master = undefined
2,704✔
706
        this.slaves = []
2,704✔
707
    }
2,704✔
708

26✔
709
    /**
26✔
710
     * Creates a schema builder used to build and sync a schema.
26✔
711
     */
26✔
712
    createSchemaBuilder() {
26✔
713
        return new RdbmsSchemaBuilder(this.connection)
7,832✔
714
    }
7,832✔
715

26✔
716
    /**
26✔
717
     * Creates a query runner used to execute database queries.
26✔
718
     */
26✔
719
    createQueryRunner(mode: ReplicationMode): PostgresQueryRunner {
26✔
720
        return new PostgresQueryRunner(this, mode)
58,360✔
721
    }
58,360✔
722

26✔
723
    /**
26✔
724
     * Prepares given value to a value to be persisted, based on its column type and metadata.
26✔
725
     */
26✔
726
    preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
26✔
727
        if (columnMetadata.transformer)
767,896✔
728
            value = ApplyValueTransformers.transformTo(
767,896✔
729
                columnMetadata.transformer,
212✔
730
                value,
212✔
731
            )
212✔
732

767,896✔
733
        if (value === null || value === undefined) return value
767,896✔
734

751,140✔
735
        if (columnMetadata.type === Boolean) {
767,896✔
736
            return value === true ? 1 : 0
8,040✔
737
        } else if (columnMetadata.type === "date") {
767,896✔
738
            return DateUtils.mixedDateToDateString(value, {
32✔
739
                utc: columnMetadata.utc,
32✔
740
            })
32✔
741
        } else if (columnMetadata.type === "time") {
743,100✔
742
            return DateUtils.mixedDateToTimeString(value)
20✔
743
        } else if (
743,068✔
744
            columnMetadata.type === "datetime" ||
743,048✔
745
            columnMetadata.type === Date ||
743,048✔
746
            columnMetadata.type === "timestamp" ||
743,048✔
747
            columnMetadata.type === "timestamp with time zone" ||
743,048✔
748
            columnMetadata.type === "timestamp without time zone"
702,800✔
749
        ) {
743,048✔
750
            return DateUtils.mixedDateToDate(value)
40,256✔
751
        } else if (
743,048✔
752
            ["json", "jsonb", ...this.spatialTypes].indexOf(
702,792✔
753
                columnMetadata.type,
702,792✔
754
            ) >= 0
702,792✔
755
        ) {
702,792✔
756
            return JSON.stringify(value)
40,212✔
757
        } else if (
702,792✔
758
            columnMetadata.type === "vector" ||
662,580✔
759
            columnMetadata.type === "halfvec"
662,500✔
760
        ) {
662,580✔
761
            if (Array.isArray(value)) {
108✔
762
                return `[${value.join(",")}]`
108✔
763
            } else {
108!
764
                return value
×
765
            }
×
766
        } else if (columnMetadata.type === "hstore") {
662,580✔
767
            if (typeof value === "string") {
24✔
768
                return value
8✔
769
            } else {
24✔
770
                // https://www.postgresql.org/docs/9.0/hstore.html
16✔
771
                const quoteString = (value: unknown) => {
16✔
772
                    // If a string to be quoted is `null` or `undefined`, we return a literal unquoted NULL.
112✔
773
                    // This way, NULL values can be stored in the hstore object.
112✔
774
                    if (value === null || typeof value === "undefined") {
112✔
775
                        return "NULL"
4✔
776
                    }
4✔
777
                    // Convert non-null values to string since HStore only stores strings anyway.
108✔
778
                    // To include a double quote or a backslash in a key or value, escape it with a backslash.
108✔
779
                    return `"${`${value}`.replace(/(?=["\\])/g, "\\")}"`
108✔
780
                }
108✔
781
                return Object.keys(value)
16✔
782
                    .map(
16✔
783
                        (key) =>
16✔
784
                            quoteString(key) + "=>" + quoteString(value[key]),
16✔
785
                    )
16✔
786
                    .join(",")
16✔
787
            }
16✔
788
        } else if (columnMetadata.type === "simple-array") {
662,472✔
789
            return DateUtils.simpleArrayToString(value)
8✔
790
        } else if (columnMetadata.type === "simple-json") {
662,448✔
791
            return DateUtils.simpleJsonToString(value)
20✔
792
        } else if (columnMetadata.type === "cube") {
662,440✔
793
            if (columnMetadata.isArray) {
40✔
794
                return `{${value
4✔
795
                    .map((cube: number[]) => `"(${cube.join(",")})"`)
4✔
796
                    .join(",")}}`
4✔
797
            }
4✔
798
            return `(${value.join(",")})`
36✔
799
        } else if (columnMetadata.type === "ltree") {
662,420✔
800
            return value
32✔
801
                .split(".")
32✔
802
                .filter(Boolean)
32✔
803
                .join(".")
32✔
804
                .replace(/[\s]+/g, "_")
32✔
805
        } else if (
662,380✔
806
            (columnMetadata.type === "enum" ||
662,348✔
807
                columnMetadata.type === "simple-enum") &&
662,348✔
808
            !columnMetadata.isArray
260✔
809
        ) {
662,348✔
810
            return "" + value
108✔
811
        }
108✔
812

662,240✔
813
        return value
662,240✔
814
    }
662,240✔
815

26✔
816
    /**
26✔
817
     * Prepares given value to a value to be persisted, based on its column type or metadata.
26✔
818
     */
26✔
819
    prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
26✔
820
        if (value === null || value === undefined)
405,176✔
821
            return columnMetadata.transformer
405,176✔
822
                ? ApplyValueTransformers.transformFrom(
16,144✔
823
                      columnMetadata.transformer,
64✔
824
                      value,
64✔
825
                  )
16,144✔
826
                : value
16,144✔
827

389,032✔
828
        if (columnMetadata.type === Boolean) {
405,176✔
829
            value = value ? true : false
2,832✔
830
        } else if (
405,176✔
831
            columnMetadata.type === "datetime" ||
386,200✔
832
            columnMetadata.type === Date ||
386,200✔
833
            columnMetadata.type === "timestamp" ||
386,200✔
834
            columnMetadata.type === "timestamp with time zone" ||
386,200✔
835
            columnMetadata.type === "timestamp without time zone"
384,772✔
836
        ) {
386,200✔
837
            value = DateUtils.normalizeHydratedDate(value)
1,436✔
838
        } else if (columnMetadata.type === "date") {
386,200✔
839
            value = DateUtils.mixedDateToDateString(value, {
72✔
840
                utc: columnMetadata.utc,
72✔
841
            })
72✔
842
        } else if (columnMetadata.type === "time") {
384,764✔
843
            value = DateUtils.mixedTimeToString(value)
20✔
844
        } else if (
384,692✔
845
            columnMetadata.type === "vector" ||
384,672✔
846
            columnMetadata.type === "halfvec"
384,648✔
847
        ) {
384,672✔
848
            if (
32✔
849
                typeof value === "string" &&
32✔
850
                value.startsWith("[") &&
32✔
851
                value.endsWith("]")
32✔
852
            ) {
32✔
853
                if (value === "[]") return []
32!
854
                return value.slice(1, -1).split(",").map(Number)
32✔
855
            }
32✔
856
        } else if (columnMetadata.type === "hstore") {
384,672✔
857
            if (columnMetadata.hstoreType === "object") {
24✔
858
                const unescapeString = (str: string) =>
16✔
859
                    str.replace(/\\./g, (m) => m[1])
108✔
860
                const regexp =
16✔
861
                    /"([^"\\]*(?:\\.[^"\\]*)*)"=>(?:(NULL)|"([^"\\]*(?:\\.[^"\\]*)*)")(?:,|$)/g
16✔
862
                const object: ObjectLiteral = {}
16✔
863
                ;`${value}`.replace(
16✔
864
                    regexp,
16✔
865
                    (_, key, nullValue, stringValue) => {
16✔
866
                        object[unescapeString(key)] = nullValue
56✔
867
                            ? null
56✔
868
                            : unescapeString(stringValue)
56✔
869
                        return ""
56✔
870
                    },
16✔
871
                )
16✔
872
                value = object
16✔
873
            }
16✔
874
        } else if (columnMetadata.type === "simple-array") {
384,640✔
875
            value = DateUtils.stringToSimpleArray(value)
8✔
876
        } else if (columnMetadata.type === "simple-json") {
384,616✔
877
            value = DateUtils.stringToSimpleJson(value)
20✔
878
        } else if (columnMetadata.type === "cube") {
384,608✔
879
            value = value.replace(/[()\s]+/g, "") // remove whitespace
36✔
880
            if (columnMetadata.isArray) {
36✔
881
                /**
4✔
882
                 * Strips these groups from `{"1,2,3","",NULL}`:
4✔
883
                 * 1. ["1,2,3", undefined]  <- cube of arity 3
4✔
884
                 * 2. ["", undefined]         <- cube of arity 0
4✔
885
                 * 3. [undefined, "NULL"]     <- NULL
4✔
886
                 */
4✔
887
                const regexp = /(?:"((?:[\d\s.,])*)")|(?:(NULL))/g
4✔
888
                const unparsedArrayString = value
4✔
889

4✔
890
                value = []
4✔
891
                let cube: RegExpExecArray | null = null
4✔
892
                // Iterate through all regexp matches for cubes/null in array
4✔
893
                while ((cube = regexp.exec(unparsedArrayString)) !== null) {
4✔
894
                    if (cube[1] !== undefined) {
8✔
895
                        value.push(
8✔
896
                            cube[1].split(",").filter(Boolean).map(Number),
8✔
897
                        )
8✔
898
                    } else {
8!
899
                        value.push(undefined)
×
900
                    }
×
901
                }
8✔
902
            } else {
36✔
903
                value = value.split(",").filter(Boolean).map(Number)
32✔
904
            }
32✔
905
        } else if (
384,588✔
906
            columnMetadata.type === "enum" ||
384,552✔
907
            columnMetadata.type === "simple-enum"
384,076✔
908
        ) {
384,552✔
909
            if (columnMetadata.isArray) {
700✔
910
                if (value === "{}") return []
412✔
911

336✔
912
                // manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
336✔
913
                value = (value as string)
336✔
914
                    .slice(1, -1)
336✔
915
                    .split(",")
336✔
916
                    .map((val) => {
336✔
917
                        // replace double quotes from the beginning and from the end
552✔
918
                        if (val.startsWith(`"`) && val.endsWith(`"`))
552✔
919
                            val = val.slice(1, -1)
552✔
920
                        // replace escaped backslash and double quotes
552✔
921
                        return val.replace(/\\(\\|")/g, "$1")
552✔
922
                    })
336✔
923

336✔
924
                // convert to number if that exists in possible enum options
336✔
925
                value = value.map((val: string) => {
336✔
926
                    return !isNaN(+val) &&
552✔
927
                        columnMetadata.enum!.indexOf(parseInt(val)) >= 0
200✔
928
                        ? parseInt(val)
552✔
929
                        : val
552✔
930
                })
336✔
931
            } else {
700✔
932
                // convert to number if that exists in possible enum options
288✔
933
                value =
288✔
934
                    !isNaN(+value) &&
288✔
935
                    columnMetadata.enum!.indexOf(parseInt(value)) >= 0
112✔
936
                        ? parseInt(value)
288✔
937
                        : value
288✔
938
            }
288✔
939
        } else if (columnMetadata.type === Number) {
384,552✔
940
            // convert to number if number
199,492✔
941
            value = !isNaN(+value) ? parseInt(value) : value
199,492!
942
        }
199,492✔
943

388,924✔
944
        if (columnMetadata.transformer)
388,924✔
945
            value = ApplyValueTransformers.transformFrom(
405,176✔
946
                columnMetadata.transformer,
332✔
947
                value,
332✔
948
            )
332✔
949
        return value
388,924✔
950
    }
388,924✔
951

26✔
952
    /**
26✔
953
     * Replaces parameters in the given sql with special escaping character
26✔
954
     * and an array of parameter names to be passed to a query.
26✔
955
     */
26✔
956
    escapeQueryWithParameters(
26✔
957
        sql: string,
67,580✔
958
        parameters: ObjectLiteral,
67,580✔
959
        nativeParameters: ObjectLiteral,
67,580✔
960
    ): [string, any[]] {
67,580✔
961
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
67,580✔
962
            (key) => nativeParameters[key],
67,580✔
963
        )
67,580✔
964
        if (!parameters || !Object.keys(parameters).length)
67,580✔
965
            return [sql, escapedParameters]
67,580✔
966

64,216✔
967
        const parameterIndexMap = new Map<string, number>()
64,216✔
968
        sql = sql.replace(
64,216✔
969
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
64,216✔
970
            (full, isArray: string, key: string): string => {
64,216✔
971
                if (!parameters.hasOwnProperty(key)) {
1,034,808✔
972
                    return full
180✔
973
                }
180✔
974

1,034,628✔
975
                if (parameterIndexMap.has(key)) {
1,034,808✔
976
                    return this.parametersPrefix + parameterIndexMap.get(key)
60✔
977
                }
60✔
978

1,034,568✔
979
                const value: any = parameters[key]
1,034,568✔
980

1,034,568✔
981
                if (isArray) {
1,034,808✔
982
                    return value
612✔
983
                        .map((v: any) => {
612✔
984
                            escapedParameters.push(v)
816✔
985
                            return this.createParameter(
816✔
986
                                key,
816✔
987
                                escapedParameters.length - 1,
816✔
988
                            )
816✔
989
                        })
612✔
990
                        .join(", ")
612✔
991
                }
612✔
992

1,033,956✔
993
                if (typeof value === "function") {
1,034,808!
994
                    return value()
×
995
                }
×
996

1,033,956✔
997
                escapedParameters.push(value)
1,033,956✔
998
                parameterIndexMap.set(key, escapedParameters.length)
1,033,956✔
999
                return this.createParameter(key, escapedParameters.length - 1)
1,033,956✔
1000
            },
64,216✔
1001
        ) // todo: make replace only in value statements, otherwise problems
64,216✔
1002
        return [sql, escapedParameters]
64,216✔
1003
    }
64,216✔
1004

26✔
1005
    /**
26✔
1006
     * Escapes a column name.
26✔
1007
     */
26✔
1008
    escape(columnName: string): string {
26✔
1009
        return '"' + columnName + '"'
16,488,380✔
1010
    }
16,488,380✔
1011

26✔
1012
    /**
26✔
1013
     * Build full table name with schema name and table name.
26✔
1014
     * E.g. myDB.mySchema.myTable
26✔
1015
     */
26✔
1016
    buildTableName(tableName: string, schema?: string): string {
26✔
1017
        const tablePath = [tableName]
1,739,608✔
1018

1,739,608✔
1019
        if (schema) {
1,739,608✔
1020
            tablePath.unshift(schema)
1,697,788✔
1021
        }
1,697,788✔
1022

1,739,608✔
1023
        return tablePath.join(".")
1,739,608✔
1024
    }
1,739,608✔
1025

26✔
1026
    /**
26✔
1027
     * Parse a target table name or other types and return a normalized table definition.
26✔
1028
     */
26✔
1029
    parseTableName(
26✔
1030
        target: EntityMetadata | Table | View | TableForeignKey | string,
2,808,528✔
1031
    ): { database?: string; schema?: string; tableName: string } {
2,808,528✔
1032
        const driverDatabase = this.database
2,808,528✔
1033
        const driverSchema = this.schema
2,808,528✔
1034

2,808,528✔
1035
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
2,808,528✔
1036
            const parsed = this.parseTableName(target.name)
931,592✔
1037

931,592✔
1038
            return {
931,592✔
1039
                database: target.database || parsed.database || driverDatabase,
931,592!
1040
                schema: target.schema || parsed.schema || driverSchema,
931,592!
1041
                tableName: parsed.tableName,
931,592✔
1042
            }
931,592✔
1043
        }
931,592✔
1044

1,876,936✔
1045
        if (InstanceChecker.isTableForeignKey(target)) {
2,808,528✔
1046
            const parsed = this.parseTableName(target.referencedTableName)
16,600✔
1047

16,600✔
1048
            return {
16,600✔
1049
                database:
16,600✔
1050
                    target.referencedDatabase ||
16,600✔
1051
                    parsed.database ||
16,600!
1052
                    driverDatabase,
16,600✔
1053
                schema:
16,600✔
1054
                    target.referencedSchema || parsed.schema || driverSchema,
16,600!
1055
                tableName: parsed.tableName,
16,600✔
1056
            }
16,600✔
1057
        }
16,600✔
1058

1,860,336✔
1059
        if (InstanceChecker.isEntityMetadata(target)) {
2,808,528✔
1060
            // EntityMetadata tableName is never a path
861,824✔
1061

861,824✔
1062
            return {
861,824✔
1063
                database: target.database || driverDatabase,
861,824✔
1064
                schema: target.schema || driverSchema,
861,824✔
1065
                tableName: target.tableName,
861,824✔
1066
            }
861,824✔
1067
        }
861,824✔
1068

998,512✔
1069
        const parts = target.split(".")
998,512✔
1070

998,512✔
1071
        return {
998,512✔
1072
            database: driverDatabase,
998,512✔
1073
            schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
2,808,528✔
1074
            tableName: parts.length > 1 ? parts[1] : parts[0],
2,808,528✔
1075
        }
2,808,528✔
1076
    }
2,808,528✔
1077

26✔
1078
    /**
26✔
1079
     * Creates a database type from a given column metadata.
26✔
1080
     */
26✔
1081
    normalizeType(column: {
26✔
1082
        type?: ColumnType
186,982✔
1083
        length?: number | string
186,982✔
1084
        precision?: number | null
186,982✔
1085
        scale?: number
186,982✔
1086
        isArray?: boolean
186,982✔
1087
    }): string {
186,982✔
1088
        if (
186,982✔
1089
            column.type === Number ||
186,982✔
1090
            column.type === "int" ||
186,982✔
1091
            column.type === "int4"
98,510✔
1092
        ) {
186,982✔
1093
            return "integer"
90,772✔
1094
        } else if (column.type === String || column.type === "varchar") {
186,982✔
1095
            return "character varying"
66,548✔
1096
        } else if (column.type === Date || column.type === "timestamp") {
96,210✔
1097
            return "timestamp without time zone"
4,928✔
1098
        } else if (column.type === "timestamptz") {
29,662✔
1099
            return "timestamp with time zone"
188✔
1100
        } else if (column.type === "time") {
24,734✔
1101
            return "time without time zone"
792✔
1102
        } else if (column.type === "timetz") {
24,546✔
1103
            return "time with time zone"
128✔
1104
        } else if (column.type === Boolean || column.type === "bool") {
23,754✔
1105
            return "boolean"
3,980✔
1106
        } else if (column.type === "simple-array") {
23,626✔
1107
            return "text"
140✔
1108
        } else if (column.type === "simple-json") {
19,646✔
1109
            return "text"
200✔
1110
        } else if (column.type === "simple-enum") {
19,506✔
1111
            return "enum"
608✔
1112
        } else if (column.type === "int2") {
19,306✔
1113
            return "smallint"
152✔
1114
        } else if (column.type === "int8") {
18,698✔
1115
            return "bigint"
304✔
1116
        } else if (column.type === "decimal") {
18,546✔
1117
            return "numeric"
424✔
1118
        } else if (column.type === "float8" || column.type === "float") {
18,242✔
1119
            return "double precision"
196✔
1120
        } else if (column.type === "float4") {
17,818✔
1121
            return "real"
128✔
1122
        } else if (column.type === "char") {
17,622✔
1123
            return "character"
288✔
1124
        } else if (column.type === "varbit") {
17,494✔
1125
            return "bit varying"
128✔
1126
        } else {
17,206✔
1127
            return (column.type as string) || ""
17,078!
1128
        }
17,078✔
1129
    }
186,982✔
1130

26✔
1131
    /**
26✔
1132
     * Normalizes "default" value of the column.
26✔
1133
     */
26✔
1134
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
26✔
1135
        const defaultValue = columnMetadata.default
144,356✔
1136

144,356✔
1137
        if (defaultValue === null || defaultValue === undefined) {
144,356✔
1138
            return undefined
133,932✔
1139
        }
133,932✔
1140

10,424✔
1141
        if (columnMetadata.isArray && Array.isArray(defaultValue)) {
144,356✔
1142
            return `'{${defaultValue.map((val) => String(val)).join(",")}}'`
324✔
1143
        }
324✔
1144

10,100✔
1145
        if (
10,100✔
1146
            (columnMetadata.type === "enum" ||
10,100✔
1147
                columnMetadata.type === "simple-enum" ||
144,356✔
1148
                typeof defaultValue === "number" ||
144,356✔
1149
                typeof defaultValue === "string") &&
144,356✔
1150
            defaultValue !== undefined
6,268✔
1151
        ) {
144,356✔
1152
            return `'${defaultValue}'`
6,268✔
1153
        }
6,268✔
1154

3,832✔
1155
        if (typeof defaultValue === "boolean") {
144,356✔
1156
            return defaultValue ? "true" : "false"
72✔
1157
        }
72✔
1158

3,760✔
1159
        if (typeof defaultValue === "function") {
144,356✔
1160
            const value = defaultValue()
3,696✔
1161

3,696✔
1162
            return this.normalizeDatetimeFunction(value)
3,696✔
1163
        }
3,696✔
1164

64✔
1165
        if (typeof defaultValue === "object") {
64✔
1166
            return `'${JSON.stringify(defaultValue)}'`
64✔
1167
        }
64✔
1168

×
1169
        return `${defaultValue}`
×
1170
    }
×
1171

26✔
1172
    /**
26✔
1173
     * Compares "default" value of the column.
26✔
1174
     * Postgres sorts json values before it is saved, so in that case a deep comparison has to be performed to see if has changed.
26✔
1175
     */
26✔
1176
    private defaultEqual(
26✔
1177
        columnMetadata: ColumnMetadata,
66,708✔
1178
        tableColumn: TableColumn,
66,708✔
1179
    ): boolean {
66,708✔
1180
        if (
66,708✔
1181
            ["json", "jsonb"].includes(columnMetadata.type as string) &&
66,708✔
1182
            !["function", "undefined"].includes(typeof columnMetadata.default)
472✔
1183
        ) {
66,708✔
1184
            const tableColumnDefault =
124✔
1185
                typeof tableColumn.default === "string"
124✔
1186
                    ? JSON.parse(
124✔
1187
                          tableColumn.default.substring(
76✔
1188
                              1,
76✔
1189
                              tableColumn.default.length - 1,
76✔
1190
                          ),
76✔
1191
                      )
124✔
1192
                    : tableColumn.default
124✔
1193

124✔
1194
            return OrmUtils.deepCompare(
124✔
1195
                columnMetadata.default,
124✔
1196
                tableColumnDefault,
124✔
1197
            )
124✔
1198
        }
124✔
1199

66,584✔
1200
        const columnDefault = this.lowerDefaultValueIfNecessary(
66,584✔
1201
            this.normalizeDefault(columnMetadata),
66,584✔
1202
        )
66,584✔
1203
        return columnDefault === tableColumn.default
66,584✔
1204
    }
66,584✔
1205

26✔
1206
    /**
26✔
1207
     * Normalizes "isUnique" value of the column.
26✔
1208
     */
26✔
1209
    normalizeIsUnique(column: ColumnMetadata): boolean {
26✔
1210
        return column.entityMetadata.uniques.some(
163,560✔
1211
            (uq) => uq.columns.length === 1 && uq.columns[0] === column,
163,560✔
1212
        )
163,560✔
1213
    }
163,560✔
1214

26✔
1215
    /**
26✔
1216
     * Returns default column lengths, which is required on column creation.
26✔
1217
     */
26✔
1218
    getColumnLength(column: ColumnMetadata): string {
26✔
1219
        return column.length ? column.length.toString() : ""
78,172✔
1220
    }
78,172✔
1221

26✔
1222
    /**
26✔
1223
     * Creates column type definition including length, precision and scale
26✔
1224
     */
26✔
1225
    createFullType(column: TableColumn): string {
26✔
1226
        let type = column.type
64,952✔
1227

64,952✔
1228
        if (column.length) {
64,952✔
1229
            type += "(" + column.length + ")"
2,064✔
1230
        } else if (
64,952✔
1231
            column.precision !== null &&
62,888✔
1232
            column.precision !== undefined &&
62,888✔
1233
            column.scale !== null &&
62,888✔
1234
            column.scale !== undefined
408✔
1235
        ) {
62,888✔
1236
            type += "(" + column.precision + "," + column.scale + ")"
144✔
1237
        } else if (
62,888✔
1238
            column.precision !== null &&
62,744✔
1239
            column.precision !== undefined
62,744✔
1240
        ) {
62,744✔
1241
            type += "(" + column.precision + ")"
264✔
1242
        }
264✔
1243

64,952✔
1244
        if (column.type === "time without time zone") {
64,952✔
1245
            type =
316✔
1246
                "TIME" +
316✔
1247
                (column.precision !== null && column.precision !== undefined
316✔
1248
                    ? "(" + column.precision + ")"
316✔
1249
                    : "")
316✔
1250
        } else if (column.type === "time with time zone") {
64,952✔
1251
            type =
188✔
1252
                "TIME" +
188✔
1253
                (column.precision !== null && column.precision !== undefined
188✔
1254
                    ? "(" + column.precision + ")"
188✔
1255
                    : "") +
188✔
1256
                " WITH TIME ZONE"
188✔
1257
        } else if (column.type === "timestamp without time zone") {
64,636✔
1258
            type =
2,060✔
1259
                "TIMESTAMP" +
2,060✔
1260
                (column.precision !== null && column.precision !== undefined
2,060✔
1261
                    ? "(" + column.precision + ")"
2,060✔
1262
                    : "")
2,060✔
1263
        } else if (column.type === "timestamp with time zone") {
64,448✔
1264
            type =
228✔
1265
                "TIMESTAMP" +
228✔
1266
                (column.precision !== null && column.precision !== undefined
228✔
1267
                    ? "(" + column.precision + ")"
228✔
1268
                    : "") +
228✔
1269
                " WITH TIME ZONE"
228✔
1270
        } else if (this.spatialTypes.indexOf(column.type as ColumnType) >= 0) {
62,388✔
1271
            if (column.spatialFeatureType != null && column.srid != null) {
136✔
1272
                type = `${column.type}(${column.spatialFeatureType},${column.srid})`
40✔
1273
            } else if (column.spatialFeatureType != null) {
136✔
1274
                type = `${column.type}(${column.spatialFeatureType})`
32✔
1275
            } else {
96✔
1276
                type = column.type
64✔
1277
            }
64✔
1278
        } else if (column.type === "vector" || column.type === "halfvec") {
62,160✔
1279
            type =
160✔
1280
                column.type + (column.length ? "(" + column.length + ")" : "")
160✔
1281
        }
160✔
1282

64,952✔
1283
        if (column.isArray) type += " array"
64,952✔
1284

64,952✔
1285
        return type
64,952✔
1286
    }
64,952✔
1287

26✔
1288
    /**
26✔
1289
     * Obtains a new database connection to a master server.
26✔
1290
     * Used for replication.
26✔
1291
     * If replication is not setup then returns default connection's database connection.
26✔
1292
     */
26✔
1293
    async obtainMasterConnection(): Promise<[any, Function]> {
26✔
1294
        if (!this.master) {
58,344!
1295
            throw new TypeORMError("Driver not Connected")
×
1296
        }
×
1297

58,344✔
1298
        return new Promise((ok, fail) => {
58,344✔
1299
            this.master.connect((err: any, connection: any, release: any) => {
58,344✔
1300
                if (err) {
58,344!
1301
                    fail(err)
×
1302
                } else {
58,344✔
1303
                    ok([connection, release])
58,344✔
1304
                }
58,344✔
1305
            })
58,344✔
1306
        })
58,344✔
1307
    }
58,344✔
1308

26✔
1309
    /**
26✔
1310
     * Obtains a new database connection to a slave server.
26✔
1311
     * Used for replication.
26✔
1312
     * If replication is not setup then returns master (default) connection's database connection.
26✔
1313
     */
26✔
1314
    async obtainSlaveConnection(): Promise<[any, Function]> {
26✔
1315
        if (!this.slaves.length) {
12!
1316
            return this.obtainMasterConnection()
×
1317
        }
×
1318

12✔
1319
        const random = Math.floor(Math.random() * this.slaves.length)
12✔
1320

12✔
1321
        return new Promise((ok, fail) => {
12✔
1322
            this.slaves[random].connect(
12✔
1323
                (err: any, connection: any, release: any) => {
12✔
1324
                    if (err) {
12!
1325
                        fail(err)
×
1326
                    } else {
12✔
1327
                        ok([connection, release])
12✔
1328
                    }
12✔
1329
                },
12✔
1330
            )
12✔
1331
        })
12✔
1332
    }
12✔
1333

26✔
1334
    /**
26✔
1335
     * Creates generated map of values generated or returned by database after INSERT query.
26✔
1336
     *
26✔
1337
     * todo: slow. optimize Object.keys(), OrmUtils.mergeDeep and column.createValueMap parts
26✔
1338
     */
26✔
1339
    createGeneratedMap(metadata: EntityMetadata, insertResult: ObjectLiteral) {
26✔
1340
        if (!insertResult) return undefined
225,056✔
1341

160,752✔
1342
        return Object.keys(insertResult).reduce((map, key) => {
160,752✔
1343
            const column = metadata.findColumnWithDatabaseName(key)
164,440✔
1344
            if (column) {
164,440✔
1345
                OrmUtils.mergeDeep(
164,440✔
1346
                    map,
164,440✔
1347
                    column.createValueMap(insertResult[key]),
164,440✔
1348
                )
164,440✔
1349
                // OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column))); // TODO: probably should be like there, but fails on enums, fix later
164,440✔
1350
            }
164,440✔
1351
            return map
164,440✔
1352
        }, {} as ObjectLiteral)
160,752✔
1353
    }
160,752✔
1354

26✔
1355
    /**
26✔
1356
     * Differentiate columns of this table and columns from the given column metadatas columns
26✔
1357
     * and returns only changed.
26✔
1358
     */
26✔
1359
    findChangedColumns(
26✔
1360
        tableColumns: TableColumn[],
24,268✔
1361
        columnMetadatas: ColumnMetadata[],
24,268✔
1362
    ): ColumnMetadata[] {
24,268✔
1363
        return columnMetadatas.filter((columnMetadata) => {
24,268✔
1364
            const tableColumn = tableColumns.find(
81,776✔
1365
                (c) => c.name === columnMetadata.databaseName,
81,776✔
1366
            )
81,776✔
1367
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
81,776✔
1368

81,756✔
1369
            const isColumnChanged =
81,756✔
1370
                tableColumn.name !== columnMetadata.databaseName ||
81,756✔
1371
                tableColumn.type !== this.normalizeType(columnMetadata) ||
81,776✔
1372
                tableColumn.length !== columnMetadata.length ||
81,776✔
1373
                tableColumn.isArray !== columnMetadata.isArray ||
81,776✔
1374
                tableColumn.precision !== columnMetadata.precision ||
81,776✔
1375
                (columnMetadata.scale !== undefined &&
81,692✔
1376
                    tableColumn.scale !== columnMetadata.scale) ||
81,776✔
1377
                tableColumn.comment !==
81,692✔
1378
                    this.escapeComment(columnMetadata.comment) ||
81,776✔
1379
                (!tableColumn.isGenerated &&
81,660✔
1380
                    !this.defaultEqual(columnMetadata, tableColumn)) || // we included check for generated here, because generated columns already can have default values
81,776✔
1381
                tableColumn.isPrimary !== columnMetadata.isPrimary ||
81,776✔
1382
                tableColumn.isNullable !== columnMetadata.isNullable ||
81,776✔
1383
                tableColumn.isUnique !==
81,548✔
1384
                    this.normalizeIsUnique(columnMetadata) ||
81,776✔
1385
                tableColumn.enumName !== columnMetadata.enumName ||
81,776✔
1386
                (tableColumn.enum &&
81,532✔
1387
                    columnMetadata.enum &&
81,532✔
1388
                    !OrmUtils.isArraysEqual(
1,112✔
1389
                        tableColumn.enum,
1,112✔
1390
                        columnMetadata.enum.map((val) => val + ""),
1,112✔
1391
                    )) || // enums in postgres are always strings
81,776✔
1392
                tableColumn.isGenerated !== columnMetadata.isGenerated ||
81,776✔
1393
                (tableColumn.spatialFeatureType || "").toLowerCase() !==
81,504✔
1394
                    (columnMetadata.spatialFeatureType || "").toLowerCase() ||
81,776✔
1395
                tableColumn.srid !== columnMetadata.srid ||
81,776✔
1396
                tableColumn.generatedType !== columnMetadata.generatedType ||
81,776✔
1397
                (tableColumn.asExpression || "").trim() !==
81,504✔
1398
                    (columnMetadata.asExpression || "").trim() ||
81,776✔
1399
                tableColumn.collation !== columnMetadata.collation
81,504✔
1400

81,776✔
1401
            // DEBUG SECTION
81,776✔
1402
            // if (isColumnChanged) {
81,776✔
1403
            //     console.log("table:", columnMetadata.entityMetadata.tableName)
81,776✔
1404
            //     console.log(
81,776✔
1405
            //         "name:",
81,776✔
1406
            //         tableColumn.name,
81,776✔
1407
            //         columnMetadata.databaseName,
81,776✔
1408
            //     )
81,776✔
1409
            //     console.log(
81,776✔
1410
            //         "type:",
81,776✔
1411
            //         tableColumn.type,
81,776✔
1412
            //         this.normalizeType(columnMetadata),
81,776✔
1413
            //     )
81,776✔
1414
            //     console.log(
81,776✔
1415
            //         "length:",
81,776✔
1416
            //         tableColumn.length,
81,776✔
1417
            //         columnMetadata.length,
81,776✔
1418
            //     )
81,776✔
1419
            //     console.log(
81,776✔
1420
            //         "isArray:",
81,776✔
1421
            //         tableColumn.isArray,
81,776✔
1422
            //         columnMetadata.isArray,
81,776✔
1423
            //     )
81,776✔
1424
            //     console.log(
81,776✔
1425
            //         "precision:",
81,776✔
1426
            //         tableColumn.precision,
81,776✔
1427
            //         columnMetadata.precision,
81,776✔
1428
            //     )
81,776✔
1429
            //     console.log("scale:", tableColumn.scale, columnMetadata.scale)
81,776✔
1430
            //     console.log(
81,776✔
1431
            //         "comment:",
81,776✔
1432
            //         tableColumn.comment,
81,776✔
1433
            //         this.escapeComment(columnMetadata.comment),
81,776✔
1434
            //     )
81,776✔
1435
            //     console.log(
81,776✔
1436
            //         "enumName:",
81,776✔
1437
            //         tableColumn.enumName,
81,776✔
1438
            //         columnMetadata.enumName,
81,776✔
1439
            //     )
81,776✔
1440
            //     console.log(
81,776✔
1441
            //         "enum:",
81,776✔
1442
            //         tableColumn.enum &&
81,776✔
1443
            //             columnMetadata.enum &&
81,776✔
1444
            //             !OrmUtils.isArraysEqual(
81,776✔
1445
            //                 tableColumn.enum,
81,776✔
1446
            //                 columnMetadata.enum.map((val) => val + ""),
81,776✔
1447
            //             ),
81,776✔
1448
            //     )
81,776✔
1449
            //     console.log(
81,776✔
1450
            //         "isPrimary:",
81,776✔
1451
            //         tableColumn.isPrimary,
81,776✔
1452
            //         columnMetadata.isPrimary,
81,776✔
1453
            //     )
81,776✔
1454
            //     console.log(
81,776✔
1455
            //         "isNullable:",
81,776✔
1456
            //         tableColumn.isNullable,
81,776✔
1457
            //         columnMetadata.isNullable,
81,776✔
1458
            //     )
81,776✔
1459
            //     console.log(
81,776✔
1460
            //         "isUnique:",
81,776✔
1461
            //         tableColumn.isUnique,
81,776✔
1462
            //         this.normalizeIsUnique(columnMetadata),
81,776✔
1463
            //     )
81,776✔
1464
            //     console.log(
81,776✔
1465
            //         "isGenerated:",
81,776✔
1466
            //         tableColumn.isGenerated,
81,776✔
1467
            //         columnMetadata.isGenerated,
81,776✔
1468
            //     )
81,776✔
1469
            //     console.log(
81,776✔
1470
            //         "generatedType:",
81,776✔
1471
            //         tableColumn.generatedType,
81,776✔
1472
            //         columnMetadata.generatedType,
81,776✔
1473
            //     )
81,776✔
1474
            //     console.log(
81,776✔
1475
            //         "asExpression:",
81,776✔
1476
            //         (tableColumn.asExpression || "").trim(),
81,776✔
1477
            //         (columnMetadata.asExpression || "").trim(),
81,776✔
1478
            //     )
81,776✔
1479
            //     console.log(
81,776✔
1480
            //         "collation:",
81,776✔
1481
            //         tableColumn.collation,
81,776✔
1482
            //         columnMetadata.collation,
81,776✔
1483
            //     )
81,776✔
1484
            //     console.log(
81,776✔
1485
            //         "isGenerated 2:",
81,776✔
1486
            //         !tableColumn.isGenerated &&
81,776✔
1487
            //             this.lowerDefaultValueIfNecessary(
81,776✔
1488
            //                 this.normalizeDefault(columnMetadata),
81,776✔
1489
            //             ) !== tableColumn.default,
81,776✔
1490
            //     )
81,776✔
1491
            //     console.log(
81,776✔
1492
            //         "spatialFeatureType:",
81,776✔
1493
            //         (tableColumn.spatialFeatureType || "").toLowerCase(),
81,776✔
1494
            //         (columnMetadata.spatialFeatureType || "").toLowerCase(),
81,776✔
1495
            //     )
81,776✔
1496
            //     console.log("srid", tableColumn.srid, columnMetadata.srid)
81,776✔
1497
            //     console.log("==========================================")
81,776✔
1498
            // }
81,776✔
1499

81,776✔
1500
            return isColumnChanged
81,776✔
1501
        })
24,268✔
1502
    }
24,268✔
1503

26✔
1504
    private lowerDefaultValueIfNecessary(value: string | undefined) {
26✔
1505
        // Postgres saves function calls in default value as lowercase #2733
66,584✔
1506
        if (!value) {
66,584✔
1507
            return value
61,204✔
1508
        }
61,204✔
1509
        return value
5,380✔
1510
            .split(`'`)
5,380✔
1511
            .map((v, i) => {
5,380✔
1512
                return i % 2 === 1 ? v : v.toLowerCase()
13,116✔
1513
            })
5,380✔
1514
            .join(`'`)
5,380✔
1515
    }
5,380✔
1516

26✔
1517
    /**
26✔
1518
     * Returns true if driver supports RETURNING / OUTPUT statement.
26✔
1519
     */
26✔
1520
    isReturningSqlSupported(): boolean {
26✔
1521
        return true
86,484✔
1522
    }
86,484✔
1523

26✔
1524
    /**
26✔
1525
     * Returns true if driver supports uuid values generation on its own.
26✔
1526
     */
26✔
1527
    isUUIDGenerationSupported(): boolean {
26✔
1528
        return true
568✔
1529
    }
568✔
1530

26✔
1531
    /**
26✔
1532
     * Returns true if driver supports fulltext indices.
26✔
1533
     */
26✔
1534
    isFullTextColumnTypeSupported(): boolean {
26✔
1535
        return false
204✔
1536
    }
204✔
1537

26✔
1538
    get uuidGenerator(): string {
26✔
1539
        return this.options.uuidExtension === "pgcrypto"
740✔
1540
            ? "gen_random_uuid()"
740✔
1541
            : "uuid_generate_v4()"
740✔
1542
    }
740✔
1543

26✔
1544
    /**
26✔
1545
     * Creates an escaped parameter.
26✔
1546
     */
26✔
1547
    createParameter(parameterName: string, index: number): string {
26✔
1548
        return this.parametersPrefix + (index + 1)
1,036,240✔
1549
    }
1,036,240✔
1550

26✔
1551
    compareTableIndexTypes = (indexA: IndexMetadata, indexB: TableIndex) => {
26✔
1552
        const normalizedA = indexA.isSpatial ? "gist" : (indexA.type ?? "btree")
204✔
1553
        const normalizedB = indexB.isSpatial ? "gist" : (indexB.type ?? "btree")
204!
1554

204✔
1555
        return normalizedA.toLowerCase() === normalizedB.toLowerCase()
204✔
1556
    }
204✔
1557

26✔
1558
    // -------------------------------------------------------------------------
26✔
1559
    // Public Methods
26✔
1560
    // -------------------------------------------------------------------------
26✔
1561

26✔
1562
    /**
26✔
1563
     * Loads postgres query stream package.
26✔
1564
     */
26✔
1565
    loadStreamDependency() {
26✔
1566
        try {
8✔
1567
            return PlatformTools.load("pg-query-stream")
8✔
1568
        } catch {
8!
1569
            // todo: better error for browser env
×
1570
            throw new TypeORMError(
×
1571
                `To use streams you should install pg-query-stream package. Please run "npm i pg-query-stream".`,
×
1572
            )
×
1573
        }
×
1574
    }
8✔
1575

26✔
1576
    // -------------------------------------------------------------------------
26✔
1577
    // Protected Methods
26✔
1578
    // -------------------------------------------------------------------------
26✔
1579

26✔
1580
    /**
26✔
1581
     * If driver dependency is not given explicitly, then try to load it via "require".
26✔
1582
     */
26✔
1583
    protected loadDependencies(): void {
26✔
1584
        try {
2,716✔
1585
            const postgres = this.options.driver || PlatformTools.load("pg")
2,716✔
1586
            this.postgres = postgres
2,716✔
1587
            try {
2,716✔
1588
                const pgNative =
2,716✔
1589
                    this.options.nativeDriver || PlatformTools.load("pg-native")
2,716✔
1590
                if (pgNative && this.postgres.native)
2,716!
1591
                    this.postgres = this.postgres.native
2,716!
1592
            } catch (e) {}
2,716✔
1593
        } catch (e) {
2,716!
1594
            // todo: better error for browser env
×
1595
            throw new DriverPackageNotInstalledError("Postgres", "pg")
×
1596
        }
×
1597
    }
2,716✔
1598

26✔
1599
    /**
26✔
1600
     * Creates a new connection pool for a given database credentials.
26✔
1601
     */
26✔
1602
    protected async createPool(
26✔
1603
        options: PostgresConnectionOptions,
2,748✔
1604
        credentials: PostgresConnectionCredentialsOptions,
2,748✔
1605
    ): Promise<any> {
2,748✔
1606
        const { logger } = this.connection
2,748✔
1607
        credentials = Object.assign({}, credentials)
2,748✔
1608

2,748✔
1609
        // build connection options for the driver
2,748✔
1610
        // See: https://github.com/brianc/node-postgres/tree/master/packages/pg-pool#create
2,748✔
1611
        const connectionOptions = Object.assign(
2,748✔
1612
            {},
2,748✔
1613
            {
2,748✔
1614
                connectionString: credentials.url,
2,748✔
1615
                host: credentials.host,
2,748✔
1616
                user: credentials.username,
2,748✔
1617
                password: credentials.password,
2,748✔
1618
                database: credentials.database,
2,748✔
1619
                port: credentials.port,
2,748✔
1620
                ssl: credentials.ssl,
2,748✔
1621
                connectionTimeoutMillis: options.connectTimeoutMS,
2,748✔
1622
                application_name:
2,748✔
1623
                    options.applicationName ?? credentials.applicationName,
2,748✔
1624
                max: options.poolSize,
2,748✔
1625
            },
2,748✔
1626
            options.extra || {},
2,748✔
1627
        )
2,748✔
1628

2,748✔
1629
        if (options.parseInt8 !== undefined) {
2,748✔
1630
            if (
4✔
1631
                this.postgres.defaults &&
4✔
1632
                Object.getOwnPropertyDescriptor(
4✔
1633
                    this.postgres.defaults,
4✔
1634
                    "parseInt8",
4✔
1635
                )?.set
4✔
1636
            ) {
4✔
1637
                this.postgres.defaults.parseInt8 = options.parseInt8
4✔
1638
            } else {
4!
1639
                logger.log(
×
1640
                    "warn",
×
1641
                    "Attempted to set parseInt8 option, but the postgres driver does not support setting defaults.parseInt8. This option will be ignored.",
×
1642
                )
×
1643
            }
×
1644
        }
4✔
1645

2,748✔
1646
        // create a connection pool
2,748✔
1647
        const pool = new this.postgres.Pool(connectionOptions)
2,748✔
1648

2,748✔
1649
        const poolErrorHandler =
2,748✔
1650
            options.poolErrorHandler ||
2,748✔
1651
            ((error: any) =>
2,748!
1652
                logger.log("warn", `Postgres pool raised an error. ${error}`))
2,748✔
1653

2,748✔
1654
        /*
2,748✔
1655
          Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
2,748✔
1656
          cause the hosting app to crash.
2,748✔
1657
         */
2,748✔
1658
        pool.on("error", poolErrorHandler)
2,748✔
1659

2,748✔
1660
        return new Promise((ok, fail) => {
2,748✔
1661
            pool.connect((err: any, connection: any, release: Function) => {
2,748✔
1662
                if (err) return fail(err)
2,748!
1663

2,748✔
1664
                if (options.logNotifications) {
2,748✔
1665
                    connection.on("notice", (msg: any) => {
4✔
1666
                        if (msg) {
12✔
1667
                            this.connection.logger.log("info", msg.message)
12✔
1668
                        }
12✔
1669
                    })
4✔
1670
                    connection.on("notification", (msg: any) => {
4✔
1671
                        if (msg) {
4✔
1672
                            this.connection.logger.log(
4✔
1673
                                "info",
4✔
1674
                                `Received NOTIFY on channel ${msg.channel}: ${msg.payload}.`,
4✔
1675
                            )
4✔
1676
                        }
4✔
1677
                    })
4✔
1678
                }
4✔
1679
                release()
2,748✔
1680
                ok(pool)
2,748✔
1681
            })
2,748✔
1682
        })
2,748✔
1683
    }
2,748✔
1684

26✔
1685
    /**
26✔
1686
     * Closes connection pool.
26✔
1687
     */
26✔
1688
    protected async closePool(pool: any): Promise<void> {
26✔
1689
        while (this.connectedQueryRunners.length) {
2,736✔
1690
            await this.connectedQueryRunners[0].release()
64✔
1691
        }
64✔
1692

2,736✔
1693
        return new Promise<void>((ok, fail) => {
2,736✔
1694
            pool.end((err: any) => (err ? fail(err) : ok()))
2,736!
1695
        })
2,736✔
1696
    }
2,736✔
1697

26✔
1698
    /**
26✔
1699
     * Executes given query.
26✔
1700
     */
26✔
1701
    protected executeQuery(connection: any, query: string) {
26✔
1702
        this.connection.logger.logQuery(query)
340✔
1703

340✔
1704
        return new Promise((ok, fail) => {
340✔
1705
            connection.query(query, (err: any, result: any) =>
340✔
1706
                err ? fail(err) : ok(result),
340!
1707
            )
340✔
1708
        })
340✔
1709
    }
340✔
1710

26✔
1711
    /**
26✔
1712
     * If parameter is a datetime function, e.g. "CURRENT_TIMESTAMP", normalizes it.
26✔
1713
     * Otherwise returns original input.
26✔
1714
     */
26✔
1715
    protected normalizeDatetimeFunction(value: string) {
26✔
1716
        // check if input is datetime function
3,696✔
1717
        const upperCaseValue = value.toUpperCase()
3,696✔
1718
        const isDatetimeFunction =
3,696✔
1719
            upperCaseValue.indexOf("CURRENT_TIMESTAMP") !== -1 ||
3,696✔
1720
            upperCaseValue.indexOf("CURRENT_DATE") !== -1 ||
3,696✔
1721
            upperCaseValue.indexOf("CURRENT_TIME") !== -1 ||
3,696✔
1722
            upperCaseValue.indexOf("LOCALTIMESTAMP") !== -1 ||
3,696✔
1723
            upperCaseValue.indexOf("LOCALTIME") !== -1
2,988✔
1724

3,696✔
1725
        if (isDatetimeFunction) {
3,696✔
1726
            // extract precision, e.g. "(3)"
888✔
1727
            const precision = value.match(/\(\d+\)/)
888✔
1728

888✔
1729
            if (upperCaseValue.indexOf("CURRENT_TIMESTAMP") !== -1) {
888✔
1730
                return precision
228✔
1731
                    ? `('now'::text)::timestamp${precision[0]} with time zone`
228✔
1732
                    : "now()"
228✔
1733
            } else if (upperCaseValue === "CURRENT_DATE") {
888✔
1734
                return "('now'::text)::date"
120✔
1735
            } else if (upperCaseValue.indexOf("CURRENT_TIME") !== -1) {
660✔
1736
                return precision
180✔
1737
                    ? `('now'::text)::time${precision[0]} with time zone`
180✔
1738
                    : "('now'::text)::time with time zone"
180✔
1739
            } else if (upperCaseValue.indexOf("LOCALTIMESTAMP") !== -1) {
540✔
1740
                return precision
180✔
1741
                    ? `('now'::text)::timestamp${precision[0]} without time zone`
180✔
1742
                    : "('now'::text)::timestamp without time zone"
180✔
1743
            } else if (upperCaseValue.indexOf("LOCALTIME") !== -1) {
180✔
1744
                return precision
180✔
1745
                    ? `('now'::text)::time${precision[0]} without time zone`
180✔
1746
                    : "('now'::text)::time without time zone"
180✔
1747
            }
180✔
1748
        }
888✔
1749

2,808✔
1750
        return value
2,808✔
1751
    }
2,808✔
1752

26✔
1753
    /**
26✔
1754
     * Escapes a given comment.
26✔
1755
     */
26✔
1756
    protected escapeComment(comment?: string) {
26✔
1757
        if (!comment) return comment
81,692✔
1758

400✔
1759
        comment = comment.replace(/\u0000/g, "") // Null bytes aren't allowed in comments
400✔
1760

400✔
1761
        return comment
400✔
1762
    }
400✔
1763
}
26✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc