Create helper utils in sql pkg
This commit is contained in:
parent
e94e4384bb
commit
db225b6689
4 changed files with 227 additions and 44 deletions
223
src/sql/lib.zig
223
src/sql/lib.zig
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const util = @import("util");
|
||||||
|
|
||||||
const postgres = @import("./postgres.zig");
|
const postgres = @import("./postgres.zig");
|
||||||
const sqlite = @import("./sqlite.zig");
|
const sqlite = @import("./sqlite.zig");
|
||||||
|
@ -19,40 +20,66 @@ pub const Config = union(Type) {
|
||||||
};
|
};
|
||||||
|
|
||||||
//pub const OpenError = sqlite.OpenError | postgres.OpenError;
|
//pub const OpenError = sqlite.OpenError | postgres.OpenError;
|
||||||
|
const RawResults = union(Type) {
|
||||||
// Represents a set of results.
|
|
||||||
// row() must be called until it returns null, or the query may not complete
|
|
||||||
// Must be deallocated by a call to finish()
|
|
||||||
pub const Results = union(Type) {
|
|
||||||
postgres: postgres.Results,
|
postgres: postgres.Results,
|
||||||
sqlite: sqlite.Results,
|
sqlite: sqlite.Results,
|
||||||
|
|
||||||
pub fn finish(self: Results) void {
|
fn finish(self: RawResults) void {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
.postgres => |pg| pg.finish(),
|
.postgres => |pg| pg.finish(),
|
||||||
.sqlite => |lite| lite.finish(),
|
.sqlite => |lite| lite.finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Represents a set of results.
|
||||||
|
// row() must be called until it returns null, or the query may not complete
|
||||||
|
// Must be deallocated by a call to finish()
|
||||||
|
pub fn Results(comptime result_types: []const type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
const RowTuple = std.meta.Tuple(result_types);
|
||||||
|
|
||||||
|
underlying: RawResults,
|
||||||
|
|
||||||
|
pub fn finish(self: Self) void {
|
||||||
|
self.underlying.finish();
|
||||||
|
}
|
||||||
|
|
||||||
// can be used as an optimization to reduce memory reallocation
|
// can be used as an optimization to reduce memory reallocation
|
||||||
// only works on postgres
|
// only works on postgres
|
||||||
pub fn rowCount(self: Results) ?usize {
|
pub fn rowCount(self: Self) ?usize {
|
||||||
return switch (self) {
|
return switch (self.underlying) {
|
||||||
.postgres => |pg| pg.rowCount(),
|
.postgres => |pg| pg.rowCount(),
|
||||||
.sqlite => null, // not possible without repeating the query
|
.sqlite => null, // not possible without repeating the query
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn row(self: *Results) !?Row {
|
pub fn row(self: *Self, alloc: ?Allocator) !?RowTuple {
|
||||||
return switch (self.*) {
|
const row_val = switch (self.underlying) {
|
||||||
.postgres => |*pg| if (try pg.row()) |r| Row{ .postgres = r } else null,
|
.postgres => |*pg| if (try pg.row()) |r| Row{ .postgres = r } else return null,
|
||||||
.sqlite => |*lite| if (try lite.row()) |r| Row{ .sqlite = r } else null,
|
.sqlite => |*lite| if (try lite.row()) |r| Row{ .sqlite = r } else return null,
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var result: RowTuple = undefined;
|
||||||
|
var fields_allocated = [_]bool{false} ** result.len;
|
||||||
|
errdefer {
|
||||||
|
inline for (result_types) |_, i| {
|
||||||
|
if (fields_allocated[i]) util.deepFree(alloc, result[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline for (result_types) |T, i| {
|
||||||
|
result[i] = try row_val.get(T, i, alloc);
|
||||||
|
fields_allocated[i] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Row is invalidated by the next call to result.row()
|
// Row is invalidated by the next call to result.row()
|
||||||
pub const Row = union(Type) {
|
const Row = union(Type) {
|
||||||
postgres: postgres.Row,
|
postgres: postgres.Row,
|
||||||
sqlite: sqlite.Row,
|
sqlite: sqlite.Row,
|
||||||
|
|
||||||
|
@ -60,7 +87,7 @@ pub const Row = union(Type) {
|
||||||
// Not all types require an allocator to be present. If an allocator is needed but
|
// Not all types require an allocator to be present. If an allocator is needed but
|
||||||
// not required, it will return error.AllocatorRequired.
|
// not required, it will return error.AllocatorRequired.
|
||||||
// The caller is responsible for deallocating T, if relevant.
|
// The caller is responsible for deallocating T, if relevant.
|
||||||
pub fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) !T {
|
fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) anyerror!T {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.postgres => |pg| pg.get(T, idx, alloc),
|
.postgres => |pg| pg.get(T, idx, alloc),
|
||||||
.sqlite => |lite| lite.get(T, idx, alloc),
|
.sqlite => |lite| lite.get(T, idx, alloc),
|
||||||
|
@ -68,31 +95,181 @@ pub const Row = union(Type) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Db = union(Type) {
|
const DbUnion = union(Type) {
|
||||||
postgres: postgres.Db,
|
postgres: postgres.Db,
|
||||||
sqlite: sqlite.Db,
|
sqlite: sqlite.Db,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Db = struct {
|
||||||
|
underlying: DbUnion,
|
||||||
pub fn open(cfg: Config) !Db {
|
pub fn open(cfg: Config) !Db {
|
||||||
return switch (cfg) {
|
return switch (cfg) {
|
||||||
.postgres => |postgres_cfg| Db{
|
.postgres => |postgres_cfg| Db{
|
||||||
|
.underlying = .{
|
||||||
.postgres = try postgres.Db.open(postgres_cfg.conn_str),
|
.postgres = try postgres.Db.open(postgres_cfg.conn_str),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
.sqlite => |lite_cfg| Db{
|
.sqlite => |lite_cfg| Db{
|
||||||
|
.underlying = .{
|
||||||
.sqlite = try sqlite.Db.open(lite_cfg.file_path),
|
.sqlite = try sqlite.Db.open(lite_cfg.file_path),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(self: Db) void {
|
pub fn close(self: Db) void {
|
||||||
switch (self) {
|
switch (self.underlying) {
|
||||||
.postgres => |pg| pg.close(),
|
.postgres => |pg| pg.close(),
|
||||||
.sqlite => |lite| lite.close(),
|
.sqlite => |lite| lite.close(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(self: Db, sql: [:0]const u8, args: anytype, alloc: Allocator) !Results {
|
pub fn query(
|
||||||
return switch (self) {
|
self: Db,
|
||||||
.postgres => |pg| Results{ .postgres = try pg.exec(sql, args, alloc) },
|
comptime result_types: []const type,
|
||||||
.sqlite => |lite| Results{ .sqlite = try lite.exec(sql, args) },
|
sql: [:0]const u8,
|
||||||
};
|
args: anytype,
|
||||||
|
alloc: ?Allocator,
|
||||||
|
) !Results(result_types) {
|
||||||
|
// Create fake transaction to use its functions
|
||||||
|
return (Tx{ .underlying = self.underlying }).query(result_types, sql, args, alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exec(
|
||||||
|
self: Db,
|
||||||
|
sql: [:0]const u8,
|
||||||
|
args: anytype,
|
||||||
|
alloc: ?Allocator,
|
||||||
|
) !void {
|
||||||
|
// Create fake transaction to use its functions
|
||||||
|
return (Tx{ .underlying = self.underlying }).exec(sql, args, alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queryRow(
|
||||||
|
self: Db,
|
||||||
|
comptime result_types: []const type,
|
||||||
|
sql: [:0]const u8,
|
||||||
|
args: anytype,
|
||||||
|
alloc: ?Allocator,
|
||||||
|
) !?Results(result_types).RowTuple {
|
||||||
|
// Create fake transaction to use its functions
|
||||||
|
return (Tx{ .underlying = self.underlying }).exec(sql, args, alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(
|
||||||
|
self: Db,
|
||||||
|
comptime table: []const u8,
|
||||||
|
value: anytype,
|
||||||
|
) !void {
|
||||||
|
// Create fake transaction to use its functions
|
||||||
|
return (Tx{ .underlying = self.underlying }).insert(table, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begins a transaction
|
||||||
|
pub fn begin(self: Db) !Tx {
|
||||||
|
const tx = Tx{ .underlying = self.underlying };
|
||||||
|
try tx.exec("BEGIN", .{}, null);
|
||||||
|
|
||||||
|
return tx;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Tx = struct {
|
||||||
|
underlying: DbUnion,
|
||||||
|
|
||||||
|
// internal helper fn
|
||||||
|
fn queryInternal(
|
||||||
|
self: Tx,
|
||||||
|
sql: [:0]const u8,
|
||||||
|
args: anytype,
|
||||||
|
alloc: ?Allocator,
|
||||||
|
) !RawResults {
|
||||||
|
return switch (self.underlying) {
|
||||||
|
.postgres => |pg| RawResults{ .postgres = try pg.exec(sql, args, alloc) },
|
||||||
|
.sqlite => |lite| RawResults{ .sqlite = try lite.exec(sql, args) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes a query and returns the result set
|
||||||
|
pub fn query(
|
||||||
|
self: Tx,
|
||||||
|
comptime result_types: []const type,
|
||||||
|
sql: [:0]const u8,
|
||||||
|
args: anytype,
|
||||||
|
alloc: ?Allocator,
|
||||||
|
) !Results(result_types) {
|
||||||
|
return Results(result_types){ .unerlying = try self.queryInternal(sql, args, alloc) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executes a query without returning results
|
||||||
|
pub fn exec(
|
||||||
|
self: Tx,
|
||||||
|
sql: [:0]const u8,
|
||||||
|
args: anytype,
|
||||||
|
alloc: ?Allocator,
|
||||||
|
) !void {
|
||||||
|
(try self.queryInternal(sql, args, alloc)).finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs a query and returns a single row
|
||||||
|
pub fn queryRow(
|
||||||
|
self: Tx,
|
||||||
|
comptime result_types: []const type,
|
||||||
|
q: [:0]const u8,
|
||||||
|
args: anytype,
|
||||||
|
alloc: ?Allocator,
|
||||||
|
) !?Results(result_types).RowTuple {
|
||||||
|
var results = try self.query(result_types, q, args, alloc);
|
||||||
|
defer results.finish();
|
||||||
|
|
||||||
|
const row = (try results.row(alloc)) orelse return null;
|
||||||
|
errdefer util.deepFree(alloc, row);
|
||||||
|
|
||||||
|
var more_rows = false;
|
||||||
|
while (try results.row(alloc)) |r| {
|
||||||
|
util.deepFree(alloc, r);
|
||||||
|
more_rows = true;
|
||||||
|
}
|
||||||
|
if (more_rows) return error.TooManyRows;
|
||||||
|
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserts a single value into a table
|
||||||
|
pub fn insert(
|
||||||
|
self: Tx,
|
||||||
|
comptime table: []const u8,
|
||||||
|
value: anytype,
|
||||||
|
) !void {
|
||||||
|
const ValueType = comptime @TypeOf(value);
|
||||||
|
const table_spec = comptime table ++ build_field_list(ValueType, null);
|
||||||
|
const value_spec = comptime build_field_list(ValueType, "?");
|
||||||
|
const q = comptime std.fmt.comptimePrint(
|
||||||
|
"INSERT INTO {s} VALUES {s}",
|
||||||
|
.{ table_spec, value_spec },
|
||||||
|
);
|
||||||
|
try self.exec(q, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rollback(self: Tx) void {
|
||||||
|
self.exec("ROLLBACK", .{}, null) catch |err| {
|
||||||
|
std.log.err("Error occured during rollback operation: {}", .{err});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(self: Tx) !void {
|
||||||
|
try self.exec("COMMIT", .{}, null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn build_field_list(comptime T: type, comptime placeholder: ?[]const u8) []const u8 {
|
||||||
|
comptime {
|
||||||
|
const joiner = ",";
|
||||||
|
var result: []const u8 = "";
|
||||||
|
inline for (std.meta.fields(T)) |f| {
|
||||||
|
result = result ++ joiner ++ (placeholder orelse f.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "(" ++ result[joiner.len..] ++ ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -84,14 +84,18 @@ pub const Db = struct {
|
||||||
|
|
||||||
const format_text = 0;
|
const format_text = 0;
|
||||||
const format_binary = 1;
|
const format_binary = 1;
|
||||||
pub fn exec(self: Db, sql: [:0]const u8, args: anytype, alloc: Allocator) !Results {
|
pub fn exec(self: Db, sql: [:0]const u8, args: anytype, alloc: ?Allocator) !Results {
|
||||||
const result = blk: {
|
const result = blk: {
|
||||||
var arena = std.heap.ArenaAllocator.init(alloc);
|
if (comptime args.len > 0) {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(alloc orelse return error.AllocatorRequired);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
const params = try arena.allocator().alloc(?[*]const u8, args.len);
|
const params = try arena.allocator().alloc(?[*]const u8, args.len);
|
||||||
inline for (args) |a, i| params[i] = if (try common.prepareParamText(arena, a)) |slice| slice.ptr else null;
|
inline for (args) |a, i| params[i] = if (try common.prepareParamText(arena, a)) |slice| slice.ptr else null;
|
||||||
|
|
||||||
break :blk c.PQexecParams(self.conn, sql.ptr, @intCast(c_int, params.len), null, params.ptr, null, null, format_text);
|
break :blk c.PQexecParams(self.conn, sql.ptr, @intCast(c_int, params.len), null, params.ptr, null, null, format_text);
|
||||||
|
} else {
|
||||||
|
break :blk c.PQexecParams(self.conn, sql.ptr, 0, null, null, null, null, format_text);
|
||||||
|
}
|
||||||
} orelse {
|
} orelse {
|
||||||
std.log.err("Error occurred in sql query: {?s}", .{c.PQerrorMessage(self.conn)});
|
std.log.err("Error occurred in sql query: {?s}", .{c.PQerrorMessage(self.conn)});
|
||||||
return error.UnknownError;
|
return error.UnknownError;
|
||||||
|
|
|
@ -105,8 +105,10 @@ pub const Db = struct {
|
||||||
|
|
||||||
pub fn exec(self: Db, sql: []const u8, args: anytype) !Results {
|
pub fn exec(self: Db, sql: []const u8, args: anytype) !Results {
|
||||||
var stmt: ?*c.sqlite3_stmt = undefined;
|
var stmt: ?*c.sqlite3_stmt = undefined;
|
||||||
const err = c.sqlite3_prepare_v2(self.db, sql.ptr, @intCast(c_int, sql.len), &stmt, null);
|
switch (c.sqlite3_prepare_v2(self.db, sql.ptr, @intCast(c_int, sql.len), &stmt, null)) {
|
||||||
if (err != c.SQLITE_OK) return handleUnexpectedError(self.db, err, sql);
|
c.SQLITE_OK => {},
|
||||||
|
else => |err| return handleUnexpectedError(self.db, err, sql),
|
||||||
|
}
|
||||||
errdefer switch (c.sqlite3_finalize(stmt)) {
|
errdefer switch (c.sqlite3_finalize(stmt)) {
|
||||||
c.SQLITE_OK => {},
|
c.SQLITE_OK => {},
|
||||||
else => |err| {
|
else => |err| {
|
||||||
|
|
|
@ -12,14 +12,14 @@ pub fn cloneStr(str: []const u8, alloc: std.mem.Allocator) ![]const u8 {
|
||||||
return new;
|
return new;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deepFree(alloc: std.mem.Allocator, val: anytype) void {
|
pub fn deepFree(alloc: ?std.mem.Allocator, val: anytype) void {
|
||||||
const T = @TypeOf(val);
|
const T = @TypeOf(val);
|
||||||
switch (@typeInfo(T)) {
|
switch (@typeInfo(T)) {
|
||||||
.Pointer => |ptr| switch (ptr.size) {
|
.Pointer => |ptr| switch (ptr.size) {
|
||||||
.One => alloc.destroy(val),
|
.One => alloc.?.destroy(val),
|
||||||
.Slice => {
|
.Slice => {
|
||||||
for (val) |v| deepFree(alloc, v);
|
for (val) |v| deepFree(alloc, v);
|
||||||
alloc.free(val);
|
alloc.?.free(val);
|
||||||
},
|
},
|
||||||
else => @compileError("Many and C-style pointers not supported by deepfree"),
|
else => @compileError("Many and C-style pointers not supported by deepfree"),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue