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

typeorm / typeorm / 15219332477

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

Pull #11332

github

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

1603 of 12759 branches covered (12.56%)

Branch coverage included in aggregate %.

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

14132 existing lines in 166 files now uncovered.

4731 of 24033 relevant lines covered (19.69%)

60.22 hits per line

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

79.35
/src/metadata/IndexMetadata.ts
1
import { EntityMetadata } from "./EntityMetadata"
2
import { IndexMetadataArgs } from "../metadata-args/IndexMetadataArgs"
3
import { NamingStrategyInterface } from "../naming-strategy/NamingStrategyInterface"
4
import { ColumnMetadata } from "./ColumnMetadata"
5
import { EmbeddedMetadata } from "./EmbeddedMetadata"
6
import { TypeORMError } from "../error"
1✔
7

8
/**
9
 * Index metadata contains all information about table's index.
10
 */
11
export class IndexMetadata {
1✔
12
    // ---------------------------------------------------------------------
13
    // Public Properties
14
    // ---------------------------------------------------------------------
15

16
    /**
17
     * Entity metadata of the class to which this index is applied.
18
     */
19
    entityMetadata: EntityMetadata
20

21
    /**
22
     * Embedded metadata if this index was applied on embedded.
23
     */
24
    embeddedMetadata?: EmbeddedMetadata
25

26
    /**
27
     * Indicates if this index must be unique.
28
     */
29
    isUnique: boolean = false
22✔
30

31
    /**
32
     * The SPATIAL modifier indexes the entire column and does not allow indexed columns to contain NULL values.
33
     * Works only in MySQL.
34
     */
35
    isSpatial: boolean = false
22✔
36

37
    /**
38
     * The FULLTEXT modifier indexes the entire column and does not allow prefixing.
39
     * Works only in MySQL.
40
     */
41
    isFulltext: boolean = false
22✔
42

43
    /**
44
     * NULL_FILTERED indexes are particularly useful for indexing sparse columns, where most rows contain a NULL value.
45
     * In these cases, the NULL_FILTERED index can be considerably smaller and more efficient to maintain than
46
     * a normal index that includes NULL values.
47
     *
48
     * Works only in Spanner.
49
     */
50
    isNullFiltered: boolean = false
22✔
51

52
    /**
53
     * Fulltext parser.
54
     * Works only in MySQL.
55
     */
56
    parser?: string
57

58
    /**
59
     * Indicates if this index must synchronize with database index.
60
     */
61
    synchronize: boolean = true
22✔
62

63
    /**
64
     * If true, the index only references documents with the specified field.
65
     * These indexes use less space but behave differently in some situations (particularly sorts).
66
     * This option is only supported for mongodb database.
67
     */
68
    isSparse?: boolean
69

70
    /**
71
     * Builds the index in the background so that building an index an does not block other database activities.
72
     * This option is only supported for mongodb database.
73
     */
74
    isBackground?: boolean
75

76
    /**
77
     * Builds the index using the concurrently option.
78
     * This options is only supported for postgres database.
79
     */
80
    isConcurrent?: boolean
81

82
    /**
83
     * Specifies a time to live, in seconds.
84
     * This option is only supported for mongodb database.
85
     */
86
    expireAfterSeconds?: number
87

88
    /**
89
     * Target class to which metadata is applied.
90
     */
91
    target?: Function | string
92

93
    /**
94
     * Indexed columns.
95
     */
96
    columns: ColumnMetadata[] = []
22✔
97

98
    /**
99
     * User specified index name.
100
     */
101
    givenName?: string
102

103
    /**
104
     * User specified column names.
105
     */
106
    givenColumnNames?:
107
        | ((object?: any) => any[] | { [key: string]: number })
108
        | string[]
109

110
    /**
111
     * Final index name.
112
     * If index name was given by a user then it stores normalized (by naming strategy) givenName.
113
     * If index name was not given then its generated.
114
     */
115
    name: string
116

117
    /**
118
     * Index filter condition.
119
     */
120
    where?: string
121

122
    /**
123
     * Map of column names with order set.
124
     * Used only by MongoDB driver.
125
     */
126
    columnNamesWithOrderingMap: { [key: string]: number } = {}
22✔
127

128
    // ---------------------------------------------------------------------
129
    // Constructor
130
    // ---------------------------------------------------------------------
131

132
    constructor(options: {
133
        entityMetadata: EntityMetadata
134
        embeddedMetadata?: EmbeddedMetadata
135
        columns?: ColumnMetadata[]
136
        args?: IndexMetadataArgs
137
    }) {
138
        this.entityMetadata = options.entityMetadata
22✔
139
        this.embeddedMetadata = options.embeddedMetadata
22✔
140
        if (options.columns) this.columns = options.columns
22✔
141

142
        if (options.args) {
22✔
143
            this.target = options.args.target
22✔
144
            if (
22✔
145
                options.args.synchronize !== null &&
44✔
146
                options.args.synchronize !== undefined
147
            )
148
                this.synchronize = options.args.synchronize
22✔
149
            this.isUnique = !!options.args.unique
22✔
150
            this.isSpatial = !!options.args.spatial
22✔
151
            this.isFulltext = !!options.args.fulltext
22✔
152
            this.isNullFiltered = !!options.args.nullFiltered
22✔
153
            this.parser = options.args.parser
22✔
154
            this.where = options.args.where
22✔
155
            this.isSparse = options.args.sparse
22✔
156
            this.isBackground = options.args.background
22✔
157
            this.isConcurrent = options.args.concurrent
22✔
158
            this.expireAfterSeconds = options.args.expireAfterSeconds
22✔
159
            this.givenName = options.args.name
22✔
160
            this.givenColumnNames = options.args.columns
22✔
161
        }
162
    }
163

164
    // ---------------------------------------------------------------------
165
    // Public Build Methods
166
    // ---------------------------------------------------------------------
167

168
    /**
169
     * Builds some depend index properties.
170
     * Must be called after all entity metadata's properties map, columns and relations are built.
171
     */
172
    build(namingStrategy: NamingStrategyInterface): this {
173
        if (this.synchronize === false) {
22!
UNCOV
174
            this.name = this.givenName!
×
UNCOV
175
            return this
×
176
        }
177

178
        const map: { [key: string]: number } = {}
22✔
179

180
        // if columns already an array of string then simply return it
181
        if (this.givenColumnNames) {
22✔
182
            let columnPropertyPaths: string[] = []
11✔
183
            if (Array.isArray(this.givenColumnNames)) {
11✔
184
                columnPropertyPaths = this.givenColumnNames.map(
6✔
185
                    (columnName) => {
186
                        if (this.embeddedMetadata)
7✔
187
                            return (
1✔
188
                                this.embeddedMetadata.propertyPath +
189
                                "." +
190
                                columnName
191
                            )
192

193
                        return columnName.trim()
6✔
194
                    },
195
                )
196
                columnPropertyPaths.forEach(
6✔
197
                    (propertyPath) => (map[propertyPath] = 1),
7✔
198
                )
199
            } else {
200
                // todo: indices in embeds are not implemented in this syntax. deprecate this syntax?
201
                // if columns is a function that returns array of field names then execute it and get columns names from it
202
                const columnsFnResult = this.givenColumnNames(
5✔
203
                    this.entityMetadata.propertiesMap,
204
                )
205
                if (Array.isArray(columnsFnResult)) {
5!
UNCOV
206
                    columnPropertyPaths = columnsFnResult.map((i: any) =>
×
UNCOV
207
                        String(i),
×
208
                    )
UNCOV
209
                    columnPropertyPaths.forEach((name) => (map[name] = 1))
×
210
                } else {
211
                    columnPropertyPaths = Object.keys(columnsFnResult).map(
5✔
212
                        (i: any) => String(i),
11✔
213
                    )
214
                    Object.keys(columnsFnResult).forEach(
5✔
215
                        (columnName) =>
216
                            (map[columnName] = columnsFnResult[columnName]),
11✔
217
                    )
218
                }
219
            }
220

221
            this.columns = columnPropertyPaths
11✔
222
                .map((propertyPath) => {
223
                    const columnWithSameName = this.entityMetadata.columns.find(
18✔
224
                        (column) => column.propertyPath === propertyPath,
56✔
225
                    )
226
                    if (columnWithSameName) {
18✔
227
                        return [columnWithSameName]
18✔
228
                    }
229
                    const relationWithSameName =
UNCOV
230
                        this.entityMetadata.relations.find(
×
231
                            (relation) =>
UNCOV
232
                                relation.isWithJoinColumn &&
×
233
                                relation.propertyName === propertyPath,
234
                        )
UNCOV
235
                    if (relationWithSameName) {
×
UNCOV
236
                        return relationWithSameName.joinColumns
×
237
                    }
238
                    const indexName = this.givenName
×
239
                        ? '"' + this.givenName + '" '
240
                        : ""
241
                    const entityName = this.entityMetadata.targetName
×
242
                    throw new TypeORMError(
×
243
                        `Index ${indexName}contains column that is missing in the entity (${entityName}): ` +
244
                            propertyPath,
245
                    )
246
                })
247
                .reduce((a, b) => a.concat(b))
7✔
248
        }
249

250
        this.columnNamesWithOrderingMap = Object.keys(map).reduce(
22✔
251
            (updatedMap, key) => {
252
                const column = this.entityMetadata.columns.find(
18✔
253
                    (column) => column.propertyPath === key,
56✔
254
                )
255
                if (column) updatedMap[column.databasePath] = map[key]
18✔
256

257
                return updatedMap
18✔
258
            },
259
            {} as { [key: string]: number },
260
        )
261

262
        this.name = this.givenName
22✔
263
            ? this.givenName
264
            : namingStrategy.indexName(
265
                  this.entityMetadata.tableName,
266
                  this.columns.map((column) => column.databaseName),
12✔
267
                  this.where,
268
              )
269
        return this
22✔
270
    }
271
}
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