Skip to content

Commit c3515c8

Browse files
authored
[MCP] Added fields section for entities and backward compatibility to mappings and key-fields. (#2917)
## Why make this change? This change introduces richer metadata for entity fields and keys in the Data API Builder configuration. The goal is to enable semantic metadata for documentation, client generation, and MCP tools, and to begin deprecating the legacy mappings and source.key-fields properties. ## What is this change? - Introduces a new `fields` property for entities in the config schema. - Each field supports: - `name`: database column name (required) - `alias`: exposed name for the field (optional) - `description`: field description (optional) - `primary-key`: whether the field is a primary key (optional) - Updates the JSON schema to enforce that `fields` cannot be used with legacy `mappings` or `source.key-fields`. - Updates CLI commands (`dab add`, `dab update`) to support specifying field alias, description, and primary-key. - Updates `dab validate` to warn if fields are missing and MCP is enabled. - Updates OpenAPI, GraphQL, and MCP `describe_entities` responses to include field descriptions. - Adds auto-migration logic to convert legacy `mappings` and `source.key-fields` to the new `fields` format when relevant CLI flags are used. - Maintains backward compatibility: legacy properties are still supported, but validation enforces that `fields` and legacy props are not mixed. ## How was this tested? All automated tests have been executed, updated as necessary, and completed successfully. Manually tested with following queries: 1. Update Entity with Field Mappings and Key Fields `dotnet C:\DAB\data-api-builder\src\out\cli\net8.0\Microsoft.DataApiBuilder.dll update BookAuthor --map "BookID:book_id,AuthorID:author_id" --source.key-fields "BookID,AuthorID" --config "C:\DAB\data-api-builder\src\Service\dab-config.json"` ------ Validate the Configuration `dotnet C:\DAB\data-api-builder\src\out\cli\net8.0\Microsoft.DataApiBuilder.dll validate -c "C:\DAB\data-api-builder\src\Service\dab-config.json"` ----- Update Entity Field Metadata (Primary Key) `dotnet C:\DAB\data-api-builder\src\out\cli\net8.0\Microsoft.DataApiBuilder.dll update Todo --fields.name owner_id --fields.primary-key True --config C:\DAB\data-api-builder\src\Service\dab-config.json` ---- Update Entity Key Fields Only `dotnet C:\DAB\data-api-builder\src\out\cli\net8.0\Microsoft.DataApiBuilder.dll update BookAuthor --source.key-fields "BookID"` ---- Sample new format `"fields": [ { "name": "id", "alias": "id", "description": "The unique identifier for a todo item", "primary-key": true }, { "name": "title", "alias": "title", "description": "The title of the todo item", "primary-key": false }, { "name": "completed", "alias": "completed", "description": "Indicates whether the todo item is completed", "primary-key": false }, { "name": "owner_id", "alias": "owner", "description": "Hello", "primary-key": true }, { "name": "position", "alias": "position", "description": "The position of the todo item in the list", "primary-key": false } ],` ----- GraphQL Introspection Example Query `{ __type(name: "Todo") { name description fields { name description } } }` Result `{ "data": { "__type": { "name": "Todo", "description": "Represents a todo item in the system", "fields": [ { "name": "id", "description": "The unique identifier for a todo item" }, { "name": "title", "description": "The title of the todo item" }, { "name": "completed", "description": "Indicates whether the todo item is completed" }, { "name": "owner", "description": "Hello" }, { "name": "position", "description": "The position of the todo item in the list" } ] } } }` ------ OpenAPI Schema Example `"Todo": { "type": "object", "properties": { "id": { "type": "string", "description": "The unique identifier for a todo item", "format": "" }, "title": { "type": "string", "description": "The title of the todo item", "format": "" }, "completed": { "type": "boolean", "description": "Indicates whether the todo item is completed", "format": "" }, "owner_id": { "type": "string", "description": "Hello", "format": "" }, "position": { "type": "number", "description": "The position of the todo item in the list", "format": "" } }, "description": "Represents a todo item in the system" }`
1 parent 4c5038f commit c3515c8

File tree

52 files changed

+1248
-297
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1248
-297
lines changed

schemas/dab.draft.schema.json

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,21 @@
797797
}
798798
]
799799
},
800+
"fields": {
801+
"type": "array",
802+
"description": "Defines the fields (columns) exposed for this entity, with metadata.",
803+
"items": {
804+
"type": "object",
805+
"properties": {
806+
"name": { "type": "string", "description": "Database column name." },
807+
"alias": { "type": "string", "description": "Exposed name for the field." },
808+
"description": { "type": "string", "description": "Field description." },
809+
"primary-key": { "type": "boolean", "description": "Indicates whether this field is a primary key." }
810+
},
811+
"required": ["name"]
812+
},
813+
"uniqueItems": true
814+
},
800815
"rest": {
801816
"oneOf": [
802817
{
@@ -1116,7 +1131,22 @@
11161131
}
11171132
}
11181133
},
1119-
"required": ["source", "permissions"]
1134+
"required": ["source", "permissions"],
1135+
"allOf": [
1136+
{
1137+
"if": {
1138+
"required": ["fields"]
1139+
},
1140+
"then": {
1141+
"not": {
1142+
"anyOf": [
1143+
{ "required": ["mappings"] },
1144+
{ "properties": { "source": { "properties": { "key-fields": { } }, "required": ["key-fields"] } } }
1145+
]
1146+
}
1147+
}
1148+
}
1149+
]
11201150
}
11211151
}
11221152
}

src/Cli.Tests/AddEntityTests.cs

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,11 @@ public Task AddNewEntityWhenEntitiesEmpty()
4949
parametersNameCollection: null,
5050
parametersDescriptionCollection: null,
5151
parametersRequiredCollection: null,
52-
parametersDefaultCollection: null
52+
parametersDefaultCollection: null,
53+
fieldsNameCollection: [],
54+
fieldsAliasCollection: [],
55+
fieldsDescriptionCollection: [],
56+
fieldsPrimaryKeyCollection: []
5357
);
5458
return ExecuteVerifyTest(options);
5559
}
@@ -82,7 +86,11 @@ public Task AddNewEntityWhenEntitiesNotEmpty()
8286
parametersNameCollection: null,
8387
parametersDescriptionCollection: null,
8488
parametersRequiredCollection: null,
85-
parametersDefaultCollection: null
89+
parametersDefaultCollection: null,
90+
fieldsNameCollection: [],
91+
fieldsAliasCollection: [],
92+
fieldsDescriptionCollection: [],
93+
fieldsPrimaryKeyCollection: []
8694
);
8795

8896
string initialConfiguration = AddPropertiesToJson(INITIAL_CONFIG, GetFirstEntityConfiguration());
@@ -118,7 +126,11 @@ public void AddDuplicateEntity()
118126
parametersNameCollection: null,
119127
parametersDescriptionCollection: null,
120128
parametersRequiredCollection: null,
121-
parametersDefaultCollection: null
129+
parametersDefaultCollection: null,
130+
fieldsNameCollection: [],
131+
fieldsAliasCollection: [],
132+
fieldsDescriptionCollection: [],
133+
fieldsPrimaryKeyCollection: []
122134
);
123135

124136
string initialConfiguration = AddPropertiesToJson(INITIAL_CONFIG, GetFirstEntityConfiguration());
@@ -158,7 +170,11 @@ public Task AddEntityWithAnExistingNameButWithDifferentCase()
158170
parametersNameCollection: null,
159171
parametersDescriptionCollection: null,
160172
parametersRequiredCollection: null,
161-
parametersDefaultCollection: null
173+
parametersDefaultCollection: null,
174+
fieldsNameCollection: [],
175+
fieldsAliasCollection: [],
176+
fieldsDescriptionCollection: [],
177+
fieldsPrimaryKeyCollection: []
162178
);
163179

164180
string initialConfiguration = AddPropertiesToJson(INITIAL_CONFIG, GetFirstEntityConfiguration());
@@ -193,7 +209,11 @@ public Task AddEntityWithCachingEnabled()
193209
parametersNameCollection: null,
194210
parametersDescriptionCollection: null,
195211
parametersRequiredCollection: null,
196-
parametersDefaultCollection: null
212+
parametersDefaultCollection: null,
213+
fieldsNameCollection: [],
214+
fieldsAliasCollection: [],
215+
fieldsDescriptionCollection: [],
216+
fieldsPrimaryKeyCollection: []
197217
);
198218

199219
return ExecuteVerifyTest(options);
@@ -234,7 +254,11 @@ public Task AddEntityWithPolicyAndFieldProperties(
234254
parametersNameCollection: null,
235255
parametersDescriptionCollection: null,
236256
parametersRequiredCollection: null,
237-
parametersDefaultCollection: null
257+
parametersDefaultCollection: null,
258+
fieldsNameCollection: [],
259+
fieldsAliasCollection: [],
260+
fieldsDescriptionCollection: [],
261+
fieldsPrimaryKeyCollection: []
238262
);
239263

240264
// Create VerifySettings and add all arguments to the method as parameters
@@ -271,7 +295,11 @@ public Task AddNewEntityWhenEntitiesWithSourceAsStoredProcedure()
271295
parametersNameCollection: null,
272296
parametersDescriptionCollection: ["This is a test parameter description."],
273297
parametersRequiredCollection: null,
274-
parametersDefaultCollection: null
298+
parametersDefaultCollection: null,
299+
fieldsNameCollection: [],
300+
fieldsAliasCollection: [],
301+
fieldsDescriptionCollection: [],
302+
fieldsPrimaryKeyCollection: []
275303
);
276304

277305
return ExecuteVerifyTest(options);
@@ -307,7 +335,11 @@ public Task TestAddStoredProcedureWithRestMethodsAndGraphQLOperations()
307335
parametersNameCollection: null,
308336
parametersDescriptionCollection: null,
309337
parametersRequiredCollection: null,
310-
parametersDefaultCollection: null
338+
parametersDefaultCollection: null,
339+
fieldsNameCollection: [],
340+
fieldsAliasCollection: [],
341+
fieldsDescriptionCollection: [],
342+
fieldsPrimaryKeyCollection: []
311343
);
312344

313345
return ExecuteVerifyTest(options);
@@ -339,7 +371,11 @@ public void AddEntityWithDescriptionAndVerifyInConfig()
339371
parametersNameCollection: null,
340372
parametersDescriptionCollection: null,
341373
parametersRequiredCollection: null,
342-
parametersDefaultCollection: null
374+
parametersDefaultCollection: null,
375+
fieldsNameCollection: [],
376+
fieldsAliasCollection: [],
377+
fieldsDescriptionCollection: [],
378+
fieldsPrimaryKeyCollection: []
343379
);
344380

345381
string config = INITIAL_CONFIG;
@@ -398,7 +434,11 @@ public void TestAddNewEntityWithSourceObjectHavingValidFields(
398434
parametersNameCollection: null,
399435
parametersDescriptionCollection: null,
400436
parametersRequiredCollection: null,
401-
parametersDefaultCollection: null
437+
parametersDefaultCollection: null,
438+
fieldsNameCollection: [],
439+
fieldsAliasCollection: [],
440+
fieldsDescriptionCollection: [],
441+
fieldsPrimaryKeyCollection: []
402442
);
403443

404444
RuntimeConfigLoader.TryParseConfig(INITIAL_CONFIG, out RuntimeConfig? runtimeConfig);
@@ -462,7 +502,11 @@ public Task TestAddNewSpWithDifferentRestAndGraphQLOptions(
462502
parametersNameCollection: null,
463503
parametersDescriptionCollection: null,
464504
parametersRequiredCollection: null,
465-
parametersDefaultCollection: null
505+
parametersDefaultCollection: null,
506+
fieldsNameCollection: [],
507+
fieldsAliasCollection: [],
508+
fieldsDescriptionCollection: [],
509+
fieldsPrimaryKeyCollection: []
466510
);
467511

468512
VerifySettings settings = new();
@@ -502,7 +546,11 @@ public void TestAddStoredProcedureWithConflictingRestGraphQLOptions(
502546
parametersNameCollection: null,
503547
parametersDescriptionCollection: null,
504548
parametersRequiredCollection: null,
505-
parametersDefaultCollection: null
549+
parametersDefaultCollection: null,
550+
fieldsNameCollection: [],
551+
fieldsAliasCollection: [],
552+
fieldsDescriptionCollection: [],
553+
fieldsPrimaryKeyCollection: []
506554
);
507555

508556
RuntimeConfigLoader.TryParseConfig(INITIAL_CONFIG, out RuntimeConfig? runtimeConfig);
@@ -545,7 +593,11 @@ public void TestAddEntityPermissionWithInvalidOperation(IEnumerable<string> perm
545593
parametersNameCollection: null,
546594
parametersDescriptionCollection: null,
547595
parametersRequiredCollection: null,
548-
parametersDefaultCollection: null
596+
parametersDefaultCollection: null,
597+
fieldsNameCollection: [],
598+
fieldsAliasCollection: [],
599+
fieldsDescriptionCollection: [],
600+
fieldsPrimaryKeyCollection: []
549601
);
550602

551603
RuntimeConfigLoader.TryParseConfig(INITIAL_CONFIG, out RuntimeConfig? runtimeConfig);

src/Cli.Tests/EndToEndTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -771,9 +771,11 @@ public void TestUpdateEntity()
771771
CollectionAssert.AreEqual(new string[] { "todo_id" }, relationship.LinkingSourceFields);
772772
CollectionAssert.AreEqual(new string[] { "id" }, relationship.LinkingTargetFields);
773773

774-
Assert.IsNotNull(entity.Mappings);
775-
Assert.AreEqual("identity", entity.Mappings["id"]);
776-
Assert.AreEqual("Company Name", entity.Mappings["name"]);
774+
Assert.IsNotNull(entity.Fields);
775+
Assert.AreEqual(2, entity.Fields.Count);
776+
Assert.AreEqual(entity.Fields[0].Alias, "identity");
777+
Assert.AreEqual(entity.Fields[1].Alias, "Company Name");
778+
Assert.IsNull(entity.Mappings);
777779
}
778780

779781
/// <summary>

src/Cli.Tests/Snapshots/UpdateEntityTests.TestConversionOfSourceObject_036a859f50ce167c.verified.txt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,18 @@
2727
MyEntity: {
2828
Source: {
2929
Object: s001.book,
30-
Type: View,
31-
KeyFields: [
32-
col1,
33-
col2
34-
]
30+
Type: View
3531
},
32+
Fields: [
33+
{
34+
Name: col1,
35+
PrimaryKey: true
36+
},
37+
{
38+
Name: col2,
39+
PrimaryKey: true
40+
}
41+
],
3642
GraphQL: {
3743
Singular: MyEntity,
3844
Plural: MyEntities,

src/Cli.Tests/Snapshots/UpdateEntityTests.TestConversionOfSourceObject_103655d39b48d89f.verified.txt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,18 @@
2727
MyEntity: {
2828
Source: {
2929
Object: s001.book,
30-
Type: Table,
31-
KeyFields: [
32-
id,
33-
name
34-
]
30+
Type: Table
3531
},
32+
Fields: [
33+
{
34+
Name: id,
35+
PrimaryKey: true
36+
},
37+
{
38+
Name: name,
39+
PrimaryKey: true
40+
}
41+
],
3642
GraphQL: {
3743
Singular: MyEntity,
3844
Plural: MyEntities,

src/Cli.Tests/Snapshots/UpdateEntityTests.TestConversionOfSourceObject_442649c7ef2176bd.verified.txt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,18 @@
2727
MyEntity: {
2828
Source: {
2929
Object: s001.book,
30-
Type: View,
31-
KeyFields: [
32-
col1,
33-
col2
34-
]
30+
Type: View
3531
},
32+
Fields: [
33+
{
34+
Name: col1,
35+
PrimaryKey: true
36+
},
37+
{
38+
Name: col2,
39+
PrimaryKey: true
40+
}
41+
],
3642
GraphQL: {
3743
Singular: MyEntity,
3844
Plural: MyEntities,

src/Cli.Tests/Snapshots/UpdateEntityTests.TestConversionOfSourceObject_c26902b0e44f97cd.verified.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
Object: s001.book,
3030
Type: stored-procedure
3131
},
32+
Fields: [
33+
{
34+
Name: id,
35+
PrimaryKey: true
36+
},
37+
{
38+
Name: name,
39+
PrimaryKey: true
40+
}
41+
],
3242
GraphQL: {
3343
Singular: MyEntity,
3444
Plural: MyEntities,

src/Cli.Tests/Snapshots/UpdateEntityTests.TestUpdateEntityWithMappings.verified.txt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@
3333
Object: MyTable,
3434
Type: Table
3535
},
36+
Fields: [
37+
{
38+
Name: id,
39+
Alias: Identity,
40+
PrimaryKey: false
41+
},
42+
{
43+
Name: name,
44+
Alias: Company Name,
45+
PrimaryKey: false
46+
}
47+
],
3648
GraphQL: {
3749
Singular: MyEntity,
3850
Plural: MyEntities,
@@ -53,11 +65,7 @@
5365
}
5466
]
5567
}
56-
],
57-
Mappings: {
58-
id: Identity,
59-
name: Company Name
60-
}
68+
]
6169
}
6270
}
6371
]

src/Cli.Tests/Snapshots/UpdateEntityTests.TestUpdateEntityWithSpecialCharacterInMappings.verified.txt

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,28 @@
3333
Object: MyTable,
3434
Type: Table
3535
},
36+
Fields: [
37+
{
38+
Name: Macaroni,
39+
Alias: Mac & Cheese,
40+
PrimaryKey: false
41+
},
42+
{
43+
Name: region,
44+
Alias: United State's Region,
45+
PrimaryKey: false
46+
},
47+
{
48+
Name: russian,
49+
Alias: русский,
50+
PrimaryKey: false
51+
},
52+
{
53+
Name: chinese,
54+
Alias: 中文,
55+
PrimaryKey: false
56+
}
57+
],
3658
GraphQL: {
3759
Singular: MyEntity,
3860
Plural: MyEntities,
@@ -53,13 +75,7 @@
5375
}
5476
]
5577
}
56-
],
57-
Mappings: {
58-
chinese: 中文,
59-
Macaroni: Mac & Cheese,
60-
region: United State's Region,
61-
russian: русский
62-
}
78+
]
6379
}
6480
}
6581
]

0 commit comments

Comments
 (0)