11import { invariant } from '@zenstackhq/common-helpers' ;
22import { CreateTableBuilder , sql , type ColumnDataType , type OnModifyForeignAction } from 'kysely' ;
3+ import toposort from 'toposort' ;
34import { match } from 'ts-pattern' ;
45import {
56 ExpressionUtils ,
67 type BuiltinType ,
78 type CascadeAction ,
89 type FieldDef ,
9- type GetModels ,
1010 type ModelDef ,
1111 type SchemaDef ,
1212} from '../../schema' ;
@@ -24,32 +24,82 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
2424 if ( this . schema . enums && this . schema . provider . type === 'postgresql' ) {
2525 for ( const [ name , enumDef ] of Object . entries ( this . schema . enums ) ) {
2626 const createEnum = tx . schema . createType ( name ) . asEnum ( Object . values ( enumDef ) ) ;
27- // console.log('Creating enum:', createEnum.compile().sql);
2827 await createEnum . execute ( ) ;
2928 }
3029 }
3130
32- for ( const model of Object . keys ( this . schema . models ) ) {
33- const createTable = this . createModelTable ( tx , model as GetModels < Schema > ) ;
34- // console.log('Creating table:', createTable.compile().sql);
31+ // sort models so that target of fk constraints are created first
32+ const sortedModels = this . sortModels ( this . schema . models ) ;
33+ for ( const modelDef of sortedModels ) {
34+ const createTable = this . createModelTable ( tx , modelDef ) ;
3535 await createTable . execute ( ) ;
3636 }
3737 } ) ;
3838 }
3939
40- private createModelTable ( kysely : ToKysely < Schema > , model : GetModels < Schema > ) {
41- let table = kysely . schema . createTable ( model ) . ifNotExists ( ) ;
42- const modelDef = requireModel ( this . schema , model ) ;
40+ private sortModels ( models : Record < string , ModelDef > ) : ModelDef [ ] {
41+ const graph : [ ModelDef , ModelDef | undefined ] [ ] = [ ] ;
42+
43+ for ( const model of Object . values ( models ) ) {
44+ let added = false ;
45+
46+ if ( model . baseModel ) {
47+ // base model should be created before concrete model
48+ const baseDef = requireModel ( this . schema , model . baseModel ) ;
49+ // edge: base model -> concrete model
50+ graph . push ( [ baseDef , model ] ) ;
51+ added = true ;
52+ }
53+
54+ for ( const field of Object . values ( model . fields ) ) {
55+ // relation order
56+ if ( field . relation && field . relation . fields && field . relation . references ) {
57+ const targetModel = requireModel ( this . schema , field . type ) ;
58+ // edge: relation target model -> fk model
59+ graph . push ( [ targetModel , model ] ) ;
60+ added = true ;
61+ }
62+ }
63+
64+ if ( ! added ) {
65+ // no relations, add self to graph to ensure it is included in the result
66+ graph . push ( [ model , undefined ] ) ;
67+ }
68+ }
69+
70+ return toposort ( graph ) . filter ( ( m ) => ! ! m ) ;
71+ }
72+
73+ private createModelTable ( kysely : ToKysely < Schema > , modelDef : ModelDef ) {
74+ let table : CreateTableBuilder < string , any > = kysely . schema . createTable ( modelDef . name ) . ifNotExists ( ) ;
75+
4376 for ( const [ fieldName , fieldDef ] of Object . entries ( modelDef . fields ) ) {
77+ if ( fieldDef . originModel && ! fieldDef . id ) {
78+ // skip non-id fields inherited from base model
79+ continue ;
80+ }
81+
4482 if ( fieldDef . relation ) {
45- table = this . addForeignKeyConstraint ( table , model , fieldName , fieldDef ) ;
83+ table = this . addForeignKeyConstraint ( table , modelDef . name , fieldName , fieldDef ) ;
4684 } else if ( ! this . isComputedField ( fieldDef ) ) {
47- table = this . createModelField ( table , fieldName , fieldDef , modelDef ) ;
85+ table = this . createModelField ( table , fieldDef , modelDef ) ;
4886 }
4987 }
5088
51- table = this . addPrimaryKeyConstraint ( table , model , modelDef ) ;
52- table = this . addUniqueConstraint ( table , model , modelDef ) ;
89+ if ( modelDef . baseModel ) {
90+ // create fk constraint
91+ const baseModelDef = requireModel ( this . schema , modelDef . baseModel ) ;
92+ table = table . addForeignKeyConstraint (
93+ `fk_${ modelDef . baseModel } _delegate` ,
94+ baseModelDef . idFields ,
95+ modelDef . baseModel ,
96+ baseModelDef . idFields ,
97+ ( cb ) => cb . onDelete ( 'cascade' ) . onUpdate ( 'cascade' ) ,
98+ ) ;
99+ }
100+
101+ table = this . addPrimaryKeyConstraint ( table , modelDef ) ;
102+ table = this . addUniqueConstraint ( table , modelDef ) ;
53103
54104 return table ;
55105 }
@@ -58,11 +108,7 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
58108 return fieldDef . attributes ?. some ( ( a ) => a . name === '@computed' ) ;
59109 }
60110
61- private addPrimaryKeyConstraint (
62- table : CreateTableBuilder < string , any > ,
63- model : GetModels < Schema > ,
64- modelDef : ModelDef ,
65- ) {
111+ private addPrimaryKeyConstraint ( table : CreateTableBuilder < string , any > , modelDef : ModelDef ) {
66112 if ( modelDef . idFields . length === 1 ) {
67113 if ( Object . values ( modelDef . fields ) . some ( ( f ) => f . id ) ) {
68114 // @id defined at field level
@@ -71,13 +117,13 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
71117 }
72118
73119 if ( modelDef . idFields . length > 0 ) {
74- table = table . addPrimaryKeyConstraint ( `pk_${ model } ` , modelDef . idFields ) ;
120+ table = table . addPrimaryKeyConstraint ( `pk_${ modelDef . name } ` , modelDef . idFields ) ;
75121 }
76122
77123 return table ;
78124 }
79125
80- private addUniqueConstraint ( table : CreateTableBuilder < string , any > , model : string , modelDef : ModelDef ) {
126+ private addUniqueConstraint ( table : CreateTableBuilder < string , any > , modelDef : ModelDef ) {
81127 for ( const [ key , value ] of Object . entries ( modelDef . uniqueFields ) ) {
82128 invariant ( typeof value === 'object' , 'expecting an object' ) ;
83129 if ( 'type' in value ) {
@@ -86,22 +132,17 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
86132 if ( fieldDef . unique ) {
87133 continue ;
88134 }
89- table = table . addUniqueConstraint ( `unique_${ model } _${ key } ` , [ key ] ) ;
135+ table = table . addUniqueConstraint ( `unique_${ modelDef . name } _${ key } ` , [ key ] ) ;
90136 } else {
91137 // multi-field constraint
92- table = table . addUniqueConstraint ( `unique_${ model } _${ key } ` , Object . keys ( value ) ) ;
138+ table = table . addUniqueConstraint ( `unique_${ modelDef . name } _${ key } ` , Object . keys ( value ) ) ;
93139 }
94140 }
95141 return table ;
96142 }
97143
98- private createModelField (
99- table : CreateTableBuilder < any > ,
100- fieldName : string ,
101- fieldDef : FieldDef ,
102- modelDef : ModelDef ,
103- ) {
104- return table . addColumn ( fieldName , this . mapFieldType ( fieldDef ) , ( col ) => {
144+ private createModelField ( table : CreateTableBuilder < any > , fieldDef : FieldDef , modelDef : ModelDef ) {
145+ return table . addColumn ( fieldDef . name , this . mapFieldType ( fieldDef ) , ( col ) => {
105146 // @id
106147 if ( fieldDef . id && modelDef . idFields . length === 1 ) {
107148 col = col . primaryKey ( ) ;
@@ -178,7 +219,7 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
178219
179220 private addForeignKeyConstraint (
180221 table : CreateTableBuilder < string , any > ,
181- model : GetModels < Schema > ,
222+ model : string ,
182223 fieldName : string ,
183224 fieldDef : FieldDef ,
184225 ) {
0 commit comments