Skip to content

Commit ac340f4

Browse files
n8mgrChrisSchinnerl
authored andcommitted
sqlite: add missing foreign keys
1 parent ced4ec0 commit ac340f4

File tree

7 files changed

+894
-40
lines changed

7 files changed

+894
-40
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: patch
3+
---
4+
5+
# Added missing block foreign key indices.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM docker.io/library/golang:1.23 AS builder
1+
FROM docker.io/library/golang:1.24 AS builder
22

33
WORKDIR /explored
44

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module go.sia.tech/explored
22

3-
go 1.23.2
4-
5-
toolchain go1.24.2
3+
go 1.24.2
64

75
require (
86
github.com/google/go-cmp v0.7.0

persist/sqlite/init.go

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,47 +16,70 @@ import (
1616
//go:embed init.sql
1717
var initDatabase string
1818

19+
func initializeSettings(tx *txn, target int64) error {
20+
_, err := tx.Exec(`INSERT INTO global_settings (id, db_version) VALUES (0, ?)`, target)
21+
return err
22+
}
23+
1924
func (s *Store) initNewDatabase(tx *txn, target int64) error {
2025
if _, err := tx.Exec(initDatabase); err != nil {
2126
return fmt.Errorf("failed to initialize database: %w", err)
22-
} else if err := setDBVersion(tx, target); err != nil {
23-
return fmt.Errorf("failed to set initial database version: %w", err)
27+
} else if err := initializeSettings(tx, target); err != nil {
28+
return fmt.Errorf("failed to initialize global settings: %w", err)
2429
}
2530
return nil
2631
}
2732

2833
func (s *Store) upgradeDatabase(current, target int64) error {
29-
log := s.log.Named("migrations")
30-
log.Info("migrating database", zap.Int64("current", current), zap.Int64("target", target))
34+
log := s.log.Named("migrations").With(zap.Int64("target", target))
35+
for ; current < target; current++ {
36+
version := current + 1 // initial schema is version 1, migration 0 is version 2, etc.
37+
log := log.With(zap.Int64("version", version))
38+
start := time.Now()
39+
fn := migrations[current-1]
40+
err := s.transaction(func(tx *txn) error {
41+
if _, err := tx.Exec("PRAGMA defer_foreign_keys=ON"); err != nil {
42+
return fmt.Errorf("failed to enable foreign key deferral: %w", err)
43+
} else if err := fn(tx, log); err != nil {
44+
return err
45+
} else if err := foreignKeyCheck(tx, log); err != nil {
46+
return fmt.Errorf("failed foreign key check: %w", err)
47+
}
48+
return setDBVersion(tx, version)
49+
})
50+
if err != nil {
51+
return fmt.Errorf("migration %d failed: %w", version, err)
52+
}
53+
log.Info("migration complete", zap.Duration("elapsed", time.Since(start)))
54+
}
55+
return nil
56+
}
3157

32-
// disable foreign key constraints during migration
33-
if _, err := s.db.Exec("PRAGMA foreign_keys = OFF"); err != nil {
34-
return fmt.Errorf("failed to disable foreign key constraints: %w", err)
58+
func foreignKeyCheck(txn *txn, log *zap.Logger) error {
59+
rows, err := txn.Query("PRAGMA foreign_key_check")
60+
if err != nil {
61+
return fmt.Errorf("failed to run foreign key check: %w", err)
3562
}
36-
defer func() {
37-
// re-enable foreign key constraints
38-
if _, err := s.db.Exec("PRAGMA foreign_keys = ON"); err != nil {
39-
log.Panic("failed to enable foreign key constraints", zap.Error(err))
40-
}
41-
}()
63+
defer rows.Close()
64+
var hasErrors bool
65+
for rows.Next() {
66+
var table string
67+
var rowid sql.NullInt64
68+
var fkTable string
69+
var fkRowid sql.NullInt64
4270

43-
return s.transaction(func(tx *txn) error {
44-
for _, fn := range migrations[current-1:] {
45-
current++
46-
start := time.Now()
47-
if err := fn(tx, log.With(zap.Int64("version", current))); err != nil {
48-
return fmt.Errorf("failed to migrate database to version %v: %w", current, err)
49-
}
50-
// check that no foreign key constraints were violated
51-
if err := tx.QueryRow("PRAGMA foreign_key_check").Scan(); !errors.Is(err, sql.ErrNoRows) {
52-
return fmt.Errorf("foreign key constraints are not satisfied")
53-
}
54-
log.Debug("migration complete", zap.Int64("current", current), zap.Int64("target", target), zap.Duration("elapsed", time.Since(start)))
71+
if err := rows.Scan(&table, &rowid, &fkTable, &fkRowid); err != nil {
72+
return fmt.Errorf("failed to scan foreign key check result: %w", err)
5573
}
56-
57-
// set the final database version
58-
return setDBVersion(tx, target)
59-
})
74+
hasErrors = true
75+
log.Error("foreign key constraint violated", zap.String("table", table), zap.Int64("rowid", rowid.Int64), zap.String("fkTable", fkTable), zap.Int64("fkRowid", fkRowid.Int64))
76+
}
77+
if err := rows.Err(); err != nil {
78+
return fmt.Errorf("failed to iterate foreign key check results: %w", err)
79+
} else if hasErrors {
80+
return errors.New("foreign key constraint violated")
81+
}
82+
return nil
6083
}
6184

6285
func (s *Store) init() error {

persist/sqlite/init.sql

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ CREATE TABLE siacoin_elements (
5757
address BLOB NOT NULL,
5858
value BLOB NOT NULL
5959
);
60-
60+
CREATE INDEX siacoin_elements_blocks_id_index ON siacoin_elements(block_id);
6161
CREATE INDEX siacoin_elements_maturity_height_index ON siacoin_elements(maturity_height);
6262
CREATE INDEX siacoin_elements_output_id_index ON siacoin_elements(output_id);
6363
CREATE INDEX siacoin_elements_address_spent_index ON siacoin_elements(address, spent_index);
@@ -74,7 +74,7 @@ CREATE TABLE siafund_elements (
7474
address BLOB NOT NULL,
7575
value BLOB NOT NULL
7676
);
77-
77+
CREATE INDEX siafund_elements_blocks_id_index ON siafund_elements(block_id);
7878
CREATE INDEX siafund_elements_output_id_index ON siafund_elements(output_id);
7979
CREATE INDEX siafund_elements_address_spent_index ON siafund_elements(address, spent_index);
8080

@@ -95,6 +95,7 @@ CREATE TABLE file_contract_elements (
9595
revision_number BLOB NOT NULL,
9696
UNIQUE(contract_id, revision_number)
9797
);
98+
CREATE INDEX file_contract_elements_blocks_id_index ON file_contract_elements(block_id);
9899
CREATE INDEX file_contract_elements_contract_id_revision_number_index ON file_contract_elements(contract_id, revision_number);
99100

100101
CREATE TABLE last_contract_revision (
@@ -116,6 +117,7 @@ CREATE TABLE last_contract_revision (
116117

117118
contract_element_id INTEGER UNIQUE REFERENCES file_contract_elements(id) ON DELETE CASCADE NOT NULL
118119
);
120+
CREATE INDEX last_contract_revision_confirmation_block_id_index ON last_contract_revision(confirmation_block_id);
119121

120122
CREATE TABLE file_contract_valid_proof_outputs (
121123
contract_id INTEGER REFERENCES file_contract_elements(id) ON DELETE CASCADE NOT NULL,
@@ -145,7 +147,6 @@ CREATE TABLE miner_payouts (
145147
output_id INTEGER REFERENCES siacoin_elements(id) ON DELETE CASCADE NOT NULL,
146148
UNIQUE(block_id, block_order)
147149
);
148-
149150
CREATE INDEX miner_payouts_block_id_index ON miner_payouts(block_id);
150151

151152
CREATE TABLE transactions (
@@ -457,6 +458,7 @@ CREATE TABLE v2_file_contract_elements (
457458

458459
UNIQUE(contract_id, revision_number)
459460
);
461+
CREATE INDEX v2_file_contract_elements_blocks_id_index ON v2_file_contract_elements(block_id);
460462
CREATE INDEX v2_file_contract_elements_contract_id_revision_number_index ON v2_file_contract_elements(contract_id, revision_number);
461463

462464
CREATE TABLE v2_last_contract_revision (
@@ -476,6 +478,7 @@ CREATE TABLE v2_last_contract_revision (
476478

477479
contract_element_id INTEGER UNIQUE REFERENCES v2_file_contract_elements(id) ON DELETE CASCADE NOT NULL
478480
);
481+
CREATE INDEX v2_last_contract_revision_confirmation_block_id_index ON v2_last_contract_revision(confirmation_block_id);
479482

480483
CREATE TABLE host_info (
481484
public_key BLOB PRIMARY KEY NOT NULL,
@@ -590,6 +593,3 @@ CREATE TABLE host_info_v2_netaddresses(
590593

591594
CREATE INDEX host_info_v2_netaddresses_public_key ON host_info_v2_netaddresses(public_key);
592595
CREATE INDEX host_info_v2_netaddresses_address ON host_info_v2_netaddresses(address);
593-
594-
-- initialize the global settings table
595-
INSERT INTO global_settings (id, db_version) VALUES (0, 0); -- should not be changed

persist/sqlite/migrations.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,19 @@ package sqlite
22

33
import "go.uber.org/zap"
44

5-
var migrations []func(tx *txn, log *zap.Logger) error
5+
func migrateV2(txn *txn, _ *zap.Logger) error {
6+
const createForeignKeyIndices = `
7+
CREATE INDEX siacoin_elements_blocks_id_index ON siacoin_elements(block_id);
8+
CREATE INDEX siafund_elements_blocks_id_index ON siafund_elements(block_id);
9+
CREATE INDEX file_contract_elements_blocks_id_index ON file_contract_elements(block_id);
10+
CREATE INDEX last_contract_revision_confirmation_block_id_index ON last_contract_revision(confirmation_block_id);
11+
CREATE INDEX v2_file_contract_elements_blocks_id_index ON v2_file_contract_elements(block_id);
12+
CREATE INDEX v2_last_contract_revision_confirmation_block_id_index ON v2_last_contract_revision(confirmation_block_id);
13+
`
14+
_, err := txn.Exec(createForeignKeyIndices)
15+
return err
16+
}
17+
18+
var migrations = []func(tx *txn, log *zap.Logger) error{
19+
migrateV2,
20+
}

0 commit comments

Comments
 (0)