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,84 @@ 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: concrete model -> base model
50+ graph . push ( [ model , baseDef ] ) ;
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: fk side -> target model
59+ graph . push ( [ model , targetModel ] ) ;
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 )
71+ . reverse ( )
72+ . filter ( ( m ) => ! ! m ) ;
73+ }
74+
75+ private createModelTable ( kysely : ToKysely < Schema > , modelDef : ModelDef ) {
76+ let table : CreateTableBuilder < string , any > = kysely . schema . createTable ( modelDef . name ) . ifNotExists ( ) ;
77+
4378 for ( const [ fieldName , fieldDef ] of Object . entries ( modelDef . fields ) ) {
79+ if ( fieldDef . originModel && ! fieldDef . id ) {
80+ // skip non-id fields inherited from base model
81+ continue ;
82+ }
83+
4484 if ( fieldDef . relation ) {
45- table = this . addForeignKeyConstraint ( table , model , fieldName , fieldDef ) ;
85+ table = this . addForeignKeyConstraint ( table , modelDef . name , fieldName , fieldDef ) ;
4686 } else if ( ! this . isComputedField ( fieldDef ) ) {
47- table = this . createModelField ( table , fieldName , fieldDef , modelDef ) ;
87+ table = this . createModelField ( table , fieldDef , modelDef ) ;
4888 }
4989 }
5090
51- table = this . addPrimaryKeyConstraint ( table , model , modelDef ) ;
52- table = this . addUniqueConstraint ( table , model , modelDef ) ;
91+ if ( modelDef . baseModel ) {
92+ // create fk constraint
93+ const baseModelDef = requireModel ( this . schema , modelDef . baseModel ) ;
94+ table = table . addForeignKeyConstraint (
95+ `fk_${ modelDef . baseModel } _delegate` ,
96+ baseModelDef . idFields ,
97+ modelDef . baseModel ,
98+ baseModelDef . idFields ,
99+ ( cb ) => cb . onDelete ( 'cascade' ) . onUpdate ( 'cascade' ) ,
100+ ) ;
101+ }
102+
103+ table = this . addPrimaryKeyConstraint ( table , modelDef ) ;
104+ table = this . addUniqueConstraint ( table , modelDef ) ;
53105
54106 return table ;
55107 }
@@ -58,11 +110,7 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
58110 return fieldDef . attributes ?. some ( ( a ) => a . name === '@computed' ) ;
59111 }
60112
61- private addPrimaryKeyConstraint (
62- table : CreateTableBuilder < string , any > ,
63- model : GetModels < Schema > ,
64- modelDef : ModelDef ,
65- ) {
113+ private addPrimaryKeyConstraint ( table : CreateTableBuilder < string , any > , modelDef : ModelDef ) {
66114 if ( modelDef . idFields . length === 1 ) {
67115 if ( Object . values ( modelDef . fields ) . some ( ( f ) => f . id ) ) {
68116 // @id defined at field level
@@ -71,13 +119,13 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
71119 }
72120
73121 if ( modelDef . idFields . length > 0 ) {
74- table = table . addPrimaryKeyConstraint ( `pk_${ model } ` , modelDef . idFields ) ;
122+ table = table . addPrimaryKeyConstraint ( `pk_${ modelDef . name } ` , modelDef . idFields ) ;
75123 }
76124
77125 return table ;
78126 }
79127
80- private addUniqueConstraint ( table : CreateTableBuilder < string , any > , model : string , modelDef : ModelDef ) {
128+ private addUniqueConstraint ( table : CreateTableBuilder < string , any > , modelDef : ModelDef ) {
81129 for ( const [ key , value ] of Object . entries ( modelDef . uniqueFields ) ) {
82130 invariant ( typeof value === 'object' , 'expecting an object' ) ;
83131 if ( 'type' in value ) {
@@ -86,22 +134,17 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
86134 if ( fieldDef . unique ) {
87135 continue ;
88136 }
89- table = table . addUniqueConstraint ( `unique_${ model } _${ key } ` , [ key ] ) ;
137+ table = table . addUniqueConstraint ( `unique_${ modelDef . name } _${ key } ` , [ key ] ) ;
90138 } else {
91139 // multi-field constraint
92- table = table . addUniqueConstraint ( `unique_${ model } _${ key } ` , Object . keys ( value ) ) ;
140+ table = table . addUniqueConstraint ( `unique_${ modelDef . name } _${ key } ` , Object . keys ( value ) ) ;
93141 }
94142 }
95143 return table ;
96144 }
97145
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 ) => {
146+ private createModelField ( table : CreateTableBuilder < any > , fieldDef : FieldDef , modelDef : ModelDef ) {
147+ return table . addColumn ( fieldDef . name , this . mapFieldType ( fieldDef ) , ( col ) => {
105148 // @id
106149 if ( fieldDef . id && modelDef . idFields . length === 1 ) {
107150 col = col . primaryKey ( ) ;
@@ -178,7 +221,7 @@ export class SchemaDbPusher<Schema extends SchemaDef> {
178221
179222 private addForeignKeyConstraint (
180223 table : CreateTableBuilder < string , any > ,
181- model : GetModels < Schema > ,
224+ model : string ,
182225 fieldName : string ,
183226 fieldDef : FieldDef ,
184227 ) {
0 commit comments