96 lines
3.8 KiB
Go
96 lines
3.8 KiB
Go
// Copyright 2023 Ross Light
|
|
// SPDX-License-Identifier: ISC
|
|
|
|
package sqlite
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"modernc.org/libc"
|
|
lib "modernc.org/sqlite/lib"
|
|
)
|
|
|
|
// A Backup represents a copy operation between two database connections.
|
|
// See https://www.sqlite.org/backup.html for more details.
|
|
type Backup struct {
|
|
tls *libc.TLS
|
|
ptr uintptr
|
|
}
|
|
|
|
// NewBackup creates a [Backup] that copies from one connection to another.
|
|
// The database name is "main" or "" for the main database,
|
|
// "temp" for the temporary database,
|
|
// or the name specified after the AS keyword in an ATTACH statement for an attached database.
|
|
// If src and dst are the same connection, NewBackup will return an error.
|
|
// It is the caller's responsibility to call [Backup.Close] on the returned Backup object.
|
|
func NewBackup(dst *Conn, dstName string, src *Conn, srcName string) (*Backup, error) {
|
|
cDstName, freeCDstName, err := cDBName(dstName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sqlite: new backup: %w", err)
|
|
}
|
|
defer freeCDstName()
|
|
cSrcName, freeCSrcName, err := cDBName(srcName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sqlite: new backup: %w", err)
|
|
}
|
|
defer freeCSrcName()
|
|
backupPtr := lib.Xsqlite3_backup_init(dst.tls, dst.conn, cDstName, src.conn, cSrcName)
|
|
if backupPtr == 0 {
|
|
res := ResultCode(lib.Xsqlite3_errcode(dst.tls, dst.conn))
|
|
return nil, fmt.Errorf("sqlite: new backup: %w", dst.extreserr(res))
|
|
}
|
|
return &Backup{dst.tls, backupPtr}, nil
|
|
}
|
|
|
|
// Step copies up to n pages from the source database to the destination database.
|
|
// If n is negative, all remaining source pages are copied.
|
|
// more will be true if there are pages still remaining to be copied.
|
|
// Step may return both an error and that more pages are still remaining:
|
|
// this indicates the error is temporary and that Step can be retried.
|
|
func (b *Backup) Step(n int) (more bool, err error) {
|
|
res := ResultCode(lib.Xsqlite3_backup_step(b.tls, b.ptr, int32(n)))
|
|
switch res {
|
|
case ResultOK:
|
|
return true, nil
|
|
case ResultDone:
|
|
return false, nil
|
|
case ResultBusy, ResultLocked:
|
|
// SQLITE_BUSY and SQLITE_LOCKED are retriable errors.
|
|
return true, fmt.Errorf("sqlite: backup step: %w", res.ToError())
|
|
default:
|
|
return false, fmt.Errorf("sqlite: backup step: %w", res.ToError())
|
|
}
|
|
}
|
|
|
|
// Remaining returns the number of pages still to be backed up
|
|
// at the conclusion of the most recent call to [Backup.Step].
|
|
// The return value of Remaining before calling [Backup.Step] is undefined.
|
|
// If the source database is modified in a way that changes the number of pages remaining,
|
|
// that change is not reflected in the output until after the next call to [Backup.Step].
|
|
func (b *Backup) Remaining() int {
|
|
return int(lib.Xsqlite3_backup_remaining(b.tls, b.ptr))
|
|
}
|
|
|
|
// PageCount returns the total number of pages in the source database
|
|
// at the conclusion of the most recent call to [Backup.Step].
|
|
// The return value of PageCount before calling [Backup.Step] is undefined.
|
|
// If the source database is modified in a way that changes the size of the source database,
|
|
// that change is not reflected in the output until after the next call to [Backup.Step].
|
|
func (b *Backup) PageCount() int {
|
|
return int(lib.Xsqlite3_backup_pagecount(b.tls, b.ptr))
|
|
}
|
|
|
|
// Close releases all resources associated with the backup.
|
|
// If [Backup.Step] has not yet returned (false, nil),
|
|
// then any active write transaction on the destination database is rolled back.
|
|
func (b *Backup) Close() error {
|
|
// The error from sqlite3_backup_finish indicates whether
|
|
// a previous call to sqlite3_backup_step returned an error.
|
|
// Since we're assuming that the caller will handle errors as they arise,
|
|
// we always return nil from Close.
|
|
// However, I'm not fully confident that Close will always be infallible,
|
|
// so I'm not documenting this as part of the API.
|
|
lib.Xsqlite3_backup_finish(b.tls, b.ptr)
|
|
return nil
|
|
}
|