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

typeorm / typeorm / 15219332477

23 May 2025 09:13PM UTC coverage: 17.216% (-59.1%) from 76.346%
15219332477

Pull #11332

github

naorpeled
cr comments - move if block
Pull Request #11332: feat: add new undefined and null behavior flags

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

0 of 31 new or added lines in 3 files covered. (0.0%)

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

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

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

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

45
    /**
46
     * Cockroach 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
     */
UNCOV
59
    slaves: any[] = []
×
60

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

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

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

75
    /**
76
     * Database name used to perform all write queries.
77
     */
78
    database?: string
79

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

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

95
    /**
96
     * Indicates if replication is enabled.
97
     */
UNCOV
98
    isReplicated: boolean = false
×
99

100
    /**
101
     * Indicates if tree tables are supported by this driver.
102
     */
UNCOV
103
    treeSupport = true
×
104

105
    /**
106
     * Represent transaction support by this driver
107
     */
UNCOV
108
    transactionSupport = "nested" as const
×
109

110
    /**
111
     * Gets list of supported column data types by a driver.
112
     *
113
     * @see https://www.cockroachlabs.com/docs/stable/data-types.html
114
     */
UNCOV
115
    supportedDataTypes: ColumnType[] = [
×
116
        "array",
117
        "bool",
118
        "boolean",
119
        "bytes",
120
        "bytea",
121
        "blob",
122
        "date",
123
        "enum",
124
        "geometry",
125
        "geography",
126
        "numeric",
127
        "decimal",
128
        "dec",
129
        "float",
130
        "float4",
131
        "float8",
132
        "double precision",
133
        "real",
134
        "inet",
135
        "int",
136
        "int4",
137
        "integer",
138
        "int2",
139
        "int8",
140
        "int64",
141
        "smallint",
142
        "bigint",
143
        "interval",
144
        "string",
145
        "character varying",
146
        "character",
147
        "char",
148
        "char varying",
149
        "varchar",
150
        "text",
151
        "time",
152
        "time without time zone",
153
        "timestamp",
154
        "timestamptz",
155
        "timestamp without time zone",
156
        "timestamp with time zone",
157
        "json",
158
        "jsonb",
159
        "uuid",
160
    ]
161

162
    /**
163
     * Returns type of upsert supported by driver if any
164
     */
UNCOV
165
    supportedUpsertTypes: UpsertType[] = [
×
166
        "on-conflict-do-update",
167
        "primary-key",
168
    ]
169

170
    /**
171
     * Gets list of spatial column data types.
172
     */
UNCOV
173
    spatialTypes: ColumnType[] = ["geometry", "geography"]
×
174

175
    /**
176
     * Gets list of column data types that support length by a driver.
177
     */
UNCOV
178
    withLengthColumnTypes: ColumnType[] = [
×
179
        "character varying",
180
        "char varying",
181
        "varchar",
182
        "character",
183
        "char",
184
        "string",
185
    ]
186

187
    /**
188
     * Gets list of column data types that support precision by a driver.
189
     */
UNCOV
190
    withPrecisionColumnTypes: ColumnType[] = ["numeric", "decimal", "dec"]
×
191

192
    /**
193
     * Gets list of column data types that support scale by a driver.
194
     */
UNCOV
195
    withScaleColumnTypes: ColumnType[] = ["numeric", "decimal", "dec"]
×
196

197
    /**
198
     * Orm has special columns and we need to know what database column types should be for those types.
199
     * Column types are driver dependant.
200
     */
UNCOV
201
    mappedDataTypes: MappedColumnTypes = {
×
202
        createDate: "timestamptz",
203
        createDateDefault: "now()",
204
        updateDate: "timestamptz",
205
        updateDateDefault: "now()",
206
        deleteDate: "timestamptz",
207
        deleteDateNullable: true,
208
        version: Number,
209
        treeLevel: Number,
210
        migrationId: Number,
211
        migrationName: "varchar",
212
        migrationTimestamp: "int8",
213
        cacheId: Number,
214
        cacheIdentifier: "varchar",
215
        cacheTime: "int8",
216
        cacheDuration: Number,
217
        cacheQuery: "string",
218
        cacheResult: "string",
219
        metadataType: "varchar",
220
        metadataDatabase: "varchar",
221
        metadataSchema: "varchar",
222
        metadataTable: "varchar",
223
        metadataName: "varchar",
224
        metadataValue: "string",
225
    }
226

227
    /**
228
     * The prefix used for the parameters
229
     */
UNCOV
230
    parametersPrefix: string = "$"
×
231

232
    /**
233
     * Default values of length, precision and scale depends on column data type.
234
     * Used in the cases when length/precision/scale is not specified by user.
235
     */
UNCOV
236
    dataTypeDefaults: DataTypeDefaults = {
×
237
        char: { length: 1 },
238
    }
239

240
    /**
241
     * No documentation specifying a maximum length for identifiers could be found
242
     * for CockroarchDb.
243
     */
244
    maxAliasLength?: number
245

UNCOV
246
    cteCapabilities: CteCapabilities = {
×
247
        enabled: true,
248
        writable: true,
249
        materializedHint: true,
250
        requiresRecursiveHint: true,
251
    }
252

253
    // -------------------------------------------------------------------------
254
    // Constructor
255
    // -------------------------------------------------------------------------
256

257
    constructor(connection: DataSource) {
UNCOV
258
        this.connection = connection
×
UNCOV
259
        this.options = connection.options as CockroachConnectionOptions
×
UNCOV
260
        this.isReplicated = this.options.replication ? true : false
×
261

262
        // load postgres package
UNCOV
263
        this.loadDependencies()
×
264

UNCOV
265
        this.database = DriverUtils.buildDriverOptions(
×
266
            this.options.replication
×
267
                ? this.options.replication.master
268
                : this.options,
269
        ).database
UNCOV
270
        this.schema = DriverUtils.buildDriverOptions(this.options).schema
×
271

272
        // ObjectUtils.assign(this.options, DriverUtils.buildDriverOptions(connection.options)); // todo: do it better way
273
        // validate options to make sure everything is set
274
        // todo: revisit validation with replication in mind
275
        // if (!this.options.host)
276
        //     throw new DriverOptionNotSetError("host");
277
        // if (!this.options.username)
278
        //     throw new DriverOptionNotSetError("username");
279
        // if (!this.options.database)
280
        //     throw new DriverOptionNotSetError("database");
281
    }
282

283
    // -------------------------------------------------------------------------
284
    // Public Implemented Methods
285
    // -------------------------------------------------------------------------
286

287
    /**
288
     * Performs connection to the database.
289
     * Based on pooling options, it can either create connection immediately,
290
     * either create a pool and create connection when needed.
291
     */
292
    async connect(): Promise<void> {
UNCOV
293
        if (this.options.replication) {
×
294
            this.slaves = await Promise.all(
×
295
                this.options.replication.slaves.map((slave) => {
296
                    return this.createPool(this.options, slave)
×
297
                }),
298
            )
299
            this.master = await this.createPool(
×
300
                this.options,
301
                this.options.replication.master,
302
            )
303
        } else {
UNCOV
304
            this.master = await this.createPool(this.options, this.options)
×
305
        }
306

UNCOV
307
        if (!this.database || !this.searchSchema) {
×
UNCOV
308
            const queryRunner = this.createQueryRunner("master")
×
309

UNCOV
310
            if (!this.database) {
×
311
                this.database = await queryRunner.getCurrentDatabase()
×
312
            }
313

UNCOV
314
            if (!this.searchSchema) {
×
UNCOV
315
                this.searchSchema = await queryRunner.getCurrentSchema()
×
316
            }
317

UNCOV
318
            await queryRunner.release()
×
319
        }
320

UNCOV
321
        if (!this.schema) {
×
UNCOV
322
            this.schema = this.searchSchema
×
323
        }
324
    }
325

326
    /**
327
     * Makes any action after connection (e.g. create extensions in Postgres driver).
328
     */
329
    async afterConnect(): Promise<void> {
330
        // enable time travel queries
UNCOV
331
        if (this.options.timeTravelQueries) {
×
332
            await this.connection.query(
×
333
                `SET default_transaction_use_follower_reads = 'on';`,
334
            )
335
        }
336

337
        // enable experimental alter column type support (we need it to alter enum types)
UNCOV
338
        await this.connection.query(
×
339
            "SET enable_experimental_alter_column_type_general = true",
340
        )
341

UNCOV
342
        return Promise.resolve()
×
343
    }
344

345
    /**
346
     * Closes connection with database.
347
     */
348
    async disconnect(): Promise<void> {
UNCOV
349
        if (!this.master)
×
350
            return Promise.reject(new ConnectionIsNotSetError("cockroachdb"))
×
351

UNCOV
352
        await this.closePool(this.master)
×
UNCOV
353
        await Promise.all(this.slaves.map((slave) => this.closePool(slave)))
×
UNCOV
354
        this.master = undefined
×
UNCOV
355
        this.slaves = []
×
356
    }
357

358
    /**
359
     * Creates a schema builder used to build and sync a schema.
360
     */
361
    createSchemaBuilder() {
UNCOV
362
        return new RdbmsSchemaBuilder(this.connection)
×
363
    }
364

365
    /**
366
     * Creates a query runner used to execute database queries.
367
     */
368
    createQueryRunner(mode: ReplicationMode) {
UNCOV
369
        return new CockroachQueryRunner(this, mode)
×
370
    }
371

372
    /**
373
     * Prepares given value to a value to be persisted, based on its column type and metadata.
374
     */
375
    preparePersistentValue(value: any, columnMetadata: ColumnMetadata): any {
UNCOV
376
        if (columnMetadata.transformer)
×
UNCOV
377
            value = ApplyValueTransformers.transformTo(
×
378
                columnMetadata.transformer,
379
                value,
380
            )
381

UNCOV
382
        if (value === null || value === undefined) return value
×
383

UNCOV
384
        if (columnMetadata.type === Boolean) {
×
UNCOV
385
            return value === true ? 1 : 0
×
UNCOV
386
        } else if (columnMetadata.type === "date") {
×
UNCOV
387
            return DateUtils.mixedDateToDateString(value)
×
UNCOV
388
        } else if (columnMetadata.type === "time") {
×
UNCOV
389
            return DateUtils.mixedDateToTimeString(value)
×
UNCOV
390
        } else if (
×
391
            columnMetadata.type === "datetime" ||
×
392
            columnMetadata.type === Date ||
393
            columnMetadata.type === "timestamp" ||
394
            columnMetadata.type === "timestamptz" ||
395
            columnMetadata.type === "timestamp with time zone" ||
396
            columnMetadata.type === "timestamp without time zone"
397
        ) {
UNCOV
398
            return DateUtils.mixedDateToDate(value)
×
UNCOV
399
        } else if (
×
400
            ["json", "jsonb", ...this.spatialTypes].indexOf(
401
                columnMetadata.type,
402
            ) >= 0
403
        ) {
UNCOV
404
            return JSON.stringify(value)
×
UNCOV
405
        } else if (columnMetadata.type === "simple-array") {
×
UNCOV
406
            return DateUtils.simpleArrayToString(value)
×
UNCOV
407
        } else if (columnMetadata.type === "simple-json") {
×
UNCOV
408
            return DateUtils.simpleJsonToString(value)
×
409
        }
410

UNCOV
411
        return value
×
412
    }
413

414
    /**
415
     * Prepares given value to a value to be persisted, based on its column type or metadata.
416
     */
417
    prepareHydratedValue(value: any, columnMetadata: ColumnMetadata): any {
UNCOV
418
        if (value === null || value === undefined)
×
UNCOV
419
            return columnMetadata.transformer
×
420
                ? ApplyValueTransformers.transformFrom(
421
                      columnMetadata.transformer,
422
                      value,
423
                  )
424
                : value
425

426
        // unique_rowid() generates bigint value and should not be converted to number
UNCOV
427
        if (
×
428
            ([Number, "int4", "smallint", "int2"].some(
×
UNCOV
429
                (v) => v === columnMetadata.type,
×
430
            ) &&
431
                !columnMetadata.isArray) ||
432
            columnMetadata.generationStrategy === "increment"
433
        ) {
UNCOV
434
            value = parseInt(value)
×
UNCOV
435
        } else if (columnMetadata.type === Boolean) {
×
UNCOV
436
            value = value ? true : false
×
UNCOV
437
        } else if (
×
438
            columnMetadata.type === "datetime" ||
×
439
            columnMetadata.type === Date ||
440
            columnMetadata.type === "timestamp" ||
441
            columnMetadata.type === "timestamptz" ||
442
            columnMetadata.type === "timestamp with time zone" ||
443
            columnMetadata.type === "timestamp without time zone"
444
        ) {
UNCOV
445
            value = DateUtils.normalizeHydratedDate(value)
×
UNCOV
446
        } else if (columnMetadata.type === "date") {
×
UNCOV
447
            value = DateUtils.mixedDateToDateString(value)
×
UNCOV
448
        } else if (columnMetadata.type === "time") {
×
UNCOV
449
            value = DateUtils.mixedTimeToString(value)
×
UNCOV
450
        } else if (columnMetadata.type === "simple-array") {
×
UNCOV
451
            value = DateUtils.stringToSimpleArray(value)
×
UNCOV
452
        } else if (columnMetadata.type === "simple-json") {
×
UNCOV
453
            value = DateUtils.stringToSimpleJson(value)
×
UNCOV
454
        } else if (
×
455
            columnMetadata.type === "enum" ||
×
456
            columnMetadata.type === "simple-enum"
457
        ) {
UNCOV
458
            if (columnMetadata.isArray) {
×
UNCOV
459
                if (value === "{}") return []
×
UNCOV
460
                if (Array.isArray(value)) return value
×
461

462
                // manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56)
UNCOV
463
                value = (value as string)
×
464
                    .slice(1, -1)
465
                    .split(",")
466
                    .map((val) => {
467
                        // replace double quotes from the beginning and from the end
UNCOV
468
                        if (val.startsWith(`"`) && val.endsWith(`"`))
×
UNCOV
469
                            val = val.slice(1, -1)
×
470
                        // replace escaped backslash and double quotes
UNCOV
471
                        return val.replace(/\\(\\|")/g, "$1")
×
472
                    })
473

474
                // convert to number if that exists in possible enum options
UNCOV
475
                value = value.map((val: string) => {
×
UNCOV
476
                    return !isNaN(+val) &&
×
477
                        columnMetadata.enum!.indexOf(parseInt(val)) >= 0
478
                        ? parseInt(val)
479
                        : val
480
                })
481
            } else {
482
                // convert to number if that exists in possible enum options
UNCOV
483
                value =
×
484
                    !isNaN(+value) &&
×
485
                    columnMetadata.enum!.indexOf(parseInt(value)) >= 0
486
                        ? parseInt(value)
487
                        : value
488
            }
489
        }
490

UNCOV
491
        if (columnMetadata.transformer)
×
UNCOV
492
            value = ApplyValueTransformers.transformFrom(
×
493
                columnMetadata.transformer,
494
                value,
495
            )
496

UNCOV
497
        return value
×
498
    }
499

500
    /**
501
     * Replaces parameters in the given sql with special escaping character
502
     * and an array of parameter names to be passed to a query.
503
     */
504
    escapeQueryWithParameters(
505
        sql: string,
506
        parameters: ObjectLiteral,
507
        nativeParameters: ObjectLiteral,
508
    ): [string, any[]] {
UNCOV
509
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
×
510
            (key) => nativeParameters[key],
×
511
        )
UNCOV
512
        if (!parameters || !Object.keys(parameters).length)
×
UNCOV
513
            return [sql, escapedParameters]
×
514

UNCOV
515
        const parameterIndexMap = new Map<string, number>()
×
UNCOV
516
        sql = sql.replace(
×
517
            /:(\.\.\.)?([A-Za-z0-9_.]+)/g,
518
            (full, isArray: string, key: string): string => {
UNCOV
519
                if (!parameters.hasOwnProperty(key)) {
×
UNCOV
520
                    return full
×
521
                }
522

UNCOV
523
                if (parameterIndexMap.has(key)) {
×
UNCOV
524
                    return this.parametersPrefix + parameterIndexMap.get(key)
×
525
                }
526

UNCOV
527
                const value: any = parameters[key]
×
528

UNCOV
529
                if (isArray) {
×
UNCOV
530
                    return value
×
531
                        .map((v: any) => {
UNCOV
532
                            escapedParameters.push(v)
×
UNCOV
533
                            return this.createParameter(
×
534
                                key,
535
                                escapedParameters.length - 1,
536
                            )
537
                        })
538
                        .join(", ")
539
                }
540

UNCOV
541
                if (typeof value === "function") {
×
542
                    return value()
×
543
                }
544

UNCOV
545
                escapedParameters.push(value)
×
UNCOV
546
                parameterIndexMap.set(key, escapedParameters.length)
×
UNCOV
547
                return this.createParameter(key, escapedParameters.length - 1)
×
548
            },
549
        ) // todo: make replace only in value statements, otherwise problems
UNCOV
550
        return [sql, escapedParameters]
×
551
    }
552

553
    /**
554
     * Escapes a column name.
555
     */
556
    escape(columnName: string): string {
UNCOV
557
        return '"' + columnName + '"'
×
558
    }
559

560
    /**
561
     * Build full table name with schema name and table name.
562
     * E.g. myDB.mySchema.myTable
563
     */
564
    buildTableName(tableName: string, schema?: string): string {
UNCOV
565
        const tablePath = [tableName]
×
566

UNCOV
567
        if (schema) {
×
UNCOV
568
            tablePath.unshift(schema)
×
569
        }
570

UNCOV
571
        return tablePath.join(".")
×
572
    }
573

574
    /**
575
     * Parse a target table name or other types and return a normalized table definition.
576
     */
577
    parseTableName(
578
        target: EntityMetadata | Table | View | TableForeignKey | string,
579
    ): { database?: string; schema?: string; tableName: string } {
UNCOV
580
        const driverDatabase = this.database
×
UNCOV
581
        const driverSchema = this.schema
×
582

UNCOV
583
        if (InstanceChecker.isTable(target) || InstanceChecker.isView(target)) {
×
584
            // name is sometimes a path
UNCOV
585
            const parsed = this.parseTableName(target.name)
×
586

UNCOV
587
            return {
×
588
                database: target.database || parsed.database || driverDatabase,
×
589
                schema: target.schema || parsed.schema || driverSchema,
×
590
                tableName: parsed.tableName,
591
            }
592
        }
593

UNCOV
594
        if (InstanceChecker.isTableForeignKey(target)) {
×
595
            // referencedTableName is sometimes a path
UNCOV
596
            const parsed = this.parseTableName(target.referencedTableName)
×
597

UNCOV
598
            return {
×
599
                database:
600
                    target.referencedDatabase ||
×
601
                    parsed.database ||
602
                    driverDatabase,
603
                schema:
604
                    target.referencedSchema || parsed.schema || driverSchema,
×
605
                tableName: parsed.tableName,
606
            }
607
        }
608

UNCOV
609
        if (InstanceChecker.isEntityMetadata(target)) {
×
610
            // EntityMetadata tableName is never a path
611

UNCOV
612
            return {
×
613
                database: target.database || driverDatabase,
×
614
                schema: target.schema || driverSchema,
×
615
                tableName: target.tableName,
616
            }
617
        }
618

UNCOV
619
        const parts = target.split(".")
×
620

UNCOV
621
        return {
×
622
            database: driverDatabase,
623
            schema: (parts.length > 1 ? parts[0] : undefined) || driverSchema,
×
624
            tableName: parts.length > 1 ? parts[1] : parts[0],
×
625
        }
626
    }
627

628
    /**
629
     * Creates a database type from a given column metadata.
630
     */
631
    normalizeType(column: {
632
        type?: ColumnType
633
        length?: number | string
634
        precision?: number | null
635
        scale?: number
636
        isArray?: boolean
637
        isGenerated?: boolean
638
        generationStrategy?: "increment" | "uuid" | "rowid"
639
    }): string {
UNCOV
640
        if (
×
641
            column.type === Number ||
×
642
            column.type === "integer" ||
643
            column.type === "int" ||
644
            column.type === "bigint" ||
645
            column.type === "int64"
646
        ) {
UNCOV
647
            return "int8"
×
UNCOV
648
        } else if (
×
649
            column.type === String ||
×
650
            column.type === "character varying" ||
651
            column.type === "char varying"
652
        ) {
UNCOV
653
            return "varchar"
×
UNCOV
654
        } else if (
×
655
            column.type === Date ||
×
656
            column.type === "timestamp without time zone"
657
        ) {
UNCOV
658
            return "timestamp"
×
UNCOV
659
        } else if (column.type === "timestamp with time zone") {
×
UNCOV
660
            return "timestamptz"
×
UNCOV
661
        } else if (column.type === "time without time zone") {
×
UNCOV
662
            return "time"
×
UNCOV
663
        } else if (column.type === Boolean || column.type === "boolean") {
×
UNCOV
664
            return "bool"
×
UNCOV
665
        } else if (
×
666
            column.type === "simple-array" ||
×
667
            column.type === "simple-json" ||
668
            column.type === "text"
669
        ) {
UNCOV
670
            return "string"
×
UNCOV
671
        } else if (column.type === "bytea" || column.type === "blob") {
×
UNCOV
672
            return "bytes"
×
UNCOV
673
        } else if (column.type === "smallint") {
×
UNCOV
674
            return "int2"
×
UNCOV
675
        } else if (column.type === "numeric" || column.type === "dec") {
×
UNCOV
676
            return "decimal"
×
UNCOV
677
        } else if (
×
678
            column.type === "double precision" ||
×
679
            column.type === "float"
680
        ) {
UNCOV
681
            return "float8"
×
UNCOV
682
        } else if (column.type === "real") {
×
UNCOV
683
            return "float4"
×
UNCOV
684
        } else if (column.type === "character") {
×
UNCOV
685
            return "char"
×
UNCOV
686
        } else if (column.type === "simple-enum") {
×
UNCOV
687
            return "enum"
×
UNCOV
688
        } else if (column.type === "json") {
×
UNCOV
689
            return "jsonb"
×
690
        } else {
UNCOV
691
            return (column.type as string) || ""
×
692
        }
693
    }
694

695
    /**
696
     * Normalizes "default" value of the column.
697
     */
698
    normalizeDefault(columnMetadata: ColumnMetadata): string | undefined {
UNCOV
699
        const defaultValue = columnMetadata.default
×
700

UNCOV
701
        if (defaultValue === undefined || defaultValue === null) {
×
UNCOV
702
            return undefined
×
703
        }
704

UNCOV
705
        if (
×
706
            (columnMetadata.type === "enum" ||
×
707
                columnMetadata.type === "simple-enum") &&
708
            defaultValue !== undefined
709
        ) {
UNCOV
710
            if (columnMetadata.isArray) {
×
UNCOV
711
                const enumName = this.buildEnumName(columnMetadata)
×
UNCOV
712
                let arrayValue = defaultValue
×
UNCOV
713
                if (typeof defaultValue === "string") {
×
UNCOV
714
                    if (defaultValue === "{}") return `ARRAY[]::${enumName}[]`
×
UNCOV
715
                    arrayValue = defaultValue
×
716
                        .replace("{", "")
717
                        .replace("}", "")
718
                        .split(",")
719
                }
UNCOV
720
                if (Array.isArray(arrayValue)) {
×
UNCOV
721
                    const expr = `ARRAY[${arrayValue
×
UNCOV
722
                        .map((it) => `'${it}'`)
×
723
                        .join(",")}]`
UNCOV
724
                    return `${expr}::${enumName}[]`
×
725
                }
726
            } else {
UNCOV
727
                return `'${defaultValue}'`
×
728
            }
UNCOV
729
        } else if (typeof defaultValue === "number") {
×
UNCOV
730
            return `(${defaultValue})`
×
731
        }
732

UNCOV
733
        if (typeof defaultValue === "boolean") {
×
UNCOV
734
            return defaultValue ? "true" : "false"
×
735
        }
736

UNCOV
737
        if (typeof defaultValue === "function") {
×
UNCOV
738
            const value = defaultValue()
×
UNCOV
739
            if (value.toUpperCase() === "CURRENT_TIMESTAMP") {
×
UNCOV
740
                return "current_timestamp()"
×
UNCOV
741
            } else if (value.toUpperCase() === "CURRENT_DATE") {
×
UNCOV
742
                return "current_date()"
×
743
            }
UNCOV
744
            return value
×
745
        }
746

UNCOV
747
        if (typeof defaultValue === "string") {
×
UNCOV
748
            const arrayCast = columnMetadata.isArray
×
749
                ? `::${columnMetadata.type}[]`
750
                : ""
UNCOV
751
            return `'${defaultValue}'${arrayCast}`
×
752
        }
753

754
        if (ObjectUtils.isObject(defaultValue) && defaultValue !== null) {
×
755
            return `'${JSON.stringify(defaultValue)}'`
×
756
        }
757

758
        return `${defaultValue}`
×
759
    }
760

761
    /**
762
     * Normalizes "isUnique" value of the column.
763
     */
764
    normalizeIsUnique(column: ColumnMetadata): boolean {
UNCOV
765
        return column.entityMetadata.uniques.some(
×
UNCOV
766
            (uq) => uq.columns.length === 1 && uq.columns[0] === column,
×
767
        )
768
    }
769

770
    /**
771
     * Returns default column lengths, which is required on column creation.
772
     */
773
    getColumnLength(column: ColumnMetadata): string {
UNCOV
774
        return column.length ? column.length.toString() : ""
×
775
    }
776

777
    /**
778
     * Creates column type definition including length, precision and scale
779
     */
780
    createFullType(column: TableColumn): string {
UNCOV
781
        let type = column.type
×
782

UNCOV
783
        if (column.length) {
×
UNCOV
784
            type += "(" + column.length + ")"
×
UNCOV
785
        } else if (
×
786
            column.precision !== null &&
×
787
            column.precision !== undefined &&
788
            column.scale !== null &&
789
            column.scale !== undefined
790
        ) {
UNCOV
791
            type += "(" + column.precision + "," + column.scale + ")"
×
UNCOV
792
        } else if (
×
793
            column.precision !== null &&
×
794
            column.precision !== undefined
795
        ) {
UNCOV
796
            type += "(" + column.precision + ")"
×
UNCOV
797
        } else if (this.spatialTypes.indexOf(column.type as ColumnType) >= 0) {
×
UNCOV
798
            if (column.spatialFeatureType != null && column.srid != null) {
×
UNCOV
799
                type = `${column.type}(${column.spatialFeatureType},${column.srid})`
×
UNCOV
800
            } else if (column.spatialFeatureType != null) {
×
UNCOV
801
                type = `${column.type}(${column.spatialFeatureType})`
×
802
            } else {
UNCOV
803
                type = column.type
×
804
            }
805
        }
806

UNCOV
807
        if (column.isArray) type += " array"
×
808

UNCOV
809
        return type
×
810
    }
811

812
    /**
813
     * Obtains a new database connection to a master server.
814
     * Used for replication.
815
     * If replication is not setup then returns default connection's database connection.
816
     */
817
    async obtainMasterConnection(): Promise<any> {
UNCOV
818
        if (!this.master) {
×
819
            throw new TypeORMError("Driver not Connected")
×
820
        }
821

UNCOV
822
        return new Promise((ok, fail) => {
×
UNCOV
823
            this.master.connect((err: any, connection: any, release: any) => {
×
UNCOV
824
                err ? fail(err) : ok([connection, release])
×
825
            })
826
        })
827
    }
828

829
    /**
830
     * Obtains a new database connection to a slave server.
831
     * Used for replication.
832
     * If replication is not setup then returns master (default) connection's database connection.
833
     */
834
    async obtainSlaveConnection(): Promise<any> {
835
        if (!this.slaves.length) return this.obtainMasterConnection()
×
836

837
        const random = Math.floor(Math.random() * this.slaves.length)
×
838

839
        return new Promise((ok, fail) => {
×
840
            this.slaves[random].connect(
×
841
                (err: any, connection: any, release: any) => {
842
                    err ? fail(err) : ok([connection, release])
×
843
                },
844
            )
845
        })
846
    }
847

848
    /**
849
     * Creates generated map of values generated or returned by database after INSERT query.
850
     *
851
     * todo: slow. optimize Object.keys(), OrmUtils.mergeDeep and column.createValueMap parts
852
     */
853
    createGeneratedMap(metadata: EntityMetadata, insertResult: ObjectLiteral) {
UNCOV
854
        if (!insertResult) return undefined
×
855

UNCOV
856
        return Object.keys(insertResult).reduce((map, key) => {
×
UNCOV
857
            const column = metadata.findColumnWithDatabaseName(key)
×
UNCOV
858
            if (column) {
×
UNCOV
859
                OrmUtils.mergeDeep(
×
860
                    map,
861
                    column.createValueMap(
862
                        this.prepareHydratedValue(insertResult[key], column),
863
                    ),
864
                )
865
            }
UNCOV
866
            return map
×
867
        }, {} as ObjectLiteral)
868
    }
869

870
    /**
871
     * Differentiate columns of this table and columns from the given column metadatas columns
872
     * and returns only changed.
873
     */
874
    findChangedColumns(
875
        tableColumns: TableColumn[],
876
        columnMetadatas: ColumnMetadata[],
877
    ): ColumnMetadata[] {
UNCOV
878
        return columnMetadatas.filter((columnMetadata) => {
×
UNCOV
879
            const tableColumn = tableColumns.find(
×
UNCOV
880
                (c) => c.name === columnMetadata.databaseName,
×
881
            )
UNCOV
882
            if (!tableColumn) return false // we don't need new columns, we only need exist and changed
×
883

884
            // console.log("table:", columnMetadata.entityMetadata.tableName)
885
            // console.log("name:", {
886
            //     tableColumn: tableColumn.name,
887
            //     columnMetadata: columnMetadata.databaseName,
888
            // })
889
            // console.log("type:", {
890
            //     tableColumn: tableColumn.type,
891
            //     columnMetadata: this.normalizeType(columnMetadata),
892
            // })
893
            // console.log("length:", {
894
            //     tableColumn: tableColumn.length,
895
            //     columnMetadata: columnMetadata.length,
896
            // })
897
            // console.log("width:", tableColumn.width, columnMetadata.width);
898
            // console.log("precision:", tableColumn.precision, columnMetadata.precision);
899
            // console.log("scale:", tableColumn.scale, columnMetadata.scale);
900
            // console.log("comment:", tableColumn.comment, this.escapeComment(columnMetadata.comment));
901
            // console.log("default:", tableColumn.default, columnMetadata.default);
902
            // console.log("default changed:", !this.compareDefaultValues(this.normalizeDefault(columnMetadata), tableColumn.default));
903
            // console.log("isPrimary:", tableColumn.isPrimary, columnMetadata.isPrimary);
904
            // console.log("isNullable:", tableColumn.isNullable, columnMetadata.isNullable);
905
            // console.log("isUnique:", tableColumn.isUnique, this.normalizeIsUnique(columnMetadata));
906
            // console.log("asExpression:", {
907
            //     tableColumn: (tableColumn.asExpression || "").trim(),
908
            //     columnMetadata: (columnMetadata.asExpression || "").trim(),
909
            // })
910
            // console.log("==========================================");
911

UNCOV
912
            return (
×
913
                tableColumn.name !== columnMetadata.databaseName ||
×
914
                tableColumn.type !== this.normalizeType(columnMetadata) ||
915
                tableColumn.length !== columnMetadata.length ||
916
                tableColumn.isArray !== columnMetadata.isArray ||
917
                tableColumn.precision !== columnMetadata.precision ||
918
                (columnMetadata.scale !== undefined &&
919
                    tableColumn.scale !== columnMetadata.scale) ||
920
                tableColumn.comment !==
921
                    this.escapeComment(columnMetadata.comment) ||
922
                (!tableColumn.isGenerated &&
923
                    this.lowerDefaultValueIfNecessary(
924
                        this.normalizeDefault(columnMetadata),
925
                    ) !== tableColumn.default) || // we included check for generated here, because generated columns already can have default values
926
                tableColumn.isPrimary !== columnMetadata.isPrimary ||
927
                tableColumn.isNullable !== columnMetadata.isNullable ||
928
                tableColumn.isUnique !==
929
                    this.normalizeIsUnique(columnMetadata) ||
930
                tableColumn.enumName !== columnMetadata.enumName ||
931
                (tableColumn.enum &&
932
                    columnMetadata.enum &&
933
                    !OrmUtils.isArraysEqual(
934
                        tableColumn.enum,
UNCOV
935
                        columnMetadata.enum.map((val) => val + ""),
×
936
                    )) || // enums in postgres are always strings
937
                tableColumn.isGenerated !== columnMetadata.isGenerated ||
938
                tableColumn.generatedType !== columnMetadata.generatedType ||
939
                (tableColumn.asExpression || "").trim() !==
×
940
                    (columnMetadata.asExpression || "").trim() ||
×
941
                (tableColumn.spatialFeatureType || "").toLowerCase() !==
×
942
                    (columnMetadata.spatialFeatureType || "").toLowerCase() ||
×
943
                tableColumn.srid !== columnMetadata.srid
944
            )
945
        })
946
    }
947

948
    private lowerDefaultValueIfNecessary(value: string | undefined) {
UNCOV
949
        if (!value) {
×
UNCOV
950
            return value
×
951
        }
UNCOV
952
        return value
×
953
            .split(`'`)
954
            .map((v, i) => {
UNCOV
955
                return i % 2 === 1 ? v : v.toLowerCase()
×
956
            })
957
            .join(`'`)
958
    }
959
    /**
960
     * Returns true if driver supports RETURNING / OUTPUT statement.
961
     */
962
    isReturningSqlSupported(): boolean {
UNCOV
963
        return true
×
964
    }
965

966
    /**
967
     * Returns true if driver supports uuid values generation on its own.
968
     */
969
    isUUIDGenerationSupported(): boolean {
UNCOV
970
        return true
×
971
    }
972

973
    /**
974
     * Returns true if driver supports fulltext indices.
975
     */
976
    isFullTextColumnTypeSupported(): boolean {
UNCOV
977
        return false
×
978
    }
979

980
    /**
981
     * Creates an escaped parameter.
982
     */
983
    createParameter(parameterName: string, index: number): string {
UNCOV
984
        return this.parametersPrefix + (index + 1)
×
985
    }
986

987
    // -------------------------------------------------------------------------
988
    // Public Methods
989
    // -------------------------------------------------------------------------
990

991
    /**
992
     * Loads postgres query stream package.
993
     */
994
    loadStreamDependency() {
UNCOV
995
        try {
×
UNCOV
996
            return PlatformTools.load("pg-query-stream")
×
997
        } catch (e) {
998
            // todo: better error for browser env
999
            throw new TypeORMError(
×
1000
                `To use streams you should install pg-query-stream package. Please run npm i pg-query-stream --save command.`,
1001
            )
1002
        }
1003
    }
1004

1005
    // -------------------------------------------------------------------------
1006
    // Protected Methods
1007
    // -------------------------------------------------------------------------
1008

1009
    /**
1010
     * If driver dependency is not given explicitly, then try to load it via "require".
1011
     */
1012
    protected loadDependencies(): void {
UNCOV
1013
        try {
×
UNCOV
1014
            const postgres = this.options.driver || PlatformTools.load("pg")
×
UNCOV
1015
            this.postgres = postgres
×
UNCOV
1016
            try {
×
1017
                const pgNative =
UNCOV
1018
                    this.options.nativeDriver || PlatformTools.load("pg-native")
×
1019
                if (pgNative && this.postgres.native)
×
1020
                    this.postgres = this.postgres.native
×
1021
            } catch (e) {}
1022
        } catch (e) {
1023
            // todo: better error for browser env
1024
            throw new DriverPackageNotInstalledError("Postgres", "pg")
×
1025
        }
1026
    }
1027

1028
    /**
1029
     * Creates a new connection pool for a given database credentials.
1030
     */
1031
    protected async createPool(
1032
        options: CockroachConnectionOptions,
1033
        credentials: CockroachConnectionCredentialsOptions,
1034
    ): Promise<any> {
UNCOV
1035
        credentials = Object.assign(
×
1036
            {},
1037
            credentials,
1038
            DriverUtils.buildDriverOptions(credentials),
1039
        ) // todo: do it better way
1040

1041
        // build connection options for the driver
UNCOV
1042
        const connectionOptions = Object.assign(
×
1043
            {},
1044
            {
1045
                host: credentials.host,
1046
                user: credentials.username,
1047
                password: credentials.password,
1048
                database: credentials.database,
1049
                port: credentials.port,
1050
                ssl: credentials.ssl,
1051
                application_name: options.applicationName,
1052
                max: options.poolSize,
1053
            },
1054
            options.extra || {},
×
1055
        )
1056

1057
        // create a connection pool
UNCOV
1058
        const pool = new this.postgres.Pool(connectionOptions)
×
UNCOV
1059
        const { logger } = this.connection
×
1060

1061
        const poolErrorHandler =
UNCOV
1062
            options.poolErrorHandler ||
×
1063
            ((error: any) =>
1064
                logger.log("warn", `Postgres pool raised an error. ${error}`))
×
1065

1066
        /*
1067
          Attaching an error handler to pool errors is essential, as, otherwise, errors raised will go unhandled and
1068
          cause the hosting app to crash.
1069
         */
UNCOV
1070
        pool.on("error", poolErrorHandler)
×
1071

UNCOV
1072
        return new Promise((ok, fail) => {
×
UNCOV
1073
            pool.connect((err: any, connection: any, release: Function) => {
×
UNCOV
1074
                if (err) return fail(err)
×
UNCOV
1075
                release()
×
UNCOV
1076
                ok(pool)
×
1077
            })
1078
        })
1079
    }
1080

1081
    /**
1082
     * Closes connection pool.
1083
     */
1084
    protected async closePool(pool: any): Promise<void> {
UNCOV
1085
        await Promise.all(
×
1086
            this.connectedQueryRunners.map((queryRunner) =>
UNCOV
1087
                queryRunner.release(),
×
1088
            ),
1089
        )
UNCOV
1090
        return new Promise<void>((ok, fail) => {
×
UNCOV
1091
            pool.end((err: any) => (err ? fail(err) : ok()))
×
1092
        })
1093
    }
1094

1095
    /**
1096
     * Escapes a given comment.
1097
     */
1098
    protected escapeComment(comment?: string) {
UNCOV
1099
        if (!comment) return comment
×
1100

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

UNCOV
1103
        return comment
×
1104
    }
1105

1106
    /**
1107
     * Builds ENUM type name from given table and column.
1108
     */
1109
    protected buildEnumName(column: ColumnMetadata): string {
UNCOV
1110
        const { schema, tableName } = this.parseTableName(column.entityMetadata)
×
UNCOV
1111
        let enumName = column.enumName
×
1112
            ? column.enumName
1113
            : `${tableName}_${column.databaseName.toLowerCase()}_enum`
UNCOV
1114
        if (schema) enumName = `${schema}.${enumName}`
×
UNCOV
1115
        return enumName
×
1116
            .split(".")
1117
            .map((i) => {
UNCOV
1118
                return `"${i}"`
×
1119
            })
1120
            .join(".")
1121
    }
1122
}
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