vrischmann / zig-sqlite

zig-sqlite is a small wrapper around sqlite's C API, making it easier to use with Zig.
MIT License
367 stars 49 forks source link

Make allocators optional for custom struct binding #117

Open lun-4 opened 1 year ago

lun-4 commented 1 year ago

My custom struct is a wrapper around a [26]u8

pub const ID = struct {
    data: [26]u8,
    // .. various helper methods to generate IDs

    /// Turn ID into a SQL argument
    pub fn sql(self: *const Self) sqlite.Text {
        return sqlite.Text{ .data = &self.data };
    }
}

While exploring things, I noticed that bindField() override exists, and that I could use it to remove the need to write id.sql() in my queries, however, that method interface would not suffice for my usecase:

https://github.com/vrischmann/zig-sqlite/blob/b98ebdf4a9f038ca6322e6091e45c4998efad946/sqlite.zig#L1661

If I wanted to remove id.sql() calls, I would then need to add unnecessary allocators to every SQL call that gives an ID struct as an argument, even though the memory that I want to pass to SQLite is already available on the stack by the time I want to execute the query. Example

            try db.exec(
                \\ delete from tag_names
                \\ where core_hash = ?
            ,
                .{},
                .{self.core.id}, // self.core.id is a full ID struct, which means its inner [26]u8 is available for us to use directly.
            );

Maybe we could check argument count at comptime and not require allocators if argument count is 1?

lun-4 commented 1 year ago

While exploring #118, I noticed that passing the entire struct as a parameter to the query turns it into stack memory by the time custom bindField is run, which breaks the assumption I had that bindField would have a custom struct with the lifetime of the entire statement, instead it has the lifetime of the bindField() internal function, so the const pointer that I take in the custom one is completely invalid by the time the query is executed. Ooops.

Not sure how to approach this one other than going back to ID.sql() as with that I know that I am using the ID with the correct lifetime. Suggestions welcome.