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

typeorm / typeorm / 14902422933

08 May 2025 08:44AM CUT coverage: 5.343% (-71.0%) from 76.299%
14902422933

Pull #11432

github

web-flow
Merge 5f0712893 into 80e9b3004
Pull Request #11432: feat: add tagged template for executing raw SQL queries

315 of 12740 branches covered (2.47%)

Branch coverage included in aggregate %.

30 of 42 new or added lines in 7 files covered. (71.43%)

17199 existing lines in 205 files now uncovered.

1648 of 23998 relevant lines covered (6.87%)

188.2 hits per line

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

1.02
/src/metadata/ColumnMetadata.ts
1
import { ColumnType } from "../driver/types/ColumnTypes"
2
import { EntityMetadata } from "./EntityMetadata"
3
import { EmbeddedMetadata } from "./EmbeddedMetadata"
4
import { RelationMetadata } from "./RelationMetadata"
5
import { ObjectLiteral } from "../common/ObjectLiteral"
6
import { ColumnMetadataArgs } from "../metadata-args/ColumnMetadataArgs"
7
import { DataSource } from "../data-source/DataSource"
8
import { OrmUtils } from "../util/OrmUtils"
40✔
9
import { ValueTransformer } from "../decorator/options/ValueTransformer"
10
import { ApplyValueTransformers } from "../util/ApplyValueTransformers"
40✔
11
import { ObjectUtils } from "../util/ObjectUtils"
40✔
12
import { InstanceChecker } from "../util/InstanceChecker"
40✔
13
import { VirtualColumnOptions } from "../decorator/options/VirtualColumnOptions"
14

15
/**
16
 * This metadata contains all information about entity's column.
17
 */
18
export class ColumnMetadata {
40✔
UNCOV
19
    readonly "@instanceof" = Symbol.for("ColumnMetadata")
×
20

21
    // ---------------------------------------------------------------------
22
    // Public Properties
23
    // ---------------------------------------------------------------------
24

25
    /**
26
     * Target class where column decorator is used.
27
     * This may not be always equal to entity metadata (for example embeds or inheritance cases).
28
     */
29
    target: Function | string
30

31
    /**
32
     * Entity metadata where this column metadata is.
33
     *
34
     * For example for @Column() name: string in Post, entityMetadata will be metadata of Post entity.
35
     */
36
    entityMetadata: EntityMetadata
37

38
    /**
39
     * Embedded metadata where this column metadata is.
40
     * If this column is not in embed then this property value is undefined.
41
     */
42
    embeddedMetadata?: EmbeddedMetadata
43

44
    /**
45
     * If column is a foreign key of some relation then this relation's metadata will be there.
46
     * If this column does not have a foreign key then this property value is undefined.
47
     */
48
    relationMetadata?: RelationMetadata
49

50
    /**
51
     * Class's property name on which this column is applied.
52
     */
53
    propertyName: string
54

55
    /**
56
     * The database type of the column.
57
     */
58
    type: ColumnType
59

60
    /**
61
     * Type's length in the database.
62
     */
UNCOV
63
    length: string = ""
×
64

65
    /**
66
     * Type's display width in the database.
67
     */
68
    width?: number
69

70
    /**
71
     * Defines column character set.
72
     */
73
    charset?: string
74

75
    /**
76
     * Defines column collation.
77
     */
78
    collation?: string
79

80
    /**
81
     * Indicates if this column is a primary key.
82
     */
UNCOV
83
    isPrimary: boolean = false
×
84

85
    /**
86
     * Indicates if this column is generated (auto increment or generated other way).
87
     */
UNCOV
88
    isGenerated: boolean = false
×
89

90
    /**
91
     * Indicates if column can contain nulls or not.
92
     */
UNCOV
93
    isNullable: boolean = false
×
94

95
    /**
96
     * Indicates if column is selected by query builder or not.
97
     */
UNCOV
98
    isSelect: boolean = true
×
99

100
    /**
101
     * Indicates if column is inserted by default or not.
102
     */
UNCOV
103
    isInsert: boolean = true
×
104

105
    /**
106
     * Indicates if column allows updates or not.
107
     */
UNCOV
108
    isUpdate: boolean = true
×
109

110
    /**
111
     * Specifies generation strategy if this column will use auto increment.
112
     */
113
    generationStrategy?: "uuid" | "increment" | "rowid"
114

115
    /**
116
     * Identity column type. Supports only in Postgres 10+.
117
     */
118
    generatedIdentity?: "ALWAYS" | "BY DEFAULT"
119

120
    /**
121
     * Column comment.
122
     * This feature is not supported by all databases.
123
     */
124
    comment?: string
125

126
    /**
127
     * Default database value.
128
     */
129
    default?:
130
        | number
131
        | boolean
132
        | string
133
        | null
134
        | (number | boolean | string)[]
135
        | Record<string, object>
136
        | (() => string)
137

138
    /**
139
     * ON UPDATE trigger. Works only for MySQL.
140
     */
141
    onUpdate?: string
142

143
    /**
144
     * The precision for a decimal (exact numeric) column (applies only for decimal column),
145
     * which is the maximum number of digits that are stored for the values.
146
     */
147
    precision?: number | null
148

149
    /**
150
     * The scale for a decimal (exact numeric) column (applies only for decimal column),
151
     * which represents the number of digits to the right of the decimal point and must not be greater than precision.
152
     */
153
    scale?: number
154

155
    /**
156
     * Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
157
     * If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to the column
158
     */
UNCOV
159
    zerofill: boolean = false
×
160

161
    /**
162
     * Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
163
     */
UNCOV
164
    unsigned: boolean = false
×
165

166
    /**
167
     * Array of possible enumerated values.
168
     *
169
     * `postgres` and `mysql` store enum values as strings but we want to keep support
170
     * for numeric and heterogeneous based typescript enums, so we need (string|number)[]
171
     */
172
    enum?: (string | number)[]
173

174
    /**
175
     * Exact name of enum
176
     */
177
    enumName?: string
178

179
    /**
180
     * Generated column expression.
181
     */
182
    asExpression?: string
183

184
    /**
185
     * Generated column type.
186
     */
187
    generatedType?: "VIRTUAL" | "STORED"
188

189
    /**
190
     * Return type of HSTORE column.
191
     * Returns value as string or as object.
192
     */
193
    hstoreType?: "object" | "string"
194

195
    /**
196
     * Indicates if this column is an array.
197
     */
UNCOV
198
    isArray: boolean = false
×
199

200
    /**
201
     * Gets full path to this column property (including column property name).
202
     * Full path is relevant when column is used in embeds (one or multiple nested).
203
     * For example it will return "counters.subcounters.likes".
204
     * If property is not in embeds then it returns just property name of the column.
205
     */
206
    propertyPath: string
207

208
    /**
209
     * Same as property path, but dots are replaced with '_'.
210
     * Used in query builder statements.
211
     */
212
    propertyAliasName: string
213

214
    /**
215
     * Gets full path to this column database name (including column database name).
216
     * Full path is relevant when column is used in embeds (one or multiple nested).
217
     * For example it will return "counters.subcounters.likes".
218
     * If property is not in embeds then it returns just database name of the column.
219
     */
220
    databasePath: string
221

222
    /**
223
     * Complete column name in the database including its embedded prefixes.
224
     */
225
    databaseName: string
226

227
    /**
228
     * Database name in the database without embedded prefixes applied.
229
     */
230
    databaseNameWithoutPrefixes: string
231

232
    /**
233
     * Database name set by entity metadata builder, not yet passed naming strategy process and without embedded prefixes.
234
     */
235
    givenDatabaseName?: string
236

237
    /**
238
     * Indicates if column is virtual. Virtual columns are not mapped to the entity.
239
     */
UNCOV
240
    isVirtual: boolean = false
×
241

242
    /**
243
     * Indicates if column is a virtual property. Virtual properties are not mapped to the entity.
244
     * This property is used in tandem the virtual column decorator.
245
     * @See https://typeorm.io/decorator-reference#virtualcolumn for more details.
246
     */
UNCOV
247
    isVirtualProperty: boolean = false
×
248

249
    /**
250
     * Query to be used to populate the column data. This query is used when generating the relational db script.
251
     * The query function is called with the current entities alias either defined by the Entity Decorator or automatically
252
     * @See https://typeorm.io/decorator-reference#virtualcolumn for more details.
253
     */
254
    query?: (alias: string) => string
255

256
    /**
257
     * Indicates if column is discriminator. Discriminator columns are not mapped to the entity.
258
     */
UNCOV
259
    isDiscriminator: boolean = false
×
260

261
    /**
262
     * Indicates if column is tree-level column. Tree-level columns are used in closure entities.
263
     */
UNCOV
264
    isTreeLevel: boolean = false
×
265

266
    /**
267
     * Indicates if this column contains an entity creation date.
268
     */
UNCOV
269
    isCreateDate: boolean = false
×
270

271
    /**
272
     * Indicates if this column contains an entity update date.
273
     */
UNCOV
274
    isUpdateDate: boolean = false
×
275

276
    /**
277
     * Indicates if this column contains an entity delete date.
278
     */
UNCOV
279
    isDeleteDate: boolean = false
×
280

281
    /**
282
     * Indicates if this column contains an entity version.
283
     */
UNCOV
284
    isVersion: boolean = false
×
285

286
    /**
287
     * Indicates if this column contains an object id.
288
     */
UNCOV
289
    isObjectId: boolean = false
×
290

291
    /**
292
     * If this column is foreign key then it references some other column,
293
     * and this property will contain reference to this column.
294
     */
295
    referencedColumn: ColumnMetadata | undefined
296

297
    /**
298
     * If this column is primary key then this specifies the name for it.
299
     */
300
    primaryKeyConstraintName?: string
301

302
    /**
303
     * If this column is foreign key then this specifies the name for it.
304
     */
305
    foreignKeyConstraintName?: string
306

307
    /**
308
     * Specifies a value transformer that is to be used to (un)marshal
309
     * this column when reading or writing to the database.
310
     */
311
    transformer?: ValueTransformer | ValueTransformer[]
312

313
    /**
314
     * Column type in the case if this column is in the closure table.
315
     * Column can be ancestor or descendant in the closure tables.
316
     */
317
    closureType?: "ancestor" | "descendant"
318

319
    /**
320
     * Indicates if this column is nested set's left column.
321
     * Used only in tree entities with nested-set type.
322
     */
UNCOV
323
    isNestedSetLeft: boolean = false
×
324

325
    /**
326
     * Indicates if this column is nested set's right column.
327
     * Used only in tree entities with nested-set type.
328
     */
UNCOV
329
    isNestedSetRight: boolean = false
×
330

331
    /**
332
     * Indicates if this column is materialized path's path column.
333
     * Used only in tree entities with materialized path type.
334
     */
UNCOV
335
    isMaterializedPath: boolean = false
×
336

337
    /**
338
     * Spatial Feature Type (Geometry, Point, Polygon, etc.)
339
     */
340
    spatialFeatureType?: string
341

342
    /**
343
     * SRID (Spatial Reference ID (EPSG code))
344
     */
345
    srid?: number
346

347
    // ---------------------------------------------------------------------
348
    // Constructor
349
    // ---------------------------------------------------------------------
350

351
    constructor(options: {
352
        connection: DataSource
353
        entityMetadata: EntityMetadata
354
        embeddedMetadata?: EmbeddedMetadata
355
        referencedColumn?: ColumnMetadata
356
        args: ColumnMetadataArgs
357
        closureType?: "ancestor" | "descendant"
358
        nestedSetLeft?: boolean
359
        nestedSetRight?: boolean
360
        materializedPath?: boolean
361
    }) {
UNCOV
362
        this.entityMetadata = options.entityMetadata
×
UNCOV
363
        this.embeddedMetadata = options.embeddedMetadata!
×
UNCOV
364
        this.referencedColumn = options.referencedColumn
×
UNCOV
365
        if (options.args.target) this.target = options.args.target
×
UNCOV
366
        if (options.args.propertyName)
×
UNCOV
367
            this.propertyName = options.args.propertyName
×
UNCOV
368
        if (options.args.options.name)
×
UNCOV
369
            this.givenDatabaseName = options.args.options.name
×
UNCOV
370
        if (options.args.options.type) this.type = options.args.options.type
×
UNCOV
371
        if (options.args.options.length)
×
UNCOV
372
            this.length = options.args.options.length
×
373
                ? options.args.options.length.toString()
374
                : ""
UNCOV
375
        if (options.args.options.width) this.width = options.args.options.width
×
UNCOV
376
        if (options.args.options.charset)
×
UNCOV
377
            this.charset = options.args.options.charset
×
UNCOV
378
        if (options.args.options.collation)
×
UNCOV
379
            this.collation = options.args.options.collation
×
UNCOV
380
        if (options.args.options.primary)
×
UNCOV
381
            this.isPrimary = options.args.options.primary
×
UNCOV
382
        if (options.args.options.default === null)
×
383
            // to make sure default: null is the same as nullable: true
UNCOV
384
            this.isNullable = true
×
UNCOV
385
        if (options.args.options.nullable !== undefined)
×
UNCOV
386
            this.isNullable = options.args.options.nullable
×
UNCOV
387
        if (options.args.options.select !== undefined)
×
UNCOV
388
            this.isSelect = options.args.options.select
×
UNCOV
389
        if (options.args.options.insert !== undefined)
×
UNCOV
390
            this.isInsert = options.args.options.insert
×
UNCOV
391
        if (options.args.options.update !== undefined)
×
UNCOV
392
            this.isUpdate = options.args.options.update
×
UNCOV
393
        if (options.args.options.readonly !== undefined)
×
UNCOV
394
            this.isUpdate = !options.args.options.readonly
×
UNCOV
395
        if (options.args.options.comment)
×
UNCOV
396
            this.comment = options.args.options.comment
×
UNCOV
397
        if (options.args.options.default !== undefined)
×
UNCOV
398
            this.default = options.args.options.default
×
UNCOV
399
        if (options.args.options.onUpdate)
×
UNCOV
400
            this.onUpdate = options.args.options.onUpdate
×
UNCOV
401
        if (options.args.options.generatedIdentity)
×
UNCOV
402
            this.generatedIdentity = options.args.options.generatedIdentity
×
UNCOV
403
        if (
×
404
            options.args.options.scale !== null &&
×
405
            options.args.options.scale !== undefined
406
        )
UNCOV
407
            this.scale = options.args.options.scale
×
UNCOV
408
        if (options.args.options.zerofill) {
×
UNCOV
409
            this.zerofill = options.args.options.zerofill
×
UNCOV
410
            this.unsigned = true // if you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to the column
×
411
        }
UNCOV
412
        if (options.args.options.unsigned)
×
UNCOV
413
            this.unsigned = options.args.options.unsigned
×
UNCOV
414
        if (options.args.options.precision !== null)
×
UNCOV
415
            this.precision = options.args.options.precision
×
UNCOV
416
        if (options.args.options.enum) {
×
UNCOV
417
            if (
×
418
                ObjectUtils.isObject(options.args.options.enum) &&
×
419
                !Array.isArray(options.args.options.enum)
420
            ) {
UNCOV
421
                this.enum = Object.keys(options.args.options.enum)
×
422
                    // remove numeric keys - typescript numeric enum types generate them
423
                    // From the documentation: “declaration merging” means that the compiler merges two separate declarations
424
                    // declared with the same name into a single definition. This concept is often used to merge enum with namespace
425
                    // where in namespace we define e.g. utility methods for creating enum. This is well known in other languages
426
                    // like Java (enum methods). Here in case if enum have function, we need to remove it from metadata, otherwise
427
                    // generated SQL statements contains string representation of that function which leads into syntax error
428
                    // at database side.
429
                    .filter(
430
                        (key) =>
UNCOV
431
                            isNaN(+key) &&
×
432
                            typeof (options.args.options.enum as ObjectLiteral)[
433
                                key
434
                            ] !== "function",
435
                    )
436
                    .map(
437
                        (key) =>
UNCOV
438
                            (options.args.options.enum as ObjectLiteral)[key],
×
439
                    )
440
            } else {
UNCOV
441
                this.enum = options.args.options.enum
×
442
            }
443
        }
UNCOV
444
        if (options.args.options.enumName) {
×
UNCOV
445
            this.enumName = options.args.options.enumName
×
446
        }
UNCOV
447
        if (options.args.options.primaryKeyConstraintName) {
×
UNCOV
448
            this.primaryKeyConstraintName =
×
449
                options.args.options.primaryKeyConstraintName
450
        }
UNCOV
451
        if (options.args.options.foreignKeyConstraintName) {
×
UNCOV
452
            this.foreignKeyConstraintName =
×
453
                options.args.options.foreignKeyConstraintName
454
        }
UNCOV
455
        if (options.args.options.asExpression) {
×
UNCOV
456
            this.asExpression = options.args.options.asExpression
×
UNCOV
457
            this.generatedType = options.args.options.generatedType
×
458
                ? options.args.options.generatedType
459
                : "VIRTUAL"
460
        }
UNCOV
461
        if (options.args.options.hstoreType)
×
UNCOV
462
            this.hstoreType = options.args.options.hstoreType
×
UNCOV
463
        if (options.args.options.array)
×
UNCOV
464
            this.isArray = options.args.options.array
×
UNCOV
465
        if (options.args.mode) {
×
UNCOV
466
            this.isVirtualProperty = options.args.mode === "virtual-property"
×
UNCOV
467
            this.isVirtual = options.args.mode === "virtual"
×
UNCOV
468
            this.isTreeLevel = options.args.mode === "treeLevel"
×
UNCOV
469
            this.isCreateDate = options.args.mode === "createDate"
×
UNCOV
470
            this.isUpdateDate = options.args.mode === "updateDate"
×
UNCOV
471
            this.isDeleteDate = options.args.mode === "deleteDate"
×
UNCOV
472
            this.isVersion = options.args.mode === "version"
×
UNCOV
473
            this.isObjectId = options.args.mode === "objectId"
×
474
        }
UNCOV
475
        if (this.isVirtualProperty) {
×
UNCOV
476
            this.isInsert = false
×
UNCOV
477
            this.isUpdate = false
×
478
        }
UNCOV
479
        if (options.args.options.transformer)
×
UNCOV
480
            this.transformer = options.args.options.transformer
×
UNCOV
481
        if (options.args.options.spatialFeatureType)
×
UNCOV
482
            this.spatialFeatureType = options.args.options.spatialFeatureType
×
UNCOV
483
        if (options.args.options.srid !== undefined)
×
UNCOV
484
            this.srid = options.args.options.srid
×
UNCOV
485
        if ((options.args.options as VirtualColumnOptions).query)
×
UNCOV
486
            this.query = (options.args.options as VirtualColumnOptions).query
×
UNCOV
487
        if (this.isTreeLevel)
×
UNCOV
488
            this.type = options.connection.driver.mappedDataTypes.treeLevel
×
UNCOV
489
        if (this.isCreateDate) {
×
UNCOV
490
            if (!this.type)
×
UNCOV
491
                this.type = options.connection.driver.mappedDataTypes.createDate
×
UNCOV
492
            if (!this.default)
×
UNCOV
493
                this.default = () =>
×
UNCOV
494
                    options.connection.driver.mappedDataTypes.createDateDefault
×
495
            // skip precision if it was explicitly set to "null" in column options. Otherwise use default precision if it exist.
UNCOV
496
            if (
×
497
                this.precision === undefined &&
×
498
                options.args.options.precision === undefined &&
499
                options.connection.driver.mappedDataTypes.createDatePrecision
500
            )
UNCOV
501
                this.precision =
×
502
                    options.connection.driver.mappedDataTypes.createDatePrecision
503
        }
UNCOV
504
        if (this.isUpdateDate) {
×
UNCOV
505
            if (!this.type)
×
UNCOV
506
                this.type = options.connection.driver.mappedDataTypes.updateDate
×
UNCOV
507
            if (!this.default)
×
UNCOV
508
                this.default = () =>
×
UNCOV
509
                    options.connection.driver.mappedDataTypes.updateDateDefault
×
UNCOV
510
            if (!this.onUpdate)
×
UNCOV
511
                this.onUpdate =
×
512
                    options.connection.driver.mappedDataTypes.updateDateDefault
513
            // skip precision if it was explicitly set to "null" in column options. Otherwise use default precision if it exist.
UNCOV
514
            if (
×
515
                this.precision === undefined &&
×
516
                options.args.options.precision === undefined &&
517
                options.connection.driver.mappedDataTypes.updateDatePrecision
518
            )
UNCOV
519
                this.precision =
×
520
                    options.connection.driver.mappedDataTypes.updateDatePrecision
521
        }
UNCOV
522
        if (this.isDeleteDate) {
×
UNCOV
523
            if (!this.type)
×
UNCOV
524
                this.type = options.connection.driver.mappedDataTypes.deleteDate
×
UNCOV
525
            if (!this.isNullable)
×
UNCOV
526
                this.isNullable =
×
527
                    options.connection.driver.mappedDataTypes.deleteDateNullable
528
            // skip precision if it was explicitly set to "null" in column options. Otherwise use default precision if it exist.
UNCOV
529
            if (
×
530
                this.precision === undefined &&
×
531
                options.args.options.precision === undefined &&
532
                options.connection.driver.mappedDataTypes.deleteDatePrecision
533
            )
UNCOV
534
                this.precision =
×
535
                    options.connection.driver.mappedDataTypes.deleteDatePrecision
536
        }
UNCOV
537
        if (this.isVersion)
×
UNCOV
538
            this.type = options.connection.driver.mappedDataTypes.version
×
UNCOV
539
        if (options.closureType) this.closureType = options.closureType
×
UNCOV
540
        if (options.nestedSetLeft) this.isNestedSetLeft = options.nestedSetLeft
×
UNCOV
541
        if (options.nestedSetRight)
×
UNCOV
542
            this.isNestedSetRight = options.nestedSetRight
×
UNCOV
543
        if (options.materializedPath)
×
UNCOV
544
            this.isMaterializedPath = options.materializedPath
×
545
    }
546

547
    // ---------------------------------------------------------------------
548
    // Public Methods
549
    // ---------------------------------------------------------------------
550

551
    /**
552
     * Creates entity id map from the given entity ids array.
553
     */
554
    createValueMap(value: any, useDatabaseName = false) {
×
555
        // extract column value from embeds of entity if column is in embedded
UNCOV
556
        if (this.embeddedMetadata) {
×
557
            // example: post[data][information][counters].id where "data", "information" and "counters" are embeddeds
558
            // we need to get value of "id" column from the post real entity object and return it in a
559
            // { data: { information: { counters: { id: ... } } } } format
560

561
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
UNCOV
562
            const propertyNames = [...this.embeddedMetadata.parentPropertyNames]
×
563

564
            // now need to access post[data][information][counters] to get column value from the counters
565
            // and on each step we need to create complex literal object, e.g. first { data },
566
            // then { data: { information } }, then { data: { information: { counters } } },
567
            // then { data: { information: { counters: [this.propertyName]: entity[data][information][counters][this.propertyName] } } }
568
            // this recursive function helps doing that
UNCOV
569
            const extractEmbeddedColumnValue = (
×
570
                propertyNames: string[],
571
                map: ObjectLiteral,
572
            ): any => {
UNCOV
573
                const propertyName = propertyNames.shift()
×
UNCOV
574
                if (propertyName) {
×
UNCOV
575
                    map[propertyName] = {}
×
UNCOV
576
                    extractEmbeddedColumnValue(propertyNames, map[propertyName])
×
UNCOV
577
                    return map
×
578
                }
579

580
                // this is bugfix for #720 when increment number is bigint we need to make sure its a string
UNCOV
581
                if (
×
582
                    (this.generationStrategy === "increment" ||
×
583
                        this.generationStrategy === "rowid") &&
584
                    this.type === "bigint" &&
585
                    value !== null
586
                )
587
                    value = String(value)
×
588

UNCOV
589
                map[useDatabaseName ? this.databaseName : this.propertyName] =
×
590
                    value
UNCOV
591
                return map
×
592
            }
UNCOV
593
            return extractEmbeddedColumnValue(propertyNames, {})
×
594
        } else {
595
            // no embeds - no problems. Simply return column property name and its value of the entity
596

597
            // this is bugfix for #720 when increment number is bigint we need to make sure its a string
UNCOV
598
            if (
×
599
                (this.generationStrategy === "increment" ||
×
600
                    this.generationStrategy === "rowid") &&
601
                this.type === "bigint" &&
602
                value !== null
603
            )
UNCOV
604
                value = String(value)
×
605

UNCOV
606
            return {
×
607
                [useDatabaseName ? this.databaseName : this.propertyName]:
×
608
                    value,
609
            }
610
        }
611
    }
612

613
    /**
614
     * Extracts column value and returns its column name with this value in a literal object.
615
     * If column is in embedded (or recursive embedded) it returns complex literal object.
616
     *
617
     * Examples what this method can return depend if this column is in embeds.
618
     * { id: 1 } or { title: "hello" }, { counters: { code: 1 } }, { data: { information: { counters: { code: 1 } } } }
619
     */
620
    getEntityValueMap(
621
        entity: ObjectLiteral,
622
        options?: { skipNulls?: boolean },
623
    ): ObjectLiteral | undefined {
UNCOV
624
        const returnNulls = false // options && options.skipNulls === false ? false : true; // todo: remove if current will not bring problems, uncomment if it will.
×
625

626
        // extract column value from embeds of entity if column is in embedded
UNCOV
627
        if (this.embeddedMetadata) {
×
628
            // example: post[data][information][counters].id where "data", "information" and "counters" are embeddeds
629
            // we need to get value of "id" column from the post real entity object and return it in a
630
            // { data: { information: { counters: { id: ... } } } } format
631

632
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
UNCOV
633
            const propertyNames = [...this.embeddedMetadata.parentPropertyNames]
×
UNCOV
634
            const isEmbeddedArray = this.embeddedMetadata.isArray
×
635

636
            // now need to access post[data][information][counters] to get column value from the counters
637
            // and on each step we need to create complex literal object, e.g. first { data },
638
            // then { data: { information } }, then { data: { information: { counters } } },
639
            // then { data: { information: { counters: [this.propertyName]: entity[data][information][counters][this.propertyName] } } }
640
            // this recursive function helps doing that
UNCOV
641
            const extractEmbeddedColumnValue = (
×
642
                propertyNames: string[],
643
                value: ObjectLiteral,
644
            ): ObjectLiteral => {
UNCOV
645
                if (value === undefined) {
×
UNCOV
646
                    return {}
×
647
                }
648

UNCOV
649
                const propertyName = propertyNames.shift()
×
650

UNCOV
651
                if (propertyName) {
×
UNCOV
652
                    const submap = extractEmbeddedColumnValue(
×
653
                        propertyNames,
654
                        value[propertyName],
655
                    )
UNCOV
656
                    if (Object.keys(submap).length > 0) {
×
UNCOV
657
                        return { [propertyName]: submap }
×
658
                    }
UNCOV
659
                    return {}
×
660
                }
661

UNCOV
662
                if (isEmbeddedArray && Array.isArray(value)) {
×
UNCOV
663
                    return value.map((v) => ({
×
664
                        [this.propertyName]: v[this.propertyName],
665
                    }))
666
                }
667

UNCOV
668
                if (
×
669
                    value[this.propertyName] !== undefined &&
×
670
                    (returnNulls === false || value[this.propertyName] !== null)
671
                ) {
UNCOV
672
                    return { [this.propertyName]: value[this.propertyName] }
×
673
                }
674

UNCOV
675
                return {}
×
676
            }
UNCOV
677
            const map = extractEmbeddedColumnValue(propertyNames, entity)
×
678

UNCOV
679
            return Object.keys(map).length > 0 ? map : undefined
×
680
        } else {
681
            // no embeds - no problems. Simply return column property name and its value of the entity
682
            /**
683
             * Object.getOwnPropertyDescriptor checks if the relation is lazy, in which case value is a Promise
684
             * DO NOT use `entity[
685
                this.relationMetadata.propertyName] instanceof Promise`, which will invoke property getter and make unwanted DB request
686
             * refer: https://github.com/typeorm/typeorm/pull/8676#issuecomment-1049906331
687
             */
UNCOV
688
            if (
×
689
                this.relationMetadata &&
×
690
                !Object.getOwnPropertyDescriptor(
691
                    entity,
692
                    this.relationMetadata.propertyName,
693
                )?.get &&
694
                entity[this.relationMetadata.propertyName] &&
695
                ObjectUtils.isObject(entity[this.relationMetadata.propertyName])
696
            ) {
UNCOV
697
                if (this.relationMetadata.joinColumns.length > 1) {
×
UNCOV
698
                    const map = this.relationMetadata.joinColumns.reduce(
×
699
                        (map, joinColumn) => {
700
                            const value =
UNCOV
701
                                joinColumn.referencedColumn!.getEntityValueMap(
×
702
                                    entity[this.relationMetadata!.propertyName],
703
                                )
UNCOV
704
                            if (value === undefined) return map
×
UNCOV
705
                            return OrmUtils.mergeDeep(map, value)
×
706
                        },
707
                        {},
708
                    )
UNCOV
709
                    if (Object.keys(map).length > 0)
×
UNCOV
710
                        return { [this.propertyName]: map }
×
711
                } else {
712
                    const value =
UNCOV
713
                        this.relationMetadata.joinColumns[0].referencedColumn!.getEntityValue(
×
714
                            entity[this.relationMetadata!.propertyName],
715
                        )
UNCOV
716
                    if (value) {
×
UNCOV
717
                        return { [this.propertyName]: value }
×
718
                    }
719
                }
720

UNCOV
721
                return undefined
×
722
            } else {
UNCOV
723
                if (
×
724
                    entity[this.propertyName] !== undefined &&
×
725
                    (returnNulls === false ||
726
                        entity[this.propertyName] !== null)
727
                ) {
UNCOV
728
                    return { [this.propertyName]: entity[this.propertyName] }
×
729
                }
730

UNCOV
731
                return undefined
×
732
            }
733
        }
734
    }
735

736
    /**
737
     * Extracts column value from the given entity.
738
     * If column is in embedded (or recursive embedded) it extracts its value from there.
739
     */
740
    getEntityValue(
741
        entity: ObjectLiteral,
742
        transform: boolean = false,
×
743
    ): any | undefined {
UNCOV
744
        if (entity === undefined || entity === null) return undefined
×
745

746
        // extract column value from embeddeds of entity if column is in embedded
UNCOV
747
        let value: any = undefined
×
UNCOV
748
        if (this.embeddedMetadata) {
×
749
            // example: post[data][information][counters].id where "data", "information" and "counters" are embeddeds
750
            // we need to get value of "id" column from the post real entity object
751

752
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
UNCOV
753
            const propertyNames = [...this.embeddedMetadata.parentPropertyNames]
×
UNCOV
754
            const isEmbeddedArray = this.embeddedMetadata.isArray
×
755

756
            // next we need to access post[data][information][counters][this.propertyName] to get column value from the counters
757
            // this recursive function takes array of generated property names and gets the post[data][information][counters] embed
UNCOV
758
            const extractEmbeddedColumnValue = (
×
759
                propertyNames: string[],
760
                value: ObjectLiteral,
761
            ): any => {
UNCOV
762
                const propertyName = propertyNames.shift()
×
UNCOV
763
                return propertyName && value
×
764
                    ? extractEmbeddedColumnValue(
765
                          propertyNames,
766
                          value[propertyName],
767
                      )
768
                    : value
769
            }
770

771
            // once we get nested embed object we get its column, e.g. post[data][information][counters][this.propertyName]
UNCOV
772
            const embeddedObject = extractEmbeddedColumnValue(
×
773
                propertyNames,
774
                entity,
775
            )
UNCOV
776
            if (embeddedObject) {
×
UNCOV
777
                if (this.relationMetadata && this.referencedColumn) {
×
778
                    const relatedEntity =
UNCOV
779
                        this.relationMetadata.getEntityValue(embeddedObject)
×
UNCOV
780
                    if (
×
781
                        relatedEntity &&
×
782
                        ObjectUtils.isObject(relatedEntity) &&
783
                        !InstanceChecker.isFindOperator(relatedEntity) &&
784
                        !Buffer.isBuffer(relatedEntity)
785
                    ) {
786
                        value =
×
787
                            this.referencedColumn.getEntityValue(relatedEntity)
UNCOV
788
                    } else if (
×
789
                        embeddedObject[this.propertyName] &&
×
790
                        ObjectUtils.isObject(
791
                            embeddedObject[this.propertyName],
792
                        ) &&
793
                        !InstanceChecker.isFindOperator(
794
                            embeddedObject[this.propertyName],
795
                        ) &&
796
                        !Buffer.isBuffer(embeddedObject[this.propertyName]) &&
797
                        !(embeddedObject[this.propertyName] instanceof Date)
798
                    ) {
UNCOV
799
                        value = this.referencedColumn.getEntityValue(
×
800
                            embeddedObject[this.propertyName],
801
                        )
802
                    } else {
UNCOV
803
                        value = embeddedObject[this.propertyName]
×
804
                    }
UNCOV
805
                } else if (this.referencedColumn) {
×
806
                    value = this.referencedColumn.getEntityValue(
×
807
                        embeddedObject[this.propertyName],
808
                    )
UNCOV
809
                } else if (isEmbeddedArray && Array.isArray(embeddedObject)) {
×
UNCOV
810
                    value = embeddedObject.map((o) => o[this.propertyName])
×
811
                } else {
UNCOV
812
                    value = embeddedObject[this.propertyName]
×
813
                }
814
            }
815
        } else {
816
            // no embeds - no problems. Simply return column name by property name of the entity
UNCOV
817
            if (this.relationMetadata && this.referencedColumn) {
×
818
                const relatedEntity =
UNCOV
819
                    this.relationMetadata.getEntityValue(entity)
×
UNCOV
820
                if (
×
821
                    relatedEntity &&
×
822
                    ObjectUtils.isObject(relatedEntity) &&
823
                    !InstanceChecker.isFindOperator(relatedEntity) &&
824
                    !(typeof relatedEntity === "function") &&
825
                    !Buffer.isBuffer(relatedEntity)
826
                ) {
UNCOV
827
                    value = this.referencedColumn.getEntityValue(relatedEntity)
×
UNCOV
828
                } else if (
×
829
                    entity[this.propertyName] &&
×
830
                    ObjectUtils.isObject(entity[this.propertyName]) &&
831
                    !InstanceChecker.isFindOperator(
832
                        entity[this.propertyName],
833
                    ) &&
834
                    !(typeof entity[this.propertyName] === "function") &&
835
                    !Buffer.isBuffer(entity[this.propertyName]) &&
836
                    !(entity[this.propertyName] instanceof Date)
837
                ) {
UNCOV
838
                    value = this.referencedColumn.getEntityValue(
×
839
                        entity[this.propertyName],
840
                    )
841
                } else {
UNCOV
842
                    value = entity[this.propertyName]
×
843
                }
UNCOV
844
            } else if (this.referencedColumn) {
×
UNCOV
845
                value = this.referencedColumn.getEntityValue(
×
846
                    entity[this.propertyName],
847
                )
848
            } else {
UNCOV
849
                value = entity[this.propertyName]
×
850
            }
851
        }
852

UNCOV
853
        if (transform && this.transformer)
×
UNCOV
854
            value = ApplyValueTransformers.transformTo(this.transformer, value)
×
855

UNCOV
856
        return value
×
857
    }
858

859
    /**
860
     * Sets given entity's column value.
861
     * Using of this method helps to set entity relation's value of the lazy and non-lazy relations.
862
     */
863
    setEntityValue(entity: ObjectLiteral, value: any): void {
UNCOV
864
        if (this.embeddedMetadata) {
×
865
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
UNCOV
866
            const extractEmbeddedColumnValue = (
×
867
                embeddedMetadatas: EmbeddedMetadata[],
868
                map: ObjectLiteral,
869
            ): any => {
870
                // if (!object[embeddedMetadata.propertyName])
871
                //     object[embeddedMetadata.propertyName] = embeddedMetadata.create();
872

UNCOV
873
                const embeddedMetadata = embeddedMetadatas.shift()
×
UNCOV
874
                if (embeddedMetadata) {
×
UNCOV
875
                    if (!map[embeddedMetadata.propertyName])
×
UNCOV
876
                        map[embeddedMetadata.propertyName] =
×
877
                            embeddedMetadata.create()
878

UNCOV
879
                    extractEmbeddedColumnValue(
×
880
                        embeddedMetadatas,
881
                        map[embeddedMetadata.propertyName],
882
                    )
UNCOV
883
                    return map
×
884
                }
UNCOV
885
                map[this.propertyName] = value
×
UNCOV
886
                return map
×
887
            }
UNCOV
888
            return extractEmbeddedColumnValue(
×
889
                [...this.embeddedMetadata.embeddedMetadataTree],
890
                entity,
891
            )
892
        } else {
893
            // we write a deep object in this entity only if the column is virtual
894
            // because if its not virtual it means the user defined a real column for this relation
895
            // also we don't do it if column is inside a junction table
UNCOV
896
            if (
×
897
                !this.entityMetadata.isJunction &&
×
898
                this.isVirtual &&
899
                this.referencedColumn &&
900
                this.referencedColumn.propertyName !== this.propertyName
901
            ) {
UNCOV
902
                if (!(this.propertyName in entity)) {
×
UNCOV
903
                    entity[this.propertyName] = {}
×
904
                }
905

UNCOV
906
                entity[this.propertyName][this.referencedColumn.propertyName] =
×
907
                    value
908
            } else {
UNCOV
909
                entity[this.propertyName] = value
×
910
            }
911
        }
912
    }
913

914
    /**
915
     * Compares given entity's column value with a given value.
916
     */
917
    compareEntityValue(entity: any, valueToCompareWith: any) {
UNCOV
918
        const columnValue = this.getEntityValue(entity)
×
UNCOV
919
        if (typeof columnValue?.equals === "function") {
×
920
            return columnValue.equals(valueToCompareWith)
×
921
        }
UNCOV
922
        return columnValue === valueToCompareWith
×
923
    }
924

925
    // ---------------------------------------------------------------------
926
    // Builder Methods
927
    // ---------------------------------------------------------------------
928

929
    build(connection: DataSource): this {
UNCOV
930
        this.propertyPath = this.buildPropertyPath()
×
UNCOV
931
        this.propertyAliasName = this.propertyPath.replace(".", "_")
×
UNCOV
932
        this.databaseName = this.buildDatabaseName(connection)
×
UNCOV
933
        this.databasePath = this.buildDatabasePath()
×
UNCOV
934
        this.databaseNameWithoutPrefixes = connection.namingStrategy.columnName(
×
935
            this.propertyName,
936
            this.givenDatabaseName,
937
            [],
938
        )
UNCOV
939
        return this
×
940
    }
941

942
    protected buildPropertyPath(): string {
UNCOV
943
        let path = ""
×
UNCOV
944
        if (
×
945
            this.embeddedMetadata &&
×
946
            this.embeddedMetadata.parentPropertyNames.length
947
        )
UNCOV
948
            path = this.embeddedMetadata.parentPropertyNames.join(".") + "."
×
949

UNCOV
950
        path += this.propertyName
×
951

952
        // we add reference column to property path only if this column is virtual
953
        // because if its not virtual it means user defined a real column for this relation
954
        // also we don't do it if column is inside a junction table
UNCOV
955
        if (
×
956
            !this.entityMetadata.isJunction &&
×
957
            this.isVirtual &&
958
            this.referencedColumn &&
959
            this.referencedColumn.propertyName !== this.propertyName
960
        )
UNCOV
961
            path += "." + this.referencedColumn.propertyName
×
962

UNCOV
963
        return path
×
964
    }
965

966
    protected buildDatabasePath(): string {
UNCOV
967
        let path = ""
×
UNCOV
968
        if (
×
969
            this.embeddedMetadata &&
×
970
            this.embeddedMetadata.parentPropertyNames.length
971
        )
UNCOV
972
            path = this.embeddedMetadata.parentPropertyNames.join(".") + "."
×
973

UNCOV
974
        path += this.databaseName
×
975

976
        // we add reference column to property path only if this column is virtual
977
        // because if its not virtual it means user defined a real column for this relation
978
        // also we don't do it if column is inside a junction table
UNCOV
979
        if (
×
980
            !this.entityMetadata.isJunction &&
×
981
            this.isVirtual &&
982
            this.referencedColumn &&
983
            this.referencedColumn.databaseName !== this.databaseName
984
        )
UNCOV
985
            path += "." + this.referencedColumn.databaseName
×
986

UNCOV
987
        return path
×
988
    }
989

990
    protected buildDatabaseName(connection: DataSource): string {
UNCOV
991
        let propertyNames = this.embeddedMetadata
×
992
            ? this.embeddedMetadata.parentPrefixes
993
            : []
UNCOV
994
        if (connection.driver.options.type === "mongodb")
×
995
            // we don't need to include embedded name for the mongodb column names
UNCOV
996
            propertyNames = []
×
UNCOV
997
        return connection.namingStrategy.columnName(
×
998
            this.propertyName,
999
            this.givenDatabaseName,
1000
            propertyNames,
1001
        )
1002
    }
1003
}
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