Stub sqlite engine
This commit is contained in:
parent
eb0d8b4ca0
commit
ed515c059d
2 changed files with 47 additions and 377 deletions
|
@ -1,384 +1,44 @@
|
||||||
|
const common = @import("../lib.zig");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const util = @import("util");
|
|
||||||
const common = @import("./common.zig");
|
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
@cInclude("sqlite3.h");
|
//@cInclude("sqlite3.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
const Uuid = util.Uuid;
|
const QueryOptions = common.QueryOptions;
|
||||||
const DateTime = util.DateTime;
|
const ExecError = common.ExecError;
|
||||||
|
const SqlValue = common.SqlValue;
|
||||||
|
const Results = common.Results;
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
fn getCharPos(text: []const u8, offset: c_int) struct { row: usize, col: usize } {
|
pub const Engine = struct {
|
||||||
var row: usize = 0;
|
//db: *c.sqlite3,
|
||||||
var col: usize = 0;
|
dummy: usize = 0,
|
||||||
var i: usize = 0;
|
|
||||||
|
|
||||||
if (offset > text.len) return .{ .row = 0, .col = 0 };
|
pub fn db(self: *Engine) common.Db {
|
||||||
|
return .{
|
||||||
while (i != offset) : (i += 1) {
|
.ptr = self,
|
||||||
if (text[i] == '\n') {
|
.vtable = &.{
|
||||||
row += 1;
|
.close = Engine.close,
|
||||||
col = 0;
|
.exec = Engine.exec,
|
||||||
} else {
|
|
||||||
col += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{ .row = row, .col = col };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handleUnexpectedError(db: *c.sqlite3, code: c_int, sql_text: ?[]const u8) error{Unexpected} {
|
|
||||||
std.log.err("Unexpected error in SQLite engine: {s} ({})", .{ c.sqlite3_errstr(code), code });
|
|
||||||
|
|
||||||
std.log.debug("Additional details:", .{});
|
|
||||||
std.log.debug("{?s}", .{c.sqlite3_errmsg(db)});
|
|
||||||
if (sql_text) |sql| {
|
|
||||||
const byte_offset = c.sqlite3_error_offset(db);
|
|
||||||
if (byte_offset >= 0) {
|
|
||||||
const pos = getCharPos(sql, byte_offset);
|
|
||||||
std.log.debug("Failed at char ({}:{}) of SQL:\n{s}", .{ pos.row, pos.col, sql });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std.log.debug("{?}", .{@errorReturnTrace()});
|
|
||||||
|
|
||||||
return error.Unexpected;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Db = struct {
|
|
||||||
db: *c.sqlite3,
|
|
||||||
|
|
||||||
pub fn open(path: [:0]const u8) common.OpenError!Db {
|
|
||||||
return openInternal(path, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn openUri(path: [:0]const u8) common.OpenError!Db {
|
|
||||||
return openInternal(path, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn openInternal(path: [:0]const u8, is_uri: bool) common.OpenError!Db {
|
|
||||||
const flags = c.SQLITE_OPEN_READWRITE | c.SQLITE_OPEN_CREATE | c.SQLITE_OPEN_EXRESCODE | if (is_uri) c.SQLITE_OPEN_URI else 0;
|
|
||||||
|
|
||||||
var db: ?*c.sqlite3 = null;
|
|
||||||
switch (c.sqlite3_open_v2(@ptrCast([*c]const u8, path), &db, flags, null)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
else => |code| {
|
|
||||||
if (db == null) {
|
|
||||||
// this path should only be hit if out of memory, but log it anyways
|
|
||||||
std.log.err(
|
|
||||||
"Unable to open SQLite DB \"{s}\". Error: {?s} ({})",
|
|
||||||
.{ path, c.sqlite3_errstr(code), code },
|
|
||||||
);
|
|
||||||
return error.BadConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ext_code = c.sqlite3_extended_errcode(db);
|
|
||||||
std.log.err(
|
|
||||||
\\Unable to open SQLite DB "{s}". Error: {?s} ({})
|
|
||||||
\\Details: {?s}
|
|
||||||
,
|
|
||||||
.{ path, c.sqlite3_errstr(ext_code), ext_code, c.sqlite3_errmsg(db) },
|
|
||||||
);
|
|
||||||
|
|
||||||
return error.Unexpected;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return Db{
|
|
||||||
.db = db.?,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn close(self: Db) void {
|
|
||||||
switch (c.sqlite3_close(self.db)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
|
|
||||||
c.SQLITE_BUSY => {
|
|
||||||
std.log.err("SQLite DB could not be closed as it is busy.\n{s}", .{c.sqlite3_errmsg(self.db)});
|
|
||||||
},
|
|
||||||
|
|
||||||
else => |err| {
|
|
||||||
std.log.err("Could not close SQLite DB", .{});
|
|
||||||
handleUnexpectedError(self.db, err, null) catch {};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exec(self: Db, sql: []const u8, args: anytype, opts: common.QueryOptions) common.ExecError!Results {
|
|
||||||
var stmt: ?*c.sqlite3_stmt = undefined;
|
|
||||||
switch (c.sqlite3_prepare_v2(self.db, sql.ptr, @intCast(c_int, sql.len), &stmt, null)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
else => |err| return handleUnexpectedError(self.db, err, sql),
|
|
||||||
}
|
|
||||||
errdefer switch (c.sqlite3_finalize(stmt)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
else => |err| {
|
|
||||||
handleUnexpectedError(self.db, err, sql) catch {};
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (@TypeOf(args) != void) {
|
|
||||||
// TODO: Fix for stage1 compiler
|
|
||||||
//inline for (args) |arg, i| {
|
|
||||||
inline for (std.meta.fields(@TypeOf(args))) |field, i| {
|
|
||||||
const arg = @field(args, field.name);
|
|
||||||
// SQLite treats $NNN args as having the name NNN, not index NNN.
|
|
||||||
// As such, if you reference $2 and not $1 in your query (such as
|
|
||||||
// when dynamically constructing queries), it could assign $2 the
|
|
||||||
// index 1. So we can't assume the index according to the 1-indexed
|
|
||||||
// arg array is equivalent to the param to bind it to.
|
|
||||||
// We can, however, look up the exact index to bind to.
|
|
||||||
// If the argument is not used in the query, then it will have an "index"
|
|
||||||
// of 0, and we must not bind the argument.
|
|
||||||
const name = std.fmt.comptimePrint("${}", .{i + 1});
|
|
||||||
const db_idx = c.sqlite3_bind_parameter_index(stmt.?, name);
|
|
||||||
if (db_idx != 0)
|
|
||||||
try self.bindArgument(stmt.?, @intCast(u15, db_idx), arg)
|
|
||||||
else if (!opts.ignore_unused_arguments)
|
|
||||||
return error.UnusedArgument;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Results{ .stmt = stmt.?, .db = self.db };
|
fn close(ctx: *anyopaque) void {
|
||||||
|
const self: *Engine = @ptrCast(*Engine, @alignCast(@alignOf(Engine), ctx));
|
||||||
|
_ = self;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bindArgument(self: Db, stmt: *c.sqlite3_stmt, idx: u15, val: anytype) !void {
|
fn exec(
|
||||||
if (comptime std.meta.trait.isZigString(@TypeOf(val))) {
|
ctx: *anyopaque,
|
||||||
return self.bindString(stmt, idx, val);
|
sql: []const u8,
|
||||||
}
|
args: []const SqlValue,
|
||||||
|
opts: QueryOptions,
|
||||||
const T = @TypeOf(val);
|
alloc: Allocator,
|
||||||
|
) ExecError!Results {
|
||||||
switch (@typeInfo(T)) {
|
const self: *Engine = @ptrCast(*Engine, @alignCast(@alignOf(Engine), ctx));
|
||||||
.Union => {
|
_ = .{ self, sql, args, opts, alloc };
|
||||||
const arr = if (@hasDecl(T, "toCharArray"))
|
@panic("unimplemented");
|
||||||
val.toCharArray()
|
|
||||||
else if (@hasDecl(T, "toCharArrayZ"))
|
|
||||||
val.toCharArrayZ()
|
|
||||||
else {
|
|
||||||
inline for (std.meta.fields(T)) |field| {
|
|
||||||
const Tag = std.meta.Tag(T);
|
|
||||||
const tag = @field(Tag, field.name);
|
|
||||||
|
|
||||||
if (val == tag) return try self.bindArgument(stmt, idx, @field(val, field.name));
|
|
||||||
}
|
|
||||||
unreachable;
|
|
||||||
};
|
|
||||||
|
|
||||||
const len = std.mem.len(&arr);
|
|
||||||
return self.bindString(stmt, idx, arr[0..len]);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Struct => {
|
|
||||||
const arr = if (@hasDecl(T, "toCharArray"))
|
|
||||||
val.toCharArray()
|
|
||||||
else if (@hasDecl(T, "toCharArrayZ"))
|
|
||||||
val.toCharArrayZ()
|
|
||||||
else
|
|
||||||
@compileError("SQLite: Could not serialize " ++ @typeName(T) ++ " into staticly sized string");
|
|
||||||
|
|
||||||
const len = std.mem.len(&arr);
|
|
||||||
return self.bindString(stmt, idx, arr[0..len]);
|
|
||||||
},
|
|
||||||
.Enum => |info| {
|
|
||||||
const name = if (info.is_exhaustive)
|
|
||||||
@tagName(val)
|
|
||||||
else
|
|
||||||
@compileError("SQLite: Could not serialize non-exhaustive enum " ++ @typeName(T) ++ " into string");
|
|
||||||
return self.bindString(stmt, idx, name);
|
|
||||||
},
|
|
||||||
.Optional => {
|
|
||||||
return if (val) |v| self.bindArgument(stmt, idx, v) else self.bindNull(stmt, idx);
|
|
||||||
},
|
|
||||||
.Null => return self.bindNull(stmt, idx),
|
|
||||||
.Int => return self.bindInt(stmt, idx, std.math.cast(i64, val) orelse unreachable),
|
|
||||||
.Float => return self.bindFloat(stmt, idx, val),
|
|
||||||
.Bool => return self.bindInt(stmt, idx, if (val) 1 else 0),
|
|
||||||
else => @compileError("Unable to serialize type " ++ @typeName(T)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bindString(self: Db, stmt: *c.sqlite3_stmt, idx: u15, str: []const u8) !void {
|
|
||||||
const len = std.math.cast(c_int, str.len) orelse {
|
|
||||||
std.log.err("SQLite: string len {} too large", .{str.len});
|
|
||||||
return error.BindException;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (c.sqlite3_bind_text(stmt, idx, str.ptr, len, c.SQLITE_TRANSIENT)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
else => |result| {
|
|
||||||
std.log.err("SQLite: Unable to bind string to index {}", .{idx});
|
|
||||||
std.log.debug("SQLite: {s}", .{str});
|
|
||||||
return handleUnexpectedError(self.db, result, null);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bindNull(self: Db, stmt: *c.sqlite3_stmt, idx: u15) !void {
|
|
||||||
switch (c.sqlite3_bind_null(stmt, idx)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
else => |result| {
|
|
||||||
std.log.err("SQLite: Unable to bind NULL to index {}", .{idx});
|
|
||||||
return handleUnexpectedError(self.db, result, null);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bindInt(self: Db, stmt: *c.sqlite3_stmt, idx: u15, val: i64) !void {
|
|
||||||
switch (c.sqlite3_bind_int64(stmt, idx, val)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
else => |result| {
|
|
||||||
std.log.err("SQLite: Unable to bind int to index {}", .{idx});
|
|
||||||
std.log.debug("SQLite: {}", .{val});
|
|
||||||
return handleUnexpectedError(self.db, result, null);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bindFloat(self: Db, stmt: *c.sqlite3_stmt, idx: u15, val: f64) !void {
|
|
||||||
switch (c.sqlite3_bind_double(stmt, idx, val)) {
|
|
||||||
c.SQLITE_OK => {},
|
|
||||||
else => |result| {
|
|
||||||
std.log.err("SQLite: Unable to bind float to index {}", .{idx});
|
|
||||||
std.log.debug("SQLite: {}", .{val});
|
|
||||||
return handleUnexpectedError(self.db, result, null);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Results = struct {
|
|
||||||
stmt: *c.sqlite3_stmt,
|
|
||||||
db: *c.sqlite3,
|
|
||||||
|
|
||||||
pub fn finish(self: Results) void {
|
|
||||||
_ = c.sqlite3_finalize(self.stmt);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn row(self: Results) common.RowError!?Row {
|
|
||||||
return switch (c.sqlite3_step(self.stmt)) {
|
|
||||||
c.SQLITE_ROW => Row{ .stmt = self.stmt, .db = self.db },
|
|
||||||
c.SQLITE_DONE => null,
|
|
||||||
|
|
||||||
c.SQLITE_CONSTRAINT_UNIQUE => return error.UniqueViolation,
|
|
||||||
c.SQLITE_CONSTRAINT_CHECK => return error.CheckViolation,
|
|
||||||
c.SQLITE_CONSTRAINT_NOTNULL => return error.NotNullViolation,
|
|
||||||
c.SQLITE_CONSTRAINT_FOREIGNKEY => return error.ForeignKeyViolation,
|
|
||||||
c.SQLITE_CONSTRAINT => return error.ConstraintViolation,
|
|
||||||
|
|
||||||
else => |err| handleUnexpectedError(self.db, err, self.getGeneratingSql()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getGeneratingSql(self: Results) ?[]const u8 {
|
|
||||||
const ptr = c.sqlite3_sql(self.stmt) orelse return null;
|
|
||||||
return ptr[0..std.mem.len(ptr)];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn columnCount(self: Results) common.ColumnCountError!u15 {
|
|
||||||
return @intCast(u15, c.sqlite3_column_count(self.stmt));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn columnName(self: Results, idx: u15) ![]const u8 {
|
|
||||||
return if (c.sqlite3_column_name(self.stmt, idx)) |ptr|
|
|
||||||
ptr[0..std.mem.len(ptr)]
|
|
||||||
else
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn columnIndex(self: Results, name: []const u8) common.ColumnIndexError!u15 {
|
|
||||||
var i: u15 = 0;
|
|
||||||
const count = try self.columnCount();
|
|
||||||
while (i < count) : (i += 1) {
|
|
||||||
const column = try self.columnName(i);
|
|
||||||
if (std.mem.eql(u8, name, column)) return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.NotFound;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Row = struct {
|
|
||||||
stmt: *c.sqlite3_stmt,
|
|
||||||
db: *c.sqlite3,
|
|
||||||
|
|
||||||
pub fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) common.GetError!T {
|
|
||||||
return getColumn(self.stmt, T, idx, alloc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn getColumn(stmt: *c.sqlite3_stmt, comptime T: type, idx: u15, alloc: ?Allocator) common.GetError!T {
|
|
||||||
const Eff = if (comptime std.meta.trait.is(.Optional)(T)) std.meta.Child(T) else T;
|
|
||||||
return switch (c.sqlite3_column_type(stmt, idx)) {
|
|
||||||
c.SQLITE_INTEGER => try getColumnInt(stmt, Eff, idx),
|
|
||||||
c.SQLITE_FLOAT => try getColumnFloat(stmt, Eff, idx),
|
|
||||||
c.SQLITE_TEXT => try getColumnText(stmt, Eff, idx, alloc),
|
|
||||||
c.SQLITE_NULL => {
|
|
||||||
if (T == DateTime) {
|
|
||||||
std.log.warn("SQLite: Treating NULL as DateTime epoch", .{});
|
|
||||||
return std.mem.zeroes(DateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@typeInfo(T) != .Optional) {
|
|
||||||
std.log.err("SQLite column {}: Expected value of type {}, got (null)", .{ idx, T });
|
|
||||||
return error.ResultTypeMismatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
c.SQLITE_BLOB => {
|
|
||||||
std.log.err("SQLite column {}: SQLite value had unsupported storage class BLOB", .{idx});
|
|
||||||
return error.ResultTypeMismatch;
|
|
||||||
},
|
|
||||||
else => |class| {
|
|
||||||
std.log.err("SQLite column {}: SQLite value had unknown storage class {}", .{ idx, class });
|
|
||||||
return error.ResultTypeMismatch;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getColumnInt(stmt: *c.sqlite3_stmt, comptime T: type, idx: u15) common.GetError!T {
|
|
||||||
const val: i64 = c.sqlite3_column_int64(stmt, idx);
|
|
||||||
|
|
||||||
switch (T) {
|
|
||||||
DateTime => return DateTime{ .seconds_since_epoch = val },
|
|
||||||
else => switch (@typeInfo(T)) {
|
|
||||||
.Int => if (std.math.cast(T, val)) |v| return v else {
|
|
||||||
std.log.err("SQLite column {}: Expected value of type {}, got {} (outside of range)", .{ idx, T, val });
|
|
||||||
return error.ResultTypeMismatch;
|
|
||||||
},
|
|
||||||
.Bool => if (val == 0) return false else return true,
|
|
||||||
else => {
|
|
||||||
std.log.err("SQLite column {}: Storage class INT cannot be parsed into type {}", .{ idx, T });
|
|
||||||
return error.ResultTypeMismatch;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getColumnFloat(stmt: *c.sqlite3_stmt, comptime T: type, idx: u15) common.GetError!T {
|
|
||||||
const val: f64 = c.sqlite3_column_double(stmt, idx);
|
|
||||||
switch (T) {
|
|
||||||
// Only support floats that fit in range for now
|
|
||||||
f16, f32, f64 => return @floatCast(T, val),
|
|
||||||
DateTime => return DateTime{
|
|
||||||
.seconds_since_epoch = std.math.lossyCast(i64, val * @intToFloat(f64, std.time.epoch.secs_per_day)),
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
std.log.err("SQLite column {}: Storage class FLOAT cannot be parsed into type {}", .{ idx, T });
|
|
||||||
return error.ResultTypeMismatch;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getColumnText(stmt: *c.sqlite3_stmt, comptime T: type, idx: u15, alloc: ?Allocator) common.GetError!T {
|
|
||||||
if (c.sqlite3_column_text(stmt, idx)) |ptr| {
|
|
||||||
const size = @intCast(usize, c.sqlite3_column_bytes(stmt, idx));
|
|
||||||
const str = std.mem.sliceTo(ptr[0..size], 0);
|
|
||||||
|
|
||||||
return common.parseValueNotNull(alloc, T, str);
|
|
||||||
} else {
|
|
||||||
std.log.err("SQLite column {}: TEXT value stored but engine returned null pointer (out of memory?)", .{idx});
|
|
||||||
return error.ResultTypeMismatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ pub const ConstraintError = error{
|
||||||
ConstraintViolation,
|
ConstraintViolation,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const OpenError = error{BadConnection} || UnexpectedError;
|
|
||||||
|
|
||||||
pub const ExecError = error{
|
pub const ExecError = error{
|
||||||
Cancelled,
|
Cancelled,
|
||||||
BadConnection,
|
BadConnection,
|
||||||
|
@ -49,7 +47,7 @@ pub const ExecError = error{
|
||||||
} || ConstraintError || UnexpectedError;
|
} || ConstraintError || UnexpectedError;
|
||||||
|
|
||||||
pub const Db = struct {
|
pub const Db = struct {
|
||||||
const VTable = struct {
|
pub const VTable = struct {
|
||||||
/// Closes the database connection
|
/// Closes the database connection
|
||||||
close: *const fn (ctx: *anyopaque) void,
|
close: *const fn (ctx: *anyopaque) void,
|
||||||
|
|
||||||
|
@ -58,7 +56,8 @@ pub const Db = struct {
|
||||||
exec: *const fn (ctx: *anyopaque, sql: []const u8, args: []const SqlValue, opt: QueryOptions, allocator: Allocator) ExecError!Results,
|
exec: *const fn (ctx: *anyopaque, sql: []const u8, args: []const SqlValue, opt: QueryOptions, allocator: Allocator) ExecError!Results,
|
||||||
};
|
};
|
||||||
|
|
||||||
vtable: VTable,
|
vtable: *const VTable,
|
||||||
|
ptr: *anyopaque,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ColumnCountError = error{OutOfRange};
|
pub const ColumnCountError = error{OutOfRange};
|
||||||
|
@ -75,14 +74,15 @@ pub const RowError = error{
|
||||||
} || ConstraintError || UnexpectedError;
|
} || ConstraintError || UnexpectedError;
|
||||||
|
|
||||||
pub const Results = struct {
|
pub const Results = struct {
|
||||||
const VTable = struct {
|
pub const VTable = struct {
|
||||||
columnCount: *const fn (ctx: *anyopaque) ColumnCountError!ColumnIndex,
|
columnCount: *const fn (ctx: *anyopaque) ColumnCountError!ColumnIndex,
|
||||||
columnIndex: *const fn (ctx: *anyopaque) ColumnIndexError!ColumnIndex,
|
columnIndex: *const fn (ctx: *anyopaque) ColumnIndexError!ColumnIndex,
|
||||||
row: *const fn (ctx: *anyopaque) RowError!?Row,
|
row: *const fn (ctx: *anyopaque) RowError!?Row,
|
||||||
finish: *const fn (ctx: *anyopaque) void,
|
finish: *const fn (ctx: *anyopaque) void,
|
||||||
};
|
};
|
||||||
|
|
||||||
vtable: VTable,
|
vtable: *const VTable,
|
||||||
|
ptr: *anyopaque,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const GetError = error{
|
pub const GetError = error{
|
||||||
|
@ -91,7 +91,7 @@ pub const GetError = error{
|
||||||
} || UnexpectedError;
|
} || UnexpectedError;
|
||||||
|
|
||||||
pub const Row = struct {
|
pub const Row = struct {
|
||||||
const VTable = struct {
|
pub const VTable = struct {
|
||||||
isNull: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError!bool,
|
isNull: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError!bool,
|
||||||
getStr: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError![]const u8,
|
getStr: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError![]const u8,
|
||||||
getInt: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError!i64,
|
getInt: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError!i64,
|
||||||
|
@ -99,5 +99,15 @@ pub const Row = struct {
|
||||||
getFloat: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError!f64,
|
getFloat: *const fn (ctx: *anyopaque, idx: ColumnIndex) GetError!f64,
|
||||||
};
|
};
|
||||||
|
|
||||||
vtable: VTable,
|
vtable: *const VTable,
|
||||||
|
ptr: *anyopaque,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "test" {
|
||||||
|
const backend = @import("./engines/sqlite2.zig");
|
||||||
|
var engine: backend.Engine = undefined;
|
||||||
|
|
||||||
|
const db = engine.db();
|
||||||
|
|
||||||
|
_ = db;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue