karlseguin / pg.zig

Native PostgreSQL driver / client for Zig
MIT License
250 stars 18 forks source link

Raw data for record? #11

Closed Krymancer closed 8 months ago

Krymancer commented 8 months ago

I'm not sure if this is possible but I have a function in postgress that return a record.

Basicly it will return (x) if something wrong happen and if is ok will return (a,b)

there is the function:

CREATE OR REPLACE FUNCTION criartransacao(
    IN id_cliente INT,
    IN valor INT,
    IN tipo CHAR(1),
    IN descricao varchar(10)
) RETURNS RECORD AS $$
DECLARE
    ret RECORD;
BEGIN
    PERFORM id FROM cliente
    WHERE id = id_cliente;

    IF not found THEN
    select 1 into ret;
    RETURN ret;
    END IF;

    INSERT INTO transacao (valor, tipo, descricao, cliente_id)
    VALUES (ABS(valor), tipo, descricao, id_cliente);
    UPDATE cliente
    SET saldo = saldo + valor
    WHERE id = id_cliente AND (valor > 0 OR saldo + valor >= -limite)
    RETURNING saldo, limite
    INTO ret;

    IF ret.limite is NULL THEN
        select 2 into ret;
    END IF;

    RETURN ret;
END;
$$ LANGUAGE plpgsql;

My first guess was that I cloud get the raw data and if the len of the data was 1, I had an error, otherwhite I would have on a on data[0] and b on data[1]. Not what I am seeing here.

This is a snnipet of what I'm trying to do

 var result = try global.pool.query("select criartransacao($1, $2, $3, $4);", .{ id, valor, payload.tipo, payload.descricao });
        defer result.deinit();

        const row = try result.next();
        const record = row.?.get([]const u8, 0);

        std.debug.print("record: '{any}' len: {}\n", .{ record, record.len });

        if (record.len == 1) {
            const status: CreateTransactionReturn = @enumFromInt(record[0]);

            switch (status) {
                CreateTransactionReturn.NotFound => {
                    res.status = 404;
                    return;
                },
                CreateTransactionReturn.LimitExceeded => {
                    res.status = 422;
                    return;
                },
            }
        }

        const saldo = record[0];
        const limite = record[1];

right now I'm working with this

record: '{ 0, 0, 0, 1, 0, 0, 0, 23, 0, 0, 0, 4, 0, 0, 0, 2 }' len: 16
record: '{ 0, 0, 0, 2, 0, 0, 0, 23, 0, 0, 0, 4, 255, 254, 121, 97, 0, 0, 0, 23, 0, 0, 0, 4, 0, 1, 134, 160 }' len: 28
record: '{ 0, 0, 0, 1, 0, 0, 0, 23, 0, 0, 0, 4, 0, 0, 0, 2 }' len: 16

I'm guessing that the 4 first values represents the quantity of the data, like, when I expect a error to return like (2) the frist record is 00001, I'm not sure what 23 and 4 means here, and 2 is the value I expect

in the middle one seems similar, 2 for the two results, 23, and 4 (I guess is shows that is an integer or smothing) and them the value.

I'm sorry if this is a dumb question but I'm lost into this, have no Idea how to convert this to the data I expect, hope someone can help me or show me an easy way to do this?

Krymancer commented 8 months ago

I've thinking about this and for now I'm "solving" this parsing the data direclty.

        var result = try global.pool.query("select criartransacao($1, $2, $3, $4);", .{ id, valor, payload.tipo, payload.descricao });
        defer result.deinit();

        const row = try result.next();
        const raw = row.?.get([]u8, 0);

        // First byte indicates how many items we have in the record
        const len = std.mem.readInt(i32, raw[0..4], .big);

        if (len == 1) {
            // Don't really care for the next two bytes, the value is in [12..16]
            const status_value = std.mem.readInt(i32, raw[12..16], .big);
            const status: CreateTransactionReturn = @enumFromInt(status_value);

            switch (status) { ... }
        }

        // If len is not 1 we expect two values so...
        const saldo = std.mem.readInt(i32, raw[12..16], .big);
        // We have more two bytes that we don't care here so...
        const limite = std.mem.readInt(i32, raw[24..28], .big);

Maybe when I more familiar with zig and postgres I can make a PR to try getting records directly

karlseguin commented 8 months ago

There's support for the record type now. The API is simple and, to avoid allocations, I opted to force reading the columns in order (rather than being able to specify an ordinal position).

FWIW, since your query is only expecting a single row, you can use pool.row(...) which wraps result + result.next. deinit has to be called on the row, since it's a special row (internally it holds the result)

var row = (try pool.row(....)).?
defer row.deinit()