Create generic query result mapper and implement it for PostgreSQL.

+ Add generic query result mapper.
+ Add query result mapper implementation for PostgreSQL.
This commit is contained in:
Madeorsk 2024-11-23 18:18:41 +01:00
parent 04b61f9787
commit e33bc5aaa2
Signed by: Madeorsk
GPG key ID: 677E51CA765BB79F
5 changed files with 148 additions and 28 deletions

View file

@ -6,6 +6,7 @@ const database = @import("database.zig");
const postgresql = @import("postgresql.zig");
const _sql = @import("sql.zig");
const repository = @import("repository.zig");
const _result = @import("result.zig");
/// Type of an insertable column. Insert shape should be composed of only these.
fn InsertableColumn(comptime ValueType: type) type {
@ -118,6 +119,9 @@ pub fn RepositoryInsert(comptime Model: type, comptime TableShape: type, comptim
const Configuration = RepositoryInsertConfiguration(InsertShape);
/// Result mapper type.
pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, null, null);
arena: std.heap.ArenaAllocator,
connector: database.Connector,
connection: *database.Connection = undefined,
@ -332,7 +336,8 @@ pub fn RepositoryInsert(comptime Model: type, comptime TableShape: type, comptim
defer queryResult.deinit();
// Map query results.
return postgresql.mapResults(Model, TableShape, repositoryConfig, allocator, queryResult);
var postgresqlReader = postgresql.QueryResultReader(TableShape, null).init(queryResult);
return try ResultMapper.map(allocator, postgresqlReader.reader());
}
/// Initialize a new repository insert query.

View file

@ -7,6 +7,7 @@ const database = @import("database.zig");
const _sql = @import("sql.zig");
const _relations = @import("relations.zig");
const repository = @import("repository.zig");
const _result = @import("result.zig");
/// PostgreSQL query error details.
pub const PostgresqlError = struct {
@ -79,32 +80,58 @@ pub fn makeMapper(comptime T: type, result: *pg.Result, allocator: std.mem.Alloc
};
}
/// Generic query results mapping.
pub fn mapResults(comptime Model: type, comptime TableShape: type,
repositoryConfig: repository.RepositoryConfiguration(Model, TableShape),
allocator: std.mem.Allocator, queryResult: *pg.Result) !repository.RepositoryResult(Model)
{
//TODO make a generic mapper and do it in repository.zig?
// Create an arena for mapper data.
var mapperArena = std.heap.ArenaAllocator.init(allocator);
// Get result mapper.
const mapper = try makeMapper(TableShape, queryResult, mapperArena.allocator(), null);
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
const InstanceInterface = _result.QueryResultReader(TableShape, inlineRelations).Instance;
// Initialize models list.
var models = std.ArrayList(*Model).init(allocator);
defer models.deinit();
return struct {
const Self = @This();
// Get all raw models from the result mapper.
while (try mapper.next()) |rawModel| {
// Parse each raw model from the mapper.
const model = try allocator.create(Model);
model.* = try repositoryConfig.fromSql(rawModel);
try models.append(model);
pub const Instance = struct {
/// Main object mapper.
mainMapper: pg.Mapper(TableShape) = undefined,
fn next(opaqueSelf: *anyopaque) !?TableShape { //TODO inline relations.
const self: *Instance = @ptrCast(@alignCast(opaqueSelf));
return try self.mainMapper.next();
}
// Return a result with the models.
return repository.RepositoryResult(Model).init(allocator,
zollections.Collection(Model).init(allocator, try models.toOwnedSlice()),
mapperArena,
);
pub fn instance(self: *Instance, allocator: std.mem.Allocator) InstanceInterface {
return .{
.__interface = .{
.instance = self,
.next = next,
},
.allocator = allocator,
};
}
};
instance: Instance = Instance{},
/// The PostgreSQL query result.
result: *pg.Result,
fn initInstance(opaqueSelf: *anyopaque, allocator: std.mem.Allocator) !InstanceInterface {
const self: *Self = @ptrCast(@alignCast(opaqueSelf));
self.instance.mainMapper = try makeMapper(TableShape, self.result, allocator, null);
return self.instance.instance(allocator);
}
pub fn reader(self: *Self) _result.QueryResultReader(TableShape, inlineRelations) {
return .{
._interface = .{
.instance = self,
.init = initInstance,
},
};
}
/// Initialize a PostgreSQL query result reader from the given query result.
pub fn init(result: *pg.Result) Self {
return .{
.result = result,
};
}
};
}

View file

@ -9,6 +9,7 @@ const _conditions = @import("conditions.zig");
const relations = @import("relations.zig");
const repository = @import("repository.zig");
const _comptime = @import("comptime.zig");
const _result = @import("result.zig");
/// Repository query configuration structure.
pub const RepositoryQueryConfiguration = struct {
@ -19,6 +20,8 @@ pub const RepositoryQueryConfiguration = struct {
/// Compiled relations structure.
const CompiledRelations = struct {
inlineRelations: []relations.ModelRelation,
otherRelations: []relations.ModelRelation,
inlineSelect: []const u8,
inlineJoins: []const u8,
};
@ -58,11 +61,15 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
}
break :compile CompiledRelations{
.inlineRelations = inlineRelations,
.otherRelations = &[0]relations.ModelRelation{},
.inlineSelect = if (inlineSelect.len > 0) ", " ++ _comptime.join(", ", inlineSelect) else "",
.inlineJoins = if (inlineJoins.len > 0) " " ++ _comptime.join(" ", inlineJoins) else "",
};
} else {
break :compile CompiledRelations{
.inlineRelations = &[0]relations.ModelRelation{},
.otherRelations = &[0]relations.ModelRelation{},
.inlineSelect = "",
.inlineJoins = "",
};
@ -80,6 +87,9 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
return struct {
const Self = @This();
/// Result mapper type.
pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, compiledRelations.inlineRelations, compiledRelations.otherRelations);
arena: std.heap.ArenaAllocator,
connector: database.Connector,
connection: *database.Connection = undefined,
@ -286,7 +296,8 @@ pub fn RepositoryQuery(comptime Model: type, comptime TableShape: type, comptime
defer queryResult.deinit();
// Map query results.
return postgresql.mapResults(Model, TableShape, repositoryConfig, allocator, queryResult);
var postgresqlReader = postgresql.QueryResultReader(TableShape, compiledRelations.inlineRelations).init(queryResult);
return try ResultMapper.map(allocator, postgresqlReader.reader());
}
/// Initialize a new repository query.

72
src/result.zig Normal file
View file

@ -0,0 +1,72 @@
const std = @import("std");
const zollections = @import("zollections");
const _repository = @import("repository.zig");
const _relations = @import("relations.zig");
/// Generic interface of a query result reader.
pub fn QueryResultReader(comptime TableShape: type, comptime inlineRelations: ?[]const _relations.ModelRelation) type {
_ = inlineRelations;
return struct {
const Self = @This();
/// Generic interface of a query result reader instance.
pub const Instance = struct {
__interface: struct {
instance: *anyopaque,
next: *const fn (self: *anyopaque) anyerror!?TableShape, //TODO inline relations.
},
allocator: std.mem.Allocator,
pub fn next(self: Instance) !?TableShape {
return self.__interface.next(self.__interface.instance);
}
};
_interface: struct {
instance: *anyopaque,
init: *const fn (self: *anyopaque, allocator: std.mem.Allocator) anyerror!Instance,
},
/// Initialize a reader instance.
pub fn init(self: Self, allocator: std.mem.Allocator) !Instance {
return self._interface.init(self._interface.instance, allocator);
}
};
}
/// Map query result to repository model structures, and load the given relations.
pub fn ResultMapper(comptime Model: type, comptime TableShape: type, comptime repositoryConfig: _repository.RepositoryConfiguration(Model, TableShape), comptime inlineRelations: ?[]const _relations.ModelRelation, comptime relations: ?[]const _relations.ModelRelation) type {
_ = relations;
return struct {
/// Map the query result to a repository result, with all the required relations.
pub fn map(allocator: std.mem.Allocator, queryReader: QueryResultReader(TableShape, inlineRelations)) !_repository.RepositoryResult(Model) {
// Create an arena for mapper data.
var mapperArena = std.heap.ArenaAllocator.init(allocator);
// Initialize query result reader.
const reader = try queryReader.init(mapperArena.allocator());
// Initialize models list.
var models = std.ArrayList(*Model).init(allocator);
defer models.deinit();
// Get all raw models from the result reader.
while (try reader.next()) |rawModel| {
// Parse each raw model from the reader.
const model = try allocator.create(Model);
model.* = try repositoryConfig.fromSql(rawModel);
//TODO inline relations.
try models.append(model);
}
//TODO load relations?
// Return a result with the models.
return _repository.RepositoryResult(Model).init(allocator,
zollections.Collection(Model).init(allocator, try models.toOwnedSlice()),
mapperArena,
);
}
};
}

View file

@ -7,6 +7,7 @@ const postgresql = @import("postgresql.zig");
const _sql = @import("sql.zig");
const conditions = @import("conditions.zig");
const repository = @import("repository.zig");
const _result = @import("result.zig");
/// Repository update query configuration structure.
pub fn RepositoryUpdateConfiguration(comptime UpdateShape: type) type {
@ -57,6 +58,9 @@ pub fn RepositoryUpdate(comptime Model: type, comptime TableShape: type, comptim
const Configuration = RepositoryUpdateConfiguration(UpdateShape);
/// Result mapper type.
pub const ResultMapper = _result.ResultMapper(Model, TableShape, repositoryConfig, null, null);
arena: std.heap.ArenaAllocator,
connector: database.Connector,
connection: *database.Connection = undefined,
@ -301,7 +305,8 @@ pub fn RepositoryUpdate(comptime Model: type, comptime TableShape: type, comptim
defer queryResult.deinit();
// Map query results.
return postgresql.mapResults(Model, TableShape, repositoryConfig, allocator, queryResult);
var postgresqlReader = postgresql.QueryResultReader(TableShape, null).init(queryResult);
return try ResultMapper.map(allocator, postgresqlReader.reader());
}
/// Initialize a new repository update query.