Split monolithic root.zig (4200 lines) into 9 focused modules: - c.zig: centralized @cImport for SQLite - errors.zig: Error enum and resultToError - types.zig: OpenFlags, ColumnType, Limit, enums - database.zig: Database struct with all methods - statement.zig: Statement struct with bindings/columns - functions.zig: UDFs, hooks, and C callbacks - backup.zig: Backup and Blob I/O - pool.zig: ConnectionPool (thread-safe) - root.zig: re-exports + tests (~1100 lines) Total: ~3600 lines (74% reduction in root.zig) All 47 tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
292 lines
8.3 KiB
Zig
292 lines
8.3 KiB
Zig
//! SQLite Backup and Blob I/O
|
|
//!
|
|
//! Provides the Backup struct for database copying and the Blob struct
|
|
//! for incremental blob I/O operations.
|
|
|
|
const std = @import("std");
|
|
const c = @import("c.zig").c;
|
|
const errors = @import("errors.zig");
|
|
|
|
const Error = errors.Error;
|
|
const resultToError = errors.resultToError;
|
|
|
|
pub const Database = @import("database.zig").Database;
|
|
|
|
// ============================================================================
|
|
// Backup API
|
|
// ============================================================================
|
|
|
|
/// SQLite online backup handle.
|
|
///
|
|
/// Allows copying database content from one database to another while
|
|
/// both databases are in use.
|
|
pub const Backup = struct {
|
|
handle: ?*c.sqlite3_backup,
|
|
dest_db: *Database,
|
|
source_db: *Database,
|
|
|
|
const Self = @This();
|
|
|
|
/// Initializes a backup from source to destination database.
|
|
pub fn init(
|
|
dest_db: *Database,
|
|
dest_name: [:0]const u8,
|
|
source_db: *Database,
|
|
source_name: [:0]const u8,
|
|
) Error!Self {
|
|
const handle = c.sqlite3_backup_init(
|
|
dest_db.handle,
|
|
dest_name.ptr,
|
|
source_db.handle,
|
|
source_name.ptr,
|
|
);
|
|
|
|
if (handle == null) {
|
|
const err_code = c.sqlite3_errcode(dest_db.handle);
|
|
return resultToError(err_code);
|
|
}
|
|
|
|
return .{
|
|
.handle = handle,
|
|
.dest_db = dest_db,
|
|
.source_db = source_db,
|
|
};
|
|
}
|
|
|
|
/// Convenience function to backup the main database.
|
|
pub fn initMain(dest_db: *Database, source_db: *Database) Error!Self {
|
|
return init(dest_db, "main", source_db, "main");
|
|
}
|
|
|
|
/// Copies up to `n_pages` pages from source to destination.
|
|
/// Use -1 to copy all remaining pages in one call.
|
|
/// Returns true if there are more pages to copy.
|
|
pub fn step(self: *Self, n_pages: i32) Error!bool {
|
|
const result = c.sqlite3_backup_step(self.handle, n_pages);
|
|
return switch (result) {
|
|
c.SQLITE_OK => true,
|
|
c.SQLITE_DONE => false,
|
|
c.SQLITE_BUSY, c.SQLITE_LOCKED => Error.Busy,
|
|
else => resultToError(result),
|
|
};
|
|
}
|
|
|
|
/// Copies all remaining pages in one call.
|
|
pub fn stepAll(self: *Self) Error!void {
|
|
_ = try self.step(-1);
|
|
}
|
|
|
|
/// Returns the number of pages still to be copied.
|
|
pub fn remaining(self: *Self) i32 {
|
|
return c.sqlite3_backup_remaining(self.handle);
|
|
}
|
|
|
|
/// Returns the total number of pages in the source database.
|
|
pub fn pageCount(self: *Self) i32 {
|
|
return c.sqlite3_backup_pagecount(self.handle);
|
|
}
|
|
|
|
/// Returns the progress as a percentage (0-100).
|
|
pub fn progress(self: *Self) u8 {
|
|
const total = self.pageCount();
|
|
if (total == 0) return 100;
|
|
const done = total - self.remaining();
|
|
return @intCast(@divFloor(done * 100, total));
|
|
}
|
|
|
|
/// Finishes the backup operation and releases resources.
|
|
pub fn finish(self: *Self) Error!void {
|
|
if (self.handle) |h| {
|
|
const result = c.sqlite3_backup_finish(h);
|
|
self.handle = null;
|
|
if (result != c.SQLITE_OK) {
|
|
return resultToError(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Alias for finish() for RAII-style usage.
|
|
pub fn deinit(self: *Self) void {
|
|
self.finish() catch {};
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Blob I/O
|
|
// ============================================================================
|
|
|
|
/// Blob handle for incremental I/O operations.
|
|
///
|
|
/// Allows reading and writing large BLOBs incrementally without
|
|
/// loading the entire blob into memory.
|
|
pub const Blob = struct {
|
|
handle: ?*c.sqlite3_blob,
|
|
db: *Database,
|
|
|
|
const Self = @This();
|
|
|
|
/// Opens a blob for incremental I/O.
|
|
pub fn open(
|
|
db: *Database,
|
|
schema: [:0]const u8,
|
|
table: [:0]const u8,
|
|
column: [:0]const u8,
|
|
rowid: i64,
|
|
writable: bool,
|
|
) Error!Self {
|
|
var handle: ?*c.sqlite3_blob = null;
|
|
const flags: c_int = if (writable) 1 else 0;
|
|
|
|
const result = c.sqlite3_blob_open(
|
|
db.handle,
|
|
schema.ptr,
|
|
table.ptr,
|
|
column.ptr,
|
|
rowid,
|
|
flags,
|
|
&handle,
|
|
);
|
|
|
|
if (result != c.SQLITE_OK) {
|
|
return resultToError(result);
|
|
}
|
|
|
|
return Self{
|
|
.handle = handle,
|
|
.db = db,
|
|
};
|
|
}
|
|
|
|
/// Opens a blob with allocator for runtime strings.
|
|
pub fn openAlloc(
|
|
db: *Database,
|
|
allocator: std.mem.Allocator,
|
|
schema: []const u8,
|
|
table: []const u8,
|
|
column: []const u8,
|
|
rowid: i64,
|
|
writable: bool,
|
|
) !Self {
|
|
const schema_z = try allocator.dupeZ(u8, schema);
|
|
defer allocator.free(schema_z);
|
|
|
|
const table_z = try allocator.dupeZ(u8, table);
|
|
defer allocator.free(table_z);
|
|
|
|
const column_z = try allocator.dupeZ(u8, column);
|
|
defer allocator.free(column_z);
|
|
|
|
return Self.open(db, schema_z, table_z, column_z, rowid, writable);
|
|
}
|
|
|
|
/// Closes the blob handle.
|
|
pub fn close(self: *Self) Error!void {
|
|
if (self.handle) |h| {
|
|
const result = c.sqlite3_blob_close(h);
|
|
self.handle = null;
|
|
if (result != c.SQLITE_OK) {
|
|
return resultToError(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Closes the blob handle without checking for errors.
|
|
pub fn deinit(self: *Self) void {
|
|
if (self.handle) |h| {
|
|
_ = c.sqlite3_blob_close(h);
|
|
self.handle = null;
|
|
}
|
|
}
|
|
|
|
/// Returns the size of the blob in bytes.
|
|
pub fn bytes(self: *Self) i32 {
|
|
if (self.handle) |h| {
|
|
return c.sqlite3_blob_bytes(h);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// Reads data from the blob.
|
|
pub fn read(self: *Self, buffer: []u8, offset: i32) Error!void {
|
|
if (self.handle == null) return error.SqliteError;
|
|
|
|
const result = c.sqlite3_blob_read(
|
|
self.handle,
|
|
buffer.ptr,
|
|
@intCast(buffer.len),
|
|
offset,
|
|
);
|
|
|
|
if (result != c.SQLITE_OK) {
|
|
return resultToError(result);
|
|
}
|
|
}
|
|
|
|
/// Writes data to the blob.
|
|
pub fn write(self: *Self, data: []const u8, offset: i32) Error!void {
|
|
if (self.handle == null) return error.SqliteError;
|
|
|
|
const result = c.sqlite3_blob_write(
|
|
self.handle,
|
|
data.ptr,
|
|
@intCast(data.len),
|
|
offset,
|
|
);
|
|
|
|
if (result != c.SQLITE_OK) {
|
|
return resultToError(result);
|
|
}
|
|
}
|
|
|
|
/// Moves the blob handle to a different row.
|
|
pub fn reopen(self: *Self, rowid: i64) Error!void {
|
|
if (self.handle == null) return error.SqliteError;
|
|
|
|
const result = c.sqlite3_blob_reopen(self.handle, rowid);
|
|
if (result != c.SQLITE_OK) {
|
|
return resultToError(result);
|
|
}
|
|
}
|
|
|
|
/// Reads the entire blob into a newly allocated buffer.
|
|
pub fn readAll(self: *Self, allocator: std.mem.Allocator) ![]u8 {
|
|
const size = self.bytes();
|
|
if (size <= 0) return &[_]u8{};
|
|
|
|
const buffer = try allocator.alloc(u8, @intCast(size));
|
|
errdefer allocator.free(buffer);
|
|
|
|
try self.read(buffer, 0);
|
|
return buffer;
|
|
}
|
|
};
|
|
|
|
// ============================================================================
|
|
// Convenience functions
|
|
// ============================================================================
|
|
|
|
/// Copies an entire database to another database.
|
|
pub fn backupDatabase(dest_db: *Database, source_db: *Database) Error!void {
|
|
var backup = try Backup.initMain(dest_db, source_db);
|
|
defer backup.deinit();
|
|
try backup.stepAll();
|
|
}
|
|
|
|
/// Copies a database to a file.
|
|
pub fn backupToFile(source_db: *Database, path: [:0]const u8) Error!void {
|
|
var dest_db = try Database.open(path);
|
|
defer dest_db.close();
|
|
try backupDatabase(&dest_db, source_db);
|
|
}
|
|
|
|
/// Loads a database from a file into memory.
|
|
pub fn loadFromFile(path: [:0]const u8) Error!Database {
|
|
var file_db = try Database.open(path);
|
|
defer file_db.close();
|
|
|
|
var mem_db = try Database.open(":memory:");
|
|
errdefer mem_db.close();
|
|
|
|
try backupDatabase(&mem_db, &file_db);
|
|
return mem_db;
|
|
}
|