func (s *SQL) AddBlobs(hash []string) error {
	if s.conn == nil {
		return errors.Err("not connected")
	}
	batch := 10000
	totalBlobs := int64(len(hash))
	work := make(chan []string, 1000)
	stopper := stop.New()
	var totalInserted atomic.Int64
	start := time.Now()
	go func() {
		for i := 0; i < len(hash); i += batch {
			j := i + batch
			if j > len(hash) {
				j = len(hash)
			}
			work <- hash[i:j]
		}
		log.Infof("done loading %d hashes in the work queue", len(hash))
		close(work)
	}()
	for i := 0; i < runtime.NumCPU(); i++ {
		stopper.Add(1)
		go func(worker int) {
			log.Infof("starting worker %d", worker)
			defer stopper.Done()
			for hashes := range work {
				inserted := totalInserted.Load()
				remaining := totalBlobs - inserted
				if inserted > 0 {
					timePerBlob := time.Since(start).Microseconds() / inserted
					remainingTime := time.Duration(remaining*timePerBlob) * time.Microsecond
					log.Infof("[T%d] processing batch of %d items. ETA: %s", worker, len(hashes), remainingTime.String())
				}
				err := s.insertBlobs(hashes)  
				if err != nil {
					log.Errorf("error while inserting batch: %s", errors.FullTrace(err))
				}
				totalInserted.Add(int64(len(hashes)))
			}
		}(i)
	}
	stopper.Wait()
	return nil
}
AddBlobs adds blobs to the database.
package db
import (
	"context"
	"database/sql"
	"fmt"
	"runtime"
	"strings"
	"time"
	"github.com/lbryio/lbry.go/v2/dht/bits"
	"github.com/lbryio/lbry.go/v2/extras/errors"
	qt "github.com/lbryio/lbry.go/v2/extras/query"
	"github.com/lbryio/lbry.go/v2/extras/stop"
	"github.com/lbryio/lbry.go/v2/stream"
	"github.com/go-sql-driver/mysql"
	_ "github.com/go-sql-driver/mysql"  
	log "github.com/sirupsen/logrus"
	"github.com/volatiletech/null"
	"go.uber.org/atomic"
)
type SdBlob struct {
	StreamName string `json:"stream_name"`
	Blobs      []struct {
		Length   int    `json:"length"`
		BlobNum  int    `json:"blob_num"`
		BlobHash string `json:"blob_hash,omitempty"`
		IV       string `json:"iv"`
	} `json:"blobs"`
	StreamType        string `json:"stream_type"`
	Key               string `json:"key"`
	SuggestedFileName string `json:"suggested_file_name"`
	StreamHash        string `json:"stream_hash"`
}
type trackAccess int
const (
	TrackAccessNone    trackAccess = iota  
	TrackAccessStreams                     
	TrackAccessBlobs                       
)
type SQL struct {
	conn *sql.DB
	TrackAccess trackAccess
	SoftDelete bool
	LogQueries bool
}
func (s SQL) logQuery(query string, args ...interface{}) {
	if !s.LogQueries {
		return
	}
	qStr, err := qt.InterpolateParams(query, args...)
	if err != nil {
		log.Errorln(err)
	} else {
		log.Debugln(qStr)
	}
}
func (s *SQL) Connect(dsn string) error {
	var err error
	dsn += "?parseTime=1&collation=utf8mb4_unicode_ci&interpolateParams=1"
	s.conn, err = sql.Open("mysql", dsn)
	if err != nil {
		return errors.Err(err)
	}
	s.conn.SetMaxIdleConns(12)
	return errors.Err(s.conn.Ping())
}
func (s *SQL) AddBlob(hash string, length int, isStored bool) error {
	if s.conn == nil {
		return errors.Err("not connected")
	}
	_, err := s.insertBlob(hash, length, isStored)
	return err
}