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

typeorm / typeorm / 15089093306

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

Pull #11437

github

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

5836 of 12767 branches covered (45.71%)

Branch coverage included in aggregate %.

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

6283 existing lines in 64 files now uncovered.

12600 of 24025 relevant lines covered (52.45%)

28708.0 hits per line

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

91.9
/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"
6✔
9
import { ValueTransformer } from "../decorator/options/ValueTransformer"
10
import { ApplyValueTransformers } from "../util/ApplyValueTransformers"
6✔
11
import { ObjectUtils } from "../util/ObjectUtils"
6✔
12
import { InstanceChecker } from "../util/InstanceChecker"
6✔
13
import { VirtualColumnOptions } from "../decorator/options/VirtualColumnOptions"
14

15
/**
16
 * This metadata contains all information about entity's column.
17
 */
18
export class ColumnMetadata {
6✔
19
    readonly "@instanceof" = Symbol.for("ColumnMetadata")
22,226✔
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
     */
63
    length: string = ""
22,226✔
64

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

70
    /**
71
     * Vector dimensions. Used only for vector type.
72
     * For example type = "vector" and dimensions = 3 means that we will create a column with type vector(3).
73
     */
74
    dimensions?: number
75

76
    /**
77
     * Defines column character set.
78
     */
79
    charset?: string
80

81
    /**
82
     * Defines column collation.
83
     */
84
    collation?: string
85

86
    /**
87
     * Indicates if this column is a primary key.
88
     */
89
    isPrimary: boolean = false
22,226✔
90

91
    /**
92
     * Indicates if this column is generated (auto increment or generated other way).
93
     */
94
    isGenerated: boolean = false
22,226✔
95

96
    /**
97
     * Indicates if column can contain nulls or not.
98
     */
99
    isNullable: boolean = false
22,226✔
100

101
    /**
102
     * Indicates if column is selected by query builder or not.
103
     */
104
    isSelect: boolean = true
22,226✔
105

106
    /**
107
     * Indicates if column is inserted by default or not.
108
     */
109
    isInsert: boolean = true
22,226✔
110

111
    /**
112
     * Indicates if column allows updates or not.
113
     */
114
    isUpdate: boolean = true
22,226✔
115

116
    /**
117
     * Specifies generation strategy if this column will use auto increment.
118
     */
119
    generationStrategy?: "uuid" | "increment" | "rowid"
120

121
    /**
122
     * Identity column type. Supports only in Postgres 10+.
123
     */
124
    generatedIdentity?: "ALWAYS" | "BY DEFAULT"
125

126
    /**
127
     * Column comment.
128
     * This feature is not supported by all databases.
129
     */
130
    comment?: string
131

132
    /**
133
     * Default database value.
134
     */
135
    default?:
136
        | number
137
        | boolean
138
        | string
139
        | null
140
        | (number | boolean | string)[]
141
        | Record<string, object>
142
        | (() => string)
143

144
    /**
145
     * ON UPDATE trigger. Works only for MySQL.
146
     */
147
    onUpdate?: string
148

149
    /**
150
     * The precision for a decimal (exact numeric) column (applies only for decimal column),
151
     * which is the maximum number of digits that are stored for the values.
152
     */
153
    precision?: number | null
154

155
    /**
156
     * The scale for a decimal (exact numeric) column (applies only for decimal column),
157
     * which represents the number of digits to the right of the decimal point and must not be greater than precision.
158
     */
159
    scale?: number
160

161
    /**
162
     * Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
163
     * If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to the column
164
     */
165
    zerofill: boolean = false
22,226✔
166

167
    /**
168
     * Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
169
     */
170
    unsigned: boolean = false
22,226✔
171

172
    /**
173
     * Array of possible enumerated values.
174
     *
175
     * `postgres` and `mysql` store enum values as strings but we want to keep support
176
     * for numeric and heterogeneous based typescript enums, so we need (string|number)[]
177
     */
178
    enum?: (string | number)[]
179

180
    /**
181
     * Exact name of enum
182
     */
183
    enumName?: string
184

185
    /**
186
     * Generated column expression.
187
     */
188
    asExpression?: string
189

190
    /**
191
     * Generated column type.
192
     */
193
    generatedType?: "VIRTUAL" | "STORED"
194

195
    /**
196
     * Return type of HSTORE column.
197
     * Returns value as string or as object.
198
     */
199
    hstoreType?: "object" | "string"
200

201
    /**
202
     * Indicates if this column is an array.
203
     */
204
    isArray: boolean = false
22,226✔
205

206
    /**
207
     * Gets full path to this column property (including column property name).
208
     * Full path is relevant when column is used in embeds (one or multiple nested).
209
     * For example it will return "counters.subcounters.likes".
210
     * If property is not in embeds then it returns just property name of the column.
211
     */
212
    propertyPath: string
213

214
    /**
215
     * Same as property path, but dots are replaced with '_'.
216
     * Used in query builder statements.
217
     */
218
    propertyAliasName: string
219

220
    /**
221
     * Gets full path to this column database name (including column database name).
222
     * Full path is relevant when column is used in embeds (one or multiple nested).
223
     * For example it will return "counters.subcounters.likes".
224
     * If property is not in embeds then it returns just database name of the column.
225
     */
226
    databasePath: string
227

228
    /**
229
     * Complete column name in the database including its embedded prefixes.
230
     */
231
    databaseName: string
232

233
    /**
234
     * Database name in the database without embedded prefixes applied.
235
     */
236
    databaseNameWithoutPrefixes: string
237

238
    /**
239
     * Database name set by entity metadata builder, not yet passed naming strategy process and without embedded prefixes.
240
     */
241
    givenDatabaseName?: string
242

243
    /**
244
     * Indicates if column is virtual. Virtual columns are not mapped to the entity.
245
     */
246
    isVirtual: boolean = false
22,226✔
247

248
    /**
249
     * Indicates if column is a virtual property. Virtual properties are not mapped to the entity.
250
     * This property is used in tandem the virtual column decorator.
251
     * @See https://typeorm.io/decorator-reference#virtualcolumn for more details.
252
     */
253
    isVirtualProperty: boolean = false
22,226✔
254

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

262
    /**
263
     * Indicates if column is discriminator. Discriminator columns are not mapped to the entity.
264
     */
265
    isDiscriminator: boolean = false
22,226✔
266

267
    /**
268
     * Indicates if column is tree-level column. Tree-level columns are used in closure entities.
269
     */
270
    isTreeLevel: boolean = false
22,226✔
271

272
    /**
273
     * Indicates if this column contains an entity creation date.
274
     */
275
    isCreateDate: boolean = false
22,226✔
276

277
    /**
278
     * Indicates if this column contains an entity update date.
279
     */
280
    isUpdateDate: boolean = false
22,226✔
281

282
    /**
283
     * Indicates if this column contains an entity delete date.
284
     */
285
    isDeleteDate: boolean = false
22,226✔
286

287
    /**
288
     * Indicates if this column contains an entity version.
289
     */
290
    isVersion: boolean = false
22,226✔
291

292
    /**
293
     * Indicates if this column contains an object id.
294
     */
295
    isObjectId: boolean = false
22,226✔
296

297
    /**
298
     * If this column is foreign key then it references some other column,
299
     * and this property will contain reference to this column.
300
     */
301
    referencedColumn: ColumnMetadata | undefined
302

303
    /**
304
     * If this column is primary key then this specifies the name for it.
305
     */
306
    primaryKeyConstraintName?: string
307

308
    /**
309
     * If this column is foreign key then this specifies the name for it.
310
     */
311
    foreignKeyConstraintName?: string
312

313
    /**
314
     * Specifies a value transformer that is to be used to (un)marshal
315
     * this column when reading or writing to the database.
316
     */
317
    transformer?: ValueTransformer | ValueTransformer[]
318

319
    /**
320
     * Column type in the case if this column is in the closure table.
321
     * Column can be ancestor or descendant in the closure tables.
322
     */
323
    closureType?: "ancestor" | "descendant"
324

325
    /**
326
     * Indicates if this column is nested set's left column.
327
     * Used only in tree entities with nested-set type.
328
     */
329
    isNestedSetLeft: boolean = false
22,226✔
330

331
    /**
332
     * Indicates if this column is nested set's right column.
333
     * Used only in tree entities with nested-set type.
334
     */
335
    isNestedSetRight: boolean = false
22,226✔
336

337
    /**
338
     * Indicates if this column is materialized path's path column.
339
     * Used only in tree entities with materialized path type.
340
     */
341
    isMaterializedPath: boolean = false
22,226✔
342

343
    /**
344
     * Spatial Feature Type (Geometry, Point, Polygon, etc.)
345
     */
346
    spatialFeatureType?: string
347

348
    /**
349
     * SRID (Spatial Reference ID (EPSG code))
350
     */
351
    srid?: number
352

353
    // ---------------------------------------------------------------------
354
    // Constructor
355
    // ---------------------------------------------------------------------
356

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

555
    // ---------------------------------------------------------------------
556
    // Public Methods
557
    // ---------------------------------------------------------------------
558

559
    /**
560
     * Creates entity id map from the given entity ids array.
561
     */
562
    createValueMap(value: any, useDatabaseName = false) {
433,202✔
563
        // extract column value from embeds of entity if column is in embedded
564
        if (this.embeddedMetadata) {
433,202✔
565
            // example: post[data][information][counters].id where "data", "information" and "counters" are embeddeds
566
            // we need to get value of "id" column from the post real entity object and return it in a
567
            // { data: { information: { counters: { id: ... } } } } format
568

569
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
570
            const propertyNames = [...this.embeddedMetadata.parentPropertyNames]
8,748✔
571

572
            // now need to access post[data][information][counters] to get column value from the counters
573
            // and on each step we need to create complex literal object, e.g. first { data },
574
            // then { data: { information } }, then { data: { information: { counters } } },
575
            // then { data: { information: { counters: [this.propertyName]: entity[data][information][counters][this.propertyName] } } }
576
            // this recursive function helps doing that
577
            const extractEmbeddedColumnValue = (
8,748✔
578
                propertyNames: string[],
579
                map: ObjectLiteral,
580
            ): any => {
581
                const propertyName = propertyNames.shift()
19,710✔
582
                if (propertyName) {
19,710✔
583
                    map[propertyName] = {}
10,962✔
584
                    extractEmbeddedColumnValue(propertyNames, map[propertyName])
10,962✔
585
                    return map
10,962✔
586
                }
587

588
                // this is bugfix for #720 when increment number is bigint we need to make sure its a string
589
                if (
8,748!
590
                    (this.generationStrategy === "increment" ||
17,496!
591
                        this.generationStrategy === "rowid") &&
592
                    this.type === "bigint" &&
593
                    value !== null
594
                )
595
                    value = String(value)
×
596

597
                map[useDatabaseName ? this.databaseName : this.propertyName] =
8,748!
598
                    value
599
                return map
8,748✔
600
            }
601
            return extractEmbeddedColumnValue(propertyNames, {})
8,748✔
602
        } else {
603
            // no embeds - no problems. Simply return column property name and its value of the entity
604

605
            // this is bugfix for #720 when increment number is bigint we need to make sure its a string
606
            if (
424,454✔
607
                (this.generationStrategy === "increment" ||
848,920✔
608
                    this.generationStrategy === "rowid") &&
609
                this.type === "bigint" &&
610
                value !== null
611
            )
612
                value = String(value)
11✔
613

614
            return {
424,454✔
615
                [useDatabaseName ? this.databaseName : this.propertyName]:
424,454!
616
                    value,
617
            }
618
        }
619
    }
620

621
    /**
622
     * Extracts column value and returns its column name with this value in a literal object.
623
     * If column is in embedded (or recursive embedded) it returns complex literal object.
624
     *
625
     * Examples what this method can return depend if this column is in embeds.
626
     * { id: 1 } or { title: "hello" }, { counters: { code: 1 } }, { data: { information: { counters: { code: 1 } } } }
627
     */
628
    getEntityValueMap(
629
        entity: ObjectLiteral,
630
        options?: { skipNulls?: boolean },
631
    ): ObjectLiteral | undefined {
632
        const returnNulls = false // options && options.skipNulls === false ? false : true; // todo: remove if current will not bring problems, uncomment if it will.
302,462✔
633

634
        // extract column value from embeds of entity if column is in embedded
635
        if (this.embeddedMetadata) {
302,462✔
636
            // example: post[data][information][counters].id where "data", "information" and "counters" are embeddeds
637
            // we need to get value of "id" column from the post real entity object and return it in a
638
            // { data: { information: { counters: { id: ... } } } } format
639

640
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
641
            const propertyNames = [...this.embeddedMetadata.parentPropertyNames]
2,326✔
642
            const isEmbeddedArray = this.embeddedMetadata.isArray
2,326✔
643

644
            // now need to access post[data][information][counters] to get column value from the counters
645
            // and on each step we need to create complex literal object, e.g. first { data },
646
            // then { data: { information } }, then { data: { information: { counters } } },
647
            // then { data: { information: { counters: [this.propertyName]: entity[data][information][counters][this.propertyName] } } }
648
            // this recursive function helps doing that
649
            const extractEmbeddedColumnValue = (
2,326✔
650
                propertyNames: string[],
651
                value: ObjectLiteral,
652
            ): ObjectLiteral => {
653
                if (value === undefined) {
5,714✔
654
                    return {}
36✔
655
                }
656

657
                const propertyName = propertyNames.shift()
5,678✔
658

659
                if (propertyName) {
5,678✔
660
                    const submap = extractEmbeddedColumnValue(
3,388✔
661
                        propertyNames,
662
                        value[propertyName],
663
                    )
664
                    if (Object.keys(submap).length > 0) {
3,388✔
665
                        return { [propertyName]: submap }
3,214✔
666
                    }
667
                    return {}
174✔
668
                }
669

670
                if (isEmbeddedArray && Array.isArray(value)) {
2,290!
UNCOV
671
                    return value.map((v) => ({
×
672
                        [this.propertyName]: v[this.propertyName],
673
                    }))
674
                }
675

676
                if (
2,290✔
677
                    value[this.propertyName] !== undefined &&
4,508!
678
                    (returnNulls === false || value[this.propertyName] !== null)
679
                ) {
680
                    return { [this.propertyName]: value[this.propertyName] }
2,218✔
681
                }
682

683
                return {}
72✔
684
            }
685
            const map = extractEmbeddedColumnValue(propertyNames, entity)
2,326✔
686

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

729
                return undefined
66✔
730
            } else {
731
                if (
299,582✔
732
                    entity[this.propertyName] !== undefined &&
512,252!
733
                    (returnNulls === false ||
734
                        entity[this.propertyName] !== null)
735
                ) {
736
                    return { [this.propertyName]: entity[this.propertyName] }
212,670✔
737
                }
738

739
                return undefined
86,912✔
740
            }
741
        }
742
    }
743

744
    /**
745
     * Extracts column value from the given entity.
746
     * If column is in embedded (or recursive embedded) it extracts its value from there.
747
     */
748
    getEntityValue(
749
        entity: ObjectLiteral,
750
        transform: boolean = false,
1,905,557✔
751
    ): any | undefined {
752
        if (entity === undefined || entity === null) return undefined
1,996,637✔
753

754
        // extract column value from embeddeds of entity if column is in embedded
755
        let value: any = undefined
1,996,472✔
756
        if (this.embeddedMetadata) {
1,996,472✔
757
            // example: post[data][information][counters].id where "data", "information" and "counters" are embeddeds
758
            // we need to get value of "id" column from the post real entity object
759

760
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
761
            const propertyNames = [...this.embeddedMetadata.parentPropertyNames]
32,054✔
762
            const isEmbeddedArray = this.embeddedMetadata.isArray
32,054✔
763

764
            // next we need to access post[data][information][counters][this.propertyName] to get column value from the counters
765
            // this recursive function takes array of generated property names and gets the post[data][information][counters] embed
766
            const extractEmbeddedColumnValue = (
32,054✔
767
                propertyNames: string[],
768
                value: ObjectLiteral,
769
            ): any => {
770
                const propertyName = propertyNames.shift()
68,339✔
771
                return propertyName && value
68,339✔
772
                    ? extractEmbeddedColumnValue(
773
                          propertyNames,
774
                          value[propertyName],
775
                      )
776
                    : value
777
            }
778

779
            // once we get nested embed object we get its column, e.g. post[data][information][counters][this.propertyName]
780
            const embeddedObject = extractEmbeddedColumnValue(
32,054✔
781
                propertyNames,
782
                entity,
783
            )
784
            if (embeddedObject) {
32,054✔
785
                if (this.relationMetadata && this.referencedColumn) {
17,574✔
786
                    const relatedEntity =
787
                        this.relationMetadata.getEntityValue(embeddedObject)
858✔
788
                    if (
858!
789
                        relatedEntity &&
858!
790
                        ObjectUtils.isObject(relatedEntity) &&
791
                        !InstanceChecker.isFindOperator(relatedEntity) &&
792
                        !Buffer.isBuffer(relatedEntity)
793
                    ) {
794
                        value =
×
795
                            this.referencedColumn.getEntityValue(relatedEntity)
796
                    } else if (
858✔
797
                        embeddedObject[this.propertyName] &&
2,946✔
798
                        ObjectUtils.isObject(
799
                            embeddedObject[this.propertyName],
800
                        ) &&
801
                        !InstanceChecker.isFindOperator(
802
                            embeddedObject[this.propertyName],
803
                        ) &&
804
                        !Buffer.isBuffer(embeddedObject[this.propertyName]) &&
805
                        !(embeddedObject[this.propertyName] instanceof Date)
806
                    ) {
807
                        value = this.referencedColumn.getEntityValue(
522✔
808
                            embeddedObject[this.propertyName],
809
                        )
810
                    } else {
811
                        value = embeddedObject[this.propertyName]
336✔
812
                    }
813
                } else if (this.referencedColumn) {
16,716!
814
                    value = this.referencedColumn.getEntityValue(
×
815
                        embeddedObject[this.propertyName],
816
                    )
817
                } else if (isEmbeddedArray && Array.isArray(embeddedObject)) {
16,716!
UNCOV
818
                    value = embeddedObject.map((o) => o[this.propertyName])
×
819
                } else {
820
                    value = embeddedObject[this.propertyName]
16,716✔
821
                }
822
            }
823
        } else {
824
            // no embeds - no problems. Simply return column name by property name of the entity
825
            if (this.relationMetadata && this.referencedColumn) {
1,964,418✔
826
                const relatedEntity =
827
                    this.relationMetadata.getEntityValue(entity)
92,012✔
828
                if (
92,012✔
829
                    relatedEntity &&
124,973✔
830
                    ObjectUtils.isObject(relatedEntity) &&
831
                    !InstanceChecker.isFindOperator(relatedEntity) &&
832
                    !(typeof relatedEntity === "function") &&
833
                    !Buffer.isBuffer(relatedEntity)
834
                ) {
835
                    value = this.referencedColumn.getEntityValue(relatedEntity)
8,234✔
836
                } else if (
83,778✔
837
                    entity[this.propertyName] &&
112,652✔
838
                    ObjectUtils.isObject(entity[this.propertyName]) &&
839
                    !InstanceChecker.isFindOperator(
840
                        entity[this.propertyName],
841
                    ) &&
842
                    !(typeof entity[this.propertyName] === "function") &&
843
                    !Buffer.isBuffer(entity[this.propertyName]) &&
844
                    !(entity[this.propertyName] instanceof Date)
845
                ) {
846
                    value = this.referencedColumn.getEntityValue(
45✔
847
                        entity[this.propertyName],
848
                    )
849
                } else {
850
                    value = entity[this.propertyName]
83,733✔
851
                }
852
            } else if (this.referencedColumn) {
1,872,406✔
853
                value = this.referencedColumn.getEntityValue(
1,562✔
854
                    entity[this.propertyName],
855
                )
856
            } else {
857
                value = entity[this.propertyName]
1,870,844✔
858
            }
859
        }
860

861
        if (transform && this.transformer)
1,996,472✔
862
            value = ApplyValueTransformers.transformTo(this.transformer, value)
111✔
863

864
        return value
1,996,472✔
865
    }
866

867
    /**
868
     * Sets given entity's column value.
869
     * Using of this method helps to set entity relation's value of the lazy and non-lazy relations.
870
     */
871
    setEntityValue(entity: ObjectLiteral, value: any): void {
872
        if (this.embeddedMetadata) {
297,678✔
873
            // first step - we extract all parent properties of the entity relative to this column, e.g. [data, information, counters]
874
            const extractEmbeddedColumnValue = (
10,712✔
875
                embeddedMetadatas: EmbeddedMetadata[],
876
                map: ObjectLiteral,
877
            ): any => {
878
                // if (!object[embeddedMetadata.propertyName])
879
                //     object[embeddedMetadata.propertyName] = embeddedMetadata.create();
880

881
                const embeddedMetadata = embeddedMetadatas.shift()
24,020✔
882
                if (embeddedMetadata) {
24,020✔
883
                    if (!map[embeddedMetadata.propertyName])
13,308✔
884
                        map[embeddedMetadata.propertyName] =
3,980✔
885
                            embeddedMetadata.create()
886

887
                    extractEmbeddedColumnValue(
13,308✔
888
                        embeddedMetadatas,
889
                        map[embeddedMetadata.propertyName],
890
                    )
891
                    return map
13,308✔
892
                }
893
                map[this.propertyName] = value
10,712✔
894
                return map
10,712✔
895
            }
896
            return extractEmbeddedColumnValue(
10,712✔
897
                [...this.embeddedMetadata.embeddedMetadataTree],
898
                entity,
899
            )
900
        } else {
901
            // we write a deep object in this entity only if the column is virtual
902
            // because if its not virtual it means the user defined a real column for this relation
903
            // also we don't do it if column is inside a junction table
904
            if (
286,966✔
905
                !this.entityMetadata.isJunction &&
574,772✔
906
                this.isVirtual &&
907
                this.referencedColumn &&
908
                this.referencedColumn.propertyName !== this.propertyName
909
            ) {
910
                if (!(this.propertyName in entity)) {
150✔
911
                    entity[this.propertyName] = {}
150✔
912
                }
913

914
                entity[this.propertyName][this.referencedColumn.propertyName] =
150✔
915
                    value
916
            } else {
917
                entity[this.propertyName] = value
286,816✔
918
            }
919
        }
920
    }
921

922
    /**
923
     * Compares given entity's column value with a given value.
924
     */
925
    compareEntityValue(entity: any, valueToCompareWith: any) {
926
        const columnValue = this.getEntityValue(entity)
10✔
927
        if (typeof columnValue?.equals === "function") {
10!
928
            return columnValue.equals(valueToCompareWith)
×
929
        }
930
        return columnValue === valueToCompareWith
10✔
931
    }
932

933
    // ---------------------------------------------------------------------
934
    // Builder Methods
935
    // ---------------------------------------------------------------------
936

937
    build(connection: DataSource): this {
938
        this.propertyPath = this.buildPropertyPath()
44,543✔
939
        this.propertyAliasName = this.propertyPath.replace(".", "_")
44,543✔
940
        this.databaseName = this.buildDatabaseName(connection)
44,543✔
941
        this.databasePath = this.buildDatabasePath()
44,543✔
942
        this.databaseNameWithoutPrefixes = connection.namingStrategy.columnName(
44,543✔
943
            this.propertyName,
944
            this.givenDatabaseName,
945
            [],
946
        )
947
        return this
44,543✔
948
    }
949

950
    protected buildPropertyPath(): string {
951
        let path = ""
44,543✔
952
        if (
44,543✔
953
            this.embeddedMetadata &&
47,349✔
954
            this.embeddedMetadata.parentPropertyNames.length
955
        )
956
            path = this.embeddedMetadata.parentPropertyNames.join(".") + "."
2,806✔
957

958
        path += this.propertyName
44,543✔
959

960
        // we add reference column to property path only if this column is virtual
961
        // because if its not virtual it means user defined a real column for this relation
962
        // also we don't do it if column is inside a junction table
963
        if (
44,543✔
964
            !this.entityMetadata.isJunction &&
97,347✔
965
            this.isVirtual &&
966
            this.referencedColumn &&
967
            this.referencedColumn.propertyName !== this.propertyName
968
        )
969
            path += "." + this.referencedColumn.propertyName
4,637✔
970

971
        return path
44,543✔
972
    }
973

974
    protected buildDatabasePath(): string {
975
        let path = ""
44,543✔
976
        if (
44,543✔
977
            this.embeddedMetadata &&
47,349✔
978
            this.embeddedMetadata.parentPropertyNames.length
979
        )
980
            path = this.embeddedMetadata.parentPropertyNames.join(".") + "."
2,806✔
981

982
        path += this.databaseName
44,543✔
983

984
        // we add reference column to property path only if this column is virtual
985
        // because if its not virtual it means user defined a real column for this relation
986
        // also we don't do it if column is inside a junction table
987
        if (
44,543✔
988
            !this.entityMetadata.isJunction &&
97,347✔
989
            this.isVirtual &&
990
            this.referencedColumn &&
991
            this.referencedColumn.databaseName !== this.databaseName
992
        )
993
            path += "." + this.referencedColumn.databaseName
4,637✔
994

995
        return path
44,543✔
996
    }
997

998
    protected buildDatabaseName(connection: DataSource): string {
999
        let propertyNames = this.embeddedMetadata
44,543✔
1000
            ? this.embeddedMetadata.parentPrefixes
1001
            : []
1002
        if (connection.driver.options.type === "mongodb")
44,543!
1003
            // we don't need to include embedded name for the mongodb column names
UNCOV
1004
            propertyNames = []
×
1005
        return connection.namingStrategy.columnName(
44,543✔
1006
            this.propertyName,
1007
            this.givenDatabaseName,
1008
            propertyNames,
1009
        )
1010
    }
1011
}
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