fediglam/src/sql/engines/common.zig

132 lines
4.6 KiB
Zig
Raw Normal View History

2022-09-11 08:55:20 +00:00
const std = @import("std");
const util = @import("util");
const Uuid = util.Uuid;
const DateTime = util.DateTime;
const Allocator = std.mem.Allocator;
2022-10-01 09:05:33 +00:00
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,
2022-10-04 02:41:59 +00:00
BadConnection,
2022-10-01 09:05:33 +00:00
InternalException,
DatabaseBusy,
PermissionDenied,
SqlException,
/// Argument could not be marshalled for query
2022-10-04 02:41:59 +00:00
BindException,
2022-10-01 09:05:33 +00:00
/// An argument was not used by the query (not checked in all DB engines)
2022-10-04 02:41:59 +00:00
UnusedArgument,
2022-10-01 09:05:33 +00:00
/// Memory error when marshalling argument for query
OutOfMemory,
AllocatorRequired,
} || ConstraintError || UnexpectedError;
pub const RowError = error{
Cancelled,
2022-10-04 02:41:59 +00:00
BadConnection,
2022-10-01 09:05:33 +00:00
InternalException,
DatabaseBusy,
PermissionDenied,
SqlException,
} || ConstraintError || UnexpectedError;
pub const GetError = error{
OutOfMemory,
AllocatorRequired,
2022-10-04 02:41:59 +00:00
ResultTypeMismatch,
2022-10-01 09:05:33 +00:00
} || UnexpectedError;
pub const ColumnCountError = error{OutOfRange};
pub const ColumnIndexError = error{ NotFound, OutOfRange };
2022-09-15 01:12:07 +00:00
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.
2022-10-02 05:18:24 +00:00
ignore_unused_arguments: bool = false,
2022-09-15 01:12:07 +00:00
// 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.
2022-11-12 13:08:01 +00:00
allocator: ?Allocator = null,
2022-09-15 01:12:07 +00:00
};
2022-09-11 08:55:20 +00:00
// Turns a value into its appropriate textual value (or null)
// as appropriate using the given arena allocator
2022-09-15 01:12:07 +00:00
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);
}
2022-09-11 08:55:20 +00:00
return switch (@TypeOf(val)) {
[:0]u8, [:0]const u8 => val,
2022-10-01 09:05:33 +00:00
[]const u8, []u8 => try std.cstr.addNullByte(arena.allocator(), val),
2022-09-11 08:55:20 +00:00
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,
.Int => try std.fmt.allocPrintZ(arena.allocator(), "{}", .{val}),
2022-10-11 04:49:36 +00:00
.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,
2022-09-11 08:55:20 +00:00
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) {
2022-10-04 02:41:59 +00:00
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,
2022-09-11 08:55:20 +00:00
else => switch (@typeInfo(T)) {
2022-10-04 02:41:59 +00:00
.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;
},
2022-09-15 01:12:07 +00:00
.Optional => try parseValueNotNull(alloc, std.meta.Child(T), str),
2022-09-11 08:55:20 +00:00
else => @compileError("Type " ++ @typeName(T) ++ " not supported"),
},
};
}