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

typeorm / typeorm / 14796576772

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

Pull #11434

github

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

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

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"
4✔
4
import { ConnectionIsNotSetError } from "../../error/ConnectionIsNotSetError"
4✔
5
import { DriverPackageNotInstalledError } from "../../error/DriverPackageNotInstalledError"
4✔
6
import { ColumnMetadata } from "../../metadata/ColumnMetadata"
7
import { EntityMetadata } from "../../metadata/EntityMetadata"
8
import { PlatformTools } from "../../platform/PlatformTools"
4✔
9
import { QueryRunner } from "../../query-runner/QueryRunner"
10
import { RdbmsSchemaBuilder } from "../../schema-builder/RdbmsSchemaBuilder"
4✔
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"
4✔
16
import { DateUtils } from "../../util/DateUtils"
4✔
17
import { InstanceChecker } from "../../util/InstanceChecker"
4✔
18
import { ObjectUtils } from "../../util/ObjectUtils"
4✔
19
import { OrmUtils } from "../../util/OrmUtils"
4✔
20
import { Driver } from "../Driver"
21
import { DriverUtils } from "../DriverUtils"
4✔
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"
4✔
31

32
/**
33
 * Organizes communication with Cockroach DBMS.
34
 */
35
export class CockroachDriver implements Driver {
4✔
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
     */
59
    slaves: any[] = []
×
60

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

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

70
    /**
71
     * Connection options.
72
     */
73
    options: 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
     */
98
    isReplicated: boolean = false
×
99

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

105
    /**
106
     * Represent transaction support by this driver
107
     */
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
     */
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
     */
165
    supportedUpsertTypes: UpsertType[] = [
×
166
        "on-conflict-do-update",
167
        "primary-key",
168
    ]
169

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

175
    /**
176
     * Gets list of column data types that support length by a driver.
177
     */
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
     */
190
    withPrecisionColumnTypes: ColumnType[] = ["numeric", "decimal", "dec"]
×
191

192
    /**
193
     * Gets list of column data types that support scale by a driver.
194
     */
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
     */
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
     */
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
     */
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

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) {
258
        this.connection = connection
×
259
        this.options = connection.options as CockroachConnectionOptions
×
260
        this.isReplicated = this.options.replication ? true : false
×
261

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

265
        this.database = DriverUtils.buildDriverOptions(
×
266
            this.options.replication
×
267
                ? this.options.replication.master
268
                : this.options,
269
        ).database
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> {
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 {
304
            this.master = await this.createPool(this.options, this.options)
×
305
        }
306

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

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

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

318
            await queryRunner.release()
×
319
        }
320

321
        if (!this.schema) {
×
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
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)
338
        await this.connection.query(
×
339
            "SET enable_experimental_alter_column_type_general = true",
340
        )
341

342
        return Promise.resolve()
×
343
    }
344

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

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

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

365
    /**
366
     * Creates a query runner used to execute database queries.
367
     */
368
    createQueryRunner(mode: ReplicationMode) {
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 {
376
        if (columnMetadata.transformer)
×
377
            value = ApplyValueTransformers.transformTo(
×
378
                columnMetadata.transformer,
379
                value,
380
            )
381

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

384
        if (columnMetadata.type === Boolean) {
×
385
            return value === true ? 1 : 0
×
386
        } else if (columnMetadata.type === "date") {
×
387
            return DateUtils.mixedDateToDateString(value)
×
388
        } else if (columnMetadata.type === "time") {
×
389
            return DateUtils.mixedDateToTimeString(value)
×
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
        ) {
398
            return DateUtils.mixedDateToDate(value)
×
399
        } else if (
×
400
            ["json", "jsonb", ...this.spatialTypes].indexOf(
401
                columnMetadata.type,
402
            ) >= 0
403
        ) {
404
            return JSON.stringify(value)
×
405
        } else if (columnMetadata.type === "simple-array") {
×
406
            return DateUtils.simpleArrayToString(value)
×
407
        } else if (columnMetadata.type === "simple-json") {
×
408
            return DateUtils.simpleJsonToString(value)
×
409
        }
410

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 {
418
        if (value === null || value === undefined)
×
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
427
        if (
×
428
            ([Number, "int4", "smallint", "int2"].some(
×
429
                (v) => v === columnMetadata.type,
×
430
            ) &&
431
                !columnMetadata.isArray) ||
432
            columnMetadata.generationStrategy === "increment"
433
        ) {
434
            value = parseInt(value)
×
435
        } else if (columnMetadata.type === Boolean) {
×
436
            value = value ? true : false
×
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
        ) {
445
            value = DateUtils.normalizeHydratedDate(value)
×
446
        } else if (columnMetadata.type === "date") {
×
447
            value = DateUtils.mixedDateToDateString(value)
×
448
        } else if (columnMetadata.type === "time") {
×
449
            value = DateUtils.mixedTimeToString(value)
×
450
        } else if (columnMetadata.type === "simple-array") {
×
451
            value = DateUtils.stringToSimpleArray(value)
×
452
        } else if (columnMetadata.type === "simple-json") {
×
453
            value = DateUtils.stringToSimpleJson(value)
×
454
        } else if (
×
455
            columnMetadata.type === "enum" ||
×
456
            columnMetadata.type === "simple-enum"
457
        ) {
458
            if (columnMetadata.isArray) {
×
459
                if (value === "{}") return []
×
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)
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
468
                        if (val.startsWith(`"`) && val.endsWith(`"`))
×
469
                            val = val.slice(1, -1)
×
470
                        // replace escaped backslash and double quotes
471
                        return val.replace(/\\(\\|")/g, "$1")
×
472
                    })
473

474
                // convert to number if that exists in possible enum options
475
                value = value.map((val: string) => {
×
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
483
                value =
×
484
                    !isNaN(+value) &&
×
485
                    columnMetadata.enum!.indexOf(parseInt(value)) >= 0
486
                        ? parseInt(value)
487
                        : value
488
            }
489
        }
490

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

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[]] {
509
        const escapedParameters: any[] = Object.keys(nativeParameters).map(
×
510
            (key) => nativeParameters[key],
×
511
        )
512
        if (!parameters || !Object.keys(parameters).length)
×
513
            return [sql, escapedParameters]
×
514

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

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

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

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

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

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

553
    /**
554
     * Escapes a column name.
555
     */
556
    escape(columnName: string): string {
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 {
565
        const tablePath = [tableName]
×
566

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

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 } {
580
        const driverDatabase = this.database
×
581
        const driverSchema = this.schema
×
582

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

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

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

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

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

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

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

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 {
640
        if (
×
641
            column.type === Number ||
×
642
            column.type === "integer" ||
643
            column.type === "int" ||
644
            column.type === "bigint" ||
645
            column.type === "int64"
646
        ) {
647
            return "int8"
×
648
        } else if (
×
649
            column.type === String ||
×
650
            column.type === "character varying" ||
651
            column.type === "char varying"
652
        ) {
653
            return "varchar"
×
654
        } else if (
×
655
            column.type === Date ||
×
656
            column.type === "timestamp without time zone"
657
        ) {
658
            return "timestamp"
×
659
        } else if (column.type === "timestamp with time zone") {
×
660
            return "timestamptz"
×
661
        } else if (column.type === "time without time zone") {
×
662
            return "time"
×
663
        } else if (column.type === Boolean || column.type === "boolean") {
×
664
            return "bool"
×
665
        } else if (
×
666
            column.type === "simple-array" ||
×
667
            column.type === "simple-json" ||
668
            column.type === "text"
669
        ) {
670
            return "string"
×
671
        } else if (column.type === "bytea" || column.type === "blob") {
×
672
            return "bytes"
×
673
        } else if (column.type === "smallint") {
×
674
            return "int2"
×
675
        } else if (column.type === "numeric" || column.type === "dec") {
×
676
            return "decimal"
×
677
        } else if (
×
678
            column.type === "double precision" ||
×
679
            column.type === "float"
680
        ) {
681
            return "float8"
×
682
        } else if (column.type === "real") {
×
683
            return "float4"
×
684
        } else if (column.type === "character") {
×
685
            return "char"
×
686
        } else if (column.type === "simple-enum") {
×
687
            return "enum"
×
688
        } else if (column.type === "json") {
×
689
            return "jsonb"
×
690
        } else {
691
            return (column.type as string) || ""
×
692
        }
693
    }
694

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

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

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

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

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

747
        if (typeof defaultValue === "string") {
×
748
            const arrayCast = columnMetadata.isArray
×
749
                ? `::${columnMetadata.type}[]`
750
                : ""
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 {
765
        return column.entityMetadata.uniques.some(
×
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 {
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 {
781
        let type = column.type
×
782

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

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

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> {
818
        if (!this.master) {
×
819
            throw new TypeORMError("Driver not Connected")
×
820
        }
821

822
        return new Promise((ok, fail) => {
×
823
            this.master.connect((err: any, connection: any, release: any) => {
×
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) {
854
        if (!insertResult) return undefined
×
855

856
        return Object.keys(insertResult).reduce((map, key) => {
×
857
            const column = metadata.findColumnWithDatabaseName(key)
×
858
            if (column) {
×
859
                OrmUtils.mergeDeep(
×
860
                    map,
861
                    column.createValueMap(
862
                        this.prepareHydratedValue(insertResult[key], column),
863
                    ),
864
                )
865
            }
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[] {
878
        return columnMetadatas.filter((columnMetadata) => {
×
879
            const tableColumn = tableColumns.find(
×
880
                (c) => c.name === columnMetadata.databaseName,
×
881
            )
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

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,
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) {
949
        if (!value) {
×
950
            return value
×
951
        }
952
        return value
×
953
            .split(`'`)
954
            .map((v, i) => {
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 {
963
        return true
×
964
    }
965

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

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

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

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

991
    /**
992
     * Loads postgres query stream package.
993
     */
994
    loadStreamDependency() {
995
        try {
×
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 {
1013
        try {
×
1014
            const postgres = this.options.driver || PlatformTools.load("pg")
×
1015
            this.postgres = postgres
×
1016
            try {
×
1017
                const pgNative =
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> {
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
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
1058
        const pool = new this.postgres.Pool(connectionOptions)
×
1059
        const { logger } = this.connection
×
1060

1061
        const poolErrorHandler =
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
         */
1070
        pool.on("error", poolErrorHandler)
×
1071

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

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

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

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

1103
        return comment
×
1104
    }
1105

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