fediglam/src/sql/engines/common.zig

133 lines
4.7 KiB
Zig

const std = @import("std");
const util = @import("util");
const Uuid = util.Uuid;
const DateTime = util.DateTime;
const Allocator = std.mem.Allocator;
const UnexpectedError = error{Unexpected};
const ConstraintError = error{
NotNullViolation,
ForeignKeyViolation,
UniqueViolation,
CheckViolation,
/// Catchall for miscellaneous types of constraints
ConstraintViolation,
};
pub const OpenError = error{BadConnection} || UnexpectedError;
pub const ExecError = error{
Cancelled,
BadConnection,
InternalException,
DatabaseBusy,
PermissionDenied,
SqlException,
/// Argument could not be marshalled for query
BindException,
/// An argument was not used by the query (not checked in all DB engines)
UnusedArgument,
/// Memory error when marshalling argument for query
OutOfMemory,
AllocatorRequired,
} || ConstraintError || UnexpectedError;
pub const RowError = error{
Cancelled,
BadConnection,
InternalException,
DatabaseBusy,
PermissionDenied,
SqlException,
} || ConstraintError || UnexpectedError;
pub const GetError = error{
OutOfMemory,
AllocatorRequired,
ResultTypeMismatch,
} || UnexpectedError;
pub const ColumnCountError = error{OutOfRange};
pub const ColumnIndexError = error{ NotFound, OutOfRange };
pub const QueryOptions = struct {
// If true, then it will not return an error on the SQLite backend
// if an argument passed does not map to a parameter in the query.
// Has no effect on the postgres backend.
ignore_unused_arguments: bool = false,
// The allocator to use for query preparation and submission.
// All memory allocated with this allocator will be freed before results
// are retrieved.
// Some types (enums with constant representation, null terminated strings)
// do not require allocators for prep. If an allocator is needed but not
// provided, `error.AllocatorRequired` will be returned.
// Only used with the postgres backend.
allocator: ?Allocator = null,
};
// Turns a value into its appropriate textual value (or null)
// as appropriate using the given arena allocator
pub fn prepareParamText(arena: *std.heap.ArenaAllocator, val: anytype) !?[:0]const u8 {
if (comptime std.meta.trait.isZigString(@TypeOf(val))) {
if (comptime std.meta.sentinel(@TypeOf(val))) |s| if (comptime s == 0) return val;
return try std.cstr.addNullByte(arena.allocator(), val);
}
return switch (@TypeOf(val)) {
[:0]u8, [:0]const u8 => val,
[]const u8, []u8 => try std.cstr.addNullByte(arena.allocator(), val),
DateTime, Uuid => try std.fmt.allocPrintZ(arena.allocator(), "{}", .{val}),
else => |T| switch (@typeInfo(T)) {
.Enum => return @tagName(val),
.Optional => if (val) |v| try prepareParamText(arena, v) else null,
.Bool, .Int => try std.fmt.allocPrintZ(arena.allocator(), "{}", .{val}),
.Union => loop: inline for (std.meta.fields(T)) |field| {
// Have to do this in a roundabout way to satisfy comptime checker
const Tag = std.meta.Tag(T);
const tag = @field(Tag, field.name);
if (val == tag) break :loop try prepareParamText(arena, @field(val, field.name));
} else unreachable,
else => @compileError("Unsupported Type " ++ @typeName(T)),
},
};
}
// Parse a (not-null) value from a string
pub fn parseValueNotNull(alloc: ?Allocator, comptime T: type, str: []const u8) !T {
return switch (T) {
Uuid => Uuid.parse(str) catch |err| {
std.log.err("Error {} parsing UUID: '{s}'", .{ err, str });
return error.ResultTypeMismatch;
},
DateTime => DateTime.parse(str) catch |err| {
std.log.err("Error {} parsing DateTime: '{s}'", .{ err, str });
return error.ResultTypeMismatch;
},
[]u8, []const u8 => if (alloc) |a| try util.deepClone(a, str) else return error.AllocatorRequired,
else => switch (@typeInfo(T)) {
.Int => std.fmt.parseInt(T, str, 0) catch |err| {
std.log.err("Could not parse int: {}", .{err});
return error.ResultTypeMismatch;
},
.Enum => std.meta.stringToEnum(T, str) orelse {
std.log.err("'{s}' is not a member of enum type {s}", .{ str, @typeName(T) });
return error.ResultTypeMismatch;
},
.Optional => try parseValueNotNull(alloc, std.meta.Child(T), str),
.Bool => return util.serialize.bool_map.get(str) orelse return error.ResultTypeMismatch,
else => @compileError("Type " ++ @typeName(T) ++ " not supported"),
},
};
}