1+ import type { ZModelServices } from '@zenstackhq/language'
2+ import type {
3+ Attribute ,
4+ AttributeArg ,
5+ DataField ,
6+ DataFieldAttribute ,
7+ DataFieldType ,
8+ DataModel ,
9+ Enum ,
10+ EnumField ,
11+ Model ,
12+ UnsupportedFieldType
13+ } from '@zenstackhq/language/ast'
14+ import type { IntrospectedEnum , IntrospectedTable , IntrospectionProvider } from './provider'
15+ import { getAttributeRef , getDbName } from './utils'
16+
17+ export function syncEnums ( dbEnums : IntrospectedEnum [ ] , model : Model ) {
18+ for ( const dbEnum of dbEnums ) {
19+ let schemaEnum = model . declarations . find (
20+ ( d ) => d . $type === 'Enum' && getDbName ( d ) === dbEnum . enum_type
21+ ) as Enum | undefined
22+
23+ if ( ! schemaEnum ) {
24+ schemaEnum = {
25+ $type : 'Enum' as const ,
26+ $container : model ,
27+ name : dbEnum . enum_type ,
28+ attributes : [ ] ,
29+ comments : [ ] ,
30+ fields : [ ] ,
31+ }
32+ model . declarations . push ( schemaEnum )
33+ }
34+ schemaEnum . fields = dbEnum . values . map ( ( v ) => {
35+ const existingValue = schemaEnum . fields . find ( ( f ) => getDbName ( f ) === v )
36+ if ( ! existingValue ) {
37+ const enumField : EnumField = {
38+ $type : 'EnumField' as const ,
39+ $container : schemaEnum ,
40+ name : v ,
41+ attributes : [ ] ,
42+ comments : [ ] ,
43+ }
44+ return enumField
45+ }
46+ return existingValue
47+ } )
48+ }
49+ }
50+
51+ export type Relation = {
52+ schema : string
53+ table : string
54+ column : string
55+ type : 'one' | 'many'
56+ fk_name : string
57+ nullable : boolean
58+ references : {
59+ schema : string | null
60+ table : string | null
61+ column : string | null
62+ }
63+ }
64+
65+ export function syncTable ( {
66+ model,
67+ provider,
68+ table,
69+ } : {
70+ table : IntrospectedTable
71+ model : Model
72+ provider : IntrospectionProvider
73+ } ) {
74+ const relations : Relation [ ] = [ ]
75+ let modelTable = model . declarations . find (
76+ ( d ) => d . $type === 'DataModel' && getDbName ( d ) === table . name
77+ ) as DataModel | undefined
78+
79+ if ( ! modelTable ) {
80+ modelTable = {
81+ $type : 'DataModel' as const ,
82+ $container : model ,
83+ name : table . name ,
84+ fields : [ ] ,
85+ attributes : [ ] ,
86+ comments : [ ] ,
87+ isView : false ,
88+ mixins : [ ] ,
89+ }
90+ model . declarations . push ( modelTable )
91+ }
92+
93+ modelTable . fields = table . columns . map ( ( col ) => {
94+ if ( col . foreign_key_table ) {
95+ relations . push ( {
96+ schema : table . schema ,
97+ table : table . name ,
98+ column : col . name ,
99+ type : col . unique ? 'one' : 'many' ,
100+ fk_name : col . foreign_key_name ! ,
101+ nullable : col . nullable ,
102+ references : {
103+ schema : col . foreign_key_schema ,
104+ table : col . foreign_key_table ,
105+ column : col . foreign_key_column ,
106+ } ,
107+ } )
108+ }
109+
110+ const fieldPrefix = / [ 0 - 9 ] / g. test ( col . name . charAt ( 0 ) ) ? '_' : ''
111+ const fieldName = `${ fieldPrefix } ${ col . name } `
112+
113+ const existingField = modelTable ! . fields . find (
114+ ( f ) => getDbName ( f ) === fieldName
115+ )
116+ if ( ! existingField ) {
117+ const builtinType = provider . getBuiltinType ( col . datatype )
118+ const unsupported : UnsupportedFieldType = {
119+ get $container ( ) {
120+ return type
121+ } ,
122+ $type : 'UnsupportedFieldType' as const ,
123+ value : {
124+ get $container ( ) {
125+ return unsupported
126+ } ,
127+ $type : 'StringLiteral' ,
128+ value : col . datatype ,
129+ } ,
130+ }
131+
132+ const type : DataFieldType = {
133+ get $container ( ) {
134+ return field
135+ } ,
136+ $type : 'DataFieldType' as const ,
137+ type : builtinType . type === 'Unsupported' ? undefined : builtinType . type ,
138+ array : builtinType . isArray ,
139+ unsupported :
140+ builtinType . type === 'Unsupported' ? unsupported : undefined ,
141+ optional : col . nullable ,
142+ reference : col . options . length
143+ ? {
144+ $refText : col . datatype ,
145+ ref : model . declarations . find (
146+ ( d ) => d . $type === 'Enum' && getDbName ( d ) === col . datatype
147+ ) as Enum | undefined ,
148+ }
149+ : undefined ,
150+ }
151+
152+ const field : DataField = {
153+ $type : 'DataField' as const ,
154+ type,
155+ $container : modelTable ! ,
156+ name : fieldName ,
157+ get attributes ( ) {
158+ if ( fieldPrefix !== '' ) return [ ]
159+
160+ const attr : DataFieldAttribute = {
161+ $type : 'DataFieldAttribute' as const ,
162+ get $container ( ) {
163+ return field
164+ } ,
165+ decl : {
166+ $refText : '@map' ,
167+ ref : model . $document ?. references . find (
168+ ( r ) =>
169+ //@ts -ignore
170+ r . ref . $type === 'Attribute' && r . ref . name === '@map'
171+ ) ?. ref as Attribute ,
172+ } ,
173+ get args ( ) {
174+ const arg : AttributeArg = {
175+ $type : 'AttributeArg' as const ,
176+ get $container ( ) {
177+ return attr
178+ } ,
179+ name : 'name' ,
180+ $resolvedParam : {
181+ name : 'name' ,
182+ } ,
183+ get value ( ) {
184+ return {
185+ $type : 'StringLiteral' as const ,
186+ $container : arg ,
187+ value : col . name ,
188+ }
189+ } ,
190+ }
191+
192+ return [ arg ]
193+ } ,
194+ }
195+
196+ return [ attr ]
197+ } ,
198+ comments : [ ] ,
199+ }
200+ return field
201+ }
202+ return existingField
203+ } )
204+
205+ return relations
206+ }
207+
208+ export function syncRelation ( model : Model , relation : Relation , services : ZModelServices ) {
209+ const idAttribute = getAttributeRef ( '@id' , services )
210+ const uniqueAttribute = getAttributeRef ( '@unique' , services )
211+ const relationAttribute = getAttributeRef ( '@relation' , services )
212+
213+ if ( ! idAttribute || ! uniqueAttribute || ! relationAttribute ) {
214+ throw new Error ( 'Cannot find required attributes in the model.' )
215+ }
216+
217+ const sourceModel = model . declarations . find (
218+ ( d ) => d . $type === 'DataModel' && getDbName ( d ) === relation . table
219+ ) as DataModel | undefined
220+ if ( ! sourceModel ) return
221+
222+ const sourceField = sourceModel . fields . find (
223+ ( f ) => getDbName ( f ) === relation . column
224+ ) as DataField | undefined
225+ if ( ! sourceField ) return
226+
227+ const targetModel = model . declarations . find (
228+ ( d ) => d . $type === 'DataModel' && getDbName ( d ) === relation . references . table
229+ ) as DataModel | undefined
230+ if ( ! targetModel ) return
231+
232+ const targetField = targetModel . fields . find (
233+ ( f ) => getDbName ( f ) === relation . references . column
234+ )
235+ if ( ! targetField ) return
236+
237+ //TODO: Finish relation sync
238+ }
0 commit comments