Skip to content

Commit b6f7c6f

Browse files
akoclaude
andcommitted
fix: revert 3 CI-breaking regressions from PR #35, fix DateTime roundtrip
Reverted: - DataTypes$LongType → DataTypes$IntegerType (4 files): Mendix type cache doesn't have LongType; Long must serialize as IntegerType - XPath token quoting restored: '[%Token%]' not [%Token%] (CE0161) - ComboBox setAssociationRef AttributeRef removed: 2-part association path causes ArgumentNullException in Mendix AttributeRef.AttributeId Fixed: - DateTime roundtrip: absent LocalizeDate defaults to DateTime, not Date - Business events writer sets LocalizeDate for Date/DateTime distinction - cmd_diff_local.go: same absent-LocalizeDate fix in both code paths Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent bca4b48 commit b6f7c6f

File tree

10 files changed

+34
-31
lines changed

10 files changed

+34
-31
lines changed

mdl/executor/bugfix_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,13 @@ func TestExpressionToXPath_TokenQuoting(t *testing.T) {
258258
name: "Token_CurrentDateTime",
259259
expr: &ast.TokenExpr{Token: "CurrentDateTime"},
260260
wantExpr: "[%CurrentDateTime%]",
261-
wantXP: "[%CurrentDateTime%]",
261+
wantXP: "'[%CurrentDateTime%]'",
262262
},
263263
{
264264
name: "Token_CurrentUser",
265265
expr: &ast.TokenExpr{Token: "CurrentUser"},
266266
wantExpr: "[%CurrentUser%]",
267-
wantXP: "[%CurrentUser%]",
267+
wantXP: "'[%CurrentUser%]'",
268268
},
269269
{
270270
name: "BinaryExpr_with_token",
@@ -274,7 +274,7 @@ func TestExpressionToXPath_TokenQuoting(t *testing.T) {
274274
Right: &ast.TokenExpr{Token: "CurrentDateTime"},
275275
},
276276
wantExpr: "DueDate < [%CurrentDateTime%]",
277-
wantXP: "DueDate < [%CurrentDateTime%]",
277+
wantXP: "DueDate < '[%CurrentDateTime%]'",
278278
},
279279
{
280280
name: "Variable_unchanged",
@@ -358,7 +358,7 @@ func TestExpressionToXPath_XPathPathExpr(t *testing.T) {
358358
Operator: "=",
359359
Right: &ast.TokenExpr{Token: "CurrentUser"},
360360
},
361-
wantXP: "System.owner = [%CurrentUser%]",
361+
wantXP: "System.owner = '[%CurrentUser%]'",
362362
},
363363
{
364364
name: "not_with_path",

mdl/executor/cmd_dbconnection.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func astDataTypeToDBType(dt ast.DataType) string {
326326
case ast.TypeInteger:
327327
return "DataTypes$IntegerType"
328328
case ast.TypeLong:
329-
return "DataTypes$LongType"
329+
return "DataTypes$IntegerType" // Long maps to IntegerType in DataTypes
330330
case ast.TypeDecimal:
331331
return "DataTypes$DecimalType"
332332
case ast.TypeBoolean:

mdl/executor/cmd_diff_local.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,8 @@ func (e *Executor) attributeBsonToMDL(raw map[string]any) string {
374374
case strings.Contains(attrType, "BooleanAttributeType"):
375375
typeStr = "Boolean"
376376
case strings.Contains(attrType, "DateTimeAttributeType"):
377-
if localize, ok := raw["LocalizeDate"].(bool); ok && localize {
377+
localize, ok := raw["LocalizeDate"].(bool)
378+
if !ok || localize {
378379
typeStr = "DateTime"
379380
} else {
380381
typeStr = "Date"
@@ -408,7 +409,8 @@ func (e *Executor) attributeBsonToMDL(raw map[string]any) string {
408409
case strings.Contains(typeType, "BooleanAttributeType"):
409410
typeStr = "Boolean"
410411
case strings.Contains(typeType, "DateTimeAttributeType"):
411-
if localize, ok := typeObj["LocalizeDate"].(bool); ok && localize {
412+
localize, ok := typeObj["LocalizeDate"].(bool)
413+
if !ok || localize {
412414
typeStr = "DateTime"
413415
} else {
414416
typeStr = "Date"

mdl/executor/cmd_microflows_helpers.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ func expressionToString(expr ast.Expression) string {
140140
}
141141

142142
// expressionToXPath converts an AST Expression to an XPath constraint string.
143-
// Mendix tokens like [%CurrentDateTime%] are written unquoted — they are
144-
// special XPath placeholders, not string literals.
143+
// Unlike expressionToString (for Mendix expressions), XPath requires Mendix
144+
// tokens like [%CurrentDateTime%] to be quoted: '[%CurrentDateTime%]'.
145145
func expressionToXPath(expr ast.Expression) string {
146146
if expr == nil {
147147
return ""
@@ -152,7 +152,7 @@ func expressionToXPath(expr ast.Expression) string {
152152

153153
switch e := expr.(type) {
154154
case *ast.TokenExpr:
155-
return "[%" + e.Token + "%]"
155+
return "'[%" + e.Token + "%]'"
156156
case *ast.BinaryExpr:
157157
left := expressionToXPath(e.Left)
158158
right := expressionToXPath(e.Right)

mdl/executor/cmd_pages_builder_input.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,24 +172,14 @@ func setDataSource(val bson.D, ds pages.DataSource) bson.D {
172172
return result
173173
}
174174

175-
// setAssociationRef sets both the AttributeRef and EntityRef fields in a WidgetValue
176-
// for an association binding on a pluggable widget.
177-
// AttributeRef stores the association qualified name (e.g., "Module.Order_Customer").
178-
// EntityRef uses DomainModels$IndirectEntityRef with a Steps array containing
175+
// setAssociationRef sets the EntityRef field in a WidgetValue for an association binding
176+
// on a pluggable widget. Uses DomainModels$IndirectEntityRef with a Steps array containing
179177
// a DomainModels$EntityRefStep that specifies the association and destination entity.
180-
// MxBuild requires both: association path in AttributeRef (CE8812) and
181-
// target entity in EntityRef (CE0642).
178+
// MxBuild requires the EntityRef to resolve the association target (CE0642).
182179
func setAssociationRef(val bson.D, assocPath string, entityName string) bson.D {
183180
result := make(bson.D, 0, len(val))
184181
for _, elem := range val {
185-
if elem.Key == "AttributeRef" && assocPath != "" {
186-
result = append(result, bson.E{Key: "AttributeRef", Value: bson.D{
187-
{Key: "$ID", Value: mpr.IDToBsonBinary(mpr.GenerateID())},
188-
{Key: "$Type", Value: "DomainModels$AttributeRef"},
189-
{Key: "Attribute", Value: assocPath},
190-
{Key: "EntityRef", Value: nil},
191-
}})
192-
} else if elem.Key == "EntityRef" && entityName != "" {
182+
if elem.Key == "EntityRef" && entityName != "" {
193183
result = append(result, bson.E{Key: "EntityRef", Value: bson.D{
194184
{Key: "$ID", Value: mpr.IDToBsonBinary(mpr.GenerateID())},
195185
{Key: "$Type", Value: "DomainModels$IndirectEntityRef"},

sdk/mpr/parser_domainmodel.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,14 @@ func parseAttributeType(raw map[string]any) domainmodel.AttributeType {
299299
t.ID = typeID
300300
return t
301301
case "DomainModels$DateTimeAttributeType":
302-
localize, _ := raw["LocalizeDate"].(bool)
303-
if localize {
302+
localize, ok := raw["LocalizeDate"].(bool)
303+
if !ok || localize {
304+
// Default to DateTime when LocalizeDate is absent or true
304305
t := &domainmodel.DateTimeAttributeType{LocalizeDate: true}
305306
t.ID = typeID
306307
return t
307308
}
308-
// LocalizeDate=false means date-only type
309+
// LocalizeDate explicitly false means date-only type
309310
dt := &domainmodel.DateAttributeType{}
310311
dt.ID = typeID
311312
return dt

sdk/mpr/writer_businessevents.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,17 @@ func serializeBusinessEventAttribute(attr *model.BusinessEventAttribute) bson.M
154154
}
155155

156156
// Convert attribute type to BSON format: "Long" → {"$Type": "DomainModels$LongAttributeType", "$ID": ...}
157-
attrDoc["AttributeType"] = bson.M{
157+
attrTypeDoc := bson.M{
158158
"$Type": attributeTypeToBsonType(attr.AttributeType),
159159
"$ID": idToBsonBinary(generateUUID()),
160160
}
161+
// Date and DateTime both use DateTimeAttributeType; distinguish via LocalizeDate
162+
if attr.AttributeType == "DateTime" {
163+
attrTypeDoc["LocalizeDate"] = true
164+
} else if attr.AttributeType == "Date" {
165+
attrTypeDoc["LocalizeDate"] = false
166+
}
167+
attrDoc["AttributeType"] = attrTypeDoc
161168

162169
return attrDoc
163170
}

sdk/mpr/writer_enumeration.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,11 @@ func serializeConstantDataType(dt model.ConstantDataType) bson.D {
161161
{Key: "$Type", Value: "DataTypes$IntegerType"},
162162
}
163163
case "Long":
164+
// Mendix uses IntegerType for both Integer and Long in BSON storage.
165+
// DataTypes$LongType does not exist in the metamodel type cache.
164166
return bson.D{
165167
{Key: "$ID", Value: typeID},
166-
{Key: "$Type", Value: "DataTypes$LongType"},
168+
{Key: "$Type", Value: "DataTypes$IntegerType"},
167169
}
168170
case "Decimal":
169171
return bson.D{

sdk/mpr/writer_microflow.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,10 @@ func serializeMicroflowDataType(dt microflows.DataType) bson.D {
254254
{Key: "$Type", Value: "DataTypes$IntegerType"},
255255
}
256256
case *microflows.LongType:
257+
// Mendix uses IntegerType for 64-bit integers (Long in Java)
257258
return bson.D{
258259
{Key: "$ID", Value: idToBsonBinary(generateUUID())},
259-
{Key: "$Type", Value: "DataTypes$LongType"},
260+
{Key: "$Type", Value: "DataTypes$IntegerType"},
260261
}
261262
case *microflows.DecimalType:
262263
return bson.D{

sdk/mpr/writer_rest.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ func serializeRestDataType(typeName string) bson.M {
269269
case "Integer":
270270
bsonType = "DataTypes$IntegerType"
271271
case "Long":
272-
bsonType = "DataTypes$LongType"
272+
bsonType = "DataTypes$IntegerType" // Long maps to IntegerType in DataTypes
273273
case "Decimal":
274274
bsonType = "DataTypes$DecimalType"
275275
case "Boolean":

0 commit comments

Comments
 (0)