Fix deliberate deletion reuse
This commit is contained in:
@@ -1357,6 +1357,14 @@ func newFeatGeneratedContext(dataset nativeDataset, lockData map[string]int) (*f
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
retiredFeatKeys, err := collectRetiredFeatKeys(filepath.Join(dataDir, "feat"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key := range retiredFeatKeys {
|
||||
delete(lockCopy, key)
|
||||
delete(existingFeat, key)
|
||||
}
|
||||
return &featGeneratedContext{
|
||||
sourceDir: sourceDir,
|
||||
dataDir: dataDir,
|
||||
@@ -1423,6 +1431,145 @@ func (c *featGeneratedContext) featKeyExists(key string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func collectRetiredFeatKeys(featDir string) (map[string]struct{}, error) {
|
||||
baseObj, err := loadJSONObject(filepath.Join(featDir, "base.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rowIDToKey := map[int]string{}
|
||||
rowKeyToID := map[string]int{}
|
||||
usedIDs := map[int]struct{}{}
|
||||
if rawRows, ok := baseObj["rows"].([]any); ok {
|
||||
for index, raw := range rawRows {
|
||||
row, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
rowID := index
|
||||
if rawID, ok := row["id"]; ok {
|
||||
parsed, err := asInt(rawID)
|
||||
if err == nil {
|
||||
rowID = parsed
|
||||
}
|
||||
}
|
||||
usedIDs[rowID] = struct{}{}
|
||||
if key, ok := row["key"].(string); ok && key != "" {
|
||||
rowIDToKey[rowID] = key
|
||||
rowKeyToID[key] = rowID
|
||||
}
|
||||
}
|
||||
}
|
||||
nextID := nextAvailableID(usedIDs)
|
||||
allocateNextID := func() int {
|
||||
rowID := nextID
|
||||
usedIDs[rowID] = struct{}{}
|
||||
nextID = nextAvailableID(usedIDs)
|
||||
return rowID
|
||||
}
|
||||
retired := map[string]struct{}{}
|
||||
modulePaths, err := collectModulePaths(filepath.Join(featDir, "modules"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, path := range modulePaths {
|
||||
obj, err := loadJSONObject(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rawEntries, ok := obj["entries"].(map[string]any); ok {
|
||||
for key, rawEntry := range rawEntries {
|
||||
entry, ok := rawEntry.(map[string]any)
|
||||
if !ok || key == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := rowKeyToID[key]; exists {
|
||||
continue
|
||||
}
|
||||
rowID := 0
|
||||
if rawID, ok := entry["id"]; ok {
|
||||
parsed, err := asInt(rawID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("entry %q in %s has non-numeric id %v", key, path, rawID)
|
||||
}
|
||||
rowID = parsed
|
||||
usedIDs[rowID] = struct{}{}
|
||||
nextID = nextAvailableID(usedIDs)
|
||||
} else {
|
||||
rowID = allocateNextID()
|
||||
}
|
||||
rowIDToKey[rowID] = key
|
||||
rowKeyToID[key] = rowID
|
||||
}
|
||||
}
|
||||
rawOverrides, ok := obj["overrides"].([]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for index, rawOverride := range rawOverrides {
|
||||
override, ok := rawOverride.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("override %d in %s must be an object", index, path)
|
||||
}
|
||||
rowID := 0
|
||||
hasRowID := false
|
||||
if rawID, ok := override["id"]; ok {
|
||||
parsed, err := asInt(rawID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("override %d in %s has non-numeric id %v", index, path, rawID)
|
||||
}
|
||||
rowID = parsed
|
||||
hasRowID = true
|
||||
} else if key, ok := override["key"].(string); ok && key != "" {
|
||||
if mappedID, exists := rowKeyToID[key]; exists {
|
||||
rowID = mappedID
|
||||
hasRowID = true
|
||||
}
|
||||
}
|
||||
if !hasRowID {
|
||||
continue
|
||||
}
|
||||
currentKey := rowIDToKey[rowID]
|
||||
if overrideRequestsNullRow(override) {
|
||||
if currentKey != "" {
|
||||
retired[currentKey] = struct{}{}
|
||||
delete(rowKeyToID, currentKey)
|
||||
delete(rowIDToKey, rowID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if rawKey, present := override["key"]; present {
|
||||
if rawKey == nil {
|
||||
if currentKey != "" {
|
||||
retired[currentKey] = struct{}{}
|
||||
delete(rowKeyToID, currentKey)
|
||||
delete(rowIDToKey, rowID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
newKey, ok := rawKey.(string)
|
||||
if !ok || newKey == "" || strings.TrimSpace(newKey) == nullValue {
|
||||
if currentKey != "" {
|
||||
retired[currentKey] = struct{}{}
|
||||
delete(rowKeyToID, currentKey)
|
||||
delete(rowIDToKey, rowID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if currentKey != "" && currentKey != newKey {
|
||||
retired[currentKey] = struct{}{}
|
||||
delete(rowKeyToID, currentKey)
|
||||
}
|
||||
if previousID, exists := rowKeyToID[newKey]; exists && previousID != rowID {
|
||||
delete(rowIDToKey, previousID)
|
||||
}
|
||||
rowKeyToID[newKey] = rowID
|
||||
rowIDToKey[rowID] = newKey
|
||||
}
|
||||
}
|
||||
}
|
||||
return retired, nil
|
||||
}
|
||||
|
||||
func (c *featGeneratedContext) familyHasExistingRows(familyKey string) bool {
|
||||
prefix := "feat:" + familyKey + "_"
|
||||
for key := range c.existingFeat {
|
||||
|
||||
Reference in New Issue
Block a user