Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ on:
branches: [main]
pull_request:

# Every job here only reads the checkout and runs tests. Granting the default
# token read-only at the workflow level means a new job cannot silently inherit
# write scope on the repository.
permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
Expand Down
22 changes: 17 additions & 5 deletions internal/store/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,20 @@ func (s *Store) loadSchemaAt(ctx context.Context, t *Table, branchID uuid.UUID,
}
dropped := map[string]int64{}
if len(droppedJSON) > 0 {
_ = json.Unmarshal(droppedJSON, &dropped)
if err := json.Unmarshal(droppedJSON, &dropped); err != nil {
return nil, fmt.Errorf("schema version %d has an unreadable dropped set: %w", e, err)
}
}
for k, at := range dropped {
if id, err := strconv.Atoi(k); err == nil {
v.Dropped[core.ColID(id)] = at
// Parse at the width of core.ColID rather than through int: nextColID
// derives the next column id from v.Dropped, so an entry lost to a
// truncating conversion or a bad key would let it reissue an id an
// earlier epoch already used (§10.5 rule 2).
id, err := strconv.ParseUint(k, 10, 32)
if err != nil {
return nil, fmt.Errorf("schema version %d has a bad dropped column id %q: %w", e, k, err)
}
v.Dropped[core.ColID(id)] = at
}
return v, nil
}
Expand Down Expand Up @@ -227,7 +235,11 @@ func (s *Store) writeSchemaVersion(ctx context.Context, tx adapter.Tx, t *Table,
}
dropped := map[string]int64{}
for id, at := range v.Dropped {
dropped[strconv.Itoa(int(id))] = at
// FormatUint, not Itoa(int(id)): core.ColID is uint32, and int is 32 bits
// on a 32-bit build, so routing through it could write a negative key that
// the ParseUint on the read side would then reject. This is the exact
// mirror of that parse.
dropped[strconv.FormatUint(uint64(id), 10)] = at
}
dj, _ := json.Marshal(dropped)

Expand All @@ -242,7 +254,7 @@ func (s *Store) writeSchemaVersion(ctx context.Context, tx adapter.Tx, t *Table,
// The mask width is recorded WITH the version, because changed_cols is over
// column ids and only grows; comparing masks across epochs zero-extends the
// shorter one (§10.5).
width := int(nextColID(v))
width := int64(nextColID(v))

return tx.Exec(ctx, s.ad.InsertOnConflict("datagit_schema_version",
[]string{"table_id", "branch_id", "epoch", "columns", "dropped", "digest",
Expand Down
Loading