Fix Lockfile Schematics

This commit is contained in:
2026-04-10 12:32:27 +02:00
parent 543945f099
commit 9300033e95
2 changed files with 130 additions and 2 deletions
+107 -2
View File
@@ -1,6 +1,7 @@
package topdata package topdata
import ( import (
"bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -3600,17 +3601,121 @@ func saveLockfile(path string, lockData map[string]int) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return fmt.Errorf("create lockfile parent: %w", err) return fmt.Errorf("create lockfile parent: %w", err)
} }
raw, err := json.MarshalIndent(lockData, "", " ") keys, err := orderedLockfileKeys(path, lockData)
if err != nil {
return fmt.Errorf("read lockfile order: %w", err)
}
raw, err := marshalOrderedLockfile(keys, lockData)
if err != nil { if err != nil {
return fmt.Errorf("marshal lockfile: %w", err) return fmt.Errorf("marshal lockfile: %w", err)
} }
raw = append(raw, '\n')
if err := os.WriteFile(path, raw, 0o644); err != nil { if err := os.WriteFile(path, raw, 0o644); err != nil {
return fmt.Errorf("write lockfile %s: %w", path, err) return fmt.Errorf("write lockfile %s: %w", path, err)
} }
return nil return nil
} }
func orderedLockfileKeys(path string, lockData map[string]int) ([]string, error) {
existingKeys, err := readLockfileKeyOrder(path)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
}
keys := make([]string, 0, len(lockData))
seen := make(map[string]struct{}, len(lockData))
for _, key := range existingKeys {
if _, ok := lockData[key]; !ok {
continue
}
keys = append(keys, key)
seen[key] = struct{}{}
}
extraKeys := make([]string, 0, len(lockData)-len(keys))
for key := range lockData {
if _, ok := seen[key]; ok {
continue
}
extraKeys = append(extraKeys, key)
}
slices.Sort(extraKeys)
keys = append(keys, extraKeys...)
return keys, nil
}
func readLockfileKeyOrder(path string) ([]string, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
decoder := json.NewDecoder(bytes.NewReader(raw))
token, err := decoder.Token()
if err != nil {
return nil, err
}
delim, ok := token.(json.Delim)
if !ok || delim != '{' {
return nil, fmt.Errorf("%s must contain a JSON object", path)
}
keys := []string{}
for decoder.More() {
token, err := decoder.Token()
if err != nil {
return nil, err
}
key, ok := token.(string)
if !ok {
return nil, fmt.Errorf("%s contains a non-string object key", path)
}
var value json.RawMessage
if err := decoder.Decode(&value); err != nil {
return nil, err
}
keys = append(keys, key)
}
token, err = decoder.Token()
if err != nil {
return nil, err
}
delim, ok = token.(json.Delim)
if !ok || delim != '}' {
return nil, fmt.Errorf("%s must contain a JSON object", path)
}
return keys, nil
}
func marshalOrderedLockfile(keys []string, lockData map[string]int) ([]byte, error) {
if len(keys) == 0 {
return []byte("{}\n"), nil
}
var buffer bytes.Buffer
buffer.WriteString("{\n")
for index, key := range keys {
keyRaw, err := json.Marshal(key)
if err != nil {
return nil, err
}
valueRaw, err := json.Marshal(lockData[key])
if err != nil {
return nil, err
}
buffer.WriteString(" ")
buffer.Write(keyRaw)
buffer.WriteString(": ")
buffer.Write(valueRaw)
if index < len(keys)-1 {
buffer.WriteByte(',')
}
buffer.WriteByte('\n')
}
buffer.WriteString("}\n")
return buffer.Bytes(), nil
}
func loadLegacyTLK(root string) (*legacyTLKData, error) { func loadLegacyTLK(root string) (*legacyTLKData, error) {
info, err := os.Stat(root) info, err := os.Stat(root)
if err != nil { if err != nil {
+23
View File
@@ -108,6 +108,29 @@ func TestResolvedTableRegistryRegistersConsistentTables(t *testing.T) {
} }
} }
func TestSaveLockfilePreservesExistingOrderAndAppendsNewKeys(t *testing.T) {
root := t.TempDir()
lockPath := filepath.Join(root, "lock.json")
writeFile(t, lockPath, "{\n \"b\": 2,\n \"a\": 1\n}\n")
if err := saveLockfile(lockPath, map[string]int{
"b": 2,
"a": 1,
"c": 3,
}); err != nil {
t.Fatalf("saveLockfile failed: %v", err)
}
got, err := os.ReadFile(lockPath)
if err != nil {
t.Fatalf("read lockfile: %v", err)
}
want := "{\n \"b\": 2,\n \"a\": 1,\n \"c\": 3\n}\n"
if string(got) != want {
t.Fatalf("unexpected lockfile contents:\n%s", string(got))
}
}
func TestResolvedTableRegistryRejectsDuplicateKeys(t *testing.T) { func TestResolvedTableRegistryRejectsDuplicateKeys(t *testing.T) {
_, err := newResolvedTableRegistry([]nativeCollectedDataset{ _, err := newResolvedTableRegistry([]nativeCollectedDataset{
{ {