siyuan-note/siyuan · error

database is nil

Error message

database is nil

What it means

CheckReadonlyStatement validates that a SQL statement is safe to run and that a target database handle was supplied. After the statement passes emptiness and read-only checks, it verifies the *sql.DB pointer is non-nil. A nil handle means the caller asked to validate/execute against a database that was never opened or has been closed.

Source

Thrown at kernel/sql/stmt_validate.go:194

func CheckReadonlyStatementInBox(stmt, boxID string) error {
	targetDB := db
	if boxDB := GetEncryptedDB(boxID); nil != boxDB {
		targetDB = boxDB
	} else if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {
		return errors.New("encrypted box db not opened for box " + boxID)
	}
	return checkReadonlyStatement(stmt, targetDB)
}

func checkReadonlyStatement(stmt string, targetDB *sql.DB) error {
	if strings.TrimSpace(stmt) == "" {
		return errors.New("SQL statement is empty")
	}
	if !isReadonlyQueryStatement(stmt) {
		return errors.New("SQL statement is not a read-only query")
	}
	if nil == targetDB {
		return errors.New("database is nil")
	}
	ctx := context.Background()
	conn, err := targetDB.Conn(ctx)
	if err != nil {
		return err
	}
	defer conn.Close()

	return conn.Raw(func(dc any) error {
		sqliteConn, ok := dc.(*sqlite3.SQLiteConn)
		if !ok {
			return fmt.Errorf("SQL driver connection type is unexpected: %T", dc)
		}
		ds, err := sqliteConn.Prepare(stmt)
		if err != nil {
			return err
		}
		defer ds.Close()

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Ensure the workspace database is initialized (sql.InitDatabase / database open) before calling CheckReadonlyStatement.
  2. Check that the correct DB handle (global db, asset-content db, or box db) is passed, not nil.
  3. If a box was expected, verify the box is not encrypted-locked and its DB was opened via the encrypted-db path.

Example fix

// before
if err := sql.CheckReadonlyStatement(nil, stmt); err != nil { ... }
// after
targetDB := sql.GetDB()
if targetDB == nil {
    return errors.New("database not initialized")
}
if err := sql.CheckReadonlyStatement(targetDB, stmt); err != nil { ... }
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof targetDB === 'undefined' || targetDB === null) { throw new Error('target database handle is not initialized') }

Type guard

function hasDB(db) { return db != null && typeof db.query === 'function' }

Try / catch

try { checkReadonly(db, stmt) } catch (e) { if (String(e.message).includes('database is nil')) { await initDatabase(); retry() } else { throw e } }

Prevention

When it happens

Trigger: Calling CheckReadonlyStatement, CheckAssetContentReadonlyStatement, or CheckReadonlyStatementInBox with targetDB == nil — typically when sql.InitDatabase has not run yet, the database was closed, or a nil box/asset-content DB handle was passed in.

Common situations: Kernel startup ordering issues (query before InitDatabase), calling the /api/query/sql API before workspace init, or tests that construct the validator without opening a DB.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/15b07efb41947662. Report an issue: GitHub.