diff --git a/fixtures/encrypted-single-file.db.age b/fixtures/encrypted-single-file.db.age new file mode 100644 index 0000000..613d717 Binary files /dev/null and b/fixtures/encrypted-single-file.db.age differ diff --git a/src/Db.zig b/src/Db.zig index bda2903..b9d1a6d 100644 --- a/src/Db.zig +++ b/src/Db.zig @@ -40,6 +40,7 @@ pub fn open( defer gpa.free(tmp_db_path); // TODO: Use std.MultiArrayList? Had json issues + // FIXME: Create a .deinit function to free this memory var private_keys: std.ArrayList([]const u8) = try .initCapacity( gpa, opts.config.keys.len, @@ -75,7 +76,10 @@ const OpenOptions = struct { fn new(config: Config) !@This() { var db = try sqlite.Db.init(.{ .mode = .Memory, - .open_flags = .{ .write = true, .create = true }, + .open_flags = .{ + .write = true, + .create = true, + }, .threading_mode = .MultiThread, }); @@ -114,13 +118,21 @@ fn restore( .{}, .{path}, ); + std.debug.print("path: {s}\n", .{path}); defer self.sql_db.exec("DETACH DATABASE source", .{}, .{}) catch unreachable; - try self.sql_db.exec( + var diags: sqlite.Diagnostics = .{}; + self.sql_db.exec( "INSERT INTO main.envr_env_files SELECT * FROM source.envr_env_files", + .{ .diags = &diags }, .{}, - .{}, - ); + ) catch |err| { + std.log.err( + "unable to prepare statement, got error {}. diagnostics: {f}", + .{ err, diags }, + ); + return err; + }; } // TODO: Finish @@ -171,21 +183,24 @@ pub fn close( /// Returns a list of all the .env files present in the database. /// The caller is responsible for freeing memory -fn list(self: @This(), gpa: std.mem.Allocator) ![]EnvFile { - var stmt = self.sql_db.prepare( +fn list(self: *@This(), gpa: std.mem.Allocator) ![]EnvFile { + var stmt = try self.sql_db.prepare( "select path, remotes, sha256, contents from envr_env_files", ); defer stmt.deinit(); - return stmt.all([]const EnvFile, gpa, .{}, .{}); + return stmt.all(EnvFile, gpa, .{}, .{}); } const EnvFile = struct { // TODO: Should use file_name in the struct and derive from the path. path: []const u8, - /// dir is derived from Path, and is not stored in the database. - dir: []const u8, - remotes: [][]const u8, + + // /// dir is derived from Path, and is not stored in the database. + // dir: []const u8, + + /// JSON encoded list of strings + remotes: []const u8, sha256: []const u8, contents: []const u8, }; @@ -269,7 +284,6 @@ test "Closing a fresh database does not create a file" { var tmp_dir = std.testing.tmpDir(.{}); defer tmp_dir.cleanup(); - // @compileLog(@typeInfo(std.Io.File.Permissions)); try tmp_dir.dir.createDir(io, "home", .default_dir); try tmp_dir.dir.createDir(io, "tmp", .default_dir); @@ -304,3 +318,67 @@ test "Closing a fresh database does not create a file" { // test "Closing an unmodified database does not update the file" {} // test "Closing a modified database does create a file" {} + +test "list displays the database's keys" { + const io = std.testing.io; + const gpa = std.testing.allocator; + + var tmp_dir = std.testing.tmpDir(.{}); + defer tmp_dir.cleanup(); + + try tmp_dir.dir.createDir(io, "home", .default_dir); + try tmp_dir.dir.createDir(io, "home/.envr", .default_dir); + try tmp_dir.dir.createDir(io, "tmp", .default_dir); + + const tmp_dir_path = try tmp_dir.dir.realPathFileAlloc(io, ".", gpa); + defer gpa.free(tmp_dir_path); + + const home = try std.fs.path.join(gpa, &.{ tmp_dir_path, "home" }); + defer gpa.free(home); + const tmp = try std.fs.path.join(gpa, &.{ tmp_dir_path, "tmp" }); + defer gpa.free(tmp); + + // TODO: Get rid of direct access + const db_path = try std.fs.path.join(gpa, &.{ home, ".envr", "data.age" }); + defer gpa.free(db_path); + + try std.Io.Dir.cwd().copyFile( + "fixtures/encrypted-single-file.db.age", + tmp_dir.dir, + "home/.envr/data.age", + io, + .{}, + ); + + // Asserts file existence + try tmp_dir.dir.access(io, db_path, .{ .read = true }); + + // TODO: Pass testing keys + const config: Config = .{ + .keys = &.{.from_pub_path("fixtures/insecure-test-key.pub")}, + }; + var db: @This() = try .open(io, gpa, .{ + .config = config, + .home = home, + .tmp = tmp, + }); + + const env_files = try db.list(gpa); + try std.testing.expectEqual(1, env_files.len); + + var hasher = std.crypto.hash.sha2.Sha256.init(.{}); + + for (env_files) |file| { + try std.testing.expectEqualSlices(u8, "", file.path); + try std.testing.expectEqualSlices(u8, "", file.contents); + try std.testing.expectEqualSlices(u8, "", file.remotes); + + hasher.update(file.contents); + const hash = hasher.finalResult(); + try std.testing.expectEqualSlices(u8, &hash, file.sha256); + } else { + return error.TestUnexpectedResult; + } + + try db.close(io, gpa, .{ .home = home, .tmp = tmp }); +} diff --git a/zig-vendor/zig-sqlite/sqlite.zig b/zig-vendor/zig-sqlite/sqlite.zig index 1702a0b..b349f4b 100644 --- a/zig-vendor/zig-sqlite/sqlite.zig +++ b/zig-vendor/zig-sqlite/sqlite.zig @@ -2257,7 +2257,7 @@ pub fn Statement(comptime opts: StatementOptions, comptime query: anytype) type pub fn all(self: *Self, comptime Type: type, allocator: mem.Allocator, options: QueryOptions, values: anytype) ![]Type { var iter = try self.iteratorAlloc(Type, allocator, values); - var rows: std.ArrayList(Type) = .{}; + var rows: std.ArrayList(Type) = .empty; while (try iter.nextAlloc(allocator, options)) |row| { try rows.append(allocator, row); }