Fix: replaced keys become stale
This commit is contained in:
@@ -207,7 +207,7 @@ func mergeExpansionData(collected []nativeCollectedDataset) ([]nativeCollectedDa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dataset %s: collect referenced keys: %w", targetDS.Dataset.Name, err)
|
return nil, fmt.Errorf("dataset %s: collect referenced keys: %w", targetDS.Dataset.Name, err)
|
||||||
}
|
}
|
||||||
if pruned, updated := pruneLockDataToActiveRows(targetDS.LockData, targetDS.Rows, referencedKeys); pruned > 0 || updated > 0 {
|
if pruned, updated := pruneLockDataToActiveRows(targetDS.LockData, targetDS.Rows, referencedKeys, targetDS.RetiredKeys); pruned > 0 || updated > 0 {
|
||||||
targetDS.LockPruned += pruned
|
targetDS.LockPruned += pruned
|
||||||
targetDS.LockModified = true
|
targetDS.LockModified = true
|
||||||
collected[targetIndex] = targetDS
|
collected[targetIndex] = targetDS
|
||||||
|
|||||||
+40
-10
@@ -43,6 +43,7 @@ type nativeCollectedDataset struct {
|
|||||||
Columns []string
|
Columns []string
|
||||||
Rows []map[string]any
|
Rows []map[string]any
|
||||||
LockData map[string]int
|
LockData map[string]int
|
||||||
|
RetiredKeys map[string]struct{}
|
||||||
TableKey string
|
TableKey string
|
||||||
LockAdded int
|
LockAdded int
|
||||||
LockPruned int
|
LockPruned int
|
||||||
@@ -714,6 +715,7 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
|||||||
lockModified := false
|
lockModified := false
|
||||||
lockAdded := 0
|
lockAdded := 0
|
||||||
lockPruned := 0
|
lockPruned := 0
|
||||||
|
retiredKeys := map[string]struct{}{}
|
||||||
|
|
||||||
for key, rowID := range lockData {
|
for key, rowID := range lockData {
|
||||||
if rowID <= baseBoundaryID {
|
if rowID <= baseBoundaryID {
|
||||||
@@ -871,13 +873,16 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
keyChanged, err := updateOverrideRowKey(dataset.Name, existing, expanded, rowByKey, lockData)
|
keyChanged, retiredKey, err := updateOverrideRowKey(dataset.Name, existing, expanded, rowByKey, lockData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if keyChanged {
|
if keyChanged {
|
||||||
lockModified = true
|
lockModified = true
|
||||||
}
|
}
|
||||||
|
if retiredKey != "" {
|
||||||
|
retiredKeys[retiredKey] = struct{}{}
|
||||||
|
}
|
||||||
for field, value := range expanded {
|
for field, value := range expanded {
|
||||||
if field == "id" || field == "_tlk" {
|
if field == "id" || field == "_tlk" {
|
||||||
continue
|
continue
|
||||||
@@ -1010,13 +1015,16 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
keyChanged, err := updateOverrideRowKey(dataset.Name, row, expanded, rowByKey, lockData)
|
keyChanged, retiredKey, err := updateOverrideRowKey(dataset.Name, row, expanded, rowByKey, lockData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if keyChanged {
|
if keyChanged {
|
||||||
lockModified = true
|
lockModified = true
|
||||||
}
|
}
|
||||||
|
if retiredKey != "" {
|
||||||
|
retiredKeys[retiredKey] = struct{}{}
|
||||||
|
}
|
||||||
for field, value := range expanded {
|
for field, value := range expanded {
|
||||||
if field == "id" || field == "_tlk" {
|
if field == "id" || field == "_tlk" {
|
||||||
continue
|
continue
|
||||||
@@ -1068,7 +1076,7 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nativeCollectedDataset{}, fmt.Errorf("dataset %s: collect referenced keys: %w", dataset.Name, err)
|
return nativeCollectedDataset{}, fmt.Errorf("dataset %s: collect referenced keys: %w", dataset.Name, err)
|
||||||
}
|
}
|
||||||
if pruned, updated := pruneLockDataToActiveRows(lockData, rows, referencedKeys); pruned > 0 || updated > 0 {
|
if pruned, updated := pruneLockDataToActiveRows(lockData, rows, referencedKeys, retiredKeys); pruned > 0 || updated > 0 {
|
||||||
lockModified = true
|
lockModified = true
|
||||||
lockPruned += pruned
|
lockPruned += pruned
|
||||||
}
|
}
|
||||||
@@ -1082,13 +1090,14 @@ func collectBaseDataset(dataset nativeDataset) (nativeCollectedDataset, error) {
|
|||||||
Columns: columns,
|
Columns: columns,
|
||||||
Rows: rows,
|
Rows: rows,
|
||||||
LockData: lockData,
|
LockData: lockData,
|
||||||
|
RetiredKeys: retiredKeys,
|
||||||
LockAdded: lockAdded,
|
LockAdded: lockAdded,
|
||||||
LockPruned: lockPruned,
|
LockPruned: lockPruned,
|
||||||
LockModified: lockModified,
|
LockModified: lockModified,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pruneLockDataToActiveRows(lockData map[string]int, rows []map[string]any, referencedKeys map[string]struct{}) (int, int) {
|
func pruneLockDataToActiveRows(lockData map[string]int, rows []map[string]any, referencedKeys map[string]struct{}, retiredKeys map[string]struct{}) (int, int) {
|
||||||
if len(lockData) == 0 {
|
if len(lockData) == 0 {
|
||||||
return 0, 0
|
return 0, 0
|
||||||
}
|
}
|
||||||
@@ -1108,6 +1117,11 @@ func pruneLockDataToActiveRows(lockData map[string]int, rows []map[string]any, r
|
|||||||
}
|
}
|
||||||
pruned := 0
|
pruned := 0
|
||||||
for key := range lockData {
|
for key := range lockData {
|
||||||
|
if _, retired := retiredKeys[key]; retired {
|
||||||
|
delete(lockData, key)
|
||||||
|
pruned++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := activeKeys[key]; ok {
|
if _, ok := activeKeys[key]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -4123,14 +4137,15 @@ func nullifyOverrideRow(row map[string]any, columns []string, rowByKey map[strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateOverrideRowKey(datasetName string, row map[string]any, expanded map[string]any, rowByKey map[string]map[string]any, lockData map[string]int) (bool, error) {
|
func updateOverrideRowKey(datasetName string, row map[string]any, expanded map[string]any, rowByKey map[string]map[string]any, lockData map[string]int) (bool, string, error) {
|
||||||
oldKey, _ := row["key"].(string)
|
oldKey, _ := row["key"].(string)
|
||||||
newKeyValue, hasKey := expanded["key"]
|
newKeyValue, hasKey := expanded["key"]
|
||||||
if !hasKey {
|
if !hasKey {
|
||||||
return false, nil
|
return false, "", nil
|
||||||
}
|
}
|
||||||
rowID, _ := row["id"].(int)
|
rowID, _ := row["id"].(int)
|
||||||
changed := false
|
changed := false
|
||||||
|
retiredKey := ""
|
||||||
if oldKey != "" {
|
if oldKey != "" {
|
||||||
if mapped, ok := rowByKey[oldKey]; ok {
|
if mapped, ok := rowByKey[oldKey]; ok {
|
||||||
mappedID, mappedHasID := mapped["id"].(int)
|
mappedID, mappedHasID := mapped["id"].(int)
|
||||||
@@ -4142,12 +4157,22 @@ func updateOverrideRowKey(datasetName string, row map[string]any, expanded map[s
|
|||||||
}
|
}
|
||||||
if newKeyValue == nil {
|
if newKeyValue == nil {
|
||||||
delete(row, "key")
|
delete(row, "key")
|
||||||
return changed, nil
|
if oldKey != "" {
|
||||||
|
retiredKey = oldKey
|
||||||
|
delete(lockData, oldKey)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
return changed, retiredKey, nil
|
||||||
}
|
}
|
||||||
newKey, ok := newKeyValue.(string)
|
newKey, ok := newKeyValue.(string)
|
||||||
if !ok || newKey == "" || strings.TrimSpace(newKey) == nullValue {
|
if !ok || newKey == "" || strings.TrimSpace(newKey) == nullValue {
|
||||||
delete(row, "key")
|
delete(row, "key")
|
||||||
return changed, nil
|
if oldKey != "" {
|
||||||
|
retiredKey = oldKey
|
||||||
|
delete(lockData, oldKey)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
return changed, retiredKey, nil
|
||||||
}
|
}
|
||||||
conflictingID := -1
|
conflictingID := -1
|
||||||
if conflicting, ok := rowByKey[newKey]; ok && conflicting != nil {
|
if conflicting, ok := rowByKey[newKey]; ok && conflicting != nil {
|
||||||
@@ -4161,20 +4186,25 @@ func updateOverrideRowKey(datasetName string, row map[string]any, expanded map[s
|
|||||||
}
|
}
|
||||||
row["key"] = newKey
|
row["key"] = newKey
|
||||||
rowByKey[newKey] = row
|
rowByKey[newKey] = row
|
||||||
|
if oldKey != "" && oldKey != newKey {
|
||||||
|
retiredKey = oldKey
|
||||||
|
delete(lockData, oldKey)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
if existingID, ok := lockData[newKey]; ok {
|
if existingID, ok := lockData[newKey]; ok {
|
||||||
if existingID != rowID {
|
if existingID != rowID {
|
||||||
if conflictingID >= 0 && existingID == conflictingID {
|
if conflictingID >= 0 && existingID == conflictingID {
|
||||||
lockData[newKey] = rowID
|
lockData[newKey] = rowID
|
||||||
changed = true
|
changed = true
|
||||||
} else {
|
} else {
|
||||||
return false, fmt.Errorf("dataset %s: key %q is locked to id %d and cannot be rewritten to id %d", datasetName, newKey, existingID, rowID)
|
return false, "", fmt.Errorf("dataset %s: key %q is locked to id %d and cannot be rewritten to id %d", datasetName, newKey, existingID, rowID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lockData[newKey] = rowID
|
lockData[newKey] = rowID
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
return changed, nil
|
return changed, retiredKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeCollectedSpellImpactScripts(rows []map[string]any, baseRowKeys map[string]struct{}) {
|
func normalizeCollectedSpellImpactScripts(rows []map[string]any, baseRowKeys map[string]struct{}) {
|
||||||
|
|||||||
@@ -6433,6 +6433,57 @@ func TestResolveOverrideTargetPrefersIDOverKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUpdateOverrideRowKeyRetiresOldKeyEvenIfReferenced(t *testing.T) {
|
||||||
|
row := map[string]any{"id": 443, "key": "spells:etherealness"}
|
||||||
|
rowByKey := map[string]map[string]any{
|
||||||
|
"spells:etherealness": row,
|
||||||
|
}
|
||||||
|
lockData := map[string]int{
|
||||||
|
"spells:etherealness": 443,
|
||||||
|
}
|
||||||
|
expanded := map[string]any{
|
||||||
|
"key": "spells:greater_sanctuary",
|
||||||
|
}
|
||||||
|
|
||||||
|
changed, retiredKey, err := updateOverrideRowKey("spells", row, expanded, rowByKey, lockData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("updateOverrideRowKey failed: %v", err)
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
t.Fatal("expected key reassignment to mark the row changed")
|
||||||
|
}
|
||||||
|
if retiredKey != "spells:etherealness" {
|
||||||
|
t.Fatalf("expected retired key spells:etherealness, got %q", retiredKey)
|
||||||
|
}
|
||||||
|
if row["key"] != "spells:greater_sanctuary" {
|
||||||
|
t.Fatalf("expected row key to be updated, got %v", row["key"])
|
||||||
|
}
|
||||||
|
if _, ok := lockData["spells:etherealness"]; ok {
|
||||||
|
t.Fatalf("expected old key to be removed from lock data, got %#v", lockData)
|
||||||
|
}
|
||||||
|
if lockData["spells:greater_sanctuary"] != 443 {
|
||||||
|
t.Fatalf("expected new key to be locked to row 443, got %#v", lockData)
|
||||||
|
}
|
||||||
|
|
||||||
|
referencedKeys := map[string]struct{}{
|
||||||
|
"spells:etherealness": {},
|
||||||
|
}
|
||||||
|
retiredKeys := map[string]struct{}{
|
||||||
|
retiredKey: {},
|
||||||
|
}
|
||||||
|
rows := []map[string]any{row}
|
||||||
|
pruned, updated := pruneLockDataToActiveRows(lockData, rows, referencedKeys, retiredKeys)
|
||||||
|
if updated != 0 {
|
||||||
|
t.Fatalf("expected no lock id updates after prune, got %d", updated)
|
||||||
|
}
|
||||||
|
if pruned != 0 {
|
||||||
|
t.Fatalf("expected no additional prune once old key was already retired, got %d", pruned)
|
||||||
|
}
|
||||||
|
if _, ok := lockData["spells:etherealness"]; ok {
|
||||||
|
t.Fatalf("expected retired key to stay removed even when referenced, got %#v", lockData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildPrunesLockedIDThatTargetsManualBaseSpace(t *testing.T) {
|
func TestBuildPrunesLockedIDThatTargetsManualBaseSpace(t *testing.T) {
|
||||||
root := testProjectRoot(t)
|
root := testProjectRoot(t)
|
||||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "dense", "modules"))
|
mkdirAll(t, filepath.Join(root, "topdata", "data", "dense", "modules"))
|
||||||
|
|||||||
Reference in New Issue
Block a user