Null key overrides add null rows 2

This commit is contained in:
2026-05-13 22:22:20 +02:00
parent f3885cf93f
commit 985bffa8fb
2 changed files with 88 additions and 4 deletions
+39
View File
@@ -974,6 +974,10 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
rowByKey[rawKey] = row
}
}
if overrideRequestsNullRow(override) {
nullifyOverrideRow(row, columns, rowByKey)
continue
}
candidate := map[string]any{}
for field, value := range row {
candidate[field] = value
@@ -4058,6 +4062,41 @@ func resolveOverrideTarget(datasetName string, override map[string]any, rowByID
return nil, fmt.Errorf("dataset %s: override must specify id or key", datasetName)
}
func overrideRequestsNullRow(override map[string]any) bool {
rawKey, ok := override["key"]
if !ok {
return false
}
if rawKey == nil {
return true
}
typed, ok := rawKey.(string)
if !ok {
return false
}
return strings.TrimSpace(typed) == nullValue
}
func nullifyOverrideRow(row map[string]any, columns []string, rowByKey map[string]map[string]any) {
if oldKey, ok := row["key"].(string); ok && oldKey != "" {
if mapped, exists := rowByKey[oldKey]; exists {
mappedID, mappedHasID := mapped["id"].(int)
rowID, rowHasID := row["id"].(int)
if mappedHasID && rowHasID && mappedID == rowID {
delete(rowByKey, oldKey)
}
}
}
for field := range row {
if field != "id" {
delete(row, field)
}
}
for _, column := range columns {
row[column] = nullValue
}
}
func updateOverrideRowKey(datasetName string, row map[string]any, expanded map[string]any, rowByKey map[string]map[string]any, lockData map[string]int) (bool, error) {
oldKey, _ := row["key"].(string)
newKeyValue, hasKey := expanded["key"]
+49 -4
View File
@@ -6776,6 +6776,51 @@ func TestBuildGeneratedApplyAfterPrunesLowLockedID(t *testing.T) {
}
}
func TestBuildOverrideNullValueKeyNullifiesEntireRow(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "dense", "modules"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "dense", "base.json"), `{
"output": "dense.2da",
"columns": ["Label", "Value"],
"rows": [
{"id": 7, "key": "dense:old", "Label": "Old", "Value": "old"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "dense", "lock.json"), `{
"dense:old": 7
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "dense", "modules", "nullify.json"), `{
"overrides": [
{
"id": 7,
"key": "****",
"Label": "ShouldNotLeak",
"Value": "still-not-kept"
}
]
}`+"\n")
result, err := BuildNative(testProject(root), nil)
if err != nil {
t.Fatalf("BuildNative failed: %v", err)
}
got, err := os.ReadFile(filepath.Join(result.Output2DADir, "dense.2da"))
if err != nil {
t.Fatalf("read dense.2da: %v", err)
}
if !strings.Contains(string(got), "7\t****\t****\n") {
t.Fatalf("expected override key \"****\" to nullify row 7, got:\n%s", string(got))
}
lockRaw, err := os.ReadFile(filepath.Join(root, "topdata", "data", "dense", "lock.json"))
if err != nil {
t.Fatalf("read lock: %v", err)
}
if strings.Contains(string(lockRaw), `"dense:old"`) {
t.Fatalf("expected nullified row key to be pruned from lock data, got:\n%s", string(lockRaw))
}
}
func TestBuildCanonicalBaseitemsMatchesReference(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data"))
@@ -8116,7 +8161,7 @@ func TestBuildSupportsCanonicalSkills(t *testing.T) {
}
text := string(got)
if !strings.Contains(text, "Label\tName\tDescription\tIcon") ||
!strings.Contains(text, "3\tDiscipline_REMOVED\t343\t347\tisk_discipline\t1\tSTR\t0\t0\t****\t****\t****\t0\t1\n") ||
!strings.Contains(text, "3\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\t****\n") ||
!strings.Contains(text, "25\tCraftarmorsmithing\t1002\t1003\tisk_x2carm\t0\tDEX\t1\t1\t****\t****\tSKILL_CRAFT_ARMORSMITHING\t0\t0\n") ||
!strings.Contains(text, "28\tAthletics\t1000\t1001\tisk_athletics\t1\tSTR\t1\t1\t****\t****\tSKILL_ATHLETICS\t0\t0\n") ||
!strings.Contains(text, "40\tKnowledgearcana\t1004\t****\tisk_knarcana\t1\tINT\t0\t1\t****\t****\tSKILL_KNOWLEDGE_ARCANA\t0\t0\n") {
@@ -8170,7 +8215,7 @@ with open(os.path.join(out, "skills.2da"), "w", encoding="utf-8") as f:
emit(0, {"Label": "AnimalEmpathy", "Name": "269", "Description": "344", "Icon": "isk_aniemp", "Untrained": "0", "KeyAbility": "CHA", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "1", "Constant": "SKILL_ANIMAL_EMPATHY", "HostileSkill": "1", "HideFromLevelUp": "0"})
emit(1, {"Label": "Concentration", "Name": "270", "Description": "345", "Icon": "isk_concen", "Untrained": "1", "KeyAbility": "CON", "ArmorCheckPenalty": "0", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CONCENTRATION", "HostileSkill": "0", "HideFromLevelUp": "0"})
emit(2, {})
emit(3, {"Label": "Discipline_REMOVED", "Name": "343", "Description": "347", "Icon": "isk_discipline", "Untrained": "1", "KeyAbility": "STR", "ArmorCheckPenalty": "0", "AllClassesCanUse": "0", "Category": "****", "MaxCR": "****", "Constant": "****", "HostileSkill": "0", "HideFromLevelUp": "1"})
emit(3, {})
for row_id in range(4, 25):
emit(row_id, {})
emit(25, {"Label": "Craftarmorsmithing", "Name": "1002", "Description": "1003", "Icon": "isk_x2carm", "Untrained": "0", "KeyAbility": "DEX", "ArmorCheckPenalty": "1", "AllClassesCanUse": "1", "Category": "****", "MaxCR": "****", "Constant": "SKILL_CRAFT_ARMORSMITHING", "HostileSkill": "0", "HideFromLevelUp": "0"})
@@ -8545,7 +8590,7 @@ func TestBuildSupportsCanonicalSpells(t *testing.T) {
}
text := string(got)
if !strings.Contains(text, "Label\tName\tIconResRef") ||
!strings.Contains(text, "7\tBless_Weapon_OLD\t123") ||
!strings.Contains(text, "7\t****\t****\t****") ||
!strings.Contains(text, "840\tSpecialAttacks\t500") ||
!strings.Contains(text, "\t841\t") ||
!strings.Contains(text, "\t22\t") ||
@@ -8611,7 +8656,7 @@ with open(os.path.join(out, "spells.2da"), "w", encoding="utf-8") as f:
f.write("\t".join(cols) + "\n")
for row_id in range(0, 7):
emit(row_id, {})
emit(7, {"Label": "Bless_Weapon_OLD", "Name": "123", "Category": "8"})
emit(7, {})
for row_id in range(8, 840):
emit(row_id, {})
emit(840, {"Label": "SpecialAttacks", "Name": "16777741", "SubRadSpell1": "841", "Category": "22", "FeatID": "1000"})