fediglam/src/sql/lib.zig

412 lines
12 KiB
Zig
Raw Normal View History

2022-07-15 00:58:08 +00:00
const std = @import("std");
2022-09-11 11:38:46 +00:00
const util = @import("util");
2022-07-15 00:58:08 +00:00
2022-09-11 08:55:20 +00:00
const postgres = @import("./postgres.zig");
const sqlite = @import("./sqlite.zig");
2022-09-15 01:12:07 +00:00
const common = @import("./common.zig");
2022-09-11 08:55:20 +00:00
const Allocator = std.mem.Allocator;
2022-07-16 19:00:33 +00:00
2022-09-15 01:12:07 +00:00
pub const QueryOptions = common.QueryOptions;
2022-09-25 08:10:30 +00:00
pub const Engine = enum {
2022-09-11 08:55:20 +00:00
postgres,
sqlite,
2022-07-15 00:58:08 +00:00
};
2022-09-25 08:10:30 +00:00
pub const Config = union(Engine) {
2022-09-11 08:55:20 +00:00
postgres: struct {
2022-09-15 01:12:07 +00:00
pg_conn_str: [:0]const u8,
2022-09-11 08:55:20 +00:00
},
sqlite: struct {
2022-09-15 01:12:07 +00:00
sqlite_file_path: [:0]const u8,
2022-09-11 08:55:20 +00:00
},
2022-07-15 00:58:08 +00:00
};
2022-09-29 21:52:01 +00:00
pub const QueryError = error{
OutOfMemory,
ConnectionLost,
};
pub fn fieldList(comptime RowType: type) []const u8 {
comptime {
const fields = std.meta.fieldNames(RowType);
const separator = ", ";
if (fields.len == 0) return "";
var size: usize = 1; // 1 for null terminator
for (fields) |f| size += f.len + separator.len;
size -= separator.len;
var buf = std.mem.zeroes([size]u8);
// can't use std.mem.join because of problems with comptime allocation
// https://github.com/ziglang/zig/issues/5873#issuecomment-1001778218
//var fba = std.heap.FixedBufferAllocator.init(&buf);
//return (std.mem.join(fba.allocator(), separator, fields) catch unreachable) ++ " ";
var buf_idx = 0;
for (fields) |f, i| {
std.mem.copy(u8, buf[buf_idx..], f);
buf_idx += f.len;
if (i != fields.len - 1) std.mem.copy(u8, buf[buf_idx..], separator);
buf_idx += separator.len;
}
return &buf;
}
}
2022-09-11 08:55:20 +00:00
//pub const OpenError = sqlite.OpenError | postgres.OpenError;
2022-09-25 08:10:30 +00:00
const RawResults = union(Engine) {
2022-09-11 08:55:20 +00:00
postgres: postgres.Results,
sqlite: sqlite.Results,
2022-07-15 00:58:08 +00:00
2022-09-11 11:38:46 +00:00
fn finish(self: RawResults) void {
2022-09-11 08:55:20 +00:00
switch (self) {
.postgres => |pg| pg.finish(),
.sqlite => |lite| lite.finish(),
}
2022-07-16 19:00:33 +00:00
}
2022-09-29 21:52:01 +00:00
fn columnCount(self: RawResults) u15 {
return switch (self) {
.postgres => |pg| pg.columnCount(),
.sqlite => |lite| lite.columnCount(),
};
}
fn columnNameToIndex(self: RawResults, name: []const u8) !u15 {
return try switch (self) {
.postgres => |pg| pg.columnNameToIndex(name),
.sqlite => |lite| lite.columnNameToIndex(name),
};
}
fn row(self: *RawResults) !?Row {
return switch (self.*) {
.postgres => |*pg| if (try pg.row()) |r| Row{ .postgres = r } else null,
.sqlite => |*lite| if (try lite.row()) |r| Row{ .sqlite = r } else null,
};
}
2022-09-11 11:38:46 +00:00
};
2022-07-16 19:00:33 +00:00
2022-09-11 11:38:46 +00:00
// 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()
2022-09-29 21:52:01 +00:00
pub fn Results(comptime T: type) type {
// would normally make this a declaration of the struct, but it causes the compiler to crash
const fields = std.meta.fields(T);
2022-09-11 11:38:46 +00:00
return struct {
const Self = @This();
2022-07-15 00:58:08 +00:00
2022-09-11 11:38:46 +00:00
underlying: RawResults,
2022-09-29 21:52:01 +00:00
column_indices: [fields.len]u15,
fn from(underlying: RawResults) !Self {
return Self{ .underlying = underlying, .column_indices = blk: {
var indices: [fields.len]u15 = undefined;
inline for (fields) |f, i| {
indices[i] = if (!std.meta.trait.isTuple(T)) try underlying.columnNameToIndex(f.name) else i;
}
break :blk indices;
} };
}
2022-09-11 11:38:46 +00:00
pub fn finish(self: Self) void {
self.underlying.finish();
}
// can be used as an optimization to reduce memory reallocation
// only works on postgres
pub fn rowCount(self: Self) ?usize {
2022-09-29 21:52:01 +00:00
return self.underlying.rowCount();
2022-09-11 11:38:46 +00:00
}
2022-09-29 21:52:01 +00:00
// Returns the next row of results, or null if there are no more rows.
// Caller owns all memory allocated. The entire object can be deallocated with a
// call to util.deepFree
pub fn row(self: *Self, alloc: ?Allocator) !?T {
if (try self.underlying.row()) |row_val| {
var result: T = undefined;
var fields_allocated: usize = 0;
errdefer inline for (fields) |f, i| {
// Iteration bounds must be defined at comptime (inline for) but the number of fields we could
// successfully allocate is defined at runtime. So we iterate over the entire field array and
// conditionally deallocate fields in the loop.
if (i < fields_allocated) util.deepFree(alloc, @field(result, f.name));
};
inline for (fields) |f, i| {
@field(result, f.name) = try row_val.get(f.field_type, self.column_indices[i], alloc);
fields_allocated += 1;
2022-09-11 11:38:46 +00:00
}
2022-09-29 21:52:01 +00:00
return result;
} else return null;
2022-09-11 11:38:46 +00:00
}
};
}
2022-07-24 04:14:46 +00:00
2022-09-11 08:55:20 +00:00
// Row is invalidated by the next call to result.row()
2022-09-25 08:10:30 +00:00
const Row = union(Engine) {
2022-09-11 08:55:20 +00:00
postgres: postgres.Row,
sqlite: sqlite.Row,
// Returns a value of type T from the zero-indexed column given by idx.
// Not all types require an allocator to be present. If an allocator is needed but
// not required, it will return error.AllocatorRequired.
// The caller is responsible for deallocating T, if relevant.
2022-09-11 11:38:46 +00:00
fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) anyerror!T {
2022-09-11 08:55:20 +00:00
return switch (self) {
.postgres => |pg| pg.get(T, idx, alloc),
.sqlite => |lite| lite.get(T, idx, alloc),
2022-07-15 00:58:08 +00:00
};
}
2022-09-11 08:55:20 +00:00
};
2022-07-15 00:58:08 +00:00
2022-09-25 08:10:30 +00:00
const DbUnion = union(Engine) {
2022-09-11 08:55:20 +00:00
postgres: postgres.Db,
sqlite: sqlite.Db,
2022-09-11 11:38:46 +00:00
};
2022-07-15 00:58:08 +00:00
2022-09-25 08:10:30 +00:00
pub const ConstraintMode = enum {
deferred,
immediate,
};
2022-09-11 11:38:46 +00:00
pub const Db = struct {
2022-09-25 08:10:30 +00:00
tx_open: bool = false,
2022-09-11 11:38:46 +00:00
underlying: DbUnion,
2022-09-25 08:10:30 +00:00
2022-09-11 08:55:20 +00:00
pub fn open(cfg: Config) !Db {
return switch (cfg) {
.postgres => |postgres_cfg| Db{
2022-09-11 11:38:46 +00:00
.underlying = .{
2022-09-15 01:12:07 +00:00
.postgres = try postgres.Db.open(postgres_cfg.pg_conn_str),
2022-09-11 11:38:46 +00:00
},
2022-09-08 01:55:55 +00:00
},
2022-09-11 08:55:20 +00:00
.sqlite => |lite_cfg| Db{
2022-09-11 11:38:46 +00:00
.underlying = .{
2022-09-15 01:12:07 +00:00
.sqlite = try sqlite.Db.open(lite_cfg.sqlite_file_path),
2022-09-11 11:38:46 +00:00
},
2022-09-08 01:55:55 +00:00
},
2022-09-11 08:55:20 +00:00
};
}
2022-09-11 11:38:46 +00:00
2022-09-25 08:10:30 +00:00
pub fn close(self: *Db) void {
2022-09-11 11:38:46 +00:00
switch (self.underlying) {
2022-09-11 08:55:20 +00:00
.postgres => |pg| pg.close(),
.sqlite => |lite| lite.close(),
2022-09-08 01:55:55 +00:00
}
2022-07-15 00:58:08 +00:00
}
2022-07-22 04:18:20 +00:00
2022-09-15 01:12:07 +00:00
pub fn queryWithOptions(
2022-09-25 08:10:30 +00:00
self: *Db,
2022-09-29 21:52:01 +00:00
comptime RowType: type,
2022-09-15 01:12:07 +00:00
sql: [:0]const u8,
args: anytype,
opt: QueryOptions,
2022-09-29 21:52:01 +00:00
) !Results(RowType) {
2022-09-25 08:10:30 +00:00
if (self.tx_open) return error.TransactionOpen;
2022-09-15 01:12:07 +00:00
// Create fake transaction to use its functions
2022-09-29 21:52:01 +00:00
return (Tx{ .db = self }).queryWithOptions(RowType, sql, args, opt);
2022-09-15 01:12:07 +00:00
}
2022-09-11 11:38:46 +00:00
pub fn query(
2022-09-25 08:10:30 +00:00
self: *Db,
2022-09-29 21:52:01 +00:00
comptime RowType: type,
2022-09-11 11:38:46 +00:00
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
2022-09-29 21:52:01 +00:00
) !Results(RowType) {
2022-09-25 08:10:30 +00:00
if (self.tx_open) return error.TransactionOpen;
2022-09-11 11:38:46 +00:00
// Create fake transaction to use its functions
2022-09-29 21:52:01 +00:00
return (Tx{ .db = self }).query(RowType, sql, args, alloc);
2022-09-11 11:38:46 +00:00
}
pub fn exec(
2022-09-25 08:10:30 +00:00
self: *Db,
2022-09-11 11:38:46 +00:00
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !void {
2022-09-25 08:10:30 +00:00
if (self.tx_open) return error.TransactionOpen;
2022-09-11 11:38:46 +00:00
// Create fake transaction to use its functions
2022-09-25 08:10:30 +00:00
return (Tx{ .db = self }).exec(sql, args, alloc);
2022-09-11 11:38:46 +00:00
}
pub fn queryRow(
2022-09-25 08:10:30 +00:00
self: *Db,
2022-09-29 21:52:01 +00:00
comptime RowType: type,
2022-09-11 11:38:46 +00:00
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
2022-09-29 21:52:01 +00:00
) !?RowType {
2022-09-25 08:10:30 +00:00
if (self.tx_open) return error.TransactionOpen;
2022-09-11 11:38:46 +00:00
// Create fake transaction to use its functions
2022-09-29 21:52:01 +00:00
return (Tx{ .db = self }).queryRow(RowType, sql, args, alloc);
2022-09-11 11:38:46 +00:00
}
pub fn insert(
2022-09-25 08:10:30 +00:00
self: *Db,
2022-09-11 11:38:46 +00:00
comptime table: []const u8,
value: anytype,
) !void {
2022-09-25 08:10:30 +00:00
if (self.tx_open) return error.TransactionOpen;
2022-09-11 11:38:46 +00:00
// Create fake transaction to use its functions
2022-09-25 08:10:30 +00:00
return (Tx{ .db = self }).insert(table, value);
}
pub fn sqlEngine(self: *Db) Engine {
return self.underlying;
2022-09-11 11:38:46 +00:00
}
// Begins a transaction
2022-09-25 08:10:30 +00:00
pub fn begin(self: *Db) !Tx {
const tx = Tx{ .db = self };
2022-09-11 11:38:46 +00:00
try tx.exec("BEGIN", .{}, null);
return tx;
}
};
pub const Tx = struct {
2022-09-25 08:10:30 +00:00
db: *Db,
2022-09-11 11:38:46 +00:00
// internal helper fn
fn queryInternal(
self: Tx,
sql: [:0]const u8,
args: anytype,
2022-09-15 01:12:07 +00:00
opt: QueryOptions,
2022-09-11 11:38:46 +00:00
) !RawResults {
2022-09-25 08:10:30 +00:00
return switch (self.db.underlying) {
2022-09-15 01:12:07 +00:00
.postgres => |pg| RawResults{ .postgres = try pg.exec(sql, args, opt.prep_allocator) },
.sqlite => |lite| RawResults{ .sqlite = try lite.exec(sql, args, opt) },
2022-09-11 11:38:46 +00:00
};
}
2022-09-15 01:12:07 +00:00
pub fn queryWithOptions(
self: Tx,
2022-09-29 21:52:01 +00:00
comptime RowType: type,
2022-09-15 01:12:07 +00:00
sql: [:0]const u8,
args: anytype,
options: QueryOptions,
2022-09-29 21:52:01 +00:00
) !Results(RowType) {
return Results(RowType).from(try self.queryInternal(sql, args, options));
2022-09-15 01:12:07 +00:00
}
2022-09-11 11:38:46 +00:00
// Executes a query and returns the result set
pub fn query(
self: Tx,
2022-09-29 21:52:01 +00:00
comptime RowType: type,
2022-09-11 11:38:46 +00:00
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
2022-09-29 21:52:01 +00:00
) !Results(RowType) {
return self.queryWithOptions(RowType, sql, args, .{ .prep_allocator = alloc });
2022-09-11 11:38:46 +00:00
}
// Executes a query without returning results
pub fn exec(
self: Tx,
sql: [:0]const u8,
args: anytype,
alloc: ?Allocator,
) !void {
2022-09-29 21:52:01 +00:00
_ = try self.queryRow(std.meta.Tuple(&.{}), sql, args, alloc);
2022-09-11 11:38:46 +00:00
}
// Runs a query and returns a single row
pub fn queryRow(
self: Tx,
2022-09-29 21:52:01 +00:00
comptime RowType: type,
2022-09-11 11:38:46 +00:00
q: [:0]const u8,
args: anytype,
alloc: ?Allocator,
2022-09-29 21:52:01 +00:00
) !?RowType {
var results = try self.query(RowType, q, args, alloc);
2022-09-11 11:38:46 +00:00
defer results.finish();
2022-09-29 21:52:01 +00:00
@compileLog(args);
2022-09-11 11:38:46 +00:00
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);
2022-09-15 01:12:07 +00:00
const fields = std.meta.fields(ValueType);
comptime var types: [fields.len]type = undefined;
comptime var table_spec: []const u8 = table ++ "(";
comptime var value_spec: []const u8 = "(";
inline for (fields) |field, i| {
types[i] = field.field_type;
table_spec = comptime (table_spec ++ field.name ++ ",");
value_spec = comptime value_spec ++ std.fmt.comptimePrint("${},", .{i + 1});
}
table_spec = comptime table_spec[0 .. table_spec.len - 1] ++ ")";
value_spec = comptime value_spec[0 .. value_spec.len - 1] ++ ")";
2022-09-11 11:38:46 +00:00
const q = comptime std.fmt.comptimePrint(
"INSERT INTO {s} VALUES {s}",
.{ table_spec, value_spec },
);
2022-09-15 01:12:07 +00:00
var args_tuple: std.meta.Tuple(&types) = undefined;
inline for (fields) |field, i| {
args_tuple[i] = @field(value, field.name);
}
try self.exec(q, args_tuple, null);
2022-09-11 11:38:46 +00:00
}
2022-09-25 08:10:30 +00:00
pub fn sqlEngine(self: Tx) Engine {
return self.db.underlying;
}
pub fn setConstraintMode(self: Tx, mode: ConstraintMode) !void {
switch (self.db.underlying) {
.sqlite => try self.exec(
switch (mode) {
.immediate => "PRAGMA defer_foreign_keys = FALSE",
.deferred => "PRAGMA defer_foreign_keys = TRUE",
},
.{},
null,
),
.postgres => try self.exec(
switch (mode) {
.immediate => "SET CONSTRAINTS ALL IMMEDIATE",
.deferred => "SET CONSTRAINTS ALL DEFERRED",
},
.{},
null,
),
}
}
2022-09-11 11:38:46 +00:00
pub fn rollback(self: Tx) void {
self.exec("ROLLBACK", .{}, null) catch |err| {
std.log.err("Error occured during rollback operation: {}", .{err});
2022-09-11 08:55:20 +00:00
};
2022-07-22 04:18:20 +00:00
}
2022-09-11 11:38:46 +00:00
pub fn commit(self: Tx) !void {
try self.exec("COMMIT", .{}, null);
}
2022-07-15 00:58:08 +00:00
};