karlseguin / pg.zig

Native PostgreSQL driver / client for Zig
MIT License
158 stars 9 forks source link

Added QueryResult.to fn #21

Closed richard-powers closed 1 month ago

richard-powers commented 1 month ago

I've been using this function to copy row data into an actual struct, and thought it may be useful to have in the library.

Feel free to modify or request modifications, this is just pulled directly from an internal work project.

zigster64 commented 1 month ago

Nice !

One small observation - the Result object has a ptr to an internal arena allocator that is lifecycled over the stamement execution.

It would be possible to drop the Allocator param, let the result dupe values using its own arena, and then everything gets cleaned up automatically when the statement is complete (no need to free all those dupes anymore at the callsite)

Will try that out here and see if it works the way I think it does

EDIT - I lie ! It is generally true that the allocator is an arena that the stmt creates. However, there are variants of calling query that let you pass in your own allocator when the query is run, and that is used instead.

Means that you can still drop the alloc param and use the one on the result. If you call query() normally, then the dupes will free when the stmt ends and frees the arena. If you call queryOpts({alloc = myalloc}), then yeah .. the caller is repsonsible for cleaning up the dupes. Still a win !

karlseguin commented 1 month ago

I'm going to tweak this in a follow up commit.

karlseguin commented 1 month ago

I guess the fancier follow up would be

1 - Supporting arrays 2 - Adding a map option to ToOpts which can either be ordinal or name. ordinal would be the current logic. name would map based on the field names, using getCol instead of get

zigster64 commented 1 month ago

It’s not unusual to have fields in the target struct that you want to skip as well, if they are not in the row

there is a discussion on discord zig-help about providing meta data in the target struct to assist mapping (sort of like go struct tags)

karlseguin commented 3 weeks ago

There's now a .{.map = .name} option. The default is .{.map = .ordinal}. When using .name , the column_names = true option must be specified in the queryOpts or rowOpts functions.

Columns with no field equivalent are ignored. Fields with no column equivalent are set to their default value. If there is no default value, to will return an error.FieldColumnMismatch.

There's overhead to using .{.map = .name}, since the name -> index has to be looked up. When done in a loop (when iterating through a result), it's better to create a mapper and iterate through it:

var result  = try conn.queryOpts("select....", .{}, .{.column_names = true});
defer result.deinit();

// this takes the same `dupe` and `allocator` opts as `row.to`
var mapper = result.mapper(User, .{});
while (try mapper.next()) |user| {
   // you have a user
}