this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add basic duckdb store

Paul Frazee 43a42cfd 37766c62

+910 -84
+5 -2
cmd/butterfly/main.go
··· 16 16 var ( 17 17 carFile = flag.String("car", "", "Path to CAR file to read") 18 18 did = flag.String("did", "", "DID to fetch (required)") 19 - outputMode = flag.String("output", "stats", "Output mode: stats, passthrough, or tarfiles") 19 + outputMode = flag.String("output", "stats", "Output mode: stats, passthrough, tarfiles, or duckdb") 20 20 outputDir = flag.String("output-dir", "./output", "Output directory for tarfiles mode") 21 + dbPath = flag.String("db", "./butterfly.db", "Path to DuckDB database file") 21 22 help = flag.Bool("help", false, "Show help") 22 23 ) 23 24 flag.Parse() 24 25 25 26 if *help || *carFile == "" || *did == "" { 26 - fmt.Fprintf(os.Stderr, "Usage: butterfly -car <path> -did <did> [-output stats|passthrough|tarfiles] [-output-dir <dir>]\n") 27 + fmt.Fprintf(os.Stderr, "Usage: butterfly -car <path> -did <did> [-output stats|passthrough|tarfiles|duckdb] [-output-dir <dir>] [-db <path>]\n") 27 28 flag.PrintDefaults() 28 29 os.Exit(1) 29 30 } ··· 43 44 s = &store.StdoutStore{Mode: store.StdoutStoreModeStats} 44 45 case "tarfiles": 45 46 s = store.NewTarfilesStore(*outputDir) 47 + case "duckdb": 48 + s = store.NewDuckdbStore(*dbPath) 46 49 default: 47 50 logger.Fatalf("unknown output mode: %s", *outputMode) 48 51 }
+351 -4
cmd/butterfly/store/duckdb.go
··· 1 - /* 2 - DuckDB storage interface 3 - */ 1 + // Package store provides a duckdb implementation of the Store interface 2 + package store 3 + 4 + import ( 5 + "context" 6 + "database/sql" 7 + "encoding/json" 8 + "fmt" 9 + "os" 10 + "path/filepath" 11 + "sync" 12 + "time" 13 + 14 + _ "github.com/marcboeker/go-duckdb" 15 + 16 + "github.com/bluesky-social/indigo/cmd/butterfly/remote" 17 + ) 18 + 19 + // DuckdbStore implements Store by storing repository data in DuckDB 20 + type DuckdbStore struct { 21 + // The filepath to store the duckdb file 22 + Dbpath string 23 + 24 + // Database connection 25 + db *sql.DB 26 + 27 + // Prepared statements for performance 28 + insertStmt *sql.Stmt 29 + deleteStmt *sql.Stmt 30 + 31 + // Mutex for thread safety 32 + mu sync.Mutex 33 + } 34 + 35 + // NewDuckdbStore creates a new DuckdbStore 36 + func NewDuckdbStore(dbpath string) *DuckdbStore { 37 + return &DuckdbStore{ 38 + Dbpath: dbpath, 39 + } 40 + } 41 + 42 + // Setup creates the database if it doesn't exist 43 + // If it does exist, ensures the needed tables and indexes are created 44 + func (d *DuckdbStore) Setup(ctx context.Context) error { 45 + // Ensure parent directory exists 46 + dir := filepath.Dir(d.Dbpath) 47 + if dir != "" && dir != "." { 48 + if err := os.MkdirAll(dir, 0755); err != nil { 49 + return fmt.Errorf("failed to create directory: %w", err) 50 + } 51 + } 52 + 53 + // Open database connection 54 + db, err := sql.Open("duckdb", d.Dbpath) 55 + if err != nil { 56 + return fmt.Errorf("failed to open database: %w", err) 57 + } 58 + d.db = db 59 + 60 + // Create main records table 61 + createTableSQL := ` 62 + CREATE TABLE IF NOT EXISTS records ( 63 + did VARCHAR NOT NULL, 64 + collection VARCHAR NOT NULL, 65 + rkey VARCHAR NOT NULL, 66 + cid VARCHAR, 67 + rev VARCHAR, 68 + record JSON, 69 + deleted BOOLEAN DEFAULT false, 70 + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 71 + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 72 + PRIMARY KEY (did, collection, rkey) 73 + )` 74 + 75 + if _, err := d.db.ExecContext(ctx, createTableSQL); err != nil { 76 + return fmt.Errorf("failed to create records table: %w", err) 77 + } 78 + 79 + // Create indexes for better query performance 80 + indexes := []string{ 81 + "CREATE INDEX IF NOT EXISTS idx_did ON records(did)", 82 + "CREATE INDEX IF NOT EXISTS idx_collection ON records(did, collection)", 83 + "CREATE INDEX IF NOT EXISTS idx_collection ON records(did, collection, rkey)", 84 + } 85 + 86 + for _, idx := range indexes { 87 + if _, err := d.db.ExecContext(ctx, idx); err != nil { 88 + return fmt.Errorf("failed to create index: %w", err) 89 + } 90 + } 91 + 92 + // Prepare statements 93 + d.insertStmt, err = d.db.PrepareContext(ctx, ` 94 + INSERT INTO records (did, collection, rkey, cid, rev, record, deleted, updated_at) 95 + VALUES (?, ?, ?, ?, ?, ?, false, ?) 96 + ON CONFLICT (did, collection, rkey) DO UPDATE SET 97 + cid = excluded.cid, 98 + rev = excluded.rev, 99 + record = excluded.record, 100 + deleted = false, 101 + updated_at = excluded.updated_at 102 + `) 103 + if err != nil { 104 + return fmt.Errorf("failed to prepare insert statement: %w", err) 105 + } 106 + 107 + d.deleteStmt, err = d.db.PrepareContext(ctx, ` 108 + UPDATE records 109 + SET deleted = true, updated_at = ? 110 + WHERE did = ? AND collection = ? AND rkey = ? 111 + `) 112 + if err != nil { 113 + return fmt.Errorf("failed to prepare delete statement: %w", err) 114 + } 115 + 116 + return nil 117 + } 118 + 119 + // Close cleans up database connection and prepared statements 120 + func (d *DuckdbStore) Close() error { 121 + d.mu.Lock() 122 + defer d.mu.Unlock() 123 + 124 + var errs []error 125 + 126 + if d.insertStmt != nil { 127 + if err := d.insertStmt.Close(); err != nil { 128 + errs = append(errs, fmt.Errorf("failed to close insert statement: %w", err)) 129 + } 130 + } 131 + 132 + if d.deleteStmt != nil { 133 + if err := d.deleteStmt.Close(); err != nil { 134 + errs = append(errs, fmt.Errorf("failed to close delete statement: %w", err)) 135 + } 136 + } 137 + 138 + if d.db != nil { 139 + if err := d.db.Close(); err != nil { 140 + errs = append(errs, fmt.Errorf("failed to close database: %w", err)) 141 + } 142 + } 143 + 144 + if len(errs) > 0 { 145 + return fmt.Errorf("errors closing DuckDB store: %v", errs) 146 + } 147 + return nil 148 + } 149 + 150 + // Receive processes events from the stream 151 + func (d *DuckdbStore) Receive(ctx context.Context, stream *remote.RemoteStream) error { 152 + // Start a transaction for better performance with batch operations 153 + tx, err := d.db.BeginTx(ctx, nil) 154 + if err != nil { 155 + return fmt.Errorf("failed to begin transaction: %w", err) 156 + } 157 + 158 + // Prepare transactional statements 159 + insertTxStmt := tx.StmtContext(ctx, d.insertStmt) 160 + deleteTxStmt := tx.StmtContext(ctx, d.deleteStmt) 161 + 162 + batchSize := 0 163 + const maxBatchSize = 1000 164 + 165 + for event := range stream.Ch { 166 + select { 167 + case <-ctx.Done(): 168 + return ctx.Err() 169 + default: 170 + } 171 + 172 + if event.Kind != remote.EventKindCommit || event.Commit == nil { 173 + continue 174 + } 175 + 176 + if err := d.processCommit(ctx, event.Did, event.Commit, insertTxStmt, deleteTxStmt); err != nil { 177 + // Log error but continue processing 178 + fmt.Fprintf(os.Stderr, "duckdb: error processing commit for %s: %v\n", event.Did, err) 179 + continue 180 + } 181 + 182 + batchSize++ 183 + // Commit transaction periodically for better performance 184 + if batchSize >= maxBatchSize { 185 + if err := tx.Commit(); err != nil { 186 + return fmt.Errorf("failed to commit transaction: %w", err) 187 + } 188 + 189 + // Start new transaction 190 + tx, err = d.db.BeginTx(ctx, nil) 191 + if err != nil { 192 + return fmt.Errorf("failed to begin new transaction: %w", err) 193 + } 194 + 195 + insertTxStmt = tx.StmtContext(ctx, d.insertStmt) 196 + deleteTxStmt = tx.StmtContext(ctx, d.deleteStmt) 197 + 198 + batchSize = 0 199 + } 200 + } 201 + 202 + // Commit any remaining operations 203 + if err := tx.Commit(); err != nil { 204 + return fmt.Errorf("failed to commit final transaction: %w", err) 205 + } 206 + 207 + return nil 208 + } 209 + 210 + // processCommit handles a single commit event 211 + func (d *DuckdbStore) processCommit(ctx context.Context, did string, commit *remote.StreamEventCommit, insertStmt, deleteStmt *sql.Stmt) error { 212 + now := time.Now() 213 + 214 + switch commit.Operation { 215 + case remote.OpCreate, remote.OpUpdate: 216 + // Marshal record to JSON 217 + recordJSON, err := json.Marshal(commit.Record) 218 + if err != nil { 219 + return fmt.Errorf("failed to marshal record: %w", err) 220 + } 221 + 222 + // Use insert with ON CONFLICT for both create and update 223 + _, err = insertStmt.ExecContext(ctx, 224 + did, commit.Collection, commit.Rkey, 225 + commit.Cid, commit.Rev, string(recordJSON), now) 226 + if err != nil { 227 + return fmt.Errorf("failed to insert/update record: %w", err) 228 + } 229 + 230 + case remote.OpDelete: 231 + // Soft delete by setting deleted flag 232 + _, err := deleteStmt.ExecContext(ctx, 233 + now, did, commit.Collection, commit.Rkey) 234 + if err != nil { 235 + return fmt.Errorf("failed to delete record: %w", err) 236 + } 237 + 238 + default: 239 + return fmt.Errorf("unknown operation: %s", commit.Operation) 240 + } 241 + 242 + return nil 243 + } 244 + 245 + // Query methods for retrieving data from DuckDB 246 + 247 + // GetRecord retrieves a single record 248 + func (d *DuckdbStore) GetRecord(ctx context.Context, did, collection, rkey string) (map[string]any, error) { 249 + d.mu.Lock() 250 + defer d.mu.Unlock() 251 + 252 + var record map[string]any 253 + var deleted bool 254 + 255 + err := d.db.QueryRowContext(ctx, 256 + "SELECT record, deleted FROM records WHERE did = ? AND collection = ? AND rkey = ?", 257 + did, collection, rkey).Scan(&record, &deleted) 258 + 259 + if err == sql.ErrNoRows || deleted { 260 + return nil, nil 261 + } 262 + if err != nil { 263 + return nil, fmt.Errorf("failed to query record: %w", err) 264 + } 265 + 266 + return record, nil 267 + } 268 + 269 + // ListRecords retrieves all records for a given DID and collection 270 + func (d *DuckdbStore) ListRecords(ctx context.Context, did, collection string, limit int) ([]map[string]any, error) { 271 + d.mu.Lock() 272 + defer d.mu.Unlock() 4 273 5 - package store 274 + query := "SELECT rkey, record FROM records WHERE did = ? AND collection = ? AND deleted = false" 275 + args := []any{did, collection} 276 + 277 + if limit > 0 { 278 + query += " LIMIT ?" 279 + args = append(args, limit) 280 + } 281 + 282 + rows, err := d.db.QueryContext(ctx, query, args...) 283 + if err != nil { 284 + return nil, fmt.Errorf("failed to query records: %w", err) 285 + } 286 + defer rows.Close() 287 + 288 + var results []map[string]any 289 + for rows.Next() { 290 + var rkey string 291 + var record map[string]any 292 + if err := rows.Scan(&rkey, &record); err != nil { 293 + return nil, fmt.Errorf("failed to scan row: %w", err) 294 + } 295 + 296 + // Add rkey to the record for reference 297 + record["_rkey"] = rkey 298 + results = append(results, record) 299 + } 300 + 301 + return results, rows.Err() 302 + } 303 + 304 + // GetStats returns statistics about the stored data 305 + func (d *DuckdbStore) GetStats(ctx context.Context) (map[string]any, error) { 306 + d.mu.Lock() 307 + defer d.mu.Unlock() 308 + 309 + stats := make(map[string]any) 310 + 311 + // Total records 312 + var totalRecords int64 313 + err := d.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM records WHERE deleted = false").Scan(&totalRecords) 314 + if err != nil { 315 + return nil, fmt.Errorf("failed to count records: %w", err) 316 + } 317 + stats["total_records"] = totalRecords 318 + 319 + // Records by collection 320 + rows, err := d.db.QueryContext(ctx, ` 321 + SELECT collection, COUNT(*) as count 322 + FROM records 323 + WHERE deleted = false 324 + GROUP BY collection 325 + ORDER BY count DESC 326 + `) 327 + if err != nil { 328 + return nil, fmt.Errorf("failed to query collection stats: %w", err) 329 + } 330 + defer rows.Close() 331 + 332 + collections := make(map[string]int64) 333 + for rows.Next() { 334 + var collection string 335 + var count int64 336 + if err := rows.Scan(&collection, &count); err != nil { 337 + return nil, fmt.Errorf("failed to scan collection stats: %w", err) 338 + } 339 + collections[collection] = count 340 + } 341 + stats["collections"] = collections 342 + 343 + // Unique DIDs 344 + var uniqueDIDs int64 345 + err = d.db.QueryRowContext(ctx, "SELECT COUNT(DISTINCT did) FROM records WHERE deleted = false").Scan(&uniqueDIDs) 346 + if err != nil { 347 + return nil, fmt.Errorf("failed to count unique DIDs: %w", err) 348 + } 349 + stats["unique_dids"] = uniqueDIDs 350 + 351 + return stats, nil 352 + }
+445
cmd/butterfly/store/duckdb_test.go
··· 1 + package store 2 + 3 + import ( 4 + "context" 5 + "path/filepath" 6 + "testing" 7 + "time" 8 + 9 + "github.com/bluesky-social/indigo/cmd/butterfly/remote" 10 + "github.com/stretchr/testify/assert" 11 + "github.com/stretchr/testify/require" 12 + ) 13 + 14 + func TestDuckdbStore_Setup(t *testing.T) { 15 + tmpDir := t.TempDir() 16 + dbPath := filepath.Join(tmpDir, "test.db") 17 + store := NewDuckdbStore(dbPath) 18 + 19 + ctx := context.Background() 20 + err := store.Setup(ctx) 21 + require.NoError(t, err) 22 + defer store.Close() 23 + 24 + // Check that database was created 25 + assert.FileExists(t, dbPath) 26 + 27 + // Verify tables exist by attempting to query 28 + stats, err := store.GetStats(ctx) 29 + require.NoError(t, err) 30 + assert.Equal(t, int64(0), stats["total_records"]) 31 + assert.Equal(t, int64(0), stats["unique_dids"]) 32 + } 33 + 34 + func TestDuckdbStore_BasicOperations(t *testing.T) { 35 + tmpDir := t.TempDir() 36 + dbPath := filepath.Join(tmpDir, "test.db") 37 + store := NewDuckdbStore(dbPath) 38 + 39 + ctx := context.Background() 40 + err := store.Setup(ctx) 41 + require.NoError(t, err) 42 + defer store.Close() 43 + 44 + // Create a test stream 45 + stream := &remote.RemoteStream{ 46 + Ch: make(chan remote.StreamEvent, 10), 47 + } 48 + 49 + testDID := "did:plc:testuser123" 50 + 51 + // Send some test events 52 + go func() { 53 + defer close(stream.Ch) 54 + 55 + // Create a post 56 + stream.Ch <- remote.StreamEvent{ 57 + Did: testDID, 58 + Timestamp: time.Now(), 59 + Kind: remote.EventKindCommit, 60 + Commit: &remote.StreamEventCommit{ 61 + Rev: "rev123", 62 + Operation: remote.OpCreate, 63 + Collection: "app.bsky.feed.post", 64 + Rkey: "3jui7kd54zh2y", 65 + Record: map[string]any{ 66 + "text": "Hello, world!", 67 + "createdAt": time.Now().Format(time.RFC3339), 68 + }, 69 + Cid: "bafyreigvcqpnqk3dqg", 70 + }, 71 + } 72 + 73 + // Create a follow 74 + stream.Ch <- remote.StreamEvent{ 75 + Did: testDID, 76 + Timestamp: time.Now(), 77 + Kind: remote.EventKindCommit, 78 + Commit: &remote.StreamEventCommit{ 79 + Rev: "rev124", 80 + Operation: remote.OpCreate, 81 + Collection: "app.bsky.graph.follow", 82 + Rkey: "3jui7kd54zh3z", 83 + Record: map[string]any{ 84 + "subject": "did:plc:alice", 85 + "createdAt": time.Now().Format(time.RFC3339), 86 + }, 87 + Cid: "bafyreigvcqpnqk3dqh", 88 + }, 89 + } 90 + 91 + // Update the post 92 + stream.Ch <- remote.StreamEvent{ 93 + Did: testDID, 94 + Timestamp: time.Now(), 95 + Kind: remote.EventKindCommit, 96 + Commit: &remote.StreamEventCommit{ 97 + Rev: "rev125", 98 + Operation: remote.OpUpdate, 99 + Collection: "app.bsky.feed.post", 100 + Rkey: "3jui7kd54zh2y", 101 + Record: map[string]any{ 102 + "text": "Hello, world! (edited)", 103 + "createdAt": time.Now().Format(time.RFC3339), 104 + }, 105 + Cid: "bafyreigvcqpnqk3dqi", 106 + }, 107 + } 108 + 109 + // Delete the follow 110 + stream.Ch <- remote.StreamEvent{ 111 + Did: testDID, 112 + Timestamp: time.Now(), 113 + Kind: remote.EventKindCommit, 114 + Commit: &remote.StreamEventCommit{ 115 + Rev: "rev126", 116 + Operation: remote.OpDelete, 117 + Collection: "app.bsky.graph.follow", 118 + Rkey: "3jui7kd54zh3z", 119 + }, 120 + } 121 + }() 122 + 123 + // Process the stream 124 + err = store.Receive(ctx, stream) 125 + require.NoError(t, err) 126 + 127 + // Verify the post was updated 128 + post, err := store.GetRecord(ctx, testDID, "app.bsky.feed.post", "3jui7kd54zh2y") 129 + require.NoError(t, err) 130 + assert.NotNil(t, post) 131 + assert.Equal(t, "Hello, world! (edited)", post["text"]) 132 + 133 + // Verify the follow was deleted (should return nil) 134 + follow, err := store.GetRecord(ctx, testDID, "app.bsky.graph.follow", "3jui7kd54zh3z") 135 + require.NoError(t, err) 136 + assert.Nil(t, follow) 137 + 138 + // Check stats 139 + stats, err := store.GetStats(ctx) 140 + require.NoError(t, err) 141 + assert.Equal(t, int64(1), stats["total_records"]) 142 + assert.Equal(t, int64(1), stats["unique_dids"]) 143 + 144 + collections := stats["collections"].(map[string]int64) 145 + assert.Equal(t, int64(1), collections["app.bsky.feed.post"]) 146 + assert.Equal(t, int64(0), collections["app.bsky.graph.follow"]) 147 + } 148 + 149 + func TestDuckdbStore_ListRecords(t *testing.T) { 150 + tmpDir := t.TempDir() 151 + dbPath := filepath.Join(tmpDir, "test.db") 152 + store := NewDuckdbStore(dbPath) 153 + 154 + ctx := context.Background() 155 + err := store.Setup(ctx) 156 + require.NoError(t, err) 157 + defer store.Close() 158 + 159 + // Create a test stream 160 + stream := &remote.RemoteStream{ 161 + Ch: make(chan remote.StreamEvent, 10), 162 + } 163 + 164 + testDID := "did:plc:testuser123" 165 + 166 + // Send multiple posts 167 + go func() { 168 + defer close(stream.Ch) 169 + 170 + for i := 0; i < 5; i++ { 171 + stream.Ch <- remote.StreamEvent{ 172 + Did: testDID, 173 + Timestamp: time.Now(), 174 + Kind: remote.EventKindCommit, 175 + Commit: &remote.StreamEventCommit{ 176 + Rev: "rev" + string(rune(i)), 177 + Operation: remote.OpCreate, 178 + Collection: "app.bsky.feed.post", 179 + Rkey: "post" + string(rune(i)), 180 + Record: map[string]any{ 181 + "text": "Post number " + string(rune(i+1)), 182 + "createdAt": time.Now().Format(time.RFC3339), 183 + }, 184 + Cid: "cid" + string(rune(i)), 185 + }, 186 + } 187 + } 188 + }() 189 + 190 + // Process the stream 191 + err = store.Receive(ctx, stream) 192 + require.NoError(t, err) 193 + 194 + // List all posts 195 + posts, err := store.ListRecords(ctx, testDID, "app.bsky.feed.post", 0) 196 + require.NoError(t, err) 197 + assert.Len(t, posts, 5) 198 + 199 + // List with limit 200 + limitedPosts, err := store.ListRecords(ctx, testDID, "app.bsky.feed.post", 3) 201 + require.NoError(t, err) 202 + assert.Len(t, limitedPosts, 3) 203 + 204 + // Verify _rkey is added 205 + for _, post := range posts { 206 + assert.Contains(t, post, "_rkey") 207 + assert.Contains(t, post, "text") 208 + } 209 + } 210 + 211 + func TestDuckdbStore_MultipleRepos(t *testing.T) { 212 + tmpDir := t.TempDir() 213 + dbPath := filepath.Join(tmpDir, "test.db") 214 + store := NewDuckdbStore(dbPath) 215 + 216 + ctx := context.Background() 217 + err := store.Setup(ctx) 218 + require.NoError(t, err) 219 + defer store.Close() 220 + 221 + // Create a test stream 222 + stream := &remote.RemoteStream{ 223 + Ch: make(chan remote.StreamEvent, 10), 224 + } 225 + 226 + testDIDs := []string{"did:plc:user1", "did:plc:user2", "did:plc:user3"} 227 + 228 + // Send events for multiple DIDs 229 + go func() { 230 + defer close(stream.Ch) 231 + 232 + for i, did := range testDIDs { 233 + stream.Ch <- remote.StreamEvent{ 234 + Did: did, 235 + Timestamp: time.Now(), 236 + Kind: remote.EventKindCommit, 237 + Commit: &remote.StreamEventCommit{ 238 + Rev: "rev" + string(rune(i)), 239 + Operation: remote.OpCreate, 240 + Collection: "app.bsky.actor.profile", 241 + Rkey: "self", 242 + Record: map[string]any{ 243 + "displayName": "User " + string(rune(i+1)), 244 + "createdAt": time.Now().Format(time.RFC3339), 245 + }, 246 + }, 247 + } 248 + } 249 + }() 250 + 251 + // Process the stream 252 + err = store.Receive(ctx, stream) 253 + require.NoError(t, err) 254 + 255 + // Verify stats 256 + stats, err := store.GetStats(ctx) 257 + require.NoError(t, err) 258 + assert.Equal(t, int64(3), stats["total_records"]) 259 + assert.Equal(t, int64(3), stats["unique_dids"]) 260 + 261 + // Verify each DID has a profile 262 + for _, did := range testDIDs { 263 + profile, err := store.GetRecord(ctx, did, "app.bsky.actor.profile", "self") 264 + require.NoError(t, err) 265 + assert.NotNil(t, profile) 266 + assert.Contains(t, profile, "displayName") 267 + } 268 + } 269 + 270 + func TestDuckdbStore_TransactionBatching(t *testing.T) { 271 + tmpDir := t.TempDir() 272 + dbPath := filepath.Join(tmpDir, "test.db") 273 + store := NewDuckdbStore(dbPath) 274 + 275 + ctx := context.Background() 276 + err := store.Setup(ctx) 277 + require.NoError(t, err) 278 + defer store.Close() 279 + 280 + // Create a test stream with many events 281 + stream := &remote.RemoteStream{ 282 + Ch: make(chan remote.StreamEvent, 2000), 283 + } 284 + 285 + testDID := "did:plc:testuser" 286 + 287 + // Send 1500 events (more than maxBatchSize of 1000) 288 + go func() { 289 + defer close(stream.Ch) 290 + 291 + for i := 0; i < 1500; i++ { 292 + stream.Ch <- remote.StreamEvent{ 293 + Did: testDID, 294 + Timestamp: time.Now(), 295 + Kind: remote.EventKindCommit, 296 + Commit: &remote.StreamEventCommit{ 297 + Operation: remote.OpCreate, 298 + Collection: "app.bsky.feed.post", 299 + Rkey: "post" + string(rune(i)), 300 + Record: map[string]any{ 301 + "text": "Test post " + string(rune(i)), 302 + "seq": i, 303 + }, 304 + }, 305 + } 306 + } 307 + }() 308 + 309 + // Process should handle transaction batching automatically 310 + err = store.Receive(ctx, stream) 311 + require.NoError(t, err) 312 + 313 + // Verify all records were saved 314 + stats, err := store.GetStats(ctx) 315 + require.NoError(t, err) 316 + assert.Equal(t, int64(1500), stats["total_records"]) 317 + } 318 + 319 + func TestDuckdbStore_ContextCancellation(t *testing.T) { 320 + tmpDir := t.TempDir() 321 + dbPath := filepath.Join(tmpDir, "test.db") 322 + store := NewDuckdbStore(dbPath) 323 + 324 + ctx, cancel := context.WithCancel(context.Background()) 325 + err := store.Setup(ctx) 326 + require.NoError(t, err) 327 + defer store.Close() 328 + 329 + // Create a test stream 330 + stream := &remote.RemoteStream{ 331 + Ch: make(chan remote.StreamEvent, 10), 332 + } 333 + 334 + // Send events indefinitely 335 + go func() { 336 + defer close(stream.Ch) 337 + for i := 0; i < 100; i++ { 338 + stream.Ch <- remote.StreamEvent{ 339 + Did: "did:plc:testuser", 340 + Timestamp: time.Now(), 341 + Kind: remote.EventKindCommit, 342 + Commit: &remote.StreamEventCommit{ 343 + Operation: remote.OpCreate, 344 + Collection: "app.bsky.feed.post", 345 + Rkey: "post" + string(rune(i+1)), 346 + Record: map[string]any{"text": "Test post"}, 347 + }, 348 + } 349 + time.Sleep(10 * time.Millisecond) 350 + } 351 + }() 352 + 353 + // Cancel context after a short time 354 + go func() { 355 + time.Sleep(50 * time.Millisecond) 356 + cancel() 357 + }() 358 + 359 + // Process should stop when context is cancelled 360 + err = store.Receive(ctx, stream) 361 + assert.ErrorIs(t, err, context.Canceled) 362 + } 363 + 364 + func TestDuckdbStore_ErrorHandling(t *testing.T) { 365 + tmpDir := t.TempDir() 366 + dbPath := filepath.Join(tmpDir, "test.db") 367 + store := NewDuckdbStore(dbPath) 368 + 369 + ctx := context.Background() 370 + err := store.Setup(ctx) 371 + require.NoError(t, err) 372 + defer store.Close() 373 + 374 + // Create a test stream with invalid events 375 + stream := &remote.RemoteStream{ 376 + Ch: make(chan remote.StreamEvent, 10), 377 + } 378 + 379 + go func() { 380 + defer close(stream.Ch) 381 + 382 + // Valid event 383 + stream.Ch <- remote.StreamEvent{ 384 + Did: "did:plc:testuser", 385 + Timestamp: time.Now(), 386 + Kind: remote.EventKindCommit, 387 + Commit: &remote.StreamEventCommit{ 388 + Operation: remote.OpCreate, 389 + Collection: "app.bsky.feed.post", 390 + Rkey: "valid", 391 + Record: map[string]any{"text": "Valid post"}, 392 + }, 393 + } 394 + 395 + // Event with nil commit (should be skipped) 396 + stream.Ch <- remote.StreamEvent{ 397 + Did: "did:plc:testuser", 398 + Timestamp: time.Now(), 399 + Kind: remote.EventKindCommit, 400 + Commit: nil, 401 + } 402 + 403 + // Non-commit event (should be skipped) 404 + stream.Ch <- remote.StreamEvent{ 405 + Did: "did:plc:testuser", 406 + Timestamp: time.Now(), 407 + Kind: remote.EventKindIdentity, 408 + Identity: &remote.StreamEventIdentity{ 409 + Did: "did:plc:testuser", 410 + Handle: "testuser.bsky.social", 411 + }, 412 + } 413 + 414 + // Another valid event 415 + stream.Ch <- remote.StreamEvent{ 416 + Did: "did:plc:testuser", 417 + Timestamp: time.Now(), 418 + Kind: remote.EventKindCommit, 419 + Commit: &remote.StreamEventCommit{ 420 + Operation: remote.OpCreate, 421 + Collection: "app.bsky.feed.post", 422 + Rkey: "valid2", 423 + Record: map[string]any{"text": "Another valid post"}, 424 + }, 425 + } 426 + }() 427 + 428 + // Should process without error, skipping invalid events 429 + err = store.Receive(ctx, stream) 430 + require.NoError(t, err) 431 + 432 + // Verify only valid events were processed 433 + stats, err := store.GetStats(ctx) 434 + require.NoError(t, err) 435 + assert.Equal(t, int64(2), stats["total_records"]) 436 + 437 + // Verify the valid records exist 438 + post1, err := store.GetRecord(ctx, "did:plc:testuser", "app.bsky.feed.post", "valid") 439 + require.NoError(t, err) 440 + assert.NotNil(t, post1) 441 + 442 + post2, err := store.GetRecord(ctx, "did:plc:testuser", "app.bsky.feed.post", "valid2") 443 + require.NoError(t, err) 444 + assert.NotNil(t, post2) 445 + }
+31 -26
go.mod
··· 13 13 github.com/brianvoe/gofakeit/v6 v6.25.0 14 14 github.com/carlmjohnson/versioninfo v0.22.5 15 15 github.com/cockroachdb/pebble v1.1.2 16 + github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038 16 17 github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2 17 18 github.com/flosch/pongo2/v6 v6.0.0 18 19 github.com/go-redis/cache/v9 v9.0.0 ··· 41 42 github.com/labstack/echo/v4 v4.11.3 42 43 github.com/lestrrat-go/jwx/v2 v2.0.12 43 44 github.com/lib/pq v1.10.9 45 + github.com/marcboeker/go-duckdb v1.8.5 44 46 github.com/minio/sha256-simd v1.0.1 45 47 github.com/mr-tron/base58 v1.2.0 46 48 github.com/multiformats/go-multihash v0.2.3 ··· 53 55 github.com/redis/go-redis/v9 v9.3.0 54 56 github.com/rivo/uniseg v0.1.0 55 57 github.com/samber/slog-echo v1.8.0 56 - github.com/stretchr/testify v1.9.0 58 + github.com/stretchr/testify v1.10.0 57 59 github.com/urfave/cli/v2 v2.25.7 58 60 github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e 59 61 github.com/whyrusleeping/go-did v0.0.0-20230824162731-404d1707d5d6 60 62 github.com/xlab/treeprint v1.2.0 61 63 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b 62 64 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 63 - go.opentelemetry.io/otel v1.21.0 65 + go.opentelemetry.io/otel v1.31.0 64 66 go.opentelemetry.io/otel/exporters/jaeger v1.14.0 65 67 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 66 68 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 67 - go.opentelemetry.io/otel/sdk v1.21.0 68 - go.opentelemetry.io/otel/trace v1.21.0 69 + go.opentelemetry.io/otel/sdk v1.31.0 70 + go.opentelemetry.io/otel/trace v1.31.0 69 71 go.uber.org/automaxprocs v1.5.3 70 72 go.uber.org/zap v1.26.0 71 - golang.org/x/crypto v0.21.0 72 - golang.org/x/sync v0.7.0 73 - golang.org/x/text v0.14.0 73 + golang.org/x/crypto v0.32.0 74 + golang.org/x/sync v0.10.0 75 + golang.org/x/text v0.21.0 74 76 golang.org/x/time v0.3.0 75 - golang.org/x/tools v0.15.0 76 - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 77 + golang.org/x/tools v0.29.0 78 + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da 77 79 gorm.io/driver/postgres v1.5.7 78 80 gorm.io/driver/sqlite v1.5.5 79 81 gorm.io/gorm v1.25.9 ··· 82 84 83 85 require ( 84 86 github.com/DataDog/zstd v1.4.5 // indirect 87 + github.com/apache/arrow-go/v18 v18.1.0 // indirect 85 88 github.com/cockroachdb/errors v1.11.3 // indirect 86 89 github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect 87 90 github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect 88 91 github.com/cockroachdb/redact v1.1.5 // indirect 89 92 github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect 90 93 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 91 - github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038 // indirect 92 94 github.com/getsentry/sentry-go v0.27.0 // indirect 93 95 github.com/go-redis/redis v6.15.9+incompatible // indirect 94 - github.com/goccy/go-json v0.10.2 // indirect 96 + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 97 + github.com/goccy/go-json v0.10.5 // indirect 95 98 github.com/golang/snappy v0.0.4 // indirect 99 + github.com/google/flatbuffers v25.1.24+incompatible // indirect 96 100 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 97 101 github.com/hashicorp/golang-lru v1.0.2 // indirect 98 102 github.com/ipfs/go-log v1.0.5 // indirect 99 103 github.com/jackc/puddle/v2 v2.2.1 // indirect 100 - github.com/klauspost/compress v1.17.3 // indirect 104 + github.com/klauspost/compress v1.17.11 // indirect 101 105 github.com/kr/pretty v0.3.1 // indirect 102 106 github.com/kr/text v0.2.0 // indirect 103 107 github.com/labstack/gommon v0.4.1 // indirect 104 108 github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 105 109 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 // indirect 110 + github.com/pierrec/lz4/v4 v4.1.22 // indirect 106 111 github.com/pkg/errors v0.9.1 // indirect 107 112 github.com/rogpeppe/go-internal v1.10.0 // indirect 108 113 github.com/vmihailenco/go-tinylfu v0.2.2 // indirect 109 114 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 110 115 github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 111 116 github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect 112 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect 117 + github.com/zeebo/xxh3 v1.0.2 // indirect 118 + golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect 113 119 gopkg.in/inf.v0 v0.9.1 // indirect 114 120 ) 115 121 ··· 117 123 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 // indirect 118 124 github.com/beorn7/perks v1.0.1 // indirect 119 125 github.com/cenkalti/backoff/v4 v4.2.1 // indirect 120 - github.com/cespare/xxhash/v2 v2.2.0 // indirect 126 + github.com/cespare/xxhash/v2 v2.3.0 // indirect 121 127 github.com/corpix/uarand v0.2.0 // indirect 122 128 github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect 123 129 github.com/davecgh/go-spew v1.1.1 // indirect 124 130 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 125 131 github.com/felixge/httpsnoop v1.0.4 // indirect 126 - github.com/go-logr/logr v1.4.1 // indirect 132 + github.com/go-logr/logr v1.4.2 // indirect 127 133 github.com/go-logr/stdr v1.2.2 // indirect 128 134 github.com/gogo/protobuf v1.3.2 // indirect 129 - github.com/golang/protobuf v1.5.3 // indirect 130 - github.com/google/uuid v1.4.0 // indirect 135 + github.com/google/uuid v1.6.0 // indirect 131 136 github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect 132 137 github.com/hashicorp/go-cleanhttp v0.5.2 133 138 github.com/ipfs/bbloom v0.0.4 // indirect ··· 146 151 github.com/jbenet/goprocess v0.1.4 // indirect 147 152 github.com/jinzhu/inflection v1.0.0 // indirect 148 153 github.com/jinzhu/now v1.1.5 // indirect 149 - github.com/klauspost/cpuid/v2 v2.2.7 // indirect 154 + github.com/klauspost/cpuid/v2 v2.2.9 // indirect 150 155 github.com/lestrrat-go/blackmagic v1.0.1 // indirect 151 156 github.com/lestrrat-go/httpcc v1.0.1 // indirect 152 157 github.com/lestrrat-go/httprc v1.0.4 // indirect ··· 173 178 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 174 179 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect 175 180 go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.45.0 176 - go.opentelemetry.io/otel/metric v1.21.0 // indirect 181 + go.opentelemetry.io/otel/metric v1.31.0 // indirect 177 182 go.opentelemetry.io/proto/otlp v1.0.0 // indirect 178 183 go.uber.org/atomic v1.11.0 // indirect 179 184 go.uber.org/multierr v1.11.0 // indirect 180 - golang.org/x/mod v0.14.0 // indirect 181 - golang.org/x/net v0.23.0 // indirect 182 - golang.org/x/sys v0.22.0 // indirect 183 - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect 184 - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect 185 - google.golang.org/grpc v1.59.0 // indirect 186 - google.golang.org/protobuf v1.33.0 // indirect 185 + golang.org/x/mod v0.22.0 // indirect 186 + golang.org/x/net v0.34.0 // indirect 187 + golang.org/x/sys v0.29.0 // indirect 188 + google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect 189 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect 190 + google.golang.org/grpc v1.69.2 // indirect 191 + google.golang.org/protobuf v1.36.1 // indirect 187 192 gopkg.in/yaml.v3 v3.0.1 // indirect 188 193 lukechampine.com/blake3 v1.2.1 // indirect 189 194 )
+78 -52
go.sum
··· 9 9 github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4= 10 10 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5 h1:iW0a5ljuFxkLGPNem5Ui+KBjFJzKg4Fv2fnxe4dvzpM= 11 11 github.com/alexbrainman/goissue34681 v0.0.0-20191006012335-3fc7a47baff5/go.mod h1:Y2QMoi1vgtOIfc+6DhrMOGkLoGzqSV2rKp4Sm+opsyA= 12 + github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= 13 + github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= 14 + github.com/apache/arrow-go/v18 v18.1.0 h1:agLwJUiVuwXZdwPYVrlITfx7bndULJ/dggbnLFgDp/Y= 15 + github.com/apache/arrow-go/v18 v18.1.0/go.mod h1:tigU/sIgKNXaesf5d7Y95jBBKS5KsxTqYBKXFsvKzo0= 16 + github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= 17 + github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= 12 18 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= 13 19 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= 14 20 github.com/aws/aws-sdk-go v1.44.263/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= ··· 46 52 github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= 47 53 github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 48 54 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 49 - github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 50 55 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 56 + github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 57 + github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 51 58 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 52 59 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 53 60 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= ··· 81 88 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 82 89 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 83 90 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 84 - github.com/did-method-plc/go-didplc v0.0.0-20250716162123-d0c3eba68797 h1:yYj4PNkUnWSh0Fhsl/pUoxMvBVaVeY6ZebkWMyGzW9k= 85 - github.com/did-method-plc/go-didplc v0.0.0-20250716162123-d0c3eba68797/go.mod h1:ddIXqTTSXWtj5kMsHAPj8SvbIx2GZdAkBFgFa6e6+CM= 86 91 github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038 h1:AGh+Vn9fXhf9eo8erG1CK4+LACduPo64P1OICQLDv88= 87 92 github.com/did-method-plc/go-didplc v0.0.0-20250716171643-635da8b4e038/go.mod h1:ddIXqTTSXWtj5kMsHAPj8SvbIx2GZdAkBFgFa6e6+CM= 88 93 github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2 h1:S6Dco8FtAhEI/qkg/00H6RdEGC+MCy5GPiQ+xweNRFE= ··· 102 107 github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 103 108 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 104 109 github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 105 - github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 106 - github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 110 + github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 111 + github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 107 112 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 108 113 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 109 114 github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0= ··· 111 116 github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= 112 117 github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 113 118 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 119 + github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= 120 + github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 114 121 github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= 115 - github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 116 122 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 123 + github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 124 + github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 117 125 github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus= 118 126 github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4= 119 127 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= ··· 132 140 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 133 141 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 134 142 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 135 - github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 136 - github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 143 + github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 144 + github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 137 145 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 138 146 github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 139 147 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 148 + github.com/google/flatbuffers v25.1.24+incompatible h1:4wPqL3K7GzBd1CwyhSd3usxLKOaJN/AC6puCca6Jm7o= 149 + github.com/google/flatbuffers v25.1.24+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 140 150 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 141 151 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 142 152 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= ··· 150 160 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 151 161 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 152 162 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 153 - github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 154 - github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 163 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 164 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 155 165 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 156 166 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 157 167 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= ··· 279 289 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 280 290 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 281 291 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 292 + github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= 293 + github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= 282 294 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 283 - github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA= 284 - github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 285 - github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 286 - github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 295 + github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= 296 + github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 297 + github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= 298 + github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= 287 299 github.com/koron/go-ssdp v0.0.3 h1:JivLMY45N76b4p/vsWGOKewBQu6uf39y8l+AQ7sDKx8= 288 300 github.com/koron/go-ssdp v0.0.3/go.mod h1:b2MxI6yh02pKrsyNoQUsk4+YNikaGhe4894J+Q5lDvA= 289 301 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= ··· 334 346 github.com/libp2p/go-nat v0.1.0/go.mod h1:X7teVkwRHNInVNWQiO/tAiAVRwSr5zoRz4YSTC3uRBM= 335 347 github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU= 336 348 github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ= 349 + github.com/marcboeker/go-duckdb v1.8.5 h1:tkYp+TANippy0DaIOP5OEfBEwbUINqiFqgwMQ44jME0= 350 + github.com/marcboeker/go-duckdb v1.8.5/go.mod h1:6mK7+WQE4P4u5AFLvVBmhFxY5fvhymFptghgJX6B+/8= 337 351 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 338 352 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 339 353 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= ··· 347 361 github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 348 362 github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= 349 363 github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= 364 + github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= 365 + github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= 366 + github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= 367 + github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= 350 368 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= 351 369 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= 352 370 github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= ··· 406 424 github.com/orandin/slog-gorm v1.3.2/go.mod h1:MoZ51+b7xE9lwGNPYEhxcUtRNrYzjdcKvA8QXQQGEPA= 407 425 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk= 408 426 github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw= 427 + github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= 428 + github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 409 429 github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= 410 430 github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= 411 431 github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= ··· 471 491 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 472 492 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 473 493 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 474 - github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 475 - github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 494 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 495 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 476 496 github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 477 497 github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= 478 498 github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= ··· 508 528 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 509 529 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 510 530 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 531 + github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= 532 + github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 533 + github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= 534 + github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= 511 535 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= 512 536 gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= 513 537 gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= ··· 518 542 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= 519 543 go.opentelemetry.io/contrib/propagators/b3 v1.20.0 h1:Yty9Vs4F3D6/liF1o6FNt0PvN85h/BJJ6DQKJ3nrcM0= 520 544 go.opentelemetry.io/contrib/propagators/b3 v1.20.0/go.mod h1:On4VgbkqYL18kbJlWsa18+cMNe6rYpBnPi1ARI/BrsU= 521 - go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= 522 - go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= 545 + go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= 546 + go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= 523 547 go.opentelemetry.io/otel/exporters/jaeger v1.14.0 h1:CjbUNd4iN2hHmWekmOqZ+zSCU+dzZppG8XsV+A3oc8Q= 524 548 go.opentelemetry.io/otel/exporters/jaeger v1.14.0/go.mod h1:4Ay9kk5vELRrbg5z4cpP9EtmQRFap2Wb0woPG4lujZA= 525 549 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= 526 550 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= 527 551 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= 528 552 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= 529 - go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4= 530 - go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= 531 - go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= 532 - go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= 533 - go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= 534 - go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= 553 + go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= 554 + go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= 555 + go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= 556 + go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= 557 + go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= 558 + go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= 559 + go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= 560 + go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= 535 561 go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= 536 562 go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= 537 563 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= ··· 562 588 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 563 589 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 564 590 golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= 565 - golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 566 - golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 567 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= 568 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= 591 + golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 592 + golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 593 + golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= 594 + golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 569 595 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 570 596 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 571 597 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= ··· 576 602 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 577 603 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 578 604 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 579 - golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 580 - golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 605 + golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 606 + golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 581 607 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 582 608 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 583 609 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= ··· 599 625 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 600 626 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 601 627 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 602 - golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 603 - golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 628 + golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 629 + golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 604 630 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 605 631 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 606 632 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= ··· 608 634 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 609 635 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 610 636 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 611 - golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 612 - golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 637 + golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 638 + golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 613 639 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 614 640 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 615 641 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= ··· 642 668 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 643 669 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 644 670 golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 645 - golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 646 - golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 671 + golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 672 + golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 647 673 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 648 674 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 649 675 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= ··· 663 689 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 664 690 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 665 691 golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 666 - golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 667 - golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 692 + golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 693 + golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 668 694 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 669 695 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 670 696 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= ··· 684 710 golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 685 711 golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 686 712 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 687 - golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= 688 - golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= 713 + golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= 714 + golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 689 715 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 690 716 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 691 717 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 692 718 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 693 - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 694 - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 695 - google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= 696 - google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= 697 - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= 698 - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= 699 - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= 700 - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= 701 - google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= 702 - google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 719 + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= 720 + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 721 + gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= 722 + gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= 723 + google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= 724 + google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= 725 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= 726 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 727 + google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= 728 + google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= 703 729 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 704 730 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 705 731 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= ··· 709 735 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 710 736 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 711 737 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 712 - google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 713 - google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 738 + google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= 739 + google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 714 740 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 715 741 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 716 742 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=