@@ -68,6 +68,62 @@ const modelSourceFromClasses = (models: ModelCtor[], sequelize: Sequelize) => {
68
68
return modelSource ( Array . from ( paths ) , sequelize ) ;
69
69
} ;
70
70
71
+ function modelToSQL (
72
+ sequelize : Sequelize ,
73
+ model : ModelCtor ,
74
+ dialect : string ,
75
+ withoutForeignKeyConstraints = false ,
76
+ ) : string {
77
+ const queryGenerator = sequelize . getQueryInterface ( ) . queryGenerator ;
78
+ const def = sequelize . modelManager . getModel ( model . name ) ;
79
+ if ( ! def ) {
80
+ return "" ;
81
+ }
82
+ const options = { ...def . options } ;
83
+ const attributesToSQLOptions = { ...options , withoutForeignKeyConstraints } ;
84
+ let sql = "" ;
85
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
86
+ // @ts -ignore
87
+ const attr = queryGenerator . attributesToSQL (
88
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
89
+ // @ts -ignore
90
+ def . getAttributes ( ) ,
91
+ attributesToSQLOptions ,
92
+ ) ;
93
+ // Remove from attr all the fields that have 'VIRTUAL' type
94
+ // https://sequelize.org/docs/v6/core-concepts/getters-setters-virtuals/#virtual-fields
95
+ for ( const key in attr ) {
96
+ if ( attr [ key ] . startsWith ( "VIRTUAL" ) ) {
97
+ delete attr [ key ] ;
98
+ }
99
+ }
100
+ // create enum types for postgres
101
+ if ( dialect === "postgres" ) {
102
+ for ( const key in attr ) {
103
+ if ( ! attr [ key ] . startsWith ( "ENUM" ) ) {
104
+ continue ;
105
+ }
106
+ const enumValues = attr [ key ] . substring (
107
+ attr [ key ] . indexOf ( "(" ) ,
108
+ attr [ key ] . lastIndexOf ( ")" ) + 1 ,
109
+ ) ;
110
+ const enumName = `enum_${ def . getTableName ( ) } _${ key } ` ;
111
+ sql += `CREATE TYPE "${ enumName } " AS ENUM${ enumValues } ;\n` ;
112
+ }
113
+ }
114
+ sql +=
115
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
116
+ // @ts -ignore
117
+ queryGenerator . createTableQuery ( def . getTableName ( ) , attr , options ) + "\n" ;
118
+ for ( const index of def . options ?. indexes ?? [ ] ) {
119
+ sql +=
120
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
121
+ // @ts -ignore
122
+ queryGenerator . addIndexQuery ( def . getTableName ( ) , index , options ) + ";\n" ;
123
+ }
124
+ return sql ;
125
+ }
126
+
71
127
export const loadModels = ( dialect : string , models : ModelCtor [ ] ) => {
72
128
if ( ! validDialects . includes ( dialect ) ) {
73
129
throw new Error ( `Invalid dialect ${ dialect } ` ) ;
@@ -89,15 +145,17 @@ export const loadSQL = (
89
145
}
90
146
const orderedModels = sequelize . modelManager
91
147
. getModelsTopoSortedByForeignKey ( )
92
- ?. reverse ( ) ;
93
- if ( ! orderedModels ) {
94
- throw new Error ( "no models found" ) ;
95
- }
148
+ ?. map ( ( m ) => sequelize . modelManager . getModel ( m . name ) )
149
+ . filter ( ( m ) : m is ModelCtor => ! ! m )
150
+ . reverse ( ) ;
151
+
96
152
let sql = "" ;
97
153
98
154
if ( srcMap && srcMap . size > 0 ) {
99
- for ( const model of orderedModels ) {
155
+ const modelsForSrcMap = orderedModels ?? sequelize . modelManager . models ;
156
+ for ( const model of modelsForSrcMap ) {
100
157
const def = sequelize . modelManager . getModel ( model . name ) ;
158
+ if ( ! def ) continue ;
101
159
const tableName = def . getTableName ( ) ;
102
160
const pos = srcMap . get ( model . name ) ;
103
161
if ( ! pos ) continue ;
@@ -107,55 +165,51 @@ export const loadSQL = (
107
165
sql += "\n" ;
108
166
}
109
167
110
- const queryGenerator = sequelize . getQueryInterface ( ) . queryGenerator ;
111
- for ( const model of orderedModels ) {
112
- const def = sequelize . modelManager . getModel ( model . name ) ;
113
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
114
- // @ts -ignore
115
- const attr = queryGenerator . attributesToSQL (
116
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
117
- // @ts -ignore
118
- def . getAttributes ( ) ,
119
- Object . assign ( { } , def . options ) ,
120
- ) ;
121
- // Remove from attr all the fields that have 'VIRTUAL' type
122
- // https://sequelize.org/docs/v6/core-concepts/getters-setters-virtuals/#virtual-fields
123
- for ( const key in attr ) {
124
- if ( attr [ key ] . startsWith ( "VIRTUAL" ) ) {
125
- delete attr [ key ] ;
126
- }
168
+ if ( orderedModels ) {
169
+ for ( const model of orderedModels ) {
170
+ sql += modelToSQL ( sequelize , model , dialect ) ;
127
171
}
128
- // create enum types for postgres
129
- if ( dialect === "postgres" ) {
130
- for ( const key in attr ) {
131
- if ( ! attr [ key ] . startsWith ( "ENUM" ) ) {
132
- continue ;
133
- }
134
- const enumValues = attr [ key ] . substring (
135
- attr [ key ] . indexOf ( "(" ) ,
136
- attr [ key ] . lastIndexOf ( ")" ) + 1 ,
137
- ) ;
138
- const enumName = `enum_${ def . getTableName ( ) } _${ key } ` ;
139
- sql += `CREATE TYPE "${ enumName } " AS ENUM${ enumValues } ;\n` ;
140
- }
172
+ return sql ;
173
+ }
174
+
175
+ // In SQLite, foreign key constraints are not enforced by default, so there's no need for special handling of circular dependencies.
176
+ if ( dialect === "sqlite" ) {
177
+ for ( const model of sequelize . modelManager . models as ModelCtor [ ] ) {
178
+ sql += modelToSQL ( sequelize , model , dialect ) ;
141
179
}
142
- sql +=
143
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
144
- // @ts -ignore
145
- queryGenerator . createTableQuery (
146
- def . getTableName ( ) ,
147
- attr ,
148
- Object . assign ( { } , def . options ) ,
149
- ) + "\n" ;
150
- for ( const index of def . options ?. indexes ?? [ ] ) {
151
- sql +=
152
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
153
- // @ts -ignore
154
- queryGenerator . addIndexQuery (
155
- def . getTableName ( ) ,
156
- index ,
157
- Object . assign ( { } , def . options ) ,
158
- ) + ";\n" ;
180
+ return sql ;
181
+ }
182
+ // If there are circular dependencies, first create tables without foreign keys, then add them.
183
+ for ( const model of sequelize . modelManager . models as ModelCtor [ ] ) {
184
+ sql += modelToSQL ( sequelize , model , dialect , true ) ;
185
+ }
186
+
187
+ const queryInterface = sequelize . getQueryInterface ( ) ;
188
+ for ( const model of sequelize . modelManager . models as ModelCtor [ ] ) {
189
+ const attributes = model . getAttributes ( ) ;
190
+ for ( const key of Object . keys ( attributes ) ) {
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ const attribute = ( attributes as Record < string , any > ) [ key ] ;
193
+ if ( ! attribute . references ) {
194
+ continue ;
195
+ }
196
+ // @ts -expect-error - queryGenerator is not in the type definition
197
+ const query = queryInterface . queryGenerator . attributesToSQL (
198
+ {
199
+ // @ts -expect-error - normalizeAttribute is not in the type definition
200
+ [ key ] : queryInterface . normalizeAttribute ( attribute ) ,
201
+ } ,
202
+ {
203
+ context : "changeColumn" ,
204
+ table : model . getTableName ( ) as string ,
205
+ } ,
206
+ ) ;
207
+ // @ts -expect-error - queryGenerator is not in the type definition
208
+ sql += queryInterface . queryGenerator . changeColumnQuery (
209
+ model . getTableName ( ) ,
210
+ query ,
211
+ ) ;
212
+ sql += "\n" ;
159
213
}
160
214
}
161
215
return sql ;
0 commit comments