• 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

80.83
/src/util/OrmUtils.ts
1
import { ObjectLiteral } from "../common/ObjectLiteral"
2
import {
3
    PrimitiveCriteria,
4
    SinglePrimitiveCriteria,
5
} from "../common/PrimitiveCriteria"
6

7
export class OrmUtils {
6✔
8
    // -------------------------------------------------------------------------
9
    // Public methods
10
    // -------------------------------------------------------------------------
11

12
    /**
13
     * Chunks array into pieces.
14
     */
15
    public static chunk<T>(array: T[], size: number): T[][] {
16
        return Array.from(Array(Math.ceil(array.length / size)), (_, i) => {
1✔
17
            return array.slice(i * size, i * size + size)
5✔
18
        })
19
    }
20

21
    public static splitClassesAndStrings<T>(
22
        classesAndStrings: (string | T)[],
23
    ): [T[], string[]] {
24
        return [
8,778✔
25
            classesAndStrings.filter(
26
                (cls): cls is T => typeof cls !== "string",
3,722✔
27
            ),
28
            classesAndStrings.filter(
29
                (str): str is string => typeof str === "string",
3,722✔
30
            ),
31
        ]
32
    }
33

34
    public static groupBy<T, R>(
35
        array: T[],
36
        propertyCallback: (item: T) => R,
37
    ): { id: R; items: T[] }[] {
38
        return array.reduce((groupedArray, value) => {
×
39
            const key = propertyCallback(value)
×
40
            let grouped = groupedArray.find((i) => i.id === key)
×
41
            if (!grouped) {
×
42
                grouped = { id: key, items: [] }
×
43
                groupedArray.push(grouped)
×
44
            }
45
            grouped.items.push(value)
×
46
            return groupedArray
×
47
        }, [] as Array<{ id: R; items: T[] }>)
48
    }
49

50
    public static uniq<T>(array: T[], criteria?: (item: T) => any): T[]
51
    public static uniq<T, K extends keyof T>(array: T[], property: K): T[]
52
    public static uniq<T, K extends keyof T>(
53
        array: T[],
54
        criteriaOrProperty?: ((item: T) => any) | K,
55
    ): T[] {
56
        return array.reduce((uniqueArray, item) => {
6,140✔
57
            let found: boolean = false
3,530✔
58
            if (typeof criteriaOrProperty === "function") {
3,530!
59
                const itemValue = criteriaOrProperty(item)
3,530✔
60
                found = !!uniqueArray.find(
3,530✔
61
                    (uniqueItem) =>
62
                        criteriaOrProperty(uniqueItem) === itemValue,
2,505✔
63
                )
64
            } else if (typeof criteriaOrProperty === "string") {
×
65
                found = !!uniqueArray.find(
×
66
                    (uniqueItem) =>
67
                        uniqueItem[criteriaOrProperty] ===
×
68
                        item[criteriaOrProperty],
69
                )
70
            } else {
71
                found = uniqueArray.indexOf(item) !== -1
×
72
            }
73

74
            if (!found) uniqueArray.push(item)
3,530✔
75

76
            return uniqueArray
3,530✔
77
        }, [] as T[])
78
    }
79

80
    /**
81
     * Deep Object.assign.
82
     */
83
    public static mergeDeep(target: any, ...sources: any[]): any {
84
        if (!sources.length) {
669,778!
85
            return target
×
86
        }
87

88
        for (const source of sources) {
669,778✔
89
            OrmUtils.merge(target, source)
670,395✔
90
        }
91

92
        return target
669,778✔
93
    }
94

95
    /**
96
     * Deep compare objects.
97
     *
98
     * @see http://stackoverflow.com/a/1144249
99
     */
100
    public static deepCompare(...args: any[]): boolean {
101
        let i: any, l: any, leftChain: any, rightChain: any
102

103
        if (arguments.length < 1) {
769!
104
            return true // Die silently? Don't know how to handle such case, please help...
×
105
            // throw "Need two or more arguments to compare";
106
        }
107

108
        for (i = 1, l = arguments.length; i < l; i++) {
769✔
109
            leftChain = [] // Todo: this can be cached
769✔
110
            rightChain = []
769✔
111

112
            if (
769✔
113
                !this.compare2Objects(
114
                    leftChain,
115
                    rightChain,
116
                    arguments[0],
117
                    arguments[i],
118
                )
119
            ) {
120
                return false
273✔
121
            }
122
        }
123

124
        return true
496✔
125
    }
126

127
    /**
128
     * Gets deeper value of object.
129
     */
130
    public static deepValue(obj: ObjectLiteral, path: string) {
131
        const segments = path.split(".")
75✔
132
        for (let i = 0, len = segments.length; i < len; i++) {
75✔
133
            obj = obj[segments[i]]
75✔
134
        }
135
        return obj
75✔
136
    }
137

138
    public static replaceEmptyObjectsWithBooleans(obj: any) {
139
        for (const key in obj) {
36✔
140
            if (obj[key] && typeof obj[key] === "object") {
36✔
141
                if (Object.keys(obj[key]).length === 0) {
36✔
142
                    obj[key] = true
30✔
143
                } else {
144
                    this.replaceEmptyObjectsWithBooleans(obj[key])
6✔
145
                }
146
            }
147
        }
148
    }
149

150
    public static propertyPathsToTruthyObject(paths: string[]) {
151
        const obj: any = {}
30✔
152
        for (const path of paths) {
30✔
153
            const props = path.split(".")
36✔
154
            if (!props.length) continue
36!
155

156
            if (!obj[props[0]] || obj[props[0]] === true) {
36✔
157
                obj[props[0]] = {}
30✔
158
            }
159
            let recursiveChild = obj[props[0]]
36✔
160
            for (const [key, prop] of props.entries()) {
36✔
161
                if (key === 0) continue
42✔
162

163
                if (recursiveChild[prop]) {
6!
164
                    recursiveChild = recursiveChild[prop]
×
165
                } else if (key === props.length - 1) {
6!
166
                    recursiveChild[prop] = {}
6✔
167
                    recursiveChild = null
6✔
168
                } else {
169
                    recursiveChild[prop] = {}
×
170
                    recursiveChild = recursiveChild[prop]
×
171
                }
172
            }
173
        }
174
        this.replaceEmptyObjectsWithBooleans(obj)
30✔
175
        return obj
30✔
176
    }
177

178
    /**
179
     * Check if two entity-id-maps are the same
180
     */
181
    public static compareIds(
182
        firstId: ObjectLiteral | undefined,
183
        secondId: ObjectLiteral | undefined,
184
    ): boolean {
185
        if (
3,435✔
186
            firstId === undefined ||
13,519✔
187
            firstId === null ||
188
            secondId === undefined ||
189
            secondId === null
190
        )
191
            return false
129✔
192

193
        // Optimized version for the common case
194
        if (
3,306✔
195
            ((typeof firstId.id === "string" &&
14,830✔
196
                typeof secondId.id === "string") ||
197
                (typeof firstId.id === "number" &&
198
                    typeof secondId.id === "number")) &&
199
            Object.keys(firstId).length === 1 &&
200
            Object.keys(secondId).length === 1
201
        ) {
202
            return firstId.id === secondId.id
2,579✔
203
        }
204

205
        return OrmUtils.deepCompare(firstId, secondId)
727✔
206
    }
207

208
    /**
209
     * Transforms given value into boolean value.
210
     */
211
    public static toBoolean(value: any): boolean {
212
        if (typeof value === "boolean") return value
54!
213

214
        if (typeof value === "string") return value === "true" || value === "1"
54!
215

216
        if (typeof value === "number") return value > 0
54!
217

218
        return false
54✔
219
    }
220

221
    /**
222
     * Composes an object from the given array of keys and values.
223
     */
224
    public static zipObject(keys: any[], values: any[]): ObjectLiteral {
225
        return keys.reduce((object, column, index) => {
×
226
            object[column] = values[index]
×
227
            return object
×
228
        }, {} as ObjectLiteral)
229
    }
230

231
    /**
232
     * Compares two arrays.
233
     */
234
    public static isArraysEqual(arr1: any[], arr2: any[]): boolean {
235
        if (arr1.length !== arr2.length) return false
343✔
236
        return arr1.every((element) => {
338✔
237
            return arr2.indexOf(element) !== -1
1,072✔
238
        })
239
    }
240

241
    public static areMutuallyExclusive<T>(...lists: T[][]): boolean {
242
        const haveSharedObjects = lists.some((list) => {
×
243
            const otherLists = lists.filter((otherList) => otherList !== list)
×
244
            return list.some((item) =>
×
245
                otherLists.some((otherList) => otherList.includes(item)),
×
246
            )
247
        })
248
        return !haveSharedObjects
×
249
    }
250

251
    /**
252
     * Parses the CHECK constraint on the specified column and returns
253
     * all values allowed by the constraint or undefined if the constraint
254
     * is not present.
255
     */
256
    public static parseSqlCheckExpression(
257
        sql: string,
258
        columnName: string,
259
    ): string[] | undefined {
260
        const enumMatch = sql.match(
3,280✔
261
            new RegExp(
262
                `"${columnName}" varchar CHECK\\s*\\(\\s*"${columnName}"\\s+IN\\s*`,
263
            ),
264
        )
265

266
        if (enumMatch && enumMatch.index) {
3,280✔
267
            const afterMatch = sql.substring(
33✔
268
                enumMatch.index + enumMatch[0].length,
269
            )
270

271
            // This is an enum
272
            // all enum values stored as a comma separated list
273
            const chars = afterMatch
33✔
274

275
            /**
276
             * * When outside quotes: empty string
277
             * * When inside single quotes: `'`
278
             */
279
            let currentQuotes = ""
33✔
280
            let nextValue = ""
33✔
281
            const enumValues: string[] = []
33✔
282
            for (let idx = 0; idx < chars.length; idx++) {
33✔
283
                const char = chars[idx]
2,577✔
284
                switch (char) {
2,577✔
285
                    case ",":
286
                        if (currentQuotes == "") {
106✔
287
                            enumValues.push(nextValue)
82✔
288
                            nextValue = ""
82✔
289
                        } else {
290
                            nextValue += char
24✔
291
                        }
292
                        break
106✔
293
                    case "'":
294
                        if (currentQuotes == char) {
272✔
295
                            const isNextCharQuote = chars[idx + 1] === char
157✔
296
                            if (isNextCharQuote) {
157✔
297
                                // double quote in sql should be treated as a
298
                                // single quote that's part of the quoted string
299
                                nextValue += char
42✔
300
                                idx += 1 // skip that next quote
42✔
301
                            } else {
302
                                currentQuotes = ""
115✔
303
                            }
304
                        } else {
305
                            currentQuotes = char
115✔
306
                        }
307
                        break
272✔
308
                    case ")":
309
                        if (currentQuotes == "") {
45✔
310
                            enumValues.push(nextValue)
33✔
311
                            return enumValues
33✔
312
                        } else {
313
                            nextValue += char
12✔
314
                        }
315
                        break
12✔
316
                    default:
317
                        if (currentQuotes != "") {
2,154✔
318
                            nextValue += char
357✔
319
                        }
320
                }
321
            }
322
        }
323
        return undefined
3,247✔
324
    }
325

326
    /**
327
     * Checks if given criteria is null or empty.
328
     */
329
    public static isCriteriaNullOrEmpty(criteria: unknown): boolean {
330
        return (
220✔
331
            criteria === undefined ||
1,102✔
332
            criteria === null ||
333
            criteria === "" ||
334
            (Array.isArray(criteria) && criteria.length === 0) ||
335
            (this.isPlainObject(criteria) && Object.keys(criteria).length === 0)
336
        )
337
    }
338

339
    /**
340
     * Checks if given criteria is a primitive value.
341
     * Primitive values are strings, numbers and dates.
342
     */
343
    public static isSinglePrimitiveCriteria(
344
        criteria: unknown,
345
    ): criteria is SinglePrimitiveCriteria {
346
        return (
187✔
347
            typeof criteria === "string" ||
477✔
348
            typeof criteria === "number" ||
349
            criteria instanceof Date
350
        )
351
    }
352

353
    /**
354
     * Checks if given criteria is a primitive value or an array of primitive values.
355
     */
356
    public static isPrimitiveCriteria(
357
        criteria: unknown,
358
    ): criteria is PrimitiveCriteria {
359
        if (Array.isArray(criteria)) {
172✔
360
            return criteria.every((value) =>
43✔
361
                this.isSinglePrimitiveCriteria(value),
58✔
362
            )
363
        }
364

365
        return this.isSinglePrimitiveCriteria(criteria)
129✔
366
    }
367

368
    // -------------------------------------------------------------------------
369
    // Private methods
370
    // -------------------------------------------------------------------------
371

372
    private static compare2Objects(
373
        leftChain: any,
374
        rightChain: any,
375
        x: any,
376
        y: any,
377
    ) {
378
        let p
379

380
        // remember that NaN === NaN returns false
381
        // and isNaN(undefined) returns true
382
        if (Number.isNaN(x) && Number.isNaN(y)) return true
1,020!
383

384
        // Compare primitives and functions.
385
        // Check if both arguments link to the same object.
386
        // Especially useful on the step where we compare prototypes
387
        if (x === y) return true
1,020✔
388

389
        // Unequal, but either is null or undefined (use case: jsonb comparison)
390
        // PR #3776, todo: add tests
391
        if (x === null || y === null || x === undefined || y === undefined)
1,019✔
392
            return false
12✔
393

394
        // Fix the buffer compare bug.
395
        // See: https://github.com/typeorm/typeorm/issues/3654
396
        if (
1,007!
397
            (typeof x.equals === "function" ||
2,014!
398
                typeof x.equals === "function") &&
399
            x.equals(y)
400
        )
UNCOV
401
            return true
×
402

403
        // Works in case when functions are created in constructor.
404
        // Comparing dates is a common scenario. Another built-ins?
405
        // We can even handle functions passed across iframes
406
        if (
1,007✔
407
            (typeof x === "function" && typeof y === "function") ||
5,045!
408
            (x instanceof Date && y instanceof Date) ||
409
            (x instanceof RegExp && y instanceof RegExp) ||
410
            (typeof x === "string" && typeof y === "string") ||
411
            (typeof x === "number" && typeof y === "number")
412
        )
413
            return x.toString() === y.toString()
1✔
414

415
        // At last checking prototypes as good as we can
416
        if (!(typeof x === "object" && typeof y === "object")) return false
1,006✔
417

418
        if (
994!
419
            Object.prototype.isPrototypeOf.call(x, y) ||
1,988✔
420
            Object.prototype.isPrototypeOf.call(y, x)
421
        )
422
            return false
×
423

424
        if (x.constructor !== y.constructor) return false
994!
425

426
        if (x.prototype !== y.prototype) return false
994!
427

428
        // Check for infinitive linking loops
429
        if (leftChain.indexOf(x) > -1 || rightChain.indexOf(y) > -1)
994!
430
            return false
×
431

432
        // Quick checking of one object being a subset of another.
433
        // todo: cache the structure of arguments[0] for performance
434
        for (p in y) {
994✔
435
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
1,799✔
436
                return false
1✔
437
            } else if (typeof y[p] !== typeof x[p]) {
1,798✔
438
                return false
32✔
439
            }
440
        }
441

442
        for (p in x) {
961✔
443
            if (y.hasOwnProperty(p) !== x.hasOwnProperty(p)) {
1,711!
444
                return false
×
445
            } else if (typeof y[p] !== typeof x[p]) {
1,711!
446
                return false
×
447
            }
448

449
            switch (typeof x[p]) {
1,711✔
450
                case "object":
451
                case "function":
452
                    leftChain.push(x)
251✔
453
                    rightChain.push(y)
251✔
454

455
                    if (
251!
456
                        !this.compare2Objects(leftChain, rightChain, x[p], y[p])
457
                    ) {
UNCOV
458
                        return false
×
459
                    }
460

461
                    leftChain.pop()
251✔
462
                    rightChain.pop()
251✔
463
                    break
251✔
464

465
                default:
466
                    if (x[p] !== y[p]) {
1,460✔
467
                        return false
216✔
468
                    }
469
                    break
1,244✔
470
            }
471
        }
472

473
        return true
745✔
474
    }
475

476
    // Checks if it's an object made by Object.create(null), {} or new Object()
477
    private static isPlainObject(item: any) {
478
        if (item === null || item === undefined) {
2,273,098✔
479
            return false
3,223✔
480
        }
481

482
        return !item.constructor || item.constructor === Object
2,269,875✔
483
    }
484

485
    private static mergeArrayKey(
486
        target: any,
487
        key: number,
488
        value: any,
489
        memo: Map<any, any>,
490
    ) {
491
        // Have we seen this before?  Prevent infinite recursion.
492
        if (memo.has(value)) {
30,378!
493
            target[key] = memo.get(value)
×
494
            return
×
495
        }
496

497
        if (value instanceof Promise) {
30,378!
498
            // Skip promises entirely.
499
            // This is a hold-over from the old code & is because we don't want to pull in
500
            // the lazy fields.  Ideally we'd remove these promises via another function first
501
            // but for now we have to do it here.
502
            return
×
503
        }
504

505
        if (!this.isPlainObject(value) && !Array.isArray(value)) {
30,378✔
506
            target[key] = value
366✔
507
            return
366✔
508
        }
509

510
        if (!target[key]) {
30,012✔
511
            target[key] = Array.isArray(value) ? [] : {}
30,012✔
512
        }
513

514
        memo.set(value, target[key])
30,012✔
515
        this.merge(target[key], value, memo)
30,012✔
516
        memo.delete(value)
30,012✔
517
    }
518

519
    private static mergeObjectKey(
520
        target: any,
521
        key: string,
522
        value: any,
523
        memo: Map<any, any>,
524
    ) {
525
        // Have we seen this before?  Prevent infinite recursion.
526
        if (memo.has(value)) {
784,911✔
527
            Object.assign(target, { [key]: memo.get(value) })
24✔
528
            return
24✔
529
        }
530

531
        if (value instanceof Promise) {
784,887✔
532
            // Skip promises entirely.
533
            // This is a hold-over from the old code & is because we don't want to pull in
534
            // the lazy fields.  Ideally we'd remove these promises via another function first
535
            // but for now we have to do it here.
536
            return
24✔
537
        }
538

539
        if (!this.isPlainObject(value) && !Array.isArray(value)) {
784,863✔
540
            Object.assign(target, { [key]: value })
751,303✔
541
            return
751,303✔
542
        }
543

544
        if (!target[key]) {
33,560✔
545
            Object.assign(target, { [key]: Array.isArray(value) ? [] : {} })
24,473✔
546
        }
547

548
        memo.set(value, target[key])
33,560✔
549
        this.merge(target[key], value, memo)
33,560✔
550
        memo.delete(value)
33,560✔
551
    }
552

553
    private static merge(
554
        target: any,
555
        source: any,
556
        memo: Map<any, any> = new Map(),
670,395✔
557
    ): any {
558
        if (this.isPlainObject(target) && this.isPlainObject(source)) {
733,967✔
559
            for (const key of Object.keys(source)) {
723,718✔
560
                if (key === "__proto__") continue
784,911!
561
                this.mergeObjectKey(target, key, source[key], memo)
784,911✔
562
            }
563
        }
564

565
        if (Array.isArray(target) && Array.isArray(source)) {
733,967✔
566
            for (let key = 0; key < source.length; key++) {
10,189✔
567
                this.mergeArrayKey(target, key, source[key], memo)
30,378✔
568
            }
569
        }
570
    }
571
}
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