package index
import (
"context"
"database/sql"
sqldriver "database/sql/driver"
"time"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
"github.com/ncruces/go-sqlite3/ext/fts5"
)
// connMaxMemory is how much memory one index connection may use. The
// driver's own ceiling is 256MB per connection (go-sqlite3 wrap.go),
// and a store of millions of artifacts went past it: netbsd-src (2.5M
// artifacts) ran SQLite out of memory folding and opening its index
// (#357). Memory grows only as a connection uses it, so this is a
// ceiling, not a reservation; wasm32 cannot address more than 4GB.
var connMaxMemory int64 = 1 << 30
// A connection's memory is mapped on demand, grows to the high-water
// mark of the heaviest query it ever ran, and is returned only when the
// connection is closed (go-sqlite3 Wrapper.Close). database/sql never
// closes an idle connection by default, so a forge holding ~58 stores
// (a writer and a reader pool each) ratcheted resident memory about
// 7MB a minute, 2.5GB at start to 10.9GB at 16 hours, and the kernel
// killed it. These three bound how long a connection can keep what it
// grew: idle ones go after connMaxIdleTime, busy ones are replaced
// after connMaxLifetime (database/sql closes them when returned to the
// pool, never inside a transaction), and one idle connection is all a
// pool keeps. Vars so a test can shrink them before Open.
var (
connMaxIdleTime = 2 * time.Minute
connMaxLifetime = 15 * time.Minute
connMaxIdle = 1
)
// openDB opens an index handle whose every connection carries
// connMaxMemory and the fts5 module, and whose pool recycles its
// connections. Every pool in this package comes through here, so none
// can be opened without the recycling policy; and because a recycled
// connection is a new one, everything a connection needs is set up in
// the DSN or in Connect below, never once on the *sql.DB. The limit travels on the context
// the connection is opened with, which database/sql supplies, so the
// driver's connector is wrapped rather than configured.
func openDB(dsn string) (*sql.DB, error) {
c, err := (&driver.SQLite{}).OpenConnector(dsn)
if err != nil {
return nil, err
}
db := sql.OpenDB(memConnector{c})
db.SetConnMaxIdleTime(connMaxIdleTime)
db.SetConnMaxLifetime(connMaxLifetime)
db.SetMaxIdleConns(connMaxIdle)
return db, nil
}
type memConnector struct{ inner sqldriver.Connector }
func (m memConnector) Connect(ctx context.Context) (sqldriver.Conn, error) {
c, err := m.inner.Connect(sqlite3.WithMaxMemory(ctx, connMaxMemory))
if err != nil {
return nil, err
}
if err := fts5.Register(c.(interface{ Raw() *sqlite3.Conn }).Raw()); err != nil {
c.Close()
return nil, err
}
return c, nil
}
func (m memConnector) Driver() sqldriver.Driver { return m.inner.Driver() }