@@ -487,49 +487,47 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
487487 existing [ent .Name ] = ent
488488 }
489489
490+ // Build a global type lookup so we can resolve BaseType references across schemas.
491+ typeByQualified := make (map [string ]* mpr.EdmEntityType )
492+ for _ , schema := range doc .Schemas {
493+ for _ , et := range schema .EntityTypes {
494+ typeByQualified [schema .Namespace + "." + et .Name ] = et
495+ }
496+ }
497+
490498 serviceRef := s .ServiceRef .String ()
491499 var created , updated , skipped , failed int
492500
493501 for _ , schema := range doc .Schemas {
494502 for _ , et := range schema .EntityTypes {
495- // Apply entity name filter (matched against entity set name when available)
496503 entitySetName := esMap [schema .Namespace + "." + et .Name ]
504+ isTopLevel := entitySetName != ""
497505
498- // Skip abstract types (no entity set) — Studio Pro creates them but
499- // points them at a parent's entity set; we don't yet handle this.
500- if entitySetName == "" {
501- if len (filterSet ) > 0 {
502- // Only skip silently if the user didn't ask for it
503- if ! filterSet [strings .ToLower (et .Name )] {
504- continue
505- }
506- }
507- fmt .Fprintf (e .output , " SKIPPED: %s (no entity set; abstract or derived type)\n " , et .Name )
508- skipped ++
509- continue
506+ // Mendix entity name: entity set name when present (e.g. "People"),
507+ // otherwise the type name (e.g. "PlanItem", "Flight", "Trip").
508+ mendixName := et .Name
509+ if isTopLevel {
510+ mendixName = entitySetName
510511 }
511512
512- // The Mendix entity name should be the entity set name (e.g. "People"),
513- // not the entity type name ("Person").
514- mendixName := entitySetName
515-
516- if len (filterSet ) > 0 && ! filterSet [strings .ToLower (et .Name )] && ! filterSet [strings .ToLower (entitySetName )] {
513+ // Apply entity name filter (matched against type name OR entity set name)
514+ if len (filterSet ) > 0 && ! filterSet [strings .ToLower (et .Name )] && ! filterSet [strings .ToLower (mendixName )] {
517515 continue
518516 }
519517
520- // keyPropSet helps both for building Source.Key and for forcing key
521- // string attributes to have a non-zero length (CE6121).
518+ // Resolve the merged property and key set by walking the BaseType chain.
519+ mergedProps , keyProps := mergedPropertiesWithKey (et , typeByQualified )
520+
522521 keyPropSet := make (map [string ]bool )
523- for _ , k := range et . KeyProperties {
522+ for _ , k := range keyProps {
524523 keyPropSet [k ] = true
525524 }
526525
527- // Build key parts (used both for Source.Key and to skip emitting key
528- // properties as plain attributes when they would collide with reserved names)
526+ // Build key parts from the resolved key (root entity in the chain)
529527 var keyParts []* domainmodel.RemoteKeyPart
530- for _ , keyName := range et . KeyProperties {
528+ for _ , keyName := range keyProps {
531529 var keyProp * mpr.EdmProperty
532- for _ , p := range et . Properties {
530+ for _ , p := range mergedProps {
533531 if p .Name == keyName {
534532 keyProp = p
535533 break
@@ -546,21 +544,24 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
546544 })
547545 }
548546
549- // Build attributes from properties
547+ // Build attributes from merged properties
550548 var attrs []* domainmodel.Attribute
551- for _ , p := range et . Properties {
552- // Drop collection-of-primitive (e.g. Collection(Edm.String)) — Studio Pro
553- // stores these via separate primitive collection entities; we skip for now .
549+ for _ , p := range mergedProps {
550+ // Drop collection-of-primitive — handled separately as primitive
551+ // collection NPEs (not yet implemented) .
554552 if strings .HasPrefix (p .Type , "Collection(" ) {
555553 continue
556554 }
557-
558- // Drop non-Edm types (complex types, entity refs) — they need to be
559- // modelled as separate non-persistent entities or external entities,
560- // which we don't yet handle. Studio Pro skips them or creates NPEs.
555+ // Drop non-Edm types (complex types and entity refs) — they need
556+ // to be modelled as NPEs/associations, not implemented yet.
561557 if ! strings .HasPrefix (p .Type , "Edm." ) {
562558 continue
563559 }
560+ // Drop Edm.Duration — Mendix has no native duration type and
561+ // Studio Pro skips these properties.
562+ if p .Type == "Edm.Duration" {
563+ continue
564+ }
564565
565566 attrName := attrNameForOData (p .Name , et .Name )
566567 attr := & domainmodel.Attribute {
@@ -570,9 +571,9 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
570571 RemoteType : p .Type ,
571572 Filterable : true ,
572573 Sortable : true ,
573- // TODO: parse Org.OData.Capabilities.V1 annotations from $metadata
574- // to derive these per-attribute. For now, defaults assume
575- // create-on-insert but no in-place update.
574+ // TODO: parse Org.OData.Capabilities.V1 annotations to set
575+ // these per-attribute. Current defaults assume create-on-insert
576+ // but no in-place update.
576577 Creatable : true ,
577578 Updatable : false ,
578579 }
@@ -586,19 +587,7 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
586587 skipped ++
587588 continue
588589 }
589- existingEntity .Source = "Rest$ODataRemoteEntitySource"
590- existingEntity .RemoteServiceName = serviceRef
591- existingEntity .RemoteEntitySet = entitySetName
592- existingEntity .RemoteEntityName = et .Name
593- existingEntity .Countable = true
594- existingEntity .Creatable = true
595- existingEntity .Deletable = false
596- existingEntity .Updatable = false
597- existingEntity .SkipSupported = true
598- existingEntity .TopSupported = true
599- existingEntity .CreateChangeLocally = false
600- existingEntity .RemoteKeyParts = keyParts
601- existingEntity .Attributes = attrs
590+ applyExternalEntityFields (existingEntity , et , isTopLevel , serviceRef , entitySetName , keyParts , attrs )
602591 if err := e .writer .UpdateEntity (dm .ID , existingEntity ); err != nil {
603592 fmt .Fprintf (e .output , " FAILED: %s.%s — %v\n " , targetModule , mendixName , err )
604593 failed ++
@@ -610,24 +599,11 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
610599
611600 location := model.Point {X : 100 + (created + updated )* 150 , Y : 100 }
612601 newEntity := & domainmodel.Entity {
613- Name : mendixName ,
614- Persistable : true , // Studio Pro stores external entities as persistable
615- Location : location ,
616- Attributes : attrs ,
617- Source : "Rest$ODataRemoteEntitySource" ,
618- RemoteServiceName : serviceRef ,
619- RemoteEntitySet : entitySetName ,
620- RemoteEntityName : et .Name ,
621- Countable : true ,
622- Creatable : true , // TODO: parse Capabilities annotations
623- Deletable : false ,
624- Updatable : false ,
625- SkipSupported : true ,
626- TopSupported : true ,
627- CreateChangeLocally : false ,
628- RemoteKeyParts : keyParts ,
602+ Name : mendixName ,
603+ Location : location ,
629604 }
630605 newEntity .ID = model .ID (mpr .GenerateID ())
606+ applyExternalEntityFields (newEntity , et , isTopLevel , serviceRef , entitySetName , keyParts , attrs )
631607 if err := e .writer .CreateEntity (dm .ID , newEntity ); err != nil {
632608 fmt .Fprintf (e .output , " FAILED: %s.%s — %v\n " , targetModule , mendixName , err )
633609 failed ++
@@ -643,6 +619,88 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
643619 return nil
644620}
645621
622+ // applyExternalEntityFields stamps the Source/RemoteServiceName/Key/Attributes
623+ // fields on a domain model entity, choosing the right BSON source type based on
624+ // whether the entity has its own entity set.
625+ //
626+ // Top-level entities (have an entity set) → Rest$ODataRemoteEntitySource.
627+ // Derived/abstract/contained types → Rest$ODataEntityTypeSource.
628+ func applyExternalEntityFields (
629+ ent * domainmodel.Entity ,
630+ et * mpr.EdmEntityType ,
631+ isTopLevel bool ,
632+ serviceRef , entitySetName string ,
633+ keyParts []* domainmodel.RemoteKeyPart ,
634+ attrs []* domainmodel.Attribute ,
635+ ) {
636+ ent .RemoteServiceName = serviceRef
637+ ent .RemoteEntityName = et .Name
638+ ent .RemoteKeyParts = keyParts
639+ ent .Attributes = attrs
640+
641+ if isTopLevel {
642+ ent .Source = "Rest$ODataRemoteEntitySource"
643+ ent .Persistable = true
644+ ent .RemoteEntitySet = entitySetName
645+ ent .Countable = true
646+ ent .Creatable = true // TODO: parse Capabilities annotations per-entity
647+ ent .Deletable = false
648+ ent .Updatable = false
649+ ent .SkipSupported = true
650+ ent .TopSupported = true
651+ ent .CreateChangeLocally = false
652+ return
653+ }
654+
655+ // Derived / abstract / contained-target entity (no entity set)
656+ ent .Source = "Rest$ODataEntityTypeSource"
657+ ent .Persistable = false
658+ ent .IsOpen = et .IsOpen
659+ ent .RemoteEntitySet = ""
660+ // CRUD/skip/top fields are not used for entity-type sources; clear them
661+ // in case we're updating an existing entity that previously had them.
662+ ent .Countable = false
663+ ent .Creatable = false
664+ ent .Deletable = false
665+ ent .Updatable = false
666+ ent .SkipSupported = false
667+ ent .TopSupported = false
668+ ent .CreateChangeLocally = false
669+ }
670+
671+ // mergedPropertiesWithKey walks the BaseType chain of an entity type and
672+ // returns the merged property list (base properties first, then derived) along
673+ // with the key property names from the root of the chain.
674+ func mergedPropertiesWithKey (et * mpr.EdmEntityType , byQualified map [string ]* mpr.EdmEntityType ) ([]* mpr.EdmProperty , []string ) {
675+ // Walk to the root, collecting types in order from base → derived.
676+ chain := []* mpr.EdmEntityType {et }
677+ current := et
678+ for current .BaseType != "" {
679+ parent := byQualified [current .BaseType ]
680+ if parent == nil {
681+ break
682+ }
683+ chain = append ([]* mpr.EdmEntityType {parent }, chain ... )
684+ current = parent
685+ }
686+
687+ var merged []* mpr.EdmProperty
688+ seen := make (map [string ]bool )
689+ for _ , t := range chain {
690+ for _ , p := range t .Properties {
691+ if seen [p .Name ] {
692+ continue
693+ }
694+ seen [p .Name ] = true
695+ merged = append (merged , p )
696+ }
697+ }
698+
699+ // The key always comes from the root of the chain.
700+ keyProps := chain [0 ].KeyProperties
701+ return merged , keyProps
702+ }
703+
646704// attrNameForOData returns a Mendix-safe attribute name for an OData property.
647705// Reserved names like Id and Name collide with Mendix's built-in entity members,
648706// so they get prefixed with the entity name (e.g. "Id" → "PhotoId").
0 commit comments