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

typeorm / typeorm / 14796576772

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

Pull #11434

github

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

5216 of 12761 branches covered (40.87%)

Branch coverage included in aggregate %.

11439 of 23951 relevant lines covered (47.76%)

15712.55 hits per line

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

81.52
/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"
4✔
7

8
/**
9
 * Index metadata contains all information about table's index.
10
 */
11
export class IndexMetadata {
4✔
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
1,765✔
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
1,765✔
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
1,765✔
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
1,765✔
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
1,765✔
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[] = []
1,765✔
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 } = {}
1,765✔
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
1,765✔
139
        this.embeddedMetadata = options.embeddedMetadata
1,765✔
140
        if (options.columns) this.columns = options.columns
1,765✔
141

142
        if (options.args) {
1,765✔
143
            this.target = options.args.target
1,765✔
144
            if (
1,765✔
145
                options.args.synchronize !== null &&
3,530✔
146
                options.args.synchronize !== undefined
147
            )
148
                this.synchronize = options.args.synchronize
1,481✔
149
            this.isUnique = !!options.args.unique
1,765✔
150
            this.isSpatial = !!options.args.spatial
1,765✔
151
            this.isFulltext = !!options.args.fulltext
1,765✔
152
            this.isNullFiltered = !!options.args.nullFiltered
1,765✔
153
            this.parser = options.args.parser
1,765✔
154
            this.where = options.args.where
1,765✔
155
            this.isSparse = options.args.sparse
1,765✔
156
            this.isBackground = options.args.background
1,765✔
157
            this.isConcurrent = options.args.concurrent
1,765✔
158
            this.expireAfterSeconds = options.args.expireAfterSeconds
1,765✔
159
            this.givenName = options.args.name
1,765✔
160
            this.givenColumnNames = options.args.columns
1,765✔
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) {
1,765✔
174
            this.name = this.givenName!
36✔
175
            return this
36✔
176
        }
177

178
        const map: { [key: string]: number } = {}
1,729✔
179

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

193
                        return columnName.trim()
399✔
194
                    },
195
                )
196
                columnPropertyPaths.forEach(
393✔
197
                    (propertyPath) => (map[propertyPath] = 1),
427✔
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(
8✔
203
                    this.entityMetadata.propertiesMap,
204
                )
205
                if (Array.isArray(columnsFnResult)) {
8!
206
                    columnPropertyPaths = columnsFnResult.map((i: any) =>
8✔
207
                        String(i),
12✔
208
                    )
209
                    columnPropertyPaths.forEach((name) => (map[name] = 1))
12✔
210
                } else {
211
                    columnPropertyPaths = Object.keys(columnsFnResult).map(
×
212
                        (i: any) => String(i),
×
213
                    )
214
                    Object.keys(columnsFnResult).forEach(
×
215
                        (columnName) =>
216
                            (map[columnName] = columnsFnResult[columnName]),
×
217
                    )
218
                }
219
            }
220

221
            this.columns = columnPropertyPaths
401✔
222
                .map((propertyPath) => {
223
                    const columnWithSameName = this.entityMetadata.columns.find(
439✔
224
                        (column) => column.propertyPath === propertyPath,
1,316✔
225
                    )
226
                    if (columnWithSameName) {
439✔
227
                        return [columnWithSameName]
439✔
228
                    }
229
                    const relationWithSameName =
230
                        this.entityMetadata.relations.find(
×
231
                            (relation) =>
232
                                relation.isWithJoinColumn &&
×
233
                                relation.propertyName === propertyPath,
234
                        )
235
                    if (relationWithSameName) {
×
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))
38✔
248
        }
249

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

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

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