const std = @import("std"); const pg = @import("pg"); const postgresql = @import("postgresql.zig"); const database = @import("database.zig"); /// Session for multiple repository operations. pub const Session = struct { const Self = @This(); _database: *pg.Pool, /// The active connection for the session. connection: *pg.Conn, /// Execute a comptime-known SQL command for the current session. fn exec(self: Self, comptime sql: []const u8) !void { _ = self.connection.exec(sql, .{}) catch |err| { return postgresql.handleRawPostgresqlError(err, self.connection); }; } /// Begin a new transaction. pub fn beginTransaction(self: Self) !void { try self.exec("BEGIN;"); } /// Rollback the current transaction. pub fn rollbackTransaction(self: Self) !void { try self.exec("ROLLBACK;"); } /// Commit the current transaction. pub fn commitTransaction(self: Self) !void { try self.exec("COMMIT;"); } /// Create a new savepoint with the given name. pub fn savepoint(self: Self, comptime _savepoint: []const u8) !void { try self.exec("SAVEPOINT " ++ _savepoint ++ ";"); } /// Rollback to the savepoint with the given name. pub fn rollbackTo(self: Self, comptime _savepoint: []const u8) !void { try self.exec("ROLLBACK TO " ++ _savepoint ++ ";"); } /// Initialize a new session. pub fn init(_database: *pg.Pool) !Session { return .{ ._database = _database, .connection = try _database.acquire(), }; } /// Deinitialize the session. pub fn deinit(self: *Self) void { self.connection.release(); } /// Get a database connector instance for the current session. pub fn connector(self: *Self) database.Connector { return database.Connector{ ._interface = .{ .instance = self, .getConnection = getConnection, }, }; } // Connector implementation. /// Get the current connection. fn getConnection(opaqueSelf: *anyopaque) !*database.Connection { const self: *Self = @ptrCast(@alignCast(opaqueSelf)); // Initialize a new connection. const sessionConnection = try self._database._allocator.create(SessionConnection); sessionConnection.* = .{ .session = self, }; return try sessionConnection.connection(); } }; fn noRelease(_: *anyopaque) void {} /// A session connection. const SessionConnection = struct { const Self = @This(); /// Session of the connection. session: *Session, /// Connection instance, to only keep one at a time. _connection: ?database.Connection = null, /// Get a database connection. pub fn connection(self: *Self) !*database.Connection { if (self._connection == null) { // A new connection needs to be initialized. self._connection = .{ .connection = self.session.connection, ._interface = .{ .instance = self, .release = releaseConnection, }, }; } return &(self._connection.?); } // Implementation. /// Free the current connection (doesn't actually release the connection, as it is required to stay the same all along the session). fn releaseConnection(self: *database.Connection) void { // Free allocated connection. const sessionConnection: *SessionConnection = @ptrCast(@alignCast(self._interface.instance)); sessionConnection.session._database._allocator.destroy(sessionConnection); } };