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

typeorm / typeorm / 15089093306

17 May 2025 09:03PM UTC coverage: 50.109% (-26.2%) from 76.346%
15089093306

Pull #11437

github

naorpeled
add comment about vector <#>
Pull Request #11437: feat(postgres): support vector data type

5836 of 12767 branches covered (45.71%)

Branch coverage included in aggregate %.

16 of 17 new or added lines in 4 files covered. (94.12%)

6283 existing lines in 64 files now uncovered.

12600 of 24025 relevant lines covered (52.45%)

28708.0 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

268
    /**
269
     * The prefix used for the parameters
270
     */
271
    parametersPrefix: string = "$"
723✔
272

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

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

293
    isGeneratedColumnsSupported: boolean = false
723✔
294

295
    cteCapabilities: CteCapabilities = {
723✔
296
        enabled: true,
297
        writable: true,
298
        requiresRecursiveHint: true,
299
        materializedHint: true,
300
    }
301

302
    // -------------------------------------------------------------------------
303
    // Constructor
304
    // -------------------------------------------------------------------------
305

306
    constructor(connection?: DataSource) {
307
        if (!connection) {
723✔
308
            return
66✔
309
        }
310

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

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

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

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

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

362
        const queryRunner = this.createQueryRunner("master")
657✔
363

364
        this.version = await queryRunner.getVersion()
657✔
365

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

370
        if (!this.searchSchema) {
657✔
371
            this.searchSchema = await queryRunner.getCurrentSchema()
657✔
372
        }
373

374
        await queryRunner.release()
657✔
375

376
        if (!this.schema) {
657✔
377
            this.schema = this.searchSchema
647✔
378
        }
379
    }
380

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

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

395
        this.isGeneratedColumnsSupported = VersionUtils.isGreaterOrEqual(
655✔
396
            this.version,
397
            "12.0",
398
        )
399

400
        await release()
655✔
401
    }
402

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

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

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

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

566
        return {
655✔
567
            hasUuidColumns,
568
            hasCitextColumns,
569
            hasHstoreColumns,
570
            hasCubeColumns,
571
            hasGeometryColumns,
572
            hasLtreeColumns,
573
            hasExclusionConstraints,
574
            hasExtensions:
575
                hasUuidColumns ||
4,353✔
576
                hasCitextColumns ||
577
                hasHstoreColumns ||
578
                hasGeometryColumns ||
579
                hasCubeColumns ||
580
                hasLtreeColumns ||
581
                hasExclusionConstraints,
582
        }
583
    }
584

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

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

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

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

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

622
        if (value === null || value === undefined) return value
191,210✔
623

624
        if (columnMetadata.type === Boolean) {
187,123✔
625
            return value === true ? 1 : 0
2,004✔
626
        } else if (columnMetadata.type === "date") {
185,119✔
627
            return DateUtils.mixedDateToDateString(value)
6✔
628
        } else if (columnMetadata.type === "time") {
185,113✔
629
            return DateUtils.mixedDateToTimeString(value)
5✔
630
        } else if (
185,108✔
631
            columnMetadata.type === "datetime" ||
915,389✔
632
            columnMetadata.type === Date ||
633
            columnMetadata.type === "timestamp" ||
634
            columnMetadata.type === "timestamp with time zone" ||
635
            columnMetadata.type === "timestamp without time zone"
636
        ) {
637
            return DateUtils.mixedDateToDate(value)
10,060✔
638
        } else if (
175,048✔
639
            ["json", "jsonb", ...this.spatialTypes].indexOf(
640
                columnMetadata.type,
641
            ) >= 0
642
        ) {
643
            return JSON.stringify(value)
10,053✔
644
        } else if (columnMetadata.type === "vector") {
164,995✔
645
            if (Array.isArray(value)) {
20!
646
                return `[${value.join(",")}]`
20✔
647
            } else {
NEW
648
                return value
×
649
            }
650
        } else if (columnMetadata.type === "hstore") {
164,975✔
651
            if (typeof value === "string") {
6✔
652
                return value
2✔
653
            } else {
654
                // https://www.postgresql.org/docs/9.0/hstore.html
655
                const quoteString = (value: unknown) => {
4✔
656
                    // If a string to be quoted is `null` or `undefined`, we return a literal unquoted NULL.
657
                    // This way, NULL values can be stored in the hstore object.
658
                    if (value === null || typeof value === "undefined") {
28✔
659
                        return "NULL"
1✔
660
                    }
661
                    // Convert non-null values to string since HStore only stores strings anyway.
662
                    // To include a double quote or a backslash in a key or value, escape it with a backslash.
663
                    return `"${`${value}`.replace(/(?=["\\])/g, "\\")}"`
27✔
664
                }
665
                return Object.keys(value)
4✔
666
                    .map(
667
                        (key) =>
668
                            quoteString(key) + "=>" + quoteString(value[key]),
14✔
669
                    )
670
                    .join(",")
671
            }
672
        } else if (columnMetadata.type === "simple-array") {
164,969✔
673
            return DateUtils.simpleArrayToString(value)
2✔
674
        } else if (columnMetadata.type === "simple-json") {
164,967✔
675
            return DateUtils.simpleJsonToString(value)
5✔
676
        } else if (columnMetadata.type === "cube") {
164,962✔
677
            if (columnMetadata.isArray) {
10✔
678
                return `{${value
1✔
679
                    .map((cube: number[]) => `"(${cube.join(",")})"`)
2✔
680
                    .join(",")}}`
681
            }
682
            return `(${value.join(",")})`
9✔
683
        } else if (columnMetadata.type === "ltree") {
164,952✔
684
            return value
8✔
685
                .split(".")
686
                .filter(Boolean)
687
                .join(".")
688
                .replace(/[\s]+/g, "_")
689
        } else if (
164,944✔
690
            (columnMetadata.type === "enum" ||
329,904✔
691
                columnMetadata.type === "simple-enum") &&
692
            !columnMetadata.isArray
693
        ) {
694
            return "" + value
27✔
695
        }
696

697
        return value
164,917✔
698
    }
699

700
    /**
701
     * Prepares given value to a value to be persisted, based on its column type or metadata.
702
     */
703
    prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
704
        if (value === null || value === undefined)
100,694✔
705
            return columnMetadata.transformer
4,019✔
706
                ? ApplyValueTransformers.transformFrom(
707
                      columnMetadata.transformer,
708
                      value,
709
                  )
710
                : value
711

712
        if (columnMetadata.type === Boolean) {
96,675✔
713
            value = value ? true : false
706✔
714
        } else if (
95,969✔
715
            columnMetadata.type === "datetime" ||
479,091✔
716
            columnMetadata.type === Date ||
717
            columnMetadata.type === "timestamp" ||
718
            columnMetadata.type === "timestamp with time zone" ||
719
            columnMetadata.type === "timestamp without time zone"
720
        ) {
721
            value = DateUtils.normalizeHydratedDate(value)
358✔
722
        } else if (columnMetadata.type === "date") {
95,611✔
723
            value = DateUtils.mixedDateToDateString(value)
12✔
724
        } else if (columnMetadata.type === "time") {
95,599✔
725
            value = DateUtils.mixedTimeToString(value)
5✔
726
        } else if (columnMetadata.type === "vector") {
95,594✔
727
            if (
6✔
728
                typeof value === "string" &&
18✔
729
                value.startsWith("[") &&
730
                value.endsWith("]")
731
            ) {
732
                if (value === "[]") return []
6!
733
                return value.slice(1, -1).split(",").map(Number)
6✔
734
            }
735
        } else if (columnMetadata.type === "hstore") {
95,588✔
736
            if (columnMetadata.hstoreType === "object") {
6✔
737
                const unescapeString = (str: string) =>
4✔
738
                    str.replace(/\\./g, (m) => m[1])
27✔
739
                const regexp =
740
                    /"([^"\\]*(?:\\.[^"\\]*)*)"=>(?:(NULL)|"([^"\\]*(?:\\.[^"\\]*)*)")(?:,|$)/g
4✔
741
                const object: ObjectLiteral = {}
4✔
742
                ;`${value}`.replace(
4✔
743
                    regexp,
744
                    (_, key, nullValue, stringValue) => {
745
                        object[unescapeString(key)] = nullValue
14✔
746
                            ? null
747
                            : unescapeString(stringValue)
748
                        return ""
14✔
749
                    },
750
                )
751
                value = object
4✔
752
            }
753
        } else if (columnMetadata.type === "simple-array") {
95,582✔
754
            value = DateUtils.stringToSimpleArray(value)
2✔
755
        } else if (columnMetadata.type === "simple-json") {
95,580✔
756
            value = DateUtils.stringToSimpleJson(value)
5✔
757
        } else if (columnMetadata.type === "cube") {
95,575✔
758
            value = value.replace(/[()\s]+/g, "") // remove whitespace
9✔
759
            if (columnMetadata.isArray) {
9✔
760
                /**
761
                 * Strips these groups from `{"1,2,3","",NULL}`:
762
                 * 1. ["1,2,3", undefined]  <- cube of arity 3
763
                 * 2. ["", undefined]         <- cube of arity 0
764
                 * 3. [undefined, "NULL"]     <- NULL
765
                 */
766
                const regexp = /(?:"((?:[\d\s.,])*)")|(?:(NULL))/g
1✔
767
                const unparsedArrayString = value
1✔
768

769
                value = []
1✔
770
                let cube: RegExpExecArray | null = null
1✔
771
                // Iterate through all regexp matches for cubes/null in array
772
                while ((cube = regexp.exec(unparsedArrayString)) !== null) {
1✔
773
                    if (cube[1] !== undefined) {
2!
774
                        value.push(
2✔
775
                            cube[1].split(",").filter(Boolean).map(Number),
776
                        )
777
                    } else {
778
                        value.push(undefined)
×
779
                    }
780
                }
781
            } else {
782
                value = value.split(",").filter(Boolean).map(Number)
8✔
783
            }
784
        } else if (
95,566✔
785
            columnMetadata.type === "enum" ||
191,013✔
786
            columnMetadata.type === "simple-enum"
787
        ) {
788
            if (columnMetadata.isArray) {
175✔
789
                if (value === "{}") return []
103✔
790

791
                // manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
792
                value = (value as string)
84✔
793
                    .slice(1, -1)
794
                    .split(",")
795
                    .map((val) => {
796
                        // replace double quotes from the beginning and from the end
797
                        if (val.startsWith(`"`) && val.endsWith(`"`))
138✔
798
                            val = val.slice(1, -1)
20✔
799
                        // replace escaped backslash and double quotes
800
                        return val.replace(/\\(\\|")/g, "$1")
138✔
801
                    })
802

803
                // convert to number if that exists in possible enum options
804
                value = value.map((val: string) => {
84✔
805
                    return !isNaN(+val) &&
138✔
806
                        columnMetadata.enum!.indexOf(parseInt(val)) >= 0
807
                        ? parseInt(val)
808
                        : val
809
                })
810
            } else {
811
                // convert to number if that exists in possible enum options
812
                value =
72✔
813
                    !isNaN(+value) &&
172✔
814
                    columnMetadata.enum!.indexOf(parseInt(value)) >= 0
815
                        ? parseInt(value)
816
                        : value
817
            }
818
        } else if (columnMetadata.type === Number) {
95,391✔
819
            // convert to number if number
820
            value = !isNaN(+value) ? parseInt(value) : value
49,468!
821
        }
822

823
        if (columnMetadata.transformer)
96,650✔
824
            value = ApplyValueTransformers.transformFrom(
83✔
825
                columnMetadata.transformer,
826
                value,
827
            )
828
        return value
96,650✔
829
    }
830

831
    /**
832
     * Replaces parameters in the given sql with special escaping character
833
     * and an array of parameter names to be passed to a query.
834
     */
835
    escapeQueryWithParameters(
836
        sql: string,
837
        parameters: ObjectLiteral,
838
        nativeParameters: ObjectLiteral,
839
    ): [string, any[]] {
840
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
16,571✔
841
            (key) => nativeParameters[key],
×
842
        )
843
        if (!parameters || !Object.keys(parameters).length)
16,571✔
844
            return [sql, escapedParameters]
799✔
845

846
        const parameterIndexMap = new Map<string, number>()
15,772✔
847
        sql = sql.replace(
15,772✔
848
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
849
            (full, isArray: string, key: string): string => {
850
                if (!parameters.hasOwnProperty(key)) {
258,026✔
851
                    return full
44✔
852
                }
853

854
                if (parameterIndexMap.has(key)) {
257,982✔
855
                    return this.parametersPrefix + parameterIndexMap.get(key)
14✔
856
                }
857

858
                const value: any = parameters[key]
257,968✔
859

860
                if (isArray) {
257,968✔
861
                    return value
153✔
862
                        .map((v: any) => {
863
                            escapedParameters.push(v)
204✔
864
                            return this.createParameter(
204✔
865
                                key,
866
                                escapedParameters.length - 1,
867
                            )
868
                        })
869
                        .join(", ")
870
                }
871

872
                if (typeof value === "function") {
257,815!
873
                    return value()
×
874
                }
875

876
                escapedParameters.push(value)
257,815✔
877
                parameterIndexMap.set(key, escapedParameters.length)
257,815✔
878
                return this.createParameter(key, escapedParameters.length - 1)
257,815✔
879
            },
880
        ) // todo: make replace only in value statements, otherwise problems
881
        return [sql, escapedParameters]
15,772✔
882
    }
883

884
    /**
885
     * Escapes a column name.
886
     */
887
    escape(columnName: string): string {
888
        return '"' + columnName + '"'
4,119,697✔
889
    }
890

891
    /**
892
     * Build full table name with schema name and table name.
893
     * E.g. myDB.mySchema.myTable
894
     */
895
    buildTableName(tableName: string, schema?: string): string {
896
        const tablePath = [tableName]
399,623✔
897

898
        if (schema) {
399,623✔
899
            tablePath.unshift(schema)
389,729✔
900
        }
901

902
        return tablePath.join(".")
399,623✔
903
    }
904

905
    /**
906
     * Parse a target table name or other types and return a normalized table definition.
907
     */
908
    parseTableName(
909
        target: EntityMetadata | Table | View | TableForeignKey | string,
910
    ): { database?: string; schema?: string; tableName: string } {
911
        const driverDatabase = this.database
646,361✔
912
        const driverSchema = this.schema
646,361✔
913

914
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
646,361✔
915
            const parsed = this.parseTableName(target.name)
214,306✔
916

917
            return {
214,306✔
918
                database: target.database || parsed.database || driverDatabase,
407,502!
919
                schema: target.schema || parsed.schema || driverSchema,
406,533!
920
                tableName: parsed.tableName,
921
            }
922
        }
923

924
        if (InstanceChecker.isTableForeignKey(target)) {
432,055✔
925
            const parsed = this.parseTableName(target.referencedTableName)
3,944✔
926

927
            return {
3,944✔
928
                database:
929
                    target.referencedDatabase ||
7,859!
930
                    parsed.database ||
931
                    driverDatabase,
932
                schema:
933
                    target.referencedSchema || parsed.schema || driverSchema,
7,581!
934
                tableName: parsed.tableName,
935
            }
936
        }
937

938
        if (InstanceChecker.isEntityMetadata(target)) {
428,111✔
939
            // EntityMetadata tableName is never a path
940

941
            return {
197,979✔
942
                database: target.database || driverDatabase,
395,947✔
943
                schema: target.schema || driverSchema,
394,625✔
944
                tableName: target.tableName,
945
            }
946
        }
947

948
        const parts = target.split(".")
230,132✔
949

950
        return {
230,132✔
951
            database: driverDatabase,
952
            schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
679,376✔
953
            tableName: parts.length > 1 ? parts[1] : parts[0],
230,132✔
954
        }
955
    }
956

957
    /**
958
     * Creates a database type from a given column metadata.
959
     */
960
    normalizeType(column: {
961
        type?: ColumnType
962
        length?: number | string
963
        precision?: number | null
964
        scale?: number
965
        isArray?: boolean
966
    }): string {
967
        if (
42,268✔
968
            column.type === Number ||
85,354✔
969
            column.type === "int" ||
970
            column.type === "int4"
971
        ) {
972
            return "integer"
21,401✔
973
        } else if (column.type === String || column.type === "varchar") {
20,867✔
974
            return "character varying"
15,598✔
975
        } else if (column.type === Date || column.type === "timestamp") {
5,269✔
976
            return "timestamp without time zone"
1,156✔
977
        } else if (column.type === "timestamptz") {
4,113✔
978
            return "timestamp with time zone"
22✔
979
        } else if (column.type === "time") {
4,091✔
980
            return "time without time zone"
148✔
981
        } else if (column.type === "timetz") {
3,943✔
982
            return "time with time zone"
7✔
983
        } else if (column.type === Boolean || column.type === "bool") {
3,936✔
984
            return "boolean"
921✔
985
        } else if (column.type === "simple-array") {
3,015✔
986
            return "text"
10✔
987
        } else if (column.type === "simple-json") {
3,005✔
988
            return "text"
25✔
989
        } else if (column.type === "simple-enum") {
2,980✔
990
            return "enum"
111✔
991
        } else if (column.type === "int2") {
2,869✔
992
            return "smallint"
13✔
993
        } else if (column.type === "int8") {
2,856✔
994
            return "bigint"
45✔
995
        } else if (column.type === "decimal") {
2,811✔
996
            return "numeric"
56✔
997
        } else if (column.type === "float8" || column.type === "float") {
2,755✔
998
            return "double precision"
29✔
999
        } else if (column.type === "float4") {
2,726✔
1000
            return "real"
7✔
1001
        } else if (column.type === "char") {
2,719✔
1002
            return "character"
22✔
1003
        } else if (column.type === "varbit") {
2,697✔
1004
            return "bit varying"
7✔
1005
        } else {
1006
            return (column.type as string) || ""
2,690!
1007
        }
1008
    }
1009

1010
    /**
1011
     * Normalizes "default" value of the column.
1012
     */
1013
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
1014
        const defaultValue = columnMetadata.default
32,307✔
1015

1016
        if (defaultValue === null || defaultValue === undefined) {
32,307✔
1017
            return undefined
29,715✔
1018
        }
1019

1020
        if (columnMetadata.isArray && Array.isArray(defaultValue)) {
2,592✔
1021
            return `'{${defaultValue
81✔
1022
                .map((val: string) => `${val}`)
98✔
1023
                .join(",")}}'`
1024
        }
1025

1026
        if (
2,511✔
1027
            (columnMetadata.type === "enum" ||
10,484✔
1028
                columnMetadata.type === "simple-enum" ||
1029
                typeof defaultValue === "number" ||
1030
                typeof defaultValue === "string") &&
1031
            defaultValue !== undefined
1032
        ) {
1033
            return `'${defaultValue}'`
1,553✔
1034
        }
1035

1036
        if (typeof defaultValue === "boolean") {
958✔
1037
            return defaultValue ? "true" : "false"
18✔
1038
        }
1039

1040
        if (typeof defaultValue === "function") {
940✔
1041
            const value = defaultValue()
924✔
1042

1043
            return this.normalizeDatetimeFunction(value)
924✔
1044
        }
1045

1046
        if (typeof defaultValue === "object") {
16✔
1047
            return `'${JSON.stringify(defaultValue)}'`
16✔
1048
        }
1049

1050
        return `${defaultValue}`
×
1051
    }
1052

1053
    /**
1054
     * Compares "default" value of the column.
1055
     * Postgres sorts json values before it is saved, so in that case a deep comparison has to be performed to see if has changed.
1056
     */
1057
    private defaultEqual(
1058
        columnMetadata: ColumnMetadata,
1059
        tableColumn: TableColumn,
1060
    ): boolean {
1061
        if (
14,913✔
1062
            ["json", "jsonb"].includes(columnMetadata.type as string) &&
15,007✔
1063
            !["function", "undefined"].includes(typeof columnMetadata.default)
1064
        ) {
1065
            const tableColumnDefault =
1066
                typeof tableColumn.default === "string"
31✔
1067
                    ? JSON.parse(
1068
                          tableColumn.default.substring(
1069
                              1,
1070
                              tableColumn.default.length - 1,
1071
                          ),
1072
                      )
1073
                    : tableColumn.default
1074

1075
            return OrmUtils.deepCompare(
31✔
1076
                columnMetadata.default,
1077
                tableColumnDefault,
1078
            )
1079
        }
1080

1081
        const columnDefault = this.lowerDefaultValueIfNecessary(
14,882✔
1082
            this.normalizeDefault(columnMetadata),
1083
        )
1084
        return columnDefault === tableColumn.default
14,882✔
1085
    }
1086

1087
    /**
1088
     * Normalizes "isUnique" value of the column.
1089
     */
1090
    normalizeIsUnique(column: ColumnMetadata): boolean {
1091
        return column.entityMetadata.uniques.some(
36,820✔
1092
            (uq) => uq.columns.length === 1 && uq.columns[0] === column,
10,729✔
1093
        )
1094
    }
1095

1096
    /**
1097
     * Returns default column lengths, which is required on column creation.
1098
     */
1099
    getColumnLength(column: ColumnMetadata): string {
1100
        return column.length ? column.length.toString() : ""
17,522✔
1101
    }
1102

1103
    /**
1104
     * Creates column type definition including length, precision and scale
1105
     */
1106
    createFullType(column: TableColumn): string {
1107
        let type = column.type
14,467✔
1108

1109
        if (column.length) {
14,467✔
1110
            type += "(" + column.length + ")"
424✔
1111
        } else if (
14,043✔
1112
            column.precision !== null &&
28,146✔
1113
            column.precision !== undefined &&
1114
            column.scale !== null &&
1115
            column.scale !== undefined
1116
        ) {
1117
            type += "(" + column.precision + "," + column.scale + ")"
12✔
1118
        } else if (
14,031✔
1119
            column.precision !== null &&
28,062✔
1120
            column.precision !== undefined
1121
        ) {
1122
            type += "(" + column.precision + ")"
18✔
1123
        }
1124

1125
        if (column.type === "time without time zone") {
14,467✔
1126
            type =
47✔
1127
                "TIME" +
1128
                (column.precision !== null && column.precision !== undefined
141✔
1129
                    ? "(" + column.precision + ")"
1130
                    : "")
1131
        } else if (column.type === "time with time zone") {
14,420✔
1132
            type =
11✔
1133
                "TIME" +
1134
                (column.precision !== null && column.precision !== undefined
33✔
1135
                    ? "(" + column.precision + ")"
1136
                    : "") +
1137
                " WITH TIME ZONE"
1138
        } else if (column.type === "timestamp without time zone") {
14,409✔
1139
            type =
479✔
1140
                "TIMESTAMP" +
1141
                (column.precision !== null && column.precision !== undefined
1,437✔
1142
                    ? "(" + column.precision + ")"
1143
                    : "")
1144
        } else if (column.type === "timestamp with time zone") {
13,930✔
1145
            type =
21✔
1146
                "TIMESTAMP" +
1147
                (column.precision !== null && column.precision !== undefined
63✔
1148
                    ? "(" + column.precision + ")"
1149
                    : "") +
1150
                " WITH TIME ZONE"
1151
        } else if (this.spatialTypes.indexOf(column.type as ColumnType) >= 0) {
13,909✔
1152
            if (column.spatialFeatureType != null && column.srid != null) {
34✔
1153
                type = `${column.type}(${column.spatialFeatureType},${column.srid})`
10✔
1154
            } else if (column.spatialFeatureType != null) {
24✔
1155
                type = `${column.type}(${column.spatialFeatureType})`
8✔
1156
            } else {
1157
                type = column.type
16✔
1158
            }
1159
        } else if (column.type === "vector") {
13,875✔
1160
            type =
16✔
1161
                "vector" +
1162
                (column.dimensions != null ? "(" + column.dimensions + ")" : "")
16✔
1163
        }
1164

1165
        if (column.isArray) type += " array"
14,467✔
1166

1167
        return type
14,467✔
1168
    }
1169

1170
    /**
1171
     * Obtains a new database connection to a master server.
1172
     * Used for replication.
1173
     * If replication is not setup then returns default connection's database connection.
1174
     */
1175
    async obtainMasterConnection(): Promise<[any, Function]> {
1176
        if (!this.master) {
14,069!
1177
            throw new TypeORMError("Driver not Connected")
×
1178
        }
1179

1180
        return new Promise((ok, fail) => {
14,069✔
1181
            this.master.connect((err: any, connection: any, release: any) => {
14,069✔
1182
                err ? fail(err) : ok([connection, release])
14,069!
1183
            })
1184
        })
1185
    }
1186

1187
    /**
1188
     * Obtains a new database connection to a slave server.
1189
     * Used for replication.
1190
     * If replication is not setup then returns master (default) connection's database connection.
1191
     */
1192
    async obtainSlaveConnection(): Promise<[any, Function]> {
1193
        if (!this.slaves.length) {
3!
1194
            return this.obtainMasterConnection()
×
1195
        }
1196

1197
        const random = Math.floor(Math.random() * this.slaves.length)
3✔
1198

1199
        return new Promise((ok, fail) => {
3✔
1200
            this.slaves[random].connect(
3✔
1201
                (err: any, connection: any, release: any) => {
1202
                    err ? fail(err) : ok([connection, release])
3!
1203
                },
1204
            )
1205
        })
1206
    }
1207

1208
    /**
1209
     * Creates generated map of values generated or returned by database after INSERT query.
1210
     *
1211
     * todo: slow. optimize Object.keys(), OrmUtils.mergeDeep and column.createValueMap parts
1212
     */
1213
    createGeneratedMap(metadata: EntityMetadata, insertResult: ObjectLiteral) {
1214
        if (!insertResult) return undefined
55,906✔
1215

1216
        return Object.keys(insertResult).reduce((map, key) => {
39,880✔
1217
            const column = metadata.findColumnWithDatabaseName(key)
40,801✔
1218
            if (column) {
40,801✔
1219
                OrmUtils.mergeDeep(
40,801✔
1220
                    map,
1221
                    column.createValueMap(insertResult[key]),
1222
                )
1223
                // OrmUtils.mergeDeep(map, column.createValueMap(this.prepareHydratedValue(insertResult[key], column))); // TODO: probably should be like there, but fails on enums, fix later
1224
            }
1225
            return map
40,801✔
1226
        }, {} as ObjectLiteral)
1227
    }
1228

1229
    /**
1230
     * Differentiate columns of this table and columns from the given column metadatas columns
1231
     * and returns only changed.
1232
     */
1233
    findChangedColumns(
1234
        tableColumns: TableColumn[],
1235
        columnMetadatas: ColumnMetadata[],
1236
    ): ColumnMetadata[] {
1237
        return columnMetadatas.filter((columnMetadata) => {
5,686✔
1238
            const tableColumn = tableColumns.find(
18,410✔
1239
                (c) => c.name === columnMetadata.databaseName,
57,190✔
1240
            )
1241
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
18,410✔
1242

1243
            const isColumnChanged =
1244
                tableColumn.name !== columnMetadata.databaseName ||
18,407✔
1245
                tableColumn.type !== this.normalizeType(columnMetadata) ||
1246
                tableColumn.length !== columnMetadata.length ||
1247
                tableColumn.isArray !== columnMetadata.isArray ||
1248
                tableColumn.precision !== columnMetadata.precision ||
1249
                (columnMetadata.scale !== undefined &&
1250
                    tableColumn.scale !== columnMetadata.scale) ||
1251
                tableColumn.comment !==
1252
                    this.escapeComment(columnMetadata.comment) ||
1253
                (!tableColumn.isGenerated &&
1254
                    !this.defaultEqual(columnMetadata, tableColumn)) || // we included check for generated here, because generated columns already can have default values
1255
                tableColumn.isPrimary !== columnMetadata.isPrimary ||
1256
                tableColumn.isNullable !== columnMetadata.isNullable ||
1257
                tableColumn.isUnique !==
1258
                    this.normalizeIsUnique(columnMetadata) ||
1259
                tableColumn.enumName !== columnMetadata.enumName ||
1260
                (tableColumn.enum &&
1261
                    columnMetadata.enum &&
1262
                    !OrmUtils.isArraysEqual(
1263
                        tableColumn.enum,
1264
                        columnMetadata.enum.map((val) => val + ""),
825✔
1265
                    )) || // enums in postgres are always strings
1266
                tableColumn.isGenerated !== columnMetadata.isGenerated ||
1267
                (tableColumn.spatialFeatureType || "").toLowerCase() !==
36,677✔
1268
                    (columnMetadata.spatialFeatureType || "").toLowerCase() ||
36,677✔
1269
                tableColumn.srid !== columnMetadata.srid ||
1270
                tableColumn.generatedType !== columnMetadata.generatedType ||
1271
                (tableColumn.asExpression || "").trim() !==
36,674✔
1272
                    (columnMetadata.asExpression || "").trim()
36,674✔
1273

1274
            // DEBUG SECTION
1275
            // if (isColumnChanged) {
1276
            //     console.log("table:", columnMetadata.entityMetadata.tableName)
1277
            //     console.log(
1278
            //         "name:",
1279
            //         tableColumn.name,
1280
            //         columnMetadata.databaseName,
1281
            //     )
1282
            //     console.log(
1283
            //         "type:",
1284
            //         tableColumn.type,
1285
            //         this.normalizeType(columnMetadata),
1286
            //     )
1287
            //     console.log(
1288
            //         "length:",
1289
            //         tableColumn.length,
1290
            //         columnMetadata.length,
1291
            //     )
1292
            //     console.log(
1293
            //         "isArray:",
1294
            //         tableColumn.isArray,
1295
            //         columnMetadata.isArray,
1296
            //     )
1297
            //     console.log(
1298
            //         "precision:",
1299
            //         tableColumn.precision,
1300
            //         columnMetadata.precision,
1301
            //     )
1302
            //     console.log("scale:", tableColumn.scale, columnMetadata.scale)
1303
            //     console.log(
1304
            //         "comment:",
1305
            //         tableColumn.comment,
1306
            //         this.escapeComment(columnMetadata.comment),
1307
            //     )
1308
            //     console.log(
1309
            //         "enumName:",
1310
            //         tableColumn.enumName,
1311
            //         columnMetadata.enumName,
1312
            //     )
1313
            //     console.log(
1314
            //         "enum:",
1315
            //         tableColumn.enum &&
1316
            //             columnMetadata.enum &&
1317
            //             !OrmUtils.isArraysEqual(
1318
            //                 tableColumn.enum,
1319
            //                 columnMetadata.enum.map((val) => val + ""),
1320
            //             ),
1321
            //     )
1322
            //     console.log(
1323
            //         "isPrimary:",
1324
            //         tableColumn.isPrimary,
1325
            //         columnMetadata.isPrimary,
1326
            //     )
1327
            //     console.log(
1328
            //         "isNullable:",
1329
            //         tableColumn.isNullable,
1330
            //         columnMetadata.isNullable,
1331
            //     )
1332
            //     console.log(
1333
            //         "isUnique:",
1334
            //         tableColumn.isUnique,
1335
            //         this.normalizeIsUnique(columnMetadata),
1336
            //     )
1337
            //     console.log(
1338
            //         "isGenerated:",
1339
            //         tableColumn.isGenerated,
1340
            //         columnMetadata.isGenerated,
1341
            //     )
1342
            //     console.log(
1343
            //         "generatedType:",
1344
            //         tableColumn.generatedType,
1345
            //         columnMetadata.generatedType,
1346
            //     )
1347
            //     console.log(
1348
            //         "asExpression:",
1349
            //         (tableColumn.asExpression || "").trim(),
1350
            //         (columnMetadata.asExpression || "").trim(),
1351
            //     )
1352
            //     console.log(
1353
            //         "collation:",
1354
            //         tableColumn.collation,
1355
            //         columnMetadata.collation,
1356
            //     )
1357
            //     console.log(
1358
            //         "isGenerated 2:",
1359
            //         !tableColumn.isGenerated &&
1360
            //             this.lowerDefaultValueIfNecessary(
1361
            //                 this.normalizeDefault(columnMetadata),
1362
            //             ) !== tableColumn.default,
1363
            //     )
1364
            //     console.log(
1365
            //         "spatialFeatureType:",
1366
            //         (tableColumn.spatialFeatureType || "").toLowerCase(),
1367
            //         (columnMetadata.spatialFeatureType || "").toLowerCase(),
1368
            //     )
1369
            //     console.log("srid", tableColumn.srid, columnMetadata.srid)
1370
            //     console.log("==========================================")
1371
            // }
1372

1373
            return isColumnChanged
18,407✔
1374
        })
1375
    }
1376

1377
    private lowerDefaultValueIfNecessary(value: string | undefined) {
1378
        // Postgres saves function calls in default value as lowercase #2733
1379
        if (!value) {
14,882✔
1380
            return value
13,544✔
1381
        }
1382
        return value
1,338✔
1383
            .split(`'`)
1384
            .map((v, i) => {
1385
                return i % 2 === 1 ? v : v.toLowerCase()
3,258✔
1386
            })
1387
            .join(`'`)
1388
    }
1389

1390
    /**
1391
     * Returns true if driver supports RETURNING / OUTPUT statement.
1392
     */
1393
    isReturningSqlSupported(): boolean {
1394
        return true
20,717✔
1395
    }
1396

1397
    /**
1398
     * Returns true if driver supports uuid values generation on its own.
1399
     */
1400
    isUUIDGenerationSupported(): boolean {
1401
        return true
142✔
1402
    }
1403

1404
    /**
1405
     * Returns true if driver supports fulltext indices.
1406
     */
1407
    isFullTextColumnTypeSupported(): boolean {
1408
        return false
51✔
1409
    }
1410

1411
    get uuidGenerator(): string {
1412
        return this.options.uuidExtension === "pgcrypto"
185✔
1413
            ? "gen_random_uuid()"
1414
            : "uuid_generate_v4()"
1415
    }
1416

1417
    /**
1418
     * Creates an escaped parameter.
1419
     */
1420
    createParameter(parameterName: string, index: number): string {
1421
        return this.parametersPrefix + (index + 1)
258,377✔
1422
    }
1423

1424
    // -------------------------------------------------------------------------
1425
    // Public Methods
1426
    // -------------------------------------------------------------------------
1427

1428
    /**
1429
     * Loads postgres query stream package.
1430
     */
1431
    loadStreamDependency() {
1432
        try {
2✔
1433
            return PlatformTools.load("pg-query-stream")
2✔
1434
        } catch (e) {
1435
            // todo: better error for browser env
1436
            throw new TypeORMError(
×
1437
                `To use streams you should install pg-query-stream package. Please run npm i pg-query-stream --save command.`,
1438
            )
1439
        }
1440
    }
1441

1442
    // -------------------------------------------------------------------------
1443
    // Protected Methods
1444
    // -------------------------------------------------------------------------
1445

1446
    /**
1447
     * If driver dependency is not given explicitly, then try to load it via "require".
1448
     */
1449
    protected loadDependencies(): void {
1450
        try {
657✔
1451
            const postgres = this.options.driver || PlatformTools.load("pg")
657✔
1452
            this.postgres = postgres
657✔
1453
            try {
657✔
1454
                const pgNative =
1455
                    this.options.nativeDriver || PlatformTools.load("pg-native")
657✔
1456
                if (pgNative && this.postgres.native)
×
1457
                    this.postgres = this.postgres.native
×
1458
            } catch (e) {}
1459
        } catch (e) {
1460
            // todo: better error for browser env
1461
            throw new DriverPackageNotInstalledError("Postgres", "pg")
×
1462
        }
1463
    }
1464

1465
    /**
1466
     * Creates a new connection pool for a given database credentials.
1467
     */
1468
    protected async createPool(
1469
        options: PostgresConnectionOptions,
1470
        credentials: PostgresConnectionCredentialsOptions,
1471
    ): Promise<any> {
1472
        const { logger } = this.connection
665✔
1473
        credentials = Object.assign({}, credentials)
665✔
1474

1475
        // build connection options for the driver
1476
        // See: https://github.com/brianc/node-postgres/tree/master/packages/pg-pool#create
1477
        const connectionOptions = Object.assign(
665✔
1478
            {},
1479
            {
1480
                connectionString: credentials.url,
1481
                host: credentials.host,
1482
                user: credentials.username,
1483
                password: credentials.password,
1484
                database: credentials.database,
1485
                port: credentials.port,
1486
                ssl: credentials.ssl,
1487
                connectionTimeoutMillis: options.connectTimeoutMS,
1488
                application_name:
1489
                    options.applicationName ?? credentials.applicationName,
1,329✔
1490
                max: options.poolSize,
1491
            },
1492
            options.extra || {},
1,330✔
1493
        )
1494

1495
        if (options.parseInt8 !== undefined) {
665✔
1496
            if (
1!
1497
                this.postgres.defaults &&
2✔
1498
                Object.getOwnPropertyDescriptor(
1499
                    this.postgres.defaults,
1500
                    "parseInt8",
1501
                )?.set
1502
            ) {
1503
                this.postgres.defaults.parseInt8 = options.parseInt8
1✔
1504
            } else {
1505
                logger.log(
×
1506
                    "warn",
1507
                    "Attempted to set parseInt8 option, but the postgres driver does not support setting defaults.parseInt8. This option will be ignored.",
1508
                )
1509
            }
1510
        }
1511

1512
        // create a connection pool
1513
        const pool = new this.postgres.Pool(connectionOptions)
665✔
1514

1515
        const poolErrorHandler =
1516
            options.poolErrorHandler ||
665✔
1517
            ((error: any) =>
1518
                logger.log("warn", `Postgres pool raised an error. ${error}`))
×
1519

1520
        /*
1521
          Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
1522
          cause the hosting app to crash.
1523
         */
1524
        pool.on("error", poolErrorHandler)
665✔
1525

1526
        return new Promise((ok, fail) => {
665✔
1527
            pool.connect((err: any, connection: any, release: Function) => {
665✔
1528
                if (err) return fail(err)
665!
1529

1530
                if (options.logNotifications) {
665✔
1531
                    connection.on("notice", (msg: any) => {
1✔
1532
                        msg && this.connection.logger.log("info", msg.message)
3✔
1533
                    })
1534
                    connection.on("notification", (msg: any) => {
1✔
1535
                        msg &&
1✔
1536
                            this.connection.logger.log(
1537
                                "info",
1538
                                `Received NOTIFY on channel ${msg.channel}: ${msg.payload}.`,
1539
                            )
1540
                    })
1541
                }
1542
                release()
665✔
1543
                ok(pool)
665✔
1544
            })
1545
        })
1546
    }
1547

1548
    /**
1549
     * Closes connection pool.
1550
     */
1551
    protected async closePool(pool: any): Promise<void> {
1552
        while (this.connectedQueryRunners.length) {
665✔
1553
            await this.connectedQueryRunners[0].release()
16✔
1554
        }
1555

1556
        return new Promise<void>((ok, fail) => {
665✔
1557
            pool.end((err: any) => (err ? fail(err) : ok()))
665!
1558
        })
1559
    }
1560

1561
    /**
1562
     * Executes given query.
1563
     */
1564
    protected executeQuery(connection: any, query: string) {
1565
        this.connection.logger.logQuery(query)
78✔
1566

1567
        return new Promise((ok, fail) => {
78✔
1568
            connection.query(query, (err: any, result: any) =>
78✔
1569
                err ? fail(err) : ok(result),
78!
1570
            )
1571
        })
1572
    }
1573

1574
    /**
1575
     * If parameter is a datetime function, e.g. "CURRENT_TIMESTAMP", normalizes it.
1576
     * Otherwise returns original input.
1577
     */
1578
    protected normalizeDatetimeFunction(value: string) {
1579
        // check if input is datetime function
1580
        const upperCaseValue = value.toUpperCase()
924✔
1581
        const isDatetimeFunction =
1582
            upperCaseValue.indexOf("CURRENT_TIMESTAMP") !== -1 ||
924✔
1583
            upperCaseValue.indexOf("CURRENT_DATE") !== -1 ||
1584
            upperCaseValue.indexOf("CURRENT_TIME") !== -1 ||
1585
            upperCaseValue.indexOf("LOCALTIMESTAMP") !== -1 ||
1586
            upperCaseValue.indexOf("LOCALTIME") !== -1
1587

1588
        if (isDatetimeFunction) {
924✔
1589
            // extract precision, e.g. "(3)"
1590
            const precision = value.match(/\(\d+\)/)
222✔
1591

1592
            if (upperCaseValue.indexOf("CURRENT_TIMESTAMP") !== -1) {
222✔
1593
                return precision
57✔
1594
                    ? `('now'::text)::timestamp${precision[0]} with time zone`
1595
                    : "now()"
1596
            } else if (upperCaseValue === "CURRENT_DATE") {
165✔
1597
                return "('now'::text)::date"
30✔
1598
            } else if (upperCaseValue.indexOf("CURRENT_TIME") !== -1) {
135✔
1599
                return precision
45✔
1600
                    ? `('now'::text)::time${precision[0]} with time zone`
1601
                    : "('now'::text)::time with time zone"
1602
            } else if (upperCaseValue.indexOf("LOCALTIMESTAMP") !== -1) {
90✔
1603
                return precision
45✔
1604
                    ? `('now'::text)::timestamp${precision[0]} without time zone`
1605
                    : "('now'::text)::timestamp without time zone"
1606
            } else if (upperCaseValue.indexOf("LOCALTIME") !== -1) {
45✔
1607
                return precision
45✔
1608
                    ? `('now'::text)::time${precision[0]} without time zone`
1609
                    : "('now'::text)::time without time zone"
1610
            }
1611
        }
1612

1613
        return value
702✔
1614
    }
1615

1616
    /**
1617
     * Escapes a given comment.
1618
     */
1619
    protected escapeComment(comment?: string) {
1620
        if (!comment) return comment
18,391✔
1621

1622
        comment = comment.replace(/\u0000/g, "") // Null bytes aren't allowed in comments
100✔
1623

1624
        return comment
100✔
1625
    }
1626
}
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