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:
parent
04b61f9787
commit
e33bc5aaa2
5 changed files with 148 additions and 28 deletions
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
72
src/result.zig
Normal 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,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue