@@ -456,8 +456,10 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
456456
457457 // Build entity set lookup: entity type qualified name → entity set name
458458 esMap := make (map [string ]string )
459+ esByType := make (map [string ]* mpr.EdmEntitySet )
459460 for _ , es := range doc .EntitySets {
460461 esMap [es .EntityType ] = es .Name
462+ esByType [es .EntityType ] = es
461463 }
462464
463465 // Build filter set if entity names specified
@@ -500,7 +502,11 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
500502
501503 for _ , schema := range doc .Schemas {
502504 for _ , et := range schema .EntityTypes {
503- entitySetName := esMap [schema .Namespace + "." + et .Name ]
505+ entitySet := esByType [schema .Namespace + "." + et .Name ]
506+ entitySetName := ""
507+ if entitySet != nil {
508+ entitySetName = entitySet .Name
509+ }
504510 isTopLevel := entitySetName != ""
505511
506512 // Mendix entity name: entity set name when present (e.g. "People"),
@@ -544,12 +550,20 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
544550 })
545551 }
546552
547- // Default Updatable depends on whether the parent will be a top-
548- // level entity-set entity or a derived/contained entity-type source.
549- // Top- level entities are typically read-only on writable attributes
550- // (matches People/Photos/Airlines in TripPin); contained types are
551- // updatable .
553+ // Default Updatable for attributes follows the entity-set's
554+ // UpdateRestrictions when available; otherwise falls back to true
555+ // for non-top- level entities and false for top-level (matching
556+ // TripPin's pattern where attributes are read-only on writable
557+ // entity sets but mutable on contained types) .
552558 defaultUpdatable := ! isTopLevel
559+ if entitySet != nil && entitySet .Updatable != nil {
560+ defaultUpdatable = * entitySet .Updatable
561+ }
562+ // Default Creatable for attributes follows InsertRestrictions.
563+ defaultCreatable := true
564+ if entitySet != nil && entitySet .Insertable != nil {
565+ defaultCreatable = * entitySet .Insertable
566+ }
553567
554568 // Build attributes from merged properties
555569 var attrs []* domainmodel.Attribute
@@ -578,10 +592,8 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
578592 RemoteType : p .Type ,
579593 Filterable : true ,
580594 Sortable : true ,
581- // TODO: parse Org.OData.Capabilities.V1 annotations to set
582- // these per-attribute. Defaults match TripPin's pattern.
583- Creatable : true ,
584- Updatable : defaultUpdatable ,
595+ Creatable : defaultCreatable ,
596+ Updatable : defaultUpdatable ,
585597 }
586598 attr .ID = model .ID (mpr .GenerateID ())
587599 attrs = append (attrs , attr )
@@ -593,7 +605,7 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
593605 skipped ++
594606 continue
595607 }
596- applyExternalEntityFields (existingEntity , et , isTopLevel , serviceRef , entitySetName , keyParts , attrs )
608+ applyExternalEntityFields (existingEntity , et , isTopLevel , serviceRef , entitySet , keyParts , attrs )
597609 if err := e .writer .UpdateEntity (dm .ID , existingEntity ); err != nil {
598610 fmt .Fprintf (e .output , " FAILED: %s.%s — %v\n " , targetModule , mendixName , err )
599611 failed ++
@@ -609,7 +621,7 @@ func (e *Executor) createExternalEntities(s *ast.CreateExternalEntitiesStmt) err
609621 Location : location ,
610622 }
611623 newEntity .ID = model .ID (mpr .GenerateID ())
612- applyExternalEntityFields (newEntity , et , isTopLevel , serviceRef , entitySetName , keyParts , attrs )
624+ applyExternalEntityFields (newEntity , et , isTopLevel , serviceRef , entitySet , keyParts , attrs )
613625 if err := e .writer .CreateEntity (dm .ID , newEntity ); err != nil {
614626 fmt .Fprintf (e .output , " FAILED: %s.%s — %v\n " , targetModule , mendixName , err )
615627 failed ++
@@ -836,14 +848,36 @@ func singular(name string) string {
836848// navigation properties from BaseType chains are walked too.
837849//
838850// Each association uses Rest$ODataRemoteAssociationSource so Studio Pro can
839- // map it back to the OData navigation property.
851+ // map it back to the OData navigation property. Per-association
852+ // CreatableFromParent / UpdatableFromParent come from the entity set's
853+ // Org.OData.Capabilities.V1.{Insert,Update}Restrictions/Non*NavigationProperties
854+ // annotations.
840855func (e * Executor ) createNavigationAssociations (
841856 dm * domainmodel.DomainModel ,
842857 doc * mpr.EdmxDocument ,
843858 typeByQualified map [string ]* mpr.EdmEntityType ,
844859 esMap map [string ]string ,
845860 serviceRef string ,
846861) int {
862+ // Build per-entity-type lookup of nav property name → restricted flags.
863+ type navRestrictions struct {
864+ nonInsertable map [string ]bool
865+ nonUpdatable map [string ]bool
866+ }
867+ restrictionsByType := make (map [string ]navRestrictions )
868+ for _ , es := range doc .EntitySets {
869+ r := navRestrictions {
870+ nonInsertable : make (map [string ]bool ),
871+ nonUpdatable : make (map [string ]bool ),
872+ }
873+ for _ , name := range es .NonInsertableNavigationProperties {
874+ r .nonInsertable [name ] = true
875+ }
876+ for _ , name := range es .NonUpdatableNavigationProperties {
877+ r .nonUpdatable [name ] = true
878+ }
879+ restrictionsByType [es .EntityType ] = r
880+ }
847881 // Build a lookup from EDMX type qualified name → existing Mendix entity.
848882 // An entity type matches by its EntitySet name when present, otherwise by
849883 // its bare type name.
@@ -924,6 +958,22 @@ func (e *Executor) createNavigationAssociations(
924958 assocType = domainmodel .AssociationTypeReferenceSet
925959 }
926960
961+ // Apply per-association capability defaults. For top-level
962+ // entities (entity-set source), Studio Pro defaults
963+ // UpdatableFromParent=false because link updates happen via
964+ // the foreign key on the entity, not by updating the nav
965+ // property. For contained/derived types, both default to true.
966+ creatable := true
967+ updatable := ! parentEnt .Persistable
968+ if r , ok := restrictionsByType [parentQN ]; ok {
969+ if r .nonInsertable [np .Name ] {
970+ creatable = false
971+ }
972+ if r .nonUpdatable [np .Name ] {
973+ updatable = false
974+ }
975+ }
976+
927977 assoc := & domainmodel.Association {
928978 Name : assocName ,
929979 ParentID : parentEnt .ID ,
@@ -934,11 +984,8 @@ func (e *Executor) createNavigationAssociations(
934984 Source : "Rest$ODataRemoteAssociationSource" ,
935985 RemoteParentNavigationProperty : np .Name ,
936986 Navigability2 : "ParentToChild" ,
937- // TODO: parse Org.OData.Capabilities.V1 annotations to
938- // derive these per-association. Defaults match TripPin's
939- // most common case.
940- CreatableFromParent : true ,
941- UpdatableFromParent : true ,
987+ CreatableFromParent : creatable ,
988+ UpdatableFromParent : updatable ,
942989 }
943990 assoc .ID = model .ID (mpr .GenerateID ())
944991
@@ -989,11 +1036,16 @@ func uniqueAssocName(base string, dm *domainmodel.DomainModel, existingAssocs ma
9891036//
9901037// Top-level entities (have an entity set) → Rest$ODataRemoteEntitySource.
9911038// Derived/abstract/contained types → Rest$ODataEntityTypeSource.
1039+ //
1040+ // When entitySet is non-nil, its parsed capability annotations
1041+ // (InsertRestrictions/UpdateRestrictions/DeleteRestrictions) override the
1042+ // optimistic defaults.
9921043func applyExternalEntityFields (
9931044 ent * domainmodel.Entity ,
9941045 et * mpr.EdmEntityType ,
9951046 isTopLevel bool ,
996- serviceRef , entitySetName string ,
1047+ serviceRef string ,
1048+ entitySet * mpr.EdmEntitySet ,
9971049 keyParts []* domainmodel.RemoteKeyPart ,
9981050 attrs []* domainmodel.Attribute ,
9991051) {
@@ -1005,11 +1057,16 @@ func applyExternalEntityFields(
10051057 if isTopLevel {
10061058 ent .Source = "Rest$ODataRemoteEntitySource"
10071059 ent .Persistable = true
1008- ent .RemoteEntitySet = entitySetName
1060+ ent .RemoteEntitySet = entitySet . Name
10091061 ent .Countable = true
1010- ent .Creatable = true // TODO: parse Capabilities annotations per-entity
1011- ent .Deletable = false
1012- ent .Updatable = false
1062+ // Capabilities default to true unless the entity set's annotations
1063+ // say otherwise.
1064+ ent .Creatable = entitySet .Insertable == nil || * entitySet .Insertable
1065+ ent .Updatable = entitySet .Updatable == nil || * entitySet .Updatable
1066+ // Deletable defaults to false in TripPin and most read-mostly OData
1067+ // services, so default to the annotation value when present and to
1068+ // false otherwise.
1069+ ent .Deletable = entitySet .Deletable != nil && * entitySet .Deletable
10131070 ent .SkipSupported = true
10141071 ent .TopSupported = true
10151072 ent .CreateChangeLocally = false
0 commit comments