Skip to content

Commit

Permalink
datum: (optional) support for OIDs to convert between PG and ZIG types (
Browse files Browse the repository at this point in the history
#78)

Add support to pass the values OID to the PG<->Zig type converter. This
fixes issues with converting different string types to Zig types.

The SPI rows now also captures the SPI frame (the current tuple). This
allows us to iterate over a tabletuple (list of rows) from a query while
executing other statements via SPI.
  • Loading branch information
urso authored Jul 15, 2024
1 parent 73f05d1 commit 589fe7b
Show file tree
Hide file tree
Showing 3 changed files with 285 additions and 104 deletions.
212 changes: 152 additions & 60 deletions src/pgzx/datum.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,116 @@ const mem = @import("mem.zig");
const meta = @import("meta.zig");
const varatt = @import("varatt.zig");

pub fn Conv(comptime T: type, comptime from: anytype, comptime to: anytype) type {
pub fn fromNullableDatum(comptime T: type, d: pg.NullableDatum) !T {
return findConv(T).fromNullableDatum(d);
}

pub fn fromNullableDatumWithOID(comptime T: type, d: pg.NullableDatum, oid: ?pg.Oid) !T {
return findConv(T).fromNullableDatumWithOID(d, oid);
}

pub fn fromDatum(comptime T: type, d: pg.Datum, is_null: bool) !T {
return findConv(T).fromNullableDatum(.{ .value = d, .isnull = is_null });
}

pub fn fromDatumWithOID(comptime T: type, d: pg.Datum, is_null: bool, oid: ?pg.Oid) !T {
return findConv(T).fromNullableDatumWithOID(.{ .value = d, .isnull = is_null }, oid);
}

pub fn toNullableDatum(v: anytype) !pg.NullableDatum {
return findConv(@TypeOf(v)).toNullableDatum(v);
}

pub fn toNullableDatumWithOID(v: anytype, oid: ?pg.Oid) !pg.NullableDatum {
return findConv(@TypeOf(v)).toNullableDatumWithOID(v, oid);
}

// pub fn Conv(comptime T: type, comptime from: anytype, comptime to: anytype) type {
pub fn Conv(comptime context: type) type {
return struct {
pub const Type = T;
pub const Type = context.Type;

const Self = @This();

pub fn fromNullableDatum(d: pg.NullableDatum) !Type {
return Self.fromNullableDatumWithOID(d, null);
}

pub fn fromNullableDatumWithOID(d: pg.NullableDatum, oid: ?pg.Oid) !Type {
if (d.isnull) {
return err.PGError.UnexpectedNullValue;
}
return try from(d.value);
return try context.from(d.value, normalizeOid(oid));
}

pub fn toNullableDatum(v: Type) !pg.NullableDatum {
return Self.toNullableDatumWithOID(v, null);
}

pub fn toNullableDatumWithOID(v: Type, oid: ?pg.Oid) !pg.NullableDatum {
return .{
.value = try to(v),
.value = try context.to(v, normalizeOid(oid)),
.isnull = false,
};
}
};
}

pub fn ConvNoFail(comptime T: type, comptime from: anytype, comptime to: anytype) type {
return struct {
pub fn ConvNoFail(comptime context: type) type {
return Conv(struct {
pub const Type = context.Type;

pub fn from(d: pg.Datum, oid: pg.Oid) !Type {
return context.from(d, oid);
}

pub fn to(v: Type, oid: pg.Oid) !pg.Datum {
return context.to(v, oid);
}
});
}

pub fn SimpleConv(comptime T: type, comptime from_datum: anytype, comptime to_datum: anytype) type {
return ConvNoFail(struct {
pub const Type = T;
pub fn fromNullableDatum(d: pg.NullableDatum) !T {
if (d.isnull) {
return err.PGError.UnexpectedNullValue;
}
return from(d.value);

pub fn from(d: pg.Datum, oid: pg.Oid) !Type {
_ = oid;
return from_datum(d);
}
pub fn toNullableDatum(v: T) !pg.NullableDatum {
return .{
.value = to(v),
.isnull = false,
};

pub fn to(v: Type, oid: pg.Oid) !pg.Datum {
_ = oid;
return to_datum(v);
}
};
});
}

/// Conversion decorator for optional types.
pub fn OptConv(comptime C: anytype) type {
return struct {
pub const Type = ?C.Type;

const Self = @This();

pub fn fromNullableDatum(d: pg.NullableDatum) !Type {
return try Self.fromNullableDatumWithOID(d, null);
}

pub fn fromNullableDatumWithOID(d: pg.NullableDatum, oid: ?pg.Oid) !Type {
if (d.isnull) {
return null;
}
return try C.fromNullableDatum(d);
return try C.fromNullableDatumWithOID(d, oid);
}

pub fn toNullableDatum(v: Type) !pg.NullableDatum {
return Self.toNullableDatumWithOID(v, null);
}

pub fn toNullableDatumWithOID(v: Type, oid: ?pg.Oid) !pg.NullableDatum {
if (v) |value| {
return try C.toNullableDatum(value);
return try C.toNullableDatumWithOID(value, oid);
} else {
return .{
.value = 0,
Expand All @@ -71,21 +132,8 @@ pub fn OptConv(comptime C: anytype) type {
/// reflection only.
var directMappings = .{
.{ pg.Datum, PGDatum },
.{ pg.NullableDatum, PGNullableDatum },
};

pub fn fromNullableDatum(comptime T: type, d: pg.NullableDatum) !T {
return findConv(T).fromNullableDatum(d);
}

pub fn fromDatum(comptime T: type, d: pg.Datum, is_null: bool) !T {
return findConv(T).fromNullableDatum(.{ .value = d, .isnull = is_null });
}

pub fn toNullableDatum(v: anytype) !pg.NullableDatum {
return findConv(@TypeOf(v)).toNullableDatum(v);
}

pub fn findConv(comptime T: type) type {
if (isConv(T)) { // is T already a converter?
return T;
Expand Down Expand Up @@ -155,32 +203,35 @@ inline fn isConv(comptime T: type) bool {
return @hasDecl(T, "Type") and @hasDecl(T, "fromNullableDatum") and @hasDecl(T, "toNullableDatum");
}

pub const Void = ConvNoFail(void, idDatum, toVoid);
pub const Bool = ConvNoFail(bool, pg.DatumGetBool, pg.BoolGetDatum);
pub const Int8 = ConvNoFail(i8, datumGetInt8, pg.Int8GetDatum);
pub const Int16 = ConvNoFail(i16, pg.DatumGetInt16, pg.Int16GetDatum);
pub const Int32 = ConvNoFail(i32, pg.DatumGetInt32, pg.Int32GetDatum);
pub const Int64 = ConvNoFail(i64, pg.DatumGetInt64, pg.Int64GetDatum);
pub const UInt8 = ConvNoFail(u8, pg.DatumGetUInt8, pg.UInt8GetDatum);
pub const UInt16 = ConvNoFail(u16, pg.DatumGetUInt16, pg.UInt16GetDatum);
pub const UInt32 = ConvNoFail(u32, pg.DatumGetUInt32, pg.UInt32GetDatum);
pub const UInt64 = ConvNoFail(u64, pg.DatumGetUInt64, pg.UInt64GetDatum);
pub const Float32 = ConvNoFail(f32, pg.DatumGetFloat4, pg.Float4GetDatum);
pub const Float64 = ConvNoFail(f64, pg.DatumGetFloat8, pg.Float8GetDatum);

pub const SliceU8 = Conv([]const u8, getDatumTextSlice, sliceToDatumText);
pub const SliceU8Z = Conv([:0]const u8, getDatumTextSliceZ, sliceToDatumTextZ);

pub const PGDatum = ConvNoFail(pg.Datum, idDatum, idDatum);
const PGNullableDatum = struct {
pub const Type = pg.NullableDatum;
pub fn fromNullableDatum(d: pg.NullableDatum) !Type {
return d;
}
pub fn toNullableDatum(v: Type) !pg.NullableDatum {
return v;
}
};
inline fn normalizeOid(oid: ?pg.Oid) pg.Oid {
return oid orelse pg.InvalidOid;
}

pub const Void = SimpleConv(void, idDatum, toVoid);
pub const Bool = SimpleConv(bool, pg.DatumGetBool, pg.BoolGetDatum);
pub const Int8 = SimpleConv(i8, datumGetInt8, pg.Int8GetDatum);
pub const Int16 = SimpleConv(i16, pg.DatumGetInt16, pg.Int16GetDatum);
pub const Int32 = SimpleConv(i32, pg.DatumGetInt32, pg.Int32GetDatum);
pub const Int64 = SimpleConv(i64, pg.DatumGetInt64, pg.Int64GetDatum);
pub const UInt8 = SimpleConv(u8, pg.DatumGetUInt8, pg.UInt8GetDatum);
pub const UInt16 = SimpleConv(u16, pg.DatumGetUInt16, pg.UInt16GetDatum);
pub const UInt32 = SimpleConv(u32, pg.DatumGetUInt32, pg.UInt32GetDatum);
pub const UInt64 = SimpleConv(u64, pg.DatumGetUInt64, pg.UInt64GetDatum);
pub const Float32 = SimpleConv(f32, pg.DatumGetFloat4, pg.Float4GetDatum);
pub const Float64 = SimpleConv(f64, pg.DatumGetFloat8, pg.Float8GetDatum);
pub const PGDatum = SimpleConv(pg.Datum, idDatum, idDatum);

pub const SliceU8Z = Conv(struct {
pub const Type = [:0]const u8;
pub const from = getDatumStringLikeZ;
pub const to = sliceToDatumStringLikeZ;
});

pub const SliceU8 = Conv(struct {
pub const Type = []const u8;
pub const from = getDatumStringLikeZ;
pub const to = sliceToDatumStringLike;
});

// TODO: conversion decorator for array types

Expand All @@ -199,10 +250,26 @@ fn datumGetInt8(d: pg.Datum) i8 {
return @as(i8, @bitCast(@as(i8, @truncate(d))));
}

pub fn getDatumStringLike(datum: pg.Datum, oid: pg.Oid) ![]const u8 {
return getDatumStringLikeZ(datum, oid);
}

/// Convert a datum to a TEXT slice. This function detoast the datum if necessary.
/// All allocations will be performed in the Current Memory Context.
pub fn getDatumTextSlice(datum: pg.Datum) ![]const u8 {
return getDatumTextSliceZ(datum);
pub fn getDatumTextSlice(datum: pg.Datum, oid: pg.Oid) ![]const u8 {
return getDatumTextSliceZ(datum, oid);
}

pub inline fn getDatumCString(datum: pg.Datum) ![]const u8 {
return getDatumCStringZ(datum);
}

pub fn getDatumStringLikeZ(datum: pg.Datum, oid: pg.Oid) ![:0]const u8 {
return if (useStringPointer(oid)) getDatumCStringZ(datum) else getDatumTextSliceZ(datum);
}

pub inline fn getDatumCStringZ(datum: pg.Datum) ![:0]const u8 {
return std.mem.span(pg.DatumGetCString(datum));
}

/// Convert a datum to a TEXT slice. This function detoast the datum if necessary.
Expand All @@ -222,6 +289,24 @@ pub fn getDatumTextSliceZ(datum: pg.Datum) ![:0]const u8 {
return buffer[0..len :0];
}

pub fn sliceToDatumStringLikeZ(slice: [:0]const u8, oid: pg.Oid) !pg.Datum {
return if (useStringPointer(oid)) sliceToDatumCStringZ(slice) else sliceToDatumTextZ(slice);
}

pub fn sliceToDatumStringLike(slice: []const u8, oid: pg.Oid) !pg.Datum {
return if (useStringPointer(oid)) sliceToDatumCString(slice) else sliceToDatumText(slice);
}

pub inline fn sliceToDatumCString(slice: []const u8) !pg.Datum {
const alloc = mem.PGCurrentContextAllocator;
const slice_z = try alloc.dupeZ(u8, slice);
return pg.CStringGetDatum(slice_z.ptr);
}

pub inline fn sliceToDatumCStringZ(slice: [:0]const u8) !pg.Datum {
return pg.CStringGetDatum(slice.ptr);
}

pub inline fn sliceToDatumText(slice: []const u8) !pg.Datum {
const text = pg.cstring_to_text_with_len(slice.ptr, @intCast(slice.len));
return pg.PointerGetDatum(text);
Expand All @@ -230,3 +315,10 @@ pub inline fn sliceToDatumText(slice: []const u8) !pg.Datum {
pub inline fn sliceToDatumTextZ(slice: [:0]const u8) !pg.Datum {
return sliceToDatumText(slice);
}

pub inline fn useStringPointer(oid: pg.Oid) bool {
return switch (oid) {
pg.CHAROID, pg.NAMEOID, pg.CSTRINGOID => true,
else => false,
};
}
6 changes: 5 additions & 1 deletion src/pgzx/fmgr/args.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const std = @import("std");
const pg = @import("pgzx_pgsys");

const err = @import("../err.zig");
const datum = @import("../datum.zig");

/// Index function argument type.
Expand Down Expand Up @@ -58,7 +60,9 @@ fn readArg(comptime T: type, fcinfo: pg.FunctionCallInfo, argNum: u32) !readArgT
return fcinfo;
}
const converter = comptime datum.findConv(T);
return converter.fromNullableDatum(try mustGetArgNullable(fcinfo, argNum));
const oid = try err.wrap(pg.get_fn_expr_argtype, .{ fcinfo.*.flinfo, @as(c_int, @intCast(argNum)) });
const ndatum = try mustGetArgNullable(fcinfo, argNum);
return converter.fromNullableDatumWithOID(ndatum, oid);
}

fn readOptionalArg(comptime T: type, fcinfo: pg.FunctionCallInfo, argNum: u32) !?T {
Expand Down
Loading

0 comments on commit 589fe7b

Please sign in to comment.