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"), }, }; }