131 lines
4.6 KiB
Zig
131 lines
4.6 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,
|
|
.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),
|
|
|
|
else => @compileError("Type " ++ @typeName(T) ++ " not supported"),
|
|
},
|
|
};
|
|
}
|