//! 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; }