@@ -89,6 +89,18 @@ func configureSQLite(ctx context.Context, db *sql.DB) error {
8989 if _ , err := db .ExecContext (ctx , "PRAGMA foreign_keys=ON" ); err != nil {
9090 return fmt .Errorf ("set foreign_keys: %w" , err )
9191 }
92+ // NORMAL is crash-safe with WAL and avoids an extra fsync per commit.
93+ if _ , err := db .ExecContext (ctx , "PRAGMA synchronous=NORMAL" ); err != nil {
94+ return fmt .Errorf ("set synchronous: %w" , err )
95+ }
96+ // 64 MB page cache (negative value = KiB).
97+ if _ , err := db .ExecContext (ctx , "PRAGMA cache_size=-65536" ); err != nil {
98+ return fmt .Errorf ("set cache_size: %w" , err )
99+ }
100+ // Keep temp tables and sort spills in memory.
101+ if _ , err := db .ExecContext (ctx , "PRAGMA temp_store=MEMORY" ); err != nil {
102+ return fmt .Errorf ("set temp_store: %w" , err )
103+ }
92104 return nil
93105}
94106
@@ -102,6 +114,7 @@ type migrationStep struct {
102114var allMigrations = []migrationStep {
103115 {version : 1 , file : "migrations/001_init.sql" },
104116 {version : 2 , file : "migrations/002_commitment_index.sql" },
117+ {version : 3 , file : "migrations/003_blob_index_unique.sql" },
105118}
106119
107120func (s * SQLiteStore ) migrate () error {
@@ -170,14 +183,23 @@ func (s *SQLiteStore) PutBlobs(ctx context.Context, blobs []types.Blob) error {
170183
171184 for i := range blobs {
172185 b := & blobs [i ]
173- if err := ensureSQLiteBlobInvariant (ctx , tx , b ); err != nil {
174- return err
175- }
176- if _ , err := stmt .ExecContext (ctx ,
186+ res , err := stmt .ExecContext (ctx ,
177187 b .Height , b .Namespace [:], b .Commitment , b .Data , b .ShareVersion , b .Signer , b .Index ,
178- ); err != nil {
188+ )
189+ if err != nil {
179190 return fmt .Errorf ("insert blob at height %d index %d: %w" , b .Height , b .Index , err )
180191 }
192+ n , err := res .RowsAffected ()
193+ if err != nil {
194+ return fmt .Errorf ("rows affected at height %d index %d: %w" , b .Height , b .Index , err )
195+ }
196+ if n == 0 {
197+ // INSERT OR IGNORE skipped this row — a unique constraint matched.
198+ // Verify it is an idempotent re-insert, not a data conflict.
199+ if err := verifyBlobNotConflicting (ctx , tx , b ); err != nil {
200+ return err
201+ }
202+ }
181203 }
182204
183205 return tx .Commit ()
@@ -367,12 +389,15 @@ func scanBlobRow(rows *sql.Rows) (types.Blob, error) {
367389 return b , nil
368390}
369391
370- func ensureSQLiteBlobInvariant (ctx context.Context , tx * sql.Tx , b * types.Blob ) error {
371- existingByIndex , err := queryBlobByIndex (ctx , tx , b .Namespace , b .Height , b .Index )
392+ // verifyBlobNotConflicting is called only when INSERT OR IGNORE skipped a row.
393+ // It distinguishes an idempotent re-insert (same data) from a true conflict
394+ // (different data at the same position or commitment).
395+ func verifyBlobNotConflicting (ctx context.Context , tx * sql.Tx , b * types.Blob ) error {
396+ existing , err := queryBlobByIndex (ctx , tx , b .Namespace , b .Height , b .Index )
372397 if err != nil {
373398 return err
374399 }
375- if existingByIndex != nil && ! sameBlob (existingByIndex , b ) {
400+ if existing != nil && ! sameBlob (existing , b ) {
376401 return fmt .Errorf ("blob conflict at height %d namespace %s index %d" , b .Height , b .Namespace , b .Index )
377402 }
378403
0 commit comments