@@ -468,6 +468,137 @@ func edmToMendixType(p *mpr.EdmProperty) string {
468468 }
469469}
470470
471+ // ============================================================================
472+ // CREATE EXTERNAL ENTITIES (bulk)
473+ // ============================================================================
474+
475+ // createExternalEntities handles CREATE [OR MODIFY] EXTERNAL ENTITIES FROM Module.Service [INTO Module] [ENTITIES (...)].
476+ // It reads entity types from the cached $metadata and creates external entities in the domain model.
477+ func (e * Executor ) createExternalEntities (s * ast.CreateExternalEntitiesStmt ) error {
478+ if e .writer == nil {
479+ return fmt .Errorf ("not connected to a project in write mode" )
480+ }
481+
482+ doc , svcQN , err := e .parseServiceContract (s .ServiceRef )
483+ if err != nil {
484+ return err
485+ }
486+
487+ // Build entity set lookup: entity type qualified name → entity set name
488+ esMap := make (map [string ]string )
489+ for _ , es := range doc .EntitySets {
490+ esMap [es .EntityType ] = es .Name
491+ }
492+
493+ // Build filter set if entity names specified
494+ filterSet := make (map [string ]bool )
495+ for _ , name := range s .EntityNames {
496+ filterSet [strings .ToLower (name )] = true
497+ }
498+
499+ // Determine target module
500+ targetModule := s .TargetModule
501+ if targetModule == "" {
502+ targetModule = s .ServiceRef .Module
503+ }
504+
505+ var created , skipped , failed int
506+
507+ for _ , schema := range doc .Schemas {
508+ for _ , et := range schema .EntityTypes {
509+ // Apply entity name filter
510+ if len (filterSet ) > 0 && ! filterSet [strings .ToLower (et .Name )] {
511+ continue
512+ }
513+
514+ entitySetName := esMap [schema .Namespace + "." + et .Name ]
515+ if entitySetName == "" {
516+ entitySetName = et .Name + "s" // fallback
517+ }
518+
519+ // Build attributes from properties
520+ var attrs []ast.Attribute
521+ for _ , p := range et .Properties {
522+ // Skip key properties named ID (Mendix manages its own ID)
523+ isKey := false
524+ for _ , k := range et .KeyProperties {
525+ if p .Name == k {
526+ isKey = true
527+ break
528+ }
529+ }
530+ if isKey && p .Name == "ID" {
531+ continue
532+ }
533+
534+ attrs = append (attrs , ast.Attribute {
535+ Name : p .Name ,
536+ Type : edmToAstDataType (p ),
537+ })
538+ }
539+
540+ stmt := & ast.CreateExternalEntityStmt {
541+ Name : ast.QualifiedName {Module : targetModule , Name : et .Name },
542+ ServiceRef : s .ServiceRef ,
543+ EntitySet : entitySetName ,
544+ RemoteName : et .Name ,
545+ Countable : true ,
546+ Attributes : attrs ,
547+ CreateOrModify : s .CreateOrModify ,
548+ }
549+
550+ if err := e .execCreateExternalEntity (stmt ); err != nil {
551+ fmt .Fprintf (e .output , " FAILED: %s.%s — %v\n " , targetModule , et .Name , err )
552+ failed ++
553+ } else {
554+ created ++
555+ }
556+ }
557+ }
558+
559+ if skipped > 0 || failed > 0 {
560+ fmt .Fprintf (e .output , "\n Imported %d entities from %s (%d failed)\n " , created , svcQN , failed )
561+ } else {
562+ fmt .Fprintf (e .output , "\n Imported %d entities from %s into %s\n " , created , svcQN , targetModule )
563+ }
564+
565+ return nil
566+ }
567+
568+ // edmToAstDataType converts an Edm property to an AST data type.
569+ func edmToAstDataType (p * mpr.EdmProperty ) ast.DataType {
570+ switch p .Type {
571+ case "Edm.String" :
572+ length := 200
573+ if p .MaxLength != "" && p .MaxLength != "max" {
574+ if n , err := fmt .Sscanf (p .MaxLength , "%d" , & length ); n == 0 || err != nil {
575+ length = 200
576+ }
577+ }
578+ return ast.DataType {Kind : ast .TypeString , Length : length }
579+ case "Edm.Int32" :
580+ return ast.DataType {Kind : ast .TypeInteger }
581+ case "Edm.Int64" :
582+ return ast.DataType {Kind : ast .TypeLong }
583+ case "Edm.Decimal" :
584+ return ast.DataType {Kind : ast .TypeDecimal }
585+ case "Edm.Boolean" :
586+ return ast.DataType {Kind : ast .TypeBoolean }
587+ case "Edm.DateTime" , "Edm.DateTimeOffset" , "Edm.Date" :
588+ return ast.DataType {Kind : ast .TypeDateTime }
589+ case "Edm.Double" , "Edm.Single" :
590+ return ast.DataType {Kind : ast .TypeDecimal }
591+ case "Edm.Byte" , "Edm.SByte" , "Edm.Int16" :
592+ return ast.DataType {Kind : ast .TypeInteger }
593+ case "Edm.Guid" :
594+ return ast.DataType {Kind : ast .TypeString , Length : 36 }
595+ case "Edm.Binary" :
596+ return ast.DataType {Kind : ast .TypeString , Length : 200 }
597+ default :
598+ return ast.DataType {Kind : ast .TypeString , Length : 200 }
599+ }
600+ }
601+
471602// ============================================================================
472603// AsyncAPI Contract Commands
473604// ============================================================================
0 commit comments